webloom-framework 0.2.0 → 0.3.0
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 +11 -8
- package/dist/{chunk-URY3E6UH.js → chunk-76BGPI6M.js} +771 -312
- package/dist/chunk-76BGPI6M.js.map +1 -0
- package/dist/{createPluginHost-ChJNBTsX.d.ts → createPluginHost-BVsUCDKN.d.ts} +67 -88
- package/dist/index.d.ts +15 -200
- package/dist/index.js +276 -884
- package/dist/index.js.map +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/resourceRegistry-BFnjmeGE.d.ts +267 -0
- package/dist/testing.d.ts +3 -3
- package/dist/testing.js +2 -2
- package/docs/api.md +20 -19
- package/docs/migration-baseline.md +2 -2
- package/docs/proposals/browser-runtime-v1/implementation-plan.md +7 -1
- package/docs/proposals/browser-runtime-v1/requirements.md +6 -1
- package/docs/proposals/browser-runtime-v1/verification.md +20 -13
- package/docs/proposals/shared-worker-call-first/SWCF-009-typed-transfer-follow-up.md +34 -0
- package/docs/proposals/shared-worker-call-first/implementation-plan.md +567 -0
- package/package.json +1 -1
- package/dist/chunk-URY3E6UH.js.map +0 -1
- package/dist/resourceRegistry-MNM1c7od.d.ts +0 -157
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { LIFECYCLE_ERROR_TEXT, LifecycleScopeRevokedError, PermissionDeniedError, PermissionLeaseRevokedError, PluginGraphValidationError, RESOURCE_OWNER, RESOURCE_REGISTRY_CAPABILITY, RUNTIME_MESSAGE_BUS,
|
|
1
|
+
import { RuntimeInitializationError, createRuntimeUnitImplementationRegistry, createPluginHost, StartupPluginError, createRemoteServiceMessageCodec, createRuntimeMessageCodec, RemoteServiceError, UpgradeGateRejectedError, unitSnapshotFromState, RuntimeUnavailableError, RUNTIME_PROTOCOL_VERSION, RUNTIME_SNAPSHOT_TYPE, RUNTIME_ERROR_TYPE } from './chunk-76BGPI6M.js';
|
|
2
|
+
export { LIFECYCLE_ERROR_TEXT, LifecycleScopeRevokedError, PermissionDeniedError, PermissionLeaseRevokedError, PluginGraphValidationError, RESOURCE_OWNER, RESOURCE_REGISTRY_CAPABILITY, RUNTIME_ERROR_TYPE, RUNTIME_MESSAGE_BUS, RUNTIME_PROTOCOL_VERSION, RUNTIME_SNAPSHOT_TYPE, RemoteServiceError, RuntimeInitializationError, RuntimeUnavailableError, SCOPED_TASK_SCHEDULER_CAPABILITY, StartupCapabilityError, StartupPluginError, UpgradeGateRejectedError, buildPluginGraph, connectSharedWorker, createCapabilityRegistry, createInMemoryPluginConfigStore, createLifecycleScope, createMessageBus, createMessagePortServiceTransport, createPermissionLease, createPluginHost, createPluginIntentController, createRemoteServiceMessageCodec, createResourceRegistry, createResourceScope, createResourceStore, createRuntimeMessageCodec, createRuntimeUnitImplementationRegistry, createScopedMessageBus, createScopedTaskScheduler, createServiceBridge, dependenciesOfManifest, isRuntimeError, isRuntimeSnapshot, isRuntimeSnapshotProtocol, lifecycleErrorText, providesOfManifest, registerOwnedResource, reverseDependentsOf, unitSnapshotFromState, validatePluginGraph, validateRuntimeUnitDependencyContracts } from './chunk-76BGPI6M.js';
|
|
3
3
|
|
|
4
4
|
// src/contracts/plugin.ts
|
|
5
5
|
function runtimeCapabilityContractVersion(capability) {
|
|
@@ -156,90 +156,6 @@ function materializePluginDefinitions(inputs, runtime) {
|
|
|
156
156
|
return inputs.map((input) => materializePluginDefinition(input, runtime));
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
-
// src/runtime/runtimeTypes.ts
|
|
160
|
-
var RuntimeInitializationError = class extends Error {
|
|
161
|
-
code = "runtime.initialization_failed";
|
|
162
|
-
details;
|
|
163
|
-
constructor(details) {
|
|
164
|
-
super(
|
|
165
|
-
`Runtime initialization failed${details.pluginId ? ` for ${details.pluginId}` : ""} during ${details.phase}: ${details.error}`
|
|
166
|
-
);
|
|
167
|
-
this.name = "RuntimeInitializationError";
|
|
168
|
-
this.details = details;
|
|
169
|
-
}
|
|
170
|
-
};
|
|
171
|
-
var RuntimeUnavailableError = class extends Error {
|
|
172
|
-
code = "runtime.unavailable";
|
|
173
|
-
constructor(message = "Runtime is unavailable") {
|
|
174
|
-
super(message);
|
|
175
|
-
this.name = "RuntimeUnavailableError";
|
|
176
|
-
}
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
// src/runtime/runtimeProtocol.ts
|
|
180
|
-
var RUNTIME_PROTOCOL_VERSION = "webloom.runtime.v1";
|
|
181
|
-
var RUNTIME_HELLO_TYPE = "webloom.runtime.hello";
|
|
182
|
-
var RUNTIME_SNAPSHOT_TYPE = "webloom.runtime.snapshot";
|
|
183
|
-
var RUNTIME_RESYNC_TYPE = "webloom.runtime.resync";
|
|
184
|
-
var RUNTIME_ERROR_TYPE = "webloom.runtime.error";
|
|
185
|
-
function isRecord(input) {
|
|
186
|
-
return typeof input === "object" && input !== null;
|
|
187
|
-
}
|
|
188
|
-
function isNonEmptyString(input) {
|
|
189
|
-
return typeof input === "string" && input.length > 0;
|
|
190
|
-
}
|
|
191
|
-
function isRuntimeKind(input) {
|
|
192
|
-
return input === "window-main" || input === "shared-worker";
|
|
193
|
-
}
|
|
194
|
-
function isPluginStateKind(input) {
|
|
195
|
-
return input === "registered" || input === "starting" || input === "stopping" || input === "enabled" || input === "disabled" || input === "blocked" || input === "error-disabled" || input === "cleanup-pending" || input === "unknown";
|
|
196
|
-
}
|
|
197
|
-
function isRemoteServiceReference(input) {
|
|
198
|
-
if (!isRecord(input)) return false;
|
|
199
|
-
const reference = input;
|
|
200
|
-
return isNonEmptyString(reference.capabilityId) && isNonEmptyString(reference.providerInstanceId) && isRuntimeKind(reference.runtime) && isNonEmptyString(reference.contractVersion) && isNonEmptyString(reference.authorityInstanceId) && isNonEmptyString(reference.scopeId) && Number.isSafeInteger(reference.handoverGeneration) && reference.handoverGeneration >= 0 && isRecord(reference.attributes) && !Array.isArray(reference.attributes) && (reference.status === "starting" || reference.status === "ready" || reference.status === "unavailable" || reference.status === "failed") && Number.isSafeInteger(reference.snapshotRevision) && reference.snapshotRevision >= 0 && (reference.connectionId === void 0 || isNonEmptyString(reference.connectionId)) && (reference.grantId === void 0 || isNonEmptyString(reference.grantId)) && (reference.authorizationRevision === void 0 || Number.isSafeInteger(reference.authorizationRevision) && reference.authorizationRevision >= 0);
|
|
201
|
-
}
|
|
202
|
-
function isRuntimeSnapshotUnit(input) {
|
|
203
|
-
if (!isRecord(input)) return false;
|
|
204
|
-
const unit = input;
|
|
205
|
-
return isNonEmptyString(unit.pluginId) && isNonEmptyString(unit.unitId) && isRuntimeKind(unit.runtime) && isPluginStateKind(unit.state) && (unit.instanceId === void 0 || isNonEmptyString(unit.instanceId));
|
|
206
|
-
}
|
|
207
|
-
function createRuntimeMessageCodec() {
|
|
208
|
-
return createRemoteServiceMessageCodec({
|
|
209
|
-
prefix: "webloom.runtime",
|
|
210
|
-
protocolVersion: RUNTIME_PROTOCOL_VERSION
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
function isRuntimeHello(input) {
|
|
214
|
-
if (!isRecord(input)) return false;
|
|
215
|
-
const message = input;
|
|
216
|
-
return message.type === RUNTIME_HELLO_TYPE && isNonEmptyString(message.protocolVersion) && isNonEmptyString(message.connectionId) && isNonEmptyString(message.runtimeId);
|
|
217
|
-
}
|
|
218
|
-
function isRuntimeResync(input) {
|
|
219
|
-
if (!isRecord(input)) return false;
|
|
220
|
-
const message = input;
|
|
221
|
-
return message.type === RUNTIME_RESYNC_TYPE && isNonEmptyString(message.connectionId);
|
|
222
|
-
}
|
|
223
|
-
function isRuntimeSnapshot(input) {
|
|
224
|
-
if (!isRecord(input)) return false;
|
|
225
|
-
const message = input;
|
|
226
|
-
return message.type === RUNTIME_SNAPSHOT_TYPE && message.protocolVersion === RUNTIME_PROTOCOL_VERSION && isNonEmptyString(message.runtimeId) && isRuntimeKind(message.runtimeKind) && isNonEmptyString(message.runtimeInstanceId) && isNonEmptyString(message.connectionId) && Number.isSafeInteger(message.snapshotRevision) && message.snapshotRevision >= 0 && typeof message.baseline === "boolean" && Array.isArray(message.units) && message.units.every(isRuntimeSnapshotUnit) && Array.isArray(message.services) && message.services.every(isRemoteServiceReference) && (message.state === "starting" || message.state === "ready" || message.state === "stopping" || message.state === "failed" || message.state === "disposed");
|
|
227
|
-
}
|
|
228
|
-
function isRuntimeError(input) {
|
|
229
|
-
if (!isRecord(input)) return false;
|
|
230
|
-
const message = input;
|
|
231
|
-
return message.type === RUNTIME_ERROR_TYPE && isNonEmptyString(message.protocolVersion) && (message.code === "runtime.protocol_mismatch" || message.code === "runtime.initialization_failed" || message.code === "runtime.invalid_connection") && isNonEmptyString(message.message) && (message.pluginId === void 0 || isNonEmptyString(message.pluginId)) && (message.unitId === void 0 || isNonEmptyString(message.unitId)) && (message.phase === void 0 || isNonEmptyString(message.phase));
|
|
232
|
-
}
|
|
233
|
-
function unitSnapshotFromState(state, runtime) {
|
|
234
|
-
return {
|
|
235
|
-
pluginId: state.pluginId,
|
|
236
|
-
unitId: state.unitId,
|
|
237
|
-
runtime,
|
|
238
|
-
...state.instanceId !== void 0 ? { instanceId: state.instanceId } : {},
|
|
239
|
-
state: state.kind
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
|
|
243
159
|
// src/runtime/windowRuntime.ts
|
|
244
160
|
function makeRuntimeInstanceId(runtimeId) {
|
|
245
161
|
try {
|
|
@@ -256,25 +172,21 @@ function errorMessage(error) {
|
|
|
256
172
|
function isStartupPluginError(error) {
|
|
257
173
|
return error instanceof StartupPluginError && typeof error.details?.pluginId === "string";
|
|
258
174
|
}
|
|
259
|
-
function buildLocalServices(host, manifests, runtimeInstanceId
|
|
175
|
+
function buildLocalServices(host, manifests, runtimeInstanceId) {
|
|
260
176
|
const services = [];
|
|
261
177
|
for (const manifest of manifests) {
|
|
262
178
|
const state = host.state(manifest.id);
|
|
263
179
|
const unit = manifest.units?.find((candidate) => candidate.id === state.unitId) ?? manifest.units?.[0];
|
|
264
180
|
if (!unit || unit.runtime === void 0 || !state.instanceId || state.kind !== "enabled") continue;
|
|
265
|
-
const scopeId = host.scope(manifest.id)?.identity.scopeId ?? `scope:${state.instanceId}`;
|
|
266
181
|
for (const capability of unit.provides ?? []) {
|
|
267
182
|
services.push({
|
|
268
183
|
capabilityId: capability,
|
|
269
|
-
providerInstanceId: state.instanceId,
|
|
270
|
-
runtime: unit.runtime,
|
|
271
184
|
contractVersion: unit.providedContracts?.[capability] ?? `${capability}.v1`,
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
attributes: Object.freeze({}),
|
|
185
|
+
runtime: unit.runtime,
|
|
186
|
+
runtimeInstanceId,
|
|
187
|
+
serviceInstanceId: state.instanceId,
|
|
276
188
|
status: "ready",
|
|
277
|
-
|
|
189
|
+
attributes: Object.freeze({})
|
|
278
190
|
});
|
|
279
191
|
}
|
|
280
192
|
}
|
|
@@ -289,7 +201,7 @@ async function createWindowApp(options) {
|
|
|
289
201
|
runtimeKind: "window-main",
|
|
290
202
|
runtimeInstanceId,
|
|
291
203
|
state: "starting",
|
|
292
|
-
|
|
204
|
+
revision: 0,
|
|
293
205
|
units: [],
|
|
294
206
|
services: []
|
|
295
207
|
};
|
|
@@ -332,6 +244,7 @@ async function createWindowApp(options) {
|
|
|
332
244
|
}
|
|
333
245
|
let host;
|
|
334
246
|
const suppliedHost = options.host;
|
|
247
|
+
const remoteServiceBridge = remoteRuntime ? remoteRuntime.serviceBridge : void 0;
|
|
335
248
|
try {
|
|
336
249
|
const {
|
|
337
250
|
id: _id,
|
|
@@ -353,7 +266,7 @@ async function createWindowApp(options) {
|
|
|
353
266
|
...unit.instanceId !== void 0 ? { instanceId: unit.instanceId } : {},
|
|
354
267
|
state: unit.state
|
|
355
268
|
})) : hostOptions.runtimeSnapshots?.() ?? [],
|
|
356
|
-
serviceBridgeForPlugin: (pluginId, instanceId) => suppliedBridgeFactory?.(pluginId, instanceId) ??
|
|
269
|
+
serviceBridgeForPlugin: (pluginId, instanceId) => suppliedBridgeFactory?.(pluginId, instanceId) ?? remoteServiceBridge,
|
|
357
270
|
rootAttributes: {
|
|
358
271
|
...hostOptions.rootAttributes ?? {},
|
|
359
272
|
runtimeId,
|
|
@@ -378,13 +291,13 @@ async function createWindowApp(options) {
|
|
|
378
291
|
unit.runtime
|
|
379
292
|
));
|
|
380
293
|
});
|
|
381
|
-
const revision = Math.max(currentState.
|
|
294
|
+
const revision = Math.max(currentState.revision + 1, host.version());
|
|
382
295
|
emit({
|
|
383
296
|
...currentState,
|
|
384
|
-
|
|
297
|
+
revision,
|
|
385
298
|
units,
|
|
386
299
|
services: [
|
|
387
|
-
...buildLocalServices(host, snapshotManifests, runtimeInstanceId
|
|
300
|
+
...buildLocalServices(host, snapshotManifests, runtimeInstanceId),
|
|
388
301
|
...remoteRuntime?.state().services ?? []
|
|
389
302
|
]
|
|
390
303
|
});
|
|
@@ -470,22 +383,46 @@ async function createWindowApp(options) {
|
|
|
470
383
|
function isCallMessage(input, codec) {
|
|
471
384
|
if (!input || typeof input !== "object") return false;
|
|
472
385
|
const message = input;
|
|
473
|
-
return message.type === codec.type("call") && typeof message.callId === "string" && message.callId.length > 0 && typeof message.
|
|
386
|
+
return message.type === codec.type("call") && typeof message.protocolVersion === "string" && typeof message.callId === "string" && message.callId.length > 0 && typeof message.capabilityId === "string" && typeof message.contractVersion === "string" && typeof message.serviceInstanceId === "string";
|
|
474
387
|
}
|
|
475
388
|
function isCancelMessage(input, codec) {
|
|
476
389
|
if (!input || typeof input !== "object") return false;
|
|
477
390
|
const message = input;
|
|
478
|
-
return message.type === codec.type("cancel") && typeof message.
|
|
391
|
+
return message.type === codec.type("cancel") && typeof message.protocolVersion === "string" && typeof message.callId === "string" && typeof message.serviceInstanceId === "string";
|
|
479
392
|
}
|
|
480
393
|
function errorMessage2(error) {
|
|
481
394
|
const candidate = error && typeof error === "object" ? error : void 0;
|
|
395
|
+
const code = typeof candidate?.code === "string" && candidate.code.length > 0 ? candidate.code : "handler_failed";
|
|
482
396
|
return {
|
|
483
397
|
...typeof candidate?.name === "string" ? { name: candidate.name } : {},
|
|
484
398
|
message: typeof candidate?.message === "string" ? candidate.message : String(error),
|
|
485
|
-
|
|
399
|
+
code,
|
|
400
|
+
...candidate?.details && typeof candidate.details === "object" && !Array.isArray(candidate.details) ? { details: candidate.details } : {}
|
|
486
401
|
};
|
|
487
402
|
}
|
|
488
|
-
function
|
|
403
|
+
function stableValue(value) {
|
|
404
|
+
if (Array.isArray(value)) return value.map(stableValue);
|
|
405
|
+
if (!value || typeof value !== "object") return value;
|
|
406
|
+
return Object.fromEntries(
|
|
407
|
+
Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key, stableValue(item)])
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
function bindingKey(reference) {
|
|
411
|
+
return [
|
|
412
|
+
reference.capabilityId,
|
|
413
|
+
reference.contractVersion,
|
|
414
|
+
reference.runtime,
|
|
415
|
+
reference.runtimeInstanceId,
|
|
416
|
+
reference.serviceInstanceId
|
|
417
|
+
].join("\0");
|
|
418
|
+
}
|
|
419
|
+
function referenceFingerprint(reference) {
|
|
420
|
+
return JSON.stringify({
|
|
421
|
+
...reference,
|
|
422
|
+
attributes: stableValue(reference.attributes)
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
function postBestEffort(port, message) {
|
|
489
426
|
try {
|
|
490
427
|
port.postMessage(message);
|
|
491
428
|
} catch {
|
|
@@ -494,114 +431,151 @@ function post(port, message) {
|
|
|
494
431
|
function createMessagePortServiceProvider(options) {
|
|
495
432
|
const pending = /* @__PURE__ */ new Map();
|
|
496
433
|
const codec = options.codec ?? createRemoteServiceMessageCodec();
|
|
497
|
-
let
|
|
498
|
-
|
|
499
|
-
|
|
434
|
+
let services = [...options.services?.() ?? []];
|
|
435
|
+
let bindings = /* @__PURE__ */ new Map();
|
|
436
|
+
let nextBindingEpoch = 0;
|
|
437
|
+
let revoked = false;
|
|
500
438
|
let disposed = false;
|
|
439
|
+
const buildBindings = (nextServices) => {
|
|
440
|
+
const nextBindings = /* @__PURE__ */ new Map();
|
|
441
|
+
for (const reference of nextServices) {
|
|
442
|
+
const key = bindingKey(reference);
|
|
443
|
+
const previous = bindings.get(key);
|
|
444
|
+
const epoch = previous && referenceFingerprint(previous.reference) === referenceFingerprint(reference) ? previous.epoch : ++nextBindingEpoch;
|
|
445
|
+
nextBindings.set(key, { reference, epoch });
|
|
446
|
+
}
|
|
447
|
+
return nextBindings;
|
|
448
|
+
};
|
|
449
|
+
const isCurrent = (callId, call) => {
|
|
450
|
+
const current = bindings.get(call.bindingKey);
|
|
451
|
+
return !disposed && !revoked && pending.get(callId) === call && current?.epoch === call.bindingEpoch && current.reference.serviceInstanceId === call.serviceInstanceId;
|
|
452
|
+
};
|
|
453
|
+
const revokeReplacedCalls = (nextBindings) => {
|
|
454
|
+
for (const call of pending.values()) {
|
|
455
|
+
const next = nextBindings.get(call.bindingKey);
|
|
456
|
+
if (!next || next.epoch !== call.bindingEpoch) {
|
|
457
|
+
sendError(call.message, new RemoteServiceError("service_revoked", "Remote service binding was replaced"));
|
|
458
|
+
call.controller.abort(new RemoteServiceError("service_revoked", "Remote service binding was replaced"));
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
bindings = buildBindings(services);
|
|
501
463
|
const sendError = (message, error) => {
|
|
502
464
|
const response = {
|
|
503
465
|
type: codec.type("error"),
|
|
466
|
+
protocolVersion: codec.protocolVersion,
|
|
504
467
|
callId: message.callId,
|
|
505
|
-
|
|
506
|
-
providerInstanceId: message.providerInstanceId,
|
|
468
|
+
serviceInstanceId: message.serviceInstanceId,
|
|
507
469
|
error: errorMessage2(error)
|
|
508
470
|
};
|
|
509
|
-
|
|
471
|
+
postBestEffort(options.port, codec.encode(response));
|
|
510
472
|
};
|
|
511
473
|
const onMessage = (event) => {
|
|
512
474
|
if (disposed) return;
|
|
513
475
|
const decoded = codec.decode(event.data);
|
|
514
476
|
if (isCancelMessage(decoded, codec)) {
|
|
515
|
-
if (decoded.
|
|
516
|
-
const
|
|
517
|
-
if (
|
|
518
|
-
|
|
477
|
+
if (decoded.protocolVersion !== codec.protocolVersion) return;
|
|
478
|
+
const call2 = pending.get(decoded.callId);
|
|
479
|
+
if (call2?.serviceInstanceId === decoded.serviceInstanceId) {
|
|
480
|
+
call2.controller.abort(new RemoteServiceError("request_cancelled", "Remote service request cancelled"));
|
|
519
481
|
}
|
|
520
482
|
return;
|
|
521
483
|
}
|
|
522
484
|
if (!isCallMessage(decoded, codec)) return;
|
|
523
485
|
const message = decoded;
|
|
524
|
-
if (message.
|
|
525
|
-
sendError(message,
|
|
486
|
+
if (message.protocolVersion !== codec.protocolVersion) {
|
|
487
|
+
sendError(message, new RemoteServiceError("protocol_mismatch", "Remote service protocol version mismatch"));
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
if (revoked) {
|
|
491
|
+
sendError(message, new RemoteServiceError("service_revoked", "Remote service Provider has been revoked"));
|
|
526
492
|
return;
|
|
527
493
|
}
|
|
528
|
-
|
|
529
|
-
|
|
494
|
+
const reference = services.find((candidate) => candidate.status === "ready" && candidate.capabilityId === message.capabilityId && candidate.contractVersion === message.contractVersion && candidate.serviceInstanceId === message.serviceInstanceId);
|
|
495
|
+
if (!reference) {
|
|
496
|
+
sendError(message, new RemoteServiceError("service_stale", "Remote service instance is stale or unavailable"));
|
|
530
497
|
return;
|
|
531
498
|
}
|
|
532
499
|
if (pending.has(message.callId)) {
|
|
533
|
-
sendError(message,
|
|
500
|
+
sendError(message, new RemoteServiceError("handler_failed", "Remote service callId is duplicated"));
|
|
534
501
|
return;
|
|
535
502
|
}
|
|
536
503
|
const controller = new AbortController();
|
|
537
|
-
|
|
504
|
+
const currentBindingKey = bindingKey(reference);
|
|
505
|
+
const currentBinding = bindings.get(currentBindingKey);
|
|
506
|
+
if (!currentBinding || currentBinding.reference !== reference) {
|
|
507
|
+
sendError(message, new RemoteServiceError("service_stale", "Remote service binding changed"));
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
const call = {
|
|
511
|
+
message,
|
|
512
|
+
controller,
|
|
513
|
+
serviceInstanceId: message.serviceInstanceId,
|
|
514
|
+
bindingKey: currentBindingKey,
|
|
515
|
+
bindingEpoch: currentBinding.epoch
|
|
516
|
+
};
|
|
517
|
+
pending.set(message.callId, call);
|
|
538
518
|
void (async () => {
|
|
539
519
|
try {
|
|
540
|
-
const result = await options.handleCall({ message, signal: controller.signal });
|
|
541
|
-
if (controller.signal.aborted ||
|
|
520
|
+
const result = await options.handleCall({ message, reference, signal: controller.signal });
|
|
521
|
+
if (controller.signal.aborted || !isCurrent(message.callId, call)) return;
|
|
542
522
|
const response = {
|
|
543
523
|
type: codec.type("result"),
|
|
524
|
+
protocolVersion: codec.protocolVersion,
|
|
544
525
|
callId: message.callId,
|
|
545
|
-
|
|
546
|
-
providerInstanceId: message.providerInstanceId,
|
|
526
|
+
serviceInstanceId: message.serviceInstanceId,
|
|
547
527
|
result
|
|
548
528
|
};
|
|
549
|
-
|
|
529
|
+
postBestEffort(options.port, codec.encode(response));
|
|
550
530
|
} catch (error) {
|
|
551
|
-
if (
|
|
531
|
+
if (controller.signal.aborted || !isCurrent(message.callId, call)) return;
|
|
552
532
|
sendError(message, error);
|
|
553
533
|
} finally {
|
|
554
|
-
pending.delete(message.callId);
|
|
534
|
+
if (pending.get(message.callId) === call) pending.delete(message.callId);
|
|
555
535
|
}
|
|
556
536
|
})();
|
|
557
537
|
};
|
|
558
538
|
options.port.addEventListener("message", onMessage);
|
|
559
539
|
options.port.start();
|
|
560
|
-
post(options.port, codec.encode({
|
|
561
|
-
type: codec.type("handshake"),
|
|
562
|
-
handshake: options.handshake
|
|
563
|
-
}));
|
|
564
|
-
post(options.port, codec.encode({
|
|
565
|
-
type: codec.type("snapshot"),
|
|
566
|
-
snapshot: options.snapshot
|
|
567
|
-
}));
|
|
568
|
-
const dispose = () => {
|
|
569
|
-
if (disposed) return;
|
|
570
|
-
disposed = true;
|
|
571
|
-
options.port.removeEventListener("message", onMessage);
|
|
572
|
-
for (const { controller } of pending.values()) controller.abort(new Error("Remote service provider disposed"));
|
|
573
|
-
pending.clear();
|
|
574
|
-
if (options.closeOnDispose !== false) options.port.close();
|
|
575
|
-
};
|
|
576
540
|
const provider = {
|
|
577
|
-
|
|
541
|
+
setServices(nextServices) {
|
|
578
542
|
if (disposed) return;
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
543
|
+
const next = [...nextServices];
|
|
544
|
+
const nextBindings = buildBindings(next);
|
|
545
|
+
revoked = true;
|
|
546
|
+
revokeReplacedCalls(nextBindings);
|
|
547
|
+
services = next;
|
|
548
|
+
bindings = nextBindings;
|
|
549
|
+
revoked = false;
|
|
586
550
|
},
|
|
587
|
-
|
|
551
|
+
revoke(reason = "Remote service Provider revoked") {
|
|
588
552
|
if (disposed) return;
|
|
589
|
-
|
|
590
|
-
for (const
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
553
|
+
revoked = true;
|
|
554
|
+
for (const call of pending.values()) {
|
|
555
|
+
sendError(call.message, new RemoteServiceError("service_revoked", reason));
|
|
556
|
+
call.controller.abort(new RemoteServiceError("service_revoked", reason));
|
|
557
|
+
}
|
|
558
|
+
services = [];
|
|
559
|
+
bindings = /* @__PURE__ */ new Map();
|
|
595
560
|
},
|
|
596
|
-
|
|
561
|
+
dispose() {
|
|
597
562
|
if (disposed) return;
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
}))
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
563
|
+
disposed = true;
|
|
564
|
+
revoked = true;
|
|
565
|
+
options.port.removeEventListener("message", onMessage);
|
|
566
|
+
for (const { controller } of pending.values()) {
|
|
567
|
+
controller.abort(new RemoteServiceError("transport_unavailable", "Remote service Provider disposed"));
|
|
568
|
+
}
|
|
569
|
+
pending.clear();
|
|
570
|
+
services = [];
|
|
571
|
+
bindings = /* @__PURE__ */ new Map();
|
|
572
|
+
if (options.closeOnDispose !== false) {
|
|
573
|
+
try {
|
|
574
|
+
options.port.close();
|
|
575
|
+
} catch {
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
605
579
|
};
|
|
606
580
|
return provider;
|
|
607
581
|
}
|
|
@@ -621,69 +595,18 @@ function errorMessage3(error) {
|
|
|
621
595
|
}
|
|
622
596
|
function startupDetails(error) {
|
|
623
597
|
if (error instanceof StartupPluginError) {
|
|
624
|
-
return {
|
|
625
|
-
pluginId: error.details.pluginId,
|
|
626
|
-
unitId: error.details.unitId,
|
|
627
|
-
message: error.details.error ?? error.message
|
|
628
|
-
};
|
|
598
|
+
return { pluginId: error.details.pluginId, unitId: error.details.unitId, message: error.details.error ?? error.message };
|
|
629
599
|
}
|
|
630
600
|
if (error instanceof RuntimeInitializationError) {
|
|
631
|
-
return {
|
|
632
|
-
pluginId: error.details.pluginId,
|
|
633
|
-
unitId: error.details.unitId,
|
|
634
|
-
message: error.message
|
|
635
|
-
};
|
|
601
|
+
return { pluginId: error.details.pluginId, unitId: error.details.unitId, message: error.message };
|
|
636
602
|
}
|
|
637
603
|
return { message: errorMessage3(error) };
|
|
638
604
|
}
|
|
639
605
|
function addPortListener(port, listener) {
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
const remove = target.removeEventListener;
|
|
643
|
-
if (add && remove) {
|
|
644
|
-
const before2 = target.onmessage;
|
|
645
|
-
const probe = () => void 0;
|
|
646
|
-
add.call(port, "message", probe);
|
|
647
|
-
const overwritesProperty = target.onmessage === probe;
|
|
648
|
-
remove.call(port, "message", probe);
|
|
649
|
-
if (!overwritesProperty) {
|
|
650
|
-
add.call(port, "message", listener);
|
|
651
|
-
return () => remove.call(port, "message", listener);
|
|
652
|
-
}
|
|
653
|
-
const listeners2 = /* @__PURE__ */ new Set();
|
|
654
|
-
const dispatch2 = (event) => {
|
|
655
|
-
before2?.call(port, event);
|
|
656
|
-
for (const current of [...listeners2]) current(event);
|
|
657
|
-
};
|
|
658
|
-
target.addEventListener = function addMessageListener2(type, current) {
|
|
659
|
-
if (type === "message") listeners2.add(current);
|
|
660
|
-
else add.call(port, type, current);
|
|
661
|
-
};
|
|
662
|
-
target.removeEventListener = function removeMessageListener(type, current) {
|
|
663
|
-
if (type === "message") listeners2.delete(current);
|
|
664
|
-
else remove.call(port, type, current);
|
|
665
|
-
};
|
|
666
|
-
target.onmessage = dispatch2;
|
|
667
|
-
listeners2.add(listener);
|
|
668
|
-
return () => listeners2.delete(listener);
|
|
669
|
-
}
|
|
670
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
671
|
-
const before = target.onmessage;
|
|
672
|
-
const dispatch = (event) => {
|
|
673
|
-
before?.call(port, event);
|
|
674
|
-
for (const current of [...listeners]) current(event);
|
|
675
|
-
};
|
|
676
|
-
target.addEventListener = function addMessageListener2(type, current) {
|
|
677
|
-
if (type === "message") listeners.add(current);
|
|
678
|
-
};
|
|
679
|
-
target.removeEventListener = function removeMessageListener(type, current) {
|
|
680
|
-
if (type === "message") listeners.delete(current);
|
|
681
|
-
};
|
|
682
|
-
target.onmessage = dispatch;
|
|
683
|
-
listeners.add(listener);
|
|
684
|
-
return () => listeners.delete(listener);
|
|
606
|
+
port.addEventListener("message", listener);
|
|
607
|
+
return () => port.removeEventListener("message", listener);
|
|
685
608
|
}
|
|
686
|
-
function
|
|
609
|
+
function post(port, message) {
|
|
687
610
|
try {
|
|
688
611
|
port.postMessage(message);
|
|
689
612
|
} catch {
|
|
@@ -710,22 +633,38 @@ function startSharedWorkerApp(options) {
|
|
|
710
633
|
let host;
|
|
711
634
|
let manifests = [];
|
|
712
635
|
let disposed = false;
|
|
636
|
+
let acceptingConnections = true;
|
|
713
637
|
let disposePromise;
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
638
|
+
let startupError;
|
|
639
|
+
const serviceReferences = () => {
|
|
640
|
+
if (!host || runtimeState !== "ready") return [];
|
|
641
|
+
const services = [];
|
|
642
|
+
for (const manifest of manifests) {
|
|
643
|
+
const state = host.state(manifest.id);
|
|
644
|
+
const unit = manifest.units?.find((candidate) => candidate.id === state.unitId) ?? manifest.units?.[0];
|
|
645
|
+
if (!unit || unit.runtime === void 0 || !state.instanceId || state.kind !== "enabled") continue;
|
|
646
|
+
const scope = host.scope(manifest.id);
|
|
647
|
+
const attributes = Object.freeze({ ...scope?.identity.attributes ?? {} });
|
|
648
|
+
for (const capability of unit.provides ?? []) {
|
|
649
|
+
services.push({
|
|
650
|
+
capabilityId: capability,
|
|
651
|
+
contractVersion: unit.providedContracts?.[capability] ?? `${capability}.v1`,
|
|
652
|
+
runtime: unit.runtime,
|
|
653
|
+
runtimeInstanceId,
|
|
654
|
+
serviceInstanceId: state.instanceId,
|
|
655
|
+
status: "ready",
|
|
656
|
+
attributes
|
|
657
|
+
});
|
|
720
658
|
}
|
|
721
659
|
}
|
|
660
|
+
return services;
|
|
722
661
|
};
|
|
723
662
|
const currentSnapshot = () => Object.freeze({
|
|
724
663
|
runtimeId,
|
|
725
664
|
runtimeKind: "shared-worker",
|
|
726
665
|
runtimeInstanceId,
|
|
727
666
|
state: runtimeState,
|
|
728
|
-
|
|
667
|
+
revision,
|
|
729
668
|
units: Object.freeze(host && runtimeState !== "failed" ? manifests.flatMap((manifest) => {
|
|
730
669
|
const state = host?.state(manifest.id);
|
|
731
670
|
return (state?.units ?? []).map((unit) => ({
|
|
@@ -736,70 +675,42 @@ function startSharedWorkerApp(options) {
|
|
|
736
675
|
state: unit.kind
|
|
737
676
|
}));
|
|
738
677
|
}) : []),
|
|
739
|
-
services: Object.freeze(
|
|
678
|
+
services: Object.freeze(serviceReferences())
|
|
740
679
|
});
|
|
741
|
-
const
|
|
742
|
-
|
|
743
|
-
const
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
if (!unit || unit.runtime === void 0 || !state.instanceId || state.kind !== "enabled") continue;
|
|
748
|
-
const scopeId = host.scope(manifest.id)?.identity.scopeId ?? `scope:${state.instanceId}`;
|
|
749
|
-
for (const capability of unit.provides ?? []) {
|
|
750
|
-
services.push({
|
|
751
|
-
capabilityId: capability,
|
|
752
|
-
providerInstanceId: state.instanceId,
|
|
753
|
-
runtime: unit.runtime,
|
|
754
|
-
contractVersion: unit.providedContracts?.[capability] ?? `${capability}.v1`,
|
|
755
|
-
authorityInstanceId: runtimeInstanceId,
|
|
756
|
-
scopeId,
|
|
757
|
-
handoverGeneration: 0,
|
|
758
|
-
attributes: Object.freeze({}),
|
|
759
|
-
status: "ready",
|
|
760
|
-
snapshotRevision,
|
|
761
|
-
// The same service object is never valid through another port.
|
|
762
|
-
...connectionId ? { connectionId } : {}
|
|
763
|
-
});
|
|
680
|
+
const emit = () => {
|
|
681
|
+
const snapshot = currentSnapshot();
|
|
682
|
+
for (const listener of [...listeners]) {
|
|
683
|
+
try {
|
|
684
|
+
listener(snapshot);
|
|
685
|
+
} catch {
|
|
764
686
|
}
|
|
765
687
|
}
|
|
766
|
-
return services;
|
|
767
688
|
};
|
|
768
|
-
const
|
|
689
|
+
const runtimeSnapshot = () => ({
|
|
769
690
|
type: RUNTIME_SNAPSHOT_TYPE,
|
|
770
691
|
protocolVersion: RUNTIME_PROTOCOL_VERSION,
|
|
771
692
|
runtimeId,
|
|
772
693
|
runtimeKind: "shared-worker",
|
|
773
694
|
runtimeInstanceId,
|
|
774
|
-
|
|
775
|
-
snapshotRevision: revision,
|
|
776
|
-
baseline,
|
|
695
|
+
revision,
|
|
777
696
|
state: runtimeState,
|
|
778
697
|
units: currentSnapshot().units,
|
|
779
|
-
services: serviceReferences(
|
|
780
|
-
});
|
|
781
|
-
const serviceSnapshotFor = (endpoint, baseline) => ({
|
|
782
|
-
connectionId: endpoint.connectionId,
|
|
783
|
-
authorityInstanceId: runtimeInstanceId,
|
|
784
|
-
snapshotRevision: revision,
|
|
785
|
-
baseline,
|
|
786
|
-
services: serviceReferences(endpoint.connectionId, revision)
|
|
698
|
+
services: serviceReferences()
|
|
787
699
|
});
|
|
788
|
-
const publishEndpoint = (endpoint
|
|
789
|
-
if (endpoint.closed
|
|
790
|
-
|
|
791
|
-
endpoint.
|
|
792
|
-
post2(endpoint.port, codec.encode(runtimeSnapshotFor(endpoint, baseline)));
|
|
700
|
+
const publishEndpoint = (endpoint) => {
|
|
701
|
+
if (endpoint.closed) return;
|
|
702
|
+
endpoint.provider.setServices(serviceReferences());
|
|
703
|
+
post(endpoint.port, codec.encode(runtimeSnapshot()));
|
|
793
704
|
};
|
|
794
|
-
const publishAll = (
|
|
795
|
-
for (const endpoint of [...endpoints]) publishEndpoint(endpoint
|
|
705
|
+
const publishAll = () => {
|
|
706
|
+
for (const endpoint of [...endpoints]) publishEndpoint(endpoint);
|
|
796
707
|
};
|
|
797
|
-
const closeEndpoint = (endpoint,
|
|
708
|
+
const closeEndpoint = (endpoint, _reason) => {
|
|
798
709
|
if (endpoint.closed) return;
|
|
799
710
|
endpoint.closed = true;
|
|
800
711
|
endpoints.delete(endpoint);
|
|
801
712
|
endpoint.removeMessage();
|
|
802
|
-
endpoint.provider.
|
|
713
|
+
endpoint.provider.dispose();
|
|
803
714
|
};
|
|
804
715
|
const sendError = (port, code, message, details = {}) => {
|
|
805
716
|
const error = {
|
|
@@ -811,31 +722,34 @@ function startSharedWorkerApp(options) {
|
|
|
811
722
|
...details.unitId !== void 0 ? { unitId: details.unitId } : {},
|
|
812
723
|
...details.phase !== void 0 ? { phase: details.phase } : {}
|
|
813
724
|
};
|
|
814
|
-
|
|
725
|
+
post(port, error);
|
|
726
|
+
};
|
|
727
|
+
const isAcceptingConnections = () => acceptingConnections && runtimeState !== "stopping" && runtimeState !== "disposed" && !disposed;
|
|
728
|
+
const rejectLateConnection = (port) => {
|
|
729
|
+
post(port, codec.encode(runtimeSnapshot()));
|
|
730
|
+
setTimeout(() => {
|
|
731
|
+
try {
|
|
732
|
+
port.close();
|
|
733
|
+
} catch {
|
|
734
|
+
}
|
|
735
|
+
}, 0);
|
|
815
736
|
};
|
|
816
|
-
const handleCall = async (
|
|
737
|
+
const handleCall = async (input) => {
|
|
817
738
|
if (!host || runtimeState !== "ready") throw new Error("SharedWorker Runtime is not ready");
|
|
818
|
-
const message = input
|
|
819
|
-
if (
|
|
820
|
-
const reference = message.reference;
|
|
821
|
-
if (message.providerInstanceId !== reference.providerInstanceId || reference.authorityInstanceId !== runtimeInstanceId || reference.connectionId !== connectionId || reference.runtime !== "shared-worker" || reference.status !== "ready" || reference.snapshotRevision !== revision || reference.handoverGeneration !== 0) {
|
|
739
|
+
const { message, reference } = input;
|
|
740
|
+
if (reference.runtimeInstanceId !== runtimeInstanceId || reference.runtime !== "shared-worker" || reference.status !== "ready") {
|
|
822
741
|
throw new Error("Runtime service reference is stale");
|
|
823
742
|
}
|
|
824
|
-
const manifest = manifests.find((candidate) =>
|
|
825
|
-
const state2 = host?.state(candidate.id);
|
|
826
|
-
return state2?.instanceId === reference.providerInstanceId;
|
|
827
|
-
});
|
|
743
|
+
const manifest = manifests.find((candidate) => host?.state(candidate.id).instanceId === reference.serviceInstanceId);
|
|
828
744
|
if (!manifest) throw new Error("Runtime service provider is no longer active");
|
|
829
745
|
const state = host.state(manifest.id);
|
|
830
|
-
if (state.kind !== "enabled" || state.instanceId !== reference.
|
|
746
|
+
if (state.kind !== "enabled" || state.instanceId !== reference.serviceInstanceId) {
|
|
831
747
|
throw new Error("Runtime service provider is no longer active");
|
|
832
748
|
}
|
|
833
749
|
const unit = manifest.units?.find((candidate) => candidate.id === state.unitId);
|
|
834
750
|
if (!unit || unit.runtime !== "shared-worker" || !unit.provides?.includes(reference.capabilityId)) {
|
|
835
751
|
throw new Error("Runtime capability is not declared");
|
|
836
752
|
}
|
|
837
|
-
const expectedScopeId = host.scope(manifest.id)?.identity.scopeId ?? `scope:${state.instanceId}`;
|
|
838
|
-
if (reference.scopeId !== expectedScopeId) throw new Error("Runtime service scope is stale");
|
|
839
753
|
const expectedVersion = unit.providedContracts?.[reference.capabilityId] ?? `${reference.capabilityId}.v1`;
|
|
840
754
|
if (expectedVersion !== reference.contractVersion) throw new Error("Runtime capability contract mismatch");
|
|
841
755
|
const value = host.capabilities.get(reference.capabilityId);
|
|
@@ -858,104 +772,50 @@ function startSharedWorkerApp(options) {
|
|
|
858
772
|
};
|
|
859
773
|
const attachPort = (port) => {
|
|
860
774
|
let endpoint;
|
|
861
|
-
let helloSeen = false;
|
|
862
775
|
let removeMessage = () => void 0;
|
|
776
|
+
const onMessageError = () => {
|
|
777
|
+
if (endpoint) closeEndpoint(endpoint);
|
|
778
|
+
};
|
|
779
|
+
endpoint = {
|
|
780
|
+
port,
|
|
781
|
+
provider: void 0,
|
|
782
|
+
removeMessage: () => void 0,
|
|
783
|
+
closed: false
|
|
784
|
+
};
|
|
785
|
+
endpoint.provider = createMessagePortServiceProvider({
|
|
786
|
+
port,
|
|
787
|
+
codec,
|
|
788
|
+
services: serviceReferences,
|
|
789
|
+
handleCall
|
|
790
|
+
});
|
|
791
|
+
endpoints.add(endpoint);
|
|
863
792
|
const onMessage = (event) => {
|
|
864
|
-
if (endpoint?.closed) return;
|
|
865
|
-
const decoded = codec.decode(event.data);
|
|
866
|
-
if (!helloSeen) {
|
|
867
|
-
if (!isRuntimeHello(event.data)) return;
|
|
868
|
-
const hello = event.data;
|
|
869
|
-
helloSeen = true;
|
|
870
|
-
if (hello.protocolVersion !== RUNTIME_PROTOCOL_VERSION) {
|
|
871
|
-
sendError(port, "runtime.protocol_mismatch", "Runtime protocol version mismatch", { phase: "handshake" });
|
|
872
|
-
try {
|
|
873
|
-
port.close();
|
|
874
|
-
} catch {
|
|
875
|
-
}
|
|
876
|
-
return;
|
|
877
|
-
}
|
|
878
|
-
if (hello.runtimeId !== runtimeId) {
|
|
879
|
-
sendError(port, "runtime.invalid_connection", "Runtime id mismatch", { phase: "handshake" });
|
|
880
|
-
try {
|
|
881
|
-
port.close();
|
|
882
|
-
} catch {
|
|
883
|
-
}
|
|
884
|
-
return;
|
|
885
|
-
}
|
|
886
|
-
if (runtimeState === "failed") {
|
|
887
|
-
const detail = startupDetails(startupError);
|
|
888
|
-
sendError(port, "runtime.initialization_failed", detail.message, {
|
|
889
|
-
pluginId: detail.pluginId,
|
|
890
|
-
unitId: detail.unitId,
|
|
891
|
-
phase: "startup"
|
|
892
|
-
});
|
|
893
|
-
try {
|
|
894
|
-
port.close();
|
|
895
|
-
} catch {
|
|
896
|
-
}
|
|
897
|
-
return;
|
|
898
|
-
}
|
|
899
|
-
if (runtimeState === "stopping" || runtimeState === "disposed") {
|
|
900
|
-
sendError(port, "runtime.invalid_connection", "SharedWorker Runtime is no longer accepting connections", { phase: "handshake" });
|
|
901
|
-
try {
|
|
902
|
-
port.close();
|
|
903
|
-
} catch {
|
|
904
|
-
}
|
|
905
|
-
return;
|
|
906
|
-
}
|
|
907
|
-
if (endpointsHasConnection(hello.connectionId)) {
|
|
908
|
-
sendError(port, "runtime.invalid_connection", "Connection id is already active", { phase: "handshake" });
|
|
909
|
-
try {
|
|
910
|
-
port.close();
|
|
911
|
-
} catch {
|
|
912
|
-
}
|
|
913
|
-
return;
|
|
914
|
-
}
|
|
915
|
-
const candidate = { port, connectionId: hello.connectionId };
|
|
916
|
-
const provider = createMessagePortServiceProvider({
|
|
917
|
-
port,
|
|
918
|
-
codec,
|
|
919
|
-
handshake: {
|
|
920
|
-
connectionId: hello.connectionId,
|
|
921
|
-
authorityInstanceId: runtimeInstanceId,
|
|
922
|
-
protocolVersion: RUNTIME_PROTOCOL_VERSION
|
|
923
|
-
},
|
|
924
|
-
snapshot: serviceSnapshotFor(candidate, true),
|
|
925
|
-
handleCall: (input) => handleCall(hello.connectionId, input)
|
|
926
|
-
});
|
|
927
|
-
endpoint = { ...candidate, provider, removeMessage, closed: false };
|
|
928
|
-
endpoints.add(endpoint);
|
|
929
|
-
if (runtimeState === "ready") publishEndpoint(endpoint, true);
|
|
930
|
-
return;
|
|
931
|
-
}
|
|
932
|
-
if (!endpoint) return;
|
|
933
|
-
if (isRuntimeResync(event.data)) {
|
|
934
|
-
if (event.data.connectionId === endpoint.connectionId) publishEndpoint(endpoint, true);
|
|
935
|
-
return;
|
|
936
|
-
}
|
|
937
|
-
if (decoded?.type === codec.type("disconnect")) {
|
|
938
|
-
closeEndpoint(endpoint, typeof decoded.reason === "string" ? decoded.reason : "Window disconnected");
|
|
939
|
-
}
|
|
940
793
|
};
|
|
941
794
|
removeMessage = addPortListener(port, onMessage);
|
|
795
|
+
endpoint.removeMessage = () => {
|
|
796
|
+
removeMessage();
|
|
797
|
+
port.removeEventListener("messageerror", onMessageError);
|
|
798
|
+
};
|
|
799
|
+
port.addEventListener("messageerror", onMessageError);
|
|
942
800
|
port.start();
|
|
801
|
+
publishEndpoint(endpoint);
|
|
943
802
|
};
|
|
944
|
-
const endpointsHasConnection = (connectionId) => [...endpoints].some((endpoint) => endpoint.connectionId === connectionId);
|
|
945
|
-
let startupError;
|
|
946
803
|
workerScope.onconnect = (event) => {
|
|
804
|
+
const ports = event.ports ?? [];
|
|
805
|
+
if (!isAcceptingConnections()) {
|
|
806
|
+
for (const port of ports) rejectLateConnection(port);
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
947
809
|
try {
|
|
948
810
|
options.onPortConnect?.(event);
|
|
949
811
|
} catch (error) {
|
|
950
812
|
const detail = startupDetails(error);
|
|
951
|
-
for (const port of
|
|
952
|
-
sendError(port, "
|
|
813
|
+
for (const port of ports) {
|
|
814
|
+
sendError(port, "runtime_initialization_failed", detail.message, {
|
|
953
815
|
pluginId: detail.pluginId,
|
|
954
816
|
unitId: detail.unitId,
|
|
955
|
-
phase: "
|
|
817
|
+
phase: "startup"
|
|
956
818
|
});
|
|
957
|
-
}
|
|
958
|
-
for (const port of event.ports ?? []) {
|
|
959
819
|
try {
|
|
960
820
|
port.close();
|
|
961
821
|
} catch {
|
|
@@ -963,39 +823,37 @@ function startSharedWorkerApp(options) {
|
|
|
963
823
|
}
|
|
964
824
|
return;
|
|
965
825
|
}
|
|
966
|
-
|
|
826
|
+
if (!isAcceptingConnections()) {
|
|
827
|
+
for (const port of ports) rejectLateConnection(port);
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
for (const port of ports) attachPort(port);
|
|
967
831
|
};
|
|
968
832
|
let materialized = [];
|
|
969
833
|
let readyPromise;
|
|
970
834
|
try {
|
|
971
835
|
materialized = materializePluginDefinitions(options.plugins, "shared-worker");
|
|
972
836
|
manifests = materialized.map((item) => item.manifest);
|
|
973
|
-
const implementations = createRuntimeUnitImplementationRegistry(
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
}))
|
|
979
|
-
);
|
|
837
|
+
const implementations = createRuntimeUnitImplementationRegistry(materialized.map((item) => ({
|
|
838
|
+
pluginId: item.manifest.id,
|
|
839
|
+
unitId: item.unitId,
|
|
840
|
+
setup: item.setup
|
|
841
|
+
})));
|
|
980
842
|
const { id: _id, plugins: _plugins, globalScope: _scope, onPortConnect: _onPortConnect, ...hostOptions } = options;
|
|
981
843
|
host = createPluginHost({
|
|
982
844
|
...hostOptions,
|
|
983
845
|
runtime: "shared-worker",
|
|
984
|
-
rootAttributes: {
|
|
985
|
-
...hostOptions.rootAttributes ?? {},
|
|
986
|
-
runtimeId,
|
|
987
|
-
runtimeInstanceId
|
|
988
|
-
},
|
|
846
|
+
rootAttributes: { ...hostOptions.rootAttributes ?? {}, runtimeId, runtimeInstanceId },
|
|
989
847
|
runtimeUnitImplementationRegistry: implementations
|
|
990
848
|
});
|
|
991
849
|
host.subscribe(() => {
|
|
992
850
|
if (runtimeState !== "ready" || disposed) return;
|
|
993
851
|
revision += 1;
|
|
994
|
-
publishAll(
|
|
852
|
+
publishAll();
|
|
995
853
|
emit();
|
|
996
854
|
});
|
|
997
855
|
readyPromise = host.registerAll(manifests).then(() => {
|
|
998
|
-
if (disposed) return;
|
|
856
|
+
if (runtimeState === "stopping" || disposed) return;
|
|
999
857
|
const runtimeHost = host;
|
|
1000
858
|
if (!runtimeHost) throw new Error("SharedWorker Plugin Host is unavailable");
|
|
1001
859
|
const failedRequired = manifests.find((manifest) => {
|
|
@@ -1009,20 +867,21 @@ function startSharedWorkerApp(options) {
|
|
|
1009
867
|
unitId: state.unitId ?? failedRequired.units?.[0]?.id ?? failedRequired.id,
|
|
1010
868
|
capabilities: [],
|
|
1011
869
|
state: state.kind,
|
|
1012
|
-
error: state.error ?? `Required plugin is ${state.kind}
|
|
870
|
+
error: state.error ?? `Required plugin is ${state.kind}`
|
|
1013
871
|
});
|
|
1014
872
|
}
|
|
1015
873
|
runtimeState = "ready";
|
|
1016
874
|
revision = Math.max(1, revision + 1);
|
|
1017
|
-
publishAll(
|
|
875
|
+
publishAll();
|
|
1018
876
|
emit();
|
|
1019
877
|
}).catch((error) => {
|
|
878
|
+
if (runtimeState === "stopping" || disposed) return;
|
|
1020
879
|
startupError = error;
|
|
1021
880
|
runtimeState = "failed";
|
|
1022
881
|
emit();
|
|
1023
882
|
const detail = startupDetails(error);
|
|
1024
883
|
for (const endpoint of [...endpoints]) {
|
|
1025
|
-
sendError(endpoint.port, "
|
|
884
|
+
sendError(endpoint.port, "runtime_initialization_failed", detail.message, {
|
|
1026
885
|
pluginId: detail.pluginId,
|
|
1027
886
|
unitId: detail.unitId,
|
|
1028
887
|
phase: "startup"
|
|
@@ -1041,6 +900,7 @@ function startSharedWorkerApp(options) {
|
|
|
1041
900
|
runtimeState = "failed";
|
|
1042
901
|
emit();
|
|
1043
902
|
readyPromise = Promise.reject(error instanceof RuntimeInitializationError ? error : new RuntimeInitializationError({ phase: "validate", error: errorMessage3(error) }));
|
|
903
|
+
void readyPromise.catch(() => void 0);
|
|
1044
904
|
}
|
|
1045
905
|
const app = {
|
|
1046
906
|
runtimeKind: "shared-worker",
|
|
@@ -1052,7 +912,7 @@ function startSharedWorkerApp(options) {
|
|
|
1052
912
|
await readyPromise;
|
|
1053
913
|
return;
|
|
1054
914
|
}
|
|
1055
|
-
if (runtimeState === "failed" || runtimeState === "disposed" || disposed) {
|
|
915
|
+
if (runtimeState === "failed" || runtimeState === "stopping" || runtimeState === "disposed" || disposed) {
|
|
1056
916
|
await readyPromise;
|
|
1057
917
|
return;
|
|
1058
918
|
}
|
|
@@ -1066,14 +926,35 @@ function startSharedWorkerApp(options) {
|
|
|
1066
926
|
},
|
|
1067
927
|
dispose(reason = "shared worker runtime disposed") {
|
|
1068
928
|
if (disposePromise) return disposePromise;
|
|
1069
|
-
|
|
929
|
+
acceptingConnections = false;
|
|
1070
930
|
runtimeState = "stopping";
|
|
931
|
+
revision += 1;
|
|
932
|
+
publishAll();
|
|
1071
933
|
emit();
|
|
1072
934
|
disposePromise = (async () => {
|
|
1073
|
-
|
|
1074
|
-
|
|
935
|
+
let result;
|
|
936
|
+
let failure;
|
|
937
|
+
try {
|
|
938
|
+
result = host ? await host.dispose(reason) : { scopeId: `runtime:${runtimeInstanceId}`, state: "stopped", attempted: 0, released: 0, pending: [], errors: [], cleanupIncomplete: false };
|
|
939
|
+
} catch (error) {
|
|
940
|
+
failure = error;
|
|
941
|
+
result = {
|
|
942
|
+
scopeId: `runtime:${runtimeInstanceId}`,
|
|
943
|
+
state: "stopped",
|
|
944
|
+
attempted: 0,
|
|
945
|
+
released: 0,
|
|
946
|
+
pending: [],
|
|
947
|
+
errors: [{ resourceId: "runtime.dispose", code: "lifecycle.cleanup_failed", message: errorMessage3(error) }],
|
|
948
|
+
cleanupIncomplete: true
|
|
949
|
+
};
|
|
950
|
+
}
|
|
1075
951
|
runtimeState = "disposed";
|
|
952
|
+
revision += 1;
|
|
953
|
+
publishAll();
|
|
1076
954
|
emit();
|
|
955
|
+
disposed = true;
|
|
956
|
+
for (const endpoint of [...endpoints]) closeEndpoint(endpoint);
|
|
957
|
+
if (failure) throw failure;
|
|
1077
958
|
return result;
|
|
1078
959
|
})();
|
|
1079
960
|
return disposePromise;
|
|
@@ -1082,495 +963,6 @@ function startSharedWorkerApp(options) {
|
|
|
1082
963
|
return app;
|
|
1083
964
|
}
|
|
1084
965
|
|
|
1085
|
-
// src/runtime/connectSharedWorker.ts
|
|
1086
|
-
function makeConnectionId(runtimeId) {
|
|
1087
|
-
try {
|
|
1088
|
-
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
1089
|
-
return `${runtimeId}:connection:${crypto.randomUUID()}`;
|
|
1090
|
-
}
|
|
1091
|
-
} catch {
|
|
1092
|
-
}
|
|
1093
|
-
return `${runtimeId}:connection:${Date.now().toString(36)}:${Math.random().toString(36).slice(2)}`;
|
|
1094
|
-
}
|
|
1095
|
-
function errorMessage4(error) {
|
|
1096
|
-
return error instanceof Error ? error.message : String(error);
|
|
1097
|
-
}
|
|
1098
|
-
function createDeferred() {
|
|
1099
|
-
let resolvePromise;
|
|
1100
|
-
let rejectPromise;
|
|
1101
|
-
const deferred = {
|
|
1102
|
-
promise: new Promise((resolve, reject) => {
|
|
1103
|
-
resolvePromise = resolve;
|
|
1104
|
-
rejectPromise = reject;
|
|
1105
|
-
}),
|
|
1106
|
-
settled: false,
|
|
1107
|
-
resolve(value) {
|
|
1108
|
-
if (deferred.settled) return;
|
|
1109
|
-
deferred.settled = true;
|
|
1110
|
-
resolvePromise(value);
|
|
1111
|
-
},
|
|
1112
|
-
reject(error) {
|
|
1113
|
-
if (deferred.settled) return;
|
|
1114
|
-
deferred.settled = true;
|
|
1115
|
-
rejectPromise(error);
|
|
1116
|
-
}
|
|
1117
|
-
};
|
|
1118
|
-
return deferred;
|
|
1119
|
-
}
|
|
1120
|
-
function post3(port, message) {
|
|
1121
|
-
try {
|
|
1122
|
-
port.postMessage(message);
|
|
1123
|
-
} catch {
|
|
1124
|
-
}
|
|
1125
|
-
}
|
|
1126
|
-
function addMessageListener(port, listener) {
|
|
1127
|
-
const before = port.onmessage;
|
|
1128
|
-
port.addEventListener("message", listener);
|
|
1129
|
-
if (port.onmessage === listener && before && before !== listener) {
|
|
1130
|
-
port.removeEventListener("message", listener);
|
|
1131
|
-
const composed = (event) => {
|
|
1132
|
-
before.call(port, event);
|
|
1133
|
-
listener(event);
|
|
1134
|
-
};
|
|
1135
|
-
port.addEventListener("message", composed);
|
|
1136
|
-
return () => port.removeEventListener("message", composed);
|
|
1137
|
-
}
|
|
1138
|
-
return () => port.removeEventListener("message", listener);
|
|
1139
|
-
}
|
|
1140
|
-
function ensureMessageEventTarget(port) {
|
|
1141
|
-
const target = port;
|
|
1142
|
-
const add = target.addEventListener;
|
|
1143
|
-
const remove = target.removeEventListener;
|
|
1144
|
-
if (add && remove) {
|
|
1145
|
-
const before = target.onmessage;
|
|
1146
|
-
const probe = () => void 0;
|
|
1147
|
-
add.call(port, "message", probe);
|
|
1148
|
-
const overwritesProperty = target.onmessage === probe;
|
|
1149
|
-
remove.call(port, "message", probe);
|
|
1150
|
-
if (!overwritesProperty) return;
|
|
1151
|
-
const listeners2 = /* @__PURE__ */ new Set();
|
|
1152
|
-
const dispatch2 = (event) => {
|
|
1153
|
-
before?.call(port, event);
|
|
1154
|
-
for (const listener of [...listeners2]) listener(event);
|
|
1155
|
-
};
|
|
1156
|
-
target.addEventListener = function addMessageListener2(type, listener) {
|
|
1157
|
-
if (type === "message") listeners2.add(listener);
|
|
1158
|
-
else add.call(port, type, listener);
|
|
1159
|
-
};
|
|
1160
|
-
target.removeEventListener = function removeMessageListener(type, listener) {
|
|
1161
|
-
if (type === "message") listeners2.delete(listener);
|
|
1162
|
-
else remove.call(port, type, listener);
|
|
1163
|
-
};
|
|
1164
|
-
target.onmessage = dispatch2;
|
|
1165
|
-
return;
|
|
1166
|
-
}
|
|
1167
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
1168
|
-
const original = target.onmessage;
|
|
1169
|
-
const dispatch = (event) => {
|
|
1170
|
-
original?.call(port, event);
|
|
1171
|
-
for (const listener of [...listeners]) listener(event);
|
|
1172
|
-
};
|
|
1173
|
-
target.addEventListener = function addMessageListener2(type, listener) {
|
|
1174
|
-
if (type === "message") listeners.add(listener);
|
|
1175
|
-
};
|
|
1176
|
-
target.removeEventListener = function removeMessageListener(type, listener) {
|
|
1177
|
-
if (type === "message") listeners.delete(listener);
|
|
1178
|
-
};
|
|
1179
|
-
target.onmessage = dispatch;
|
|
1180
|
-
}
|
|
1181
|
-
function makeWorker(options) {
|
|
1182
|
-
const workerOptions = {
|
|
1183
|
-
type: "module",
|
|
1184
|
-
...options.name ? { name: options.name } : {}
|
|
1185
|
-
};
|
|
1186
|
-
if (options.workerFactory) return options.workerFactory(options.url, workerOptions);
|
|
1187
|
-
const WorkerConstructor = globalThis.SharedWorker;
|
|
1188
|
-
if (!WorkerConstructor) throw new RuntimeUnavailableError("SharedWorker is not supported by this browser");
|
|
1189
|
-
return new WorkerConstructor(options.url, workerOptions);
|
|
1190
|
-
}
|
|
1191
|
-
async function connectSharedWorker(options) {
|
|
1192
|
-
if (!options || typeof options.id !== "string" || options.id.trim() === "") {
|
|
1193
|
-
throw new Error("SharedWorker runtime id must be a non-empty string");
|
|
1194
|
-
}
|
|
1195
|
-
const codec = createRuntimeMessageCodec();
|
|
1196
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
1197
|
-
const autoReconnect = options.autoReconnect ?? true;
|
|
1198
|
-
const reconnectDelayMs = Math.max(0, options.reconnectDelayMs ?? 25);
|
|
1199
|
-
const handshakeTimeoutMs = Math.max(1, options.handshakeTimeoutMs ?? 1e4);
|
|
1200
|
-
let disposed = false;
|
|
1201
|
-
let currentWorker;
|
|
1202
|
-
let currentPort;
|
|
1203
|
-
let currentTransport;
|
|
1204
|
-
let removeMessage;
|
|
1205
|
-
let removeWorkerError;
|
|
1206
|
-
let handshakeTimer;
|
|
1207
|
-
let reconnectTimer;
|
|
1208
|
-
let connectionGeneration = 0;
|
|
1209
|
-
let currentConnection;
|
|
1210
|
-
let readyDeferred = createDeferred();
|
|
1211
|
-
let initialConnection = true;
|
|
1212
|
-
let runtimeInstanceId;
|
|
1213
|
-
let connectionId;
|
|
1214
|
-
let currentSnapshot = {
|
|
1215
|
-
runtimeId: options.id,
|
|
1216
|
-
runtimeKind: "shared-worker",
|
|
1217
|
-
runtimeInstanceId: "",
|
|
1218
|
-
state: "connecting",
|
|
1219
|
-
snapshotRevision: 0,
|
|
1220
|
-
units: [],
|
|
1221
|
-
services: []
|
|
1222
|
-
};
|
|
1223
|
-
const bridgeTransport = {
|
|
1224
|
-
call(request, context) {
|
|
1225
|
-
if (!currentTransport) return Promise.reject(new RuntimeUnavailableError("SharedWorker connection is unavailable"));
|
|
1226
|
-
return currentTransport.call(request, context);
|
|
1227
|
-
}
|
|
1228
|
-
};
|
|
1229
|
-
const bridge = createServiceBridge({ protocolVersion: RUNTIME_PROTOCOL_VERSION, transport: bridgeTransport });
|
|
1230
|
-
const emit = (next) => {
|
|
1231
|
-
currentSnapshot = Object.freeze({
|
|
1232
|
-
...next,
|
|
1233
|
-
units: Object.freeze([...next.units]),
|
|
1234
|
-
services: Object.freeze([...next.services])
|
|
1235
|
-
});
|
|
1236
|
-
for (const listener of [...listeners]) {
|
|
1237
|
-
try {
|
|
1238
|
-
listener(currentSnapshot);
|
|
1239
|
-
} catch {
|
|
1240
|
-
}
|
|
1241
|
-
}
|
|
1242
|
-
};
|
|
1243
|
-
const rejectReady = (error) => {
|
|
1244
|
-
readyDeferred.reject(error);
|
|
1245
|
-
void readyDeferred.promise.catch(() => void 0);
|
|
1246
|
-
};
|
|
1247
|
-
const beginReadinessGeneration = (reason) => {
|
|
1248
|
-
rejectReady(new RuntimeUnavailableError(reason));
|
|
1249
|
-
readyDeferred = createDeferred();
|
|
1250
|
-
};
|
|
1251
|
-
const replaceWithRejectedReadiness = (error) => {
|
|
1252
|
-
rejectReady(error);
|
|
1253
|
-
readyDeferred = createDeferred();
|
|
1254
|
-
rejectReady(error);
|
|
1255
|
-
};
|
|
1256
|
-
const scheduleReconnect = () => {
|
|
1257
|
-
if (disposed || !autoReconnect || reconnectTimer !== void 0) return;
|
|
1258
|
-
reconnectTimer = setTimeout(() => {
|
|
1259
|
-
reconnectTimer = void 0;
|
|
1260
|
-
void openConnection().catch((error) => {
|
|
1261
|
-
if (disposed) return;
|
|
1262
|
-
if (currentSnapshot.state !== "disconnected") {
|
|
1263
|
-
emit({ ...currentSnapshot, state: "disconnected", error: errorMessage4(error), units: [], services: [] });
|
|
1264
|
-
}
|
|
1265
|
-
rejectReady(error);
|
|
1266
|
-
beginReadinessGeneration("SharedWorker reconnecting");
|
|
1267
|
-
scheduleReconnect();
|
|
1268
|
-
});
|
|
1269
|
-
}, reconnectDelayMs);
|
|
1270
|
-
};
|
|
1271
|
-
const cleanupConnection = (connection) => {
|
|
1272
|
-
if (!connection || connection.cleaned) return;
|
|
1273
|
-
connection.cleaned = true;
|
|
1274
|
-
const timer = connection.handshakeTimer;
|
|
1275
|
-
const removeMessageForConnection = connection.removeMessage;
|
|
1276
|
-
const removeWorkerErrorForConnection = connection.removeWorkerError;
|
|
1277
|
-
const transport = connection.transport;
|
|
1278
|
-
if (timer !== void 0) clearTimeout(timer);
|
|
1279
|
-
connection.handshakeTimer = void 0;
|
|
1280
|
-
removeMessageForConnection?.();
|
|
1281
|
-
connection.removeMessage = void 0;
|
|
1282
|
-
removeWorkerErrorForConnection?.();
|
|
1283
|
-
connection.removeWorkerError = void 0;
|
|
1284
|
-
transport?.dispose();
|
|
1285
|
-
connection.transport = void 0;
|
|
1286
|
-
if (currentConnection === connection) currentConnection = void 0;
|
|
1287
|
-
if (currentWorker === connection.worker) currentWorker = void 0;
|
|
1288
|
-
if (currentPort === connection.port) currentPort = void 0;
|
|
1289
|
-
if (currentTransport === transport) currentTransport = void 0;
|
|
1290
|
-
if (removeMessage === removeMessageForConnection) removeMessage = void 0;
|
|
1291
|
-
if (removeWorkerError === removeWorkerErrorForConnection) removeWorkerError = void 0;
|
|
1292
|
-
if (handshakeTimer === timer) handshakeTimer = void 0;
|
|
1293
|
-
try {
|
|
1294
|
-
connection.port.close();
|
|
1295
|
-
} catch {
|
|
1296
|
-
}
|
|
1297
|
-
};
|
|
1298
|
-
const disconnect = (reason, failure, fatal = false) => {
|
|
1299
|
-
if (disposed) return;
|
|
1300
|
-
const connection = currentConnection;
|
|
1301
|
-
connection?.port ?? currentPort;
|
|
1302
|
-
cleanupConnection(connection);
|
|
1303
|
-
currentTransport = void 0;
|
|
1304
|
-
currentWorker = void 0;
|
|
1305
|
-
currentPort = void 0;
|
|
1306
|
-
connectionId = void 0;
|
|
1307
|
-
bridge.disconnect(reason);
|
|
1308
|
-
emit({
|
|
1309
|
-
...currentSnapshot,
|
|
1310
|
-
state: fatal ? "failed" : "disconnected",
|
|
1311
|
-
error: failure ? errorMessage4(failure) : reason,
|
|
1312
|
-
runtimeInstanceId: "",
|
|
1313
|
-
connectionId: void 0,
|
|
1314
|
-
units: [],
|
|
1315
|
-
services: []
|
|
1316
|
-
});
|
|
1317
|
-
runtimeInstanceId = void 0;
|
|
1318
|
-
const unavailable = failure ?? new RuntimeUnavailableError(reason);
|
|
1319
|
-
if (!initialConnection && !fatal && autoReconnect) {
|
|
1320
|
-
beginReadinessGeneration("SharedWorker reconnecting");
|
|
1321
|
-
scheduleReconnect();
|
|
1322
|
-
} else {
|
|
1323
|
-
replaceWithRejectedReadiness(unavailable);
|
|
1324
|
-
}
|
|
1325
|
-
};
|
|
1326
|
-
const onRuntimeError = (message) => {
|
|
1327
|
-
const error = new RuntimeInitializationError({
|
|
1328
|
-
pluginId: message.pluginId,
|
|
1329
|
-
unitId: message.unitId,
|
|
1330
|
-
phase: message.phase === "handshake" ? "handshake" : "startup",
|
|
1331
|
-
error: message.message
|
|
1332
|
-
});
|
|
1333
|
-
const fatal = message.code === "runtime.protocol_mismatch" || message.code === "runtime.initialization_failed";
|
|
1334
|
-
disconnect(message.message, error, fatal);
|
|
1335
|
-
};
|
|
1336
|
-
const onSnapshot = (snapshot) => {
|
|
1337
|
-
if (snapshot.runtimeId !== options.id || snapshot.connectionId !== connectionId) return;
|
|
1338
|
-
if (snapshot.runtimeKind !== "shared-worker") {
|
|
1339
|
-
onRuntimeError({
|
|
1340
|
-
code: "runtime.protocol_mismatch",
|
|
1341
|
-
message: "Connected Runtime is not a SharedWorker Runtime",
|
|
1342
|
-
phase: "handshake"
|
|
1343
|
-
});
|
|
1344
|
-
return;
|
|
1345
|
-
}
|
|
1346
|
-
const isNewAuthority = runtimeInstanceId !== snapshot.runtimeInstanceId;
|
|
1347
|
-
if (isNewAuthority || bridge.state === "disconnected") {
|
|
1348
|
-
const result = bridge.handshake({
|
|
1349
|
-
connectionId: snapshot.connectionId,
|
|
1350
|
-
authorityInstanceId: snapshot.runtimeInstanceId,
|
|
1351
|
-
protocolVersion: snapshot.protocolVersion
|
|
1352
|
-
});
|
|
1353
|
-
if (!result.accepted) {
|
|
1354
|
-
onRuntimeError({
|
|
1355
|
-
code: "runtime.protocol_mismatch",
|
|
1356
|
-
message: "Runtime protocol version mismatch",
|
|
1357
|
-
phase: "handshake"
|
|
1358
|
-
});
|
|
1359
|
-
return;
|
|
1360
|
-
}
|
|
1361
|
-
}
|
|
1362
|
-
runtimeInstanceId = snapshot.runtimeInstanceId;
|
|
1363
|
-
const applied = bridge.applySnapshot({
|
|
1364
|
-
connectionId: snapshot.connectionId,
|
|
1365
|
-
authorityInstanceId: snapshot.runtimeInstanceId,
|
|
1366
|
-
snapshotRevision: snapshot.snapshotRevision,
|
|
1367
|
-
baseline: snapshot.baseline,
|
|
1368
|
-
services: snapshot.services
|
|
1369
|
-
});
|
|
1370
|
-
if (!applied.accepted) {
|
|
1371
|
-
if (applied.reason === "revision-gap" || applied.reason === "baseline-required") {
|
|
1372
|
-
beginReadinessGeneration(`Runtime snapshot ${applied.reason}`);
|
|
1373
|
-
emit({
|
|
1374
|
-
...currentSnapshot,
|
|
1375
|
-
state: "disconnected",
|
|
1376
|
-
error: `Runtime snapshot ${applied.reason}`,
|
|
1377
|
-
units: [],
|
|
1378
|
-
services: []
|
|
1379
|
-
});
|
|
1380
|
-
const port = currentPort;
|
|
1381
|
-
if (port && connectionId) post3(port, { type: "webloom.runtime.resync", connectionId });
|
|
1382
|
-
}
|
|
1383
|
-
return;
|
|
1384
|
-
}
|
|
1385
|
-
emit({
|
|
1386
|
-
runtimeId: snapshot.runtimeId,
|
|
1387
|
-
runtimeKind: snapshot.runtimeKind,
|
|
1388
|
-
runtimeInstanceId: snapshot.runtimeInstanceId,
|
|
1389
|
-
state: snapshot.state === "ready" ? "ready" : snapshot.state,
|
|
1390
|
-
snapshotRevision: snapshot.snapshotRevision,
|
|
1391
|
-
connectionId: snapshot.connectionId,
|
|
1392
|
-
units: snapshot.units,
|
|
1393
|
-
services: snapshot.services
|
|
1394
|
-
});
|
|
1395
|
-
if (snapshot.baseline && snapshot.state === "ready" && bridge.state === "ready") {
|
|
1396
|
-
if (handshakeTimer !== void 0) clearTimeout(handshakeTimer);
|
|
1397
|
-
handshakeTimer = void 0;
|
|
1398
|
-
readyDeferred.resolve(void 0);
|
|
1399
|
-
initialConnection = false;
|
|
1400
|
-
}
|
|
1401
|
-
};
|
|
1402
|
-
async function openConnection() {
|
|
1403
|
-
if (disposed) return;
|
|
1404
|
-
const generation = ++connectionGeneration;
|
|
1405
|
-
if (readyDeferred.settled) readyDeferred = createDeferred();
|
|
1406
|
-
let connection;
|
|
1407
|
-
try {
|
|
1408
|
-
const worker = makeWorker(options);
|
|
1409
|
-
const port = worker.port;
|
|
1410
|
-
const nextConnectionId = makeConnectionId(options.id);
|
|
1411
|
-
connection = {
|
|
1412
|
-
generation,
|
|
1413
|
-
worker,
|
|
1414
|
-
port,
|
|
1415
|
-
connectionId: nextConnectionId,
|
|
1416
|
-
cleaned: false
|
|
1417
|
-
};
|
|
1418
|
-
currentConnection = connection;
|
|
1419
|
-
currentWorker = worker;
|
|
1420
|
-
currentPort = port;
|
|
1421
|
-
connectionId = nextConnectionId;
|
|
1422
|
-
emit({
|
|
1423
|
-
...currentSnapshot,
|
|
1424
|
-
state: "connecting",
|
|
1425
|
-
connectionId: nextConnectionId,
|
|
1426
|
-
error: void 0
|
|
1427
|
-
});
|
|
1428
|
-
await options.onConnection?.({
|
|
1429
|
-
worker,
|
|
1430
|
-
port,
|
|
1431
|
-
connectionId: nextConnectionId
|
|
1432
|
-
});
|
|
1433
|
-
if (disposed || currentConnection !== connection) {
|
|
1434
|
-
cleanupConnection(connection);
|
|
1435
|
-
return;
|
|
1436
|
-
}
|
|
1437
|
-
ensureMessageEventTarget(port);
|
|
1438
|
-
connection.transport = createMessagePortServiceTransport({ port, codec });
|
|
1439
|
-
currentTransport = connection.transport;
|
|
1440
|
-
const onWorkerError = (event) => {
|
|
1441
|
-
if (disposed || generation !== connectionGeneration || worker !== currentWorker) return;
|
|
1442
|
-
disconnect(`SharedWorker error${event.type ? `: ${event.type}` : ""}`);
|
|
1443
|
-
};
|
|
1444
|
-
if (worker.addEventListener) {
|
|
1445
|
-
worker.addEventListener("error", onWorkerError);
|
|
1446
|
-
connection.removeWorkerError = () => worker.removeEventListener?.("error", onWorkerError);
|
|
1447
|
-
removeWorkerError = connection.removeWorkerError;
|
|
1448
|
-
} else {
|
|
1449
|
-
const workerWithOnError = worker;
|
|
1450
|
-
const previousOnError = workerWithOnError.onerror;
|
|
1451
|
-
const composedOnError = (event) => {
|
|
1452
|
-
previousOnError?.(event);
|
|
1453
|
-
onWorkerError(event);
|
|
1454
|
-
};
|
|
1455
|
-
workerWithOnError.onerror = composedOnError;
|
|
1456
|
-
connection.removeWorkerError = () => {
|
|
1457
|
-
if (workerWithOnError.onerror === composedOnError) workerWithOnError.onerror = previousOnError;
|
|
1458
|
-
};
|
|
1459
|
-
removeWorkerError = connection.removeWorkerError;
|
|
1460
|
-
}
|
|
1461
|
-
const onMessage = (event) => {
|
|
1462
|
-
if (disposed || generation !== connectionGeneration || port !== currentPort) return;
|
|
1463
|
-
if (isRuntimeError(event.data)) {
|
|
1464
|
-
onRuntimeError(event.data);
|
|
1465
|
-
return;
|
|
1466
|
-
}
|
|
1467
|
-
if (isSnapshot(event.data)) {
|
|
1468
|
-
onSnapshot(event.data);
|
|
1469
|
-
return;
|
|
1470
|
-
}
|
|
1471
|
-
const decoded = codec.decode(event.data);
|
|
1472
|
-
if (decoded?.type === codec.type("disconnect")) {
|
|
1473
|
-
disconnect(typeof decoded.reason === "string" ? decoded.reason : "SharedWorker disconnected");
|
|
1474
|
-
}
|
|
1475
|
-
};
|
|
1476
|
-
connection.removeMessage = addMessageListener(port, onMessage);
|
|
1477
|
-
removeMessage = connection.removeMessage;
|
|
1478
|
-
port.start();
|
|
1479
|
-
const timer = setTimeout(() => {
|
|
1480
|
-
if (disposed || generation !== connectionGeneration || port !== currentPort) return;
|
|
1481
|
-
const error = new RuntimeInitializationError({
|
|
1482
|
-
phase: "handshake",
|
|
1483
|
-
error: `SharedWorker handshake timed out after ${handshakeTimeoutMs}ms`
|
|
1484
|
-
});
|
|
1485
|
-
disconnect(error.message, error, false);
|
|
1486
|
-
}, handshakeTimeoutMs);
|
|
1487
|
-
connection.handshakeTimer = timer;
|
|
1488
|
-
handshakeTimer = timer;
|
|
1489
|
-
post3(port, {
|
|
1490
|
-
type: RUNTIME_HELLO_TYPE,
|
|
1491
|
-
protocolVersion: RUNTIME_PROTOCOL_VERSION,
|
|
1492
|
-
connectionId: nextConnectionId,
|
|
1493
|
-
runtimeId: options.id
|
|
1494
|
-
});
|
|
1495
|
-
await readyDeferred.promise;
|
|
1496
|
-
} catch (error) {
|
|
1497
|
-
if (connection && !connection.cleaned && currentConnection === connection) {
|
|
1498
|
-
disconnect("SharedWorker connection setup failed", error, false);
|
|
1499
|
-
} else if (connection && !connection.cleaned) {
|
|
1500
|
-
cleanupConnection(connection);
|
|
1501
|
-
} else if (!connection && !disposed && initialConnection) {
|
|
1502
|
-
replaceWithRejectedReadiness(error);
|
|
1503
|
-
}
|
|
1504
|
-
throw error;
|
|
1505
|
-
}
|
|
1506
|
-
}
|
|
1507
|
-
function isSnapshot(input) {
|
|
1508
|
-
return isRuntimeSnapshot(input);
|
|
1509
|
-
}
|
|
1510
|
-
const app = {
|
|
1511
|
-
runtimeKind: "shared-worker",
|
|
1512
|
-
runtimeId: options.id,
|
|
1513
|
-
serviceBridge: bridge,
|
|
1514
|
-
get runtimeInstanceId() {
|
|
1515
|
-
return runtimeInstanceId;
|
|
1516
|
-
},
|
|
1517
|
-
get connectionId() {
|
|
1518
|
-
return connectionId;
|
|
1519
|
-
},
|
|
1520
|
-
state: () => currentSnapshot,
|
|
1521
|
-
ready: () => readyDeferred.promise,
|
|
1522
|
-
capability(capabilityId, capabilityOptions = {}) {
|
|
1523
|
-
if (disposed || currentSnapshot.state !== "ready") {
|
|
1524
|
-
throw new RuntimeUnavailableError(`Capability "${capabilityId}" is not ready`);
|
|
1525
|
-
}
|
|
1526
|
-
return bridge.requireProxy({
|
|
1527
|
-
capabilityId,
|
|
1528
|
-
contractVersion: capabilityOptions.contractVersion ?? `${capabilityId}.v1`
|
|
1529
|
-
});
|
|
1530
|
-
},
|
|
1531
|
-
subscribe(listener) {
|
|
1532
|
-
listeners.add(listener);
|
|
1533
|
-
listener(currentSnapshot);
|
|
1534
|
-
return () => listeners.delete(listener);
|
|
1535
|
-
},
|
|
1536
|
-
dispose(reason = "SharedWorker connection disposed") {
|
|
1537
|
-
if (disposed) return Promise.resolve();
|
|
1538
|
-
disposed = true;
|
|
1539
|
-
if (reconnectTimer !== void 0) clearTimeout(reconnectTimer);
|
|
1540
|
-
replaceWithRejectedReadiness(new RuntimeUnavailableError(reason));
|
|
1541
|
-
const connection = currentConnection;
|
|
1542
|
-
const port = connection?.port ?? currentPort;
|
|
1543
|
-
if (port) post3(port, codec.encode({ type: codec.type("disconnect"), reason }));
|
|
1544
|
-
cleanupConnection(connection);
|
|
1545
|
-
currentWorker = void 0;
|
|
1546
|
-
currentPort = void 0;
|
|
1547
|
-
connectionId = void 0;
|
|
1548
|
-
runtimeInstanceId = void 0;
|
|
1549
|
-
currentTransport?.dispose();
|
|
1550
|
-
currentTransport = void 0;
|
|
1551
|
-
bridge.disconnect(reason);
|
|
1552
|
-
emit({
|
|
1553
|
-
...currentSnapshot,
|
|
1554
|
-
state: "disposed",
|
|
1555
|
-
runtimeInstanceId: "",
|
|
1556
|
-
connectionId: void 0,
|
|
1557
|
-
units: [],
|
|
1558
|
-
services: []
|
|
1559
|
-
});
|
|
1560
|
-
return Promise.resolve();
|
|
1561
|
-
}
|
|
1562
|
-
};
|
|
1563
|
-
try {
|
|
1564
|
-
await openConnection();
|
|
1565
|
-
} catch (error) {
|
|
1566
|
-
if (!disposed) {
|
|
1567
|
-
emit({ ...currentSnapshot, state: "failed", error: errorMessage4(error) });
|
|
1568
|
-
}
|
|
1569
|
-
throw error;
|
|
1570
|
-
}
|
|
1571
|
-
return app;
|
|
1572
|
-
}
|
|
1573
|
-
|
|
1574
966
|
// src/lifecycle/permissionVerifier.ts
|
|
1575
967
|
function verifyPermissionLease(options) {
|
|
1576
968
|
options.lease.assert(options.permission);
|
|
@@ -1988,6 +1380,6 @@ function createUpgradeGate(options) {
|
|
|
1988
1380
|
};
|
|
1989
1381
|
}
|
|
1990
1382
|
|
|
1991
|
-
export {
|
|
1383
|
+
export { createMessagePortServiceProvider, createScopedRegistryFacade, createUpgradeGate, createWindowApp, definePlugin, defineRuntimeUnitDependencies, defineRuntimeUnitProvidedContracts, materializePluginDefinition, materializePluginDefinitions, runtimeCapabilityContractVersion, startSharedWorkerApp, verifyPermissionLease };
|
|
1992
1384
|
//# sourceMappingURL=index.js.map
|
|
1993
1385
|
//# sourceMappingURL=index.js.map
|