overmux 0.0.2 → 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.
- package/CHANGELOG.md +21 -0
- 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 +430 -18
- package/dist/bin.js.map +1 -1
- package/dist/docs/200-getting-started/100-install-and-run-overmux.md +1 -2
- package/dist/docs/300-fundamentals/100-project-structure.md +20 -0
- package/dist/docs/300-fundamentals/300-theming.md +51 -0
- package/dist/docs/400-reference/100-configuration.md +20 -0
- package/dist/docs/400-reference/200-server-api.md +18 -0
- package/dist/docs/400-reference/300-client-api.md +36 -0
- package/dist/docs/400-reference/400-cli/050-init.md +17 -0
- package/dist/docs/400-reference/400-cli/350-instance.md +24 -0
- package/dist/exports/client.d.ts +33 -3
- package/dist/exports/client.d.ts.map +1 -1
- package/dist/exports/client.js +123 -14
- package/dist/exports/client.js.map +1 -1
- package/dist/exports/{index-D3_Dpy_T.d.ts → index-DS70rKzo.d.ts} +14 -2
- package/dist/exports/index-DS70rKzo.d.ts.map +1 -0
- package/dist/exports/index.d.ts +2 -2
- package/dist/exports/index.js +1 -1
- package/dist/exports/index.js.map +1 -1
- package/dist/exports/{notifications-kDp16bU_.js → notifications-av0FK0yZ.js} +7 -2
- package/dist/exports/{notifications-kDp16bU_.js.map → notifications-av0FK0yZ.js.map} +1 -1
- package/dist/exports/server.d.ts.map +1 -1
- package/dist/exports/server.js +52 -10
- package/dist/exports/server.js.map +1 -1
- package/dist/internal/server/coordinator/server-child.js +110 -46
- package/dist/internal/server/coordinator/server-child.js.map +1 -1
- package/docs/200-getting-started/100-install-and-run-overmux.md +1 -2
- package/docs/300-fundamentals/100-project-structure.md +20 -0
- package/docs/300-fundamentals/300-theming.md +51 -0
- package/docs/400-reference/100-configuration.md +20 -0
- package/docs/400-reference/200-server-api.md +18 -0
- package/docs/400-reference/300-client-api.md +36 -0
- package/docs/400-reference/400-cli/050-init.md +17 -0
- package/docs/400-reference/400-cli/350-instance.md +24 -0
- package/package.json +2 -2
- package/src/internal/cli/app.ts +7 -1
- package/src/internal/cli/commands/init-template.ts +146 -0
- package/src/internal/cli/commands/init.ts +267 -0
- package/src/internal/cli/commands/instance.ts +58 -0
- package/src/internal/cli/commands/integration.ts +15 -0
- package/src/internal/cli/commands/zellij-install.ts +96 -0
- 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/clipboard.ts +22 -0
- package/src/internal/client/host/desktop-host.ts +15 -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/notification-forwarding.ts +1 -10
- package/src/internal/client/host/overmux-host.tsx +8 -0
- package/src/internal/client/overmux-react.ts +24 -2
- package/src/internal/client/websocket.ts +48 -20
- package/src/internal/server/auth/instance-control.ts +24 -3
- package/src/internal/server/runtime/create-runtime.ts +12 -1
- package/src/internal/server/runtime/runtime-instance.ts +46 -0
- package/src/internal/server/runtime/runtime-resources.ts +3 -0
- package/src/internal/server/server-startup-options.ts +9 -4
- package/src/internal/server/start-application-server.ts +20 -12
- 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/client.ts +5 -0
- package/src/public/config.ts +7 -0
- package/src/public/index.ts +2 -0
- package/dist/exports/index-D3_Dpy_T.d.ts.map +0 -1
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
clientProtocolMessageSchema,
|
|
3
|
+
createInstanceIdentity,
|
|
4
|
+
type InstanceIdentity,
|
|
3
5
|
decodeProtocolFrame,
|
|
4
6
|
encodeProtocolMessage,
|
|
5
7
|
serverProtocolMessageSchema,
|
|
@@ -42,6 +44,8 @@ export type TransportStatus =
|
|
|
42
44
|
| "disconnected";
|
|
43
45
|
|
|
44
46
|
export type WebSocketTransport = {
|
|
47
|
+
getInstance: () => InstanceIdentity | undefined;
|
|
48
|
+
subscribeInstance: (listener: () => void) => () => void;
|
|
45
49
|
activate: () => void;
|
|
46
50
|
dispose: () => void;
|
|
47
51
|
openStream: (input: {
|
|
@@ -99,6 +103,12 @@ export const createWebSocketTransport = ({
|
|
|
99
103
|
let reconnectTimer: ReturnType<typeof setTimeout> | undefined;
|
|
100
104
|
let socket: WebSocket | undefined;
|
|
101
105
|
let status: TransportStatus = "disconnected";
|
|
106
|
+
let instance: InstanceIdentity | undefined;
|
|
107
|
+
const instanceListeners = new Set<() => void>();
|
|
108
|
+
const setInstance = (next: InstanceIdentity | undefined) => {
|
|
109
|
+
instance = next;
|
|
110
|
+
instanceListeners.forEach((listener) => listener());
|
|
111
|
+
};
|
|
102
112
|
const diagnosticQueue: ClientDiagnosticEvent[] = [];
|
|
103
113
|
const requests = new Map<string, Request>();
|
|
104
114
|
const subscriptions = new Map<string, Subscription>();
|
|
@@ -115,7 +125,7 @@ export const createWebSocketTransport = ({
|
|
|
115
125
|
statusListeners.forEach((listener) => listener(status));
|
|
116
126
|
};
|
|
117
127
|
const send = (message: unknown) => {
|
|
118
|
-
if (socket?.readyState !== WebSocket.OPEN) {
|
|
128
|
+
if (socket?.readyState !== WebSocket.OPEN || !instance) {
|
|
119
129
|
return false;
|
|
120
130
|
}
|
|
121
131
|
// Apply outbound backpressure before queued messages consume unbounded memory.
|
|
@@ -189,6 +199,7 @@ export const createWebSocketTransport = ({
|
|
|
189
199
|
requests.clear();
|
|
190
200
|
};
|
|
191
201
|
const reportDisconnect = () => {
|
|
202
|
+
setInstance(undefined);
|
|
192
203
|
const error = disconnected();
|
|
193
204
|
rejectRequests(error);
|
|
194
205
|
subscriptions.forEach(({ onError }) => onError(error));
|
|
@@ -208,17 +219,8 @@ export const createWebSocketTransport = ({
|
|
|
208
219
|
const next = createSocket();
|
|
209
220
|
next.binaryType = "arraybuffer";
|
|
210
221
|
socket = next;
|
|
211
|
-
next.addEventListener("open", () => {
|
|
212
|
-
if (socket !== next) {
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
reconnectDelay = 250;
|
|
216
|
-
setStatus("connected");
|
|
217
|
-
flushDiagnostics();
|
|
218
|
-
restore();
|
|
219
|
-
});
|
|
220
222
|
next.addEventListener("message", (event) => {
|
|
221
|
-
if (socket !== next) {
|
|
223
|
+
if (socket !== next || disposed) {
|
|
222
224
|
return;
|
|
223
225
|
}
|
|
224
226
|
let decoded: unknown;
|
|
@@ -238,6 +240,19 @@ export const createWebSocketTransport = ({
|
|
|
238
240
|
return;
|
|
239
241
|
}
|
|
240
242
|
const message = parsed.data;
|
|
243
|
+
if (message.type === "server-info" && !instance) {
|
|
244
|
+
setInstance(createInstanceIdentity(message.instanceId));
|
|
245
|
+
reconnectDelay = 250;
|
|
246
|
+
setStatus("connected");
|
|
247
|
+
flushDiagnostics();
|
|
248
|
+
restore();
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (!instance || message.type === "server-info") {
|
|
252
|
+
reportDisconnect();
|
|
253
|
+
next.close(1002, "expected server discovery before traffic");
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
241
256
|
if (
|
|
242
257
|
message.type === "update-available" ||
|
|
243
258
|
message.type === "restarting"
|
|
@@ -312,6 +327,11 @@ export const createWebSocketTransport = ({
|
|
|
312
327
|
};
|
|
313
328
|
|
|
314
329
|
return {
|
|
330
|
+
getInstance: () => instance,
|
|
331
|
+
subscribeInstance: (listener) => {
|
|
332
|
+
instanceListeners.add(listener);
|
|
333
|
+
return () => instanceListeners.delete(listener);
|
|
334
|
+
},
|
|
315
335
|
activate: () => {
|
|
316
336
|
active = true;
|
|
317
337
|
connect();
|
|
@@ -335,6 +355,10 @@ export const createWebSocketTransport = ({
|
|
|
335
355
|
onOpen,
|
|
336
356
|
streamName,
|
|
337
357
|
}) => {
|
|
358
|
+
if (!active || disposed) {
|
|
359
|
+
onError(disconnected());
|
|
360
|
+
return { close: () => {}, send: () => false };
|
|
361
|
+
}
|
|
338
362
|
const streamId = operationId();
|
|
339
363
|
const stream: Stream = {
|
|
340
364
|
input,
|
|
@@ -347,15 +371,19 @@ export const createWebSocketTransport = ({
|
|
|
347
371
|
streamName,
|
|
348
372
|
};
|
|
349
373
|
streams.set(streamId, stream);
|
|
350
|
-
stream
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
374
|
+
// Initial connection is not a failure: restore sends stream-open when the socket
|
|
375
|
+
// opens, then stream-opened flushes queued messages. Actual failures still reject.
|
|
376
|
+
stream.status =
|
|
377
|
+
status === "connecting" ||
|
|
378
|
+
send({
|
|
379
|
+
input,
|
|
380
|
+
operationId: streamId,
|
|
381
|
+
streamId,
|
|
382
|
+
streamName,
|
|
383
|
+
type: "stream-open",
|
|
384
|
+
})
|
|
385
|
+
? "opening"
|
|
386
|
+
: "disconnected";
|
|
359
387
|
if (stream.status === "disconnected") {
|
|
360
388
|
onError(disconnected());
|
|
361
389
|
}
|
|
@@ -5,6 +5,8 @@ import { tmpdir } from "node:os";
|
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
|
|
8
|
+
import { instanceIdSchema } from "@overmux/shared";
|
|
9
|
+
|
|
8
10
|
import { getOvermuxPaths } from "../paths";
|
|
9
11
|
import {
|
|
10
12
|
readPrivateJson,
|
|
@@ -15,6 +17,7 @@ import type { AuthService } from "./auth-service";
|
|
|
15
17
|
const maxControlMessageBytes = 16_384;
|
|
16
18
|
|
|
17
19
|
const controlRequestSchema = z.discriminatedUnion("type", [
|
|
20
|
+
z.object({ type: z.literal("get-instance") }).strict(),
|
|
18
21
|
z.object({ type: z.literal("ping") }).strict(),
|
|
19
22
|
z.object({ type: z.literal("create-login") }).strict(),
|
|
20
23
|
z.object({ type: z.literal("issue-bearer") }).strict(),
|
|
@@ -44,6 +47,9 @@ const sessionSchema = z
|
|
|
44
47
|
.strict();
|
|
45
48
|
|
|
46
49
|
const controlResponseSchemas = {
|
|
50
|
+
"get-instance": z
|
|
51
|
+
.object({ instanceId: instanceIdSchema, ok: z.literal(true) })
|
|
52
|
+
.strict(),
|
|
47
53
|
"create-login": z
|
|
48
54
|
.object({ login: loginGrantSchema, ok: z.literal(true) })
|
|
49
55
|
.strict(),
|
|
@@ -164,7 +170,18 @@ const writeResponse = (socket: Socket, response: unknown) => {
|
|
|
164
170
|
socket.end(`${JSON.stringify(response)}\n`);
|
|
165
171
|
};
|
|
166
172
|
|
|
167
|
-
const handleRequest = async (
|
|
173
|
+
const handleRequest = async ({
|
|
174
|
+
auth,
|
|
175
|
+
instanceId,
|
|
176
|
+
request,
|
|
177
|
+
}: {
|
|
178
|
+
auth: AuthService;
|
|
179
|
+
instanceId: string;
|
|
180
|
+
request: ControlRequest;
|
|
181
|
+
}) => {
|
|
182
|
+
if (request.type === "get-instance") {
|
|
183
|
+
return { instanceId, ok: true };
|
|
184
|
+
}
|
|
168
185
|
if (request.type === "ping") {
|
|
169
186
|
return { ok: true };
|
|
170
187
|
}
|
|
@@ -185,9 +202,11 @@ const handleRequest = async (auth: AuthService, request: ControlRequest) => {
|
|
|
185
202
|
|
|
186
203
|
const handleConnection = async ({
|
|
187
204
|
auth,
|
|
205
|
+
instanceId,
|
|
188
206
|
socket,
|
|
189
207
|
}: {
|
|
190
208
|
auth: AuthService;
|
|
209
|
+
instanceId: string;
|
|
191
210
|
socket: Socket;
|
|
192
211
|
}) => {
|
|
193
212
|
try {
|
|
@@ -196,7 +215,7 @@ const handleConnection = async ({
|
|
|
196
215
|
socket,
|
|
197
216
|
});
|
|
198
217
|
const request = controlRequestSchema.parse(JSON.parse(frame.toString()));
|
|
199
|
-
writeResponse(socket, await handleRequest(auth, request));
|
|
218
|
+
writeResponse(socket, await handleRequest({ auth, instanceId, request }));
|
|
200
219
|
} catch (cause) {
|
|
201
220
|
writeResponse(socket, {
|
|
202
221
|
error: cause instanceof Error ? cause.message : "Invalid control request",
|
|
@@ -214,11 +233,13 @@ const handleConnection = async ({
|
|
|
214
233
|
export const startInstanceControl = async ({
|
|
215
234
|
auth,
|
|
216
235
|
apiUrl,
|
|
236
|
+
instanceId,
|
|
217
237
|
port,
|
|
218
238
|
url,
|
|
219
239
|
}: {
|
|
220
240
|
auth: AuthService;
|
|
221
241
|
apiUrl: string;
|
|
242
|
+
instanceId: string;
|
|
222
243
|
port: number;
|
|
223
244
|
url: string;
|
|
224
245
|
}): Promise<InstanceControl> => {
|
|
@@ -232,7 +253,7 @@ export const startInstanceControl = async ({
|
|
|
232
253
|
const server = createServer((socket) => {
|
|
233
254
|
sockets.add(socket);
|
|
234
255
|
socket.once("close", () => sockets.delete(socket));
|
|
235
|
-
void handleConnection({ auth, socket });
|
|
256
|
+
void handleConnection({ auth, instanceId, socket });
|
|
236
257
|
});
|
|
237
258
|
const close = async () => {
|
|
238
259
|
sockets.forEach((socket) => socket.destroy());
|
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import type { RuntimeConfigDefinition } from "@overmux/shared/node";
|
|
4
4
|
|
|
5
|
-
import type {
|
|
5
|
+
import type {
|
|
6
|
+
InstanceContext,
|
|
7
|
+
InstanceIdentity,
|
|
8
|
+
Notifications,
|
|
9
|
+
} from "../../../public/index";
|
|
10
|
+
import { createRuntimeInstance } from "./runtime-instance";
|
|
6
11
|
import { protocolVersion, type RuntimeManifest } from "../../shared/index";
|
|
7
12
|
|
|
8
13
|
import { createRuntimeLifecycle } from "./runtime-lifecycle";
|
|
@@ -23,6 +28,8 @@ import {
|
|
|
23
28
|
} from "./runtime-streams";
|
|
24
29
|
|
|
25
30
|
export type Runtime = {
|
|
31
|
+
instance: InstanceContext;
|
|
32
|
+
establishInstance: (port: number) => InstanceIdentity;
|
|
26
33
|
manifest: RuntimeManifest;
|
|
27
34
|
dispose: () => Promise<void>;
|
|
28
35
|
getOperation: (name: string) => RuntimeOperation | undefined;
|
|
@@ -44,10 +51,12 @@ export const createRuntime = async ({
|
|
|
44
51
|
signal,
|
|
45
52
|
}: CreateRuntimeOptions): Promise<Runtime> => {
|
|
46
53
|
const lifecycle = createRuntimeLifecycle(signal);
|
|
54
|
+
const instance = createRuntimeInstance(config.instanceId);
|
|
47
55
|
try {
|
|
48
56
|
const resources = createRuntimeResources({
|
|
49
57
|
definitions: (config.server.resources ??
|
|
50
58
|
{}) as RuntimeResourceDefinitions,
|
|
59
|
+
instance: instance.context,
|
|
51
60
|
lifecycle,
|
|
52
61
|
});
|
|
53
62
|
const context = resources.context;
|
|
@@ -74,6 +83,8 @@ export const createRuntime = async ({
|
|
|
74
83
|
streams: [...streams.names],
|
|
75
84
|
};
|
|
76
85
|
return {
|
|
86
|
+
instance: instance.context,
|
|
87
|
+
establishInstance: instance.establish,
|
|
77
88
|
manifest,
|
|
78
89
|
dispose: lifecycle.dispose,
|
|
79
90
|
getOperation: operations.get,
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { hostname } from "node:os";
|
|
2
|
+
import type { RuntimeConfigDefinition } from "@overmux/shared/node";
|
|
3
|
+
import {
|
|
4
|
+
createInstanceIdentity,
|
|
5
|
+
type InstanceIdentity,
|
|
6
|
+
} from "../../shared/index";
|
|
7
|
+
import type { InstanceContext } from "../../../public/config";
|
|
8
|
+
|
|
9
|
+
// The scope exists during preparation, but identity is immutable once the listener
|
|
10
|
+
// binds. No handler or connection may observe it before establishment.
|
|
11
|
+
export const createRuntimeInstance = (
|
|
12
|
+
configured: RuntimeConfigDefinition["instanceId"],
|
|
13
|
+
) => {
|
|
14
|
+
let identity: InstanceIdentity | undefined;
|
|
15
|
+
const get = () => {
|
|
16
|
+
if (!identity) {
|
|
17
|
+
throw new Error("Overmux instance identity is not ready");
|
|
18
|
+
}
|
|
19
|
+
return identity;
|
|
20
|
+
};
|
|
21
|
+
const context: InstanceContext = {
|
|
22
|
+
getInstanceId: () => get().instanceId,
|
|
23
|
+
getDeepLinkPrefix: () => get().deepLinkPrefix,
|
|
24
|
+
};
|
|
25
|
+
return {
|
|
26
|
+
context,
|
|
27
|
+
establish: (port: number) => {
|
|
28
|
+
if (identity) {
|
|
29
|
+
throw new Error("Overmux instance identity is already established");
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
identity = createInstanceIdentity(
|
|
33
|
+
typeof configured === "function"
|
|
34
|
+
? configured({ port })
|
|
35
|
+
: (configured ?? `${hostname()}-${port}`),
|
|
36
|
+
);
|
|
37
|
+
} catch (cause) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
"Invalid Overmux instance ID; configure instanceId with a canonical lowercase ID",
|
|
40
|
+
{ cause },
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
return identity;
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
};
|
|
@@ -42,11 +42,13 @@ type InvalidationListener = {
|
|
|
42
42
|
|
|
43
43
|
type CreateRuntimeResourcesOptions = {
|
|
44
44
|
definitions: RuntimeResourceDefinitions;
|
|
45
|
+
instance: HandlerContext["instance"];
|
|
45
46
|
lifecycle: RuntimeLifecycle;
|
|
46
47
|
};
|
|
47
48
|
|
|
48
49
|
export const createRuntimeResources = ({
|
|
49
50
|
definitions,
|
|
51
|
+
instance,
|
|
50
52
|
lifecycle,
|
|
51
53
|
}: CreateRuntimeResourcesOptions): RuntimeResources => {
|
|
52
54
|
const graph = createResourceDependencyGraph(definitions);
|
|
@@ -104,6 +106,7 @@ export const createRuntimeResources = ({
|
|
|
104
106
|
};
|
|
105
107
|
|
|
106
108
|
const context = (signal?: AbortSignal): HandlerContext => ({
|
|
109
|
+
instance,
|
|
107
110
|
invalidate,
|
|
108
111
|
signal: lifecycle.requestSignal(signal),
|
|
109
112
|
});
|
|
@@ -7,6 +7,13 @@ import { z } from "zod";
|
|
|
7
7
|
|
|
8
8
|
import type { ServerConfigDefinition } from "../../public/index";
|
|
9
9
|
|
|
10
|
+
const serverStartupOverridesRuntimeSchema = serverConfigRuntimeSchema
|
|
11
|
+
.omit({ port: true })
|
|
12
|
+
.extend({
|
|
13
|
+
// Port 0 asks the OS for a free port for internal listeners and tests; it must not leak into user config.
|
|
14
|
+
port: z.number().int().min(0).max(65_535).optional(),
|
|
15
|
+
});
|
|
16
|
+
|
|
10
17
|
export type ResolvedServerStartupOptions = {
|
|
11
18
|
host: string;
|
|
12
19
|
logFile?: string;
|
|
@@ -24,10 +31,8 @@ export const resolveServerStartupOptions = ({
|
|
|
24
31
|
configPath: string;
|
|
25
32
|
overrides?: ServerConfigDefinition;
|
|
26
33
|
}): ResolvedServerStartupOptions => {
|
|
27
|
-
const validatedOverrides =
|
|
28
|
-
.
|
|
29
|
-
.extend({ port: z.number().int().min(0).max(65_535).optional() })
|
|
30
|
-
.parse(overrides);
|
|
34
|
+
const validatedOverrides =
|
|
35
|
+
serverStartupOverridesRuntimeSchema.parse(overrides);
|
|
31
36
|
const configuredLogFile = config.logFile
|
|
32
37
|
? resolve(dirname(configPath), config.logFile)
|
|
33
38
|
: undefined;
|
|
@@ -279,24 +279,20 @@ const startApplicationTransport = async ({
|
|
|
279
279
|
? undefined
|
|
280
280
|
: startup.productionWebAssetsDir,
|
|
281
281
|
});
|
|
282
|
+
// Binding allocates the actual port. Reject early HTTP traffic until identity
|
|
283
|
+
// and trusted origins are established; WebSocket upgrades attach only after that.
|
|
284
|
+
let ready = false;
|
|
282
285
|
const server = serve({
|
|
283
|
-
fetch:
|
|
286
|
+
fetch: (request, env) =>
|
|
287
|
+
ready
|
|
288
|
+
? app.fetch(request, env)
|
|
289
|
+
: new Response("Starting", { status: 503 }),
|
|
284
290
|
hostname: startup.host,
|
|
285
291
|
port: startup.port,
|
|
286
292
|
});
|
|
287
293
|
resources.add(() => closeNodeServer(server, runtime.dispose));
|
|
288
|
-
const webSockets = createWebSocketServer({
|
|
289
|
-
auth,
|
|
290
|
-
developmentWebTarget,
|
|
291
|
-
runtime,
|
|
292
|
-
server,
|
|
293
|
-
serverLogger: logger,
|
|
294
|
-
});
|
|
295
|
-
resources.add(webSockets.close);
|
|
296
|
-
announceRestarting = webSockets.announceRestarting;
|
|
297
|
-
notifications.attachLiveDelivery(webSockets.broadcastNotification);
|
|
298
|
-
|
|
299
294
|
const address = await getServerAddress(server);
|
|
295
|
+
runtime.establishInstance(address.port);
|
|
300
296
|
const urlHost = startup.host.includes(":")
|
|
301
297
|
? `[${startup.host}]`
|
|
302
298
|
: startup.host;
|
|
@@ -309,9 +305,21 @@ const startApplicationTransport = async ({
|
|
|
309
305
|
: urlHost;
|
|
310
306
|
const origins = authOrigins.establish(localUrl);
|
|
311
307
|
authService.setOrigins(origins);
|
|
308
|
+
const webSockets = createWebSocketServer({
|
|
309
|
+
auth,
|
|
310
|
+
developmentWebTarget,
|
|
311
|
+
runtime,
|
|
312
|
+
server,
|
|
313
|
+
serverLogger: logger,
|
|
314
|
+
});
|
|
315
|
+
resources.add(webSockets.close);
|
|
316
|
+
announceRestarting = webSockets.announceRestarting;
|
|
317
|
+
notifications.attachLiveDelivery(webSockets.broadcastNotification);
|
|
318
|
+
ready = true;
|
|
312
319
|
const instanceControl = await startInstanceControl({
|
|
313
320
|
auth: authService,
|
|
314
321
|
apiUrl: `http://${controlApiHost}:${address.port}`,
|
|
322
|
+
instanceId: runtime.instance.getInstanceId(),
|
|
315
323
|
port: address.port,
|
|
316
324
|
url: origins[0]!,
|
|
317
325
|
});
|
|
@@ -247,6 +247,13 @@ export const createWebSocketServer = ({
|
|
|
247
247
|
sessionSockets,
|
|
248
248
|
socket: webSocket,
|
|
249
249
|
});
|
|
250
|
+
// Discovery precedes all connection traffic, including sticky announcements.
|
|
251
|
+
webSocket.send(
|
|
252
|
+
encodeProtocolFrame({
|
|
253
|
+
type: "server-info",
|
|
254
|
+
instanceId: runtime.instance.getInstanceId(),
|
|
255
|
+
}),
|
|
256
|
+
);
|
|
250
257
|
webSocketServer.emit("connection", webSocket, request);
|
|
251
258
|
attachWebSocketConnection({ runtime, serverLogger, socket: webSocket });
|
|
252
259
|
if (updateAvailable) {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { instanceIdSchema } from "@overmux/shared";
|
|
2
3
|
|
|
3
4
|
import { notificationSchema } from "../../public/notifications";
|
|
4
5
|
|
|
5
6
|
// Compatibility gate, not negotiation: open tabs may still run older client assets.
|
|
6
7
|
// The exact version rejects incompatible runtime manifest and wire formats before later traffic.
|
|
7
|
-
export const protocolVersion =
|
|
8
|
+
export const protocolVersion = 10 as const;
|
|
8
9
|
|
|
9
10
|
// Application-defined WebSocket close codes shared by server and browser transports.
|
|
10
11
|
export const webSocketCloseCode = {
|
|
@@ -139,7 +140,15 @@ export const protocolErrorMessageSchema = z.object({
|
|
|
139
140
|
type: z.literal("error"),
|
|
140
141
|
});
|
|
141
142
|
|
|
143
|
+
export const serverInfoMessageSchema = z
|
|
144
|
+
.object({
|
|
145
|
+
type: z.literal("server-info"),
|
|
146
|
+
instanceId: instanceIdSchema,
|
|
147
|
+
})
|
|
148
|
+
.strict();
|
|
149
|
+
|
|
142
150
|
export const serverProtocolMessageSchema = z.discriminatedUnion("type", [
|
|
151
|
+
serverInfoMessageSchema,
|
|
143
152
|
resourceResultMessageSchema,
|
|
144
153
|
resourceInvalidatedMessageSchema,
|
|
145
154
|
streamOpenedMessageSchema,
|
package/src/public/client.ts
CHANGED
|
@@ -2,7 +2,12 @@ export {
|
|
|
2
2
|
disableBackgroundNotifications,
|
|
3
3
|
enableBackgroundNotifications,
|
|
4
4
|
} from "../internal/client/background-notifications";
|
|
5
|
+
export {
|
|
6
|
+
readClipboardText,
|
|
7
|
+
writeClipboardText,
|
|
8
|
+
} from "../internal/client/clipboard";
|
|
5
9
|
export type { OvermuxServerApi } from "../internal/client/browser-api";
|
|
10
|
+
export type { InstanceIdentity } from "@overmux/shared";
|
|
6
11
|
export {
|
|
7
12
|
createOvermuxHooks,
|
|
8
13
|
type ResourceResult,
|
package/src/public/config.ts
CHANGED
|
@@ -6,7 +6,13 @@ import type { ResourceContract, StreamContract } from "./contracts";
|
|
|
6
6
|
|
|
7
7
|
export type RuntimeDisposer = () => Promise<void> | void;
|
|
8
8
|
|
|
9
|
+
export type InstanceContext = {
|
|
10
|
+
getInstanceId: () => string;
|
|
11
|
+
getDeepLinkPrefix: () => string;
|
|
12
|
+
};
|
|
13
|
+
|
|
9
14
|
export type HandlerContext = {
|
|
15
|
+
instance: InstanceContext;
|
|
10
16
|
invalidate: (contract: ResourceContract, input?: unknown) => void;
|
|
11
17
|
signal: AbortSignal;
|
|
12
18
|
};
|
|
@@ -245,6 +251,7 @@ export type OvermuxConfigDefinition<
|
|
|
245
251
|
aiContextSnippets?: AiContextSnippetDefinition;
|
|
246
252
|
auth: AuthConfigDefinition;
|
|
247
253
|
debug?: boolean;
|
|
254
|
+
instanceId?: string | ((context: { port: number }) => string);
|
|
248
255
|
server: TServer;
|
|
249
256
|
vite?: string;
|
|
250
257
|
};
|
package/src/public/index.ts
CHANGED
|
@@ -21,6 +21,7 @@ export type {
|
|
|
21
21
|
ConfigDefinition,
|
|
22
22
|
DerivedResourceDefinition,
|
|
23
23
|
HandlerContext,
|
|
24
|
+
InstanceContext,
|
|
24
25
|
OperationContext,
|
|
25
26
|
OperationDefinition,
|
|
26
27
|
OvermuxConfigDefinition,
|
|
@@ -49,6 +50,7 @@ export type {
|
|
|
49
50
|
} from "./contracts";
|
|
50
51
|
export { notificationLinkSchema, notificationSchema } from "./notifications";
|
|
51
52
|
export type { Notification, Notifications } from "./notifications";
|
|
53
|
+
export type { InstanceIdentity } from "@overmux/shared";
|
|
52
54
|
export {
|
|
53
55
|
createOvermuxSettingsPath,
|
|
54
56
|
overmuxLogoutPath,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index-D3_Dpy_T.d.ts","names":[],"sources":["../../../shared/dist/index.d.ts","../../src/public/ai-context.ts","../../src/public/notifications.ts","../../src/public/contracts.ts","../../src/public/config.ts"],"mappings":"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"}
|