@ifc-lite/collab-server 0.2.5 → 0.3.0

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.
@@ -0,0 +1,387 @@
1
+ /* This Source Code Form is subject to the terms of the Mozilla Public
2
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
+ /**
5
+ * Room tokens — accountless, link-based access control (plan §3).
6
+ *
7
+ * Sharing is a signed capability, not an account: a short JWT (HS256, a
8
+ * server-held secret) carries the room id and the granted role
9
+ * (`viewer` / `commenter` / `editor` / `admin`). Anyone with the link
10
+ * presents the token as `?token=` on connect; the server verifies the
11
+ * signature + expiry and derives the `Principal.role` from it. The display
12
+ * identity (handle / color) travels separately over awareness — the token
13
+ * collects no PII.
14
+ *
15
+ * HS256 is implemented directly on `node:crypto` so the reference server
16
+ * needs no JWT dependency. `kid` supports key rotation ("revoke all links"
17
+ * rotates the active key); `jti` supports single-link revocation via an
18
+ * `isRevoked` deny-list.
19
+ */
20
+ import { createHmac, randomUUID, timingSafeEqual } from 'node:crypto';
21
+ const VALID_ROLES = new Set([
22
+ 'viewer',
23
+ 'commenter',
24
+ 'editor',
25
+ 'admin',
26
+ ]);
27
+ const DEFAULT_TTL_SECONDS = 7 * 24 * 60 * 60; // 7 days
28
+ const DEFAULT_CLOCK_TOLERANCE_SEC = 30;
29
+ function b64urlEncode(input) {
30
+ const buf = typeof input === 'string' ? Buffer.from(input, 'utf8') : input;
31
+ return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
32
+ }
33
+ function b64urlToBuffer(input) {
34
+ const b64 = input.replace(/-/g, '+').replace(/_/g, '/');
35
+ const pad = b64.length % 4 === 0 ? '' : '='.repeat(4 - (b64.length % 4));
36
+ return Buffer.from(b64 + pad, 'base64');
37
+ }
38
+ function hmac(signingInput, secret) {
39
+ return createHmac('sha256', secret).update(signingInput).digest();
40
+ }
41
+ function resolveSecret(secret, kid) {
42
+ return typeof secret === 'function' ? secret(kid) : secret;
43
+ }
44
+ /** Mint a signed room token. */
45
+ export function signRoomToken(opts) {
46
+ if (!VALID_ROLES.has(opts.role)) {
47
+ throw new Error(`signRoomToken: invalid role "${opts.role}"`);
48
+ }
49
+ if (!opts.roomId)
50
+ throw new Error('signRoomToken: roomId is required');
51
+ if (!opts.secret)
52
+ throw new Error('signRoomToken: secret is required');
53
+ const nowSec = Math.floor((opts.now ? opts.now() : Date.now()) / 1000);
54
+ const claims = {
55
+ room: opts.roomId,
56
+ role: opts.role,
57
+ iat: nowSec,
58
+ exp: nowSec + (opts.ttlSeconds ?? DEFAULT_TTL_SECONDS),
59
+ jti: opts.jti ?? randomUUID(),
60
+ ...(opts.kid ? { kid: opts.kid } : {}),
61
+ };
62
+ const header = { alg: 'HS256', typ: 'JWT', ...(opts.kid ? { kid: opts.kid } : {}) };
63
+ const signingInput = `${b64urlEncode(JSON.stringify(header))}.${b64urlEncode(JSON.stringify(claims))}`;
64
+ const sig = b64urlEncode(hmac(signingInput, opts.secret));
65
+ return `${signingInput}.${sig}`;
66
+ }
67
+ /**
68
+ * Verify a room token. Returns the decoded claims, or `null` for any failure
69
+ * (bad shape, wrong/unknown key, tampered signature, expired, room mismatch).
70
+ * Never throws on malformed input.
71
+ */
72
+ export function verifyRoomToken(token, opts) {
73
+ if (typeof token !== 'string' || token.length === 0)
74
+ return null;
75
+ const parts = token.split('.');
76
+ if (parts.length !== 3)
77
+ return null;
78
+ const [headerB64, payloadB64, sigB64] = parts;
79
+ let header;
80
+ let claims;
81
+ try {
82
+ header = JSON.parse(b64urlToBuffer(headerB64).toString('utf8'));
83
+ claims = JSON.parse(b64urlToBuffer(payloadB64).toString('utf8'));
84
+ }
85
+ catch {
86
+ return null;
87
+ }
88
+ if (header.alg !== 'HS256')
89
+ return null;
90
+ const kid = typeof header.kid === 'string' ? header.kid : undefined;
91
+ const secret = resolveSecret(opts.secret, kid);
92
+ if (!secret)
93
+ return null;
94
+ // Constant-time signature comparison.
95
+ const expected = hmac(`${headerB64}.${payloadB64}`, secret);
96
+ const provided = b64urlToBuffer(sigB64);
97
+ if (provided.length !== expected.length || !timingSafeEqual(provided, expected)) {
98
+ return null;
99
+ }
100
+ // Structural + role validation.
101
+ if (typeof claims.room !== 'string' ||
102
+ typeof claims.exp !== 'number' ||
103
+ typeof claims.iat !== 'number' ||
104
+ typeof claims.jti !== 'string' ||
105
+ !VALID_ROLES.has(claims.role)) {
106
+ return null;
107
+ }
108
+ const nowSec = Math.floor((opts.now ? opts.now() : Date.now()) / 1000);
109
+ const tolerance = opts.clockToleranceSec ?? DEFAULT_CLOCK_TOLERANCE_SEC;
110
+ if (claims.exp + tolerance < nowSec)
111
+ return null; // expired
112
+ if (claims.iat - tolerance > nowSec)
113
+ return null; // issued in the future
114
+ if (opts.room !== undefined && claims.room !== opts.room)
115
+ return null;
116
+ return claims;
117
+ }
118
+ /**
119
+ * Build an `AuthenticateFn` that authorizes a websocket connection from a
120
+ * room token. Pass to `startCollabServer({ authenticate })`.
121
+ */
122
+ export function createRoomTokenAuthenticator(opts) {
123
+ return async (token, roomId) => {
124
+ const claims = verifyRoomToken(token ?? '', {
125
+ secret: opts.secret,
126
+ room: roomId,
127
+ now: opts.now,
128
+ clockToleranceSec: opts.clockToleranceSec,
129
+ });
130
+ if (!claims)
131
+ return null;
132
+ if (opts.isRevoked && (await opts.isRevoked(claims.jti)))
133
+ return null;
134
+ if (opts.principalFor)
135
+ return opts.principalFor(claims);
136
+ return {
137
+ userId: `anon-${randomUUID()}`,
138
+ role: claims.role,
139
+ expiresAt: claims.exp * 1000,
140
+ meta: { jti: claims.jti, room: claims.room },
141
+ };
142
+ };
143
+ }
144
+ function readJsonBody(req, maxBytes) {
145
+ return new Promise((resolve, reject) => {
146
+ const chunks = [];
147
+ let size = 0;
148
+ req.on('data', (chunk) => {
149
+ size += chunk.length;
150
+ if (size > maxBytes) {
151
+ reject(new Error('body-too-large'));
152
+ req.destroy();
153
+ return;
154
+ }
155
+ chunks.push(chunk);
156
+ });
157
+ req.on('end', () => {
158
+ try {
159
+ resolve(JSON.parse(Buffer.concat(chunks).toString('utf8')));
160
+ }
161
+ catch {
162
+ reject(new Error('invalid-json'));
163
+ }
164
+ });
165
+ req.on('error', reject);
166
+ });
167
+ }
168
+ function bearerToken(req) {
169
+ const header = req.headers['authorization'];
170
+ if (typeof header !== 'string')
171
+ return undefined;
172
+ const m = header.match(/^Bearer\s+(.+)$/i);
173
+ return m ? m[1] : undefined;
174
+ }
175
+ /**
176
+ * Handle `POST /collab/token` (and its CORS preflight). Returns `true` when the
177
+ * request matched this route (and a response was sent), `false` otherwise so
178
+ * the caller can fall through to its own 404.
179
+ */
180
+ export async function handleTokenMintRequest(req, res, opts) {
181
+ const url = new URL(req.url ?? '/', 'http://localhost');
182
+ if (url.pathname !== '/collab/token')
183
+ return false;
184
+ const allowOrigin = opts.allowOrigin ?? '*';
185
+ const cors = {
186
+ 'access-control-allow-origin': allowOrigin,
187
+ 'access-control-allow-methods': 'POST, OPTIONS',
188
+ 'access-control-allow-headers': 'content-type, authorization',
189
+ };
190
+ if (req.method === 'OPTIONS') {
191
+ res.writeHead(204, cors);
192
+ res.end();
193
+ return true;
194
+ }
195
+ if (req.method !== 'POST') {
196
+ res.writeHead(405, { ...cors, 'content-type': 'application/json' });
197
+ res.end(JSON.stringify({ error: 'method-not-allowed' }));
198
+ return true;
199
+ }
200
+ let body;
201
+ try {
202
+ body = await readJsonBody(req, opts.maxBodyBytes ?? 4096);
203
+ }
204
+ catch (err) {
205
+ const reason = err instanceof Error ? err.message : 'bad-request';
206
+ res.writeHead(reason === 'body-too-large' ? 413 : 400, {
207
+ ...cors,
208
+ 'content-type': 'application/json',
209
+ });
210
+ res.end(JSON.stringify({ error: reason }));
211
+ return true;
212
+ }
213
+ const reqBody = body;
214
+ if (typeof reqBody?.roomId !== 'string' ||
215
+ !reqBody.roomId ||
216
+ typeof reqBody.role !== 'string' ||
217
+ !VALID_ROLES.has(reqBody.role)) {
218
+ res.writeHead(400, { ...cors, 'content-type': 'application/json' });
219
+ res.end(JSON.stringify({ error: 'invalid-request' }));
220
+ return true;
221
+ }
222
+ const mintReq = {
223
+ roomId: reqBody.roomId,
224
+ role: reqBody.role,
225
+ ttlSeconds: typeof reqBody.ttlSeconds === 'number' ? reqBody.ttlSeconds : undefined,
226
+ };
227
+ let bearerClaims = verifyRoomToken(bearerToken(req) ?? '', {
228
+ secret: opts.verifySecret ?? opts.secret,
229
+ now: opts.now,
230
+ });
231
+ // A revoked bearer (kicked admin / revoked link) is treated as no bearer at
232
+ // all, so the mint policy can't be exercised with a denied credential.
233
+ if (bearerClaims && opts.isRevoked && (await opts.isRevoked(bearerClaims.jti))) {
234
+ bearerClaims = null;
235
+ }
236
+ let grantedRole;
237
+ try {
238
+ grantedRole = await opts.authorize(mintReq, { bearerClaims });
239
+ }
240
+ catch (err) {
241
+ // eslint-disable-next-line no-console
242
+ console.error('[collab-server] token authorize threw:', err);
243
+ res.writeHead(500, { ...cors, 'content-type': 'application/json' });
244
+ res.end(JSON.stringify({ error: 'authorize-error' }));
245
+ return true;
246
+ }
247
+ if (!grantedRole) {
248
+ res.writeHead(403, { ...cors, 'content-type': 'application/json' });
249
+ res.end(JSON.stringify({ error: 'forbidden' }));
250
+ return true;
251
+ }
252
+ const maxTtl = opts.maxTtlSeconds ?? 30 * 24 * 60 * 60;
253
+ const ttlSeconds = Math.min(mintReq.ttlSeconds ?? opts.defaultTtlSeconds ?? DEFAULT_TTL_SECONDS, maxTtl);
254
+ const token = signRoomToken({
255
+ roomId: mintReq.roomId,
256
+ role: grantedRole,
257
+ secret: opts.secret,
258
+ ttlSeconds,
259
+ kid: opts.kid,
260
+ now: opts.now,
261
+ });
262
+ const expSec = Math.floor((opts.now ? opts.now() : Date.now()) / 1000) + ttlSeconds;
263
+ res.writeHead(200, { ...cors, 'content-type': 'application/json' });
264
+ res.end(JSON.stringify({ token, roomId: mintReq.roomId, role: grantedRole, exp: expSec }));
265
+ return true;
266
+ }
267
+ /**
268
+ * Handle `POST /collab/revoke` (and its CORS preflight). The caller must
269
+ * present an `admin` bearer token for the same room as the token being revoked.
270
+ * Returns `true` when this route matched (and a response was sent).
271
+ */
272
+ export async function handleRevokeRequest(req, res, opts) {
273
+ const url = new URL(req.url ?? '/', 'http://localhost');
274
+ if (url.pathname !== '/collab/revoke')
275
+ return false;
276
+ const allowOrigin = opts.allowOrigin ?? '*';
277
+ const cors = {
278
+ 'access-control-allow-origin': allowOrigin,
279
+ 'access-control-allow-methods': 'POST, OPTIONS',
280
+ 'access-control-allow-headers': 'content-type, authorization',
281
+ };
282
+ if (req.method === 'OPTIONS') {
283
+ res.writeHead(204, cors);
284
+ res.end();
285
+ return true;
286
+ }
287
+ if (req.method !== 'POST') {
288
+ res.writeHead(405, { ...cors, 'content-type': 'application/json' });
289
+ res.end(JSON.stringify({ error: 'method-not-allowed' }));
290
+ return true;
291
+ }
292
+ let body;
293
+ try {
294
+ body = await readJsonBody(req, opts.maxBodyBytes ?? 4096);
295
+ }
296
+ catch (err) {
297
+ const reason = err instanceof Error ? err.message : 'bad-request';
298
+ res.writeHead(reason === 'body-too-large' ? 413 : 400, { ...cors, 'content-type': 'application/json' });
299
+ res.end(JSON.stringify({ error: reason }));
300
+ return true;
301
+ }
302
+ const target = verifyRoomToken(body?.token ?? '', {
303
+ secret: opts.secret,
304
+ now: opts.now,
305
+ });
306
+ if (!target) {
307
+ res.writeHead(400, { ...cors, 'content-type': 'application/json' });
308
+ res.end(JSON.stringify({ error: 'invalid-token' }));
309
+ return true;
310
+ }
311
+ // Only a non-revoked admin token for the *same room* may revoke.
312
+ const bearer = verifyRoomToken(bearerToken(req) ?? '', {
313
+ secret: opts.secret,
314
+ room: target.room,
315
+ now: opts.now,
316
+ });
317
+ if (!bearer ||
318
+ bearer.role !== 'admin' ||
319
+ (opts.isRevoked && (await opts.isRevoked(bearer.jti)))) {
320
+ res.writeHead(403, { ...cors, 'content-type': 'application/json' });
321
+ res.end(JSON.stringify({ error: 'forbidden' }));
322
+ return true;
323
+ }
324
+ await opts.recordRevocation(target.jti, target.room);
325
+ res.writeHead(200, { ...cors, 'content-type': 'application/json' });
326
+ res.end(JSON.stringify({ revoked: true, jti: target.jti }));
327
+ return true;
328
+ }
329
+ /**
330
+ * Handle `POST /collab/kick` (and its CORS preflight). The caller must present
331
+ * an `admin` bearer token for the target room. Returns `true` when this route
332
+ * matched (and a response was sent).
333
+ */
334
+ export async function handleKickRequest(req, res, opts) {
335
+ const url = new URL(req.url ?? '/', 'http://localhost');
336
+ if (url.pathname !== '/collab/kick')
337
+ return false;
338
+ const allowOrigin = opts.allowOrigin ?? '*';
339
+ const cors = {
340
+ 'access-control-allow-origin': allowOrigin,
341
+ 'access-control-allow-methods': 'POST, OPTIONS',
342
+ 'access-control-allow-headers': 'content-type, authorization',
343
+ };
344
+ if (req.method === 'OPTIONS') {
345
+ res.writeHead(204, cors);
346
+ res.end();
347
+ return true;
348
+ }
349
+ if (req.method !== 'POST') {
350
+ res.writeHead(405, { ...cors, 'content-type': 'application/json' });
351
+ res.end(JSON.stringify({ error: 'method-not-allowed' }));
352
+ return true;
353
+ }
354
+ let body;
355
+ try {
356
+ body = await readJsonBody(req, opts.maxBodyBytes ?? 4096);
357
+ }
358
+ catch (err) {
359
+ const reason = err instanceof Error ? err.message : 'bad-request';
360
+ res.writeHead(reason === 'body-too-large' ? 413 : 400, { ...cors, 'content-type': 'application/json' });
361
+ res.end(JSON.stringify({ error: reason }));
362
+ return true;
363
+ }
364
+ const reqBody = body;
365
+ if (typeof reqBody?.roomId !== 'string' || !reqBody.roomId || typeof reqBody.clientId !== 'number') {
366
+ res.writeHead(400, { ...cors, 'content-type': 'application/json' });
367
+ res.end(JSON.stringify({ error: 'invalid-request' }));
368
+ return true;
369
+ }
370
+ const bearer = verifyRoomToken(bearerToken(req) ?? '', {
371
+ secret: opts.secret,
372
+ room: reqBody.roomId,
373
+ now: opts.now,
374
+ });
375
+ if (!bearer ||
376
+ bearer.role !== 'admin' ||
377
+ (opts.isRevoked && (await opts.isRevoked(bearer.jti)))) {
378
+ res.writeHead(403, { ...cors, 'content-type': 'application/json' });
379
+ res.end(JSON.stringify({ error: 'forbidden' }));
380
+ return true;
381
+ }
382
+ const kicked = await opts.kick(reqBody.roomId, reqBody.clientId);
383
+ res.writeHead(200, { ...cors, 'content-type': 'application/json' });
384
+ res.end(JSON.stringify({ kicked }));
385
+ return true;
386
+ }
387
+ //# sourceMappingURL=room-token.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"room-token.js","sourceRoot":"","sources":["../src/room-token.ts"],"names":[],"mappings":"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"}
package/dist/server.d.ts CHANGED
@@ -10,7 +10,26 @@ import { type AuditSink } from './audit-log.js';
10
10
  import { type RateLimitOptions } from './rate-limit.js';
