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