@floegence/redevplugin-ui 3.0.27 → 3.0.29

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.
@@ -340,6 +340,7 @@ export interface paths {
340
340
  };
341
341
  get?: never;
342
342
  put?: never;
343
+ /** @description Authorized worker calls wait for runtime startup and plugin preparation before execution admission. Cancellation or preparation failure prevents dispatch. Executed calls are never automatically retried. */
343
344
  post: operations["callPluginMethod"];
344
345
  delete?: never;
345
346
  options?: never;
@@ -356,6 +357,7 @@ export interface paths {
356
357
  };
357
358
  get?: never;
358
359
  put?: never;
360
+ /** @description Worker confirmation targets wait for runtime startup and plugin preparation before binding the current runtime generation. Preparation does not execute the method. */
359
361
  post: operations["preparePluginMethodConfirmation"];
360
362
  delete?: never;
361
363
  options?: never;
package/dist/surface.d.ts CHANGED
@@ -455,9 +455,14 @@ export type PluginSurfaceHostBootstrap = {
455
455
  runtimeGenerationId: string;
456
456
  };
457
457
  export type PluginSurfaceHostContext = PluginSurfaceContext;
458
+ export type PluginSurfaceOpeningStage = "preparing" | "connecting" | "authorizing" | "initializing" | "committing";
459
+ export type PluginSurfaceOpeningMilestone = "frame_load" | "prepare" | "port_ack" | "token" | "renderer_ready" | "worker_ready" | "first_commit";
458
460
  export type PluginSurfaceOpeningProgress = {
459
461
  phase: "opening";
460
462
  elapsedMs: number;
463
+ stage: PluginSurfaceOpeningStage;
464
+ stageElapsedMs: number;
465
+ pendingMilestones: PluginSurfaceOpeningMilestone[];
461
466
  };
462
467
  export type PluginSurfaceInteractionKind = "activation" | "focus" | "wheel" | "text-selection" | "action";
