返回

解决OneSignal login()重复用户记录问题

前端

使用 OneSignal Web SDK 进行推送通知时,常会遇到一个问题:调用 login() 方法关联外部用户 ID (external ID) 后,OneSignal 并没有将外部 ID 关联到已存在的用户记录,反而创建了一个新的、独立的用户记录。 这种情况会带来管理上的不便,并且可能影响推送的准确性。

问题分析

出现这个问题,通常是由于 OneSignal.login() 方法的调用时机和 OneSignal SDK 的初始化过程之间的交互导致的。OneSignal SDK 在页面加载时会立即创建一个匿名用户记录(包含 onesignalId)。 如果在 SDK 完全初始化 之前 调用 login(),就有可能发生上述的重复记录问题。

即使 onesignalId 等相关标识符看起来已正确加载,OneSignal SDK 内部状态可能尚未完全就绪。 初次登录时出现此问题,而后续登录(例如,注销后重新登录)却能正常工作,这一现象进一步支持了上述推断。

解决方案

解决问题的核心在于确保 OneSignal.login() 在 OneSignal SDK 完全准备就绪后才被调用,并且正确处理用户标识的关联。以下是几种可行的解决方案:

1. 使用 onSession() 监听

OneSignal 提供了一个 onSession() 事件,会在新会话开始或现有会话恢复时触发。可以在该事件的回调函数中执行 login() 操作。 这样可以确保在 OneSignal 完全初始化并准备好处理用户标识后,才进行用户关联。

操作步骤及代码示例:

  1. 在 OneSignal 初始化代码 之后 添加 onSession() 监听。
  2. onSession() 回调中调用 login()
OneSignal.push(function() {
  OneSignal.onSession(async function(event) {
      // 只有当用户已登录时才进行关联
    if (userIsLoggedIn()) { 
        const userId = await getUserId(); // 获取用户外部 ID
        OneSignal.login(userId);
    }
  });
});

// 判断用户是否已经登录,并获取外部Id 的方法,需要按照网站实际的方法填写
function userIsLoggedIn(){
    //请根据您的网站登录状态处理填写这里。
    return true/false; 
}
async function getUserId()
{
      //请根据您的网站用户ID系统实现。
    return "user-external-id";
}

原理: onSession() 事件提供了一个可靠的时机,保证 OneSignal SDK 处于可操作状态,避免了过早调用 login() 导致的问题。

2. 延时调用 login() (不推荐)

一个不太推荐但有时有效的解决方法是,人为地延迟 login() 的调用。例如,使用 setTimeout() 函数。

操作步骤及代码示例:

  1. 在调用 login() 的代码周围加上 setTimeout()
  2. 设置一个合适的延迟时间(例如,几秒钟)。
OneSignalDeferred.push(async function (OneSignal) {
      const id = await getUserId()

      console.log(id)
      console.log(OneSignal.User.onesignalId)
  
        // 延迟调用 login()
      setTimeout(async () => {  
        await OneSignal.login(id).catch((error) => {
          console.error('OneSignal login error: ', error)
        })
      }, 3000); // 延迟3秒
})

原理: 这种方法试图通过延迟执行,给 OneSignal SDK 更多的初始化时间。 但是,选择一个“合适”的延迟时间是比较困难的,而且这种方法不如使用事件监听可靠。

3. 结合使用 getUserId()setEmail()setExternalUserId()(如果适用)

如果正在使用 OneSignal 的电子邮件或外部用户 ID 功能,可以尝试在设置这些属性的回调中调用 login(), 或者首先尝试 setExternalUserId(),将ID设置到用户的记录中,可能可以解决这个问题。

 OneSignal.push(async function() {

   if(userIsLoggedIn)
   {
        const userId = await getUserId();
         OneSignal.setExternalUserId(userId);
   }
});

function userIsLoggedIn(){
    //请根据您的网站登录状态处理填写这里。
    return true/false;
}
async function getUserId()
{
     //请根据您的网站用户ID系统实现。
    return "user-external-id";
}

原理: 如果有外部Id或者Email作为用户唯一凭证,优先设置这些凭证,然后再进行其他的设置,也能增加用户ID绑定的成功率。

安全建议

  • 保护用户 ID: 外部用户 ID 通常是敏感信息。确保在客户端代码中安全地处理这些 ID,避免泄露。例如,不要将它们直接暴露在 URL 或不安全的存储中。
  • 服务器端验证(如适用): 如果应用场景允许,推荐在服务器之间调用API进行绑定和操作,这样会更安全。
  • 避免硬编码延迟: 在延迟调用中不要写入一个固定值,应该从用户的实际操作出发进行处理。
  • 最小化 SDK 初始化的时延: 尽可能提速,减少初始化的时间,能够一定程度规避风险。

以上方案的核心思想都是保证了 OneSignal SDK 初始化完成后,再去调用 API 进行设置。 这能规避掉可能存在的大部分绑定失效的问题。