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.
- package/CHANGELOG.md +44 -0
- package/README.md +2 -2
- package/dist/auth/.vite/manifest.json +2 -2
- package/dist/auth/assets/{index-Bx8JhwVi.css → index-BM9gETvx.css} +1 -1
- package/dist/auth/assets/{index-CHIE7qYj.js → index-DabAfcs1.js} +1 -1
- package/dist/auth/index.html +2 -2
- package/dist/bin.js +219 -126
- package/dist/bin.js.map +1 -1
- package/dist/docs/000-index.md +2 -4
- package/dist/docs/100-introduction/200-how-overmux-works.md +126 -2
- package/{docs/100-introduction/300-why-overmux.md → dist/docs/100-introduction/300-why-i-built-overmux.md} +1 -1
- package/dist/docs/200-getting-started/100-install-and-run-overmux.md +2 -2
- package/dist/docs/200-getting-started/400-secure-with-https/200-tailscale-serve.md +3 -3
- package/dist/docs/200-getting-started/400-secure-with-https/300-cloudflare-tunnel.md +2 -2
- package/dist/docs/200-getting-started/400-secure-with-https/400-self-hosted-reverse-proxy.md +2 -2
- package/dist/docs/400-reference/100-project-structure.md +83 -0
- package/dist/docs/400-reference/200-configuration.md +257 -0
- package/dist/docs/400-reference/300-storage-locations.md +46 -0
- package/dist/docs/400-reference/400-authentication-and-security.md +37 -0
- package/dist/docs/400-reference/500-server/000-index.md +13 -0
- package/dist/docs/400-reference/500-server/100-resources.md +191 -0
- package/dist/docs/400-reference/500-server/200-operations.md +111 -0
- package/dist/docs/400-reference/500-server/300-streams.md +140 -0
- package/dist/docs/400-reference/500-server/400-notifications.md +32 -0
- package/dist/docs/400-reference/500-server/500-api.md +118 -0
- package/dist/docs/400-reference/600-client/000-index.md +9 -0
- package/dist/docs/400-reference/600-client/100-api.md +370 -0
- package/dist/docs/400-reference/600-client/200-theming.md +94 -0
- package/dist/docs/400-reference/600-client/300-tech-stack-recommendations.md +12 -0
- package/{docs/400-reference/400-cli → dist/docs/400-reference/700-cli}/050-init.md +2 -4
- package/dist/docs/400-reference/{400-cli → 700-cli}/100-serve.md +9 -4
- package/{docs/400-reference/400-cli → dist/docs/400-reference/700-cli}/200-auth.md +1 -4
- package/{docs/400-reference/400-cli → dist/docs/400-reference/700-cli}/300-call.md +1 -4
- package/dist/docs/400-reference/700-cli/350-instance.md +21 -0
- package/dist/docs/400-reference/700-cli/600-docs.md +128 -0
- package/{docs/400-reference/400-cli → dist/docs/400-reference/700-cli}/700-desktop.md +1 -3
- package/dist/docs/500-hosted-pages.md +0 -1
- package/dist/exports/client.d.ts +19 -15
- package/dist/exports/client.d.ts.map +1 -1
- package/dist/exports/client.js +240 -58
- package/dist/exports/client.js.map +1 -1
- package/dist/exports/{index-D3_Dpy_T.d.ts → index-Cz3xkCa4.d.ts} +62 -36
- package/dist/exports/index-Cz3xkCa4.d.ts.map +1 -0
- package/dist/exports/index.d.ts +2 -2
- package/dist/exports/index.js +19 -5
- package/dist/exports/index.js.map +1 -1
- package/dist/exports/{notifications-kDp16bU_.js → notifications-BFAD3QQl.js} +34 -4
- package/dist/exports/notifications-BFAD3QQl.js.map +1 -0
- package/dist/exports/server.d.ts +1 -44
- package/dist/exports/server.d.ts.map +1 -1
- package/dist/exports/server.js +8 -1018
- package/dist/exports/server.js.map +1 -1
- package/dist/internal/server/coordinator/server-child.js +153 -85
- package/dist/internal/server/coordinator/server-child.js.map +1 -1
- package/docs/000-index.md +2 -4
- package/docs/100-introduction/200-how-overmux-works.md +126 -2
- package/{dist/docs/100-introduction/300-why-overmux.md → docs/100-introduction/300-why-i-built-overmux.md} +1 -1
- package/docs/200-getting-started/100-install-and-run-overmux.md +2 -2
- package/docs/200-getting-started/400-secure-with-https/200-tailscale-serve.md +3 -3
- package/docs/200-getting-started/400-secure-with-https/300-cloudflare-tunnel.md +2 -2
- package/docs/200-getting-started/400-secure-with-https/400-self-hosted-reverse-proxy.md +2 -2
- package/docs/400-reference/100-project-structure.md +83 -0
- package/docs/400-reference/200-configuration.md +257 -0
- package/docs/400-reference/300-storage-locations.md +46 -0
- package/docs/400-reference/400-authentication-and-security.md +37 -0
- package/docs/400-reference/500-server/000-index.md +13 -0
- package/docs/400-reference/500-server/100-resources.md +191 -0
- package/docs/400-reference/500-server/200-operations.md +111 -0
- package/docs/400-reference/500-server/300-streams.md +140 -0
- package/docs/400-reference/500-server/400-notifications.md +32 -0
- package/docs/400-reference/500-server/500-api.md +118 -0
- package/docs/400-reference/600-client/000-index.md +9 -0
- package/docs/400-reference/600-client/100-api.md +370 -0
- package/docs/400-reference/600-client/200-theming.md +94 -0
- package/docs/400-reference/600-client/300-tech-stack-recommendations.md +12 -0
- package/{dist/docs/400-reference/400-cli → docs/400-reference/700-cli}/050-init.md +2 -4
- package/docs/400-reference/{400-cli → 700-cli}/100-serve.md +9 -4
- package/{dist/docs/400-reference/400-cli → docs/400-reference/700-cli}/200-auth.md +1 -4
- package/{dist/docs/400-reference/400-cli → docs/400-reference/700-cli}/300-call.md +1 -4
- package/docs/400-reference/700-cli/350-instance.md +21 -0
- package/docs/400-reference/700-cli/600-docs.md +128 -0
- package/{dist/docs/400-reference/400-cli → docs/400-reference/700-cli}/700-desktop.md +1 -3
- package/docs/500-hosted-pages.md +0 -1
- package/package.json +3 -2
- package/src/internal/cli/app.ts +5 -5
- package/src/internal/cli/commands/docs-ai-context.ts +33 -0
- package/src/internal/cli/commands/docs.ts +19 -2
- package/src/internal/cli/commands/init-template.ts +1 -1
- package/src/internal/cli/commands/instance.ts +58 -0
- package/src/internal/cli/commands/serve.ts +3 -0
- package/src/internal/cli/login.ts +9 -9
- package/src/internal/cli/parse-port.ts +2 -1
- package/src/internal/client/auth/auth-shell.css +17 -1
- package/src/internal/client/auth/auth-shell.tsx +2 -11
- package/src/internal/client/auth/login-code-input.tsx +140 -0
- package/src/internal/client/browser-api.ts +17 -3
- package/src/internal/client/client-definition.ts +6 -8
- package/src/internal/client/host/deep-link-navigation.ts +75 -0
- package/src/internal/client/host/desktop-host.ts +3 -0
- package/src/internal/client/host/environment.ts +67 -0
- package/src/internal/client/host/instance-forwarding.ts +21 -0
- package/src/internal/client/host/overmux-host.tsx +17 -0
- package/src/internal/client/index.ts +0 -6
- package/src/internal/client/overmux-react.ts +95 -42
- package/src/internal/client/websocket.ts +32 -12
- package/src/internal/server/auth/auth-service.ts +2 -2
- package/src/internal/server/auth/instance-control.ts +89 -17
- package/src/internal/server/coordinator/ipc-protocol.ts +0 -1
- package/src/internal/server/runtime/create-runtime.ts +15 -2
- package/src/internal/server/runtime/runtime-instance.ts +47 -0
- package/src/internal/server/runtime/runtime-operations.ts +9 -3
- package/src/internal/server/runtime/runtime-resources.ts +15 -32
- package/src/internal/server/runtime/runtime-streams.ts +5 -6
- package/src/internal/server/server-logger.ts +5 -10
- package/src/internal/server/server-startup-options.ts +14 -14
- package/src/internal/server/start-application-server.ts +24 -19
- package/src/internal/server/websocket-server.ts +7 -0
- package/src/internal/shared/index.ts +2 -0
- package/src/internal/shared/protocol.ts +10 -1
- package/src/public/ai-context.ts +7 -29
- package/src/public/client.ts +1 -6
- package/src/public/config.ts +163 -31
- package/src/public/index.ts +2 -0
- package/src/public/server.ts +1 -16
- package/dist/docs/100-introduction/100-what-is-overmux.md +0 -7
- package/dist/docs/300-fundamentals/100-project-structure.md +0 -23
- package/dist/docs/300-fundamentals/200-configuration.md +0 -3
- package/dist/docs/300-fundamentals/300-theming.md +0 -3
- package/dist/docs/300-fundamentals/400-server.md +0 -3
- package/dist/docs/300-fundamentals/500-client.md +0 -3
- package/dist/docs/300-fundamentals/600-operations.md +0 -3
- package/dist/docs/300-fundamentals/700-resources.md +0 -3
- package/dist/docs/300-fundamentals/800-streams.md +0 -3
- package/dist/docs/300-fundamentals/900-authentication-and-security.md +0 -3
- package/dist/docs/400-reference/100-configuration.md +0 -3
- package/dist/docs/400-reference/200-server-api.md +0 -3
- package/dist/docs/400-reference/300-client-api.md +0 -20
- package/dist/docs/400-reference/400-cli/400-check.md +0 -20
- package/dist/docs/400-reference/400-cli/500-ai-context.md +0 -102
- package/dist/docs/400-reference/400-cli/600-docs.md +0 -23
- package/dist/exports/index-D3_Dpy_T.d.ts.map +0 -1
- package/dist/exports/notifications-kDp16bU_.js.map +0 -1
- package/docs/100-introduction/100-what-is-overmux.md +0 -7
- package/docs/300-fundamentals/100-project-structure.md +0 -23
- package/docs/300-fundamentals/200-configuration.md +0 -3
- package/docs/300-fundamentals/300-theming.md +0 -3
- package/docs/300-fundamentals/400-server.md +0 -3
- package/docs/300-fundamentals/500-client.md +0 -3
- package/docs/300-fundamentals/600-operations.md +0 -3
- package/docs/300-fundamentals/700-resources.md +0 -3
- package/docs/300-fundamentals/800-streams.md +0 -3
- package/docs/300-fundamentals/900-authentication-and-security.md +0 -3
- package/docs/400-reference/100-configuration.md +0 -3
- package/docs/400-reference/200-server-api.md +0 -3
- package/docs/400-reference/300-client-api.md +0 -20
- package/docs/400-reference/400-cli/400-check.md +0 -20
- package/docs/400-reference/400-cli/500-ai-context.md +0 -102
- package/docs/400-reference/400-cli/600-docs.md +0 -23
- package/src/internal/cli/commands/ai.ts +0 -44
|
@@ -4,13 +4,13 @@ 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,
|
|
7
|
+
import { homedir, hostname } 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";
|
|
11
11
|
import { bodyLimit } from "hono/body-limit";
|
|
12
12
|
import { parse } from "hono/utils/cookie";
|
|
13
|
-
import { appendFileSync, mkdirSync, readFileSync } from "node:fs";
|
|
13
|
+
import { appendFileSync, constants, mkdirSync, readFileSync } from "node:fs";
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
|
15
15
|
import { Hono } from "hono";
|
|
16
16
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
@@ -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({
|
|
@@ -83,12 +84,11 @@ const loopbackPeerSchema = z.string().refine((value) => isIP(value) !== 0 && (va
|
|
|
83
84
|
const authConfigRuntimeSchema = z.object({
|
|
84
85
|
mode: z.literal("cli-login"),
|
|
85
86
|
origins: z.array(exactOriginSchema).min(1).refine((origins) => new Set(origins).size === origins.length, "Expected unique origins").optional(),
|
|
86
|
-
sessionLifetime: z.union([z.literal("
|
|
87
|
+
sessionLifetime: z.union([z.literal("forever"), authDurationSchema]).default("forever"),
|
|
87
88
|
trustedProxyPeer: loopbackPeerSchema.optional()
|
|
88
89
|
}).strict();
|
|
89
90
|
const serverConfigRuntimeSchema = z.object({
|
|
90
91
|
host: z.string().min(1).optional(),
|
|
91
|
-
logFile: z.string().min(1).optional(),
|
|
92
92
|
port: z.number().int().min(1).max(65535).optional(),
|
|
93
93
|
productionWebAssetsDir: z.string().min(1).optional(),
|
|
94
94
|
watch: z.boolean().optional()
|
|
@@ -102,6 +102,7 @@ const configDefinitionRuntimeSchema = serverConfigRuntimeSchema.extend({
|
|
|
102
102
|
aiContextSnippets: z.array(z.enum(aiContextSnippets)).optional(),
|
|
103
103
|
auth: authConfigRuntimeSchema,
|
|
104
104
|
debug: z.boolean().optional(),
|
|
105
|
+
instanceId: z.union([instanceIdSchema$1, z.custom((value) => typeof value === "function", "Expected an instance ID or a function of the bound port")]).optional(),
|
|
105
106
|
server: serverDefinitionRuntimeSchema,
|
|
106
107
|
vite: z.string().min(1).optional()
|
|
107
108
|
});
|
|
@@ -400,7 +401,7 @@ const createAuthService = ({ config, environment = process.env, homeDirectory =
|
|
|
400
401
|
const createSession = ({ kind, lifetimeMs, origin }) => {
|
|
401
402
|
const token = randomToken();
|
|
402
403
|
const createdAt = new Date(now()).toISOString();
|
|
403
|
-
const configuredLifetime = config.sessionLifetime && config.sessionLifetime !== "
|
|
404
|
+
const configuredLifetime = config.sessionLifetime && config.sessionLifetime !== "forever" ? durationMilliseconds(config.sessionLifetime) : void 0;
|
|
404
405
|
const expiresIn = lifetimeMs ?? configuredLifetime;
|
|
405
406
|
return {
|
|
406
407
|
stored: {
|
|
@@ -844,9 +845,41 @@ const createHttpAuthenticationHandlers = ({ auth, authAssetsDirectory }) => {
|
|
|
844
845
|
};
|
|
845
846
|
};
|
|
846
847
|
//#endregion
|
|
848
|
+
//#region ../shared/dist/index.js
|
|
849
|
+
const overmuxSettingsPath = "/_overmux/settings";
|
|
850
|
+
const overmuxLogoutPath = "/_overmux/logout";
|
|
851
|
+
const safeRelativeNotificationLink = (link) => {
|
|
852
|
+
let decoded;
|
|
853
|
+
try {
|
|
854
|
+
decoded = decodeURIComponent(link);
|
|
855
|
+
} catch {
|
|
856
|
+
return false;
|
|
857
|
+
}
|
|
858
|
+
return !decoded.startsWith("//") && !decoded.includes("\\") && ![...decoded].some((character) => {
|
|
859
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
860
|
+
return codePoint < 32 || codePoint === 127;
|
|
861
|
+
});
|
|
862
|
+
};
|
|
863
|
+
const notificationLinkSchema = z.union([z.string().max(4096).startsWith("/").refine(safeRelativeNotificationLink), z.string().max(4096).url().refine((link) => {
|
|
864
|
+
if (!URL.canParse(link)) return false;
|
|
865
|
+
const url = new URL(link);
|
|
866
|
+
return ["http:", "https:"].includes(url.protocol) && !url.username && !url.password;
|
|
867
|
+
})]);
|
|
868
|
+
const notificationSchema = z.object({
|
|
869
|
+
body: z.string().max(4096).optional(),
|
|
870
|
+
open: z.object({ link: notificationLinkSchema }).strict().optional(),
|
|
871
|
+
title: z.string().min(1).max(4096)
|
|
872
|
+
}).strict();
|
|
873
|
+
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");
|
|
874
|
+
const createInstanceIdentity = (instanceId) => ({
|
|
875
|
+
instanceId: instanceIdSchema.parse(instanceId),
|
|
876
|
+
deepLinkPrefix: `overmux://${instanceId}`
|
|
877
|
+
});
|
|
878
|
+
//#endregion
|
|
847
879
|
//#region src/internal/server/auth/instance-control.ts
|
|
848
880
|
const maxControlMessageBytes = 16384;
|
|
849
881
|
const controlRequestSchema = z.discriminatedUnion("type", [
|
|
882
|
+
z.object({ type: z.literal("get-instance") }).strict(),
|
|
850
883
|
z.object({ type: z.literal("ping") }).strict(),
|
|
851
884
|
z.object({ type: z.literal("create-login") }).strict(),
|
|
852
885
|
z.object({ type: z.literal("issue-bearer") }).strict(),
|
|
@@ -872,6 +905,9 @@ const sessionSchema = z.object({
|
|
|
872
905
|
revokedAt: z.string().optional()
|
|
873
906
|
}).strict();
|
|
874
907
|
z.object({
|
|
908
|
+
instanceId: instanceIdSchema,
|
|
909
|
+
ok: z.literal(true)
|
|
910
|
+
}).strict(), z.object({
|
|
875
911
|
login: loginGrantSchema,
|
|
876
912
|
ok: z.literal(true)
|
|
877
913
|
}).strict(), z.object({
|
|
@@ -902,12 +938,26 @@ z.object({
|
|
|
902
938
|
port: z.number().int().min(1).max(65535),
|
|
903
939
|
url: z.string().url()
|
|
904
940
|
});
|
|
941
|
+
const currentUid = () => {
|
|
942
|
+
if (!process.getuid) throw new Error("Overmux instance control requires a Unix user ID");
|
|
943
|
+
return process.getuid();
|
|
944
|
+
};
|
|
905
945
|
const runtimeDirectory = () => {
|
|
906
|
-
const
|
|
907
|
-
const preferred =
|
|
946
|
+
const fallback = `/tmp/overmux-${currentUid()}`;
|
|
947
|
+
const preferred = getOvermuxPaths().runtimeDir;
|
|
948
|
+
if (!preferred) return fallback;
|
|
908
949
|
if (Buffer.byteLength(preferred) <= 80) return preferred;
|
|
909
|
-
|
|
910
|
-
|
|
950
|
+
return `${fallback}-${createHash("sha256").update(preferred).digest("hex").slice(0, 12)}`;
|
|
951
|
+
};
|
|
952
|
+
const secureRuntimeDirectory = async (directory) => {
|
|
953
|
+
const handle = await open(directory, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
|
|
954
|
+
try {
|
|
955
|
+
const metadata = await handle.stat();
|
|
956
|
+
if (!metadata.isDirectory() || metadata.uid !== currentUid()) throw new Error(`Unsafe Overmux runtime directory: ${directory}`);
|
|
957
|
+
await handle.chmod(448);
|
|
958
|
+
} finally {
|
|
959
|
+
await handle.close();
|
|
960
|
+
}
|
|
911
961
|
};
|
|
912
962
|
const readNewlineFrame = ({ maximumBytes, socket, timeout }) => new Promise((resolve, reject) => {
|
|
913
963
|
let buffer = Buffer.alloc(0);
|
|
@@ -950,7 +1000,11 @@ const readNewlineFrame = ({ maximumBytes, socket, timeout }) => new Promise((res
|
|
|
950
1000
|
const writeResponse = (socket, response) => {
|
|
951
1001
|
socket.end(`${JSON.stringify(response)}\n`);
|
|
952
1002
|
};
|
|
953
|
-
const handleRequest = async (auth, request) => {
|
|
1003
|
+
const handleRequest = async ({ auth, instanceId, request }) => {
|
|
1004
|
+
if (request.type === "get-instance") return {
|
|
1005
|
+
instanceId,
|
|
1006
|
+
ok: true
|
|
1007
|
+
};
|
|
954
1008
|
if (request.type === "ping") return { ok: true };
|
|
955
1009
|
if (request.type === "create-login") return {
|
|
956
1010
|
login: await auth.createLoginGrant(),
|
|
@@ -973,14 +1027,18 @@ const handleRequest = async (auth, request) => {
|
|
|
973
1027
|
ok: true
|
|
974
1028
|
};
|
|
975
1029
|
};
|
|
976
|
-
const handleConnection = async ({ auth, socket }) => {
|
|
1030
|
+
const handleConnection = async ({ auth, instanceId, socket }) => {
|
|
977
1031
|
try {
|
|
978
1032
|
const frame = await readNewlineFrame({
|
|
979
1033
|
maximumBytes: maxControlMessageBytes,
|
|
980
1034
|
socket
|
|
981
1035
|
});
|
|
982
1036
|
const request = controlRequestSchema.parse(JSON.parse(frame.toString()));
|
|
983
|
-
writeResponse(socket, await handleRequest(
|
|
1037
|
+
writeResponse(socket, await handleRequest({
|
|
1038
|
+
auth,
|
|
1039
|
+
instanceId,
|
|
1040
|
+
request
|
|
1041
|
+
}));
|
|
984
1042
|
} catch (cause) {
|
|
985
1043
|
writeResponse(socket, {
|
|
986
1044
|
error: cause instanceof Error ? cause.message : "Invalid control request",
|
|
@@ -988,13 +1046,13 @@ const handleConnection = async ({ auth, socket }) => {
|
|
|
988
1046
|
});
|
|
989
1047
|
}
|
|
990
1048
|
};
|
|
991
|
-
const startInstanceControl = async ({ auth, apiUrl, port, url }) => {
|
|
1049
|
+
const startInstanceControl = async ({ auth, apiUrl, instanceId, port, url }) => {
|
|
992
1050
|
const directory = runtimeDirectory();
|
|
993
1051
|
await mkdir(directory, {
|
|
994
1052
|
mode: 448,
|
|
995
1053
|
recursive: true
|
|
996
1054
|
});
|
|
997
|
-
await
|
|
1055
|
+
await secureRuntimeDirectory(directory);
|
|
998
1056
|
const id = randomBytes(8).toString("hex");
|
|
999
1057
|
const socketPath = join(directory, `${id}.sock`);
|
|
1000
1058
|
const registrationPath = join(directory, `${id}.json`);
|
|
@@ -1004,6 +1062,7 @@ const startInstanceControl = async ({ auth, apiUrl, port, url }) => {
|
|
|
1004
1062
|
socket.once("close", () => sockets.delete(socket));
|
|
1005
1063
|
handleConnection({
|
|
1006
1064
|
auth,
|
|
1065
|
+
instanceId,
|
|
1007
1066
|
socket
|
|
1008
1067
|
});
|
|
1009
1068
|
});
|
|
@@ -1054,30 +1113,6 @@ const backgroundNotificationSubscriptionSchema = z.object({
|
|
|
1054
1113
|
}).strict();
|
|
1055
1114
|
z.object({ publicKey: deliveryKeySchema }).strict();
|
|
1056
1115
|
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
1116
|
const webSocketCloseCode = {
|
|
1082
1117
|
authenticationRevoked: 4001,
|
|
1083
1118
|
authenticationUnavailable: 4002
|
|
@@ -1085,7 +1120,7 @@ const webSocketCloseCode = {
|
|
|
1085
1120
|
z.object({
|
|
1086
1121
|
debug: z.boolean(),
|
|
1087
1122
|
operations: z.array(z.string().min(1)),
|
|
1088
|
-
protocolVersion: z.literal(
|
|
1123
|
+
protocolVersion: z.literal(10),
|
|
1089
1124
|
resources: z.array(z.string().min(1)),
|
|
1090
1125
|
streams: z.array(z.string().min(1))
|
|
1091
1126
|
});
|
|
@@ -1191,7 +1226,12 @@ const protocolErrorMessageSchema = z.object({
|
|
|
1191
1226
|
subscriptionId: operationIdSchema.optional(),
|
|
1192
1227
|
type: z.literal("error")
|
|
1193
1228
|
});
|
|
1229
|
+
const serverInfoMessageSchema = z.object({
|
|
1230
|
+
type: z.literal("server-info"),
|
|
1231
|
+
instanceId: instanceIdSchema
|
|
1232
|
+
}).strict();
|
|
1194
1233
|
const serverProtocolMessageSchema = z.discriminatedUnion("type", [
|
|
1234
|
+
serverInfoMessageSchema,
|
|
1195
1235
|
resourceResultMessageSchema,
|
|
1196
1236
|
resourceInvalidatedMessageSchema,
|
|
1197
1237
|
streamOpenedMessageSchema,
|
|
@@ -1308,7 +1348,7 @@ const lifecycleEvents = /* @__PURE__ */ new Set([
|
|
|
1308
1348
|
"websocket-connect",
|
|
1309
1349
|
"websocket-open"
|
|
1310
1350
|
]);
|
|
1311
|
-
const
|
|
1351
|
+
const getServerLogFile = ({ homeDir, xdgStateHome } = {}) => join(getOvermuxPaths({
|
|
1312
1352
|
homeDir,
|
|
1313
1353
|
xdgStateHome
|
|
1314
1354
|
}).stateDir, "overmux.log");
|
|
@@ -1324,8 +1364,7 @@ const safeJson = (value) => {
|
|
|
1324
1364
|
});
|
|
1325
1365
|
}
|
|
1326
1366
|
};
|
|
1327
|
-
const createServerLogger = ({ enabled, logFile
|
|
1328
|
-
let activeLogFile = logFile;
|
|
1367
|
+
const createServerLogger = ({ enabled, logFile }) => {
|
|
1329
1368
|
let debugEnabled = enabled;
|
|
1330
1369
|
return {
|
|
1331
1370
|
get enabled() {
|
|
@@ -1340,15 +1379,12 @@ const createServerLogger = ({ enabled, logFile = getDefaultServerLogFile() }) =>
|
|
|
1340
1379
|
source: entry.source ?? "server",
|
|
1341
1380
|
...entry
|
|
1342
1381
|
});
|
|
1343
|
-
mkdirSync(dirname(
|
|
1344
|
-
appendFileSync(
|
|
1382
|
+
mkdirSync(dirname(logFile), { recursive: true });
|
|
1383
|
+
appendFileSync(logFile, `${record}\n`, "utf8");
|
|
1345
1384
|
process.stderr.write(`[overmux:debug] ${record}\n`);
|
|
1346
1385
|
},
|
|
1347
1386
|
setEnabled: (nextEnabled) => {
|
|
1348
1387
|
debugEnabled = nextEnabled;
|
|
1349
|
-
},
|
|
1350
|
-
setLogFile: (nextLogFile) => {
|
|
1351
|
-
activeLogFile = nextLogFile;
|
|
1352
1388
|
}
|
|
1353
1389
|
};
|
|
1354
1390
|
};
|
|
@@ -2334,6 +2370,10 @@ const createWebSocketServer = ({ auth, developmentWebTarget, runtime, server, se
|
|
|
2334
2370
|
sessionSockets,
|
|
2335
2371
|
socket: webSocket
|
|
2336
2372
|
});
|
|
2373
|
+
webSocket.send(encodeProtocolFrame({
|
|
2374
|
+
type: "server-info",
|
|
2375
|
+
instanceId: runtime.instance.getInstanceId()
|
|
2376
|
+
}));
|
|
2337
2377
|
webSocketServer.emit("connection", webSocket, request);
|
|
2338
2378
|
attachWebSocketConnection({
|
|
2339
2379
|
runtime,
|
|
@@ -2607,6 +2647,31 @@ const createNotificationService = ({ backgroundNotifications, logger }) => {
|
|
|
2607
2647
|
};
|
|
2608
2648
|
};
|
|
2609
2649
|
//#endregion
|
|
2650
|
+
//#region src/internal/server/runtime/runtime-instance.ts
|
|
2651
|
+
const defaultInstanceId = ({ port }) => `${hostname()}-${port}`;
|
|
2652
|
+
const createRuntimeInstance = (configured) => {
|
|
2653
|
+
let identity;
|
|
2654
|
+
const get = () => {
|
|
2655
|
+
if (!identity) throw new Error("Overmux instance identity is not ready");
|
|
2656
|
+
return identity;
|
|
2657
|
+
};
|
|
2658
|
+
return {
|
|
2659
|
+
context: {
|
|
2660
|
+
getInstanceId: () => get().instanceId,
|
|
2661
|
+
getDeepLinkPrefix: () => get().deepLinkPrefix
|
|
2662
|
+
},
|
|
2663
|
+
establish: (port) => {
|
|
2664
|
+
if (identity) throw new Error("Overmux instance identity is already established");
|
|
2665
|
+
try {
|
|
2666
|
+
identity = createInstanceIdentity(typeof configured === "function" ? configured({ port }) : configured);
|
|
2667
|
+
} catch (cause) {
|
|
2668
|
+
throw new Error("Invalid Overmux instance ID; configure instanceId with a canonical lowercase ID", { cause });
|
|
2669
|
+
}
|
|
2670
|
+
return identity;
|
|
2671
|
+
}
|
|
2672
|
+
};
|
|
2673
|
+
};
|
|
2674
|
+
//#endregion
|
|
2610
2675
|
//#region src/internal/server/runtime/runtime-lifecycle.ts
|
|
2611
2676
|
const asError = (cause) => cause instanceof Error ? cause : new Error(String(cause));
|
|
2612
2677
|
const disposeAll = async (disposers) => {
|
|
@@ -2728,14 +2793,10 @@ const getInvalidatedResourceIds = (graph, resourceId) => {
|
|
|
2728
2793
|
};
|
|
2729
2794
|
//#endregion
|
|
2730
2795
|
//#region src/internal/server/runtime/runtime-resources.ts
|
|
2731
|
-
const createRuntimeResources = ({ definitions, lifecycle }) => {
|
|
2796
|
+
const createRuntimeResources = ({ definitions, instance, lifecycle }) => {
|
|
2732
2797
|
const graph = createResourceDependencyGraph(definitions);
|
|
2733
2798
|
const invalidationListeners = /* @__PURE__ */ new Set();
|
|
2734
|
-
const contracts = /* @__PURE__ */ new Map();
|
|
2735
2799
|
const resources = /* @__PURE__ */ new Map();
|
|
2736
|
-
Object.entries(definitions).forEach(([id, definition]) => {
|
|
2737
|
-
contracts.set(definition.contract, [...contracts.get(definition.contract) ?? [], id]);
|
|
2738
|
-
});
|
|
2739
2800
|
const invalidateResource = (id, input, allInputs = false) => {
|
|
2740
2801
|
if (lifecycle.signal.aborted) return;
|
|
2741
2802
|
const ids = getInvalidatedResourceIds(graph, id);
|
|
@@ -2743,16 +2804,14 @@ const createRuntimeResources = ({ definitions, lifecycle }) => {
|
|
|
2743
2804
|
if (ids.has(entry.resourceId) && (allInputs || isDeepStrictEqual(entry.input, input))) entry.listener();
|
|
2744
2805
|
});
|
|
2745
2806
|
};
|
|
2746
|
-
const invalidate = (
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
const parsedInput = input === void 0 ? void 0 : definition.contract.input.parse(input);
|
|
2752
|
-
invalidateResource(id, parsedInput, input === void 0);
|
|
2753
|
-
});
|
|
2807
|
+
const invalidate = (resourceId, input) => {
|
|
2808
|
+
if (!resources.has(resourceId)) throw new Error(`Unknown resource ID: ${resourceId}`);
|
|
2809
|
+
const definition = definitions[resourceId];
|
|
2810
|
+
const parsedInput = input === void 0 ? void 0 : definition.contract.input.parse(input);
|
|
2811
|
+
invalidateResource(resourceId, parsedInput, input === void 0);
|
|
2754
2812
|
};
|
|
2755
2813
|
const context = (signal) => ({
|
|
2814
|
+
instance,
|
|
2756
2815
|
invalidate,
|
|
2757
2816
|
signal: lifecycle.requestSignal(signal)
|
|
2758
2817
|
});
|
|
@@ -2885,11 +2944,13 @@ const createRuntimeStreams = ({ context, definitions, lifecycle }) => {
|
|
|
2885
2944
|
};
|
|
2886
2945
|
//#endregion
|
|
2887
2946
|
//#region src/internal/server/runtime/create-runtime.ts
|
|
2888
|
-
const createRuntime = async ({ config, debug =
|
|
2947
|
+
const createRuntime = async ({ config, debug = true, notifications = { send: async () => void 0 }, signal }) => {
|
|
2889
2948
|
const lifecycle = createRuntimeLifecycle(signal);
|
|
2949
|
+
const instance = createRuntimeInstance(config.instanceId ?? defaultInstanceId);
|
|
2890
2950
|
try {
|
|
2891
2951
|
const resources = createRuntimeResources({
|
|
2892
2952
|
definitions: config.server.resources ?? {},
|
|
2953
|
+
instance: instance.context,
|
|
2893
2954
|
lifecycle
|
|
2894
2955
|
});
|
|
2895
2956
|
const context = resources.context;
|
|
@@ -2905,14 +2966,17 @@ const createRuntime = async ({ config, debug = false, notifications = { send: as
|
|
|
2905
2966
|
});
|
|
2906
2967
|
lifecycle.signal.throwIfAborted();
|
|
2907
2968
|
lifecycle.completePreparation();
|
|
2969
|
+
const manifest = {
|
|
2970
|
+
debug,
|
|
2971
|
+
operations: [...operations.names],
|
|
2972
|
+
protocolVersion: 10,
|
|
2973
|
+
resources: [...resources.names],
|
|
2974
|
+
streams: [...streams.names]
|
|
2975
|
+
};
|
|
2908
2976
|
return {
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
protocolVersion: 9,
|
|
2913
|
-
resources: [...resources.names],
|
|
2914
|
-
streams: [...streams.names]
|
|
2915
|
-
},
|
|
2977
|
+
instance: instance.context,
|
|
2978
|
+
establishInstance: instance.establish,
|
|
2979
|
+
manifest,
|
|
2916
2980
|
dispose: lifecycle.dispose,
|
|
2917
2981
|
getOperation: operations.get,
|
|
2918
2982
|
getResource: resources.get,
|
|
@@ -2925,16 +2989,18 @@ const createRuntime = async ({ config, debug = false, notifications = { send: as
|
|
|
2925
2989
|
};
|
|
2926
2990
|
//#endregion
|
|
2927
2991
|
//#region src/internal/server/server-startup-options.ts
|
|
2992
|
+
const serverStartupOverridesRuntimeSchema = serverConfigRuntimeSchema.omit({ port: true }).extend({
|
|
2993
|
+
debug: z.boolean().optional(),
|
|
2994
|
+
port: z.number().int().min(0).max(65535).optional()
|
|
2995
|
+
});
|
|
2928
2996
|
const resolveServerStartupOptions = ({ config, configPath, overrides = {} }) => {
|
|
2929
|
-
const validatedOverrides =
|
|
2930
|
-
const configuredLogFile = config.logFile ? resolve(dirname(configPath), config.logFile) : void 0;
|
|
2931
|
-
const logFile = validatedOverrides.logFile ? resolve(validatedOverrides.logFile) : configuredLogFile;
|
|
2997
|
+
const validatedOverrides = serverStartupOverridesRuntimeSchema.parse(overrides);
|
|
2932
2998
|
const configuredWebAssetsDir = config.productionWebAssetsDir ? resolve(dirname(configPath), config.productionWebAssetsDir) : void 0;
|
|
2933
2999
|
const productionWebAssetsDir = validatedOverrides.productionWebAssetsDir ? resolve(validatedOverrides.productionWebAssetsDir) : configuredWebAssetsDir;
|
|
2934
3000
|
return {
|
|
3001
|
+
debug: validatedOverrides.debug ?? config.debug ?? true,
|
|
2935
3002
|
host: validatedOverrides.host ?? config.host ?? "localhost",
|
|
2936
|
-
|
|
2937
|
-
port: validatedOverrides.port ?? config.port ?? 4200,
|
|
3003
|
+
port: validatedOverrides.port ?? config.port ?? 4242,
|
|
2938
3004
|
...productionWebAssetsDir === void 0 ? {} : { productionWebAssetsDir },
|
|
2939
3005
|
watch: validatedOverrides.watch ?? config.watch ?? true
|
|
2940
3006
|
};
|
|
@@ -3076,12 +3142,20 @@ const startApplicationTransport = async ({ auth, authOrigins, authService, backg
|
|
|
3076
3142
|
serverLogger: logger,
|
|
3077
3143
|
webAssetsDir: developmentWebTarget ? void 0 : startup.productionWebAssetsDir
|
|
3078
3144
|
});
|
|
3145
|
+
let ready = false;
|
|
3079
3146
|
const server = serve({
|
|
3080
|
-
fetch: app.fetch,
|
|
3147
|
+
fetch: (request, env) => ready ? app.fetch(request, env) : new Response("Starting", { status: 503 }),
|
|
3081
3148
|
hostname: startup.host,
|
|
3082
3149
|
port: startup.port
|
|
3083
3150
|
});
|
|
3084
3151
|
resources.add(() => closeNodeServer(server, runtime.dispose));
|
|
3152
|
+
const address = await getServerAddress(server);
|
|
3153
|
+
runtime.establishInstance(address.port);
|
|
3154
|
+
const urlHost = startup.host.includes(":") ? `[${startup.host}]` : startup.host;
|
|
3155
|
+
const localUrl = `http://${urlHost}:${address.port}`;
|
|
3156
|
+
const controlApiHost = startup.host === "0.0.0.0" ? "127.0.0.1" : startup.host === "::" ? "[::1]" : urlHost;
|
|
3157
|
+
const origins = authOrigins.establish(localUrl);
|
|
3158
|
+
authService.setOrigins(origins);
|
|
3085
3159
|
const webSockets = createWebSocketServer({
|
|
3086
3160
|
auth,
|
|
3087
3161
|
developmentWebTarget,
|
|
@@ -3092,15 +3166,11 @@ const startApplicationTransport = async ({ auth, authOrigins, authService, backg
|
|
|
3092
3166
|
resources.add(webSockets.close);
|
|
3093
3167
|
announceRestarting = webSockets.announceRestarting;
|
|
3094
3168
|
notifications.attachLiveDelivery(webSockets.broadcastNotification);
|
|
3095
|
-
|
|
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);
|
|
3169
|
+
ready = true;
|
|
3101
3170
|
const instanceControl = await startInstanceControl({
|
|
3102
3171
|
auth: authService,
|
|
3103
3172
|
apiUrl: `http://${controlApiHost}:${address.port}`,
|
|
3173
|
+
instanceId: runtime.instance.getInstanceId(),
|
|
3104
3174
|
port: address.port,
|
|
3105
3175
|
url: origins[0]
|
|
3106
3176
|
});
|
|
@@ -3127,8 +3197,8 @@ const failApplicationServerStartup = async (cause, resources) => {
|
|
|
3127
3197
|
};
|
|
3128
3198
|
const startApplicationServer = async ({ onRestartRequested, options }) => {
|
|
3129
3199
|
const logger = createServerLogger({
|
|
3130
|
-
enabled: options.debug ??
|
|
3131
|
-
logFile:
|
|
3200
|
+
enabled: options.debug ?? true,
|
|
3201
|
+
logFile: getServerLogFile()
|
|
3132
3202
|
});
|
|
3133
3203
|
const resources = createResources();
|
|
3134
3204
|
resources.add(registerProcessErrorLogging(logger));
|
|
@@ -3142,8 +3212,7 @@ const startApplicationServer = async ({ onRestartRequested, options }) => {
|
|
|
3142
3212
|
configPath,
|
|
3143
3213
|
options
|
|
3144
3214
|
});
|
|
3145
|
-
logger.setEnabled(
|
|
3146
|
-
if (startup.logFile) logger.setLogFile(startup.logFile);
|
|
3215
|
+
logger.setEnabled(startup.debug);
|
|
3147
3216
|
const { auth, authOrigins, authService, backgroundNotifications, notifications, runtime } = await createApplicationServices({
|
|
3148
3217
|
config,
|
|
3149
3218
|
logger,
|
|
@@ -3187,7 +3256,6 @@ const childOptionsSchema = z.object({
|
|
|
3187
3256
|
debug: z.boolean().optional(),
|
|
3188
3257
|
developmentWebTarget: z.url().optional(),
|
|
3189
3258
|
host: z.string().optional(),
|
|
3190
|
-
logFile: z.string().optional(),
|
|
3191
3259
|
port: z.number().int().min(0).max(65535).optional(),
|
|
3192
3260
|
productionWebAssetsDir: z.string().optional(),
|
|
3193
3261
|
watch: z.boolean().optional()
|