@partylayer/provider 0.5.2 → 0.6.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/index.mjs CHANGED
@@ -513,7 +513,7 @@ var PartyLayerProvider = class {
513
513
  };
514
514
 
515
515
  // src/bridge.ts
516
- var PROVIDER_VERSION = "0.5.2" ;
516
+ var PROVIDER_VERSION = "0.6.1" ;
517
517
  function createProviderBridge(client) {
518
518
  const eventBus = new CIP0103EventBus();
519
519
  const pendingEmitted = /* @__PURE__ */ new Set();
@@ -578,9 +578,8 @@ async function handleRequest(client, args, eventBus, pendingEmitted) {
578
578
  }
579
579
  case "getActiveNetwork": {
580
580
  const session = await client.getActiveSession();
581
- return toCAIP2Network(
582
- session?.network ?? "devnet"
583
- );
581
+ if (!session) throw disconnected("No active session");
582
+ return toCAIP2Network(session.network);
584
583
  }
585
584
  case "listAccounts": {
586
585
  const session = await client.getActiveSession();
@@ -605,55 +604,42 @@ async function handleRequest(client, args, eventBus, pendingEmitted) {
605
604
  }
606
605
  case "prepareExecute": {
607
606
  const p = normalizeParams(params);
608
- const cmdId = `cmd_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
607
+ const session = await client.getActiveSession();
608
+ if (!session) throw disconnected("No active session");
609
+ const cmdId = nonempty(p.commandId) ? p.commandId : `cmd_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
610
+ if (pendingEmitted.has(cmdId)) throw resourceUnavailable("This commandId is already pending");
611
+ const payload = { ...p, commandId: cmdId };
609
612
  pendingEmitted.add(cmdId);
610
- eventBus.emit(CIP0103_EVENTS.TX_CHANGED, {
611
- status: "pending",
612
- commandId: cmdId
613
- });
613
+ eventBus.emit(CIP0103_EVENTS.TX_CHANGED, { status: "pending", commandId: cmdId });
614
+ let submitting = false;
614
615
  try {
615
- const signResult = await client.signTransaction({ tx: p });
616
- const session = await client.getActiveSession();
617
- const partyId = String(signResult.partyId ?? session?.partyId ?? "unknown");
618
- eventBus.emit(CIP0103_EVENTS.TX_CHANGED, {
619
- status: "signed",
620
- commandId: cmdId,
621
- payload: {
622
- signature: String(signResult.transactionHash),
623
- signedBy: partyId,
624
- party: partyId
616
+ let signedTx = payload;
617
+ if (!session.capabilitiesSnapshot || session.capabilitiesSnapshot.includes("signTransaction")) {
618
+ const signed = await client.signTransaction({ tx: payload });
619
+ if (signed.partyId !== void 0 && signed.partyId !== session.partyId) {
620
+ throw disconnected("Wallet account changed during signing");
625
621
  }
626
- });
627
- try {
628
- const receipt = await client.submitTransaction({
629
- signedTx: signResult.signedTx ?? signResult.transactionHash
630
- });
631
- pendingEmitted.delete(cmdId);
632
- eventBus.emit(CIP0103_EVENTS.TX_CHANGED, {
633
- status: "executed",
634
- commandId: cmdId,
635
- payload: {
636
- updateId: receipt.updateId ?? receipt.commandId ?? String(receipt.transactionHash),
637
- completionOffset: 0
638
- }
639
- });
640
- } catch (submitErr) {
641
- pendingEmitted.delete(cmdId);
642
- eventBus.emit(CIP0103_EVENTS.TX_CHANGED, {
643
- status: "failed",
644
- commandId: cmdId
645
- });
646
- throw submitErr;
622
+ signedTx = signed.signedTx;
623
+ if (signedTx === void 0) throw new Error("Wallet returned no signed transaction");
624
+ const event2 = signedEvent({ signature: signed.signature, signedBy: signed.signedBy, party: signed.partyId }, cmdId);
625
+ if (event2) eventBus.emit(CIP0103_EVENTS.TX_CHANGED, event2);
626
+ }
627
+ const current = await client.getActiveSession();
628
+ if (!current || current.sessionId !== session.sessionId || current.partyId !== session.partyId || current.network !== session.network) {
629
+ throw disconnected("Wallet session changed before submission");
647
630
  }
648
- } catch (signErr) {
649
- if (pendingEmitted.has(cmdId)) {
650
- pendingEmitted.delete(cmdId);
651
- eventBus.emit(CIP0103_EVENTS.TX_CHANGED, {
652
- status: "failed",
653
- commandId: cmdId
654
- });
631
+ submitting = true;
632
+ const receipt = await client.submitTransaction({ signedTx });
633
+ const event = executedEvent(receipt, cmdId);
634
+ if (event) eventBus.emit(CIP0103_EVENTS.TX_CHANGED, event);
635
+ } catch (reason) {
636
+ const code = reason?.code;
637
+ if (!submitting || code === 4001 || code === "USER_REJECTED" || code === -32003) {
638
+ eventBus.emit(CIP0103_EVENTS.TX_CHANGED, { status: "failed", commandId: cmdId });
655
639
  }
656
- throw signErr;
640
+ throw reason;
641
+ } finally {
642
+ pendingEmitted.delete(cmdId);
657
643
  }
658
644
  return void 0;
659
645
  }
@@ -728,59 +714,51 @@ function wireEvents(client, eventBus, pendingEmitted) {
728
714
  });
729
715
  });
730
716
  client.on("tx:status", (event) => {
731
- const e = event;
732
- if (!e || typeof e !== "object" || !("status" in e)) return;
733
- const statusMap = {
734
- pending: "pending",
735
- submitted: "pending",
736
- signed: "signed",
737
- committed: "executed",
738
- rejected: "failed",
739
- failed: "failed"
740
- };
741
- const mappedStatus = statusMap[e.status] ?? "pending";
742
- const commandId = String(e.txId);
743
- if (mappedStatus === "pending" && pendingEmitted.has(commandId)) {
744
- return;
745
- }
746
- if (mappedStatus === "executed" || mappedStatus === "failed") {
747
- pendingEmitted.delete(commandId);
748
- }
717
+ const e = record(event);
718
+ if (!e) return;
719
+ const raw = record(e.raw);
720
+ const tx = record(raw?.tx) ?? raw;
721
+ const commandId = nonempty(tx?.commandId) ? tx.commandId : e.txId;
722
+ if (!nonempty(commandId)) return;
749
723
  let txEvent;
750
- switch (mappedStatus) {
724
+ switch (e.status) {
725
+ case "pending":
726
+ case "submitted":
727
+ if (pendingEmitted.has(commandId)) return;
728
+ txEvent = { status: "pending", commandId };
729
+ break;
751
730
  case "signed":
752
- txEvent = {
753
- status: "signed",
754
- commandId,
755
- payload: {
756
- signature: "",
757
- signedBy: "",
758
- party: ""
759
- }
760
- };
731
+ txEvent = signedEvent(tx, commandId);
761
732
  break;
762
- case "executed": {
763
- const raw = e.raw;
764
- txEvent = {
765
- status: "executed",
766
- commandId,
767
- payload: {
768
- updateId: raw?.updateId ?? raw?.commandId ?? commandId,
769
- completionOffset: 0
770
- }
771
- };
733
+ case "committed":
734
+ txEvent = executedEvent(tx, commandId);
772
735
  break;
773
- }
736
+ case "rejected":
774
737
  case "failed":
775
738
  txEvent = { status: "failed", commandId };
776
739
  break;
777
740
  default:
778
- txEvent = { status: "pending", commandId };
779
- break;
741
+ return;
780
742
  }
781
- eventBus.emit(CIP0103_EVENTS.TX_CHANGED, txEvent);
743
+ if (txEvent) eventBus.emit(CIP0103_EVENTS.TX_CHANGED, txEvent);
782
744
  });
783
745
  }
746
+ function record(value) {
747
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
748
+ }
749
+ function nonempty(value) {
750
+ return typeof value === "string" && value.trim().length > 0;
751
+ }
752
+ function signedEvent(value, commandId) {
753
+ const tx = record(value);
754
+ const p = record(tx?.payload) ?? tx;
755
+ return nonempty(p?.signature) && nonempty(p?.signedBy) && nonempty(p?.party) ? { status: "signed", commandId, payload: { signature: p.signature, signedBy: p.signedBy, party: p.party } } : null;
756
+ }
757
+ function executedEvent(value, commandId) {
758
+ const tx = record(value);
759
+ const p = record(tx?.payload) ?? tx;
760
+ 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;
761
+ }
784
762
  function normalizeParams(params) {
785
763
  if (!params) return {};
786
764
  if (Array.isArray(params)) return params[0] ?? {};
@@ -812,11 +790,33 @@ function createExtensionChannelProvider(options = {}) {
812
790
  const eventBus = new CIP0103EventBus();
813
791
  const channelId = `pl-${++channelSeq}-${target ?? "ext"}`;
814
792
  let requestSeq = 0;
793
+ let listening = false;
794
+ let destroyed = false;
795
+ const subscribedEvents = /* @__PURE__ */ new Set();
796
+ function listen() {
797
+ if (!listening && typeof window !== "undefined") {
798
+ window.addEventListener("message", onMessage);
799
+ listening = true;
800
+ }
801
+ }
802
+ function detachIfIdle() {
803
+ if (pending.size > 0 || [...subscribedEvents].some((name) => eventBus.listenerCount(name) > 0)) return;
804
+ if (listening && typeof window !== "undefined") window.removeEventListener("message", onMessage);
805
+ listening = false;
806
+ }
815
807
  const pending = /* @__PURE__ */ new Map();
816
808
  function onMessage(event) {
817
809
  if (event.source !== window) return;
818
810
  const selfOrigin = typeof window !== "undefined" ? window.location?.origin : void 0;
819
811
  if (selfOrigin && selfOrigin !== "null" && event.origin !== selfOrigin) return;
812
+ const notification = event.data;
813
+ if (notification?.type === SPLICE_WALLET_REQUEST && notification.request?.jsonrpc === "2.0" && notification.request.id == null && (!target || notification.target === target)) {
814
+ const method = notification.request.method;
815
+ if (method && ["statusChanged", "accountsChanged", "txChanged", "connected"].includes(method)) {
816
+ eventBus.emit(method, notification.request.params);
817
+ }
818
+ return;
819
+ }
820
820
  const data = event.data;
821
821
  if (!data || data.type !== SPLICE_WALLET_RESPONSE || !data.response) return;
822
822
  const { id, result, error } = data.response;
@@ -825,6 +825,7 @@ function createExtensionChannelProvider(options = {}) {
825
825
  if (!entry) return;
826
826
  pending.delete(String(id));
827
827
  clearTimeout(entry.timer);
828
+ detachIfIdle();
828
829
  if (error) {
829
830
  entry.reject(
830
831
  new ProviderRpcError(
@@ -837,24 +838,24 @@ function createExtensionChannelProvider(options = {}) {
837
838
  entry.resolve(result);
838
839
  }
839
840
  }
840
- if (typeof window !== "undefined") {
841
- window.addEventListener("message", onMessage);
842
- }
843
841
  function postTargetOrigin() {
844
842
  const origin = typeof window !== "undefined" ? window.location?.origin : void 0;
845
843
  return origin && origin !== "null" ? origin : "*";
846
844
  }
847
845
  const provider = {
848
846
  request(args) {
847
+ if (destroyed) return Promise.reject(new ProviderRpcError("Extension channel was destroyed", RPC_ERRORS.DISCONNECTED));
849
848
  if (typeof window === "undefined") {
850
849
  return Promise.reject(
851
850
  new ProviderRpcError("No window: extension channel unavailable", JSON_RPC_ERRORS.INTERNAL_ERROR)
852
851
  );
853
852
  }
853
+ listen();
854
854
  const id = `${channelId}-${++requestSeq}`;
855
855
  return new Promise((resolve, reject) => {
856
856
  const timer = setTimeout(() => {
857
857
  pending.delete(id);
858
+ detachIfIdle();
858
859
  reject(
859
860
  new ProviderRpcError(
860
861
  `Wallet request "${args.method}" timed out after ${timeoutMs}ms`,
@@ -873,11 +874,21 @@ function createExtensionChannelProvider(options = {}) {
873
874
  },
874
875
  ...target ? { target } : {}
875
876
  };
876
- window.postMessage(message, postTargetOrigin());
877
+ try {
878
+ window.postMessage(message, postTargetOrigin());
879
+ } catch (error) {
880
+ pending.delete(id);
881
+ clearTimeout(timer);
882
+ detachIfIdle();
883
+ reject(error);
884
+ }
877
885
  });
878
886
  },
879
887
  on(event, listener) {
888
+ if (destroyed) return provider;
880
889
  eventBus.on(event, listener);
890
+ subscribedEvents.add(event);
891
+ listen();
881
892
  return provider;
882
893
  },
883
894
  emit(event, ...args) {
@@ -885,7 +896,20 @@ function createExtensionChannelProvider(options = {}) {
885
896
  },
886
897
  removeListener(event, listener) {
887
898
  eventBus.removeListener(event, listener);
899
+ detachIfIdle();
888
900
  return provider;
901
+ },
902
+ destroy() {
903
+ if (destroyed) return;
904
+ destroyed = true;
905
+ eventBus.removeAllListeners();
906
+ subscribedEvents.clear();
907
+ for (const entry of pending.values()) {
908
+ clearTimeout(entry.timer);
909
+ entry.reject(new ProviderRpcError("Extension channel was destroyed", RPC_ERRORS.DISCONNECTED));
910
+ }
911
+ pending.clear();
912
+ detachIfIdle();
889
913
  }
890
914
  };
891
915
  eventBus.setOwner(provider);