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,1643 @@
1
+ import "./style.css";
2
+ import { i as overmuxLogoutPath, r as notificationSchema, s as resolveOvermuxReturnTo } from "./notifications-kDp16bU_.js";
3
+ import { z } from "zod";
4
+ import { QueryClient, QueryClientProvider, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
5
+ import { Component, createContext, createElement, useCallback, useContext, useEffect, useMemo, useReducer, useRef, useState } from "react";
6
+ import { formatKeyBinding, formatKeyBinding as formatKeyBinding$1, keyBindingSchema, keyBindingSchema as keyBindingSchema$1, matchesKeyBinding, matchesKeyBinding as matchesKeyBinding$1, platformBinding } from "@overmux/keybindings";
7
+ import { createPortal } from "react-dom";
8
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
+ //#region src/internal/shared/background-notifications.ts
10
+ const deliveryKeySchema = z.string().min(1).max(1024).regex(/^[\w-]+$/u);
11
+ const backgroundNotificationSubscriptionSchema = z.object({
12
+ endpoint: z.url().max(4096).refine((endpoint) => {
13
+ const url = new URL(endpoint);
14
+ return url.protocol === "https:" && !url.username && !url.password;
15
+ }),
16
+ expirationTime: z.number().int().nonnegative().nullable(),
17
+ keys: z.object({
18
+ auth: deliveryKeySchema,
19
+ p256dh: deliveryKeySchema
20
+ }).strict()
21
+ }).strict();
22
+ const backgroundNotificationPublicKeyResponseSchema = z.object({ publicKey: deliveryKeySchema }).strict();
23
+ z.object({ endpoint: backgroundNotificationSubscriptionSchema.shape.endpoint }).strict();
24
+ const webSocketCloseCode = {
25
+ authenticationRevoked: 4001,
26
+ authenticationUnavailable: 4002
27
+ };
28
+ const runtimeManifestSchema = z.object({
29
+ debug: z.boolean(),
30
+ operations: z.array(z.string().min(1)),
31
+ protocolVersion: z.literal(9),
32
+ resources: z.array(z.string().min(1)),
33
+ streams: z.array(z.string().min(1))
34
+ });
35
+ const updateAvailableEventSchema = z.object({ type: z.literal("update-available") });
36
+ const restartingEventSchema = z.object({ type: z.literal("restarting") });
37
+ z.discriminatedUnion("type", [updateAvailableEventSchema, restartingEventSchema]);
38
+ const notificationEventSchema = z.object({
39
+ notification: notificationSchema,
40
+ type: z.literal("notification")
41
+ }).strict();
42
+ const operationIdSchema = z.string().min(1).max(256);
43
+ const registrationNameSchema = z.string().min(1).max(256);
44
+ const resourceReadMessageSchema = z.object({
45
+ input: z.unknown().optional(),
46
+ operationId: operationIdSchema,
47
+ resourceName: registrationNameSchema,
48
+ type: z.literal("resource-read")
49
+ });
50
+ const resourceSubscribeMessageSchema = z.object({
51
+ input: z.unknown().optional(),
52
+ operationId: operationIdSchema,
53
+ resourceName: registrationNameSchema,
54
+ subscriptionId: operationIdSchema,
55
+ type: z.literal("resource-subscribe")
56
+ });
57
+ const resourceUnsubscribeMessageSchema = z.object({
58
+ subscriptionId: operationIdSchema,
59
+ type: z.literal("resource-unsubscribe")
60
+ });
61
+ const streamOpenMessageSchema = z.object({
62
+ input: z.unknown().optional(),
63
+ operationId: operationIdSchema,
64
+ streamId: operationIdSchema,
65
+ streamName: registrationNameSchema,
66
+ type: z.literal("stream-open")
67
+ });
68
+ const streamMessageSchema = z.object({
69
+ message: z.unknown(),
70
+ streamId: operationIdSchema,
71
+ type: z.literal("stream-message")
72
+ });
73
+ const streamCloseMessageSchema = z.object({
74
+ streamId: operationIdSchema,
75
+ type: z.literal("stream-close")
76
+ });
77
+ const clientDiagnosticEventSchema = z.object({
78
+ arguments: z.string().max(16100),
79
+ level: z.enum([
80
+ "log",
81
+ "info",
82
+ "warn",
83
+ "error",
84
+ "debug"
85
+ ]),
86
+ message: z.string().max(4100),
87
+ stack: z.string().max(8100).optional(),
88
+ timestamp: z.iso.datetime(),
89
+ type: z.literal("client-diagnostic"),
90
+ url: z.string().max(2100).optional()
91
+ });
92
+ const clientProtocolMessageSchema = z.discriminatedUnion("type", [
93
+ resourceReadMessageSchema,
94
+ resourceSubscribeMessageSchema,
95
+ resourceUnsubscribeMessageSchema,
96
+ streamOpenMessageSchema,
97
+ streamMessageSchema,
98
+ streamCloseMessageSchema,
99
+ clientDiagnosticEventSchema
100
+ ]);
101
+ const resourceResultMessageSchema = z.object({
102
+ operationId: operationIdSchema,
103
+ output: z.unknown(),
104
+ type: z.literal("resource-result")
105
+ });
106
+ const resourceInvalidatedMessageSchema = z.object({
107
+ subscriptionId: operationIdSchema,
108
+ type: z.literal("resource-invalidated")
109
+ });
110
+ const streamOpenedMessageSchema = z.object({
111
+ operationId: operationIdSchema,
112
+ streamId: operationIdSchema,
113
+ type: z.literal("stream-opened")
114
+ });
115
+ const streamOutputMessageSchema = z.object({
116
+ message: z.unknown(),
117
+ streamId: operationIdSchema,
118
+ type: z.literal("stream-output")
119
+ });
120
+ const streamClosedMessageSchema = z.object({
121
+ streamId: operationIdSchema,
122
+ type: z.literal("stream-closed")
123
+ });
124
+ const protocolErrorMessageSchema = z.object({
125
+ code: z.enum([
126
+ "bad-request",
127
+ "conflict",
128
+ "internal",
129
+ "not-found"
130
+ ]),
131
+ message: z.string(),
132
+ operationId: operationIdSchema.optional(),
133
+ streamId: operationIdSchema.optional(),
134
+ subscriptionId: operationIdSchema.optional(),
135
+ type: z.literal("error")
136
+ });
137
+ const serverProtocolMessageSchema = z.discriminatedUnion("type", [
138
+ resourceResultMessageSchema,
139
+ resourceInvalidatedMessageSchema,
140
+ streamOpenedMessageSchema,
141
+ streamOutputMessageSchema,
142
+ streamClosedMessageSchema,
143
+ protocolErrorMessageSchema,
144
+ updateAvailableEventSchema,
145
+ restartingEventSchema,
146
+ notificationEventSchema
147
+ ]);
148
+ const byteMarker = "$overmuxBytes";
149
+ const encodeValue = (value) => {
150
+ if (value instanceof Uint8Array) return { [byteMarker]: Array.from(value) };
151
+ if (Array.isArray(value)) return value.map(encodeValue);
152
+ if (typeof value === "object" && value !== null) return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, encodeValue(item)]));
153
+ return value;
154
+ };
155
+ const decodeValue = (value) => {
156
+ if (Array.isArray(value)) return value.map(decodeValue);
157
+ if (typeof value !== "object" || value === null) return value;
158
+ const record = value;
159
+ if (Object.keys(record).length === 1 && Array.isArray(record[byteMarker]) && record[byteMarker].every((item) => Number.isInteger(item) && item >= 0 && item <= 255)) return Uint8Array.from(record[byteMarker]);
160
+ return Object.fromEntries(Object.entries(record).map(([key, item]) => [key, decodeValue(item)]));
161
+ };
162
+ const binaryMarker = "$overmuxBinary";
163
+ const binaryMagic = 1330468865;
164
+ const decodeBinaryValue = (value, payload) => {
165
+ if (Array.isArray(value)) return value.map((item) => decodeBinaryValue(item, payload));
166
+ if (typeof value !== "object" || value === null) return value;
167
+ const record = value;
168
+ const marker = record[binaryMarker];
169
+ if (Object.keys(record).length === 1 && Array.isArray(marker) && marker.length === 2 && marker.every((item) => Number.isInteger(item) && item >= 0)) {
170
+ const [offset, length] = marker;
171
+ if (offset + length > payload.byteLength) throw new Error("Invalid Overmux binary payload range");
172
+ return payload.slice(offset, offset + length);
173
+ }
174
+ return Object.fromEntries(Object.entries(record).map(([key, item]) => [key, decodeBinaryValue(item, payload)]));
175
+ };
176
+ const encodeProtocolMessage = (message) => JSON.stringify(encodeValue(message));
177
+ const decodeProtocolMessage = (message) => decodeValue(JSON.parse(message));
178
+ const decodeProtocolFrame = (frame) => {
179
+ if (typeof frame === "string") return decodeProtocolMessage(frame);
180
+ const bytes = frame instanceof Uint8Array ? frame : new Uint8Array(frame, 0, frame.byteLength);
181
+ if (bytes.byteLength < 8) throw new Error("Invalid Overmux binary frame");
182
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
183
+ if (view.getUint32(0) !== binaryMagic) throw new Error("Invalid Overmux binary frame magic");
184
+ const headerLength = view.getUint32(4);
185
+ if (8 + headerLength > bytes.byteLength) throw new Error("Invalid Overmux binary frame header");
186
+ const header = JSON.parse(new TextDecoder().decode(bytes.subarray(8, 8 + headerLength)));
187
+ return decodeBinaryValue(header, bytes.subarray(8 + headerLength));
188
+ };
189
+ //#endregion
190
+ //#region src/internal/client/background-notifications.ts
191
+ const enabledStorageKey = "overmux.backgroundNotificationsEnabled";
192
+ const isIos = () => /iPad|iPhone|iPod/u.test(navigator.userAgent) || navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1;
193
+ const isStandalone = () => window.matchMedia("(display-mode: standalone)").matches || Boolean(navigator.standalone);
194
+ const supportStatus = () => {
195
+ if (window.overmuxHost?.notifications) return "unsupported";
196
+ if (isIos() && !isStandalone()) return "ios-home-screen";
197
+ if (!window.isSecureContext || !("Notification" in window) || !("serviceWorker" in navigator) || !("PushManager" in window)) return "unsupported";
198
+ };
199
+ const decodeApplicationServerKey = (value) => {
200
+ const padded = `${value}${"=".repeat((4 - value.length % 4) % 4)}`;
201
+ return Uint8Array.from(atob(padded.replaceAll("-", "+").replaceAll("_", "/")), (character) => character.charCodeAt(0));
202
+ };
203
+ const encodeApplicationServerKey = (value) => {
204
+ if (!value) return;
205
+ const binary = String.fromCharCode(...new Uint8Array(value));
206
+ return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/u, "");
207
+ };
208
+ const request = async (path, init) => {
209
+ const response = await fetch(path, {
210
+ cache: "no-store",
211
+ ...init
212
+ });
213
+ if (!response.ok) throw new Error(`Background notification request failed: ${response.status}`);
214
+ return response;
215
+ };
216
+ const getPublicKey = async () => {
217
+ const response = await request("/api/background-notifications/public-key");
218
+ return backgroundNotificationPublicKeyResponseSchema.parse(await response.json()).publicKey;
219
+ };
220
+ const serializeSubscription = (subscription) => {
221
+ const json = subscription.toJSON();
222
+ return backgroundNotificationSubscriptionSchema.parse({
223
+ endpoint: subscription.endpoint,
224
+ expirationTime: subscription.expirationTime ?? null,
225
+ keys: json.keys
226
+ });
227
+ };
228
+ const enableSubscription = async (subscription) => {
229
+ await request("/api/background-notifications/subscription", {
230
+ body: JSON.stringify(serializeSubscription(subscription)),
231
+ headers: { "Content-Type": "application/json" },
232
+ method: "PUT"
233
+ });
234
+ };
235
+ const disableSubscription = async (subscription) => {
236
+ await request("/api/background-notifications/subscription", {
237
+ body: JSON.stringify({ endpoint: subscription.endpoint }),
238
+ headers: { "Content-Type": "application/json" },
239
+ method: "DELETE"
240
+ });
241
+ };
242
+ const subscribe = async (registration, publicKey) => registration.pushManager.subscribe({
243
+ applicationServerKey: decodeApplicationServerKey(publicKey),
244
+ userVisibleOnly: true
245
+ });
246
+ const canManageSubscription = () => window.isSecureContext && "serviceWorker" in navigator && "PushManager" in window;
247
+ const removeCurrentSubscription = async () => {
248
+ if (!canManageSubscription()) return;
249
+ const subscription = await (await navigator.serviceWorker.getRegistration())?.pushManager.getSubscription();
250
+ if (!subscription) return;
251
+ await disableSubscription(subscription);
252
+ if (!await subscription.unsubscribe()) throw new Error("The background subscription could not be removed");
253
+ };
254
+ const reconcileBackgroundNotifications = async () => {
255
+ const registration = await navigator.serviceWorker.ready;
256
+ const publicKey = await getPublicKey();
257
+ let subscription = await registration.pushManager.getSubscription();
258
+ const subscriptionKey = subscription ? encodeApplicationServerKey(subscription.options.applicationServerKey) : void 0;
259
+ if (subscription && subscriptionKey !== publicKey) {
260
+ await disableSubscription(subscription);
261
+ if (!await subscription.unsubscribe()) throw new Error("The previous background subscription is still active");
262
+ subscription = null;
263
+ }
264
+ if (!subscription) subscription = await subscribe(registration, publicKey);
265
+ await enableSubscription(subscription);
266
+ };
267
+ const enableBackgroundNotifications = async () => {
268
+ if (supportStatus()) {
269
+ localStorage.removeItem(enabledStorageKey);
270
+ return false;
271
+ }
272
+ if ((Notification.permission === "default" ? await Notification.requestPermission() : Notification.permission) !== "granted") {
273
+ localStorage.removeItem(enabledStorageKey);
274
+ return false;
275
+ }
276
+ await reconcileBackgroundNotifications();
277
+ localStorage.setItem(enabledStorageKey, "true");
278
+ return true;
279
+ };
280
+ const disableBackgroundNotifications = async () => {
281
+ localStorage.removeItem(enabledStorageKey);
282
+ await removeCurrentSubscription();
283
+ };
284
+ const reconciledStatus = async () => {
285
+ const unavailable = supportStatus();
286
+ if (unavailable) {
287
+ await disableBackgroundNotifications().catch(() => void 0);
288
+ return unavailable;
289
+ }
290
+ if (Notification.permission !== "granted") {
291
+ await disableBackgroundNotifications().catch(() => void 0);
292
+ return Notification.permission === "denied" ? "denied" : "disabled";
293
+ }
294
+ if (localStorage.getItem(enabledStorageKey) !== "true") {
295
+ await disableBackgroundNotifications();
296
+ return "disabled";
297
+ }
298
+ await reconcileBackgroundNotifications();
299
+ return "enabled";
300
+ };
301
+ //#endregion
302
+ //#region src/internal/client/client-definition.ts
303
+ const shortcutBindingSchema = z.union([keyBindingSchema$1, z.array(keyBindingSchema$1).min(2)]);
304
+ const commandBindingSchema = z.union([shortcutBindingSchema, z.object({
305
+ binding: shortcutBindingSchema,
306
+ when: z.object({ media: z.string().trim().min(1) })
307
+ })]);
308
+ const chordPrefixSchema = z.object({
309
+ binding: keyBindingSchema$1,
310
+ unmatched: z.literal("replay-to-focused-input")
311
+ });
312
+ const formatShortcutBinding = (binding) => typeof binding === "string" ? formatKeyBinding$1(binding) : binding.map(formatKeyBinding$1).join(" ");
313
+ const validateBindings = (bindings) => bindings?.forEach((binding) => commandBindingSchema.parse(binding));
314
+ const defineCommandRegistry = () => (commands) => Object.fromEntries(Object.entries(commands).map(([id, command]) => {
315
+ validateBindings(command.defaultBindings);
316
+ return [id, {
317
+ ...command,
318
+ id
319
+ }];
320
+ }));
321
+ const defineOvermuxClient = (definition) => {
322
+ definition.chordPrefixes?.forEach((prefix) => chordPrefixSchema.parse(prefix));
323
+ Object.values(definition.shortcutOverrides ?? {}).forEach(validateBindings);
324
+ return definition;
325
+ };
326
+ const isConditionalShortcutBinding = (binding) => typeof binding !== "string" && !Array.isArray(binding);
327
+ const resolveShortcutBinding = (binding) => isConditionalShortcutBinding(binding) ? binding.binding : binding;
328
+ const configuredCommandBindings = (definition, command) => definition.shortcutOverrides?.[command.id] ?? command.defaultBindings ?? [];
329
+ const activeCommandBindings = (bindings, matchesMedia) => bindings.flatMap((binding) => !isConditionalShortcutBinding(binding) || matchesMedia(binding.when.media) ? [resolveShortcutBinding(binding)] : []);
330
+ const shortcutMediaQueries = (definition) => [...new Set(Object.values(definition.commands).flatMap((command) => configuredCommandBindings(definition, command).flatMap((binding) => isConditionalShortcutBinding(binding) ? [binding.when.media] : [])))];
331
+ //#endregion
332
+ //#region src/internal/client/commands.tsx
333
+ const skipToken = Symbol("overmux command registration");
334
+ const RuntimeContext = createContext(void 0);
335
+ const useRuntime = () => {
336
+ const runtime = useContext(RuntimeContext);
337
+ if (!runtime) throw new Error("Overmux hooks must be used inside an active Overmux client runtime");
338
+ return runtime;
339
+ };
340
+ const executeRegistration = async (command, registration, overmuxServerApi) => {
341
+ const params = command.params ? command.params.parse(registration.params) : void 0;
342
+ if (registration.run) {
343
+ await registration.run();
344
+ return;
345
+ }
346
+ if (typeof command.run !== "function") throw new Error(`Command has no implementation: ${command.id}`);
347
+ await command.run({
348
+ overmuxServerApi,
349
+ params
350
+ });
351
+ };
352
+ const createRegisteredCommand = ({ command, getRegistration, overmuxServerApi }) => ({
353
+ command,
354
+ element: () => {
355
+ const registration = getRegistration();
356
+ return registration === skipToken ? void 0 : registration.element?.current;
357
+ },
358
+ enabled: () => {
359
+ const registration = getRegistration();
360
+ return registration === skipToken ? false : registration.enabled ?? true;
361
+ },
362
+ execute: () => {
363
+ const registration = getRegistration();
364
+ return registration === skipToken ? Promise.resolve() : executeRegistration(command, registration, overmuxServerApi);
365
+ }
366
+ });
367
+ const useCommand = (command, registration) => {
368
+ const { overmuxServerApi, refreshCommands, registerCommand } = useRuntime();
369
+ const registrationRef = useRef(registration);
370
+ registrationRef.current = registration;
371
+ const skipped = registration === skipToken;
372
+ useEffect(() => {
373
+ if (skipped) return;
374
+ return registerCommand(createRegisteredCommand({
375
+ command,
376
+ getRegistration: () => registrationRef.current,
377
+ overmuxServerApi
378
+ }));
379
+ }, [
380
+ command,
381
+ overmuxServerApi,
382
+ registerCommand,
383
+ skipped
384
+ ]);
385
+ const enabled = skipped ? false : registration.enabled ?? true;
386
+ useEffect(refreshCommands, [
387
+ skipped ? void 0 : registration.element,
388
+ enabled,
389
+ refreshCommands,
390
+ skipped
391
+ ]);
392
+ };
393
+ const elementDepth$1 = (element) => {
394
+ let depth = 0;
395
+ for (let current = element; current; current = current.parentElement) depth += 1;
396
+ return depth;
397
+ };
398
+ const selectCommandRegistration = (command, registrations, activeElement) => {
399
+ const candidates = [...registrations].filter((registration) => registration.command === command);
400
+ return candidates.filter((registration) => {
401
+ const element = registration.element();
402
+ return Boolean(element && activeElement && element.contains(activeElement));
403
+ }).reduce((selected, candidate) => {
404
+ const selectedElement = selected?.element();
405
+ const candidateElement = candidate.element();
406
+ if (!selectedElement || !candidateElement) return candidate;
407
+ return elementDepth$1(candidateElement) >= elementDepth$1(selectedElement) ? candidate : selected;
408
+ }, void 0) ?? candidates.at(-1);
409
+ };
410
+ const currentActiveElement = () => document.activeElement;
411
+ const selectedRegistration = (command, registrations, getActiveElement = currentActiveElement) => selectCommandRegistration(command, registrations, getActiveElement());
412
+ const executeCommand = async (command, registrations, getActiveElement = currentActiveElement) => {
413
+ const selected = selectedRegistration(command, registrations, getActiveElement);
414
+ if (selected?.enabled()) await selected.execute();
415
+ };
416
+ const createCommandEntries = (definition, registrations, getActiveElement = currentActiveElement, matchesMedia = (media) => typeof window !== "undefined" && window.matchMedia(media).matches) => Object.values(definition.commands).flatMap((command) => {
417
+ const selected = selectedRegistration(command, registrations, getActiveElement);
418
+ return selected ? [{
419
+ bindings: activeCommandBindings(configuredCommandBindings(definition, command), matchesMedia),
420
+ enabled: selected.enabled(),
421
+ execute: () => executeCommand(command, registrations, getActiveElement),
422
+ id: command.id,
423
+ title: command.title
424
+ }] : [];
425
+ });
426
+ const CommandsContext = createContext([]);
427
+ const useCommands = () => useContext(CommandsContext);
428
+ //#endregion
429
+ //#region src/internal/client/overmux-react.ts
430
+ const inputKey = (input) => JSON.stringify(input) ?? "";
431
+ const useCanonicalInput = (input) => {
432
+ const key = inputKey(input);
433
+ return useMemo(() => key ? JSON.parse(key) : void 0, [key]);
434
+ };
435
+ const useResourceById = (id, input, skipped) => {
436
+ const { manifest, overmuxServerApi } = useRuntime();
437
+ const queryClient = useQueryClient();
438
+ const canonicalInput = useCanonicalInput(input);
439
+ const queryKey = useMemo(() => [id, canonicalInput], [canonicalInput, id]);
440
+ const query = useQuery({
441
+ enabled: !skipped,
442
+ queryFn: () => overmuxServerApi.readResource({
443
+ id,
444
+ input: canonicalInput
445
+ }),
446
+ queryKey
447
+ });
448
+ useEffect(() => !skipped && manifest.resources.includes(id) ? overmuxServerApi.subscribeResource({
449
+ id,
450
+ input: canonicalInput,
451
+ onError: () => void queryClient.invalidateQueries({
452
+ exact: true,
453
+ queryKey
454
+ }),
455
+ onInvalidate: () => void queryClient.invalidateQueries({
456
+ exact: true,
457
+ queryKey
458
+ })
459
+ }) : void 0, [
460
+ manifest.resources,
461
+ canonicalInput,
462
+ id,
463
+ overmuxServerApi,
464
+ queryClient,
465
+ queryKey,
466
+ skipped
467
+ ]);
468
+ const refetch = useCallback(() => {
469
+ queryClient.invalidateQueries({
470
+ exact: true,
471
+ queryKey
472
+ });
473
+ }, [queryClient, queryKey]);
474
+ if (skipped) return;
475
+ if (query.status === "pending") return {
476
+ refetch,
477
+ status: "pending"
478
+ };
479
+ if (query.status === "error") return query.data === void 0 ? {
480
+ error: query.error,
481
+ refetch,
482
+ status: "error"
483
+ } : {
484
+ data: query.data,
485
+ error: query.error,
486
+ refetch,
487
+ status: "error"
488
+ };
489
+ return {
490
+ data: query.data,
491
+ refetch,
492
+ status: "success"
493
+ };
494
+ };
495
+ const useOperationById = (id, signal) => {
496
+ const { overmuxServerApi } = useRuntime();
497
+ return useMutation({ mutationFn: (input) => overmuxServerApi.executeOperation(id, input, signal) });
498
+ };
499
+ const useStreamById = (id, input) => {
500
+ const { manifest, overmuxServerApi } = useRuntime();
501
+ const streamAvailable = manifest.streams.includes(id);
502
+ const [state, setState] = useState({ status: "opening" });
503
+ const listeners = useMemo(() => /* @__PURE__ */ new Set(), []);
504
+ const canonicalInput = useCanonicalInput(input);
505
+ const streamRef = useRef(void 0);
506
+ useEffect(() => {
507
+ streamRef.current?.close();
508
+ streamRef.current = void 0;
509
+ if (!streamAvailable) {
510
+ setState({ status: "closed" });
511
+ return;
512
+ }
513
+ setState({ status: "opening" });
514
+ const stream = overmuxServerApi.openStream({
515
+ id,
516
+ input: canonicalInput,
517
+ onClose: () => setState({ status: "closed" }),
518
+ onError: (error) => setState({
519
+ error,
520
+ status: "closed"
521
+ }),
522
+ onOpen: () => setState({ status: "open" }),
523
+ onMessage: (message) => listeners.forEach((listener) => listener(message))
524
+ });
525
+ streamRef.current = stream;
526
+ return () => {
527
+ stream.close();
528
+ if (streamRef.current === stream) streamRef.current = void 0;
529
+ };
530
+ }, [
531
+ canonicalInput,
532
+ id,
533
+ listeners,
534
+ overmuxServerApi,
535
+ streamAvailable
536
+ ]);
537
+ const close = useCallback(() => {
538
+ streamRef.current?.close();
539
+ streamRef.current = void 0;
540
+ setState({ status: "closed" });
541
+ }, []);
542
+ const send = useCallback((message) => streamRef.current?.send(message) ?? false, []);
543
+ const subscribe = useCallback((listener) => {
544
+ listeners.add(listener);
545
+ return () => listeners.delete(listener);
546
+ }, [listeners]);
547
+ return {
548
+ close,
549
+ error: streamAvailable ? state.error : /* @__PURE__ */ new Error(`Stream is not available: ${id}`),
550
+ send,
551
+ status: streamAvailable ? state.status : "closed",
552
+ subscribe
553
+ };
554
+ };
555
+ const createOvermuxHooks = () => ({
556
+ useOperation: ({ id, signal }) => {
557
+ return useOperationById(id, signal);
558
+ },
559
+ useResource: (({ id, input }) => useResourceById(id, input, input === skipToken)),
560
+ useStream: ({ id, input }) => useStreamById(id, input)
561
+ });
562
+ //#endregion
563
+ //#region src/internal/client/theme-scope.tsx
564
+ const PortalRootContext = createContext(void 0);
565
+ const useOvermuxPortalContainer = (external) => {
566
+ const scopeRoot = useContext(PortalRootContext);
567
+ if (external) return external.container;
568
+ if (scopeRoot !== void 0) return scopeRoot;
569
+ return typeof document === "undefined" ? null : document.body;
570
+ };
571
+ const OvermuxPortal = ({ children, external }) => {
572
+ const container = useOvermuxPortalContainer(external);
573
+ return container ? createPortal(children, container) : null;
574
+ };
575
+ const OvermuxThemeScope = ({ children, className, contrast = "auto", scheme = "system", style }) => {
576
+ const [portalRoot, setPortalRoot] = useState(null);
577
+ const capturePortalRoot = useCallback((element) => {
578
+ setPortalRoot(element);
579
+ }, []);
580
+ return /* @__PURE__ */ jsxs("div", {
581
+ className,
582
+ "data-om-contrast": contrast,
583
+ "data-om-scheme": scheme,
584
+ "data-om-scope": "",
585
+ style,
586
+ children: [/* @__PURE__ */ jsx(PortalRootContext.Provider, {
587
+ value: portalRoot,
588
+ children
589
+ }), /* @__PURE__ */ jsx("div", {
590
+ "data-om-overlay-root": "",
591
+ ref: capturePortalRoot
592
+ })]
593
+ });
594
+ };
595
+ //#endregion
596
+ //#region src/internal/client/browser-api.ts
597
+ const availableName = (names, kind, name) => {
598
+ if (!names.includes(name)) throw new Error(`${kind} is not available: ${name}`);
599
+ return name;
600
+ };
601
+ const createOvermuxServerApi = ({ manifest, transport }) => ({
602
+ executeOperation: (id, input, signal) => {
603
+ if (!manifest.operations.includes(id)) throw new Error(`Operation is not available: ${id}`);
604
+ return transport.invokeOperation({
605
+ input,
606
+ name: id,
607
+ signal
608
+ });
609
+ },
610
+ openStream: ({ id, ...input }) => transport.openStream({
611
+ ...input,
612
+ streamName: availableName(manifest.streams, "Stream", id)
613
+ }),
614
+ readResource: ({ id, input }) => transport.readResource({
615
+ input,
616
+ resourceName: availableName(manifest.resources, "Resource", id)
617
+ }),
618
+ subscribeResource: ({ id, ...input }) => transport.subscribeResource({
619
+ ...input,
620
+ resourceName: availableName(manifest.resources, "Resource", id)
621
+ })
622
+ });
623
+ //#endregion
624
+ //#region src/internal/client/host/browser-log-forwarding.ts
625
+ const maxStringLength = 2e3;
626
+ const truncate = (value, length = maxStringLength) => value.length <= length ? value : `${value.slice(0, length)}…[truncated]`;
627
+ const serializeValue = (value, seen, depth = 0) => {
628
+ if (typeof value === "string") return truncate(value);
629
+ if (value === null || typeof value === "boolean" || typeof value === "number") return value;
630
+ if (typeof value === "bigint") return `${value}n`;
631
+ if (typeof value === "undefined" || typeof value === "symbol") return String(value);
632
+ if (typeof value === "function") return `[Function ${value.name || "anonymous"}]`;
633
+ if (depth >= 4) return "[Max depth]";
634
+ if (typeof value !== "object") return truncate(String(value));
635
+ if (seen.has(value)) return "[Circular]";
636
+ seen.add(value);
637
+ if (value instanceof Error) return {
638
+ message: truncate(value.message),
639
+ name: value.name,
640
+ ...value.stack ? { stack: truncate(value.stack, 8e3) } : {}
641
+ };
642
+ if (Array.isArray(value)) return value.slice(0, 25).map((item) => serializeValue(item, seen, depth + 1));
643
+ try {
644
+ return Object.fromEntries(Object.entries(value).slice(0, 25).map(([key, item]) => [key, serializeValue(item, seen, depth + 1)]));
645
+ } catch {
646
+ return `[Unserializable ${Object.prototype.toString.call(value)}]`;
647
+ }
648
+ };
649
+ const serializeBrowserLogArguments = (values) => {
650
+ const seen = /* @__PURE__ */ new WeakSet();
651
+ const serialized = values.map((value) => serializeValue(value, seen));
652
+ const json = JSON.stringify(serialized);
653
+ return truncate(json, 16e3);
654
+ };
655
+ const diagnosticUrl = () => {
656
+ try {
657
+ return `${location.origin}${location.pathname}`;
658
+ } catch {
659
+ return;
660
+ }
661
+ };
662
+ const stackOf = (values) => {
663
+ const error = values.find((value) => value instanceof Error);
664
+ return error instanceof Error && error.stack ? truncate(error.stack, 8e3) : void 0;
665
+ };
666
+ const installBrowserLogForwarding = (report) => {
667
+ const methods = [
668
+ "log",
669
+ "info",
670
+ "warn",
671
+ "error",
672
+ "debug"
673
+ ];
674
+ const originals = Object.fromEntries(methods.map((level) => [level, console[level].bind(console)]));
675
+ let reporting = false;
676
+ const emit = (level, values, stack) => {
677
+ if (reporting) return;
678
+ reporting = true;
679
+ try {
680
+ const serializedArguments = serializeBrowserLogArguments(values);
681
+ report({
682
+ arguments: serializedArguments,
683
+ level,
684
+ message: truncate(values.map((value) => typeof value === "string" ? value : serializedArguments).join(" "), 4e3),
685
+ ...stack ? { stack: truncate(stack, 8e3) } : {},
686
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
687
+ type: "client-diagnostic",
688
+ ...diagnosticUrl() ? { url: diagnosticUrl() } : {}
689
+ });
690
+ } finally {
691
+ reporting = false;
692
+ }
693
+ };
694
+ methods.forEach((level) => {
695
+ console[level] = (...values) => {
696
+ originals[level](...values);
697
+ emit(level, values, stackOf(values));
698
+ };
699
+ });
700
+ const onError = (event) => emit("error", [event.message], event.error?.stack);
701
+ const onRejection = (event) => emit("error", ["Unhandled rejection", event.reason], stackOf([event.reason]));
702
+ window.addEventListener("error", onError);
703
+ window.addEventListener("unhandledrejection", onRejection);
704
+ return () => {
705
+ methods.forEach((level) => {
706
+ console[level] = originals[level];
707
+ });
708
+ window.removeEventListener("error", onError);
709
+ window.removeEventListener("unhandledrejection", onRejection);
710
+ };
711
+ };
712
+ //#endregion
713
+ //#region src/internal/client/host/notification-forwarding.ts
714
+ const installNotificationForwarding = (transport) => transport.subscribeNotification((event) => {
715
+ const notification = notificationEventSchema.safeParse(event);
716
+ if (notification.success) window.overmuxHost?.notifications?.show(notification.data);
717
+ });
718
+ //#endregion
719
+ //#region src/internal/client/shortcuts.tsx
720
+ const bindingSequence = (binding) => typeof binding === "string" ? [binding] : [...binding];
721
+ const normalizedBinding = (binding) => bindingSequence(binding).map((key) => platformBinding(key));
722
+ const sequenceMatches = (binding, pending, event) => {
723
+ const sequence = normalizedBinding(binding);
724
+ return sequence.length > pending.length && pending.every((key, index) => sequence[index] === key) && matchesKeyBinding$1(sequence[pending.length], event);
725
+ };
726
+ const isTextInput = (element) => element instanceof HTMLInputElement || element instanceof HTMLSelectElement || element instanceof HTMLTextAreaElement || element instanceof HTMLElement && (element.isContentEditable || element.getAttribute("role") === "textbox");
727
+ const acceptsTextInput = (bindings) => {
728
+ if (!(document.activeElement instanceof Element)) return false;
729
+ const terminal = document.activeElement.closest(".xterm");
730
+ if (!terminal) return false;
731
+ if (terminal.closest("[data-copy-mode]")) return true;
732
+ return bindings.some((binding) => {
733
+ const sequence = bindingSequence(binding);
734
+ return sequence.length > 1 || sequence[0]?.startsWith("F") || sequence[0]?.includes("+");
735
+ });
736
+ };
737
+ const flattenShortcuts = (definition, matchesMedia) => Object.values(definition.commands).flatMap((command) => {
738
+ const bindings = activeCommandBindings(configuredCommandBindings(definition, command), matchesMedia);
739
+ return bindings.length ? [{
740
+ bindings,
741
+ command
742
+ }] : [];
743
+ });
744
+ const canRunShortcut = (shortcut, event, registrations) => {
745
+ const selected = selectCommandRegistration(shortcut.command, registrations, document.activeElement);
746
+ return Boolean(selected?.enabled() && !event.repeat && !(isTextInput(document.activeElement) && !acceptsTextInput(shortcut.bindings)));
747
+ };
748
+ const runShortcut = (shortcut, event, registrations) => {
749
+ if (!canRunShortcut(shortcut, event, registrations)) return false;
750
+ executeCommand(shortcut.command, registrations);
751
+ event.preventDefault();
752
+ return true;
753
+ };
754
+ const ShortcutInputTargetsContext = createContext(void 0);
755
+ const useShortcutInputTarget = (target) => {
756
+ const register = useContext(ShortcutInputTargetsContext);
757
+ const { container, input } = target;
758
+ useEffect(() => register?.({
759
+ container,
760
+ input
761
+ }), [
762
+ container,
763
+ input,
764
+ register
765
+ ]);
766
+ };
767
+ const elementDepth = (element) => {
768
+ let depth = 0;
769
+ for (let current = element; current; current = current.parentElement) depth += 1;
770
+ return depth;
771
+ };
772
+ const selectShortcutInputTarget = (targets, activeElement) => [...targets].filter((target) => {
773
+ const container = target.container.current;
774
+ return Boolean(container && activeElement && container.contains(activeElement));
775
+ }).reduce((selected, candidate) => {
776
+ const selectedElement = selected?.container.current;
777
+ const candidateElement = candidate.container.current;
778
+ if (!selectedElement || !candidateElement) return candidate;
779
+ return elementDepth(candidateElement) >= elementDepth(selectedElement) ? candidate : selected;
780
+ }, void 0);
781
+ const replayedKeyboardEvents = /* @__PURE__ */ new WeakSet();
782
+ const keyboardEventProperties = [
783
+ "charCode",
784
+ "keyCode",
785
+ "which"
786
+ ];
787
+ const replayKeyboardEvent = (target, source) => {
788
+ const replay = new KeyboardEvent(source.type, {
789
+ altKey: source.altKey,
790
+ bubbles: true,
791
+ cancelable: true,
792
+ code: source.code,
793
+ composed: true,
794
+ ctrlKey: source.ctrlKey,
795
+ key: source.key,
796
+ location: source.location,
797
+ metaKey: source.metaKey,
798
+ repeat: source.repeat,
799
+ shiftKey: source.shiftKey
800
+ });
801
+ keyboardEventProperties.forEach((property) => Object.defineProperty(replay, property, { value: source[property] }));
802
+ replayedKeyboardEvents.add(replay);
803
+ target.dispatchEvent(replay);
804
+ };
805
+ const replayChord = (target, events) => {
806
+ const input = target?.input.current;
807
+ if (!input) return;
808
+ events.forEach((event) => replayKeyboardEvent(input, event));
809
+ };
810
+ const ShortcutHost = ({ children, definition, registrations, registrationRevision }) => {
811
+ const [, setFocusRevision] = useState(0);
812
+ const [inputTargets] = useState(() => /* @__PURE__ */ new Set());
813
+ const [mediaRevision, setMediaRevision] = useState(0);
814
+ const registerInputTarget = useCallback((target) => {
815
+ inputTargets.add(target);
816
+ return () => {
817
+ inputTargets.delete(target);
818
+ };
819
+ }, [inputTargets]);
820
+ useEffect(() => {
821
+ const refreshMedia = () => setMediaRevision((revision) => revision + 1);
822
+ const queries = shortcutMediaQueries(definition).map((media) => window.matchMedia(media));
823
+ queries.forEach((query) => query.addEventListener("change", refreshMedia));
824
+ return () => queries.forEach((query) => query.removeEventListener("change", refreshMedia));
825
+ }, [definition]);
826
+ useEffect(() => {
827
+ const refreshFocus = () => setFocusRevision((revision) => revision + 1);
828
+ window.addEventListener("focusin", refreshFocus);
829
+ return () => window.removeEventListener("focusin", refreshFocus);
830
+ }, []);
831
+ useEffect(() => {
832
+ const shortcuts = flattenShortcuts(definition, (media) => window.matchMedia(media).matches);
833
+ let pending = [];
834
+ let pendingEvents = [];
835
+ let pendingTarget;
836
+ let timeout;
837
+ const clearPending = () => {
838
+ pending = [];
839
+ pendingEvents = [];
840
+ pendingTarget = void 0;
841
+ if (timeout !== void 0) {
842
+ window.clearTimeout(timeout);
843
+ timeout = void 0;
844
+ }
845
+ };
846
+ const replayPending = (events = pendingEvents) => {
847
+ replayChord(pendingTarget, events);
848
+ clearPending();
849
+ };
850
+ const scheduleTimeout = () => {
851
+ if (timeout !== void 0) window.clearTimeout(timeout);
852
+ timeout = window.setTimeout(() => replayPending(), 1e3);
853
+ };
854
+ const onKeyDown = (event) => {
855
+ if (replayedKeyboardEvents.has(event)) return;
856
+ if (pending.length && event.key === "Escape") {
857
+ event.preventDefault();
858
+ event.stopPropagation();
859
+ clearPending();
860
+ return;
861
+ }
862
+ const matches = shortcuts.flatMap((shortcut) => shortcut.bindings.filter((binding) => sequenceMatches(binding, pending, event) && canRunShortcut(shortcut, event, registrations)).map((binding) => ({
863
+ binding,
864
+ shortcut
865
+ })));
866
+ if (!matches.length) {
867
+ if (pending.length) {
868
+ event.preventDefault();
869
+ event.stopPropagation();
870
+ replayPending([...pendingEvents, event]);
871
+ }
872
+ return;
873
+ }
874
+ const complete = matches.find(({ binding }) => normalizedBinding(binding).length === pending.length + 1);
875
+ if (complete) {
876
+ clearPending();
877
+ if (runShortcut(complete.shortcut, event, registrations)) event.stopPropagation();
878
+ return;
879
+ }
880
+ if (!pending.length) pendingTarget = definition.chordPrefixes?.some((prefix) => matchesKeyBinding$1(prefix.binding, event)) ? selectShortcutInputTarget(inputTargets, document.activeElement) : void 0;
881
+ pending = [...pending, normalizedBinding(matches[0].binding)[pending.length]];
882
+ pendingEvents = [...pendingEvents, event];
883
+ event.preventDefault();
884
+ event.stopPropagation();
885
+ scheduleTimeout();
886
+ };
887
+ window.addEventListener("keydown", onKeyDown, { capture: true });
888
+ return () => {
889
+ window.removeEventListener("keydown", onKeyDown, { capture: true });
890
+ if (timeout !== void 0) window.clearTimeout(timeout);
891
+ };
892
+ }, [
893
+ definition,
894
+ inputTargets,
895
+ mediaRevision,
896
+ registrationRevision,
897
+ registrations
898
+ ]);
899
+ const commands = createCommandEntries(definition, registrations, () => document.activeElement, (media) => window.matchMedia(media).matches);
900
+ return /* @__PURE__ */ jsx(ShortcutInputTargetsContext.Provider, {
901
+ value: registerInputTarget,
902
+ children: /* @__PURE__ */ jsx(CommandsContext.Provider, {
903
+ value: commands,
904
+ children
905
+ })
906
+ });
907
+ };
908
+ //#endregion
909
+ //#region src/internal/client/websocket.ts
910
+ const maxWebSocketBufferedBytes = 1048576;
911
+ const socketUrl = () => `${location.protocol === "https:" ? "wss:" : "ws:"}//${location.host}/api/socket`;
912
+ const operationId = () => {
913
+ const randomUUID = crypto.randomUUID?.();
914
+ if (randomUUID) return randomUUID;
915
+ const bytes = crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(16));
916
+ bytes[6] = bytes[6] & 15 | 64;
917
+ bytes[8] = bytes[8] & 63 | 128;
918
+ const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
919
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
920
+ };
921
+ const disconnected = () => /* @__PURE__ */ new Error("Overmux transport disconnected.");
922
+ const createWebSocketTransport = ({ createSocket = () => new WebSocket(socketUrl()) } = {}) => {
923
+ let active = false;
924
+ let disposed = false;
925
+ let reconnectDelay = 250;
926
+ let reconnectTimer;
927
+ let socket;
928
+ let status = "disconnected";
929
+ const diagnosticQueue = [];
930
+ const requests = /* @__PURE__ */ new Map();
931
+ const subscriptions = /* @__PURE__ */ new Map();
932
+ const streams = /* @__PURE__ */ new Map();
933
+ const lifecycleListeners = /* @__PURE__ */ new Set();
934
+ const notificationListeners = /* @__PURE__ */ new Set();
935
+ const statusListeners = /* @__PURE__ */ new Set();
936
+ const setStatus = (next) => {
937
+ if (status === next) return;
938
+ status = next;
939
+ statusListeners.forEach((listener) => listener(status));
940
+ };
941
+ const send = (message) => {
942
+ if (socket?.readyState !== WebSocket.OPEN) return false;
943
+ if (socket.bufferedAmount > maxWebSocketBufferedBytes) {
944
+ socket.close(1013, "client could not keep up");
945
+ return false;
946
+ }
947
+ socket.send(encodeProtocolMessage(clientProtocolMessageSchema.parse(message)));
948
+ return true;
949
+ };
950
+ const flushDiagnostics = () => {
951
+ const unsent = diagnosticQueue.filter((event) => !send(event));
952
+ diagnosticQueue.splice(0, diagnosticQueue.length, ...unsent);
953
+ };
954
+ const request = ({ input, resourceName }) => new Promise((resolve, reject) => {
955
+ const id = operationId();
956
+ requests.set(id, {
957
+ reject,
958
+ resolve
959
+ });
960
+ if (!send({
961
+ input,
962
+ operationId: id,
963
+ resourceName,
964
+ type: "resource-read"
965
+ })) {
966
+ requests.delete(id);
967
+ reject(disconnected());
968
+ }
969
+ });
970
+ const restore = () => {
971
+ subscriptions.forEach(({ input, onInvalidate, resourceName }, subscriptionId) => {
972
+ if (send({
973
+ input,
974
+ operationId: subscriptionId,
975
+ resourceName,
976
+ subscriptionId,
977
+ type: "resource-subscribe"
978
+ })) onInvalidate();
979
+ });
980
+ streams.forEach((stream, streamId) => {
981
+ if (stream.status === "failed") return;
982
+ stream.status = send({
983
+ input: stream.input,
984
+ operationId: streamId,
985
+ streamId,
986
+ streamName: stream.streamName,
987
+ type: "stream-open"
988
+ }) ? "opening" : "disconnected";
989
+ });
990
+ };
991
+ const rejectRequests = (error) => {
992
+ requests.forEach(({ reject }) => reject(error));
993
+ requests.clear();
994
+ };
995
+ const reportDisconnect = () => {
996
+ const error = disconnected();
997
+ rejectRequests(error);
998
+ subscriptions.forEach(({ onError }) => onError(error));
999
+ streams.forEach((stream) => {
1000
+ stream.pendingMessages.length = 0;
1001
+ if (stream.status !== "failed") stream.status = "disconnected";
1002
+ stream.onError(error);
1003
+ });
1004
+ };
1005
+ const connect = () => {
1006
+ if (!active || disposed || socket) return;
1007
+ setStatus("connecting");
1008
+ const next = createSocket();
1009
+ next.binaryType = "arraybuffer";
1010
+ socket = next;
1011
+ next.addEventListener("open", () => {
1012
+ if (socket !== next) return;
1013
+ reconnectDelay = 250;
1014
+ setStatus("connected");
1015
+ flushDiagnostics();
1016
+ restore();
1017
+ });
1018
+ next.addEventListener("message", (event) => {
1019
+ if (socket !== next) return;
1020
+ let decoded;
1021
+ try {
1022
+ decoded = decodeProtocolFrame(event.data);
1023
+ } catch (cause) {
1024
+ reportDisconnect();
1025
+ console.error("Invalid Overmux transport message", cause);
1026
+ next.close(1002, "invalid protocol message");
1027
+ return;
1028
+ }
1029
+ const parsed = serverProtocolMessageSchema.safeParse(decoded);
1030
+ if (!parsed.success) {
1031
+ reportDisconnect();
1032
+ console.error("Invalid Overmux transport message", parsed.error);
1033
+ next.close(1002, "invalid protocol message");
1034
+ return;
1035
+ }
1036
+ const message = parsed.data;
1037
+ if (message.type === "update-available" || message.type === "restarting") lifecycleListeners.forEach((listener) => listener(message));
1038
+ if (message.type === "notification") notificationListeners.forEach((listener) => listener(message));
1039
+ if (message.type === "resource-invalidated") subscriptions.get(message.subscriptionId)?.onInvalidate();
1040
+ if (message.type === "stream-opened") {
1041
+ const stream = streams.get(message.streamId);
1042
+ if (stream?.status === "opening") {
1043
+ stream.status = "open";
1044
+ if (stream.pendingMessages.splice(0).every(send)) stream.onOpen();
1045
+ }
1046
+ }
1047
+ if (message.type === "stream-output") streams.get(message.streamId)?.onMessage(message.message);
1048
+ if (message.type === "stream-closed") {
1049
+ const stream = streams.get(message.streamId);
1050
+ if (stream) {
1051
+ stream.pendingMessages.length = 0;
1052
+ stream.onClose();
1053
+ streams.delete(message.streamId);
1054
+ }
1055
+ }
1056
+ if (message.type === "error") {
1057
+ if (message.operationId) requests.get(message.operationId)?.reject(new Error(message.message));
1058
+ const stream = streams.get(message.operationId ?? "");
1059
+ if (stream) {
1060
+ stream.pendingMessages.length = 0;
1061
+ stream.status = "failed";
1062
+ stream.onError(new Error(message.message));
1063
+ }
1064
+ subscriptions.get(message.operationId ?? "")?.onError(new Error(message.message));
1065
+ requests.delete(message.operationId ?? "");
1066
+ }
1067
+ if (message.type === "resource-result") {
1068
+ requests.get(message.operationId)?.resolve(message.output);
1069
+ requests.delete(message.operationId);
1070
+ }
1071
+ });
1072
+ next.addEventListener("close", (event) => {
1073
+ if (socket !== next || disposed) return;
1074
+ socket = void 0;
1075
+ reportDisconnect();
1076
+ if (event.code === webSocketCloseCode.authenticationRevoked) {
1077
+ active = false;
1078
+ setStatus("authentication-required");
1079
+ return;
1080
+ }
1081
+ setStatus("disconnected");
1082
+ reconnectTimer = setTimeout(() => {
1083
+ reconnectTimer = void 0;
1084
+ connect();
1085
+ }, reconnectDelay);
1086
+ reconnectDelay = Math.min(reconnectDelay * 2, 5e3);
1087
+ });
1088
+ };
1089
+ return {
1090
+ activate: () => {
1091
+ active = true;
1092
+ connect();
1093
+ },
1094
+ dispose: () => {
1095
+ disposed = true;
1096
+ if (reconnectTimer !== void 0) clearTimeout(reconnectTimer);
1097
+ reportDisconnect();
1098
+ streams.forEach(({ onClose }) => onClose());
1099
+ streams.clear();
1100
+ subscriptions.clear();
1101
+ socket?.close();
1102
+ },
1103
+ openStream: ({ input, onClose, onError, onMessage, onOpen, streamName }) => {
1104
+ const streamId = operationId();
1105
+ const stream = {
1106
+ input,
1107
+ onClose,
1108
+ onError,
1109
+ onMessage,
1110
+ onOpen,
1111
+ pendingMessages: [],
1112
+ status: "disconnected",
1113
+ streamName
1114
+ };
1115
+ streams.set(streamId, stream);
1116
+ stream.status = send({
1117
+ input,
1118
+ operationId: streamId,
1119
+ streamId,
1120
+ streamName,
1121
+ type: "stream-open"
1122
+ }) ? "opening" : "disconnected";
1123
+ if (stream.status === "disconnected") onError(disconnected());
1124
+ return {
1125
+ close: () => {
1126
+ const activeStream = streams.get(streamId);
1127
+ if (activeStream) {
1128
+ activeStream.pendingMessages.length = 0;
1129
+ activeStream.onClose();
1130
+ streams.delete(streamId);
1131
+ }
1132
+ send({
1133
+ streamId,
1134
+ type: "stream-close"
1135
+ });
1136
+ },
1137
+ send: (message) => {
1138
+ const activeStream = streams.get(streamId);
1139
+ if (activeStream?.status !== "open" && activeStream?.status !== "opening") return false;
1140
+ const protocolMessage = streamMessageSchema.parse({
1141
+ message,
1142
+ streamId,
1143
+ type: "stream-message"
1144
+ });
1145
+ if (activeStream.status === "opening") {
1146
+ activeStream.pendingMessages.push(protocolMessage);
1147
+ return true;
1148
+ }
1149
+ return send(protocolMessage);
1150
+ }
1151
+ };
1152
+ },
1153
+ readResource: (input) => request(input),
1154
+ reportDiagnostic: (event) => {
1155
+ if (send(event)) return;
1156
+ diagnosticQueue.push(event);
1157
+ if (diagnosticQueue.length > 100) diagnosticQueue.shift();
1158
+ },
1159
+ subscribeResource: ({ input, onError, onInvalidate, resourceName }) => {
1160
+ const subscriptionId = operationId();
1161
+ subscriptions.set(subscriptionId, {
1162
+ input,
1163
+ onError,
1164
+ onInvalidate,
1165
+ resourceName
1166
+ });
1167
+ if (!send({
1168
+ input,
1169
+ operationId: subscriptionId,
1170
+ resourceName,
1171
+ subscriptionId,
1172
+ type: "resource-subscribe"
1173
+ })) onError(disconnected());
1174
+ return () => {
1175
+ subscriptions.delete(subscriptionId);
1176
+ send({
1177
+ subscriptionId,
1178
+ type: "resource-unsubscribe"
1179
+ });
1180
+ };
1181
+ },
1182
+ subscribeStatus: (listener) => {
1183
+ statusListeners.add(listener);
1184
+ listener(status);
1185
+ return () => statusListeners.delete(listener);
1186
+ },
1187
+ subscribeLifecycle: (listener) => {
1188
+ lifecycleListeners.add(listener);
1189
+ return () => lifecycleListeners.delete(listener);
1190
+ },
1191
+ subscribeNotification: (listener) => {
1192
+ notificationListeners.add(listener);
1193
+ return () => notificationListeners.delete(listener);
1194
+ }
1195
+ };
1196
+ };
1197
+ //#endregion
1198
+ //#region src/internal/client/transport.ts
1199
+ const TransportContext = createContext(void 0);
1200
+ const operationError = async (response) => {
1201
+ const body = await response.text();
1202
+ try {
1203
+ const error = JSON.parse(body).error;
1204
+ if (typeof error === "string") return new Error(error);
1205
+ } catch {}
1206
+ return /* @__PURE__ */ new Error(`Operation request failed: ${response.status} ${response.statusText}`);
1207
+ };
1208
+ const createClientTransport = ({ createSocket, fetch: fetchRequest = globalThis.fetch } = {}) => ({
1209
+ ...createWebSocketTransport({ createSocket }),
1210
+ invokeOperation: async ({ input, name, signal }) => {
1211
+ const response = await fetchRequest(`/api/operations/${encodeURIComponent(name)}`, {
1212
+ body: input === void 0 ? void 0 : JSON.stringify(input),
1213
+ headers: { "content-type": "application/json" },
1214
+ method: "POST",
1215
+ signal
1216
+ });
1217
+ if (!response.ok) throw await operationError(response);
1218
+ return response.status === 204 ? void 0 : response.json();
1219
+ }
1220
+ });
1221
+ //#endregion
1222
+ //#region src/internal/client/host/hosted-pages/hosted-page.tsx
1223
+ const HostedPage = ({ children }) => /* @__PURE__ */ jsx("main", {
1224
+ "data-om-hosted-page": "",
1225
+ children: /* @__PURE__ */ jsx("article", {
1226
+ "data-om-hosted-panel": "",
1227
+ children
1228
+ })
1229
+ });
1230
+ //#endregion
1231
+ //#region src/internal/client/host/hosted-pages/logout-page.tsx
1232
+ const logoutRequests = /* @__PURE__ */ new WeakMap();
1233
+ const minimumLogoutDuration = 3e3;
1234
+ const requestLogout = () => {
1235
+ const existing = logoutRequests.get(document);
1236
+ if (existing) return existing;
1237
+ const request = fetch("/api/auth/logout", { method: "POST" }).then((response) => {
1238
+ if (!response.ok) throw new Error(`Logout failed: ${response.status}`);
1239
+ });
1240
+ logoutRequests.set(document, request);
1241
+ return request;
1242
+ };
1243
+ const minimumDelay = () => new Promise((resolve) => setTimeout(resolve, minimumLogoutDuration));
1244
+ const HostedLogoutPage = () => {
1245
+ const [attempt, setAttempt] = useState(0);
1246
+ const [error, setError] = useState(false);
1247
+ useEffect(() => {
1248
+ let active = true;
1249
+ setError(false);
1250
+ Promise.allSettled([requestLogout(), minimumDelay()]).then(([logout]) => {
1251
+ if (!active) return;
1252
+ if (logout?.status === "fulfilled") {
1253
+ location.replace("/login");
1254
+ return;
1255
+ }
1256
+ setError(true);
1257
+ });
1258
+ return () => {
1259
+ active = false;
1260
+ };
1261
+ }, [attempt]);
1262
+ return /* @__PURE__ */ jsxs(HostedPage, { children: [/* @__PURE__ */ jsx("h1", { children: error ? "Log out failed" : "Logging out…" }), error ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("p", {
1263
+ role: "alert",
1264
+ children: "Overmux could not log you out. Check your connection and try again."
1265
+ }), /* @__PURE__ */ jsx("button", {
1266
+ "data-om-hosted-retry": "",
1267
+ onClick: () => {
1268
+ logoutRequests.delete(document);
1269
+ setAttempt((current) => current + 1);
1270
+ },
1271
+ type: "button",
1272
+ children: "Retry"
1273
+ })] }) : /* @__PURE__ */ jsx("p", { children: "Please wait while Overmux ends your session." })] });
1274
+ };
1275
+ //#endregion
1276
+ //#region src/internal/client/host/hosted-pages/browser-notification-settings.tsx
1277
+ const statusMessage = (status) => {
1278
+ return {
1279
+ checking: "Checking background notification status…",
1280
+ denied: "Background notifications are blocked in this browser's site settings.",
1281
+ disabled: "Background notifications are disabled for this browser.",
1282
+ enabled: "Background notifications are enabled for this browser.",
1283
+ error: "Background notification settings could not be updated. Try again.",
1284
+ "ios-home-screen": "On iPhone or iPad, add Overmux to the Home Screen before enabling background notifications.",
1285
+ unsupported: "Background notifications are not supported in this browser."
1286
+ }[status];
1287
+ };
1288
+ const BrowserNotificationSettings = () => {
1289
+ const [status, setStatus] = useState("checking");
1290
+ useEffect(() => {
1291
+ let active = true;
1292
+ const reconcile = () => {
1293
+ reconciledStatus().then((next) => {
1294
+ if (active) setStatus(next);
1295
+ }).catch(() => {
1296
+ if (active) setStatus("error");
1297
+ });
1298
+ };
1299
+ const onVisibilityChange = () => {
1300
+ if (document.visibilityState === "visible") reconcile();
1301
+ };
1302
+ reconcile();
1303
+ document.addEventListener("visibilitychange", onVisibilityChange);
1304
+ navigator.serviceWorker?.addEventListener("controllerchange", reconcile);
1305
+ return () => {
1306
+ active = false;
1307
+ document.removeEventListener("visibilitychange", onVisibilityChange);
1308
+ navigator.serviceWorker?.removeEventListener("controllerchange", reconcile);
1309
+ };
1310
+ }, []);
1311
+ const enable = useCallback(async () => {
1312
+ try {
1313
+ setStatus("checking");
1314
+ const enabled = await enableBackgroundNotifications();
1315
+ setStatus(enabled ? "enabled" : Notification.permission === "denied" ? "denied" : "disabled");
1316
+ } catch {
1317
+ setStatus("error");
1318
+ }
1319
+ }, []);
1320
+ const disable = useCallback(async () => {
1321
+ try {
1322
+ setStatus("checking");
1323
+ await disableBackgroundNotifications();
1324
+ setStatus("disabled");
1325
+ } catch {
1326
+ setStatus("error");
1327
+ }
1328
+ }, []);
1329
+ const canEnable = status === "disabled" || status === "error";
1330
+ const canDisable = status === "enabled" || status === "error";
1331
+ return /* @__PURE__ */ jsxs("div", {
1332
+ "data-om-browser-notifications": "",
1333
+ children: [
1334
+ /* @__PURE__ */ jsx("h3", { children: "Browser notifications" }),
1335
+ /* @__PURE__ */ jsx("p", {
1336
+ "aria-live": "polite",
1337
+ "data-om-background-notifications-status": "",
1338
+ children: statusMessage(status)
1339
+ }),
1340
+ /* @__PURE__ */ jsxs("div", {
1341
+ "data-om-hosted-actions": "",
1342
+ children: [canEnable ? /* @__PURE__ */ jsx("button", {
1343
+ "data-om-background-notifications-enable": "",
1344
+ onClick: enable,
1345
+ type: "button",
1346
+ children: "Enable"
1347
+ }) : null, canDisable ? /* @__PURE__ */ jsx("button", {
1348
+ "data-om-background-notifications-disable": "",
1349
+ onClick: disable,
1350
+ type: "button",
1351
+ children: "Disable"
1352
+ }) : null]
1353
+ })
1354
+ ]
1355
+ });
1356
+ };
1357
+ //#endregion
1358
+ //#region src/internal/client/host/hosted-pages/settings-page.tsx
1359
+ const DesktopNotificationSettings = () => /* @__PURE__ */ jsxs("div", {
1360
+ "data-om-desktop-notifications": "",
1361
+ children: [/* @__PURE__ */ jsx("h3", { children: "Desktop notifications" }), /* @__PURE__ */ jsx("p", { children: "Notifications are delivered through the Overmux desktop app and managed by the OS. Change notification permissions and preferences in your operating system's settings." })]
1362
+ });
1363
+ const HostedSettingsPage = () => {
1364
+ const returnTo = resolveOvermuxReturnTo(new URLSearchParams(location.search).get("returnTo"));
1365
+ const desktopNotifications = Boolean(window.overmuxHost?.notifications);
1366
+ return /* @__PURE__ */ jsxs(HostedPage, { children: [
1367
+ /* @__PURE__ */ jsxs("header", {
1368
+ "data-om-hosted-header": "",
1369
+ children: [/* @__PURE__ */ jsx("h1", { children: "Overmux settings" }), /* @__PURE__ */ jsx("a", {
1370
+ href: returnTo,
1371
+ children: "Back to Overmux"
1372
+ })]
1373
+ }),
1374
+ /* @__PURE__ */ jsxs("section", {
1375
+ "data-om-hosted-section": "",
1376
+ children: [/* @__PURE__ */ jsx("h2", { children: "Notifications" }), desktopNotifications ? /* @__PURE__ */ jsx(DesktopNotificationSettings, {}) : /* @__PURE__ */ jsx(BrowserNotificationSettings, {})]
1377
+ }),
1378
+ /* @__PURE__ */ jsxs("section", {
1379
+ "data-om-hosted-section": "",
1380
+ children: [
1381
+ /* @__PURE__ */ jsx("h2", { children: "Session" }),
1382
+ /* @__PURE__ */ jsx("p", { children: "End this Overmux session on this device." }),
1383
+ /* @__PURE__ */ jsx("a", {
1384
+ "data-om-hosted-button": "",
1385
+ href: overmuxLogoutPath,
1386
+ children: "Log out"
1387
+ })
1388
+ ]
1389
+ })
1390
+ ] });
1391
+ };
1392
+ //#endregion
1393
+ //#region src/internal/client/host/recovery-screen.tsx
1394
+ const RecoveryScreen = ({ error }) => /* @__PURE__ */ jsx("main", {
1395
+ "data-om-recovery": "",
1396
+ children: /* @__PURE__ */ jsxs("section", {
1397
+ "data-om-recovery-panel": "",
1398
+ children: [
1399
+ /* @__PURE__ */ jsx("h1", {
1400
+ "data-om-recovery-title": "",
1401
+ children: "Overmux recovery"
1402
+ }),
1403
+ error ? /* @__PURE__ */ jsx("pre", {
1404
+ "data-om-recovery-error": "",
1405
+ children: error
1406
+ }) : null,
1407
+ /* @__PURE__ */ jsx("button", {
1408
+ "data-om-recovery-reload": "",
1409
+ onClick: () => location.reload(),
1410
+ type: "button",
1411
+ children: "Reload"
1412
+ })
1413
+ ]
1414
+ })
1415
+ });
1416
+ //#endregion
1417
+ //#region ../lib/dist/index.js
1418
+ const delay = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
1419
+ //#endregion
1420
+ //#region src/internal/client/host/update-popover.tsx
1421
+ const reduceUpdateState = (_state, action) => {
1422
+ if (action.type === "update-available") return { type: "available" };
1423
+ if (action.type === "restart-requested") return { type: "requesting" };
1424
+ if (action.type === "restart-failed") return {
1425
+ type: "available",
1426
+ error: action.error
1427
+ };
1428
+ return { type: "restarting" };
1429
+ };
1430
+ const pollUntilHealthy = async (signal) => {
1431
+ if (signal.aborted) return;
1432
+ try {
1433
+ if ((await fetch("/api/health", {
1434
+ cache: "no-store",
1435
+ signal
1436
+ })).ok) {
1437
+ location.reload();
1438
+ return;
1439
+ }
1440
+ } catch {}
1441
+ await delay(250);
1442
+ return pollUntilHealthy(signal);
1443
+ };
1444
+ const ReconnectingScreen = () => /* @__PURE__ */ jsx("main", {
1445
+ "aria-live": "polite",
1446
+ "data-om-reconnecting": "",
1447
+ role: "status",
1448
+ children: /* @__PURE__ */ jsxs("section", {
1449
+ "data-om-reconnecting-panel": "",
1450
+ children: [/* @__PURE__ */ jsx("h1", {
1451
+ "data-om-reconnecting-title": "",
1452
+ children: "Reconnecting"
1453
+ }), /* @__PURE__ */ jsx("p", {
1454
+ "data-om-reconnecting-message": "",
1455
+ children: "Reconnecting to Overmux. Your workspace will resume shortly."
1456
+ })]
1457
+ })
1458
+ });
1459
+ const useUpdateState = (transport) => {
1460
+ const [state, dispatch] = useReducer(reduceUpdateState, { type: "current" });
1461
+ useEffect(() => transport.subscribeLifecycle((event) => dispatch(event)), [transport]);
1462
+ useEffect(() => {
1463
+ if (state.type !== "restarting") return;
1464
+ const controller = new AbortController();
1465
+ pollUntilHealthy(controller.signal);
1466
+ return () => controller.abort();
1467
+ }, [state.type]);
1468
+ return {
1469
+ requestRestart: useCallback(async () => {
1470
+ dispatch({ type: "restart-requested" });
1471
+ try {
1472
+ const response = await fetch("/api/restart", { method: "POST" });
1473
+ if (!response.ok) throw new Error(`Restart request failed: ${response.status}`);
1474
+ dispatch({ type: "restarting" });
1475
+ } catch (cause) {
1476
+ const error = cause instanceof Error ? cause.message : String(cause);
1477
+ dispatch({
1478
+ type: "restart-failed",
1479
+ error
1480
+ });
1481
+ }
1482
+ }, []),
1483
+ state
1484
+ };
1485
+ };
1486
+ const useConnectionState = (transport) => {
1487
+ const connected = useRef(false);
1488
+ const [connection, setConnection] = useState("initial");
1489
+ useEffect(() => transport.subscribeStatus((status) => {
1490
+ if (status === "connected") {
1491
+ connected.current = true;
1492
+ setConnection("connected");
1493
+ return;
1494
+ }
1495
+ if (!connected.current) return;
1496
+ connected.current = false;
1497
+ setConnection("reconnecting");
1498
+ }), [transport]);
1499
+ return connection;
1500
+ };
1501
+ const UpdatePopover = ({ transport }) => {
1502
+ const connection = useConnectionState(transport);
1503
+ const { requestRestart, state } = useUpdateState(transport);
1504
+ if (state.type === "restarting") return /* @__PURE__ */ jsx("main", {
1505
+ "aria-live": "polite",
1506
+ "data-om-restarting": "",
1507
+ role: "status",
1508
+ children: /* @__PURE__ */ jsxs("section", {
1509
+ "data-om-restarting-panel": "",
1510
+ children: [/* @__PURE__ */ jsx("h1", {
1511
+ "data-om-restarting-title": "",
1512
+ children: "Restarting Overmux"
1513
+ }), /* @__PURE__ */ jsx("p", {
1514
+ "data-om-restarting-message": "",
1515
+ children: "Waiting for the new version."
1516
+ })]
1517
+ })
1518
+ });
1519
+ if (connection === "reconnecting") return /* @__PURE__ */ jsx(ReconnectingScreen, {});
1520
+ if (state.type === "current") return null;
1521
+ const error = state.type === "available" ? state.error : void 0;
1522
+ return /* @__PURE__ */ jsxs("aside", {
1523
+ "data-om-update": "",
1524
+ role: "status",
1525
+ children: [
1526
+ /* @__PURE__ */ jsx("p", { children: "A new version is available. Reload to update." }),
1527
+ /* @__PURE__ */ jsx("button", {
1528
+ "data-om-update-reload": "",
1529
+ disabled: state.type === "requesting",
1530
+ onClick: () => void requestRestart(),
1531
+ type: "button",
1532
+ children: "Reload"
1533
+ }),
1534
+ error ? /* @__PURE__ */ jsx("p", {
1535
+ "data-om-update-error": "",
1536
+ children: error
1537
+ }) : null
1538
+ ]
1539
+ });
1540
+ };
1541
+ //#endregion
1542
+ //#region src/internal/client/host/overmux-host.tsx
1543
+ var RootErrorBoundary = class extends Component {
1544
+ state = {};
1545
+ static getDerivedStateFromError(error) {
1546
+ return { error };
1547
+ }
1548
+ render() {
1549
+ return this.state.error ? this.props.fallback(this.state.error) : this.props.children;
1550
+ }
1551
+ };
1552
+ var AuthenticationRequiredError = class extends Error {};
1553
+ const redirectToLogin = () => location.replace("/login?reason=session-expired");
1554
+ const loadRuntimeManifest = async () => {
1555
+ const response = await fetch("/api/runtime-manifest", { cache: "no-store" });
1556
+ if (response.status === 401) throw new AuthenticationRequiredError("Authentication required");
1557
+ if (!response.ok) throw new Error(`Runtime manifest request failed: ${response.status}`);
1558
+ return runtimeManifestSchema.parse(await response.json());
1559
+ };
1560
+ const OvermuxRuntime = ({ definition }) => {
1561
+ const safeMode = new URLSearchParams(location.search).get("safe") === "1";
1562
+ const transport = useMemo(createClientTransport, []);
1563
+ const queryClient = useMemo(() => new QueryClient(), []);
1564
+ const logForwardingDisposer = useRef(void 0);
1565
+ const [manifest, setManifest] = useState();
1566
+ const [error, setError] = useState();
1567
+ useEffect(() => {
1568
+ let active = true;
1569
+ loadRuntimeManifest().then((next) => {
1570
+ if (!active) return;
1571
+ setManifest(next);
1572
+ transport.activate();
1573
+ }).catch((cause) => {
1574
+ if (cause instanceof AuthenticationRequiredError) {
1575
+ redirectToLogin();
1576
+ return;
1577
+ }
1578
+ if (active) setError(cause instanceof Error ? cause.message : String(cause));
1579
+ });
1580
+ return () => {
1581
+ active = false;
1582
+ };
1583
+ }, [transport]);
1584
+ const registrations = useMemo(() => /* @__PURE__ */ new Set(), []);
1585
+ const [registrationRevision, setRegistrationRevision] = useState(0);
1586
+ useEffect(() => {
1587
+ if (manifest?.debug && !logForwardingDisposer.current) logForwardingDisposer.current = installBrowserLogForwarding(transport.reportDiagnostic);
1588
+ }, [manifest?.debug, transport]);
1589
+ useEffect(() => transport.subscribeStatus((status) => {
1590
+ if (status === "authentication-required") redirectToLogin();
1591
+ }), [transport]);
1592
+ useEffect(() => () => {
1593
+ logForwardingDisposer.current?.();
1594
+ transport.dispose();
1595
+ }, [transport]);
1596
+ useEffect(() => installNotificationForwarding(transport), [transport]);
1597
+ const refreshCommands = useCallback(() => setRegistrationRevision((revision) => revision + 1), []);
1598
+ const registerCommand = useCallback((registration) => {
1599
+ registrations.add(registration);
1600
+ refreshCommands();
1601
+ return () => {
1602
+ registrations.delete(registration);
1603
+ refreshCommands();
1604
+ };
1605
+ }, [refreshCommands, registrations]);
1606
+ const overmuxServerApi = useMemo(() => manifest ? createOvermuxServerApi({
1607
+ manifest,
1608
+ transport
1609
+ }) : void 0, [manifest, transport]);
1610
+ if (safeMode || !manifest || !overmuxServerApi) return /* @__PURE__ */ jsx(RecoveryScreen, { error });
1611
+ const App = definition.component;
1612
+ return /* @__PURE__ */ jsx(QueryClientProvider, {
1613
+ client: queryClient,
1614
+ children: /* @__PURE__ */ jsx(RuntimeContext.Provider, {
1615
+ value: {
1616
+ manifest,
1617
+ overmuxServerApi,
1618
+ refreshCommands,
1619
+ registerCommand
1620
+ },
1621
+ children: /* @__PURE__ */ jsx(TransportContext.Provider, {
1622
+ value: transport,
1623
+ children: /* @__PURE__ */ jsxs(ShortcutHost, {
1624
+ definition,
1625
+ registrations,
1626
+ registrationRevision,
1627
+ children: [/* @__PURE__ */ jsx(RootErrorBoundary, {
1628
+ fallback: (cause) => /* @__PURE__ */ jsx(RecoveryScreen, { error: cause.message }),
1629
+ children: createElement(App)
1630
+ }), /* @__PURE__ */ jsx(UpdatePopover, { transport })]
1631
+ })
1632
+ })
1633
+ })
1634
+ });
1635
+ };
1636
+ const OvermuxHost = ({ definition }) => /* @__PURE__ */ jsx(OvermuxThemeScope, {
1637
+ ...definition.appearance,
1638
+ children: location.pathname === "/_overmux/settings" ? /* @__PURE__ */ jsx(HostedSettingsPage, {}) : location.pathname === "/_overmux/logout" ? /* @__PURE__ */ jsx(HostedLogoutPage, {}) : /* @__PURE__ */ jsx(OvermuxRuntime, { definition })
1639
+ });
1640
+ //#endregion
1641
+ export { OvermuxHost, OvermuxPortal, OvermuxThemeScope, chordPrefixSchema, commandBindingSchema, createOvermuxHooks, defineCommandRegistry, defineOvermuxClient, disableBackgroundNotifications, enableBackgroundNotifications, formatKeyBinding, formatShortcutBinding, keyBindingSchema, matchesKeyBinding, shortcutBindingSchema, skipToken, useCommand, useCommands, useShortcutInputTarget };
1642
+
1643
+ //# sourceMappingURL=client.js.map