overmux 0.0.3 → 0.0.4

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 (60) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/auth/.vite/manifest.json +2 -2
  3. package/dist/auth/assets/{index-Bx8JhwVi.css → index-BM9gETvx.css} +1 -1
  4. package/dist/auth/assets/{index-CHIE7qYj.js → index-DabAfcs1.js} +1 -1
  5. package/dist/auth/index.html +2 -2
  6. package/dist/bin.js +91 -11
  7. package/dist/bin.js.map +1 -1
  8. package/dist/docs/300-fundamentals/300-theming.md +51 -0
  9. package/dist/docs/400-reference/100-configuration.md +20 -0
  10. package/dist/docs/400-reference/200-server-api.md +18 -0
  11. package/dist/docs/400-reference/300-client-api.md +19 -0
  12. package/dist/docs/400-reference/400-cli/350-instance.md +24 -0
  13. package/dist/exports/client.d.ts +16 -3
  14. package/dist/exports/client.d.ts.map +1 -1
  15. package/dist/exports/client.js +100 -13
  16. package/dist/exports/client.js.map +1 -1
  17. package/dist/exports/{index-D3_Dpy_T.d.ts → index-DS70rKzo.d.ts} +14 -2
  18. package/dist/exports/index-DS70rKzo.d.ts.map +1 -0
  19. package/dist/exports/index.d.ts +2 -2
  20. package/dist/exports/index.js +1 -1
  21. package/dist/exports/index.js.map +1 -1
  22. package/dist/exports/{notifications-kDp16bU_.js → notifications-av0FK0yZ.js} +7 -2
  23. package/dist/exports/{notifications-kDp16bU_.js.map → notifications-av0FK0yZ.js.map} +1 -1
  24. package/dist/exports/server.d.ts.map +1 -1
  25. package/dist/exports/server.js +52 -10
  26. package/dist/exports/server.js.map +1 -1
  27. package/dist/internal/server/coordinator/server-child.js +110 -46
  28. package/dist/internal/server/coordinator/server-child.js.map +1 -1
  29. package/docs/300-fundamentals/300-theming.md +51 -0
  30. package/docs/400-reference/100-configuration.md +20 -0
  31. package/docs/400-reference/200-server-api.md +18 -0
  32. package/docs/400-reference/300-client-api.md +19 -0
  33. package/docs/400-reference/400-cli/350-instance.md +24 -0
  34. package/package.json +1 -1
  35. package/src/internal/cli/app.ts +2 -0
  36. package/src/internal/cli/commands/instance.ts +58 -0
  37. package/src/internal/cli/parse-port.ts +2 -1
  38. package/src/internal/client/auth/auth-shell.css +17 -1
  39. package/src/internal/client/auth/auth-shell.tsx +2 -11
  40. package/src/internal/client/auth/login-code-input.tsx +140 -0
  41. package/src/internal/client/browser-api.ts +17 -3
  42. package/src/internal/client/host/desktop-host.ts +3 -0
  43. package/src/internal/client/host/environment.ts +67 -0
  44. package/src/internal/client/host/instance-forwarding.ts +21 -0
  45. package/src/internal/client/host/overmux-host.tsx +8 -0
  46. package/src/internal/client/overmux-react.ts +24 -2
  47. package/src/internal/client/websocket.ts +32 -12
  48. package/src/internal/server/auth/instance-control.ts +24 -3
  49. package/src/internal/server/runtime/create-runtime.ts +12 -1
  50. package/src/internal/server/runtime/runtime-instance.ts +46 -0
  51. package/src/internal/server/runtime/runtime-resources.ts +3 -0
  52. package/src/internal/server/server-startup-options.ts +9 -4
  53. package/src/internal/server/start-application-server.ts +20 -12
  54. package/src/internal/server/websocket-server.ts +7 -0
  55. package/src/internal/shared/index.ts +2 -0
  56. package/src/internal/shared/protocol.ts +10 -1
  57. package/src/public/client.ts +1 -0
  58. package/src/public/config.ts +7 -0
  59. package/src/public/index.ts +2 -0
  60. package/dist/exports/index-D3_Dpy_T.d.ts.map +0 -1
@@ -4,7 +4,7 @@ import { createServer, isIP } from "node:net";
4
4
  import { dirname, isAbsolute, join, resolve } from "node:path";
5
5
  import { createJiti } from "jiti";
6
6
  import { ZodError, z } from "zod";
7
- import { homedir, tmpdir } from "node:os";
7
+ import { homedir, hostname, tmpdir } from "node:os";
8
8
  import { once } from "node:events";
