@fitzzero/quickdraw-core 5.1.2 → 5.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +793 -57
- package/dist/{admin-DWWrT1h6.d.ts → admin-CS7JB_UH.d.ts} +2 -2
- package/dist/{adminSchemas-D4bFKsrb.d.ts → adminSchemas-29cGgrg5.d.ts} +1 -1
- package/dist/{auth-DXiSJZaf.d.ts → auth-DtdYjrZ9.d.ts} +11 -7
- package/dist/{caller-BT-Mv4bD.d.ts → caller-BTwGl7vm.d.ts} +183 -15
- package/dist/chunk-2FDSKLBO.js +82 -0
- package/dist/chunk-2FDSKLBO.js.map +1 -0
- package/dist/{chunk-GSIPUNJL.js → chunk-64I6MYBC.js} +139 -792
- package/dist/chunk-64I6MYBC.js.map +1 -0
- package/dist/chunk-7473HK24.js +17 -0
- package/dist/chunk-7473HK24.js.map +1 -0
- package/dist/chunk-7Y5ROPXL.js +33 -0
- package/dist/chunk-7Y5ROPXL.js.map +1 -0
- package/dist/chunk-A2RBQD66.js +7 -0
- package/dist/chunk-A2RBQD66.js.map +1 -0
- package/dist/chunk-ASAMUGHG.js +651 -0
- package/dist/chunk-ASAMUGHG.js.map +1 -0
- package/dist/{chunk-B2P7AZ25.js → chunk-CYE4UBOB.js} +6 -4
- package/dist/chunk-CYE4UBOB.js.map +1 -0
- package/dist/chunk-DNP65TD5.js +832 -0
- package/dist/chunk-DNP65TD5.js.map +1 -0
- package/dist/{chunk-5CGO426C.js → chunk-GYLWEJHW.js} +5 -1
- package/dist/chunk-GYLWEJHW.js.map +1 -0
- package/dist/{chunk-AONAFOYU.js → chunk-I3JCFS7H.js} +25 -13
- package/dist/chunk-I3JCFS7H.js.map +1 -0
- package/dist/{chunk-X5CWPX7H.js → chunk-IIQ3WMJK.js} +13 -2
- package/dist/chunk-IIQ3WMJK.js.map +1 -0
- package/dist/{chunk-6K5BY47X.js → chunk-JVTONQFI.js} +1277 -277
- package/dist/chunk-JVTONQFI.js.map +1 -0
- package/dist/{chunk-RL4VKLBX.js → chunk-KKQANRLG.js} +2 -2
- package/dist/{chunk-RL4VKLBX.js.map → chunk-KKQANRLG.js.map} +1 -1
- package/dist/{chunk-GKH3LJ7N.js → chunk-MPQAT55F.js} +38 -205
- package/dist/chunk-MPQAT55F.js.map +1 -0
- package/dist/chunk-NX2V6SIL.js +202 -0
- package/dist/chunk-NX2V6SIL.js.map +1 -0
- package/dist/{chunk-7QCUX4TK.js → chunk-NZHA4TZD.js} +72 -175
- package/dist/chunk-NZHA4TZD.js.map +1 -0
- package/dist/{chunk-MBH6IDSM.js → chunk-OCZJ4EYV.js} +40 -17
- package/dist/chunk-OCZJ4EYV.js.map +1 -0
- package/dist/{chunk-RJ5I2VO3.js → chunk-PHCURHC7.js} +6 -6
- package/dist/chunk-PHCURHC7.js.map +1 -0
- package/dist/{chunk-4YO5H5ON.js → chunk-Q4OGQM5I.js} +4 -10
- package/dist/chunk-Q4OGQM5I.js.map +1 -0
- package/dist/chunk-QSU47XEX.js +13 -0
- package/dist/chunk-QSU47XEX.js.map +1 -0
- package/dist/{chunk-LCC7HNFP.js → chunk-RZXJTDH5.js} +2 -2
- package/dist/{chunk-Z2LZ7IZ7.js → chunk-SN3OZVEQ.js} +5 -31
- package/dist/chunk-SN3OZVEQ.js.map +1 -0
- package/dist/{chunk-Z5ILULF2.js → chunk-SQ5VUG77.js} +4 -3
- package/dist/{chunk-Z5ILULF2.js.map → chunk-SQ5VUG77.js.map} +1 -1
- package/dist/chunk-STH5DLQP.js +1192 -0
- package/dist/chunk-STH5DLQP.js.map +1 -0
- package/dist/{chunk-NMXYENH4.js → chunk-VZFJTKIW.js} +6 -6
- package/dist/{chunk-753VJIUP.js → chunk-X4ZU4QH5.js} +43 -9
- package/dist/chunk-X4ZU4QH5.js.map +1 -0
- package/dist/chunk-XD7SRDUY.js +188 -0
- package/dist/chunk-XD7SRDUY.js.map +1 -0
- package/dist/chunk-Z7I6SONO.js +825 -0
- package/dist/chunk-Z7I6SONO.js.map +1 -0
- package/dist/cli/quickdraw-docs.js +131 -113
- package/dist/cli/quickdraw-docs.js.map +1 -1
- package/dist/client/core.d.ts +387 -0
- package/dist/client/core.js +109 -0
- package/dist/client/core.js.map +1 -0
- package/dist/client/index.d.ts +30 -193
- package/dist/client/index.js +56 -217
- package/dist/client/index.js.map +1 -1
- package/dist/{clientTypes-hWcPUVS9.d.ts → clientTypes-CUUNJwWs.d.ts} +3 -3
- package/dist/{connection-CgijdGpT.d.ts → connection-2EeQ_Et0.d.ts} +16 -9
- package/dist/{createServer-Cp-Wk500.d.ts → createServer-Bx5HOcYK.d.ts} +37 -9
- package/dist/{createTestApp-BB_PkqQ7.d.ts → createTestApp-DAMANT79.d.ts} +36 -9
- package/dist/{envelope-DzWyIXjj.d.ts → envelope-BMBaxppV.d.ts} +200 -8
- package/dist/{http-DotQ5bj8.d.ts → http-YzLAar3w.d.ts} +15 -50
- package/dist/index.d.ts +7 -6
- package/dist/index.js +61 -28
- package/dist/index.js.map +1 -1
- package/dist/{infer-BmoZsL9a.d.ts → infer-Br62cnyW.d.ts} +39 -1
- package/dist/{metrics-DW80Exve.d.ts → metrics-CTVpw0gu.d.ts} +2 -2
- package/dist/parser.js +2 -1
- package/dist/prisma/index.d.ts +13 -6
- package/dist/prisma/index.js +155 -9
- package/dist/prisma/index.js.map +1 -1
- package/dist/remote-N7OUAGOW.js +15 -0
- package/dist/remote-N7OUAGOW.js.map +1 -0
- package/dist/server/auth/index.d.ts +3 -3
- package/dist/server/auth/index.js +5 -3
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/index.d.ts +140 -36
- package/dist/server/index.js +162 -55
- package/dist/server/index.js.map +1 -1
- package/dist/server/mcp/index.d.ts +34 -18
- package/dist/server/mcp/index.js +29 -12
- package/dist/server/mcp/index.js.map +1 -1
- package/dist/server/otel.d.ts +3 -3
- package/dist/{sharing-UDLn1049.d.ts → sharing-BrsmlDv_.d.ts} +3 -3
- package/dist/testing/client.d.ts +13 -13
- package/dist/testing/client.js +17 -10
- package/dist/testing/client.js.map +1 -1
- package/dist/testing/index.d.ts +49 -12
- package/dist/testing/index.js +102 -19
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/mock.d.ts +5 -5
- package/dist/testing/mock.js +15 -9
- package/dist/tracked-BR5b2O-U.d.ts +43 -0
- package/dist/{types-CYliTP65.d.ts → types-CLRCQGnO.d.ts} +4 -3
- package/dist/types-tMagHbjy.d.ts +151 -0
- package/dist/utils/index.d.ts +16 -4
- package/dist/utils/index.js +6 -3
- package/package.json +5 -1
- package/dist/chunk-4YO5H5ON.js.map +0 -1
- package/dist/chunk-5CGO426C.js.map +0 -1
- package/dist/chunk-6K5BY47X.js.map +0 -1
- package/dist/chunk-753VJIUP.js.map +0 -1
- package/dist/chunk-7QCUX4TK.js.map +0 -1
- package/dist/chunk-AONAFOYU.js.map +0 -1
- package/dist/chunk-B2P7AZ25.js.map +0 -1
- package/dist/chunk-GKH3LJ7N.js.map +0 -1
- package/dist/chunk-GSIPUNJL.js.map +0 -1
- package/dist/chunk-MBH6IDSM.js.map +0 -1
- package/dist/chunk-RJ5I2VO3.js.map +0 -1
- package/dist/chunk-X5CWPX7H.js.map +0 -1
- package/dist/chunk-Z2LZ7IZ7.js.map +0 -1
- /package/dist/{chunk-LCC7HNFP.js.map → chunk-RZXJTDH5.js.map} +0 -0
- /package/dist/{chunk-NMXYENH4.js.map → chunk-VZFJTKIW.js.map} +0 -0
|
@@ -0,0 +1,1192 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isRoomName,
|
|
3
|
+
isSocketId
|
|
4
|
+
} from "./chunk-2FDSKLBO.js";
|
|
5
|
+
import {
|
|
6
|
+
QUICKDRAW_VERSION
|
|
7
|
+
} from "./chunk-A2RBQD66.js";
|
|
8
|
+
import {
|
|
9
|
+
CLIENT_EVENTS,
|
|
10
|
+
MAX_PEER_MESSAGE_BYTES,
|
|
11
|
+
MAX_SUBSCRIBE_IDS,
|
|
12
|
+
PROTOCOL_VERSION,
|
|
13
|
+
SERVER_EVENTS,
|
|
14
|
+
isAuthenticationRefused,
|
|
15
|
+
isAuthenticationUnavailable,
|
|
16
|
+
isProtocolMismatch
|
|
17
|
+
} from "./chunk-X4ZU4QH5.js";
|
|
18
|
+
import {
|
|
19
|
+
createJsonParser
|
|
20
|
+
} from "./chunk-SN3OZVEQ.js";
|
|
21
|
+
import {
|
|
22
|
+
QuickdrawError,
|
|
23
|
+
fromWire
|
|
24
|
+
} from "./chunk-Q4OGQM5I.js";
|
|
25
|
+
import {
|
|
26
|
+
isRecord
|
|
27
|
+
} from "./chunk-QSU47XEX.js";
|
|
28
|
+
|
|
29
|
+
// src/client/backoff.ts
|
|
30
|
+
var DEFAULT_BACKOFF_MS = 5e3;
|
|
31
|
+
var MIN_BACKOFF_MS = 250;
|
|
32
|
+
var MAX_BACKOFF_MS = 5 * 60 * 1e3;
|
|
33
|
+
var JITTER_RATIO = 0.5;
|
|
34
|
+
function retryAfterOf(error) {
|
|
35
|
+
const retryAfterMs = isRecord(error.data) ? error.data.retryAfterMs : void 0;
|
|
36
|
+
return typeof retryAfterMs === "number" ? retryAfterMs : void 0;
|
|
37
|
+
}
|
|
38
|
+
function waitFor(retryAfterMs) {
|
|
39
|
+
const wait = typeof retryAfterMs === "number" && Number.isFinite(retryAfterMs) ? retryAfterMs : DEFAULT_BACKOFF_MS;
|
|
40
|
+
return Math.min(Math.max(wait, MIN_BACKOFF_MS), MAX_BACKOFF_MS);
|
|
41
|
+
}
|
|
42
|
+
function createBackoff(onChange) {
|
|
43
|
+
const until = /* @__PURE__ */ new Map();
|
|
44
|
+
const timers = /* @__PURE__ */ new Map();
|
|
45
|
+
const windows = () => Object.freeze(Object.fromEntries(until));
|
|
46
|
+
function end(kind) {
|
|
47
|
+
timers.delete(kind);
|
|
48
|
+
until.delete(kind);
|
|
49
|
+
onChange(windows());
|
|
50
|
+
}
|
|
51
|
+
return Object.freeze({
|
|
52
|
+
report(kind, retryAfterMs) {
|
|
53
|
+
const wait = waitFor(retryAfterMs);
|
|
54
|
+
const delay = wait + Math.random() * wait * JITTER_RATIO;
|
|
55
|
+
const ends = Date.now() + delay;
|
|
56
|
+
if (ends <= (until.get(kind) ?? 0)) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
clearTimeout(timers.get(kind));
|
|
60
|
+
until.set(kind, ends);
|
|
61
|
+
timers.set(
|
|
62
|
+
kind,
|
|
63
|
+
setTimeout(() => {
|
|
64
|
+
end(kind);
|
|
65
|
+
}, delay)
|
|
66
|
+
);
|
|
67
|
+
onChange(windows());
|
|
68
|
+
},
|
|
69
|
+
remaining(kind) {
|
|
70
|
+
const ends = until.get(kind);
|
|
71
|
+
return ends === void 0 ? 0 : Math.max(0, ends - Date.now());
|
|
72
|
+
},
|
|
73
|
+
windows,
|
|
74
|
+
clear() {
|
|
75
|
+
for (const timer of timers.values()) {
|
|
76
|
+
clearTimeout(timer);
|
|
77
|
+
}
|
|
78
|
+
timers.clear();
|
|
79
|
+
if (until.size > 0) {
|
|
80
|
+
until.clear();
|
|
81
|
+
onChange(windows());
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/client/watch.ts
|
|
88
|
+
function topicId(service, topic) {
|
|
89
|
+
return `${service}\0${topic}`;
|
|
90
|
+
}
|
|
91
|
+
function rethrowLater(error) {
|
|
92
|
+
queueMicrotask(() => {
|
|
93
|
+
throw error;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
function notifyEach(listeners, notify) {
|
|
97
|
+
for (const listener of [...listeners]) {
|
|
98
|
+
try {
|
|
99
|
+
notify(listener);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
rethrowLater(error);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function oncePerKey(records) {
|
|
106
|
+
const keys = /* @__PURE__ */ new Set();
|
|
107
|
+
return [...records].filter(({ watch }) => {
|
|
108
|
+
if (watch.key === void 0) {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
const first = !keys.has(watch.key);
|
|
112
|
+
keys.add(watch.key);
|
|
113
|
+
return first;
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
function modelName(model) {
|
|
117
|
+
return `${model.charAt(0).toLowerCase()}${model.slice(1)}`;
|
|
118
|
+
}
|
|
119
|
+
function concerns(watch, frame) {
|
|
120
|
+
const { models } = watch;
|
|
121
|
+
if (models === void 0 || !Array.isArray(frame.models)) {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
const changed = new Set(frame.models.map((model) => modelName(String(model))));
|
|
125
|
+
return models.some((model) => changed.has(modelName(model)));
|
|
126
|
+
}
|
|
127
|
+
function tell(topic, frame) {
|
|
128
|
+
const concerned = [...topic.watches].filter(({ watch }) => concerns(watch, frame));
|
|
129
|
+
notifyEach(oncePerKey(concerned), ({ watch }) => {
|
|
130
|
+
watch.onChanged(frame);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
function tellJoined(topic) {
|
|
134
|
+
const waited = new Set([...topic.holds].map((hold) => hold.key));
|
|
135
|
+
const unjoined = new Set(topic.unjoinedReads);
|
|
136
|
+
topic.unjoinedReads.clear();
|
|
137
|
+
const owed = [...topic.watches].filter(
|
|
138
|
+
({ watch, told }) => !told || watch.key !== void 0 && unjoined.has(watch.key)
|
|
139
|
+
);
|
|
140
|
+
for (const record of owed) {
|
|
141
|
+
record.told = true;
|
|
142
|
+
}
|
|
143
|
+
const toTell = owed.filter(({ watch }) => watch.key === void 0 || !waited.has(watch.key));
|
|
144
|
+
notifyEach(oncePerKey(toTell), ({ watch }) => {
|
|
145
|
+
watch.onJoined?.();
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
function releaseHolds(topic) {
|
|
149
|
+
const holds = [...topic.holds];
|
|
150
|
+
topic.holds.clear();
|
|
151
|
+
for (const hold of holds) {
|
|
152
|
+
if (topic.state !== "joined" && hold.key !== void 0) {
|
|
153
|
+
topic.unjoinedReads.add(hold.key);
|
|
154
|
+
}
|
|
155
|
+
hold.release();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function isChangedFrame(value) {
|
|
159
|
+
return isRecord(value) && typeof value.s === "string" && typeof value.topic === "string";
|
|
160
|
+
}
|
|
161
|
+
function retryIn(registry, topic, delayMs) {
|
|
162
|
+
clearTimeout(topic.retry);
|
|
163
|
+
topic.state = "waiting";
|
|
164
|
+
topic.retry = setTimeout(() => {
|
|
165
|
+
topic.retry = void 0;
|
|
166
|
+
if (registry.topics.get(topic.id) === topic) {
|
|
167
|
+
join(registry, topic);
|
|
168
|
+
}
|
|
169
|
+
}, delayMs);
|
|
170
|
+
}
|
|
171
|
+
function refused(registry, topic, reply) {
|
|
172
|
+
const { host } = registry;
|
|
173
|
+
const failure = fromWire(isRecord(reply) ? reply.e : void 0);
|
|
174
|
+
if (failure.code === "RATE_LIMITED") {
|
|
175
|
+
host.reportRateLimited("subscription", retryAfterOf(failure));
|
|
176
|
+
retryIn(registry, topic, host.backoffRemaining("subscription"));
|
|
177
|
+
} else {
|
|
178
|
+
topic.state = "refused";
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function answered(registry, topic, error, reply) {
|
|
182
|
+
if (error !== null) {
|
|
183
|
+
if (registry.host.socket.connected) {
|
|
184
|
+
retryIn(registry, topic, DEFAULT_BACKOFF_MS);
|
|
185
|
+
} else {
|
|
186
|
+
topic.state = "waiting";
|
|
187
|
+
}
|
|
188
|
+
} else if (isRecord(reply) && reply.ok === true) {
|
|
189
|
+
topic.state = "joined";
|
|
190
|
+
tellJoined(topic);
|
|
191
|
+
} else {
|
|
192
|
+
refused(registry, topic, reply);
|
|
193
|
+
}
|
|
194
|
+
releaseHolds(topic);
|
|
195
|
+
}
|
|
196
|
+
function join(registry, topic) {
|
|
197
|
+
const { host } = registry;
|
|
198
|
+
clearTimeout(topic.retry);
|
|
199
|
+
topic.retry = void 0;
|
|
200
|
+
topic.attempt += 1;
|
|
201
|
+
const wait = host.backoffRemaining("subscription");
|
|
202
|
+
if (!host.socket.connected) {
|
|
203
|
+
topic.state = "waiting";
|
|
204
|
+
releaseHolds(topic);
|
|
205
|
+
} else if (wait > 0) {
|
|
206
|
+
retryIn(registry, topic, wait);
|
|
207
|
+
releaseHolds(topic);
|
|
208
|
+
} else {
|
|
209
|
+
topic.state = "joining";
|
|
210
|
+
const { attempt } = topic;
|
|
211
|
+
host.lane.send(CLIENT_EVENTS.watch, topic.frame, (error, reply) => {
|
|
212
|
+
if (registry.topics.get(topic.id) === topic && topic.attempt === attempt) {
|
|
213
|
+
answered(registry, topic, error, reply);
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
function leave(registry, topic) {
|
|
219
|
+
registry.topics.delete(topic.id);
|
|
220
|
+
clearTimeout(topic.retry);
|
|
221
|
+
clearTimeout(topic.leaving);
|
|
222
|
+
topic.leaving = void 0;
|
|
223
|
+
topic.attempt += 1;
|
|
224
|
+
releaseHolds(topic);
|
|
225
|
+
const sent = topic.state === "joining" || topic.state === "joined";
|
|
226
|
+
if (sent && registry.host.socket.connected) {
|
|
227
|
+
registry.host.socket.emit(CLIENT_EVENTS.unwatch, topic.frame);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function waitForJoin(registry, wait) {
|
|
231
|
+
const topic = registry.topics.get(topicId(wait.service, wait.topic));
|
|
232
|
+
if (topic === void 0) {
|
|
233
|
+
return void 0;
|
|
234
|
+
}
|
|
235
|
+
if (topic.state === "joining" && registry.host.socket.connected) {
|
|
236
|
+
return new Promise((resolve) => {
|
|
237
|
+
topic.holds.add({ key: wait.key, release: resolve });
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
if (topic.state !== "joined" && wait.key !== void 0) {
|
|
241
|
+
topic.unjoinedReads.add(wait.key);
|
|
242
|
+
}
|
|
243
|
+
return void 0;
|
|
244
|
+
}
|
|
245
|
+
function startWatch(registry, watch) {
|
|
246
|
+
const id = topicId(watch.service, watch.topic);
|
|
247
|
+
const topic = registry.topics.get(id) ?? {
|
|
248
|
+
id,
|
|
249
|
+
frame: Object.freeze({ s: watch.service, topic: watch.topic }),
|
|
250
|
+
watches: /* @__PURE__ */ new Set(),
|
|
251
|
+
holds: /* @__PURE__ */ new Set(),
|
|
252
|
+
unjoinedReads: /* @__PURE__ */ new Set(),
|
|
253
|
+
state: "waiting",
|
|
254
|
+
retry: void 0,
|
|
255
|
+
leaving: void 0,
|
|
256
|
+
attempt: 0
|
|
257
|
+
};
|
|
258
|
+
registry.topics.set(id, topic);
|
|
259
|
+
const kept = topic.leaving !== void 0;
|
|
260
|
+
clearTimeout(topic.leaving);
|
|
261
|
+
topic.leaving = void 0;
|
|
262
|
+
const record = { watch, told: topic.state === "joined" };
|
|
263
|
+
topic.watches.add(record);
|
|
264
|
+
if (topic.watches.size === 1 && !kept) {
|
|
265
|
+
join(registry, topic);
|
|
266
|
+
}
|
|
267
|
+
return () => {
|
|
268
|
+
if (topic.watches.delete(record) && topic.watches.size === 0) {
|
|
269
|
+
topic.leaving = setTimeout(() => {
|
|
270
|
+
if (topic.watches.size === 0 && registry.topics.get(topic.id) === topic) {
|
|
271
|
+
leave(registry, topic);
|
|
272
|
+
}
|
|
273
|
+
}, 0);
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
function createTopics(host) {
|
|
278
|
+
const registry = { host, topics: /* @__PURE__ */ new Map() };
|
|
279
|
+
host.socket.on(SERVER_EVENTS.changed, (frame) => {
|
|
280
|
+
const topic = isChangedFrame(frame) ? registry.topics.get(topicId(frame.s, frame.topic)) : void 0;
|
|
281
|
+
if (topic !== void 0) {
|
|
282
|
+
tell(topic, frame);
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
host.socket.on("disconnect", () => {
|
|
286
|
+
for (const topic of registry.topics.values()) {
|
|
287
|
+
if (topic.state === "joined") {
|
|
288
|
+
topic.state = "waiting";
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
return Object.freeze({
|
|
293
|
+
watch: (watch) => startWatch(registry, watch),
|
|
294
|
+
waitForJoin: (wait) => waitForJoin(registry, wait),
|
|
295
|
+
rejoin() {
|
|
296
|
+
for (const topic of [...registry.topics.values()]) {
|
|
297
|
+
if (topic.watches.size === 0) {
|
|
298
|
+
leave(registry, topic);
|
|
299
|
+
} else {
|
|
300
|
+
join(registry, topic);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
},
|
|
304
|
+
stop() {
|
|
305
|
+
for (const topic of [...registry.topics.values()]) {
|
|
306
|
+
if (topic.watches.size === 0) {
|
|
307
|
+
leave(registry, topic);
|
|
308
|
+
} else {
|
|
309
|
+
clearTimeout(topic.retry);
|
|
310
|
+
topic.retry = void 0;
|
|
311
|
+
topic.attempt += 1;
|
|
312
|
+
topic.state = "waiting";
|
|
313
|
+
releaseHolds(topic);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// src/client/lane.ts
|
|
321
|
+
var DEFAULT_SUBSCRIPTION_LANE = 8;
|
|
322
|
+
function positiveInteger(value, fallback) {
|
|
323
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : fallback;
|
|
324
|
+
}
|
|
325
|
+
function subscriptionLimits(hello) {
|
|
326
|
+
const limits = hello?.limits;
|
|
327
|
+
const record = isRecord(limits) ? limits : {};
|
|
328
|
+
const lane = isRecord(record.subscriptions) ? record.subscriptions : {};
|
|
329
|
+
return {
|
|
330
|
+
maxInFlight: positiveInteger(lane.maxInFlight, DEFAULT_SUBSCRIPTION_LANE),
|
|
331
|
+
maxSubscribeIds: positiveInteger(record.maxSubscribeIds, MAX_SUBSCRIBE_IDS)
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
function notConnected() {
|
|
335
|
+
return new Error("The socket is not connected; the event was not sent");
|
|
336
|
+
}
|
|
337
|
+
function createSubscriptionLane(host) {
|
|
338
|
+
const queue = [];
|
|
339
|
+
let inFlight = 0;
|
|
340
|
+
let backoffTimer;
|
|
341
|
+
const answer = (pending, error, reply) => {
|
|
342
|
+
notifyEach([pending.done], (done) => {
|
|
343
|
+
done(error, reply);
|
|
344
|
+
});
|
|
345
|
+
};
|
|
346
|
+
function sendOne(pending) {
|
|
347
|
+
inFlight += 1;
|
|
348
|
+
const emitter = host.socket.timeout(host.timeoutMs());
|
|
349
|
+
emitter.emit(pending.event, pending.frame, (error, reply) => {
|
|
350
|
+
inFlight -= 1;
|
|
351
|
+
answer(pending, error, reply);
|
|
352
|
+
pump();
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
function waitForBackoff(wait) {
|
|
356
|
+
backoffTimer ??= setTimeout(() => {
|
|
357
|
+
backoffTimer = void 0;
|
|
358
|
+
pump();
|
|
359
|
+
}, wait);
|
|
360
|
+
}
|
|
361
|
+
function dropAll() {
|
|
362
|
+
for (const dropped of queue.splice(0)) {
|
|
363
|
+
answer(dropped, notConnected(), void 0);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
function pump() {
|
|
367
|
+
const { maxInFlight } = subscriptionLimits(host.hello());
|
|
368
|
+
while (queue.length > 0 && inFlight < maxInFlight) {
|
|
369
|
+
if (!host.socket.connected) {
|
|
370
|
+
dropAll();
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const wait = host.backoffRemaining("subscription");
|
|
374
|
+
if (wait > 0) {
|
|
375
|
+
waitForBackoff(wait);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
sendOne(queue.shift());
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return Object.freeze({
|
|
382
|
+
send(event, frame, done) {
|
|
383
|
+
queue.push({ event, frame, done });
|
|
384
|
+
pump();
|
|
385
|
+
},
|
|
386
|
+
inFlight: () => inFlight,
|
|
387
|
+
waiting: () => queue.length,
|
|
388
|
+
stop() {
|
|
389
|
+
clearTimeout(backoffTimer);
|
|
390
|
+
backoffTimer = void 0;
|
|
391
|
+
dropAll();
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// src/client/reload.ts
|
|
397
|
+
var RELOAD_KEY_PREFIX = "quickdraw:protocol-reload:";
|
|
398
|
+
function firstReload(scope, key) {
|
|
399
|
+
try {
|
|
400
|
+
const storage = scope.sessionStorage;
|
|
401
|
+
if (storage === void 0 || storage.getItem(key) !== null) {
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
404
|
+
storage.setItem(key, String(Date.now()));
|
|
405
|
+
return true;
|
|
406
|
+
} catch {
|
|
407
|
+
return false;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
function reloadOncePerSession(mismatch) {
|
|
411
|
+
const scope = globalThis;
|
|
412
|
+
const location = scope.location;
|
|
413
|
+
if (typeof location?.reload !== "function") {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
if (firstReload(scope, `${RELOAD_KEY_PREFIX}${mismatch.expected}`)) {
|
|
417
|
+
location.reload();
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// src/client/retry.ts
|
|
422
|
+
var MAX_DOUBLINGS = 20;
|
|
423
|
+
function retryDelay(socket, attempt) {
|
|
424
|
+
const manager = socket.io;
|
|
425
|
+
const wait = manager.reconnectionDelay() * 2 ** Math.min(attempt, MAX_DOUBLINGS);
|
|
426
|
+
const deviation = Math.floor(Math.random() * manager.randomizationFactor() * wait);
|
|
427
|
+
const jittered = Math.random() < 0.5 ? wait - deviation : wait + deviation;
|
|
428
|
+
return Math.min(jittered, manager.reconnectionDelayMax());
|
|
429
|
+
}
|
|
430
|
+
function createRetry(socket, wanted) {
|
|
431
|
+
let timer;
|
|
432
|
+
let attempts = 0;
|
|
433
|
+
return Object.freeze({
|
|
434
|
+
later() {
|
|
435
|
+
const manager = socket.io;
|
|
436
|
+
if (!wanted() || !manager.reconnection() || attempts >= manager.reconnectionAttempts()) {
|
|
437
|
+
return false;
|
|
438
|
+
}
|
|
439
|
+
clearTimeout(timer);
|
|
440
|
+
timer = setTimeout(
|
|
441
|
+
() => {
|
|
442
|
+
timer = void 0;
|
|
443
|
+
if (wanted() && !socket.connected && !socket.active) {
|
|
444
|
+
socket.connect();
|
|
445
|
+
}
|
|
446
|
+
},
|
|
447
|
+
retryDelay(socket, attempts)
|
|
448
|
+
);
|
|
449
|
+
attempts += 1;
|
|
450
|
+
return true;
|
|
451
|
+
},
|
|
452
|
+
stop() {
|
|
453
|
+
clearTimeout(timer);
|
|
454
|
+
timer = void 0;
|
|
455
|
+
attempts = 0;
|
|
456
|
+
},
|
|
457
|
+
connected() {
|
|
458
|
+
attempts = 0;
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// src/client/socket.ts
|
|
464
|
+
import { io } from "socket.io-client";
|
|
465
|
+
function createSocket(settings, credentials) {
|
|
466
|
+
return io(settings.url, {
|
|
467
|
+
forceNew: true,
|
|
468
|
+
withCredentials: true,
|
|
469
|
+
transports: ["websocket", "polling"],
|
|
470
|
+
...settings.binary === true ? {} : { parser: createJsonParser() },
|
|
471
|
+
...settings.socketOptions,
|
|
472
|
+
...settings.transports === void 0 ? {} : { transports: settings.transports },
|
|
473
|
+
autoConnect: false,
|
|
474
|
+
auth: (send) => {
|
|
475
|
+
send({ ...credentials(), qd: { protocol: PROTOCOL_VERSION, client: QUICKDRAW_VERSION } });
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// src/client/connection.ts
|
|
481
|
+
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
482
|
+
var HELLO_TIMEOUT_MARGIN_MS = 2e3;
|
|
483
|
+
var INITIAL_CONNECTION_STATE = Object.freeze({
|
|
484
|
+
status: "idle",
|
|
485
|
+
reconnecting: false,
|
|
486
|
+
hello: null,
|
|
487
|
+
serviceAccess: null,
|
|
488
|
+
refusal: null,
|
|
489
|
+
backoff: Object.freeze({})
|
|
490
|
+
});
|
|
491
|
+
function createStore() {
|
|
492
|
+
let state = INITIAL_CONNECTION_STATE;
|
|
493
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
494
|
+
return {
|
|
495
|
+
get: () => state,
|
|
496
|
+
set(patch) {
|
|
497
|
+
state = Object.freeze({ ...state, ...patch });
|
|
498
|
+
for (const listener of [...listeners]) {
|
|
499
|
+
listener();
|
|
500
|
+
}
|
|
501
|
+
},
|
|
502
|
+
subscribe(listener) {
|
|
503
|
+
listeners.add(listener);
|
|
504
|
+
return () => {
|
|
505
|
+
listeners.delete(listener);
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
function credentialsOf(auth) {
|
|
511
|
+
if (typeof auth === "string") {
|
|
512
|
+
return auth === "" ? {} : { token: auth };
|
|
513
|
+
}
|
|
514
|
+
return auth ?? {};
|
|
515
|
+
}
|
|
516
|
+
function authKey(auth) {
|
|
517
|
+
const sorted = (_key, value) => isRecord(value) ? Object.fromEntries(Object.entries(value).sort(([a], [b]) => a < b ? -1 : 1)) : value;
|
|
518
|
+
return JSON.stringify(credentialsOf(auth), sorted);
|
|
519
|
+
}
|
|
520
|
+
function failBuffered(socket, error) {
|
|
521
|
+
const buffered = socket.sendBuffer.splice(0);
|
|
522
|
+
const { acks } = socket;
|
|
523
|
+
for (const packet of buffered) {
|
|
524
|
+
const id = packet.id === void 0 ? void 0 : String(packet.id);
|
|
525
|
+
const ack = id === void 0 ? void 0 : acks[id];
|
|
526
|
+
if (id !== void 0 && ack !== void 0) {
|
|
527
|
+
Reflect.deleteProperty(acks, id);
|
|
528
|
+
if (ack.withError === true) {
|
|
529
|
+
ack.call(socket, error);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
function isServerDisconnect(reason) {
|
|
535
|
+
return reason === "io server disconnect";
|
|
536
|
+
}
|
|
537
|
+
function mayConnect(connection) {
|
|
538
|
+
return connection.socket.active || connection.getState().status === "connecting";
|
|
539
|
+
}
|
|
540
|
+
function isSessionRefusal(data) {
|
|
541
|
+
return isRecord(data) && (data.code === "UNAUTHENTICATED" || data.code === "FORBIDDEN");
|
|
542
|
+
}
|
|
543
|
+
function refusalOf(error) {
|
|
544
|
+
if (isProtocolMismatch(error.data)) {
|
|
545
|
+
return { code: error.data.code, expected: error.data.expected };
|
|
546
|
+
}
|
|
547
|
+
if (isAuthenticationRefused(error.data)) {
|
|
548
|
+
return { code: "UNAUTHENTICATED" };
|
|
549
|
+
}
|
|
550
|
+
if (isAuthenticationUnavailable(error.data)) {
|
|
551
|
+
return { code: "INTERNAL" };
|
|
552
|
+
}
|
|
553
|
+
return { code: "REFUSED", message: error.message };
|
|
554
|
+
}
|
|
555
|
+
var MAX_TIMEOUT_MS = 2147483647;
|
|
556
|
+
function isTimeLimit(value) {
|
|
557
|
+
return typeof value === "number" && value > 0 && value <= MAX_TIMEOUT_MS;
|
|
558
|
+
}
|
|
559
|
+
function checkOptions(options) {
|
|
560
|
+
if (typeof options.url !== "string" || options.url === "") {
|
|
561
|
+
throw new TypeError("createQuickdrawConnection: url must be the server's URL");
|
|
562
|
+
}
|
|
563
|
+
const { timeoutMs } = options;
|
|
564
|
+
if (timeoutMs !== void 0 && !isTimeLimit(timeoutMs)) {
|
|
565
|
+
throw new TypeError(
|
|
566
|
+
"createQuickdrawConnection: timeoutMs must be a number of milliseconds, above 0 and at most 2^31 - 1"
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
return timeoutMs;
|
|
570
|
+
}
|
|
571
|
+
function defaultTimeout(option, hello) {
|
|
572
|
+
if (option !== void 0) {
|
|
573
|
+
return option;
|
|
574
|
+
}
|
|
575
|
+
const limits = hello?.limits;
|
|
576
|
+
const serverMs = isRecord(limits) ? limits.callTimeoutMs : void 0;
|
|
577
|
+
const fromHello = typeof serverMs === "number" ? serverMs + HELLO_TIMEOUT_MARGIN_MS : void 0;
|
|
578
|
+
return isTimeLimit(fromHello) ? fromHello : DEFAULT_TIMEOUT_MS;
|
|
579
|
+
}
|
|
580
|
+
function createLifecycle(socket, store, backoff, session) {
|
|
581
|
+
let opened2 = false;
|
|
582
|
+
let switching = false;
|
|
583
|
+
let users = 0;
|
|
584
|
+
let closeTimer;
|
|
585
|
+
let rotateTimer;
|
|
586
|
+
function connect2() {
|
|
587
|
+
socket.connect();
|
|
588
|
+
store.set({ status: "connecting", refusal: null });
|
|
589
|
+
}
|
|
590
|
+
function reconnect() {
|
|
591
|
+
if (!opened2) {
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
switching = true;
|
|
595
|
+
socket.disconnect();
|
|
596
|
+
switching = false;
|
|
597
|
+
connect2();
|
|
598
|
+
}
|
|
599
|
+
function open() {
|
|
600
|
+
clearTimeout(closeTimer);
|
|
601
|
+
closeTimer = void 0;
|
|
602
|
+
opened2 = true;
|
|
603
|
+
if (!socket.connected && !socket.active) {
|
|
604
|
+
connect2();
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
function close() {
|
|
608
|
+
opened2 = false;
|
|
609
|
+
session.established = false;
|
|
610
|
+
session.topics.stop();
|
|
611
|
+
session.lane.stop();
|
|
612
|
+
session.retry.stop();
|
|
613
|
+
clearTimeout(closeTimer);
|
|
614
|
+
closeTimer = void 0;
|
|
615
|
+
clearTimeout(rotateTimer);
|
|
616
|
+
rotateTimer = void 0;
|
|
617
|
+
backoff.clear();
|
|
618
|
+
failBuffered(
|
|
619
|
+
socket,
|
|
620
|
+
new QuickdrawError("CANCELLED", "The connection closed before the call was sent")
|
|
621
|
+
);
|
|
622
|
+
socket.disconnect();
|
|
623
|
+
store.set({ status: "idle", reconnecting: false });
|
|
624
|
+
}
|
|
625
|
+
function retain() {
|
|
626
|
+
users += 1;
|
|
627
|
+
open();
|
|
628
|
+
let released = false;
|
|
629
|
+
return () => {
|
|
630
|
+
if (!released) {
|
|
631
|
+
released = true;
|
|
632
|
+
users -= 1;
|
|
633
|
+
if (users === 0) {
|
|
634
|
+
closeTimer = setTimeout(close, 0);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
function rotateWithin(withinMs) {
|
|
640
|
+
if (rotateTimer === void 0) {
|
|
641
|
+
rotateTimer = setTimeout(() => {
|
|
642
|
+
rotateTimer = void 0;
|
|
643
|
+
reconnect();
|
|
644
|
+
}, Math.random() * withinMs);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
function stoppedStatus() {
|
|
648
|
+
if (switching) {
|
|
649
|
+
return "connecting";
|
|
650
|
+
}
|
|
651
|
+
return opened2 ? "disconnected" : "idle";
|
|
652
|
+
}
|
|
653
|
+
return { open, close, retain, reconnect, rotateWithin, stoppedStatus };
|
|
654
|
+
}
|
|
655
|
+
function sayHello(hellos, entry, hello) {
|
|
656
|
+
if (hellos.has(entry) && entry.last !== hello) {
|
|
657
|
+
entry.last = hello;
|
|
658
|
+
entry.listener(hello);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
function addListener(set, listener) {
|
|
662
|
+
set.add(listener);
|
|
663
|
+
return () => {
|
|
664
|
+
set.delete(listener);
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
function listenForHellos(wiring, listener) {
|
|
668
|
+
const { hellos, store } = wiring;
|
|
669
|
+
const entry = { listener, last: void 0 };
|
|
670
|
+
hellos.add(entry);
|
|
671
|
+
const { status, hello } = store.get();
|
|
672
|
+
if (status === "connected" && hello !== null) {
|
|
673
|
+
queueMicrotask(() => {
|
|
674
|
+
notifyEach([entry], (current) => {
|
|
675
|
+
sayHello(hellos, current, hello);
|
|
676
|
+
});
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
return () => {
|
|
680
|
+
hellos.delete(entry);
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
function listenToSocket(wiring) {
|
|
684
|
+
const { socket, store, lifecycle, session } = wiring;
|
|
685
|
+
socket.on("connect", () => {
|
|
686
|
+
const again = session.established;
|
|
687
|
+
session.established = true;
|
|
688
|
+
session.retry.connected();
|
|
689
|
+
store.set({ status: "connected", reconnecting: false, refusal: null });
|
|
690
|
+
session.topics.rejoin();
|
|
691
|
+
if (again) {
|
|
692
|
+
notifyEach(wiring.reconnected, (listener) => {
|
|
693
|
+
listener();
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
});
|
|
697
|
+
socket.on("disconnect", (reason) => {
|
|
698
|
+
const status = socket.active ? "connecting" : lifecycle.stoppedStatus();
|
|
699
|
+
const ended = isServerDisconnect(reason) ? { hello: null, serviceAccess: null } : {};
|
|
700
|
+
store.set({ status, reconnecting: status === "connecting" && session.established, ...ended });
|
|
701
|
+
});
|
|
702
|
+
socket.on("connect_error", (error) => {
|
|
703
|
+
if (socket.active) {
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
if (isAuthenticationUnavailable(error.data) && session.retry.later()) {
|
|
707
|
+
store.set({ status: "connecting", reconnecting: session.established, refusal: null });
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
const refusal = refusalOf(error);
|
|
711
|
+
session.established = false;
|
|
712
|
+
const ended = isSessionRefusal(error.data);
|
|
713
|
+
if (ended) {
|
|
714
|
+
failBuffered(
|
|
715
|
+
socket,
|
|
716
|
+
new QuickdrawError("CANCELLED", "The session ended before the call was sent")
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
store.set({
|
|
720
|
+
status: "refused",
|
|
721
|
+
reconnecting: false,
|
|
722
|
+
refusal,
|
|
723
|
+
...ended ? { hello: null, serviceAccess: null } : {}
|
|
724
|
+
});
|
|
725
|
+
if (refusal.code === "PROTOCOL_MISMATCH") {
|
|
726
|
+
wiring.onProtocolMismatch(refusal);
|
|
727
|
+
}
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
function listenToPushes(wiring) {
|
|
731
|
+
const { socket, store, lifecycle } = wiring;
|
|
732
|
+
socket.on(SERVER_EVENTS.hello, (frame) => {
|
|
733
|
+
const grants = isRecord(frame) && isRecord(frame.serviceAccess) ? frame.serviceAccess : null;
|
|
734
|
+
store.set({ hello: frame, serviceAccess: grants });
|
|
735
|
+
notifyEach(wiring.hellos, (entry) => {
|
|
736
|
+
sayHello(wiring.hellos, entry, frame);
|
|
737
|
+
});
|
|
738
|
+
});
|
|
739
|
+
socket.on(SERVER_EVENTS.access, (frame) => {
|
|
740
|
+
if (isRecord(frame) && isRecord(frame.serviceAccess)) {
|
|
741
|
+
store.set({ serviceAccess: frame.serviceAccess });
|
|
742
|
+
}
|
|
743
|
+
});
|
|
744
|
+
socket.on(SERVER_EVENTS.rotate, (frame) => {
|
|
745
|
+
const withinMs = isRecord(frame) ? frame.withinMs : void 0;
|
|
746
|
+
if (typeof withinMs === "number" && Number.isFinite(withinMs) && withinMs >= 0) {
|
|
747
|
+
lifecycle.rotateWithin(withinMs);
|
|
748
|
+
}
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
function createQuickdrawConnection(options) {
|
|
752
|
+
const timeoutOption = checkOptions(options);
|
|
753
|
+
const store = createStore();
|
|
754
|
+
const backoff = createBackoff((windows) => {
|
|
755
|
+
store.set({ backoff: windows });
|
|
756
|
+
});
|
|
757
|
+
let auth = options.auth;
|
|
758
|
+
let nextId = 0;
|
|
759
|
+
const socket = createSocket(options, () => credentialsOf(auth));
|
|
760
|
+
const timeoutMs = () => defaultTimeout(timeoutOption, store.get().hello);
|
|
761
|
+
const subscriptionLane = createSubscriptionLane({
|
|
762
|
+
socket,
|
|
763
|
+
timeoutMs,
|
|
764
|
+
hello: () => store.get().hello,
|
|
765
|
+
backoffRemaining: backoff.remaining
|
|
766
|
+
});
|
|
767
|
+
const topics = createTopics({
|
|
768
|
+
socket,
|
|
769
|
+
lane: subscriptionLane,
|
|
770
|
+
backoffRemaining: backoff.remaining,
|
|
771
|
+
reportRateLimited: backoff.report
|
|
772
|
+
});
|
|
773
|
+
const retry = createRetry(socket, () => store.get().status === "connecting");
|
|
774
|
+
const session = { established: false, topics, lane: subscriptionLane, retry };
|
|
775
|
+
const lifecycle = createLifecycle(socket, store, backoff, session);
|
|
776
|
+
const reconnected = /* @__PURE__ */ new Set();
|
|
777
|
+
const wiring = {
|
|
778
|
+
socket,
|
|
779
|
+
store,
|
|
780
|
+
lifecycle,
|
|
781
|
+
session,
|
|
782
|
+
reconnected,
|
|
783
|
+
hellos: /* @__PURE__ */ new Set(),
|
|
784
|
+
onProtocolMismatch: options.onProtocolMismatch ?? reloadOncePerSession
|
|
785
|
+
};
|
|
786
|
+
listenToSocket(wiring);
|
|
787
|
+
listenToPushes(wiring);
|
|
788
|
+
return Object.freeze({
|
|
789
|
+
url: options.url,
|
|
790
|
+
get timeoutMs() {
|
|
791
|
+
return timeoutMs();
|
|
792
|
+
},
|
|
793
|
+
socket,
|
|
794
|
+
subscriptionLane,
|
|
795
|
+
getState: store.get,
|
|
796
|
+
subscribe: store.subscribe,
|
|
797
|
+
open: lifecycle.open,
|
|
798
|
+
close: lifecycle.close,
|
|
799
|
+
retain: lifecycle.retain,
|
|
800
|
+
setAuth(next) {
|
|
801
|
+
if (authKey(next) === authKey(auth)) {
|
|
802
|
+
return false;
|
|
803
|
+
}
|
|
804
|
+
auth = next;
|
|
805
|
+
session.established = false;
|
|
806
|
+
retry.stop();
|
|
807
|
+
failBuffered(
|
|
808
|
+
socket,
|
|
809
|
+
new QuickdrawError("CANCELLED", "The credentials changed before the call was sent")
|
|
810
|
+
);
|
|
811
|
+
store.set({ hello: null, serviceAccess: null });
|
|
812
|
+
lifecycle.reconnect();
|
|
813
|
+
return true;
|
|
814
|
+
},
|
|
815
|
+
nextCallId() {
|
|
816
|
+
const id = nextId;
|
|
817
|
+
nextId = nextId >= Number.MAX_SAFE_INTEGER ? 0 : nextId + 1;
|
|
818
|
+
return id;
|
|
819
|
+
},
|
|
820
|
+
reportRateLimited: backoff.report,
|
|
821
|
+
backoffRemaining: backoff.remaining,
|
|
822
|
+
onReconnect: (listener) => addListener(reconnected, listener),
|
|
823
|
+
onHello: (listener) => listenForHellos(wiring, listener),
|
|
824
|
+
watch: topics.watch,
|
|
825
|
+
waitForJoin: topics.waitForJoin
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// src/client/host/hosts.ts
|
|
830
|
+
function createDirectory(connection) {
|
|
831
|
+
const rooms = /* @__PURE__ */ new Map();
|
|
832
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
833
|
+
const set = (room, host) => {
|
|
834
|
+
if ((rooms.get(room) ?? null) === host) {
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
if (host === null) {
|
|
838
|
+
rooms.delete(room);
|
|
839
|
+
} else {
|
|
840
|
+
rooms.set(room, host);
|
|
841
|
+
}
|
|
842
|
+
notifyEach(listeners, (listener) => {
|
|
843
|
+
listener(room, host);
|
|
844
|
+
});
|
|
845
|
+
};
|
|
846
|
+
const { socket } = connection;
|
|
847
|
+
socket.on(SERVER_EVENTS.host, (frame) => {
|
|
848
|
+
const [room, host] = Array.isArray(frame) ? frame : [];
|
|
849
|
+
if (isRoomName(room) && (host === null || isSocketId(host))) {
|
|
850
|
+
set(room, host);
|
|
851
|
+
}
|
|
852
|
+
});
|
|
853
|
+
socket.on(SERVER_EVENTS.presence, (frame) => {
|
|
854
|
+
if (isRecord(frame) && isRoomName(frame.room) && Array.isArray(frame.users) && frame.users.length === 0) {
|
|
855
|
+
set(frame.room, null);
|
|
856
|
+
}
|
|
857
|
+
});
|
|
858
|
+
socket.on("disconnect", () => {
|
|
859
|
+
for (const room of [...rooms.keys()]) {
|
|
860
|
+
set(room, null);
|
|
861
|
+
}
|
|
862
|
+
});
|
|
863
|
+
return Object.freeze({
|
|
864
|
+
of: (room) => rooms.get(room),
|
|
865
|
+
rooms,
|
|
866
|
+
listen(listener) {
|
|
867
|
+
listeners.add(listener);
|
|
868
|
+
return () => {
|
|
869
|
+
listeners.delete(listener);
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
var directories = /* @__PURE__ */ new WeakMap();
|
|
875
|
+
function hostsOf(connection) {
|
|
876
|
+
let directory = directories.get(connection);
|
|
877
|
+
if (directory === void 0) {
|
|
878
|
+
directory = createDirectory(connection);
|
|
879
|
+
directories.set(connection, directory);
|
|
880
|
+
}
|
|
881
|
+
return directory;
|
|
882
|
+
}
|
|
883
|
+
function hostOf(connection, room) {
|
|
884
|
+
return hostsOf(connection).of(room) ?? null;
|
|
885
|
+
}
|
|
886
|
+
function onHostChange(connection, listener) {
|
|
887
|
+
return hostsOf(connection).listen(listener);
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// src/client/host/links.ts
|
|
891
|
+
var HOST_LINK_RETRY_MS = 1e3;
|
|
892
|
+
var HOST_LINK_MAX_RETRY_MS = 15e3;
|
|
893
|
+
var MAX_SEED_ITEMS = 1e4;
|
|
894
|
+
var remote;
|
|
895
|
+
function loadRemote() {
|
|
896
|
+
remote ??= import("./remote-N7OUAGOW.js").catch((error) => {
|
|
897
|
+
remote = void 0;
|
|
898
|
+
throw error;
|
|
899
|
+
});
|
|
900
|
+
return remote;
|
|
901
|
+
}
|
|
902
|
+
function loadWhenAnnounced(connection) {
|
|
903
|
+
const announced = () => {
|
|
904
|
+
const limits = connection.getState().hello?.limits;
|
|
905
|
+
return isRecord(limits) && limits.peers !== void 0;
|
|
906
|
+
};
|
|
907
|
+
const load = () => {
|
|
908
|
+
loadRemote().catch(() => void 0);
|
|
909
|
+
};
|
|
910
|
+
if (announced()) {
|
|
911
|
+
load();
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
const stop = connection.subscribe(() => {
|
|
915
|
+
if (announced()) {
|
|
916
|
+
stop();
|
|
917
|
+
load();
|
|
918
|
+
}
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
var feedKey = (service, stream, scope) => `${service}\0${stream}\0${scope}`;
|
|
922
|
+
function onMessage(state, message) {
|
|
923
|
+
if (message[0] !== 6 && message[0] !== 7 && message[0] !== 8) {
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
const entry = state.feeds.get(feedKey(message[1], message[2], message[3]));
|
|
927
|
+
if (entry === void 0) {
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
if (message[0] === 6) {
|
|
931
|
+
const chunks = [...entry.chunks ?? [], ...message[4]].slice(-MAX_SEED_ITEMS);
|
|
932
|
+
entry.chunks = message[5] ? void 0 : chunks;
|
|
933
|
+
if (message[5]) {
|
|
934
|
+
entry.feed.seeded(chunks);
|
|
935
|
+
}
|
|
936
|
+
} else if (message[0] === 7) {
|
|
937
|
+
entry.feed.item(message[4]);
|
|
938
|
+
} else if (message[0] === 8) {
|
|
939
|
+
entry.feed.refused(message[4]);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
function subscribeOn(way, feed) {
|
|
943
|
+
way.send("rel", [4, feed.service, feed.stream, feed.scope]);
|
|
944
|
+
}
|
|
945
|
+
function opened(state, way) {
|
|
946
|
+
state.way = way;
|
|
947
|
+
state.retryMs = HOST_LINK_RETRY_MS;
|
|
948
|
+
state.backoff = void 0;
|
|
949
|
+
for (const entry of state.feeds.values()) {
|
|
950
|
+
entry.chunks = void 0;
|
|
951
|
+
subscribeOn(way, entry.feed);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
function disconnect(state) {
|
|
955
|
+
const { pending } = state;
|
|
956
|
+
state.pending = void 0;
|
|
957
|
+
state.way = void 0;
|
|
958
|
+
state.host = void 0;
|
|
959
|
+
clearTimeout(state.retry);
|
|
960
|
+
state.retry = void 0;
|
|
961
|
+
pending?.();
|
|
962
|
+
for (const entry of state.feeds.values()) {
|
|
963
|
+
entry.chunks = void 0;
|
|
964
|
+
entry.feed.lost();
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
function takeLink(links, state, host, link, control) {
|
|
968
|
+
if (control === void 0) {
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
const way = {
|
|
972
|
+
send: (label, message) => control.send(label, message),
|
|
973
|
+
close: () => {
|
|
974
|
+
link.close();
|
|
975
|
+
}
|
|
976
|
+
};
|
|
977
|
+
state.pending = way.close;
|
|
978
|
+
control.on((message) => {
|
|
979
|
+
onMessage(state, message);
|
|
980
|
+
});
|
|
981
|
+
const open = () => {
|
|
982
|
+
if (state.pending === way.close) {
|
|
983
|
+
opened(state, way);
|
|
984
|
+
}
|
|
985
|
+
};
|
|
986
|
+
link.on("open", open);
|
|
987
|
+
link.on("close", () => {
|
|
988
|
+
if (state.pending === way.close) {
|
|
989
|
+
disconnect(state);
|
|
990
|
+
retryLater(links, state, host);
|
|
991
|
+
}
|
|
992
|
+
});
|
|
993
|
+
if (link.state === "open") {
|
|
994
|
+
open();
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
function connectLocal(links, state, local, self) {
|
|
998
|
+
const { connection } = links;
|
|
999
|
+
const userId = connection.getState().hello?.userId ?? "";
|
|
1000
|
+
const [near, far] = local.createLink(
|
|
1001
|
+
state.room,
|
|
1002
|
+
self,
|
|
1003
|
+
userId,
|
|
1004
|
+
() => connection.getState().hello?.limits.peers?.maxMessageBytes ?? MAX_PEER_MESSAGE_BYTES
|
|
1005
|
+
);
|
|
1006
|
+
local.host(far);
|
|
1007
|
+
takeLink(links, state, self, near, local.controlOf(near));
|
|
1008
|
+
}
|
|
1009
|
+
function connectLink(links, state, host) {
|
|
1010
|
+
const loading = () => void 0;
|
|
1011
|
+
state.pending = loading;
|
|
1012
|
+
void loadRemote().then(
|
|
1013
|
+
({ createPeerLink, controlOf }) => {
|
|
1014
|
+
if (state.pending !== loading) {
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
state.pending = void 0;
|
|
1018
|
+
if (links.directory.of(state.room) !== host) {
|
|
1019
|
+
disconnect(state);
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
let link;
|
|
1023
|
+
try {
|
|
1024
|
+
link = createPeerLink(links.connection, { room: state.room, to: host, offerer: true });
|
|
1025
|
+
} catch {
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
takeLink(links, state, host, link, controlOf(link));
|
|
1029
|
+
},
|
|
1030
|
+
() => {
|
|
1031
|
+
if (state.pending === loading) {
|
|
1032
|
+
disconnect(state);
|
|
1033
|
+
retryLater(links, state, host);
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
);
|
|
1037
|
+
}
|
|
1038
|
+
function connect(links, state) {
|
|
1039
|
+
const host = links.directory.of(state.room);
|
|
1040
|
+
if (host === void 0 || state.host === host && state.pending !== void 0) {
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
1043
|
+
if (state.backoff?.host === host && Date.now() < state.backoff.until) {
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
if (state.pending !== void 0) {
|
|
1047
|
+
disconnect(state);
|
|
1048
|
+
}
|
|
1049
|
+
state.host = host;
|
|
1050
|
+
const self = links.connection.socket.id;
|
|
1051
|
+
const local = links.locals.get(state.room);
|
|
1052
|
+
if (host === self) {
|
|
1053
|
+
if (local !== void 0 && self !== void 0) {
|
|
1054
|
+
connectLocal(links, state, local, self);
|
|
1055
|
+
}
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
connectLink(links, state, host);
|
|
1059
|
+
}
|
|
1060
|
+
function retryLater(links, state, host) {
|
|
1061
|
+
const wait = state.retryMs;
|
|
1062
|
+
state.backoff = { host, until: Date.now() + wait };
|
|
1063
|
+
state.retryMs = Math.min(HOST_LINK_MAX_RETRY_MS, wait * 2);
|
|
1064
|
+
if (links.directory.of(state.room) === void 0) {
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1067
|
+
clearTimeout(state.retry);
|
|
1068
|
+
state.retry = setTimeout(() => {
|
|
1069
|
+
state.retry = void 0;
|
|
1070
|
+
state.backoff = void 0;
|
|
1071
|
+
if (state.feeds.size > 0) {
|
|
1072
|
+
connect(links, state);
|
|
1073
|
+
}
|
|
1074
|
+
}, wait);
|
|
1075
|
+
}
|
|
1076
|
+
function roomOf(links, room) {
|
|
1077
|
+
let state = links.rooms.get(room);
|
|
1078
|
+
if (state === void 0) {
|
|
1079
|
+
state = {
|
|
1080
|
+
room,
|
|
1081
|
+
host: void 0,
|
|
1082
|
+
way: void 0,
|
|
1083
|
+
pending: void 0,
|
|
1084
|
+
feeds: /* @__PURE__ */ new Map(),
|
|
1085
|
+
retry: void 0,
|
|
1086
|
+
retryMs: HOST_LINK_RETRY_MS,
|
|
1087
|
+
backoff: void 0
|
|
1088
|
+
};
|
|
1089
|
+
links.rooms.set(room, state);
|
|
1090
|
+
}
|
|
1091
|
+
return state;
|
|
1092
|
+
}
|
|
1093
|
+
function createLinks(connection) {
|
|
1094
|
+
const links = {
|
|
1095
|
+
connection,
|
|
1096
|
+
directory: hostsOf(connection),
|
|
1097
|
+
rooms: /* @__PURE__ */ new Map(),
|
|
1098
|
+
locals: /* @__PURE__ */ new Map()
|
|
1099
|
+
};
|
|
1100
|
+
loadWhenAnnounced(connection);
|
|
1101
|
+
links.directory.listen((room, host) => {
|
|
1102
|
+
const state = links.rooms.get(room);
|
|
1103
|
+
if (state === void 0) {
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
if (host === null) {
|
|
1107
|
+
disconnect(state);
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
state.backoff = void 0;
|
|
1111
|
+
state.retryMs = HOST_LINK_RETRY_MS;
|
|
1112
|
+
if (state.feeds.size > 0) {
|
|
1113
|
+
connect(links, state);
|
|
1114
|
+
}
|
|
1115
|
+
});
|
|
1116
|
+
return Object.freeze({
|
|
1117
|
+
...links,
|
|
1118
|
+
send(room, service, channel, payload) {
|
|
1119
|
+
const state = roomOf(links, room);
|
|
1120
|
+
connect(links, state);
|
|
1121
|
+
return state.way?.send("fast", [3, service, channel, payload]) ?? false;
|
|
1122
|
+
},
|
|
1123
|
+
subscribe(room, feed) {
|
|
1124
|
+
const state = roomOf(links, room);
|
|
1125
|
+
const key = feedKey(feed.service, feed.stream, feed.scope);
|
|
1126
|
+
const entry = { feed, chunks: void 0 };
|
|
1127
|
+
state.feeds.set(key, entry);
|
|
1128
|
+
const { way } = state;
|
|
1129
|
+
connect(links, state);
|
|
1130
|
+
if (way !== void 0 && state.way === way) {
|
|
1131
|
+
subscribeOn(way, feed);
|
|
1132
|
+
}
|
|
1133
|
+
return () => {
|
|
1134
|
+
if (state.feeds.get(key) === entry) {
|
|
1135
|
+
state.feeds.delete(key);
|
|
1136
|
+
state.way?.send("rel", [5, feed.service, feed.stream, feed.scope]);
|
|
1137
|
+
}
|
|
1138
|
+
};
|
|
1139
|
+
}
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
var all = /* @__PURE__ */ new WeakMap();
|
|
1143
|
+
function linksStateOf(connection) {
|
|
1144
|
+
let links = all.get(connection);
|
|
1145
|
+
if (links === void 0) {
|
|
1146
|
+
links = createLinks(connection);
|
|
1147
|
+
all.set(connection, links);
|
|
1148
|
+
}
|
|
1149
|
+
return links;
|
|
1150
|
+
}
|
|
1151
|
+
function hostLinksOf(connection) {
|
|
1152
|
+
return linksStateOf(connection);
|
|
1153
|
+
}
|
|
1154
|
+
function registerLocalHost(connection, room, host, local) {
|
|
1155
|
+
const links = linksStateOf(connection);
|
|
1156
|
+
const serving = { host, ...local };
|
|
1157
|
+
links.locals.set(room, serving);
|
|
1158
|
+
const state = links.rooms.get(room);
|
|
1159
|
+
if (state !== void 0 && state.feeds.size > 0) {
|
|
1160
|
+
connect(links, state);
|
|
1161
|
+
}
|
|
1162
|
+
return () => {
|
|
1163
|
+
if (links.locals.get(room) === serving) {
|
|
1164
|
+
links.locals.delete(room);
|
|
1165
|
+
const current = links.rooms.get(room);
|
|
1166
|
+
if (current !== void 0 && current.host === connection.socket.id) {
|
|
1167
|
+
disconnect(current);
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
export {
|
|
1174
|
+
DEFAULT_BACKOFF_MS,
|
|
1175
|
+
retryAfterOf,
|
|
1176
|
+
notifyEach,
|
|
1177
|
+
DEFAULT_SUBSCRIPTION_LANE,
|
|
1178
|
+
subscriptionLimits,
|
|
1179
|
+
reloadOncePerSession,
|
|
1180
|
+
INITIAL_CONNECTION_STATE,
|
|
1181
|
+
isServerDisconnect,
|
|
1182
|
+
mayConnect,
|
|
1183
|
+
isSessionRefusal,
|
|
1184
|
+
isTimeLimit,
|
|
1185
|
+
createQuickdrawConnection,
|
|
1186
|
+
hostsOf,
|
|
1187
|
+
hostOf,
|
|
1188
|
+
onHostChange,
|
|
1189
|
+
hostLinksOf,
|
|
1190
|
+
registerLocalHost
|
|
1191
|
+
};
|
|
1192
|
+
//# sourceMappingURL=chunk-STH5DLQP.js.map
|