overmux 0.0.3 → 0.0.5

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 (159) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +2 -2
  3. package/dist/auth/.vite/manifest.json +2 -2
  4. package/dist/auth/assets/{index-Bx8JhwVi.css → index-BM9gETvx.css} +1 -1
  5. package/dist/auth/assets/{index-CHIE7qYj.js → index-DabAfcs1.js} +1 -1
  6. package/dist/auth/index.html +2 -2
  7. package/dist/bin.js +219 -126
  8. package/dist/bin.js.map +1 -1
  9. package/dist/docs/000-index.md +2 -4
  10. package/dist/docs/100-introduction/200-how-overmux-works.md +126 -2
  11. package/{docs/100-introduction/300-why-overmux.md → dist/docs/100-introduction/300-why-i-built-overmux.md} +1 -1
  12. package/dist/docs/200-getting-started/100-install-and-run-overmux.md +2 -2
  13. package/dist/docs/200-getting-started/400-secure-with-https/200-tailscale-serve.md +3 -3
  14. package/dist/docs/200-getting-started/400-secure-with-https/300-cloudflare-tunnel.md +2 -2
  15. package/dist/docs/200-getting-started/400-secure-with-https/400-self-hosted-reverse-proxy.md +2 -2
  16. package/dist/docs/400-reference/100-project-structure.md +83 -0
  17. package/dist/docs/400-reference/200-configuration.md +257 -0
  18. package/dist/docs/400-reference/300-storage-locations.md +46 -0
  19. package/dist/docs/400-reference/400-authentication-and-security.md +37 -0
  20. package/dist/docs/400-reference/500-server/000-index.md +13 -0
  21. package/dist/docs/400-reference/500-server/100-resources.md +191 -0
  22. package/dist/docs/400-reference/500-server/200-operations.md +111 -0
  23. package/dist/docs/400-reference/500-server/300-streams.md +140 -0
  24. package/dist/docs/400-reference/500-server/400-notifications.md +32 -0
  25. package/dist/docs/400-reference/500-server/500-api.md +118 -0
  26. package/dist/docs/400-reference/600-client/000-index.md +9 -0
  27. package/dist/docs/400-reference/600-client/100-api.md +370 -0
  28. package/dist/docs/400-reference/600-client/200-theming.md +94 -0
  29. package/dist/docs/400-reference/600-client/300-tech-stack-recommendations.md +12 -0
  30. package/{docs/400-reference/400-cli → dist/docs/400-reference/700-cli}/050-init.md +2 -4
  31. package/dist/docs/400-reference/{400-cli → 700-cli}/100-serve.md +9 -4
  32. package/{docs/400-reference/400-cli → dist/docs/400-reference/700-cli}/200-auth.md +1 -4
  33. package/{docs/400-reference/400-cli → dist/docs/400-reference/700-cli}/300-call.md +1 -4
  34. package/dist/docs/400-reference/700-cli/350-instance.md +21 -0
  35. package/dist/docs/400-reference/700-cli/600-docs.md +128 -0
  36. package/{docs/400-reference/400-cli → dist/docs/400-reference/700-cli}/700-desktop.md +1 -3
  37. package/dist/docs/500-hosted-pages.md +0 -1
  38. package/dist/exports/client.d.ts +19 -15
  39. package/dist/exports/client.d.ts.map +1 -1
  40. package/dist/exports/client.js +240 -58
  41. package/dist/exports/client.js.map +1 -1
  42. package/dist/exports/{index-D3_Dpy_T.d.ts → index-Cz3xkCa4.d.ts} +62 -36
  43. package/dist/exports/index-Cz3xkCa4.d.ts.map +1 -0
  44. package/dist/exports/index.d.ts +2 -2
  45. package/dist/exports/index.js +19 -5
  46. package/dist/exports/index.js.map +1 -1
  47. package/dist/exports/{notifications-kDp16bU_.js → notifications-BFAD3QQl.js} +34 -4
  48. package/dist/exports/notifications-BFAD3QQl.js.map +1 -0
  49. package/dist/exports/server.d.ts +1 -44
  50. package/dist/exports/server.d.ts.map +1 -1
  51. package/dist/exports/server.js +8 -1018
  52. package/dist/exports/server.js.map +1 -1
  53. package/dist/internal/server/coordinator/server-child.js +153 -85
  54. package/dist/internal/server/coordinator/server-child.js.map +1 -1
  55. package/docs/000-index.md +2 -4
  56. package/docs/100-introduction/200-how-overmux-works.md +126 -2
  57. package/{dist/docs/100-introduction/300-why-overmux.md → docs/100-introduction/300-why-i-built-overmux.md} +1 -1
  58. package/docs/200-getting-started/100-install-and-run-overmux.md +2 -2
  59. package/docs/200-getting-started/400-secure-with-https/200-tailscale-serve.md +3 -3
  60. package/docs/200-getting-started/400-secure-with-https/300-cloudflare-tunnel.md +2 -2
  61. package/docs/200-getting-started/400-secure-with-https/400-self-hosted-reverse-proxy.md +2 -2
  62. package/docs/400-reference/100-project-structure.md +83 -0
  63. package/docs/400-reference/200-configuration.md +257 -0
  64. package/docs/400-reference/300-storage-locations.md +46 -0
  65. package/docs/400-reference/400-authentication-and-security.md +37 -0
  66. package/docs/400-reference/500-server/000-index.md +13 -0
  67. package/docs/400-reference/500-server/100-resources.md +191 -0
  68. package/docs/400-reference/500-server/200-operations.md +111 -0
  69. package/docs/400-reference/500-server/300-streams.md +140 -0
  70. package/docs/400-reference/500-server/400-notifications.md +32 -0
  71. package/docs/400-reference/500-server/500-api.md +118 -0
  72. package/docs/400-reference/600-client/000-index.md +9 -0
  73. package/docs/400-reference/600-client/100-api.md +370 -0
  74. package/docs/400-reference/600-client/200-theming.md +94 -0
  75. package/docs/400-reference/600-client/300-tech-stack-recommendations.md +12 -0
  76. package/{dist/docs/400-reference/400-cli → docs/400-reference/700-cli}/050-init.md +2 -4
  77. package/docs/400-reference/{400-cli → 700-cli}/100-serve.md +9 -4
  78. package/{dist/docs/400-reference/400-cli → docs/400-reference/700-cli}/200-auth.md +1 -4
  79. package/{dist/docs/400-reference/400-cli → docs/400-reference/700-cli}/300-call.md +1 -4
  80. package/docs/400-reference/700-cli/350-instance.md +21 -0
  81. package/docs/400-reference/700-cli/600-docs.md +128 -0
  82. package/{dist/docs/400-reference/400-cli → docs/400-reference/700-cli}/700-desktop.md +1 -3
  83. package/docs/500-hosted-pages.md +0 -1
  84. package/package.json +3 -2
  85. package/src/internal/cli/app.ts +5 -5
  86. package/src/internal/cli/commands/docs-ai-context.ts +33 -0
  87. package/src/internal/cli/commands/docs.ts +19 -2
  88. package/src/internal/cli/commands/init-template.ts +1 -1
  89. package/src/internal/cli/commands/instance.ts +58 -0
  90. package/src/internal/cli/commands/serve.ts +3 -0
  91. package/src/internal/cli/login.ts +9 -9
  92. package/src/internal/cli/parse-port.ts +2 -1
  93. package/src/internal/client/auth/auth-shell.css +17 -1
  94. package/src/internal/client/auth/auth-shell.tsx +2 -11
  95. package/src/internal/client/auth/login-code-input.tsx +140 -0
  96. package/src/internal/client/browser-api.ts +17 -3
  97. package/src/internal/client/client-definition.ts +6 -8
  98. package/src/internal/client/host/deep-link-navigation.ts +75 -0
  99. package/src/internal/client/host/desktop-host.ts +3 -0
  100. package/src/internal/client/host/environment.ts +67 -0
  101. package/src/internal/client/host/instance-forwarding.ts +21 -0
  102. package/src/internal/client/host/overmux-host.tsx +17 -0
  103. package/src/internal/client/index.ts +0 -6
  104. package/src/internal/client/overmux-react.ts +95 -42
  105. package/src/internal/client/websocket.ts +32 -12
  106. package/src/internal/server/auth/auth-service.ts +2 -2
  107. package/src/internal/server/auth/instance-control.ts +89 -17
  108. package/src/internal/server/coordinator/ipc-protocol.ts +0 -1
  109. package/src/internal/server/runtime/create-runtime.ts +15 -2
  110. package/src/internal/server/runtime/runtime-instance.ts +47 -0
  111. package/src/internal/server/runtime/runtime-operations.ts +9 -3
  112. package/src/internal/server/runtime/runtime-resources.ts +15 -32
  113. package/src/internal/server/runtime/runtime-streams.ts +5 -6
  114. package/src/internal/server/server-logger.ts +5 -10
  115. package/src/internal/server/server-startup-options.ts +14 -14
  116. package/src/internal/server/start-application-server.ts +24 -19
  117. package/src/internal/server/websocket-server.ts +7 -0
  118. package/src/internal/shared/index.ts +2 -0
  119. package/src/internal/shared/protocol.ts +10 -1
  120. package/src/public/ai-context.ts +7 -29
  121. package/src/public/client.ts +1 -6
  122. package/src/public/config.ts +163 -31
  123. package/src/public/index.ts +2 -0
  124. package/src/public/server.ts +1 -16
  125. package/dist/docs/100-introduction/100-what-is-overmux.md +0 -7
  126. package/dist/docs/300-fundamentals/100-project-structure.md +0 -23
  127. package/dist/docs/300-fundamentals/200-configuration.md +0 -3
  128. package/dist/docs/300-fundamentals/300-theming.md +0 -3
  129. package/dist/docs/300-fundamentals/400-server.md +0 -3
  130. package/dist/docs/300-fundamentals/500-client.md +0 -3
  131. package/dist/docs/300-fundamentals/600-operations.md +0 -3
  132. package/dist/docs/300-fundamentals/700-resources.md +0 -3
  133. package/dist/docs/300-fundamentals/800-streams.md +0 -3
  134. package/dist/docs/300-fundamentals/900-authentication-and-security.md +0 -3
  135. package/dist/docs/400-reference/100-configuration.md +0 -3
  136. package/dist/docs/400-reference/200-server-api.md +0 -3
  137. package/dist/docs/400-reference/300-client-api.md +0 -20
  138. package/dist/docs/400-reference/400-cli/400-check.md +0 -20
  139. package/dist/docs/400-reference/400-cli/500-ai-context.md +0 -102
  140. package/dist/docs/400-reference/400-cli/600-docs.md +0 -23
  141. package/dist/exports/index-D3_Dpy_T.d.ts.map +0 -1
  142. package/dist/exports/notifications-kDp16bU_.js.map +0 -1
  143. package/docs/100-introduction/100-what-is-overmux.md +0 -7
  144. package/docs/300-fundamentals/100-project-structure.md +0 -23
  145. package/docs/300-fundamentals/200-configuration.md +0 -3
  146. package/docs/300-fundamentals/300-theming.md +0 -3
  147. package/docs/300-fundamentals/400-server.md +0 -3
  148. package/docs/300-fundamentals/500-client.md +0 -3
  149. package/docs/300-fundamentals/600-operations.md +0 -3
  150. package/docs/300-fundamentals/700-resources.md +0 -3
  151. package/docs/300-fundamentals/800-streams.md +0 -3
  152. package/docs/300-fundamentals/900-authentication-and-security.md +0 -3
  153. package/docs/400-reference/100-configuration.md +0 -3
  154. package/docs/400-reference/200-server-api.md +0 -3
  155. package/docs/400-reference/300-client-api.md +0 -20
  156. package/docs/400-reference/400-cli/400-check.md +0 -20
  157. package/docs/400-reference/400-cli/500-ai-context.md +0 -102
  158. package/docs/400-reference/400-cli/600-docs.md +0 -23
  159. package/src/internal/cli/commands/ai.ts +0 -44
