overmux 0.0.1 → 0.0.2

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 (193) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +7 -0
  3. package/dist/auth/.vite/manifest.json +11 -0
  4. package/dist/auth/assets/index-Bx8JhwVi.css +1 -0
  5. package/dist/auth/assets/index-CHIE7qYj.js +72 -0
  6. package/dist/auth/index.html +14 -0
  7. package/dist/bin.js +4504 -7870
  8. package/dist/bin.js.map +1 -0
  9. package/dist/docs/000-index.md +21 -0
  10. package/dist/docs/100-introduction/100-what-is-overmux.md +7 -0
  11. package/dist/docs/100-introduction/200-how-overmux-works.md +7 -0
  12. package/dist/docs/100-introduction/300-why-overmux.md +22 -0
  13. package/dist/docs/200-getting-started/100-install-and-run-overmux.md +51 -0
  14. package/dist/docs/200-getting-started/200-install-overmux-desktop.md +41 -0
  15. package/dist/docs/200-getting-started/300-install-overmux-pwa.mdx +92 -0
  16. package/dist/docs/200-getting-started/400-secure-with-https/100-choose-an-https-setup.md +14 -0
  17. package/dist/docs/200-getting-started/400-secure-with-https/200-tailscale-serve.md +44 -0
  18. package/dist/docs/200-getting-started/400-secure-with-https/300-cloudflare-tunnel.md +59 -0
  19. package/dist/docs/200-getting-started/400-secure-with-https/400-self-hosted-reverse-proxy.md +32 -0
  20. package/dist/docs/300-fundamentals/100-project-structure.md +3 -0
  21. package/dist/docs/300-fundamentals/200-configuration.md +3 -0
  22. package/dist/docs/300-fundamentals/300-theming.md +3 -0
  23. package/dist/docs/300-fundamentals/400-server.md +3 -0
  24. package/dist/docs/300-fundamentals/500-client.md +3 -0
  25. package/dist/docs/300-fundamentals/600-operations.md +3 -0
  26. package/dist/docs/300-fundamentals/700-resources.md +3 -0
  27. package/dist/docs/300-fundamentals/800-streams.md +3 -0
  28. package/dist/docs/300-fundamentals/900-authentication-and-security.md +3 -0
  29. package/dist/docs/400-reference/100-configuration.md +3 -0
  30. package/dist/docs/400-reference/200-server-api.md +3 -0
  31. package/dist/docs/400-reference/300-client-api.md +3 -0
  32. package/dist/docs/400-reference/400-cli/100-serve.md +33 -0
  33. package/dist/docs/400-reference/400-cli/200-auth.md +52 -0
  34. package/dist/docs/400-reference/400-cli/300-call.md +28 -0
  35. package/dist/docs/400-reference/400-cli/400-check.md +20 -0
  36. package/dist/docs/400-reference/400-cli/500-ai-context.md +102 -0
  37. package/dist/docs/400-reference/400-cli/600-docs.md +23 -0
  38. package/dist/docs/400-reference/400-cli/700-desktop.md +35 -0
  39. package/dist/docs/500-hosted-pages.md +31 -0
  40. package/dist/exports/client.d.ts +237 -0
  41. package/dist/exports/client.d.ts.map +1 -0
  42. package/dist/exports/client.js +1643 -0
  43. package/dist/exports/client.js.map +1 -0
  44. package/dist/exports/index-D3_Dpy_T.d.ts +155 -0
  45. package/dist/exports/index-D3_Dpy_T.d.ts.map +1 -0
  46. package/dist/exports/index.d.ts +2 -0
  47. package/dist/exports/index.js +49 -0
  48. package/dist/exports/index.js.map +1 -0
  49. package/dist/exports/notifications-kDp16bU_.js +56 -0
  50. package/dist/exports/notifications-kDp16bU_.js.map +1 -0
  51. package/dist/exports/server.d.ts +63 -0
  52. package/dist/exports/server.d.ts.map +1 -0
  53. package/dist/exports/server.js +1120 -0
  54. package/dist/exports/server.js.map +1 -0
  55. package/dist/exports/style.css +368 -0
  56. package/dist/internal/server/coordinator/server-child.d.ts +1 -0
  57. package/dist/internal/server/coordinator/server-child.js +3299 -0
  58. package/dist/internal/server/coordinator/server-child.js.map +1 -0
  59. package/docs/000-index.md +21 -0
  60. package/docs/100-introduction/100-what-is-overmux.md +7 -0
  61. package/docs/100-introduction/200-how-overmux-works.md +7 -0
  62. package/docs/100-introduction/300-why-overmux.md +22 -0
  63. package/docs/200-getting-started/100-install-and-run-overmux.md +51 -0
  64. package/docs/200-getting-started/200-install-overmux-desktop.md +41 -0
  65. package/docs/200-getting-started/300-install-overmux-pwa.mdx +92 -0
  66. package/docs/200-getting-started/400-secure-with-https/100-choose-an-https-setup.md +14 -0
  67. package/docs/200-getting-started/400-secure-with-https/200-tailscale-serve.md +44 -0
  68. package/docs/200-getting-started/400-secure-with-https/300-cloudflare-tunnel.md +59 -0
  69. package/docs/200-getting-started/400-secure-with-https/400-self-hosted-reverse-proxy.md +32 -0
  70. package/docs/300-fundamentals/100-project-structure.md +3 -0
  71. package/docs/300-fundamentals/200-configuration.md +3 -0
  72. package/docs/300-fundamentals/300-theming.md +3 -0
  73. package/docs/300-fundamentals/400-server.md +3 -0
  74. package/docs/300-fundamentals/500-client.md +3 -0
  75. package/docs/300-fundamentals/600-operations.md +3 -0
  76. package/docs/300-fundamentals/700-resources.md +3 -0
  77. package/docs/300-fundamentals/800-streams.md +3 -0
  78. package/docs/300-fundamentals/900-authentication-and-security.md +3 -0
  79. package/docs/400-reference/100-configuration.md +3 -0
  80. package/docs/400-reference/200-server-api.md +3 -0
  81. package/docs/400-reference/300-client-api.md +3 -0
  82. package/docs/400-reference/400-cli/100-serve.md +33 -0
  83. package/docs/400-reference/400-cli/200-auth.md +52 -0
  84. package/docs/400-reference/400-cli/300-call.md +28 -0
  85. package/docs/400-reference/400-cli/400-check.md +20 -0
  86. package/docs/400-reference/400-cli/500-ai-context.md +102 -0
  87. package/docs/400-reference/400-cli/600-docs.md +23 -0
  88. package/docs/400-reference/400-cli/700-desktop.md +35 -0
  89. package/docs/500-hosted-pages.md +31 -0
  90. package/package.json +59 -18
  91. package/src/internal/cli/app.ts +72 -0
  92. package/src/internal/cli/bin.ts +3 -0
  93. package/src/internal/cli/commands/ai.ts +44 -0
  94. package/src/internal/cli/commands/auth.ts +195 -0
  95. package/src/internal/cli/commands/call.ts +128 -0
  96. package/src/internal/cli/commands/check.ts +48 -0
  97. package/src/internal/cli/commands/desktop-installer.ts +446 -0
  98. package/src/internal/cli/commands/desktop-release.ts +190 -0
  99. package/src/internal/cli/commands/desktop.ts +47 -0
  100. package/src/internal/cli/commands/docs.ts +34 -0
  101. package/src/internal/cli/commands/serve.ts +322 -0
  102. package/src/internal/cli/environment.ts +54 -0
  103. package/src/internal/cli/login.ts +46 -0
  104. package/src/internal/cli/managed-vite.ts +267 -0
  105. package/src/internal/cli/parse-port.ts +8 -0
  106. package/src/internal/client/auth/auth-api.ts +53 -0
  107. package/src/internal/client/auth/auth-shell.css +74 -0
  108. package/src/internal/client/auth/auth-shell.tsx +167 -0
  109. package/src/internal/client/auth/entry.tsx +26 -0
  110. package/src/internal/client/auth/index.html +13 -0
  111. package/src/internal/client/background-notifications.ts +194 -0
  112. package/src/internal/client/browser-api.ts +97 -0
  113. package/src/internal/client/client-definition.ts +184 -0
  114. package/src/internal/client/commands.tsx +260 -0
  115. package/src/internal/client/host/browser-log-forwarding.ts +149 -0
  116. package/src/internal/client/host/hosted-pages/browser-notification-settings.tsx +118 -0
  117. package/src/internal/client/host/hosted-pages/hosted-page.css +87 -0
  118. package/src/internal/client/host/hosted-pages/hosted-page.tsx +9 -0
  119. package/src/internal/client/host/hosted-pages/logout-page.tsx +75 -0
  120. package/src/internal/client/host/hosted-pages/settings-page.tsx +45 -0
  121. package/src/internal/client/host/notification-forwarding.ts +22 -0
  122. package/src/internal/client/host/overmux-host.tsx +200 -0
  123. package/src/internal/client/host/recovery-screen.css +75 -0
  124. package/src/internal/client/host/recovery-screen.tsx +17 -0
  125. package/src/internal/client/host/update-popover.css +82 -0
  126. package/src/internal/client/host/update-popover.tsx +166 -0
  127. package/src/internal/client/index.ts +45 -0
  128. package/src/internal/client/overmux-react.ts +283 -0
  129. package/src/internal/client/shortcuts.tsx +373 -0
  130. package/src/internal/client/theme-scope.css +114 -0
  131. package/src/internal/client/theme-scope.tsx +107 -0
  132. package/src/internal/client/transport.ts +69 -0
  133. package/src/internal/client/websocket.ts +441 -0
  134. package/src/internal/server/auth/auth-http.ts +414 -0
  135. package/src/internal/server/auth/auth-service.ts +627 -0
  136. package/src/internal/server/auth/auth-shell-http.ts +90 -0
  137. package/src/internal/server/auth/instance-control.ts +381 -0
  138. package/src/internal/server/check-overmux.ts +364 -0
  139. package/src/internal/server/coordinator/ipc-protocol.ts +44 -0
  140. package/src/internal/server/coordinator/server-child.ts +108 -0
  141. package/src/internal/server/coordinator/server-coordinator.ts +262 -0
  142. package/src/internal/server/coordinator/update-watcher.ts +54 -0
  143. package/src/internal/server/http/authenticated-http-app-test-helper.ts +45 -0
  144. package/src/internal/server/http/background-notification-handlers.ts +83 -0
  145. package/src/internal/server/http/control-handlers.ts +44 -0
  146. package/src/internal/server/http/create-http-app.ts +53 -0
  147. package/src/internal/server/http/development-web-backend.ts +83 -0
  148. package/src/internal/server/http/operation-handler.ts +93 -0
  149. package/src/internal/server/http/request-logging.ts +34 -0
  150. package/src/internal/server/http/routes.ts +241 -0
  151. package/src/internal/server/http/runtime-manifest-handler.ts +12 -0
  152. package/src/internal/server/index.ts +16 -0
  153. package/src/internal/server/notifications/background-notification-payload.ts +74 -0
  154. package/src/internal/server/notifications/background-notification-service.ts +139 -0
  155. package/src/internal/server/notifications/background-notification-store.ts +162 -0
  156. package/src/internal/server/notifications/notification-service.ts +70 -0
  157. package/src/internal/server/notifications/private-json-file.ts +84 -0
  158. package/src/internal/server/paths.ts +13 -0
  159. package/src/internal/server/rate-limiter.ts +58 -0
  160. package/src/internal/server/runtime/create-runtime.ts +87 -0
  161. package/src/internal/server/runtime/resource-dependency-graph.ts +128 -0
  162. package/src/internal/server/runtime/runtime-lifecycle.ts +95 -0
  163. package/src/internal/server/runtime/runtime-operations.ts +78 -0
  164. package/src/internal/server/runtime/runtime-resources.ts +201 -0
  165. package/src/internal/server/runtime/runtime-streams.ts +130 -0
  166. package/src/internal/server/server-logger.ts +110 -0
  167. package/src/internal/server/server-startup-options.ts +51 -0
  168. package/src/internal/server/start-application-server.ts +412 -0
  169. package/src/internal/server/start-overmux-server.ts +26 -0
  170. package/src/internal/server/test-port.ts +23 -0
  171. package/src/internal/server/vite-websocket-proxy.ts +201 -0
  172. package/src/internal/server/websocket-connection.ts +586 -0
  173. package/src/internal/server/websocket-limits.ts +2 -0
  174. package/src/internal/server/websocket-server.ts +311 -0
  175. package/src/internal/shared/background-notifications.ts +37 -0
  176. package/src/internal/shared/index.ts +2 -0
  177. package/src/internal/shared/protocol.ts +317 -0
  178. package/src/internal/shared/routes.ts +3 -0
  179. package/src/public/ai-context.ts +44 -0
  180. package/src/public/client.ts +53 -0
  181. package/src/public/config.ts +287 -0
  182. package/src/public/contracts.ts +55 -0
  183. package/src/public/index.ts +57 -0
  184. package/src/public/notifications.ts +10 -0
  185. package/src/public/server.ts +16 -0
  186. package/dist/public/core.ts +0 -119
  187. package/dist/public/protocol.ts +0 -51
  188. package/dist/public/terminal.ts +0 -23
  189. package/dist/web/assets/index-DC___IUw.css +0 -2
  190. package/dist/web/assets/index-DF9Cptse.js +0 -74
  191. package/dist/web/assets/terminal-component-BrP-ENHg.css +0 -1
  192. package/dist/web/assets/terminal-component-DyLEbMrx.js +0 -36
  193. package/dist/web/index.html +0 -14
