webloom-framework 0.1.0 → 0.2.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 +83 -36
- package/dist/{chunk-KGNF36DZ.js → chunk-URY3E6UH.js} +333 -267
- package/dist/chunk-URY3E6UH.js.map +1 -0
- package/dist/{createPluginHost-CcW9sPNs.d.ts → createPluginHost-ChJNBTsX.d.ts} +76 -77
- package/dist/index.d.ts +300 -10
- package/dist/index.js +1556 -146
- package/dist/index.js.map +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/{resourceRegistry-BAnqKcp7.d.ts → resourceRegistry-MNM1c7od.d.ts} +1 -1
- package/dist/testing.d.ts +3 -3
- package/dist/testing.js +2 -2
- package/docs/api.md +156 -14
- package/docs/migration-baseline.md +20 -12
- package/docs/proposals/browser-runtime-v1/implementation-plan.md +527 -0
- package/docs/proposals/browser-runtime-v1/requirements.md +349 -0
- package/docs/proposals/browser-runtime-v1/verification.md +49 -0
- package/package.json +4 -1
- package/dist/chunk-KGNF36DZ.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
import { createRemoteServiceMessageCodec, UpgradeGateRejectedError } from './chunk-
|
|
2
|
-
export { LIFECYCLE_ERROR_TEXT, LifecycleScopeRevokedError, PermissionDeniedError, PermissionLeaseRevokedError, PluginGraphValidationError, RESOURCE_OWNER, RESOURCE_REGISTRY_CAPABILITY, RUNTIME_MESSAGE_BUS, RemoteServiceUnavailableError, SCOPED_TASK_SCHEDULER_CAPABILITY, StartupCapabilityError, StartupPluginError, UpgradeGateRejectedError, buildPluginGraph, createCapabilityRegistry, createInMemoryPluginConfigStore, createLifecycleScope, createMessageBus, createMessagePortServiceTransport, createPermissionLease, createPluginHost, createPluginIntentController, createRemoteServiceBridge, createRemoteServiceMessageCodec, createResourceRegistry, createResourceScope, createResourceStore, createRuntimeUnitImplementationRegistry, createScopedMessageBus, createScopedTaskScheduler, createServiceBridge, dependenciesOfManifest, lifecycleErrorText, providesOfManifest, registerOwnedResource, reverseDependentsOf, validatePluginGraph, validateRuntimeUnitDependencyContracts } from './chunk-
|
|
1
|
+
import { createRemoteServiceMessageCodec, createRuntimeUnitImplementationRegistry, createPluginHost, StartupPluginError, createServiceBridge, createMessagePortServiceTransport, UpgradeGateRejectedError } from './chunk-URY3E6UH.js';
|
|
2
|
+
export { LIFECYCLE_ERROR_TEXT, LifecycleScopeRevokedError, PermissionDeniedError, PermissionLeaseRevokedError, PluginGraphValidationError, RESOURCE_OWNER, RESOURCE_REGISTRY_CAPABILITY, RUNTIME_MESSAGE_BUS, RemoteServiceUnavailableError, SCOPED_TASK_SCHEDULER_CAPABILITY, StartupCapabilityError, StartupPluginError, UpgradeGateRejectedError, buildPluginGraph, createCapabilityRegistry, createInMemoryPluginConfigStore, createLifecycleScope, createMessageBus, createMessagePortServiceTransport, createPermissionLease, createPluginHost, createPluginIntentController, createRemoteServiceBridge, createRemoteServiceMessageCodec, createResourceRegistry, createResourceScope, createResourceStore, createRuntimeUnitImplementationRegistry, createScopedMessageBus, createScopedTaskScheduler, createServiceBridge, dependenciesOfManifest, lifecycleErrorText, providesOfManifest, registerOwnedResource, reverseDependentsOf, validatePluginGraph, validateRuntimeUnitDependencyContracts } from './chunk-URY3E6UH.js';
|
|
3
3
|
|
|
4
4
|
// src/contracts/plugin.ts
|
|
5
5
|
function runtimeCapabilityContractVersion(capability) {
|
|
6
6
|
return `${capability}.v1`;
|
|
7
7
|
}
|
|
8
|
-
function defineRuntimeUnitDependencies(dependencies, defaults
|
|
8
|
+
function defineRuntimeUnitDependencies(dependencies, defaults) {
|
|
9
9
|
return dependencies.map((dependency) => ({
|
|
10
10
|
capability: dependency.capability,
|
|
11
11
|
contractVersion: runtimeCapabilityContractVersion(dependency.capability),
|
|
12
|
-
|
|
13
|
-
scope: defaults.scope ?? "root",
|
|
12
|
+
sourceRuntime: defaults.sourceRuntime,
|
|
14
13
|
...dependency.reason !== void 0 ? { reason: dependency.reason } : {},
|
|
15
14
|
...dependency.optional !== void 0 ? { optional: dependency.optional } : {}
|
|
16
15
|
}));
|
|
@@ -21,6 +20,1557 @@ function defineRuntimeUnitProvidedContracts(capabilities) {
|
|
|
21
20
|
);
|
|
22
21
|
}
|
|
23
22
|
|
|
23
|
+
// src/authoring/definePlugin.ts
|
|
24
|
+
function normalizeDependency(dependency) {
|
|
25
|
+
const contractVersion = dependency.contractVersion ?? runtimeCapabilityContractVersion(dependency.capability);
|
|
26
|
+
return {
|
|
27
|
+
capability: dependency.capability,
|
|
28
|
+
contractVersion,
|
|
29
|
+
...dependency.sourceRuntime !== void 0 ? { sourceRuntime: dependency.sourceRuntime } : {},
|
|
30
|
+
...dependency.reason !== void 0 ? { reason: dependency.reason } : {},
|
|
31
|
+
...dependency.optional !== void 0 ? { optional: dependency.optional } : {}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function definePlugin(options) {
|
|
35
|
+
if (!options || typeof options.id !== "string" || options.id.trim() === "") {
|
|
36
|
+
throw new Error("Plugin id must be a non-empty string");
|
|
37
|
+
}
|
|
38
|
+
if (typeof options.setup !== "function") throw new Error(`Plugin "${options.id}" setup must be a function`);
|
|
39
|
+
const unitId = options.unitId ?? options.id;
|
|
40
|
+
if (unitId.trim() === "") throw new Error(`Plugin "${options.id}" unitId must be a non-empty string`);
|
|
41
|
+
const provides = [...new Set(options.provides ?? [])];
|
|
42
|
+
const dependencies = (options.dependencies ?? []).map(normalizeDependency);
|
|
43
|
+
const providedContracts = {
|
|
44
|
+
...defineRuntimeUnitProvidedContracts(provides),
|
|
45
|
+
...options.providedContracts ?? {}
|
|
46
|
+
};
|
|
47
|
+
const descriptor = {
|
|
48
|
+
id: unitId,
|
|
49
|
+
...options.runtime !== void 0 ? { runtime: options.runtime } : {},
|
|
50
|
+
...dependencies.length > 0 ? { dependencies } : {},
|
|
51
|
+
...provides.length > 0 ? { provides } : {},
|
|
52
|
+
...Object.keys(providedContracts).length > 0 ? { providedContracts } : {},
|
|
53
|
+
...options.contribution !== void 0 ? { contribution: options.contribution } : {},
|
|
54
|
+
...options.permissions !== void 0 ? { permissions: [...options.permissions] } : {},
|
|
55
|
+
...options.config !== void 0 ? { config: options.config } : {}
|
|
56
|
+
};
|
|
57
|
+
const metaInput = options.meta ?? {};
|
|
58
|
+
const required = options.required === true || options.startup === "required" || metaInput.startup === "required";
|
|
59
|
+
const meta = {
|
|
60
|
+
...metaInput,
|
|
61
|
+
defaultEnabled: options.defaultEnabled ?? metaInput.defaultEnabled ?? true,
|
|
62
|
+
canDisable: options.canDisable ?? metaInput.canDisable ?? !required,
|
|
63
|
+
startup: required ? "required" : options.startup ?? metaInput.startup ?? "optional"
|
|
64
|
+
};
|
|
65
|
+
if (meta.startup === "required" && (meta.canDisable || !meta.defaultEnabled)) {
|
|
66
|
+
throw new Error(`Plugin "${options.id}" required metadata is inconsistent`);
|
|
67
|
+
}
|
|
68
|
+
const manifest = {
|
|
69
|
+
id: options.id,
|
|
70
|
+
name: options.name ?? options.id,
|
|
71
|
+
...options.description !== void 0 ? { description: options.description } : {},
|
|
72
|
+
meta,
|
|
73
|
+
units: [descriptor]
|
|
74
|
+
};
|
|
75
|
+
return Object.freeze({
|
|
76
|
+
manifest: Object.freeze(manifest),
|
|
77
|
+
descriptor: Object.freeze(descriptor),
|
|
78
|
+
setup: options.setup
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/runtime/pluginDefinitions.ts
|
|
83
|
+
function normalizeProductDependencies(dependencies, runtime) {
|
|
84
|
+
return dependencies?.map((dependency) => ({
|
|
85
|
+
capability: dependency.capability,
|
|
86
|
+
contractVersion: dependency.contractVersion ?? runtimeCapabilityContractVersion(dependency.capability),
|
|
87
|
+
sourceRuntime: dependency.sourceRuntime ?? runtime,
|
|
88
|
+
...dependency.reason !== void 0 ? { reason: dependency.reason } : {},
|
|
89
|
+
...dependency.optional !== void 0 ? { optional: dependency.optional } : {}
|
|
90
|
+
}));
|
|
91
|
+
}
|
|
92
|
+
function materializePluginDefinition(input, runtime) {
|
|
93
|
+
const manifest = input.manifest;
|
|
94
|
+
const setup = input.setup;
|
|
95
|
+
if (!manifest || typeof manifest.id !== "string" || manifest.id.trim() === "") {
|
|
96
|
+
throw new Error("Plugin manifest id must be a non-empty string");
|
|
97
|
+
}
|
|
98
|
+
const existingUnits = manifest.units ?? [];
|
|
99
|
+
const requestedUnitId = "unitId" in input ? input.unitId : void 0;
|
|
100
|
+
const matchingUnits = existingUnits.filter((unit) => unit.runtime === void 0 || unit.runtime === runtime);
|
|
101
|
+
let implementationUnitId;
|
|
102
|
+
if (existingUnits.length === 0) {
|
|
103
|
+
implementationUnitId = manifest.id;
|
|
104
|
+
} else if (requestedUnitId !== void 0) {
|
|
105
|
+
const selected = existingUnits.find((unit) => unit.id === requestedUnitId);
|
|
106
|
+
if (!selected) throw new Error(`Plugin "${manifest.id}" does not declare unit "${requestedUnitId}"`);
|
|
107
|
+
if (selected.runtime !== void 0 && selected.runtime !== runtime) {
|
|
108
|
+
throw new Error(`Plugin "${manifest.id}" unit "${requestedUnitId}" targets ${selected.runtime}, not ${runtime}`);
|
|
109
|
+
}
|
|
110
|
+
implementationUnitId = selected.id;
|
|
111
|
+
} else if (matchingUnits.length === 1 && matchingUnits[0]) {
|
|
112
|
+
implementationUnitId = matchingUnits[0].id;
|
|
113
|
+
} else {
|
|
114
|
+
throw new Error(
|
|
115
|
+
`Plugin "${manifest.id}" has ${matchingUnits.length} implementation units for ${runtime}; specify unitId explicitly`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
const selectedUnit = existingUnits.length > 0 ? existingUnits.find((unit) => unit.id === implementationUnitId) : void 0;
|
|
119
|
+
const units = selectedUnit ? [(() => {
|
|
120
|
+
const unitRuntime = selectedUnit.runtime ?? runtime;
|
|
121
|
+
const provides = [...selectedUnit.provides ?? []];
|
|
122
|
+
const dependencies = selectedUnit.dependencies?.map((dependency) => ({
|
|
123
|
+
capability: dependency.capability,
|
|
124
|
+
contractVersion: dependency.contractVersion ?? runtimeCapabilityContractVersion(dependency.capability),
|
|
125
|
+
sourceRuntime: dependency.sourceRuntime ?? unitRuntime,
|
|
126
|
+
...dependency.reason !== void 0 ? { reason: dependency.reason } : {},
|
|
127
|
+
...dependency.optional !== void 0 ? { optional: dependency.optional } : {}
|
|
128
|
+
}));
|
|
129
|
+
return {
|
|
130
|
+
...selectedUnit,
|
|
131
|
+
runtime: unitRuntime,
|
|
132
|
+
dependencies,
|
|
133
|
+
...provides.length > 0 ? { providedContracts: { ...defineRuntimeUnitProvidedContracts(provides), ...selectedUnit.providedContracts ?? {} } } : {}
|
|
134
|
+
};
|
|
135
|
+
})()] : [{
|
|
136
|
+
id: manifest.id,
|
|
137
|
+
runtime,
|
|
138
|
+
dependencies: normalizeProductDependencies(manifest.dependencies, runtime),
|
|
139
|
+
...manifest.provides && manifest.provides.length > 0 ? {
|
|
140
|
+
provides: [...manifest.provides],
|
|
141
|
+
providedContracts: defineRuntimeUnitProvidedContracts(manifest.provides)
|
|
142
|
+
} : {},
|
|
143
|
+
...manifest.permissions !== void 0 ? { permissions: [...manifest.permissions] } : {},
|
|
144
|
+
...manifest.config !== void 0 ? { config: manifest.config } : {},
|
|
145
|
+
...manifest.contribution !== void 0 ? { contribution: manifest.contribution } : {}
|
|
146
|
+
}];
|
|
147
|
+
const normalized = {
|
|
148
|
+
...manifest,
|
|
149
|
+
units
|
|
150
|
+
// Product-level declarations are retained only for compatibility with
|
|
151
|
+
// low-level manifests; the selected unit is the source of truth in Host.
|
|
152
|
+
};
|
|
153
|
+
return { manifest: normalized, unitId: implementationUnitId, setup };
|
|
154
|
+
}
|
|
155
|
+
function materializePluginDefinitions(inputs, runtime) {
|
|
156
|
+
return inputs.map((input) => materializePluginDefinition(input, runtime));
|
|
157
|
+
}
|
|
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
|
+
// src/runtime/windowRuntime.ts
|
|
244
|
+
function makeRuntimeInstanceId(runtimeId) {
|
|
245
|
+
try {
|
|
246
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
247
|
+
return `${runtimeId}:${crypto.randomUUID()}`;
|
|
248
|
+
}
|
|
249
|
+
} catch {
|
|
250
|
+
}
|
|
251
|
+
return `${runtimeId}:${Date.now().toString(36)}:${Math.random().toString(36).slice(2)}`;
|
|
252
|
+
}
|
|
253
|
+
function errorMessage(error) {
|
|
254
|
+
return error instanceof Error ? error.message : String(error);
|
|
255
|
+
}
|
|
256
|
+
function isStartupPluginError(error) {
|
|
257
|
+
return error instanceof StartupPluginError && typeof error.details?.pluginId === "string";
|
|
258
|
+
}
|
|
259
|
+
function buildLocalServices(host, manifests, runtimeInstanceId, revision) {
|
|
260
|
+
const services = [];
|
|
261
|
+
for (const manifest of manifests) {
|
|
262
|
+
const state = host.state(manifest.id);
|
|
263
|
+
const unit = manifest.units?.find((candidate) => candidate.id === state.unitId) ?? manifest.units?.[0];
|
|
264
|
+
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
|
+
for (const capability of unit.provides ?? []) {
|
|
267
|
+
services.push({
|
|
268
|
+
capabilityId: capability,
|
|
269
|
+
providerInstanceId: state.instanceId,
|
|
270
|
+
runtime: unit.runtime,
|
|
271
|
+
contractVersion: unit.providedContracts?.[capability] ?? `${capability}.v1`,
|
|
272
|
+
authorityInstanceId: runtimeInstanceId,
|
|
273
|
+
scopeId,
|
|
274
|
+
handoverGeneration: 0,
|
|
275
|
+
attributes: Object.freeze({}),
|
|
276
|
+
status: "ready",
|
|
277
|
+
snapshotRevision: revision
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return services;
|
|
282
|
+
}
|
|
283
|
+
async function createWindowApp(options) {
|
|
284
|
+
const runtimeId = options.id ?? "window-main";
|
|
285
|
+
const remoteRuntime = options.remoteRuntime;
|
|
286
|
+
const runtimeInstanceId = makeRuntimeInstanceId(runtimeId);
|
|
287
|
+
let currentState = {
|
|
288
|
+
runtimeId,
|
|
289
|
+
runtimeKind: "window-main",
|
|
290
|
+
runtimeInstanceId,
|
|
291
|
+
state: "starting",
|
|
292
|
+
snapshotRevision: 0,
|
|
293
|
+
units: [],
|
|
294
|
+
services: []
|
|
295
|
+
};
|
|
296
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
297
|
+
let disposed = false;
|
|
298
|
+
let disposePromise;
|
|
299
|
+
const emit = (next) => {
|
|
300
|
+
currentState = Object.freeze({
|
|
301
|
+
...next,
|
|
302
|
+
units: Object.freeze([...next.units]),
|
|
303
|
+
services: Object.freeze([...next.services])
|
|
304
|
+
});
|
|
305
|
+
for (const listener of [...listeners]) {
|
|
306
|
+
try {
|
|
307
|
+
listener(currentState);
|
|
308
|
+
} catch {
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
let materialized;
|
|
313
|
+
try {
|
|
314
|
+
materialized = materializePluginDefinitions(options.plugins, "window-main");
|
|
315
|
+
} catch (error) {
|
|
316
|
+
throw new RuntimeInitializationError({ phase: "validate", error: errorMessage(error) });
|
|
317
|
+
}
|
|
318
|
+
const manifests = materialized.map((item) => item.manifest);
|
|
319
|
+
let implementations;
|
|
320
|
+
try {
|
|
321
|
+
const duplicate = manifests.find((manifest, index) => manifests.findIndex((candidate) => candidate.id === manifest.id) !== index);
|
|
322
|
+
if (duplicate) throw new Error(`Plugin "${duplicate.id}" is declared more than once`);
|
|
323
|
+
implementations = createRuntimeUnitImplementationRegistry(
|
|
324
|
+
materialized.map((item) => ({
|
|
325
|
+
pluginId: item.manifest.id,
|
|
326
|
+
unitId: item.unitId,
|
|
327
|
+
setup: item.setup
|
|
328
|
+
}))
|
|
329
|
+
);
|
|
330
|
+
} catch (error) {
|
|
331
|
+
throw new RuntimeInitializationError({ phase: "validate", error: errorMessage(error) });
|
|
332
|
+
}
|
|
333
|
+
let host;
|
|
334
|
+
const suppliedHost = options.host;
|
|
335
|
+
try {
|
|
336
|
+
const {
|
|
337
|
+
id: _id,
|
|
338
|
+
plugins: _plugins,
|
|
339
|
+
host: _host,
|
|
340
|
+
remoteRuntime: _remoteRuntime,
|
|
341
|
+
...hostOptions
|
|
342
|
+
} = options;
|
|
343
|
+
const suppliedBridgeFactory = hostOptions.serviceBridgeForPlugin;
|
|
344
|
+
host = suppliedHost ?? createPluginHost({
|
|
345
|
+
...hostOptions,
|
|
346
|
+
runtime: "window-main",
|
|
347
|
+
externalRuntimeDependencies: remoteRuntime !== void 0 || hostOptions.externalRuntimeDependencies,
|
|
348
|
+
remoteServiceReferences: () => remoteRuntime?.state().services ?? hostOptions.remoteServiceReferences?.() ?? [],
|
|
349
|
+
runtimeSnapshots: () => remoteRuntime ? remoteRuntime.state().units.map((unit) => ({
|
|
350
|
+
pluginId: unit.pluginId,
|
|
351
|
+
unitId: unit.unitId,
|
|
352
|
+
runtime: unit.runtime,
|
|
353
|
+
...unit.instanceId !== void 0 ? { instanceId: unit.instanceId } : {},
|
|
354
|
+
state: unit.state
|
|
355
|
+
})) : hostOptions.runtimeSnapshots?.() ?? [],
|
|
356
|
+
serviceBridgeForPlugin: (pluginId, instanceId) => suppliedBridgeFactory?.(pluginId, instanceId) ?? remoteRuntime?.serviceBridge,
|
|
357
|
+
rootAttributes: {
|
|
358
|
+
...hostOptions.rootAttributes ?? {},
|
|
359
|
+
runtimeId,
|
|
360
|
+
runtimeInstanceId
|
|
361
|
+
},
|
|
362
|
+
runtimeUnitImplementationRegistry: implementations
|
|
363
|
+
});
|
|
364
|
+
} catch (error) {
|
|
365
|
+
throw new RuntimeInitializationError({ phase: "validate", error: errorMessage(error) });
|
|
366
|
+
}
|
|
367
|
+
let removeRemoteSubscription;
|
|
368
|
+
const manifestsForSnapshot = () => {
|
|
369
|
+
if (!suppliedHost) return manifests;
|
|
370
|
+
return suppliedHost.manifests().map((pluginId) => suppliedHost.getManifest(pluginId)).filter((manifest) => manifest !== void 0);
|
|
371
|
+
};
|
|
372
|
+
const refresh = () => {
|
|
373
|
+
const snapshotManifests = manifestsForSnapshot();
|
|
374
|
+
const units = snapshotManifests.flatMap((manifest) => {
|
|
375
|
+
const state = host.state(manifest.id);
|
|
376
|
+
return (state.units ?? []).map((unit) => unitSnapshotFromState(
|
|
377
|
+
unit,
|
|
378
|
+
unit.runtime
|
|
379
|
+
));
|
|
380
|
+
});
|
|
381
|
+
const revision = Math.max(currentState.snapshotRevision + 1, host.version());
|
|
382
|
+
emit({
|
|
383
|
+
...currentState,
|
|
384
|
+
snapshotRevision: revision,
|
|
385
|
+
units,
|
|
386
|
+
services: [
|
|
387
|
+
...buildLocalServices(host, snapshotManifests, runtimeInstanceId, revision),
|
|
388
|
+
...remoteRuntime?.state().services ?? []
|
|
389
|
+
]
|
|
390
|
+
});
|
|
391
|
+
};
|
|
392
|
+
const removeHostSubscription = host.subscribe(refresh);
|
|
393
|
+
try {
|
|
394
|
+
if (!suppliedHost) await host.registerAll(manifests);
|
|
395
|
+
const failedRequired = suppliedHost ? void 0 : manifests.find((manifest) => {
|
|
396
|
+
const required = manifest.meta.startup === "required" || manifest.meta.canDisable === false;
|
|
397
|
+
return required && host.state(manifest.id).kind !== "enabled";
|
|
398
|
+
});
|
|
399
|
+
if (failedRequired) {
|
|
400
|
+
const state = host.state(failedRequired.id);
|
|
401
|
+
throw new StartupPluginError({
|
|
402
|
+
pluginId: failedRequired.id,
|
|
403
|
+
unitId: state.unitId ?? failedRequired.units?.[0]?.id ?? failedRequired.id,
|
|
404
|
+
capabilities: [],
|
|
405
|
+
state: state.kind,
|
|
406
|
+
error: state.error ?? `Required plugin is ${state.kind}${state.blockedBy ? `: ${state.blockedBy.join(", ")}` : ""}`
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
} catch (error) {
|
|
410
|
+
refresh();
|
|
411
|
+
const pluginId = isStartupPluginError(error) ? error.details.pluginId : void 0;
|
|
412
|
+
const manifest = pluginId ? manifests.find((candidate) => candidate.id === pluginId) : void 0;
|
|
413
|
+
const required = manifest?.meta.startup === "required" || manifest?.meta.canDisable === false;
|
|
414
|
+
if (required || !isStartupPluginError(error)) {
|
|
415
|
+
removeHostSubscription();
|
|
416
|
+
removeRemoteSubscription?.();
|
|
417
|
+
await host.dispose("window runtime initialization failed").catch(() => void 0);
|
|
418
|
+
if (error instanceof RuntimeInitializationError) throw error;
|
|
419
|
+
throw new RuntimeInitializationError({
|
|
420
|
+
pluginId,
|
|
421
|
+
unitId: isStartupPluginError(error) ? error.details.unitId : manifest?.units?.[0]?.id,
|
|
422
|
+
phase: "startup",
|
|
423
|
+
error: errorMessage(error)
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
refresh();
|
|
428
|
+
emit({ ...currentState, state: "ready" });
|
|
429
|
+
removeRemoteSubscription = remoteRuntime?.subscribe(() => {
|
|
430
|
+
host.refreshRuntimeUnitSnapshots();
|
|
431
|
+
void host.reconcile().catch(() => void 0);
|
|
432
|
+
});
|
|
433
|
+
const app = {
|
|
434
|
+
runtimeKind: "window-main",
|
|
435
|
+
runtimeId,
|
|
436
|
+
runtimeInstanceId,
|
|
437
|
+
host,
|
|
438
|
+
state: () => currentState,
|
|
439
|
+
capability(capabilityId) {
|
|
440
|
+
if (disposed || currentState.state === "disposed" || currentState.state === "stopping") {
|
|
441
|
+
throw new RuntimeUnavailableError("Window Runtime has been disposed");
|
|
442
|
+
}
|
|
443
|
+
return host.capabilities.get(capabilityId);
|
|
444
|
+
},
|
|
445
|
+
subscribe(listener) {
|
|
446
|
+
listeners.add(listener);
|
|
447
|
+
listener(currentState);
|
|
448
|
+
return () => listeners.delete(listener);
|
|
449
|
+
},
|
|
450
|
+
dispose(reason = "window runtime disposed") {
|
|
451
|
+
if (disposePromise) return disposePromise;
|
|
452
|
+
disposed = true;
|
|
453
|
+
emit({ ...currentState, state: "stopping" });
|
|
454
|
+
removeHostSubscription();
|
|
455
|
+
removeRemoteSubscription?.();
|
|
456
|
+
disposePromise = host.dispose(reason).then((result) => {
|
|
457
|
+
emit({ ...currentState, state: "disposed" });
|
|
458
|
+
return result;
|
|
459
|
+
}, (error) => {
|
|
460
|
+
emit({ ...currentState, state: "failed", error: errorMessage(error) });
|
|
461
|
+
throw error;
|
|
462
|
+
});
|
|
463
|
+
return disposePromise;
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
return app;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// src/transport/messagePortServiceProvider.ts
|
|
470
|
+
function isCallMessage(input, codec) {
|
|
471
|
+
if (!input || typeof input !== "object") return false;
|
|
472
|
+
const message = input;
|
|
473
|
+
return message.type === codec.type("call") && typeof message.callId === "string" && message.callId.length > 0 && typeof message.connectionId === "string" && typeof message.providerInstanceId === "string" && Boolean(message.reference);
|
|
474
|
+
}
|
|
475
|
+
function isCancelMessage(input, codec) {
|
|
476
|
+
if (!input || typeof input !== "object") return false;
|
|
477
|
+
const message = input;
|
|
478
|
+
return message.type === codec.type("cancel") && typeof message.callId === "string" && typeof message.connectionId === "string" && typeof message.providerInstanceId === "string";
|
|
479
|
+
}
|
|
480
|
+
function errorMessage2(error) {
|
|
481
|
+
const candidate = error && typeof error === "object" ? error : void 0;
|
|
482
|
+
return {
|
|
483
|
+
...typeof candidate?.name === "string" ? { name: candidate.name } : {},
|
|
484
|
+
message: typeof candidate?.message === "string" ? candidate.message : String(error),
|
|
485
|
+
...typeof candidate?.code === "string" ? { code: candidate.code } : {}
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
function post(port, message) {
|
|
489
|
+
try {
|
|
490
|
+
port.postMessage(message);
|
|
491
|
+
} catch {
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
function createMessagePortServiceProvider(options) {
|
|
495
|
+
const pending = /* @__PURE__ */ new Map();
|
|
496
|
+
const codec = options.codec ?? createRemoteServiceMessageCodec();
|
|
497
|
+
let activeProviderInstanceIds = new Set(
|
|
498
|
+
options.snapshot.services.map((service) => service.providerInstanceId)
|
|
499
|
+
);
|
|
500
|
+
let disposed = false;
|
|
501
|
+
const sendError = (message, error) => {
|
|
502
|
+
const response = {
|
|
503
|
+
type: codec.type("error"),
|
|
504
|
+
callId: message.callId,
|
|
505
|
+
connectionId: message.connectionId,
|
|
506
|
+
providerInstanceId: message.providerInstanceId,
|
|
507
|
+
error: errorMessage2(error)
|
|
508
|
+
};
|
|
509
|
+
post(options.port, codec.encode(response));
|
|
510
|
+
};
|
|
511
|
+
const onMessage = (event) => {
|
|
512
|
+
if (disposed) return;
|
|
513
|
+
const decoded = codec.decode(event.data);
|
|
514
|
+
if (isCancelMessage(decoded, codec)) {
|
|
515
|
+
if (decoded.connectionId !== options.handshake.connectionId) return;
|
|
516
|
+
const call = pending.get(decoded.callId);
|
|
517
|
+
if (call?.providerInstanceId === decoded.providerInstanceId) {
|
|
518
|
+
call.controller.abort(new Error("Remote service request cancelled"));
|
|
519
|
+
}
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
if (!isCallMessage(decoded, codec)) return;
|
|
523
|
+
const message = decoded;
|
|
524
|
+
if (message.connectionId !== options.handshake.connectionId) {
|
|
525
|
+
sendError(message, Object.assign(new Error("Remote service connection mismatch"), { code: "service.connection_mismatch" }));
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
if (!activeProviderInstanceIds.has(message.providerInstanceId)) {
|
|
529
|
+
sendError(message, Object.assign(new Error("Remote service provider mismatch"), { code: "service.provider_mismatch" }));
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
if (pending.has(message.callId)) {
|
|
533
|
+
sendError(message, Object.assign(new Error("Remote service callId is duplicated"), { code: "service.duplicate_call" }));
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
const controller = new AbortController();
|
|
537
|
+
pending.set(message.callId, { controller, providerInstanceId: message.providerInstanceId });
|
|
538
|
+
void (async () => {
|
|
539
|
+
try {
|
|
540
|
+
const result = await options.handleCall({ message, signal: controller.signal });
|
|
541
|
+
if (controller.signal.aborted || disposed) return;
|
|
542
|
+
const response = {
|
|
543
|
+
type: codec.type("result"),
|
|
544
|
+
callId: message.callId,
|
|
545
|
+
connectionId: message.connectionId,
|
|
546
|
+
providerInstanceId: message.providerInstanceId,
|
|
547
|
+
result
|
|
548
|
+
};
|
|
549
|
+
post(options.port, codec.encode(response));
|
|
550
|
+
} catch (error) {
|
|
551
|
+
if (disposed) return;
|
|
552
|
+
sendError(message, error);
|
|
553
|
+
} finally {
|
|
554
|
+
pending.delete(message.callId);
|
|
555
|
+
}
|
|
556
|
+
})();
|
|
557
|
+
};
|
|
558
|
+
options.port.addEventListener("message", onMessage);
|
|
559
|
+
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
|
+
const provider = {
|
|
577
|
+
publishSnapshot(snapshot) {
|
|
578
|
+
if (disposed) return;
|
|
579
|
+
activeProviderInstanceIds = new Set(
|
|
580
|
+
snapshot.services.map((service) => service.providerInstanceId)
|
|
581
|
+
);
|
|
582
|
+
post(options.port, codec.encode({
|
|
583
|
+
type: codec.type("snapshot"),
|
|
584
|
+
snapshot
|
|
585
|
+
}));
|
|
586
|
+
},
|
|
587
|
+
invalidate(reason = "Remote service invalidated") {
|
|
588
|
+
if (disposed) return;
|
|
589
|
+
activeProviderInstanceIds = /* @__PURE__ */ new Set();
|
|
590
|
+
for (const { controller } of pending.values()) controller.abort(new Error(reason));
|
|
591
|
+
post(options.port, codec.encode({
|
|
592
|
+
type: codec.type("invalidate"),
|
|
593
|
+
reason
|
|
594
|
+
}));
|
|
595
|
+
},
|
|
596
|
+
disconnect(reason = "Remote service disconnected") {
|
|
597
|
+
if (disposed) return;
|
|
598
|
+
post(options.port, codec.encode({
|
|
599
|
+
type: codec.type("disconnect"),
|
|
600
|
+
reason
|
|
601
|
+
}));
|
|
602
|
+
dispose();
|
|
603
|
+
},
|
|
604
|
+
dispose
|
|
605
|
+
};
|
|
606
|
+
return provider;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// src/runtime/sharedWorkerHost.ts
|
|
610
|
+
function makeRuntimeInstanceId2(runtimeId) {
|
|
611
|
+
try {
|
|
612
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
613
|
+
return `${runtimeId}:${crypto.randomUUID()}`;
|
|
614
|
+
}
|
|
615
|
+
} catch {
|
|
616
|
+
}
|
|
617
|
+
return `${runtimeId}:${Date.now().toString(36)}:${Math.random().toString(36).slice(2)}`;
|
|
618
|
+
}
|
|
619
|
+
function errorMessage3(error) {
|
|
620
|
+
return error instanceof Error ? error.message : String(error);
|
|
621
|
+
}
|
|
622
|
+
function startupDetails(error) {
|
|
623
|
+
if (error instanceof StartupPluginError) {
|
|
624
|
+
return {
|
|
625
|
+
pluginId: error.details.pluginId,
|
|
626
|
+
unitId: error.details.unitId,
|
|
627
|
+
message: error.details.error ?? error.message
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
if (error instanceof RuntimeInitializationError) {
|
|
631
|
+
return {
|
|
632
|
+
pluginId: error.details.pluginId,
|
|
633
|
+
unitId: error.details.unitId,
|
|
634
|
+
message: error.message
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
return { message: errorMessage3(error) };
|
|
638
|
+
}
|
|
639
|
+
function addPortListener(port, listener) {
|
|
640
|
+
const target = port;
|
|
641
|
+
const add = target.addEventListener;
|
|
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);
|
|
685
|
+
}
|
|
686
|
+
function post2(port, message) {
|
|
687
|
+
try {
|
|
688
|
+
port.postMessage(message);
|
|
689
|
+
} catch {
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
function startSharedWorkerApp(options) {
|
|
693
|
+
if (!options || typeof options.id !== "string" || options.id.trim() === "") {
|
|
694
|
+
throw new Error("SharedWorker runtime id must be a non-empty string");
|
|
695
|
+
}
|
|
696
|
+
const workerScope = options.globalScope ?? ("onconnect" in globalThis ? globalThis : void 0);
|
|
697
|
+
if (!workerScope) {
|
|
698
|
+
throw new RuntimeInitializationError({
|
|
699
|
+
phase: "validate",
|
|
700
|
+
error: "startSharedWorkerApp must run in a SharedWorkerGlobalScope"
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
const runtimeId = options.id;
|
|
704
|
+
const runtimeInstanceId = makeRuntimeInstanceId2(runtimeId);
|
|
705
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
706
|
+
const endpoints = /* @__PURE__ */ new Set();
|
|
707
|
+
const codec = createRuntimeMessageCodec();
|
|
708
|
+
let runtimeState = "starting";
|
|
709
|
+
let revision = 0;
|
|
710
|
+
let host;
|
|
711
|
+
let manifests = [];
|
|
712
|
+
let disposed = false;
|
|
713
|
+
let disposePromise;
|
|
714
|
+
const emit = () => {
|
|
715
|
+
const snapshot = currentSnapshot();
|
|
716
|
+
for (const listener of [...listeners]) {
|
|
717
|
+
try {
|
|
718
|
+
listener(snapshot);
|
|
719
|
+
} catch {
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
};
|
|
723
|
+
const currentSnapshot = () => Object.freeze({
|
|
724
|
+
runtimeId,
|
|
725
|
+
runtimeKind: "shared-worker",
|
|
726
|
+
runtimeInstanceId,
|
|
727
|
+
state: runtimeState,
|
|
728
|
+
snapshotRevision: revision,
|
|
729
|
+
units: Object.freeze(host && runtimeState !== "failed" ? manifests.flatMap((manifest) => {
|
|
730
|
+
const state = host?.state(manifest.id);
|
|
731
|
+
return (state?.units ?? []).map((unit) => ({
|
|
732
|
+
pluginId: unit.pluginId,
|
|
733
|
+
unitId: unit.unitId,
|
|
734
|
+
runtime: unit.runtime,
|
|
735
|
+
...unit.instanceId !== void 0 ? { instanceId: unit.instanceId } : {},
|
|
736
|
+
state: unit.kind
|
|
737
|
+
}));
|
|
738
|
+
}) : []),
|
|
739
|
+
services: Object.freeze(host && runtimeState !== "failed" ? serviceReferences("", revision) : [])
|
|
740
|
+
});
|
|
741
|
+
const serviceReferences = (connectionId, snapshotRevision) => {
|
|
742
|
+
if (!host || runtimeState === "failed" || runtimeState === "disposed") return [];
|
|
743
|
+
const services = [];
|
|
744
|
+
for (const manifest of manifests) {
|
|
745
|
+
const state = host.state(manifest.id);
|
|
746
|
+
const unit = manifest.units?.find((candidate) => candidate.id === state.unitId) ?? manifest.units?.[0];
|
|
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
|
+
});
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
return services;
|
|
767
|
+
};
|
|
768
|
+
const runtimeSnapshotFor = (endpoint, baseline) => ({
|
|
769
|
+
type: RUNTIME_SNAPSHOT_TYPE,
|
|
770
|
+
protocolVersion: RUNTIME_PROTOCOL_VERSION,
|
|
771
|
+
runtimeId,
|
|
772
|
+
runtimeKind: "shared-worker",
|
|
773
|
+
runtimeInstanceId,
|
|
774
|
+
connectionId: endpoint.connectionId,
|
|
775
|
+
snapshotRevision: revision,
|
|
776
|
+
baseline,
|
|
777
|
+
state: runtimeState,
|
|
778
|
+
units: currentSnapshot().units,
|
|
779
|
+
services: serviceReferences(endpoint.connectionId, revision)
|
|
780
|
+
});
|
|
781
|
+
const serviceSnapshotFor = (endpoint, baseline) => ({
|
|
782
|
+
connectionId: endpoint.connectionId,
|
|
783
|
+
authorityInstanceId: runtimeInstanceId,
|
|
784
|
+
snapshotRevision: revision,
|
|
785
|
+
baseline,
|
|
786
|
+
services: serviceReferences(endpoint.connectionId, revision)
|
|
787
|
+
});
|
|
788
|
+
const publishEndpoint = (endpoint, baseline) => {
|
|
789
|
+
if (endpoint.closed || disposed) return;
|
|
790
|
+
const serviceSnapshot = serviceSnapshotFor(endpoint, baseline);
|
|
791
|
+
endpoint.provider.publishSnapshot(serviceSnapshot);
|
|
792
|
+
post2(endpoint.port, codec.encode(runtimeSnapshotFor(endpoint, baseline)));
|
|
793
|
+
};
|
|
794
|
+
const publishAll = (baseline = false) => {
|
|
795
|
+
for (const endpoint of [...endpoints]) publishEndpoint(endpoint, baseline);
|
|
796
|
+
};
|
|
797
|
+
const closeEndpoint = (endpoint, reason) => {
|
|
798
|
+
if (endpoint.closed) return;
|
|
799
|
+
endpoint.closed = true;
|
|
800
|
+
endpoints.delete(endpoint);
|
|
801
|
+
endpoint.removeMessage();
|
|
802
|
+
endpoint.provider.disconnect(reason);
|
|
803
|
+
};
|
|
804
|
+
const sendError = (port, code, message, details = {}) => {
|
|
805
|
+
const error = {
|
|
806
|
+
type: RUNTIME_ERROR_TYPE,
|
|
807
|
+
protocolVersion: RUNTIME_PROTOCOL_VERSION,
|
|
808
|
+
code,
|
|
809
|
+
message,
|
|
810
|
+
...details.pluginId !== void 0 ? { pluginId: details.pluginId } : {},
|
|
811
|
+
...details.unitId !== void 0 ? { unitId: details.unitId } : {},
|
|
812
|
+
...details.phase !== void 0 ? { phase: details.phase } : {}
|
|
813
|
+
};
|
|
814
|
+
post2(port, error);
|
|
815
|
+
};
|
|
816
|
+
const handleCall = async (connectionId, input) => {
|
|
817
|
+
if (!host || runtimeState !== "ready") throw new Error("SharedWorker Runtime is not ready");
|
|
818
|
+
const message = input.message;
|
|
819
|
+
if (message.connectionId !== connectionId) throw new Error("Runtime connection mismatch");
|
|
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) {
|
|
822
|
+
throw new Error("Runtime service reference is stale");
|
|
823
|
+
}
|
|
824
|
+
const manifest = manifests.find((candidate) => {
|
|
825
|
+
const state2 = host?.state(candidate.id);
|
|
826
|
+
return state2?.instanceId === reference.providerInstanceId;
|
|
827
|
+
});
|
|
828
|
+
if (!manifest) throw new Error("Runtime service provider is no longer active");
|
|
829
|
+
const state = host.state(manifest.id);
|
|
830
|
+
if (state.kind !== "enabled" || state.instanceId !== reference.providerInstanceId) {
|
|
831
|
+
throw new Error("Runtime service provider is no longer active");
|
|
832
|
+
}
|
|
833
|
+
const unit = manifest.units?.find((candidate) => candidate.id === state.unitId);
|
|
834
|
+
if (!unit || unit.runtime !== "shared-worker" || !unit.provides?.includes(reference.capabilityId)) {
|
|
835
|
+
throw new Error("Runtime capability is not declared");
|
|
836
|
+
}
|
|
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
|
+
const expectedVersion = unit.providedContracts?.[reference.capabilityId] ?? `${reference.capabilityId}.v1`;
|
|
840
|
+
if (expectedVersion !== reference.contractVersion) throw new Error("Runtime capability contract mismatch");
|
|
841
|
+
const value = host.capabilities.get(reference.capabilityId);
|
|
842
|
+
if (input.signal.aborted) throw input.signal.reason ?? new Error("Runtime service request cancelled");
|
|
843
|
+
const request = message.request;
|
|
844
|
+
if (typeof value === "function") return await value(request, input.signal);
|
|
845
|
+
if (value && typeof value === "object") {
|
|
846
|
+
const object = value;
|
|
847
|
+
if (typeof object.handle === "function") return await object.handle(request, input.signal);
|
|
848
|
+
if (request && typeof request === "object" && typeof request.method === "string") {
|
|
849
|
+
const method = request.method;
|
|
850
|
+
const methodValue = object[method];
|
|
851
|
+
if (typeof methodValue === "function") {
|
|
852
|
+
const args = request.args;
|
|
853
|
+
return await methodValue(...Array.isArray(args) ? args : []);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
throw new Error(`Runtime capability "${reference.capabilityId}" does not expose an RPC handler`);
|
|
858
|
+
};
|
|
859
|
+
const attachPort = (port) => {
|
|
860
|
+
let endpoint;
|
|
861
|
+
let helloSeen = false;
|
|
862
|
+
let removeMessage = () => void 0;
|
|
863
|
+
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
|
+
};
|
|
941
|
+
removeMessage = addPortListener(port, onMessage);
|
|
942
|
+
port.start();
|
|
943
|
+
};
|
|
944
|
+
const endpointsHasConnection = (connectionId) => [...endpoints].some((endpoint) => endpoint.connectionId === connectionId);
|
|
945
|
+
let startupError;
|
|
946
|
+
workerScope.onconnect = (event) => {
|
|
947
|
+
try {
|
|
948
|
+
options.onPortConnect?.(event);
|
|
949
|
+
} catch (error) {
|
|
950
|
+
const detail = startupDetails(error);
|
|
951
|
+
for (const port of event.ports ?? []) {
|
|
952
|
+
sendError(port, "runtime.initialization_failed", detail.message, {
|
|
953
|
+
pluginId: detail.pluginId,
|
|
954
|
+
unitId: detail.unitId,
|
|
955
|
+
phase: "handshake"
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
for (const port of event.ports ?? []) {
|
|
959
|
+
try {
|
|
960
|
+
port.close();
|
|
961
|
+
} catch {
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
for (const port of event.ports ?? []) attachPort(port);
|
|
967
|
+
};
|
|
968
|
+
let materialized = [];
|
|
969
|
+
let readyPromise;
|
|
970
|
+
try {
|
|
971
|
+
materialized = materializePluginDefinitions(options.plugins, "shared-worker");
|
|
972
|
+
manifests = materialized.map((item) => item.manifest);
|
|
973
|
+
const implementations = createRuntimeUnitImplementationRegistry(
|
|
974
|
+
materialized.map((item) => ({
|
|
975
|
+
pluginId: item.manifest.id,
|
|
976
|
+
unitId: item.unitId,
|
|
977
|
+
setup: item.setup
|
|
978
|
+
}))
|
|
979
|
+
);
|
|
980
|
+
const { id: _id, plugins: _plugins, globalScope: _scope, onPortConnect: _onPortConnect, ...hostOptions } = options;
|
|
981
|
+
host = createPluginHost({
|
|
982
|
+
...hostOptions,
|
|
983
|
+
runtime: "shared-worker",
|
|
984
|
+
rootAttributes: {
|
|
985
|
+
...hostOptions.rootAttributes ?? {},
|
|
986
|
+
runtimeId,
|
|
987
|
+
runtimeInstanceId
|
|
988
|
+
},
|
|
989
|
+
runtimeUnitImplementationRegistry: implementations
|
|
990
|
+
});
|
|
991
|
+
host.subscribe(() => {
|
|
992
|
+
if (runtimeState !== "ready" || disposed) return;
|
|
993
|
+
revision += 1;
|
|
994
|
+
publishAll(false);
|
|
995
|
+
emit();
|
|
996
|
+
});
|
|
997
|
+
readyPromise = host.registerAll(manifests).then(() => {
|
|
998
|
+
if (disposed) return;
|
|
999
|
+
const runtimeHost = host;
|
|
1000
|
+
if (!runtimeHost) throw new Error("SharedWorker Plugin Host is unavailable");
|
|
1001
|
+
const failedRequired = manifests.find((manifest) => {
|
|
1002
|
+
const required = manifest.meta.startup === "required" || manifest.meta.canDisable === false;
|
|
1003
|
+
return required && runtimeHost.state(manifest.id).kind !== "enabled";
|
|
1004
|
+
});
|
|
1005
|
+
if (failedRequired) {
|
|
1006
|
+
const state = runtimeHost.state(failedRequired.id);
|
|
1007
|
+
throw new StartupPluginError({
|
|
1008
|
+
pluginId: failedRequired.id,
|
|
1009
|
+
unitId: state.unitId ?? failedRequired.units?.[0]?.id ?? failedRequired.id,
|
|
1010
|
+
capabilities: [],
|
|
1011
|
+
state: state.kind,
|
|
1012
|
+
error: state.error ?? `Required plugin is ${state.kind}${state.blockedBy ? `: ${state.blockedBy.join(", ")}` : ""}`
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
runtimeState = "ready";
|
|
1016
|
+
revision = Math.max(1, revision + 1);
|
|
1017
|
+
publishAll(true);
|
|
1018
|
+
emit();
|
|
1019
|
+
}).catch((error) => {
|
|
1020
|
+
startupError = error;
|
|
1021
|
+
runtimeState = "failed";
|
|
1022
|
+
emit();
|
|
1023
|
+
const detail = startupDetails(error);
|
|
1024
|
+
for (const endpoint of [...endpoints]) {
|
|
1025
|
+
sendError(endpoint.port, "runtime.initialization_failed", detail.message, {
|
|
1026
|
+
pluginId: detail.pluginId,
|
|
1027
|
+
unitId: detail.unitId,
|
|
1028
|
+
phase: "startup"
|
|
1029
|
+
});
|
|
1030
|
+
closeEndpoint(endpoint, "Runtime initialization failed");
|
|
1031
|
+
}
|
|
1032
|
+
throw new RuntimeInitializationError({
|
|
1033
|
+
pluginId: detail.pluginId,
|
|
1034
|
+
unitId: detail.unitId,
|
|
1035
|
+
phase: "startup",
|
|
1036
|
+
error: detail.message
|
|
1037
|
+
});
|
|
1038
|
+
});
|
|
1039
|
+
} catch (error) {
|
|
1040
|
+
startupError = error;
|
|
1041
|
+
runtimeState = "failed";
|
|
1042
|
+
emit();
|
|
1043
|
+
readyPromise = Promise.reject(error instanceof RuntimeInitializationError ? error : new RuntimeInitializationError({ phase: "validate", error: errorMessage3(error) }));
|
|
1044
|
+
}
|
|
1045
|
+
const app = {
|
|
1046
|
+
runtimeKind: "shared-worker",
|
|
1047
|
+
runtimeId,
|
|
1048
|
+
runtimeInstanceId,
|
|
1049
|
+
ready: () => readyPromise,
|
|
1050
|
+
async reconcile() {
|
|
1051
|
+
if (!host) {
|
|
1052
|
+
await readyPromise;
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
if (runtimeState === "failed" || runtimeState === "disposed" || disposed) {
|
|
1056
|
+
await readyPromise;
|
|
1057
|
+
return;
|
|
1058
|
+
}
|
|
1059
|
+
await host.reconcile();
|
|
1060
|
+
},
|
|
1061
|
+
state: currentSnapshot,
|
|
1062
|
+
subscribe(listener) {
|
|
1063
|
+
listeners.add(listener);
|
|
1064
|
+
listener(currentSnapshot());
|
|
1065
|
+
return () => listeners.delete(listener);
|
|
1066
|
+
},
|
|
1067
|
+
dispose(reason = "shared worker runtime disposed") {
|
|
1068
|
+
if (disposePromise) return disposePromise;
|
|
1069
|
+
disposed = true;
|
|
1070
|
+
runtimeState = "stopping";
|
|
1071
|
+
emit();
|
|
1072
|
+
disposePromise = (async () => {
|
|
1073
|
+
for (const endpoint of [...endpoints]) closeEndpoint(endpoint, reason);
|
|
1074
|
+
const result = host ? await host.dispose(reason) : { scopeId: `runtime:${runtimeInstanceId}`, state: "stopped", attempted: 0, released: 0, pending: [], errors: [], cleanupIncomplete: false };
|
|
1075
|
+
runtimeState = "disposed";
|
|
1076
|
+
emit();
|
|
1077
|
+
return result;
|
|
1078
|
+
})();
|
|
1079
|
+
return disposePromise;
|
|
1080
|
+
}
|
|
1081
|
+
};
|
|
1082
|
+
return app;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
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
|
+
|
|
24
1574
|
// src/lifecycle/permissionVerifier.ts
|
|
25
1575
|
function verifyPermissionLease(options) {
|
|
26
1576
|
options.lease.assert(options.permission);
|
|
@@ -438,146 +1988,6 @@ function createUpgradeGate(options) {
|
|
|
438
1988
|
};
|
|
439
1989
|
}
|
|
440
1990
|
|
|
441
|
-
|
|
442
|
-
function isCallMessage(input, codec) {
|
|
443
|
-
if (!input || typeof input !== "object") return false;
|
|
444
|
-
const message = input;
|
|
445
|
-
return message.type === codec.type("call") && typeof message.callId === "string" && message.callId.length > 0 && typeof message.connectionId === "string" && typeof message.providerInstanceId === "string" && Boolean(message.reference);
|
|
446
|
-
}
|
|
447
|
-
function isCancelMessage(input, codec) {
|
|
448
|
-
if (!input || typeof input !== "object") return false;
|
|
449
|
-
const message = input;
|
|
450
|
-
return message.type === codec.type("cancel") && typeof message.callId === "string" && typeof message.connectionId === "string" && typeof message.providerInstanceId === "string";
|
|
451
|
-
}
|
|
452
|
-
function errorMessage(error) {
|
|
453
|
-
const candidate = error && typeof error === "object" ? error : void 0;
|
|
454
|
-
return {
|
|
455
|
-
...typeof candidate?.name === "string" ? { name: candidate.name } : {},
|
|
456
|
-
message: typeof candidate?.message === "string" ? candidate.message : String(error),
|
|
457
|
-
...typeof candidate?.code === "string" ? { code: candidate.code } : {}
|
|
458
|
-
};
|
|
459
|
-
}
|
|
460
|
-
function post(port, message) {
|
|
461
|
-
try {
|
|
462
|
-
port.postMessage(message);
|
|
463
|
-
} catch {
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
function createMessagePortServiceProvider(options) {
|
|
467
|
-
const pending = /* @__PURE__ */ new Map();
|
|
468
|
-
const codec = options.codec ?? createRemoteServiceMessageCodec();
|
|
469
|
-
let activeProviderInstanceIds = new Set(
|
|
470
|
-
options.snapshot.services.map((service) => service.providerInstanceId)
|
|
471
|
-
);
|
|
472
|
-
let disposed = false;
|
|
473
|
-
const sendError = (message, error) => {
|
|
474
|
-
const response = {
|
|
475
|
-
type: codec.type("error"),
|
|
476
|
-
callId: message.callId,
|
|
477
|
-
connectionId: message.connectionId,
|
|
478
|
-
providerInstanceId: message.providerInstanceId,
|
|
479
|
-
error: errorMessage(error)
|
|
480
|
-
};
|
|
481
|
-
post(options.port, codec.encode(response));
|
|
482
|
-
};
|
|
483
|
-
const onMessage = (event) => {
|
|
484
|
-
if (disposed) return;
|
|
485
|
-
const decoded = codec.decode(event.data);
|
|
486
|
-
if (isCancelMessage(decoded, codec)) {
|
|
487
|
-
if (decoded.connectionId !== options.handshake.connectionId) return;
|
|
488
|
-
const call = pending.get(decoded.callId);
|
|
489
|
-
if (call?.providerInstanceId === decoded.providerInstanceId) {
|
|
490
|
-
call.controller.abort(new Error("Remote service request cancelled"));
|
|
491
|
-
}
|
|
492
|
-
return;
|
|
493
|
-
}
|
|
494
|
-
if (!isCallMessage(decoded, codec)) return;
|
|
495
|
-
const message = decoded;
|
|
496
|
-
if (message.connectionId !== options.handshake.connectionId) {
|
|
497
|
-
sendError(message, Object.assign(new Error("Remote service connection mismatch"), { code: "service.connection_mismatch" }));
|
|
498
|
-
return;
|
|
499
|
-
}
|
|
500
|
-
if (!activeProviderInstanceIds.has(message.providerInstanceId)) {
|
|
501
|
-
sendError(message, Object.assign(new Error("Remote service provider mismatch"), { code: "service.provider_mismatch" }));
|
|
502
|
-
return;
|
|
503
|
-
}
|
|
504
|
-
if (pending.has(message.callId)) {
|
|
505
|
-
sendError(message, Object.assign(new Error("Remote service callId is duplicated"), { code: "service.duplicate_call" }));
|
|
506
|
-
return;
|
|
507
|
-
}
|
|
508
|
-
const controller = new AbortController();
|
|
509
|
-
pending.set(message.callId, { controller, providerInstanceId: message.providerInstanceId });
|
|
510
|
-
void (async () => {
|
|
511
|
-
try {
|
|
512
|
-
const result = await options.handleCall({ message, signal: controller.signal });
|
|
513
|
-
if (controller.signal.aborted || disposed) return;
|
|
514
|
-
const response = {
|
|
515
|
-
type: codec.type("result"),
|
|
516
|
-
callId: message.callId,
|
|
517
|
-
connectionId: message.connectionId,
|
|
518
|
-
providerInstanceId: message.providerInstanceId,
|
|
519
|
-
result
|
|
520
|
-
};
|
|
521
|
-
post(options.port, codec.encode(response));
|
|
522
|
-
} catch (error) {
|
|
523
|
-
if (disposed) return;
|
|
524
|
-
sendError(message, error);
|
|
525
|
-
} finally {
|
|
526
|
-
pending.delete(message.callId);
|
|
527
|
-
}
|
|
528
|
-
})();
|
|
529
|
-
};
|
|
530
|
-
options.port.addEventListener("message", onMessage);
|
|
531
|
-
options.port.start();
|
|
532
|
-
post(options.port, codec.encode({
|
|
533
|
-
type: codec.type("handshake"),
|
|
534
|
-
handshake: options.handshake
|
|
535
|
-
}));
|
|
536
|
-
post(options.port, codec.encode({
|
|
537
|
-
type: codec.type("snapshot"),
|
|
538
|
-
snapshot: options.snapshot
|
|
539
|
-
}));
|
|
540
|
-
const dispose = () => {
|
|
541
|
-
if (disposed) return;
|
|
542
|
-
disposed = true;
|
|
543
|
-
options.port.removeEventListener("message", onMessage);
|
|
544
|
-
for (const { controller } of pending.values()) controller.abort(new Error("Remote service provider disposed"));
|
|
545
|
-
pending.clear();
|
|
546
|
-
if (options.closeOnDispose !== false) options.port.close();
|
|
547
|
-
};
|
|
548
|
-
const provider = {
|
|
549
|
-
publishSnapshot(snapshot) {
|
|
550
|
-
if (disposed) return;
|
|
551
|
-
activeProviderInstanceIds = new Set(
|
|
552
|
-
snapshot.services.map((service) => service.providerInstanceId)
|
|
553
|
-
);
|
|
554
|
-
post(options.port, codec.encode({
|
|
555
|
-
type: codec.type("snapshot"),
|
|
556
|
-
snapshot
|
|
557
|
-
}));
|
|
558
|
-
},
|
|
559
|
-
invalidate(reason = "Remote service invalidated") {
|
|
560
|
-
if (disposed) return;
|
|
561
|
-
activeProviderInstanceIds = /* @__PURE__ */ new Set();
|
|
562
|
-
for (const { controller } of pending.values()) controller.abort(new Error(reason));
|
|
563
|
-
post(options.port, codec.encode({
|
|
564
|
-
type: codec.type("invalidate"),
|
|
565
|
-
reason
|
|
566
|
-
}));
|
|
567
|
-
},
|
|
568
|
-
disconnect(reason = "Remote service disconnected") {
|
|
569
|
-
if (disposed) return;
|
|
570
|
-
post(options.port, codec.encode({
|
|
571
|
-
type: codec.type("disconnect"),
|
|
572
|
-
reason
|
|
573
|
-
}));
|
|
574
|
-
dispose();
|
|
575
|
-
},
|
|
576
|
-
dispose
|
|
577
|
-
};
|
|
578
|
-
return provider;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
export { createMessagePortServiceProvider, createScopedRegistryFacade, createUpgradeGate, defineRuntimeUnitDependencies, defineRuntimeUnitProvidedContracts, runtimeCapabilityContractVersion, verifyPermissionLease };
|
|
1991
|
+
export { RUNTIME_ERROR_TYPE, RUNTIME_HELLO_TYPE, RUNTIME_PROTOCOL_VERSION, RUNTIME_RESYNC_TYPE, RUNTIME_SNAPSHOT_TYPE, RuntimeInitializationError, RuntimeUnavailableError, connectSharedWorker, createMessagePortServiceProvider, createRuntimeMessageCodec, createScopedRegistryFacade, createUpgradeGate, createWindowApp, definePlugin, defineRuntimeUnitDependencies, defineRuntimeUnitProvidedContracts, isRuntimeError, isRuntimeHello, isRuntimeResync, isRuntimeSnapshot, materializePluginDefinition, materializePluginDefinitions, runtimeCapabilityContractVersion, startSharedWorkerApp, unitSnapshotFromState, verifyPermissionLease };
|
|
582
1992
|
//# sourceMappingURL=index.js.map
|
|
583
1993
|
//# sourceMappingURL=index.js.map
|