@floegence/redevplugin-ui 0.4.2 → 0.5.1

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/dist/platform.js CHANGED
@@ -1,6 +1,8 @@
1
- import { defaultFetch, readHostEnvelope, trimTrailingSlash } from "./http.js";
1
+ import { assertMutationDispatchable, defaultFetch, dispatchMutationRequest, readMutationPlatformResponse, readPlatformResponse, trimTrailingSlash, } from "./http.js";
2
+ import { PluginBridgeError, PluginMutationLifecycleError, PluginTransportError, pluginMutationOutcome, } from "./errors.js";
3
+ import { createReDevPluginSurfaceTransport, openPluginSurfaceInSlot, } from "./surface.js";
2
4
  import { defaultPluginSurfaceScope, disposePluginSurfaceScope, } from "./surface-scope.js";
3
- export function toPluginSurfaceHostBootstrap(value) {
5
+ function toPluginSurfaceHostBootstrap(value) {
4
6
  return {
5
7
  pluginId: value.plugin_id,
6
8
  pluginInstanceId: value.plugin_instance_id,
@@ -13,7 +15,7 @@ export function toPluginSurfaceHostBootstrap(value) {
13
15
  entrySHA256: value.entry_sha256,
14
16
  assetTicket: value.asset_ticket,
15
17
  assetSessionNonce: value.asset_session_nonce,
16
- pluginStateVersion: value.plugin_state_version,
18
+ managementRevision: value.management_revision,
17
19
  revokeEpoch: value.revoke_epoch,
18
20
  runtimeGenerationId: value.runtime_generation_id,
19
21
  };
@@ -22,121 +24,288 @@ export class PluginPlatformClient {
22
24
  #fetch;
23
25
  #apiBaseURL;
24
26
  #surfaceScope;
27
+ #surfaceTransport;
28
+ #onMutationOutcomeUnknown;
25
29
  constructor(options = {}) {
26
30
  this.#fetch = options.fetch ?? defaultFetch();
27
31
  this.#apiBaseURL = trimTrailingSlash(options.apiBaseURL ?? "");
28
32
  this.#surfaceScope = options.surfaceScope ?? defaultPluginSurfaceScope;
33
+ this.#surfaceTransport = options.surfaceTransport;
34
+ this.#onMutationOutcomeUnknown = options.onMutationOutcomeUnknown;
29
35
  }
30
- catalog() { return this.#getJSON("/_redevplugin/api/plugins/catalog"); }
31
- getCompatibility() { return this.#getJSON("/_redevplugin/api/plugins/platform/compatibility"); }
32
- installReleaseRef(request) { return this.#postJSON("/_redevplugin/api/plugins/install-release-ref", request); }
33
- updateReleaseRef(request) { return this.#mutatePlugin("/_redevplugin/api/plugins/update-release-ref", request); }
34
- downgradePlugin(request) { return this.#mutatePlugin("/_redevplugin/api/plugins/downgrade", request); }
35
- enablePlugin(request) { return this.#postJSON("/_redevplugin/api/plugins/enable", request); }
36
- disablePlugin(request) { return this.#mutatePlugin("/_redevplugin/api/plugins/disable", request); }
37
- uninstallPlugin(request) { return this.#mutatePlugin("/_redevplugin/api/plugins/uninstall", request); }
38
- openSurface(request) { return this.#postJSON("/_redevplugin/api/plugins/surfaces/open", request); }
39
- async revokeSurfaceScope() {
40
- try {
41
- return await this.#postJSON("/_redevplugin/api/plugins/surfaces/revoke-scope", {});
36
+ catalog(options = {}) { return this.#getJSON("/_redevplugin/api/plugins/catalog", options); }
37
+ features(options = {}) { return this.#getJSON("/_redevplugin/api/plugins/features", options); }
38
+ getCompatibility(options = {}) { return this.#getJSON("/_redevplugin/api/plugins/platform/compatibility", options); }
39
+ installReleaseRef(request, options = {}) {
40
+ return this.#requestMutation("POST", "/_redevplugin/api/plugins/install-release-ref", request, options);
41
+ }
42
+ updateReleaseRef(request, options = {}) {
43
+ return this.#mutatePlugin("/_redevplugin/api/plugins/update-release-ref", request, options);
44
+ }
45
+ downgradePlugin(request, options = {}) {
46
+ return this.#mutatePlugin("/_redevplugin/api/plugins/downgrade", request, options);
47
+ }
48
+ enablePlugin(request, options = {}) {
49
+ return this.#requestMutation("POST", "/_redevplugin/api/plugins/enable", request, options);
50
+ }
51
+ disablePlugin(request, options = {}) {
52
+ return this.#mutatePlugin("/_redevplugin/api/plugins/disable", request, options);
53
+ }
54
+ uninstallPlugin(request, options = {}) {
55
+ return this.#mutatePlugin("/_redevplugin/api/plugins/uninstall", request, options);
56
+ }
57
+ #openSurface(request) {
58
+ return this.#requestMutation("POST", "/_redevplugin/api/plugins/surfaces/open", request, {});
59
+ }
60
+ openSurfaceInSlot(slot, request, options = {}) {
61
+ const { signal, ...hostOptions } = options;
62
+ const pluginInstanceId = typeof request.plugin_instance_id === "string"
63
+ ? request.plugin_instance_id.trim()
64
+ : "";
65
+ if (!pluginInstanceId) {
66
+ return Promise.reject(new PluginBridgeError("PLUGIN_INVALID_REQUEST", "Plugin surface opening requires a canonical plugin instance identifier"));
42
67
  }
43
- finally {
44
- disposePluginSurfaceScope(this.#surfaceScope);
68
+ if (signal?.aborted) {
69
+ return Promise.reject(new PluginTransportError("Plugin surface opening was aborted before dispatch", signal.reason, "not_committed"));
45
70
  }
71
+ const surfaceTransport = this.#surfaceTransport ??= createReDevPluginSurfaceTransport({
72
+ fetch: this.#fetch,
73
+ apiBaseURL: surfaceTransportAPIBaseURL(this.#apiBaseURL),
74
+ });
75
+ const canonicalRequest = {
76
+ ...request,
77
+ plugin_instance_id: pluginInstanceId,
78
+ };
79
+ return openPluginSurfaceInSlot(slot, {
80
+ pluginInstanceId,
81
+ surfaceScope: this.#surfaceScope,
82
+ signal,
83
+ abortError: () => new PluginTransportError("Plugin surface opening was aborted after dispatch", signal?.reason, "unknown"),
84
+ open: () => this.#openSurface(canonicalRequest).then((result) => ({
85
+ ...hostOptions,
86
+ bootstrap: toPluginSurfaceHostBootstrap(result),
87
+ hostTransport: surfaceTransport,
88
+ surfaceScope: this.#surfaceScope,
89
+ })),
90
+ });
46
91
  }
47
- startRuntime(request = {}) { return this.#postJSON("/_redevplugin/api/plugins/runtime/start", request); }
48
- async stopRuntime() {
92
+ async revokeSurfaceScope(options = {}) {
93
+ let result;
49
94
  try {
50
- return await this.#postJSON("/_redevplugin/api/plugins/runtime/stop", {});
95
+ result = await this.#requestMutation("POST", "/_redevplugin/api/plugins/surfaces/revoke-scope", {}, options);
51
96
  }
52
- finally {
53
- disposePluginSurfaceScope(this.#surfaceScope);
97
+ catch (error) {
98
+ await this.#handleMutationFailure(error, undefined, "Plugin surface scope revocation and local teardown failed");
99
+ throw error;
54
100
  }
101
+ await disposePluginSurfaceScope(this.#surfaceScope);
102
+ return result;
103
+ }
104
+ startRuntime(request, options = {}) {
105
+ return this.#requestMutation("POST", "/_redevplugin/api/plugins/runtime/start", request, options);
106
+ }
107
+ stopRuntime(options = {}) {
108
+ return this.#requestMutation("POST", "/_redevplugin/api/plugins/runtime/stop", {}, options);
109
+ }
110
+ refreshEnabledRuntimeState(options = {}) {
111
+ return this.#requestMutation("POST", "/_redevplugin/api/plugins/runtime/refresh-enabled", {}, options);
112
+ }
113
+ runtimeHealth(options = {}) { return this.#getJSON("/_redevplugin/api/plugins/runtime/health", options); }
114
+ getSettingsSchema(pluginInstanceId, scope, options = {}) {
115
+ return this.#getJSON(`/_redevplugin/api/plugins/${encodeURIComponent(pluginInstanceId)}/settings/schema?scope=${encodeURIComponent(scope)}`, options);
116
+ }
117
+ getSettings(pluginInstanceId, scope, options = {}) {
118
+ return this.#getJSON(`/_redevplugin/api/plugins/${encodeURIComponent(pluginInstanceId)}/settings?scope=${encodeURIComponent(scope)}`, options);
119
+ }
120
+ patchSettings(pluginInstanceId, request, options = {}) {
121
+ return this.#mutatePluginAt("PATCH", `/_redevplugin/api/plugins/${encodeURIComponent(pluginInstanceId)}/settings`, pluginInstanceId, request, options);
55
122
  }
56
- refreshEnabledRuntimeState() { return this.#postJSON("/_redevplugin/api/plugins/runtime/refresh-enabled", {}); }
57
- runtimeHealth() { return this.#getJSON("/_redevplugin/api/plugins/runtime/health"); }
58
- getSettingsSchema(pluginInstanceId) { return this.#getJSON(`/_redevplugin/api/plugins/${encodeURIComponent(pluginInstanceId)}/settings/schema`); }
59
- getSettings(pluginInstanceId) { return this.#getJSON(`/_redevplugin/api/plugins/${encodeURIComponent(pluginInstanceId)}/settings`); }
60
- patchSettings(pluginInstanceId, values) { return this.#patchJSON(`/_redevplugin/api/plugins/${encodeURIComponent(pluginInstanceId)}/settings`, { values }); }
61
- listOperations(pluginInstanceId) { return this.#getJSON(`/_redevplugin/api/plugins/operations${pluginInstanceId ? `?plugin_instance_id=${encodeURIComponent(pluginInstanceId)}` : ""}`); }
62
- getOperation(operationId) { return this.#getJSON(`/_redevplugin/api/plugins/operations/${encodeURIComponent(operationId)}`); }
63
- cancelOperation(operationId, reason) { return this.#postJSON(`/_redevplugin/api/plugins/operations/${encodeURIComponent(operationId)}/cancel`, reason ? { reason } : {}); }
64
- listIntents(options = {}) {
123
+ listOperations(options = {}, requestOptions = {}) {
65
124
  const params = new URLSearchParams();
66
- if (options.intent_id)
67
- params.set("intent_id", options.intent_id);
68
125
  if (options.plugin_instance_id)
69
126
  params.set("plugin_instance_id", options.plugin_instance_id);
127
+ if (options.cursor)
128
+ params.set("cursor", options.cursor);
129
+ if (options.limit !== undefined)
130
+ params.set("limit", String(options.limit));
70
131
  const query = params.toString();
71
- return this.#getJSON(`/_redevplugin/api/plugins/intents${query ? `?${query}` : ""}`);
132
+ return this.#getJSON(`/_redevplugin/api/plugins/operations${query ? `?${query}` : ""}`, requestOptions);
133
+ }
134
+ getOperation(operationId, options = {}) {
135
+ return this.#getJSON(`/_redevplugin/api/plugins/operations/${encodeURIComponent(operationId)}`, options);
72
136
  }
73
- invokeIntent(request) {
74
- return this.#postJSON("/_redevplugin/api/plugins/intents/invoke", request);
137
+ cancelOperation(operationId, reason, options = {}) {
138
+ return this.#requestMutation("POST", `/_redevplugin/api/plugins/operations/${encodeURIComponent(operationId)}/cancel`, reason ? { reason } : {}, options);
75
139
  }
76
- exportData(request) { return this.#postJSON("/_redevplugin/api/plugins/data/export", request); }
77
- importData(request) { return this.#postJSON("/_redevplugin/api/plugins/data/import", request); }
78
- listRetainedData(options = {}) {
140
+ listIntents(options = {}, requestOptions = {}) {
79
141
  const params = new URLSearchParams();
80
- if (options.publisher_id)
81
- params.set("publisher_id", options.publisher_id);
82
- if (options.plugin_id)
83
- params.set("plugin_id", options.plugin_id);
84
- if (options.source_plugin_instance_id)
85
- params.set("source_plugin_instance_id", options.source_plugin_instance_id);
86
- if (options.state)
87
- params.set("state", options.state);
142
+ if (options.intent_id !== undefined)
143
+ params.set("intent_id", options.intent_id);
144
+ if (options.plugin_instance_id !== undefined)
145
+ params.set("plugin_instance_id", options.plugin_instance_id);
88
146
  const query = params.toString();
89
- return this.#getJSON(`/_redevplugin/api/plugins/retained-data${query ? `?${query}` : ""}`);
147
+ return this.#getJSON(`/_redevplugin/api/plugins/intents${query ? `?${query}` : ""}`, requestOptions);
148
+ }
149
+ invokeIntent(request, options = {}) {
150
+ return this.#requestMutation("POST", "/_redevplugin/api/plugins/intents/invoke", request, options);
151
+ }
152
+ exportData(request, options = {}) {
153
+ return this.#requestMutation("POST", "/_redevplugin/api/plugins/data/export", request, options);
154
+ }
155
+ deleteDataExport(request, options = {}) {
156
+ return this.#requestMutation("POST", "/_redevplugin/api/plugins/data/export/delete", request, options);
157
+ }
158
+ importData(request, options = {}) {
159
+ return this.#mutatePluginAt("POST", "/_redevplugin/api/plugins/data/import", request.plugin_instance_id, request, options);
90
160
  }
91
- deleteRetainedData(retainedId) { return this.#postJSON("/_redevplugin/api/plugins/retained-data/delete", { retained_id: retainedId }); }
92
- bindRetainedData(request) { return this.#postJSON("/_redevplugin/api/plugins/retained-data/bind", request); }
93
- cleanupExpiredRetainedData(request = {}) { return this.#postJSON("/_redevplugin/api/plugins/retained-data/cleanup-expired", request); }
94
- listPermissions(pluginInstanceId, activeOnly) {
161
+ listRetainedData(options = {}, requestOptions = {}) {
95
162
  const params = new URLSearchParams();
96
- if (pluginInstanceId)
97
- params.set("plugin_instance_id", pluginInstanceId);
98
- if (activeOnly != null)
99
- params.set("active_only", activeOnly ? "true" : "false");
163
+ if (options.plugin_instance_id)
164
+ params.set("plugin_instance_id", options.plugin_instance_id);
100
165
  const query = params.toString();
101
- return this.#getJSON(`/_redevplugin/api/plugins/permissions${query ? `?${query}` : ""}`);
102
- }
103
- grantPermission(request) { return this.#postJSON("/_redevplugin/api/plugins/permissions/grant", request); }
104
- revokePermission(request) { return this.#postJSON("/_redevplugin/api/plugins/permissions/revoke", request); }
105
- bindSecret(request) { return this.#postJSON("/_redevplugin/api/plugins/secrets/bind", request); }
106
- testSecret(request) { return this.#postJSON("/_redevplugin/api/plugins/secrets/test", request); }
107
- deleteSecret(request) { return this.#postJSON("/_redevplugin/api/plugins/secrets/delete", request); }
108
- listAuditEvents(options = {}) { return this.#getJSON(`/_redevplugin/api/plugins/audit${queryString(options)}`); }
109
- listDiagnosticEvents(options = {}) { return this.#getJSON(`/_redevplugin/api/plugins/diagnostics${queryString(options)}`); }
110
- #getJSON(path) { return this.#requestJSON("GET", path); }
111
- #postJSON(path, body) { return this.#requestJSON("POST", path, body); }
112
- #patchJSON(path, body) { return this.#requestJSON("PATCH", path, body); }
113
- async #mutatePlugin(path, request) {
166
+ return this.#getJSON(`/_redevplugin/api/plugins/retained-data${query ? `?${query}` : ""}`, requestOptions);
167
+ }
168
+ deleteRetainedData(request, options = {}) {
169
+ return this.#requestMutation("POST", "/_redevplugin/api/plugins/retained-data/delete", request, options);
170
+ }
171
+ bindRetainedData(request, options = {}) {
172
+ return this.#mutatePluginAt("POST", "/_redevplugin/api/plugins/retained-data/bind", request.target_plugin_instance_id, request, options);
173
+ }
174
+ cleanupExpiredRetainedData(request = {}, options = {}) {
175
+ return this.#requestMutation("POST", "/_redevplugin/api/plugins/retained-data/cleanup-expired", request, options);
176
+ }
177
+ listPermissions(options = {}, requestOptions = {}) {
178
+ const params = new URLSearchParams();
179
+ if (options.plugin_instance_id !== undefined)
180
+ params.set("plugin_instance_id", options.plugin_instance_id);
181
+ if (options.active_only !== undefined)
182
+ params.set("active_only", options.active_only ? "true" : "false");
183
+ const query = params.toString();
184
+ return this.#getJSON(`/_redevplugin/api/plugins/permissions${query ? `?${query}` : ""}`, requestOptions);
185
+ }
186
+ grantPermission(request, options = {}) {
187
+ return this.#mutatePlugin("/_redevplugin/api/plugins/permissions/grant", request, options);
188
+ }
189
+ revokePermission(request, options = {}) {
190
+ return this.#mutatePlugin("/_redevplugin/api/plugins/permissions/revoke", request, options);
191
+ }
192
+ listSecurityPolicies(options = {}) {
193
+ return this.#getJSON("/_redevplugin/api/plugins/security-policies", options);
194
+ }
195
+ getSecurityPolicy(pluginInstanceId, options = {}) {
196
+ return this.#getJSON(`/_redevplugin/api/plugins/security-policies/${encodeURIComponent(pluginInstanceId)}`, options);
197
+ }
198
+ putSecurityPolicy(pluginInstanceId, request, options = {}) {
199
+ return this.#mutatePluginAt("PUT", `/_redevplugin/api/plugins/security-policies/${encodeURIComponent(pluginInstanceId)}`, pluginInstanceId, request, options);
200
+ }
201
+ deleteSecurityPolicy(pluginInstanceId, request, options = {}) {
202
+ return this.#mutatePluginAt("DELETE", `/_redevplugin/api/plugins/security-policies/${encodeURIComponent(pluginInstanceId)}`, pluginInstanceId, request, options);
203
+ }
204
+ bindSecret(request, options = {}) {
205
+ return this.#requestMutation("POST", "/_redevplugin/api/plugins/secrets/bind", request, options);
206
+ }
207
+ testSecret(request, options = {}) {
208
+ return this.#requestMutation("POST", "/_redevplugin/api/plugins/secrets/test", request, options);
209
+ }
210
+ deleteSecret(request, options = {}) {
211
+ return this.#requestMutation("POST", "/_redevplugin/api/plugins/secrets/delete", request, options);
212
+ }
213
+ listDiagnosticEvents(options = {}, requestOptions = {}) {
214
+ return this.#getJSON(`/_redevplugin/api/plugins/diagnostics${queryString(options)}`, requestOptions);
215
+ }
216
+ #getJSON(path, options) {
217
+ return this.#requestJSON("GET", path, options);
218
+ }
219
+ #mutatePlugin(path, request, options) {
220
+ return this.#mutatePluginAt("POST", path, request.plugin_instance_id, request, options);
221
+ }
222
+ async #mutatePluginAt(method, path, pluginInstanceId, request, options) {
223
+ let result;
114
224
  try {
115
- return await this.#postJSON(path, request);
225
+ result = await this.#requestMutation(method, path, request, options);
116
226
  }
117
- finally {
118
- disposePluginSurfaceScope(this.#surfaceScope, request.plugin_instance_id);
227
+ catch (error) {
228
+ await this.#handleMutationFailure(error, pluginInstanceId, "Plugin mutation and local surface teardown failed");
229
+ throw error;
119
230
  }
231
+ await disposePluginSurfaceScope(this.#surfaceScope, pluginInstanceId);
232
+ return result;
120
233
  }
121
- async #requestJSON(method, path, body) {
122
- const headers = { "Accept": "application/json" };
123
- if (body !== undefined)
124
- headers["Content-Type"] = "application/json";
125
- const response = await this.#fetch(this.#apiBaseURL + path, {
234
+ async #handleMutationFailure(error, pluginInstanceId, message) {
235
+ if (pluginMutationOutcome(error) === "not_committed")
236
+ return;
237
+ const lifecycleErrors = [];
238
+ try {
239
+ await disposePluginSurfaceScope(this.#surfaceScope, pluginInstanceId);
240
+ }
241
+ catch (caught) {
242
+ lifecycleErrors.push(caught);
243
+ }
244
+ try {
245
+ this.#onMutationOutcomeUnknown?.(pluginInstanceId);
246
+ }
247
+ catch (caught) {
248
+ lifecycleErrors.push(caught);
249
+ }
250
+ if (lifecycleErrors.length > 0)
251
+ throw new PluginMutationLifecycleError(message, error, lifecycleErrors);
252
+ }
253
+ async #requestJSON(method, path, options) {
254
+ let response;
255
+ try {
256
+ response = await this.#fetch(this.#apiBaseURL + path, {
257
+ method,
258
+ headers: { "Accept": "application/json" },
259
+ credentials: "same-origin",
260
+ signal: options.signal,
261
+ });
262
+ }
263
+ catch (cause) {
264
+ throw new PluginTransportError(`Plugin platform request failed for ${method} ${path}`, cause);
265
+ }
266
+ return readPlatformResponse(response);
267
+ }
268
+ async #requestMutation(method, path, body, options) {
269
+ const operation = `${method} ${path}`;
270
+ assertMutationDispatchable(options.signal, operation);
271
+ const headers = {
272
+ "Accept": "application/json",
273
+ "Content-Type": "application/json",
274
+ };
275
+ let encodedBody;
276
+ try {
277
+ encodedBody = JSON.stringify(body);
278
+ }
279
+ catch (cause) {
280
+ throw new PluginTransportError(`Plugin platform request body serialization failed for ${operation}`, cause, "not_committed");
281
+ }
282
+ const response = await dispatchMutationRequest(this.#fetch, this.#apiBaseURL + path, {
126
283
  method,
127
284
  headers,
128
- body: body === undefined ? undefined : JSON.stringify(body),
285
+ body: encodedBody,
129
286
  credentials: "same-origin",
130
- });
131
- return readHostEnvelope(response, "PLUGIN_PLATFORM_REQUEST_FAILED");
287
+ signal: options.signal,
288
+ }, operation);
289
+ return readMutationPlatformResponse(response);
132
290
  }
133
291
  }
134
292
  function queryString(values) {
135
293
  const params = new URLSearchParams();
136
294
  for (const [key, value] of Object.entries(values)) {
137
- if (value != null && value !== "")
295
+ if (value !== undefined)
138
296
  params.set(key, String(value));
139
297
  }
140
298
  const query = params.toString();
141
299
  return query ? `?${query}` : "";
142
300
  }
301
+ function surfaceTransportAPIBaseURL(value) {
302
+ if (!value || value.startsWith("/"))
303
+ return value;
304
+ const parsed = new URL(value);
305
+ const currentOrigin = globalThis.location?.origin;
306
+ if (!currentOrigin || currentOrigin === "null" || parsed.origin !== currentOrigin ||
307
+ parsed.username !== "" || parsed.password !== "" || parsed.search !== "" || parsed.hash !== "") {
308
+ throw new TypeError("Plugin surface transport apiBaseURL must be same-origin");
309
+ }
310
+ return trimTrailingSlash(parsed.pathname);
311
+ }
package/dist/plugin.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { PluginBridgeClient } from "./surface.js";
2
2
  export { PluginBridgeError } from "./errors.js";
3
3
  export { callCapabilityOperation, callCapabilityStream, callCapabilitySync, isCapabilityBusinessError, } from "./capability-client.js";
4
- export type { BridgeLifecycleEvent, MessagePortLike, PluginBridgeClientOptions, PluginCanvasAccessibilityState, PluginCanvasInputEvent, PluginCanvasKeyEvent, PluginCanvasPointerEvent, PluginCanvasResizeEvent, PluginCanvasSurface, PluginJSONObject, PluginJSONValue, PluginMethodResult, PluginStreamTerminalStatus, PluginUIActionEvent, PluginUIAttributeValue, PluginUIVNode, } from "./surface.js";
5
- export type { PluginCapabilitySchema, PluginCapabilityStreamEvent, PluginCapabilityStreamReadResult, PluginOperation, PluginStream, } from "./capability-client.js";
4
+ export type { BridgeLifecycleEvent, MessagePortLike, PluginBridgeClientOptions, PluginBridgeRequestOptions, PluginCanvasAccessibilityState, PluginCanvasInputEvent, PluginCanvasKeyEvent, PluginCanvasPointerEvent, PluginCanvasResizeEvent, PluginCanvasSurface, PluginJSONObject, PluginJSONValue, PluginMethodResult, PluginStreamTerminalStatus, PluginUIActionEvent, PluginUIAttributeValue, PluginUIElementVNode, PluginUIPatchOperation, PluginUIVNode, } from "./surface.js";
5
+ export type { PluginCapabilitySchema, PluginCapabilityEffect, PluginCapabilityOperationContract, PluginCapabilityStreamContract, PluginCapabilitySyncContract, PluginCapabilityStreamEvent, PluginCapabilityStreamReadResult, PluginOperation, PluginStream, } from "./capability-client.js";
@@ -4,5 +4,5 @@ export declare class PluginSurfaceScope {
4
4
  }
5
5
  export declare function createPluginSurfaceScope(): PluginSurfaceScope;
6
6
  export declare const defaultPluginSurfaceScope: PluginSurfaceScope;
7
- export declare function registerPluginSurface(scope: PluginSurfaceScope, pluginInstanceId: string, dispose: () => void): () => void;
8
- export declare function disposePluginSurfaceScope(scope: PluginSurfaceScope, pluginInstanceId?: string): void;
7
+ export declare function registerPluginSurface(scope: PluginSurfaceScope, pluginInstanceId: string, dispose: () => Promise<void> | void): () => void;
8
+ export declare function disposePluginSurfaceScope(scope: PluginSurfaceScope, pluginInstanceId?: string): Promise<void>;
@@ -1,4 +1,10 @@
1
1
  const registrations = new WeakMap();
2
+ function canonicalPluginInstanceId(pluginInstanceId) {
3
+ const canonical = typeof pluginInstanceId === "string" ? pluginInstanceId.trim() : "";
4
+ if (!canonical)
5
+ throw new TypeError("Plugin instance identifier must be a non-empty string");
6
+ return canonical;
7
+ }
2
8
  export class PluginSurfaceScope {
3
9
  constructor() {
4
10
  registrations.set(this, new Map());
@@ -15,17 +21,27 @@ export function registerPluginSurface(scope, pluginInstanceId, dispose) {
15
21
  const state = registrations.get(scope);
16
22
  if (!state)
17
23
  throw new TypeError("Plugin surface scope is invalid");
18
- const registration = Symbol(pluginInstanceId);
19
- state.set(registration, { pluginInstanceId, dispose });
24
+ const canonicalPluginId = canonicalPluginInstanceId(pluginInstanceId);
25
+ const registration = Symbol(canonicalPluginId);
26
+ state.set(registration, { pluginInstanceId: canonicalPluginId, dispose });
20
27
  return () => state.delete(registration);
21
28
  }
22
- export function disposePluginSurfaceScope(scope, pluginInstanceId) {
29
+ export async function disposePluginSurfaceScope(scope, pluginInstanceId) {
23
30
  const state = registrations.get(scope);
24
31
  if (!state)
25
32
  throw new TypeError("Plugin surface scope is invalid");
26
- const selected = [...state.entries()].filter(([, registration]) => pluginInstanceId === undefined || registration.pluginInstanceId === pluginInstanceId);
33
+ const canonicalPluginId = pluginInstanceId === undefined
34
+ ? undefined
35
+ : canonicalPluginInstanceId(pluginInstanceId);
36
+ const selected = [...state.entries()].filter(([, registration]) => canonicalPluginId === undefined || registration.pluginInstanceId === canonicalPluginId);
27
37
  for (const [key] of selected)
28
38
  state.delete(key);
29
- for (const [, registration] of selected)
30
- registration.dispose();
39
+ const results = await Promise.allSettled(selected.map(([, registration]) => Promise.resolve().then(() => registration.dispose())));
40
+ const failures = results
41
+ .filter((result) => result.status === "rejected")
42
+ .map((result) => result.reason);
43
+ if (failures.length === 1)
44
+ throw failures[0];
45
+ if (failures.length > 1)
46
+ throw new AggregateError(failures, "Plugin surface scope teardown failed");
31
47
  }
package/dist/surface.d.ts CHANGED
@@ -1,9 +1,10 @@
1
- import { PluginBridgeError } from "./errors.js";
1
+ import { PluginBridgeError, type PluginMutationOutcome } from "./errors.js";
2
2
  import { pluginUIProtocolVersion } from "./contracts.gen.js";
3
- import { type OpaqueSurfaceAllowedTag } from "./opaque-surface-policy.gen.js";
4
3
  import { type FetchLike } from "./http.js";
5
4
  import { type PluginSurfaceScope } from "./surface-scope.js";
6
- export declare const opaqueSurfaceDocumentSchemaVersion: "redevplugin.opaque_surface_document.v2";
5
+ import { type PluginUIVNode } from "./ui-reconciler.js";
6
+ export type { PluginUIAttributeValue, PluginUIElementVNode, PluginUIPatchOperation, PluginUIVNode, } from "./ui-reconciler.js";
7
+ export declare const opaqueSurfaceDocumentSchemaVersion: "redevplugin.opaque_surface_document.v3";
7
8
  export declare const pluginRiskPlanSchemaVersion: "redevplugin.capability.risk_plan.v1";
8
9
  export type PluginJSONValue = null | boolean | number | string | PluginJSONValue[] | PluginJSONObject;
9
10
  export type PluginJSONObject = {
@@ -26,7 +27,7 @@ export type TrustedParentBridgeHandshake = {
26
27
  active_fingerprint: string;
27
28
  bridge_nonce: string;
28
29
  asset_session_nonce: string;
29
- plugin_state_version: number;
30
+ management_revision: number;
30
31
  revoke_epoch: number;
31
32
  ui_protocol_version: typeof pluginUIProtocolVersion;
32
33
  };
@@ -53,6 +54,7 @@ export type PluginBridgeResponse = {
53
54
  error_code: string;
54
55
  error: string;
55
56
  error_details?: PluginJSONObject;
57
+ mutation_outcome?: PluginMutationOutcome;
56
58
  };
57
59
  export type PluginBridgeCancelMessage = {
58
60
  type: "redevplugin.bridge.cancel";
@@ -70,6 +72,9 @@ export type PluginBridgeLifecycleAckMessage = {
70
72
  export type PluginUIActionEvent = {
71
73
  action: string;
72
74
  event: "click" | "input" | "change" | "submit" | "escape";
75
+ targetKey: string;
76
+ editRevision: number;
77
+ isComposing: boolean;
73
78
  value?: string;
74
79
  checked?: boolean;
75
80
  form_data?: Record<string, string>;
@@ -117,13 +122,6 @@ export type PluginCanvasPointerEvent = {
117
122
  pressure: number;
118
123
  };
119
124
  export type PluginCanvasInputEvent = PluginCanvasFocusEvent | PluginCanvasResizeEvent | PluginCanvasKeyEvent | PluginCanvasPointerEvent;
120
- export type PluginUIAttributeValue = string | number | boolean;
121
- export type PluginUIVNode = string | {
122
- type: "element";
123
- tag: OpaqueSurfaceAllowedTag;
124
- attributes?: Record<string, PluginUIAttributeValue>;
125
- children?: PluginUIVNode[];
126
- };
127
125
  export type PluginMethodResult<T = unknown> = {
128
126
  data: T;
129
127
  operation_id?: string;
@@ -227,16 +225,19 @@ export type PluginBridgeClientOptions = {
227
225
  port?: MessagePortLike;
228
226
  surfaceHandle?: string;
229
227
  };
228
+ export type PluginBridgeRequestOptions = {
229
+ signal?: AbortSignal;
230
+ };
230
231
  export declare class PluginBridgeClient {
231
232
  #private;
232
233
  readonly surfaceHandle: string;
233
234
  readonly timeoutMs: number;
234
235
  constructor(options?: PluginBridgeClientOptions);
235
236
  ready(): Promise<void>;
236
- call<T = unknown>(method: string, params?: PluginJSONObject): Promise<T>;
237
- readStream(streamHandle: string): Promise<PluginStreamReadResult>;
238
- cancelOperation(operationID: string, reason?: string): Promise<void>;
239
- render(tree: PluginUIVNode | PluginUIVNode[]): Promise<void>;
237
+ call<T = unknown>(method: string, params?: PluginJSONObject, options?: PluginBridgeRequestOptions): Promise<T>;
238
+ readStream(streamHandle: string, options?: PluginBridgeRequestOptions): Promise<PluginStreamReadResult>;
239
+ cancelOperation(operationID: string, reason?: string, options?: PluginBridgeRequestOptions): Promise<void>;
240
+ render(tree: PluginUIVNode): Promise<void>;
240
241
  openCanvas(canvasId: string): Promise<PluginCanvasSurface>;
241
242
  updateCanvasAccessibility(canvasId: string, state: PluginCanvasAccessibilityState): Promise<void>;
242
243
  loadImageAsset(assetId: string): Promise<ImageBitmap>;
@@ -322,7 +323,7 @@ export type PluginSurfacePreparationResult = {
322
323
  asset_session_nonce: string;
323
324
  entry_path: string;
324
325
  entry_sha256: string;
325
- plugin_state_version: number;
326
+ management_revision: number;
326
327
  revoke_epoch: number;
327
328
  issued_at: string;
328
329
  expires_at: string;
@@ -340,7 +341,7 @@ export type PluginSurfaceHostBootstrap = {
340
341
  entrySHA256: string;
341
342
  assetTicket: string;
342
343
  assetSessionNonce: string;
343
- pluginStateVersion: number;
344
+ managementRevision: number;
344
345
  revokeEpoch: number;
345
346
  runtimeGenerationId: string;
346
347
  };
@@ -348,6 +349,22 @@ export type PluginSurfaceOpeningProgress = {
348
349
  phase: "opening";
349
350
  elapsedMs: number;
350
351
  };
352
+ export type PluginSurfaceHost = {
353
+ readonly element: HTMLIFrameElement;
354
+ readonly surfaceInstanceId: string;
355
+ sendLifecycle(event: Exclude<BridgeLifecycleEvent, {
356
+ type: "ready" | "dispose";
357
+ }>): void;
358
+ close(): Promise<PluginSurfaceCloseResult>;
359
+ dispose(): Promise<void>;
360
+ };
361
+ export type PreparedPluginSurfaceHost = PluginSurfaceHost & {
362
+ readonly bootstrap: PluginSurfaceHostBootstrap;
363
+ readonly bridgeChannelId: string;
364
+ readonly frameGenerationId: string;
365
+ readonly surfaceHandle: string;
366
+ open(): Promise<void>;
367
+ };
351
368
  export type PluginSurfaceHostOptions = {
352
369
  bootstrap: PluginSurfaceHostBootstrap;
353
370
  hostTransport: ReDevPluginSurfaceTransport;
@@ -361,6 +378,13 @@ export type PluginSurfaceHostOptions = {
361
378
  onOpeningProgress?: (progress: PluginSurfaceOpeningProgress) => void;
362
379
  onError?: (error: PluginBridgeError) => void;
363
380
  };
381
+ type PluginSurfaceOpeningRequest = {
382
+ pluginInstanceId: string;
383
+ surfaceScope: PluginSurfaceScope;
384
+ signal?: AbortSignal;
385
+ abortError: () => Error;
386
+ open: () => Promise<PluginSurfaceHostOptions>;
387
+ };
364
388
  declare const redevPluginSurfaceTransportBrand: unique symbol;
365
389
  export type ReDevPluginSurfaceTransport = {
366
390
  readonly [redevPluginSurfaceTransportBrand]: true;
@@ -373,21 +397,30 @@ export declare function createReDevPluginSurfaceTransport(options?: ReDevPluginS
373
397
  export type OpaquePluginBootstrapHTMLOptions = {
374
398
  scriptNonce?: string;
375
399
  };
400
+ export type PluginSurfaceQuiesceResult = {
401
+ outcome: "acknowledged" | "not_ready" | "timed_out";
402
+ durationMs: number;
403
+ };
404
+ export type PluginSurfaceCloseResult = {
405
+ quiesce: PluginSurfaceQuiesceResult;
406
+ revokeDurationMs: number;
407
+ totalDurationMs: number;
408
+ };
376
409
  export declare function createOpaquePluginBootstrapHTML(options?: OpaquePluginBootstrapHTMLOptions): string;
377
- export declare class PluginSurfaceHost {
410
+ export declare function createPreparedPluginSurfaceHost(options: PluginSurfaceHostOptions): PreparedPluginSurfaceHost;
411
+ export type PluginSurfaceSlotState = "empty" | "opening" | "ready" | "error" | "disposed";
412
+ export type PluginSurfaceSlotOptions = {
413
+ stage: HTMLElement;
414
+ onStateChange?: (state: PluginSurfaceSlotState, error?: PluginBridgeError) => void;
415
+ onSurfaceClosed?: (result: PluginSurfaceCloseResult) => void;
416
+ };
417
+ export declare function openPluginSurfaceInSlot(slot: PluginSurfaceSlot, request: PluginSurfaceOpeningRequest): Promise<PluginSurfaceHost>;
418
+ export declare function openPreparedPluginSurfaceInSlot(slot: PluginSurfaceSlot, options: PluginSurfaceHostOptions | PromiseLike<PluginSurfaceHostOptions>): Promise<PluginSurfaceHost>;
419
+ export declare class PluginSurfaceSlot {
378
420
  #private;
379
- readonly element: HTMLIFrameElement;
380
- readonly bootstrap: PluginSurfaceHostBootstrap;
381
- readonly bridgeChannelId: string;
382
- readonly frameGenerationId: string;
383
- readonly surfaceHandle: string;
384
- static create(options: PluginSurfaceHostOptions): PluginSurfaceHost;
421
+ readonly element: HTMLElement;
422
+ static create(options: PluginSurfaceSlotOptions): PluginSurfaceSlot;
385
423
  private constructor();
386
- open(): Promise<void>;
387
- sendLifecycle(event: Exclude<BridgeLifecycleEvent, {
388
- type: "ready" | "dispose";
389
- }>): void;
390
- close(): Promise<void>;
391
- dispose(): void;
424
+ close(): Promise<PluginSurfaceCloseResult | undefined>;
425
+ dispose(): Promise<void>;
392
426
  }
393
- export {};