@marimo-team/frontend 0.25.3-dev81 → 0.25.3-dev85

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 (101) hide show
  1. package/dist/assets/{CellStatus-DXroYLOP.js → CellStatus-DYRvLOiP.js} +1 -1
  2. package/dist/assets/{ErrorBoundary-DaQ9THH_.js → ErrorBoundary-BKQo68Df.js} +2 -2
  3. package/dist/assets/{JsonOutput-VxzHyItt.js → JsonOutput-3NheuQEt.js} +1 -1
  4. package/dist/assets/{MarimoErrorOutput-D-hLaDA-.js → MarimoErrorOutput-XrTcOYTI.js} +1 -1
  5. package/dist/assets/{RenderHTML-DjOEZL25.js → RenderHTML-DaVbCAsb.js} +1 -1
  6. package/dist/assets/{RunButton-CuhNTo5_.js → RunButton-D3ScLvwb.js} +1 -1
  7. package/dist/assets/{add-cell-with-ai-BbVntTh7.js → add-cell-with-ai-BKk0pvon.js} +1 -1
  8. package/dist/assets/{add-connection-dialog-Qf63aKV6.js → add-connection-dialog-CNIwGJVL.js} +1 -1
  9. package/dist/assets/{agent-panel-DMOfF3qF.js → agent-panel-BCCe7-0m.js} +1 -1
  10. package/dist/assets/{ai-model-dropdown-B15l0R62.js → ai-model-dropdown-prnCk5hq.js} +1 -1
  11. package/dist/assets/{app-config-button-BIMB8qjD.js → app-config-button-O_yI5nzB.js} +1 -1
  12. package/dist/assets/{blob-De9GR2s6.js → blob-FjxwDE1i.js} +1 -1
  13. package/dist/assets/{cell-editor-DFNLDjfX.js → cell-editor-B6Wvf232.js} +32 -11
  14. package/dist/assets/{cell-link-DR3W2HC7.js → cell-link-D8EbRFJJ.js} +1 -1
  15. package/dist/assets/{cells-styV7IX8.js → cells-DfF1DLV_.js} +1 -1
  16. package/dist/assets/{chat-display-BzdYdN39.js → chat-display-BtQI9LeB.js} +1 -1
  17. package/dist/assets/{chat-panel-De3YzkSF.js → chat-panel-tuUN0wet.js} +1 -1
  18. package/dist/assets/{chat-ui-NTNapdtP.js → chat-ui-Bb-HxAv4.js} +1 -1
  19. package/dist/assets/{code-visibility-csc3tgA0.js → code-visibility-ny5n3-GH.js} +1 -1
  20. package/dist/assets/{column-preview-DkH-oD8f.js → column-preview-BNPxoRnw.js} +1 -1
  21. package/dist/assets/{command-palette-OOQHBTnr.js → command-palette-CFLLUKbE.js} +1 -1
  22. package/dist/assets/{common-D7VAS6rd.js → common-D2oGA09G.js} +1 -1
  23. package/dist/assets/{components-CNmvC781.js → components-1zsrzJ_K.js} +1 -1
  24. package/dist/assets/config-BWKfkUWA.js +1 -0
  25. package/dist/assets/{config-BbRbT9Ww.js → config-CP8Y8_R0.js} +1 -1
  26. package/dist/assets/{datasource-C3yfeDa2.js → datasource-rV_-mqbc.js} +1 -1
  27. package/dist/assets/{dependency-graph-panel-CjoA6L0w.js → dependency-graph-panel-By-cv0Nw.js} +1 -1
  28. package/dist/assets/{documentation-panel-DHWFOiG1.js → documentation-panel-DHMjvOPt.js} +1 -1
  29. package/dist/assets/{download-BrCpRZKN.js → download-CMTzX3rG.js} +1 -1
  30. package/dist/assets/{edit-page-DafDcIAI.js → edit-page-Bh44QoBu.js} +4 -4
  31. package/dist/assets/{error-entries-BkEAmmnD.js → error-entries-BGCjRkVy.js} +1 -1
  32. package/dist/assets/{error-panel-BhTfPA_i.js → error-panel-BDTFHLEt.js} +1 -1
  33. package/dist/assets/{feedback-button-D9InK87G.js → feedback-button-C-ScLxY2.js} +1 -1
  34. package/dist/assets/feedback-button-CLRAP9DE.js +1 -0
  35. package/dist/assets/{file-explorer-panel-BSd_ZbUC.js → file-explorer-panel-CAq2LZFc.js} +1 -1
  36. package/dist/assets/{file-name-input-RHURglL_.js → file-name-input-DIvrcC82.js} +1 -1
  37. package/dist/assets/{floating-outline-Cm49rYYe.js → floating-outline-DJnZ45VA.js} +1 -1
  38. package/dist/assets/{gallery-page-DdMdOtdv.js → gallery-page-CHJD1mdU.js} +1 -1
  39. package/dist/assets/{glide-data-editor-eiSlEUyR.js → glide-data-editor-BsE9Em-a.js} +1 -1
  40. package/dist/assets/{home-page-DwWvBB0R.js → home-page-CCbdig5p.js} +1 -1
  41. package/dist/assets/{hooks-C4GjmtQ-.js → hooks-BPw6SVXo.js} +1 -1
  42. package/dist/assets/{index-jR_KRDMC.js → index-D2O1B6ZY.js} +7 -7
  43. package/dist/assets/{layout-Bq7wRzdR.js → layout-NkCTvSyj.js} +3 -3
  44. package/dist/assets/{logs-panel-CAAk72Gb.js → logs-panel-B34vxRV7.js} +1 -1
  45. package/dist/assets/{markdown-renderer-CAyCgogC.js → markdown-renderer-CkN_xAjW.js} +1 -1
  46. package/dist/assets/{name-cell-input-DA8hU9XT.js → name-cell-input-eyYneuGK.js} +1 -1
  47. package/dist/assets/{outline-panel-Cc5oQ4LL.js → outline-panel-rpjpMDYX.js} +1 -1
  48. package/dist/assets/{packages-panel-DTfKez6I.js → packages-panel-gXdFgCro.js} +1 -1
  49. package/dist/assets/{panel-accordion-state-BZHex38W.js → panel-accordion-state-CImIsdc8.js} +1 -1
  50. package/dist/assets/panels-CiR0Zzhg.js +105 -0
  51. package/dist/assets/{process-output-CrDc_O1D.js → process-output-BClx23Jv.js} +1 -1
  52. package/dist/assets/{readonly-code-display-BR-_jUj8.js → readonly-code-display-JC_YsLL_.js} +1 -1
  53. package/dist/assets/{readonly-python-code-D7jcy9ds.js → readonly-python-code-DeBED4OB.js} +1 -1
  54. package/dist/assets/{reveal-component-BWu6kkxZ.js → reveal-component-D2xhcDzw.js} +1 -1
  55. package/dist/assets/{run-page-BJq9MULx.js → run-page-B9Jb5LMt.js} +1 -1
  56. package/dist/assets/{runtime-details-state-hb7A1jgR.js → runtime-details-state-7JHj3efu.js} +1 -1
  57. package/dist/assets/{scratchpad-panel-OHQ7Z0RY.js → scratchpad-panel-B_xrdEvJ.js} +1 -1
  58. package/dist/assets/{session-panel-DmDjaPsN.js → session-panel-DwuCGJU1.js} +1 -1
  59. package/dist/assets/{snippets-panel-o9KL5jMi.js → snippets-panel-BNjtDbnR.js} +1 -1
  60. package/dist/assets/{state-BkAqJoW7.js → state-DUP4ToPd.js} +1 -1
  61. package/dist/assets/{state-Os7LSUUZ.js → state-DhaPGGBO.js} +1 -1
  62. package/dist/assets/{terminal-BI85khrS.js → terminal-BwLC9HS5.js} +1 -1
  63. package/dist/assets/{tracing-AsNGuDsh.js → tracing-Ch4LR62-.js} +1 -1
  64. package/dist/assets/{tracing-panel-CBcmea6L.js → tracing-panel-B8fX4PhX.js} +2 -2
  65. package/dist/assets/{tree-actions-Cx5EDpA6.js → tree-actions-Zg3Ycse4.js} +1 -1
  66. package/dist/assets/{useAddCell-uJJYtgwa.js → useAddCell-BX9lq_0J.js} +1 -1
  67. package/dist/assets/{useBoolean-8wlrTeus.js → useBoolean-WABMLM83.js} +1 -1
  68. package/dist/assets/{useCellActionButton-CmQ-OcPa.js → useCellActionButton-D7Ruyes-.js} +1 -1
  69. package/dist/assets/{useDeleteCell-CXgOCDqY.js → useDeleteCell-AbkQI6-c.js} +1 -1
  70. package/dist/assets/{useDependencyPanelTab-Ch6gm7uM.js → useDependencyPanelTab-B0b2KkvS.js} +1 -1
  71. package/dist/assets/{useInstallPackage-CYQoMdMQ.js → useInstallPackage-CWcPxSLP.js} +1 -1
  72. package/dist/assets/{useNotebookActions-CqXa3U6u.js → useNotebookActions-DNuGk3m2.js} +1 -1
  73. package/dist/assets/{useRunCells-DAfDmOMC.js → useRunCells-CV81rge-.js} +1 -1
  74. package/dist/assets/{useSplitCell-NLULgcq7.js → useSplitCell-BTwCB3JS.js} +1 -1
  75. package/dist/assets/{vega-component-BKJU3uaB.js → vega-component-CY6HmM06.js} +1 -1
  76. package/dist/index.html +24 -24
  77. package/package.json +1 -1
  78. package/src/core/host/AGENTS.md +6 -5
  79. package/src/core/host/connection.ts +1 -40
  80. package/src/core/runtime/__tests__/createWsUrl.test.ts +77 -7
  81. package/src/core/runtime/__tests__/runtime.test.ts +13 -5
  82. package/src/core/runtime/runtime.ts +23 -27
  83. package/src/core/wasm/bridge.ts +0 -8
  84. package/src/core/websocket/__tests__/connection-state.test.ts +0 -12
  85. package/src/core/websocket/connection-state.ts +0 -10
  86. package/dist/assets/config-BQT0uYUo.js +0 -1
  87. package/dist/assets/feedback-button-BAcClv7c.js +0 -1
  88. package/dist/assets/panels-Cnu2Flfw.js +0 -105
  89. package/dist/assets/ws-BXWY541V.js +0 -22
  90. package/src/core/host/__tests__/events.test.ts +0 -49
  91. package/src/core/host/events.ts +0 -68
  92. package/src/core/websocket/__tests__/createConnectionTransport.test.ts +0 -49
  93. package/src/core/websocket/transports/__tests__/basic.test.ts +0 -275
  94. package/src/core/websocket/transports/__tests__/sse.test.ts +0 -352
  95. package/src/core/websocket/transports/__tests__/transport.test.ts +0 -308
  96. package/src/core/websocket/transports/__tests__/ws.test.ts +0 -125
  97. package/src/core/websocket/transports/basic.ts +0 -102
  98. package/src/core/websocket/transports/create.ts +0 -31
  99. package/src/core/websocket/transports/sse.ts +0 -320
  100. package/src/core/websocket/transports/transport.ts +0 -50
  101. package/src/core/websocket/transports/ws.ts +0 -98
