我猜在现实世界中,大多数用户不会经常取消订阅和重新订阅。然而,推送订阅变得陈旧可能是一个问题。如果您的服务器正在使用最新的推送订阅,而用户同时返回到一个订阅较旧但仍然有效的设备上,则拥有多个设备的用户可能会在错误的设备上收到通知。
一种处理这个问题的方法是,每次用户启动客户端时都重新订阅用户。然而,我找不到来自Apple/Google等(大型浏览器推送服务提供商)的任何信息,以了解是否存在任何限制或者这是否是一种不好的做法。我们不想被列入黑名单... [顺便说一下,苹果将推送请求作为JSON发送到响应内容中,而谷歌则发送纯文本。在Safari测试时编写代码后,在Chrome客户端上打破了我的服务器代码。]
因此,与其简单地将最新的订阅插入到用户数据库表中的某一列中,也许应该将其插入到一个新表中,该表的主键由user_id和browser_id组成。我将展示browser_id可能是什么的示例。然后,当用户从特定浏览器的特定设备进入时,查找相应的订阅并在端点处触发。
[另外一个选择是将完整的订阅 JSON 存储在 cookie 或 localStorage 中,并经常将其发送到服务器,但每个 HTTP 请求上多几百个字节似乎是一种浪费。大家有什么想法吗?]
无论如何,这是我对于制作更小的 browser_id 并与 user_id 一起在服务器端查找订阅 JSON 的看法(注释有点多余;您可能只想看代码):
/*
This is to help the server use the right web Push API subscription if the user switches
between different browsers; e.g. say a user is on browser A and the server caches a push
subscription for them, and then they go to browser B and we cache a new subscription for
them, and then they go back to browser A and we don't cache a new subscription for them
because browser A already has a subscription going because the service worker is still
running from their previous session; then the server wants to send them a notification
and it uses the browser B subscription instead of the browser A one; clearly the
subscriptions need to be cached per user, per browser -- but how to identify each browser
uniquely? Upon first visiting the site, we'll create a browser_id that combines the
users initial IP address with a timestamp; if the visitor is later issued a user_id,
we will store any Push subscriptions in a db table with primary key of user_id and
browser_id. Sample browser_id: 10.0.0.255-1680711958431
*/
async function set_browser_id() {
let browser_id = Cookies.get('browser_id') || localStorage.browser_id;
if ( ! browser_id) {
/*
const response = await fetch("https://api.ipify.org?format=json");
const jsonData = await response.json();
const ip_addr = jsonData.ip;
*/
const timestamp = Date.now();
browser_id = `${ip_addr}-${timestamp}`; // server set JS const ip_addr earlier
console.log(`NEW browser_id: ${browser_id}`);
}
else { console.log(`EXISTING browser_id: ${browser_id}`); }
// set whether new or existing to keep it from ever expiring
Cookies.set('browser_id', browser_id,
{ path: '/', domain: `.${hostname}`, expires: 365, secure: true });
localStorage.browser_id = browser_id; // backup in persistent storage
}
window.onload = set_browser_id; // do it after everything else
browser_id 应该是相当持久的,因为它有 cookie 的 localStorage 备份(并且可能对于分析目的很有用)。现在,您的服务器可以获得一个短小的 browser_id cookie,并使用它来查找更长的 Push 订阅 JSON。实际上,您可能可以跳过 db 表中的 user_id,因为 browser_id 命名空间冲突的机会是微不足道的。当然,每当用户生成新的订阅时,您需要客户端将其发送过来,以便服务器可以更新 db 表。
我很想知道其他人对这个计划的看法;我正在实施它。还有关于在 cookie 中重复传递完整的订阅 JSON 的任何安全问题 - 发送通知需要您的私有 VAPID 密钥,对吧?但是....
p.s. 我现在正在谈论这个问题,因为苹果终于在 iOS 16.4 中启用了 Push API - 耶!是时候了。