@salesforce/sf-embedding-bridge 2.2.4 → 2.2.5-rc.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.
@@ -1,5 +1,5 @@
1
1
  import { type JsonRpcMeta, type Transport } from "@salesforce/jsonrpc";
2
- import { type EventsDispatchParams, type EventsSubscribeResult, type HostMetaData, type ResizeParams, type UiStateChangedParams, type UiStateSubscribeResult } from "@salesforce/sf-embedding-contract";
2
+ import { type EmbeddingErrorParams, type EventsDispatchParams, type EventsSubscribeResult, type HostMetaData, type ResizeParams, type UiStateChangedParams, type UiStateSubscribeResult } from "@salesforce/sf-embedding-contract";
3
3
  import { EmbeddingResizer, JsonRpcRouter, type RequestHandler, type Unregister } from "utils";
4
4
  import { type BootstrapFailureReason } from "./bootstrap-listener";
5
5
  export type HostInitializedPayload = Record<string, unknown>;
@@ -54,7 +54,15 @@ export declare class EmbeddingBridge extends JsonRpcRouter {
54
54
  sendResize(dimensions: ResizeParams): void;
55
55
  /** Attach an auto-emit resizer; stopped on dispose. Replaces any prior resizer. */
56
56
  attachResizer(resizer: EmbeddingResizer): void;
57
+ /** Send outbound RPC request; on rejection, report error to host then rethrow. */
58
+ send<TParams = unknown, TResult = unknown>(method: string, params: TParams): Promise<TResult>;
57
59
  dispose(): void;
60
+ /**
61
+ * Report an error to the host for observability. Non-PII by construction.
62
+ * Two usage modes: (1) automatic uncaught-error capture (sdkMethod="uncaught"); (2) deliberate
63
+ * author reporting from try/catch (concrete sdkMethod name passed by the author).
64
+ */
65
+ reportError(params: EmbeddingErrorParams): void;
58
66
  }
59
67
  export type { RequestHandler, Unregister };
60
68
  /** Single-attempt per iframe; recovery from failure requires a host-driven remount. */
