Web Push通知“未经授权的注册”或“已删除”或“未经授权”-订阅过期。

11

我为我的网站开发了一个推送通知服务。服务工作者如下:

    'use strict';
    self.addEventListener('push', function (event) {    
    var msg = {};
    if (event.data) {
        msg = event.data.json();
    }    
    let notificationTitle = msg.title;
    const notificationOptions = {
        body: msg.body,//body
        dir:'rtl',//direction
        icon: msg.icon,//image
        data: {
            url: msg.url,//click
        },
    };
    event.waitUntil(
      Promise.all([
        self.registration.showNotification(
          notificationTitle, notificationOptions),      
      ])
    );
    });
    self.addEventListener('notificationclick', function (event) {    
        event.notification.close();

    let clickResponsePromise = Promise.resolve();
    if (event.notification.data && event.notification.data.url) {
        clickResponsePromise = clients.openWindow(event.notification.data.url);
    }
    const fetchOptions = 
        { method: 'post'}; 
    fetch('http://localhost:5333/usrh.ashx?click=true', fetchOptions).
    then(function (response) 
    {
        if (response.status >= 400 && response.status < 500) 
        {         
            throw new Error('Failed to send push message via web push protocol');
        } 
    }).catch((err) => 
    { 
        this.showErrorMessage('Ooops Unable to Send a Click', err); 
    });
});

self.addEventListener('notificationclose', function (event) {
    const fetchOptions = 
        { method: 'post'}; 
    fetch('http://localhost:5333/usrh.ashx?close=true', fetchOptions).
    then(function (response) 
    {
        if (response.status >= 400 && response.status < 500) 
        {         
            throw new Error('Failed to send push message via web push protocol');
        } 
    }).catch((err) => 
    { 
        this.showErrorMessage('Ooops Unable to Send a Click', err); 
    }); 
});
self.addEventListener('pushsubscriptionchange', function () {
    const fetchOptions = {
        method: 'post'
        ,
    };

    fetch('http://localhost:5333/usru.ashx', fetchOptions)
        .then(function (response) {
            if (response.status >= 400 && response.status < 500) {
                console.log('Failed web push response: ', response, response.status);
                throw new Error('Failed to update users.');
            }
        })
        .catch((err) => {
            this.showErrorMessage('Ooops Unable to Send a user', err);
        });
});

我已经成功使用以下代码订阅了用户:

registerServiceWorker() {
        if ('serviceWorker' in navigator) {
            navigator.serviceWorker.register('http://localhost:5333/service-worker.js')
                .catch((err) => {
                    this.showErrorMessage('Unable to Register SW', 'Sorry this demo requires a service worker to work and it ' + 'failed to install - sorry :(');
                    console.error(err);
                });
        } else {
            this.showErrorMessage('Service Worker Not Supported', 'Sorry this demo requires service worker support in your browser. ' +
                'Please try this demo in Chrome or Firefox Nightly.');
        }
    }