@@ -1,9 +1,9 @@
1
1
  import "./style.css";
2
- import { i as overmuxLogoutPath, r as notificationSchema, s as resolveOvermuxReturnTo } from "./notifications-kDp16bU_.js";
2
+ import { a as notificationSchema, c as parseDeepLink, l as resolveOvermuxReturnTo, o as overmuxLogoutPath, r as instanceIdSchema, t as createInstanceIdentity } from "./notifications-BFAD3QQl.js";
3
3
  import { z } from "zod";
4
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";
5
+ import { Component, createContext, createElement, useCallback, useContext, useEffect, useMemo, useReducer, useRef, useState, useSyncExternalStore } from "react";
6
+ import { formatKeyBinding, keyBindingSchema, matchesKeyBinding, platformBinding } from "@overmux/keybindings";
7
7
  import { createPortal } from "react-dom";
8
8
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
9
  //#region src/internal/shared/background-notifications.ts
@@ -28,7 +28,7 @@ const webSocketCloseCode = {
28
28
  const runtimeManifestSchema = z.object({
29
29
  debug: z.boolean(),
30
30
  operations: z.array(z.string().min(1)),
31
- protocolVersion: z.literal(9),
31
+ protocolVersion: z.literal(10),
32
32
  resources: z.array(z.string().min(1)),
33
33
  streams: z.array(z.string().min(1))
34
34
  });
@@ -134,7 +134,12 @@ const protocolErrorMessageSchema = z.object({
134
134
  subscriptionId: operationIdSchema.optional(),
135
135
  type: z.literal("error")
136
136
  });
137
+ const serverInfoMessageSchema = z.object({
138
+ type: z.literal("server-info"),
139
+ instanceId: instanceIdSchema
140
+ }).strict();
137
141
  const serverProtocolMessageSchema = z.discriminatedUnion("type", [
142
+ serverInfoMessageSchema,
138
143
  resourceResultMessageSchema,
139
144
  resourceInvalidatedMessageSchema,
140
145
  streamOpenedMessageSchema,
@@ -315,16 +320,16 @@ const writeClipboardText = async (text) => {
315
320
  };
316
321
  //#endregion
317
322
  //#region src/internal/client/client-definition.ts
318
- const shortcutBindingSchema = z.union([keyBindingSchema$1, z.array(keyBindingSchema$1).min(2)]);
323
+ const shortcutBindingSchema = z.union([keyBindingSchema, z.array(keyBindingSchema).min(2)]);
319
324
  const commandBindingSchema = z.union([shortcutBindingSchema, z.object({
320
325
  binding: shortcutBindingSchema,
321
326
  when: z.object({ media: z.string().trim().min(1) })
322
327
  })]);
323
328
  const chordPrefixSchema = z.object({
324
- binding: keyBindingSchema$1,
329
+ binding: keyBindingSchema,
325
330
  unmatched: z.literal("replay-to-focused-input")
326
331
  });
327
- const formatShortcutBinding = (binding) => typeof binding === "string" ? formatKeyBinding$1(binding) : binding.map(formatKeyBinding$1).join(" ");
332
+ const formatShortcutBinding = (binding) => typeof binding === "string" ? formatKeyBinding(binding) : binding.map(formatKeyBinding).join(" ");
328
333
  const validateBindings = (bindings) => bindings?.forEach((binding) => commandBindingSchema.parse(binding));
329
334
  const defineCommandRegistry = () => (commands) => Object.fromEntries(Object.entries(commands).map(([id, command]) => {
330
335
  validateBindings(command.defaultBindings);
@@ -514,60 +519,107 @@ const useOperationById = (id, signal) => {
514
519
  const useStreamById = (id, input) => {
515
520
  const { manifest, overmuxServerApi } = useRuntime();
516
521
  const streamAvailable = manifest.streams.includes(id);
517
- const [state, setState] = useState({ status: "opening" });
518
- const listeners = useMemo(() => /* @__PURE__ */ new Set(), []);
519
522
  const canonicalInput = useCanonicalInput(input);
523
+ const scope = useMemo(() => ({
524
+ canonicalInput,
525
+ id,
526
+ overmuxServerApi,
527
+ streamAvailable
528
+ }), [
529
+ canonicalInput,
530
+ id,
531
+ overmuxServerApi,
532
+ streamAvailable
533
+ ]);
534
+ const [state, setState] = useState({
535
+ scope,
536
+ status: "opening"
537
+ });
538
+ const [listeners] = useState(() => /* @__PURE__ */ new Set());
520
539
  const streamRef = useRef(void 0);
521
540
  useEffect(() => {
522
541
  streamRef.current?.close();
523
542
  streamRef.current = void 0;
524
- if (!streamAvailable) {
525
- setState({ status: "closed" });
543
+ if (!scope.streamAvailable) {
544
+ setState({
545
+ scope,
546
+ status: "closed"
547
+ });
526
548
  return;
527
549
  }
528
- setState({ status: "opening" });
529
- const stream = overmuxServerApi.openStream({
530
- id,
531
- input: canonicalInput,
532
- onClose: () => setState({ status: "closed" }),
533
- onError: (error) => setState({
550
+ setState({
551
+ scope,
552
+ status: "opening"
553
+ });
554
+ let active = true;
555
+ const stream = scope.overmuxServerApi.openStream({
556
+ id: scope.id,
557
+ input: scope.canonicalInput,
558
+ onClose: () => active && setState({
559
+ scope,
560
+ status: "closed"
561
+ }),
562
+ onError: (error) => active && setState({
563
+ scope,
534
564
  error,
535
565
  status: "closed"
536
566
  }),
537
- onOpen: () => setState({ status: "open" }),
538
- onMessage: (message) => listeners.forEach((listener) => listener(message))
567
+ onOpen: () => active && setState({
568
+ scope,
569
+ connectionId: Symbol(scope.id),
570
+ status: "open"
571
+ }),
572
+ onMessage: (message) => active && listeners.forEach((listener) => listener(message))
539
573
  });
540
- streamRef.current = stream;
541
- return () => {
542
- stream.close();
543
- if (streamRef.current === stream) streamRef.current = void 0;
574
+ const connection = {
575
+ close: () => {
576
+ if (!active) return;
577
+ active = false;
578
+ if (streamRef.current === connection) streamRef.current = void 0;
579
+ setState({
580
+ scope,
581
+ status: "closed"
582
+ });
583
+ stream.close();
584
+ },
585
+ send: stream.send
544
586
  };
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]);
587
+ streamRef.current = connection;
588
+ return connection.close;
589
+ }, [listeners, scope]);
590
+ const [methods] = useState(() => ({
591
+ close: () => streamRef.current?.close(),
592
+ send: (message) => streamRef.current?.send(message) ?? false,
593
+ subscribe: (listener) => {
594
+ listeners.add(listener);
595
+ return () => listeners.delete(listener);
596
+ }
597
+ }));
598
+ if (!streamAvailable) return {
599
+ ...methods,
600
+ connectionId: void 0,
601
+ error: /* @__PURE__ */ new Error(`Stream is not available: ${id}`),
602
+ status: "closed"
603
+ };
604
+ if (state.scope !== scope) return {
605
+ ...methods,
606
+ connectionId: void 0,
607
+ status: "opening"
608
+ };
562
609
  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
610
+ ...methods,
611
+ connectionId: state.connectionId,
612
+ error: state.error,
613
+ status: state.status
568
614
  };
569
615
  };
616
+ const undiscoveredInstance = () => void 0;
617
+ const useInstance = () => {
618
+ const { overmuxServerApi } = useRuntime();
619
+ return useSyncExternalStore(overmuxServerApi.subscribeInstance, overmuxServerApi.getInstance, undiscoveredInstance);
620
+ };
570
621
  const createOvermuxHooks = () => ({
622
+ useInstance,
571
623
  useOperation: ({ id, signal }) => {
572
624
  return useOperationById(id, signal);
573
625
  },
@@ -614,6 +666,10 @@ const availableName = (names, kind, name) => {
614
666
  return name;
615
667
  };
616
668
  const createOvermuxServerApi = ({ manifest, transport }) => ({
669
+ getInstance: transport.getInstance,
670
+ subscribeInstance: transport.subscribeInstance,
671
+ getInstanceId: () => transport.getInstance()?.instanceId,
672
+ getDeepLinkPrefix: () => transport.getInstance()?.deepLinkPrefix,
617
673
  executeOperation: (id, input, signal) => {
618
674
  if (!manifest.operations.includes(id)) throw new Error(`Operation is not available: ${id}`);
619
675
  return transport.invokeOperation({
@@ -731,12 +787,79 @@ const installNotificationForwarding = (transport) => transport.subscribeNotifica
731
787
  if (notification.success) window.overmuxHost?.notifications?.show(notification.data);
732
788
  });
733
789
  //#endregion
790
+ //#region src/internal/client/host/instance-forwarding.ts
791
+ const installInstanceForwarding = (transport) => {
792
+ const report = () => {
793
+ const identity = transport.getInstance();
794
+ const bridge = window.overmuxHost?.instance;
795
+ if (!identity || bridge?.version !== 1) return;
796
+ try {
797
+ bridge.report({ instanceId: identity.instanceId });
798
+ } catch (cause) {
799
+ console.error("Overmux host instance reporting failed", cause);
800
+ }
801
+ };
802
+ const unsubscribe = transport.subscribeInstance(report);
803
+ report();
804
+ return unsubscribe;
805
+ };
806
+ //#endregion
807
+ //#region src/internal/client/host/deep-link-navigation.ts
808
+ const defaultNavigate = (route) => window.location.assign(route);
809
+ /**
810
+ * Intercepts overmux:// anchor activation inside the running app, including the
811
+ * temporary anchors created by xterm's default link handler. Without this,
812
+ * browser/PWA clicks dispatch the custom protocol to the OS, which fails in
813
+ * Android PWAs where it is not possible to install an Overmux protocol handler.
814
+ *
815
+ * Validates the original URL and compares its instance ID with authenticated
816
+ * runtime discovery. For same-instance links, preventDefault stops OS dispatch
817
+ * and navigate receives only the local path, query, and fragment. By default,
818
+ * this loads that route on the current origin; a supplied router callback avoids
819
+ * a document reload. Instance IDs are never treated as network hostnames.
820
+ *
821
+ * Valid cross-instance links remain Desktop's responsibility. Browsers/PWAs
822
+ * block unknown or different instances; all hosts block invalid links. Ordinary
823
+ * links and clicks already cancelled by application code are left untouched.
824
+ * Returns cleanup for the document listeners when the runtime unmounts.
825
+ */
826
+ const installDeepLinkNavigation = ({ getInstance, navigate = defaultNavigate }) => {
827
+ const onClick = (event) => {
828
+ if (event.defaultPrevented || event.button > 1) return;
829
+ const anchor = event.composedPath().find((target) => target instanceof HTMLAnchorElement);
830
+ if (anchor?.protocol !== "overmux:") return;
831
+ let link;
832
+ try {
833
+ link = parseDeepLink(anchor.getAttribute("href") ?? "");
834
+ } catch {
835
+ event.preventDefault();
836
+ console.warn("Cannot open an invalid Overmux deep link.");
837
+ return;
838
+ }
839
+ if (link.instanceId !== getInstance()?.instanceId) {
840
+ if (window.overmuxHost?.version !== 1) {
841
+ event.preventDefault();
842
+ console.warn("Cannot open an Overmux link to an unknown or different instance in this browser.");
843
+ }
844
+ return;
845
+ }
846
+ event.preventDefault();
847
+ navigate(link.route);
848
+ };
849
+ document.addEventListener("click", onClick);
850
+ document.addEventListener("auxclick", onClick);
851
+ return () => {
852
+ document.removeEventListener("click", onClick);
853
+ document.removeEventListener("auxclick", onClick);
854
+ };
855
+ };
856
+ //#endregion
734
857
  //#region src/internal/client/shortcuts.tsx
735
858
  const bindingSequence = (binding) => typeof binding === "string" ? [binding] : [...binding];
736
859
  const normalizedBinding = (binding) => bindingSequence(binding).map((key) => platformBinding(key));
737
860
  const sequenceMatches = (binding, pending, event) => {
738
861
  const sequence = normalizedBinding(binding);
739
- return sequence.length > pending.length && pending.every((key, index) => sequence[index] === key) && matchesKeyBinding$1(sequence[pending.length], event);
862
+ return sequence.length > pending.length && pending.every((key, index) => sequence[index] === key) && matchesKeyBinding(sequence[pending.length], event);
740
863
  };
741
864
  const isTextInput = (element) => element instanceof HTMLInputElement || element instanceof HTMLSelectElement || element instanceof HTMLTextAreaElement || element instanceof HTMLElement && (element.isContentEditable || element.getAttribute("role") === "textbox");
742
865
  const acceptsTextInput = (bindings) => {
@@ -892,7 +1015,7 @@ const ShortcutHost = ({ children, definition, registrations, registrationRevisio
892
1015
  if (runShortcut(complete.shortcut, event, registrations)) event.stopPropagation();
893
1016
  return;
894
1017
  }
895
- if (!pending.length) pendingTarget = definition.chordPrefixes?.some((prefix) => matchesKeyBinding$1(prefix.binding, event)) ? selectShortcutInputTarget(inputTargets, document.activeElement) : void 0;
1018
+ if (!pending.length) pendingTarget = definition.chordPrefixes?.some((prefix) => matchesKeyBinding(prefix.binding, event)) ? selectShortcutInputTarget(inputTargets, document.activeElement) : void 0;
896
1019
  pending = [...pending, normalizedBinding(matches[0].binding)[pending.length]];
897
1020
  pendingEvents = [...pendingEvents, event];
898
1021
  event.preventDefault();
@@ -941,6 +1064,12 @@ const createWebSocketTransport = ({ createSocket = () => new WebSocket(socketUrl
941
1064
  let reconnectTimer;
942
1065
  let socket;
943
1066
  let status = "disconnected";
1067
+ let instance;
1068
+ const instanceListeners = /* @__PURE__ */ new Set();
1069
+ const setInstance = (next) => {
1070
+ instance = next;
1071
+ instanceListeners.forEach((listener) => listener());
1072
+ };
944
1073
  const diagnosticQueue = [];
945
1074
  const requests = /* @__PURE__ */ new Map();
946
1075
  const subscriptions = /* @__PURE__ */ new Map();
@@ -954,7 +1083,7 @@ const createWebSocketTransport = ({ createSocket = () => new WebSocket(socketUrl
954
1083
  statusListeners.forEach((listener) => listener(status));
955
1084
  };
956
1085
  const send = (message) => {
957
- if (socket?.readyState !== WebSocket.OPEN) return false;
1086
+ if (socket?.readyState !== WebSocket.OPEN || !instance) return false;
958
1087
  if (socket.bufferedAmount > maxWebSocketBufferedBytes) {
959
1088
  socket.close(1013, "client could not keep up");
960
1089
  return false;
@@ -1008,6 +1137,7 @@ const createWebSocketTransport = ({ createSocket = () => new WebSocket(socketUrl
1008
1137
  requests.clear();
1009
1138
  };
1010
1139
  const reportDisconnect = () => {
1140
+ setInstance(void 0);
1011
1141
  const error = disconnected();
1012
1142
  rejectRequests(error);
1013
1143
  subscriptions.forEach(({ onError }) => onError(error));
@@ -1023,15 +1153,8 @@ const createWebSocketTransport = ({ createSocket = () => new WebSocket(socketUrl
1023
1153
  const next = createSocket();
1024
1154
  next.binaryType = "arraybuffer";
1025
1155
  socket = next;
1026
- next.addEventListener("open", () => {
1027
- if (socket !== next) return;
1028
- reconnectDelay = 250;
1029
- setStatus("connected");
1030
- flushDiagnostics();
1031
- restore();
1032
- });
1033
1156
  next.addEventListener("message", (event) => {
1034
- if (socket !== next) return;
1157
+ if (socket !== next || disposed) return;
1035
1158
  let decoded;
1036
1159
  try {
1037
1160
  decoded = decodeProtocolFrame(event.data);
@@ -1049,6 +1172,19 @@ const createWebSocketTransport = ({ createSocket = () => new WebSocket(socketUrl
1049
1172
  return;
1050
1173
  }
1051
1174
  const message = parsed.data;
1175
+ if (message.type === "server-info" && !instance) {
1176
+ setInstance(createInstanceIdentity(message.instanceId));
1177
+ reconnectDelay = 250;
1178
+ setStatus("connected");
1179
+ flushDiagnostics();
1180
+ restore();
1181
+ return;
1182
+ }
1183
+ if (!instance || message.type === "server-info") {
1184
+ reportDisconnect();
1185
+ next.close(1002, "expected server discovery before traffic");
1186
+ return;
1187
+ }
1052
1188
  if (message.type === "update-available" || message.type === "restarting") lifecycleListeners.forEach((listener) => listener(message));
1053
1189
  if (message.type === "notification") notificationListeners.forEach((listener) => listener(message));
1054
1190
  if (message.type === "resource-invalidated") subscriptions.get(message.subscriptionId)?.onInvalidate();
@@ -1102,6 +1238,11 @@ const createWebSocketTransport = ({ createSocket = () => new WebSocket(socketUrl
1102
1238
  });
1103
1239
  };
1104
1240
  return {
1241
+ getInstance: () => instance,
1242
+ subscribeInstance: (listener) => {
1243
+ instanceListeners.add(listener);
1244
+ return () => instanceListeners.delete(listener);
1245
+ },
1105
1246
  activate: () => {
1106
1247
  active = true;
1107
1248
  connect();
@@ -1135,7 +1276,7 @@ const createWebSocketTransport = ({ createSocket = () => new WebSocket(socketUrl
1135
1276
  streamName
1136
1277
  };
1137
1278
  streams.set(streamId, stream);
1138
- stream.status = socket?.readyState === WebSocket.CONNECTING || send({
1279
+ stream.status = status === "connecting" || send({
1139
1280
  input,
1140
1281
  operationId: streamId,
1141
1282
  streamId,
@@ -1561,7 +1702,43 @@ const UpdatePopover = ({ transport }) => {
1561
1702
  });
1562
1703
  };
1563
1704
  //#endregion
1705
+ //#region src/internal/client/host/environment.ts
1706
+ const browserPlatform = (navigator) => {
1707
+ const { userAgent, maxTouchPoints } = navigator;
1708
+ const identity = `${navigator.userAgentData?.platform ?? navigator.platform} ${userAgent}`;
1709
+ if (/iPad|iPhone|iPod/i.test(identity) || /Mac/i.test(identity) && maxTouchPoints > 1) return "ios";
1710
+ if (/Android/i.test(identity)) return "android";
1711
+ if (/Win/i.test(identity)) return "windows";
1712
+ if (/Mac/i.test(identity)) return "macos";
1713
+ return /Linux/i.test(identity) ? "linux" : "unknown";
1714
+ };
1715
+ const desktopPlatform = (platform) => {
1716
+ if (platform === "darwin") return "macos";
1717
+ if (platform === "win32") return "windows";
1718
+ return platform === "linux" ? "linux" : "unknown";
1719
+ };
1720
+ const installEnvironmentSelectors = () => {
1721
+ const html = document.documentElement;
1722
+ const host = window.overmuxHost;
1723
+ const desktop = host?.version === 1;
1724
+ const navigator = window.navigator;
1725
+ html.dataset.omPlatform = desktop ? desktopPlatform(host.platform) : browserPlatform(navigator);
1726
+ if (desktop) {
1727
+ html.dataset.omHost = "desktop";
1728
+ return () => {};
1729
+ }
1730
+ const displayMode = window.matchMedia("(display-mode: standalone), (display-mode: minimal-ui), (display-mode: window-controls-overlay)");
1731
+ const updateHost = () => {
1732
+ html.dataset.omHost = displayMode.matches || navigator.standalone === true ? "pwa" : "browser";
1733
+ };
1734
+ updateHost();
1735
+ displayMode.addEventListener("change", updateHost);
1736
+ return () => displayMode.removeEventListener("change", updateHost);
1737
+ };
1738
+ //#endregion
1564
1739
  //#region src/internal/client/host/overmux-host.tsx
1740
+ const disposeEnvironment = typeof window === "undefined" ? void 0 : installEnvironmentSelectors();
1741
+ import.meta.hot?.dispose(() => disposeEnvironment?.());
1565
1742
  var RootErrorBoundary = class extends Component {
1566
1743
  state = {};
1567
1744
  static getDerivedStateFromError(error) {
@@ -1616,6 +1793,11 @@ const OvermuxRuntime = ({ definition }) => {
1616
1793
  transport.dispose();
1617
1794
  }, [transport]);
1618
1795
  useEffect(() => installNotificationForwarding(transport), [transport]);
1796
+ useEffect(() => installInstanceForwarding(transport), [transport]);
1797
+ useEffect(() => installDeepLinkNavigation({
1798
+ getInstance: transport.getInstance,
1799
+ navigate: definition.navigate
1800
+ }), [transport, definition.navigate]);
1619
1801
  const refreshCommands = useCallback(() => setRegistrationRevision((revision) => revision + 1), []);
1620
1802
  const registerCommand = useCallback((registration) => {
1621
1803
  registrations.add(registration);
@@ -1660,6 +1842,6 @@ const OvermuxHost = ({ definition }) => /* @__PURE__ */ jsx(OvermuxThemeScope, {
1660
1842
  children: location.pathname === "/_overmux/settings" ? /* @__PURE__ */ jsx(HostedSettingsPage, {}) : location.pathname === "/_overmux/logout" ? /* @__PURE__ */ jsx(HostedLogoutPage, {}) : /* @__PURE__ */ jsx(OvermuxRuntime, { definition })
1661
1843
  });
1662
1844
  //#endregion
1663
- export { OvermuxHost, OvermuxPortal, OvermuxThemeScope, chordPrefixSchema, commandBindingSchema, createOvermuxHooks, defineCommandRegistry, defineOvermuxClient, disableBackgroundNotifications, enableBackgroundNotifications, formatKeyBinding, formatShortcutBinding, keyBindingSchema, matchesKeyBinding, readClipboardText, shortcutBindingSchema, skipToken, useCommand, useCommands, useShortcutInputTarget, writeClipboardText };
1845
+ export { OvermuxHost, OvermuxPortal, OvermuxThemeScope, createOvermuxHooks, defineCommandRegistry, defineOvermuxClient, disableBackgroundNotifications, enableBackgroundNotifications, formatShortcutBinding, readClipboardText, skipToken, useCommand, useCommands, useShortcutInputTarget, writeClipboardText };
1664
1846
 
1665
1847
  //# sourceMappingURL=client.js.map