@partylayer/provider 0.6.0 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,7 +18,7 @@
18
18
 
19
19
  ### Features
20
20
 
21
- - **CIP-0103 Compliant**: All 10 mandatory methods implemented (`connect`, `disconnect`, `isConnected`, `status`, `getActiveNetwork`, `listAccounts`, `getPrimaryAccount`, `signMessage`, `prepareExecute`, `ledgerApi`)
21
+ - **Provider request surface**: Handles the ten method names according to backing wallet capabilities (`connect`, `disconnect`, `isConnected`, `status`, `getActiveNetwork`, `listAccounts`, `getPrimaryAccount`, `signMessage`, `prepareExecute`, `ledgerApi`)
22
22
  - **Wallet Discovery**: Automatic scanning for injected CIP-0103 providers at `window.canton.*`
23
23
  - **Async Wallet Support**: Handles both synchronous (browser extension) and asynchronous (mobile/QR) wallet flows
24
24
  - **Standard Error Model**: `ProviderRpcError` with EIP-1193 / EIP-1474 numeric codes
@@ -58,7 +58,15 @@ const result = await provider.request({ method: 'connect' });
58
58
  const account = await provider.request({ method: 'getPrimaryAccount' });
59
59
  ```
60
60
 
61
- ### Legacy Bridge (with PartyLayerClient)
61
+ ### Client compatibility bridge
62
+
63
+ The bridge projects the active SDK party, retains the legacy `{ response: string }`
64
+ Ledger API result, and emits transaction states only with genuine evidence. It does
65
+ not establish full account/key enumeration or complete native protocol ownership.
66
+ See [alignment and limits](../../docs/CIP-0103-ALIGNMENT.md) before relying on it
67
+ as an exact standard wire implementation.
68
+
69
+ Existing low-level composition:
62
70
 
63
71
  ```typescript
64
72
  import { createProviderBridge } from '@partylayer/provider';
@@ -67,8 +75,10 @@ import { createPartyLayer } from '@partylayer/sdk';
67
75
  const client = createPartyLayer({ network: 'devnet' });
68
76
  const provider = createProviderBridge(client);
69
77
 