@@ -1,320 +0,0 @@
1
- /* Copyright 2026 Marimo. All rights reserved. */
2
-
3
- import { Logger } from "@/utils/Logger";
4
- import {
5
- type ConnectionEvent,
6
- ConnectionSubscriptions,
7
- type ConnectionTransportCallback,
8
- type IConnectionTransport,
9
- } from "./transport";
10
- import { MAX_RETRIES, TRANSPORT_EXHAUSTED_REASON } from "./ws";
11
-
12
- // Mirrors WsTransport/partysocket retry behavior.
13
- const CONNECTION_TIMEOUT_MS = 10_000;
14
- const MIN_RETRY_DELAY_MS = 1000;
15
- const MAX_RETRY_DELAY_MS = 10_000;
16
- const RETRY_GROWTH_FACTOR = 1.3;
17
-
18
- // Stream ended without a server close event (transient disconnect); matches
19
- // the WebSocket abnormal-closure code so close handling stays uniform.
20
- const ABNORMAL_CLOSURE = 1006;
21
-
22
- interface ServerClose {
23
- code: number;
24
- reason: string;
25
- }
26
-
27
- /**
28
- * A kernel-connection transport over server-sent events, used when
29
- * `server.transport` is `"sse"` (experimental).
30
- *
31
- * The kernel connection only carries messages server to client (control
32
- * requests go over HTTP POST), so an SSE stream can fully replace the
33
- * WebSocket. The server mirrors WebSocket close frames with a `close` SSE
34
- * event carrying `{code, reason}`; a stream that ends without one is a
35
- * transient disconnect.
36
- *
37
- * Uses `fetch` + stream parsing rather than `EventSource`: we need to
38
- * surface server close reasons, send auth headers, and match partysocket's
39
- * retry semantics (per-`reconnect()` retry budget, exhaustion surfaced via
40
- * `TRANSPORT_EXHAUSTED_REASON`), none of which `EventSource` supports.
41
- *
42
- * Like `WsTransport`, starts closed; the first `reconnect()` connects.
43
- * `close()` stops retrying and does not dispatch a close event.
44
- */
45
- export class SseTransport implements IConnectionTransport {
46
- private subscriptions = new ConnectionSubscriptions();
47
- private urlProvider: () => string;
48
- private headersProvider: () => Record<string, string>;
49
- private abortController: AbortController | null = null;
50
- private retryTimer: ReturnType<typeof setTimeout> | null = null;
51
- private retryCount = 0;
52
- private userClosed = false;
53
- private state: WebSocket["readyState"] = WebSocket.CLOSED;
54
-
55
- public constructor(
56
- urlProvider: () => string,
57
- headersProvider: () => Record<string, string> = () => ({}),
58
- ) {
59
- this.urlProvider = urlProvider;
60
- this.headersProvider = headersProvider;
61
- }
62
-
63
- public get readyState(): WebSocket["readyState"] {
64
- return this.state;
65
- }
66
-
67
- public reconnect(_code?: number, _reason?: string): void {
68
- this.userClosed = false;
69
- this.retryCount = 0;
70
- this.clearRetryTimer();
71
- this.abortController?.abort();
72
- this.connect();
73
- }
74
-
75
- public close(): void {
76
- this.userClosed = true;
77
- this.clearRetryTimer();
78
- this.state = WebSocket.CLOSED;
79
- this.abortController?.abort();
80
- this.abortController = null;
81
- }
82
-
83
- public send(_data: string | ArrayBuffer | Blob | ArrayBufferView): void {
84
- // The kernel connection is receive-only; all client requests go over
85
- // HTTP POST endpoints.
86
- Logger.warn("SseTransport does not support send(); dropping message");
87
- }
88
-
89
- public addEventListener<T extends ConnectionEvent>(
90
- event: T,
91
- callback: ConnectionTransportCallback<T>,
92
- ): void {
93
- this.subscriptions.addSubscription(
94
- event,
95
- callback as ConnectionTransportCallback<ConnectionEvent>,
96
- );
97
- }
98
-
99
- public removeEventListener<T extends ConnectionEvent>(
100
- event: T,
101
- callback: ConnectionTransportCallback<T>,
102
- ): void {
103
- this.subscriptions.removeSubscription(
104
- event,
105
- callback as ConnectionTransportCallback<ConnectionEvent>,
106
- );
107
- }
108
-
109
- private connect(): void {
110
- this.state = WebSocket.CONNECTING;
111
- void this.run();
112
- }
113
-
114
- private async run(): Promise<void> {
115
- const controller = new AbortController();
116
- this.abortController = controller;
117
- // Long timeout — the server can become slow when many notebooks are open.
118
- const connectTimeout = setTimeout(
119
- () => controller.abort(),
120
- CONNECTION_TIMEOUT_MS,
121
- );
122
-
123
- let serverClose: ServerClose | null = null;
124
- try {
125
- const response = await fetch(this.urlProvider(), {
126
- headers: {
127
- ...this.headersProvider(),
128
- Accept: "text/event-stream",
129
- },
130
- signal: controller.signal,
131
- });
132
- clearTimeout(connectTimeout);
133
- if (!response.ok || response.body === null) {
134
- throw new Error(`Unexpected SSE response: ${response.status}`);
135
- }
136
-
137
- this.state = WebSocket.OPEN;
138
- this.subscriptions.notify("open", new Event("open"));
139
-
140
- serverClose = await this.readEvents(response.body);
141
- } catch (error) {
142
- clearTimeout(connectTimeout);
143
- if (this.isSuperseded(controller)) {
144
- return;
145
- }
146
- Logger.warn("SSE connection failed", error);
147
- this.subscriptions.notify("error", new Event("error"));
148
- }
149
-
150
- if (this.isSuperseded(controller)) {
151
- return;
152
- }
153
- controller.abort();
154
- this.state = WebSocket.CLOSED;
155
-
156
- // The retry budget counts consecutive attempts without a healthy
157
- // stream (readEvents resets it on the first message), so a server
158
- // that accepts and immediately closes still exhausts the budget.
159
- this.retryCount += 1;
160
- const exhausted = this.retryCount >= MAX_RETRIES;
161
- this.subscriptions.notify(
162
- "close",
163
- new CloseEvent("close", {
164
- code: serverClose?.code ?? ABNORMAL_CLOSURE,
165
- // On exhaustion the reason is rewritten so the consumer stops
166
- // retrying, matching WsTransport.
167
- reason: exhausted
168
- ? TRANSPORT_EXHAUSTED_REASON
169
- : (serverClose?.reason ?? ""),
170
- wasClean: serverClose !== null,
171
- }),
172
- );
173
- // The consumer's close handler runs synchronously inside notify and
174
- // may have called reconnect() (starting a fresh connection) or
175
- // close(); re-check before scheduling our own retry so we never
176
- // stack a second connection on top of theirs.
177
- if (exhausted || this.isSuperseded(controller)) {
178
- return;
179
- }
180
- this.scheduleRetry();
181
- }
182
-
183
- /** Whether this run was replaced by a newer reconnect() or close(). */
184
- private isSuperseded(controller: AbortController): boolean {
185
- return this.userClosed || this.abortController !== controller;
186
- }
187
-
188
- private scheduleRetry(): void {
189
- this.clearRetryTimer();
190
- this.state = WebSocket.CONNECTING;
191
- const delay = Math.min(
192
- MIN_RETRY_DELAY_MS * RETRY_GROWTH_FACTOR ** this.retryCount,
193
- MAX_RETRY_DELAY_MS,
194
- );
195
- this.retryTimer = setTimeout(() => {
196
- this.retryTimer = null;
197
- this.connect();
198
- }, delay);
199
- }
200
-
201
- private clearRetryTimer(): void {
202
- if (this.retryTimer !== null) {
203
- clearTimeout(this.retryTimer);
204
- this.retryTimer = null;
205
- }
206
- }
207
-
208
- /**
209
- * Read and dispatch SSE events until the stream ends.
210
- *
211
- * Returns the server close payload if a `close` event arrives, or null
212
- * if the stream ends without one.
213
- */
214
- private async readEvents(
215
- body: ReadableStream<Uint8Array>,
216
- ): Promise<ServerClose | null> {
217
- const reader = body.getReader();
218
- const decoder = new TextDecoder();
219
- let buffer = "";
220
- let eventName: string | null = null;
221
- let eventId: string | null = null;
222
- let dataLines: string[] = [];
223
-
224
- const dispatch = (): ServerClose | null => {
225
- if (dataLines.length === 0) {
226
- eventName = null;
227
- eventId = null;
228
- return null;
229
- }
230
- const data = dataLines.join("\n");
231
- const name = eventName;
232
- const id = eventId;
233
- eventName = null;
234
- eventId = null;
235
- dataLines = [];
236
- if (name === "close") {
237
- return parseServerClose(data);
238
- }
239
- // A message means the stream is healthy: reset the retry budget
240
- // (rather than on open, so a server that accepts and immediately
241
- // closes cannot retry forever).
242
- this.retryCount = 0;
243
- // Normalize SSE fields into the same envelope as host WebSockets.
244
- // Leave the payload text intact for the client's JSON decoder.
245
- const envelope =
246
- name === null
247
- ? data
248
- : `{"event":${JSON.stringify(name)},"data":${data}${id === null ? "" : `,"id":${JSON.stringify(id)}`}}`;
249
- this.subscriptions.notify(
250
- "message",
251
- new MessageEvent("message", { data: envelope }),
252
- );
253
- return null;
254
- };
255
-
256
- while (true) {
257
- const { done, value } = await reader.read();
258
- if (done) {
259
- return null;
260
- }
261
- buffer += decoder.decode(value, { stream: true });
262
-
263
- // Consume complete lines with a cursor; a single tail slice per
264
- // read keeps parsing linear even for many buffered lines.
265
- let cursor = 0;
266
- let newlineIndex = buffer.indexOf("\n", cursor);
267
- while (newlineIndex !== -1) {
268
- let line = buffer.slice(cursor, newlineIndex);
269
- cursor = newlineIndex + 1;
270
- newlineIndex = buffer.indexOf("\n", cursor);
271
- if (line.endsWith("\r")) {
272
- line = line.slice(0, -1);
273
- }
274
-
275
- if (line === "") {
276
- const serverClose = dispatch();
277
- if (serverClose !== null) {
278
- return serverClose;
279
- }
280
- continue;
281
- }
282
- if (line.startsWith(":")) {
283
- // Comment (keep-alive)
284
- continue;
285
- }
286
-
287
- const colonIndex = line.indexOf(":");
288
- const field = colonIndex === -1 ? line : line.slice(0, colonIndex);
289
- let fieldValue = colonIndex === -1 ? "" : line.slice(colonIndex + 1);
290
- if (fieldValue.startsWith(" ")) {
291
- fieldValue = fieldValue.slice(1);
292
- }
293
- if (field === "data") {
294
- dataLines.push(fieldValue);
295
- } else if (field === "event") {
296
- eventName = fieldValue;
297
- } else if (field === "id") {
298
- eventId = fieldValue;
299
- }
300
- // The host client owns the cursor; `retry` does not override our policy.
301
- }
302
- if (cursor > 0) {
303
- buffer = buffer.slice(cursor);
304
- }
305
- }
306
- }
307
- }
308
-
309
- function parseServerClose(data: string): ServerClose {
310
- try {
311
- const parsed = JSON.parse(data) as Partial<ServerClose>;
312
- return {
313
- code: typeof parsed.code === "number" ? parsed.code : 1000,
314
- reason: typeof parsed.reason === "string" ? parsed.reason : "",
315
- };
316
- } catch {
317
- Logger.warn("Failed to parse SSE close event", data);
318
- return { code: 1000, reason: "" };
319
- }
320
- }
@@ -1,50 +0,0 @@
1
- /* Copyright 2026 Marimo. All rights reserved. */
2
-
3
- import type {
4
- NotebookTransport,
5
- ConnectionEvent,
6
- ConnectionTransportCallback,
7
- IConnectionTransportMap,
8
- } from "../../host/connection";
9
- export type {
10
- ConnectionEvent,
11
- ConnectionTransportCallback,
12
- IConnectionTransportMap,
13
- } from "../../host/connection";
14
-
15
- export interface IConnectionTransport extends NotebookTransport {
16
- send(data: string | ArrayBuffer | Blob | ArrayBufferView<ArrayBuffer>): void;
17
- }
18
-
19
- export class ConnectionSubscriptions {
20
- private subscriptions = new Map<
21
- ConnectionEvent,
22
- Set<ConnectionTransportCallback<ConnectionEvent>>
23
- >();
24
-
25
- public addSubscription(
26
- event: ConnectionEvent,
27
- callback: ConnectionTransportCallback<ConnectionEvent>,
28
- ): void {
29
- if (!this.subscriptions.has(event)) {
30
- this.subscriptions.set(event, new Set());
31
- }
32
- this.subscriptions.get(event)?.add(callback);
33
- }
34
-
35
- public removeSubscription(
36
- event: ConnectionEvent,
37
- callback: ConnectionTransportCallback<ConnectionEvent>,
38
- ): void {
39
- this.subscriptions.get(event)?.delete(callback);
40
- }
41
-
42
- public notify(
43
- event: ConnectionEvent,
44
- data: IConnectionTransportMap[ConnectionEvent],
45
- ): void {
46
- for (const callback of this.subscriptions.get(event) ?? []) {
47
- callback(data);
48
- }
49
- }
50
- }
@@ -1,98 +0,0 @@
1
- /* Copyright 2026 Marimo. All rights reserved. */
2
-
3
- import ReconnectingWebSocket from "partysocket/ws";
4
- import type {
5
- ConnectionEvent,
6
- ConnectionTransportCallback,
7
- IConnectionTransport,
8
- } from "./transport";
9
-
10
- // Per-`reconnect()` retry budget. After exhaustion, partysocket stops silently;
11
- // the wrapper rewrites the close-event reason to surface the give-up.
12
- export const MAX_RETRIES = 10;
13
-
14
- export const TRANSPORT_EXHAUSTED_REASON = "MARIMO_TRANSPORT_EXHAUSTED";
15
-
16
- export class WsTransport implements IConnectionTransport {
17
- private inner: ReconnectingWebSocket;
18
- private closeWrappers = new WeakMap<
19
- ConnectionTransportCallback<"close">,
20
- ConnectionTransportCallback<"close">
21
- >();
22
-
23
- public constructor(urlProvider: () => string) {
24
- this.inner = new ReconnectingWebSocket(urlProvider, undefined, {
25
- maxRetries: MAX_RETRIES,
26
- debug: false,
27
- startClosed: true,
28
- // long timeout — the server can become slow when many notebooks are open.
29
- connectionTimeout: 10_000,
30
- });
31
- }
32
-
33
- public get readyState(): WebSocket["readyState"] {
34
- return this.inner.readyState as WebSocket["readyState"];
35
- }
36
-
37
- public reconnect(code?: number, reason?: string): void {
38
- this.inner.reconnect(code, reason);
39
- }
40
-
41
- public close(): void {
42
- this.inner.close();
43
- }
44
-
45
- public send(
46
- data: string | ArrayBuffer | Blob | ArrayBufferView<ArrayBuffer>,
47
- ): void {
48
- this.inner.send(data);
49
- }
50
-
51
- public addEventListener<T extends ConnectionEvent>(
52
- event: T,
53
- callback: ConnectionTransportCallback<T>,
54
- ): void {
55
- if (event === "close") {
56
- const userCb = callback as ConnectionTransportCallback<"close">;
57
- // Match native EventTarget dedupe: a second addEventListener with the
58
- // same listener is a no-op. Without this, repeated adds leak wrappers
59
- // on the inner socket and double-fire on close.
60
- if (this.closeWrappers.has(userCb)) {
61
- return;
62
- }
63
- const wrapper: ConnectionTransportCallback<"close"> = (e) => {
64
- if (this.inner.retryCount >= MAX_RETRIES) {
65
- userCb(
66
- new CloseEvent("close", {
67
- code: e.code,
68
- reason: TRANSPORT_EXHAUSTED_REASON,
69
- wasClean: e.wasClean,
70
- }),
71
- );
72
- } else {
73
- userCb(e);
74
- }
75
- };
76
- this.closeWrappers.set(userCb, wrapper);
77
- this.inner.addEventListener("close", wrapper);
78
- return;
79
- }
80
- this.inner.addEventListener(event, callback as never);
81
- }
82
-
83
- public removeEventListener<T extends ConnectionEvent>(
84
- event: T,
85
- callback: ConnectionTransportCallback<T>,
86
- ): void {
87
- if (event === "close") {
88
- const userCb = callback as ConnectionTransportCallback<"close">;
89
- const wrapper = this.closeWrappers.get(userCb);
90
- if (wrapper) {
91
- this.closeWrappers.delete(userCb);
92
- this.inner.removeEventListener("close", wrapper);
93
- }
94
- return;
95
- }
96
- this.inner.removeEventListener(event, callback as never);
97
- }
98
- }