@partylayer/provider 0.6.0 → 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.6.0" ;
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,61 +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
- if (signResult.transactionHash !== void 0) {
619
- eventBus.emit(CIP0103_EVENTS.TX_CHANGED, {
620
- status: "signed",
621
- commandId: cmdId,
622
- payload: {
623
- signature: String(signResult.transactionHash),
624
- signedBy: partyId,
625
- party: partyId
626
- }
627
- });
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");
621
+ }
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);
628
626
  }
629
- try {
630
- const receipt = await client.submitTransaction({
631
- signedTx: signResult.signedTx ?? signResult.transactionHash
632
- });
633
- pendingEmitted.delete(cmdId);
634
- eventBus.emit(CIP0103_EVENTS.TX_CHANGED, {
635
- status: "executed",
636
- commandId: cmdId,
637
- payload: {
638
- // All three are real identifiers — the receipt's update id, the
639
- // wallet's command id, or the bridge's own command id for this
640
- // operation. `String(receipt.transactionHash)` was the old tail
641
- // and would now stringify `undefined`.
642
- updateId: receipt.updateId ?? receipt.commandId ?? cmdId,
643
- completionOffset: 0
644
- }
645
- });
646
- } catch (submitErr) {
647
- pendingEmitted.delete(cmdId);
648
- eventBus.emit(CIP0103_EVENTS.TX_CHANGED, {
649
- status: "failed",
650
- commandId: cmdId
651
- });
652
- throw submitErr;
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");
653
630
  }
654
- } catch (signErr) {
655
- if (pendingEmitted.has(cmdId)) {
656
- pendingEmitted.delete(cmdId);
657
- eventBus.emit(CIP0103_EVENTS.TX_CHANGED, {
658
- status: "failed",
659
- commandId: cmdId
660
- });
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 });
661
639
  }
662
- throw signErr;
640
+ throw reason;
641
+ } finally {
642
+ pendingEmitted.delete(cmdId);
663
643
  }
664
644
  return void 0;
665
645
  }
@@ -734,59 +714,51 @@ function wireEvents(client, eventBus, pendingEmitted) {
734
714
  });
735
715
  });
736
716
  client.on("tx:status", (event) => {
737
- const e = event;
738
- if (!e || typeof e !== "object" || !("status" in e)) return;
739
- const statusMap = {
740
- pending: "pending",
741
- submitted: "pending",
742
- signed: "signed",
743
- committed: "executed",
744
- rejected: "failed",
745
- failed: "failed"
746
- };
747
- const mappedStatus = statusMap[e.status] ?? "pending";
748
- const commandId = String(e.txId);
749
- if (mappedStatus === "pending" && pendingEmitted.has(commandId)) {
750
- return;
751
- }
752
- if (mappedStatus === "executed" || mappedStatus === "failed") {
753
- pendingEmitted.delete(commandId);
754
- }
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;
755
723
  let txEvent;
756
- 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;
757
730
  case "signed":
758
- txEvent = {
759
- status: "signed",
760
- commandId,
761
- payload: {
762
- signature: "",
763
- signedBy: "",
764
- party: ""
765
- }
766
- };
731
+ txEvent = signedEvent(tx, commandId);
767
732
  break;
768
- case "executed": {
769
- const raw = e.raw;
770
- txEvent = {
771
- status: "executed",
772
- commandId,
773
- payload: {
774
- updateId: raw?.updateId ?? raw?.commandId ?? commandId,
775
- completionOffset: 0
776
- }
777
- };
733
+ case "committed":
734
+ txEvent = executedEvent(tx, commandId);
778
735
  break;
779
- }
736
+ case "rejected":
780
737
  case "failed":
781
738
  txEvent = { status: "failed", commandId };
782
739
  break;
783
740
  default:
784
- txEvent = { status: "pending", commandId };
785
- break;
741
+ return;
786
742
  }
787
- eventBus.emit(CIP0103_EVENTS.TX_CHANGED, txEvent);
743
+ if (txEvent) eventBus.emit(CIP0103_EVENTS.TX_CHANGED, txEvent);
788
744
  });
789
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
+ }
790
762
  function normalizeParams(params) {
791
763
  if (!params) return {};
792
764
  if (Array.isArray(params)) return params[0] ?? {};
@@ -818,11 +790,33 @@ function createExtensionChannelProvider(options = {}) {
818
790
  const eventBus = new CIP0103EventBus();
819
791
  const channelId = `pl-${++channelSeq}-${target ?? "ext"}`;
820
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
+ }
821
807
  const pending = /* @__PURE__ */ new Map();
822
808
  function onMessage(event) {
823
809
  if (event.source !== window) return;
824
810
  const selfOrigin = typeof window !== "undefined" ? window.location?.origin : void 0;
825
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
+ }
826
820
  const data = event.data;
827
821
  if (!data || data.type !== SPLICE_WALLET_RESPONSE || !data.response) return;
828
822
  const { id, result, error } = data.response;
@@ -831,6 +825,7 @@ function createExtensionChannelProvider(options = {}) {
831
825
  if (!entry) return;
832
826
  pending.delete(String(id));
833
827
  clearTimeout(entry.timer);
828
+ detachIfIdle();
834
829
  if (error) {
835
830
  entry.reject(
836
831
  new ProviderRpcError(
@@ -843,24 +838,24 @@ function createExtensionChannelProvider(options = {}) {
843
838
  entry.resolve(result);
844
839
  }
845
840
  }
846
- if (typeof window !== "undefined") {
847
- window.addEventListener("message", onMessage);
848
- }
849
841
  function postTargetOrigin() {
850
842
  const origin = typeof window !== "undefined" ? window.location?.origin : void 0;
851
843
  return origin && origin !== "null" ? origin : "*";
852
844
  }
853
845
  const provider = {
854
846
  request(args) {
847
+ if (destroyed) return Promise.reject(new ProviderRpcError("Extension channel was destroyed", RPC_ERRORS.DISCONNECTED));
855
848
  if (typeof window === "undefined") {
856
849
  return Promise.reject(
857
850
  new ProviderRpcError("No window: extension channel unavailable", JSON_RPC_ERRORS.INTERNAL_ERROR)
858
851
  );
859
852
  }
853
+ listen();
860
854
  const id = `${channelId}-${++requestSeq}`;
861
855
  return new Promise((resolve, reject) => {
862
856
  const timer = setTimeout(() => {
863
857
  pending.delete(id);
858
+ detachIfIdle();
864
859
  reject(
865
860
  new ProviderRpcError(
866
861
  `Wallet request "${args.method}" timed out after ${timeoutMs}ms`,
@@ -879,11 +874,21 @@ function createExtensionChannelProvider(options = {}) {
879
874
  },
880
875
  ...target ? { target } : {}
881
876
  };
882
- 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
+ }
883
885
  });
884
886
  },
885
887
  on(event, listener) {
888
+ if (destroyed) return provider;
886
889
  eventBus.on(event, listener);
890
+ subscribedEvents.add(event);
891
+ listen();
887
892
  return provider;
888
893
  },
889
894
  emit(event, ...args) {
@@ -891,7 +896,20 @@ function createExtensionChannelProvider(options = {}) {
891
896
  },
892
897
  removeListener(event, listener) {
893
898
  eventBus.removeListener(event, listener);
899
+ detachIfIdle();
894
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();
895
913
  }
896
914
  };
897
915
  eventBus.setOwner(provider);