70
- // Use standard CIP-0103 interface
71
- await provider.request({ method: 'connect', params: { walletId: 'console' } });
78
+ // Choose a wallet through the SDK's documented application interface.
79
+ await client.connect({ walletId: 'console' });
80
+ // Then use the supported provider requests.
81
+ await provider.request({ method: 'status' });
72
82
  ```
73
83
 
74
84
  ---
package/dist/index.d.mts CHANGED
@@ -258,6 +258,9 @@ interface BridgeableClient {
258
258
  transactionHash?: unknown;
259
259
  signedTx?: unknown;
260
260
  partyId?: unknown;
261
+ /** Actual signature metadata, never a transaction hash. */
262
+ signature?: unknown;
263
+ signedBy?: unknown;
261
264
  }>;
262
265
  submitTransaction(params: {
263
266
  signedTx: unknown;
@@ -266,6 +269,7 @@ interface BridgeableClient {
266
269
  submittedAt?: number;
267
270
  commandId?: string;
268
271
  updateId?: string;
272
+ completionOffset?: number;
269
273
  }>;
270
274
  ledgerApi?(params: {
271
275
  requestMethod: string;
@@ -396,11 +400,15 @@ interface ExtensionChannelOptions {
396
400
  /** Per-request timeout in ms. Default 30000. */
397
401
  timeoutMs?: number;
398
402
  }
403
+ interface ExtensionChannelProvider extends CIP0103Provider {
404
+ /** Remove the channel listener, reject pending RPCs and release subscriptions. */
405
+ destroy(): void;
406
+ }
399
407
  /**
400
408
  * Create a CIP-0103 provider that talks the splice-wallet postMessage protocol
401
409
  * over the page `window`, routed by `target`.
402
410
  */
403
- declare function createExtensionChannelProvider(options?: ExtensionChannelOptions): CIP0103Provider;
411
+ declare function createExtensionChannelProvider(options?: ExtensionChannelOptions): ExtensionChannelProvider;
404
412
 
405
413
  /**
406
414
  * Async Wallet Handling
@@ -496,4 +504,4 @@ declare class MethodRouter {
496
504
  private registerDefaults;
497
505
  }
498
506
 
499
- export { type AnnounceDiscoveryOptions, type AnnounceSubscribeOptions, type AnnouncedWallet, type AsyncConnectOptions, type AsyncPrepareExecuteOptions, type BridgeableClient, CIP0103EventBus, type DiscoveredProvider, type ExtensionChannelOptions, JSON_RPC_ERRORS, type MethodHandler, MethodRouter, PartyLayerProvider, type PartyLayerProviderOptions, ProviderRpcError, RPC_ERRORS, type WaitForAnnouncedOptions, chainDisconnected, createExtensionChannelProvider, createProviderBridge, disconnected, discoverAnnouncedProviders, discoverInjectedProviders, discoverProviders, handleAsyncConnect, handleAsyncPrepareExecute, internalError, invalidParams, isCIP0103Provider, methodNotFound, resourceNotFound, resourceUnavailable, subscribeAnnouncedProviders, toPartyLayerError, toProviderRpcError, transactionRejected, unauthorized, unsupportedMethod, userRejected, waitForAnnouncedProvider, waitForProvider };
507
+ export { type AnnounceDiscoveryOptions, type AnnounceSubscribeOptions, type AnnouncedWallet, type AsyncConnectOptions, type AsyncPrepareExecuteOptions, type BridgeableClient, CIP0103EventBus, type DiscoveredProvider, type ExtensionChannelOptions, type ExtensionChannelProvider, JSON_RPC_ERRORS, type MethodHandler, MethodRouter, PartyLayerProvider, type PartyLayerProviderOptions, ProviderRpcError, RPC_ERRORS, type WaitForAnnouncedOptions, chainDisconnected, createExtensionChannelProvider, createProviderBridge, disconnected, discoverAnnouncedProviders, discoverInjectedProviders, discoverProviders, handleAsyncConnect, handleAsyncPrepareExecute, internalError, invalidParams, isCIP0103Provider, methodNotFound, resourceNotFound, resourceUnavailable, subscribeAnnouncedProviders, toPartyLayerError, toProviderRpcError, transactionRejected, unauthorized, unsupportedMethod, userRejected, waitForAnnouncedProvider, waitForProvider };
package/dist/index.d.ts CHANGED
@@ -258,6 +258,9 @@ interface BridgeableClient {
258
258
  transactionHash?: unknown;
259
259
  signedTx?: unknown;
260
260
  partyId?: unknown;
261
+ /** Actual signature metadata, never a transaction hash. */
262
+ signature?: unknown;
263
+ signedBy?: unknown;
261
264
  }>;
262
265
  submitTransaction(params: {
263
266
  signedTx: unknown;
@@ -266,6 +269,7 @@ interface BridgeableClient {
266
269
  submittedAt?: number;
267
270
  commandId?: string;
268
271
  updateId?: string;
272
+ completionOffset?: number;
269
273
  }>;
270
274
  ledgerApi?(params: {
271
275
  requestMethod: string;
@@ -396,11 +400,15 @@ interface ExtensionChannelOptions {
396
400
  /** Per-request timeout in ms. Default 30000. */
397
401
  timeoutMs?: number;
398
402
  }
403
+ interface ExtensionChannelProvider extends CIP0103Provider {
404
+ /** Remove the channel listener, reject pending RPCs and release subscriptions. */
405
+ destroy(): void;
406
+ }
399
407
  /**
400
408
  * Create a CIP-0103 provider that talks the splice-wallet postMessage protocol
401
409
  * over the page `window`, routed by `target`.
402
410
  */
403
- declare function createExtensionChannelProvider(options?: ExtensionChannelOptions): CIP0103Provider;
411
+ declare function createExtensionChannelProvider(options?: ExtensionChannelOptions): ExtensionChannelProvider;
404
412
 
405
413
  /**
406
414
  * Async Wallet Handling
@@ -496,4 +504,4 @@ declare class MethodRouter {
496
504
  private registerDefaults;
497
505
  }
498
506
 
499
- export { type AnnounceDiscoveryOptions, type AnnounceSubscribeOptions, type AnnouncedWallet, type AsyncConnectOptions, type AsyncPrepareExecuteOptions, type BridgeableClient, CIP0103EventBus, type DiscoveredProvider, type ExtensionChannelOptions, JSON_RPC_ERRORS, type MethodHandler, MethodRouter, PartyLayerProvider, type PartyLayerProviderOptions, ProviderRpcError, RPC_ERRORS, type WaitForAnnouncedOptions, chainDisconnected, createExtensionChannelProvider, createProviderBridge, disconnected, discoverAnnouncedProviders, discoverInjectedProviders, discoverProviders, handleAsyncConnect, handleAsyncPrepareExecute, internalError, invalidParams, isCIP0103Provider, methodNotFound, resourceNotFound, resourceUnavailable, subscribeAnnouncedProviders, toPartyLayerError, toProviderRpcError, transactionRejected, unauthorized, unsupportedMethod, userRejected, waitForAnnouncedProvider, waitForProvider };
507
+ export { type AnnounceDiscoveryOptions, type AnnounceSubscribeOptions, type AnnouncedWallet, type AsyncConnectOptions, type AsyncPrepareExecuteOptions, type BridgeableClient, CIP0103EventBus, type DiscoveredProvider, type ExtensionChannelOptions, type ExtensionChannelProvider, JSON_RPC_ERRORS, type MethodHandler, MethodRouter, PartyLayerProvider, type PartyLayerProviderOptions, ProviderRpcError, RPC_ERRORS, type WaitForAnnouncedOptions, chainDisconnected, createExtensionChannelProvider, createProviderBridge, disconnected, discoverAnnouncedProviders, discoverInjectedProviders, discoverProviders, handleAsyncConnect, handleAsyncPrepareExecute, internalError, invalidParams, isCIP0103Provider, methodNotFound, resourceNotFound, resourceUnavailable, subscribeAnnouncedProviders, toPartyLayerError, toProviderRpcError, transactionRejected, unauthorized, unsupportedMethod, userRejected, waitForAnnouncedProvider, waitForProvider };
package/dist/index.js CHANGED
@@ -220,6 +220,17 @@ function isRpcShaped(err) {
220
220
  return typeof e.message === "string" && typeof e.code === "number";
221
221
  }
222
222
 
223
+ // src/request-validation.ts
224
+ function validateRequestPayload(payload) {
225
+ if (payload === null || typeof payload !== "object" || Array.isArray(payload) || !("method" in payload) || typeof payload.method !== "string") {
226
+ throw new ProviderRpcError("Request must be an object with a string method", JSON_RPC_ERRORS.INVALID_REQUEST);
227
+ }
228
+ const params = "params" in payload ? payload.params : void 0;
229
+ if (params !== void 0 && (params === null || typeof params !== "object")) {
230
+ throw new ProviderRpcError("Request params must be an object or array", JSON_RPC_ERRORS.INVALID_PARAMS);
231
+ }
232
+ }
233
+
223
234
  // src/method-router.ts
224
235
  var MethodRouter = class {
225
236
  handlers = /* @__PURE__ */ new Map();
@@ -241,6 +252,7 @@ var MethodRouter = class {
241
252
  }
242
253
  /** Route a request to the appropriate handler */
243
254
  async route(payload) {
255
+ validateRequestPayload(payload);
244
256
  const { method, params } = payload;
245
257
  const handler = this.handlers.get(method);
246
258
  if (!handler) {
@@ -514,7 +526,7 @@ var PartyLayerProvider = class {
514
526
  };
515
527
 
516
528
  // src/bridge.ts
517
- var PROVIDER_VERSION = "0.6.0" ;
529
+ var PROVIDER_VERSION = "0.6.2" ;
518
530
  function createProviderBridge(client) {
519
531
  const eventBus = new CIP0103EventBus();
520
532
  const pendingEmitted = /* @__PURE__ */ new Set();
@@ -539,6 +551,7 @@ function createProviderBridge(client) {
539
551
  return bridge;
540
552
  }
541
553
  async function handleRequest(client, args, eventBus, pendingEmitted) {
554
+ validateRequestPayload(args);
542
555
  const { method, params } = args;
543
556
  try {
544
557
  switch (method) {
@@ -579,9 +592,8 @@ async function handleRequest(client, args, eventBus, pendingEmitted) {
579
592
  }
580
593
  case "getActiveNetwork": {
581
594
  const session = await client.getActiveSession();
582
- return core.toCAIP2Network(
583
- session?.network ?? "devnet"
584
- );
595
+ if (!session) throw disconnected("No active session");
596
+ return core.toCAIP2Network(session.network);
585
597
  }
586
598
  case "listAccounts": {
587
599
  const session = await client.getActiveSession();
@@ -606,61 +618,42 @@ async function handleRequest(client, args, eventBus, pendingEmitted) {
606
618
  }
607
619
  case "prepareExecute": {
608
620
  const p = normalizeParams(params);
609
- const cmdId = `cmd_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
621
+ const session = await client.getActiveSession();
622
+ if (!session) throw disconnected("No active session");
623
+ const cmdId = nonempty(p.commandId) ? p.commandId : `cmd_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
624
+ if (pendingEmitted.has(cmdId)) throw resourceUnavailable("This commandId is already pending");
625
+ const payload = { ...p, commandId: cmdId };
610
626
  pendingEmitted.add(cmdId);
611
- eventBus.emit(core.CIP0103_EVENTS.TX_CHANGED, {
612
- status: "pending",
613
- commandId: cmdId
614
- });
627
+ eventBus.emit(core.CIP0103_EVENTS.TX_CHANGED, { status: "pending", commandId: cmdId });
628
+ let submitting = false;
615
629
  try {
616
- const signResult = await client.signTransaction({ tx: p });
617
- const session = await client.getActiveSession();
618
- const partyId = String(signResult.partyId ?? session?.partyId ?? "unknown");
619
- if (signResult.transactionHash !== void 0) {
620
- eventBus.emit(core.CIP0103_EVENTS.TX_CHANGED, {
621
- status: "signed",
622
- commandId: cmdId,
623
- payload: {
624
- signature: String(signResult.transactionHash),
625
- signedBy: partyId,
626
- party: partyId
627
- }
628
- });
630
+ let signedTx = payload;
631
+ if (!session.capabilitiesSnapshot || session.capabilitiesSnapshot.includes("signTransaction")) {
632
+ const signed = await client.signTransaction({ tx: payload });
633
+ if (signed.partyId !== void 0 && signed.partyId !== session.partyId) {
634
+ throw disconnected("Wallet account changed during signing");
635
+ }
636
+ signedTx = signed.signedTx;
637
+ if (signedTx === void 0) throw new Error("Wallet returned no signed transaction");
638
+ const event2 = signedEvent({ signature: signed.signature, signedBy: signed.signedBy, party: signed.partyId }, cmdId);
639
+ if (event2) eventBus.emit(core.CIP0103_EVENTS.TX_CHANGED, event2);
629
640
  }
630
- try {
631
- const receipt = await client.submitTransaction({
632
- signedTx: signResult.signedTx ?? signResult.transactionHash
633
- });
634
- pendingEmitted.delete(cmdId);
635
- eventBus.emit(core.CIP0103_EVENTS.TX_CHANGED, {
636
- status: "executed",
637
- commandId: cmdId,
638
- payload: {
639
- // All three are real identifiers — the receipt's update id, the
640
- // wallet's command id, or the bridge's own command id for this
641
- // operation. `String(receipt.transactionHash)` was the old tail
642
- // and would now stringify `undefined`.
643
- updateId: receipt.updateId ?? receipt.commandId ?? cmdId,
644
- completionOffset: 0
645
- }
646
- });
647
- } catch (submitErr) {
648
- pendingEmitted.delete(cmdId);
649
- eventBus.emit(core.CIP0103_EVENTS.TX_CHANGED, {
650
- status: "failed",
651
- commandId: cmdId
652
- });
653
- throw submitErr;
641
+ const current = await client.getActiveSession();
642
+ if (!current || current.sessionId !== session.sessionId || current.partyId !== session.partyId || current.network !== session.network) {
643
+ throw disconnected("Wallet session changed before submission");
654
644
  }
655
- } catch (signErr) {
656
- if (pendingEmitted.has(cmdId)) {
657
- pendingEmitted.delete(cmdId);
658
- eventBus.emit(core.CIP0103_EVENTS.TX_CHANGED, {
659
- status: "failed",
660
- commandId: cmdId
661
- });
645
+ submitting = true;
646
+ const receipt = await client.submitTransaction({ signedTx });
647
+ const event = executedEvent(receipt, cmdId);
648
+ if (event) eventBus.emit(core.CIP0103_EVENTS.TX_CHANGED, event);
649
+ } catch (reason) {
650
+ const code = reason?.code;
651
+ if (!submitting || code === 4001 || code === "USER_REJECTED" || code === -32003) {
652
+ eventBus.emit(core.CIP0103_EVENTS.TX_CHANGED, { status: "failed", commandId: cmdId });
662
653
  }
663
- throw signErr;
654
+ throw reason;
655
+ } finally {
656
+ pendingEmitted.delete(cmdId);
664
657
  }
665
658
  return void 0;
666
659
  }
@@ -735,59 +728,51 @@ function wireEvents(client, eventBus, pendingEmitted) {
735
728
  });
736
729
  });
737
730
  client.on("tx:status", (event) => {
738
- const e = event;
739
- if (!e || typeof e !== "object" || !("status" in e)) return;
740
- const statusMap = {
741
- pending: "pending",
742
- submitted: "pending",
743
- signed: "signed",
744
- committed: "executed",
745
- rejected: "failed",
746
- failed: "failed"
747
- };
748
- const mappedStatus = statusMap[e.status] ?? "pending";
749
- const commandId = String(e.txId);
750
- if (mappedStatus === "pending" && pendingEmitted.has(commandId)) {
751
- return;
752
- }
753
- if (mappedStatus === "executed" || mappedStatus === "failed") {
754
- pendingEmitted.delete(commandId);
755
- }
731
+ const e = record(event);
732
+ if (!e) return;
733
+ const raw = record(e.raw);
734
+ const tx = record(raw?.tx) ?? raw;
735
+ const commandId = nonempty(tx?.commandId) ? tx.commandId : e.txId;
736
+ if (!nonempty(commandId)) return;
756
737
  let txEvent;
757
- switch (mappedStatus) {
738
+ switch (e.status) {
739
+ case "pending":
740
+ case "submitted":
741
+ if (pendingEmitted.has(commandId)) return;
742
+ txEvent = { status: "pending", commandId };
743
+ break;
758
744
  case "signed":
759
- txEvent = {
760
- status: "signed",
761
- commandId,
762
- payload: {
763
- signature: "",
764
- signedBy: "",
765
- party: ""
766
- }
767
- };
745
+ txEvent = signedEvent(tx, commandId);
768
746
  break;
769
- case "executed": {
770
- const raw = e.raw;
771
- txEvent = {
772
- status: "executed",
773
- commandId,
774
- payload: {
775
- updateId: raw?.updateId ?? raw?.commandId ?? commandId,
776
- completionOffset: 0
777
- }
778
- };
747
+ case "committed":
748
+ txEvent = executedEvent(tx, commandId);
779
749
  break;
780
- }
750
+ case "rejected":
781
751
  case "failed":
782
752
  txEvent = { status: "failed", commandId };
783
753
  break;
784
754
  default:
785
- txEvent = { status: "pending", commandId };
786
- break;
755
+ return;
787
756
  }
788
- eventBus.emit(core.CIP0103_EVENTS.TX_CHANGED, txEvent);
757
+ if (txEvent) eventBus.emit(core.CIP0103_EVENTS.TX_CHANGED, txEvent);
789
758
  });
790
759
  }
760
+ function record(value) {
761
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
762
+ }
763
+ function nonempty(value) {
764
+ return typeof value === "string" && value.trim().length > 0;
765
+ }
766
+ function signedEvent(value, commandId) {
767
+ const tx = record(value);
768
+ const p = record(tx?.payload) ?? tx;
769
+ return nonempty(p?.signature) && nonempty(p?.signedBy) && nonempty(p?.party) ? { status: "signed", commandId, payload: { signature: p.signature, signedBy: p.signedBy, party: p.party } } : null;
770
+ }
771
+ function executedEvent(value, commandId) {
772
+ const tx = record(value);
773
+ const p = record(tx?.payload) ?? tx;
774
+ return nonempty(p?.updateId) && typeof p.completionOffset === "number" && Number.isSafeInteger(p.completionOffset) && p.completionOffset >= 0 ? { status: "executed", commandId, payload: { updateId: p.updateId, completionOffset: p.completionOffset } } : null;
775
+ }
791
776
  function normalizeParams(params) {
792
777
  if (!params) return {};
793
778
  if (Array.isArray(params)) return params[0] ?? {};
@@ -819,11 +804,33 @@ function createExtensionChannelProvider(options = {}) {
819
804
  const eventBus = new CIP0103EventBus();
820
805
  const channelId = `pl-${++channelSeq}-${target ?? "ext"}`;
821
806
  let requestSeq = 0;
807
+ let listening = false;
808
+ let destroyed = false;
809
+ const subscribedEvents = /* @__PURE__ */ new Set();
810
+ function listen() {
811
+ if (!listening && typeof window !== "undefined") {
812
+ window.addEventListener("message", onMessage);
813
+ listening = true;
814
+ }
815
+ }
816
+ function detachIfIdle() {
817
+ if (pending.size > 0 || [...subscribedEvents].some((name) => eventBus.listenerCount(name) > 0)) return;
818
+ if (listening && typeof window !== "undefined") window.removeEventListener("message", onMessage);
819
+ listening = false;
820
+ }
822
821
  const pending = /* @__PURE__ */ new Map();
823
822
  function onMessage(event) {
824
823
  if (event.source !== window) return;
825
824
  const selfOrigin = typeof window !== "undefined" ? window.location?.origin : void 0;
826
825
  if (selfOrigin && selfOrigin !== "null" && event.origin !== selfOrigin) return;
826
+ const notification = event.data;
827
+ if (notification?.type === SPLICE_WALLET_REQUEST && notification.request?.jsonrpc === "2.0" && notification.request.id == null && (!target || notification.target === target)) {
828
+ const method = notification.request.method;
829
+ if (method && ["statusChanged", "accountsChanged", "txChanged", "connected"].includes(method)) {
830
+ eventBus.emit(method, notification.request.params);
831
+ }
832
+ return;
833
+ }
827
834
  const data = event.data;
828
835
  if (!data || data.type !== SPLICE_WALLET_RESPONSE || !data.response) return;
829
836
  const { id, result, error } = data.response;
@@ -832,6 +839,7 @@ function createExtensionChannelProvider(options = {}) {
832
839
  if (!entry) return;
833
840
  pending.delete(String(id));
834
841
  clearTimeout(entry.timer);
842
+ detachIfIdle();
835
843
  if (error) {
836
844
  entry.reject(
837
845
  new ProviderRpcError(
@@ -844,24 +852,29 @@ function createExtensionChannelProvider(options = {}) {
844
852
  entry.resolve(result);
845
853
  }
846
854
  }
847
- if (typeof window !== "undefined") {
848
- window.addEventListener("message", onMessage);
849
- }
850
855
  function postTargetOrigin() {
851
856
  const origin = typeof window !== "undefined" ? window.location?.origin : void 0;
852
857
  return origin && origin !== "null" ? origin : "*";
853
858
  }
854
859
  const provider = {
855
860
  request(args) {
861
+ try {
862
+ validateRequestPayload(args);
863
+ } catch (error) {
864
+ return Promise.reject(error);
865
+ }
866
+ if (destroyed) return Promise.reject(new ProviderRpcError("Extension channel was destroyed", RPC_ERRORS.DISCONNECTED));
856
867
  if (typeof window === "undefined") {
857
868
  return Promise.reject(
858
869
  new ProviderRpcError("No window: extension channel unavailable", JSON_RPC_ERRORS.INTERNAL_ERROR)
859
870
  );
860
871
  }
872
+ listen();
861
873
  const id = `${channelId}-${++requestSeq}`;
862
874
  return new Promise((resolve, reject) => {
863
875
  const timer = setTimeout(() => {
864
876
  pending.delete(id);
877
+ detachIfIdle();
865
878
  reject(
866
879
  new ProviderRpcError(
867
880
  `Wallet request "${args.method}" timed out after ${timeoutMs}ms`,
@@ -880,11 +893,21 @@ function createExtensionChannelProvider(options = {}) {
880
893
  },
881
894
  ...target ? { target } : {}
882
895
  };
883
- window.postMessage(message, postTargetOrigin());
896
+ try {
897
+ window.postMessage(message, postTargetOrigin());
898
+ } catch (error) {
899
+ pending.delete(id);
900
+ clearTimeout(timer);
901
+ detachIfIdle();
902
+ reject(error);
903
+ }
884
904
  });
885
905
  },
886
906
  on(event, listener) {
907
+ if (destroyed) return provider;
887
908
  eventBus.on(event, listener);
909
+ subscribedEvents.add(event);
910
+ listen();
888
911
  return provider;
889
912
  },
890
913
  emit(event, ...args) {
@@ -892,7 +915,20 @@ function createExtensionChannelProvider(options = {}) {
892
915
  },
893
916
  removeListener(event, listener) {
894
917
  eventBus.removeListener(event, listener);
918
+ detachIfIdle();
895
919
  return provider;
920
+ },
921
+ destroy() {
922
+ if (destroyed) return;
923
+ destroyed = true;
924
+ eventBus.removeAllListeners();
925
+ subscribedEvents.clear();
926
+ for (const entry of pending.values()) {
927
+ clearTimeout(entry.timer);
928
+ entry.reject(new ProviderRpcError("Extension channel was destroyed", RPC_ERRORS.DISCONNECTED));
929
+ }
930
+ pending.clear();
931
+ detachIfIdle();
896
932
  }
897
933
  };
898
934
  eventBus.setOwner(provider);