463
468
  export type PluginSurfaceInteractionEvent = {
package/dist/surface.js CHANGED
@@ -2586,10 +2586,10 @@ export function createOpaquePluginBootstrapHTML(options = {}) {
2586
2586
  currentContext = message.context;
2587
2587
  try { applyStaticDocument(currentDocument); if (currentContext) applySurfaceContext(currentContext); startWorker(currentDocument); }
2588
2588
  catch (error) { return fail(error); }
2589
- requestAnimationFrame(() => requestAnimationFrame(() => {
2590
- sendParent({ type: "redevplugin.surface.first_paint" });
2591
- loadAssets();
2592
- }));
2589
+ // Readiness describes applied initialization, not a browser paint. Hidden
2590
+ // and offscreen opaque frames may never receive an animation frame.
2591
+ sendParent({ type: "redevplugin.surface.renderer_ready" });
2592
+ loadAssets();
2593
2593
  return;
2594
2594
  }
2595
2595
  if (isRecord(message) && exactKeys(message, ["type", "frame_generation_id", "surface_handle", "context"]) && message.type === "redevplugin.surface.context" && message.frame_generation_id === frameGenerationID && message.surface_handle === surfaceHandle && validSurfaceContext(message.context)) {
@@ -2687,6 +2687,10 @@ class PluginSurfaceHostImplementation {
2687
2687
  #port;
2688
2688
  #openSignals;
2689
2689
  #openingFailure;
2690
+ #openingStartedAt = 0;
2691
+ #openingStageStartedAt = 0;
2692
+ #openingStage = "preparing";
2693
+ #openingPending = new Set();
2690
2694
  #quiesce;
2691
2695
  #initialFrameLoad;
2692
2696
  #frameLoaded = false;
@@ -2742,23 +2746,22 @@ class PluginSurfaceHostImplementation {
2742
2746
  }
2743
2747
  this.#opened = true;
2744
2748
  this.#openingFailure = undefined;
2745
- const startedAt = Date.now();
2746
- const progressTimer = setTimeout(() => {
2749
+ this.#openingStartedAt = performance.now();
2750
+ this.#setOpeningStage("preparing", ["frame_load", "prepare"]);
2751
+ const progressTimer = setInterval(() => {
2747
2752
  if (!this.#ready && !this.#disposed) {
2748
- this.#reportOpeningProgress({
2749
- phase: "opening",
2750
- elapsedMs: Math.max(openingProgressDelayMs, Date.now() - startedAt),
2751
- });
2753
+ const progress = this.#openingProgress();
2754
+ this.#reportOpeningProgress({ ...progress, elapsedMs: Math.max(openingProgressDelayMs, progress.elapsedMs) });
2752
2755
  }
2753
2756
  }, openingProgressDelayMs);
2754
2757
  const signals = {
2755
2758
  portAcknowledged: deferred(),
2756
- firstPaint: deferred(),
2759
+ rendererReady: deferred(),
2757
2760
  workerReady: deferred(),
2758
2761
  firstCommit: deferred(),
2759
2762
  };
2760
2763
  void signals.portAcknowledged.promise.catch(() => undefined);
2761
- void signals.firstPaint.promise.catch(() => undefined);
2764
+ void signals.rendererReady.promise.catch(() => undefined);
2762
2765
  void signals.workerReady.promise.catch(() => undefined);
2763
2766
  void signals.firstCommit.promise.catch(() => undefined);
2764
2767
  this.#openSignals = signals;
@@ -2773,9 +2776,11 @@ class PluginSurfaceHostImplementation {
2773
2776
  await withTimeout(this.#completeOpening(signals, initialFrameLoad.promise), this.#loadTimeoutMs, "Plugin surface opening timed out");
2774
2777
  }
2775
2778
  catch (error) {
2776
- const bridgeError = this.#openingFailure ?? toBridgeError(error, "PLUGIN_BRIDGE_HANDSHAKE_FAILED");
2777
- if (bridgeError.errorCode === "PLUGIN_BRIDGE_TIMEOUT")
2779
+ let bridgeError = this.#openingFailure ?? toBridgeError(error, "PLUGIN_BRIDGE_HANDSHAKE_FAILED");
2780
+ if (bridgeError.errorCode === "PLUGIN_BRIDGE_TIMEOUT") {
2781
+ bridgeError = new PluginBridgeError(bridgeError.errorCode, bridgeError.message, undefined, this.#openingProgress(), bridgeError.mutationOutcome);
2778
2782
  this.#reloadLimiter.recordCrash();
2783
+ }
2779
2784
  this.#reportError(bridgeError);
2780
2785
  const revoke = this.#revokeSurface(false);
2781
2786
  this.#disposeLocal();
@@ -2783,20 +2788,28 @@ class PluginSurfaceHostImplementation {
2783
2788
  throw bridgeError;
2784
2789
  }
2785
2790
  finally {
2786
- clearTimeout(progressTimer);
2791
+ clearInterval(progressTimer);
2792
+ this.#openingPending.clear();
2787
2793
  this.#openSignals = undefined;
2788
2794
  this.#openingFailure = undefined;
2789
2795
  this.#initialFrameLoad = undefined;
2790
2796
  }
2791
2797
  }
2792
2798
  async #completeOpening(signals, initialFrameLoad) {
2793
- const [preparation] = await Promise.all([this.#prepareSurface(), initialFrameLoad]);
2799
+ const [preparation] = await Promise.all([
2800
+ this.#prepareSurface().then((value) => {
2801
+ this.#openingPending.delete("prepare");
2802
+ return value;
2803
+ }),
2804
+ initialFrameLoad.then(() => { this.#openingPending.delete("frame_load"); }),
2805
+ ]);
2794
2806
  this.#assertActive();
2795
2807
  validateSurfacePreparation(this.bootstrap, preparation);
2796
2808
  this.#assetSession = preparation.asset_session;
2797
2809
  this.#assetSessionID = preparation.asset_session_id;
2798
2810
  this.#document = preparation.document;
2799
2811
  this.#assets = new Map(preparation.document.assets.map((asset) => [asset.binding_id, asset]));
2812
+ this.#setOpeningStage("connecting", ["port_ack"]);
2800
2813
  const channel = this.#createMessageChannel();
2801
2814
  this.#port = channel.port1;
2802
2815
  this.#port.addEventListener("message", this.#onPortMessage);
@@ -2811,9 +2824,11 @@ class PluginSurfaceHostImplementation {
2811
2824
  }, "*", [channel.port2]);
2812
2825
  await signals.portAcknowledged.promise;
2813
2826
  this.#assertActive();
2827
+ this.#setOpeningStage("authorizing", ["token"]);
2814
2828
  const token = await this.#mintBridgeToken();
2815
2829
  this.#assertActive();
2816
2830
  this.#applyLease(token);
2831
+ this.#setOpeningStage("initializing", ["renderer_ready", "worker_ready"]);
2817
2832
  this.#postToRenderer(removeUndefined({
2818
2833
  type: "redevplugin.surface.initialize",
2819
2834
  frame_generation_id: this.frameGenerationId,
@@ -2822,10 +2837,11 @@ class PluginSurfaceHostImplementation {
2822
2837
  context: this.#surfaceContext,
2823
2838
  }));
2824
2839
  this.#rendererInitialized = true;
2825
- await Promise.all([signals.firstPaint.promise, signals.workerReady.promise]);
2840
+ await Promise.all([signals.rendererReady.promise, signals.workerReady.promise]);
2826
2841
  this.#assertActive();
2827
2842
  this.#bridgeReady = true;
2828
2843
  this.#scheduleLeaseRenewal();
2844
+ this.#setOpeningStage("committing", ["first_commit"]);
2829
2845
  this.#postToRenderer({ type: "redevplugin.bridge.lifecycle", event: { type: "ready" } });
2830
2846
  await signals.firstCommit.promise;
2831
2847
  this.#assertActive();
@@ -2921,7 +2937,7 @@ class PluginSurfaceHostImplementation {
2921
2937
  controller.abort();
2922
2938
  this.#pendingRequestControllers.clear();
2923
2939
  const disposedError = new PluginBridgeError("PLUGIN_BRIDGE_DISPOSED", "Plugin surface host was disposed");
2924
- this.#openSignals?.firstPaint.reject(disposedError);
2940
+ this.#openSignals?.rendererReady.reject(disposedError);
2925
2941
  this.#openSignals?.workerReady.reject(disposedError);
2926
2942
  this.#openSignals?.firstCommit.reject(disposedError);
2927
2943
  this.#openSignals?.portAcknowledged.reject(disposedError);
@@ -2969,20 +2985,24 @@ class PluginSurfaceHostImplementation {
2969
2985
  }
2970
2986
  if (!messageWithinLimit(data))
2971
2987
  return;
2972
- if (hasExactKeys(data, ["type"]) && data.type === "redevplugin.surface.first_paint") {
2973
- this.#openSignals?.firstPaint.resolve();
2988
+ if (hasExactKeys(data, ["type"]) && data.type === "redevplugin.surface.renderer_ready") {
2989
+ this.#openingPending.delete("renderer_ready");
2990
+ this.#openSignals?.rendererReady.resolve();
2974
2991
  return;
2975
2992
  }
2976
2993
  if (hasExactKeys(data, ["type", "frame_generation_id"]) &&
2977
2994
  data.type === "redevplugin.surface.port_ack" && data.frame_generation_id === this.frameGenerationId) {
2995
+ this.#openingPending.delete("port_ack");
2978
2996
  this.#openSignals?.portAcknowledged.resolve();
2979
2997
  return;
2980
2998
  }
2981
2999
  if (hasExactKeys(data, ["type"]) && data.type === "redevplugin.surface.worker_ready") {
3000
+ this.#openingPending.delete("worker_ready");
2982
3001
  this.#openSignals?.workerReady.resolve();
2983
3002
  return;
2984
3003
  }
2985
3004
  if (hasExactKeys(data, ["type"]) && data.type === "redevplugin.surface.first_commit") {
3005
+ this.#openingPending.delete("first_commit");
2986
3006
  this.#openSignals?.firstCommit.resolve();
2987
3007
  return;
2988
3008
  }
@@ -3528,7 +3548,7 @@ class PluginSurfaceHostImplementation {
3528
3548
  this.#rendererInitialized = false;
3529
3549
  const terminalError = opening ? (this.#openingFailure ??= error) : error;
3530
3550
  this.#openSignals?.portAcknowledged.reject(terminalError);
3531
- this.#openSignals?.firstPaint.reject(terminalError);
3551
+ this.#openSignals?.rendererReady.reject(terminalError);
3532
3552
  this.#openSignals?.workerReady.reject(terminalError);
3533
3553
  this.#openSignals?.firstCommit.reject(terminalError);
3534
3554
  if (!opening)
@@ -3583,6 +3603,21 @@ class PluginSurfaceHostImplementation {
3583
3603
  // Observers cannot weaken revocation or local teardown invariants.
3584
3604
  }
3585
3605
  }
3606
+ #setOpeningStage(stage, pending) {
3607
+ this.#openingStage = stage;
3608
+ this.#openingStageStartedAt = performance.now();
3609
+ this.#openingPending = new Set(pending);
3610
+ }
3611
+ #openingProgress() {
3612
+ const now = performance.now();
3613
+ return {
3614
+ phase: "opening",
3615
+ elapsedMs: Math.max(0, Math.floor(now - this.#openingStartedAt)),
3616
+ stage: this.#openingStage,
3617
+ stageElapsedMs: Math.max(0, Math.floor(now - this.#openingStageStartedAt)),
3618
+ pendingMilestones: [...this.#openingPending],
3619
+ };
3620
+ }
3586
3621
  #reportOpeningProgress(progress) {
3587
3622
  try {
3588
3623
  this.#onOpeningProgress?.(progress);
@@ -3,5 +3,5 @@ export * from "./errors.js";
3
3
  export * from "./platform.js";
4
4
  export { PluginSurfaceScope, createPluginSurfaceScope } from "./surface-scope.js";
5
5
  export { PluginSurfaceReloadLimiter, PluginSurfaceSlot, createReDevPluginSurfaceTransport, defaultPluginSurfaceReloadMax, defaultPluginSurfaceReloadWindowMs, isPluginRiskPlan, opaqueSurfaceDocumentSchemaVersion, pluginSurfaceContextSchemaVersion, pluginRiskPlanSchemaVersion, pluginSurfaceFileExportActionWindowMs, pluginSurfaceFileExportMaxBytes, pluginSurfaceFileExportMediaTypes, trustedParentBridgeHandshakeTranscriptSHA256, } from "./surface.js";
6
- export type { BridgeLifecycleEvent, MessageChannelLike, MessageEventLike, MessagePortLike, OpaqueSurfaceAsset, OpaqueSurfaceDocument, OpaqueSurfaceStyle, OpaqueSurfaceWorker, PluginConfirmationDecision, PluginConfirmationHandler, PluginConfirmationIntent, PluginConfirmationPlan, PluginMethodResult, PluginRiskEffect, PluginRiskFlag, PluginRiskPlan, PluginRiskSeverity, PluginExecutionEvent, PluginSurfaceHost, PluginSurfaceCloseResult, PluginSurfaceOpeningProgress, PluginSurfaceInteractionEvent, PluginSurfaceInteractionKind, PluginSurfaceFileExport, PluginSurfaceFileExportHandler, PluginSurfaceFileExportMediaType, PluginSurfaceQuiesceResult, PluginSurfaceRevocationReconciliation, PluginSurfacePreparationResult, PluginSurfaceContext, PluginSurfaceReloadDecision, PluginSurfaceReloadLimiterOptions, PluginSurfaceReloadState, PluginSurfaceSlotOptions, PluginSurfaceSlotState, ReDevPluginSurfaceTransport, ReDevPluginSurfaceTransportOptions, TrustedParentBridgeHandshake, TrustedParentBridgeTokenRequest, WindowLike, } from "./surface.js";
6
+ export type { BridgeLifecycleEvent, MessageChannelLike, MessageEventLike, MessagePortLike, OpaqueSurfaceAsset, OpaqueSurfaceDocument, OpaqueSurfaceStyle, OpaqueSurfaceWorker, PluginConfirmationDecision, PluginConfirmationHandler, PluginConfirmationIntent, PluginConfirmationPlan, PluginMethodResult, PluginRiskEffect, PluginRiskFlag, PluginRiskPlan, PluginRiskSeverity, PluginExecutionEvent, PluginSurfaceHost, PluginSurfaceCloseResult, PluginSurfaceOpeningProgress, PluginSurfaceOpeningStage, PluginSurfaceOpeningMilestone, PluginSurfaceInteractionEvent, PluginSurfaceInteractionKind, PluginSurfaceFileExport, PluginSurfaceFileExportHandler, PluginSurfaceFileExportMediaType, PluginSurfaceQuiesceResult, PluginSurfaceRevocationReconciliation, PluginSurfacePreparationResult, PluginSurfaceContext, PluginSurfaceReloadDecision, PluginSurfaceReloadLimiterOptions, PluginSurfaceReloadState, PluginSurfaceSlotOptions, PluginSurfaceSlotState, ReDevPluginSurfaceTransport, ReDevPluginSurfaceTransportOptions, TrustedParentBridgeHandshake, TrustedParentBridgeTokenRequest, WindowLike, } from "./surface.js";
7
7
  export type { FetchInitLike, FetchLike, FetchResponseLike, PlatformResponse } from "./http.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@floegence/redevplugin-ui",
3
- "version": "3.0.27",
3
+ "version": "3.0.29",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {
@@ -40,7 +40,7 @@
40
40
  "dist"
41
41
  ],
42
42
  "dependencies": {
43
- "@floegence/redevplugin-contracts": "3.0.27"
43
+ "@floegence/redevplugin-contracts": "3.0.29"
44
44
  },
45
45
  "publishConfig": {
46
46
  "access": "public"