@floegence/redevplugin-ui 0.6.17 → 0.6.18
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/capability-client.d.ts +20 -3
- package/dist/capability-client.js +116 -0
- package/dist/contracts.gen.d.ts +50 -34
- package/dist/contracts.gen.js +43 -34
- package/dist/error-codes.gen.d.ts +3 -3
- package/dist/error-codes.gen.js +4 -1
- package/dist/openapi.gen.d.ts +338 -28
- package/dist/platform.js +1 -0
- package/dist/plugin.d.ts +2 -2
- package/dist/surface.d.ts +34 -2
- package/dist/surface.js +148 -6
- package/package.json +2 -2
package/dist/surface.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { PluginBridgeError, type PluginMutationOutcome } from "./errors.js";
|
|
2
|
-
import { pluginUIProtocolVersion } from "./contracts.gen.js";
|
|
3
2
|
import { type FetchLike } from "./http.js";
|
|
4
3
|
import { type PluginSurfaceScope } from "./surface-scope.js";
|
|
5
4
|
import { type PluginUIVNode } from "./ui-reconciler.js";
|
|
@@ -10,6 +9,7 @@ export type PluginJSONValue = null | boolean | number | string | PluginJSONValue
|
|
|
10
9
|
export type PluginJSONObject = {
|
|
11
10
|
[key: string]: PluginJSONValue;
|
|
12
11
|
};
|
|
12
|
+
export type PluginUIProtocolVersion = "plugin-ui-v5" | "plugin-ui-v6";
|
|
13
13
|
export type BridgeLifecycleEvent = {
|
|
14
14
|
type: "ready";
|
|
15
15
|
} | {
|
|
@@ -29,7 +29,7 @@ export type TrustedParentBridgeHandshake = {
|
|
|
29
29
|
asset_session_nonce: string;
|
|
30
30
|
management_revision: number;
|
|
31
31
|
revoke_epoch: number;
|
|
32
|
-
ui_protocol_version:
|
|
32
|
+
ui_protocol_version: PluginUIProtocolVersion;
|
|
33
33
|
};
|
|
34
34
|
export type TrustedParentBridgeTokenRequest = {
|
|
35
35
|
bridge_channel_id: string;
|
|
@@ -154,6 +154,35 @@ export type PluginStreamReadResult = {
|
|
|
154
154
|
terminal_status: PluginStreamTerminalStatus;
|
|
155
155
|
retry_after_ms: 0;
|
|
156
156
|
};
|
|
157
|
+
export type PluginExecutionFailureCode = "adapter_failed" | "contract_invalid" | "platform_failed" | "quota_exceeded" | "runtime_failed";
|
|
158
|
+
type PluginOperationSnapshotActive = {
|
|
159
|
+
operation_id: string;
|
|
160
|
+
status: "running" | "cancel_requested";
|
|
161
|
+
cancelable: boolean;
|
|
162
|
+
created_at: string;
|
|
163
|
+
updated_at: string;
|
|
164
|
+
retry_after_ms: number;
|
|
165
|
+
};
|
|
166
|
+
type PluginOperationSnapshotTerminal = {
|
|
167
|
+
operation_id: string;
|
|
168
|
+
status: "completed" | "canceled" | "orphaned_after_disable" | "orphaned_after_uninstall";
|
|
169
|
+
cancelable: boolean;
|
|
170
|
+
created_at: string;
|
|
171
|
+
updated_at: string;
|
|
172
|
+
retry_after_ms: number;
|
|
173
|
+
terminal_at: string;
|
|
174
|
+
};
|
|
175
|
+
type PluginOperationSnapshotFailed = {
|
|
176
|
+
operation_id: string;
|
|
177
|
+
status: "failed";
|
|
178
|
+
cancelable: boolean;
|
|
179
|
+
created_at: string;
|
|
180
|
+
updated_at: string;
|
|
181
|
+
retry_after_ms: number;
|
|
182
|
+
terminal_at: string;
|
|
183
|
+
failure_code: PluginExecutionFailureCode;
|
|
184
|
+
};
|
|
185
|
+
export type PluginOperationSnapshot = PluginOperationSnapshotActive | PluginOperationSnapshotTerminal | PluginOperationSnapshotFailed;
|
|
157
186
|
export type PluginRiskSeverity = "info" | "low" | "medium" | "high" | "critical";
|
|
158
187
|
export type PluginRiskEffect = "read" | "write" | "execute" | "delete" | "admin";
|
|
159
188
|
export type PluginRiskFlag = {
|
|
@@ -237,6 +266,7 @@ export declare class PluginBridgeClient {
|
|
|
237
266
|
call<T = unknown>(method: string, params?: PluginJSONObject, options?: PluginBridgeRequestOptions): Promise<T>;
|
|
238
267
|
readStream(streamHandle: string, options?: PluginBridgeRequestOptions): Promise<PluginStreamReadResult>;
|
|
239
268
|
cancelOperation(operationID: string, reason?: string, options?: PluginBridgeRequestOptions): Promise<void>;
|
|
269
|
+
operationSnapshot(operationID: string, options?: PluginBridgeRequestOptions): Promise<PluginOperationSnapshot>;
|
|
240
270
|
render(tree: PluginUIVNode): Promise<void>;
|
|
241
271
|
openCanvas(canvasId: string): Promise<PluginCanvasSurface>;
|
|
242
272
|
updateCanvasAccessibility(canvasId: string, state: PluginCanvasAccessibilityState): Promise<void>;
|
|
@@ -333,6 +363,7 @@ export type PluginSurfaceHostBootstrap = {
|
|
|
333
363
|
pluginId: string;
|
|
334
364
|
pluginInstanceId: string;
|
|
335
365
|
pluginVersion: string;
|
|
366
|
+
uiProtocolVersion: PluginUIProtocolVersion;
|
|
336
367
|
surfaceId: string;
|
|
337
368
|
surfaceInstanceId: string;
|
|
338
369
|
activeFingerprint: string;
|
|
@@ -406,6 +437,7 @@ export type ReDevPluginSurfaceTransportOptions = {
|
|
|
406
437
|
export declare function createReDevPluginSurfaceTransport(options?: ReDevPluginSurfaceTransportOptions): ReDevPluginSurfaceTransport;
|
|
407
438
|
export type OpaquePluginBootstrapHTMLOptions = {
|
|
408
439
|
scriptNonce?: string;
|
|
440
|
+
uiProtocolVersion?: PluginUIProtocolVersion;
|
|
409
441
|
};
|
|
410
442
|
export type PluginSurfaceQuiesceResult = {
|
|
411
443
|
outcome: "acknowledged" | "not_ready" | "timed_out";
|
package/dist/surface.js
CHANGED
|
@@ -171,6 +171,23 @@ export class PluginBridgeClient {
|
|
|
171
171
|
reason,
|
|
172
172
|
}), { mutation: true, signal: options.signal });
|
|
173
173
|
}
|
|
174
|
+
operationSnapshot(operationID, options = {}) {
|
|
175
|
+
this.#assertActive();
|
|
176
|
+
if (!validOpaqueHandle(operationID, "operation")) {
|
|
177
|
+
throw new PluginBridgeError("PLUGIN_INVALID_REQUEST", "Plugin operation handle is invalid");
|
|
178
|
+
}
|
|
179
|
+
const id = this.#requestID("operation");
|
|
180
|
+
return this.#request(id, {
|
|
181
|
+
type: "redevplugin.bridge.operation.snapshot",
|
|
182
|
+
id,
|
|
183
|
+
operation_id: operationID,
|
|
184
|
+
}, { signal: options.signal }).then((snapshot) => {
|
|
185
|
+
if (!isPluginOperationSnapshot(snapshot) || snapshot.operation_id !== operationID) {
|
|
186
|
+
throw new PluginBridgeError("PLUGIN_CONTRACT_MISMATCH", "Plugin operation snapshot is invalid");
|
|
187
|
+
}
|
|
188
|
+
return snapshot;
|
|
189
|
+
});
|
|
190
|
+
}
|
|
174
191
|
render(tree) {
|
|
175
192
|
this.#assertActive();
|
|
176
193
|
let validated;
|
|
@@ -714,6 +731,10 @@ export function createOpaquePluginBootstrapHTML(options = {}) {
|
|
|
714
731
|
if (!/^[A-Za-z0-9_-]{8,128}$/.test(scriptNonce)) {
|
|
715
732
|
throw new Error("scriptNonce must contain 8-128 URL-safe characters");
|
|
716
733
|
}
|
|
734
|
+
const uiProtocolVersion = options.uiProtocolVersion ?? pluginUIProtocolVersion;
|
|
735
|
+
if (uiProtocolVersion !== "plugin-ui-v5" && uiProtocolVersion !== "plugin-ui-v6") {
|
|
736
|
+
throw new Error("uiProtocolVersion must be plugin-ui-v5 or plugin-ui-v6");
|
|
737
|
+
}
|
|
717
738
|
const csp = [
|
|
718
739
|
"default-src 'none'",
|
|
719
740
|
`script-src 'nonce-${scriptNonce}'`,
|
|
@@ -732,7 +753,7 @@ export function createOpaquePluginBootstrapHTML(options = {}) {
|
|
|
732
753
|
].join("; ");
|
|
733
754
|
const bootstrapScript = `(() => {
|
|
734
755
|
"use strict";
|
|
735
|
-
const protocolVersion = ${JSON.stringify(
|
|
756
|
+
const protocolVersion = ${JSON.stringify(uiProtocolVersion)};
|
|
736
757
|
const documentSchema = ${JSON.stringify(opaqueSurfaceDocumentSchemaVersion)};
|
|
737
758
|
const workerGlobalKey = ${JSON.stringify(opaquePluginBridgeGlobalKey)};
|
|
738
759
|
const scriptNonce = ${JSON.stringify(scriptNonce)};
|
|
@@ -859,6 +880,13 @@ export function createOpaquePluginBootstrapHTML(options = {}) {
|
|
|
859
880
|
let pendingQuiesceID;
|
|
860
881
|
const pendingWorkerRequests = new Set();
|
|
861
882
|
const requestSequence = { rpc: 0, stream: 0, stream_ack: 0, render: 0, operation: 0, canvas: 0, asset: 0 };
|
|
883
|
+
let operationSnapshotSurfaceTokens = 8;
|
|
884
|
+
let operationSnapshotSurfaceRefillAt = performance.now();
|
|
885
|
+
const operationSnapshotStateIdleMS = 120000;
|
|
886
|
+
let operationSnapshotNextPruneAt = performance.now() + 30000;
|
|
887
|
+
const maxOperationSnapshotStates = 1024;
|
|
888
|
+
const operationSnapshotStates = new Map();
|
|
889
|
+
const operationSnapshotRequests = new Map();
|
|
862
890
|
let renderWindowStartedAt = 0;
|
|
863
891
|
let renderCount = 0;
|
|
864
892
|
let uiRevision = 0;
|
|
@@ -968,6 +996,8 @@ export function createOpaquePluginBootstrapHTML(options = {}) {
|
|
|
968
996
|
for (const url of blobURLs) URL.revokeObjectURL(url);
|
|
969
997
|
blobURLs.clear();
|
|
970
998
|
pendingWorkerRequests.clear();
|
|
999
|
+
operationSnapshotStates.clear();
|
|
1000
|
+
operationSnapshotRequests.clear();
|
|
971
1001
|
pendingAssets.clear();
|
|
972
1002
|
queuedAssets.length = 0;
|
|
973
1003
|
activeAssetReads = 0;
|
|
@@ -1950,6 +1980,48 @@ export function createOpaquePluginBootstrapHTML(options = {}) {
|
|
|
1950
1980
|
return true;
|
|
1951
1981
|
};
|
|
1952
1982
|
const completeWorkerRequest = (id) => pendingWorkerRequests.delete(id);
|
|
1983
|
+
const completeOperationSnapshotRequest = (id) => {
|
|
1984
|
+
const operationID = operationSnapshotRequests.get(id);
|
|
1985
|
+
operationSnapshotRequests.delete(id);
|
|
1986
|
+
const state = operationSnapshotStates.get(operationID);
|
|
1987
|
+
if (state) { state.inFlight = false; state.lastSeen = performance.now(); }
|
|
1988
|
+
completeWorkerRequest(id);
|
|
1989
|
+
};
|
|
1990
|
+
const refillSnapshotTokens = (tokens, refillAt, rate, burst, now) => ({
|
|
1991
|
+
tokens: Math.min(burst, tokens + Math.max(0, now - refillAt) * rate / 1000),
|
|
1992
|
+
refillAt: Math.max(refillAt, now),
|
|
1993
|
+
});
|
|
1994
|
+
const operationSnapshotRateLimit = (operationID, requestID) => {
|
|
1995
|
+
const now = performance.now();
|
|
1996
|
+
const surface = refillSnapshotTokens(operationSnapshotSurfaceTokens, operationSnapshotSurfaceRefillAt, 8, 8, now);
|
|
1997
|
+
operationSnapshotSurfaceTokens = surface.tokens;
|
|
1998
|
+
operationSnapshotSurfaceRefillAt = surface.refillAt;
|
|
1999
|
+
if (operationSnapshotSurfaceTokens < 1) return Math.max(500, Math.min(10000, Math.ceil((1 - operationSnapshotSurfaceTokens) / 8 * 1000)));
|
|
2000
|
+
operationSnapshotSurfaceTokens -= 1;
|
|
2001
|
+
let existing = operationSnapshotStates.get(operationID);
|
|
2002
|
+
if (!existing) {
|
|
2003
|
+
if (now >= operationSnapshotNextPruneAt) {
|
|
2004
|
+
for (const [retainedOperationID, state] of operationSnapshotStates) {
|
|
2005
|
+
if (!state.inFlight && now - state.lastSeen >= operationSnapshotStateIdleMS) operationSnapshotStates.delete(retainedOperationID);
|
|
2006
|
+
}
|
|
2007
|
+
operationSnapshotNextPruneAt = now + 30000;
|
|
2008
|
+
}
|
|
2009
|
+
if (operationSnapshotStates.size >= maxOperationSnapshotStates) return 10000;
|
|
2010
|
+
existing = { tokens: 2, refillAt: now, lastSeen: now, inFlight: false };
|
|
2011
|
+
operationSnapshotStates.set(operationID, existing);
|
|
2012
|
+
}
|
|
2013
|
+
const operation = refillSnapshotTokens(existing.tokens, existing.refillAt, 2, 2, now);
|
|
2014
|
+
existing.tokens = operation.tokens;
|
|
2015
|
+
existing.refillAt = operation.refillAt;
|
|
2016
|
+
existing.lastSeen = now;
|
|
2017
|
+
let retryAfterMS = existing.inFlight ? 500 : 0;
|
|
2018
|
+
if (existing.tokens < 1) retryAfterMS = Math.max(retryAfterMS, Math.ceil((1 - existing.tokens) / 2 * 1000));
|
|
2019
|
+
if (retryAfterMS > 0) return Math.max(500, Math.min(10000, retryAfterMS));
|
|
2020
|
+
existing.tokens -= 1;
|
|
2021
|
+
existing.inFlight = true;
|
|
2022
|
+
operationSnapshotRequests.set(requestID, operationID);
|
|
2023
|
+
return 0;
|
|
2024
|
+
};
|
|
1953
2025
|
const renderRateAllowed = () => {
|
|
1954
2026
|
const now = performance.now();
|
|
1955
2027
|
if (now - renderWindowStartedAt >= 1000) { renderWindowStartedAt = now; renderCount = 0; }
|
|
@@ -2181,6 +2253,18 @@ export function createOpaquePluginBootstrapHTML(options = {}) {
|
|
|
2181
2253
|
sendParent(message);
|
|
2182
2254
|
return;
|
|
2183
2255
|
}
|
|
2256
|
+
if (exactKeys(message, ["type", "id", "operation_id"]) && message.type === "redevplugin.bridge.operation.snapshot" &&
|
|
2257
|
+
typeof message.id === "string" && validOpaqueHandle(message.operation_id, "operation")) {
|
|
2258
|
+
if (protocolVersion !== "plugin-ui-v6") return rejectWorkerRequest(message.id, "plugin operation observation requires plugin-ui-v6");
|
|
2259
|
+
if (!acceptWorkerRequest(message.id, "operation")) return rejectWorkerRequest(message.id, "duplicate, replayed, or excessive plugin request");
|
|
2260
|
+
const retryAfterMS = operationSnapshotRateLimit(message.operation_id, message.id);
|
|
2261
|
+
if (retryAfterMS > 0) {
|
|
2262
|
+
completeWorkerRequest(message.id);
|
|
2263
|
+
return sendWorker({ type: "redevplugin.bridge.response", id: message.id, ok: false, error_code: "PLUGIN_OPERATION_RATE_LIMITED", error: "plugin operation snapshot rate limited", error_details: { retry_after_ms: retryAfterMS } });
|
|
2264
|
+
}
|
|
2265
|
+
sendParent(message);
|
|
2266
|
+
return;
|
|
2267
|
+
}
|
|
2184
2268
|
if (exactKeys(message, ["type", "id", "canvas_id"]) && message.type === "redevplugin.ui.canvas.open" && typeof message.id === "string" && validResourceIdentifier(message.canvas_id)) {
|
|
2185
2269
|
if (!acceptWorkerRequest(message.id, "canvas")) return rejectWorkerRequest(message.id, "duplicate, replayed, or excessive plugin request");
|
|
2186
2270
|
openCanvas(message.id, message.canvas_id);
|
|
@@ -2234,8 +2318,10 @@ export function createOpaquePluginBootstrapHTML(options = {}) {
|
|
|
2234
2318
|
completeWorkerRequest(message.id);
|
|
2235
2319
|
return;
|
|
2236
2320
|
}
|
|
2237
|
-
|
|
2321
|
+
if (operationSnapshotRequests.has(message.id)) completeOperationSnapshotRequest(message.id);
|
|
2322
|
+
else completeWorkerRequest(message.id);
|
|
2238
2323
|
sendParent(message);
|
|
2324
|
+
return;
|
|
2239
2325
|
}
|
|
2240
2326
|
};
|
|
2241
2327
|
const onParentMessage = async (event) => {
|
|
@@ -2254,7 +2340,8 @@ export function createOpaquePluginBootstrapHTML(options = {}) {
|
|
|
2254
2340
|
return;
|
|
2255
2341
|
}
|
|
2256
2342
|
if (message && message.type === "redevplugin.bridge.response" && typeof message.id === "string" && pendingWorkerRequests.has(message.id) && withinLimit(message)) {
|
|
2257
|
-
|
|
2343
|
+
if (operationSnapshotRequests.has(message.id)) completeOperationSnapshotRequest(message.id);
|
|
2344
|
+
else completeWorkerRequest(message.id);
|
|
2258
2345
|
sendWorker(message);
|
|
2259
2346
|
return;
|
|
2260
2347
|
}
|
|
@@ -2414,7 +2501,7 @@ class PluginSurfaceHostImplementation {
|
|
|
2414
2501
|
this.#initialFrameLoad = initialFrameLoad;
|
|
2415
2502
|
this.#iframe.addEventListener("load", this.#onFrameLoad);
|
|
2416
2503
|
hardenPluginSurfaceFrame(this.#iframe);
|
|
2417
|
-
this.#iframe.srcdoc = createOpaquePluginBootstrapHTML({ scriptNonce });
|
|
2504
|
+
this.#iframe.srcdoc = createOpaquePluginBootstrapHTML({ scriptNonce, uiProtocolVersion: this.bootstrap.uiProtocolVersion });
|
|
2418
2505
|
try {
|
|
2419
2506
|
await withTimeout(this.#completeOpening(signals, initialFrameLoad.promise), this.#loadTimeoutMs, "Plugin surface opening timed out");
|
|
2420
2507
|
}
|
|
@@ -2453,7 +2540,7 @@ class PluginSurfaceHostImplementation {
|
|
|
2453
2540
|
iframeWindow.postMessage({
|
|
2454
2541
|
type: "redevplugin.surface.port",
|
|
2455
2542
|
frame_generation_id: this.frameGenerationId,
|
|
2456
|
-
ui_protocol_version:
|
|
2543
|
+
ui_protocol_version: this.bootstrap.uiProtocolVersion,
|
|
2457
2544
|
}, "*", [channel.port2]);
|
|
2458
2545
|
await signals.portAcknowledged.promise;
|
|
2459
2546
|
this.#assertActive();
|
|
@@ -2645,6 +2732,10 @@ class PluginSurfaceHostImplementation {
|
|
|
2645
2732
|
await this.#handleOperationCancel(data);
|
|
2646
2733
|
return;
|
|
2647
2734
|
}
|
|
2735
|
+
if (isOperationSnapshotMessage(data)) {
|
|
2736
|
+
await this.#handleOperationSnapshot(data);
|
|
2737
|
+
return;
|
|
2738
|
+
}
|
|
2648
2739
|
if (isAssetReadMessage(data)) {
|
|
2649
2740
|
await this.#handleAssetRead(data);
|
|
2650
2741
|
}
|
|
@@ -2872,6 +2963,30 @@ class PluginSurfaceHostImplementation {
|
|
|
2872
2963
|
this.#pendingRequestControllers.delete(message.id);
|
|
2873
2964
|
}
|
|
2874
2965
|
}
|
|
2966
|
+
async #handleOperationSnapshot(message) {
|
|
2967
|
+
if (this.bootstrap.uiProtocolVersion !== "plugin-ui-v6") {
|
|
2968
|
+
this.#postError(message.id, "PLUGIN_UI_PROTOCOL_UNSUPPORTED", "Plugin operation observation requires plugin-ui-v6");
|
|
2969
|
+
return;
|
|
2970
|
+
}
|
|
2971
|
+
const controller = this.#registerPendingRequest(message.id);
|
|
2972
|
+
try {
|
|
2973
|
+
const snapshot = await this.#postJSON(`/_redevplugin/api/plugins/surfaces/${encodeURIComponent(this.bootstrap.surfaceInstanceId)}/operations/query`, { operation_id: message.operation_id, bridge_channel_id: this.bridgeChannelId }, controller.signal);
|
|
2974
|
+
if (!isPluginOperationSnapshot(snapshot) || snapshot.operation_id !== message.operation_id) {
|
|
2975
|
+
throw new PluginBridgeError("PLUGIN_CONTRACT_MISMATCH", "Plugin operation snapshot endpoint returned an invalid response");
|
|
2976
|
+
}
|
|
2977
|
+
if (!controller.signal.aborted && !this.#disposed)
|
|
2978
|
+
this.#postResponse(message.id, snapshot);
|
|
2979
|
+
}
|
|
2980
|
+
catch (error) {
|
|
2981
|
+
if (controller.signal.aborted || this.#disposed)
|
|
2982
|
+
return;
|
|
2983
|
+
const bridgeError = toBridgeError(error, "PLUGIN_OPERATION_NOT_FOUND");
|
|
2984
|
+
this.#postError(message.id, bridgeError.errorCode, bridgeError.message, bridgeError.details, bridgeError.mutationOutcome);
|
|
2985
|
+
}
|
|
2986
|
+
finally {
|
|
2987
|
+
this.#pendingRequestControllers.delete(message.id);
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2875
2990
|
async #handleAssetRead(message) {
|
|
2876
2991
|
if (this.#activeAssetReads >= maxConcurrentAssetReads) {
|
|
2877
2992
|
throw new PluginBridgeError("PLUGIN_INVALID_REQUEST", "Plugin asset reads exceed the concurrency limit");
|
|
@@ -3017,7 +3132,7 @@ class PluginSurfaceHostImplementation {
|
|
|
3017
3132
|
asset_session_nonce: this.bootstrap.assetSessionNonce,
|
|
3018
3133
|
management_revision: this.bootstrap.managementRevision,
|
|
3019
3134
|
revoke_epoch: this.bootstrap.revokeEpoch,
|
|
3020
|
-
ui_protocol_version:
|
|
3135
|
+
ui_protocol_version: this.bootstrap.uiProtocolVersion,
|
|
3021
3136
|
};
|
|
3022
3137
|
}
|
|
3023
3138
|
async #postJSON(path, body, signal) {
|
|
@@ -3830,6 +3945,7 @@ function validateHostBootstrap(bootstrap) {
|
|
|
3830
3945
|
bootstrap.pluginId,
|
|
3831
3946
|
bootstrap.pluginInstanceId,
|
|
3832
3947
|
bootstrap.pluginVersion,
|
|
3948
|
+
bootstrap.uiProtocolVersion,
|
|
3833
3949
|
bootstrap.surfaceId,
|
|
3834
3950
|
bootstrap.surfaceInstanceId,
|
|
3835
3951
|
bootstrap.activeFingerprint,
|
|
@@ -3844,6 +3960,9 @@ function validateHostBootstrap(bootstrap) {
|
|
|
3844
3960
|
throw new PluginBridgeError("PLUGIN_CONTRACT_MISMATCH", "Plugin surface bootstrap is incomplete");
|
|
3845
3961
|
}
|
|
3846
3962
|
}
|
|
3963
|
+
if (bootstrap.uiProtocolVersion !== "plugin-ui-v5" && bootstrap.uiProtocolVersion !== "plugin-ui-v6") {
|
|
3964
|
+
throw new PluginBridgeError("PLUGIN_CONTRACT_MISMATCH", "Plugin surface UI protocol is unsupported");
|
|
3965
|
+
}
|
|
3847
3966
|
if (!Number.isSafeInteger(bootstrap.managementRevision) || bootstrap.managementRevision < 1 ||
|
|
3848
3967
|
!Number.isSafeInteger(bootstrap.revokeEpoch) || bootstrap.revokeEpoch < 1) {
|
|
3849
3968
|
throw new PluginBridgeError("PLUGIN_CONTRACT_MISMATCH", "Plugin surface revision is invalid");
|
|
@@ -3965,6 +4084,29 @@ function isOperationCancelMessage(value) {
|
|
|
3965
4084
|
validOpaqueHandle(value.operation_id, "operation") &&
|
|
3966
4085
|
(value.reason === undefined || (typeof value.reason === "string" && value.reason.length <= 256));
|
|
3967
4086
|
}
|
|
4087
|
+
function isOperationSnapshotMessage(value) {
|
|
4088
|
+
return hasExactKeys(value, ["type", "id", "operation_id"]) &&
|
|
4089
|
+
value.type === "redevplugin.bridge.operation.snapshot" &&
|
|
4090
|
+
validBridgeRequestID(value.id, "operation") &&
|
|
4091
|
+
validOpaqueHandle(value.operation_id, "operation");
|
|
4092
|
+
}
|
|
4093
|
+
function isPluginOperationSnapshot(value) {
|
|
4094
|
+
if (!isRecord(value) || !validOpaqueHandle(value.operation_id, "operation") || typeof value.cancelable !== "boolean" ||
|
|
4095
|
+
!Number.isInteger(value.retry_after_ms) || Number(value.retry_after_ms) < 500 || Number(value.retry_after_ms) > 10000 ||
|
|
4096
|
+
!validDateTime(value.created_at) || !validDateTime(value.updated_at))
|
|
4097
|
+
return false;
|
|
4098
|
+
const common = ["operation_id", "status", "cancelable", "created_at", "updated_at", "retry_after_ms"];
|
|
4099
|
+
if (value.status === "running" || value.status === "cancel_requested")
|
|
4100
|
+
return hasExactKeys(value, common);
|
|
4101
|
+
if (["completed", "canceled", "orphaned_after_disable", "orphaned_after_uninstall"].includes(String(value.status))) {
|
|
4102
|
+
return hasExactKeys(value, [...common, "terminal_at"]) && validDateTime(value.terminal_at);
|
|
4103
|
+
}
|
|
4104
|
+
return value.status === "failed" && hasExactKeys(value, [...common, "terminal_at", "failure_code"]) &&
|
|
4105
|
+
validDateTime(value.terminal_at) && ["adapter_failed", "contract_invalid", "platform_failed", "quota_exceeded", "runtime_failed"].includes(String(value.failure_code));
|
|
4106
|
+
}
|
|
4107
|
+
function validDateTime(value) {
|
|
4108
|
+
return typeof value === "string" && value.length > 0 && Number.isFinite(Date.parse(value));
|
|
4109
|
+
}
|
|
3968
4110
|
function isBridgeCancelMessage(value) {
|
|
3969
4111
|
return hasExactKeys(value, ["type", "id"]) &&
|
|
3970
4112
|
value.type === "redevplugin.bridge.cancel" &&
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@floegence/redevplugin-ui",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.18",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"dist"
|
|
33
33
|
],
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@floegence/redevplugin-contracts": "0.6.
|
|
35
|
+
"@floegence/redevplugin-contracts": "0.6.18"
|
|
36
36
|
},
|
|
37
37
|
"publishConfig": {
|
|
38
38
|
"access": "public"
|