CommLink.js

A userscript library for cross-window communication via the userscript storage

لا ينبغي أن لا يتم تثبيت هذا السكريت مباشرة. هو مكتبة لسكبتات لتشمل مع التوجيه الفوقية // @require https://update.greatest.deepsurf.us/scripts/470418/1579426/CommLinkjs.js

  1. /* CommLink.js
  2. - Version: 1.0.2
  3. - Author: Haka
  4. - Description: A userscript library for cross-window communication via the userscript storage
  5. - GitHub: https://github.com/AugmentedWeb/CommLink
  6. */
  7.  
  8. class CommLinkHandler {
  9. constructor(commlinkID, configObj) {
  10. this.commlinkID = commlinkID;
  11. this.singlePacketResponseWaitTime = configObj?.singlePacketResponseWaitTime || 1500;
  12. this.maxSendAttempts = configObj?.maxSendAttempts || 3;
  13. this.statusCheckInterval = configObj?.statusCheckInterval || 1;
  14. this.silentMode = configObj?.silentMode || false;
  15.  
  16. this.commlinkValueIndicator = 'commlink-packet-';
  17. this.commands = {};
  18. this.listeners = [];
  19.  
  20. const grants = GM_info?.script?.grant || [];
  21. const missingGrants = ['GM_getValue', 'GM_setValue', 'GM_deleteValue', 'GM_listValues']
  22. .filter(grant => !grants.includes(grant));
  23.  
  24. if(missingGrants.length > 0 && !this.silentMode)
  25. alert(`[CommLink] The following userscript grants are missing: ${missingGrants.join(', ')}. CommLink might not work.`);
  26.  
  27. this.getStoredPackets()
  28. .filter(packet => Date.now() - packet.date > 2e4)
  29. .forEach(packet => this.removePacketByID(packet.id));
  30. }
  31.  
  32. setIntervalAsync(callback, interval = this.statusCheckInterval) {
  33. let running = true;
  34.  
  35. async function loop() {
  36. while(running) {
  37. try {
  38. await callback();
  39.  
  40. await new Promise((resolve) => setTimeout(resolve, interval));
  41. } catch (e) {
  42. continue;
  43. }
  44. }
  45. };
  46.  
  47. loop();
  48.  
  49. return { stop: () => running = false };
  50. }
  51.  
  52. getUniqueID() {
  53. return ([1e7]+-1e3+4e3+-8e3+-1e11).replace(/[018]/g, c =>
  54. (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
  55. )
  56. }
  57.  
  58. getCommKey(packetID) {
  59. return this.commlinkValueIndicator + packetID;
  60. }
  61.  
  62. getStoredPackets() {
  63. return GM_listValues()
  64. .filter(key => key.includes(this.commlinkValueIndicator))
  65. .map(key => GM_getValue(key));
  66. }
  67.  
  68. addPacket(packet) {
  69. GM_setValue(this.getCommKey(packet.id), packet);
  70. }
  71.  
  72. removePacketByID(packetID) {
  73. GM_deleteValue(this.getCommKey(packetID));
  74. }
  75.  
  76. findPacketByID(packetID) {
  77. return GM_getValue(this.getCommKey(packetID));
  78. }
  79.  
  80. editPacket(newPacket) {
  81. GM_setValue(this.getCommKey(newPacket.id), newPacket);
  82. }
  83.  
  84. send(platform, cmd, d) {
  85. return new Promise(async resolve => {
  86. const packetWaitTimeMs = this.singlePacketResponseWaitTime;
  87. const maxAttempts = this.maxSendAttempts;
  88.  
  89. let attempts = 0;
  90.  
  91. for (;;) {
  92. attempts++;
  93.  
  94. const packetID = this.getUniqueID();
  95. const attemptStartDate = Date.now();
  96.  
  97. const packet = { sender: platform, id: packetID, command: cmd, data: d, date: attemptStartDate };
  98.  
  99. if(!this.silentMode)
  100. console.log(`[CommLink Sender] Sending packet! (#${attempts} attempt):`, packet);
  101.  
  102. this.addPacket(packet);
  103.  
  104. for (;;) {
  105. const poolPacket = this.findPacketByID(packetID);
  106. const packetResult = poolPacket?.result;
  107.  
  108. if (poolPacket && packetResult) {
  109. if(!this.silentMode)
  110. console.log(`[CommLink Sender] Got result for a packet (${packetID}):`, packetResult);
  111.  
  112. resolve(poolPacket.result);
  113.  
  114. attempts = maxAttempts; // stop main loop
  115.  
  116. break;
  117. }
  118.  
  119. if (!poolPacket || Date.now() - attemptStartDate > packetWaitTimeMs) {
  120. break;
  121. }
  122.  
  123. await new Promise(res => setTimeout(res, this.statusCheckInterval));
  124. }
  125.  
  126. this.removePacketByID(packetID);
  127.  
  128. if (attempts == maxAttempts) {
  129. break;
  130. }
  131. }
  132.  
  133. return resolve(null);
  134. });
  135. }
  136.  
  137. registerSendCommand(name, obj) {
  138. this.commands[name] = async data => await this.send(obj?.commlinkID || this.commlinkID , name, obj?.data || data);
  139. }
  140.  
  141. registerListener(sender, commandHandler) {
  142. const listener = {
  143. sender,
  144. commandHandler,
  145. intervalObj: this.setIntervalAsync(this.receivePackets.bind(this), this.statusCheckInterval),
  146. };
  147.  
  148. this.listeners.push(listener);
  149. }
  150.  
  151. receivePackets() {
  152. this.getStoredPackets().forEach(packet => {
  153. this.listeners.forEach(listener => {
  154. if(packet.sender === listener.sender && !packet.hasOwnProperty('result')) {
  155. const result = listener.commandHandler(packet);
  156.  
  157. packet.result = result;
  158.  
  159. this.editPacket(packet);
  160.  
  161. if(!this.silentMode) {
  162. if(packet.result == null)
  163. console.log('[CommLink Receiver] Possibly failed to handle packet:', packet);
  164. else
  165. console.log('[CommLink Receiver] Successfully handled a packet:', packet);
  166. }
  167. }
  168. });
  169. });
  170. }
  171.  
  172. kill() {
  173. this.listeners.forEach(listener => listener.intervalObj.stop());
  174. }
  175. }