push_manager.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966
  1. // var window.mode_env = 'development';
  2. window.mode_env = 'production';
  3. let API_URL=window.themeConfig.api;
  4. // if (
  5. // window.location.href.indexOf('localhost') >= 0 ||
  6. // window.location.href.indexOf('192.168.1.') >= 0 ||
  7. // window.location.href.indexOf('192.168.11.') >= 0 ||
  8. // window.location.href.indexOf('10.10.100.') >= 0 ||
  9. // window.location.href.indexOf('bigwinga.me') >= 0 ||
  10. // window.location.href.indexOf('thekongfu.com') >= 0 ||
  11. // window.location.href.indexOf('kysdkllc.com') >= 0
  12. // ) {
  13. // window.mode_env = 'development';
  14. // } else {
  15. // window.mode_env = 'production';
  16. // }
  17. // 初始化 渠道信息
  18. var SingletonChannelInfo = (function () {
  19. // 存储单例实例的变量
  20. var instance;
  21. // 初始化实例的函数
  22. function init(newh2, newh3, newh4) {
  23. var h2 = newh2 || 'h5';
  24. var h3 = newh3 || 'web';
  25. var h4 = newh4 || '';
  26. return {
  27. // 公有方法和属性
  28. h2,
  29. h3,
  30. h4
  31. };
  32. }
  33. return {
  34. // 获取单例的实例的方法
  35. getInstance: function (...args) {
  36. if (!instance && args.length > 0) {
  37. instance = init(...args); // 如果实例不存在,创建它
  38. }
  39. if (!instance && args.length === 0) {
  40. instance = init();
  41. }
  42. return instance; // 返回单例实例
  43. }
  44. };
  45. })();
  46. // function getCookie(key) {
  47. // const cookie = document.cookie;
  48. // const cookieArr = cookie.split(";");
  49. // const cookieObj = {};
  50. // cookieArr.forEach((item) => {
  51. // const [key, value] = item.split("=");
  52. // cookieObj[key.trim()] = value;
  53. // });
  54. // return key ? cookieObj[key] : cookieObj;
  55. // }
  56. window.hasFixexpire = false;
  57. function getCookie(key) {
  58. const cookie = document.cookie;
  59. const cookieArr = cookie.split(';');
  60. const cookieObj = {};
  61. cookieArr.forEach(item => {
  62. const [k, v] = item.split('=');
  63. const trimmedKey = k ? k.trim() : '';
  64. const trimmedValue = v ? v.trim() : '';
  65. if (trimmedKey && trimmedValue) cookieObj[trimmedKey] = trimmedValue;
  66. });
  67. if (!window.hasFixexpire) {
  68. if (cookieObj['winus_uuid']) {
  69. setCookie('winus_uuid', cookieObj['winus_uuid']);
  70. }
  71. if (cookieObj['us_h5_sourceurl_key']) {
  72. setCookie('us_h5_sourceurl_key', cookieObj['us_h5_sourceurl_key']);
  73. }
  74. window.hasFixexpire = true;
  75. }
  76. return key ? cookieObj[key] : cookieObj;
  77. }
  78. function setCookie(key, value, expires = 365 * 24 * 60 * 60 * 1000) {
  79. const date = new Date();
  80. date.setTime(date.getTime() + expires);
  81. document.cookie = `${key}=${value};path=/;domain=.${window.location.hostname.split('.').slice(-2).join('.')};expires=${date.toUTCString()};SameSite=None;Secure;`;
  82. }
  83. function isInApk() {
  84. if (!window.android) return false;
  85. if (!window.android?.getDeviceID || !window.android?.saveUserID) return false;
  86. return true;
  87. }
  88. function getSubscriptionInfo() {
  89. var installed = isPwaInstall();
  90. var cacheKey = 'submit_subscription_' + udatadict.channel + '_' + udatadict.userid + '_' + installed;
  91. return localStorage.getItem(cacheKey) === '1';
  92. }
  93. function getIsPostData() {
  94. var installed = isPwaInstall();
  95. var cacheKey = 'submit_subscription_post_' + udatadict.channel + '_' + udatadict.userid + '_' + installed;
  96. return localStorage.getItem(cacheKey) === '1';
  97. }
  98. function setIsPostData() {
  99. var installed = isPwaInstall();
  100. var cacheKey = 'submit_subscription_post_' + udatadict.channel + '_' + udatadict.userid + '_' + installed;
  101. localStorage.setItem(cacheKey, '1');
  102. }
  103. // 获取全局唯一标识
  104. var getUniqueID = (function () {
  105. // 缓存全局唯一标识
  106. let __global_unique_ID;
  107. // 缓存 promise 避免多次执行
  108. let promiseInProgress = null;
  109. return function () {
  110. if (__global_unique_ID) {
  111. return Promise.resolve(__global_unique_ID);
  112. }
  113. // 如果已经有全局唯一标识, 直接返回
  114. var cookie_md5_val = getCookie('winus_uuid');
  115. // if(!cookie_md5_val){
  116. // cookie_md5_val=getLocalUUID();
  117. // if(cookie_md5_val){
  118. // setCookie('winus_uuid', cookie_md5_val, 604800000);
  119. // }
  120. // }
  121. if (cookie_md5_val) {
  122. __global_unique_ID = cookie_md5_val;
  123. return Promise.resolve(__global_unique_ID);
  124. }
  125. if (promiseInProgress) {
  126. return promiseInProgress;
  127. }
  128. if (isInApk()) {
  129. // apk 环境下, 直接返回
  130. promiseInProgress = new Promise(resolve => {
  131. try {
  132. // IPA 环境
  133. if (window?.android?.isIpa) {
  134. // 确保回调函数在调用 getDeviceID 之前就绑定好
  135. window.android.deviceIdCallback = function (deviceId) {
  136. console.log('deviceIdCallback', deviceId);
  137. // 如果禁止了第三方cookie, 则将全局唯一标识保存到 当前域名下 cookie 中
  138. if (deviceId) setCookie('winus_uuid', deviceId, 604800000);
  139. resolve(deviceId);
  140. };
  141. window.android.getDeviceID?.();
  142. return;
  143. }
  144. // 普通 APK 环境
  145. const deviceId = window?.android?.getDeviceID?.();
  146. if (deviceId) {
  147. // 如果禁止了第三方cookie, 则将全局唯一标识保存到 当前域名下 cookie 中
  148. setCookie('winus_uuid', deviceId, 604800000);
  149. resolve(deviceId);
  150. }
  151. } catch (error) {
  152. console.log(error);
  153. }
  154. });
  155. return promiseInProgress;
  156. }
  157. promiseInProgress = new Promise(function (resolve) {
  158. var iframe = document.createElement('iframe');
  159. iframe.src = window.location.protocol + '//' + 'cen.greatus777.com' + '/storage';
  160. // iframe.src = window.location.protocol + '//' + 'dev.winus777.com' + '/storage';
  161. iframe.style.display = 'none';
  162. document.body.appendChild(iframe);
  163. iframe.addEventListener('load', function () {
  164. iframe.contentWindow.postMessage('getUniqueID', '*');
  165. });
  166. function messageHandler(event) {
  167. if (event && event.data && event.data !== 'undefined') {
  168. const data = event.data;
  169. __global_unique_ID = data.md5val;
  170. // 如果禁止了第三方cookie, 则将全局唯一标识保存到 当前域名下 cookie 中
  171. setCookie('winus_uuid', __global_unique_ID, 604800000);
  172. resolve(__global_unique_ID);
  173. window.removeEventListener('message', messageHandler);
  174. document.body.removeChild(iframe);
  175. }
  176. }
  177. // 监听 iframe 的消息
  178. window.addEventListener('message', messageHandler);
  179. });
  180. return promiseInProgress;
  181. };
  182. })();
  183. /**
  184. * 上报订阅信息
  185. * @param {*} data
  186. * {
  187. * userid: 1, // 用户ID
  188. * result: 1, // 1 接受 2未接受 3 不支持 4 拒绝
  189. * machineserial 机器码
  190. * ish5 0:app(w2a),1:h5(pwa)
  191. * p_source
  192. * }
  193. */
  194. function uploadSubscription(data) {
  195. if (+data.ish5 !== 0 && +data.ish5 !== 1) return;
  196. const ajax = new XMLHttpRequest();
  197. const callback = data.callback;
  198. ajax.open('POST', API_URL+"/api/upload_subscribe_state", true);
  199. ajax.setRequestHeader('Content-Type', 'application/json');
  200. ajax.onreadystatechange = function () {
  201. if (ajax.readyState === 4 && ajax.status === 200) {
  202. setIsPostData();
  203. }
  204. callback && callback();
  205. };
  206. ajax.onerror = function () {
  207. callback && callback();
  208. };
  209. ajax.send(JSON.stringify(data));
  210. }
  211. function isIOS() {
  212. try {
  213. let u = navigator.userAgent;
  214. let isIOS = false;
  215. // 检查是否包含 iPhone, iPad, iPod 或 Mac OS X (通常Safari在iOS上运行)
  216. if (/(iPhone|iPad|iPod|iOS)/i.test(u)) {
  217. isIOS = true;
  218. }
  219. // 排除 Android,避免误判
  220. if (u.indexOf('Android') > -1 || u.indexOf('Adr') > -1) {
  221. isIOS = false;
  222. }
  223. return isIOS
  224. } catch (t) {
  225. return false
  226. }
  227. }
  228. function openUrlUseSafari(url = "", isNewTab = true, isBFP = true) {
  229. if (url == null || url === "") url = window.location.href;
  230. if (global.BFP != null && global.BFP !== "" && isBFP) {
  231. if (url.indexOf("?") === -1) url += "?bfp=" + global.BFP;
  232. else url += "&bfp=" + global.BFP;
  233. }
  234. if (isIOS()) {
  235. const safariUrl = url.startsWith('https')
  236. ? url.replace('https://', 'x-safari-https://')
  237. : url.replace('http://', 'x-safari-https://');
  238. if (isNewTab) {
  239. window.open(safariUrl, '_blank');
  240. } else {
  241. window.location.href = safariUrl;
  242. }
  243. }
  244. }
  245. function openChrome() {
  246. var url = location.href;
  247. if (isIOS()) {
  248. let r = "googlechromes://" + url.replace(/^https?:\/\//, "");
  249. window.open(r, "_blank");
  250. } else {
  251. url = url.replace('https://', '');
  252. url = url.replace('http://', '');
  253. var newLink =
  254. 'intent://' + url + '#Intent;scheme=https;action=android.intent.action.VIEW;component=com.android.chrome;end';
  255. var a = document.getElementById('pwaIFrame');
  256. a ||
  257. (((a = document.createElement('iframe')).id = 'pwaIFrame'),
  258. (a.style.width = '0'),
  259. (a.style.height = '0'),
  260. (a.style.opacity = '0'),
  261. (a.name = 'pwaIFrame'),
  262. document.body.appendChild(a)),
  263. (a.src = newLink);
  264. }
  265. setTimeout(() => {
  266. if (!document.hidden) {
  267. uploadSubscription({
  268. userid: udatadict.userid || '',
  269. result: 3,
  270. machineserial: udatadict.machineserial,
  271. ish5: 1,
  272. p_source: udatadict.channel
  273. });
  274. }
  275. }, 1000);
  276. document.body.removeEventListener('click', openChrome);
  277. }
  278. // 判断是否在 chrome 浏览器中
  279. function isInChromeBrowser() {
  280. return /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor);
  281. }
  282. var pushOfFingerprint = '';
  283. var udatadict = {};
  284. var pushLogOpen = true;
  285. function pushmlog(...args) {
  286. if (pushLogOpen) {
  287. console.log('Push log:', ...args);
  288. }
  289. }
  290. function errorpushlog(...args) {
  291. if (true) {
  292. console.error('Push error log:', ...args);
  293. }
  294. }
  295. // 将URL Base64编码的字符串转换为Uint8Array
  296. function urlBase64ToUint8Array(base64String) {
  297. var padding = '='.repeat((4 - (base64String.length % 4)) % 4);
  298. var base64 = (base64String + padding).replace(/\-/g, '+').replace(/_/g, '/');
  299. var rawData = window.atob(base64);
  300. var outputArray = new Uint8Array(rawData.length);
  301. for (var i = 0; i < rawData.length; ++i) {
  302. outputArray[i] = rawData.charCodeAt(i);
  303. }
  304. return outputArray;
  305. }
  306. // 判断是否在PWA模式下运行
  307. function isPwaInstall() {
  308. if (
  309. window.matchMedia('(display-mode: standalone)').matches ||
  310. window.navigator.standalone ||
  311. document.referrer.includes('android-app://')
  312. ) {
  313. return 1;
  314. } else {
  315. return 0;
  316. }
  317. }
  318. // 获取平台
  319. function getPlatform() {
  320. var ua = navigator.userAgent;
  321. if (ua.match(/Android/i)) {
  322. return 'Android';
  323. } else if (ua.match(/iPhone|iPad|iPod/i)) {
  324. return 'iOS';
  325. } else {
  326. return 'Other';
  327. }
  328. }
  329. // 将订阅信息发送到服务器
  330. function submitSubscription(subscription, channel, userid) {
  331. // 注意落地页和游戏内的指纹算法保持一致!!!
  332. var fingerprint = pushOfFingerprint;
  333. var website = window.location.href;
  334. // 发送订阅信息到服务器
  335. // xmlhttprequest
  336. var xhr = new XMLHttpRequest();
  337. // 墨西哥订阅地址
  338. if (window.mode_env === 'development') {
  339. xhr.open('POST', API_URL+'/api/push_subscribe', true);
  340. } else {
  341. xhr.open('POST', API_URL+'/api/push_subscribe', true);
  342. }
  343. xhr.setRequestHeader('Content-Type', 'application/json');
  344. var contentData = {
  345. fingerprint: fingerprint,
  346. userid: userid, // 如果有用户登录,可以传用户ID
  347. subscibe_info: JSON.stringify(subscription),
  348. channel: channel,
  349. web_site: website,
  350. platform: getPlatform(),
  351. installed: isPwaInstall()
  352. };
  353. xhr.onreadystatechange = function () {
  354. if (xhr.readyState === 4 && xhr.status === 200) {
  355. pushmlog('Subscription info sent to server:', xhr.responseText);
  356. var installed = isPwaInstall();
  357. var cacheKey = 'submit_subscription_' + channel + '_' + userid + '_' + installed;
  358. localStorage.setItem(cacheKey, '1');
  359. }
  360. };
  361. xhr.onerror = function () {
  362. errorpushlog('Failed to send subscription info');
  363. };
  364. xhr.send(JSON.stringify(contentData));
  365. }
  366. // 埋点日志
  367. function recordEvent(eventName, channel, remark, userid) {
  368. var info = SingletonChannelInfo.getInstance();
  369. var h2 = info.h2;
  370. var h3 = info.h3;
  371. var h4 = info.h4 || channel;
  372. var host = 'us_' + h2 + '_' + h3 + '_' + h4;
  373. var url =
  374. API_URL+'/api/weblog?host=' + host + '&act=' + eventName + '&remark=' + remark + '&orderid=' + userid;
  375. var xhr = new XMLHttpRequest();
  376. xhr.open('GET', url, true);
  377. xhr.send();
  378. }
  379. // 请求用户授权订阅推送通知
  380. function requestNotificationPermission(registration, channel, userid, machineserial, ish5) {
  381. // 检查浏览器是否支持Notification API
  382. if ('Notification' in window) {
  383. // 请求授权
  384. Notification.requestPermission().then(function (permission) {
  385. if (permission === 'granted') {
  386. pushmlog('User has authorized push notifications');
  387. // 注册Service Worker
  388. registration.pushManager
  389. .subscribe({
  390. userVisibleOnly: true,
  391. applicationServerKey: urlBase64ToUint8Array(
  392. 'BEKE0qlrGMp-XyUHppeFMscDiJg-sL98jr4b51glbvicHnD69oNZ9JiLHKIQ9_3K0ImSZlo0Ggw-RE3FxYOUwJo'
  393. )
  394. })
  395. .then(function (subscription) {
  396. var saveSubScription = JSON.stringify(subscription);
  397. localStorage.setItem('subscription', saveSubScription);
  398. pushmlog('User has subscribed to push notifications:', subscription);
  399. // 将订阅信息发送到服务器
  400. submitSubscription(subscription, channel, userid);
  401. uploadSubscription({
  402. userid: userid || '',
  403. result: 1,
  404. machineserial,
  405. ish5,
  406. p_source: channel,
  407. callback: function () {
  408. if (+ish5 === 1 || +ish5 === 0) {
  409. // 如果是H5游戏, 则打开网址
  410. if (+ish5 === 1) {
  411. if (udatadict.returnurl) {
  412. setTimeout(() => {
  413. location.href = udatadict.returnurl;
  414. }, 1000);
  415. }
  416. } else {
  417. // 打开游戏
  418. const div = document.createElement('div');
  419. div.style =
  420. 'position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); z-index: 9999;';
  421. div.innerHTML = `<div
  422. style="padding: 6px 10px; border-radius: 4px; background: rgba(89, 92, 115, 0.65); position: absolute; left: 50%;transform: translateX(-50%); color: #fff;font-size: 14px;top: 10%;">
  423. Go back to the game
  424. </div>`;
  425. document.body.appendChild(div);
  426. setTimeout(function () {
  427. document.body.removeChild(div);
  428. }, 3000);
  429. }
  430. }
  431. }
  432. });
  433. });
  434. recordEvent('push_permission', channel, 'allow', userid);
  435. } else if (permission === 'denied') {
  436. pushmlog('User denied push notifications', userid, machineserial, ish5, channel);
  437. recordEvent('push_permission', channel, 'deny', userid);
  438. uploadSubscription({
  439. userid: userid || '',
  440. result: 4,
  441. machineserial,
  442. ish5,
  443. p_source: channel,
  444. callback: function () {
  445. if (+ish5 === 1 || +ish5 === 0) {
  446. // 如果是H5游戏, 则打开网址
  447. if (+ish5 === 1) {
  448. if (udatadict.returnurl) {
  449. setTimeout(() => {
  450. location.href = udatadict.returnurl;
  451. }, 1000);
  452. }
  453. } else {
  454. // 打开游戏
  455. const div = document.createElement('div');
  456. div.style =
  457. 'position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); z-index: 9999;';
  458. div.innerHTML = `<div
  459. style="padding: 6px 10px; border-radius: 4px; background: rgba(89, 92, 115, 0.65); position: absolute; left: 50%;transform: translateX(-50%); color: #fff;font-size: 14px;top: 10%;">
  460. Go back to the game
  461. </div>`;
  462. document.body.appendChild(div);
  463. setTimeout(function () {
  464. document.body.removeChild(div);
  465. }, 3000);
  466. }
  467. }
  468. }
  469. });
  470. } else {
  471. pushmlog('User has not decided whether to authorize push notifications');
  472. uploadSubscription({
  473. userid: userid || '',
  474. result: 2,
  475. machineserial,
  476. ish5,
  477. p_source: channel,
  478. callback: function () {
  479. if (+ish5 === 1 || +ish5 === 0) {
  480. // 如果是H5游戏, 则打开网址
  481. if (+ish5 === 1) {
  482. if (udatadict.returnurl) {
  483. setTimeout(() => {
  484. location.href = udatadict.returnurl;
  485. }, 1000);
  486. }
  487. } else {
  488. // 打开游戏
  489. const div = document.createElement('div');
  490. div.style =
  491. 'position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); z-index: 9999;';
  492. div.innerHTML = `<div
  493. style="padding: 6px 10px; border-radius: 4px; background: rgba(89, 92, 115, 0.65); position: absolute; left: 50%;transform: translateX(-50%); color: #fff;font-size: 14px;top: 10%;">
  494. Go back to the game
  495. </div>`;
  496. document.body.appendChild(div);
  497. setTimeout(function () {
  498. document.body.removeChild(div);
  499. }, 3000);
  500. }
  501. }
  502. }
  503. });
  504. recordEvent('push_permission', channel, 'pending', userid);
  505. }
  506. });
  507. }
  508. }
  509. var isCheckingPushPermission = false; // 如果正在检查推送权限, 则不再检查, 避免在未收到结果前再次调用,会有多个订阅上报
  510. /**
  511. * 外部调用 检查推送权限
  512. * @param {string} channel 渠道标识
  513. * @param {number} userid 用户ID
  514. * @param {string} machineserial 机器码
  515. * @param {number} type 0:app(w2a),1:h5(pwa)
  516. * @param {object} options 其他参数
  517. * */
  518. function checkPushPermission(channel, userid, machineserial, type, options) {
  519. var returnurl = options && options.returnurl;
  520. var endCallback = options && options.endCallback;
  521. try {
  522. if (userid == undefined) {
  523. userid = 0;
  524. }
  525. if (channel == undefined) {
  526. channel = 'default';
  527. }
  528. getUniqueID().then(fingerprint => {
  529. pushOfFingerprint = fingerprint; // 先记录下来
  530. // 如果有连续调用, 那么只记录最后一次的 udatadict
  531. udatadict.userid = userid;
  532. udatadict.channel = channel;
  533. udatadict.fingerprint = fingerprint;
  534. udatadict.machineserial = machineserial;
  535. udatadict.type = type;
  536. udatadict.returnurl = returnurl;
  537. // 保存到 localstorage
  538. localStorage.setItem('pushudatadict', JSON.stringify(udatadict));
  539. if ('serviceWorker' in navigator)
  540. navigator.serviceWorker.ready
  541. .then(function (registration) {
  542. if (isCheckingPushPermission) {
  543. return;
  544. }
  545. isCheckingPushPermission = true;
  546. // 检查是否支持service worker
  547. if (registration == undefined || registration == null || registration.pushManager == undefined) {
  548. !isInApk() &&
  549. setTimeout(() => {
  550. document.body.addEventListener('click', openChrome);
  551. }, 500);
  552. errorpushlog('registration or pushManager is undefined');
  553. return;
  554. }
  555. var installed = isPwaInstall();
  556. var cacheKey = 'submit_subscription_' + udatadict.channel + '_' + udatadict.userid + '_' + installed;
  557. registration.pushManager.getSubscription().then(function (subscription) {
  558. isCheckingPushPermission = false; // 已经有结果了
  559. if (subscription) {
  560. var subscriptionOld = localStorage.getItem('subscription');
  561. endCallback && endCallback('subscription');
  562. // 用户已经订阅推送通知
  563. pushmlog('User has already subscribed to push notifications');
  564. // 判断 当前渠道+当前用户 是否已经上报过授权. 如果未上报过, 则上报一次
  565. var cacheValue = localStorage.getItem(cacheKey);
  566. if (cacheValue && subscriptionOld && subscriptionOld == JSON.stringify(subscription)) {
  567. pushmlog('User has already reported subscription info');
  568. return;
  569. }
  570. recordEvent('push_permission', udatadict.channel, 'update', udatadict.userid);
  571. localStorage.setItem('subscription', JSON.stringify(subscription)); // 更新
  572. submitSubscription(subscription, udatadict.channel, udatadict.userid); // 已经授权过就再上报一次 如果未上报
  573. var ispost = getIsPostData();
  574. if (!ispost) {
  575. uploadSubscription({
  576. userid: udatadict.userid || '',
  577. result: 1,
  578. machineserial: udatadict.machineserial,
  579. ish5: udatadict.type,
  580. p_source: udatadict.channel
  581. });
  582. }
  583. return;
  584. } else {
  585. // 用户尚未订阅推送通知
  586. pushmlog('User has not yet subscribed to push notifications');
  587. localStorage.removeItem(cacheKey);
  588. // 判断 通知状态
  589. if (Notification.permission === 'denied') {
  590. endCallback && endCallback('denied');
  591. var ispost = getIsPostData();
  592. if (!ispost) {
  593. uploadSubscription({
  594. userid: udatadict.userid || '',
  595. result: 4,
  596. machineserial: udatadict.machineserial,
  597. ish5: udatadict.type,
  598. p_source: udatadict.channel
  599. });
  600. }
  601. pushmlog('User has already denied push notifications');
  602. } else {
  603. endCallback && endCallback('pending');
  604. // 状态是授权或者用户未决定, 但是拿不到订阅信息, 重新请求授权
  605. requestNotificationPermission(registration, udatadict.channel, udatadict.userid, machineserial, type);
  606. }
  607. }
  608. });
  609. })
  610. .catch(e => {
  611. console.log(e);
  612. });
  613. });
  614. } catch (error) {
  615. errorpushlog('Failed to check push permission:', error);
  616. isCheckingPushPermission = false;
  617. }
  618. }
  619. // 外部调用。每次
  620. function checkAlive(userid, vippoint, channel, score) {
  621. try {
  622. if (udatadict.fingerprint == undefined) {
  623. var udatadictstr = localStorage.getItem('pushudatadict');
  624. if (udatadictstr) {
  625. udatadict = JSON.parse(udatadictstr);
  626. }
  627. if (udatadict.fingerprint == undefined) {
  628. errorpushlog('udatadict.fingerprint is undefined');
  629. return;
  630. }
  631. }
  632. if (userid == undefined) {
  633. errorpushlog('userid is undefined');
  634. return;
  635. }
  636. // 1 小时最多上报一次
  637. var isInstalled = isPwaInstall();
  638. var reportkey = 'lastreporttime_' + userid + '_' + channel + '_' + isInstalled;
  639. var lastreporttime = localStorage.getItem(reportkey);
  640. var now = new Date().getTime();
  641. if (lastreporttime) {
  642. var last = parseInt(lastreporttime);
  643. if (now - last < 1000 * 60 * 60) {
  644. return;
  645. }
  646. }
  647. localStorage.setItem(reportkey, now);
  648. // 请求用户活跃接口
  649. var xhr = new XMLHttpRequest();
  650. xhr.open('POST', 'https://pushme.us3slot77.com/alive', true);
  651. xhr.setRequestHeader('Content-Type', 'application/json');
  652. var contentData = {
  653. userid: userid,
  654. vippoint: vippoint,
  655. channel: channel,
  656. fingerprint: udatadict.fingerprint,
  657. installed: isInstalled,
  658. platform: getPlatform(),
  659. score: score,
  660. client_active_time: new Date().toISOString()
  661. };
  662. xhr.send(JSON.stringify(contentData));
  663. xhr.onreadystatechange = function () {
  664. if (xhr.readyState === 4 && xhr.status === 200) {
  665. pushmlog('User activity info sent to server:', xhr.responseText);
  666. }
  667. };
  668. } catch (error) {
  669. errorpushlog('Failed to check user activity:', error);
  670. }
  671. }
  672. //简单加密
  673. const encrypt = (str, pwd) => {
  674. if (str == '') {
  675. return '';
  676. }
  677. str = encodeURIComponent(str);
  678. if (!pwd || pwd == '') {
  679. pwd = 'xbd4jl';
  680. }
  681. pwd = encodeURIComponent(pwd);
  682. if (pwd == '' || pwd.length <= 0) {
  683. return '';
  684. }
  685. var prand = '';
  686. for (var i = 0, len = pwd.length; i < len; i += 1) {
  687. prand += pwd.charCodeAt(i).toString();
  688. }
  689. var sPos = Math.floor(prand.length / 5);
  690. var mult = parseInt(prand.charAt(sPos) + prand.charAt(sPos * 2) + prand.charAt(sPos * 3) +
  691. prand.charAt(sPos * 4) + prand.charAt(sPos * 5));
  692. var incr = Math.ceil(pwd.length / 2);
  693. var modu = Math.pow(2, 31) - 1;
  694. if (mult < 2) {
  695. return '';
  696. }
  697. var salt = Math.round(Math.random() * 1000000000) % 100000000;
  698. prand += salt;
  699. while (prand.length > 10) {
  700. let left = prand;
  701. let num = 0;
  702. while (left.length > 0) {
  703. let temp = parseInt(left.substring(0, 10));
  704. num = num + temp;
  705. left = left.substring(10, left.length);
  706. }
  707. prand = num.toString();
  708. }
  709. prand = (mult * prand + incr) % modu;
  710. var encChr = '';
  711. var encStr = '';
  712. for (var i = 0, len = str.length; i < len; i += 1) {
  713. encChr = parseInt(str.charCodeAt(i) ^ Math.floor((prand / modu) * 255));
  714. if (encChr < 16) {
  715. encStr += '0' + encChr.toString(16);
  716. } else {
  717. encStr += encChr.toString(16);
  718. }
  719. prand = (mult * prand + incr) % modu;
  720. }
  721. salt = salt.toString(16);
  722. while (salt.length < 8) {
  723. salt = "0" + salt;
  724. }
  725. encStr += salt;
  726. return encStr;
  727. }
  728. //简单解密
  729. const decrypt = (str, pwd) => {
  730. if (str == '') {
  731. return '';
  732. }
  733. if (!pwd || pwd == '') {
  734. pwd = 'xbd4jl';
  735. }
  736. pwd = encodeURIComponent(pwd);
  737. if (str == undefined || str.length < 8) {
  738. return '';
  739. }
  740. if (pwd == undefined || pwd.length <= 0) {
  741. return '';
  742. }
  743. var prand = '';
  744. for (var i = 0, len = pwd.length; i < len; i += 1) {
  745. prand += pwd.charCodeAt(i).toString();
  746. }
  747. var sPos = Math.floor(prand.length / 5);
  748. // console.log(prand.length+"sPos____"+sPos+"prand"+prand);
  749. var mult = parseInt(prand.charAt(sPos) + prand.charAt(sPos * 2) + prand.charAt(sPos * 3) +
  750. prand.charAt(sPos * 4) + prand.charAt(sPos * 5));
  751. var incr = Math.round(pwd.length / 2);
  752. var modu = Math.pow(2, 31) - 1;
  753. var salt = parseInt(str.substring(str.length - 8, str.length), 16);
  754. str = str.substring(0, str.length - 8);
  755. // console.log("prand"+prand);
  756. prand += salt;
  757. // console.log("prand"+prand);
  758. while (prand.length > 10) {
  759. let left = prand;
  760. let num = 0;
  761. while (left.length > 0) {
  762. let temp = parseInt(left.substring(0, 10));
  763. num = num + temp;
  764. left = left.substring(10, left.length);
  765. }
  766. prand = num.toString();
  767. }
  768. // console.log("prand"+prand);
  769. prand = (mult * prand + incr) % modu;
  770. var encChr = '';
  771. var encStr = '';
  772. for (var i = 0, len = str.length; i < len; i += 2) {
  773. encChr = parseInt(parseInt(str.substring(i, i + 2), 16) ^ Math.floor((prand / modu) * 255));
  774. encStr += String.fromCharCode(encChr);
  775. prand = (mult * prand + incr) % modu;
  776. }
  777. return decodeURIComponent(encStr);
  778. }
  779. const LOCAL_VISTOR_ID = "LOCAL_VISTOR_ID";
  780. const head = "a6072637";
  781. const isenc = true;
  782. const localStorageGet = (key, defaultValue = "") => {
  783. let value = localStorage.getItem(key);
  784. if (value == null) value = defaultValue;
  785. else {
  786. if (value.startsWith(head)) {
  787. try {
  788. value = decrypt(value.substring(head.length));
  789. }
  790. catch (err) {
  791. //如果出错返回空
  792. value = "";
  793. }
  794. }
  795. }
  796. return value;
  797. }
  798. const localStorageSet = (key, value) => {
  799. if (isenc) {
  800. if (value != null && value !== "") {
  801. if (typeof value !== "string") value = value.toString();
  802. try {
  803. value = head + encrypt(value);
  804. }
  805. catch (err) { }
  806. }
  807. }
  808. localStorage.setItem(key, value);
  809. }
  810. const localStorageRemove = (key) => {
  811. localStorage.removeItem(key);
  812. }
  813. const getLocalUUID = () => {
  814. let bfp=localStorageGet(LOCAL_VISTOR_ID, "");
  815. if(!bfp){
  816. localStorageSet(LOCAL_VISTOR_ID, generateUUIDv7());
  817. bfp=localStorageGet(LOCAL_VISTOR_ID, "")
  818. }
  819. return bfp;
  820. }
  821. let params={}
  822. let urlParams=new URLSearchParams(window.location.search)
  823. for (const [key, value] of urlParams.entries()) {
  824. params[key] = value
  825. }
  826. const LOCAL_ACTIVITY_C = "LOCAL_ACTIVITY_C";
  827. const DEFAULT_CHANNEL = "100";
  828. function ProcessChannel() {
  829. let storedChannel = localStorageGet(LOCAL_ACTIVITY_C, "");
  830. let channelFromUrl = params["c"] || "";
  831. let finalChannel = channelFromUrl || storedChannel || DEFAULT_CHANNEL;
  832. // 保存渠道号(如有变化)
  833. if (finalChannel && finalChannel !== storedChannel) {
  834. localStorageSet(LOCAL_ACTIVITY_C, finalChannel);
  835. }
  836. window.APKCHSource = finalChannel;
  837. window.channel = finalChannel;
  838. }/**
  839. * 生成 UUID v7 (基于时间排序)
  840. * 格式: 48位时间戳 | 4位版本(7) | 12位随机数 | 2位变体(10) | 62位随机数
  841. */
  842. function generateUUIDv7() {
  843. const bytes = new Uint8Array(16);
  844. // 1. 填充随机数 (16 字节)
  845. crypto.getRandomValues(bytes);
  846. // 2. 写入 48 位毫秒时间戳 (大端序)
  847. const now = Date.now();
  848. bytes[0] = (now / 0x10000000000) & 0xff;
  849. bytes[1] = (now / 0x100000000) & 0xff;
  850. bytes[2] = (now / 0x1000000) & 0xff;
  851. bytes[3] = (now / 0x10000) & 0xff;
  852. bytes[4] = (now / 0x100) & 0xff;
  853. bytes[5] = now & 0xff;
  854. // 3. 设置版本号 (Version 7)
  855. // 清除前 4 位并设为 0111 (0x70)
  856. bytes[6] = (bytes[6] & 0x0f) | 0x70;
  857. // 4. 设置变体号 (Variant 10xx)
  858. // 清除前 2 位并设为 10 (0x80)
  859. bytes[8] = (bytes[8] & 0x3f) | 0x80;
  860. // 5. 转换为 8-4-4-4-12 字符串格式
  861. return Array.from(bytes)
  862. .map((b, i) => {
  863. let s = b.toString(16).padStart(2, '0');
  864. // 在特定位置添加连字符
  865. if (i === 3 || i === 5 || i === 7 || i === 9) return s + '-';
  866. return s;
  867. })
  868. .join('');
  869. }
  870. if (isIOS()) {
  871. window.location.href = window.location.origin + '/' + window.location.search;
  872. }