and
class PushClient {
    constructor(subscriptionUpdate, appkeys) {
        this._subscriptionUpdate = subscriptionUpdate;
        this._publicApplicationKey = appkeys;
        if (!('serviceWorker' in navigator)) {
            return;
        }
        if (!('PushManager' in window)) {
            return;
        }
        if (!('showNotification' in ServiceWorkerRegistration.prototype)) {
            return;
        }
        navigator.serviceWorker.ready.then(() => {
            this.setUpPushPermission();
        });
    }
    setUpPushPermission() {
        return navigator.serviceWorker.ready.then((serviceWorkerRegistration) => {
            return serviceWorkerRegistration.pushManager.getSubscription();
        })
            .then((subscription) => {
                if (!subscription) {
                    return;
                }
                this._subscriptionUpdate(subscription);
            })
            .catch((err) => {
                console.log('setUpPushPermission() ', err);
            });
    }
    subscribeDevice() {
        return new Promise((resolve, reject) => {
            if (Notification.permission === 'denied') {
                sc(3);
                return reject(new Error('Push messages are blocked.'));
            }
            if (Notification.permission === 'granted') {
                sc(3);
                return resolve();
            }
            if (Notification.permission === 'default') {
                Notification.requestPermission((result) => {
                    if (result === 'denied') {
                        sc(0);
                    } else if (result === 'granted') {
                        sc(1);
                    } else {
                        sc(2);
                    }
                    if (result !== 'granted') {
                        reject(new Error('Bad permission result'));
                    }
                    resolve();
                });
            }
        })
            .then(() => {
                return navigator.serviceWorker.ready.then((serviceWorkerRegistration) => {
                    return serviceWorkerRegistration.pushManager.subscribe({
                        userVisibleOnly: true
                        , applicationServerKey: this._publicApplicationKey.publicKey
                    ,
                    });
                })
                    .then((subscription) => {
                        this._subscriptionUpdate(subscription);
                        if (subscription) {
                            this.sendPushMessage(subscription);
                        }
                    })
                    .catch((subscriptionErr) => { });
            })
            .catch(() => { });
    }
    toBase64(arrayBuffer, start, end) {
        start = start || 0;
        end = end || arrayBuffer.byteLength;
        const partialBuffer = new Uint8Array(arrayBuffer.slice(start, end));
        return btoa(String.fromCharCode.apply(null, partialBuffer));
    }
    unsubscribeDevice() {
        navigator.serviceWorker.ready.then((serviceWorkerRegistration) => {
            return serviceWorkerRegistration.pushManager.getSubscription();
        })
            .then((pushSubscription) => {
                if (!pushSubscription) {
                    this._subscriptionUpdate(null);
                    return;
                }
                return pushSubscription.unsubscribe()
                    .then(function (successful) {
                        if (!successful) {
                            console.error('We were unable to unregister from push');
                        }
                    });
            })
            .then(() => {
                this._subscriptionUpdate(null);
            })
            .catch((err) => {
                console.error('Error thrown while revoking push notifications. ' + 'Most likely because push was never registered', err);
            });
    }
    sendPushMessage(subscription) {
        let payloadPromise = Promise.resolve(null);
        payloadPromise = JSON.parse(JSON.stringify(subscription));
        const vapidPromise = EncryptionHelperFactory.createVapidAuthHeader(this._publicApplicationKey, subscription.endpoint, 'http://localhost:5333/');
        return Promise.all([payloadPromise, vapidPromise, ])
            .then((results) => {
                const payload = results[0];
                const vapidHeaders = results[1];
                let infoFunction = this.getWebPushInfo;
                infoFunction = () => {
                    return this.getWebPushInfo(subscription, payload, vapidHeaders);
                };
                const requestInfo = infoFunction();
                this.sendRequestToProxyServer(requestInfo);
            });
    }
    getWebPushInfo(subscription, payload, vapidHeaders) {
        let body = null;
        const headers = {};
        headers.TTL = 60;
        if (payload) {
            headers.Encryption = `auth=${payload.keys.auth}`;
            headers['Crypto-Key'] = `p256dh=${payload.keys.p256dh}`;
            headers['Content-Encoding'] = 'aesgcm';
        } else {
            headers['Content-Length'] = 0;
        }
        if (vapidHeaders) {
            headers.Authorization = `WebPush ${vapidHeaders.authorization}`;
            if (headers['Crypto-Key']) {
                headers['Crypto-Key'] = `${headers['Crypto-Key']}; ` + `p256ecdsa=${vapidHeaders.p256ecdsa}`;
            } else {
                headers['Crypto-Key'] = `p256ecdsa=${vapidHeaders.p256ecdsa}`;
            }
        }
        const response = {
            headers: headers
            , endpoint: subscription.endpoint
        ,
        };
        if (body) {
            response.body = body;
        }
        return response;
    }
    sendRequestToProxyServer(requestInfo) {
        const fetchOptions = {
            method: 'post'
        ,
        };
        if (requestInfo.body && requestInfo.body instanceof ArrayBuffer) {
            requestInfo.body = this.toBase64(requestInfo.body);
            fetchOptions.body = requestInfo;
        }
        fetchOptions.body = JSON.stringify(requestInfo);
        fetch('http://localhost:5333/usrh.ashx', fetchOptions)
            .then(function (response) {
                if (response.status >= 400 && response.status < 500) {
                    console.log('Failed web push response: ', response, response.status);
                    throw new Error('Failed to send push message via web push protocol');
                }
            })
            .catch((err) => {
                this.showErrorMessage('Ooops Unable to Send a Push', err);
            });
    }
}

所有这些代码都是用JavaScript编写的。我可以成功地在我的服务器上接收到用户订阅信息,例如:

Authorization: WebPush eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwcxxxxx  
Crypto-Key: p256dh=BBp90dwDWxxxxc1TfdBjFPqxxxxxwjO9fCip-K_Eebmg=; p256ecdsa=BDd3_hVL9fZi9Yboxxxxxxo
endpoint: https://fcm.googleapis.com/fcm/send/cxxxxxxxxxxxxxxJRorOMHKLQ3gtT7
Encryption: auth=9PzQZ1mut99qxxxxxxxxxxyw== 
Content-Encoding: aesgcm

同时,我可以使用下面的C#代码成功向该用户发送推送:

public static async Task<bool> SendNotificationByte(string endpoint, string[] Keys, byte[] userSecret, byte[] data = null,
                                        int ttl = 0, ushort padding = 0, bool randomisePadding = false, string auth="")
        {
            #region send
            HttpRequestMessage Request = new HttpRequestMessage(HttpMethod.Post, endpoint);                
                Request.Headers.TryAddWithoutValidation("Authorization", auth);
            Request.Headers.Add("TTL", ttl.ToString());
            if (data != null && Keys[1] != null && userSecret != null)
            {
                EncryptionResult Package = EncryptMessage(Decode(Keys[1]), userSecret, data, padding, randomisePadding);
                Request.Content = new ByteArrayContent(Package.Payload);
                Request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
                Request.Content.Headers.ContentLength = Package.Payload.Length;
                Request.Content.Headers.ContentEncoding.Add("aesgcm");
                Request.Headers.Add("Crypto-Key", "dh=" + Encode(Package.PublicKey)+" ;"+Keys[2]+"="+Keys[3]);
                Request.Headers.Add("Encryption", "salt=" + Encode(Package.Salt));
            }
            using (HttpClient HC = new HttpClient())
            {
                HttpResponseMessage res = await HC.SendAsync(Request).ConfigureAwait(false);
                if (res.StatusCode == HttpStatusCode.Created)
                    return true;
                else return false;
            }
            #endregion
        }