9
9
  import { createHash, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
10
10
  import { generateCookie } from "hono/cookie";
@@ -35,6 +35,7 @@ const durationToMilliseconds$1 = (duration) => {
35
35
  return Number.isSafeInteger(milliseconds) ? milliseconds : void 0;
36
36
  };
37
37
  const aiContextSnippets = ["package-source", "tech-stack-recommendations"];
38
+ const instanceIdSchema$1 = z.string().max(253).regex(/^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?(?![\s\S])/, "Expected a lowercase instance ID with only ASCII letters, digits, internal dots and hyphens");
38
39
  const zodTypeSchema = z.custom((value) => value instanceof z.ZodType, "Expected a Zod schema");
39
40
  const functionSchema = z.custom((value) => typeof value === "function", "Expected a function");
40
41
  const contractRuntimeSchema = z.object({
@@ -102,6 +103,7 @@ const configDefinitionRuntimeSchema = serverConfigRuntimeSchema.extend({
102
103
  aiContextSnippets: z.array(z.enum(aiContextSnippets)).optional(),
103
104
  auth: authConfigRuntimeSchema,
104
105
  debug: z.boolean().optional(),
106
+ instanceId: z.union([instanceIdSchema$1, z.custom((value) => typeof value === "function", "Expected an instance ID or a function of the bound port")]).optional(),
105
107
  server: serverDefinitionRuntimeSchema,
106
108
  vite: z.string().min(1).optional()
107
109
  });
@@ -843,10 +845,40 @@ const createHttpAuthenticationHandlers = ({ auth, authAssetsDirectory }) => {
843
845
  }
844
846
  };
845
847
  };
848
+ const overmuxSettingsPath = "/_overmux/settings";
849
+ const overmuxLogoutPath = "/_overmux/logout";
850
+ const safeRelativeNotificationLink = (link) => {
851
+ let decoded;
852
+ try {
853
+ decoded = decodeURIComponent(link);
854
+ } catch {
855
+ return false;
856
+ }
857
+ return !decoded.startsWith("//") && !decoded.includes("\\") && ![...decoded].some((character) => {
858
+ const codePoint = character.codePointAt(0) ?? 0;
859
+ return codePoint < 32 || codePoint === 127;
860
+ });
861
+ };
862
+ const notificationLinkSchema = z.union([z.string().max(4096).startsWith("/").refine(safeRelativeNotificationLink), z.string().max(4096).url().refine((link) => {
863
+ if (!URL.canParse(link)) return false;
864
+ const url = new URL(link);
865
+ return ["http:", "https:"].includes(url.protocol) && !url.username && !url.password;
866
+ })]);
867
+ const notificationSchema = z.object({
868
+ body: z.string().max(4096).optional(),
869
+ open: z.object({ link: notificationLinkSchema }).strict().optional(),
870
+ title: z.string().min(1).max(4096)
871
+ }).strict();
872
+ const instanceIdSchema = z.string().max(253).regex(/^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?(?![\s\S])/, "Expected a lowercase instance ID with only ASCII letters, digits, internal dots and hyphens");
873
+ const createInstanceIdentity = (instanceId) => ({
874
+ instanceId: instanceIdSchema.parse(instanceId),
875
+ deepLinkPrefix: `overmux://${instanceId}`
876
+ });
846
877
  //#endregion
847
878
  //#region src/internal/server/auth/instance-control.ts
848
879
  const maxControlMessageBytes = 16384;
849
880
  const controlRequestSchema = z.discriminatedUnion("type", [
881
+ z.object({ type: z.literal("get-instance") }).strict(),
850
882
  z.object({ type: z.literal("ping") }).strict(),
851
883
  z.object({ type: z.literal("create-login") }).strict(),
852
884
  z.object({ type: z.literal("issue-bearer") }).strict(),
@@ -872,6 +904,9 @@ const sessionSchema = z.object({
872
904
  revokedAt: z.string().optional()
873
905
  }).strict();
874
906
  z.object({
907
+ instanceId: instanceIdSchema,
908
+ ok: z.literal(true)
909
+ }).strict(), z.object({
875
910
  login: loginGrantSchema,
876
911
  ok: z.literal(true)
877
912
  }).strict(), z.object({
@@ -950,7 +985,11 @@ const readNewlineFrame = ({ maximumBytes, socket, timeout }) => new Promise((res
950
985
  const writeResponse = (socket, response) => {
951
986
  socket.end(`${JSON.stringify(response)}\n`);
952
987
  };
953
- const handleRequest = async (auth, request) => {
988
+ const handleRequest = async ({ auth, instanceId, request }) => {
989
+ if (request.type === "get-instance") return {
990
+ instanceId,
991
+ ok: true
992
+ };
954
993
  if (request.type === "ping") return { ok: true };
955
994
  if (request.type === "create-login") return {
956
995
  login: await auth.createLoginGrant(),
@@ -973,14 +1012,18 @@ const handleRequest = async (auth, request) => {
973
1012
  ok: true
974
1013
  };
975
1014
  };
976
- const handleConnection = async ({ auth, socket }) => {
1015
+ const handleConnection = async ({ auth, instanceId, socket }) => {
977
1016
  try {
978
1017
  const frame = await readNewlineFrame({
979
1018
  maximumBytes: maxControlMessageBytes,
980
1019
  socket
981
1020
  });
982
1021
  const request = controlRequestSchema.parse(JSON.parse(frame.toString()));
983
- writeResponse(socket, await handleRequest(auth, request));
1022
+ writeResponse(socket, await handleRequest({
1023
+ auth,
1024
+ instanceId,
1025
+ request
1026
+ }));
984
1027
  } catch (cause) {
985
1028
  writeResponse(socket, {
986
1029
  error: cause instanceof Error ? cause.message : "Invalid control request",
@@ -988,7 +1031,7 @@ const handleConnection = async ({ auth, socket }) => {
988
1031
  });
989
1032
  }
990
1033
  };
991
- const startInstanceControl = async ({ auth, apiUrl, port, url }) => {
1034
+ const startInstanceControl = async ({ auth, apiUrl, instanceId, port, url }) => {
992
1035
  const directory = runtimeDirectory();
993
1036
  await mkdir(directory, {
994
1037
  mode: 448,
@@ -1004,6 +1047,7 @@ const startInstanceControl = async ({ auth, apiUrl, port, url }) => {
1004
1047
  socket.once("close", () => sockets.delete(socket));
1005
1048
  handleConnection({
1006
1049
  auth,
1050
+ instanceId,
1007
1051
  socket
1008
1052
  });
1009
1053
  });
@@ -1054,30 +1098,6 @@ const backgroundNotificationSubscriptionSchema = z.object({
1054
1098
  }).strict();
1055
1099
  z.object({ publicKey: deliveryKeySchema }).strict();
1056
1100
  const backgroundNotificationDisableRequestSchema = z.object({ endpoint: backgroundNotificationSubscriptionSchema.shape.endpoint }).strict();
1057
- const overmuxSettingsPath = "/_overmux/settings";
1058
- const overmuxLogoutPath = "/_overmux/logout";
1059
- const safeRelativeNotificationLink = (link) => {
1060
- let decoded;
1061
- try {
1062
- decoded = decodeURIComponent(link);
1063
- } catch {
1064
- return false;
1065
- }
1066
- return !decoded.startsWith("//") && !decoded.includes("\\") && ![...decoded].some((character) => {
1067
- const codePoint = character.codePointAt(0) ?? 0;
1068
- return codePoint < 32 || codePoint === 127;
1069
- });
1070
- };
1071
- const notificationLinkSchema = z.union([z.string().max(4096).startsWith("/").refine(safeRelativeNotificationLink), z.string().max(4096).url().refine((link) => {
1072
- if (!URL.canParse(link)) return false;
1073
- const url = new URL(link);
1074
- return ["http:", "https:"].includes(url.protocol) && !url.username && !url.password;
1075
- })]);
1076
- const notificationSchema = z.object({
1077
- body: z.string().max(4096).optional(),
1078
- open: z.object({ link: notificationLinkSchema }).strict().optional(),
1079
- title: z.string().min(1).max(4096)
1080
- }).strict();
1081
1101
  const webSocketCloseCode = {
1082
1102
  authenticationRevoked: 4001,
1083
1103
  authenticationUnavailable: 4002
@@ -1085,7 +1105,7 @@ const webSocketCloseCode = {
1085
1105
  z.object({
1086
1106
  debug: z.boolean(),
1087
1107
  operations: z.array(z.string().min(1)),
1088
- protocolVersion: z.literal(9),
1108
+ protocolVersion: z.literal(10),
1089
1109
  resources: z.array(z.string().min(1)),
1090
1110
  streams: z.array(z.string().min(1))
1091
1111
  });
@@ -1191,7 +1211,12 @@ const protocolErrorMessageSchema = z.object({
1191
1211
  subscriptionId: operationIdSchema.optional(),
1192
1212
  type: z.literal("error")
1193
1213
  });
1214
+ const serverInfoMessageSchema = z.object({
1215
+ type: z.literal("server-info"),
1216
+ instanceId: instanceIdSchema
1217
+ }).strict();
1194
1218
  const serverProtocolMessageSchema = z.discriminatedUnion("type", [
1219
+ serverInfoMessageSchema,
1195
1220
  resourceResultMessageSchema,
1196
1221
  resourceInvalidatedMessageSchema,
1197
1222
  streamOpenedMessageSchema,
@@ -2334,6 +2359,10 @@ const createWebSocketServer = ({ auth, developmentWebTarget, runtime, server, se
2334
2359
  sessionSockets,
2335
2360
  socket: webSocket
2336
2361
  });
2362
+ webSocket.send(encodeProtocolFrame({
2363
+ type: "server-info",
2364
+ instanceId: runtime.instance.getInstanceId()
2365
+ }));
2337
2366
  webSocketServer.emit("connection", webSocket, request);
2338
2367
  attachWebSocketConnection({
2339
2368
  runtime,
@@ -2607,6 +2636,30 @@ const createNotificationService = ({ backgroundNotifications, logger }) => {
2607
2636
  };
2608
2637
  };
2609
2638
  //#endregion
2639
+ //#region src/internal/server/runtime/runtime-instance.ts
2640
+ const createRuntimeInstance = (configured) => {
2641
+ let identity;
2642
+ const get = () => {
2643
+ if (!identity) throw new Error("Overmux instance identity is not ready");
2644
+ return identity;
2645
+ };
2646
+ return {
2647
+ context: {
2648
+ getInstanceId: () => get().instanceId,
2649
+ getDeepLinkPrefix: () => get().deepLinkPrefix
2650
+ },
2651
+ establish: (port) => {
2652
+ if (identity) throw new Error("Overmux instance identity is already established");
2653
+ try {
2654
+ identity = createInstanceIdentity(typeof configured === "function" ? configured({ port }) : configured ?? `${hostname()}-${port}`);
2655
+ } catch (cause) {
2656
+ throw new Error("Invalid Overmux instance ID; configure instanceId with a canonical lowercase ID", { cause });
2657
+ }
2658
+ return identity;
2659
+ }
2660
+ };
2661
+ };
2662
+ //#endregion
2610
2663
  //#region src/internal/server/runtime/runtime-lifecycle.ts
2611
2664
  const asError = (cause) => cause instanceof Error ? cause : new Error(String(cause));
2612
2665
  const disposeAll = async (disposers) => {
@@ -2728,7 +2781,7 @@ const getInvalidatedResourceIds = (graph, resourceId) => {
2728
2781
  };
2729
2782
  //#endregion
2730
2783
  //#region src/internal/server/runtime/runtime-resources.ts
2731
- const createRuntimeResources = ({ definitions, lifecycle }) => {
2784
+ const createRuntimeResources = ({ definitions, instance, lifecycle }) => {
2732
2785
  const graph = createResourceDependencyGraph(definitions);
2733
2786
  const invalidationListeners = /* @__PURE__ */ new Set();
2734
2787
  const contracts = /* @__PURE__ */ new Map();
@@ -2753,6 +2806,7 @@ const createRuntimeResources = ({ definitions, lifecycle }) => {
2753
2806
  });
2754
2807
  };
2755
2808
  const context = (signal) => ({
2809
+ instance,
2756
2810
  invalidate,
2757
2811
  signal: lifecycle.requestSignal(signal)
2758
2812
  });
@@ -2887,9 +2941,11 @@ const createRuntimeStreams = ({ context, definitions, lifecycle }) => {
2887
2941
  //#region src/internal/server/runtime/create-runtime.ts
2888
2942
  const createRuntime = async ({ config, debug = false, notifications = { send: async () => void 0 }, signal }) => {
2889
2943
  const lifecycle = createRuntimeLifecycle(signal);
2944
+ const instance = createRuntimeInstance(config.instanceId);
2890
2945
  try {
2891
2946
  const resources = createRuntimeResources({
2892
2947
  definitions: config.server.resources ?? {},
2948
+ instance: instance.context,
2893
2949
  lifecycle
2894
2950
  });
2895
2951
  const context = resources.context;
@@ -2905,14 +2961,17 @@ const createRuntime = async ({ config, debug = false, notifications = { send: as
2905
2961
  });
2906
2962
  lifecycle.signal.throwIfAborted();
2907
2963
  lifecycle.completePreparation();
2964
+ const manifest = {
2965
+ debug,
2966
+ operations: [...operations.names],
2967
+ protocolVersion: 10,
2968
+ resources: [...resources.names],
2969
+ streams: [...streams.names]
2970
+ };
2908
2971
  return {
2909
- manifest: {
2910
- debug,
2911
- operations: [...operations.names],
2912
- protocolVersion: 9,
2913
- resources: [...resources.names],
2914
- streams: [...streams.names]
2915
- },
2972
+ instance: instance.context,
2973
+ establishInstance: instance.establish,
2974
+ manifest,
2916
2975
  dispose: lifecycle.dispose,
2917
2976
  getOperation: operations.get,
2918
2977
  getResource: resources.get,
@@ -2925,8 +2984,9 @@ const createRuntime = async ({ config, debug = false, notifications = { send: as
2925
2984
  };
2926
2985
  //#endregion
2927
2986
  //#region src/internal/server/server-startup-options.ts
2987
+ const serverStartupOverridesRuntimeSchema = serverConfigRuntimeSchema.omit({ port: true }).extend({ port: z.number().int().min(0).max(65535).optional() });
2928
2988
  const resolveServerStartupOptions = ({ config, configPath, overrides = {} }) => {
2929
- const validatedOverrides = serverConfigRuntimeSchema.omit({ port: true }).extend({ port: z.number().int().min(0).max(65535).optional() }).parse(overrides);
2989
+ const validatedOverrides = serverStartupOverridesRuntimeSchema.parse(overrides);
2930
2990
  const configuredLogFile = config.logFile ? resolve(dirname(configPath), config.logFile) : void 0;
2931
2991
  const logFile = validatedOverrides.logFile ? resolve(validatedOverrides.logFile) : configuredLogFile;
2932
2992
  const configuredWebAssetsDir = config.productionWebAssetsDir ? resolve(dirname(configPath), config.productionWebAssetsDir) : void 0;
@@ -3076,12 +3136,20 @@ const startApplicationTransport = async ({ auth, authOrigins, authService, backg
3076
3136
  serverLogger: logger,
3077
3137
  webAssetsDir: developmentWebTarget ? void 0 : startup.productionWebAssetsDir
3078
3138
  });
3139
+ let ready = false;
3079
3140
  const server = serve({
3080
- fetch: app.fetch,
3141
+ fetch: (request, env) => ready ? app.fetch(request, env) : new Response("Starting", { status: 503 }),
3081
3142
  hostname: startup.host,
3082
3143
  port: startup.port
3083
3144
  });
3084
3145
  resources.add(() => closeNodeServer(server, runtime.dispose));
3146
+ const address = await getServerAddress(server);
3147
+ runtime.establishInstance(address.port);
3148
+ const urlHost = startup.host.includes(":") ? `[${startup.host}]` : startup.host;
3149
+ const localUrl = `http://${urlHost}:${address.port}`;
3150
+ const controlApiHost = startup.host === "0.0.0.0" ? "127.0.0.1" : startup.host === "::" ? "[::1]" : urlHost;
3151
+ const origins = authOrigins.establish(localUrl);
3152
+ authService.setOrigins(origins);
3085
3153
  const webSockets = createWebSocketServer({
3086
3154
  auth,
3087
3155
  developmentWebTarget,
@@ -3092,15 +3160,11 @@ const startApplicationTransport = async ({ auth, authOrigins, authService, backg
3092
3160
  resources.add(webSockets.close);
3093
3161
  announceRestarting = webSockets.announceRestarting;
3094
3162
  notifications.attachLiveDelivery(webSockets.broadcastNotification);
3095
- const address = await getServerAddress(server);
3096
- const urlHost = startup.host.includes(":") ? `[${startup.host}]` : startup.host;
3097
- const localUrl = `http://${urlHost}:${address.port}`;
3098
- const controlApiHost = startup.host === "0.0.0.0" ? "127.0.0.1" : startup.host === "::" ? "[::1]" : urlHost;
3099
- const origins = authOrigins.establish(localUrl);
3100
- authService.setOrigins(origins);
3163
+ ready = true;
3101
3164
  const instanceControl = await startInstanceControl({
3102
3165
  auth: authService,
3103
3166
  apiUrl: `http://${controlApiHost}:${address.port}`,
3167
+ instanceId: runtime.instance.getInstanceId(),
3104
3168
  port: address.port,
3105
3169
  url: origins[0]
3106
3170
  });