11
11
  import { type VerifyMessageFn } from './room-manager.js';
12
12
  import { type BlobAuthorizeFn, type ServerBlobStorage } from './blob-route.js';
13
+ import { type TokenEndpointOptions, type RevokeEndpointOptions, type KickEndpointOptions } from './room-token.js';
14
+ import { type LayerRegistryStore } from './layer-registry.js';
13
15
  import { MetricsRegistry } from './metrics.js';
16
+ /**
17
+ * Cross-origin policy for the HTTP routes (`/blobs`, `/collab/*`, `/healthz`,
18
+ * `/metrics`). The viewer is typically served from a different origin than the
19
+ * collab-server, so a browser `fetch()`/`PUT` to the blob route needs
20
+ * `Access-Control-Allow-*` headers and an `OPTIONS` preflight response —
21
+ * without them the WebSocket doc syncs but geometry blobs are blocked, so
22
+ * recipients see an empty model.
23
+ */
24
+ export interface CorsOptions {
25
+ /**
26
+ * Allowed origin(s). `'*'` or omitted reflects whatever `Origin` the
27
+ * request carries (permissive — fine for dev and same-trust deployments).
28
+ * An explicit string or array restricts to those origins; a non-matching
29
+ * origin gets no CORS headers (and the browser blocks the request).
30
+ */
31
+ origin?: '*' | string | string[];
32
+ }
14
33
  export interface StartCollabServerOptions {
15
34
  port?: number;
16
35
  host?: string;
@@ -62,6 +81,47 @@ export interface StartCollabServerOptions {
62
81
  * `reject` with the reason and drops the message.
63
82
  */
64
83
  verifyMessage?: VerifyMessageFn;
84
+ /**
85
+ * Enable the `POST /collab/token` mint route for link-based sharing.
86
+ * Omit to leave the route disabled (404). Pair with
87
+ * `authenticate: createRoomTokenAuthenticator({ secret })` so connections
88
+ * are verified against the same secret.
89
+ */
90
+ tokenEndpoint?: TokenEndpointOptions;
91
+ /**
92
+ * Enable the `POST /collab/revoke` route so an admin can invalidate a share
93
+ * link (its `jti` is added to a deny-list the authenticator's `isRevoked`
94
+ * consults). Omit to leave the route disabled (404).
95
+ */
96
+ revokeEndpoint?: RevokeEndpointOptions;
97
+ /**
98
+ * Enable the `POST /collab/kick` route so an admin can force-disconnect a
99
+ * peer by awareness clientId. The server binds the kick to its room manager;
100
+ * only the verifying `secret` is supplied here. Omit to disable (404).
101
+ */
102
+ kickEndpoint?: Pick<KickEndpointOptions, 'secret' | 'isRevoked'>;
103
+ /**
104
+ * Cross-origin access for the HTTP routes. Default: enabled with origin
105
+ * reflection (permissive). Pass an allow-list to restrict, or `false` to
106
+ * disable entirely (e.g. when a reverse proxy owns CORS). Only applies to
107
+ * the server this function creates — ignored when you pass your own
108
+ * `server`.
109
+ */
110
+ cors?: CorsOptions | false;
111
+ /**
112
+ * Mount the layer-registry routes (`/api/v1/layers|refs|reviews`,
113
+ * 10-registry.md): push/pull content-addressed layers, refs with
114
+ * server-side merge-policy enforcement, and review objects. Off by
115
+ * default. Pass `true` for an in-memory registry, or supply a store.
116
+ * Authorization derives from `authenticate` exactly like the blob
117
+ * route: any authenticated principal may read, writes require write
118
+ * capability, and the principal's userId becomes the acting resolver
119
+ * for merges and waivers.
120
+ */
121
+ layerRegistry?: boolean | {
122
+ store?: LayerRegistryStore;
123
+ maxBytes?: number;
124
+ };
65
125
  }
66
126
  export interface CollabServerHandle {
67
127
  readonly url: string;
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAIA;;GAEG;AAEH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EAAE,eAAe,EAAkB,MAAM,IAAI,CAAC;AACrD,OAAO,EAAE,WAAW,EAAuB,MAAM,mBAAmB,CAAC;AACrE,OAAO,EAAsC,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACxF,OAAO,EAAkC,KAAK,cAAc,EAAE,KAAK,SAAS,EAAE,MAAM,WAAW,CAAC;AAChG,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAkB,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/D,MAAM,WAAW,wBAAwB;IACvC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,YAAY,CAAC,EAAE,cAAc,CAAC;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kEAAkE;IAClE,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC;IACrB,wDAAwD;IACxD,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,qEAAqE;IACrE,SAAS,CAAC,EAAE,gBAAgB,GAAG,CAAC,CAAC,SAAS,EAAE,SAAS,KAAK,gBAAgB,CAAC,CAAC;IAC5E;;;;OAIG;IACH,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChC,yDAAyD;IACzD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;IACvC;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oFAAoF;IACpF,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B;;;;OAIG;IACH,aAAa,CAAC,EAAE,eAAe,CAAC;CACjC;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC;IACjC,QAAQ,CAAC,GAAG,EAAE,eAAe,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAClC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAID,wBAAsB,iBAAiB,CACrC,IAAI,GAAE,wBAA6B,GAClC,OAAO,CAAC,kBAAkB,CAAC,CAmK7B;AAsID,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}