package/dist/index.cjs.js CHANGED
@@ -90,6 +90,8 @@ class JsonRpcHandlerError extends Error {
90
90
  const HOST_INITIALIZED_METHOD = "ui/notifications/host-initialized";
91
91
  /** Embedding acknowledges receipt of `host-initialized`. Fire-and-forget. */
92
92
  const EMBEDDING_INITIALIZED_METHOD = "ui/notifications/embedding-initialized";
93
+ /** Iframe-side error notification. Fire-and-forget, non-PII observability signal. */
94
+ const ERROR_METHOD = "ui/notifications/error";
93
95
 
94
96
  // Resize hint channel: embedding → host, fire-and-forget. CSS pixels.
95
97
  const RESIZE_METHOD = "ui/notifications/resize";
@@ -99,6 +101,10 @@ const UI_STATE_SUBSCRIBE_METHOD = "ui/subscribe/ui-state";
99
101
  const UI_STATE_UNSUBSCRIBE_METHOD = "ui/unsubscribe/ui-state";
100
102
  const UI_STATE_CHANGED_METHOD = "ui/notifications/ui-state-changed";
101
103
 
104
+ /*
105
+ * Copyright 2026 Salesforce, Inc. All rights reserved.
106
+ * For full license text, see the LICENSE.txt file in this package.
107
+ */
102
108
  // Bootstrap envelope validator for the iframe (embedding) side of the
103
109
  // sf-embedding handshake.
104
110
  //
@@ -142,6 +148,10 @@ function validateBootstrapEnvelope(input) {
142
148
  return { ok: true, envelope: input.data, port: input.ports[0] };
143
149
  }
144
150
 
151
+ /*
152
+ * Copyright 2026 Salesforce, Inc. All rights reserved.
153
+ * For full license text, see the LICENSE.txt file in this package.
154
+ */
145
155
  /**
146
156
  * Parses the JSON-encoded `hostMetaData` query parameter from an iframe URL's
147
157
  * search string (per ADR 0029). Returns null on any malformed input — caller
@@ -187,6 +197,10 @@ function readHostMetaData(search) {
187
197
  return { instanceId, hostAppOrigin };
188
198
  }
189
199
 
200
+ /*
201
+ * Copyright 2026 Salesforce, Inc. All rights reserved.
202
+ * For full license text, see the LICENSE.txt file in this package.
203
+ */
190
204
  const BRIDGE_PROTOCOL_VERSION = "1.0.0";
191
205
  class BootstrapFailureError extends Error {
192
206
  reason;
@@ -550,12 +564,20 @@ class JsonRpcRouter {
550
564
  }
551
565
  }
552
566
 
567
+ /*
568
+ * Copyright 2026 Salesforce, Inc. All rights reserved.
569
+ * For full license text, see the LICENSE.txt file in this package.
570
+ */
553
571
  /** Identity emitted on `ui/notifications/embedding-initialized`. */
554
572
  const EMBEDDING_INFO = {
555
573
  name: "@salesforce/sf-embedding-bridge",
556
- version: "2.2.4",
574
+ version: "2.2.5-rc.1",
557
575
  };
558
576
 
577
+ /*
578
+ * Copyright 2026 Salesforce, Inc. All rights reserved.
579
+ * For full license text, see the LICENSE.txt file in this package.
580
+ */
559
581
  class SessionFailureError extends Error {
560
582
  reason;
561
583
  constructor(reason, cause) {
@@ -637,11 +659,35 @@ class EmbeddingBridge extends JsonRpcRouter {
637
659
  this.resizer.stop();
638
660
  this.resizer = resizer;
639
661
  }
662
+ /** Send outbound RPC request; on rejection, report error to host then rethrow. */
663
+ send(method, params) {
664
+ return super.send(method, params).catch((err) => {
665
+ this.reportError({ sdkMethod: method, type: "rpc-failed" });
666
+ throw err;
667
+ });
668
+ }
640
669
  dispose() {
641
670
  this.resizer?.stop();
642
671
  this.resizer = null;
672
+ uninstallErrorListeners();
643
673
  super.dispose();
644
674
  }
675
+ /**
676
+ * Report an error to the host for observability. Non-PII by construction.
677
+ * Two usage modes: (1) automatic uncaught-error capture (sdkMethod="uncaught"); (2) deliberate
678
+ * author reporting from try/catch (concrete sdkMethod name passed by the author).
679
+ */
680
+ reportError(params) {
681
+ if (errorEmitCount >= ERROR_MAX_PER_SESSION)
682
+ return;
683
+ errorEmitCount++;
684
+ try {
685
+ this.notify(ERROR_METHOD, params);
686
+ }
687
+ catch {
688
+ // Rethrow would refire window.onerror and spiral.
689
+ }
690
+ }
645
691
  }
646
692
  // `cleanup` lets the caller drop our listener from the consumer-owned signal once the race resolves.
647
693
  function rejectOnAbort(signal, cleanup) {
@@ -657,6 +703,71 @@ function rejectOnAbort(signal, cleanup) {
657
703
  });
658
704
  }
659
705
  let sessionPromise = null;
706
+ // Safe as module state: sessionPromise singleton ensures one bridge per realm; listeners are realm-global.
707
+ let errorListenersAttached = false;
708
+ let errorEmitCount = 0;
709
+ let errorHandler = null;
710
+ let rejectionHandler = null;
711
+ const ERROR_MAX_PER_SESSION = 50;
712
+ function sanitizeFilename(filename) {
713
+ if (!filename)
714
+ return undefined;
715
+ // Blob/data URLs: scheme marker only (unparseable for pathname).
716
+ if (filename.startsWith("blob:") || filename.startsWith("data:")) {
717
+ return filename.split(":")[0] + ":<sanitized>";
718
+ }
719
+ try {
720
+ return new URL(filename).pathname;
721
+ }
722
+ catch {
723
+ return undefined;
724
+ }
725
+ }
726
+ function stripUndefined(obj) {
727
+ const out = {};
728
+ for (const key of Object.keys(obj)) {
729
+ if (obj[key] !== undefined)
730
+ out[key] = obj[key];
731
+ }
732
+ return out;
733
+ }
734
+ function extractFromErrorEvent(e) {
735
+ return stripUndefined({
736
+ sdkMethod: "uncaught",
737
+ type: "javascript-error",
738
+ filename: sanitizeFilename(e.filename),
739
+ });
740
+ }
741
+ function extractFromRejectionEvent(_e) {
742
+ return stripUndefined({
743
+ sdkMethod: "uncaught",
744
+ type: "unhandled-rejection",
745
+ });
746
+ }
747
+ // Non-PII by construction: all EmbeddingErrorParams fields are sanitized.
748
+ function installErrorListeners(bridge) {
749
+ if (errorListenersAttached)
750
+ return;
751
+ errorListenersAttached = true;
752
+ errorHandler = (e) => bridge.reportError(extractFromErrorEvent(e));
753
+ rejectionHandler = (e) => bridge.reportError(extractFromRejectionEvent());
754
+ window.addEventListener("error", errorHandler);
755
+ window.addEventListener("unhandledrejection", rejectionHandler);
756
+ }
757
+ function uninstallErrorListeners() {
758
+ if (!errorListenersAttached)
759
+ return;
760
+ if (errorHandler) {
761
+ window.removeEventListener("error", errorHandler);
762
+ errorHandler = null;
763
+ }
764
+ if (rejectionHandler) {
765
+ window.removeEventListener("unhandledrejection", rejectionHandler);
766
+ rejectionHandler = null;
767
+ }
768
+ errorListenersAttached = false;
769
+ errorEmitCount = 0;
770
+ }
660
771
  /** Single-attempt per iframe; recovery from failure requires a host-driven remount. */
661
772
  function bootstrapSession(options = {}) {
662
773
  if (sessionPromise)
@@ -672,6 +783,7 @@ function bootstrapSession(options = {}) {
672
783
  rejectOnAbort(options.signal, abortCleanup.signal),
673
784
  ]);
674
785
  bridge.sendInitializedNotification();
786
+ installErrorListeners(bridge);
675
787
  const handle = {
676
788
  bridge,
677
789
  hostMetaData: capture.hostMetaData,
@@ -688,7 +800,12 @@ function bootstrapSession(options = {}) {
688
800
  return handle;
689
801
  }
690
802
  catch (err) {
691
- // Abort or unexpected failure after construction: dispose so the port doesn't leak.
803
+ // Abort or unexpected failure after construction: report error, then dispose so the port doesn't leak.
804
+ const reason = err instanceof SessionFailureError && err.reason === "ABORTED";
805
+ bridge.reportError({
806
+ sdkMethod: "bootstrapSession",
807
+ type: reason ? "session-aborted" : "bootstrap-failed",
808
+ });
692
809
  bridge.dispose();
693
810
  throw err;
694
811
  }
@@ -711,6 +828,10 @@ function bootstrapSession(options = {}) {
711
828
  return sessionPromise;
712
829
  }
713
830
 
831
+ /*
832
+ * Copyright 2026 Salesforce, Inc. All rights reserved.
833
+ * For full license text, see the LICENSE.txt file in this package.
834
+ */
714
835
  // Bridge-side error vocabulary: composes the standard JSON-RPC + transport
715
836
  // codes from `@salesforce/jsonrpc` with the sf-embedding-specific
716
837
  // codes from `@salesforce/sf-embedding-contract`, and wraps `makeJsonRpcError`
@@ -732,6 +853,7 @@ exports.BOOTSTRAP_ENVELOPE_TYPE = BOOTSTRAP_ENVELOPE_TYPE;
732
853
  exports.BootstrapFailureError = BootstrapFailureError;
733
854
  exports.BridgeClient = BridgeClient;
734
855
  exports.EMBEDDING_INITIALIZED_METHOD = EMBEDDING_INITIALIZED_METHOD;
856
+ exports.ERROR_METHOD = ERROR_METHOD;
735
857
  exports.EVENTS_DISPATCH_METHOD = EVENTS_DISPATCH_METHOD;
736
858
  exports.EVENTS_SUBSCRIBE_METHOD = EVENTS_SUBSCRIBE_METHOD;
737
859
  exports.EVENTS_UNSUBSCRIBE_METHOD = EVENTS_UNSUBSCRIBE_METHOD;
package/dist/index.d.ts CHANGED
@@ -153,6 +153,17 @@ interface HostInitializedParams {
153
153
  interface EmbeddingInitializedParams {
154
154
  embeddingInfo: PeerInfo;
155
155
  }
156
+ /** Iframe-side error notification. Fire-and-forget, non-PII observability signal. */
157
+ declare const ERROR_METHOD: "ui/notifications/error";
158
+ /** Params for `ui/notifications/error`. */
159
+ interface EmbeddingErrorParams {
160
+ /** Method the error belongs to. "uncaught" when captured by global listeners (origin unknown); concrete method name when reported deliberately. */
161
+ sdkMethod: string;
162
+ /** Error category. Automatic capture: "javascript-error" / "unhandled-rejection". Deliberate reports: any freeform string. */
163
+ type: string;
164
+ /** Sanitized pathname only (no origin/query/hash). */
165
+ filename?: string;
166
+ }
156
167
 
157
168
  declare const RESIZE_METHOD: "ui/notifications/resize";
158
169
  interface ResizeParams {
@@ -698,7 +709,15 @@ declare class EmbeddingBridge extends JsonRpcRouter {
698
709
  sendResize(dimensions: ResizeParams): void;
699
710
  /** Attach an auto-emit resizer; stopped on dispose. Replaces any prior resizer. */
700
711
  attachResizer(resizer: EmbeddingResizer): void;
712
+ /** Send outbound RPC request; on rejection, report error to host then rethrow. */
713
+ send<TParams = unknown, TResult = unknown>(method: string, params: TParams): Promise<TResult>;
701
714
  dispose(): void;
715
+ /**
716
+ * Report an error to the host for observability. Non-PII by construction.
717
+ * Two usage modes: (1) automatic uncaught-error capture (sdkMethod="uncaught"); (2) deliberate
718
+ * author reporting from try/catch (concrete sdkMethod name passed by the author).
719
+ */
720
+ reportError(params: EmbeddingErrorParams): void;
702
721
  }
703
722
 
704
723
  /** Single-attempt per iframe; recovery from failure requires a host-driven remount. */
@@ -740,5 +759,5 @@ declare function makeJsonRpcError(code: number, options?: {
740
759
  */
741
760
  declare function readHostMetaData(search: string): HostMetaData | null;
742
761
 
743
- export { BOOTSTRAP_ENVELOPE_TYPE, BootstrapFailureError, BridgeClient, EMBEDDING_INITIALIZED_METHOD, EVENTS_DISPATCH_METHOD, EVENTS_SUBSCRIBE_METHOD, EVENTS_UNSUBSCRIBE_METHOD, EmbeddingBridge, ErrorCode, HEARTBEAT_TYPE, HOST_INITIALIZED_METHOD, HOST_META_DATA_PARAM, HostLwcEvent, JsonRpcHandlerError, JsonRpcRouter, NON_RETRYABLE_CODES, PROTOCOL_NAME, RESIZE_METHOD, SHUTDOWN_TYPE, SessionFailureError, SfEmbeddingErrorCode, UI_STATE_CHANGED_METHOD, UI_STATE_SUBSCRIBE_METHOD, UI_STATE_UNSUBSCRIBE_METHOD, bootstrapSession, isBootstrapEnvelope, makeJsonRpcError, readHostMetaData, validateBootstrapEnvelope };
744
- export type { BootstrapEnvelope, BootstrapEnvelopeValidationFailure, BootstrapEnvelopeValidationInput, BootstrapEnvelopeValidationResult, BootstrapFailureReason, BootstrapSessionOptions, EmbeddingInitializedParams, ErrorCodeValue, EventsDispatchParams, EventsSubscribeParams, EventsSubscribeResult, EventsUnsubscribeParams, HeartbeatEnvelope, HostErrorEventDetail, HostInitializedParams, HostInitializedPayload, HostLwcEventName, HostMetaData, HostReadyEventDetail, HostStyles, NotificationHandler, PeerInfo, RequestHandler, ResizeParams, SessionFailureReason, SessionHandle, SfEmbeddingErrorCodeValue, ShutdownEnvelope, UiState, UiStateChangedParams, UiStateSubscribeParams, UiStateSubscribeResult, UiStateUnsubscribeParams, Unregister };
762
+ export { BOOTSTRAP_ENVELOPE_TYPE, BootstrapFailureError, BridgeClient, EMBEDDING_INITIALIZED_METHOD, ERROR_METHOD, EVENTS_DISPATCH_METHOD, EVENTS_SUBSCRIBE_METHOD, EVENTS_UNSUBSCRIBE_METHOD, EmbeddingBridge, ErrorCode, HEARTBEAT_TYPE, HOST_INITIALIZED_METHOD, HOST_META_DATA_PARAM, HostLwcEvent, JsonRpcHandlerError, JsonRpcRouter, NON_RETRYABLE_CODES, PROTOCOL_NAME, RESIZE_METHOD, SHUTDOWN_TYPE, SessionFailureError, SfEmbeddingErrorCode, UI_STATE_CHANGED_METHOD, UI_STATE_SUBSCRIBE_METHOD, UI_STATE_UNSUBSCRIBE_METHOD, bootstrapSession, isBootstrapEnvelope, makeJsonRpcError, readHostMetaData, validateBootstrapEnvelope };
763
+ export type { BootstrapEnvelope, BootstrapEnvelopeValidationFailure, BootstrapEnvelopeValidationInput, BootstrapEnvelopeValidationResult, BootstrapFailureReason, BootstrapSessionOptions, EmbeddingErrorParams, EmbeddingInitializedParams, ErrorCodeValue, EventsDispatchParams, EventsSubscribeParams, EventsSubscribeResult, EventsUnsubscribeParams, HeartbeatEnvelope, HostErrorEventDetail, HostInitializedParams, HostInitializedPayload, HostLwcEventName, HostMetaData, HostReadyEventDetail, HostStyles, NotificationHandler, PeerInfo, RequestHandler, ResizeParams, SessionFailureReason, SessionHandle, SfEmbeddingErrorCodeValue, ShutdownEnvelope, UiState, UiStateChangedParams, UiStateSubscribeParams, UiStateSubscribeResult, UiStateUnsubscribeParams, Unregister };
package/dist/index.esm.js CHANGED
@@ -88,6 +88,8 @@ class JsonRpcHandlerError extends Error {
88
88
  const HOST_INITIALIZED_METHOD = "ui/notifications/host-initialized";
89
89
  /** Embedding acknowledges receipt of `host-initialized`. Fire-and-forget. */
90
90
  const EMBEDDING_INITIALIZED_METHOD = "ui/notifications/embedding-initialized";
91
+ /** Iframe-side error notification. Fire-and-forget, non-PII observability signal. */
92
+ const ERROR_METHOD = "ui/notifications/error";
91
93
 
92
94
  // Resize hint channel: embedding → host, fire-and-forget. CSS pixels.
93
95
  const RESIZE_METHOD = "ui/notifications/resize";
@@ -97,6 +99,10 @@ const UI_STATE_SUBSCRIBE_METHOD = "ui/subscribe/ui-state";
97
99
  const UI_STATE_UNSUBSCRIBE_METHOD = "ui/unsubscribe/ui-state";
98
100
  const UI_STATE_CHANGED_METHOD = "ui/notifications/ui-state-changed";
99
101
 
102
+ /*
103
+ * Copyright 2026 Salesforce, Inc. All rights reserved.
104
+ * For full license text, see the LICENSE.txt file in this package.
105
+ */
100
106
  // Bootstrap envelope validator for the iframe (embedding) side of the
101
107
  // sf-embedding handshake.
102
108
  //
@@ -140,6 +146,10 @@ function validateBootstrapEnvelope(input) {
140
146
  return { ok: true, envelope: input.data, port: input.ports[0] };
141
147
  }
142
148
 
149
+ /*
150
+ * Copyright 2026 Salesforce, Inc. All rights reserved.
151
+ * For full license text, see the LICENSE.txt file in this package.
152
+ */
143
153
  /**
144
154
  * Parses the JSON-encoded `hostMetaData` query parameter from an iframe URL's
145
155
  * search string (per ADR 0029). Returns null on any malformed input — caller
@@ -185,6 +195,10 @@ function readHostMetaData(search) {
185
195
  return { instanceId, hostAppOrigin };
186
196
  }
187
197
 
198
+ /*
199
+ * Copyright 2026 Salesforce, Inc. All rights reserved.
200
+ * For full license text, see the LICENSE.txt file in this package.
201
+ */
188
202
  const BRIDGE_PROTOCOL_VERSION = "1.0.0";
189
203
  class BootstrapFailureError extends Error {
190
204
  reason;
@@ -548,12 +562,20 @@ class JsonRpcRouter {
548
562
  }
549
563
  }
550
564
 
565
+ /*
566
+ * Copyright 2026 Salesforce, Inc. All rights reserved.
567
+ * For full license text, see the LICENSE.txt file in this package.
568
+ */
551
569
  /** Identity emitted on `ui/notifications/embedding-initialized`. */
552
570
  const EMBEDDING_INFO = {
553
571
  name: "@salesforce/sf-embedding-bridge",
554
- version: "2.2.4",
572
+ version: "2.2.5-rc.1",
555
573
  };
556
574
 
575
+ /*
576
+ * Copyright 2026 Salesforce, Inc. All rights reserved.
577
+ * For full license text, see the LICENSE.txt file in this package.
578
+ */
557
579
  class SessionFailureError extends Error {
558
580
  reason;
559
581
  constructor(reason, cause) {
@@ -635,11 +657,35 @@ class EmbeddingBridge extends JsonRpcRouter {
635
657
  this.resizer.stop();
636
658
  this.resizer = resizer;
637
659
  }
660
+ /** Send outbound RPC request; on rejection, report error to host then rethrow. */
661
+ send(method, params) {
662
+ return super.send(method, params).catch((err) => {
663
+ this.reportError({ sdkMethod: method, type: "rpc-failed" });
664
+ throw err;
665
+ });
666
+ }
638
667
  dispose() {
639
668
  this.resizer?.stop();
640
669
  this.resizer = null;
670
+ uninstallErrorListeners();
641
671
  super.dispose();
642
672
  }
673
+ /**
674
+ * Report an error to the host for observability. Non-PII by construction.
675
+ * Two usage modes: (1) automatic uncaught-error capture (sdkMethod="uncaught"); (2) deliberate
676
+ * author reporting from try/catch (concrete sdkMethod name passed by the author).
677
+ */
678
+ reportError(params) {
679
+ if (errorEmitCount >= ERROR_MAX_PER_SESSION)
680
+ return;
681
+ errorEmitCount++;
682
+ try {
683
+ this.notify(ERROR_METHOD, params);
684
+ }
685
+ catch {
686
+ // Rethrow would refire window.onerror and spiral.
687
+ }
688
+ }
643
689
  }
644
690
  // `cleanup` lets the caller drop our listener from the consumer-owned signal once the race resolves.
645
691
  function rejectOnAbort(signal, cleanup) {
@@ -655,6 +701,71 @@ function rejectOnAbort(signal, cleanup) {
655
701
  });
656
702
  }
657
703
  let sessionPromise = null;
704
+ // Safe as module state: sessionPromise singleton ensures one bridge per realm; listeners are realm-global.
705
+ let errorListenersAttached = false;
706
+ let errorEmitCount = 0;
707
+ let errorHandler = null;
708
+ let rejectionHandler = null;
709
+ const ERROR_MAX_PER_SESSION = 50;
710
+ function sanitizeFilename(filename) {
711
+ if (!filename)
712
+ return undefined;
713
+ // Blob/data URLs: scheme marker only (unparseable for pathname).
714
+ if (filename.startsWith("blob:") || filename.startsWith("data:")) {
715
+ return filename.split(":")[0] + ":<sanitized>";
716
+ }
717
+ try {
718
+ return new URL(filename).pathname;
719
+ }
720
+ catch {
721
+ return undefined;
722
+ }
723
+ }
724
+ function stripUndefined(obj) {
725
+ const out = {};
726
+ for (const key of Object.keys(obj)) {
727
+ if (obj[key] !== undefined)
728
+ out[key] = obj[key];
729
+ }
730
+ return out;
731
+ }
732
+ function extractFromErrorEvent(e) {
733
+ return stripUndefined({
734
+ sdkMethod: "uncaught",
735
+ type: "javascript-error",
736
+ filename: sanitizeFilename(e.filename),
737
+ });
738
+ }
739
+ function extractFromRejectionEvent(_e) {
740
+ return stripUndefined({
741
+ sdkMethod: "uncaught",
742
+ type: "unhandled-rejection",
743
+ });
744
+ }
745
+ // Non-PII by construction: all EmbeddingErrorParams fields are sanitized.
746
+ function installErrorListeners(bridge) {
747
+ if (errorListenersAttached)
748
+ return;
749
+ errorListenersAttached = true;
750
+ errorHandler = (e) => bridge.reportError(extractFromErrorEvent(e));
751
+ rejectionHandler = (e) => bridge.reportError(extractFromRejectionEvent());
752
+ window.addEventListener("error", errorHandler);
753
+ window.addEventListener("unhandledrejection", rejectionHandler);
754
+ }
755
+ function uninstallErrorListeners() {
756
+ if (!errorListenersAttached)
757
+ return;
758
+ if (errorHandler) {
759
+ window.removeEventListener("error", errorHandler);
760
+ errorHandler = null;
761
+ }
762
+ if (rejectionHandler) {
763
+ window.removeEventListener("unhandledrejection", rejectionHandler);
764
+ rejectionHandler = null;
765
+ }
766
+ errorListenersAttached = false;
767
+ errorEmitCount = 0;
768
+ }
658
769
  /** Single-attempt per iframe; recovery from failure requires a host-driven remount. */
659
770
  function bootstrapSession(options = {}) {
660
771
  if (sessionPromise)
@@ -670,6 +781,7 @@ function bootstrapSession(options = {}) {
670
781
  rejectOnAbort(options.signal, abortCleanup.signal),
671
782
  ]);
672
783
  bridge.sendInitializedNotification();
784
+ installErrorListeners(bridge);
673
785
  const handle = {
674
786
  bridge,
675
787
  hostMetaData: capture.hostMetaData,
@@ -686,7 +798,12 @@ function bootstrapSession(options = {}) {
686
798
  return handle;
687
799
  }
688
800
  catch (err) {
689
- // Abort or unexpected failure after construction: dispose so the port doesn't leak.
801
+ // Abort or unexpected failure after construction: report error, then dispose so the port doesn't leak.
802
+ const reason = err instanceof SessionFailureError && err.reason === "ABORTED";
803
+ bridge.reportError({
804
+ sdkMethod: "bootstrapSession",
805
+ type: reason ? "session-aborted" : "bootstrap-failed",
806
+ });
690
807
  bridge.dispose();
691
808
  throw err;
692
809
  }
@@ -709,6 +826,10 @@ function bootstrapSession(options = {}) {
709
826
  return sessionPromise;
710
827
  }
711
828
 
829
+ /*
830
+ * Copyright 2026 Salesforce, Inc. All rights reserved.
831
+ * For full license text, see the LICENSE.txt file in this package.
832
+ */
712
833
  // Bridge-side error vocabulary: composes the standard JSON-RPC + transport
713
834
  // codes from `@salesforce/jsonrpc` with the sf-embedding-specific
714
835
  // codes from `@salesforce/sf-embedding-contract`, and wraps `makeJsonRpcError`
@@ -726,5 +847,5 @@ function makeJsonRpcError(code, options = {}) {
726
847
  return makeJsonRpcError$1(code, options);
727
848
  }
728
849
 
729
- export { BOOTSTRAP_ENVELOPE_TYPE, BootstrapFailureError, BridgeClient, EMBEDDING_INITIALIZED_METHOD, EVENTS_DISPATCH_METHOD, EVENTS_SUBSCRIBE_METHOD, EVENTS_UNSUBSCRIBE_METHOD, EmbeddingBridge, ErrorCode, HEARTBEAT_TYPE, HOST_INITIALIZED_METHOD, HOST_META_DATA_PARAM, HostLwcEvent, JsonRpcHandlerError, JsonRpcRouter, NON_RETRYABLE_CODES, PROTOCOL_NAME, RESIZE_METHOD, SHUTDOWN_TYPE, SessionFailureError, SfEmbeddingErrorCode, UI_STATE_CHANGED_METHOD, UI_STATE_SUBSCRIBE_METHOD, UI_STATE_UNSUBSCRIBE_METHOD, bootstrapSession, isBootstrapEnvelope, makeJsonRpcError, readHostMetaData, validateBootstrapEnvelope };
850
+ export { BOOTSTRAP_ENVELOPE_TYPE, BootstrapFailureError, BridgeClient, EMBEDDING_INITIALIZED_METHOD, ERROR_METHOD, EVENTS_DISPATCH_METHOD, EVENTS_SUBSCRIBE_METHOD, EVENTS_UNSUBSCRIBE_METHOD, EmbeddingBridge, ErrorCode, HEARTBEAT_TYPE, HOST_INITIALIZED_METHOD, HOST_META_DATA_PARAM, HostLwcEvent, JsonRpcHandlerError, JsonRpcRouter, NON_RETRYABLE_CODES, PROTOCOL_NAME, RESIZE_METHOD, SHUTDOWN_TYPE, SessionFailureError, SfEmbeddingErrorCode, UI_STATE_CHANGED_METHOD, UI_STATE_SUBSCRIBE_METHOD, UI_STATE_UNSUBSCRIBE_METHOD, bootstrapSession, isBootstrapEnvelope, makeJsonRpcError, readHostMetaData, validateBootstrapEnvelope };
730
851
  //# sourceMappingURL=index.esm.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/sf-embedding-bridge",
3
- "version": "2.2.4",
3
+ "version": "2.2.5-rc.1",
4
4
  "private": false,
5
5
  "description": "JSON-RPC 2.0 bridge for the sf-embedding host ↔ embedding protocol",
6
6
  "license": "SEE LICENSE IN LICENSE.txt",
@@ -24,8 +24,8 @@
24
24
  "@salesforce/jsonrpc": "^10.12.3"
25
25
  },
26
26
  "devDependencies": {
27
- "@salesforce/sf-embedding-contract": "2.2.4",
28
- "utils": "2.2.4"
27
+ "@salesforce/sf-embedding-contract": "2.2.5-rc.1",
28
+ "utils": "2.2.5-rc.1"
29
29
  },
30
30
  "files": [
31
31
  "dist/",