我使用 Service Worker 实现了网络推送通知。我使用特定的应用程序服务器密钥收集了用户订阅。假设如果我们更改应用程序服务器密钥,那么当我们使用“reg.pushManager.getSubscription()”获取订阅时,我们将获取使用旧应用程序服务器密钥创建的旧订阅信息。如何处理这种情况?如何获取用户的新订阅?
使用
reg.pushManager.getSubscription()
获取订阅并检查当前订阅是否使用新的应用程序服务器密钥。如果没有,则对现有订阅调用 unsubscribe()
函数并再次重新订阅。
正确启动 Service Worker 并获得权限后,调用
navigator.serviceWorker.ready
即可访问 *.pushManager
对象。
从这个对象中,我们调用另一个 Promise 来获取我们真正关心的
pushSubscription
对象。
如果用户从未订阅过,
pushSubscription
将是null
,否则我们会从中获取密钥并检查它是否不同,如果是这种情况,我们取消订阅用户并再次订阅他们。
var NEW_PUBLIC_KEY = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
Notification.requestPermission(function (result) {
if (permissionResult == 'granted'){
subscribeUser();
}
});
function subscribeUser() {
navigator.serviceWorker.ready
.then(registration => {
registration.pushManager.getSubscription()
.then(pushSubscription => {
if(!pushSubscription){
//the user was never subscribed
subscribe(registration);
}
else{
//check if user was subscribed with a different key
let json = pushSubscription.toJSON();
let public_key = json.keys.p256dh;
console.log(public_key);
if(public_key != NEW_PUBLIC_KEY){
pushSubscription.unsubscribe().then(successful => {
// You've successfully unsubscribed
subscribe(registration);
}).catch(e => {
// Unsubscription failed
})
}
}
});
})
}
function subscribe(registration){
registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(NEW_PUBLIC_KEY)
})
.then(pushSubscription => {
//successfully subscribed to push
//save it to your DB etc....
});
}
function urlBase64ToUint8Array(base64String) {
var padding = '='.repeat((4 - base64String.length % 4) % 4);
var base64 = (base64String + padding)
.replace(/\-/g, '+')
.replace(/_/g, '/');
var rawData = window.atob(base64);
var outputArray = new Uint8Array(rawData.length);
for (var i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
就我而言,我设法通过清除缓存和cookie来解决这个问题
您通过调用
sub.getKey('p256dh')
(或sub.toJSON.keys.p256dh
)获得的密钥是客户端的公钥,它始终与服务器公钥不同。您需要比较新的服务器公钥和sub.options.applicationServerKey
。
以上sub
是reg.pushManager.getSubscription()
已解决的承诺。
因此:
reg.pushManager.getSubscription().then(sub => {...})
,如果 sub 为 null,则不存在订阅,因此不用担心,但如果定义了:sub.options.applicationServerKey
const curKey = btoa(String.fromCharCode.apply(null, new Uint8Array(sub.options.applicationServerKey)))
sub.unsubscribe()
,然后通过调用 reg.pushManager.subscribe(subscribeOptions)
再次订阅,其中 subscribeOptions 使用您的新密钥。您在 PushSubscription 上调用“取消订阅”,但在 PushManager 上调用 subscribe
这是用于处理比较应用程序服务器密钥更改的真实代码。
我留下了更多如何处理的细节。 以前的答案对我来说还不够。
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;
}
function subscribe(registration) {
registration.pushManager
.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(APPLICATON_SERVER_KEY),
})
.then(async function (pushSubscription) {
//successfully subscribed to push
//save it to your DB etc....
});
}
navigator.serviceWorker.ready.then(function (registration) {
registration.pushManager.getSubscription().then(function (subscription) {
if (!subscription) {
return subscribe(registration);
}
const applicationServerKey = btoa(
String.fromCharCode.apply(
null,
new Uint8Array(subscription.options.applicationServerKey)
)
);
const currentKey = applicationServerKey
.replaceAll("+", "-")
.replaceAll("/", "_")
.replaceAll("=", "");
if (currentKey.indexOf(APPLICATON_SERVER_KEY) > -1) {
console.log("Already subscribed");
return subscription;
} else {
subscription
.unsubscribe()
.then(() => {
console.log("subscribe again because of different key");
return subscribe(registration);
})
.catch(function (e) {
console.log("unsubscribed error", e);
});
}
});
});
当您更改 applicationServerKey 时,后端将通过端点发送推送消息而收到响应 401 或 403 错误。
所以你需要在服务器端处理它。
我用这段代码完成了。
for subscription in SubscriptionInfo.objects.all():
try:
send_to_subscription(subscription, payload)
except Exception as e:
if e.response.status_code == 403 or e.response.status_code == 401:
subscription.delete()
print(e)
pass