push_manager.js 30 KB

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