statewire 0.13.1 → 0.15.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.
Files changed (95) hide show
  1. package/README.md +29 -78
  2. package/dist/client/connection.d.ts +20 -6
  3. package/dist/client/connection.d.ts.map +1 -1
  4. package/dist/client/connection.js +120 -52
  5. package/dist/client/connection.js.map +1 -1
  6. package/dist/client/core.d.ts +3 -2
  7. package/dist/client/core.d.ts.map +1 -1
  8. package/dist/client/core.js +10 -4
  9. package/dist/client/core.js.map +1 -1
  10. package/dist/client/session.d.ts +2 -0
  11. package/dist/client/session.d.ts.map +1 -1
  12. package/dist/client/session.js +97 -17
  13. package/dist/client/session.js.map +1 -1
  14. package/dist/host/adapter.d.ts +1 -1
  15. package/dist/host/adapter.d.ts.map +1 -1
  16. package/dist/host/adapter.js +7 -1
  17. package/dist/host/adapter.js.map +1 -1
  18. package/dist/host/host.d.ts +1 -0
  19. package/dist/host/host.d.ts.map +1 -1
  20. package/dist/host/host.js +45 -5
  21. package/dist/host/host.js.map +1 -1
  22. package/dist/host/http.d.ts +2 -1
  23. package/dist/host/http.d.ts.map +1 -1
  24. package/dist/host/http.js +108 -47
  25. package/dist/host/http.js.map +1 -1
  26. package/dist/host/socket.d.ts +33 -8
  27. package/dist/host/socket.d.ts.map +1 -1
  28. package/dist/host/socket.js +388 -58
  29. package/dist/host/socket.js.map +1 -1
  30. package/dist/host/work.d.ts +7 -0
  31. package/dist/host/work.d.ts.map +1 -0
  32. package/dist/host/work.js +53 -0
  33. package/dist/host/work.js.map +1 -0
  34. package/dist/host-internal.d.ts +42 -4
  35. package/dist/host-internal.d.ts.map +1 -1
  36. package/dist/host-internal.js +2 -1
  37. package/dist/host.d.ts +16 -3
  38. package/dist/host.d.ts.map +1 -1
  39. package/dist/host.js +2 -1
  40. package/dist/statewire.d.ts +21 -11
  41. package/dist/statewire.d.ts.map +1 -1
  42. package/dist/statewire.js +2 -2
  43. package/dist/statewire.js.map +1 -1
  44. package/dist/transports/sse.d.ts +1 -4
  45. package/dist/transports/sse.d.ts.map +1 -1
  46. package/dist/transports/sse.js +47 -56
  47. package/dist/transports/sse.js.map +1 -1
  48. package/dist/transports/testing.js +2 -2
  49. package/dist/transports/testing.js.map +1 -1
  50. package/dist/transports/ws.d.ts +1 -1
  51. package/dist/transports/ws.d.ts.map +1 -1
  52. package/dist/transports/ws.js +54 -41
  53. package/dist/transports/ws.js.map +1 -1
  54. package/dist/types.d.ts +3 -0
  55. package/dist/types.d.ts.map +1 -1
  56. package/dist/types.js.map +1 -1
  57. package/dist/wire/documents.d.ts +20 -0
  58. package/dist/wire/documents.d.ts.map +1 -0
  59. package/dist/wire/documents.js +74 -0
  60. package/dist/wire/documents.js.map +1 -0
  61. package/dist/wire/limits.d.ts +10 -0
  62. package/dist/wire/limits.d.ts.map +1 -0
  63. package/dist/wire/limits.js +11 -0
  64. package/dist/wire/limits.js.map +1 -0
  65. package/dist/wire/meta.d.ts.map +1 -1
  66. package/dist/wire/meta.js +1 -1
  67. package/dist/wire/meta.js.map +1 -1
  68. package/dist/wire/ops.d.ts.map +1 -1
  69. package/dist/wire/ops.js +1 -2
  70. package/dist/wire/ops.js.map +1 -1
  71. package/dist/wire/version.d.ts +7 -0
  72. package/dist/wire/version.d.ts.map +1 -0
  73. package/dist/wire/version.js +8 -0
  74. package/dist/wire/version.js.map +1 -0
  75. package/package.json +1 -1
  76. package/src/client/connection.ts +213 -67
  77. package/src/client/core.ts +10 -3
  78. package/src/client/session.ts +106 -30
  79. package/src/host/adapter.ts +10 -2
  80. package/src/host/host.ts +54 -4
  81. package/src/host/http.ts +168 -64
  82. package/src/host/socket.ts +603 -60
  83. package/src/host/work.ts +50 -0
  84. package/src/host-internal.ts +44 -3
  85. package/src/host.ts +18 -2
  86. package/src/statewire.ts +33 -10
  87. package/src/transports/sse.ts +60 -68
  88. package/src/transports/testing.ts +2 -2
  89. package/src/transports/ws.ts +29 -48
  90. package/src/types.ts +1 -0
  91. package/src/wire/documents.ts +109 -0
  92. package/src/wire/limits.ts +6 -0
  93. package/src/wire/meta.ts +6 -1
  94. package/src/wire/ops.ts +1 -2
  95. package/src/wire/version.ts +3 -0
@@ -1,7 +1,8 @@
1
1
  import { SocketHostState } from "./socket.js";
2
+ import { MAX_FRAME_BODY_BYTES } from "../wire/limits.js";
2
3
  //#region src/host/http.d.ts
3
4
  declare const stream: (h: SocketHostState, request: Request) => Promise<Response>;
4
5
  declare const frames: (h: SocketHostState, request: Request) => Promise<Response>;
5
6
  //#endregion
6
- export { frames, stream };
7
+ export { MAX_FRAME_BODY_BYTES, frames, stream };
7
8
  //# sourceMappingURL=http.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"http.d.ts","names":[],"sources":["../../src/host/http.ts"],"mappings":";;cAiNa,SACX,GAAG,iBACH,SAAS,YACR,QAAQ;cAkCE,SACX,GAAG,iBACH,SAAS,YACR,QAAQ"}
1
+ {"version":3,"file":"http.d.ts","names":[],"sources":["../../src/host/http.ts"],"mappings":";;;cAiTa,SACX,GAAG,iBACH,SAAS,YACR,QAAQ;cAkCE,SACX,GAAG,iBACH,SAAS,YACR,QAAQ"}
package/dist/host/http.js CHANGED
@@ -1,10 +1,39 @@
1
- import { CLIENT_ID_RE, admitFrame, closeConn, flush, parseFrame, parseFrameValue, register, sendTo } from "./socket.js";
1
+ import { MAX_FRAME_BODY_BYTES } from "../wire/limits.js";
2
+ import { VERSION_HEADER } from "../wire/version.js";
3
+ import { CLIENT_ID_RE, PRINCIPAL_MISMATCH_MESSAGE, SUBSCRIBER_LIMIT_MESSAGE, admitFrame, atSubscriberLimit, authEquals, closeConn, flush, parseFrame, parseFrameValue, register, sendTo } from "./socket.js";
2
4
  //#region src/host/http.ts
