webloom-framework 0.4.1 → 0.4.2
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/README.md +1 -1
- package/dist/advanced.d.ts +20 -8
- package/dist/advanced.js +4 -4
- package/dist/advanced.js.map +1 -1
- package/dist/{chunk-HJKPKWI7.js → chunk-CKML74MG.js} +19 -4
- package/dist/chunk-CKML74MG.js.map +1 -0
- package/dist/{chunk-SX46RHDI.js → chunk-RRNUE457.js} +103 -23
- package/dist/chunk-RRNUE457.js.map +1 -0
- package/dist/{chunk-ANA6GBEI.js → chunk-XVD7ALPV.js} +461 -46
- package/dist/chunk-XVD7ALPV.js.map +1 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.js +4 -4
- package/dist/{messageBus-CtrwkjrO.d.ts → messageBus-BDtkjs6y.d.ts} +1 -1
- package/dist/messagePortServiceTransport-DyNmdgoa.d.ts +150 -0
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{runtimeTypes-DquUCHz-.d.ts → runtimeTypes-CleHMFaq.d.ts} +269 -3
- package/dist/{sharedWorkerHost-KI7TIGdX.d.ts → sharedWorkerHost-BNMUW-8v.d.ts} +35 -2
- package/dist/testing.d.ts +6 -6
- package/dist/testing.js +4 -4
- package/dist/{windowRuntime-BKkLPsAS.d.ts → windowRuntime-BGl_jfbK.d.ts} +2 -2
- package/docs/api.md +1 -1
- package/docs/proposals/webloom-v4/verification.md +26 -11
- package/package.json +1 -1
- package/dist/chunk-ANA6GBEI.js.map +0 -1
- package/dist/chunk-HJKPKWI7.js.map +0 -1
- package/dist/chunk-SX46RHDI.js.map +0 -1
- package/dist/messagePortServiceTransport-BYprNvQY.d.ts +0 -264
|
@@ -1,4 +1,171 @@
|
|
|
1
|
-
import { createRuntimeMessageCodec, normalizeRuntimeLimits, createRuntimeBudget, WebLoomError, validateTransferList, DEFAULT_RUNTIME_LIMITS, capabilityKey, createReceivePortLedger, RUNTIME_CALL_TYPE, RUNTIME_CANCEL_TYPE, RUNTIME_CREDIT_TYPE, RUNTIME_SNAPSHOT_TYPE, RUNTIME_ERROR_TYPE, RUNTIME_RESULT_TYPE, RUNTIME_ERROR_MESSAGE_TYPE, RUNTIME_NEXT_TYPE,
|
|
1
|
+
import { createRuntimeMessageCodec, normalizeRuntimeLimits, createRuntimeBudget, WebLoomError, validateTransferList, DEFAULT_RUNTIME_LIMITS, capabilityKey, RUNTIME_PROTOCOL_VERSION, RUNTIME_CLOSE_TYPE, createReceivePortLedger, RUNTIME_CLOSE_ACK_TYPE, RUNTIME_CALL_TYPE, RUNTIME_CANCEL_TYPE, RUNTIME_CREDIT_TYPE, RUNTIME_SNAPSHOT_TYPE, RUNTIME_ERROR_TYPE, RUNTIME_RESULT_TYPE, RUNTIME_ERROR_MESSAGE_TYPE, RUNTIME_NEXT_TYPE, validateDto, cloneFrozenAttributes, hostForWindowApp, RuntimeUnavailableError, validateRawDto, validateTransferListWithStats, assertReceivedPortSet } from './chunk-CKML74MG.js';
|
|
2
|
+
|
|
3
|
+
// src/runtime/runtimeSession.ts
|
|
4
|
+
var DEFAULT_RUNTIME_DRAIN_TIMEOUT_MS = 2e3;
|
|
5
|
+
function validText(value) {
|
|
6
|
+
return typeof value === "string" && value.length > 0 && value.length <= 256;
|
|
7
|
+
}
|
|
8
|
+
function isRuntimeEndpointBinding(value) {
|
|
9
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
10
|
+
const candidate = value;
|
|
11
|
+
return validText(candidate.runtimeInstanceId) && validText(candidate.connectionId);
|
|
12
|
+
}
|
|
13
|
+
function sameRuntimeEndpointBinding(left, right) {
|
|
14
|
+
return left !== void 0 && right !== void 0 && left.runtimeInstanceId === right.runtimeInstanceId && left.connectionId === right.connectionId;
|
|
15
|
+
}
|
|
16
|
+
function copyBinding(value) {
|
|
17
|
+
return Object.freeze({ runtimeInstanceId: value.runtimeInstanceId, connectionId: value.connectionId });
|
|
18
|
+
}
|
|
19
|
+
function createRuntimeEndpointBinding(runtimeInstanceId) {
|
|
20
|
+
let connectionId;
|
|
21
|
+
try {
|
|
22
|
+
connectionId = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? `connection:${crypto.randomUUID()}` : `connection:${Date.now().toString(36)}:${Math.random().toString(36).slice(2)}`;
|
|
23
|
+
} catch {
|
|
24
|
+
connectionId = `connection:${Date.now().toString(36)}:${Math.random().toString(36).slice(2)}`;
|
|
25
|
+
}
|
|
26
|
+
if (!validText(runtimeInstanceId)) throw new TypeError("Runtime endpoint requires a valid runtimeInstanceId");
|
|
27
|
+
return copyBinding({ runtimeInstanceId, connectionId });
|
|
28
|
+
}
|
|
29
|
+
function createRuntimeEndpointSession(binding, options = {}) {
|
|
30
|
+
if (!isRuntimeEndpointBinding(binding)) throw new TypeError("Runtime endpoint binding is invalid");
|
|
31
|
+
const localBinding = copyBinding(binding);
|
|
32
|
+
const beginCloseListeners = /* @__PURE__ */ new Set();
|
|
33
|
+
const closedListeners = /* @__PURE__ */ new Set();
|
|
34
|
+
const participants = /* @__PURE__ */ new Set();
|
|
35
|
+
const defaultTimeout = options.defaultDrainTimeoutMs ?? DEFAULT_RUNTIME_DRAIN_TIMEOUT_MS;
|
|
36
|
+
if (!Number.isFinite(defaultTimeout) || defaultTimeout < 1 || defaultTimeout > 3e5) {
|
|
37
|
+
throw new TypeError("defaultDrainTimeoutMs must be a finite number from 1 to 300000");
|
|
38
|
+
}
|
|
39
|
+
let state = "active";
|
|
40
|
+
let remoteBindingValue;
|
|
41
|
+
let closeReasonValue;
|
|
42
|
+
let drainPromise;
|
|
43
|
+
const session = {
|
|
44
|
+
binding: localBinding,
|
|
45
|
+
maxDrainTimeoutMs: defaultTimeout,
|
|
46
|
+
get remoteBinding() {
|
|
47
|
+
return remoteBindingValue;
|
|
48
|
+
},
|
|
49
|
+
get state() {
|
|
50
|
+
return state;
|
|
51
|
+
},
|
|
52
|
+
get closeReason() {
|
|
53
|
+
return closeReasonValue;
|
|
54
|
+
},
|
|
55
|
+
acceptRemoteBinding(value) {
|
|
56
|
+
if (!isRuntimeEndpointBinding(value) || state === "closed") return false;
|
|
57
|
+
const next = copyBinding(value);
|
|
58
|
+
if (remoteBindingValue === void 0) {
|
|
59
|
+
remoteBindingValue = next;
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
return sameRuntimeEndpointBinding(remoteBindingValue, next);
|
|
63
|
+
},
|
|
64
|
+
beginClose(reason = "Runtime endpoint closing") {
|
|
65
|
+
if (state !== "active") return;
|
|
66
|
+
state = "closing";
|
|
67
|
+
closeReasonValue = reason.slice(0, 256);
|
|
68
|
+
for (const listener of [...beginCloseListeners]) {
|
|
69
|
+
try {
|
|
70
|
+
listener(closeReasonValue);
|
|
71
|
+
} catch {
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
drain(timeoutMs2 = defaultTimeout) {
|
|
76
|
+
if (!Number.isFinite(timeoutMs2) || timeoutMs2 < 1 || timeoutMs2 > 3e5) {
|
|
77
|
+
return Promise.reject(new TypeError("drain timeoutMs must be a finite number from 1 to 300000"));
|
|
78
|
+
}
|
|
79
|
+
if (drainPromise) return drainPromise;
|
|
80
|
+
const currentParticipants = [...participants];
|
|
81
|
+
if (state === "closed") {
|
|
82
|
+
const pendingExecutions = currentParticipants.reduce((total, participant) => {
|
|
83
|
+
try {
|
|
84
|
+
return total + Math.max(0, participant.pending());
|
|
85
|
+
} catch {
|
|
86
|
+
return total;
|
|
87
|
+
}
|
|
88
|
+
}, 0);
|
|
89
|
+
const result = {
|
|
90
|
+
state,
|
|
91
|
+
drained: pendingExecutions === 0,
|
|
92
|
+
timedOut: pendingExecutions !== 0,
|
|
93
|
+
pendingExecutions
|
|
94
|
+
};
|
|
95
|
+
drainPromise = Promise.resolve(result);
|
|
96
|
+
return drainPromise;
|
|
97
|
+
}
|
|
98
|
+
if (state === "active") session.beginClose("Runtime endpoint drain requested");
|
|
99
|
+
drainPromise = new Promise((resolve) => {
|
|
100
|
+
let settled = false;
|
|
101
|
+
const settle = (timedOut, failed) => {
|
|
102
|
+
if (settled) return;
|
|
103
|
+
settled = true;
|
|
104
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
105
|
+
const pendingExecutions = currentParticipants.reduce((total, participant) => {
|
|
106
|
+
try {
|
|
107
|
+
return total + Math.max(0, participant.pending());
|
|
108
|
+
} catch {
|
|
109
|
+
return total;
|
|
110
|
+
}
|
|
111
|
+
}, 0);
|
|
112
|
+
resolve({ state, drained: !timedOut && !failed && pendingExecutions === 0, timedOut, pendingExecutions });
|
|
113
|
+
};
|
|
114
|
+
const effectiveTimeout = Math.min(timeoutMs2, defaultTimeout);
|
|
115
|
+
const timer = setTimeout(() => settle(true, false), effectiveTimeout);
|
|
116
|
+
void Promise.allSettled(currentParticipants.map((participant) => {
|
|
117
|
+
try {
|
|
118
|
+
return participant.drain();
|
|
119
|
+
} catch (error) {
|
|
120
|
+
return Promise.reject(error);
|
|
121
|
+
}
|
|
122
|
+
})).then((results) => settle(false, results.some((result) => result.status === "rejected")));
|
|
123
|
+
});
|
|
124
|
+
return drainPromise;
|
|
125
|
+
},
|
|
126
|
+
close() {
|
|
127
|
+
if (state === "closed") return;
|
|
128
|
+
if (state === "active") session.beginClose("Runtime endpoint physically closed");
|
|
129
|
+
state = "closed";
|
|
130
|
+
for (const listener of [...closedListeners]) {
|
|
131
|
+
try {
|
|
132
|
+
listener();
|
|
133
|
+
} catch {
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
beginCloseListeners.clear();
|
|
137
|
+
closedListeners.clear();
|
|
138
|
+
},
|
|
139
|
+
onBeginClose(listener) {
|
|
140
|
+
if (state !== "active") {
|
|
141
|
+
try {
|
|
142
|
+
listener(closeReasonValue ?? "Runtime endpoint closing");
|
|
143
|
+
} catch {
|
|
144
|
+
}
|
|
145
|
+
return () => void 0;
|
|
146
|
+
}
|
|
147
|
+
beginCloseListeners.add(listener);
|
|
148
|
+
return () => beginCloseListeners.delete(listener);
|
|
149
|
+
},
|
|
150
|
+
onClosed(listener) {
|
|
151
|
+
if (state === "closed") {
|
|
152
|
+
try {
|
|
153
|
+
listener();
|
|
154
|
+
} catch {
|
|
155
|
+
}
|
|
156
|
+
return () => void 0;
|
|
157
|
+
}
|
|
158
|
+
closedListeners.add(listener);
|
|
159
|
+
return () => closedListeners.delete(listener);
|
|
160
|
+
},
|
|
161
|
+
registerDrainParticipant(participant) {
|
|
162
|
+
if (state === "closed") return () => void 0;
|
|
163
|
+
participants.add(participant);
|
|
164
|
+
return () => participants.delete(participant);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
return session;
|
|
168
|
+
}
|
|
2
169
|
|
|
3
170
|
// src/transport/serviceBridge.ts
|
|
4
171
|
function id(prefix) {
|
|
@@ -73,11 +240,15 @@ function createCapabilityBridge(options) {
|
|
|
73
240
|
const codec = createRuntimeMessageCodec();
|
|
74
241
|
const limits = normalizeRuntimeLimits(options.limits);
|
|
75
242
|
const budget = options.budget ?? createRuntimeBudget(limits);
|
|
243
|
+
const session = options.session ?? createRuntimeEndpointSession(options.binding ?? createRuntimeEndpointBinding(`bridge:${id("runtime")}`), { defaultDrainTimeoutMs: options.drainTimeoutMs });
|
|
244
|
+
if (options.binding && !sameRuntimeEndpointBinding(options.binding, session.binding)) throw new TypeError("Capability bridge binding disagrees with endpoint session");
|
|
245
|
+
const localBinding = session.binding;
|
|
76
246
|
const listeners = /* @__PURE__ */ new Set();
|
|
77
247
|
const pending = /* @__PURE__ */ new Map();
|
|
78
248
|
const streams = /* @__PURE__ */ new Map();
|
|
79
249
|
const waitingCalls = /* @__PURE__ */ new Set();
|
|
80
250
|
const callbackExecutions = /* @__PURE__ */ new Set();
|
|
251
|
+
const executionDrainWaiters = /* @__PURE__ */ new Set();
|
|
81
252
|
const proxies = /* @__PURE__ */ new Map();
|
|
82
253
|
const clients = /* @__PURE__ */ new Map();
|
|
83
254
|
let pendingCount = 0;
|
|
@@ -91,6 +262,11 @@ function createCapabilityBridge(options) {
|
|
|
91
262
|
let appliedSnapshotFingerprint;
|
|
92
263
|
let currentServices = [];
|
|
93
264
|
let disposed = false;
|
|
265
|
+
let closeMessageSent = false;
|
|
266
|
+
let closeAckWaiter;
|
|
267
|
+
let receivedCloseAck;
|
|
268
|
+
let closeAckSent = false;
|
|
269
|
+
let drainPromise;
|
|
94
270
|
let reservedStreamCount = 0;
|
|
95
271
|
const defaultTimeout = timeoutMs(options.defaultCallTimeoutMs, 3e4);
|
|
96
272
|
const emit = () => {
|
|
@@ -143,6 +319,10 @@ function createCapabilityBridge(options) {
|
|
|
143
319
|
peerRetainedPayloadBytes = Math.max(0, peerRetainedPayloadBytes - record.item.budgetBytes);
|
|
144
320
|
budget.retainedPayloadBytes = Math.max(0, budget.retainedPayloadBytes - record.item.budgetBytes);
|
|
145
321
|
budget.releaseExecutionSlot();
|
|
322
|
+
if (callbackExecutions.size === 0) {
|
|
323
|
+
for (const resolve of [...executionDrainWaiters]) resolve();
|
|
324
|
+
executionDrainWaiters.clear();
|
|
325
|
+
}
|
|
146
326
|
emit();
|
|
147
327
|
};
|
|
148
328
|
const releaseQueueItem = (item) => {
|
|
@@ -165,7 +345,7 @@ function createCapabilityBridge(options) {
|
|
|
165
345
|
if (entry.cancelSent) return;
|
|
166
346
|
entry.cancelSent = true;
|
|
167
347
|
try {
|
|
168
|
-
options.transport.send({ type: RUNTIME_CANCEL_TYPE, protocolVersion: RUNTIME_PROTOCOL_VERSION, callId: entry.callId, serviceInstanceId: entry.reference.serviceInstanceId });
|
|
348
|
+
options.transport.send({ type: RUNTIME_CANCEL_TYPE, protocolVersion: RUNTIME_PROTOCOL_VERSION, binding: localBinding, callId: entry.callId, serviceInstanceId: entry.reference.serviceInstanceId });
|
|
169
349
|
} catch {
|
|
170
350
|
}
|
|
171
351
|
try {
|
|
@@ -244,6 +424,7 @@ function createCapabilityBridge(options) {
|
|
|
244
424
|
const findService = (capability) => currentServices.find((service) => serviceMatches(service, capability));
|
|
245
425
|
const terminalError = (proxy, capability) => {
|
|
246
426
|
if (terminalFailure && !disposed) return frameworkError(terminalFailure.code, terminalFailure.phase, contextFor(capability, proxy.bound));
|
|
427
|
+
if (session.state !== "active") return frameworkError("service_revoked", "dispose", contextFor(capability, proxy.bound));
|
|
247
428
|
if (disposed || proxy.revoked) return frameworkError("service_revoked", "dispatch", contextFor(capability, proxy.bound));
|
|
248
429
|
if (currentState === "stale" || currentState === "disposed") return frameworkError("service_revoked", "dispatch", contextFor(capability, proxy.bound));
|
|
249
430
|
if (proxy.bound && !currentServices.some((service) => serviceKey(service) === serviceKey(proxy.bound) && service.serviceInstanceId === proxy.bound?.serviceInstanceId)) {
|
|
@@ -334,7 +515,7 @@ function createCapabilityBridge(options) {
|
|
|
334
515
|
return;
|
|
335
516
|
}
|
|
336
517
|
try {
|
|
337
|
-
sendWire({ type: RUNTIME_CALL_TYPE, protocolVersion: RUNTIME_PROTOCOL_VERSION, callId, capabilityId: entry.capability.id, contractVersion: entry.capability.version, serviceInstanceId: reference.serviceInstanceId, mode: "unary", timeoutMs: timeout, request: prepared.value, ...callOptions.operationId !== void 0 ? { operationId: callOptions.operationId } : {} }, prepared.transfer);
|
|
518
|
+
sendWire({ type: RUNTIME_CALL_TYPE, protocolVersion: RUNTIME_PROTOCOL_VERSION, binding: localBinding, callId, capabilityId: entry.capability.id, contractVersion: entry.capability.version, serviceInstanceId: reference.serviceInstanceId, mode: "unary", timeoutMs: timeout, request: prepared.value, ...reference.grantId !== void 0 ? { grantId: reference.grantId } : {}, ...callOptions.operationId !== void 0 ? { operationId: callOptions.operationId } : {} }, prepared.transfer);
|
|
338
519
|
} catch {
|
|
339
520
|
settleUnary(entry, frameworkError("request_clone_failed", "dispatch", contextFor(entry.capability, reference)));
|
|
340
521
|
}
|
|
@@ -509,7 +690,7 @@ function createCapabilityBridge(options) {
|
|
|
509
690
|
return { ready, closed, cancel };
|
|
510
691
|
}
|
|
511
692
|
try {
|
|
512
|
-
sendWire({ type: RUNTIME_CALL_TYPE, protocolVersion: RUNTIME_PROTOCOL_VERSION, callId, capabilityId: entry.capability.id, contractVersion: entry.capability.version, serviceInstanceId: reference.serviceInstanceId, mode: "stream", timeoutMs: timeout, request: prepared.value, initialCredit: window, ...optionsForSubscribe.operationId !== void 0 ? { operationId: optionsForSubscribe.operationId } : {} }, prepared.transfer);
|
|
693
|
+
sendWire({ type: RUNTIME_CALL_TYPE, protocolVersion: RUNTIME_PROTOCOL_VERSION, binding: localBinding, callId, capabilityId: entry.capability.id, contractVersion: entry.capability.version, serviceInstanceId: reference.serviceInstanceId, mode: "stream", timeoutMs: timeout, request: prepared.value, initialCredit: window, ...reference.grantId !== void 0 ? { grantId: reference.grantId } : {}, ...optionsForSubscribe.operationId !== void 0 ? { operationId: optionsForSubscribe.operationId } : {} }, prepared.transfer);
|
|
513
694
|
} catch {
|
|
514
695
|
terminateStream(entry, frameworkError("request_clone_failed", "dispatch", contextFor(entry.capability, reference)), false);
|
|
515
696
|
}
|
|
@@ -652,7 +833,7 @@ function createCapabilityBridge(options) {
|
|
|
652
833
|
if (stream.state === "active" && !stream.doneReceived) {
|
|
653
834
|
stream.credit += 1;
|
|
654
835
|
try {
|
|
655
|
-
sendWire({ type: RUNTIME_CREDIT_TYPE, protocolVersion: RUNTIME_PROTOCOL_VERSION, callId: stream.callId, serviceInstanceId: stream.reference.serviceInstanceId, count: 1 });
|
|
836
|
+
sendWire({ type: RUNTIME_CREDIT_TYPE, protocolVersion: RUNTIME_PROTOCOL_VERSION, binding: localBinding, callId: stream.callId, serviceInstanceId: stream.reference.serviceInstanceId, count: 1 });
|
|
656
837
|
} catch {
|
|
657
838
|
terminateStream(stream, frameworkError("transport_unavailable", "dispatch", contextFor(stream.capability, stream.reference)), true);
|
|
658
839
|
break;
|
|
@@ -682,6 +863,14 @@ function createCapabilityBridge(options) {
|
|
|
682
863
|
terminateStream(stream, frameworkError("handler_failed", "execute", contextFor(stream.capability, stream.reference)), true);
|
|
683
864
|
});
|
|
684
865
|
};
|
|
866
|
+
const drainCallbackExecutions = () => {
|
|
867
|
+
if (callbackExecutions.size === 0) return Promise.resolve();
|
|
868
|
+
return new Promise((resolve) => executionDrainWaiters.add(resolve));
|
|
869
|
+
};
|
|
870
|
+
session.registerDrainParticipant({
|
|
871
|
+
drain: drainCallbackExecutions,
|
|
872
|
+
pending: () => callbackExecutions.size
|
|
873
|
+
});
|
|
685
874
|
const invalidate = (reason = "remote service directory replaced", terminalCause) => {
|
|
686
875
|
if (terminalCause) {
|
|
687
876
|
for (const record of [...waitingCalls]) record.fail(errorWithTerminalCause(terminalCause, record.proxy.capability, record.proxy.bound));
|
|
@@ -706,10 +895,81 @@ function createCapabilityBridge(options) {
|
|
|
706
895
|
if (disposed) return;
|
|
707
896
|
terminalFailure = frameworkError(code, "receive");
|
|
708
897
|
invalidate(reason, terminalFailure);
|
|
898
|
+
session.beginClose(reason);
|
|
709
899
|
try {
|
|
710
900
|
options.transport.close?.();
|
|
711
901
|
} catch {
|
|
712
902
|
}
|
|
903
|
+
session.close();
|
|
904
|
+
};
|
|
905
|
+
const removeSessionBeginClose = session.onBeginClose((reason) => {
|
|
906
|
+
if (!disposed) invalidate(reason);
|
|
907
|
+
});
|
|
908
|
+
const bounded = async (promise, fallback, deadline) => {
|
|
909
|
+
const remaining = deadline - Date.now();
|
|
910
|
+
if (remaining <= 0) return fallback;
|
|
911
|
+
return Promise.race([promise, new Promise((resolve) => setTimeout(() => resolve(fallback), remaining))]);
|
|
912
|
+
};
|
|
913
|
+
const beginClose = (reason = "Runtime bridge closing") => {
|
|
914
|
+
session.beginClose(reason);
|
|
915
|
+
};
|
|
916
|
+
const drain = (requestedTimeoutMs = DEFAULT_RUNTIME_DRAIN_TIMEOUT_MS) => {
|
|
917
|
+
if (drainPromise) return drainPromise;
|
|
918
|
+
if (!Number.isFinite(requestedTimeoutMs) || requestedTimeoutMs < 1 || requestedTimeoutMs > 3e5) {
|
|
919
|
+
return Promise.reject(new TypeError("drain timeoutMs must be a finite number from 1 to 300000"));
|
|
920
|
+
}
|
|
921
|
+
const alreadyClosed = session.state === "closed";
|
|
922
|
+
beginClose("Runtime bridge drain requested");
|
|
923
|
+
const effectiveTimeout = Math.min(requestedTimeoutMs, session.maxDrainTimeoutMs);
|
|
924
|
+
const deadline = Date.now() + effectiveTimeout;
|
|
925
|
+
const localDrain = session.drain(effectiveTimeout);
|
|
926
|
+
if (alreadyClosed) {
|
|
927
|
+
drainPromise = (async () => {
|
|
928
|
+
const localFallback = {
|
|
929
|
+
state: session.state,
|
|
930
|
+
drained: false,
|
|
931
|
+
timedOut: true,
|
|
932
|
+
pendingExecutions: callbackExecutions.size
|
|
933
|
+
};
|
|
934
|
+
return bounded(localDrain, localFallback, deadline);
|
|
935
|
+
})();
|
|
936
|
+
return drainPromise;
|
|
937
|
+
}
|
|
938
|
+
const ackPromise = receivedCloseAck ? Promise.resolve(receivedCloseAck) : new Promise((resolve) => {
|
|
939
|
+
closeAckWaiter = { resolve };
|
|
940
|
+
});
|
|
941
|
+
if (!closeMessageSent) {
|
|
942
|
+
try {
|
|
943
|
+
sendWire({
|
|
944
|
+
type: RUNTIME_CLOSE_TYPE,
|
|
945
|
+
protocolVersion: RUNTIME_PROTOCOL_VERSION,
|
|
946
|
+
binding: localBinding,
|
|
947
|
+
timeoutMs: effectiveTimeout
|
|
948
|
+
});
|
|
949
|
+
closeMessageSent = true;
|
|
950
|
+
} catch {
|
|
951
|
+
closeAckWaiter = void 0;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
drainPromise = (async () => {
|
|
955
|
+
const localFallback = {
|
|
956
|
+
state: session.state,
|
|
957
|
+
drained: false,
|
|
958
|
+
timedOut: true,
|
|
959
|
+
pendingExecutions: callbackExecutions.size
|
|
960
|
+
};
|
|
961
|
+
const local = await bounded(localDrain, localFallback, deadline);
|
|
962
|
+
const ack = await bounded(ackPromise, void 0, deadline);
|
|
963
|
+
if (ack === void 0) closeAckWaiter = void 0;
|
|
964
|
+
const timedOut = local.timedOut || !ack || ack.timedOut;
|
|
965
|
+
return {
|
|
966
|
+
state: session.state,
|
|
967
|
+
drained: local.drained && !!ack && ack.drained,
|
|
968
|
+
timedOut,
|
|
969
|
+
pendingExecutions: local.pendingExecutions + (ack?.pendingExecutions ?? 0)
|
|
970
|
+
};
|
|
971
|
+
})();
|
|
972
|
+
return drainPromise;
|
|
713
973
|
};
|
|
714
974
|
const onMessage = (message, metadata) => {
|
|
715
975
|
const ledger = metadata?.ledger ?? createReceivePortLedger(metadata?.ports, { limits, phase: "receive" });
|
|
@@ -718,9 +978,7 @@ function createCapabilityBridge(options) {
|
|
|
718
978
|
return;
|
|
719
979
|
}
|
|
720
980
|
try {
|
|
721
|
-
if (!metadata?.decoded)
|
|
722
|
-
message = codec.decode(message);
|
|
723
|
-
}
|
|
981
|
+
if (!metadata?.decoded) message = codec.decode(message);
|
|
724
982
|
} catch (error) {
|
|
725
983
|
ledger.closeUndelivered();
|
|
726
984
|
if (error instanceof WebLoomError && error.code === "protocol_mismatch") {
|
|
@@ -731,6 +989,60 @@ function createCapabilityBridge(options) {
|
|
|
731
989
|
return;
|
|
732
990
|
}
|
|
733
991
|
try {
|
|
992
|
+
const bindingAccepted = session.acceptRemoteBinding(message.binding);
|
|
993
|
+
const closedKnownCloseAck = !bindingAccepted && session.state === "closed" && message.type === RUNTIME_CLOSE_ACK_TYPE && sameRuntimeEndpointBinding(session.remoteBinding, message.binding);
|
|
994
|
+
if (!bindingAccepted && !closedKnownCloseAck) {
|
|
995
|
+
ledger.closeUndelivered();
|
|
996
|
+
failClose("Runtime endpoint binding mismatch", "invalid_message");
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
if (message.type === RUNTIME_CLOSE_TYPE) {
|
|
1000
|
+
ledger.closeUndelivered();
|
|
1001
|
+
session.beginClose("Remote Runtime endpoint closing");
|
|
1002
|
+
const timeout = Math.min(message.timeoutMs ?? session.maxDrainTimeoutMs, session.maxDrainTimeoutMs);
|
|
1003
|
+
void session.drain(timeout).then((result) => {
|
|
1004
|
+
if (closeAckSent) return;
|
|
1005
|
+
closeAckSent = true;
|
|
1006
|
+
try {
|
|
1007
|
+
sendWire({
|
|
1008
|
+
type: RUNTIME_CLOSE_ACK_TYPE,
|
|
1009
|
+
protocolVersion: RUNTIME_PROTOCOL_VERSION,
|
|
1010
|
+
binding: localBinding,
|
|
1011
|
+
acknowledgedBinding: message.binding,
|
|
1012
|
+
drained: result.drained,
|
|
1013
|
+
timedOut: result.timedOut,
|
|
1014
|
+
pendingExecutions: result.pendingExecutions
|
|
1015
|
+
});
|
|
1016
|
+
} catch {
|
|
1017
|
+
}
|
|
1018
|
+
session.close();
|
|
1019
|
+
setTimeout(() => {
|
|
1020
|
+
try {
|
|
1021
|
+
options.transport.close?.();
|
|
1022
|
+
} catch {
|
|
1023
|
+
}
|
|
1024
|
+
}, 10);
|
|
1025
|
+
});
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
if (message.type === RUNTIME_CLOSE_ACK_TYPE) {
|
|
1029
|
+
ledger.closeUndelivered();
|
|
1030
|
+
if (!sameRuntimeEndpointBinding(message.acknowledgedBinding, localBinding)) {
|
|
1031
|
+
failClose("Runtime close acknowledgement binding mismatch", "invalid_message");
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
const result = {
|
|
1035
|
+
state: session.state,
|
|
1036
|
+
drained: message.drained,
|
|
1037
|
+
timedOut: message.timedOut,
|
|
1038
|
+
pendingExecutions: message.pendingExecutions
|
|
1039
|
+
};
|
|
1040
|
+
receivedCloseAck = result;
|
|
1041
|
+
const waiter = closeAckWaiter;
|
|
1042
|
+
closeAckWaiter = void 0;
|
|
1043
|
+
waiter?.resolve(result);
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
734
1046
|
if (message.type === RUNTIME_CALL_TYPE || message.type === RUNTIME_CANCEL_TYPE || message.type === RUNTIME_CREDIT_TYPE) return;
|
|
735
1047
|
if (message.type === RUNTIME_SNAPSHOT_TYPE) {
|
|
736
1048
|
if (ledger.ports.length > 0) throw frameworkError("transfer_invalid", "receive");
|
|
@@ -886,10 +1198,13 @@ function createCapabilityBridge(options) {
|
|
|
886
1198
|
if (disposed) return { accepted: false, reason: "disposed" };
|
|
887
1199
|
if (!snapshot || typeof snapshot !== "object") return { accepted: false, reason: "invalid-snapshot" };
|
|
888
1200
|
if (snapshot.protocolVersion !== RUNTIME_PROTOCOL_VERSION) return { accepted: false, reason: "protocol-mismatch" };
|
|
1201
|
+
const binding = snapshot.binding;
|
|
1202
|
+
if (!session.acceptRemoteBinding(binding)) return { accepted: false, reason: "invalid-snapshot" };
|
|
889
1203
|
try {
|
|
890
1204
|
if (snapshot.units.length > limits.maxSnapshotUnits || snapshot.services.length > limits.maxSnapshotServices) return { accepted: false, reason: "invalid-snapshot" };
|
|
891
|
-
|
|
892
|
-
|
|
1205
|
+
const wireSnapshot = { ...snapshot, type: RUNTIME_SNAPSHOT_TYPE };
|
|
1206
|
+
codec.encode(wireSnapshot);
|
|
1207
|
+
validateDto(wireSnapshot, { limits: { maxDepth: limits.maxDtoDepth, maxNodes: limits.maxDtoNodes, maxEdges: limits.maxDtoEdges, maxBudgetBytes: limits.maxMessageBudgetBytes }, phase: "receive" });
|
|
893
1208
|
} catch {
|
|
894
1209
|
return { accepted: false, reason: "invalid-snapshot" };
|
|
895
1210
|
}
|
|
@@ -953,6 +1268,12 @@ function createCapabilityBridge(options) {
|
|
|
953
1268
|
get runtimeKind() {
|
|
954
1269
|
return remoteRuntimeKind;
|
|
955
1270
|
},
|
|
1271
|
+
get binding() {
|
|
1272
|
+
return localBinding;
|
|
1273
|
+
},
|
|
1274
|
+
get endpointState() {
|
|
1275
|
+
return session.state;
|
|
1276
|
+
},
|
|
956
1277
|
getClient(capability, scope) {
|
|
957
1278
|
const key = `${capability.kind}\0${capability.id}\0${capability.version}\0${scope?.identity.scopeId ?? "root"}`;
|
|
958
1279
|
let proxy = proxies.get(key);
|
|
@@ -973,13 +1294,18 @@ function createCapabilityBridge(options) {
|
|
|
973
1294
|
invalidate(reason);
|
|
974
1295
|
currentState = "stale";
|
|
975
1296
|
},
|
|
1297
|
+
beginClose,
|
|
1298
|
+
drain,
|
|
976
1299
|
dispose(reason = "Runtime bridge disposed") {
|
|
977
1300
|
if (disposed) return;
|
|
1301
|
+
session.beginClose(reason);
|
|
978
1302
|
disposed = true;
|
|
979
1303
|
invalidate(reason);
|
|
980
1304
|
removeTransport();
|
|
981
1305
|
removeTransportError?.();
|
|
982
1306
|
options.transport.close?.();
|
|
1307
|
+
session.close();
|
|
1308
|
+
removeSessionBeginClose();
|
|
983
1309
|
currentState = "disposed";
|
|
984
1310
|
emit();
|
|
985
1311
|
},
|
|
@@ -1116,9 +1442,14 @@ function createMessagePortServiceProvider(options) {
|
|
|
1116
1442
|
const configuredTransport = options.transport ?? (options.port ? createMessagePortRuntimeTransport(options.port, { limits }) : void 0);
|
|
1117
1443
|
if (!configuredTransport) throw new TypeError("MessagePort service provider requires transport or port");
|
|
1118
1444
|
const transport = configuredTransport;
|
|
1445
|
+
const ownsSession = options.session === void 0;
|
|
1446
|
+
const session = options.session ?? createRuntimeEndpointSession(options.binding ?? createRuntimeEndpointBinding(`provider:${Date.now().toString(36)}`), { defaultDrainTimeoutMs: options.drainTimeoutMs });
|
|
1447
|
+
if (options.binding && !sameRuntimeEndpointBinding(options.binding, session.binding)) throw new TypeError("MessagePort service provider binding disagrees with endpoint session");
|
|
1448
|
+
const localBinding = session.binding;
|
|
1119
1449
|
const activeCalls = /* @__PURE__ */ new Map();
|
|
1120
1450
|
const streams = /* @__PURE__ */ new Map();
|
|
1121
1451
|
const executionSlots = /* @__PURE__ */ new Map();
|
|
1452
|
+
const executionDrainWaiters = /* @__PURE__ */ new Set();
|
|
1122
1453
|
let peerRetainedPayloadBytes = 0;
|
|
1123
1454
|
let disposed = false;
|
|
1124
1455
|
const serviceFor = (message) => options.services().find((service) => service.kind === (message.mode === "stream" ? "stream" : "rpc") && service.capabilityId === message.capabilityId && service.contractVersion === message.contractVersion && service.serviceInstanceId === message.serviceInstanceId);
|
|
@@ -1130,6 +1461,7 @@ function createMessagePortServiceProvider(options) {
|
|
|
1130
1461
|
post(transport, codec, {
|
|
1131
1462
|
type: RUNTIME_ERROR_MESSAGE_TYPE,
|
|
1132
1463
|
protocolVersion: RUNTIME_PROTOCOL_VERSION,
|
|
1464
|
+
binding: localBinding,
|
|
1133
1465
|
callId: call.callId,
|
|
1134
1466
|
serviceInstanceId: call.serviceInstanceId,
|
|
1135
1467
|
error: { code, message: safeErrorMessage(code), phase }
|
|
@@ -1144,6 +1476,10 @@ function createMessagePortServiceProvider(options) {
|
|
|
1144
1476
|
budget.retainedPayloadBytes = Math.max(0, budget.retainedPayloadBytes - active.requestBytes);
|
|
1145
1477
|
active.removePeerRevoke?.();
|
|
1146
1478
|
active.removePeerRevoke = void 0;
|
|
1479
|
+
if (executionSlots.size === 0) {
|
|
1480
|
+
for (const resolve of [...executionDrainWaiters]) resolve();
|
|
1481
|
+
executionDrainWaiters.clear();
|
|
1482
|
+
}
|
|
1147
1483
|
};
|
|
1148
1484
|
const releaseStreamReservation = (active) => {
|
|
1149
1485
|
if (!active.streamReserved) return;
|
|
@@ -1297,7 +1633,7 @@ function createMessagePortServiceProvider(options) {
|
|
|
1297
1633
|
if (entry.closed || entry.active.cancelled) break;
|
|
1298
1634
|
if (next.done) {
|
|
1299
1635
|
entry.iteratorDone = true;
|
|
1300
|
-
if (post(transport, codec, { type: RUNTIME_RESULT_TYPE, protocolVersion: RUNTIME_PROTOCOL_VERSION, callId: entry.call.callId, serviceInstanceId: entry.reference.serviceInstanceId, done: true })) closeStream(entry, true);
|
|
1636
|
+
if (post(transport, codec, { type: RUNTIME_RESULT_TYPE, protocolVersion: RUNTIME_PROTOCOL_VERSION, binding: localBinding, callId: entry.call.callId, serviceInstanceId: entry.reference.serviceInstanceId, done: true })) closeStream(entry, true);
|
|
1301
1637
|
else {
|
|
1302
1638
|
sendError(entry.call, "transport_unavailable", "receive");
|
|
1303
1639
|
closeStream(entry, true);
|
|
@@ -1307,7 +1643,7 @@ function createMessagePortServiceProvider(options) {
|
|
|
1307
1643
|
try {
|
|
1308
1644
|
const prepared = options.prepareItem?.(next.value, entry.call) ?? { value: next.value };
|
|
1309
1645
|
const output = prepareOutput(prepared);
|
|
1310
|
-
if (!post(transport, codec, { type: RUNTIME_NEXT_TYPE, protocolVersion: RUNTIME_PROTOCOL_VERSION, callId: entry.call.callId, serviceInstanceId: entry.reference.serviceInstanceId, sequence: entry.sequence, item: output.value }, output.transfer)) {
|
|
1646
|
+
if (!post(transport, codec, { type: RUNTIME_NEXT_TYPE, protocolVersion: RUNTIME_PROTOCOL_VERSION, binding: localBinding, callId: entry.call.callId, serviceInstanceId: entry.reference.serviceInstanceId, sequence: entry.sequence, item: output.value }, output.transfer)) {
|
|
1311
1647
|
sendError(entry.call, "response_clone_failed", "receive");
|
|
1312
1648
|
closeStream(entry);
|
|
1313
1649
|
break;
|
|
@@ -1332,6 +1668,20 @@ function createMessagePortServiceProvider(options) {
|
|
|
1332
1668
|
failClose();
|
|
1333
1669
|
return;
|
|
1334
1670
|
}
|
|
1671
|
+
try {
|
|
1672
|
+
if (!metadata?.decoded) message = codec.decode(message);
|
|
1673
|
+
} catch {
|
|
1674
|
+
ledger.closeUndelivered();
|
|
1675
|
+
failClose();
|
|
1676
|
+
return;
|
|
1677
|
+
}
|
|
1678
|
+
const bindingAccepted = session.acceptRemoteBinding(message.binding);
|
|
1679
|
+
const closedKnownControl = !bindingAccepted && session.state === "closed" && sameRuntimeEndpointBinding(session.remoteBinding, message.binding);
|
|
1680
|
+
if (!bindingAccepted && !closedKnownControl) {
|
|
1681
|
+
ledger.closeUndelivered();
|
|
1682
|
+
failClose();
|
|
1683
|
+
return;
|
|
1684
|
+
}
|
|
1335
1685
|
if (message.type === RUNTIME_CANCEL_TYPE) {
|
|
1336
1686
|
ledger.closeUndelivered();
|
|
1337
1687
|
const active2 = activeCalls.get(message.callId) ?? streams.get(message.callId)?.active;
|
|
@@ -1353,6 +1703,11 @@ function createMessagePortServiceProvider(options) {
|
|
|
1353
1703
|
return;
|
|
1354
1704
|
}
|
|
1355
1705
|
if (message.type !== RUNTIME_CALL_TYPE) return;
|
|
1706
|
+
if (session.state !== "active" || disposed) {
|
|
1707
|
+
ledger.closeUndelivered();
|
|
1708
|
+
sendError(message, "service_revoked", "dispose");
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1356
1711
|
const reference = serviceFor(message);
|
|
1357
1712
|
if (!reference) {
|
|
1358
1713
|
ledger.closeUndelivered();
|
|
@@ -1364,7 +1719,7 @@ function createMessagePortServiceProvider(options) {
|
|
|
1364
1719
|
sendError(message, "invalid_message", "dispatch");
|
|
1365
1720
|
return;
|
|
1366
1721
|
}
|
|
1367
|
-
if (message.grantId !==
|
|
1722
|
+
if (message.grantId !== reference.grantId) {
|
|
1368
1723
|
ledger.closeUndelivered();
|
|
1369
1724
|
sendError(message, "permission_denied", "dispatch");
|
|
1370
1725
|
return;
|
|
@@ -1403,7 +1758,7 @@ function createMessagePortServiceProvider(options) {
|
|
|
1403
1758
|
releaseExecution(active);
|
|
1404
1759
|
return;
|
|
1405
1760
|
}
|
|
1406
|
-
const result = await options.handleCall({ message: active.call, request: request.value, reference, signal: controller.signal, deadlineAt: active.deadlineAt, peer: callPeer });
|
|
1761
|
+
const result = await options.handleCall({ message: active.call, request: request.value, reference, signal: controller.signal, deadlineAt: active.deadlineAt, binding: active.call.binding, peer: callPeer });
|
|
1407
1762
|
if (active.cancelled || disposed || executionSlots.get(message.callId) !== active) {
|
|
1408
1763
|
if (message.mode === "stream") await closeLateIterable(result);
|
|
1409
1764
|
releaseExecution(active);
|
|
@@ -1412,7 +1767,7 @@ function createMessagePortServiceProvider(options) {
|
|
|
1412
1767
|
if (message.mode === "unary") {
|
|
1413
1768
|
try {
|
|
1414
1769
|
const output = prepareOutput(options.prepareResult?.(result, active.call) ?? { value: result });
|
|
1415
|
-
if (!active.cancelled && !post(transport, codec, { type: RUNTIME_RESULT_TYPE, protocolVersion: RUNTIME_PROTOCOL_VERSION, callId: message.callId, serviceInstanceId: reference.serviceInstanceId, result: output.value }, output.transfer)) sendError(message, "response_clone_failed", "receive");
|
|
1770
|
+
if (!active.cancelled && !post(transport, codec, { type: RUNTIME_RESULT_TYPE, protocolVersion: RUNTIME_PROTOCOL_VERSION, binding: localBinding, callId: message.callId, serviceInstanceId: reference.serviceInstanceId, result: output.value }, output.transfer)) sendError(message, "response_clone_failed", "receive");
|
|
1416
1771
|
} catch (error) {
|
|
1417
1772
|
if (!active.cancelled) sendError(message, safeErrorCode(error, "response_validation_failed"), "receive");
|
|
1418
1773
|
}
|
|
@@ -1424,7 +1779,7 @@ function createMessagePortServiceProvider(options) {
|
|
|
1424
1779
|
const iterator = result[Symbol.asyncIterator]();
|
|
1425
1780
|
stream = { active, call: active.call, controller, reference, window: message.initialCredit ?? 16, iterator, credit: message.initialCredit ?? 16, sequence: 1, running: false, closed: false, iteratorDone: false, returnStarted: false, returnDone: false, pumpDone: false };
|
|
1426
1781
|
streams.set(message.callId, stream);
|
|
1427
|
-
if (!post(transport, codec, { type: RUNTIME_RESULT_TYPE, protocolVersion: RUNTIME_PROTOCOL_VERSION, callId: message.callId, serviceInstanceId: reference.serviceInstanceId, streamReady: true })) {
|
|
1782
|
+
if (!post(transport, codec, { type: RUNTIME_RESULT_TYPE, protocolVersion: RUNTIME_PROTOCOL_VERSION, binding: localBinding, callId: message.callId, serviceInstanceId: reference.serviceInstanceId, streamReady: true })) {
|
|
1428
1783
|
sendError(message, "transport_unavailable", "dispatch");
|
|
1429
1784
|
closeStream(stream);
|
|
1430
1785
|
return;
|
|
@@ -1448,12 +1803,22 @@ function createMessagePortServiceProvider(options) {
|
|
|
1448
1803
|
}
|
|
1449
1804
|
});
|
|
1450
1805
|
};
|
|
1806
|
+
const drainExecutions = () => {
|
|
1807
|
+
if (executionSlots.size === 0) return Promise.resolve();
|
|
1808
|
+
return new Promise((resolve) => executionDrainWaiters.add(resolve));
|
|
1809
|
+
};
|
|
1810
|
+
session.registerDrainParticipant({
|
|
1811
|
+
drain: drainExecutions,
|
|
1812
|
+
pending: () => executionSlots.size
|
|
1813
|
+
});
|
|
1814
|
+
let removeTransport = () => void 0;
|
|
1451
1815
|
function dispose() {
|
|
1452
1816
|
if (disposed) return;
|
|
1453
1817
|
disposed = true;
|
|
1454
1818
|
for (const active of [...activeCalls.values()]) cancelActive(active, "service_revoked");
|
|
1455
1819
|
for (const stream of [...streams.values()]) closeStream(stream);
|
|
1456
1820
|
removeTransport();
|
|
1821
|
+
if (ownsSession) session.close();
|
|
1457
1822
|
}
|
|
1458
1823
|
function failClose() {
|
|
1459
1824
|
dispose();
|
|
@@ -1461,12 +1826,30 @@ function createMessagePortServiceProvider(options) {
|
|
|
1461
1826
|
transport.close?.();
|
|
1462
1827
|
} catch {
|
|
1463
1828
|
}
|
|
1829
|
+
session.close();
|
|
1464
1830
|
}
|
|
1465
|
-
|
|
1831
|
+
removeTransport = transport.subscribe(onMessage);
|
|
1832
|
+
let removeSessionBeginClose = () => void 0;
|
|
1833
|
+
removeSessionBeginClose = session.onBeginClose(() => dispose());
|
|
1834
|
+
session.onClosed(() => {
|
|
1835
|
+
removeSessionBeginClose();
|
|
1836
|
+
});
|
|
1466
1837
|
return {
|
|
1467
1838
|
setServices() {
|
|
1468
1839
|
},
|
|
1469
1840
|
dispose,
|
|
1841
|
+
get binding() {
|
|
1842
|
+
return localBinding;
|
|
1843
|
+
},
|
|
1844
|
+
get endpointState() {
|
|
1845
|
+
return session.state;
|
|
1846
|
+
},
|
|
1847
|
+
beginClose(reason = "Runtime endpoint closing") {
|
|
1848
|
+
session.beginClose(reason);
|
|
1849
|
+
},
|
|
1850
|
+
drain(timeoutMs2) {
|
|
1851
|
+
return session.drain(timeoutMs2);
|
|
1852
|
+
},
|
|
1470
1853
|
pendingCount() {
|
|
1471
1854
|
return activeCalls.size;
|
|
1472
1855
|
},
|
|
@@ -1498,6 +1881,7 @@ function createCapabilityPeerView(options) {
|
|
|
1498
1881
|
const allowed = options.allowed === void 0 ? void 0 : new Set(options.allowed.map((descriptor) => capabilityKey(descriptor)));
|
|
1499
1882
|
return Object.freeze({
|
|
1500
1883
|
peerId: options.peerId,
|
|
1884
|
+
binding: Object.freeze({ ...options.binding ?? options.bridge.binding }),
|
|
1501
1885
|
get runtime() {
|
|
1502
1886
|
return options.bridge.runtimeKind;
|
|
1503
1887
|
},
|
|
@@ -1523,7 +1907,7 @@ function makeWorker(options) {
|
|
|
1523
1907
|
if (!WorkerConstructor) throw new RuntimeUnavailableError("SharedWorker is not supported by this browser");
|
|
1524
1908
|
return new WorkerConstructor(options.url, workerOptions);
|
|
1525
1909
|
}
|
|
1526
|
-
function snapshotForClient(app, runtimeId, runtimeInstanceId, exposed, revision) {
|
|
1910
|
+
function snapshotForClient(app, runtimeId, runtimeInstanceId, binding, exposed, revision) {
|
|
1527
1911
|
const allowed = new Set(exposed.map((capability) => capabilityKey(capability)));
|
|
1528
1912
|
const state = app.state();
|
|
1529
1913
|
const runtimeState = state.state === "disconnected" ? "failed" : state.state;
|
|
@@ -1536,7 +1920,8 @@ function snapshotForClient(app, runtimeId, runtimeInstanceId, exposed, revision)
|
|
|
1536
1920
|
revision,
|
|
1537
1921
|
state: runtimeState,
|
|
1538
1922
|
units: state.units,
|
|
1539
|
-
services: runtimeState === "ready" ? state.services.filter((service) => allowed.has(capabilityKey({ kind: service.kind, id: service.capabilityId, version: service.contractVersion }))).map((service) => ({ kind: service.kind, capabilityId: service.capabilityId, contractVersion: service.contractVersion, serviceInstanceId: service.serviceInstanceId, attributes: cloneFrozenAttributes(service.attributes), ...service.grantId !== void 0 ? { grantId: service.grantId } : {}, ...service.authorizationRevision !== void 0 ? { authorizationRevision: service.authorizationRevision } : {} })) : []
|
|
1923
|
+
services: runtimeState === "ready" ? state.services.filter((service) => allowed.has(capabilityKey({ kind: service.kind, id: service.capabilityId, version: service.contractVersion }))).map((service) => ({ kind: service.kind, capabilityId: service.capabilityId, contractVersion: service.contractVersion, serviceInstanceId: service.serviceInstanceId, attributes: cloneFrozenAttributes(service.attributes), ...service.grantId !== void 0 ? { grantId: service.grantId } : {}, ...service.authorizationRevision !== void 0 ? { authorizationRevision: service.authorizationRevision } : {} })) : [],
|
|
1924
|
+
binding
|
|
1540
1925
|
};
|
|
1541
1926
|
}
|
|
1542
1927
|
function connectInternal(options) {
|
|
@@ -1573,18 +1958,22 @@ function connectInternal(options) {
|
|
|
1573
1958
|
}, { limits });
|
|
1574
1959
|
const outboundBudget = createRuntimeBudget(limits);
|
|
1575
1960
|
const inboundBudget = createRuntimeBudget(limits);
|
|
1576
|
-
const
|
|
1961
|
+
const localRuntimeInstanceId = options.client?.app.runtimeInstanceId ?? `window:${Date.now().toString(36)}`;
|
|
1962
|
+
const binding = createRuntimeEndpointBinding(localRuntimeInstanceId);
|
|
1963
|
+
const session = createRuntimeEndpointSession(binding);
|
|
1964
|
+
const bridge = createCapabilityBridge({ transport, remoteRuntimeKind: "shared-worker", remoteRuntimeId: options.id, defaultCallTimeoutMs: options.defaultCallTimeoutMs, limits, budget: outboundBudget, binding, session });
|
|
1577
1965
|
const listeners = /* @__PURE__ */ new Set();
|
|
1578
1966
|
const workerInstanceId = { value: "" };
|
|
1579
|
-
const localRuntimeInstanceId = options.client?.app.runtimeInstanceId ?? `window:${Date.now().toString(36)}`;
|
|
1580
1967
|
let revision = 0;
|
|
1581
1968
|
let disposed = false;
|
|
1969
|
+
let disposePromise;
|
|
1582
1970
|
let current = Object.freeze({ protocolVersion: RUNTIME_PROTOCOL_VERSION, runtimeId: options.id, runtimeKind: "shared-worker", runtimeInstanceId: "", state: "starting", revision: 0, units: [], services: [] });
|
|
1583
1971
|
const clientHost = requestedClientHost;
|
|
1584
1972
|
const clientPeerScope = options.client && clientHost ? clientHost.rootScope.child("peer", { attributes: { peerId: `peer:${Date.now().toString(36)}:${Math.random().toString(36).slice(2)}` } }) : void 0;
|
|
1585
1973
|
const clientPeerScopeView = clientPeerScope ? createPeerScopeView(clientPeerScope) : void 0;
|
|
1586
1974
|
const clientPeer = clientPeerScope && clientPeerScopeView ? createCapabilityPeerView({
|
|
1587
1975
|
peerId: clientPeerScope.identity.attributes.peerId,
|
|
1976
|
+
binding,
|
|
1588
1977
|
scope: clientPeerScopeView,
|
|
1589
1978
|
bridge,
|
|
1590
1979
|
capabilityScope: clientPeerScope
|
|
@@ -1593,6 +1982,8 @@ function connectInternal(options) {
|
|
|
1593
1982
|
transport,
|
|
1594
1983
|
peerScope: clientPeerScope,
|
|
1595
1984
|
peer: clientPeer,
|
|
1985
|
+
binding,
|
|
1986
|
+
session,
|
|
1596
1987
|
budget: inboundBudget,
|
|
1597
1988
|
limits,
|
|
1598
1989
|
services: () => clientHost.serviceReferences().filter((service) => exposed.some((capability) => capabilityKey(capability) === capabilityKey({ kind: service.kind, id: service.capabilityId, version: service.contractVersion }))),
|
|
@@ -1600,6 +1991,7 @@ function connectInternal(options) {
|
|
|
1600
1991
|
const registration = clientHost.capabilities.registration({ kind: reference.kind, id: reference.capabilityId, version: reference.contractVersion });
|
|
1601
1992
|
return clientPeerScope && clientPeerScopeView ? createCapabilityPeerView({
|
|
1602
1993
|
peerId: clientPeerScope.identity.attributes.peerId,
|
|
1994
|
+
binding,
|
|
1603
1995
|
scope: clientPeerScopeView,
|
|
1604
1996
|
bridge,
|
|
1605
1997
|
allowed: registration?.peerDependencies ?? [],
|
|
@@ -1618,12 +2010,12 @@ function connectInternal(options) {
|
|
|
1618
2010
|
}
|
|
1619
2011
|
return { value, transfer: capability.transfer?.request?.(value) };
|
|
1620
2012
|
},
|
|
1621
|
-
handleCall: async ({ request, reference, signal, deadlineAt, peer }) => {
|
|
2013
|
+
handleCall: async ({ request, reference, signal, deadlineAt, binding: callBinding, peer }) => {
|
|
1622
2014
|
const registration = clientHost.capabilities.registration({ kind: reference.kind, id: reference.capabilityId, version: reference.contractVersion });
|
|
1623
2015
|
if (!registration) throw new WebLoomError("service_stale", "Window exposure is no longer available", "dispatch");
|
|
1624
2016
|
const handler = registration.handler;
|
|
1625
2017
|
if (!handler) throw new WebLoomError("service_stale", "Window service handler is unavailable", "dispatch");
|
|
1626
|
-
return handler(request, { signal, deadlineAt, reference, origin: "remote", peer });
|
|
2018
|
+
return handler(request, { signal, deadlineAt, reference, binding: callBinding, origin: "remote", peer });
|
|
1627
2019
|
},
|
|
1628
2020
|
prepareResult: (value, call) => {
|
|
1629
2021
|
const registration = clientHost.capabilities.registration({ kind: "rpc", id: call.capabilityId, version: call.contractVersion });
|
|
@@ -1653,7 +2045,7 @@ function connectInternal(options) {
|
|
|
1653
2045
|
if (!options.client || disposed) return;
|
|
1654
2046
|
revision += 1;
|
|
1655
2047
|
try {
|
|
1656
|
-
transport.send(snapshotForClient(options.client.app, `${options.client.app.runtimeId}`, localRuntimeInstanceId, exposed, revision));
|
|
2048
|
+
transport.send(snapshotForClient(options.client.app, `${options.client.app.runtimeId}`, localRuntimeInstanceId, binding, exposed, revision));
|
|
1657
2049
|
} catch {
|
|
1658
2050
|
}
|
|
1659
2051
|
};
|
|
@@ -1662,7 +2054,7 @@ function connectInternal(options) {
|
|
|
1662
2054
|
const applied = bridge.applySnapshot(messageValue);
|
|
1663
2055
|
if (applied.accepted) {
|
|
1664
2056
|
workerInstanceId.value = messageValue.runtimeInstanceId;
|
|
1665
|
-
emit({ protocolVersion: RUNTIME_PROTOCOL_VERSION, runtimeId: messageValue.runtimeId, runtimeKind: messageValue.runtimeKind, runtimeInstanceId: messageValue.runtimeInstanceId, state: messageValue.state, revision: messageValue.revision, units: messageValue.units, services: messageValue.services });
|
|
2057
|
+
emit({ protocolVersion: RUNTIME_PROTOCOL_VERSION, runtimeId: messageValue.runtimeId, runtimeKind: messageValue.runtimeKind, runtimeInstanceId: messageValue.runtimeInstanceId, state: messageValue.state, revision: messageValue.revision, units: messageValue.units, services: messageValue.services, binding: messageValue.binding });
|
|
1666
2058
|
}
|
|
1667
2059
|
} else if (messageValue.type === RUNTIME_ERROR_TYPE) {
|
|
1668
2060
|
bridge.invalidate(messageValue.message);
|
|
@@ -1670,12 +2062,13 @@ function connectInternal(options) {
|
|
|
1670
2062
|
}
|
|
1671
2063
|
});
|
|
1672
2064
|
const onError = () => {
|
|
1673
|
-
if (
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
2065
|
+
if (disposed) return;
|
|
2066
|
+
session.beginClose("SharedWorker disconnected");
|
|
2067
|
+
clientPeerScope?.revoke("SharedWorker disconnected");
|
|
2068
|
+
provider?.dispose();
|
|
2069
|
+
bridge.disconnect("SharedWorker disconnected");
|
|
2070
|
+
session.close();
|
|
2071
|
+
emit({ ...current, state: "disconnected", runtimeInstanceId: "", revision: 0, units: [], services: [], binding: void 0, error: "SharedWorker disconnected" });
|
|
1679
2072
|
};
|
|
1680
2073
|
port.addEventListener("messageerror", onError);
|
|
1681
2074
|
if (worker.addEventListener) worker.addEventListener("error", onError);
|
|
@@ -1685,6 +2078,10 @@ function connectInternal(options) {
|
|
|
1685
2078
|
const handle = {
|
|
1686
2079
|
runtimeKind: "shared-worker",
|
|
1687
2080
|
runtimeId: options.id,
|
|
2081
|
+
binding,
|
|
2082
|
+
get endpointState() {
|
|
2083
|
+
return session.state;
|
|
2084
|
+
},
|
|
1688
2085
|
get runtimeInstanceId() {
|
|
1689
2086
|
return workerInstanceId.value;
|
|
1690
2087
|
},
|
|
@@ -1701,20 +2098,38 @@ function connectInternal(options) {
|
|
|
1701
2098
|
listener(current);
|
|
1702
2099
|
return () => listeners.delete(listener);
|
|
1703
2100
|
},
|
|
2101
|
+
beginClose(reason = "SharedWorker connection closing") {
|
|
2102
|
+
if (disposed) return;
|
|
2103
|
+
session.beginClose(reason);
|
|
2104
|
+
clientPeerScope?.revoke(reason);
|
|
2105
|
+
},
|
|
2106
|
+
drain(timeoutMs2) {
|
|
2107
|
+
session.beginClose("SharedWorker connection drain requested");
|
|
2108
|
+
clientPeerScope?.revoke("SharedWorker connection drain requested");
|
|
2109
|
+
return bridge.drain(timeoutMs2);
|
|
2110
|
+
},
|
|
1704
2111
|
dispose(reason = "SharedWorker connection disposed") {
|
|
1705
2112
|
if (disposed) return Promise.resolve();
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
2113
|
+
if (disposePromise) return disposePromise;
|
|
2114
|
+
disposePromise = (async () => {
|
|
2115
|
+
emit({ ...current, state: "stopping" });
|
|
2116
|
+
removeClient?.();
|
|
2117
|
+
removeRaw();
|
|
2118
|
+
clientPeerScope?.revoke(reason);
|
|
2119
|
+
session.beginClose(reason);
|
|
2120
|
+
const drainResult = bridge.drain();
|
|
2121
|
+
await drainResult.catch(() => void 0);
|
|
2122
|
+
disposed = true;
|
|
2123
|
+
provider?.dispose();
|
|
2124
|
+
bridge.dispose(reason);
|
|
2125
|
+
port.removeEventListener("messageerror", onError);
|
|
2126
|
+
worker.removeEventListener?.("error", onError);
|
|
2127
|
+
transport.close?.();
|
|
2128
|
+
session.close();
|
|
2129
|
+
emit({ ...current, state: "disposed", runtimeInstanceId: "", revision: 0, units: [], services: [], binding: void 0 });
|
|
2130
|
+
if (clientPeerScope) await clientPeerScope.dispose({ reason });
|
|
2131
|
+
})();
|
|
2132
|
+
return disposePromise;
|
|
1718
2133
|
}
|
|
1719
2134
|
};
|
|
1720
2135
|
handleBridges.set(handle, bridge);
|
|
@@ -1732,6 +2147,6 @@ function bridgeForRuntimeHandle(handle) {
|
|
|
1732
2147
|
return bridge;
|
|
1733
2148
|
}
|
|
1734
2149
|
|
|
1735
|
-
export { bridgeForRuntimeHandle, connectSharedWorker, connectSharedWorkerForTesting, createCapabilityBridge, createCapabilityPeerView, createMessagePortRuntimeTransport, createMessagePortServiceProvider, createPeerScopeView, validateTransferables };
|
|
1736
|
-
//# sourceMappingURL=chunk-
|
|
1737
|
-
//# sourceMappingURL=chunk-
|
|
2150
|
+
export { DEFAULT_RUNTIME_DRAIN_TIMEOUT_MS, bridgeForRuntimeHandle, connectSharedWorker, connectSharedWorkerForTesting, createCapabilityBridge, createCapabilityPeerView, createMessagePortRuntimeTransport, createMessagePortServiceProvider, createPeerScopeView, createRuntimeEndpointBinding, createRuntimeEndpointSession, isRuntimeEndpointBinding, sameRuntimeEndpointBinding, validateTransferables };
|
|
2151
|
+
//# sourceMappingURL=chunk-XVD7ALPV.js.map
|
|
2152
|
+
//# sourceMappingURL=chunk-XVD7ALPV.js.map
|