@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,825 @@
|
|
|
1
|
+
import {
|
|
2
|
+
onPeerRequest
|
|
3
|
+
} from "./chunk-DNP65TD5.js";
|
|
4
|
+
import {
|
|
5
|
+
controlOf,
|
|
6
|
+
createLinkCore,
|
|
7
|
+
encodeMessage,
|
|
8
|
+
messageBytes,
|
|
9
|
+
throwLater
|
|
10
|
+
} from "./chunk-ASAMUGHG.js";
|
|
11
|
+
import {
|
|
12
|
+
channelRoomOf,
|
|
13
|
+
streamRoomOf
|
|
14
|
+
} from "./chunk-NX2V6SIL.js";
|
|
15
|
+
import {
|
|
16
|
+
registerLocalHost
|
|
17
|
+
} from "./chunk-STH5DLQP.js";
|
|
18
|
+
import {
|
|
19
|
+
validateNow
|
|
20
|
+
} from "./chunk-7473HK24.js";
|
|
21
|
+
import {
|
|
22
|
+
isRoomName
|
|
23
|
+
} from "./chunk-2FDSKLBO.js";
|
|
24
|
+
import {
|
|
25
|
+
MAX_PEER_MESSAGE_BYTES
|
|
26
|
+
} from "./chunk-X4ZU4QH5.js";
|
|
27
|
+
import {
|
|
28
|
+
CHANNEL_DEFAULT_RATE,
|
|
29
|
+
isHostServed
|
|
30
|
+
} from "./chunk-GYLWEJHW.js";
|
|
31
|
+
import {
|
|
32
|
+
QuickdrawError,
|
|
33
|
+
isErrorCode
|
|
34
|
+
} from "./chunk-Q4OGQM5I.js";
|
|
35
|
+
import {
|
|
36
|
+
isRecord
|
|
37
|
+
} from "./chunk-QSU47XEX.js";
|
|
38
|
+
|
|
39
|
+
// src/client/auth.ts
|
|
40
|
+
var AUTH_TOKEN_KEY = "auth_token";
|
|
41
|
+
var DEFAULT_API_URL = "http://localhost:4000";
|
|
42
|
+
var memoryToken = null;
|
|
43
|
+
function onClient() {
|
|
44
|
+
return typeof window !== "undefined";
|
|
45
|
+
}
|
|
46
|
+
function storedToken() {
|
|
47
|
+
try {
|
|
48
|
+
return globalThis.localStorage.getItem(AUTH_TOKEN_KEY);
|
|
49
|
+
} catch {
|
|
50
|
+
return void 0;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function announce() {
|
|
54
|
+
if (typeof window.dispatchEvent === "function" && typeof Event === "function") {
|
|
55
|
+
window.dispatchEvent(new Event("auth-token-changed"));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function apiUrlOf(apiUrl) {
|
|
59
|
+
if (apiUrl !== void 0) {
|
|
60
|
+
return apiUrl;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
return process.env.NEXT_PUBLIC_API_URL ?? DEFAULT_API_URL;
|
|
64
|
+
} catch {
|
|
65
|
+
return DEFAULT_API_URL;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function getAuthToken() {
|
|
69
|
+
if (!onClient()) return null;
|
|
70
|
+
const stored = storedToken();
|
|
71
|
+
return stored === void 0 ? memoryToken : stored;
|
|
72
|
+
}
|
|
73
|
+
function setAuthToken(token) {
|
|
74
|
+
if (!onClient()) return;
|
|
75
|
+
memoryToken = token;
|
|
76
|
+
try {
|
|
77
|
+
globalThis.localStorage.setItem(AUTH_TOKEN_KEY, token);
|
|
78
|
+
} catch {
|
|
79
|
+
}
|
|
80
|
+
announce();
|
|
81
|
+
}
|
|
82
|
+
function clearAuthToken() {
|
|
83
|
+
if (!onClient()) return;
|
|
84
|
+
memoryToken = null;
|
|
85
|
+
try {
|
|
86
|
+
globalThis.localStorage.removeItem(AUTH_TOKEN_KEY);
|
|
87
|
+
} catch {
|
|
88
|
+
}
|
|
89
|
+
announce();
|
|
90
|
+
}
|
|
91
|
+
var PROVIDER_ID = /^[a-z0-9][a-z0-9-]*$/;
|
|
92
|
+
function routesUrl(owner, target) {
|
|
93
|
+
const base = apiUrlOf(target.apiUrl).replace(/\/+$/, "");
|
|
94
|
+
const path = target.basePath ?? "/auth";
|
|
95
|
+
if (typeof path !== "string" || !path.startsWith("/")) {
|
|
96
|
+
throw new TypeError(`${owner}: basePath must start with "/", as createAuthRoutes takes it`);
|
|
97
|
+
}
|
|
98
|
+
return `${base}${path.replace(/\/+$/, "")}`;
|
|
99
|
+
}
|
|
100
|
+
function pageOrigin() {
|
|
101
|
+
try {
|
|
102
|
+
const origin = globalThis.location?.origin;
|
|
103
|
+
return typeof origin === "string" && origin !== "null" ? origin : void 0;
|
|
104
|
+
} catch {
|
|
105
|
+
return void 0;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function signInUrl(provider, options = {}) {
|
|
109
|
+
if (typeof provider !== "string" || !PROVIDER_ID.test(provider)) {
|
|
110
|
+
throw new TypeError(
|
|
111
|
+
`signInUrl: a provider id is lowercase letters, digits and hyphens; got ${String(provider)}`
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
const url = new URL(`${routesUrl("signInUrl", options)}/${provider}/start`);
|
|
115
|
+
const returnTo = options.returnTo ?? pageOrigin();
|
|
116
|
+
if (returnTo !== void 0) {
|
|
117
|
+
url.searchParams.set("returnTo", returnTo);
|
|
118
|
+
}
|
|
119
|
+
return url.toString();
|
|
120
|
+
}
|
|
121
|
+
async function refusalOf(response) {
|
|
122
|
+
let body;
|
|
123
|
+
try {
|
|
124
|
+
body = await response.json();
|
|
125
|
+
} catch {
|
|
126
|
+
body = void 0;
|
|
127
|
+
}
|
|
128
|
+
const code = isRecord(body) && isErrorCode(body.error) ? body.error : void 0;
|
|
129
|
+
const message = isRecord(body) && typeof body.message === "string" ? body.message : `The auth route answered ${String(response.status)}`;
|
|
130
|
+
if (code !== void 0) {
|
|
131
|
+
return new QuickdrawError(code, message);
|
|
132
|
+
}
|
|
133
|
+
return new QuickdrawError(response.status === 429 ? "RATE_LIMITED" : "INTERNAL", message);
|
|
134
|
+
}
|
|
135
|
+
async function postRoute(owner, route, target) {
|
|
136
|
+
const url = `${routesUrl(owner, target)}${route}`;
|
|
137
|
+
const token = getAuthToken();
|
|
138
|
+
let response;
|
|
139
|
+
try {
|
|
140
|
+
response = await fetch(url, {
|
|
141
|
+
method: "POST",
|
|
142
|
+
credentials: "include",
|
|
143
|
+
headers: {
|
|
144
|
+
"Content-Type": "application/json",
|
|
145
|
+
...token === null ? {} : { Authorization: `Bearer ${token}` }
|
|
146
|
+
},
|
|
147
|
+
body: "{}"
|
|
148
|
+
});
|
|
149
|
+
} catch (error) {
|
|
150
|
+
throw new QuickdrawError("INTERNAL", `${owner}: ${url} could not be reached`, {
|
|
151
|
+
cause: error instanceof Error ? error.message : String(error)
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
if (!response.ok) {
|
|
155
|
+
throw await refusalOf(response);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
async function signOut(options = {}) {
|
|
159
|
+
try {
|
|
160
|
+
await postRoute("signOut", "/logout", options);
|
|
161
|
+
} finally {
|
|
162
|
+
clearAuthToken();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
var PROVIDER_KINDS = /* @__PURE__ */ new Set(["oauth", "mock", "guest"]);
|
|
166
|
+
function providerInfoOf(value) {
|
|
167
|
+
if (!isRecord(value) || typeof value.id !== "string" || !PROVIDER_ID.test(value.id)) {
|
|
168
|
+
return void 0;
|
|
169
|
+
}
|
|
170
|
+
const kind = PROVIDER_KINDS.has(value.kind) ? value.kind : void 0;
|
|
171
|
+
return kind === void 0 ? void 0 : { id: value.id, name: typeof value.name === "string" ? value.name : value.id, kind };
|
|
172
|
+
}
|
|
173
|
+
async function authProviders(options = {}) {
|
|
174
|
+
const url = `${routesUrl("authProviders", options)}/providers`;
|
|
175
|
+
let response;
|
|
176
|
+
try {
|
|
177
|
+
response = await fetch(url, { headers: { Accept: "application/json" } });
|
|
178
|
+
} catch (error) {
|
|
179
|
+
throw new QuickdrawError("INTERNAL", `authProviders: ${url} could not be reached`, {
|
|
180
|
+
cause: error instanceof Error ? error.message : String(error)
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
if (!response.ok) {
|
|
184
|
+
throw await refusalOf(response);
|
|
185
|
+
}
|
|
186
|
+
let body;
|
|
187
|
+
try {
|
|
188
|
+
body = await response.json();
|
|
189
|
+
} catch {
|
|
190
|
+
body = void 0;
|
|
191
|
+
}
|
|
192
|
+
if (!isRecord(body) || !Array.isArray(body.providers)) {
|
|
193
|
+
throw new QuickdrawError("INTERNAL", `authProviders: ${url} answered no list of providers`);
|
|
194
|
+
}
|
|
195
|
+
return body.providers.flatMap((entry) => {
|
|
196
|
+
const info = providerInfoOf(entry);
|
|
197
|
+
return info === void 0 ? [] : [info];
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
async function signOutEverywhere(options = {}) {
|
|
201
|
+
try {
|
|
202
|
+
await postRoute("signOutEverywhere", "/logout-all", options);
|
|
203
|
+
} finally {
|
|
204
|
+
clearAuthToken();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// src/client/newId.ts
|
|
209
|
+
function uuidByte(byte, index) {
|
|
210
|
+
if (index === 6) {
|
|
211
|
+
return 64 + byte % 16;
|
|
212
|
+
}
|
|
213
|
+
return index === 8 ? 128 + byte % 64 : byte;
|
|
214
|
+
}
|
|
215
|
+
function uuidOf(bytes) {
|
|
216
|
+
const hex = Array.from(
|
|
217
|
+
bytes,
|
|
218
|
+
(byte, index) => uuidByte(byte, index).toString(16).padStart(2, "0")
|
|
219
|
+
).join("");
|
|
220
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
221
|
+
}
|
|
222
|
+
function newId() {
|
|
223
|
+
const { crypto } = globalThis;
|
|
224
|
+
if (typeof crypto?.randomUUID === "function") {
|
|
225
|
+
return crypto.randomUUID();
|
|
226
|
+
}
|
|
227
|
+
if (typeof crypto?.getRandomValues !== "function") {
|
|
228
|
+
throw new TypeError("newId: this runtime has no crypto.getRandomValues");
|
|
229
|
+
}
|
|
230
|
+
return uuidOf(crypto.getRandomValues(new Uint8Array(16)));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// src/client/host/local.ts
|
|
234
|
+
function createLocalLink(room, socketId, userId, maxMessageBytes) {
|
|
235
|
+
const ends = [];
|
|
236
|
+
const endOf = (other) => createLinkCore({
|
|
237
|
+
room,
|
|
238
|
+
peer: socketId,
|
|
239
|
+
maxMessageBytes,
|
|
240
|
+
watchdog: false,
|
|
241
|
+
hooks: {
|
|
242
|
+
send: (label, text) => {
|
|
243
|
+
other()?.receive(text, label);
|
|
244
|
+
return true;
|
|
245
|
+
},
|
|
246
|
+
closed: (_reason, fromHere) => {
|
|
247
|
+
if (fromHere) {
|
|
248
|
+
other()?.end("bye");
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
const near = endOf(() => ends[1]);
|
|
254
|
+
const far = endOf(() => ends[0]);
|
|
255
|
+
ends.push(near, far);
|
|
256
|
+
for (const end of ends) {
|
|
257
|
+
end.identify(userId);
|
|
258
|
+
}
|
|
259
|
+
far.carry("direct");
|
|
260
|
+
near.carry("direct");
|
|
261
|
+
return [near.link, far.link];
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// src/client/host/serving.ts
|
|
265
|
+
var all = /* @__PURE__ */ new WeakMap();
|
|
266
|
+
function attachAll(state, at) {
|
|
267
|
+
for (const [service, runtime] of state.runtimes) {
|
|
268
|
+
if (!at.attached.has(service)) {
|
|
269
|
+
at.attached.set(service, runtime.attach(at.peer, at.send, at.close));
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
function deliver(at, message) {
|
|
274
|
+
const attached = at.attached.get(message[1]);
|
|
275
|
+
if (attached !== void 0) {
|
|
276
|
+
attached.receive(message);
|
|
277
|
+
} else if (message[0] === 4) {
|
|
278
|
+
at.attached.values().next().value?.unserved(message);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
function join(state, peer, send, close) {
|
|
282
|
+
const at = { peer, send, close, attached: /* @__PURE__ */ new Map() };
|
|
283
|
+
state.peers.add(at);
|
|
284
|
+
attachAll(state, at);
|
|
285
|
+
return {
|
|
286
|
+
receive: (message) => {
|
|
287
|
+
deliver(at, message);
|
|
288
|
+
},
|
|
289
|
+
detach: () => {
|
|
290
|
+
for (const attached of at.attached.values()) {
|
|
291
|
+
attached.detach();
|
|
292
|
+
}
|
|
293
|
+
at.attached.clear();
|
|
294
|
+
state.peers.delete(at);
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
function adopt(state, link, userId, close) {
|
|
299
|
+
const control = controlOf(link);
|
|
300
|
+
if (control === void 0 || link.state === "closed") {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
state.links.add(link);
|
|
304
|
+
const peer = join(state, { socketId: link.peer, userId, link }, control.send, close);
|
|
305
|
+
const off = control.on((message) => {
|
|
306
|
+
peer.receive(message);
|
|
307
|
+
});
|
|
308
|
+
link.on("close", () => {
|
|
309
|
+
off();
|
|
310
|
+
peer.detach();
|
|
311
|
+
state.links.delete(link);
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
function accept(state, request) {
|
|
315
|
+
let link;
|
|
316
|
+
try {
|
|
317
|
+
link = request.accept();
|
|
318
|
+
} catch {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
adopt(state, link, request.userId, () => {
|
|
322
|
+
link.close();
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
function open(connection, room, rooms) {
|
|
326
|
+
const state = { room, runtimes: /* @__PURE__ */ new Map(), peers: /* @__PURE__ */ new Set(), links: /* @__PURE__ */ new Set() };
|
|
327
|
+
const stopRequests = onPeerRequest(connection, (request) => {
|
|
328
|
+
if (request.room === room) {
|
|
329
|
+
accept(state, request);
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
const unregister = registerLocalHost(
|
|
333
|
+
connection,
|
|
334
|
+
room,
|
|
335
|
+
(link) => {
|
|
336
|
+
adopt(state, link, link.userId ?? "", () => void 0);
|
|
337
|
+
},
|
|
338
|
+
{ createLink: createLocalLink, controlOf }
|
|
339
|
+
);
|
|
340
|
+
return {
|
|
341
|
+
add(service, runtime) {
|
|
342
|
+
if (state.runtimes.has(service)) {
|
|
343
|
+
throw new TypeError(
|
|
344
|
+
`serve: this connection serves ${service} in room "${room}" already; close that Host first`
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
state.runtimes.set(service, runtime);
|
|
348
|
+
for (const at of state.peers) {
|
|
349
|
+
attachAll(state, at);
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
remove(service, runtime) {
|
|
353
|
+
if (state.runtimes.get(service) !== runtime) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
state.runtimes.delete(service);
|
|
357
|
+
for (const at of state.peers) {
|
|
358
|
+
at.attached.get(service)?.detach();
|
|
359
|
+
at.attached.delete(service);
|
|
360
|
+
}
|
|
361
|
+
runtime.close();
|
|
362
|
+
if (state.runtimes.size > 0) {
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
rooms.delete(room);
|
|
366
|
+
stopRequests();
|
|
367
|
+
unregister();
|
|
368
|
+
for (const link of [...state.links]) {
|
|
369
|
+
link.close();
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
function serving(connection, room) {
|
|
375
|
+
let rooms = all.get(connection);
|
|
376
|
+
if (rooms === void 0) {
|
|
377
|
+
rooms = /* @__PURE__ */ new Map();
|
|
378
|
+
all.set(connection, rooms);
|
|
379
|
+
}
|
|
380
|
+
let found = rooms.get(room);
|
|
381
|
+
if (found === void 0) {
|
|
382
|
+
found = open(connection, room, rooms);
|
|
383
|
+
rooms.set(room, found);
|
|
384
|
+
}
|
|
385
|
+
return found;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// src/client/host/outbox.ts
|
|
389
|
+
var RETRY_MS = 50;
|
|
390
|
+
var MAX_RETRY_MS = 1e3;
|
|
391
|
+
var MAX_QUEUED_BYTES = 8 * 1024 * 1024;
|
|
392
|
+
function createOutbox(send, close) {
|
|
393
|
+
const queue = [];
|
|
394
|
+
let queued = 0;
|
|
395
|
+
let timer;
|
|
396
|
+
let wait = RETRY_MS;
|
|
397
|
+
const clear = () => {
|
|
398
|
+
clearTimeout(timer);
|
|
399
|
+
timer = void 0;
|
|
400
|
+
queue.length = 0;
|
|
401
|
+
queued = 0;
|
|
402
|
+
};
|
|
403
|
+
const sent = (message) => {
|
|
404
|
+
try {
|
|
405
|
+
return send("rel", message);
|
|
406
|
+
} catch (error) {
|
|
407
|
+
throwLater(error);
|
|
408
|
+
return true;
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
const flush = () => {
|
|
412
|
+
timer = void 0;
|
|
413
|
+
for (let next = queue[0]; next !== void 0; next = queue[0]) {
|
|
414
|
+
if (!sent(next.message)) {
|
|
415
|
+
timer = setTimeout(flush, wait);
|
|
416
|
+
wait = Math.min(MAX_RETRY_MS, wait * 2);
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
queue.shift();
|
|
420
|
+
queued -= next.bytes;
|
|
421
|
+
}
|
|
422
|
+
wait = RETRY_MS;
|
|
423
|
+
};
|
|
424
|
+
return {
|
|
425
|
+
send(label, message) {
|
|
426
|
+
if (label === "fast") {
|
|
427
|
+
send("fast", message);
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
if (queue.length === 0 && send("rel", message)) {
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
const bytes = messageBytes(encodeMessage(message));
|
|
434
|
+
queue.push({ message, bytes });
|
|
435
|
+
queued += bytes;
|
|
436
|
+
if (queued > MAX_QUEUED_BYTES) {
|
|
437
|
+
clear();
|
|
438
|
+
close();
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
timer ??= setTimeout(flush, wait);
|
|
442
|
+
},
|
|
443
|
+
clear
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// src/client/host/runtime.ts
|
|
448
|
+
var ABUSE_WINDOW_MS = 1e4;
|
|
449
|
+
var ABUSE_MULTIPLIER = 100;
|
|
450
|
+
var MAX_HOST_FEEDS = 500;
|
|
451
|
+
var HOST_SUBSCRIBE_RATE = 50;
|
|
452
|
+
var MAX_KEPT_SCOPES = 1e4;
|
|
453
|
+
var feedKey = (stream, scope) => `${stream}\0${scope}`;
|
|
454
|
+
function own(record, name) {
|
|
455
|
+
return Object.hasOwn(record, name) ? record[name] : void 0;
|
|
456
|
+
}
|
|
457
|
+
function overRate(bucket, rate, burst, now) {
|
|
458
|
+
bucket.tokens = Math.min(burst, bucket.tokens + (now - bucket.refilledAt) / 1e3 * rate);
|
|
459
|
+
bucket.refilledAt = now;
|
|
460
|
+
if (bucket.tokens >= 1) {
|
|
461
|
+
bucket.tokens -= 1;
|
|
462
|
+
return void 0;
|
|
463
|
+
}
|
|
464
|
+
if (now - bucket.windowStart > ABUSE_WINDOW_MS) {
|
|
465
|
+
bucket.windowStart = now;
|
|
466
|
+
bucket.dropped = 0;
|
|
467
|
+
}
|
|
468
|
+
bucket.dropped += 1;
|
|
469
|
+
return bucket.dropped > rate * ABUSE_MULTIPLIER;
|
|
470
|
+
}
|
|
471
|
+
function onChannel(state, from, name, payload) {
|
|
472
|
+
const def = own(state.contract.channels, name);
|
|
473
|
+
const handler = own(state.implementation.channels ?? {}, name);
|
|
474
|
+
if (def === void 0 || !isHostServed(def) || handler === void 0) {
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
const rate = def.ratePerSecond ?? CHANNEL_DEFAULT_RATE;
|
|
478
|
+
const burst = def.burst ?? Math.max(1, rate * 2);
|
|
479
|
+
const now = Date.now();
|
|
480
|
+
let bucket = from.buckets.get(name);
|
|
481
|
+
if (bucket === void 0) {
|
|
482
|
+
bucket = { tokens: burst, refilledAt: now, windowStart: now, dropped: 0 };
|
|
483
|
+
from.buckets.set(name, bucket);
|
|
484
|
+
}
|
|
485
|
+
const dropped = overRate(bucket, rate, burst, now);
|
|
486
|
+
if (dropped !== void 0) {
|
|
487
|
+
if (dropped) {
|
|
488
|
+
from.close();
|
|
489
|
+
}
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
const parsed = validateNow(def.payload, payload);
|
|
493
|
+
if (parsed === "async") {
|
|
494
|
+
if (!state.warned.has(name)) {
|
|
495
|
+
state.warned.add(name);
|
|
496
|
+
throwLater(
|
|
497
|
+
new TypeError(
|
|
498
|
+
`Host of ${state.room}: channel ${state.contract.name}.${name} has a payload schema that validates asynchronously; its messages are dropped, since a host checks them synchronously`
|
|
499
|
+
)
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
if (parsed.issues !== void 0 || channelRoomOf(def, parsed.value, [state.room]) !== state.room) {
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
try {
|
|
508
|
+
handler(parsed.value, { peer: from.peer, room: state.room });
|
|
509
|
+
} catch (error) {
|
|
510
|
+
throwLater(error);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
function seedOf(state, name, def, scope) {
|
|
514
|
+
const seed = own(state.implementation.streams ?? {}, name)?.seed;
|
|
515
|
+
if (seed === void 0) {
|
|
516
|
+
return state.kept.get(name)?.get(scope) ?? [];
|
|
517
|
+
}
|
|
518
|
+
return seed(scope).map((item) => {
|
|
519
|
+
const checked = validateNow(def.item, item);
|
|
520
|
+
if (checked === "async" || checked.issues !== void 0) {
|
|
521
|
+
throw new TypeError(
|
|
522
|
+
`Host of ${state.room}: the seed of ${state.contract.name}.${name} holds an item its schema refuses`
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
return checked.value;
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
function aloneBytes(state, name, scope, item) {
|
|
529
|
+
return messageBytes(encodeMessage([6, state.contract.name, name, scope, [item], false]));
|
|
530
|
+
}
|
|
531
|
+
function chunksOf(state, name, scope, items) {
|
|
532
|
+
const max = state.maxBytes();
|
|
533
|
+
const overhead = messageBytes(encodeMessage([6, state.contract.name, name, scope, [], false]));
|
|
534
|
+
const chunks = [[]];
|
|
535
|
+
let size = overhead;
|
|
536
|
+
for (const item of items) {
|
|
537
|
+
const bytes = messageBytes(JSON.stringify(item) ?? "null");
|
|
538
|
+
if (overhead + bytes > max) {
|
|
539
|
+
return void 0;
|
|
540
|
+
}
|
|
541
|
+
const chunk = chunks.at(-1) ?? [];
|
|
542
|
+
if (chunk.length === 0) {
|
|
543
|
+
chunk.push(item);
|
|
544
|
+
size += bytes;
|
|
545
|
+
} else if (size + 1 + bytes > max) {
|
|
546
|
+
chunks.push([item]);
|
|
547
|
+
size = overhead + bytes;
|
|
548
|
+
} else {
|
|
549
|
+
chunk.push(item);
|
|
550
|
+
size += 1 + bytes;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
return chunks;
|
|
554
|
+
}
|
|
555
|
+
function refusal(from, service, name, scope) {
|
|
556
|
+
return (code, message) => {
|
|
557
|
+
from.out.send("rel", [8, service, name, scope, { code, message }]);
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
function admits(from, refuse) {
|
|
561
|
+
const flood = overRate(from.subscribes, HOST_SUBSCRIBE_RATE, MAX_HOST_FEEDS, Date.now());
|
|
562
|
+
if (flood === void 0) {
|
|
563
|
+
return true;
|
|
564
|
+
}
|
|
565
|
+
if (flood) {
|
|
566
|
+
from.close();
|
|
567
|
+
} else {
|
|
568
|
+
refuse("RATE_LIMITED", `A peer subscribes at most ${HOST_SUBSCRIBE_RATE} times a second`);
|
|
569
|
+
}
|
|
570
|
+
return false;
|
|
571
|
+
}
|
|
572
|
+
function onUnserved(state, from, message) {
|
|
573
|
+
const [, service, name, scope] = message;
|
|
574
|
+
const refuse = refusal(from, service, name, scope);
|
|
575
|
+
if (!state.closed && state.peers.has(from) && admits(from, refuse)) {
|
|
576
|
+
refuse("NOT_FOUND", `The host of ${state.room} serves no service "${service}"`);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
function onSubscribe(state, from, name, scope) {
|
|
580
|
+
const service = state.contract.name;
|
|
581
|
+
const refuse = refusal(from, service, name, scope);
|
|
582
|
+
if (!admits(from, refuse)) {
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
const def = own(state.contract.streams, name);
|
|
586
|
+
if (def === void 0 || !isHostServed(def)) {
|
|
587
|
+
refuse("NOT_FOUND", `${service} has no host-served stream "${name}"`);
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
if (streamRoomOf(def, scope) !== state.room) {
|
|
591
|
+
refuse("FORBIDDEN", `The feed of scope "${scope}" is served by the host of another room`);
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
const key = feedKey(name, scope);
|
|
595
|
+
if (!from.feeds.has(key) && from.feeds.size >= MAX_HOST_FEEDS) {
|
|
596
|
+
refuse("CONFLICT", `A peer may hold at most ${MAX_HOST_FEEDS} feeds of a host`);
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
let chunks;
|
|
600
|
+
try {
|
|
601
|
+
chunks = chunksOf(state, name, scope, seedOf(state, name, def, scope));
|
|
602
|
+
} catch (error) {
|
|
603
|
+
refuse("INTERNAL", "The host could not make the feed's seed");
|
|
604
|
+
throwLater(error);
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
if (chunks === void 0) {
|
|
608
|
+
refuse("INTERNAL", "The feed's seed holds an item over the message limit");
|
|
609
|
+
throwLater(
|
|
610
|
+
new RangeError(
|
|
611
|
+
`Host of ${state.room}: the seed of ${service}.${name} holds an item over ${state.maxBytes()} bytes with its message; keep each item smaller`
|
|
612
|
+
)
|
|
613
|
+
);
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
from.feeds.add(key);
|
|
617
|
+
const subscribers = state.feeds.get(key) ?? /* @__PURE__ */ new Set();
|
|
618
|
+
state.feeds.set(key, subscribers);
|
|
619
|
+
subscribers.add(from);
|
|
620
|
+
chunks.forEach((chunk, index) => {
|
|
621
|
+
from.out.send("rel", [6, service, name, scope, chunk, index === chunks.length - 1]);
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
function leave(state, from, key) {
|
|
625
|
+
from.feeds.delete(key);
|
|
626
|
+
const subscribers = state.feeds.get(key);
|
|
627
|
+
subscribers?.delete(from);
|
|
628
|
+
if (subscribers?.size === 0) {
|
|
629
|
+
state.feeds.delete(key);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
function receive(state, from, message) {
|
|
633
|
+
if (state.closed || !state.peers.has(from) || message[1] !== state.contract.name) {
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
if (message[0] === 3) {
|
|
637
|
+
onChannel(state, from, message[2], message[3]);
|
|
638
|
+
} else if (message[0] === 4) {
|
|
639
|
+
onSubscribe(state, from, message[2], message[3]);
|
|
640
|
+
} else if (message[0] === 5) {
|
|
641
|
+
leave(state, from, feedKey(message[2], message[3]));
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
function keep(state, name, keepCount, scope, item) {
|
|
645
|
+
const scopes = state.kept.get(name) ?? /* @__PURE__ */ new Map();
|
|
646
|
+
state.kept.set(name, scopes);
|
|
647
|
+
const items = scopes.get(scope) ?? [];
|
|
648
|
+
scopes.delete(scope);
|
|
649
|
+
scopes.set(scope, [...items, item].slice(-keepCount));
|
|
650
|
+
if (scopes.size > MAX_KEPT_SCOPES) {
|
|
651
|
+
const [oldest] = scopes.keys();
|
|
652
|
+
if (oldest !== void 0) {
|
|
653
|
+
scopes.delete(oldest);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
function push(state, name, scope, item) {
|
|
658
|
+
const service = state.contract.name;
|
|
659
|
+
const def = own(state.contract.streams, name);
|
|
660
|
+
if (def === void 0 || !isHostServed(def)) {
|
|
661
|
+
throw new TypeError(`host.stream: ${service} has no host-served stream "${name}"`);
|
|
662
|
+
}
|
|
663
|
+
if (typeof scope !== "string" || scope === "") {
|
|
664
|
+
throw new TypeError(`host.stream("${name}").push: pass (scope, item), the scope a string`);
|
|
665
|
+
}
|
|
666
|
+
const checked = validateNow(def.item, item);
|
|
667
|
+
if (checked === "async" || checked.issues !== void 0) {
|
|
668
|
+
throw new TypeError(
|
|
669
|
+
`host.stream("${name}").push: the item does not match the stream's schema${checked === "async" ? " (it validates asynchronously)" : ""}`
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
const message = [7, service, name, scope, checked.value];
|
|
673
|
+
const bytes = aloneBytes(state, name, scope, checked.value);
|
|
674
|
+
if (bytes > state.maxBytes()) {
|
|
675
|
+
throw new RangeError(
|
|
676
|
+
`host.stream("${name}").push: an item holds at most ${state.maxBytes()} bytes as JSON with its message; this one has ${bytes}`
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
const keepCount = own(state.implementation.streams ?? {}, name)?.seed === void 0 ? def.seed ?? 0 : 0;
|
|
680
|
+
if (keepCount > 0) {
|
|
681
|
+
keep(state, name, keepCount, scope, checked.value);
|
|
682
|
+
}
|
|
683
|
+
const label = def.volatile === true ? "fast" : "rel";
|
|
684
|
+
for (const to of state.feeds.get(feedKey(name, scope)) ?? []) {
|
|
685
|
+
to.out.send(label, message);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
function createHostRuntime(contract, implementation, room, maxBytes) {
|
|
689
|
+
const state = {
|
|
690
|
+
contract,
|
|
691
|
+
implementation,
|
|
692
|
+
room,
|
|
693
|
+
maxBytes,
|
|
694
|
+
peers: /* @__PURE__ */ new Set(),
|
|
695
|
+
feeds: /* @__PURE__ */ new Map(),
|
|
696
|
+
kept: /* @__PURE__ */ new Map(),
|
|
697
|
+
warned: /* @__PURE__ */ new Set(),
|
|
698
|
+
closed: false
|
|
699
|
+
};
|
|
700
|
+
return Object.freeze({
|
|
701
|
+
attach(peer, send, close) {
|
|
702
|
+
const now = Date.now();
|
|
703
|
+
const from = {
|
|
704
|
+
peer,
|
|
705
|
+
out: createOutbox(send, close),
|
|
706
|
+
close,
|
|
707
|
+
buckets: /* @__PURE__ */ new Map(),
|
|
708
|
+
subscribes: { tokens: MAX_HOST_FEEDS, refilledAt: now, windowStart: now, dropped: 0 },
|
|
709
|
+
feeds: /* @__PURE__ */ new Set()
|
|
710
|
+
};
|
|
711
|
+
state.peers.add(from);
|
|
712
|
+
return {
|
|
713
|
+
receive: (message) => {
|
|
714
|
+
receive(state, from, message);
|
|
715
|
+
},
|
|
716
|
+
unserved: (message) => {
|
|
717
|
+
onUnserved(state, from, message);
|
|
718
|
+
},
|
|
719
|
+
detach: () => {
|
|
720
|
+
for (const key of [...from.feeds]) {
|
|
721
|
+
leave(state, from, key);
|
|
722
|
+
}
|
|
723
|
+
from.out.clear();
|
|
724
|
+
state.peers.delete(from);
|
|
725
|
+
}
|
|
726
|
+
};
|
|
727
|
+
},
|
|
728
|
+
get peers() {
|
|
729
|
+
return [...state.peers].map(({ peer }) => peer);
|
|
730
|
+
},
|
|
731
|
+
push: (stream, scope, item) => {
|
|
732
|
+
push(state, stream, scope, item);
|
|
733
|
+
},
|
|
734
|
+
close() {
|
|
735
|
+
state.closed = true;
|
|
736
|
+
for (const from of state.peers) {
|
|
737
|
+
from.out.clear();
|
|
738
|
+
}
|
|
739
|
+
state.peers.clear();
|
|
740
|
+
state.feeds.clear();
|
|
741
|
+
}
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// src/client/host/host.ts
|
|
746
|
+
function checkImplementation(contract, implementation) {
|
|
747
|
+
const owner = `defineHost(${contract.name})`;
|
|
748
|
+
const channels = implementation.channels ?? {};
|
|
749
|
+
for (const [name, def] of Object.entries(contract.channels)) {
|
|
750
|
+
const handler = Object.hasOwn(channels, name) ? channels[name] : void 0;
|
|
751
|
+
if (isHostServed(def) && typeof handler !== "function") {
|
|
752
|
+
throw new TypeError(
|
|
753
|
+
`${owner}: channels.${name} must be a handler: the room's host serves it`
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
for (const name of Object.keys(channels)) {
|
|
758
|
+
const def = Object.hasOwn(contract.channels, name) ? contract.channels[name] : void 0;
|
|
759
|
+
if (def === void 0 || !isHostServed(def)) {
|
|
760
|
+
throw new TypeError(`${owner}: "${name}" is not a channel the room's host serves`);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
for (const [name, options] of Object.entries(implementation.streams ?? {})) {
|
|
764
|
+
const def = Object.hasOwn(contract.streams, name) ? contract.streams[name] : void 0;
|
|
765
|
+
if (def === void 0 || !isHostServed(def)) {
|
|
766
|
+
throw new TypeError(`${owner}: "${name}" is not a stream the room's host serves`);
|
|
767
|
+
}
|
|
768
|
+
if (options?.seed !== void 0 && typeof options.seed !== "function") {
|
|
769
|
+
throw new TypeError(`${owner}: streams.${name}.seed must be a function of the scope`);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
function serve(contract, implementation, connection, room) {
|
|
774
|
+
if (!isRoomName(room)) {
|
|
775
|
+
throw new TypeError("serve: room must name an app room, of 1 to 256 characters");
|
|
776
|
+
}
|
|
777
|
+
const service = contract.name;
|
|
778
|
+
const runtime = createHostRuntime(
|
|
779
|
+
contract,
|
|
780
|
+
implementation,
|
|
781
|
+
room,
|
|
782
|
+
() => connection.getState().hello?.limits.peers?.maxMessageBytes ?? MAX_PEER_MESSAGE_BYTES
|
|
783
|
+
);
|
|
784
|
+
const served = serving(connection, room);
|
|
785
|
+
served.add(service, runtime);
|
|
786
|
+
let closed = false;
|
|
787
|
+
return Object.freeze({
|
|
788
|
+
room,
|
|
789
|
+
get peers() {
|
|
790
|
+
return runtime.peers;
|
|
791
|
+
},
|
|
792
|
+
stream: (name) => Object.freeze({
|
|
793
|
+
push: (scope, item) => {
|
|
794
|
+
runtime.push(name, scope, item);
|
|
795
|
+
}
|
|
796
|
+
}),
|
|
797
|
+
close() {
|
|
798
|
+
if (!closed) {
|
|
799
|
+
closed = true;
|
|
800
|
+
served.remove(service, runtime);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
function defineHost(contract, implementation) {
|
|
806
|
+
const any = implementation;
|
|
807
|
+
checkImplementation(contract, any);
|
|
808
|
+
return Object.freeze({
|
|
809
|
+
contract,
|
|
810
|
+
serve: (connection, options) => serve(contract, any, connection, options.room)
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
export {
|
|
815
|
+
getAuthToken,
|
|
816
|
+
setAuthToken,
|
|
817
|
+
clearAuthToken,
|
|
818
|
+
signInUrl,
|
|
819
|
+
signOut,
|
|
820
|
+
authProviders,
|
|
821
|
+
signOutEverywhere,
|
|
822
|
+
newId,
|
|
823
|
+
defineHost
|
|
824
|
+
};
|
|
825
|
+
//# sourceMappingURL=chunk-Z7I6SONO.js.map
|