overmux 0.0.1 → 0.0.3

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