使用OneSignal外部ID时用户未注册/无法设置外部ID问题求助
我之前也踩过OneSignal用户同步的坑,结合你的代码和日志来看,核心问题出在异步操作时机错误,以及对OneSignal订阅流程的顺序理解偏差上。以下是一步步的排查和修复方案:
1. 先搞懂核心问题:为什么getExternalUserId返回null?
你的日志显示OneSignal thinks playerID is null,这说明在你调用getExternalUserId的时候,要么:
- OneSignal还没完成Player(用户)对象的创建(订阅流程没走完)
setExternalUserId的Promise还没执行完成,你就读取了旧值
2. 修复异步操作顺序
setExternalUserId是异步Promise操作,你现在在调用它之后立刻执行getExternalUserId,这时候异步任务还没结束,读到的自然是初始的null值。必须把后续操作放到Promise的then回调里,还要加上catch捕获隐藏的错误:
// 错误写法: OneSignal.setExternalUserId('<? echo $user_playerid?>').then(function (response) { console.log("OneSignal's response is " + response); }); OneSignal.getExternalUserId().then(function (id) { console.log("OneSignal thinks playerID is " + id); }); // 正确写法: OneSignal.setExternalUserId('<? echo $user_playerid?>') .then(function (response) { console.log("OneSignal's response is ", response); // 用逗号避免[object Object]的字符串拼接问题 // 只有在set完成后,再读取external ID return OneSignal.getExternalUserId(); }) .then(function (id) { console.log("OneSignal thinks playerID is " + id); }) .catch(function (error) { // 一定要加catch!之前你没捕获错误,所以看不到失败信息 console.error("Failed to set external ID:", error); });
3. 确保在正确的时机触发External ID设置
你同时监听了notificationPermissionChange和subscriptionChange,但这两个事件的触发顺序是:
- 用户授予权限 →
notificationPermissionChange触发(此时可能还没生成Player ID) - OneSignal完成订阅流程,生成Player ID →
subscriptionChange触发(isSubscribed为true)
所以只需要在subscriptionChange事件里处理External ID的设置,避免重复操作和时机错误:
OneSignal.on('subscriptionChange', function (isSubscribed) { if (isSubscribed) { // 先确认Player ID是否存在,再设置External ID OneSignal.getUserId() .then(function(playerId) { console.log("Generated Player ID:", playerId); if (playerId) { return OneSignal.setExternalUserId('<? echo $user_playerid?>'); } else { throw new Error("Player ID not generated yet"); } }) .then(function(response) { console.log("External ID set successfully:", response); return OneSignal.getExternalUserId(); }) .then(function(externalId) { console.log("Current External ID:", externalId); }) .catch(function(error) { console.error("Subscription/External ID error:", error); }); } else { OneSignal.removeExternalUserId() .then(function() { console.log("External ID removed"); }); } });
4. 修复初始化后的订阅逻辑
你代码最后一段的if (u_playerid != null)逻辑有问题:u_playerid初始值是null,所以会直接执行showNativePrompt,但这时候OneSignal可能还没完成初始化。要把这段逻辑放到OneSignal.push的回调里,确保初始化完成后再执行:
OneSignal.push(function () { // ... 你的init代码 ... // 把订阅逻辑移到这里,确保OneSignal已初始化 var savedPlayerId = '<? echo $user_playerid?>'; // 用更清晰的变量名 if (savedPlayerId) { OneSignal.setSubscription(true); // 这里也要用Promise链式调用 OneSignal.setExternalUserId(savedPlayerId) .then(function() { console.log("Restored external ID:", savedPlayerId); }); } else { OneSignal.showNativePrompt(); } });
5. 排查其他常见问题
- HTTPS要求:OneSignal仅在HTTPS环境下工作(localhost除外),如果你的网站是HTTP,订阅流程会静默失败。
- App ID正确性:检查
init里的appId是否和OneSignal控制台的一致,少打一个字符都会导致失败。 - 加密后的External ID格式:确保
encryptIDToken($user_id)返回的是无特殊字符、非空的字符串,避免OneSignal无法解析。 - 浏览器权限:确认用户真的授予了通知权限,你可以在
notificationPermissionChange里打印currentPermission的值,确保是granted。
修正后的完整代码示例
<? $user_playerid = encryptIDToken($user_id);?> $(function () { var OneSignal = window.OneSignal || []; OneSignal.push(function () { OneSignal.init({ appId: "YOUR_APP_ID_HERE", autoRegister: false, autoResubscribe: true, persistNotification: true, welcomeNotification: { "title": "...", "message": "..." } }); // 监听权限变化(仅用于调试) OneSignal.on('notificationPermissionChange', function (permissionChange) { var currentPermission = permissionChange.to; console.log("Permission changed to:", currentPermission); }); // 核心:订阅变化时处理External ID OneSignal.on('subscriptionChange', function (isSubscribed) { console.log("Subscription status:", isSubscribed); if (isSubscribed) { OneSignal.getUserId() .then(function(playerId) { console.log("Player ID created:", playerId); if (!playerId) throw new Error("No Player ID found"); return OneSignal.setExternalUserId('<? echo $user_playerid?>'); }) .then(function(response) { console.log("Set External ID response:", response); return OneSignal.getExternalUserId(); }) .then(function(externalId) { console.log("Current External ID:", externalId); }) .catch(function(error) { console.error("Error during subscription:", error); }); } else { OneSignal.removeExternalUserId() .then(function() { console.log("External ID removed after unsubscription"); }) .catch(function(error) { console.error("Error removing External ID:", error); }); } }); // 初始化完成后处理已保存的用户ID var savedExternalId = '<? echo $user_playerid?>'; if (savedExternalId) { OneSignal.setSubscription(true) .then(function() { return OneSignal.setExternalUserId(savedExternalId); }) .then(function() { console.log("Restored External ID for returning user"); }); } else { OneSignal.showNativePrompt(); } }); });
按照这个方案调整后,你应该能在OneSignal控制台看到注册用户,并且getExternalUserId能正确返回设置的值。记得打开浏览器控制台查看详细日志,尤其是catch里的错误信息,这会帮你快速定位问题。
内容的提问来源于stack exchange,提问作者blakeaholics