问题在于经过一段时间后(大约20小时甚至更短),当我想向该用户发送推送时,我会遇到以下错误:

Firefox 订阅:

{StatusCode: 410, ReasonPhrase: 'Gone', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
  Access-Control-Allow-Headers: content-encoding,encryption,crypto-key,ttl,encryption-key,content-type,authorization
  Access-Control-Allow-Methods: POST
  Access-Control-Allow-Origin: *
  Access-Control-Expose-Headers: location,www-authenticate
  Connection: keep-alive
  Cache-Control: max-age=86400
  Date: Tue, 21 Feb 2017 08:19:03 GMT
  Server: nginx
  Content-Length: 179
  Content-Type: application/json
}}

Chrome 订阅:

{StatusCode: 400, ReasonPhrase: 'UnauthorizedRegistration', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
  X-Content-Type-Options: nosniff
  X-Frame-Options: SAMEORIGIN
  X-XSS-Protection: 1; mode=block
  Alt-Svc: quic=":443"; ma=2592000; v="35,34"
  Vary: Accept-Encoding
  Transfer-Encoding: chunked
  Accept-Ranges: none
  Cache-Control: max-age=0, private
  Date: Tue, 21 Feb 2017 08:18:35 GMT
  Server: GSE
  Content-Type: text/html; charset=UTF-8
  Expires: Tue, 21 Feb 2017 08:18:35 GMT
}}

我觉得我错过了一些东西,导致订阅到期,或者当用户的订阅信息更改或过期时需要重新订阅,但我不知道如何处理?!!

4个回答

3
问题可以通过向已订阅用户发送推送回声通知来解决。我编写了一个作业,定期发送推送回声并重新订阅用户并更新其信息。
为此,我使用下面的代码发送一个名为“推送回声”的特殊消息:
self.addEventListener('push', function (event) {
lastEventName = 'push';
var msg = {};
if (event.data) {
    msg = event.data.json();
    if (!!msg.isEcho) {
        self.registration.pushManager.getSubscription()
            .then(function (subscription) {
                if (!subscription) {
                } else {
                    subscription.unsubscribe().then(function () {
                        self.registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: base64UrlToUint8Array('xxxxxxxxxxxxxxxx') })
                            .then(function (subscription) {
                                resubscription(subscription);
                            });
                    });
                    }
            });
        return;
    }
}
if (!!msg.isEcho)
    return;
let notificationTitle = msg.title;
const notificationOptions = {
    body: msg.body,
    dir: 'rtl',
    icon: msg.icon,
    data: {
        url: msg.url,
        id: msg.id,
        key: msg.key
    },
};
event.waitUntil(
  Promise.all([
    self.registration.showNotification(
      notificationTitle, notificationOptions),
  ])
);

const fetchOptions =
    { method: 'post', mode: 'no-cors' };
fetch('http://example.com', fetchOptions).
    then(function (response) {
        if (response.status >= 400 && response.status < 500) {
            throw new Error('Failed to send push message via web push protocol');
        }
        lastEventName = 'view';
    }).catch((err) => {
        this.showErrorMessage('Ooops Unable to Send a Click', err);
    });
});

resubscription方法中,您可以取消订阅用户,然后再次订阅并更新服务器数据。

2

我认为问题可以通过重新订阅用户来解决。


2

我认为问题是关于如何发送你的applicationServerKey。我刚刚做了一个你想要做的例子,我不得不使用这个函数对那个密钥进行编码:

  function urlBase64ToUint8Array(base64String) {
    const padding = '='.repeat((4 - base64String.length % 4) % 4);
    const base64 = (base64String + padding)
      .replace(/\-/g, '+')
      .replace(/_/g, '/');

    const rawData = window.atob(base64);
    const outputArray = new Uint8Array(rawData.length);

    for (let i = 0; i < rawData.length; ++i) {
      outputArray[i] = rawData.charCodeAt(i);
    }
    return outputArray;
  }

因此,您需要按照以下方式创建订阅对象:

  registration.pushManager
    .subscribe({
      userVisibleOnly: true,
      applicationServerKey: urlBase64ToUint8Array(this._publicApplicationKey.publicKey),
    })

主要是我按照这个教程操作。我在此 github 库中有实现示例。README 文件是用西班牙语编写的,但我认为它可以帮助您。


1
我不这么认为!如果这是解决方案,我就永远无法发送推送通知。正如我所提到的,我可以在大约20个小时内向用户发送通知!之后似乎订阅已过期! - Alireza Mahmoudi

1

一些线索:

在推送事件到达时,需要注册和激活软件。这意味着您可能不能在其中清除会话、使用私人浏览模式或清除计算机缓存。

推送事件必须来自相同的来源。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接