@koed-labs/server 0.0.0-stage → 0.10.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +64 -2
- package/bin/koed.js +7 -0
- package/package.json +26 -4
- package/third-party-notices.json +92 -0
- package/vendor/chunk-2BWTLF7A.js +13883 -0
- package/vendor/chunk-2DA7EFZV.js +924 -0
- package/vendor/chunk-2S3OGPUC.js +58 -0
- package/vendor/chunk-473OA62O.js +26827 -0
- package/vendor/chunk-6JRWGOQG.js +236 -0
- package/vendor/chunk-7NOQSX4T.js +14 -0
- package/vendor/chunk-7NXKPDF3.js +1586 -0
- package/vendor/chunk-A4NKL2PK.js +153 -0
- package/vendor/chunk-A6WMU2GG.js +48 -0
- package/vendor/chunk-AFLKCQHF.js +73 -0
- package/vendor/chunk-BTS4ZTF3.js +43 -0
- package/vendor/chunk-C3N4N5HR.js +108 -0
- package/vendor/chunk-DXTAWHY3.js +25 -0
- package/vendor/chunk-G4IIVVJO.js +56 -0
- package/vendor/chunk-GELDEJJ2.js +4337 -0
- package/vendor/chunk-HFM6ROJL.js +188 -0
- package/vendor/chunk-HK2H3MVE.js +328 -0
- package/vendor/chunk-HZJK5RWC.js +5088 -0
- package/vendor/chunk-J2L726BC.js +2497 -0
- package/vendor/chunk-JBGD4JLR.js +3779 -0
- package/vendor/chunk-JJNLH5GA.js +2470 -0
- package/vendor/chunk-JKOG52PE.js +128 -0
- package/vendor/chunk-K2EIFEQF.js +89 -0
- package/vendor/chunk-KUIWH33N.js +16 -0
- package/vendor/chunk-NEWIOOM6.js +34 -0
- package/vendor/chunk-NM5NXCVM.js +2404 -0
- package/vendor/chunk-PT3AUPGE.js +459 -0
- package/vendor/chunk-PY5LYLV7.js +143 -0
- package/vendor/chunk-QIIKMG3M.js +103 -0
- package/vendor/chunk-QT24SAW6.js +110 -0
- package/vendor/chunk-RVYJKKWA.js +22 -0
- package/vendor/chunk-SKVYP7NY.js +1647 -0
- package/vendor/chunk-TSRPHKAC.js +780 -0
- package/vendor/chunk-UI5RLBKQ.js +30 -0
- package/vendor/chunk-VPC5NLZG.js +259 -0
- package/vendor/chunk-W2HNI6AA.js +75 -0
- package/vendor/chunk-X6M525ZJ.js +20 -0
- package/vendor/chunk-XIZVLPHZ.js +18 -0
- package/vendor/chunk-XNFVSVBS.js +242 -0
- package/vendor/chunk-YCDEMYY6.js +2076 -0
- package/vendor/chunk-YJ6XUZT6.js +292 -0
- package/vendor/chunk-YJWREXN6.js +26020 -0
- package/vendor/chunk-YK4F4VY2.js +173 -0
- package/vendor/chunk-YOFI4PCH.js +36 -0
- package/vendor/chunk-ZUGNCQZE.js +247 -0
- package/vendor/chunk-ZXX4ZNJY.js +21 -0
- package/vendor/cli.js +7 -0
- package/vendor/component-manifest-catalog.js +8 -0
- package/vendor/control-plane.js +23 -0
- package/vendor/package.json +1 -0
- package/vendor/privacy-service-bootstrap.js +6 -0
|
@@ -0,0 +1,2470 @@
|
|
|
1
|
+
import { createRequire as createKoedRequire } from "node:module"; const require = createKoedRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
disconnectUpstreamBackendEnrollment,
|
|
4
|
+
getUpstreamEnrollmentStatus,
|
|
5
|
+
readUpstreamEnrollmentBinding,
|
|
6
|
+
startUpstreamEnrollment
|
|
7
|
+
} from "./chunk-SKVYP7NY.js";
|
|
8
|
+
import {
|
|
9
|
+
applyPersistedLocalPorts
|
|
10
|
+
} from "./chunk-HFM6ROJL.js";
|
|
11
|
+
import {
|
|
12
|
+
removeProjectTeamWorkspaceLinksForBackend,
|
|
13
|
+
removeProjectTeamWorkspaceLinksForMismatchedBinding
|
|
14
|
+
} from "./chunk-6JRWGOQG.js";
|
|
15
|
+
import {
|
|
16
|
+
applyActiveRuntimeUrls,
|
|
17
|
+
readActiveRuntimeState
|
|
18
|
+
} from "./chunk-G4IIVVJO.js";
|
|
19
|
+
import {
|
|
20
|
+
beginUpstreamDisconnectCleanup,
|
|
21
|
+
completeUpstreamDisconnectCleanup,
|
|
22
|
+
createSecureUpstreamFetch,
|
|
23
|
+
getActiveUpstreamBackend,
|
|
24
|
+
listUpstreamBackends,
|
|
25
|
+
listUpstreamDisconnectCleanupRecords,
|
|
26
|
+
refreshUpstreamBackendCapabilities,
|
|
27
|
+
registerUpstreamBackend,
|
|
28
|
+
registeredPrivateNetworkPolicy,
|
|
29
|
+
setActiveUpstreamBackend,
|
|
30
|
+
updateUpstreamBackendRoutePolicy,
|
|
31
|
+
updateUpstreamDisconnectCleanup,
|
|
32
|
+
upstreamDisconnectCleanupPending
|
|
33
|
+
} from "./chunk-YJWREXN6.js";
|
|
34
|
+
import "./chunk-XNFVSVBS.js";
|
|
35
|
+
import "./chunk-7NXKPDF3.js";
|
|
36
|
+
import {
|
|
37
|
+
resolveKoedServerPaths
|
|
38
|
+
} from "./chunk-K2EIFEQF.js";
|
|
39
|
+
import "./chunk-UI5RLBKQ.js";
|
|
40
|
+
import {
|
|
41
|
+
loadRepoEnv,
|
|
42
|
+
resolveApiUrl
|
|
43
|
+
} from "./chunk-W2HNI6AA.js";
|
|
44
|
+
import {
|
|
45
|
+
COLLABORATION_CONTRACT_VERSION,
|
|
46
|
+
COLLABORATION_RECONNECT_BACKOFF_CAP_MS,
|
|
47
|
+
COLLABORATION_RECONNECT_MAX_ATTEMPTS,
|
|
48
|
+
COLLABORATION_RECONNECT_UNAVAILABLE_COOLDOWN_MS,
|
|
49
|
+
COLLABORATION_RECONNECT_WINDOW_MS,
|
|
50
|
+
COLLABORATION_RENDERER_MAX_PENDING_BYTES,
|
|
51
|
+
COLLABORATION_SEND_RETRY_MAX_ATTEMPTS,
|
|
52
|
+
calculateCollaborationReconnectDelay,
|
|
53
|
+
clearCollaborationActionGrantCustodyForBackend,
|
|
54
|
+
clearCollaborationPendingTeamSends,
|
|
55
|
+
collaborationBackendIdentitySchema,
|
|
56
|
+
collaborationCommandResultSchema,
|
|
57
|
+
collaborationCommandReturnsSnapshot,
|
|
58
|
+
collaborationConnectionEventSchema,
|
|
59
|
+
collaborationDeliveryIdSchema,
|
|
60
|
+
collaborationDurableSendEventSchema,
|
|
61
|
+
collaborationDurableSendSchema,
|
|
62
|
+
collaborationRemoteBackendUrlSchema,
|
|
63
|
+
collaborationRendererCommandSchema,
|
|
64
|
+
collaborationRendererEventSchema,
|
|
65
|
+
collaborationSafeErrorMessages,
|
|
66
|
+
collaborationSnapshotSchema,
|
|
67
|
+
collaborationSubscriptionSchema,
|
|
68
|
+
deleteCollaborationPendingSend,
|
|
69
|
+
external_exports,
|
|
70
|
+
isTeamCollaborationSelection,
|
|
71
|
+
listCollaborationPendingSends,
|
|
72
|
+
readBoundedJsonObject,
|
|
73
|
+
readDesktopLocalCredentialAuthorization,
|
|
74
|
+
readLocalEdgeClientCredentialAuthorization,
|
|
75
|
+
storeCollaborationPendingSend,
|
|
76
|
+
updateCollaborationPendingSendState
|
|
77
|
+
} from "./chunk-473OA62O.js";
|
|
78
|
+
import "./chunk-BTS4ZTF3.js";
|
|
79
|
+
|
|
80
|
+
// packages/koed-server/dist/desktop-collaboration-broker.js
|
|
81
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
82
|
+
|
|
83
|
+
// packages/koed-server/dist/desktop-collaboration-broker-contract.js
|
|
84
|
+
import { Buffer as Buffer2 } from "node:buffer";
|
|
85
|
+
var DESKTOP_COLLABORATION_BROKER_PROTOCOL_VERSION = 1;
|
|
86
|
+
var DESKTOP_COLLABORATION_BROKER_MAX_MESSAGE_BYTES = 32 * 1024 * 1024;
|
|
87
|
+
var ownerIdSchema = external_exports.string().min(1).max(128).regex(/^[A-Za-z0-9:_-]+$/);
|
|
88
|
+
var sessionTokenSchema = external_exports.string().min(32).max(256).regex(/^[A-Za-z0-9_-]+$/);
|
|
89
|
+
var browserUrlSchema = collaborationRemoteBackendUrlSchema.or(external_exports.string().trim().min(1).max(4096).superRefine((value, context) => {
|
|
90
|
+
let parsed;
|
|
91
|
+
try {
|
|
92
|
+
parsed = new URL(value);
|
|
93
|
+
} catch {
|
|
94
|
+
context.addIssue({ code: "custom", message: "Browser URL is invalid" });
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
98
|
+
context.addIssue({
|
|
99
|
+
code: "custom",
|
|
100
|
+
message: "Browser URL must use HTTP or HTTPS"
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
if (!parsed.hostname || parsed.username || parsed.password) {
|
|
104
|
+
context.addIssue({
|
|
105
|
+
code: "custom",
|
|
106
|
+
message: "Browser URL must be credential-free"
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
if (parsed.search || parsed.hash) {
|
|
110
|
+
context.addIssue({
|
|
111
|
+
code: "custom",
|
|
112
|
+
message: "Browser URL must not include a query string or fragment"
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}));
|
|
116
|
+
var baseEnvelopeSchema = external_exports.object({
|
|
117
|
+
protocolVersion: external_exports.literal(DESKTOP_COLLABORATION_BROKER_PROTOCOL_VERSION),
|
|
118
|
+
contractVersion: external_exports.literal(COLLABORATION_CONTRACT_VERSION),
|
|
119
|
+
sessionToken: sessionTokenSchema
|
|
120
|
+
});
|
|
121
|
+
var desktopCollaborationBrokerParentMessageSchema = external_exports.discriminatedUnion("type", [
|
|
122
|
+
baseEnvelopeSchema.extend({
|
|
123
|
+
type: external_exports.literal("command"),
|
|
124
|
+
envelopeId: external_exports.uuid(),
|
|
125
|
+
ownerId: ownerIdSchema,
|
|
126
|
+
command: collaborationRendererCommandSchema
|
|
127
|
+
}),
|
|
128
|
+
baseEnvelopeSchema.extend({
|
|
129
|
+
type: external_exports.literal("release_owner"),
|
|
130
|
+
envelopeId: external_exports.uuid(),
|
|
131
|
+
ownerId: ownerIdSchema
|
|
132
|
+
}),
|
|
133
|
+
baseEnvelopeSchema.extend({
|
|
134
|
+
type: external_exports.literal("shutdown"),
|
|
135
|
+
envelopeId: external_exports.uuid()
|
|
136
|
+
})
|
|
137
|
+
]);
|
|
138
|
+
var desktopCollaborationBrokerChildErrorCodeSchema = external_exports.enum([
|
|
139
|
+
"invalid_message",
|
|
140
|
+
"duplicate_request",
|
|
141
|
+
"unknown_owner",
|
|
142
|
+
"unknown_command",
|
|
143
|
+
"timeout",
|
|
144
|
+
"internal_error"
|
|
145
|
+
]);
|
|
146
|
+
var desktopCollaborationBrokerChildMessageSchema = external_exports.discriminatedUnion("type", [
|
|
147
|
+
baseEnvelopeSchema.extend({
|
|
148
|
+
type: external_exports.literal("ready"),
|
|
149
|
+
brokerPid: external_exports.number().int().positive()
|
|
150
|
+
}),
|
|
151
|
+
baseEnvelopeSchema.extend({
|
|
152
|
+
type: external_exports.literal("command_result"),
|
|
153
|
+
envelopeId: external_exports.uuid(),
|
|
154
|
+
ownerId: ownerIdSchema,
|
|
155
|
+
result: collaborationCommandResultSchema
|
|
156
|
+
}),
|
|
157
|
+
baseEnvelopeSchema.extend({
|
|
158
|
+
type: external_exports.literal("renderer_event"),
|
|
159
|
+
ownerId: ownerIdSchema,
|
|
160
|
+
event: collaborationRendererEventSchema
|
|
161
|
+
}),
|
|
162
|
+
baseEnvelopeSchema.extend({
|
|
163
|
+
type: external_exports.literal("open_external"),
|
|
164
|
+
envelopeId: external_exports.uuid(),
|
|
165
|
+
ownerId: ownerIdSchema,
|
|
166
|
+
url: browserUrlSchema
|
|
167
|
+
}),
|
|
168
|
+
baseEnvelopeSchema.extend({
|
|
169
|
+
type: external_exports.literal("owner_released"),
|
|
170
|
+
envelopeId: external_exports.uuid(),
|
|
171
|
+
ownerId: ownerIdSchema
|
|
172
|
+
}),
|
|
173
|
+
baseEnvelopeSchema.extend({
|
|
174
|
+
type: external_exports.literal("shutdown_ack"),
|
|
175
|
+
envelopeId: external_exports.uuid()
|
|
176
|
+
}),
|
|
177
|
+
baseEnvelopeSchema.extend({
|
|
178
|
+
type: external_exports.literal("error"),
|
|
179
|
+
envelopeId: external_exports.uuid().nullable(),
|
|
180
|
+
ownerId: ownerIdSchema.nullable(),
|
|
181
|
+
code: desktopCollaborationBrokerChildErrorCodeSchema,
|
|
182
|
+
message: external_exports.string().min(1).max(512)
|
|
183
|
+
})
|
|
184
|
+
]);
|
|
185
|
+
var measureDesktopCollaborationBrokerMessageBytes = (value) => Buffer2.byteLength(JSON.stringify(value), "utf8");
|
|
186
|
+
|
|
187
|
+
// packages/koed-server/dist/desktop-collaboration-broker-local-transport.js
|
|
188
|
+
import { randomUUID } from "node:crypto";
|
|
189
|
+
|
|
190
|
+
// packages/shared/src/durable-realtime.ts
|
|
191
|
+
var negotiateDurableRealtimeTransport = (offered, supported) => {
|
|
192
|
+
const supportedSet = new Set(supported);
|
|
193
|
+
return offered.find((transportId) => supportedSet.has(transportId)) ?? null;
|
|
194
|
+
};
|
|
195
|
+
var defaultSleep = (delayMs, signal) => new Promise((resolve) => {
|
|
196
|
+
if (signal.aborted) return resolve();
|
|
197
|
+
const finish = () => {
|
|
198
|
+
clearTimeout(timer);
|
|
199
|
+
signal.removeEventListener("abort", finish);
|
|
200
|
+
resolve();
|
|
201
|
+
};
|
|
202
|
+
const timer = setTimeout(finish, delayMs);
|
|
203
|
+
timer.unref?.();
|
|
204
|
+
signal.addEventListener("abort", finish, { once: true });
|
|
205
|
+
});
|
|
206
|
+
var assertRetryPolicy = (policy) => {
|
|
207
|
+
if (!Number.isSafeInteger(policy.maxAttempts) || policy.maxAttempts < 1 || !Number.isSafeInteger(policy.attemptWindowMs) || policy.attemptWindowMs < 1 || !Number.isSafeInteger(policy.unavailableCooldownMs) || policy.unavailableCooldownMs < 1) {
|
|
208
|
+
throw new TypeError("Durable realtime retry policy is invalid");
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
var retryDelay = (policy, attempt) => {
|
|
212
|
+
const delay = policy.delayForAttempt(attempt);
|
|
213
|
+
if (!Number.isFinite(delay) || delay < 0) {
|
|
214
|
+
throw new TypeError("Durable realtime retry delay is invalid");
|
|
215
|
+
}
|
|
216
|
+
return Math.round(delay);
|
|
217
|
+
};
|
|
218
|
+
var runDurableRealtime = async (options) => {
|
|
219
|
+
assertRetryPolicy(options.retry);
|
|
220
|
+
const now = options.now ?? Date.now;
|
|
221
|
+
const sleep = options.sleep ?? defaultSleep;
|
|
222
|
+
const isCurrent = () => !options.signal.aborted && (options.isCurrent?.() ?? true);
|
|
223
|
+
const emitState = (state) => {
|
|
224
|
+
if (!isCurrent()) return false;
|
|
225
|
+
options.onState(state);
|
|
226
|
+
return isCurrent();
|
|
227
|
+
};
|
|
228
|
+
const attempts = [];
|
|
229
|
+
let reconnecting = false;
|
|
230
|
+
if (!emitState({
|
|
231
|
+
state: "connecting",
|
|
232
|
+
reconnectAttempt: 0,
|
|
233
|
+
retryAtMs: null,
|
|
234
|
+
retryDelayMs: null
|
|
235
|
+
})) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
while (isCurrent()) {
|
|
239
|
+
try {
|
|
240
|
+
let live = false;
|
|
241
|
+
const outcome = await options.connect({
|
|
242
|
+
signal: options.signal,
|
|
243
|
+
reconnecting,
|
|
244
|
+
isCurrent,
|
|
245
|
+
markLive: () => {
|
|
246
|
+
if (live) return;
|
|
247
|
+
live = true;
|
|
248
|
+
emitState({
|
|
249
|
+
state: "live",
|
|
250
|
+
reconnectAttempt: 0,
|
|
251
|
+
retryAtMs: null,
|
|
252
|
+
retryDelayMs: null
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
if (!isCurrent() || outcome === "terminal") return;
|
|
257
|
+
} catch {
|
|
258
|
+
if (!isCurrent()) return;
|
|
259
|
+
}
|
|
260
|
+
reconnecting = true;
|
|
261
|
+
const attemptNow = now();
|
|
262
|
+
while (attempts.length > 0 && attemptNow - attempts[0] >= options.retry.attemptWindowMs) {
|
|
263
|
+
attempts.shift();
|
|
264
|
+
}
|
|
265
|
+
if (attempts.length >= options.retry.maxAttempts) {
|
|
266
|
+
const delay2 = options.retry.unavailableCooldownMs;
|
|
267
|
+
if (!emitState({
|
|
268
|
+
state: "unavailable",
|
|
269
|
+
reconnectAttempt: options.retry.maxAttempts,
|
|
270
|
+
retryAtMs: attemptNow + delay2,
|
|
271
|
+
retryDelayMs: delay2
|
|
272
|
+
})) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
await sleep(delay2, options.signal);
|
|
276
|
+
attempts.length = 0;
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
attempts.push(attemptNow);
|
|
280
|
+
const attempt = attempts.length;
|
|
281
|
+
const delay = retryDelay(options.retry, attempt);
|
|
282
|
+
if (!emitState({
|
|
283
|
+
state: "reconnecting",
|
|
284
|
+
reconnectAttempt: attempt,
|
|
285
|
+
retryAtMs: attemptNow + delay,
|
|
286
|
+
retryDelayMs: delay
|
|
287
|
+
})) {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
await sleep(delay, options.signal);
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
var utf8Bytes = (value) => new TextEncoder().encode(value).byteLength;
|
|
294
|
+
var readBoundedSse = async (options) => {
|
|
295
|
+
if (!Number.isSafeInteger(options.maxFrameBytes) || options.maxFrameBytes < 1) {
|
|
296
|
+
throw new TypeError("SSE frame byte limit is invalid");
|
|
297
|
+
}
|
|
298
|
+
const reader = options.body.getReader();
|
|
299
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
300
|
+
let buffer = "";
|
|
301
|
+
let previousChunkEndedWithCr = false;
|
|
302
|
+
const appendDecoded = (text) => {
|
|
303
|
+
if (!text) return;
|
|
304
|
+
if (previousChunkEndedWithCr && text.startsWith("\n")) {
|
|
305
|
+
text = text.slice(1);
|
|
306
|
+
}
|
|
307
|
+
previousChunkEndedWithCr = text.endsWith("\r");
|
|
308
|
+
buffer += text.replace(/\r\n|\r/g, "\n");
|
|
309
|
+
};
|
|
310
|
+
const consumeFrame = async (frame) => {
|
|
311
|
+
if (utf8Bytes(frame) > options.maxFrameBytes) {
|
|
312
|
+
throw new Error("SSE frame exceeded its byte limit");
|
|
313
|
+
}
|
|
314
|
+
const lines = frame.split("\n");
|
|
315
|
+
if (lines.every((line) => line === "" || line.startsWith(":"))) {
|
|
316
|
+
return "continue";
|
|
317
|
+
}
|
|
318
|
+
let event = "message";
|
|
319
|
+
const data = [];
|
|
320
|
+
for (const line of lines) {
|
|
321
|
+
if (line.startsWith(":")) continue;
|
|
322
|
+
const separator = line.indexOf(":");
|
|
323
|
+
const field = separator < 0 ? line : line.slice(0, separator);
|
|
324
|
+
let value = separator < 0 ? "" : line.slice(separator + 1);
|
|
325
|
+
if (value.startsWith(" ")) value = value.slice(1);
|
|
326
|
+
if (field === "event") event = value;
|
|
327
|
+
else if (field === "data") data.push(value);
|
|
328
|
+
}
|
|
329
|
+
return options.onFrame({ event, data: data.join("\n") });
|
|
330
|
+
};
|
|
331
|
+
try {
|
|
332
|
+
for (; ; ) {
|
|
333
|
+
if (options.signal.aborted) return "terminal";
|
|
334
|
+
const chunk = await reader.read();
|
|
335
|
+
if (chunk.done) break;
|
|
336
|
+
appendDecoded(decoder.decode(chunk.value, { stream: true }));
|
|
337
|
+
for (; ; ) {
|
|
338
|
+
const boundary = buffer.indexOf("\n\n");
|
|
339
|
+
if (boundary < 0) break;
|
|
340
|
+
const frame = buffer.slice(0, boundary);
|
|
341
|
+
buffer = buffer.slice(boundary + 2);
|
|
342
|
+
if (await consumeFrame(frame) === "terminal") return "terminal";
|
|
343
|
+
}
|
|
344
|
+
if (utf8Bytes(buffer) > options.maxFrameBytes) {
|
|
345
|
+
throw new Error("SSE frame exceeded its byte limit");
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
appendDecoded(decoder.decode());
|
|
349
|
+
if (buffer.trim() && await consumeFrame(buffer) === "terminal") {
|
|
350
|
+
return "terminal";
|
|
351
|
+
}
|
|
352
|
+
return "ended";
|
|
353
|
+
} finally {
|
|
354
|
+
await reader.cancel().catch(() => void 0);
|
|
355
|
+
reader.releaseLock();
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
// packages/koed-server/dist/desktop-collaboration-broker-local-transport.js
|
|
360
|
+
var collaborationCommandPath = "/v1/local-edge/collaboration/command";
|
|
361
|
+
var collaborationRealtimeSubscriptionsPath = "/v1/local-edge/collaboration/realtime/subscriptions";
|
|
362
|
+
var collaborationRealtimeBackendSubscriptionsPath = (backendId) => `/v1/local-edge/collaboration/realtime/backends/${encodeURIComponent(backendId)}/subscriptions`;
|
|
363
|
+
var commandRequestMaxBytes = 128 * 1024;
|
|
364
|
+
var commandResponseMaxBytes = 32 * 1024 * 1024;
|
|
365
|
+
var commandTimeoutMs = 3e4;
|
|
366
|
+
var reconnectBaseMs = 250;
|
|
367
|
+
var reconnectJitterRatio = 0.2;
|
|
368
|
+
var commandRequiresTeamBackend = (command, existing) => {
|
|
369
|
+
if (existing)
|
|
370
|
+
return existing.scope === "team";
|
|
371
|
+
switch (command.command) {
|
|
372
|
+
case "collaboration.load":
|
|
373
|
+
case "collaboration.connect_backend":
|
|
374
|
+
case "collaboration.reconnect_backend":
|
|
375
|
+
case "collaboration.disconnect_backend":
|
|
376
|
+
case "collaboration.create_personal_channel":
|
|
377
|
+
case "collaboration.preview_shared_memory_candidate":
|
|
378
|
+
return false;
|
|
379
|
+
case "collaboration.select":
|
|
380
|
+
return isTeamCollaborationSelection(command.input.selection);
|
|
381
|
+
case "collaboration.rename_thread":
|
|
382
|
+
case "collaboration.update_thread_topic":
|
|
383
|
+
case "collaboration.archive_thread":
|
|
384
|
+
case "collaboration.restore_thread":
|
|
385
|
+
case "collaboration.send_message":
|
|
386
|
+
case "collaboration.retry_message":
|
|
387
|
+
case "collaboration.mark_read":
|
|
388
|
+
case "collaboration.load_message_page":
|
|
389
|
+
return command.input.thread.scope === "team";
|
|
390
|
+
case "collaboration.subscribe":
|
|
391
|
+
return command.input.scope.scope === "team";
|
|
392
|
+
case "collaboration.unsubscribe":
|
|
393
|
+
case "collaboration.acknowledge_delivery":
|
|
394
|
+
return false;
|
|
395
|
+
default:
|
|
396
|
+
return true;
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
var safeError = (code, retryAfterMs = null) => ({
|
|
400
|
+
code,
|
|
401
|
+
userMessage: collaborationSafeErrorMessages[code],
|
|
402
|
+
retryable: code === "offline" || code === "temporarily_unavailable" || code === "rate_limited" || code === "conflict",
|
|
403
|
+
retryAfterMs
|
|
404
|
+
});
|
|
405
|
+
var failureResult = (command, error) => collaborationCommandResultSchema.parse({
|
|
406
|
+
contractVersion: COLLABORATION_CONTRACT_VERSION,
|
|
407
|
+
requestId: command.requestId,
|
|
408
|
+
command: command.command,
|
|
409
|
+
ok: false,
|
|
410
|
+
error
|
|
411
|
+
});
|
|
412
|
+
var retryAfterFrom = (response) => {
|
|
413
|
+
const value = response.headers.get("retry-after");
|
|
414
|
+
if (value === null)
|
|
415
|
+
return null;
|
|
416
|
+
const seconds = Number(value);
|
|
417
|
+
return Number.isFinite(seconds) && seconds >= 0 ? Math.min(3e5, Math.round(seconds * 1e3)) : null;
|
|
418
|
+
};
|
|
419
|
+
var errorForStatus = (response) => {
|
|
420
|
+
if (response.status === 400 || response.status === 422) {
|
|
421
|
+
return safeError("invalid_input");
|
|
422
|
+
}
|
|
423
|
+
if (response.status === 401 || response.status === 403) {
|
|
424
|
+
return safeError("permission_denied");
|
|
425
|
+
}
|
|
426
|
+
if (response.status === 404)
|
|
427
|
+
return safeError("not_available");
|
|
428
|
+
if (response.status === 409)
|
|
429
|
+
return safeError("conflict");
|
|
430
|
+
if (response.status === 410)
|
|
431
|
+
return safeError("access_revoked");
|
|
432
|
+
if (response.status === 429) {
|
|
433
|
+
return safeError("rate_limited", retryAfterFrom(response));
|
|
434
|
+
}
|
|
435
|
+
if ([502, 503, 504].includes(response.status)) {
|
|
436
|
+
return safeError("temporarily_unavailable", retryAfterFrom(response));
|
|
437
|
+
}
|
|
438
|
+
return safeError("internal_error");
|
|
439
|
+
};
|
|
440
|
+
var linkedAbortController = (signal, timeoutMs) => {
|
|
441
|
+
const controller = new AbortController();
|
|
442
|
+
const abort = () => controller.abort();
|
|
443
|
+
if (signal.aborted)
|
|
444
|
+
controller.abort();
|
|
445
|
+
else
|
|
446
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
447
|
+
const timer = timeoutMs === void 0 ? null : setTimeout(() => controller.abort(), timeoutMs);
|
|
448
|
+
timer?.unref?.();
|
|
449
|
+
return {
|
|
450
|
+
controller,
|
|
451
|
+
dispose: () => {
|
|
452
|
+
signal.removeEventListener("abort", abort);
|
|
453
|
+
if (timer)
|
|
454
|
+
clearTimeout(timer);
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
};
|
|
458
|
+
var calculateCollaborationReconnectDelay2 = (attempt, random) => calculateCollaborationReconnectDelay({
|
|
459
|
+
attempt: Math.max(0, attempt - 1),
|
|
460
|
+
baseMs: reconnectBaseMs,
|
|
461
|
+
maxMs: COLLABORATION_RECONNECT_BACKOFF_CAP_MS,
|
|
462
|
+
jitter: reconnectJitterRatio,
|
|
463
|
+
random
|
|
464
|
+
});
|
|
465
|
+
var parseCorrelatedResult = (payload, command) => {
|
|
466
|
+
const parsed = collaborationCommandResultSchema.safeParse(payload);
|
|
467
|
+
if (!parsed.success || parsed.data.requestId !== command.requestId || parsed.data.command !== command.command) {
|
|
468
|
+
return null;
|
|
469
|
+
}
|
|
470
|
+
return parsed.data;
|
|
471
|
+
};
|
|
472
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
473
|
+
var hasExactKeys = (value, keys) => {
|
|
474
|
+
const actual = Object.keys(value).sort();
|
|
475
|
+
const expected = [...keys].sort();
|
|
476
|
+
return actual.length === expected.length && actual.every((key, index) => key === expected[index]);
|
|
477
|
+
};
|
|
478
|
+
var parseBrokerSubscription = (raw, expectedScope, expectedTeamId) => {
|
|
479
|
+
if (!isRecord(raw) || !hasExactKeys(raw, [
|
|
480
|
+
"id",
|
|
481
|
+
"protocolVersion",
|
|
482
|
+
"scope",
|
|
483
|
+
"state",
|
|
484
|
+
"version",
|
|
485
|
+
"expiresAt"
|
|
486
|
+
]) || raw.protocolVersion !== COLLABORATION_CONTRACT_VERSION || !Number.isSafeInteger(raw.version) || Number(raw.version) < 0 || !isRecord(raw.scope)) {
|
|
487
|
+
throw new Error("Collaboration broker subscription is invalid");
|
|
488
|
+
}
|
|
489
|
+
const scope = expectedScope === "team" ? { scope: "team", teamId: expectedTeamId } : { scope: "personal" };
|
|
490
|
+
if (expectedScope === "personal" && (!hasExactKeys(raw.scope, ["scope"]) || raw.scope.scope !== "personal") || expectedScope === "team" && (!hasExactKeys(raw.scope, ["scope", "teamId"]) || raw.scope.scope !== "team" || raw.scope.teamId !== expectedTeamId)) {
|
|
491
|
+
throw new Error("Collaboration broker subscription binding is invalid");
|
|
492
|
+
}
|
|
493
|
+
const brokerVersion = Number(raw.version);
|
|
494
|
+
return {
|
|
495
|
+
brokerVersion,
|
|
496
|
+
subscription: collaborationSubscriptionSchema.parse({
|
|
497
|
+
id: raw.id,
|
|
498
|
+
scope,
|
|
499
|
+
state: raw.state,
|
|
500
|
+
version: Math.max(1, brokerVersion),
|
|
501
|
+
expiresAt: raw.expiresAt
|
|
502
|
+
})
|
|
503
|
+
};
|
|
504
|
+
};
|
|
505
|
+
var parseBrokerSnapshotResponse = (raw, scope, teamId) => {
|
|
506
|
+
if (!isRecord(raw) || !hasExactKeys(raw, ["protocolVersion", "subscription", "delivery"]) || raw.protocolVersion !== COLLABORATION_CONTRACT_VERSION || !isRecord(raw.delivery) || !hasExactKeys(raw.delivery, [
|
|
507
|
+
"deliveryId",
|
|
508
|
+
"eventId",
|
|
509
|
+
"type",
|
|
510
|
+
"snapshot"
|
|
511
|
+
]) || raw.delivery.type !== "snapshot" || raw.delivery.eventId !== null || !isRecord(raw.delivery.snapshot) || !Array.isArray(raw.delivery.snapshot.threads) || raw.delivery.snapshot.threads.length > 5e3) {
|
|
512
|
+
throw new Error("Collaboration broker snapshot response is invalid");
|
|
513
|
+
}
|
|
514
|
+
const snapshot = raw.delivery.snapshot;
|
|
515
|
+
const threads = snapshot.threads;
|
|
516
|
+
const snapshotMatches = scope === "team" ? hasExactKeys(snapshot, ["scope", "teamId", "threads"]) && snapshot.scope === "team" && snapshot.teamId === teamId && threads.every((thread) => isRecord(thread) && thread.scope === "team" && thread.teamId === teamId) : hasExactKeys(snapshot, [
|
|
517
|
+
"scope",
|
|
518
|
+
"personalOwnerUserId",
|
|
519
|
+
"highWaterCursor",
|
|
520
|
+
"threads"
|
|
521
|
+
]) && snapshot.scope === "personal" && typeof snapshot.personalOwnerUserId === "string" && Number.isSafeInteger(snapshot.highWaterCursor) && Number(snapshot.highWaterCursor) >= 0 && threads.every((thread) => isRecord(thread) && thread.scope === "personal");
|
|
522
|
+
if (!snapshotMatches) {
|
|
523
|
+
throw new Error("Collaboration broker snapshot binding is invalid");
|
|
524
|
+
}
|
|
525
|
+
const parsed = parseBrokerSubscription(raw.subscription, scope, teamId);
|
|
526
|
+
if (parsed.subscription.state !== "awaiting_snapshot_ack") {
|
|
527
|
+
throw new Error("Collaboration broker snapshot is not awaiting ack");
|
|
528
|
+
}
|
|
529
|
+
return {
|
|
530
|
+
...parsed,
|
|
531
|
+
deliveryId: collaborationDeliveryIdSchema.parse(raw.delivery.deliveryId)
|
|
532
|
+
};
|
|
533
|
+
};
|
|
534
|
+
var parseBrokerAckResponse = (raw, subscription) => {
|
|
535
|
+
if (!isRecord(raw) || !hasExactKeys(raw, ["protocolVersion", "subscription"]) || raw.protocolVersion !== COLLABORATION_CONTRACT_VERSION) {
|
|
536
|
+
throw new Error("Collaboration broker acknowledgement is invalid");
|
|
537
|
+
}
|
|
538
|
+
const parsed = parseBrokerSubscription(raw.subscription, subscription.scope, subscription.teamId);
|
|
539
|
+
if (parsed.subscription.id !== subscription.id || parsed.subscription.state !== "active") {
|
|
540
|
+
throw new Error("Collaboration broker acknowledgement binding is invalid");
|
|
541
|
+
}
|
|
542
|
+
return parsed;
|
|
543
|
+
};
|
|
544
|
+
var createDesktopCollaborationBrokerLocalTransport = (options) => {
|
|
545
|
+
const random = options.random ?? Math.random;
|
|
546
|
+
const now = options.now ?? Date.now;
|
|
547
|
+
const subscriptions = /* @__PURE__ */ new Map();
|
|
548
|
+
const latestTeamSelections = /* @__PURE__ */ new Map();
|
|
549
|
+
const activeTeamIds = /* @__PURE__ */ new Map();
|
|
550
|
+
const snapshotRequestGenerations = /* @__PURE__ */ new Map();
|
|
551
|
+
const authorityGenerations = /* @__PURE__ */ new Map();
|
|
552
|
+
let preferredBackendId;
|
|
553
|
+
const authorityGeneration = (ownerId) => authorityGenerations.get(ownerId) ?? 0;
|
|
554
|
+
const commandAffectsActiveSelection = (command) => collaborationCommandReturnsSnapshot(command.command) && !(command.command === "collaboration.select" && command.input.navigationIntent === "prewarm");
|
|
555
|
+
const invalidateAuthority = (ownerId, teamId) => {
|
|
556
|
+
authorityGenerations.set(ownerId, authorityGeneration(ownerId) + 1);
|
|
557
|
+
if (teamId) {
|
|
558
|
+
const selections = latestTeamSelections.get(ownerId);
|
|
559
|
+
selections?.delete(teamId);
|
|
560
|
+
if (selections?.size === 0)
|
|
561
|
+
latestTeamSelections.delete(ownerId);
|
|
562
|
+
} else {
|
|
563
|
+
latestTeamSelections.delete(ownerId);
|
|
564
|
+
}
|
|
565
|
+
};
|
|
566
|
+
const rememberTeamSelection = (ownerId, selection) => {
|
|
567
|
+
if (!("teamId" in selection))
|
|
568
|
+
return;
|
|
569
|
+
const selections = latestTeamSelections.get(ownerId) ?? /* @__PURE__ */ new Map();
|
|
570
|
+
selections.set(selection.teamId, selection);
|
|
571
|
+
latestTeamSelections.set(ownerId, selections);
|
|
572
|
+
};
|
|
573
|
+
const eventInvalidatesSelection = (event) => event.type === "connection" && (event.connection.state === "disconnected" || event.connection.state === "access_revoked") || event.type === "control" && event.reason === "access_revoked" || event.type === "update" && event.family === "access_revoked";
|
|
574
|
+
const emit = (subscription, event) => {
|
|
575
|
+
const parsed = collaborationRendererEventSchema.parse(event);
|
|
576
|
+
const matchesSubscription = parsed.type === "snapshot" && parsed.subscription.id === subscription.id && parsed.subscription.scope.scope === subscription.scope && (subscription.scope !== "team" || parsed.subscription.scope.scope === "team" && parsed.subscription.scope.teamId === subscription.teamId) || parsed.type === "update" && parsed.subscriptionId === subscription.id && (subscription.scope !== "team" || parsed.resource.teamId === subscription.teamId) || parsed.type === "control" && parsed.subscriptionId === subscription.id || parsed.type === "connection" && subscription.scope === "team" && parsed.connection.backendId === subscription.connection.backendId;
|
|
577
|
+
if (!matchesSubscription) {
|
|
578
|
+
throw new Error("Collaboration stream event binding is invalid");
|
|
579
|
+
}
|
|
580
|
+
if (eventInvalidatesSelection(parsed)) {
|
|
581
|
+
invalidateAuthority(subscription.ownerId, subscription.teamId);
|
|
582
|
+
}
|
|
583
|
+
if (parsed.type === "connection" && parsed.connection.state !== "access_revoked" && activeTeamIds.get(subscription.ownerId) !== subscription.teamId) {
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
subscription.emit(parsed);
|
|
587
|
+
};
|
|
588
|
+
const emitConnection = (subscription, state, reconnectAttempt, retryAt, error) => {
|
|
589
|
+
if (subscription.scope === "personal")
|
|
590
|
+
return;
|
|
591
|
+
emit(subscription, {
|
|
592
|
+
contractVersion: COLLABORATION_CONTRACT_VERSION,
|
|
593
|
+
type: "connection",
|
|
594
|
+
connection: {
|
|
595
|
+
state,
|
|
596
|
+
backendId: subscription.connection.backendId,
|
|
597
|
+
connectedAt: state === "live" ? new Date(now()).toISOString() : null,
|
|
598
|
+
retryAt,
|
|
599
|
+
reconnectAttempt,
|
|
600
|
+
protocolVersion: COLLABORATION_CONTRACT_VERSION
|
|
601
|
+
},
|
|
602
|
+
error
|
|
603
|
+
});
|
|
604
|
+
};
|
|
605
|
+
const emitTerminalControl = (subscription, reason) => {
|
|
606
|
+
emit(subscription, {
|
|
607
|
+
contractVersion: COLLABORATION_CONTRACT_VERSION,
|
|
608
|
+
type: "control",
|
|
609
|
+
subscriptionId: subscription.id,
|
|
610
|
+
occurredAt: new Date(now()).toISOString(),
|
|
611
|
+
reason
|
|
612
|
+
});
|
|
613
|
+
};
|
|
614
|
+
const parseBrokerStreamFrame = (subscription, eventName, payload) => {
|
|
615
|
+
const sharedEvent = collaborationRendererEventSchema.safeParse(payload);
|
|
616
|
+
if (sharedEvent.success) {
|
|
617
|
+
if (sharedEvent.data.type === "connection" && subscription.scope === "personal") {
|
|
618
|
+
return "continue";
|
|
619
|
+
}
|
|
620
|
+
emit(subscription, sharedEvent.data);
|
|
621
|
+
return sharedEvent.data.type === "control" ? "terminal" : "continue";
|
|
622
|
+
}
|
|
623
|
+
if (!isRecord(payload) || payload.protocolVersion !== COLLABORATION_CONTRACT_VERSION) {
|
|
624
|
+
throw new Error("Collaboration broker frame is invalid");
|
|
625
|
+
}
|
|
626
|
+
if (eventName === "ready") {
|
|
627
|
+
if (!hasExactKeys(payload, ["protocolVersion", "subscription"]) || !isRecord(payload.subscription) || !hasExactKeys(payload.subscription, ["id", "state", "version"]) || payload.subscription.id !== subscription.id || payload.subscription.state !== "active" || !Number.isSafeInteger(payload.subscription.version) || Number(payload.subscription.version) !== subscription.brokerVersion) {
|
|
628
|
+
throw new Error("Collaboration broker ready frame is invalid");
|
|
629
|
+
}
|
|
630
|
+
return "continue";
|
|
631
|
+
}
|
|
632
|
+
if (eventName === "heartbeat") {
|
|
633
|
+
if (!hasExactKeys(payload, ["protocolVersion", "subscription"]) || !isRecord(payload.subscription) || !hasExactKeys(payload.subscription, ["id"]) || payload.subscription.id !== subscription.id) {
|
|
634
|
+
throw new Error("Collaboration broker heartbeat frame is invalid");
|
|
635
|
+
}
|
|
636
|
+
return "continue";
|
|
637
|
+
}
|
|
638
|
+
if (eventName === "control" || eventName === "access_revoked") {
|
|
639
|
+
if (!hasExactKeys(payload, ["protocolVersion", "subscription", "reason"]) || !isRecord(payload.subscription) || !hasExactKeys(payload.subscription, ["id"]) || payload.subscription.id !== subscription.id || ![
|
|
640
|
+
"access_revoked",
|
|
641
|
+
"requires_snapshot",
|
|
642
|
+
"backpressure",
|
|
643
|
+
"server_shutdown"
|
|
644
|
+
].includes(String(payload.reason))) {
|
|
645
|
+
throw new Error("Collaboration broker control frame is invalid");
|
|
646
|
+
}
|
|
647
|
+
const reason = eventName === "access_revoked" ? "access_revoked" : payload.reason;
|
|
648
|
+
emit(subscription, {
|
|
649
|
+
contractVersion: COLLABORATION_CONTRACT_VERSION,
|
|
650
|
+
type: "control",
|
|
651
|
+
subscriptionId: subscription.id,
|
|
652
|
+
occurredAt: new Date(now()).toISOString(),
|
|
653
|
+
reason
|
|
654
|
+
});
|
|
655
|
+
return "terminal";
|
|
656
|
+
}
|
|
657
|
+
if (eventName === "collaboration_event") {
|
|
658
|
+
if (!hasExactKeys(payload, [
|
|
659
|
+
"protocolVersion",
|
|
660
|
+
"deliveryId",
|
|
661
|
+
"eventId",
|
|
662
|
+
"type",
|
|
663
|
+
"occurredAt",
|
|
664
|
+
"subscription",
|
|
665
|
+
"resource",
|
|
666
|
+
"actor"
|
|
667
|
+
]) || !isRecord(payload.subscription) || !hasExactKeys(payload.subscription, ["id"]) || payload.subscription.id !== subscription.id || !isRecord(payload.resource) || payload.resource.scope !== subscription.scope || subscription.scope === "team" && payload.resource.teamId !== subscription.teamId || !isRecord(payload.actor)) {
|
|
668
|
+
throw new Error("Collaboration broker event frame is invalid");
|
|
669
|
+
}
|
|
670
|
+
collaborationDeliveryIdSchema.parse(payload.deliveryId);
|
|
671
|
+
emitTerminalControl(subscription, "requires_snapshot");
|
|
672
|
+
return "terminal";
|
|
673
|
+
}
|
|
674
|
+
throw new Error("Collaboration broker event type is unsupported");
|
|
675
|
+
};
|
|
676
|
+
const runStream = async (subscription) => {
|
|
677
|
+
await runDurableRealtime({
|
|
678
|
+
signal: subscription.controller.signal,
|
|
679
|
+
isCurrent: () => subscriptions.get(subscription.id) === subscription,
|
|
680
|
+
now,
|
|
681
|
+
sleep: options.sleep,
|
|
682
|
+
retry: {
|
|
683
|
+
maxAttempts: COLLABORATION_RECONNECT_MAX_ATTEMPTS,
|
|
684
|
+
attemptWindowMs: COLLABORATION_RECONNECT_WINDOW_MS,
|
|
685
|
+
unavailableCooldownMs: COLLABORATION_RECONNECT_UNAVAILABLE_COOLDOWN_MS,
|
|
686
|
+
delayForAttempt: (attempt) => calculateCollaborationReconnectDelay2(attempt, random())
|
|
687
|
+
},
|
|
688
|
+
onState: (state) => {
|
|
689
|
+
if (state.state === "connecting" || state.state === "live") {
|
|
690
|
+
emitConnection(subscription, state.state, 0, null, null);
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
emitConnection(subscription, state.state, state.reconnectAttempt, new Date(state.retryAtMs).toISOString(), safeError("temporarily_unavailable", state.retryDelayMs));
|
|
694
|
+
},
|
|
695
|
+
connect: async ({ signal, reconnecting, isCurrent, markLive }) => {
|
|
696
|
+
if (reconnecting) {
|
|
697
|
+
const backendId = subscription.connection.backendId;
|
|
698
|
+
const refreshedConnection = await options.resolveConnection(subscription.scope === "team", backendId ?? void 0).catch(() => null);
|
|
699
|
+
if (!refreshedConnection || refreshedConnection.backendId !== backendId) {
|
|
700
|
+
throw new Error("Collaboration stream connection is unavailable");
|
|
701
|
+
}
|
|
702
|
+
subscription.connection = refreshedConnection;
|
|
703
|
+
}
|
|
704
|
+
if (!isCurrent())
|
|
705
|
+
return "terminal";
|
|
706
|
+
const transportId = negotiateDurableRealtimeTransport(options.offeredRealtimeTransports?.(subscription.connection) ?? [
|
|
707
|
+
"sse"
|
|
708
|
+
], ["sse"]);
|
|
709
|
+
if (transportId !== "sse") {
|
|
710
|
+
throw new Error("Collaboration realtime transport is unavailable");
|
|
711
|
+
}
|
|
712
|
+
const url = new URL(`${collaborationRealtimeSubscriptionsPath}/${encodeURIComponent(subscription.id)}/stream`, subscription.connection.apiUrl);
|
|
713
|
+
if (subscription.connection.backendId) {
|
|
714
|
+
url.searchParams.set("scope", "team");
|
|
715
|
+
url.searchParams.set("upstream_backend_id", subscription.connection.backendId);
|
|
716
|
+
}
|
|
717
|
+
if (subscription.teamId)
|
|
718
|
+
url.searchParams.set("team_id", subscription.teamId);
|
|
719
|
+
else
|
|
720
|
+
url.searchParams.set("scope", "personal");
|
|
721
|
+
const response = await options.fetch(url, {
|
|
722
|
+
method: "GET",
|
|
723
|
+
headers: {
|
|
724
|
+
accept: "text/event-stream",
|
|
725
|
+
authorization: subscription.connection.authorization
|
|
726
|
+
},
|
|
727
|
+
redirect: "error",
|
|
728
|
+
signal
|
|
729
|
+
});
|
|
730
|
+
if (!response.ok || !response.body || !response.headers.get("content-type")?.toLowerCase().startsWith("text/event-stream")) {
|
|
731
|
+
await response.body?.cancel().catch(() => void 0);
|
|
732
|
+
if ([401, 403, 410].includes(response.status)) {
|
|
733
|
+
emitConnection(subscription, "access_revoked", 0, null, safeError("access_revoked"));
|
|
734
|
+
return "terminal";
|
|
735
|
+
}
|
|
736
|
+
if (response.status === 404 || response.status === 409) {
|
|
737
|
+
emitTerminalControl(subscription, "requires_snapshot");
|
|
738
|
+
return "terminal";
|
|
739
|
+
}
|
|
740
|
+
throw new Error("Collaboration stream is unavailable");
|
|
741
|
+
}
|
|
742
|
+
markLive();
|
|
743
|
+
return readBoundedSse({
|
|
744
|
+
body: response.body,
|
|
745
|
+
signal,
|
|
746
|
+
maxFrameBytes: COLLABORATION_RENDERER_MAX_PENDING_BYTES,
|
|
747
|
+
onFrame: ({ event, data }) => {
|
|
748
|
+
if (!isCurrent())
|
|
749
|
+
return "terminal";
|
|
750
|
+
if (!data) {
|
|
751
|
+
throw new Error("Collaboration stream frame has no payload");
|
|
752
|
+
}
|
|
753
|
+
const payload = JSON.parse(data);
|
|
754
|
+
return parseBrokerStreamFrame(subscription, event, payload);
|
|
755
|
+
}
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
});
|
|
759
|
+
};
|
|
760
|
+
const stopSubscription = (subscriptionId) => {
|
|
761
|
+
const subscription = subscriptions.get(subscriptionId);
|
|
762
|
+
if (!subscription)
|
|
763
|
+
return;
|
|
764
|
+
subscription.controller.abort();
|
|
765
|
+
subscriptions.delete(subscriptionId);
|
|
766
|
+
};
|
|
767
|
+
const serializeSubscriptionOperation = (subscription, operation) => {
|
|
768
|
+
const result = subscription.operationTail.then(operation, operation);
|
|
769
|
+
subscription.operationTail = result.then(() => void 0, () => void 0);
|
|
770
|
+
return result;
|
|
771
|
+
};
|
|
772
|
+
const realtimeBinding = (subscription) => subscription.scope === "team" ? {
|
|
773
|
+
scope: "team",
|
|
774
|
+
upstream_backend_id: subscription.connection.backendId,
|
|
775
|
+
team_id: subscription.teamId
|
|
776
|
+
} : { scope: "personal" };
|
|
777
|
+
const acknowledgeDelivery = async (command, subscription, context) => {
|
|
778
|
+
if (subscription.scope === "team" && (!subscription.connection.backendId || !subscription.teamId)) {
|
|
779
|
+
return failureResult(command, safeError("not_available"));
|
|
780
|
+
}
|
|
781
|
+
if (command.input.expectedSubscriptionVersion !== subscription.version) {
|
|
782
|
+
emitTerminalControl(subscription, "requires_snapshot");
|
|
783
|
+
stopSubscription(subscription.id);
|
|
784
|
+
return failureResult(command, safeError("conflict"));
|
|
785
|
+
}
|
|
786
|
+
const linked = linkedAbortController(context.signal, commandTimeoutMs);
|
|
787
|
+
try {
|
|
788
|
+
const response = await options.fetch(new URL(`${collaborationRealtimeSubscriptionsPath}/${encodeURIComponent(subscription.id)}/ack`, subscription.connection.apiUrl), {
|
|
789
|
+
method: "POST",
|
|
790
|
+
headers: {
|
|
791
|
+
accept: "application/json",
|
|
792
|
+
authorization: subscription.connection.authorization,
|
|
793
|
+
"content-type": "application/json"
|
|
794
|
+
},
|
|
795
|
+
body: JSON.stringify({
|
|
796
|
+
...realtimeBinding(subscription),
|
|
797
|
+
delivery_id: command.input.deliveryId,
|
|
798
|
+
event_id: command.input.eventId,
|
|
799
|
+
expected_version: subscription.brokerVersion
|
|
800
|
+
}),
|
|
801
|
+
redirect: "error",
|
|
802
|
+
signal: linked.controller.signal
|
|
803
|
+
});
|
|
804
|
+
const payload = await readBoundedJsonObject(response, 1024 * 1024).catch(() => null);
|
|
805
|
+
if (!response.ok) {
|
|
806
|
+
if ([401, 403, 410].includes(response.status)) {
|
|
807
|
+
emitTerminalControl(subscription, "access_revoked");
|
|
808
|
+
stopSubscription(subscription.id);
|
|
809
|
+
return failureResult(command, safeError("access_revoked"));
|
|
810
|
+
}
|
|
811
|
+
if (response.status === 404 || response.status === 409) {
|
|
812
|
+
emitTerminalControl(subscription, "requires_snapshot");
|
|
813
|
+
stopSubscription(subscription.id);
|
|
814
|
+
return failureResult(command, safeError("conflict"));
|
|
815
|
+
}
|
|
816
|
+
return failureResult(command, errorForStatus(response));
|
|
817
|
+
}
|
|
818
|
+
const acknowledged = parseBrokerAckResponse(payload, subscription);
|
|
819
|
+
subscription.brokerVersion = acknowledged.brokerVersion;
|
|
820
|
+
subscription.version = acknowledged.subscription.version;
|
|
821
|
+
const startsStream = subscription.initialDeliveryId === command.input.deliveryId;
|
|
822
|
+
if (startsStream)
|
|
823
|
+
subscription.initialDeliveryId = null;
|
|
824
|
+
if (startsStream && !subscription.streamStarted) {
|
|
825
|
+
subscription.streamStarted = true;
|
|
826
|
+
void runStream(subscription).finally(() => {
|
|
827
|
+
if (subscriptions.get(subscription.id) === subscription) {
|
|
828
|
+
subscriptions.delete(subscription.id);
|
|
829
|
+
}
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
return collaborationCommandResultSchema.parse({
|
|
833
|
+
contractVersion: COLLABORATION_CONTRACT_VERSION,
|
|
834
|
+
requestId: command.requestId,
|
|
835
|
+
command: command.command,
|
|
836
|
+
ok: true,
|
|
837
|
+
data: {
|
|
838
|
+
subscriptionId: subscription.id,
|
|
839
|
+
acknowledgedEventId: command.input.eventId,
|
|
840
|
+
subscriptionVersion: subscription.version
|
|
841
|
+
}
|
|
842
|
+
});
|
|
843
|
+
} catch {
|
|
844
|
+
return failureResult(command, safeError("offline"));
|
|
845
|
+
} finally {
|
|
846
|
+
linked.dispose();
|
|
847
|
+
}
|
|
848
|
+
};
|
|
849
|
+
const unsubscribe = async (command, subscription, context) => {
|
|
850
|
+
if (subscription.scope === "team" && (!subscription.connection.backendId || !subscription.teamId)) {
|
|
851
|
+
return failureResult(command, safeError("not_available"));
|
|
852
|
+
}
|
|
853
|
+
subscription.controller.abort();
|
|
854
|
+
const linked = linkedAbortController(context.signal, commandTimeoutMs);
|
|
855
|
+
try {
|
|
856
|
+
const response = await options.fetch(new URL(`${collaborationRealtimeSubscriptionsPath}/${encodeURIComponent(subscription.id)}`, subscription.connection.apiUrl), {
|
|
857
|
+
method: "DELETE",
|
|
858
|
+
headers: {
|
|
859
|
+
accept: "application/json",
|
|
860
|
+
authorization: subscription.connection.authorization,
|
|
861
|
+
"content-type": "application/json"
|
|
862
|
+
},
|
|
863
|
+
body: JSON.stringify({
|
|
864
|
+
...realtimeBinding(subscription),
|
|
865
|
+
expected_version: subscription.version
|
|
866
|
+
}),
|
|
867
|
+
redirect: "error",
|
|
868
|
+
signal: linked.controller.signal
|
|
869
|
+
});
|
|
870
|
+
await response.body?.cancel().catch(() => void 0);
|
|
871
|
+
if (!response.ok) {
|
|
872
|
+
if ([401, 403, 409, 410].includes(response.status)) {
|
|
873
|
+
emitTerminalControl(subscription, "access_revoked");
|
|
874
|
+
stopSubscription(subscription.id);
|
|
875
|
+
return failureResult(command, safeError("access_revoked"));
|
|
876
|
+
}
|
|
877
|
+
return failureResult(command, errorForStatus(response));
|
|
878
|
+
}
|
|
879
|
+
stopSubscription(subscription.id);
|
|
880
|
+
return collaborationCommandResultSchema.parse({
|
|
881
|
+
contractVersion: COLLABORATION_CONTRACT_VERSION,
|
|
882
|
+
requestId: command.requestId,
|
|
883
|
+
command: command.command,
|
|
884
|
+
ok: true,
|
|
885
|
+
data: {}
|
|
886
|
+
});
|
|
887
|
+
} catch {
|
|
888
|
+
return failureResult(command, safeError("offline"));
|
|
889
|
+
} finally {
|
|
890
|
+
linked.dispose();
|
|
891
|
+
stopSubscription(subscription.id);
|
|
892
|
+
}
|
|
893
|
+
};
|
|
894
|
+
const fetchAuthorizedResnapshot = async (scope, teamId, connection, context) => {
|
|
895
|
+
const latestSelection = teamId ? latestTeamSelections.get(context.ownerId)?.get(teamId) : void 0;
|
|
896
|
+
let selection = null;
|
|
897
|
+
if (scope === "team") {
|
|
898
|
+
if (!latestSelection || teamId === null || !("teamId" in latestSelection) || latestSelection.teamId !== teamId) {
|
|
899
|
+
return null;
|
|
900
|
+
}
|
|
901
|
+
selection = latestSelection;
|
|
902
|
+
}
|
|
903
|
+
const command = collaborationRendererCommandSchema.parse({
|
|
904
|
+
contractVersion: COLLABORATION_CONTRACT_VERSION,
|
|
905
|
+
requestId: randomUUID(),
|
|
906
|
+
command: scope === "team" ? "collaboration.select" : "collaboration.load",
|
|
907
|
+
input: scope === "team" ? { selection } : {}
|
|
908
|
+
});
|
|
909
|
+
const body = JSON.stringify({
|
|
910
|
+
...scope === "team" ? { upstream_backend_id: connection.backendId } : {},
|
|
911
|
+
command
|
|
912
|
+
});
|
|
913
|
+
const linked = linkedAbortController(context.signal, commandTimeoutMs);
|
|
914
|
+
try {
|
|
915
|
+
const response = await options.fetch(new URL(collaborationCommandPath, connection.apiUrl), {
|
|
916
|
+
method: "POST",
|
|
917
|
+
headers: {
|
|
918
|
+
accept: "application/json",
|
|
919
|
+
authorization: connection.authorization,
|
|
920
|
+
"content-type": "application/json"
|
|
921
|
+
},
|
|
922
|
+
body,
|
|
923
|
+
redirect: "error",
|
|
924
|
+
signal: linked.controller.signal
|
|
925
|
+
});
|
|
926
|
+
const payload = await readBoundedJsonObject(response, commandResponseMaxBytes).catch(() => null);
|
|
927
|
+
const result = parseCorrelatedResult(payload, command);
|
|
928
|
+
if (!response.ok || !result?.ok || !("snapshot" in result.data)) {
|
|
929
|
+
return null;
|
|
930
|
+
}
|
|
931
|
+
return collaborationSnapshotSchema.parse(result.data.snapshot);
|
|
932
|
+
} catch {
|
|
933
|
+
return null;
|
|
934
|
+
} finally {
|
|
935
|
+
linked.dispose();
|
|
936
|
+
}
|
|
937
|
+
};
|
|
938
|
+
const realtimeSnapshotFrom = (scope, teamId, full) => {
|
|
939
|
+
if (scope === "personal") {
|
|
940
|
+
const personalSelection = !("teamId" in full.selection);
|
|
941
|
+
return {
|
|
942
|
+
scope: "personal",
|
|
943
|
+
snapshotRevision: full.snapshotRevision,
|
|
944
|
+
personalOwner: full.navigation.personalOwner,
|
|
945
|
+
personal: full.navigation.personal,
|
|
946
|
+
selection: personalSelection ? full.selection : { kind: "personal_memory" },
|
|
947
|
+
view: personalSelection ? full.view : {
|
|
948
|
+
kind: "personal_memory",
|
|
949
|
+
entries: full.navigation.personal.memory
|
|
950
|
+
}
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
const team = full.navigation.teams.find((item) => item.id === teamId);
|
|
954
|
+
if (!team || !full.navigation.teamPrincipal || !("teamId" in full.selection) || full.selection.teamId !== teamId) {
|
|
955
|
+
throw new Error("Authorized Team resnapshot binding is invalid");
|
|
956
|
+
}
|
|
957
|
+
return {
|
|
958
|
+
scope: "team",
|
|
959
|
+
teamId: team.id,
|
|
960
|
+
snapshotRevision: full.snapshotRevision,
|
|
961
|
+
teamPrincipal: full.navigation.teamPrincipal,
|
|
962
|
+
team,
|
|
963
|
+
selection: full.selection,
|
|
964
|
+
view: full.view
|
|
965
|
+
};
|
|
966
|
+
};
|
|
967
|
+
const subscribeRealtime = async (command, connection, context, expectedAuthorityGeneration) => {
|
|
968
|
+
const scope = command.input.scope.scope;
|
|
969
|
+
const teamId = command.input.scope.scope === "team" ? command.input.scope.teamId : null;
|
|
970
|
+
if (scope === "team" && !connection.backendId) {
|
|
971
|
+
return failureResult(command, safeError("not_available"));
|
|
972
|
+
}
|
|
973
|
+
const binding = scope === "team" ? {
|
|
974
|
+
scope: "team",
|
|
975
|
+
upstream_backend_id: connection.backendId,
|
|
976
|
+
team_id: teamId
|
|
977
|
+
} : { scope: "personal" };
|
|
978
|
+
const linked = linkedAbortController(context.signal, commandTimeoutMs);
|
|
979
|
+
try {
|
|
980
|
+
const response = await options.fetch(new URL(collaborationRealtimeSubscriptionsPath, connection.apiUrl), {
|
|
981
|
+
method: "POST",
|
|
982
|
+
headers: {
|
|
983
|
+
accept: "application/json",
|
|
984
|
+
authorization: connection.authorization,
|
|
985
|
+
"content-type": "application/json"
|
|
986
|
+
},
|
|
987
|
+
body: JSON.stringify(binding),
|
|
988
|
+
redirect: "error",
|
|
989
|
+
signal: linked.controller.signal
|
|
990
|
+
});
|
|
991
|
+
const payload = await readBoundedJsonObject(response, commandResponseMaxBytes).catch(() => null);
|
|
992
|
+
if (authorityGeneration(context.ownerId) !== expectedAuthorityGeneration) {
|
|
993
|
+
return failureResult(command, safeError("access_revoked"));
|
|
994
|
+
}
|
|
995
|
+
if (!response.ok) {
|
|
996
|
+
return failureResult(command, [401, 403, 409, 410].includes(response.status) ? safeError("access_revoked") : errorForStatus(response));
|
|
997
|
+
}
|
|
998
|
+
const broker = parseBrokerSnapshotResponse(payload, scope, teamId);
|
|
999
|
+
const full = await fetchAuthorizedResnapshot(scope, teamId, connection, context);
|
|
1000
|
+
if (authorityGeneration(context.ownerId) !== expectedAuthorityGeneration) {
|
|
1001
|
+
return failureResult(command, safeError("access_revoked"));
|
|
1002
|
+
}
|
|
1003
|
+
if (!full)
|
|
1004
|
+
return failureResult(command, safeError("internal_error"));
|
|
1005
|
+
const event = collaborationRendererEventSchema.parse({
|
|
1006
|
+
contractVersion: COLLABORATION_CONTRACT_VERSION,
|
|
1007
|
+
type: "snapshot",
|
|
1008
|
+
subscription: broker.subscription,
|
|
1009
|
+
deliveryId: broker.deliveryId,
|
|
1010
|
+
eventId: null,
|
|
1011
|
+
snapshot: realtimeSnapshotFrom(scope, teamId, full)
|
|
1012
|
+
});
|
|
1013
|
+
stopSubscription(broker.subscription.id);
|
|
1014
|
+
const active = {
|
|
1015
|
+
id: broker.subscription.id,
|
|
1016
|
+
ownerId: context.ownerId,
|
|
1017
|
+
scope,
|
|
1018
|
+
teamId,
|
|
1019
|
+
version: broker.subscription.version,
|
|
1020
|
+
brokerVersion: broker.brokerVersion,
|
|
1021
|
+
initialDeliveryId: broker.deliveryId,
|
|
1022
|
+
streamStarted: false,
|
|
1023
|
+
connection,
|
|
1024
|
+
controller: new AbortController(),
|
|
1025
|
+
emit: context.emitCollaborationEvent,
|
|
1026
|
+
operationTail: Promise.resolve()
|
|
1027
|
+
};
|
|
1028
|
+
subscriptions.set(active.id, active);
|
|
1029
|
+
const abortForOwner = () => stopSubscription(active.id);
|
|
1030
|
+
context.signal.addEventListener("abort", abortForOwner, { once: true });
|
|
1031
|
+
setTimeout(() => {
|
|
1032
|
+
if (subscriptions.get(active.id) !== active || active.controller.signal.aborted) {
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
1035
|
+
try {
|
|
1036
|
+
emit(active, event);
|
|
1037
|
+
} catch {
|
|
1038
|
+
stopSubscription(active.id);
|
|
1039
|
+
}
|
|
1040
|
+
}, 0);
|
|
1041
|
+
return collaborationCommandResultSchema.parse({
|
|
1042
|
+
contractVersion: COLLABORATION_CONTRACT_VERSION,
|
|
1043
|
+
requestId: command.requestId,
|
|
1044
|
+
command: command.command,
|
|
1045
|
+
ok: true,
|
|
1046
|
+
data: { subscription: broker.subscription }
|
|
1047
|
+
});
|
|
1048
|
+
} catch {
|
|
1049
|
+
return failureResult(command, safeError("internal_error"));
|
|
1050
|
+
} finally {
|
|
1051
|
+
linked.dispose();
|
|
1052
|
+
}
|
|
1053
|
+
};
|
|
1054
|
+
const request = async (command, context) => {
|
|
1055
|
+
const snapshotRequestGeneration = commandAffectsActiveSelection(command) ? (snapshotRequestGenerations.get(context.ownerId) ?? 0) + 1 : null;
|
|
1056
|
+
if (snapshotRequestGeneration !== null) {
|
|
1057
|
+
snapshotRequestGenerations.set(context.ownerId, snapshotRequestGeneration);
|
|
1058
|
+
}
|
|
1059
|
+
const requestAuthorityGeneration = authorityGeneration(context.ownerId);
|
|
1060
|
+
const existing = "subscriptionId" in command.input ? subscriptions.get(command.input.subscriptionId) : void 0;
|
|
1061
|
+
if (existing && existing.ownerId !== context.ownerId) {
|
|
1062
|
+
return failureResult(command, safeError("not_available"));
|
|
1063
|
+
}
|
|
1064
|
+
const connection = existing?.connection ?? await options.resolveConnection(commandRequiresTeamBackend(command, existing), preferredBackendId, ...command.command === "collaboration.amend_rule_detail" || command.command === "collaboration.amend_rule_content_policy" || command.command === "collaboration.control_sharing_rule" || command.command === "collaboration.create_conversation_rule" || command.command === "collaboration.create_repository_rule" || command.command === "collaboration.list_sharing_rule_deliveries" ? [true] : []).catch(() => null);
|
|
1065
|
+
if (!connection)
|
|
1066
|
+
return failureResult(command, safeError("offline"));
|
|
1067
|
+
const requiresTeamBackend = commandRequiresTeamBackend(command, existing);
|
|
1068
|
+
if (requiresTeamBackend && !connection.backendId) {
|
|
1069
|
+
return failureResult(command, safeError("offline"));
|
|
1070
|
+
}
|
|
1071
|
+
if (command.command === "collaboration.acknowledge_delivery") {
|
|
1072
|
+
return existing ? serializeSubscriptionOperation(existing, () => acknowledgeDelivery(command, existing, context)) : failureResult(command, safeError("not_available"));
|
|
1073
|
+
}
|
|
1074
|
+
if (command.command === "collaboration.unsubscribe") {
|
|
1075
|
+
existing?.controller.abort();
|
|
1076
|
+
return existing ? serializeSubscriptionOperation(existing, () => unsubscribe(command, existing, context)) : failureResult(command, safeError("not_available"));
|
|
1077
|
+
}
|
|
1078
|
+
if (command.command === "collaboration.subscribe") {
|
|
1079
|
+
return subscribeRealtime(command, connection, context, requestAuthorityGeneration);
|
|
1080
|
+
}
|
|
1081
|
+
const body = JSON.stringify({
|
|
1082
|
+
...requiresTeamBackend ? { upstream_backend_id: connection.backendId } : {},
|
|
1083
|
+
command
|
|
1084
|
+
});
|
|
1085
|
+
if (Buffer.byteLength(body, "utf8") > commandRequestMaxBytes) {
|
|
1086
|
+
return failureResult(command, safeError("invalid_input"));
|
|
1087
|
+
}
|
|
1088
|
+
const linked = linkedAbortController(context.signal, commandTimeoutMs);
|
|
1089
|
+
try {
|
|
1090
|
+
const response = await options.fetch(new URL(collaborationCommandPath, connection.apiUrl), {
|
|
1091
|
+
method: "POST",
|
|
1092
|
+
headers: {
|
|
1093
|
+
accept: "application/json",
|
|
1094
|
+
authorization: connection.authorization,
|
|
1095
|
+
"content-type": "application/json"
|
|
1096
|
+
},
|
|
1097
|
+
body,
|
|
1098
|
+
redirect: "error",
|
|
1099
|
+
signal: linked.controller.signal
|
|
1100
|
+
});
|
|
1101
|
+
const payload = await readBoundedJsonObject(response, commandResponseMaxBytes).catch(() => null);
|
|
1102
|
+
const result = parseCorrelatedResult(payload, command);
|
|
1103
|
+
if (!result) {
|
|
1104
|
+
return failureResult(command, response.ok ? safeError("internal_error") : errorForStatus(response));
|
|
1105
|
+
}
|
|
1106
|
+
if (authorityGeneration(context.ownerId) !== requestAuthorityGeneration) {
|
|
1107
|
+
return failureResult(command, safeError("access_revoked"));
|
|
1108
|
+
}
|
|
1109
|
+
if (result.ok && result.command === "collaboration.connect_backend") {
|
|
1110
|
+
preferredBackendId = result.data.backend.id;
|
|
1111
|
+
}
|
|
1112
|
+
if (result.ok && result.command === "collaboration.disconnect_backend") {
|
|
1113
|
+
invalidateAuthority(context.ownerId);
|
|
1114
|
+
}
|
|
1115
|
+
if (result.ok && "snapshot" in result.data && snapshotRequestGeneration !== null && snapshotRequestGenerations.get(context.ownerId) === snapshotRequestGeneration) {
|
|
1116
|
+
const parsedSnapshot = collaborationSnapshotSchema.parse(result.data.snapshot);
|
|
1117
|
+
rememberTeamSelection(context.ownerId, parsedSnapshot.selection);
|
|
1118
|
+
activeTeamIds.set(context.ownerId, "teamId" in parsedSnapshot.selection ? parsedSnapshot.selection.teamId : null);
|
|
1119
|
+
}
|
|
1120
|
+
return result;
|
|
1121
|
+
} catch {
|
|
1122
|
+
return failureResult(command, safeError("offline"));
|
|
1123
|
+
} finally {
|
|
1124
|
+
linked.dispose();
|
|
1125
|
+
}
|
|
1126
|
+
};
|
|
1127
|
+
const stopOwner = (ownerId) => {
|
|
1128
|
+
for (const subscription of [...subscriptions.values()]) {
|
|
1129
|
+
if (subscription.ownerId === ownerId)
|
|
1130
|
+
stopSubscription(subscription.id);
|
|
1131
|
+
}
|
|
1132
|
+
latestTeamSelections.delete(ownerId);
|
|
1133
|
+
activeTeamIds.delete(ownerId);
|
|
1134
|
+
snapshotRequestGenerations.delete(ownerId);
|
|
1135
|
+
authorityGenerations.delete(ownerId);
|
|
1136
|
+
};
|
|
1137
|
+
const stop = () => {
|
|
1138
|
+
for (const subscription of [...subscriptions.values()]) {
|
|
1139
|
+
stopSubscription(subscription.id);
|
|
1140
|
+
}
|
|
1141
|
+
latestTeamSelections.clear();
|
|
1142
|
+
activeTeamIds.clear();
|
|
1143
|
+
snapshotRequestGenerations.clear();
|
|
1144
|
+
authorityGenerations.clear();
|
|
1145
|
+
};
|
|
1146
|
+
const revokeBackendSubscriptions = async (backendId) => {
|
|
1147
|
+
const connection = await options.resolveConnection(false).catch(() => null);
|
|
1148
|
+
if (!connection)
|
|
1149
|
+
return false;
|
|
1150
|
+
const linked = linkedAbortController(new AbortController().signal, commandTimeoutMs);
|
|
1151
|
+
try {
|
|
1152
|
+
const response = await options.fetch(new URL(collaborationRealtimeBackendSubscriptionsPath(backendId), connection.apiUrl), {
|
|
1153
|
+
method: "DELETE",
|
|
1154
|
+
headers: {
|
|
1155
|
+
accept: "application/json",
|
|
1156
|
+
authorization: connection.authorization
|
|
1157
|
+
},
|
|
1158
|
+
redirect: "error",
|
|
1159
|
+
signal: linked.controller.signal
|
|
1160
|
+
});
|
|
1161
|
+
await response.body?.cancel().catch(() => void 0);
|
|
1162
|
+
return response.ok;
|
|
1163
|
+
} catch {
|
|
1164
|
+
return false;
|
|
1165
|
+
} finally {
|
|
1166
|
+
linked.dispose();
|
|
1167
|
+
}
|
|
1168
|
+
};
|
|
1169
|
+
return { request, revokeBackendSubscriptions, stop, stopOwner };
|
|
1170
|
+
};
|
|
1171
|
+
|
|
1172
|
+
// packages/koed-server/dist/desktop-collaboration-broker.js
|
|
1173
|
+
var requestHistoryMaxPerOwner = 512;
|
|
1174
|
+
var requestResultCacheMaxBytesPerOwner = 16 * 1024 * 1024;
|
|
1175
|
+
var capabilityRefreshLeadMs = 6e4;
|
|
1176
|
+
var capabilityRefreshRetryMs = 3e4;
|
|
1177
|
+
var disconnectCleanupRetryMs = 5e3;
|
|
1178
|
+
var uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
1179
|
+
var isOkRecord = (value) => Boolean(value && typeof value === "object" && "ok" in value);
|
|
1180
|
+
var collaborationSafeError = (code, retryAfterMs = null) => ({
|
|
1181
|
+
code,
|
|
1182
|
+
userMessage: collaborationSafeErrorMessages[code],
|
|
1183
|
+
retryable: code === "offline" || code === "temporarily_unavailable" || code === "rate_limited" || code === "conflict",
|
|
1184
|
+
retryAfterMs
|
|
1185
|
+
});
|
|
1186
|
+
var commandFailure = (command, error) => collaborationCommandResultSchema.parse({
|
|
1187
|
+
contractVersion: COLLABORATION_CONTRACT_VERSION,
|
|
1188
|
+
requestId: command.requestId,
|
|
1189
|
+
command: command.command,
|
|
1190
|
+
ok: false,
|
|
1191
|
+
error
|
|
1192
|
+
});
|
|
1193
|
+
var resultState = (value) => value && typeof value === "object" && typeof value.state === "string" ? value.state : null;
|
|
1194
|
+
var backendIdentityFromSummary = (value) => {
|
|
1195
|
+
if (!value || typeof value !== "object")
|
|
1196
|
+
return null;
|
|
1197
|
+
const parsed = collaborationBackendIdentitySchema.safeParse({
|
|
1198
|
+
id: value.id,
|
|
1199
|
+
baseUrl: value.baseUrl
|
|
1200
|
+
});
|
|
1201
|
+
return parsed.success ? parsed.data : null;
|
|
1202
|
+
};
|
|
1203
|
+
var findBackendIdentity = (paths, preferredBackendId) => {
|
|
1204
|
+
const backends = listUpstreamBackends(paths).backends ?? [];
|
|
1205
|
+
if (preferredBackendId) {
|
|
1206
|
+
const preferred = backends.find((item) => item.id === preferredBackendId);
|
|
1207
|
+
const parsedPreferred = backendIdentityFromSummary(preferred);
|
|
1208
|
+
if (parsedPreferred)
|
|
1209
|
+
return parsedPreferred;
|
|
1210
|
+
return null;
|
|
1211
|
+
}
|
|
1212
|
+
return backendIdentityFromSummary(getActiveUpstreamBackend(paths));
|
|
1213
|
+
};
|
|
1214
|
+
var enrollmentSupportsCollaboration = (value) => {
|
|
1215
|
+
if (!value || typeof value !== "object")
|
|
1216
|
+
return false;
|
|
1217
|
+
const enrollment = value.enrollment;
|
|
1218
|
+
if (!enrollment || typeof enrollment !== "object")
|
|
1219
|
+
return false;
|
|
1220
|
+
const families = enrollment.requestedOperationFamilies;
|
|
1221
|
+
return Array.isArray(families) && families.includes("personal_collaboration_read") && families.includes("personal_collaboration_write") && families.includes("team_workspace_read") && families.includes("team_chat_read") && families.includes("team_chat_write") && families.includes("share_grant_management") && families.includes("sync") && families.includes("managed_execution") && families.includes("managed_file_read") && families.includes("managed_terminal") && families.includes("action_grant");
|
|
1222
|
+
};
|
|
1223
|
+
var enableCollaborationRoutePolicy = (paths, backendId) => updateUpstreamBackendRoutePolicy(paths, backendId, {
|
|
1224
|
+
personalCollaboration: "enabled",
|
|
1225
|
+
teamWorkspaceRead: "enabled",
|
|
1226
|
+
shareGrantManagement: "enabled",
|
|
1227
|
+
sync: "enabled",
|
|
1228
|
+
managedExecution: "enabled",
|
|
1229
|
+
admin: "enabled"
|
|
1230
|
+
});
|
|
1231
|
+
var browserActivationUrlFromResult = (value) => {
|
|
1232
|
+
if (!value || typeof value !== "object")
|
|
1233
|
+
return null;
|
|
1234
|
+
const enrollment = value.enrollment;
|
|
1235
|
+
if (!enrollment || typeof enrollment !== "object")
|
|
1236
|
+
return null;
|
|
1237
|
+
const activationUrl = enrollment.activationUrl;
|
|
1238
|
+
if (typeof activationUrl !== "string" || !activationUrl.trim())
|
|
1239
|
+
return null;
|
|
1240
|
+
const parsed = collaborationRemoteBackendUrlSchema.safeParse(activationUrl.trim());
|
|
1241
|
+
if (parsed.success)
|
|
1242
|
+
return parsed.data;
|
|
1243
|
+
try {
|
|
1244
|
+
const url = new URL(activationUrl.trim());
|
|
1245
|
+
return (url.protocol === "http:" || url.protocol === "https:") && url.hostname && !url.username && !url.password && !url.search && !url.hash ? url.toString() : null;
|
|
1246
|
+
} catch {
|
|
1247
|
+
return null;
|
|
1248
|
+
}
|
|
1249
|
+
};
|
|
1250
|
+
var pruneOwnerRequests = (owner) => {
|
|
1251
|
+
let inspected = 0;
|
|
1252
|
+
while ((owner.requestOrder.length > requestHistoryMaxPerOwner || owner.resultCacheBytes > requestResultCacheMaxBytesPerOwner) && inspected < owner.requestOrder.length) {
|
|
1253
|
+
const requestId = owner.requestOrder.shift();
|
|
1254
|
+
if (!requestId)
|
|
1255
|
+
break;
|
|
1256
|
+
const record = owner.requests.get(requestId);
|
|
1257
|
+
if (record?.inFlight) {
|
|
1258
|
+
owner.requestOrder.push(requestId);
|
|
1259
|
+
inspected += 1;
|
|
1260
|
+
continue;
|
|
1261
|
+
}
|
|
1262
|
+
if (record) {
|
|
1263
|
+
owner.resultCacheBytes -= record.resultBytes;
|
|
1264
|
+
owner.requests.delete(requestId);
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
};
|
|
1268
|
+
var claimOwnerRequest = (owner, command) => {
|
|
1269
|
+
const serialized = JSON.stringify(command);
|
|
1270
|
+
const existing = owner.requests.get(command.requestId);
|
|
1271
|
+
if (existing) {
|
|
1272
|
+
if (existing.command !== serialized || existing.inFlight) {
|
|
1273
|
+
return { state: "conflict" };
|
|
1274
|
+
}
|
|
1275
|
+
if (existing.result && !existing.retryable) {
|
|
1276
|
+
return { state: "cached", result: existing.result };
|
|
1277
|
+
}
|
|
1278
|
+
if (!existing.retryable)
|
|
1279
|
+
return { state: "conflict" };
|
|
1280
|
+
owner.resultCacheBytes -= existing.resultBytes;
|
|
1281
|
+
existing.inFlight = true;
|
|
1282
|
+
existing.retryable = false;
|
|
1283
|
+
existing.result = null;
|
|
1284
|
+
existing.resultBytes = 0;
|
|
1285
|
+
return { state: "execute" };
|
|
1286
|
+
}
|
|
1287
|
+
owner.requests.set(command.requestId, {
|
|
1288
|
+
command: serialized,
|
|
1289
|
+
inFlight: true,
|
|
1290
|
+
retryable: false,
|
|
1291
|
+
result: null,
|
|
1292
|
+
resultBytes: 0
|
|
1293
|
+
});
|
|
1294
|
+
owner.requestOrder.push(command.requestId);
|
|
1295
|
+
pruneOwnerRequests(owner);
|
|
1296
|
+
return { state: "execute" };
|
|
1297
|
+
};
|
|
1298
|
+
var finishOwnerRequest = (owner, requestId, result) => {
|
|
1299
|
+
const record = owner.requests.get(requestId);
|
|
1300
|
+
if (!record)
|
|
1301
|
+
return;
|
|
1302
|
+
const resultBytes = Buffer.byteLength(JSON.stringify(result), "utf8");
|
|
1303
|
+
record.inFlight = false;
|
|
1304
|
+
record.retryable = !result.ok && result.error.retryable;
|
|
1305
|
+
record.result = result;
|
|
1306
|
+
record.resultBytes = resultBytes;
|
|
1307
|
+
owner.resultCacheBytes += resultBytes;
|
|
1308
|
+
pruneOwnerRequests(owner);
|
|
1309
|
+
};
|
|
1310
|
+
var failOwnerRequest = (owner, requestId) => {
|
|
1311
|
+
const record = owner.requests.get(requestId);
|
|
1312
|
+
if (!record)
|
|
1313
|
+
return;
|
|
1314
|
+
record.inFlight = false;
|
|
1315
|
+
record.retryable = true;
|
|
1316
|
+
};
|
|
1317
|
+
var createConnectionEvent = (state, backendId, error = null, now = Date.now()) => collaborationConnectionEventSchema.parse({
|
|
1318
|
+
contractVersion: COLLABORATION_CONTRACT_VERSION,
|
|
1319
|
+
type: "connection",
|
|
1320
|
+
connection: {
|
|
1321
|
+
state,
|
|
1322
|
+
backendId,
|
|
1323
|
+
connectedAt: state === "live" ? new Date(now).toISOString() : null,
|
|
1324
|
+
retryAt: null,
|
|
1325
|
+
reconnectAttempt: state === "reconnecting" ? 1 : 0,
|
|
1326
|
+
protocolVersion: COLLABORATION_CONTRACT_VERSION
|
|
1327
|
+
},
|
|
1328
|
+
error
|
|
1329
|
+
});
|
|
1330
|
+
var lifecycleFailure = (command, error) => commandFailure(command, error);
|
|
1331
|
+
var createDesktopCollaborationBroker = (dependencies = {}) => {
|
|
1332
|
+
const environment = dependencies.environment ?? process.env;
|
|
1333
|
+
const paths = dependencies.paths ?? resolveKoedServerPaths(environment);
|
|
1334
|
+
const fetcher = dependencies.fetch ?? globalThis.fetch.bind(globalThis);
|
|
1335
|
+
const ownedUpstreamFetcher = dependencies.fetch ? null : createSecureUpstreamFetch({
|
|
1336
|
+
allowPrivateNetworkForUrl: registeredPrivateNetworkPolicy(() => (listUpstreamBackends(paths).backends ?? []).map((backend) => ({
|
|
1337
|
+
baseUrl: backend.baseUrl,
|
|
1338
|
+
profile: backend.profile
|
|
1339
|
+
})))
|
|
1340
|
+
});
|
|
1341
|
+
const upstreamFetcher = dependencies.fetch ? fetcher : ownedUpstreamFetcher;
|
|
1342
|
+
const enrollmentStatus = dependencies.getUpstreamEnrollmentStatus ?? getUpstreamEnrollmentStatus;
|
|
1343
|
+
const refreshCapabilities = dependencies.refreshUpstreamBackendCapabilities ?? refreshUpstreamBackendCapabilities;
|
|
1344
|
+
const startEnrollment = dependencies.startUpstreamEnrollment ?? startUpstreamEnrollment;
|
|
1345
|
+
const disconnectEnrollment = dependencies.disconnectUpstreamBackendEnrollment ?? disconnectUpstreamBackendEnrollment;
|
|
1346
|
+
const sleep = dependencies.sleep ?? ((delayMs, signal) => new Promise((resolve) => {
|
|
1347
|
+
if (signal.aborted)
|
|
1348
|
+
return resolve();
|
|
1349
|
+
const onAbort = () => {
|
|
1350
|
+
clearTimeout(timer);
|
|
1351
|
+
resolve();
|
|
1352
|
+
};
|
|
1353
|
+
const timer = setTimeout(() => {
|
|
1354
|
+
signal.removeEventListener("abort", onAbort);
|
|
1355
|
+
resolve();
|
|
1356
|
+
}, delayMs);
|
|
1357
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1358
|
+
}));
|
|
1359
|
+
const repoEnv = loadRepoEnv(paths.repoRoot);
|
|
1360
|
+
const ownerContexts = /* @__PURE__ */ new Map();
|
|
1361
|
+
const enrollmentMonitors = /* @__PURE__ */ new Map();
|
|
1362
|
+
const capabilityRefreshes = /* @__PURE__ */ new Map();
|
|
1363
|
+
const capabilityRefreshTimers = /* @__PURE__ */ new Map();
|
|
1364
|
+
const pendingSendExecutions = /* @__PURE__ */ new Map();
|
|
1365
|
+
const pendingSendStarts = /* @__PURE__ */ new Map();
|
|
1366
|
+
let disconnectCleanupTimer = null;
|
|
1367
|
+
let lifecycleTail = Promise.resolve();
|
|
1368
|
+
const sessionSend = (message) => {
|
|
1369
|
+
const parsed = desktopCollaborationBrokerChildMessageSchema.parse(message);
|
|
1370
|
+
if (measureDesktopCollaborationBrokerMessageBytes(parsed) > DESKTOP_COLLABORATION_BROKER_MAX_MESSAGE_BYTES) {
|
|
1371
|
+
throw new Error("Desktop collaboration broker message exceeded its byte limit.");
|
|
1372
|
+
}
|
|
1373
|
+
dependencies.sendMessage?.(parsed);
|
|
1374
|
+
};
|
|
1375
|
+
const ownerContext = (ownerId) => {
|
|
1376
|
+
const current = ownerContexts.get(ownerId);
|
|
1377
|
+
if (current)
|
|
1378
|
+
return current;
|
|
1379
|
+
const created = {
|
|
1380
|
+
ownerId,
|
|
1381
|
+
controller: new AbortController(),
|
|
1382
|
+
requests: /* @__PURE__ */ new Map(),
|
|
1383
|
+
requestOrder: [],
|
|
1384
|
+
resultCacheBytes: 0,
|
|
1385
|
+
snapshot: null
|
|
1386
|
+
};
|
|
1387
|
+
ownerContexts.set(ownerId, created);
|
|
1388
|
+
return created;
|
|
1389
|
+
};
|
|
1390
|
+
const eventInvalidatesProtectedCustody = (event) => event.type === "connection" && (event.connection.state === "disconnected" || event.connection.state === "access_revoked") || event.type === "control" && event.reason === "access_revoked" || event.type === "update" && ([
|
|
1391
|
+
"team_lifecycle",
|
|
1392
|
+
"team_membership_access",
|
|
1393
|
+
"workspace_lifecycle_access",
|
|
1394
|
+
"share_grant_lifecycle",
|
|
1395
|
+
"access_revoked"
|
|
1396
|
+
].includes(event.family) || event.family === "thread_lifecycle" && (event.update.type === "thread_removed" || event.update.type === "thread_upserted" && (!event.update.thread.canPost || event.update.thread.lifecycle !== "active")));
|
|
1397
|
+
const cachedProtectedThreadIds = (owner, event) => {
|
|
1398
|
+
const threadIds = /* @__PURE__ */ new Set();
|
|
1399
|
+
if (event.resource.threadId)
|
|
1400
|
+
threadIds.add(event.resource.threadId);
|
|
1401
|
+
for (const record of owner.requests.values()) {
|
|
1402
|
+
if (!record.result?.ok)
|
|
1403
|
+
continue;
|
|
1404
|
+
const data = record.result.data;
|
|
1405
|
+
const cached = collaborationSnapshotSchema.safeParse(data.snapshot);
|
|
1406
|
+
if (!cached.success)
|
|
1407
|
+
continue;
|
|
1408
|
+
for (const team of cached.data.navigation.teams) {
|
|
1409
|
+
if (event.resource.teamId && team.id !== event.resource.teamId)
|
|
1410
|
+
continue;
|
|
1411
|
+
if (!event.resource.workspaceId) {
|
|
1412
|
+
for (const thread of team.directMessages)
|
|
1413
|
+
threadIds.add(thread.id);
|
|
1414
|
+
}
|
|
1415
|
+
for (const workspace of team.workspaces) {
|
|
1416
|
+
if (event.resource.workspaceId && workspace.id !== event.resource.workspaceId) {
|
|
1417
|
+
continue;
|
|
1418
|
+
}
|
|
1419
|
+
for (const thread of workspace.channels)
|
|
1420
|
+
threadIds.add(thread.id);
|
|
1421
|
+
for (const session of workspace.sharedMemory) {
|
|
1422
|
+
if (event.resource.sharedSessionId && session.id !== event.resource.sharedSessionId) {
|
|
1423
|
+
continue;
|
|
1424
|
+
}
|
|
1425
|
+
threadIds.add(session.companionThreadId);
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
if (cached.data.view.kind === "shared_session" && (!event.resource.sharedSessionId || cached.data.view.session.id === event.resource.sharedSessionId)) {
|
|
1430
|
+
threadIds.add(cached.data.view.companion.thread.id);
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
return threadIds;
|
|
1434
|
+
};
|
|
1435
|
+
const clearOwnerProtectedCustody = (owner, event) => {
|
|
1436
|
+
if (!eventInvalidatesProtectedCustody(event))
|
|
1437
|
+
return;
|
|
1438
|
+
const backendId = findBackendIdentity(paths)?.id ?? null;
|
|
1439
|
+
const fullBackendRevocation = event.type !== "update" || (event.family === "access_revoked" || event.family === "team_membership_access" || event.family === "team_lifecycle") && !event.resource.shareGrantId && !event.resource.workspaceId;
|
|
1440
|
+
if (backendId && fullBackendRevocation) {
|
|
1441
|
+
clearCollaborationActionGrantCustodyForBackend(paths.koedHome, backendId);
|
|
1442
|
+
clearCollaborationPendingTeamSends(paths.koedHome, backendId);
|
|
1443
|
+
} else if (event.type === "update") {
|
|
1444
|
+
const revokedThreadIds = cachedProtectedThreadIds(owner, event);
|
|
1445
|
+
for (const pending of listCollaborationPendingSends(paths.koedHome)) {
|
|
1446
|
+
if (pending.thread.scope === "team" && revokedThreadIds.has(pending.thread.threadId)) {
|
|
1447
|
+
deleteCollaborationPendingSend(paths.koedHome, pending.key);
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
owner.requests.clear();
|
|
1452
|
+
owner.requestOrder = [];
|
|
1453
|
+
owner.resultCacheBytes = 0;
|
|
1454
|
+
owner.snapshot = null;
|
|
1455
|
+
};
|
|
1456
|
+
const sendOwnerEvent = (ownerId, event) => {
|
|
1457
|
+
clearOwnerProtectedCustody(ownerContext(ownerId), event);
|
|
1458
|
+
sessionSend({
|
|
1459
|
+
protocolVersion: DESKTOP_COLLABORATION_BROKER_PROTOCOL_VERSION,
|
|
1460
|
+
contractVersion: COLLABORATION_CONTRACT_VERSION,
|
|
1461
|
+
sessionToken: environment.KOED_DESKTOP_COLLABORATION_BROKER_SESSION_TOKEN,
|
|
1462
|
+
type: "renderer_event",
|
|
1463
|
+
ownerId,
|
|
1464
|
+
event
|
|
1465
|
+
});
|
|
1466
|
+
};
|
|
1467
|
+
const transportContextForOwner = (ownerId) => {
|
|
1468
|
+
const owner = ownerContext(ownerId);
|
|
1469
|
+
return {
|
|
1470
|
+
ownerId,
|
|
1471
|
+
signal: owner.controller.signal,
|
|
1472
|
+
emitCollaborationEvent: (event) => sendOwnerEvent(ownerId, event)
|
|
1473
|
+
};
|
|
1474
|
+
};
|
|
1475
|
+
const durablePendingSendOwnerId = () => readDesktopLocalCredentialAuthorization(paths.koedHome)?.ownerUserId ?? null;
|
|
1476
|
+
const pendingSendThreadKey = (record) => [
|
|
1477
|
+
record.backendId ?? "personal",
|
|
1478
|
+
record.remotePrincipalId ?? record.ownerId,
|
|
1479
|
+
record.thread.scope === "team" ? record.thread.teamId : "personal",
|
|
1480
|
+
record.thread.threadId
|
|
1481
|
+
].join(":");
|
|
1482
|
+
const durableSendFromRecord = (record, snapshot, overrides = {}) => {
|
|
1483
|
+
if (record.ownerId !== durablePendingSendOwnerId())
|
|
1484
|
+
return null;
|
|
1485
|
+
let authority;
|
|
1486
|
+
let bodyAuthorized = overrides.bodyAuthorized ?? true;
|
|
1487
|
+
if (record.thread.scope === "personal") {
|
|
1488
|
+
const thread = snapshot?.navigation.personal.channels.find((candidate) => candidate.id === record.thread.threadId);
|
|
1489
|
+
if (snapshot && (!thread || !thread.canPost))
|
|
1490
|
+
return null;
|
|
1491
|
+
authority = {
|
|
1492
|
+
scope: "personal",
|
|
1493
|
+
ownerUserId: record.ownerId,
|
|
1494
|
+
threadId: record.thread.threadId
|
|
1495
|
+
};
|
|
1496
|
+
} else {
|
|
1497
|
+
const activeBackendId = findBackendIdentity(paths)?.id ?? null;
|
|
1498
|
+
const binding = activeBackendId ? readUpstreamEnrollmentBinding(paths, activeBackendId) : null;
|
|
1499
|
+
if (!snapshot || snapshot.connection.backendId !== record.backendId || snapshot.navigation.teamPrincipal?.id !== record.remotePrincipalId || !overrides.allowStaleTeamBinding && (!binding || activeBackendId !== record.backendId || binding.principalUserId !== record.remotePrincipalId || binding.deviceCredentialId !== record.deviceCredentialId)) {
|
|
1500
|
+
return null;
|
|
1501
|
+
}
|
|
1502
|
+
const teamId = record.thread.teamId;
|
|
1503
|
+
const team = snapshot.navigation.teams.find((candidate) => candidate.id === teamId && candidate.lifecycle === "active");
|
|
1504
|
+
if (!team)
|
|
1505
|
+
return null;
|
|
1506
|
+
const directMessage = team.directMessages.find((candidate) => candidate.id === record.thread.threadId);
|
|
1507
|
+
let workspaceId = null;
|
|
1508
|
+
let canPost = directMessage?.canPost ?? false;
|
|
1509
|
+
if (!directMessage) {
|
|
1510
|
+
for (const workspace of team.workspaces) {
|
|
1511
|
+
if (workspace.lifecycle !== "active" || workspace.access !== "write") {
|
|
1512
|
+
continue;
|
|
1513
|
+
}
|
|
1514
|
+
const channel = workspace.channels.find((candidate) => candidate.id === record.thread.threadId);
|
|
1515
|
+
const sharedSession = workspace.sharedMemory.find((candidate) => candidate.companionThreadId === record.thread.threadId);
|
|
1516
|
+
if (channel || sharedSession) {
|
|
1517
|
+
workspaceId = workspace.id;
|
|
1518
|
+
canPost = channel?.canPost ?? (snapshot.view.kind === "shared_session" && snapshot.view.companion.thread.id === record.thread.threadId && snapshot.view.companion.thread.canPost);
|
|
1519
|
+
break;
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
if (!canPost)
|
|
1524
|
+
return null;
|
|
1525
|
+
authority = {
|
|
1526
|
+
scope: "team",
|
|
1527
|
+
backendId: record.backendId,
|
|
1528
|
+
principalUserId: record.remotePrincipalId,
|
|
1529
|
+
teamId: record.thread.teamId,
|
|
1530
|
+
workspaceId,
|
|
1531
|
+
threadId: record.thread.threadId
|
|
1532
|
+
};
|
|
1533
|
+
}
|
|
1534
|
+
const state = overrides.state ?? (record.state === "pending" ? "queued" : record.state);
|
|
1535
|
+
const failure = overrides.failure === void 0 ? record.failure : overrides.failure;
|
|
1536
|
+
if (overrides.bodyAuthorized === false)
|
|
1537
|
+
bodyAuthorized = false;
|
|
1538
|
+
return collaborationDurableSendSchema.parse({
|
|
1539
|
+
clientMessageId: record.clientMessageId,
|
|
1540
|
+
authority,
|
|
1541
|
+
body: bodyAuthorized ? record.body : null,
|
|
1542
|
+
localCreationOrder: record.localCreationOrder,
|
|
1543
|
+
state,
|
|
1544
|
+
retryable: state === "queued" || state === "manual_retry",
|
|
1545
|
+
removalSupported: false,
|
|
1546
|
+
failure,
|
|
1547
|
+
createdAt: record.createdAt,
|
|
1548
|
+
updatedAt: record.updatedAt
|
|
1549
|
+
});
|
|
1550
|
+
};
|
|
1551
|
+
const snapshotWithDurableSends = (value, purgeUnauthorized = true) => {
|
|
1552
|
+
const outbox = [];
|
|
1553
|
+
for (const record of listCollaborationPendingSends(paths.koedHome)) {
|
|
1554
|
+
const projected = durableSendFromRecord(record, value);
|
|
1555
|
+
if (projected) {
|
|
1556
|
+
outbox.push(projected);
|
|
1557
|
+
} else if (purgeUnauthorized && record.thread.scope === "team") {
|
|
1558
|
+
deleteCollaborationPendingSend(paths.koedHome, record.key);
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
return collaborationSnapshotSchema.parse({ ...value, outbox });
|
|
1562
|
+
};
|
|
1563
|
+
const emitDurableSend = (ownerId, send, message) => {
|
|
1564
|
+
sendOwnerEvent(ownerId, collaborationDurableSendEventSchema.parse({
|
|
1565
|
+
contractVersion: COLLABORATION_CONTRACT_VERSION,
|
|
1566
|
+
type: "durable_send",
|
|
1567
|
+
eventId: randomUUID2(),
|
|
1568
|
+
send,
|
|
1569
|
+
message
|
|
1570
|
+
}));
|
|
1571
|
+
};
|
|
1572
|
+
const runLifecycleExclusive = async (operation) => {
|
|
1573
|
+
const previous = lifecycleTail;
|
|
1574
|
+
let release;
|
|
1575
|
+
lifecycleTail = new Promise((resolve) => {
|
|
1576
|
+
release = resolve;
|
|
1577
|
+
});
|
|
1578
|
+
await previous;
|
|
1579
|
+
try {
|
|
1580
|
+
return await operation();
|
|
1581
|
+
} finally {
|
|
1582
|
+
release();
|
|
1583
|
+
}
|
|
1584
|
+
};
|
|
1585
|
+
const broadcastEvent = (event) => {
|
|
1586
|
+
for (const owner of ownerContexts.values()) {
|
|
1587
|
+
sendOwnerEvent(owner.ownerId, event);
|
|
1588
|
+
}
|
|
1589
|
+
};
|
|
1590
|
+
const stopEnrollmentMonitors = () => {
|
|
1591
|
+
for (const controller of enrollmentMonitors.values())
|
|
1592
|
+
controller.abort();
|
|
1593
|
+
enrollmentMonitors.clear();
|
|
1594
|
+
};
|
|
1595
|
+
const clearCapabilityRefreshTimers = () => {
|
|
1596
|
+
for (const timer of capabilityRefreshTimers.values())
|
|
1597
|
+
clearTimeout(timer);
|
|
1598
|
+
capabilityRefreshTimers.clear();
|
|
1599
|
+
};
|
|
1600
|
+
const scheduleCapabilityRefresh = (backendId, expiresAt) => {
|
|
1601
|
+
const existing = capabilityRefreshTimers.get(backendId);
|
|
1602
|
+
if (existing)
|
|
1603
|
+
clearTimeout(existing);
|
|
1604
|
+
capabilityRefreshTimers.delete(backendId);
|
|
1605
|
+
if (getActiveUpstreamBackend(paths)?.id !== backendId)
|
|
1606
|
+
return;
|
|
1607
|
+
const now = dependencies.now?.() ?? Date.now();
|
|
1608
|
+
const delay = expiresAt === null ? capabilityRefreshRetryMs : Math.max(0, expiresAt - now - capabilityRefreshLeadMs);
|
|
1609
|
+
const timer = setTimeout(() => {
|
|
1610
|
+
capabilityRefreshTimers.delete(backendId);
|
|
1611
|
+
void ensureBackendCapabilities(backendId);
|
|
1612
|
+
}, delay);
|
|
1613
|
+
timer.unref?.();
|
|
1614
|
+
capabilityRefreshTimers.set(backendId, timer);
|
|
1615
|
+
};
|
|
1616
|
+
async function ensureBackendCapabilities(preferredBackendId) {
|
|
1617
|
+
const backend = preferredBackendId ? (listUpstreamBackends(paths).backends ?? []).find((candidate) => candidate.id === preferredBackendId) : getActiveUpstreamBackend(paths);
|
|
1618
|
+
if (!backend)
|
|
1619
|
+
return false;
|
|
1620
|
+
const now = dependencies.now?.() ?? Date.now();
|
|
1621
|
+
const expiresAt = backend.capabilities.expiresAt ? Date.parse(backend.capabilities.expiresAt) : Number.NaN;
|
|
1622
|
+
if (backend.capabilities.state === "validated" && Number.isFinite(expiresAt) && expiresAt > now + capabilityRefreshLeadMs) {
|
|
1623
|
+
scheduleCapabilityRefresh(backend.id, expiresAt);
|
|
1624
|
+
return true;
|
|
1625
|
+
}
|
|
1626
|
+
const existing = capabilityRefreshes.get(backend.id);
|
|
1627
|
+
if (existing)
|
|
1628
|
+
return existing;
|
|
1629
|
+
const refresh = refreshCapabilities(paths, backend.id, {
|
|
1630
|
+
fetch: upstreamFetcher,
|
|
1631
|
+
now: () => new Date(dependencies.now?.() ?? Date.now())
|
|
1632
|
+
}).then((result) => {
|
|
1633
|
+
const refreshed = (listUpstreamBackends(paths).backends ?? []).find((candidate) => candidate.id === backend.id);
|
|
1634
|
+
const refreshedExpiry = refreshed?.capabilities.expiresAt ? Date.parse(refreshed.capabilities.expiresAt) : Number.NaN;
|
|
1635
|
+
scheduleCapabilityRefresh(backend.id, result.ok && Number.isFinite(refreshedExpiry) ? refreshedExpiry : null);
|
|
1636
|
+
return result.ok;
|
|
1637
|
+
}).catch(() => {
|
|
1638
|
+
scheduleCapabilityRefresh(backend.id, null);
|
|
1639
|
+
return false;
|
|
1640
|
+
}).finally(() => capabilityRefreshes.delete(backend.id));
|
|
1641
|
+
capabilityRefreshes.set(backend.id, refresh);
|
|
1642
|
+
return refresh;
|
|
1643
|
+
}
|
|
1644
|
+
const createTransport = () => createDesktopCollaborationBrokerLocalTransport({
|
|
1645
|
+
fetch: fetcher,
|
|
1646
|
+
random: dependencies.random,
|
|
1647
|
+
now: dependencies.now,
|
|
1648
|
+
sleep: dependencies.sleep,
|
|
1649
|
+
resolveConnection: async (requiresTeamBackend, preferredBackendId, localDeliveryOnly) => {
|
|
1650
|
+
if (requiresTeamBackend && !localDeliveryOnly && !await ensureBackendCapabilities(preferredBackendId)) {
|
|
1651
|
+
return null;
|
|
1652
|
+
}
|
|
1653
|
+
const credential = readDesktopLocalCredentialAuthorization(paths.koedHome);
|
|
1654
|
+
if (!credential)
|
|
1655
|
+
return Promise.resolve(null);
|
|
1656
|
+
const backendId = requiresTeamBackend ? findBackendIdentity(paths, preferredBackendId)?.id ?? null : null;
|
|
1657
|
+
if (requiresTeamBackend && !backendId)
|
|
1658
|
+
return Promise.resolve(null);
|
|
1659
|
+
const activeRuntime = readActiveRuntimeState(paths.runtimeStatePath);
|
|
1660
|
+
return Promise.resolve({
|
|
1661
|
+
apiUrl: (activeRuntime?.apiUrl ?? resolveApiUrl(applyActiveRuntimeUrls(applyPersistedLocalPorts(paths, environment, { force: true }), activeRuntime), repoEnv)).replace(/\/$/, ""),
|
|
1662
|
+
backendId,
|
|
1663
|
+
authorization: credential.authorization
|
|
1664
|
+
});
|
|
1665
|
+
}
|
|
1666
|
+
});
|
|
1667
|
+
let transport = createTransport();
|
|
1668
|
+
const resetTransport = () => {
|
|
1669
|
+
transport.stop();
|
|
1670
|
+
transport = createTransport();
|
|
1671
|
+
};
|
|
1672
|
+
const clearOwnerTeamRuntimeState = () => {
|
|
1673
|
+
for (const owner of ownerContexts.values()) {
|
|
1674
|
+
owner.controller.abort();
|
|
1675
|
+
owner.controller = new AbortController();
|
|
1676
|
+
owner.requests.clear();
|
|
1677
|
+
owner.requestOrder = [];
|
|
1678
|
+
owner.resultCacheBytes = 0;
|
|
1679
|
+
owner.snapshot = null;
|
|
1680
|
+
}
|
|
1681
|
+
pendingSendExecutions.clear();
|
|
1682
|
+
};
|
|
1683
|
+
const clearBackendDurableState = async (backendId) => {
|
|
1684
|
+
let complete = true;
|
|
1685
|
+
try {
|
|
1686
|
+
if (!await transport.revokeBackendSubscriptions(backendId)) {
|
|
1687
|
+
complete = false;
|
|
1688
|
+
}
|
|
1689
|
+
} catch {
|
|
1690
|
+
complete = false;
|
|
1691
|
+
}
|
|
1692
|
+
for (const clear of [
|
|
1693
|
+
() => clearCollaborationActionGrantCustodyForBackend(paths.koedHome, backendId),
|
|
1694
|
+
() => clearCollaborationPendingTeamSends(paths.koedHome, backendId),
|
|
1695
|
+
() => removeProjectTeamWorkspaceLinksForBackend(paths, backendId)
|
|
1696
|
+
]) {
|
|
1697
|
+
try {
|
|
1698
|
+
clear();
|
|
1699
|
+
} catch {
|
|
1700
|
+
complete = false;
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
if (complete) {
|
|
1704
|
+
completeUpstreamDisconnectCleanup(paths, backendId);
|
|
1705
|
+
} else if (upstreamDisconnectCleanupPending(paths, backendId)) {
|
|
1706
|
+
updateUpstreamDisconnectCleanup(paths, backendId, {
|
|
1707
|
+
phase: "local_cleanup_pending",
|
|
1708
|
+
lastFailureCategory: "local_cleanup_failed"
|
|
1709
|
+
});
|
|
1710
|
+
}
|
|
1711
|
+
return complete;
|
|
1712
|
+
};
|
|
1713
|
+
const clearBackendLocalState = async (backendId) => {
|
|
1714
|
+
transport.stop();
|
|
1715
|
+
const complete = await clearBackendDurableState(backendId);
|
|
1716
|
+
clearOwnerTeamRuntimeState();
|
|
1717
|
+
transport = createTransport();
|
|
1718
|
+
return complete;
|
|
1719
|
+
};
|
|
1720
|
+
const retryRevokedBackendDurableCleanup = async () => {
|
|
1721
|
+
for (const record of listUpstreamDisconnectCleanupRecords(paths)) {
|
|
1722
|
+
if (record.phase === "remote_revocation_pending") {
|
|
1723
|
+
const backend = (listUpstreamBackends(paths).backends ?? []).find((candidate) => candidate.id === record.backendId);
|
|
1724
|
+
if (!backend)
|
|
1725
|
+
continue;
|
|
1726
|
+
const disconnected = await disconnectBackend(backend);
|
|
1727
|
+
if (!disconnected.revoked)
|
|
1728
|
+
continue;
|
|
1729
|
+
} else {
|
|
1730
|
+
await clearBackendDurableState(record.backendId);
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
};
|
|
1734
|
+
const scheduleDisconnectCleanupRetry = () => {
|
|
1735
|
+
if (disconnectCleanupTimer || !upstreamDisconnectCleanupPending(paths)) {
|
|
1736
|
+
return;
|
|
1737
|
+
}
|
|
1738
|
+
disconnectCleanupTimer = setTimeout(() => {
|
|
1739
|
+
disconnectCleanupTimer = null;
|
|
1740
|
+
void runLifecycleExclusive(retryRevokedBackendDurableCleanup).finally(scheduleDisconnectCleanupRetry);
|
|
1741
|
+
}, disconnectCleanupRetryMs);
|
|
1742
|
+
disconnectCleanupTimer.unref?.();
|
|
1743
|
+
};
|
|
1744
|
+
const disconnectBackend = async (backend) => {
|
|
1745
|
+
const disconnected = await disconnectEnrollment(paths, backend.id, {
|
|
1746
|
+
fetch: upstreamFetcher
|
|
1747
|
+
});
|
|
1748
|
+
if (!isOkRecord(disconnected) || disconnected.ok !== true) {
|
|
1749
|
+
return { revoked: false, cleanupComplete: false };
|
|
1750
|
+
}
|
|
1751
|
+
if (!upstreamDisconnectCleanupPending(paths, backend.id)) {
|
|
1752
|
+
beginUpstreamDisconnectCleanup(paths, backend.id);
|
|
1753
|
+
updateUpstreamDisconnectCleanup(paths, backend.id, {
|
|
1754
|
+
phase: "local_cleanup_pending",
|
|
1755
|
+
lastFailureCategory: null
|
|
1756
|
+
});
|
|
1757
|
+
}
|
|
1758
|
+
return {
|
|
1759
|
+
revoked: true,
|
|
1760
|
+
cleanupComplete: await clearBackendLocalState(backend.id)
|
|
1761
|
+
};
|
|
1762
|
+
};
|
|
1763
|
+
const remapSendResult = (command, result) => collaborationCommandResultSchema.parse({
|
|
1764
|
+
...result,
|
|
1765
|
+
requestId: command.requestId,
|
|
1766
|
+
command: command.command
|
|
1767
|
+
});
|
|
1768
|
+
const executePendingSend = async (command, pending, context) => {
|
|
1769
|
+
if (pending.backendId) {
|
|
1770
|
+
const activeBackend = getActiveUpstreamBackend(paths);
|
|
1771
|
+
const activeBackendId = activeBackend?.id ?? null;
|
|
1772
|
+
const binding = activeBackendId ? readUpstreamEnrollmentBinding(paths, activeBackendId) : null;
|
|
1773
|
+
const credential = activeBackendId ? readLocalEdgeClientCredentialAuthorization(paths.koedHome, activeBackendId) : null;
|
|
1774
|
+
const requiredFamily = pending.thread.scope === "personal" ? "personal_collaboration_write" : "team_chat_write";
|
|
1775
|
+
if (pending.backendId !== activeBackendId || pending.thread.scope === "personal" && activeBackend?.routePolicy.personalCollaboration !== "enabled" || !binding || !credential?.operationFamilies.includes(requiredFamily) || pending.remotePrincipalId !== binding.principalUserId || pending.deviceCredentialId !== binding.deviceCredentialId) {
|
|
1776
|
+
const error = collaborationSafeError("access_revoked");
|
|
1777
|
+
const failed = durableSendFromRecord(pending, ownerContext(context.ownerId).snapshot, {
|
|
1778
|
+
state: "failed",
|
|
1779
|
+
failure: error,
|
|
1780
|
+
bodyAuthorized: false,
|
|
1781
|
+
allowStaleTeamBinding: true
|
|
1782
|
+
});
|
|
1783
|
+
deleteCollaborationPendingSend(paths.koedHome, pending.key);
|
|
1784
|
+
if (failed)
|
|
1785
|
+
emitDurableSend(context.ownerId, failed, null);
|
|
1786
|
+
return commandFailure(command, error);
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
const currentExecution = pendingSendExecutions.get(pending.key);
|
|
1790
|
+
if (currentExecution) {
|
|
1791
|
+
return remapSendResult(command, await currentExecution);
|
|
1792
|
+
}
|
|
1793
|
+
const earlierBlockingSend = listCollaborationPendingSends(paths.koedHome).find((candidate) => pendingSendThreadKey(candidate) === pendingSendThreadKey(pending) && candidate.localCreationOrder < pending.localCreationOrder && (candidate.state === "pending" || candidate.state === "manual_retry"));
|
|
1794
|
+
if (earlierBlockingSend) {
|
|
1795
|
+
return commandFailure(command, collaborationSafeError("temporarily_unavailable"));
|
|
1796
|
+
}
|
|
1797
|
+
const execution = (async () => {
|
|
1798
|
+
let record = pending;
|
|
1799
|
+
let lastResult = null;
|
|
1800
|
+
while (!context.signal.aborted && record.attemptCount < COLLABORATION_SEND_RETRY_MAX_ATTEMPTS) {
|
|
1801
|
+
if (record.nextAttemptAt) {
|
|
1802
|
+
const delayMs2 = Math.max(0, Date.parse(record.nextAttemptAt) - (dependencies.now?.() ?? Date.now()));
|
|
1803
|
+
if (delayMs2 > 0)
|
|
1804
|
+
await sleep(delayMs2, context.signal);
|
|
1805
|
+
if (context.signal.aborted)
|
|
1806
|
+
break;
|
|
1807
|
+
}
|
|
1808
|
+
const attemptCommand = collaborationRendererCommandSchema.parse({
|
|
1809
|
+
contractVersion: COLLABORATION_CONTRACT_VERSION,
|
|
1810
|
+
requestId: record.attemptCount === 0 ? command.requestId : randomUUID2(),
|
|
1811
|
+
command: record.attemptCount === 0 ? command.command : "collaboration.retry_message",
|
|
1812
|
+
input: {
|
|
1813
|
+
thread: record.thread,
|
|
1814
|
+
clientMessageId: record.clientMessageId,
|
|
1815
|
+
body: record.body
|
|
1816
|
+
}
|
|
1817
|
+
});
|
|
1818
|
+
lastResult = await transport.request(attemptCommand, context);
|
|
1819
|
+
const attemptCount = record.attemptCount + 1;
|
|
1820
|
+
if (lastResult.ok) {
|
|
1821
|
+
if (lastResult.command !== "collaboration.send_message" && lastResult.command !== "collaboration.retry_message" || !("message" in lastResult.data)) {
|
|
1822
|
+
return commandFailure(command, collaborationSafeError("internal_error"));
|
|
1823
|
+
}
|
|
1824
|
+
const confirmedMessage = {
|
|
1825
|
+
...lastResult.data.message,
|
|
1826
|
+
clientMessageId: record.clientMessageId
|
|
1827
|
+
};
|
|
1828
|
+
const sent = durableSendFromRecord(record, ownerContext(context.ownerId).snapshot, { state: "sent", failure: null });
|
|
1829
|
+
deleteCollaborationPendingSend(paths.koedHome, record.key);
|
|
1830
|
+
if (sent)
|
|
1831
|
+
emitDurableSend(context.ownerId, sent, confirmedMessage);
|
|
1832
|
+
return collaborationCommandResultSchema.parse({
|
|
1833
|
+
...lastResult,
|
|
1834
|
+
data: { message: confirmedMessage }
|
|
1835
|
+
});
|
|
1836
|
+
}
|
|
1837
|
+
if (!lastResult.error.retryable) {
|
|
1838
|
+
const protectedAuthorityLost = record.thread.scope === "team" && (lastResult.error.code === "access_revoked" || lastResult.error.code === "permission_denied" || lastResult.error.code === "not_available");
|
|
1839
|
+
const failedRecord = protectedAuthorityLost ? record : updateCollaborationPendingSendState(paths.koedHome, {
|
|
1840
|
+
key: record.key,
|
|
1841
|
+
attemptCount,
|
|
1842
|
+
state: "failed",
|
|
1843
|
+
failure: lastResult.error,
|
|
1844
|
+
nextAttemptAt: null
|
|
1845
|
+
}) ?? record;
|
|
1846
|
+
const failed = durableSendFromRecord(failedRecord, ownerContext(context.ownerId).snapshot, {
|
|
1847
|
+
state: "failed",
|
|
1848
|
+
failure: lastResult.error,
|
|
1849
|
+
bodyAuthorized: !protectedAuthorityLost
|
|
1850
|
+
});
|
|
1851
|
+
if (protectedAuthorityLost) {
|
|
1852
|
+
deleteCollaborationPendingSend(paths.koedHome, record.key);
|
|
1853
|
+
}
|
|
1854
|
+
if (failed)
|
|
1855
|
+
emitDurableSend(context.ownerId, failed, null);
|
|
1856
|
+
return lastResult;
|
|
1857
|
+
}
|
|
1858
|
+
if (attemptCount >= COLLABORATION_SEND_RETRY_MAX_ATTEMPTS) {
|
|
1859
|
+
const manualRetry = updateCollaborationPendingSendState(paths.koedHome, {
|
|
1860
|
+
key: record.key,
|
|
1861
|
+
attemptCount,
|
|
1862
|
+
state: "manual_retry",
|
|
1863
|
+
failure: lastResult.error,
|
|
1864
|
+
nextAttemptAt: null
|
|
1865
|
+
}) ?? record;
|
|
1866
|
+
const projected = durableSendFromRecord(manualRetry, ownerContext(context.ownerId).snapshot);
|
|
1867
|
+
if (projected)
|
|
1868
|
+
emitDurableSend(context.ownerId, projected, null);
|
|
1869
|
+
return lastResult;
|
|
1870
|
+
}
|
|
1871
|
+
const delayMs = Math.min(Math.max(lastResult.error.retryAfterMs ?? 250 * 2 ** (attemptCount - 1), 250), 3e4);
|
|
1872
|
+
record = updateCollaborationPendingSendState(paths.koedHome, {
|
|
1873
|
+
key: record.key,
|
|
1874
|
+
attemptCount,
|
|
1875
|
+
state: "pending",
|
|
1876
|
+
failure: null,
|
|
1877
|
+
nextAttemptAt: new Date((dependencies.now?.() ?? Date.now()) + delayMs).toISOString()
|
|
1878
|
+
}) ?? record;
|
|
1879
|
+
}
|
|
1880
|
+
return lastResult ?? commandFailure(command, collaborationSafeError("offline"));
|
|
1881
|
+
})();
|
|
1882
|
+
pendingSendExecutions.set(pending.key, execution);
|
|
1883
|
+
try {
|
|
1884
|
+
return remapSendResult(command, await execution);
|
|
1885
|
+
} finally {
|
|
1886
|
+
if (pendingSendExecutions.get(pending.key) === execution) {
|
|
1887
|
+
pendingSendExecutions.delete(pending.key);
|
|
1888
|
+
}
|
|
1889
|
+
await startNextPendingSend(pending, context);
|
|
1890
|
+
}
|
|
1891
|
+
};
|
|
1892
|
+
async function startNextPendingSend(completed, context) {
|
|
1893
|
+
if (context.signal.aborted)
|
|
1894
|
+
return;
|
|
1895
|
+
const next = listCollaborationPendingSends(paths.koedHome).find((candidate) => pendingSendThreadKey(candidate) === pendingSendThreadKey(completed) && (candidate.state === "pending" || candidate.state === "manual_retry"));
|
|
1896
|
+
if (!next || next.state !== "pending")
|
|
1897
|
+
return;
|
|
1898
|
+
const retryCommand = collaborationRendererCommandSchema.parse({
|
|
1899
|
+
contractVersion: COLLABORATION_CONTRACT_VERSION,
|
|
1900
|
+
requestId: randomUUID2(),
|
|
1901
|
+
command: "collaboration.retry_message",
|
|
1902
|
+
input: {
|
|
1903
|
+
thread: next.thread,
|
|
1904
|
+
clientMessageId: next.clientMessageId,
|
|
1905
|
+
body: next.body
|
|
1906
|
+
}
|
|
1907
|
+
});
|
|
1908
|
+
if (retryCommand.command === "collaboration.retry_message") {
|
|
1909
|
+
await executePendingSend(retryCommand, next, context);
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
const persistAndExecuteSend = (command, context) => {
|
|
1913
|
+
const pendingOwnerId = durablePendingSendOwnerId();
|
|
1914
|
+
if (!pendingOwnerId) {
|
|
1915
|
+
return commandFailure(command, collaborationSafeError("not_available"));
|
|
1916
|
+
}
|
|
1917
|
+
const activeBackend = getActiveUpstreamBackend(paths);
|
|
1918
|
+
const activeBackendId = activeBackend?.id ?? null;
|
|
1919
|
+
const activeCredential = activeBackendId ? readLocalEdgeClientCredentialAuthorization(paths.koedHome, activeBackendId) : null;
|
|
1920
|
+
const backendId = command.input.thread.scope === "team" || activeBackend?.routePolicy.personalCollaboration === "enabled" && activeCredential?.operationFamilies.includes("personal_collaboration_write") ? activeBackendId : null;
|
|
1921
|
+
if (command.input.thread.scope === "team" && !backendId) {
|
|
1922
|
+
return commandFailure(command, collaborationSafeError("not_available"));
|
|
1923
|
+
}
|
|
1924
|
+
const remoteBinding = backendId ? readUpstreamEnrollmentBinding(paths, backendId) : null;
|
|
1925
|
+
if (command.input.thread.scope === "team" && !remoteBinding) {
|
|
1926
|
+
return commandFailure(command, collaborationSafeError("not_available"));
|
|
1927
|
+
}
|
|
1928
|
+
let existing;
|
|
1929
|
+
if (command.command === "collaboration.retry_message") {
|
|
1930
|
+
existing = listCollaborationPendingSends(paths.koedHome).find((record) => record.ownerId === pendingOwnerId && record.thread.scope === command.input.thread.scope && record.thread.threadId === command.input.thread.threadId && record.clientMessageId === command.input.clientMessageId);
|
|
1931
|
+
if (existing?.backendId && (existing.backendId !== backendId || existing.remotePrincipalId !== remoteBinding?.principalUserId || existing.deviceCredentialId !== remoteBinding?.deviceCredentialId)) {
|
|
1932
|
+
deleteCollaborationPendingSend(paths.koedHome, existing.key);
|
|
1933
|
+
return commandFailure(command, collaborationSafeError("access_revoked"));
|
|
1934
|
+
}
|
|
1935
|
+
if (!existing || existing.state !== "manual_retry") {
|
|
1936
|
+
return commandFailure(command, collaborationSafeError("conflict"));
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
const pending = storeCollaborationPendingSend(paths.koedHome, {
|
|
1940
|
+
ownerId: pendingOwnerId,
|
|
1941
|
+
backendId,
|
|
1942
|
+
remotePrincipalId: remoteBinding?.principalUserId ?? null,
|
|
1943
|
+
deviceCredentialId: remoteBinding?.deviceCredentialId ?? null,
|
|
1944
|
+
thread: command.input.thread,
|
|
1945
|
+
clientMessageId: command.input.clientMessageId,
|
|
1946
|
+
body: command.input.body
|
|
1947
|
+
}, { resetAttempts: command.command === "collaboration.retry_message" });
|
|
1948
|
+
const projected = durableSendFromRecord(pending, ownerContext(context.ownerId).snapshot);
|
|
1949
|
+
if (!projected) {
|
|
1950
|
+
deleteCollaborationPendingSend(paths.koedHome, pending.key);
|
|
1951
|
+
return commandFailure(command, collaborationSafeError("not_available"));
|
|
1952
|
+
}
|
|
1953
|
+
pendingSendStarts.set(command.requestId, { command, pending, context });
|
|
1954
|
+
return collaborationCommandResultSchema.parse({
|
|
1955
|
+
contractVersion: COLLABORATION_CONTRACT_VERSION,
|
|
1956
|
+
requestId: command.requestId,
|
|
1957
|
+
command: command.command,
|
|
1958
|
+
ok: true,
|
|
1959
|
+
data: { durableSend: projected }
|
|
1960
|
+
});
|
|
1961
|
+
};
|
|
1962
|
+
const resumeInterruptedSends = async (context, snapshot) => {
|
|
1963
|
+
const pendingOwnerId = durablePendingSendOwnerId();
|
|
1964
|
+
if (!pendingOwnerId)
|
|
1965
|
+
return;
|
|
1966
|
+
const activeBackendId = findBackendIdentity(paths)?.id ?? null;
|
|
1967
|
+
const binding = activeBackendId ? readUpstreamEnrollmentBinding(paths, activeBackendId) : null;
|
|
1968
|
+
const pending = listCollaborationPendingSends(paths.koedHome).filter((record) => record.ownerId === pendingOwnerId && record.state === "pending");
|
|
1969
|
+
if (activeBackendId && binding) {
|
|
1970
|
+
removeProjectTeamWorkspaceLinksForMismatchedBinding(paths, {
|
|
1971
|
+
backendId: binding.backendId,
|
|
1972
|
+
remotePrincipalId: binding.principalUserId,
|
|
1973
|
+
deviceCredentialId: binding.deviceCredentialId
|
|
1974
|
+
});
|
|
1975
|
+
}
|
|
1976
|
+
if (activeBackendId && (!binding || snapshot.navigation.teamPrincipal?.id !== binding.principalUserId)) {
|
|
1977
|
+
clearCollaborationActionGrantCustodyForBackend(paths.koedHome, activeBackendId);
|
|
1978
|
+
clearCollaborationPendingTeamSends(paths.koedHome, activeBackendId);
|
|
1979
|
+
removeProjectTeamWorkspaceLinksForBackend(paths, activeBackendId);
|
|
1980
|
+
return;
|
|
1981
|
+
}
|
|
1982
|
+
const startedThreads = /* @__PURE__ */ new Set();
|
|
1983
|
+
for (const record of pending) {
|
|
1984
|
+
if (context.signal.aborted)
|
|
1985
|
+
return;
|
|
1986
|
+
if (record.thread.scope === "team" && (!binding || snapshot.navigation.teamPrincipal?.id !== binding.principalUserId || record.backendId !== binding.backendId || record.remotePrincipalId !== binding.principalUserId || record.deviceCredentialId !== binding.deviceCredentialId)) {
|
|
1987
|
+
deleteCollaborationPendingSend(paths.koedHome, record.key);
|
|
1988
|
+
continue;
|
|
1989
|
+
}
|
|
1990
|
+
const threadKey = pendingSendThreadKey(record);
|
|
1991
|
+
if (startedThreads.has(threadKey))
|
|
1992
|
+
continue;
|
|
1993
|
+
const earliestBlocking = listCollaborationPendingSends(paths.koedHome).find((candidate) => pendingSendThreadKey(candidate) === threadKey && (candidate.state === "pending" || candidate.state === "manual_retry"));
|
|
1994
|
+
if (!earliestBlocking || earliestBlocking.state !== "pending")
|
|
1995
|
+
continue;
|
|
1996
|
+
startedThreads.add(threadKey);
|
|
1997
|
+
const command = collaborationRendererCommandSchema.parse({
|
|
1998
|
+
contractVersion: COLLABORATION_CONTRACT_VERSION,
|
|
1999
|
+
requestId: randomUUID2(),
|
|
2000
|
+
command: "collaboration.retry_message",
|
|
2001
|
+
input: {
|
|
2002
|
+
thread: earliestBlocking.thread,
|
|
2003
|
+
clientMessageId: earliestBlocking.clientMessageId,
|
|
2004
|
+
body: earliestBlocking.body
|
|
2005
|
+
}
|
|
2006
|
+
});
|
|
2007
|
+
if (command.command !== "collaboration.retry_message") {
|
|
2008
|
+
throw new Error("Collaboration retry command validation failed.");
|
|
2009
|
+
}
|
|
2010
|
+
await executePendingSend(command, earliestBlocking, context);
|
|
2011
|
+
}
|
|
2012
|
+
};
|
|
2013
|
+
const releaseOwner = (ownerId) => {
|
|
2014
|
+
const owner = ownerContexts.get(ownerId);
|
|
2015
|
+
if (!owner)
|
|
2016
|
+
return;
|
|
2017
|
+
owner.controller.abort();
|
|
2018
|
+
transport.stopOwner(ownerId);
|
|
2019
|
+
ownerContexts.delete(ownerId);
|
|
2020
|
+
};
|
|
2021
|
+
const loadSnapshot = async (requestId, context) => {
|
|
2022
|
+
const command = collaborationRendererCommandSchema.parse({
|
|
2023
|
+
contractVersion: COLLABORATION_CONTRACT_VERSION,
|
|
2024
|
+
requestId,
|
|
2025
|
+
command: "collaboration.load",
|
|
2026
|
+
input: {}
|
|
2027
|
+
});
|
|
2028
|
+
const result = await transport.request(command, context);
|
|
2029
|
+
if (!result.ok)
|
|
2030
|
+
return { ok: false, error: result.error };
|
|
2031
|
+
return result.command === "collaboration.load" ? {
|
|
2032
|
+
ok: true,
|
|
2033
|
+
snapshot: snapshotWithDurableSends(result.data.snapshot)
|
|
2034
|
+
} : { ok: false, error: collaborationSafeError("internal_error") };
|
|
2035
|
+
};
|
|
2036
|
+
const connectBackend = async (remoteUrl, sourceOwnerPrincipalId) => {
|
|
2037
|
+
const parsed = collaborationRemoteBackendUrlSchema.safeParse(remoteUrl);
|
|
2038
|
+
if (!parsed.success) {
|
|
2039
|
+
return { ok: false };
|
|
2040
|
+
}
|
|
2041
|
+
const registered = registerUpstreamBackend(paths, {
|
|
2042
|
+
url: parsed.data,
|
|
2043
|
+
displayName: "Team Backend",
|
|
2044
|
+
profile: "team_self_hosted"
|
|
2045
|
+
});
|
|
2046
|
+
if (!registered.ok || !registered.backend)
|
|
2047
|
+
return { ok: false };
|
|
2048
|
+
const refreshed = await refreshCapabilities(paths, registered.backend.id, {
|
|
2049
|
+
fetch: upstreamFetcher
|
|
2050
|
+
});
|
|
2051
|
+
if (!refreshed.ok)
|
|
2052
|
+
return { ok: false };
|
|
2053
|
+
const policy = enableCollaborationRoutePolicy(paths, registered.backend.id);
|
|
2054
|
+
if (!policy.ok)
|
|
2055
|
+
return { ok: false };
|
|
2056
|
+
const enrollment = await startEnrollment(paths, registered.backend.id, {
|
|
2057
|
+
fetch: upstreamFetcher,
|
|
2058
|
+
sourceOwnerPrincipalId
|
|
2059
|
+
});
|
|
2060
|
+
const enrollmentState = resultState(enrollment);
|
|
2061
|
+
if (enrollmentState === "pending" || enrollmentState === "approved" || enrollmentState === "exchanged") {
|
|
2062
|
+
const activated = setActiveUpstreamBackend(paths, registered.backend.id);
|
|
2063
|
+
if (!activated.ok)
|
|
2064
|
+
return { ok: false };
|
|
2065
|
+
}
|
|
2066
|
+
const backend = backendIdentityFromSummary(registered.backend);
|
|
2067
|
+
return backend ? { ok: true, backend, enrollment } : { ok: false };
|
|
2068
|
+
};
|
|
2069
|
+
const reconnectBackend = async (sourceOwnerPrincipalId) => {
|
|
2070
|
+
const backend = backendIdentityFromSummary(getActiveUpstreamBackend(paths));
|
|
2071
|
+
if (!backend)
|
|
2072
|
+
return { ok: false };
|
|
2073
|
+
if (!await ensureBackendCapabilities(backend.id)) {
|
|
2074
|
+
return { ok: false };
|
|
2075
|
+
}
|
|
2076
|
+
const policy = enableCollaborationRoutePolicy(paths, backend.id);
|
|
2077
|
+
if (!policy.ok)
|
|
2078
|
+
return { ok: false };
|
|
2079
|
+
let enrollment = await enrollmentStatus(paths, backend.id, {
|
|
2080
|
+
fetch: upstreamFetcher
|
|
2081
|
+
});
|
|
2082
|
+
let state = resultState(enrollment);
|
|
2083
|
+
if ((state === "pending" || state === "approved" || state === "exchanged") && !enrollmentSupportsCollaboration(enrollment)) {
|
|
2084
|
+
return { ok: false };
|
|
2085
|
+
}
|
|
2086
|
+
if (state === "pending" || state === "approved" || state === "exchanged") {
|
|
2087
|
+
return { ok: true, backend, enrollment };
|
|
2088
|
+
}
|
|
2089
|
+
const restarted = await connectBackend(backend.baseUrl, sourceOwnerPrincipalId);
|
|
2090
|
+
if (!restarted.ok)
|
|
2091
|
+
return { ok: false };
|
|
2092
|
+
enrollment = restarted.enrollment;
|
|
2093
|
+
state = resultState(enrollment);
|
|
2094
|
+
return state ? { ok: true, backend, enrollment } : { ok: false };
|
|
2095
|
+
};
|
|
2096
|
+
const resolveLocalOwnerPrincipalId = async (ownerId) => {
|
|
2097
|
+
const durableOwnerId = readDesktopLocalCredentialAuthorization(paths.koedHome)?.ownerUserId;
|
|
2098
|
+
if (durableOwnerId && uuidPattern.test(durableOwnerId)) {
|
|
2099
|
+
return durableOwnerId;
|
|
2100
|
+
}
|
|
2101
|
+
const owner = ownerContext(ownerId);
|
|
2102
|
+
const cachedOwnerId = owner.snapshot?.navigation.personalOwner.id;
|
|
2103
|
+
if (cachedOwnerId && uuidPattern.test(cachedOwnerId)) {
|
|
2104
|
+
return cachedOwnerId;
|
|
2105
|
+
}
|
|
2106
|
+
const loaded = await loadSnapshot(randomUUID2(), transportContextForOwner(ownerId));
|
|
2107
|
+
if (!loaded.ok)
|
|
2108
|
+
return null;
|
|
2109
|
+
owner.snapshot = loaded.snapshot;
|
|
2110
|
+
const loadedOwnerId = loaded.snapshot.navigation.personalOwner.id;
|
|
2111
|
+
return uuidPattern.test(loadedOwnerId) ? loadedOwnerId : null;
|
|
2112
|
+
};
|
|
2113
|
+
const handleLifecycleCommand = async (command, ownerId) => {
|
|
2114
|
+
if (command.command === "collaboration.disconnect_backend") {
|
|
2115
|
+
stopEnrollmentMonitors();
|
|
2116
|
+
clearCapabilityRefreshTimers();
|
|
2117
|
+
const backend = backendIdentityFromSummary(getActiveUpstreamBackend(paths));
|
|
2118
|
+
if (backend) {
|
|
2119
|
+
const disconnected = await disconnectBackend(backend);
|
|
2120
|
+
if (!disconnected.revoked) {
|
|
2121
|
+
return lifecycleFailure(command, collaborationSafeError("temporarily_unavailable"));
|
|
2122
|
+
}
|
|
2123
|
+
if (!disconnected.cleanupComplete) {
|
|
2124
|
+
scheduleDisconnectCleanupRetry();
|
|
2125
|
+
for (const owner of ownerContexts.values()) {
|
|
2126
|
+
sendOwnerEvent(owner.ownerId, createConnectionEvent("disconnected", null));
|
|
2127
|
+
}
|
|
2128
|
+
return lifecycleFailure(command, collaborationSafeError("temporarily_unavailable"));
|
|
2129
|
+
}
|
|
2130
|
+
} else {
|
|
2131
|
+
resetTransport();
|
|
2132
|
+
clearOwnerTeamRuntimeState();
|
|
2133
|
+
}
|
|
2134
|
+
for (const owner of ownerContexts.values()) {
|
|
2135
|
+
sendOwnerEvent(owner.ownerId, createConnectionEvent("disconnected", null));
|
|
2136
|
+
}
|
|
2137
|
+
const loaded2 = await loadSnapshot(command.requestId, transportContextForOwner(ownerId));
|
|
2138
|
+
if (!loaded2.ok)
|
|
2139
|
+
return lifecycleFailure(command, loaded2.error);
|
|
2140
|
+
return collaborationCommandResultSchema.parse({
|
|
2141
|
+
contractVersion: COLLABORATION_CONTRACT_VERSION,
|
|
2142
|
+
requestId: command.requestId,
|
|
2143
|
+
command: command.command,
|
|
2144
|
+
ok: true,
|
|
2145
|
+
data: {
|
|
2146
|
+
snapshot: {
|
|
2147
|
+
...loaded2.snapshot,
|
|
2148
|
+
connection: {
|
|
2149
|
+
state: "disconnected",
|
|
2150
|
+
backendId: null,
|
|
2151
|
+
connectedAt: null,
|
|
2152
|
+
retryAt: null,
|
|
2153
|
+
reconnectAttempt: 0,
|
|
2154
|
+
protocolVersion: COLLABORATION_CONTRACT_VERSION
|
|
2155
|
+
},
|
|
2156
|
+
navigation: {
|
|
2157
|
+
...loaded2.snapshot.navigation,
|
|
2158
|
+
teamPrincipal: null,
|
|
2159
|
+
teams: []
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
});
|
|
2164
|
+
}
|
|
2165
|
+
const sourceOwnerPrincipalId = await resolveLocalOwnerPrincipalId(ownerId);
|
|
2166
|
+
if (!sourceOwnerPrincipalId) {
|
|
2167
|
+
return lifecycleFailure(command, collaborationSafeError("temporarily_unavailable"));
|
|
2168
|
+
}
|
|
2169
|
+
let connectResult;
|
|
2170
|
+
await retryRevokedBackendDurableCleanup();
|
|
2171
|
+
if (upstreamDisconnectCleanupPending(paths)) {
|
|
2172
|
+
return lifecycleFailure(command, collaborationSafeError("temporarily_unavailable"));
|
|
2173
|
+
}
|
|
2174
|
+
if (command.command === "collaboration.connect_backend") {
|
|
2175
|
+
const parsedTarget = collaborationRemoteBackendUrlSchema.safeParse(command.input.remoteUrl);
|
|
2176
|
+
if (!parsedTarget.success) {
|
|
2177
|
+
return lifecycleFailure(command, collaborationSafeError("invalid_input"));
|
|
2178
|
+
}
|
|
2179
|
+
const activeBackend = backendIdentityFromSummary(getActiveUpstreamBackend(paths));
|
|
2180
|
+
const normalizedTarget = parsedTarget.data.replace(/\/+$/, "");
|
|
2181
|
+
const sameBackend = activeBackend?.baseUrl.replace(/\/+$/, "") === normalizedTarget;
|
|
2182
|
+
if (activeBackend && !sameBackend) {
|
|
2183
|
+
stopEnrollmentMonitors();
|
|
2184
|
+
clearCapabilityRefreshTimers();
|
|
2185
|
+
const disconnected = await disconnectBackend(activeBackend);
|
|
2186
|
+
if (!disconnected.revoked) {
|
|
2187
|
+
return lifecycleFailure(command, collaborationSafeError("temporarily_unavailable"));
|
|
2188
|
+
}
|
|
2189
|
+
if (!disconnected.cleanupComplete) {
|
|
2190
|
+
scheduleDisconnectCleanupRetry();
|
|
2191
|
+
return lifecycleFailure(command, collaborationSafeError("temporarily_unavailable"));
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
connectResult = sameBackend ? await reconnectBackend(sourceOwnerPrincipalId) : await connectBackend(parsedTarget.data, sourceOwnerPrincipalId);
|
|
2195
|
+
} else {
|
|
2196
|
+
connectResult = await reconnectBackend(sourceOwnerPrincipalId);
|
|
2197
|
+
}
|
|
2198
|
+
if (!connectResult.ok) {
|
|
2199
|
+
return lifecycleFailure(command, collaborationSafeError("not_available"));
|
|
2200
|
+
}
|
|
2201
|
+
const enrollment = connectResult.enrollment;
|
|
2202
|
+
const state = resultState(enrollment);
|
|
2203
|
+
if (!enrollmentSupportsCollaboration(enrollment)) {
|
|
2204
|
+
return lifecycleFailure(command, collaborationSafeError("not_available"));
|
|
2205
|
+
}
|
|
2206
|
+
if (state === "pending" || state === "approved") {
|
|
2207
|
+
const activationUrl = browserActivationUrlFromResult(enrollment);
|
|
2208
|
+
if (activationUrl) {
|
|
2209
|
+
dependencies.onBrowserOpenRequest?.({
|
|
2210
|
+
ownerId,
|
|
2211
|
+
url: activationUrl
|
|
2212
|
+
});
|
|
2213
|
+
}
|
|
2214
|
+
sendOwnerEvent(ownerId, createConnectionEvent(command.command === "collaboration.connect_backend" ? "connecting" : "reconnecting", connectResult.backend.id));
|
|
2215
|
+
const enrollmentId = enrollment.enrollment?.requestId;
|
|
2216
|
+
if (enrollmentId) {
|
|
2217
|
+
const monitorKey = `${connectResult.backend.id}:${enrollmentId}`;
|
|
2218
|
+
if (!enrollmentMonitors.has(monitorKey)) {
|
|
2219
|
+
const monitorController = new AbortController();
|
|
2220
|
+
enrollmentMonitors.set(monitorKey, monitorController);
|
|
2221
|
+
void (async () => {
|
|
2222
|
+
const expiresAt = enrollment.enrollment?.expiresAt ? Date.parse(enrollment.enrollment.expiresAt) : Number.NaN;
|
|
2223
|
+
try {
|
|
2224
|
+
while (!monitorController.signal.aborted) {
|
|
2225
|
+
if (Number.isFinite(expiresAt) && (dependencies.now?.() ?? Date.now()) >= expiresAt) {
|
|
2226
|
+
broadcastEvent(createConnectionEvent("unavailable", connectResult.backend.id, collaborationSafeError("not_available")));
|
|
2227
|
+
return;
|
|
2228
|
+
}
|
|
2229
|
+
await sleep(1e3, monitorController.signal);
|
|
2230
|
+
if (monitorController.signal.aborted)
|
|
2231
|
+
return;
|
|
2232
|
+
const current = await enrollmentStatus(paths, connectResult.backend.id, { fetch: upstreamFetcher });
|
|
2233
|
+
if (current.enrollment?.requestId !== enrollmentId)
|
|
2234
|
+
return;
|
|
2235
|
+
if (current.state === "pending" || current.state === "approved") {
|
|
2236
|
+
continue;
|
|
2237
|
+
}
|
|
2238
|
+
if (current.state === "exchanged" && findBackendIdentity(paths)?.id === connectResult.backend.id) {
|
|
2239
|
+
resetTransport();
|
|
2240
|
+
broadcastEvent(createConnectionEvent("live", connectResult.backend.id, null, dependencies.now?.() ?? Date.now()));
|
|
2241
|
+
return;
|
|
2242
|
+
}
|
|
2243
|
+
broadcastEvent(createConnectionEvent("unavailable", connectResult.backend.id, collaborationSafeError("not_available")));
|
|
2244
|
+
return;
|
|
2245
|
+
}
|
|
2246
|
+
} catch {
|
|
2247
|
+
if (!monitorController.signal.aborted) {
|
|
2248
|
+
broadcastEvent(createConnectionEvent("unavailable", connectResult.backend.id, collaborationSafeError("temporarily_unavailable")));
|
|
2249
|
+
}
|
|
2250
|
+
} finally {
|
|
2251
|
+
enrollmentMonitors.delete(monitorKey);
|
|
2252
|
+
}
|
|
2253
|
+
})();
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
return lifecycleFailure(command, collaborationSafeError("temporarily_unavailable"));
|
|
2257
|
+
}
|
|
2258
|
+
if (state !== "exchanged") {
|
|
2259
|
+
return lifecycleFailure(command, collaborationSafeError("not_available"));
|
|
2260
|
+
}
|
|
2261
|
+
const loaded = await loadSnapshot(command.requestId, transportContextForOwner(ownerId));
|
|
2262
|
+
if (!loaded.ok)
|
|
2263
|
+
return lifecycleFailure(command, loaded.error);
|
|
2264
|
+
return collaborationCommandResultSchema.parse({
|
|
2265
|
+
contractVersion: COLLABORATION_CONTRACT_VERSION,
|
|
2266
|
+
requestId: command.requestId,
|
|
2267
|
+
command: command.command,
|
|
2268
|
+
ok: true,
|
|
2269
|
+
data: command.command === "collaboration.connect_backend" ? {
|
|
2270
|
+
backend: connectResult.backend,
|
|
2271
|
+
snapshot: loaded.snapshot
|
|
2272
|
+
} : {
|
|
2273
|
+
snapshot: loaded.snapshot
|
|
2274
|
+
}
|
|
2275
|
+
});
|
|
2276
|
+
};
|
|
2277
|
+
const sendCommandResult = (envelopeId, ownerId, result) => {
|
|
2278
|
+
sessionSend({
|
|
2279
|
+
protocolVersion: DESKTOP_COLLABORATION_BROKER_PROTOCOL_VERSION,
|
|
2280
|
+
contractVersion: COLLABORATION_CONTRACT_VERSION,
|
|
2281
|
+
sessionToken: environment.KOED_DESKTOP_COLLABORATION_BROKER_SESSION_TOKEN,
|
|
2282
|
+
type: "command_result",
|
|
2283
|
+
envelopeId,
|
|
2284
|
+
ownerId,
|
|
2285
|
+
result
|
|
2286
|
+
});
|
|
2287
|
+
};
|
|
2288
|
+
const sendError = (error) => {
|
|
2289
|
+
sessionSend({
|
|
2290
|
+
protocolVersion: DESKTOP_COLLABORATION_BROKER_PROTOCOL_VERSION,
|
|
2291
|
+
contractVersion: COLLABORATION_CONTRACT_VERSION,
|
|
2292
|
+
sessionToken: environment.KOED_DESKTOP_COLLABORATION_BROKER_SESSION_TOKEN,
|
|
2293
|
+
type: "error",
|
|
2294
|
+
...error
|
|
2295
|
+
});
|
|
2296
|
+
};
|
|
2297
|
+
const rejectInvalidMessage = (value, message = "Desktop collaboration broker rejected an invalid message.") => {
|
|
2298
|
+
sendError({
|
|
2299
|
+
envelopeId: value && typeof value === "object" && typeof value.envelopeId === "string" ? value.envelopeId : null,
|
|
2300
|
+
ownerId: value && typeof value === "object" && typeof value.ownerId === "string" ? value.ownerId : null,
|
|
2301
|
+
code: "invalid_message",
|
|
2302
|
+
message
|
|
2303
|
+
});
|
|
2304
|
+
};
|
|
2305
|
+
const handleMessage = async (value) => {
|
|
2306
|
+
let claimedRequest;
|
|
2307
|
+
try {
|
|
2308
|
+
if (measureDesktopCollaborationBrokerMessageBytes(value) > DESKTOP_COLLABORATION_BROKER_MAX_MESSAGE_BYTES) {
|
|
2309
|
+
throw new Error("Desktop collaboration broker message exceeded its byte limit.");
|
|
2310
|
+
}
|
|
2311
|
+
const message = desktopCollaborationBrokerParentMessageSchema.parse(value);
|
|
2312
|
+
if (message.sessionToken !== environment.KOED_DESKTOP_COLLABORATION_BROKER_SESSION_TOKEN) {
|
|
2313
|
+
rejectInvalidMessage(message, "Desktop collaboration broker session token is invalid.");
|
|
2314
|
+
return;
|
|
2315
|
+
}
|
|
2316
|
+
if (message.type === "release_owner") {
|
|
2317
|
+
releaseOwner(message.ownerId);
|
|
2318
|
+
sessionSend({
|
|
2319
|
+
protocolVersion: DESKTOP_COLLABORATION_BROKER_PROTOCOL_VERSION,
|
|
2320
|
+
contractVersion: COLLABORATION_CONTRACT_VERSION,
|
|
2321
|
+
sessionToken: environment.KOED_DESKTOP_COLLABORATION_BROKER_SESSION_TOKEN,
|
|
2322
|
+
type: "owner_released",
|
|
2323
|
+
envelopeId: message.envelopeId,
|
|
2324
|
+
ownerId: message.ownerId
|
|
2325
|
+
});
|
|
2326
|
+
return;
|
|
2327
|
+
}
|
|
2328
|
+
if (message.type === "shutdown") {
|
|
2329
|
+
stopEnrollmentMonitors();
|
|
2330
|
+
transport.stop();
|
|
2331
|
+
for (const ownerId of [...ownerContexts.keys()]) {
|
|
2332
|
+
releaseOwner(ownerId);
|
|
2333
|
+
}
|
|
2334
|
+
await ownedUpstreamFetcher?.close();
|
|
2335
|
+
sessionSend({
|
|
2336
|
+
protocolVersion: DESKTOP_COLLABORATION_BROKER_PROTOCOL_VERSION,
|
|
2337
|
+
contractVersion: COLLABORATION_CONTRACT_VERSION,
|
|
2338
|
+
sessionToken: environment.KOED_DESKTOP_COLLABORATION_BROKER_SESSION_TOKEN,
|
|
2339
|
+
type: "shutdown_ack",
|
|
2340
|
+
envelopeId: message.envelopeId
|
|
2341
|
+
});
|
|
2342
|
+
return;
|
|
2343
|
+
}
|
|
2344
|
+
const owner = ownerContext(message.ownerId);
|
|
2345
|
+
const command = collaborationRendererCommandSchema.parse(message.command);
|
|
2346
|
+
const claim = claimOwnerRequest(owner, command);
|
|
2347
|
+
if (claim.state === "conflict") {
|
|
2348
|
+
sendCommandResult(message.envelopeId, message.ownerId, commandFailure(command, collaborationSafeError("conflict")));
|
|
2349
|
+
return;
|
|
2350
|
+
}
|
|
2351
|
+
if (claim.state === "cached") {
|
|
2352
|
+
sendCommandResult(message.envelopeId, message.ownerId, claim.result);
|
|
2353
|
+
return;
|
|
2354
|
+
}
|
|
2355
|
+
claimedRequest = { owner, requestId: command.requestId };
|
|
2356
|
+
const context = {
|
|
2357
|
+
ownerId: message.ownerId,
|
|
2358
|
+
signal: owner.controller.signal,
|
|
2359
|
+
emitCollaborationEvent: (event) => sendOwnerEvent(message.ownerId, event)
|
|
2360
|
+
};
|
|
2361
|
+
if (command.command === "collaboration.load") {
|
|
2362
|
+
await retryRevokedBackendDurableCleanup();
|
|
2363
|
+
await ensureBackendCapabilities();
|
|
2364
|
+
}
|
|
2365
|
+
let result = command.command === "collaboration.connect_backend" || command.command === "collaboration.reconnect_backend" || command.command === "collaboration.disconnect_backend" ? await runLifecycleExclusive(() => handleLifecycleCommand(command, message.ownerId)) : command.command === "collaboration.send_message" || command.command === "collaboration.retry_message" ? persistAndExecuteSend(command, context) : await transport.request(command, context);
|
|
2366
|
+
if (result.ok) {
|
|
2367
|
+
const resultData = result.data;
|
|
2368
|
+
if (resultData.snapshot) {
|
|
2369
|
+
const projectedSnapshot = snapshotWithDurableSends(resultData.snapshot);
|
|
2370
|
+
result = collaborationCommandResultSchema.parse({
|
|
2371
|
+
...result,
|
|
2372
|
+
data: { ...result.data, snapshot: projectedSnapshot }
|
|
2373
|
+
});
|
|
2374
|
+
owner.snapshot = projectedSnapshot;
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
const shouldResumeInterruptedSends = result.ok && result.command === "collaboration.load" && !upstreamDisconnectCleanupPending(paths);
|
|
2378
|
+
if (result.ok && result.command === "collaboration.request_action_grant" && result.data.status.state === "pending" && result.data.status.activationUrl) {
|
|
2379
|
+
dependencies.onBrowserOpenRequest?.({
|
|
2380
|
+
ownerId: context.ownerId,
|
|
2381
|
+
url: result.data.status.activationUrl
|
|
2382
|
+
});
|
|
2383
|
+
}
|
|
2384
|
+
finishOwnerRequest(owner, command.requestId, result);
|
|
2385
|
+
claimedRequest = void 0;
|
|
2386
|
+
sendCommandResult(message.envelopeId, message.ownerId, result);
|
|
2387
|
+
const pendingSendStart = pendingSendStarts.get(command.requestId);
|
|
2388
|
+
if (pendingSendStart) {
|
|
2389
|
+
pendingSendStarts.delete(command.requestId);
|
|
2390
|
+
await executePendingSend(pendingSendStart.command, pendingSendStart.pending, pendingSendStart.context);
|
|
2391
|
+
}
|
|
2392
|
+
if (shouldResumeInterruptedSends && result.ok && result.command === "collaboration.load") {
|
|
2393
|
+
await resumeInterruptedSends(context, result.data.snapshot);
|
|
2394
|
+
}
|
|
2395
|
+
} catch (error) {
|
|
2396
|
+
if (claimedRequest) {
|
|
2397
|
+
failOwnerRequest(claimedRequest.owner, claimedRequest.requestId);
|
|
2398
|
+
}
|
|
2399
|
+
console.error(JSON.stringify({
|
|
2400
|
+
event: "desktop.collaboration.command.failed",
|
|
2401
|
+
errorName: error instanceof Error ? error.name : "UnknownError"
|
|
2402
|
+
}));
|
|
2403
|
+
rejectInvalidMessage(value);
|
|
2404
|
+
}
|
|
2405
|
+
};
|
|
2406
|
+
const sendReady = () => {
|
|
2407
|
+
sessionSend({
|
|
2408
|
+
protocolVersion: DESKTOP_COLLABORATION_BROKER_PROTOCOL_VERSION,
|
|
2409
|
+
contractVersion: COLLABORATION_CONTRACT_VERSION,
|
|
2410
|
+
sessionToken: environment.KOED_DESKTOP_COLLABORATION_BROKER_SESSION_TOKEN,
|
|
2411
|
+
type: "ready",
|
|
2412
|
+
brokerPid: process.pid
|
|
2413
|
+
});
|
|
2414
|
+
};
|
|
2415
|
+
scheduleDisconnectCleanupRetry();
|
|
2416
|
+
return {
|
|
2417
|
+
handleMessage,
|
|
2418
|
+
releaseOwner,
|
|
2419
|
+
shutdown: async () => {
|
|
2420
|
+
stopEnrollmentMonitors();
|
|
2421
|
+
clearCapabilityRefreshTimers();
|
|
2422
|
+
if (disconnectCleanupTimer)
|
|
2423
|
+
clearTimeout(disconnectCleanupTimer);
|
|
2424
|
+
disconnectCleanupTimer = null;
|
|
2425
|
+
transport.stop();
|
|
2426
|
+
for (const ownerId of [...ownerContexts.keys()]) {
|
|
2427
|
+
releaseOwner(ownerId);
|
|
2428
|
+
}
|
|
2429
|
+
await ownedUpstreamFetcher?.close();
|
|
2430
|
+
},
|
|
2431
|
+
sendReady
|
|
2432
|
+
};
|
|
2433
|
+
};
|
|
2434
|
+
var runDesktopCollaborationBrokerProcess = () => {
|
|
2435
|
+
const sessionToken = process.env.KOED_DESKTOP_COLLABORATION_BROKER_SESSION_TOKEN?.trim();
|
|
2436
|
+
if (!sessionToken) {
|
|
2437
|
+
throw new Error("KOED_DESKTOP_COLLABORATION_BROKER_SESSION_TOKEN is required.");
|
|
2438
|
+
}
|
|
2439
|
+
if (typeof process.send !== "function") {
|
|
2440
|
+
throw new Error("Desktop collaboration broker requires an inherited Node IPC channel.");
|
|
2441
|
+
}
|
|
2442
|
+
const broker = createDesktopCollaborationBroker({
|
|
2443
|
+
sendMessage: (message) => {
|
|
2444
|
+
process.send?.(message);
|
|
2445
|
+
},
|
|
2446
|
+
onBrowserOpenRequest: ({ ownerId, url }) => {
|
|
2447
|
+
process.send?.(desktopCollaborationBrokerChildMessageSchema.parse({
|
|
2448
|
+
protocolVersion: DESKTOP_COLLABORATION_BROKER_PROTOCOL_VERSION,
|
|
2449
|
+
contractVersion: COLLABORATION_CONTRACT_VERSION,
|
|
2450
|
+
sessionToken,
|
|
2451
|
+
type: "open_external",
|
|
2452
|
+
envelopeId: randomUUID2(),
|
|
2453
|
+
ownerId,
|
|
2454
|
+
url
|
|
2455
|
+
}));
|
|
2456
|
+
}
|
|
2457
|
+
});
|
|
2458
|
+
process.on("message", (value) => {
|
|
2459
|
+
void broker.handleMessage(value);
|
|
2460
|
+
});
|
|
2461
|
+
process.once("disconnect", () => {
|
|
2462
|
+
void broker.shutdown().finally(() => process.exit(0));
|
|
2463
|
+
});
|
|
2464
|
+
broker.sendReady();
|
|
2465
|
+
return Promise.resolve();
|
|
2466
|
+
};
|
|
2467
|
+
export {
|
|
2468
|
+
createDesktopCollaborationBroker,
|
|
2469
|
+
runDesktopCollaborationBrokerProcess
|
|
2470
|
+
};
|