3
5
  const CLIENT_ID_HEADER = "Statewire-Client-Id";
4
6
  const LEASE_HEADER = "Statewire-Lease";
5
- const VERSION_HEADER = "Statewire-Version";
6
- const VERSION = "1";
7
7
  const encoder = new TextEncoder();
8
+ const laggardFin = encoder.encode("data: {\"fin\":{\"reason\":\"reconnect\",\"code\":\"client-fell-behind\"}}\n\n");
9
+ const readFrameBody = async (request) => {
10
+ const declared = request.headers.get("content-length");
11
+ if (declared !== null && Number(declared) > 8388608) {
12
+ await request.body?.cancel("frame body too large").catch(() => {});
13
+ return null;
14
+ }
15
+ if (request.body === null) return "";
16
+ const reader = request.body.getReader();
17
+ const chunks = [];
18
+ let size = 0;
19
+ for (;;) {
20
+ const { done, value } = await reader.read();
21
+ if (done) break;
22
+ size += value.length;
23
+ if (size > 8388608) {
24
+ await reader.cancel("frame body too large").catch(() => {});
25
+ return null;
26
+ }
27
+ chunks.push(value);
28
+ }
29
+ const body = new Uint8Array(size);
30
+ let offset = 0;
31
+ for (const chunk of chunks) {
32
+ body.set(chunk, offset);
33
+ offset += chunk.length;
34
+ }
35
+ return new TextDecoder().decode(body);
36
+ };
8
37
  const invalidClientIdReason = (clientId) => {
9
38
  if (clientId === "") return "id must be non-empty";
10
39
  if (clientId.length > 256) return "id must be at most 256 characters";
@@ -19,28 +48,29 @@ const clientIdResponse = (request) => {
19
48
  return { clientId };
20
49
  };
21
50
  const headersOf = (request) => Object.fromEntries(request.headers);
22
- const mergedHeaders = (request, extra) => {
23
- const headers = headersOf(request);
24
- for (const [name, value] of Object.entries(extra)) headers[name.toLowerCase()] = value;
25
- return headers;
26
- };
27
51
  const authorize = async (h, headers) => {
28
52
  try {
29
- await h.authorize?.({ headers });
30
- return null;
53
+ return { auth: await h.authorize?.({ headers }) };
31
54
  } catch (error) {
32
- if (error instanceof Response) return error;
55
+ if (error instanceof Response) return { refused: error };
33
56
  throw error;
34
57
  }
35
58
  };
59
+ const subscriberLimitResponse = () => Response.json({ detail: SUBSCRIBER_LIMIT_MESSAGE }, { status: 503 });
60
+ const principalMismatchResponse = (h, clientId, auth) => {
61
+ const rec = h.clients.get(clientId);
62
+ if (rec !== void 0 && !authEquals(rec.principal, auth)) return Response.json({ detail: PRINCIPAL_MISMATCH_MESSAGE }, { status: 403 });
63
+ return null;
64
+ };
36
65
  const mintLease = () => {
37
66
  const bytes = crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(16));
38
67
  return btoa(String.fromCharCode(...bytes)).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
39
68
  };
40
- const attachSse = (h, clientId) => {
69
+ const attachSse = (h, clientId, auth) => {
41
70
  let controller;
42
71
  let connection = null;
43
72
  let open = true;
73
+ let backpressureActive = false;
44
74
  const body = new ReadableStream({
45
75
  start: (next) => {
46
76
  controller = next;
@@ -49,10 +79,19 @@ const attachSse = (h, clientId) => {
49
79
  open = false;
50
80
  connection?.close();
51
81
  }
52
- });
82
+ }, { highWaterMark: h.streamQueueSize });
53
83
  const registration = register(h, clientId, {
54
84
  send: (data) => {
55
85
  if (!open) return;
86
+ if (backpressureActive && controller.desiredSize !== null && controller.desiredSize <= 0) {
87
+ open = false;
88
+ try {
89
+ controller.enqueue(laggardFin);
90
+ controller.close();
91
+ } catch {}
92
+ connection?.close();
93
+ return;
94
+ }
56
95
  const frame = data === "{}" ? ": ping\n\n" : `data: ${data}\n\n`;
57
96
  try {
58
97
  controller.enqueue(encoder.encode(frame));
@@ -70,33 +109,42 @@ const attachSse = (h, clientId) => {
70
109
  return;
71
110
  }
72
111
  }
73
- });
112
+ }, false, auth);
74
113
  connection = registration?.connection ?? null;
75
114
  if (!open) connection?.close();
76
115
  return {
77
116
  body,
78
- registration
117
+ registration,
118
+ activateBackpressure: () => {
119
+ backpressureActive = true;
120
+ }
79
121
  };
80
122
  };
81
- const streamResponse = (attachment, lease) => new Response(attachment.body, {
82
- status: 200,
83
- headers: {
84
- "Content-Type": "text/event-stream",
85
- [VERSION_HEADER]: VERSION,
86
- ...lease !== null && { [LEASE_HEADER]: lease }
87
- }
88
- });
123
+ const streamResponse = (attachment, lease) => {
124
+ attachment.activateBackpressure();
125
+ return new Response(attachment.body, {
126
+ status: 200,
127
+ headers: {
128
+ "Content-Type": "text/event-stream",
129
+ [VERSION_HEADER]: "1",
130
+ ...lease !== null && { [LEASE_HEADER]: lease }
131
+ }
132
+ });
133
+ };
89
134
  const admitAttachFrame = (h, registration, frame) => {
90
135
  if (frame.ctx.length === 0 && frame.cmd.length === 0) return;
91
- const admission = admitFrame(h, registration.rec, frame);
136
+ const admission = admitFrame(h, registration.rec, frame, registration.conn.auth);
92
137
  if (admission.type === "refused") sendTo(h, registration.conn, JSON.stringify(admission.syn));
93
138
  };
