push_manager.js 32 KB

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