@@ -0,0 +1,441 @@
1
+ import {
2
+ clientProtocolMessageSchema,
3
+ decodeProtocolFrame,
4
+ encodeProtocolMessage,
5
+ serverProtocolMessageSchema,
6
+ streamMessageSchema,
7
+ webSocketCloseCode,
8
+ type ClientDiagnosticEvent,
9
+ type ClientProtocolMessage,
10
+ type ServerLifecycleEvent,
11
+ } from "../shared/index";
12
+
13
+ type Request = {
14
+ reject: (error: Error) => void;
15
+ resolve: (output: unknown) => void;
16
+ };
17
+ type Subscription = {
18
+ input: unknown;
19
+ onError: (error: Error) => void;
20
+ onInvalidate: () => void;
21
+ resourceName: string;
22
+ };
23
+ type StreamMessage = Extract<ClientProtocolMessage, { type: "stream-message" }>;
24
+
25
+ type Stream = {
26
+ input: unknown;
27
+ onOpen: () => void;
28
+ onClose: () => void;
29
+ onError: (error: Error) => void;
30
+ onMessage: (message: unknown) => void;
31
+ pendingMessages: StreamMessage[];
32
+ status: "disconnected" | "failed" | "open" | "opening";
33
+ streamName: string;
34
+ };
35
+
36
+ const maxWebSocketBufferedBytes = 1_048_576;
37
+
38
+ export type TransportStatus =
39
+ | "authentication-required"
40
+ | "connecting"
41
+ | "connected"
42
+ | "disconnected";
43
+
44
+ export type WebSocketTransport = {
45
+ activate: () => void;
46
+ dispose: () => void;
47
+ openStream: (input: {
48
+ input?: unknown;
49
+ onClose: () => void;
50
+ onError: (error: Error) => void;
51
+ onOpen: () => void;
52
+ onMessage: (message: unknown) => void;
53
+ streamName: string;
54
+ }) => { close: () => void; send: (message: unknown) => boolean };
55
+ readResource: (input: {
56
+ input?: unknown;
57
+ resourceName: string;
58
+ }) => Promise<unknown>;
59
+ reportDiagnostic: (event: ClientDiagnosticEvent) => void;
60
+ subscribeResource: (input: {
61
+ input?: unknown;
62
+ onError: (error: Error) => void;
63
+ onInvalidate: () => void;
64
+ resourceName: string;
65
+ }) => () => void;
66
+ subscribeStatus: (listener: (status: TransportStatus) => void) => () => void;
67
+ subscribeLifecycle: (
68
+ listener: (event: ServerLifecycleEvent) => void,
69
+ ) => () => void;
70
+ subscribeNotification: (listener: (event: unknown) => void) => () => void;
71
+ };
72
+
73
+ const socketUrl = () =>
74
+ `${location.protocol === "https:" ? "wss:" : "ws:"}//${location.host}/api/socket`;
75
+
76
+ const operationId = () => {
77
+ const randomUUID = crypto.randomUUID?.();
78
+ if (randomUUID) {
79
+ return randomUUID;
80
+ }
81
+ const bytes = crypto.getRandomValues(new Uint8Array(16));
82
+ bytes[6] = (bytes[6] & 0x0f) | 0x40;
83
+ bytes[8] = (bytes[8] & 0x3f) | 0x80;
84
+ const hex = Array.from(bytes, (byte) =>
85
+ byte.toString(16).padStart(2, "0"),
86
+ ).join("");
87
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
88
+ };
89
+ const disconnected = () => new Error("Overmux transport disconnected.");
90
+
91
+ export const createWebSocketTransport = ({
92
+ createSocket = () => new WebSocket(socketUrl()),
93
+ }: {
94
+ createSocket?: () => WebSocket;
95
+ } = {}): WebSocketTransport => {
96
+ let active = false;
97
+ let disposed = false;
98
+ let reconnectDelay = 250;
99
+ let reconnectTimer: ReturnType<typeof setTimeout> | undefined;
100
+ let socket: WebSocket | undefined;
101
+ let status: TransportStatus = "disconnected";
102
+ const diagnosticQueue: ClientDiagnosticEvent[] = [];
103
+ const requests = new Map<string, Request>();
104
+ const subscriptions = new Map<string, Subscription>();
105
+ const streams = new Map<string, Stream>();
106
+ const lifecycleListeners = new Set<(event: ServerLifecycleEvent) => void>();
107
+ const notificationListeners = new Set<(event: unknown) => void>();
108
+ const statusListeners = new Set<(status: TransportStatus) => void>();
109
+
110
+ const setStatus = (next: TransportStatus) => {
111
+ if (status === next) {
112
+ return;
113
+ }
114
+ status = next;
115
+ statusListeners.forEach((listener) => listener(status));
116
+ };
117
+ const send = (message: unknown) => {
118
+ if (socket?.readyState !== WebSocket.OPEN) {
119
+ return false;
120
+ }
121
+ // Apply outbound backpressure before queued messages consume unbounded memory.
122
+ if (socket.bufferedAmount > maxWebSocketBufferedBytes) {
123
+ socket.close(1013, "client could not keep up");
124
+ return false;
125
+ }
126
+ socket.send(
127
+ encodeProtocolMessage(clientProtocolMessageSchema.parse(message)),
128
+ );
129
+ return true;
130
+ };
131
+ const flushDiagnostics = () => {
132
+ const unsent = diagnosticQueue.filter((event) => !send(event));
133
+ diagnosticQueue.splice(0, diagnosticQueue.length, ...unsent);
134
+ };
135
+ const request = ({
136
+ input,
137
+ resourceName,
138
+ }: {
139
+ input?: unknown;
140
+ resourceName: string;
141
+ }) =>
142
+ new Promise<unknown>((resolve, reject) => {
143
+ const id = operationId();
144
+ requests.set(id, { reject, resolve });
145
+ const message = {
146
+ input,
147
+ operationId: id,
148
+ resourceName,
149
+ type: "resource-read",
150
+ };
151
+ if (!send(message)) {
152
+ requests.delete(id);
153
+ reject(disconnected());
154
+ }
155
+ });
156
+ const restore = () => {
157
+ subscriptions.forEach(
158
+ ({ input, onInvalidate, resourceName }, subscriptionId) => {
159
+ if (
160
+ send({
161
+ input,
162
+ operationId: subscriptionId,
163
+ resourceName,
164
+ subscriptionId,
165
+ type: "resource-subscribe",
166
+ })
167
+ ) {
168
+ onInvalidate();
169
+ }
170
+ },
171
+ );
172
+ streams.forEach((stream, streamId) => {
173
+ if (stream.status === "failed") {
174
+ return;
175
+ }
176
+ stream.status = send({
177
+ input: stream.input,
178
+ operationId: streamId,
179
+ streamId,
180
+ streamName: stream.streamName,
181
+ type: "stream-open",
182
+ })
183
+ ? "opening"
184
+ : "disconnected";
185
+ });
186
+ };
187
+ const rejectRequests = (error: Error) => {
188
+ requests.forEach(({ reject }) => reject(error));
189
+ requests.clear();
190
+ };
191
+ const reportDisconnect = () => {
192
+ const error = disconnected();
193
+ rejectRequests(error);
194
+ subscriptions.forEach(({ onError }) => onError(error));
195
+ streams.forEach((stream) => {
196
+ stream.pendingMessages.length = 0;
197
+ if (stream.status !== "failed") {
198
+ stream.status = "disconnected";
199
+ }
200
+ stream.onError(error);
201
+ });
202
+ };
203
+ const connect = () => {
204
+ if (!active || disposed || socket) {
205
+ return;
206
+ }
207
+ setStatus("connecting");
208
+ const next = createSocket();
209
+ next.binaryType = "arraybuffer";
210
+ socket = next;
211
+ next.addEventListener("open", () => {
212
+ if (socket !== next) {
213
+ return;
214
+ }
215
+ reconnectDelay = 250;
216
+ setStatus("connected");
217
+ flushDiagnostics();
218
+ restore();
219
+ });
220
+ next.addEventListener("message", (event) => {
221
+ if (socket !== next) {
222
+ return;
223
+ }
224
+ let decoded: unknown;
225
+ try {
226
+ decoded = decodeProtocolFrame(event.data as string | ArrayBuffer);
227
+ } catch (cause) {
228
+ reportDisconnect();
229
+ console.error("Invalid Overmux transport message", cause);
230
+ next.close(1002, "invalid protocol message");
231
+ return;
232
+ }
233
+ const parsed = serverProtocolMessageSchema.safeParse(decoded);
234
+ if (!parsed.success) {
235
+ reportDisconnect();
236
+ console.error("Invalid Overmux transport message", parsed.error);
237
+ next.close(1002, "invalid protocol message");
238
+ return;
239
+ }
240
+ const message = parsed.data;
241
+ if (
242
+ message.type === "update-available" ||
243
+ message.type === "restarting"
244
+ ) {
245
+ lifecycleListeners.forEach((listener) => listener(message));
246
+ }
247
+ if (message.type === "notification") {
248
+ notificationListeners.forEach((listener) => listener(message));
249
+ }
250
+ if (message.type === "resource-invalidated") {
251
+ subscriptions.get(message.subscriptionId)?.onInvalidate();
252
+ }
253
+ if (message.type === "stream-opened") {
254
+ const stream = streams.get(message.streamId);
255
+ if (stream?.status === "opening") {
256
+ stream.status = "open";
257
+ const pendingMessages = stream.pendingMessages.splice(0);
258
+ if (pendingMessages.every(send)) {
259
+ stream.onOpen();
260
+ }
261
+ }
262
+ }
263
+ if (message.type === "stream-output") {
264
+ streams.get(message.streamId)?.onMessage(message.message);
265
+ }
266
+ if (message.type === "stream-closed") {
267
+ const stream = streams.get(message.streamId);
268
+ if (stream) {
269
+ stream.pendingMessages.length = 0;
270
+ stream.onClose();
271
+ streams.delete(message.streamId);
272
+ }
273
+ }
274
+ if (message.type === "error") {
275
+ if (message.operationId) {
276
+ requests.get(message.operationId)?.reject(new Error(message.message));
277
+ }
278
+ const stream = streams.get(message.operationId ?? "");
279
+ if (stream) {
280
+ stream.pendingMessages.length = 0;
281
+ stream.status = "failed";
282
+ stream.onError(new Error(message.message));
283
+ }
284
+ subscriptions
285
+ .get(message.operationId ?? "")
286
+ ?.onError(new Error(message.message));
287
+ requests.delete(message.operationId ?? "");
288
+ }
289
+ if (message.type === "resource-result") {
290
+ requests.get(message.operationId)?.resolve(message.output);
291
+ requests.delete(message.operationId);
292
+ }
293
+ });
294
+ next.addEventListener("close", (event) => {
295
+ if (socket !== next || disposed) {
296
+ return;
297
+ }
298
+ socket = undefined;
299
+ reportDisconnect();
300
+ if (event.code === webSocketCloseCode.authenticationRevoked) {
301
+ active = false;
302
+ setStatus("authentication-required");
303
+ return;
304
+ }
305
+ setStatus("disconnected");
306
+ reconnectTimer = setTimeout(() => {
307
+ reconnectTimer = undefined;
308
+ connect();
309
+ }, reconnectDelay);
310
+ reconnectDelay = Math.min(reconnectDelay * 2, 5_000);
311
+ });
312
+ };
313
+
314
+ return {
315
+ activate: () => {
316
+ active = true;
317
+ connect();
318
+ },
319
+ dispose: () => {
320
+ disposed = true;
321
+ if (reconnectTimer !== undefined) {
322
+ clearTimeout(reconnectTimer);
323
+ }
324
+ reportDisconnect();
325
+ streams.forEach(({ onClose }) => onClose());
326
+ streams.clear();
327
+ subscriptions.clear();
328
+ socket?.close();
329
+ },
330
+ openStream: ({
331
+ input,
332
+ onClose,
333
+ onError,
334
+ onMessage,
335
+ onOpen,
336
+ streamName,
337
+ }) => {
338
+ const streamId = operationId();
339
+ const stream: Stream = {
340
+ input,
341
+ onClose,
342
+ onError,
343
+ onMessage,
344
+ onOpen,
345
+ pendingMessages: [],
346
+ status: "disconnected",
347
+ streamName,
348
+ };
349
+ streams.set(streamId, stream);
350
+ stream.status = send({
351
+ input,
352
+ operationId: streamId,
353
+ streamId,
354
+ streamName,
355
+ type: "stream-open",
356
+ })
357
+ ? "opening"
358
+ : "disconnected";
359
+ if (stream.status === "disconnected") {
360
+ onError(disconnected());
361
+ }
362
+ return {
363
+ close: () => {
364
+ const activeStream = streams.get(streamId);
365
+ if (activeStream) {
366
+ activeStream.pendingMessages.length = 0;
367
+ activeStream.onClose();
368
+ streams.delete(streamId);
369
+ }
370
+ send({ streamId, type: "stream-close" });
371
+ },
372
+ send: (message) => {
373
+ const activeStream = streams.get(streamId);
374
+ if (
375
+ activeStream?.status !== "open" &&
376
+ activeStream?.status !== "opening"
377
+ ) {
378
+ return false;
379
+ }
380
+ const protocolMessage = streamMessageSchema.parse({
381
+ message,
382
+ streamId,
383
+ type: "stream-message",
384
+ });
385
+ if (activeStream.status === "opening") {
386
+ activeStream.pendingMessages.push(protocolMessage);
387
+ return true;
388
+ }
389
+ return send(protocolMessage);
390
+ },
391
+ };
392
+ },
393
+ readResource: (input) => request(input),
394
+ reportDiagnostic: (event) => {
395
+ if (send(event)) {
396
+ return;
397
+ }
398
+ diagnosticQueue.push(event);
399
+ if (diagnosticQueue.length > 100) {
400
+ diagnosticQueue.shift();
401
+ }
402
+ },
403
+ subscribeResource: ({ input, onError, onInvalidate, resourceName }) => {
404
+ const subscriptionId = operationId();
405
+ subscriptions.set(subscriptionId, {
406
+ input,
407
+ onError,
408
+ onInvalidate,
409
+ resourceName,
410
+ });
411
+ if (
412
+ !send({
413
+ input,
414
+ operationId: subscriptionId,
415
+ resourceName,
416
+ subscriptionId,
417
+ type: "resource-subscribe",
418
+ })
419
+ ) {
420
+ onError(disconnected());
421
+ }
422
+ return () => {
423
+ subscriptions.delete(subscriptionId);
424
+ send({ subscriptionId, type: "resource-unsubscribe" });
425
+ };
426
+ },
427
+ subscribeStatus: (listener) => {
428
+ statusListeners.add(listener);
429
+ listener(status);
430
+ return () => statusListeners.delete(listener);
431
+ },
432
+ subscribeLifecycle: (listener) => {
433
+ lifecycleListeners.add(listener);
434
+ return () => lifecycleListeners.delete(listener);
435
+ },
436
+ subscribeNotification: (listener) => {
437
+ notificationListeners.add(listener);
438
+ return () => notificationListeners.delete(listener);
439
+ },
440
+ };
441
+ };