94
139
  const getStream = async (h, request) => {
95
- const refused = await authorize(h, headersOf(request));
96
- if (refused !== null) return refused;
140
+ const authorized = await authorize(h, headersOf(request));
141
+ if ("refused" in authorized) return authorized.refused;
97
142
  const client = clientIdResponse(request);
98
143
  if ("response" in client) return client.response;
99
- const attachment = attachSse(h, client.clientId);
144
+ const mismatch = principalMismatchResponse(h, client.clientId, authorized.auth);
145
+ if (mismatch !== null) return mismatch;
146
+ if (atSubscriberLimit(h)) return subscriberLimitResponse();
147
+ const attachment = attachSse(h, client.clientId, authorized.auth);
100
148
  const lease = attachment.registration === null ? null : mintLease();
101
149
  if (attachment.registration !== null) attachment.registration.conn.lease = lease;
102
150
  return streamResponse(attachment, lease);
@@ -104,23 +152,30 @@ const getStream = async (h, request) => {
104
152
  const postStream = async (h, request) => {
105
153
  const client = clientIdResponse(request);
106
154
  if ("response" in client) return client.response;
155
+ const raw = await readFrameBody(request);
156
+ if (raw === null) return Response.json({ detail: "frame body too large" }, { status: 413 });
107
157
  let body;
108
158
  try {
109
- body = JSON.parse(await request.text());
159
+ body = raw.trim() ? JSON.parse(raw) : null;
110
160
  } catch {
111
161
  return Response.json({ detail: "body must be JSON" }, { status: 400 });
112
162
  }
113
163
  let frame;
114
164
  try {
115
- frame = parseFrameValue(body, true);
165
+ frame = body === null && !raw.trim() ? {
166
+ ctx: [],
167
+ cmd: []
168
+ } : parseFrameValue(body);
116
169
  } catch (error) {
117
170
  if (!(error instanceof Error) || error.name !== "StatewireViolation") throw error;
118
171
  return Response.json({ detail: error.message }, { status: 400 });
119
172
  }
120
- if (frame.headers === null) return Response.json({ detail: "first frame must carry headers" }, { status: 400 });
121
- const refused = await authorize(h, mergedHeaders(request, frame.headers));
122
- if (refused !== null) return refused;
123
- const attachment = attachSse(h, client.clientId);
173
+ const authorized = await authorize(h, headersOf(request));
174
+ if ("refused" in authorized) return authorized.refused;
175
+ const mismatch = principalMismatchResponse(h, client.clientId, authorized.auth);
176
+ if (mismatch !== null) return mismatch;
177
+ if (atSubscriberLimit(h)) return subscriberLimitResponse();
178
+ const attachment = attachSse(h, client.clientId, authorized.auth);
124
179
  if (attachment.registration === null) return streamResponse(attachment, null);
125
180
  const lease = mintLease();
126
181
  attachment.registration.conn.lease = lease;
@@ -145,32 +200,38 @@ const finClient = (h, clientId, fin) => {
145
200
  };
146
201
  const violationResponse = (h, clientId, reason) => {
147
202
  finClient(h, clientId, {
148
- reason: "error",
149
- message: `protocol violation: ${reason}`
203
+ reason: "protocol-error",
204
+ message: reason
150
205
  });
151
206
  return Response.json({ error: reason }, { status: 400 });
152
207
  };
153
208
  const frames = async (h, request) => {
154
- const refused = await authorize(h, headersOf(request));
155
- if (refused !== null) return refused;
156
- const client = clientIdResponse(request);
157
- if ("response" in client) return client.response;
158
- const rec = h.clients.get(client.clientId);
159
209
  const lease = request.headers.get(LEASE_HEADER);
160
- const live = lease !== null && [...h.conns].some((conn) => conn.open && conn.clientId === client.clientId && conn.lease === lease);
161
- if (h.disposed || h.finished !== null || rec === void 0 || !live) return Response.json({ error: "stale-lease" }, { status: 423 });
210
+ const leased = lease === null ? void 0 : [...h.conns].find((conn) => conn.open && conn.lease === lease);
211
+ if (h.disposed || h.finished !== null || leased === void 0) return Response.json({ error: "stale-lease" }, { status: 423 });
212
+ const clientId = leased.clientId;
213
+ const rec = h.clients.get(clientId);
214
+ const raw = await readFrameBody(request);
215
+ if (raw === null) return Response.json({ error: "frame body too large" }, { status: 413 });
162
216
  let frame;
163
217
  try {
164
- frame = parseFrame(await request.text(), false);
218
+ frame = parseFrame(raw);
165
219
  } catch (error) {
166
220
  if (!(error instanceof Error) || error.name !== "StatewireViolation") throw error;
167
- return violationResponse(h, client.clientId, error.message);
221
+ return violationResponse(h, clientId, error.message);
222
+ }
223
+ if (!leased.open || h.disposed || h.finished !== null) return Response.json({ error: "stale-lease" }, { status: 423 });
224
+ const admission = admitFrame(h, rec, frame, leased.auth);
225
+ if (admission.type === "refused") {
226
+ const retry = admission.syn.syn.retry;
227
+ return Response.json(admission.syn, {
228
+ status: retry === void 0 ? 409 : 429,
229
+ ...retry !== void 0 && { headers: { "Retry-After": String(Math.ceil(retry / 1e3)) } }
230
+ });
168
231
  }
169
- const admission = admitFrame(h, rec, frame);
170
- if (admission.type === "refused") return Response.json(admission.syn, { status: 409 });
171
232
  return Response.json({});
172
233
  };
173
234
  //#endregion
174
- export { frames, stream };
235
+ export { MAX_FRAME_BODY_BYTES, frames, stream };
175
236
 
176
237
  //# sourceMappingURL=http.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"http.js","names":["StatewireHost","CLIENT_ID_RE","admitFrame","closeConn","flush","parseFrame","parseFrameValue","register","sendTo","ParsedFrame","Registration","SocketHostState","CLIENT_ID_HEADER","LEASE_HEADER","VERSION_HEADER","VERSION","encoder","TextEncoder","invalidClientIdReason","clientId","length","test","clientIdResponse","request","Request","response","Response","headers","get","json","detail","status","reason","headersOf","Record","Object","fromEntries","mergedHeaders","extra","name","value","entries","toLowerCase","authorize","h","Promise","error","mintLease","bytes","crypto","getRandomValues","Uint8Array","btoa","String","fromCharCode","replaceAll","replace","SseAttachment","body","ReadableStream","registration","attachSse","controller","ReadableStreamDefaultController","connection","open","start","next","cancel","close","socket","send","data","frame","enqueue","encode","streamResponse","attachment","lease","admitAttachFrame","ctx","cmd","admission","rec","type","conn","JSON","stringify","syn","getStream","refused","client","postStream","parse","text","Error","message","stream","method","Allow","finClient","fin","Fin","conns","violationResponse","frames","clients","live","some","disposed","finished","undefined"],"sources":["../../src/host/http.ts"],"sourcesContent":["import type { StatewireHost } from \"../host\";\nimport {\n CLIENT_ID_RE,\n admitFrame,\n closeConn,\n flush,\n parseFrame,\n parseFrameValue,\n register,\n sendTo,\n} from \"./socket\";\nimport type { ParsedFrame, Registration, SocketHostState } from \"./socket\";\n\nconst CLIENT_ID_HEADER = \"Statewire-Client-Id\";\nconst LEASE_HEADER = \"Statewire-Lease\";\nconst VERSION_HEADER = \"Statewire-Version\";\nconst VERSION = \"1\";\nconst encoder = new TextEncoder();\n\nconst invalidClientIdReason = (clientId: string): string | null => {\n if (clientId === \"\") return \"id must be non-empty\";\n if (clientId.length > 256) return \"id must be at most 256 characters\";\n if (!CLIENT_ID_RE.test(clientId)) {\n return \"id must contain only [A-Za-z0-9._~-]\";\n }\n return null;\n};\n\nconst clientIdResponse = (\n request: Request,\n): { clientId: string } | { response: Response } => {\n const clientId = request.headers.get(CLIENT_ID_HEADER);\n if (clientId === null) {\n return {\n response: Response.json(\n { detail: `missing ${CLIENT_ID_HEADER} header` },\n { status: 400 },\n ),\n };\n }\n const reason = invalidClientIdReason(clientId);\n if (reason !== null) {\n return {\n response: Response.json(\n { detail: `invalid ${CLIENT_ID_HEADER}: ${reason}` },\n { status: 400 },\n ),\n };\n }\n return { clientId };\n};\n\nconst headersOf = (request: Request): Record<string, string> =>\n Object.fromEntries(request.headers);\n\nconst mergedHeaders = (\n request: Request,\n extra: Record<string, string>,\n): Record<string, string> => {\n const headers = headersOf(request);\n for (const [name, value] of Object.entries(extra)) {\n headers[name.toLowerCase()] = value;\n }\n return headers;\n};\n\nconst authorize = async (\n h: SocketHostState,\n headers: Record<string, string>,\n): Promise<Response | null> => {\n try {\n await h.authorize?.({ headers });\n return null;\n } catch (error) {\n if (error instanceof Response) return error;\n throw error;\n }\n};\n\nconst mintLease = (): string => {\n const bytes = crypto.getRandomValues(new Uint8Array(16));\n return btoa(String.fromCharCode(...bytes))\n .replaceAll(\"+\", \"-\")\n .replaceAll(\"/\", \"_\")\n .replace(/=+$/, \"\");\n};\n\ntype SseAttachment = {\n body: ReadableStream<Uint8Array>;\n registration: Registration | null;\n};\n\nconst attachSse = (h: SocketHostState, clientId: string): SseAttachment => {\n let controller: ReadableStreamDefaultController<Uint8Array>;\n let connection: Registration[\"connection\"] | null = null;\n let open = true;\n const body = new ReadableStream({\n start: (next) => {\n controller = next;\n },\n cancel: () => {\n open = false;\n connection?.close();\n },\n });\n const socket = {\n send: (data: string) => {\n if (!open) return;\n const frame = data === \"{}\" ? \": ping\\n\\n\" : `data: ${data}\\n\\n`;\n try {\n controller.enqueue(encoder.encode(frame));\n } catch {\n open = false;\n connection?.close();\n }\n },\n close: () => {\n if (!open) return;\n open = false;\n try {\n controller.close();\n } catch {\n return;\n }\n },\n };\n const registration = register(h, clientId, socket);\n connection = registration?.connection ?? null;\n if (!open) connection?.close();\n return { body, registration };\n};\n\nconst streamResponse = (\n attachment: SseAttachment,\n lease: string | null,\n): Response =>\n new Response(attachment.body, {\n status: 200,\n headers: {\n \"Content-Type\": \"text/event-stream\",\n [VERSION_HEADER]: VERSION,\n ...(lease !== null && { [LEASE_HEADER]: lease }),\n },\n });\n\nconst admitAttachFrame = (\n h: SocketHostState,\n registration: Registration,\n frame: ParsedFrame,\n) => {\n if (frame.ctx.length === 0 && frame.cmd.length === 0) return;\n const admission = admitFrame(h, registration.rec, frame);\n if (admission.type === \"refused\") {\n sendTo(h, registration.conn, JSON.stringify(admission.syn));\n }\n};\n\nconst getStream = async (\n h: SocketHostState,\n request: Request,\n): Promise<Response> => {\n const refused = await authorize(h, headersOf(request));\n if (refused !== null) return refused;\n const client = clientIdResponse(request);\n if (\"response\" in client) return client.response;\n const attachment = attachSse(h, client.clientId);\n const lease = attachment.registration === null ? null : mintLease();\n if (attachment.registration !== null)\n attachment.registration.conn.lease = lease;\n return streamResponse(attachment, lease);\n};\n\nconst postStream = async (\n h: SocketHostState,\n request: Request,\n): Promise<Response> => {\n const client = clientIdResponse(request);\n if (\"response\" in client) return client.response;\n let body: unknown;\n try {\n body = JSON.parse(await request.text());\n } catch {\n return Response.json({ detail: \"body must be JSON\" }, { status: 400 });\n }\n let frame: ParsedFrame;\n try {\n frame = parseFrameValue(body, true);\n } catch (error) {\n if (!(error instanceof Error) || error.name !== \"StatewireViolation\") {\n throw error;\n }\n return Response.json({ detail: error.message }, { status: 400 });\n }\n if (frame.headers === null) {\n return Response.json(\n { detail: \"first frame must carry headers\" },\n { status: 400 },\n );\n }\n const refused = await authorize(h, mergedHeaders(request, frame.headers));\n if (refused !== null) return refused;\n const attachment = attachSse(h, client.clientId);\n if (attachment.registration === null) return streamResponse(attachment, null);\n const lease = mintLease();\n attachment.registration.conn.lease = lease;\n admitAttachFrame(h, attachment.registration, frame);\n return streamResponse(attachment, lease);\n};\n\nexport const stream = async (\n h: SocketHostState,\n request: Request,\n): Promise<Response> => {\n if (request.method === \"GET\") return getStream(h, request);\n if (request.method === \"POST\") return postStream(h, request);\n return new Response(null, {\n status: 405,\n headers: { Allow: \"GET, POST\" },\n });\n};\n\nconst finClient = (\n h: SocketHostState,\n clientId: string,\n fin: StatewireHost.Fin,\n) => {\n flush(h);\n for (const conn of [...h.conns]) {\n if (conn.clientId !== clientId) continue;\n sendTo(h, conn, JSON.stringify({ fin }));\n closeConn(h, conn);\n }\n};\n\nconst violationResponse = (\n h: SocketHostState,\n clientId: string,\n reason: string,\n) => {\n finClient(h, clientId, {\n reason: \"error\",\n message: `protocol violation: ${reason}`,\n });\n return Response.json({ error: reason }, { status: 400 });\n};\n\nexport const frames = async (\n h: SocketHostState,\n request: Request,\n): Promise<Response> => {\n const refused = await authorize(h, headersOf(request));\n if (refused !== null) return refused;\n const client = clientIdResponse(request);\n if (\"response\" in client) return client.response;\n const rec = h.clients.get(client.clientId);\n const lease = request.headers.get(LEASE_HEADER);\n // A lease lives and dies with its attach: any live attach of the id accepts.\n const live =\n lease !== null &&\n [...h.conns].some(\n (conn) =>\n conn.open && conn.clientId === client.clientId && conn.lease === lease,\n );\n if (h.disposed || h.finished !== null || rec === undefined || !live) {\n return Response.json({ error: \"stale-lease\" }, { status: 423 });\n }\n let frame: ParsedFrame;\n try {\n frame = parseFrame(await request.text(), false);\n } catch (error) {\n if (!(error instanceof Error) || error.name !== \"StatewireViolation\") {\n throw error;\n }\n return violationResponse(h, client.clientId, error.message);\n }\n const admission = admitFrame(h, rec, frame);\n if (admission.type === \"refused\") {\n return Response.json(admission.syn, { status: 409 });\n }\n return Response.json({});\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"http.js","names":["StatewireHost","CLIENT_ID_RE","PRINCIPAL_MISMATCH_MESSAGE","SUBSCRIBER_LIMIT_MESSAGE","admitFrame","atSubscriberLimit","authEquals","closeConn","flush","parseFrame","parseFrameValue","register","sendTo","ParsedFrame","Registration","SocketHostState","MAX_FRAME_BODY_BYTES","PROTOCOL_VERSION","VERSION_HEADER","CLIENT_ID_HEADER","LEASE_HEADER","encoder","TextEncoder","laggardFin","encode","readFrameBody","request","Request","Promise","declared","headers","get","Number","body","cancel","catch","reader","getReader","chunks","Uint8Array","size","done","value","read","length","push","offset","chunk","set","TextDecoder","decode","invalidClientIdReason","clientId","test","clientIdResponse","response","Response","json","detail","status","reason","headersOf","Record","Object","fromEntries","authorize","h","auth","refused","error","subscriberLimitResponse","principalMismatchResponse","rec","clients","undefined","principal","mintLease","bytes","crypto","getRandomValues","btoa","String","fromCharCode","replaceAll","replace","SseAttachment","ReadableStream","registration","activateBackpressure","attachSse","controller","ReadableStreamDefaultController","connection","open","backpressureActive","start","next","close","highWaterMark","streamQueueSize","socket","send","data","desiredSize","enqueue","frame","streamResponse","attachment","lease","admitAttachFrame","ctx","cmd","admission","conn","type","JSON","stringify","syn","getStream","authorized","client","mismatch","postStream","raw","trim","parse","Error","name","message","stream","method","Allow","finClient","fin","Fin","conns","violationResponse","frames","leased","find","disposed","finished","retry","Math","ceil"],"sources":["../../src/host/http.ts"],"sourcesContent":["import type { StatewireHost } from \"../host\";\nimport {\n CLIENT_ID_RE,\n PRINCIPAL_MISMATCH_MESSAGE,\n SUBSCRIBER_LIMIT_MESSAGE,\n admitFrame,\n atSubscriberLimit,\n authEquals,\n closeConn,\n flush,\n parseFrame,\n parseFrameValue,\n register,\n sendTo,\n} from \"./socket\";\nimport type { ParsedFrame, Registration, SocketHostState } from \"./socket\";\nimport { MAX_FRAME_BODY_BYTES } from \"../wire/limits\";\nimport { PROTOCOL_VERSION, VERSION_HEADER } from \"../wire/version\";\n\nexport { MAX_FRAME_BODY_BYTES } from \"../wire/limits\";\n\nconst CLIENT_ID_HEADER = \"Statewire-Client-Id\";\nconst LEASE_HEADER = \"Statewire-Lease\";\nconst encoder = new TextEncoder();\nconst laggardFin = encoder.encode(\n 'data: {\"fin\":{\"reason\":\"reconnect\",\"code\":\"client-fell-behind\"}}\\n\\n',\n);\nconst readFrameBody = async (request: Request): Promise<string | null> => {\n const declared = request.headers.get(\"content-length\");\n if (declared !== null && Number(declared) > MAX_FRAME_BODY_BYTES) {\n await request.body?.cancel(\"frame body too large\").catch(() => {});\n return null;\n }\n if (request.body === null) return \"\";\n const reader = request.body.getReader();\n const chunks: Uint8Array[] = [];\n let size = 0;\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n size += value.length;\n if (size > MAX_FRAME_BODY_BYTES) {\n await reader.cancel(\"frame body too large\").catch(() => {});\n return null;\n }\n chunks.push(value);\n }\n const body = new Uint8Array(size);\n let offset = 0;\n for (const chunk of chunks) {\n body.set(chunk, offset);\n offset += chunk.length;\n }\n return new TextDecoder().decode(body);\n};\n\nconst invalidClientIdReason = (clientId: string): string | null => {\n if (clientId === \"\") return \"id must be non-empty\";\n if (clientId.length > 256) return \"id must be at most 256 characters\";\n if (!CLIENT_ID_RE.test(clientId)) {\n return \"id must contain only [A-Za-z0-9._~-]\";\n }\n return null;\n};\n\nconst clientIdResponse = (\n request: Request,\n): { clientId: string } | { response: Response } => {\n const clientId = request.headers.get(CLIENT_ID_HEADER);\n if (clientId === null) {\n return {\n response: Response.json(\n { detail: `missing ${CLIENT_ID_HEADER} header` },\n { status: 400 },\n ),\n };\n }\n const reason = invalidClientIdReason(clientId);\n if (reason !== null) {\n return {\n response: Response.json(\n { detail: `invalid ${CLIENT_ID_HEADER}: ${reason}` },\n { status: 400 },\n ),\n };\n }\n return { clientId };\n};\n\nconst headersOf = (request: Request): Record<string, string> =>\n Object.fromEntries(request.headers);\n\nconst authorize = async (\n h: SocketHostState,\n headers: Record<string, string>,\n): Promise<{ auth: unknown } | { refused: Response }> => {\n try {\n return { auth: await h.authorize?.({ headers }) };\n } catch (error) {\n if (error instanceof Response) return { refused: error };\n throw error;\n }\n};\n\nconst subscriberLimitResponse = (): Response =>\n Response.json({ detail: SUBSCRIBER_LIMIT_MESSAGE }, { status: 503 });\n\n// No awaits between this check and register: the binding cannot race.\nconst principalMismatchResponse = (\n h: SocketHostState,\n clientId: string,\n auth: unknown,\n): Response | null => {\n const rec = h.clients.get(clientId);\n if (rec !== undefined && !authEquals(rec.principal, auth)) {\n return Response.json(\n { detail: PRINCIPAL_MISMATCH_MESSAGE },\n { status: 403 },\n );\n }\n return null;\n};\n\nconst mintLease = (): string => {\n const bytes = crypto.getRandomValues(new Uint8Array(16));\n return btoa(String.fromCharCode(...bytes))\n .replaceAll(\"+\", \"-\")\n .replaceAll(\"/\", \"_\")\n .replace(/=+$/, \"\");\n};\n\ntype SseAttachment = {\n body: ReadableStream<Uint8Array>;\n registration: Registration | null;\n activateBackpressure: () => void;\n};\n\nconst attachSse = (\n h: SocketHostState,\n clientId: string,\n auth: unknown,\n): SseAttachment => {\n let controller: ReadableStreamDefaultController<Uint8Array>;\n let connection: Registration[\"connection\"] | null = null;\n let open = true;\n let backpressureActive = false;\n const body = new ReadableStream(\n {\n start: (next) => {\n controller = next;\n },\n cancel: () => {\n open = false;\n connection?.close();\n },\n },\n { highWaterMark: h.streamQueueSize },\n );\n const socket = {\n send: (data: string) => {\n if (!open) return;\n if (\n backpressureActive &&\n controller.desiredSize !== null &&\n controller.desiredSize <= 0\n ) {\n open = false;\n try {\n controller.enqueue(laggardFin);\n controller.close();\n } catch {\n /* stream already gone */\n }\n connection?.close();\n return;\n }\n const frame = data === \"{}\" ? \": ping\\n\\n\" : `data: ${data}\\n\\n`;\n try {\n controller.enqueue(encoder.encode(frame));\n } catch {\n open = false;\n connection?.close();\n }\n },\n close: () => {\n if (!open) return;\n open = false;\n try {\n controller.close();\n } catch {\n return;\n }\n },\n };\n const registration = register(h, clientId, socket, false, auth);\n connection = registration?.connection ?? null;\n if (!open) connection?.close();\n return {\n body,\n registration,\n activateBackpressure: () => {\n backpressureActive = true;\n },\n };\n};\n\nconst streamResponse = (\n attachment: SseAttachment,\n lease: string | null,\n): Response => {\n attachment.activateBackpressure();\n return new Response(attachment.body, {\n status: 200,\n headers: {\n \"Content-Type\": \"text/event-stream\",\n [VERSION_HEADER]: PROTOCOL_VERSION,\n ...(lease !== null && { [LEASE_HEADER]: lease }),\n },\n });\n};\n\nconst admitAttachFrame = (\n h: SocketHostState,\n registration: Registration,\n frame: ParsedFrame,\n) => {\n if (frame.ctx.length === 0 && frame.cmd.length === 0) return;\n const admission = admitFrame(\n h,\n registration.rec,\n frame,\n registration.conn.auth,\n );\n if (admission.type === \"refused\") {\n sendTo(h, registration.conn, JSON.stringify(admission.syn));\n }\n};\n\nconst getStream = async (\n h: SocketHostState,\n request: Request,\n): Promise<Response> => {\n const authorized = await authorize(h, headersOf(request));\n if (\"refused\" in authorized) return authorized.refused;\n const client = clientIdResponse(request);\n if (\"response\" in client) return client.response;\n const mismatch = principalMismatchResponse(\n h,\n client.clientId,\n authorized.auth,\n );\n if (mismatch !== null) return mismatch;\n if (atSubscriberLimit(h)) return subscriberLimitResponse();\n const attachment = attachSse(h, client.clientId, authorized.auth);\n const lease = attachment.registration === null ? null : mintLease();\n if (attachment.registration !== null)\n attachment.registration.conn.lease = lease;\n return streamResponse(attachment, lease);\n};\n\nconst postStream = async (\n h: SocketHostState,\n request: Request,\n): Promise<Response> => {\n const client = clientIdResponse(request);\n if (\"response\" in client) return client.response;\n const raw = await readFrameBody(request);\n if (raw === null) {\n return Response.json({ detail: \"frame body too large\" }, { status: 413 });\n }\n let body: unknown;\n try {\n body = raw.trim() ? JSON.parse(raw) : null;\n } catch {\n return Response.json({ detail: \"body must be JSON\" }, { status: 400 });\n }\n let frame: ParsedFrame;\n try {\n frame =\n body === null && !raw.trim()\n ? { ctx: [], cmd: [] }\n : parseFrameValue(body);\n } catch (error) {\n if (!(error instanceof Error) || error.name !== \"StatewireViolation\") {\n throw error;\n }\n return Response.json({ detail: error.message }, { status: 400 });\n }\n const authorized = await authorize(h, headersOf(request));\n if (\"refused\" in authorized) return authorized.refused;\n const mismatch = principalMismatchResponse(\n h,\n client.clientId,\n authorized.auth,\n );\n if (mismatch !== null) return mismatch;\n if (atSubscriberLimit(h)) return subscriberLimitResponse();\n const attachment = attachSse(h, client.clientId, authorized.auth);\n if (attachment.registration === null) return streamResponse(attachment, null);\n const lease = mintLease();\n attachment.registration.conn.lease = lease;\n admitAttachFrame(h, attachment.registration, frame);\n return streamResponse(attachment, lease);\n};\n\nexport const stream = async (\n h: SocketHostState,\n request: Request,\n): Promise<Response> => {\n if (request.method === \"GET\") return getStream(h, request);\n if (request.method === \"POST\") return postStream(h, request);\n return new Response(null, {\n status: 405,\n headers: { Allow: \"GET, POST\" },\n });\n};\n\nconst finClient = (\n h: SocketHostState,\n clientId: string,\n fin: StatewireHost.Fin,\n) => {\n flush(h);\n for (const conn of [...h.conns]) {\n if (conn.clientId !== clientId) continue;\n sendTo(h, conn, JSON.stringify({ fin }));\n closeConn(h, conn);\n }\n};\n\nconst violationResponse = (\n h: SocketHostState,\n clientId: string,\n reason: string,\n) => {\n finClient(h, clientId, {\n reason: \"protocol-error\",\n message: reason,\n });\n return Response.json({ error: reason }, { status: 400 });\n};\n\nexport const frames = async (\n h: SocketHostState,\n request: Request,\n): Promise<Response> => {\n const lease = request.headers.get(LEASE_HEADER);\n // A lease lives and dies with its attach: any live attach of the id accepts.\n const leased =\n lease === null\n ? undefined\n : [...h.conns].find((conn) => conn.open && conn.lease === lease);\n if (h.disposed || h.finished !== null || leased === undefined) {\n return Response.json({ error: \"stale-lease\" }, { status: 423 });\n }\n const clientId = leased.clientId;\n const rec = h.clients.get(clientId)!;\n const raw = await readFrameBody(request);\n if (raw === null) {\n return Response.json({ error: \"frame body too large\" }, { status: 413 });\n }\n let frame: ParsedFrame;\n try {\n frame = parseFrame(raw);\n } catch (error) {\n if (!(error instanceof Error) || error.name !== \"StatewireViolation\") {\n throw error;\n }\n return violationResponse(h, clientId, error.message);\n }\n if (!leased.open || h.disposed || h.finished !== null) {\n return Response.json({ error: \"stale-lease\" }, { status: 423 });\n }\n const admission = admitFrame(h, rec, frame, leased.auth);\n if (admission.type === \"refused\") {\n const retry = admission.syn.syn.retry;\n return Response.json(admission.syn, {\n status: retry === undefined ? 409 : 429,\n ...(retry !== undefined && {\n headers: { \"Retry-After\": String(Math.ceil(retry / 1000)) },\n }),\n });\n }\n return Response.json({});\n};\n"],"mappings":"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"}
@@ -8,6 +8,11 @@ import { StatewireHostInternal } from "../host-internal.js";
8
8
  import { ResourceElement } from "@assistant-ui/tap";
9
9
  //#region src/host/socket.d.ts
10
10
  declare const CLIENT_ID_RE: RegExp;
11
+ declare const PRINCIPAL_MISMATCH_MESSAGE = "client id is bound to another principal";
12
+ /** Structural equality over auth values: primitives, Dates, arrays, and plain
13
+ * objects compare by value; anything else by identity. */
14
+ declare const authEquals: (a: unknown, b: unknown) => boolean;
15
+ declare const SUBSCRIBER_LIMIT_MESSAGE = "stream subscriber limit reached";
11
16
  type WireOp = {
12
17
  op: string;
13
18
  path?: StatewirePath;
@@ -32,6 +37,8 @@ type ClientRec = {
32
37
  cmd: StatewireCmd;
33
38
  }>;
34
39
  inflight: Map<number, Admission>;
40
+ /** The auth captured at the record's first attach; later attaches must match. */
41
+ principal: unknown;
35
42
  };
36
43
  type Conn = {
37
44
  clientId: string;
@@ -40,6 +47,16 @@ type Conn = {
40
47
  sawFrame: boolean;
41
48
  lastSentAt: number;
42
49
  lease: string | null;
50
+ /** The awaited `authorize` return captured at this attach's authorization. */
51
+ auth: unknown;
52
+ };
53
+ type PendingConn = {
54
+ socket: StatewireHostInternal.Socket;
55
+ open: boolean;
56
+ authorizing: boolean;
57
+ queued: string[];
58
+ timer: ReturnType<typeof setTimeout> | null;
59
+ connection: StatewireHostInternal.SocketConnection | null;
43
60
  };
44
61
  type CtxStatement = {
45
62
  seq: number;
@@ -52,19 +69,24 @@ type CmdStatement = {
52
69
  meta?: unknown;
53
70
  };
54
71
  type ParsedFrame = {
55
- headers: Record<string, string> | null;
56
72
  ctx: CtxStatement[];
57
73
  cmd: CmdStatement[];
58
74
  };
59
- declare const parseFrameValue: (frame: unknown, first: boolean) => ParsedFrame;
60
- declare const parseFrame: (raw: string, first: boolean) => ParsedFrame;
75
+ declare const parseFrameValue: (frame: unknown) => ParsedFrame;
76
+ declare const parseFrame: (raw: string) => ParsedFrame;
61
77
  type SocketHostState = {
62
78
  heartbeatMs: number;
79
+ wsInitTimeoutMs: number;
80
+ maxSubscribers: number;
81
+ maxInflightCommands: number;
82
+ streamQueueSize: number;
63
83
  clientRecordLimit: number;
64
84
  finOnIdle: boolean;
85
+ pushback: (() => number | null) | undefined;
65
86
  authorize: StatewireHostInternal.SocketHostOptions["authorize"];
66
87
  clients: Map<string, ClientRec>;
67
88
  conns: Set<Conn>;
89
+ pendingConns: Set<PendingConn>;
68
90
  pendingOps: WireOp[];
69
91
  pendingApplied: Map<string, number[]>;
70
92
  pendingCmds: Map<string, StatewireCmd[]>;
@@ -76,14 +98,16 @@ type SocketHostState = {
76
98
  stateHost: StatewireHost.StateHost | null;
77
99
  commandsHost: CommandsHost | null;
78
100
  current: Statewire<unknown> | null;
101
+ persistence: StatewireHostInternal.SocketHostPersistence | undefined;
79
102
  post: () => void;
80
- connect: (clientId: string, socket: StatewireHostInternal.Socket) => StatewireHostInternal.SocketConnection | null;
103
+ connect: (clientId: string, socket: StatewireHostInternal.Socket, options?: StatewireHostInternal.SocketConnectOptions) => StatewireHostInternal.SocketConnection | null;
81
104
  finish: (fin: StatewireHost.Fin) => void;
82
105
  stream: (request: Request) => Promise<Response>;
83
106
  frames: (request: Request) => Promise<Response>;
84
107
  };
85
108
  declare const closeConn: (h: SocketHostState, conn: Conn, code?: number, reason?: string) => void;
86
109
  declare const sendTo: (h: SocketHostState, conn: Conn, data: string) => void;
110
+ declare const atSubscriberLimit: (h: SocketHostState) => boolean;
87
111
  declare const flush: (h: SocketHostState, fin?: StatewireHost.Fin) => void;
88
112
  type FrameAdmission = {
89
113
  type: "ok";
@@ -93,16 +117,17 @@ type FrameAdmission = {
93
117
  syn: {
94
118
  seq: number;
95
119
  ctxSeq: number;
120
+ retry?: number;
96
121
  };
97
122
  };
98
123
  };
99
- declare const admitFrame: (h: SocketHostState, rec: ClientRec, frame: ParsedFrame) => FrameAdmission;
124
+ declare const admitFrame: (h: SocketHostState, rec: ClientRec, frame: ParsedFrame, auth: unknown) => FrameAdmission;
100
125
  type Registration = {
101
126
  rec: ClientRec;
102
127
  conn: Conn;
103
128
  connection: StatewireHostInternal.SocketConnection;
104
129
  };
105
- declare const register: (h: SocketHostState, clientId: string, socket: StatewireHostInternal.Socket) => Registration | null;
130
+ declare const register: (h: SocketHostState, clientId: string, socket: StatewireHostInternal.Socket, sawFrame?: boolean, auth?: unknown) => Registration | null;
106
131
  /**
107
132
  * Transport-agnostic WS binding over a statewire/host transport:
108
133
  * feed it sockets (anything with `send`/`close`) and raw text frames, and it
@@ -111,7 +136,7 @@ declare const register: (h: SocketHostState, clientId: string, socket: Statewire
111
136
  * arrays fanned out to every attach of a client id, idle edges, and `fin` on
112
137
  * finish.
113
138
  */
114
- declare const StatewireSocketHost: (transport: ResourceElement<Statewire>, options?: StatewireHostInternal.SocketHostOptions) => StatewireHostInternal.SocketHost;
139
+ declare const StatewireSocketHost: (transport: ResourceElement<Statewire>, options?: StatewireHostInternal.SocketHostOptions, persistence?: StatewireHostInternal.SocketHostPersistence) => StatewireHostInternal.SocketHost;
115
140
  //#endregion
116
- export { CLIENT_ID_RE, ClientRec, Conn, FrameAdmission, ParsedFrame, Registration, SocketHostState, StatewireSocketHost, admitFrame, closeConn, flush, parseFrame, parseFrameValue, register, sendTo };
141
+ export { CLIENT_ID_RE, ClientRec, Conn, FrameAdmission, PRINCIPAL_MISMATCH_MESSAGE, ParsedFrame, Registration, SUBSCRIBER_LIMIT_MESSAGE, SocketHostState, StatewireSocketHost, admitFrame, atSubscriberLimit, authEquals, closeConn, flush, parseFrame, parseFrameValue, register, sendTo };
117
142
  //# sourceMappingURL=socket.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"socket.d.ts","names":[],"sources":["../../src/host/socket.ts"],"mappings":";;;;;;;;;cAgBa,cAAY;KAKpB;EACH;EACA,OAAO;EACP;EACA,OAAO;;KAGJ;EACH;EACA;EACA;EACA;EACA;;KAGU;EACV;EACA;EACA;EACA;EACA,SAAS;EACT,SAAS;IAAc;IAAY,KAAK;;EACxC,UAAU,YAAY;;KAGZ;EACV;EACA,QAAQ,sBAAsB;EAC9B;EACA;EACA;EACA;;KAcG;EAAiB;EAAa,OAAO;;KACrC;EACH;EACA;EACA;EACA;;KAGU;EACV,SAAS;EACT,KAAK;EACL,KAAK;;cAgDM,kBACX,gBACA,mBACC;cAoDU,aAAc,aAAa,mBAAiB;KAmE7C;EACV;EACA;EACA;EACA,WAAW,sBAAsB;EACjC,SAAS,YAAY;EACrB,OAAO,IAAI;EACX,YAAY;EACZ,gBAAgB;EAChB,aAAa,YAAY;EACzB;EACA,UAAU,cAAc;EACxB;EACA;EACA,gBAAgB,kBAAkB;EAClC,WAAW,cAAc;EACzB,cAAc;EACd,SAAS;EACT;EACA,UACE,kBACA,QAAQ,sBAAsB,WAC3B,sBAAsB;EAC3B,SAAS,KAAK,cAAc;EAC5B,SAAS,SAAS,YAAY,QAAQ;EACtC,SAAS,SAAS,YAAY,QAAQ;;cA+C3B,YACX,GAAG,iBACH,MAAM,MACN,eACA;cAgBW,SAAU,GAAG,iBAAiB,MAAM,MAAM;cAsC1C,QAAS,GAAG,iBAAiB,MAAM,cAAc;KAyOlD;EACN;;EACA;EAAiB;IAAO;MAAO;MAAa;;;;cAErC,aACX,GAAG,iBACH,KAAK,WACL,OAAO,gBACN;KAgES;EACV,KAAK;EACL,MAAM;EACN,YAAY,sBAAsB;;cAGvB,WACX,GAAG,iBACH,kBACA,QAAQ,sBAAsB,WAC7B;;;;;;;;;cAoKU,sBACX,WAAW,gBAAgB,YAC3B,UAAU,sBAAsB,sBAC/B,sBAAsB"}
1
+ {"version":3,"file":"socket.d.ts","names":[],"sources":["../../src/host/socket.ts"],"mappings":";;;;;;;;;cAwBa,cAAY;cACZ;;;cAKA,aAAc,YAAY;cAoC1B;KAGR;EACH;EACA,OAAO;EACP;EACA,OAAO;;KAGJ;EACH;EACA;EACA;EACA;EACA;;KAGU;EACV;EACA;EACA;EACA;EACA,SAAS;EACT,SAAS;IAAc;IAAY,KAAK;;EACxC,UAAU,YAAY;;EAEtB;;KAGU;EACV;EACA,QAAQ,sBAAsB;EAC9B;EACA;EACA;EACA;;EAEA;;KAGG;EACH,QAAQ,sBAAsB;EAC9B;EACA;EACA;EACA,OAAO,kBAAkB;EACzB,YAAY,sBAAsB;;KAc/B;EAAiB;EAAa,OAAO;;KACrC;EACH;EACA;EACA;EACA;;KAGU;EACV,KAAK;EACL,KAAK;;cAkDM,kBAAmB,mBAAiB;cAmDpC,aAAc,gBAAc;KAiG7B;EACV;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,WAAW,sBAAsB;EACjC,SAAS,YAAY;EACrB,OAAO,IAAI;EACX,cAAc,IAAI;EAClB,YAAY;EACZ,gBAAgB;EAChB,aAAa,YAAY;EACzB;EACA,UAAU,cAAc;EACxB;EACA;EACA,gBAAgB,kBAAkB;EAClC,WAAW,cAAc;EACzB,cAAc;EACd,SAAS;EACT,aAAa,sBAAsB;EACnC;EACA,UACE,kBACA,QAAQ,sBAAsB,QAC9B,UAAU,sBAAsB,yBAC7B,sBAAsB;EAC3B,SAAS,KAAK,cAAc;EAC5B,SAAS,SAAS,YAAY,QAAQ;EACtC,SAAS,SAAS,YAAY,QAAQ;;cA+C3B,YACX,GAAG,iBACH,MAAM,MACN,eACA;cAgBW,SAAU,GAAG,iBAAiB,MAAM,MAAM;cAU1C,oBAAqB,GAAG;cAkCxB,QAAS,GAAG,iBAAiB,MAAM,cAAc;KAkQlD;EACN;;EAEA;EACA;IACE;MAAO;MAAa;MAAgB;;;;cAI/B,aACX,GAAG,iBACH,KAAK,WACL,OAAO,aACP,kBACC;KA8XS;EACV,KAAK;EACL,MAAM;EACN,YAAY,sBAAsB;;cAGvB,WACX,GAAG,iBACH,kBACA,QAAQ,sBAAsB,QAC9B,oBACA,mBACC;;;;;;;;;cAyPU,sBACX,WAAW,gBAAgB,YAC3B,UAAU,sBAAsB,mBAChC,cAAc,sBAAsB,0BACnC,sBAAsB"}