@ckb-ccc/libp2p 0.0.2 → 0.1.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # @ckb-ccc/libp2p
2
2
 
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#525](https://github.com/ckb-devrel/ccc/pull/525) [`f8c79a5`](https://github.com/ckb-devrel/ccc/commit/f8c79a5dc6f60a52b286b5caa744f00ad2d0ffb4) Thanks [@Hanssen0](https://github.com/Hanssen0)! - Improve Khie connection fallback and path selection. Allow configuring the
8
+ default Relay and add the `hide-mark` Connector attribute.
9
+
10
+ - [#523](https://github.com/ckb-devrel/ccc/pull/523) [`58b3b4d`](https://github.com/ckb-devrel/ccc/commit/58b3b4d307f5eb65f5f710127cb3bd5bf3191d22) Thanks [@Hanssen0](https://github.com/Hanssen0)! - Expose the actual stream connection on incoming JSON-RPC requests so consumers
11
+ can report the transport path used by each request.
12
+
13
+ ### Patch Changes
14
+
15
+ - Updated dependencies [[`58b3b4d`](https://github.com/ckb-devrel/ccc/commit/58b3b4d307f5eb65f5f710127cb3bd5bf3191d22), [`fc937f0`](https://github.com/ckb-devrel/ccc/commit/fc937f08f9e33c852697831dc5b17a9423fd9e3f)]:
16
+ - @ckb-ccc/core@1.20.1
17
+
3
18
  ## 0.0.2
4
19
 
5
20
  ### Patch Changes
package/dist/barrel.d.mts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { ccc } from "@ckb-ccc/core";
2
2
  import { Multiaddr } from "@multiformats/multiaddr";
3
- import { AbortOptions, Libp2p, PeerId } from "@libp2p/interface";
4
- import { ConnectionManager, Registrar } from "@libp2p/interface-internal";
3
+ import { AbortOptions, Connection, Libp2p, PeerId } from "@libp2p/interface";
4
+ import { AddressManager, ConnectionManager, Registrar } from "@libp2p/interface-internal";
5
5
  //#region src/jsonRpcService.d.ts
6
6
  type JsonRpcServiceComponents = {
7
7
  registrar: Registrar;
@@ -11,6 +11,7 @@ type JsonRpcServiceConfig = {
11
11
  maxMessageLength?: number;
12
12
  };
13
13
  type JsonRpcRequest = {
14
+ connection: Connection;
14
15
  peerId: PeerId;
15
16
  payload: ccc.JsonRpcPayload;
16
17
  };
@@ -46,10 +47,13 @@ declare class JsonRpcTransportLibp2p implements ccc.JsonRpcTransport {
46
47
  private readonly timeout;
47
48
  constructor(node: Libp2p, peerId: PeerId, config: JsonRpcTransportLibp2pConfig);
48
49
  request(payload: ccc.JsonRpcPayload): Promise<ccc.JsonRpcResponse>;
50
+ private openStream;
51
+ private connections;
49
52
  }
50
53
  //#endregion
51
54
  //#region src/pairingService.d.ts
52
55
  type PairingServiceComponents = {
56
+ addressManager?: Pick<AddressManager, "getAddresses">;
53
57
  connectionManager: ConnectionManager;
54
58
  registrar: Registrar;
55
59
  };
@@ -90,6 +94,8 @@ declare abstract class PairingService<Components extends PairingServiceComponent
90
94
  private handleProtocol;
91
95
  private addPairedPeer;
92
96
  private notifyPairedPeer;
97
+ private addresses;
98
+ private openFallbackConnection;
93
99
  private rotateSecret;
94
100
  private removePairedPeer;
95
101
  private createPeerTimeout;
@@ -1 +1 @@
1
- {"version":3,"file":"barrel.d.mts","names":[],"sources":["../src/jsonRpcService.ts","../src/jsonRpcTransport.ts","../src/pairingService.ts","../src/pairingEndpoint.ts","../src/barrel.ts"],"mappings":";;;;;KAOY;EACV,WAAW;;KAGD;EACV;EACA;;KAGU;EACV,QAAQ;EACR,SAAS,IAAI;;KAGH,sBACV,mBAAmB,2BAA2B,6BAC3C,MAAM,eAAe,aAAa,SAAS;uBAE1B,eACpB,mBAAmB,2BAA2B;WAMnC,YAAY;WACZ,QAAQ;mBALF;WACR;EAGE,YAAA,YAAY,YACZ,QAAQ;qBAaA,cAAc,SAAS;EAE1C,QAAQ,WAAW,OAAO;EAKpB,SAAK;EAQL,QAAI;UAII;UA6CN;;iBA4CM,eACd,mBAAmB,2BAA2B,0BAE9C,QAAQ,sBACR,SAAS,sBAAsB,eAC7B,YAAY,eAAe,eAAe;;;KCxJlC;EACV;EACA;EACA;EACA,SAAS;EACT,cAAc,UAAU,IAAI;;cAGjB,kCAAkC,IAAI;mBAK9B;WACR,QAAQ;mBACA;mBANF;mBACA;EAGE,YAAA,MAAM,QACd,QAAQ,QACA,QAAQ;EAoBrB,QAAQ,SAAS,IAAI,iBAAiB,QAAQ,IAAI;;;;KC5B9C;EACV,mBAAmB;EACnB,WAAW;;KAGD;;EAEV;EACA;EACA;;KAGU;EACV,WAAW;EACX;;KAGU,aACV,mBAAmB,2BAA2B,6BAC3C,MAAM,eAAe,aAAa,QAAQ;uBAQzB,eACpB,mBAAmB,2BAA2B;WAenC,YAAY;WACZ,QAAQ;UAdX;mBACS;mBACA;mBAGA;mBACA;mBACA;EAMN,YAAA,YAAY,YACZ,QAAQ;qBAOA,QAAQ,QAAQ;;MAG/B;EAIJ,QAAQ,WAAW,OAAO;EAK1B,SAAS,WAAW,QAAQ,QAAQ;EAKpC,WAAW,WAAW,QAAQ;EAK9B,gBAAgB,WAAW;EAKrB,SAAK;EAQL,QAAI;EAOV,SAAS,QAAQ;EAIjB,QAAQ,QAAQ;EAWV,KACJ,QAAQ,eACR,UAAS,eACR,QAAQ;EA6CL,OAAO,QAAQ,SAAM;UAyBb;UAkDN;UAWA;UAIA;UAOA;UAgBA;UAMM;UAsBN;;iBASM,eACd,mBAAmB,2BAA2B,0BAE9C,QAAQ,sBACR,SAAS,aAAa,eACpB,YAAY,eAAe,eAAe;;;cC5TjC,iCAAiC;WAEjC;WACA;EADA,YAAA,sBACA;;iBASS,sBACpB,qBACA,oBAAoB,aACpB,gBACA,gBAAa;iBAuCO,sBACpB,kBACA,wBACC,QAAQ"}
1
+ {"version":3,"file":"barrel.d.mts","names":[],"sources":["../src/jsonRpcService.ts","../src/jsonRpcTransport.ts","../src/pairingService.ts","../src/pairingEndpoint.ts","../src/barrel.ts"],"mappings":";;;;;KAOY;EACV,WAAW;;KAGD;EACV;EACA;;KAGU;EACV,YAAY;EACZ,QAAQ;EACR,SAAS,IAAI;;KAGH,sBACV,mBAAmB,2BAA2B,6BAC3C,MAAM,eAAe,aAAa,SAAS;uBAE1B,eACpB,mBAAmB,2BAA2B;WAMnC,YAAY;WACZ,QAAQ;mBALF;WACR;EAGE,YAAA,YAAY,YACZ,QAAQ;qBAaA,cAAc,SAAS;EAE1C,QAAQ,WAAW,OAAO;EAKpB,SAAK;EAQL,QAAI;UAII;UA8CN;;iBA4CM,eACd,mBAAmB,2BAA2B,0BAE9C,QAAQ,sBACR,SAAS,sBAAsB,eAC7B,YAAY,eAAe,eAAe;;;KC1JlC;EACV;EACA;EACA;EACA,SAAS;EACT,cAAc,UAAU,IAAI;;cAGjB,kCAAkC,IAAI;mBAK9B;WACR,QAAQ;mBACA;mBANF;mBACA;EAGE,YAAA,MAAM,QACd,QAAQ,QACA,QAAQ;EAoBrB,QAAQ,SAAS,IAAI,iBAAiB,QAAQ,IAAI;UAmC1C;UAkBN;;;;KC7EE;EACV,iBAAiB,KAAK;EACtB,mBAAmB;EACnB,WAAW;;KAGD;;EAEV;EACA;EACA;;KAGU;EACV,WAAW;EACX;;KAGU,aACV,mBAAmB,2BAA2B,6BAC3C,MAAM,eAAe,aAAa,QAAQ;uBASzB,eACpB,mBAAmB,2BAA2B;WAenC,YAAY;WACZ,QAAQ;UAdX;mBACS;mBACA;mBAGA;mBACA;mBACA;EAMN,YAAA,YAAY,YACZ,QAAQ;qBAOA,QAAQ,QAAQ;;MAG/B;EAIJ,QAAQ,WAAW,OAAO;EAK1B,SAAS,WAAW,QAAQ,QAAQ;EAKpC,WAAW,WAAW,QAAQ;EAK9B,gBAAgB,WAAW;EAKrB,SAAK;EAQL,QAAI;EAOV,SAAS,QAAQ;EAIjB,QAAQ,QAAQ;EAWV,KACJ,QAAQ,eACR,UAAS,eACR,QAAQ;EAyCL,OAAO,QAAQ,SAAM;UAyBb;UAwDN;UAWA;UAIA;UAIM;UAYN;UAOA;UAgBA;UAMM;UAsBN;;iBASM,eACd,mBAAmB,2BAA2B,0BAE9C,QAAQ,sBACR,SAAS,aAAa,eACpB,YAAY,eAAe,eAAe;;;cCpVjC,iCAAiC;WAEjC;WACA;EADA,YAAA,sBACA;;iBASS,sBACpB,qBACA,oBAAoB,aACpB,gBACA,gBAAa;iBAuCO,sBACpB,kBACA,wBACC,QAAQ"}
package/dist/barrel.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import{t as e}from"./rolldown-runtime-DK3Fl9T5.mjs";import{JsonRpcError as t,ccc as n}from"@ckb-ccc/core";import{lpStream as r}from"@libp2p/utils";import{multiaddr as i}from"@multiformats/multiaddr";import*as a from"it-length-prefixed";var o=class{constructor(e,t){this.components=e,this.config=t,this.errorListeners=new Set;let n=t.maxMessageLength??1048576;if(!Number.isSafeInteger(n)||n<=0)throw Error(`Maximum JSON-RPC message length must be a positive integer`);this.maxMessageLength=n}onError(e){return this.errorListeners.add(e),()=>this.errorListeners.delete(e)}async start(){await this.components.registrar.handle(this.config.protocol,this.handleProtocol.bind(this),{runOnLimitedConnection:!0})}async stop(){await this.components.registrar.unhandle(this.config.protocol)}async handleProtocol(e,t){let i=!1;try{let a=r(e,{maxDataLength:this.maxMessageLength}),o=c(n.bytesTo((await a.read()).subarray(),`utf8`)),l;try{l={jsonrpc:`2.0`,id:o.id,result:await this.handleRequest({peerId:t.remotePeer,payload:o})}}catch(e){i=u(e);let t=s(e);l={jsonrpc:`2.0`,id:o.id,error:{code:t.code,message:t.message,data:t.data}}}await a.write(n.bytesFrom(JSON.stringify(l),`utf8`)),await e.close()}catch(t){if(i)return;this.handleError(t,e)}}handleError(e,t){let n=l(e);return t?.abort(n),this.errorListeners.forEach(e=>e(n)),n}};function s(e){return e instanceof t?e:new t({code:-32603,message:l(e).message})}function c(e){let t=JSON.parse(e);if(t?.jsonrpc!==`2.0`||typeof t.id!=`number`&&typeof t.id!=`string`||typeof t.method!=`string`)throw Error(`Invalid JSON-RPC request`);return t}function l(e){return e instanceof Error?e:Error(`JSON-RPC request failed`)}function u(e){return e instanceof Error&&(e.name===`AbortError`||e.name===`TimeoutError`)}function d(e,t){return n=>new class extends o{handleRequest(e){return t.call(this,e)}}(n,e)}var f=class{constructor(e,t,n){this.node=e,this.peerId=t,this.config=n;let r=n.maxMessageLength??1048576;if(!Number.isSafeInteger(r)||r<=0)throw Error(`Maximum JSON-RPC message length must be a positive integer`);this.maxMessageLength=r;let i=n.timeout??12e4;if(!Number.isSafeInteger(i)||i<=0)throw Error(`JSON-RPC request timeout must be a positive integer`);this.timeout=i}async request(e){let t=AbortSignal.timeout(this.timeout),i=this.config.signal?n.abortSignalAny([this.config.signal,t]):t,a,o;try{a=await this.node.dialProtocol(this.peerId,this.config.protocol,{runOnLimitedConnection:!0,signal:i});let t=r(a,{maxDataLength:this.maxMessageLength});await t.write(n.bytesFrom(JSON.stringify(e),`utf8`),{signal:i}),await a.close({signal:i}),o=JSON.parse(n.bytesTo((await t.read({signal:i})).subarray(),`utf8`)),await a.closeRead({signal:i}),i.throwIfAborted()}catch(e){let t=p(e);throw a?.abort(t),t}return this.config.onResponse?.(o),o}};function p(e){return e instanceof Error?e:Error(`JSON-RPC request failed`)}const m=16384,h=[`addresses`,`role`,`secret`];var g=class extends Error{constructor(e,t){super(`Expected a ${e} pairing endpoint, received ${t?`role "${t}"`:`an endpoint without a role`}`),this.expectedRole=e,this.actualRole=t,this.name=`PairingEndpointRoleError`}};async function _(e,t,r,i){if(t.length===0)throw Error(`Pairing endpoint requires at least one address`);if(t.length>16)throw Error(`Pairing endpoint supports at most 16 addresses`);let a=b(t);if(a.byteLength>m)throw Error(`Pairing endpoint address data is too large`);let o=await S(a,new CompressionStream(`deflate`)),s=n.bytesTo(o,`base64url`),c=new URL(e),{params:l,route:u}=y(c);return h.forEach(e=>{l.delete(e)}),i&&l.set(`role`,i),l.set(`addresses`,s),l.set(`secret`,r),c.hash=`${u}?${l.toString()}`,c.toString()}async function v(e,t){let{params:n}=y(new URL(e.trim()));if(t){let e=n.get(`role`)?.trim()||void 0;if(e!==t)throw new g(t,e)}let r=n.get(`addresses`)?.trim(),i=n.get(`secret`)?.trim();if(!r||!i)throw Error(`Pairing endpoint is incomplete`);return{addresses:await x(r),secret:i}}function y(e){let t=e.hash.slice(1),n=t.indexOf(`?`);return n===-1?{params:new URLSearchParams,route:t}:{params:new URLSearchParams(t.slice(n+1)),route:t.slice(0,n)}}function b(e){return n.bytesConcat(...a.encode(e.map(e=>e.bytes)))}async function x(e){try{let t=await S(n.bytesFrom(e,`base64url`),new DecompressionStream(`deflate`),m),r=[];for(let e of a.decode([t],{maxDataLength:m})){if(r.length>=16)throw Error(`Too many addresses`);r.push(i(e.subarray()))}if(r.length===0)throw Error(`Missing addresses`);return r}catch(e){throw Error(`Pairing endpoint contains invalid compressed addresses`,{cause:e})}}async function S(e,t,n){let r=n===void 0?t.readable:t.readable.pipeThrough(C(n)),i=new Response(r).arrayBuffer().then(e=>({buffer:e,ok:!0}),e=>({error:e,ok:!1})),a=t.writable.getWriter();try{await a.write(Uint8Array.from(e)),await a.close()}catch(e){let t=await i;throw t.ok?e:t.error}let o=await i;if(!o.ok)throw o.error;return new Uint8Array(o.buffer)}function C(e){let t=0;return new TransformStream({transform(n,r){if(n.byteLength>e-t)throw Error(`Decompressed address data is too large`);t+=n.byteLength,r.enqueue(n)}})}const w=1024;var T=class{constructor(e,t){if(this.components=e,this.config=t,this.currentSecret=j(),this.errorListeners=new Set,this.pairedListeners=new Set,this.unpairedListeners=new Set,this.secretChangedListeners=new Set,this.pairingTimeouts=new Map,t.pairedPeerTimeoutMs<=0)throw Error(`Paired peer timeout must be greater than zero`)}get secret(){return this.currentSecret}onError(e){return this.errorListeners.add(e),()=>this.errorListeners.delete(e)}onPaired(e){return this.pairedListeners.add(e),()=>this.pairedListeners.delete(e)}onUnpaired(e){return this.unpairedListeners.add(e),()=>this.unpairedListeners.delete(e)}onSecretChanged(e){return this.secretChangedListeners.add(e),()=>this.secretChangedListeners.delete(e)}async start(){await this.components.registrar.handle(this.config.protocol,this.handleProtocol.bind(this),{runOnLimitedConnection:!0})}async stop(){await this.components.registrar.unhandle(this.config.protocol),this.pairingTimeouts.forEach(e=>clearTimeout(e)),this.pairingTimeouts.clear()}isPaired(e){return this.pairingTimeouts.has(e.toString())}refresh(e){let t=e.toString(),n=this.pairingTimeouts.get(t);n!==void 0&&(clearTimeout(n),this.pairingTimeouts.set(t,this.createPeerTimeout(e)))}async pair(e,t={}){let n,r;try{let i=await this.openStream(e.addresses,t);if(n=i.stream,!i.connection)throw Error(`Could not determine remote peer`);this.addPairedPeer(i.connection.remotePeer)&&(r=i.connection.remotePeer);let a=await D(n,{type:`pair`,secret:e.secret,name:this.config.name},t);if(t.signal?.throwIfAborted(),n=void 0,!a.ok)throw Error(a.error);return r&&(this.rotateSecret(),this.notifyPairedPeer(r,a.name)),i.connection.remotePeer}catch(e){throw r&&this.removePairedPeer(r,!1),this.handleError(e,n)}}async unpair(e){if(!this.removePairedPeer(e))return;let t={signal:AbortSignal.timeout(1e4)},n;try{let r=await this.openStream(e,t);n=r.stream;let i=await D(r.stream,{type:`unpair`},t);if(n=void 0,!i.ok)throw Error(i.error)}catch(e){this.handleError(e,n)}}async handleProtocol(e,t){let n;try{let i=r(e,{maxDataLength:w}),a=k((await i.read()).subarray());if(a.type===`unpair`){this.removePairedPeer(t.remotePeer),await O(i,e,{ok:!0});return}if(a.secret!==this.secret||!this.canPair(t.remotePeer)){await O(i,e,{ok:!1,error:`Pairing rejected`});return}this.rotateSecret(),this.addPairedPeer(t.remotePeer)&&(n=t.remotePeer),await O(i,e,{ok:!0,name:this.config.name}),n&&this.notifyPairedPeer(n,a.name)}catch(t){n&&this.removePairedPeer(n,!1),this.handleError(t,e)}}addPairedPeer(e){let t=e.toString(),n=this.pairingTimeouts.get(t);return n!==void 0&&clearTimeout(n),this.pairingTimeouts.set(t,this.createPeerTimeout(e)),n===void 0}notifyPairedPeer(e,t){this.pairedListeners.forEach(n=>n(e,t))}rotateSecret(){this.currentSecret=j(),this.secretChangedListeners.forEach(e=>e(this.currentSecret))}removePairedPeer(e,t=!0){let n=e.toString(),r=this.pairingTimeouts.get(n);return r!==void 0&&(clearTimeout(r),this.pairingTimeouts.delete(n),t&&this.unpairedListeners.forEach(t=>t(e)),!0)}createPeerTimeout(e){return setTimeout(()=>{this.removePairedPeer(e)},this.config.pairedPeerTimeoutMs)}async openStream(e,t){let n;return{stream:await this.components.connectionManager.openStream(e,this.config.protocol,{runOnLimitedConnection:!0,signal:t.signal,onProgress(e){e.type===`connection:opened-stream`&&(n=e.detail.connection)}}),connection:n}}handleError(e,t){let n=M(e,`Pairing operation failed`);return t?.abort(n),this.errorListeners.forEach(e=>e(n)),n}};function E(e,t){return n=>new class extends T{canPair(e){return t.call(this,e)}}(n,e)}async function D(e,t,i){let a=r(e,{maxDataLength:w});await a.write(n.bytesFrom(JSON.stringify(t),`utf8`),{signal:i.signal}),await e.close(i);let o=A((await a.read(i)).subarray());return await e.closeRead(i),o}async function O(e,t,r){await e.write(n.bytesFrom(JSON.stringify(r),`utf8`)),await t.close()}function k(e){let t=JSON.parse(n.bytesTo(e,`utf8`));if(t.type===`pair`&&typeof t.secret==`string`&&t.secret)return{type:`pair`,secret:t.secret,name:typeof t.name==`string`?t.name:void 0};if(t.type===`unpair`)return{type:`unpair`};throw Error(`Invalid pairing request`)}function A(e){let t=JSON.parse(n.bytesTo(e,`utf8`));if(t.ok===!0)return{ok:!0,name:typeof t.name==`string`?t.name:void 0};if(t.ok===!1&&typeof t.error==`string`)return{ok:!1,error:t.error};throw Error(`Invalid pairing response`)}function j(){return n.bytesTo(crypto.getRandomValues(new Uint8Array(16)),`base64url`)}function M(e,t){return e instanceof Error?e:Error(t)}var N=e({JsonRpcService:()=>o,JsonRpcTransportLibp2p:()=>f,PairingEndpointRoleError:()=>g,PairingService:()=>T,decodePairingEndpoint:()=>v,encodePairingEndpoint:()=>_,jsonRpcService:()=>d,pairingService:()=>E});export{o as JsonRpcService,f as JsonRpcTransportLibp2p,g as PairingEndpointRoleError,T as PairingService,v as decodePairingEndpoint,_ as encodePairingEndpoint,d as jsonRpcService,E as pairingService,N as t};
1
+ import{t as e}from"./rolldown-runtime-DK3Fl9T5.mjs";import{JsonRpcError as t,ccc as n}from"@ckb-ccc/core";import{lpStream as r}from"@libp2p/utils";import{CODE_P2P as i,multiaddr as a}from"@multiformats/multiaddr";import*as o from"it-length-prefixed";var s=class{constructor(e,t){this.components=e,this.config=t,this.errorListeners=new Set;let n=t.maxMessageLength??1048576;if(!Number.isSafeInteger(n)||n<=0)throw Error(`Maximum JSON-RPC message length must be a positive integer`);this.maxMessageLength=n}onError(e){return this.errorListeners.add(e),()=>this.errorListeners.delete(e)}async start(){await this.components.registrar.handle(this.config.protocol,this.handleProtocol.bind(this),{runOnLimitedConnection:!0})}async stop(){await this.components.registrar.unhandle(this.config.protocol)}async handleProtocol(e,t){let i=!1;try{let a=r(e,{maxDataLength:this.maxMessageLength}),o=l(n.bytesTo((await a.read()).subarray(),`utf8`)),s;try{s={jsonrpc:`2.0`,id:o.id,result:await this.handleRequest({connection:t,peerId:t.remotePeer,payload:o})}}catch(e){i=d(e);let t=c(e);s={jsonrpc:`2.0`,id:o.id,error:{code:t.code,message:t.message,data:t.data}}}await a.write(n.bytesFrom(JSON.stringify(s),`utf8`)),await e.close()}catch(t){if(i)return;this.handleError(t,e)}}handleError(e,t){let n=u(e);return t?.abort(n),this.errorListeners.forEach(e=>e(n)),n}};function c(e){return e instanceof t?e:new t({code:-32603,message:u(e).message})}function l(e){let t=JSON.parse(e);if(t?.jsonrpc!==`2.0`||typeof t.id!=`number`&&typeof t.id!=`string`||typeof t.method!=`string`)throw Error(`Invalid JSON-RPC request`);return t}function u(e){return e instanceof Error?e:Error(`JSON-RPC request failed`)}function d(e){return e instanceof Error&&(e.name===`AbortError`||e.name===`TimeoutError`)}function f(e,t){return n=>new class extends s{handleRequest(e){return t.call(this,e)}}(n,e)}var p=class{constructor(e,t,n){this.node=e,this.peerId=t,this.config=n;let r=n.maxMessageLength??1048576;if(!Number.isSafeInteger(r)||r<=0)throw Error(`Maximum JSON-RPC message length must be a positive integer`);this.maxMessageLength=r;let i=n.timeout??12e4;if(!Number.isSafeInteger(i)||i<=0)throw Error(`JSON-RPC request timeout must be a positive integer`);this.timeout=i}async request(e){let t=AbortSignal.timeout(this.timeout),i=this.config.signal?n.abortSignalAny([this.config.signal,t]):t,a,o;try{a=await this.openStream(i);let t=r(a,{maxDataLength:this.maxMessageLength});await t.write(n.bytesFrom(JSON.stringify(e),`utf8`),{signal:i}),await a.close({signal:i}),o=JSON.parse(n.bytesTo((await t.read({signal:i})).subarray(),`utf8`)),await a.closeRead({signal:i}),i.throwIfAborted()}catch(e){let t=h(e);throw a?.abort(t),t}return this.config.onResponse?.(o),o}async openStream(e){for(let t of this.connections())try{return await t.newStream(this.config.protocol,{runOnLimitedConnection:!0,signal:e})}catch{e.throwIfAborted()}return this.node.dialProtocol(this.peerId,this.config.protocol,{runOnLimitedConnection:!0,signal:e})}connections(){return this.node.getConnections(this.peerId).filter(({status:e})=>e===`open`).sort(m)}};function m(e,t){return e.direct===t.direct?e.rtt===void 0?t.rtt===void 0?0:1:t.rtt===void 0?-1:e.rtt-t.rtt:e.direct?-1:1}function h(e){return e instanceof Error?e:Error(`JSON-RPC request failed`)}const g=16384,_=[`addresses`,`role`,`secret`];var v=class extends Error{constructor(e,t){super(`Expected a ${e} pairing endpoint, received ${t?`role "${t}"`:`an endpoint without a role`}`),this.expectedRole=e,this.actualRole=t,this.name=`PairingEndpointRoleError`}};async function y(e,t,r,i){if(t.length===0)throw Error(`Pairing endpoint requires at least one address`);if(t.length>16)throw Error(`Pairing endpoint supports at most 16 addresses`);let a=S(t);if(a.byteLength>g)throw Error(`Pairing endpoint address data is too large`);let o=await w(a,new CompressionStream(`deflate`)),s=n.bytesTo(o,`base64url`),c=new URL(e),{params:l,route:u}=x(c);return _.forEach(e=>{l.delete(e)}),i&&l.set(`role`,i),l.set(`addresses`,s),l.set(`secret`,r),c.hash=`${u}?${l.toString()}`,c.toString()}async function b(e,t){let{params:n}=x(new URL(e.trim()));if(t){let e=n.get(`role`)?.trim()||void 0;if(e!==t)throw new v(t,e)}let r=n.get(`addresses`)?.trim(),i=n.get(`secret`)?.trim();if(!r||!i)throw Error(`Pairing endpoint is incomplete`);return{addresses:await C(r),secret:i}}function x(e){let t=e.hash.slice(1),n=t.indexOf(`?`);return n===-1?{params:new URLSearchParams,route:t}:{params:new URLSearchParams(t.slice(n+1)),route:t.slice(0,n)}}function S(e){return n.bytesConcat(...o.encode(e.map(e=>e.bytes)))}async function C(e){try{let t=await w(n.bytesFrom(e,`base64url`),new DecompressionStream(`deflate`),g),r=[];for(let e of o.decode([t],{maxDataLength:g})){if(r.length>=16)throw Error(`Too many addresses`);r.push(a(e.subarray()))}if(r.length===0)throw Error(`Missing addresses`);return r}catch(e){throw Error(`Pairing endpoint contains invalid compressed addresses`,{cause:e})}}async function w(e,t,n){let r=n===void 0?t.readable:t.readable.pipeThrough(T(n)),i=new Response(r).arrayBuffer().then(e=>({buffer:e,ok:!0}),e=>({error:e,ok:!1})),a=t.writable.getWriter();try{await a.write(Uint8Array.from(e)),await a.close()}catch(e){let t=await i;throw t.ok?e:t.error}let o=await i;if(!o.ok)throw o.error;return new Uint8Array(o.buffer)}function T(e){let t=0;return new TransformStream({transform(n,r){if(n.byteLength>e-t)throw Error(`Decompressed address data is too large`);t+=n.byteLength,r.enqueue(n)}})}const E=1024;var D=class{constructor(e,t){if(this.components=e,this.config=t,this.currentSecret=L(),this.errorListeners=new Set,this.pairedListeners=new Set,this.unpairedListeners=new Set,this.secretChangedListeners=new Set,this.pairingTimeouts=new Map,t.pairedPeerTimeoutMs<=0)throw Error(`Paired peer timeout must be greater than zero`)}get secret(){return this.currentSecret}onError(e){return this.errorListeners.add(e),()=>this.errorListeners.delete(e)}onPaired(e){return this.pairedListeners.add(e),()=>this.pairedListeners.delete(e)}onUnpaired(e){return this.unpairedListeners.add(e),()=>this.unpairedListeners.delete(e)}onSecretChanged(e){return this.secretChangedListeners.add(e),()=>this.secretChangedListeners.delete(e)}async start(){await this.components.registrar.handle(this.config.protocol,this.handleProtocol.bind(this),{runOnLimitedConnection:!0})}async stop(){await this.components.registrar.unhandle(this.config.protocol),this.pairingTimeouts.forEach(e=>clearTimeout(e)),this.pairingTimeouts.clear()}isPaired(e){return this.pairingTimeouts.has(e.toString())}refresh(e){let t=e.toString(),n=this.pairingTimeouts.get(t);n!==void 0&&(clearTimeout(n),this.pairingTimeouts.set(t,this.createPeerTimeout(e)))}async pair(e,t={}){let n,r;try{let i=await this.openStream(e.addresses,t);if(n=i.stream,!i.connection)throw Error(`Could not determine remote peer`);this.addPairedPeer(i.connection.remotePeer)&&(r=i.connection.remotePeer);let a=await k(n,A(e.secret,this.config.name,this.addresses()),t);if(t.signal?.throwIfAborted(),n=void 0,!a.ok)throw Error(a.error);return r&&(this.rotateSecret(),this.notifyPairedPeer(r,a.name)),i.connection.remotePeer}catch(e){throw r&&this.removePairedPeer(r,!1),this.handleError(e,n)}}async unpair(e){if(!this.removePairedPeer(e))return;let t={signal:AbortSignal.timeout(1e4)},n;try{let r=await this.openStream(e,t);n=r.stream;let i=await k(r.stream,{type:`unpair`},t);if(n=void 0,!i.ok)throw Error(i.error)}catch(e){this.handleError(e,n)}}async handleProtocol(e,t){let n;try{let i=r(e,{maxDataLength:E}),a=N((await i.read()).subarray());if(a.type===`unpair`){this.removePairedPeer(t.remotePeer),await M(i,e,{ok:!0});return}if(a.secret!==this.secret||!this.canPair(t.remotePeer)){await M(i,e,{ok:!1,error:`Pairing rejected`});return}let o=F(a.addresses,t.remotePeer);this.rotateSecret(),this.addPairedPeer(t.remotePeer)&&(n=t.remotePeer),await M(i,e,{ok:!0,name:this.config.name}),n&&(this.notifyPairedPeer(n,a.name),this.openFallbackConnection(o))}catch(t){n&&this.removePairedPeer(n,!1),this.handleError(t,e)}}addPairedPeer(e){let t=e.toString(),n=this.pairingTimeouts.get(t);return n!==void 0&&clearTimeout(n),this.pairingTimeouts.set(t,this.createPeerTimeout(e)),n===void 0}notifyPairedPeer(e,t){this.pairedListeners.forEach(n=>n(e,t))}addresses(){return this.components.addressManager?.getAddresses()??[]}async openFallbackConnection(e){if(e.length!==0)try{await this.components.connectionManager.openConnection(e)}catch{}}rotateSecret(){this.currentSecret=L(),this.secretChangedListeners.forEach(e=>e(this.currentSecret))}removePairedPeer(e,t=!0){let n=e.toString(),r=this.pairingTimeouts.get(n);return r!==void 0&&(clearTimeout(r),this.pairingTimeouts.delete(n),t&&this.unpairedListeners.forEach(t=>t(e)),!0)}createPeerTimeout(e){return setTimeout(()=>{this.removePairedPeer(e)},this.config.pairedPeerTimeoutMs)}async openStream(e,t){let n;return{stream:await this.components.connectionManager.openStream(e,this.config.protocol,{runOnLimitedConnection:!0,signal:t.signal,onProgress(e){e.type===`connection:opened-stream`&&(n=e.detail.connection)}}),connection:n}}handleError(e,t){let n=R(e,`Pairing operation failed`);return t?.abort(n),this.errorListeners.forEach(e=>e(n)),n}};function O(e,t){return n=>new class extends D{canPair(e){return t.call(this,e)}}(n,e)}async function k(e,t,i){let a=r(e,{maxDataLength:E});await a.write(n.bytesFrom(JSON.stringify(t),`utf8`),{signal:i.signal}),await e.close(i);let o=I((await a.read(i)).subarray());return await e.closeRead(i),o}function A(e,t,n){let r=n.map(e=>e.toString()),i={type:`pair`,secret:e,name:t,addresses:r};for(;r.length>0&&!j(i);)r.pop();if(j(i)||delete i.addresses,j(i)||delete i.name,!j(i))throw Error(`Pairing request is too large`);return i}function j(e){return n.bytesFrom(JSON.stringify(e),`utf8`).byteLength<=E}async function M(e,t,r){await e.write(n.bytesFrom(JSON.stringify(r),`utf8`)),await t.close()}function N(e){let t=JSON.parse(n.bytesTo(e,`utf8`));if(t.type===`pair`&&typeof t.secret==`string`&&t.secret)return{type:`pair`,secret:t.secret,name:typeof t.name==`string`?t.name:void 0,addresses:P(t.addresses)};if(t.type===`unpair`)return{type:`unpair`};throw Error(`Invalid pairing request`)}function P(e){if(Array.isArray(e))return e.filter(e=>typeof e==`string`&&e.length>0)}function F(e,t){return(e??[]).flatMap(e=>{try{let n=a(e);return n.getComponents().filter(({code:e})=>e===i).at(-1)?.value===t.toString()?[n]:[]}catch{return[]}})}function I(e){let t=JSON.parse(n.bytesTo(e,`utf8`));if(t.ok===!0)return{ok:!0,name:typeof t.name==`string`?t.name:void 0};if(t.ok===!1&&typeof t.error==`string`)return{ok:!1,error:t.error};throw Error(`Invalid pairing response`)}function L(){return n.bytesTo(crypto.getRandomValues(new Uint8Array(16)),`base64url`)}function R(e,t){return e instanceof Error?e:Error(t)}var z=e({JsonRpcService:()=>s,JsonRpcTransportLibp2p:()=>p,PairingEndpointRoleError:()=>v,PairingService:()=>D,decodePairingEndpoint:()=>b,encodePairingEndpoint:()=>y,jsonRpcService:()=>f,pairingService:()=>O});export{s as JsonRpcService,p as JsonRpcTransportLibp2p,v as PairingEndpointRoleError,D as PairingService,b as decodePairingEndpoint,y as encodePairingEndpoint,f as jsonRpcService,O as pairingService,z as t};
2
2
  //# sourceMappingURL=barrel.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"barrel.mjs","names":["DEFAULT_MAX_MESSAGE_LENGTH","asJsonRpcError"],"sources":["../src/jsonRpcService.ts","../src/jsonRpcTransport.ts","../src/pairingEndpoint.ts","../src/pairingService.ts","../src/barrel.ts"],"sourcesContent":["import { ccc, JsonRpcError } from \"@ckb-ccc/core\";\nimport type { Connection, PeerId, Stream } from \"@libp2p/interface\";\nimport type { Registrar } from \"@libp2p/interface-internal\";\nimport { lpStream } from \"@libp2p/utils\";\n\nconst DEFAULT_MAX_MESSAGE_LENGTH = 1024 * 1024;\n\nexport type JsonRpcServiceComponents = {\n registrar: Registrar;\n};\n\nexport type JsonRpcServiceConfig = {\n protocol: string;\n maxMessageLength?: number;\n};\n\nexport type JsonRpcRequest = {\n peerId: PeerId;\n payload: ccc.JsonRpcPayload;\n};\n\nexport type JsonRpcRequestHandler<\n Components extends JsonRpcServiceComponents = JsonRpcServiceComponents,\n> = (this: JsonRpcService<Components>, request: JsonRpcRequest) => unknown;\n\nexport abstract class JsonRpcService<\n Components extends JsonRpcServiceComponents = JsonRpcServiceComponents,\n> {\n private readonly errorListeners = new Set<(error: Error) => void>();\n readonly maxMessageLength: number;\n\n constructor(\n readonly components: Components,\n readonly config: JsonRpcServiceConfig,\n ) {\n const maxMessageLength =\n config.maxMessageLength ?? DEFAULT_MAX_MESSAGE_LENGTH;\n if (!Number.isSafeInteger(maxMessageLength) || maxMessageLength <= 0) {\n throw new Error(\n \"Maximum JSON-RPC message length must be a positive integer\",\n );\n }\n\n this.maxMessageLength = maxMessageLength;\n }\n\n protected abstract handleRequest(request: JsonRpcRequest): unknown;\n\n onError(listener: (error: Error) => void) {\n this.errorListeners.add(listener);\n return () => this.errorListeners.delete(listener);\n }\n\n async start() {\n await this.components.registrar.handle(\n this.config.protocol,\n this.handleProtocol.bind(this),\n { runOnLimitedConnection: true },\n );\n }\n\n async stop() {\n await this.components.registrar.unhandle(this.config.protocol);\n }\n\n private async handleProtocol(stream: Stream, connection: Connection) {\n let requestAborted = false;\n\n try {\n const rpcStream = lpStream(stream, {\n maxDataLength: this.maxMessageLength,\n });\n const payload = parseJsonRpcRequest(\n ccc.bytesTo((await rpcStream.read()).subarray(), \"utf8\"),\n );\n\n let response: ccc.JsonRpcResponse;\n try {\n response = {\n jsonrpc: \"2.0\",\n id: payload.id,\n result: await this.handleRequest({\n peerId: connection.remotePeer,\n payload,\n }),\n };\n } catch (cause) {\n requestAborted = isAbortError(cause);\n const error = toJsonRpcError(cause);\n response = {\n jsonrpc: \"2.0\",\n id: payload.id,\n error: {\n code: error.code,\n message: error.message,\n data: error.data,\n },\n };\n }\n\n await rpcStream.write(ccc.bytesFrom(JSON.stringify(response), \"utf8\"));\n await stream.close();\n } catch (cause) {\n if (requestAborted) {\n return;\n }\n this.handleError(cause, stream);\n }\n }\n\n private handleError(cause: unknown, stream?: Stream) {\n const error = asJsonRpcError(cause);\n\n stream?.abort(error);\n this.errorListeners.forEach((listener) => listener(error));\n return error;\n }\n}\n\nfunction toJsonRpcError(cause: unknown) {\n if (cause instanceof JsonRpcError) {\n return cause;\n }\n\n return new JsonRpcError({\n code: -32603,\n message: asJsonRpcError(cause).message,\n });\n}\n\nfunction parseJsonRpcRequest(data: string) {\n const payload = JSON.parse(data) as Partial<ccc.JsonRpcPayload>;\n if (\n payload?.jsonrpc !== \"2.0\" ||\n (typeof payload.id !== \"number\" && typeof payload.id !== \"string\") ||\n typeof payload.method !== \"string\"\n ) {\n throw new Error(\"Invalid JSON-RPC request\");\n }\n\n return payload as ccc.JsonRpcPayload;\n}\n\nfunction asJsonRpcError(cause: unknown) {\n return cause instanceof Error ? cause : new Error(\"JSON-RPC request failed\");\n}\n\nfunction isAbortError(cause: unknown) {\n return (\n cause instanceof Error &&\n (cause.name === \"AbortError\" || cause.name === \"TimeoutError\")\n );\n}\n\nexport function jsonRpcService<\n Components extends JsonRpcServiceComponents = JsonRpcServiceComponents,\n>(\n config: JsonRpcServiceConfig,\n handler: JsonRpcRequestHandler<Components>,\n): (components: Components) => JsonRpcService<Components> {\n return (components: Components) =>\n new (class extends JsonRpcService<Components> {\n protected handleRequest(request: JsonRpcRequest) {\n return handler.call(this, request);\n }\n })(components, config);\n}\n","import { ccc } from \"@ckb-ccc/core\";\nimport type { Libp2p, PeerId, Stream } from \"@libp2p/interface\";\nimport { lpStream } from \"@libp2p/utils\";\n\nconst DEFAULT_MAX_MESSAGE_LENGTH = 1024 * 1024;\nconst DEFAULT_TIMEOUT = 120_000;\n\nexport type JsonRpcTransportLibp2pConfig = {\n protocol: string;\n maxMessageLength?: number;\n timeout?: number;\n signal?: AbortSignal;\n onResponse?: (response: ccc.JsonRpcResponse) => void;\n};\n\nexport class JsonRpcTransportLibp2p implements ccc.JsonRpcTransport {\n private readonly maxMessageLength: number;\n private readonly timeout: number;\n\n constructor(\n private readonly node: Libp2p,\n readonly peerId: PeerId,\n private readonly config: JsonRpcTransportLibp2pConfig,\n ) {\n const maxMessageLength =\n config.maxMessageLength ?? DEFAULT_MAX_MESSAGE_LENGTH;\n if (!Number.isSafeInteger(maxMessageLength) || maxMessageLength <= 0) {\n throw new Error(\n \"Maximum JSON-RPC message length must be a positive integer\",\n );\n }\n\n this.maxMessageLength = maxMessageLength;\n\n const timeout = config.timeout ?? DEFAULT_TIMEOUT;\n if (!Number.isSafeInteger(timeout) || timeout <= 0) {\n throw new Error(\"JSON-RPC request timeout must be a positive integer\");\n }\n\n this.timeout = timeout;\n }\n\n async request(payload: ccc.JsonRpcPayload): Promise<ccc.JsonRpcResponse> {\n const timeoutSignal = AbortSignal.timeout(this.timeout);\n const signal = this.config.signal\n ? ccc.abortSignalAny([this.config.signal, timeoutSignal])\n : timeoutSignal;\n\n let stream: Stream | undefined;\n let response: ccc.JsonRpcResponse;\n try {\n // dialProtocol handles the signal, so no separate entry check is needed.\n stream = await this.node.dialProtocol(this.peerId, this.config.protocol, {\n runOnLimitedConnection: true,\n signal,\n });\n\n const rpcStream = lpStream(stream, {\n maxDataLength: this.maxMessageLength,\n });\n\n await rpcStream.write(ccc.bytesFrom(JSON.stringify(payload), \"utf8\"), {\n signal,\n });\n await stream.close({ signal });\n\n response = JSON.parse(\n ccc.bytesTo((await rpcStream.read({ signal })).subarray(), \"utf8\"),\n ) as ccc.JsonRpcResponse;\n await stream.closeRead({ signal });\n signal.throwIfAborted();\n } catch (cause) {\n const error = asJsonRpcError(cause);\n stream?.abort(error);\n throw error;\n }\n\n this.config.onResponse?.(response);\n return response;\n }\n}\n\nfunction asJsonRpcError(cause: unknown) {\n return cause instanceof Error ? cause : new Error(\"JSON-RPC request failed\");\n}\n","import { ccc } from \"@ckb-ccc/core\";\nimport { multiaddr, type Multiaddr } from \"@multiformats/multiaddr\";\nimport * as lp from \"it-length-prefixed\";\nimport type { PairingTarget } from \"./pairingService.js\";\n\nconst MAX_DECOMPRESSED_ADDRESSES_LENGTH = 16 * 1024;\nconst MAX_ADDRESSES = 16;\nconst PAIRING_PARAMETER_NAMES = [\"addresses\", \"role\", \"secret\"] as const;\n\nexport class PairingEndpointRoleError extends Error {\n constructor(\n readonly expectedRole: string,\n readonly actualRole: string | undefined,\n ) {\n super(\n `Expected a ${expectedRole} pairing endpoint, received ${actualRole ? `role \"${actualRole}\"` : \"an endpoint without a role\"}`,\n );\n this.name = \"PairingEndpointRoleError\";\n }\n}\n\nexport async function encodePairingEndpoint(\n endpointUrl: string,\n addresses: readonly Multiaddr[],\n secret: string,\n role?: string,\n) {\n if (addresses.length === 0) {\n throw new Error(\"Pairing endpoint requires at least one address\");\n }\n if (addresses.length > MAX_ADDRESSES) {\n throw new Error(\n `Pairing endpoint supports at most ${MAX_ADDRESSES} addresses`,\n );\n }\n\n const addressBytes = encodeAddresses(addresses);\n if (addressBytes.byteLength > MAX_DECOMPRESSED_ADDRESSES_LENGTH) {\n throw new Error(\"Pairing endpoint address data is too large\");\n }\n\n const compressedAddressBytes = await transformBytes(\n addressBytes,\n new CompressionStream(\"deflate\"),\n );\n\n const encodedAddresses = ccc.bytesTo(compressedAddressBytes, \"base64url\");\n\n const url = new URL(endpointUrl);\n const { params, route } = fragmentParameters(url);\n\n PAIRING_PARAMETER_NAMES.forEach((name) => {\n params.delete(name);\n });\n if (role) {\n params.set(\"role\", role);\n }\n params.set(\"addresses\", encodedAddresses);\n params.set(\"secret\", secret);\n url.hash = `${route}?${params.toString()}`;\n\n return url.toString();\n}\n\nexport async function decodePairingEndpoint(\n endpoint: string,\n expectedRole?: string,\n): Promise<PairingTarget> {\n const url = new URL(endpoint.trim());\n const { params } = fragmentParameters(url);\n if (expectedRole) {\n const actualRole = params.get(\"role\")?.trim() || undefined;\n if (actualRole !== expectedRole) {\n throw new PairingEndpointRoleError(expectedRole, actualRole);\n }\n }\n const compressedAddresses = params.get(\"addresses\")?.trim();\n const secret = params.get(\"secret\")?.trim();\n\n if (!compressedAddresses || !secret) {\n throw new Error(\"Pairing endpoint is incomplete\");\n }\n\n const addresses = await decodeCompressedAddresses(compressedAddresses);\n\n return { addresses, secret };\n}\n\nfunction fragmentParameters(url: URL) {\n const fragment = url.hash.slice(1);\n const separator = fragment.indexOf(\"?\");\n if (separator === -1) {\n return { params: new URLSearchParams(), route: fragment };\n }\n\n return {\n params: new URLSearchParams(fragment.slice(separator + 1)),\n route: fragment.slice(0, separator),\n };\n}\n\nfunction encodeAddresses(addresses: readonly Multiaddr[]): Uint8Array {\n return ccc.bytesConcat(\n ...lp.encode(addresses.map((address) => address.bytes)),\n );\n}\n\nasync function decodeCompressedAddresses(value: string): Promise<Multiaddr[]> {\n try {\n const compressedBytes = ccc.bytesFrom(value, \"base64url\");\n const bytes = await transformBytes(\n compressedBytes,\n new DecompressionStream(\"deflate\"),\n MAX_DECOMPRESSED_ADDRESSES_LENGTH,\n );\n const addresses: Multiaddr[] = [];\n for (const address of lp.decode([bytes], {\n maxDataLength: MAX_DECOMPRESSED_ADDRESSES_LENGTH,\n })) {\n if (addresses.length >= MAX_ADDRESSES) {\n throw new Error(\"Too many addresses\");\n }\n addresses.push(multiaddr(address.subarray()));\n }\n\n if (addresses.length === 0) {\n throw new Error(\"Missing addresses\");\n }\n return addresses;\n } catch (cause) {\n throw new Error(\"Pairing endpoint contains invalid compressed addresses\", {\n cause,\n });\n }\n}\n\nasync function transformBytes(\n bytes: Uint8Array,\n transform: CompressionStream | DecompressionStream,\n maxOutputLength?: number,\n) {\n const readable =\n maxOutputLength === undefined\n ? transform.readable\n : transform.readable.pipeThrough(limitBytes(maxOutputLength));\n const output = new Response(readable).arrayBuffer().then(\n (buffer) => ({ buffer, ok: true }) as const,\n (error: unknown) => ({ error, ok: false }) as const,\n );\n const writer = transform.writable.getWriter();\n try {\n await writer.write(Uint8Array.from(bytes));\n await writer.close();\n } catch (cause) {\n const result = await output;\n if (!result.ok) {\n throw result.error;\n }\n throw cause;\n }\n\n const result = await output;\n if (!result.ok) {\n throw result.error;\n }\n return new Uint8Array(result.buffer);\n}\n\nfunction limitBytes(maxLength: number) {\n let length = 0;\n\n return new TransformStream<Uint8Array, Uint8Array>({\n transform(chunk, controller) {\n if (chunk.byteLength > maxLength - length) {\n throw new Error(\"Decompressed address data is too large\");\n }\n length += chunk.byteLength;\n controller.enqueue(chunk);\n },\n });\n}\n","import { ccc } from \"@ckb-ccc/core\";\nimport type {\n AbortOptions,\n Connection,\n PeerId,\n Stream,\n} from \"@libp2p/interface\";\nimport type { ConnectionManager, Registrar } from \"@libp2p/interface-internal\";\nimport { lpStream } from \"@libp2p/utils\";\nimport type { Multiaddr } from \"@multiformats/multiaddr\";\n\nconst MAX_PAIRING_MESSAGE_LENGTH = 1024;\nconst UNPAIR_TIMEOUT_MS = 10_000;\n\nexport type PairingServiceComponents = {\n connectionManager: ConnectionManager;\n registrar: Registrar;\n};\n\nexport type PairingServiceConfig = {\n /** Optional self-reported label shared with the paired peer. */\n name?: string;\n protocol: string;\n pairedPeerTimeoutMs: number;\n};\n\nexport type PairingTarget = {\n addresses: Multiaddr[];\n secret: string;\n};\n\nexport type PairingGuard<\n Components extends PairingServiceComponents = PairingServiceComponents,\n> = (this: PairingService<Components>, peerId: PeerId) => boolean;\n\ntype PairingRequest =\n { type: \"pair\"; secret: string; name?: string } | { type: \"unpair\" };\n\ntype PairingResponse =\n { ok: true; name?: string } | { ok: false; error: string };\n\nexport abstract class PairingService<\n Components extends PairingServiceComponents = PairingServiceComponents,\n> {\n private currentSecret = createSecret();\n private readonly errorListeners = new Set<(error: Error) => void>();\n private readonly pairedListeners = new Set<\n (peerId: PeerId, name?: string) => void\n >();\n private readonly unpairedListeners = new Set<(peerId: PeerId) => void>();\n private readonly secretChangedListeners = new Set<(secret: string) => void>();\n private readonly pairingTimeouts = new Map<\n string,\n ReturnType<typeof setTimeout>\n >();\n\n constructor(\n readonly components: Components,\n readonly config: PairingServiceConfig,\n ) {\n if (config.pairedPeerTimeoutMs <= 0) {\n throw new Error(\"Paired peer timeout must be greater than zero\");\n }\n }\n\n protected abstract canPair(peerId: PeerId): boolean;\n\n /** The credential to include in the currently issued pairing endpoint. */\n get secret() {\n return this.currentSecret;\n }\n\n onError(listener: (error: Error) => void) {\n this.errorListeners.add(listener);\n return () => this.errorListeners.delete(listener);\n }\n\n onPaired(listener: (peerId: PeerId, name?: string) => void) {\n this.pairedListeners.add(listener);\n return () => this.pairedListeners.delete(listener);\n }\n\n onUnpaired(listener: (peerId: PeerId) => void) {\n this.unpairedListeners.add(listener);\n return () => this.unpairedListeners.delete(listener);\n }\n\n onSecretChanged(listener: (secret: string) => void) {\n this.secretChangedListeners.add(listener);\n return () => this.secretChangedListeners.delete(listener);\n }\n\n async start() {\n await this.components.registrar.handle(\n this.config.protocol,\n this.handleProtocol.bind(this),\n { runOnLimitedConnection: true },\n );\n }\n\n async stop() {\n await this.components.registrar.unhandle(this.config.protocol);\n\n this.pairingTimeouts.forEach((timeout) => clearTimeout(timeout));\n this.pairingTimeouts.clear();\n }\n\n isPaired(peerId: PeerId) {\n return this.pairingTimeouts.has(peerId.toString());\n }\n\n refresh(peerId: PeerId) {\n const key = peerId.toString();\n const timeout = this.pairingTimeouts.get(key);\n if (timeout === undefined) {\n return;\n }\n\n clearTimeout(timeout);\n this.pairingTimeouts.set(key, this.createPeerTimeout(peerId));\n }\n\n async pair(\n target: PairingTarget,\n options: AbortOptions = {},\n ): Promise<PeerId> {\n let stream: Stream | undefined;\n let addedPeer: PeerId | undefined;\n\n try {\n const opened = await this.openStream(target.addresses, options);\n stream = opened.stream;\n\n if (!opened.connection) {\n throw new Error(\"Could not determine remote peer\");\n }\n\n if (this.addPairedPeer(opened.connection.remotePeer)) {\n addedPeer = opened.connection.remotePeer;\n }\n\n const response = await exchange(\n stream,\n {\n type: \"pair\",\n secret: target.secret,\n name: this.config.name,\n },\n options,\n );\n options.signal?.throwIfAborted();\n stream = undefined;\n\n if (!response.ok) {\n throw new Error(response.error);\n }\n\n if (addedPeer) {\n this.rotateSecret();\n this.notifyPairedPeer(addedPeer, response.name);\n }\n return opened.connection.remotePeer;\n } catch (cause) {\n if (addedPeer) {\n this.removePairedPeer(addedPeer, false);\n }\n throw this.handleError(cause, stream);\n }\n }\n\n async unpair(peerId: PeerId) {\n if (!this.removePairedPeer(peerId)) {\n return;\n }\n\n const options = { signal: AbortSignal.timeout(UNPAIR_TIMEOUT_MS) };\n\n let stream: Stream | undefined;\n try {\n const opened = await this.openStream(peerId, options);\n stream = opened.stream;\n const response = await exchange(\n opened.stream,\n { type: \"unpair\" },\n options,\n );\n stream = undefined;\n if (!response.ok) {\n throw new Error(response.error);\n }\n } catch (cause) {\n this.handleError(cause, stream);\n }\n }\n\n private async handleProtocol(stream: Stream, connection: Connection) {\n let addedPeer: PeerId | undefined;\n\n try {\n const pairingStream = lpStream(stream, {\n maxDataLength: MAX_PAIRING_MESSAGE_LENGTH,\n });\n const request = parsePairingRequest(\n (await pairingStream.read()).subarray(),\n );\n\n if (request.type === \"unpair\") {\n this.removePairedPeer(connection.remotePeer);\n await writeResponse(pairingStream, stream, { ok: true });\n return;\n }\n\n if (\n request.secret !== this.secret ||\n !this.canPair(connection.remotePeer)\n ) {\n await writeResponse(pairingStream, stream, {\n ok: false,\n error: \"Pairing rejected\",\n });\n return;\n }\n\n // Consume the credential before the next asynchronous boundary so two\n // concurrent requests cannot both pair with the same endpoint.\n this.rotateSecret();\n\n if (this.addPairedPeer(connection.remotePeer)) {\n addedPeer = connection.remotePeer;\n }\n await writeResponse(pairingStream, stream, {\n ok: true,\n name: this.config.name,\n });\n if (addedPeer) {\n this.notifyPairedPeer(addedPeer, request.name);\n }\n } catch (cause) {\n if (addedPeer) {\n this.removePairedPeer(addedPeer, false);\n }\n this.handleError(cause, stream);\n }\n }\n\n private addPairedPeer(peerId: PeerId) {\n const key = peerId.toString();\n const timeout = this.pairingTimeouts.get(key);\n if (timeout !== undefined) {\n clearTimeout(timeout);\n }\n\n this.pairingTimeouts.set(key, this.createPeerTimeout(peerId));\n return timeout === undefined;\n }\n\n private notifyPairedPeer(peerId: PeerId, name?: string) {\n this.pairedListeners.forEach((listener) => listener(peerId, name));\n }\n\n private rotateSecret() {\n this.currentSecret = createSecret();\n this.secretChangedListeners.forEach((listener) =>\n listener(this.currentSecret),\n );\n }\n\n private removePairedPeer(peerId: PeerId, notify = true) {\n const key = peerId.toString();\n const timeout = this.pairingTimeouts.get(key);\n if (timeout === undefined) {\n return false;\n }\n\n clearTimeout(timeout);\n this.pairingTimeouts.delete(key);\n\n if (notify) {\n this.unpairedListeners.forEach((listener) => listener(peerId));\n }\n return true;\n }\n\n private createPeerTimeout(peerId: PeerId) {\n return setTimeout(() => {\n this.removePairedPeer(peerId);\n }, this.config.pairedPeerTimeoutMs);\n }\n\n private async openStream(\n target: PeerId | Multiaddr | Multiaddr[],\n options: AbortOptions,\n ) {\n let connection: Connection | undefined;\n const stream = await this.components.connectionManager.openStream(\n target,\n this.config.protocol,\n {\n runOnLimitedConnection: true,\n signal: options.signal,\n onProgress(event) {\n if (event.type === \"connection:opened-stream\") {\n connection = event.detail.connection;\n }\n },\n },\n );\n\n return { stream, connection };\n }\n\n private handleError(cause: unknown, stream?: Stream) {\n const error = asError(cause, \"Pairing operation failed\");\n\n stream?.abort(error);\n this.errorListeners.forEach((listener) => listener(error));\n return error;\n }\n}\n\nexport function pairingService<\n Components extends PairingServiceComponents = PairingServiceComponents,\n>(\n config: PairingServiceConfig,\n canPair: PairingGuard<Components>,\n): (components: Components) => PairingService<Components> {\n return (components: Components) =>\n new (class extends PairingService<Components> {\n protected canPair(peerId: PeerId) {\n return canPair.call(this, peerId);\n }\n })(components, config);\n}\n\nasync function exchange(\n stream: Stream,\n request: PairingRequest,\n options: AbortOptions,\n) {\n const pairingStream = lpStream(stream, {\n maxDataLength: MAX_PAIRING_MESSAGE_LENGTH,\n });\n\n await pairingStream.write(ccc.bytesFrom(JSON.stringify(request), \"utf8\"), {\n signal: options.signal,\n });\n await stream.close(options);\n\n const response = parsePairingResponse(\n (await pairingStream.read(options)).subarray(),\n );\n await stream.closeRead(options);\n return response;\n}\n\nasync function writeResponse(\n pairingStream: ReturnType<typeof lpStream>,\n stream: Stream,\n response: PairingResponse,\n) {\n await pairingStream.write(ccc.bytesFrom(JSON.stringify(response), \"utf8\"));\n await stream.close();\n}\n\nfunction parsePairingRequest(data: Uint8Array): PairingRequest {\n const request = JSON.parse(\n ccc.bytesTo(data, \"utf8\"),\n ) as Partial<PairingRequest>;\n if (\n request.type === \"pair\" &&\n typeof request.secret === \"string\" &&\n request.secret\n ) {\n return {\n type: \"pair\",\n secret: request.secret,\n name: typeof request.name === \"string\" ? request.name : undefined,\n };\n }\n\n if (request.type === \"unpair\") {\n return { type: \"unpair\" };\n }\n\n throw new Error(\"Invalid pairing request\");\n}\n\nfunction parsePairingResponse(data: Uint8Array): PairingResponse {\n const response = JSON.parse(\n ccc.bytesTo(data, \"utf8\"),\n ) as Partial<PairingResponse>;\n if (response.ok === true) {\n return {\n ok: true,\n name: typeof response.name === \"string\" ? response.name : undefined,\n };\n }\n if (response.ok === false && typeof response.error === \"string\") {\n return { ok: false, error: response.error };\n }\n\n throw new Error(\"Invalid pairing response\");\n}\n\nfunction createSecret() {\n return ccc.bytesTo(crypto.getRandomValues(new Uint8Array(16)), \"base64url\");\n}\n\nfunction asError(cause: unknown, fallback: string) {\n return cause instanceof Error ? cause : new Error(fallback);\n}\n","export * from \"./jsonRpcService.js\";\nexport * from \"./jsonRpcTransport.js\";\nexport * from \"./pairingEndpoint.js\";\nexport * from \"./pairingService.js\";\n"],"mappings":"4OAyBA,IAAsB,EAAtB,KAEE,CAIA,YACE,EACA,EACA,CAFS,KAAA,WAAA,EACA,KAAA,OAAA,EALuB,KAAA,eAAA,IAAI,IAOpC,IAAM,EACJ,EAAO,kBAAoBA,QAC7B,GAAI,CAAC,OAAO,cAAc,CAAgB,GAAK,GAAoB,EACjE,MAAU,MACR,4DACF,EAGF,KAAK,iBAAmB,CAC1B,CAIA,QAAQ,EAAkC,CAExC,OADA,KAAK,eAAe,IAAI,CAAQ,MACnB,KAAK,eAAe,OAAO,CAAQ,CAClD,CAEA,MAAM,OAAQ,CACZ,MAAM,KAAK,WAAW,UAAU,OAC9B,KAAK,OAAO,SACZ,KAAK,eAAe,KAAK,IAAI,EAC7B,CAAE,uBAAwB,EAAK,CACjC,CACF,CAEA,MAAM,MAAO,CACX,MAAM,KAAK,WAAW,UAAU,SAAS,KAAK,OAAO,QAAQ,CAC/D,CAEA,MAAc,eAAe,EAAgB,EAAwB,CACnE,IAAI,EAAiB,GAErB,GAAI,CACF,IAAM,EAAY,EAAS,EAAQ,CACjC,cAAe,KAAK,gBACtB,CAAC,EACK,EAAU,EACd,EAAI,SAAS,MAAM,EAAU,KAAK,EAAA,CAAG,SAAS,EAAG,MAAM,CACzD,EAEI,EACJ,GAAI,CACF,EAAW,CACT,QAAS,MACT,GAAI,EAAQ,GACZ,OAAQ,MAAM,KAAK,cAAc,CAC/B,OAAQ,EAAW,WACnB,SACF,CAAC,CACH,CACF,OAAS,EAAO,CACd,EAAiB,EAAa,CAAK,EACnC,IAAM,EAAQ,EAAe,CAAK,EAClC,EAAW,CACT,QAAS,MACT,GAAI,EAAQ,GACZ,MAAO,CACL,KAAM,EAAM,KACZ,QAAS,EAAM,QACf,KAAM,EAAM,IACd,CACF,CACF,CAEA,MAAM,EAAU,MAAM,EAAI,UAAU,KAAK,UAAU,CAAQ,EAAG,MAAM,CAAC,EACrE,MAAM,EAAO,MAAM,CACrB,OAAS,EAAO,CACd,GAAI,EACF,OAEF,KAAK,YAAY,EAAO,CAAM,CAChC,CACF,CAEA,YAAoB,EAAgB,EAAiB,CACnD,IAAM,EAAQC,EAAe,CAAK,EAIlC,OAFA,GAAQ,MAAM,CAAK,EACnB,KAAK,eAAe,QAAS,GAAa,EAAS,CAAK,CAAC,EAClD,CACT,CACF,EAEA,SAAS,EAAe,EAAgB,CAKtC,OAJI,aAAiB,EACZ,EAGF,IAAI,EAAa,CACtB,KAAM,OACN,QAASA,EAAe,CAAK,CAAC,CAAC,OACjC,CAAC,CACH,CAEA,SAAS,EAAoB,EAAc,CACzC,IAAM,EAAU,KAAK,MAAM,CAAI,EAC/B,GACE,GAAS,UAAY,OACpB,OAAO,EAAQ,IAAO,UAAY,OAAO,EAAQ,IAAO,UACzD,OAAO,EAAQ,QAAW,SAE1B,MAAU,MAAM,0BAA0B,EAG5C,OAAO,CACT,CAEA,SAASA,EAAe,EAAgB,CACtC,OAAO,aAAiB,MAAQ,EAAY,MAAM,yBAAyB,CAC7E,CAEA,SAAS,EAAa,EAAgB,CACpC,OACE,aAAiB,QAChB,EAAM,OAAS,cAAgB,EAAM,OAAS,eAEnD,CAEA,SAAgB,EAGd,EACA,EACwD,CACxD,MAAQ,IACN,IAAK,cAAc,CAA2B,CAC5C,cAAwB,EAAyB,CAC/C,OAAO,EAAQ,KAAK,KAAM,CAAO,CACnC,CACF,EAAG,EAAY,CAAM,CACzB,CCvJA,IAAa,EAAb,KAAoE,CAIlE,YACE,EACA,EACA,EACA,CAHiB,KAAA,KAAA,EACR,KAAA,OAAA,EACQ,KAAA,OAAA,EAEjB,IAAM,EACJ,EAAO,kBAAoB,QAC7B,GAAI,CAAC,OAAO,cAAc,CAAgB,GAAK,GAAoB,EACjE,MAAU,MACR,4DACF,EAGF,KAAK,iBAAmB,EAExB,IAAM,EAAU,EAAO,SAAW,KAClC,GAAI,CAAC,OAAO,cAAc,CAAO,GAAK,GAAW,EAC/C,MAAU,MAAM,qDAAqD,EAGvE,KAAK,QAAU,CACjB,CAEA,MAAM,QAAQ,EAA2D,CACvE,IAAM,EAAgB,YAAY,QAAQ,KAAK,OAAO,EAChD,EAAS,KAAK,OAAO,OACvB,EAAI,eAAe,CAAC,KAAK,OAAO,OAAQ,CAAa,CAAC,EACtD,EAEA,EACA,EACJ,GAAI,CAEF,EAAS,MAAM,KAAK,KAAK,aAAa,KAAK,OAAQ,KAAK,OAAO,SAAU,CACvE,uBAAwB,GACxB,QACF,CAAC,EAED,IAAM,EAAY,EAAS,EAAQ,CACjC,cAAe,KAAK,gBACtB,CAAC,EAED,MAAM,EAAU,MAAM,EAAI,UAAU,KAAK,UAAU,CAAO,EAAG,MAAM,EAAG,CACpE,QACF,CAAC,EACD,MAAM,EAAO,MAAM,CAAE,QAAO,CAAC,EAE7B,EAAW,KAAK,MACd,EAAI,SAAS,MAAM,EAAU,KAAK,CAAE,QAAO,CAAC,EAAA,CAAG,SAAS,EAAG,MAAM,CACnE,EACA,MAAM,EAAO,UAAU,CAAE,QAAO,CAAC,EACjC,EAAO,eAAe,CACxB,OAAS,EAAO,CACd,IAAM,EAAQ,EAAe,CAAK,EAElC,MADA,GAAQ,MAAM,CAAK,EACb,CACR,CAGA,OADA,KAAK,OAAO,aAAa,CAAQ,EAC1B,CACT,CACF,EAEA,SAAS,EAAe,EAAgB,CACtC,OAAO,aAAiB,MAAQ,EAAY,MAAM,yBAAyB,CAC7E,CC/EA,MAAM,EAAoC,MAEpC,EAA0B,CAAC,YAAa,OAAQ,QAAQ,EAE9D,IAAa,EAAb,cAA8C,KAAM,CAClD,YACE,EACA,EACA,CACA,MACE,cAAc,EAAa,8BAA8B,EAAa,SAAS,EAAW,GAAK,8BACjG,EALS,KAAA,aAAA,EACA,KAAA,WAAA,EAKT,KAAK,KAAO,0BACd,CACF,EAEA,eAAsB,EACpB,EACA,EACA,EACA,EACA,CACA,GAAI,EAAU,SAAW,EACvB,MAAU,MAAM,gDAAgD,EAElE,GAAI,EAAU,OAAS,GACrB,MAAU,MACR,gDACF,EAGF,IAAM,EAAe,EAAgB,CAAS,EAC9C,GAAI,EAAa,WAAa,EAC5B,MAAU,MAAM,4CAA4C,EAG9D,IAAM,EAAyB,MAAM,EACnC,EACA,IAAI,kBAAkB,SAAS,CACjC,EAEM,EAAmB,EAAI,QAAQ,EAAwB,WAAW,EAElE,EAAM,IAAI,IAAI,CAAW,EACzB,CAAE,SAAQ,SAAU,EAAmB,CAAG,EAYhD,OAVA,EAAwB,QAAS,GAAS,CACxC,EAAO,OAAO,CAAI,CACpB,CAAC,EACG,GACF,EAAO,IAAI,OAAQ,CAAI,EAEzB,EAAO,IAAI,YAAa,CAAgB,EACxC,EAAO,IAAI,SAAU,CAAM,EAC3B,EAAI,KAAO,GAAG,EAAM,GAAG,EAAO,SAAS,IAEhC,EAAI,SAAS,CACtB,CAEA,eAAsB,EACpB,EACA,EACwB,CAExB,GAAM,CAAE,UAAW,EAAmB,IADtB,IAAI,EAAS,KAAK,CACM,CAAC,EACzC,GAAI,EAAc,CAChB,IAAM,EAAa,EAAO,IAAI,MAAM,CAAC,EAAE,KAAK,GAAK,IAAA,GACjD,GAAI,IAAe,EACjB,MAAM,IAAI,EAAyB,EAAc,CAAU,CAE/D,CACA,IAAM,EAAsB,EAAO,IAAI,WAAW,CAAC,EAAE,KAAK,EACpD,EAAS,EAAO,IAAI,QAAQ,CAAC,EAAE,KAAK,EAE1C,GAAI,CAAC,GAAuB,CAAC,EAC3B,MAAU,MAAM,gCAAgC,EAKlD,MAAO,CAAE,UAAA,MAFe,EAA0B,CAAmB,EAEjD,QAAO,CAC7B,CAEA,SAAS,EAAmB,EAAU,CACpC,IAAM,EAAW,EAAI,KAAK,MAAM,CAAC,EAC3B,EAAY,EAAS,QAAQ,GAAG,EAKtC,OAJI,IAAc,GACT,CAAE,OAAQ,IAAI,gBAAmB,MAAO,CAAS,EAGnD,CACL,OAAQ,IAAI,gBAAgB,EAAS,MAAM,EAAY,CAAC,CAAC,EACzD,MAAO,EAAS,MAAM,EAAG,CAAS,CACpC,CACF,CAEA,SAAS,EAAgB,EAA6C,CACpE,OAAO,EAAI,YACT,GAAG,EAAG,OAAO,EAAU,IAAK,GAAY,EAAQ,KAAK,CAAC,CACxD,CACF,CAEA,eAAe,EAA0B,EAAqC,CAC5E,GAAI,CAEF,IAAM,EAAQ,MAAM,EADI,EAAI,UAAU,EAAO,WAE7B,EACd,IAAI,oBAAoB,SAAS,EACjC,CACF,EACM,EAAyB,CAAC,EAChC,IAAK,IAAM,KAAW,EAAG,OAAO,CAAC,CAAK,EAAG,CACvC,cAAe,CACjB,CAAC,EAAG,CACF,GAAI,EAAU,QAAU,GACtB,MAAU,MAAM,oBAAoB,EAEtC,EAAU,KAAK,EAAU,EAAQ,SAAS,CAAC,CAAC,CAC9C,CAEA,GAAI,EAAU,SAAW,EACvB,MAAU,MAAM,mBAAmB,EAErC,OAAO,CACT,OAAS,EAAO,CACd,MAAU,MAAM,yDAA0D,CACxE,OACF,CAAC,CACH,CACF,CAEA,eAAe,EACb,EACA,EACA,EACA,CACA,IAAM,EACJ,IAAoB,IAAA,GAChB,EAAU,SACV,EAAU,SAAS,YAAY,EAAW,CAAe,CAAC,EAC1D,EAAS,IAAI,SAAS,CAAQ,CAAC,CAAC,YAAY,CAAC,CAAC,KACjD,IAAY,CAAE,SAAQ,GAAI,EAAK,GAC/B,IAAoB,CAAE,QAAO,GAAI,EAAM,EAC1C,EACM,EAAS,EAAU,SAAS,UAAU,EAC5C,GAAI,CACF,MAAM,EAAO,MAAM,WAAW,KAAK,CAAK,CAAC,EACzC,MAAM,EAAO,MAAM,CACrB,OAAS,EAAO,CACd,IAAM,EAAS,MAAM,EAIrB,MAHK,EAAO,GAGN,EAFE,EAAO,KAGjB,CAEA,IAAM,EAAS,MAAM,EACrB,GAAI,CAAC,EAAO,GACV,MAAM,EAAO,MAEf,OAAO,IAAI,WAAW,EAAO,MAAM,CACrC,CAEA,SAAS,EAAW,EAAmB,CACrC,IAAI,EAAS,EAEb,OAAO,IAAI,gBAAwC,CACjD,UAAU,EAAO,EAAY,CAC3B,GAAI,EAAM,WAAa,EAAY,EACjC,MAAU,MAAM,wCAAwC,EAE1D,GAAU,EAAM,WAChB,EAAW,QAAQ,CAAK,CAC1B,CACF,CAAC,CACH,CCzKA,MAAM,EAA6B,KA8BnC,IAAsB,EAAtB,KAEE,CAaA,YACE,EACA,EACA,CACA,GAHS,KAAA,WAAA,EACA,KAAA,OAAA,EAda,KAAA,cAAA,EAAa,EACH,KAAA,eAAA,IAAI,IACH,KAAA,gBAAA,IAAI,IAGF,KAAA,kBAAA,IAAI,IACC,KAAA,uBAAA,IAAI,IACX,KAAA,gBAAA,IAAI,IASjC,EAAO,qBAAuB,EAChC,MAAU,MAAM,+CAA+C,CAEnE,CAKA,IAAI,QAAS,CACX,OAAO,KAAK,aACd,CAEA,QAAQ,EAAkC,CAExC,OADA,KAAK,eAAe,IAAI,CAAQ,MACnB,KAAK,eAAe,OAAO,CAAQ,CAClD,CAEA,SAAS,EAAmD,CAE1D,OADA,KAAK,gBAAgB,IAAI,CAAQ,MACpB,KAAK,gBAAgB,OAAO,CAAQ,CACnD,CAEA,WAAW,EAAoC,CAE7C,OADA,KAAK,kBAAkB,IAAI,CAAQ,MACtB,KAAK,kBAAkB,OAAO,CAAQ,CACrD,CAEA,gBAAgB,EAAoC,CAElD,OADA,KAAK,uBAAuB,IAAI,CAAQ,MAC3B,KAAK,uBAAuB,OAAO,CAAQ,CAC1D,CAEA,MAAM,OAAQ,CACZ,MAAM,KAAK,WAAW,UAAU,OAC9B,KAAK,OAAO,SACZ,KAAK,eAAe,KAAK,IAAI,EAC7B,CAAE,uBAAwB,EAAK,CACjC,CACF,CAEA,MAAM,MAAO,CACX,MAAM,KAAK,WAAW,UAAU,SAAS,KAAK,OAAO,QAAQ,EAE7D,KAAK,gBAAgB,QAAS,GAAY,aAAa,CAAO,CAAC,EAC/D,KAAK,gBAAgB,MAAM,CAC7B,CAEA,SAAS,EAAgB,CACvB,OAAO,KAAK,gBAAgB,IAAI,EAAO,SAAS,CAAC,CACnD,CAEA,QAAQ,EAAgB,CACtB,IAAM,EAAM,EAAO,SAAS,EACtB,EAAU,KAAK,gBAAgB,IAAI,CAAG,EACxC,IAAY,IAAA,KAIhB,aAAa,CAAO,EACpB,KAAK,gBAAgB,IAAI,EAAK,KAAK,kBAAkB,CAAM,CAAC,EAC9D,CAEA,MAAM,KACJ,EACA,EAAwB,CAAC,EACR,CACjB,IAAI,EACA,EAEJ,GAAI,CACF,IAAM,EAAS,MAAM,KAAK,WAAW,EAAO,UAAW,CAAO,EAG9D,GAFA,EAAS,EAAO,OAEZ,CAAC,EAAO,WACV,MAAU,MAAM,iCAAiC,EAG/C,KAAK,cAAc,EAAO,WAAW,UAAU,IACjD,EAAY,EAAO,WAAW,YAGhC,IAAM,EAAW,MAAM,EACrB,EACA,CACE,KAAM,OACN,OAAQ,EAAO,OACf,KAAM,KAAK,OAAO,IACpB,EACA,CACF,EAIA,GAHA,EAAQ,QAAQ,eAAe,EAC/B,EAAS,IAAA,GAEL,CAAC,EAAS,GACZ,MAAU,MAAM,EAAS,KAAK,EAOhC,OAJI,IACF,KAAK,aAAa,EAClB,KAAK,iBAAiB,EAAW,EAAS,IAAI,GAEzC,EAAO,WAAW,UAC3B,OAAS,EAAO,CAId,MAHI,GACF,KAAK,iBAAiB,EAAW,EAAK,EAElC,KAAK,YAAY,EAAO,CAAM,CACtC,CACF,CAEA,MAAM,OAAO,EAAgB,CAC3B,GAAI,CAAC,KAAK,iBAAiB,CAAM,EAC/B,OAGF,IAAM,EAAU,CAAE,OAAQ,YAAY,QAAQ,GAAiB,CAAE,EAE7D,EACJ,GAAI,CACF,IAAM,EAAS,MAAM,KAAK,WAAW,EAAQ,CAAO,EACpD,EAAS,EAAO,OAChB,IAAM,EAAW,MAAM,EACrB,EAAO,OACP,CAAE,KAAM,QAAS,EACjB,CACF,EAEA,GADA,EAAS,IAAA,GACL,CAAC,EAAS,GACZ,MAAU,MAAM,EAAS,KAAK,CAElC,OAAS,EAAO,CACd,KAAK,YAAY,EAAO,CAAM,CAChC,CACF,CAEA,MAAc,eAAe,EAAgB,EAAwB,CACnE,IAAI,EAEJ,GAAI,CACF,IAAM,EAAgB,EAAS,EAAQ,CACrC,cAAe,CACjB,CAAC,EACK,EAAU,GACb,MAAM,EAAc,KAAK,EAAA,CAAG,SAAS,CACxC,EAEA,GAAI,EAAQ,OAAS,SAAU,CAC7B,KAAK,iBAAiB,EAAW,UAAU,EAC3C,MAAM,EAAc,EAAe,EAAQ,CAAE,GAAI,EAAK,CAAC,EACvD,MACF,CAEA,GACE,EAAQ,SAAW,KAAK,QACxB,CAAC,KAAK,QAAQ,EAAW,UAAU,EACnC,CACA,MAAM,EAAc,EAAe,EAAQ,CACzC,GAAI,GACJ,MAAO,kBACT,CAAC,EACD,MACF,CAIA,KAAK,aAAa,EAEd,KAAK,cAAc,EAAW,UAAU,IAC1C,EAAY,EAAW,YAEzB,MAAM,EAAc,EAAe,EAAQ,CACzC,GAAI,GACJ,KAAM,KAAK,OAAO,IACpB,CAAC,EACG,GACF,KAAK,iBAAiB,EAAW,EAAQ,IAAI,CAEjD,OAAS,EAAO,CACV,GACF,KAAK,iBAAiB,EAAW,EAAK,EAExC,KAAK,YAAY,EAAO,CAAM,CAChC,CACF,CAEA,cAAsB,EAAgB,CACpC,IAAM,EAAM,EAAO,SAAS,EACtB,EAAU,KAAK,gBAAgB,IAAI,CAAG,EAM5C,OALI,IAAY,IAAA,IACd,aAAa,CAAO,EAGtB,KAAK,gBAAgB,IAAI,EAAK,KAAK,kBAAkB,CAAM,CAAC,EACrD,IAAY,IAAA,EACrB,CAEA,iBAAyB,EAAgB,EAAe,CACtD,KAAK,gBAAgB,QAAS,GAAa,EAAS,EAAQ,CAAI,CAAC,CACnE,CAEA,cAAuB,CACrB,KAAK,cAAgB,EAAa,EAClC,KAAK,uBAAuB,QAAS,GACnC,EAAS,KAAK,aAAa,CAC7B,CACF,CAEA,iBAAyB,EAAgB,EAAS,GAAM,CACtD,IAAM,EAAM,EAAO,SAAS,EACtB,EAAU,KAAK,gBAAgB,IAAI,CAAG,EAW5C,OAVI,IAAY,IAAA,KAIhB,aAAa,CAAO,EACpB,KAAK,gBAAgB,OAAO,CAAG,EAE3B,GACF,KAAK,kBAAkB,QAAS,GAAa,EAAS,CAAM,CAAC,EAExD,GACT,CAEA,kBAA0B,EAAgB,CACxC,OAAO,eAAiB,CACtB,KAAK,iBAAiB,CAAM,CAC9B,EAAG,KAAK,OAAO,mBAAmB,CACpC,CAEA,MAAc,WACZ,EACA,EACA,CACA,IAAI,EAeJ,MAAO,CAAE,OAAA,MAdY,KAAK,WAAW,kBAAkB,WACrD,EACA,KAAK,OAAO,SACZ,CACE,uBAAwB,GACxB,OAAQ,EAAQ,OAChB,WAAW,EAAO,CACZ,EAAM,OAAS,6BACjB,EAAa,EAAM,OAAO,WAE9B,CACF,CACF,EAEiB,YAAW,CAC9B,CAEA,YAAoB,EAAgB,EAAiB,CACnD,IAAM,EAAQ,EAAQ,EAAO,0BAA0B,EAIvD,OAFA,GAAQ,MAAM,CAAK,EACnB,KAAK,eAAe,QAAS,GAAa,EAAS,CAAK,CAAC,EAClD,CACT,CACF,EAEA,SAAgB,EAGd,EACA,EACwD,CACxD,MAAQ,IACN,IAAK,cAAc,CAA2B,CAC5C,QAAkB,EAAgB,CAChC,OAAO,EAAQ,KAAK,KAAM,CAAM,CAClC,CACF,EAAG,EAAY,CAAM,CACzB,CAEA,eAAe,EACb,EACA,EACA,EACA,CACA,IAAM,EAAgB,EAAS,EAAQ,CACrC,cAAe,CACjB,CAAC,EAED,MAAM,EAAc,MAAM,EAAI,UAAU,KAAK,UAAU,CAAO,EAAG,MAAM,EAAG,CACxE,OAAQ,EAAQ,MAClB,CAAC,EACD,MAAM,EAAO,MAAM,CAAO,EAE1B,IAAM,EAAW,GACd,MAAM,EAAc,KAAK,CAAO,EAAA,CAAG,SAAS,CAC/C,EAEA,OADA,MAAM,EAAO,UAAU,CAAO,EACvB,CACT,CAEA,eAAe,EACb,EACA,EACA,EACA,CACA,MAAM,EAAc,MAAM,EAAI,UAAU,KAAK,UAAU,CAAQ,EAAG,MAAM,CAAC,EACzE,MAAM,EAAO,MAAM,CACrB,CAEA,SAAS,EAAoB,EAAkC,CAC7D,IAAM,EAAU,KAAK,MACnB,EAAI,QAAQ,EAAM,MAAM,CAC1B,EACA,GACE,EAAQ,OAAS,QACjB,OAAO,EAAQ,QAAW,UAC1B,EAAQ,OAER,MAAO,CACL,KAAM,OACN,OAAQ,EAAQ,OAChB,KAAM,OAAO,EAAQ,MAAS,SAAW,EAAQ,KAAO,IAAA,EAC1D,EAGF,GAAI,EAAQ,OAAS,SACnB,MAAO,CAAE,KAAM,QAAS,EAG1B,MAAU,MAAM,yBAAyB,CAC3C,CAEA,SAAS,EAAqB,EAAmC,CAC/D,IAAM,EAAW,KAAK,MACpB,EAAI,QAAQ,EAAM,MAAM,CAC1B,EACA,GAAI,EAAS,KAAO,GAClB,MAAO,CACL,GAAI,GACJ,KAAM,OAAO,EAAS,MAAS,SAAW,EAAS,KAAO,IAAA,EAC5D,EAEF,GAAI,EAAS,KAAO,IAAS,OAAO,EAAS,OAAU,SACrD,MAAO,CAAE,GAAI,GAAO,MAAO,EAAS,KAAM,EAG5C,MAAU,MAAM,0BAA0B,CAC5C,CAEA,SAAS,GAAe,CACtB,OAAO,EAAI,QAAQ,OAAO,gBAAgB,IAAI,WAAW,EAAE,CAAC,EAAG,WAAW,CAC5E,CAEA,SAAS,EAAQ,EAAgB,EAAkB,CACjD,OAAO,aAAiB,MAAQ,EAAY,MAAM,CAAQ,CAC5D"}
1
+ {"version":3,"file":"barrel.mjs","names":["DEFAULT_MAX_MESSAGE_LENGTH","asJsonRpcError"],"sources":["../src/jsonRpcService.ts","../src/jsonRpcTransport.ts","../src/pairingEndpoint.ts","../src/pairingService.ts","../src/barrel.ts"],"sourcesContent":["import { ccc, JsonRpcError } from \"@ckb-ccc/core\";\nimport type { Connection, PeerId, Stream } from \"@libp2p/interface\";\nimport type { Registrar } from \"@libp2p/interface-internal\";\nimport { lpStream } from \"@libp2p/utils\";\n\nconst DEFAULT_MAX_MESSAGE_LENGTH = 1024 * 1024;\n\nexport type JsonRpcServiceComponents = {\n registrar: Registrar;\n};\n\nexport type JsonRpcServiceConfig = {\n protocol: string;\n maxMessageLength?: number;\n};\n\nexport type JsonRpcRequest = {\n connection: Connection;\n peerId: PeerId;\n payload: ccc.JsonRpcPayload;\n};\n\nexport type JsonRpcRequestHandler<\n Components extends JsonRpcServiceComponents = JsonRpcServiceComponents,\n> = (this: JsonRpcService<Components>, request: JsonRpcRequest) => unknown;\n\nexport abstract class JsonRpcService<\n Components extends JsonRpcServiceComponents = JsonRpcServiceComponents,\n> {\n private readonly errorListeners = new Set<(error: Error) => void>();\n readonly maxMessageLength: number;\n\n constructor(\n readonly components: Components,\n readonly config: JsonRpcServiceConfig,\n ) {\n const maxMessageLength =\n config.maxMessageLength ?? DEFAULT_MAX_MESSAGE_LENGTH;\n if (!Number.isSafeInteger(maxMessageLength) || maxMessageLength <= 0) {\n throw new Error(\n \"Maximum JSON-RPC message length must be a positive integer\",\n );\n }\n\n this.maxMessageLength = maxMessageLength;\n }\n\n protected abstract handleRequest(request: JsonRpcRequest): unknown;\n\n onError(listener: (error: Error) => void) {\n this.errorListeners.add(listener);\n return () => this.errorListeners.delete(listener);\n }\n\n async start() {\n await this.components.registrar.handle(\n this.config.protocol,\n this.handleProtocol.bind(this),\n { runOnLimitedConnection: true },\n );\n }\n\n async stop() {\n await this.components.registrar.unhandle(this.config.protocol);\n }\n\n private async handleProtocol(stream: Stream, connection: Connection) {\n let requestAborted = false;\n\n try {\n const rpcStream = lpStream(stream, {\n maxDataLength: this.maxMessageLength,\n });\n const payload = parseJsonRpcRequest(\n ccc.bytesTo((await rpcStream.read()).subarray(), \"utf8\"),\n );\n\n let response: ccc.JsonRpcResponse;\n try {\n response = {\n jsonrpc: \"2.0\",\n id: payload.id,\n result: await this.handleRequest({\n connection,\n peerId: connection.remotePeer,\n payload,\n }),\n };\n } catch (cause) {\n requestAborted = isAbortError(cause);\n const error = toJsonRpcError(cause);\n response = {\n jsonrpc: \"2.0\",\n id: payload.id,\n error: {\n code: error.code,\n message: error.message,\n data: error.data,\n },\n };\n }\n\n await rpcStream.write(ccc.bytesFrom(JSON.stringify(response), \"utf8\"));\n await stream.close();\n } catch (cause) {\n if (requestAborted) {\n return;\n }\n this.handleError(cause, stream);\n }\n }\n\n private handleError(cause: unknown, stream?: Stream) {\n const error = asJsonRpcError(cause);\n\n stream?.abort(error);\n this.errorListeners.forEach((listener) => listener(error));\n return error;\n }\n}\n\nfunction toJsonRpcError(cause: unknown) {\n if (cause instanceof JsonRpcError) {\n return cause;\n }\n\n return new JsonRpcError({\n code: -32603,\n message: asJsonRpcError(cause).message,\n });\n}\n\nfunction parseJsonRpcRequest(data: string) {\n const payload = JSON.parse(data) as Partial<ccc.JsonRpcPayload>;\n if (\n payload?.jsonrpc !== \"2.0\" ||\n (typeof payload.id !== \"number\" && typeof payload.id !== \"string\") ||\n typeof payload.method !== \"string\"\n ) {\n throw new Error(\"Invalid JSON-RPC request\");\n }\n\n return payload as ccc.JsonRpcPayload;\n}\n\nfunction asJsonRpcError(cause: unknown) {\n return cause instanceof Error ? cause : new Error(\"JSON-RPC request failed\");\n}\n\nfunction isAbortError(cause: unknown) {\n return (\n cause instanceof Error &&\n (cause.name === \"AbortError\" || cause.name === \"TimeoutError\")\n );\n}\n\nexport function jsonRpcService<\n Components extends JsonRpcServiceComponents = JsonRpcServiceComponents,\n>(\n config: JsonRpcServiceConfig,\n handler: JsonRpcRequestHandler<Components>,\n): (components: Components) => JsonRpcService<Components> {\n return (components: Components) =>\n new (class extends JsonRpcService<Components> {\n protected handleRequest(request: JsonRpcRequest) {\n return handler.call(this, request);\n }\n })(components, config);\n}\n","import { ccc } from \"@ckb-ccc/core\";\nimport type { Connection, Libp2p, PeerId, Stream } from \"@libp2p/interface\";\nimport { lpStream } from \"@libp2p/utils\";\n\nconst DEFAULT_MAX_MESSAGE_LENGTH = 1024 * 1024;\nconst DEFAULT_TIMEOUT = 120_000;\n\nexport type JsonRpcTransportLibp2pConfig = {\n protocol: string;\n maxMessageLength?: number;\n timeout?: number;\n signal?: AbortSignal;\n onResponse?: (response: ccc.JsonRpcResponse) => void;\n};\n\nexport class JsonRpcTransportLibp2p implements ccc.JsonRpcTransport {\n private readonly maxMessageLength: number;\n private readonly timeout: number;\n\n constructor(\n private readonly node: Libp2p,\n readonly peerId: PeerId,\n private readonly config: JsonRpcTransportLibp2pConfig,\n ) {\n const maxMessageLength =\n config.maxMessageLength ?? DEFAULT_MAX_MESSAGE_LENGTH;\n if (!Number.isSafeInteger(maxMessageLength) || maxMessageLength <= 0) {\n throw new Error(\n \"Maximum JSON-RPC message length must be a positive integer\",\n );\n }\n\n this.maxMessageLength = maxMessageLength;\n\n const timeout = config.timeout ?? DEFAULT_TIMEOUT;\n if (!Number.isSafeInteger(timeout) || timeout <= 0) {\n throw new Error(\"JSON-RPC request timeout must be a positive integer\");\n }\n\n this.timeout = timeout;\n }\n\n async request(payload: ccc.JsonRpcPayload): Promise<ccc.JsonRpcResponse> {\n const timeoutSignal = AbortSignal.timeout(this.timeout);\n const signal = this.config.signal\n ? ccc.abortSignalAny([this.config.signal, timeoutSignal])\n : timeoutSignal;\n\n let stream: Stream | undefined;\n let response: ccc.JsonRpcResponse;\n try {\n stream = await this.openStream(signal);\n\n const rpcStream = lpStream(stream, {\n maxDataLength: this.maxMessageLength,\n });\n\n await rpcStream.write(ccc.bytesFrom(JSON.stringify(payload), \"utf8\"), {\n signal,\n });\n await stream.close({ signal });\n\n response = JSON.parse(\n ccc.bytesTo((await rpcStream.read({ signal })).subarray(), \"utf8\"),\n ) as ccc.JsonRpcResponse;\n await stream.closeRead({ signal });\n signal.throwIfAborted();\n } catch (cause) {\n const error = asJsonRpcError(cause);\n stream?.abort(error);\n throw error;\n }\n\n this.config.onResponse?.(response);\n return response;\n }\n\n private async openStream(signal: AbortSignal) {\n for (const connection of this.connections()) {\n try {\n return await connection.newStream(this.config.protocol, {\n runOnLimitedConnection: true,\n signal,\n });\n } catch {\n signal.throwIfAborted();\n }\n }\n\n return this.node.dialProtocol(this.peerId, this.config.protocol, {\n runOnLimitedConnection: true,\n signal,\n });\n }\n\n private connections() {\n return this.node\n .getConnections(this.peerId)\n .filter(({ status }) => status === \"open\")\n .sort(compareConnections);\n }\n}\n\nfunction compareConnections(a: Connection, b: Connection) {\n if (a.direct !== b.direct) {\n return a.direct ? -1 : 1;\n }\n if (a.rtt === undefined) {\n return b.rtt === undefined ? 0 : 1;\n }\n return b.rtt === undefined ? -1 : a.rtt - b.rtt;\n}\n\nfunction asJsonRpcError(cause: unknown) {\n return cause instanceof Error ? cause : new Error(\"JSON-RPC request failed\");\n}\n","import { ccc } from \"@ckb-ccc/core\";\nimport { multiaddr, type Multiaddr } from \"@multiformats/multiaddr\";\nimport * as lp from \"it-length-prefixed\";\nimport type { PairingTarget } from \"./pairingService.js\";\n\nconst MAX_DECOMPRESSED_ADDRESSES_LENGTH = 16 * 1024;\nconst MAX_ADDRESSES = 16;\nconst PAIRING_PARAMETER_NAMES = [\"addresses\", \"role\", \"secret\"] as const;\n\nexport class PairingEndpointRoleError extends Error {\n constructor(\n readonly expectedRole: string,\n readonly actualRole: string | undefined,\n ) {\n super(\n `Expected a ${expectedRole} pairing endpoint, received ${actualRole ? `role \"${actualRole}\"` : \"an endpoint without a role\"}`,\n );\n this.name = \"PairingEndpointRoleError\";\n }\n}\n\nexport async function encodePairingEndpoint(\n endpointUrl: string,\n addresses: readonly Multiaddr[],\n secret: string,\n role?: string,\n) {\n if (addresses.length === 0) {\n throw new Error(\"Pairing endpoint requires at least one address\");\n }\n if (addresses.length > MAX_ADDRESSES) {\n throw new Error(\n `Pairing endpoint supports at most ${MAX_ADDRESSES} addresses`,\n );\n }\n\n const addressBytes = encodeAddresses(addresses);\n if (addressBytes.byteLength > MAX_DECOMPRESSED_ADDRESSES_LENGTH) {\n throw new Error(\"Pairing endpoint address data is too large\");\n }\n\n const compressedAddressBytes = await transformBytes(\n addressBytes,\n new CompressionStream(\"deflate\"),\n );\n\n const encodedAddresses = ccc.bytesTo(compressedAddressBytes, \"base64url\");\n\n const url = new URL(endpointUrl);\n const { params, route } = fragmentParameters(url);\n\n PAIRING_PARAMETER_NAMES.forEach((name) => {\n params.delete(name);\n });\n if (role) {\n params.set(\"role\", role);\n }\n params.set(\"addresses\", encodedAddresses);\n params.set(\"secret\", secret);\n url.hash = `${route}?${params.toString()}`;\n\n return url.toString();\n}\n\nexport async function decodePairingEndpoint(\n endpoint: string,\n expectedRole?: string,\n): Promise<PairingTarget> {\n const url = new URL(endpoint.trim());\n const { params } = fragmentParameters(url);\n if (expectedRole) {\n const actualRole = params.get(\"role\")?.trim() || undefined;\n if (actualRole !== expectedRole) {\n throw new PairingEndpointRoleError(expectedRole, actualRole);\n }\n }\n const compressedAddresses = params.get(\"addresses\")?.trim();\n const secret = params.get(\"secret\")?.trim();\n\n if (!compressedAddresses || !secret) {\n throw new Error(\"Pairing endpoint is incomplete\");\n }\n\n const addresses = await decodeCompressedAddresses(compressedAddresses);\n\n return { addresses, secret };\n}\n\nfunction fragmentParameters(url: URL) {\n const fragment = url.hash.slice(1);\n const separator = fragment.indexOf(\"?\");\n if (separator === -1) {\n return { params: new URLSearchParams(), route: fragment };\n }\n\n return {\n params: new URLSearchParams(fragment.slice(separator + 1)),\n route: fragment.slice(0, separator),\n };\n}\n\nfunction encodeAddresses(addresses: readonly Multiaddr[]): Uint8Array {\n return ccc.bytesConcat(\n ...lp.encode(addresses.map((address) => address.bytes)),\n );\n}\n\nasync function decodeCompressedAddresses(value: string): Promise<Multiaddr[]> {\n try {\n const compressedBytes = ccc.bytesFrom(value, \"base64url\");\n const bytes = await transformBytes(\n compressedBytes,\n new DecompressionStream(\"deflate\"),\n MAX_DECOMPRESSED_ADDRESSES_LENGTH,\n );\n const addresses: Multiaddr[] = [];\n for (const address of lp.decode([bytes], {\n maxDataLength: MAX_DECOMPRESSED_ADDRESSES_LENGTH,\n })) {\n if (addresses.length >= MAX_ADDRESSES) {\n throw new Error(\"Too many addresses\");\n }\n addresses.push(multiaddr(address.subarray()));\n }\n\n if (addresses.length === 0) {\n throw new Error(\"Missing addresses\");\n }\n return addresses;\n } catch (cause) {\n throw new Error(\"Pairing endpoint contains invalid compressed addresses\", {\n cause,\n });\n }\n}\n\nasync function transformBytes(\n bytes: Uint8Array,\n transform: CompressionStream | DecompressionStream,\n maxOutputLength?: number,\n) {\n const readable =\n maxOutputLength === undefined\n ? transform.readable\n : transform.readable.pipeThrough(limitBytes(maxOutputLength));\n const output = new Response(readable).arrayBuffer().then(\n (buffer) => ({ buffer, ok: true }) as const,\n (error: unknown) => ({ error, ok: false }) as const,\n );\n const writer = transform.writable.getWriter();\n try {\n await writer.write(Uint8Array.from(bytes));\n await writer.close();\n } catch (cause) {\n const result = await output;\n if (!result.ok) {\n throw result.error;\n }\n throw cause;\n }\n\n const result = await output;\n if (!result.ok) {\n throw result.error;\n }\n return new Uint8Array(result.buffer);\n}\n\nfunction limitBytes(maxLength: number) {\n let length = 0;\n\n return new TransformStream<Uint8Array, Uint8Array>({\n transform(chunk, controller) {\n if (chunk.byteLength > maxLength - length) {\n throw new Error(\"Decompressed address data is too large\");\n }\n length += chunk.byteLength;\n controller.enqueue(chunk);\n },\n });\n}\n","import { ccc } from \"@ckb-ccc/core\";\nimport type {\n AbortOptions,\n Connection,\n PeerId,\n Stream,\n} from \"@libp2p/interface\";\nimport type {\n AddressManager,\n ConnectionManager,\n Registrar,\n} from \"@libp2p/interface-internal\";\nimport { lpStream } from \"@libp2p/utils\";\nimport { CODE_P2P, multiaddr, type Multiaddr } from \"@multiformats/multiaddr\";\n\nconst MAX_PAIRING_MESSAGE_LENGTH = 1024;\nconst UNPAIR_TIMEOUT_MS = 10_000;\n\nexport type PairingServiceComponents = {\n addressManager?: Pick<AddressManager, \"getAddresses\">;\n connectionManager: ConnectionManager;\n registrar: Registrar;\n};\n\nexport type PairingServiceConfig = {\n /** Optional self-reported label shared with the paired peer. */\n name?: string;\n protocol: string;\n pairedPeerTimeoutMs: number;\n};\n\nexport type PairingTarget = {\n addresses: Multiaddr[];\n secret: string;\n};\n\nexport type PairingGuard<\n Components extends PairingServiceComponents = PairingServiceComponents,\n> = (this: PairingService<Components>, peerId: PeerId) => boolean;\n\ntype PairingRequest =\n | { type: \"pair\"; secret: string; name?: string; addresses?: string[] }\n | { type: \"unpair\" };\n\ntype PairingResponse =\n { ok: true; name?: string } | { ok: false; error: string };\n\nexport abstract class PairingService<\n Components extends PairingServiceComponents = PairingServiceComponents,\n> {\n private currentSecret = createSecret();\n private readonly errorListeners = new Set<(error: Error) => void>();\n private readonly pairedListeners = new Set<\n (peerId: PeerId, name?: string) => void\n >();\n private readonly unpairedListeners = new Set<(peerId: PeerId) => void>();\n private readonly secretChangedListeners = new Set<(secret: string) => void>();\n private readonly pairingTimeouts = new Map<\n string,\n ReturnType<typeof setTimeout>\n >();\n\n constructor(\n readonly components: Components,\n readonly config: PairingServiceConfig,\n ) {\n if (config.pairedPeerTimeoutMs <= 0) {\n throw new Error(\"Paired peer timeout must be greater than zero\");\n }\n }\n\n protected abstract canPair(peerId: PeerId): boolean;\n\n /** The credential to include in the currently issued pairing endpoint. */\n get secret() {\n return this.currentSecret;\n }\n\n onError(listener: (error: Error) => void) {\n this.errorListeners.add(listener);\n return () => this.errorListeners.delete(listener);\n }\n\n onPaired(listener: (peerId: PeerId, name?: string) => void) {\n this.pairedListeners.add(listener);\n return () => this.pairedListeners.delete(listener);\n }\n\n onUnpaired(listener: (peerId: PeerId) => void) {\n this.unpairedListeners.add(listener);\n return () => this.unpairedListeners.delete(listener);\n }\n\n onSecretChanged(listener: (secret: string) => void) {\n this.secretChangedListeners.add(listener);\n return () => this.secretChangedListeners.delete(listener);\n }\n\n async start() {\n await this.components.registrar.handle(\n this.config.protocol,\n this.handleProtocol.bind(this),\n { runOnLimitedConnection: true },\n );\n }\n\n async stop() {\n await this.components.registrar.unhandle(this.config.protocol);\n\n this.pairingTimeouts.forEach((timeout) => clearTimeout(timeout));\n this.pairingTimeouts.clear();\n }\n\n isPaired(peerId: PeerId) {\n return this.pairingTimeouts.has(peerId.toString());\n }\n\n refresh(peerId: PeerId) {\n const key = peerId.toString();\n const timeout = this.pairingTimeouts.get(key);\n if (timeout === undefined) {\n return;\n }\n\n clearTimeout(timeout);\n this.pairingTimeouts.set(key, this.createPeerTimeout(peerId));\n }\n\n async pair(\n target: PairingTarget,\n options: AbortOptions = {},\n ): Promise<PeerId> {\n let stream: Stream | undefined;\n let addedPeer: PeerId | undefined;\n\n try {\n const opened = await this.openStream(target.addresses, options);\n stream = opened.stream;\n\n if (!opened.connection) {\n throw new Error(\"Could not determine remote peer\");\n }\n\n if (this.addPairedPeer(opened.connection.remotePeer)) {\n addedPeer = opened.connection.remotePeer;\n }\n\n const response = await exchange(\n stream,\n createPairRequest(target.secret, this.config.name, this.addresses()),\n options,\n );\n options.signal?.throwIfAborted();\n stream = undefined;\n\n if (!response.ok) {\n throw new Error(response.error);\n }\n\n if (addedPeer) {\n this.rotateSecret();\n this.notifyPairedPeer(addedPeer, response.name);\n }\n return opened.connection.remotePeer;\n } catch (cause) {\n if (addedPeer) {\n this.removePairedPeer(addedPeer, false);\n }\n throw this.handleError(cause, stream);\n }\n }\n\n async unpair(peerId: PeerId) {\n if (!this.removePairedPeer(peerId)) {\n return;\n }\n\n const options = { signal: AbortSignal.timeout(UNPAIR_TIMEOUT_MS) };\n\n let stream: Stream | undefined;\n try {\n const opened = await this.openStream(peerId, options);\n stream = opened.stream;\n const response = await exchange(\n opened.stream,\n { type: \"unpair\" },\n options,\n );\n stream = undefined;\n if (!response.ok) {\n throw new Error(response.error);\n }\n } catch (cause) {\n this.handleError(cause, stream);\n }\n }\n\n private async handleProtocol(stream: Stream, connection: Connection) {\n let addedPeer: PeerId | undefined;\n\n try {\n const pairingStream = lpStream(stream, {\n maxDataLength: MAX_PAIRING_MESSAGE_LENGTH,\n });\n const request = parsePairingRequest(\n (await pairingStream.read()).subarray(),\n );\n\n if (request.type === \"unpair\") {\n this.removePairedPeer(connection.remotePeer);\n await writeResponse(pairingStream, stream, { ok: true });\n return;\n }\n\n if (\n request.secret !== this.secret ||\n !this.canPair(connection.remotePeer)\n ) {\n await writeResponse(pairingStream, stream, {\n ok: false,\n error: \"Pairing rejected\",\n });\n return;\n }\n\n const addresses = parseAddresses(\n request.addresses,\n connection.remotePeer,\n );\n\n // Consume the credential before the next asynchronous boundary so two\n // concurrent requests cannot both pair with the same endpoint.\n this.rotateSecret();\n\n if (this.addPairedPeer(connection.remotePeer)) {\n addedPeer = connection.remotePeer;\n }\n await writeResponse(pairingStream, stream, {\n ok: true,\n name: this.config.name,\n });\n if (addedPeer) {\n this.notifyPairedPeer(addedPeer, request.name);\n void this.openFallbackConnection(addresses);\n }\n } catch (cause) {\n if (addedPeer) {\n this.removePairedPeer(addedPeer, false);\n }\n this.handleError(cause, stream);\n }\n }\n\n private addPairedPeer(peerId: PeerId) {\n const key = peerId.toString();\n const timeout = this.pairingTimeouts.get(key);\n if (timeout !== undefined) {\n clearTimeout(timeout);\n }\n\n this.pairingTimeouts.set(key, this.createPeerTimeout(peerId));\n return timeout === undefined;\n }\n\n private notifyPairedPeer(peerId: PeerId, name?: string) {\n this.pairedListeners.forEach((listener) => listener(peerId, name));\n }\n\n private addresses() {\n return this.components.addressManager?.getAddresses() ?? [];\n }\n\n private async openFallbackConnection(addresses: Multiaddr[]) {\n if (addresses.length === 0) {\n return;\n }\n\n try {\n await this.components.connectionManager.openConnection(addresses);\n } catch {\n // The original pairing connection remains usable when the fallback fails.\n }\n }\n\n private rotateSecret() {\n this.currentSecret = createSecret();\n this.secretChangedListeners.forEach((listener) =>\n listener(this.currentSecret),\n );\n }\n\n private removePairedPeer(peerId: PeerId, notify = true) {\n const key = peerId.toString();\n const timeout = this.pairingTimeouts.get(key);\n if (timeout === undefined) {\n return false;\n }\n\n clearTimeout(timeout);\n this.pairingTimeouts.delete(key);\n\n if (notify) {\n this.unpairedListeners.forEach((listener) => listener(peerId));\n }\n return true;\n }\n\n private createPeerTimeout(peerId: PeerId) {\n return setTimeout(() => {\n this.removePairedPeer(peerId);\n }, this.config.pairedPeerTimeoutMs);\n }\n\n private async openStream(\n target: PeerId | Multiaddr | Multiaddr[],\n options: AbortOptions,\n ) {\n let connection: Connection | undefined;\n const stream = await this.components.connectionManager.openStream(\n target,\n this.config.protocol,\n {\n runOnLimitedConnection: true,\n signal: options.signal,\n onProgress(event) {\n if (event.type === \"connection:opened-stream\") {\n connection = event.detail.connection;\n }\n },\n },\n );\n\n return { stream, connection };\n }\n\n private handleError(cause: unknown, stream?: Stream) {\n const error = asError(cause, \"Pairing operation failed\");\n\n stream?.abort(error);\n this.errorListeners.forEach((listener) => listener(error));\n return error;\n }\n}\n\nexport function pairingService<\n Components extends PairingServiceComponents = PairingServiceComponents,\n>(\n config: PairingServiceConfig,\n canPair: PairingGuard<Components>,\n): (components: Components) => PairingService<Components> {\n return (components: Components) =>\n new (class extends PairingService<Components> {\n protected canPair(peerId: PeerId) {\n return canPair.call(this, peerId);\n }\n })(components, config);\n}\n\nasync function exchange(\n stream: Stream,\n request: PairingRequest,\n options: AbortOptions,\n) {\n const pairingStream = lpStream(stream, {\n maxDataLength: MAX_PAIRING_MESSAGE_LENGTH,\n });\n\n await pairingStream.write(ccc.bytesFrom(JSON.stringify(request), \"utf8\"), {\n signal: options.signal,\n });\n await stream.close(options);\n\n const response = parsePairingResponse(\n (await pairingStream.read(options)).subarray(),\n );\n await stream.closeRead(options);\n return response;\n}\n\nfunction createPairRequest(\n secret: string,\n name: string | undefined,\n addresses: Multiaddr[],\n): PairingRequest {\n const addressStrings = addresses.map((address) => address.toString());\n const request: PairingRequest = {\n type: \"pair\",\n secret,\n name,\n addresses: addressStrings,\n };\n while (addressStrings.length > 0 && !pairingRequestFits(request)) {\n addressStrings.pop();\n }\n\n if (!pairingRequestFits(request)) {\n delete request.addresses;\n }\n if (!pairingRequestFits(request)) {\n delete request.name;\n }\n if (!pairingRequestFits(request)) {\n throw new Error(\"Pairing request is too large\");\n }\n return request;\n}\n\nfunction pairingRequestFits(request: PairingRequest) {\n return (\n ccc.bytesFrom(JSON.stringify(request), \"utf8\").byteLength <=\n MAX_PAIRING_MESSAGE_LENGTH\n );\n}\n\nasync function writeResponse(\n pairingStream: ReturnType<typeof lpStream>,\n stream: Stream,\n response: PairingResponse,\n) {\n await pairingStream.write(ccc.bytesFrom(JSON.stringify(response), \"utf8\"));\n await stream.close();\n}\n\nfunction parsePairingRequest(data: Uint8Array): PairingRequest {\n const request = JSON.parse(\n ccc.bytesTo(data, \"utf8\"),\n ) as Partial<PairingRequest>;\n if (\n request.type === \"pair\" &&\n typeof request.secret === \"string\" &&\n request.secret\n ) {\n return {\n type: \"pair\",\n secret: request.secret,\n name: typeof request.name === \"string\" ? request.name : undefined,\n addresses: parseAddressStrings(request.addresses),\n };\n }\n\n if (request.type === \"unpair\") {\n return { type: \"unpair\" };\n }\n\n throw new Error(\"Invalid pairing request\");\n}\n\nfunction parseAddressStrings(value: unknown): string[] | undefined {\n if (!Array.isArray(value)) {\n return undefined;\n }\n return value.filter(\n (address): address is string =>\n typeof address === \"string\" && address.length > 0,\n );\n}\n\nfunction parseAddresses(values: string[] | undefined, peerId: PeerId) {\n return (values ?? []).flatMap((value) => {\n try {\n const address = multiaddr(value);\n const peer = address\n .getComponents()\n .filter(({ code }) => code === CODE_P2P)\n .at(-1)?.value;\n return peer === peerId.toString() ? [address] : [];\n } catch {\n return [];\n }\n });\n}\n\nfunction parsePairingResponse(data: Uint8Array): PairingResponse {\n const response = JSON.parse(\n ccc.bytesTo(data, \"utf8\"),\n ) as Partial<PairingResponse>;\n if (response.ok === true) {\n return {\n ok: true,\n name: typeof response.name === \"string\" ? response.name : undefined,\n };\n }\n if (response.ok === false && typeof response.error === \"string\") {\n return { ok: false, error: response.error };\n }\n\n throw new Error(\"Invalid pairing response\");\n}\n\nfunction createSecret() {\n return ccc.bytesTo(crypto.getRandomValues(new Uint8Array(16)), \"base64url\");\n}\n\nfunction asError(cause: unknown, fallback: string) {\n return cause instanceof Error ? cause : new Error(fallback);\n}\n","export * from \"./jsonRpcService.js\";\nexport * from \"./jsonRpcTransport.js\";\nexport * from \"./pairingEndpoint.js\";\nexport * from \"./pairingService.js\";\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { ccc } from "@ckb-ccc/core";
2
- import { AbortOptions, Libp2p, PeerId } from "@libp2p/interface";
3
- import { ConnectionManager, Registrar } from "@libp2p/interface-internal";
2
+ import { AbortOptions, Connection, Libp2p, PeerId } from "@libp2p/interface";
3
+ import { AddressManager, ConnectionManager, Registrar } from "@libp2p/interface-internal";
4
4
  import { Multiaddr } from "@multiformats/multiaddr";
5
5
  //#endregion
6
6
  //#region src/jsonRpcService.d.ts
@@ -12,6 +12,7 @@ type JsonRpcServiceConfig = {
12
12
  maxMessageLength?: number;
13
13
  };
14
14
  type JsonRpcRequest = {
15
+ connection: Connection;
15
16
  peerId: PeerId;
16
17
  payload: ccc.JsonRpcPayload;
17
18
  };
@@ -47,10 +48,13 @@ declare class JsonRpcTransportLibp2p implements ccc.JsonRpcTransport {
47
48
  private readonly timeout;
48
49
  constructor(node: Libp2p, peerId: PeerId, config: JsonRpcTransportLibp2pConfig);
49
50
  request(payload: ccc.JsonRpcPayload): Promise<ccc.JsonRpcResponse>;
51
+ private openStream;
52
+ private connections;
50
53
  }
51
54
  //#endregion
52
55
  //#region src/pairingService.d.ts
53
56
  type PairingServiceComponents = {
57
+ addressManager?: Pick<AddressManager, "getAddresses">;
54
58
  connectionManager: ConnectionManager;
55
59
  registrar: Registrar;
56
60
  };
@@ -91,6 +95,8 @@ declare abstract class PairingService<Components extends PairingServiceComponent
91
95
  private handleProtocol;
92
96
  private addPairedPeer;
93
97
  private notifyPairedPeer;
98
+ private addresses;
99
+ private openFallbackConnection;
94
100
  private rotateSecret;
95
101
  private removePairedPeer;
96
102
  private createPeerTimeout;
@@ -1 +1 @@
1
- {"version":3,"file":"barrel.d.ts","names":[],"sources":["../src/jsonRpcService.ts","../src/jsonRpcTransport.ts","../src/pairingService.ts","../src/pairingEndpoint.ts","../src/barrel.ts"],"mappings":";;;;;;KAOY;EACV,WAAW;;KAGD;EACV;EACA;;KAGU;EACV,QAAQ;EACR,SAAS,IAAI;;KAGH,sBACV,mBAAmB,2BAA2B,6BAC3C,MAAM,eAAe,aAAa,SAAS;uBAE1B,eACpB,mBAAmB,2BAA2B;WAMnC,YAAY;WACZ,QAAQ;mBALF;WACR;EAGE,YAAA,YAAY,YACZ,QAAQ;qBAaA,cAAc,SAAS;EAE1C,QAAQ,WAAW,OAAO;EAKpB,SAAK;EAQL,QAAI;UAII;UA6CN;;iBA4CM,eACd,mBAAmB,2BAA2B,0BAE9C,QAAQ,sBACR,SAAS,sBAAsB,eAC7B,YAAY,eAAe,eAAe;;;KCxJlC;EACV;EACA;EACA;EACA,SAAS;EACT,cAAc,UAAU,IAAI;;cAGjB,kCAAkC,IAAI;mBAK9B;WACR,QAAQ;mBACA;mBANF;mBACA;EAGE,YAAA,MAAM,QACd,QAAQ,QACA,QAAQ;EAoBrB,QAAQ,SAAS,IAAI,iBAAiB,QAAQ,IAAI;;;;KC5B9C;EACV,mBAAmB;EACnB,WAAW;;KAGD;;EAEV;EACA;EACA;;KAGU;EACV,WAAW;EACX;;KAGU,aACV,mBAAmB,2BAA2B,6BAC3C,MAAM,eAAe,aAAa,QAAQ;uBAQzB,eACpB,mBAAmB,2BAA2B;WAenC,YAAY;WACZ,QAAQ;UAdX;mBACS;mBACA;mBAGA;mBACA;mBACA;EAMN,YAAA,YAAY,YACZ,QAAQ;qBAOA,QAAQ,QAAQ;;MAG/B;EAIJ,QAAQ,WAAW,OAAO;EAK1B,SAAS,WAAW,QAAQ,QAAQ;EAKpC,WAAW,WAAW,QAAQ;EAK9B,gBAAgB,WAAW;EAKrB,SAAK;EAQL,QAAI;EAOV,SAAS,QAAQ;EAIjB,QAAQ,QAAQ;EAWV,KACJ,QAAQ,eACR,UAAS,eACR,QAAQ;EA6CL,OAAO,QAAQ,SAAM;UAyBb;UAkDN;UAWA;UAIA;UAOA;UAgBA;UAMM;UAsBN;;iBASM,eACd,mBAAmB,2BAA2B,0BAE9C,QAAQ,sBACR,SAAS,aAAa,eACpB,YAAY,eAAe,eAAe;;;cC5TjC,iCAAiC;WAEjC;WACA;EADA,YAAA,sBACA;;iBASS,sBACpB,qBACA,oBAAoB,aACpB,gBACA,gBAAa;iBAuCO,sBACpB,kBACA,wBACC,QAAQ"}
1
+ {"version":3,"file":"barrel.d.ts","names":[],"sources":["../src/jsonRpcService.ts","../src/jsonRpcTransport.ts","../src/pairingService.ts","../src/pairingEndpoint.ts","../src/barrel.ts"],"mappings":";;;;;;KAOY;EACV,WAAW;;KAGD;EACV;EACA;;KAGU;EACV,YAAY;EACZ,QAAQ;EACR,SAAS,IAAI;;KAGH,sBACV,mBAAmB,2BAA2B,6BAC3C,MAAM,eAAe,aAAa,SAAS;uBAE1B,eACpB,mBAAmB,2BAA2B;WAMnC,YAAY;WACZ,QAAQ;mBALF;WACR;EAGE,YAAA,YAAY,YACZ,QAAQ;qBAaA,cAAc,SAAS;EAE1C,QAAQ,WAAW,OAAO;EAKpB,SAAK;EAQL,QAAI;UAII;UA8CN;;iBA4CM,eACd,mBAAmB,2BAA2B,0BAE9C,QAAQ,sBACR,SAAS,sBAAsB,eAC7B,YAAY,eAAe,eAAe;;;KC1JlC;EACV;EACA;EACA;EACA,SAAS;EACT,cAAc,UAAU,IAAI;;cAGjB,kCAAkC,IAAI;mBAK9B;WACR,QAAQ;mBACA;mBANF;mBACA;EAGE,YAAA,MAAM,QACd,QAAQ,QACA,QAAQ;EAoBrB,QAAQ,SAAS,IAAI,iBAAiB,QAAQ,IAAI;UAmC1C;UAkBN;;;;KC7EE;EACV,iBAAiB,KAAK;EACtB,mBAAmB;EACnB,WAAW;;KAGD;;EAEV;EACA;EACA;;KAGU;EACV,WAAW;EACX;;KAGU,aACV,mBAAmB,2BAA2B,6BAC3C,MAAM,eAAe,aAAa,QAAQ;uBASzB,eACpB,mBAAmB,2BAA2B;WAenC,YAAY;WACZ,QAAQ;UAdX;mBACS;mBACA;mBAGA;mBACA;mBACA;EAMN,YAAA,YAAY,YACZ,QAAQ;qBAOA,QAAQ,QAAQ;;MAG/B;EAIJ,QAAQ,WAAW,OAAO;EAK1B,SAAS,WAAW,QAAQ,QAAQ;EAKpC,WAAW,WAAW,QAAQ;EAK9B,gBAAgB,WAAW;EAKrB,SAAK;EAQL,QAAI;EAOV,SAAS,QAAQ;EAIjB,QAAQ,QAAQ;EAWV,KACJ,QAAQ,eACR,UAAS,eACR,QAAQ;EAyCL,OAAO,QAAQ,SAAM;UAyBb;UAwDN;UAWA;UAIA;UAIM;UAYN;UAOA;UAgBA;UAMM;UAsBN;;iBASM,eACd,mBAAmB,2BAA2B,0BAE9C,QAAQ,sBACR,SAAS,aAAa,eACpB,YAAY,eAAe,eAAe;;;cCpVjC,iCAAiC;WAEjC;WACA;EADA,YAAA,sBACA;;iBASS,sBACpB,qBACA,oBAAoB,aACpB,gBACA,gBAAa;iBAuCO,sBACpB,kBACA,wBACC,QAAQ"}
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,n)=>{let r={};for(var i in e)t(r,i,{get:e[i],enumerable:!0});return n||t(r,Symbol.toStringTag,{value:`Module`}),r},s=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},c=(n,r,o)=>(o=n==null?{}:e(i(n)),s(r||!n||!n.__esModule||!a.call(n,`default`)?t(o,`default`,{value:n,enumerable:!0}):o,n));let l=require("@ckb-ccc/core"),u=require("@libp2p/utils"),d=require("@multiformats/multiaddr"),f=require("it-length-prefixed");f=c(f);var p=class{constructor(e,t){this.components=e,this.config=t,this.errorListeners=new Set;let n=t.maxMessageLength??1048576;if(!Number.isSafeInteger(n)||n<=0)throw Error(`Maximum JSON-RPC message length must be a positive integer`);this.maxMessageLength=n}onError(e){return this.errorListeners.add(e),()=>this.errorListeners.delete(e)}async start(){await this.components.registrar.handle(this.config.protocol,this.handleProtocol.bind(this),{runOnLimitedConnection:!0})}async stop(){await this.components.registrar.unhandle(this.config.protocol)}async handleProtocol(e,t){let n=!1;try{let r=(0,u.lpStream)(e,{maxDataLength:this.maxMessageLength}),i=h(l.ccc.bytesTo((await r.read()).subarray(),`utf8`)),a;try{a={jsonrpc:`2.0`,id:i.id,result:await this.handleRequest({peerId:t.remotePeer,payload:i})}}catch(e){n=_(e);let t=m(e);a={jsonrpc:`2.0`,id:i.id,error:{code:t.code,message:t.message,data:t.data}}}await r.write(l.ccc.bytesFrom(JSON.stringify(a),`utf8`)),await e.close()}catch(t){if(n)return;this.handleError(t,e)}}handleError(e,t){let n=g(e);return t?.abort(n),this.errorListeners.forEach(e=>e(n)),n}};function m(e){return e instanceof l.JsonRpcError?e:new l.JsonRpcError({code:-32603,message:g(e).message})}function h(e){let t=JSON.parse(e);if(t?.jsonrpc!==`2.0`||typeof t.id!=`number`&&typeof t.id!=`string`||typeof t.method!=`string`)throw Error(`Invalid JSON-RPC request`);return t}function g(e){return e instanceof Error?e:Error(`JSON-RPC request failed`)}function _(e){return e instanceof Error&&(e.name===`AbortError`||e.name===`TimeoutError`)}function v(e,t){return n=>new class extends p{handleRequest(e){return t.call(this,e)}}(n,e)}var y=class{constructor(e,t,n){this.node=e,this.peerId=t,this.config=n;let r=n.maxMessageLength??1048576;if(!Number.isSafeInteger(r)||r<=0)throw Error(`Maximum JSON-RPC message length must be a positive integer`);this.maxMessageLength=r;let i=n.timeout??12e4;if(!Number.isSafeInteger(i)||i<=0)throw Error(`JSON-RPC request timeout must be a positive integer`);this.timeout=i}async request(e){let t=AbortSignal.timeout(this.timeout),n=this.config.signal?l.ccc.abortSignalAny([this.config.signal,t]):t,r,i;try{r=await this.node.dialProtocol(this.peerId,this.config.protocol,{runOnLimitedConnection:!0,signal:n});let t=(0,u.lpStream)(r,{maxDataLength:this.maxMessageLength});await t.write(l.ccc.bytesFrom(JSON.stringify(e),`utf8`),{signal:n}),await r.close({signal:n}),i=JSON.parse(l.ccc.bytesTo((await t.read({signal:n})).subarray(),`utf8`)),await r.closeRead({signal:n}),n.throwIfAborted()}catch(e){let t=b(e);throw r?.abort(t),t}return this.config.onResponse?.(i),i}};function b(e){return e instanceof Error?e:Error(`JSON-RPC request failed`)}const x=16384,S=[`addresses`,`role`,`secret`];var C=class extends Error{constructor(e,t){super(`Expected a ${e} pairing endpoint, received ${t?`role "${t}"`:`an endpoint without a role`}`),this.expectedRole=e,this.actualRole=t,this.name=`PairingEndpointRoleError`}};async function w(e,t,n,r){if(t.length===0)throw Error(`Pairing endpoint requires at least one address`);if(t.length>16)throw Error(`Pairing endpoint supports at most 16 addresses`);let i=D(t);if(i.byteLength>x)throw Error(`Pairing endpoint address data is too large`);let a=await k(i,new CompressionStream(`deflate`)),o=l.ccc.bytesTo(a,`base64url`),s=new URL(e),{params:c,route:u}=E(s);return S.forEach(e=>{c.delete(e)}),r&&c.set(`role`,r),c.set(`addresses`,o),c.set(`secret`,n),s.hash=`${u}?${c.toString()}`,s.toString()}async function T(e,t){let{params:n}=E(new URL(e.trim()));if(t){let e=n.get(`role`)?.trim()||void 0;if(e!==t)throw new C(t,e)}let r=n.get(`addresses`)?.trim(),i=n.get(`secret`)?.trim();if(!r||!i)throw Error(`Pairing endpoint is incomplete`);return{addresses:await O(r),secret:i}}function E(e){let t=e.hash.slice(1),n=t.indexOf(`?`);return n===-1?{params:new URLSearchParams,route:t}:{params:new URLSearchParams(t.slice(n+1)),route:t.slice(0,n)}}function D(e){return l.ccc.bytesConcat(...f.encode(e.map(e=>e.bytes)))}async function O(e){try{let t=await k(l.ccc.bytesFrom(e,`base64url`),new DecompressionStream(`deflate`),x),n=[];for(let e of f.decode([t],{maxDataLength:x})){if(n.length>=16)throw Error(`Too many addresses`);n.push((0,d.multiaddr)(e.subarray()))}if(n.length===0)throw Error(`Missing addresses`);return n}catch(e){throw Error(`Pairing endpoint contains invalid compressed addresses`,{cause:e})}}async function k(e,t,n){let r=n===void 0?t.readable:t.readable.pipeThrough(A(n)),i=new Response(r).arrayBuffer().then(e=>({buffer:e,ok:!0}),e=>({error:e,ok:!1})),a=t.writable.getWriter();try{await a.write(Uint8Array.from(e)),await a.close()}catch(e){let t=await i;throw t.ok?e:t.error}let o=await i;if(!o.ok)throw o.error;return new Uint8Array(o.buffer)}function A(e){let t=0;return new TransformStream({transform(n,r){if(n.byteLength>e-t)throw Error(`Decompressed address data is too large`);t+=n.byteLength,r.enqueue(n)}})}const j=1024;var M=class{constructor(e,t){if(this.components=e,this.config=t,this.currentSecret=R(),this.errorListeners=new Set,this.pairedListeners=new Set,this.unpairedListeners=new Set,this.secretChangedListeners=new Set,this.pairingTimeouts=new Map,t.pairedPeerTimeoutMs<=0)throw Error(`Paired peer timeout must be greater than zero`)}get secret(){return this.currentSecret}onError(e){return this.errorListeners.add(e),()=>this.errorListeners.delete(e)}onPaired(e){return this.pairedListeners.add(e),()=>this.pairedListeners.delete(e)}onUnpaired(e){return this.unpairedListeners.add(e),()=>this.unpairedListeners.delete(e)}onSecretChanged(e){return this.secretChangedListeners.add(e),()=>this.secretChangedListeners.delete(e)}async start(){await this.components.registrar.handle(this.config.protocol,this.handleProtocol.bind(this),{runOnLimitedConnection:!0})}async stop(){await this.components.registrar.unhandle(this.config.protocol),this.pairingTimeouts.forEach(e=>clearTimeout(e)),this.pairingTimeouts.clear()}isPaired(e){return this.pairingTimeouts.has(e.toString())}refresh(e){let t=e.toString(),n=this.pairingTimeouts.get(t);n!==void 0&&(clearTimeout(n),this.pairingTimeouts.set(t,this.createPeerTimeout(e)))}async pair(e,t={}){let n,r;try{let i=await this.openStream(e.addresses,t);if(n=i.stream,!i.connection)throw Error(`Could not determine remote peer`);this.addPairedPeer(i.connection.remotePeer)&&(r=i.connection.remotePeer);let a=await P(n,{type:`pair`,secret:e.secret,name:this.config.name},t);if(t.signal?.throwIfAborted(),n=void 0,!a.ok)throw Error(a.error);return r&&(this.rotateSecret(),this.notifyPairedPeer(r,a.name)),i.connection.remotePeer}catch(e){throw r&&this.removePairedPeer(r,!1),this.handleError(e,n)}}async unpair(e){if(!this.removePairedPeer(e))return;let t={signal:AbortSignal.timeout(1e4)},n;try{let r=await this.openStream(e,t);n=r.stream;let i=await P(r.stream,{type:`unpair`},t);if(n=void 0,!i.ok)throw Error(i.error)}catch(e){this.handleError(e,n)}}async handleProtocol(e,t){let n;try{let r=(0,u.lpStream)(e,{maxDataLength:j}),i=I((await r.read()).subarray());if(i.type===`unpair`){this.removePairedPeer(t.remotePeer),await F(r,e,{ok:!0});return}if(i.secret!==this.secret||!this.canPair(t.remotePeer)){await F(r,e,{ok:!1,error:`Pairing rejected`});return}this.rotateSecret(),this.addPairedPeer(t.remotePeer)&&(n=t.remotePeer),await F(r,e,{ok:!0,name:this.config.name}),n&&this.notifyPairedPeer(n,i.name)}catch(t){n&&this.removePairedPeer(n,!1),this.handleError(t,e)}}addPairedPeer(e){let t=e.toString(),n=this.pairingTimeouts.get(t);return n!==void 0&&clearTimeout(n),this.pairingTimeouts.set(t,this.createPeerTimeout(e)),n===void 0}notifyPairedPeer(e,t){this.pairedListeners.forEach(n=>n(e,t))}rotateSecret(){this.currentSecret=R(),this.secretChangedListeners.forEach(e=>e(this.currentSecret))}removePairedPeer(e,t=!0){let n=e.toString(),r=this.pairingTimeouts.get(n);return r!==void 0&&(clearTimeout(r),this.pairingTimeouts.delete(n),t&&this.unpairedListeners.forEach(t=>t(e)),!0)}createPeerTimeout(e){return setTimeout(()=>{this.removePairedPeer(e)},this.config.pairedPeerTimeoutMs)}async openStream(e,t){let n;return{stream:await this.components.connectionManager.openStream(e,this.config.protocol,{runOnLimitedConnection:!0,signal:t.signal,onProgress(e){e.type===`connection:opened-stream`&&(n=e.detail.connection)}}),connection:n}}handleError(e,t){let n=z(e,`Pairing operation failed`);return t?.abort(n),this.errorListeners.forEach(e=>e(n)),n}};function N(e,t){return n=>new class extends M{canPair(e){return t.call(this,e)}}(n,e)}async function P(e,t,n){let r=(0,u.lpStream)(e,{maxDataLength:j});await r.write(l.ccc.bytesFrom(JSON.stringify(t),`utf8`),{signal:n.signal}),await e.close(n);let i=L((await r.read(n)).subarray());return await e.closeRead(n),i}async function F(e,t,n){await e.write(l.ccc.bytesFrom(JSON.stringify(n),`utf8`)),await t.close()}function I(e){let t=JSON.parse(l.ccc.bytesTo(e,`utf8`));if(t.type===`pair`&&typeof t.secret==`string`&&t.secret)return{type:`pair`,secret:t.secret,name:typeof t.name==`string`?t.name:void 0};if(t.type===`unpair`)return{type:`unpair`};throw Error(`Invalid pairing request`)}function L(e){let t=JSON.parse(l.ccc.bytesTo(e,`utf8`));if(t.ok===!0)return{ok:!0,name:typeof t.name==`string`?t.name:void 0};if(t.ok===!1&&typeof t.error==`string`)return{ok:!1,error:t.error};throw Error(`Invalid pairing response`)}function R(){return l.ccc.bytesTo(crypto.getRandomValues(new Uint8Array(16)),`base64url`)}function z(e,t){return e instanceof Error?e:Error(t)}var B=o({JsonRpcService:()=>p,JsonRpcTransportLibp2p:()=>y,PairingEndpointRoleError:()=>C,PairingService:()=>M,decodePairingEndpoint:()=>T,encodePairingEndpoint:()=>w,jsonRpcService:()=>v,pairingService:()=>N});exports.JsonRpcService=p,exports.JsonRpcTransportLibp2p=y,exports.PairingEndpointRoleError=C,exports.PairingService=M,exports.decodePairingEndpoint=T,exports.encodePairingEndpoint=w,exports.jsonRpcService=v,exports.pairingService=N,Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return B}});
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,n)=>{let r={};for(var i in e)t(r,i,{get:e[i],enumerable:!0});return n||t(r,Symbol.toStringTag,{value:`Module`}),r},s=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},c=(n,r,o)=>(o=n==null?{}:e(i(n)),s(r||!n||!n.__esModule||!a.call(n,`default`)?t(o,`default`,{value:n,enumerable:!0}):o,n));let l=require("@ckb-ccc/core"),u=require("@libp2p/utils"),d=require("@multiformats/multiaddr"),f=require("it-length-prefixed");f=c(f);var p=class{constructor(e,t){this.components=e,this.config=t,this.errorListeners=new Set;let n=t.maxMessageLength??1048576;if(!Number.isSafeInteger(n)||n<=0)throw Error(`Maximum JSON-RPC message length must be a positive integer`);this.maxMessageLength=n}onError(e){return this.errorListeners.add(e),()=>this.errorListeners.delete(e)}async start(){await this.components.registrar.handle(this.config.protocol,this.handleProtocol.bind(this),{runOnLimitedConnection:!0})}async stop(){await this.components.registrar.unhandle(this.config.protocol)}async handleProtocol(e,t){let n=!1;try{let r=(0,u.lpStream)(e,{maxDataLength:this.maxMessageLength}),i=h(l.ccc.bytesTo((await r.read()).subarray(),`utf8`)),a;try{a={jsonrpc:`2.0`,id:i.id,result:await this.handleRequest({connection:t,peerId:t.remotePeer,payload:i})}}catch(e){n=_(e);let t=m(e);a={jsonrpc:`2.0`,id:i.id,error:{code:t.code,message:t.message,data:t.data}}}await r.write(l.ccc.bytesFrom(JSON.stringify(a),`utf8`)),await e.close()}catch(t){if(n)return;this.handleError(t,e)}}handleError(e,t){let n=g(e);return t?.abort(n),this.errorListeners.forEach(e=>e(n)),n}};function m(e){return e instanceof l.JsonRpcError?e:new l.JsonRpcError({code:-32603,message:g(e).message})}function h(e){let t=JSON.parse(e);if(t?.jsonrpc!==`2.0`||typeof t.id!=`number`&&typeof t.id!=`string`||typeof t.method!=`string`)throw Error(`Invalid JSON-RPC request`);return t}function g(e){return e instanceof Error?e:Error(`JSON-RPC request failed`)}function _(e){return e instanceof Error&&(e.name===`AbortError`||e.name===`TimeoutError`)}function v(e,t){return n=>new class extends p{handleRequest(e){return t.call(this,e)}}(n,e)}var y=class{constructor(e,t,n){this.node=e,this.peerId=t,this.config=n;let r=n.maxMessageLength??1048576;if(!Number.isSafeInteger(r)||r<=0)throw Error(`Maximum JSON-RPC message length must be a positive integer`);this.maxMessageLength=r;let i=n.timeout??12e4;if(!Number.isSafeInteger(i)||i<=0)throw Error(`JSON-RPC request timeout must be a positive integer`);this.timeout=i}async request(e){let t=AbortSignal.timeout(this.timeout),n=this.config.signal?l.ccc.abortSignalAny([this.config.signal,t]):t,r,i;try{r=await this.openStream(n);let t=(0,u.lpStream)(r,{maxDataLength:this.maxMessageLength});await t.write(l.ccc.bytesFrom(JSON.stringify(e),`utf8`),{signal:n}),await r.close({signal:n}),i=JSON.parse(l.ccc.bytesTo((await t.read({signal:n})).subarray(),`utf8`)),await r.closeRead({signal:n}),n.throwIfAborted()}catch(e){let t=x(e);throw r?.abort(t),t}return this.config.onResponse?.(i),i}async openStream(e){for(let t of this.connections())try{return await t.newStream(this.config.protocol,{runOnLimitedConnection:!0,signal:e})}catch{e.throwIfAborted()}return this.node.dialProtocol(this.peerId,this.config.protocol,{runOnLimitedConnection:!0,signal:e})}connections(){return this.node.getConnections(this.peerId).filter(({status:e})=>e===`open`).sort(b)}};function b(e,t){return e.direct===t.direct?e.rtt===void 0?t.rtt===void 0?0:1:t.rtt===void 0?-1:e.rtt-t.rtt:e.direct?-1:1}function x(e){return e instanceof Error?e:Error(`JSON-RPC request failed`)}const S=16384,C=[`addresses`,`role`,`secret`];var w=class extends Error{constructor(e,t){super(`Expected a ${e} pairing endpoint, received ${t?`role "${t}"`:`an endpoint without a role`}`),this.expectedRole=e,this.actualRole=t,this.name=`PairingEndpointRoleError`}};async function T(e,t,n,r){if(t.length===0)throw Error(`Pairing endpoint requires at least one address`);if(t.length>16)throw Error(`Pairing endpoint supports at most 16 addresses`);let i=O(t);if(i.byteLength>S)throw Error(`Pairing endpoint address data is too large`);let a=await A(i,new CompressionStream(`deflate`)),o=l.ccc.bytesTo(a,`base64url`),s=new URL(e),{params:c,route:u}=D(s);return C.forEach(e=>{c.delete(e)}),r&&c.set(`role`,r),c.set(`addresses`,o),c.set(`secret`,n),s.hash=`${u}?${c.toString()}`,s.toString()}async function E(e,t){let{params:n}=D(new URL(e.trim()));if(t){let e=n.get(`role`)?.trim()||void 0;if(e!==t)throw new w(t,e)}let r=n.get(`addresses`)?.trim(),i=n.get(`secret`)?.trim();if(!r||!i)throw Error(`Pairing endpoint is incomplete`);return{addresses:await k(r),secret:i}}function D(e){let t=e.hash.slice(1),n=t.indexOf(`?`);return n===-1?{params:new URLSearchParams,route:t}:{params:new URLSearchParams(t.slice(n+1)),route:t.slice(0,n)}}function O(e){return l.ccc.bytesConcat(...f.encode(e.map(e=>e.bytes)))}async function k(e){try{let t=await A(l.ccc.bytesFrom(e,`base64url`),new DecompressionStream(`deflate`),S),n=[];for(let e of f.decode([t],{maxDataLength:S})){if(n.length>=16)throw Error(`Too many addresses`);n.push((0,d.multiaddr)(e.subarray()))}if(n.length===0)throw Error(`Missing addresses`);return n}catch(e){throw Error(`Pairing endpoint contains invalid compressed addresses`,{cause:e})}}async function A(e,t,n){let r=n===void 0?t.readable:t.readable.pipeThrough(j(n)),i=new Response(r).arrayBuffer().then(e=>({buffer:e,ok:!0}),e=>({error:e,ok:!1})),a=t.writable.getWriter();try{await a.write(Uint8Array.from(e)),await a.close()}catch(e){let t=await i;throw t.ok?e:t.error}let o=await i;if(!o.ok)throw o.error;return new Uint8Array(o.buffer)}function j(e){let t=0;return new TransformStream({transform(n,r){if(n.byteLength>e-t)throw Error(`Decompressed address data is too large`);t+=n.byteLength,r.enqueue(n)}})}const M=1024;var N=class{constructor(e,t){if(this.components=e,this.config=t,this.currentSecret=U(),this.errorListeners=new Set,this.pairedListeners=new Set,this.unpairedListeners=new Set,this.secretChangedListeners=new Set,this.pairingTimeouts=new Map,t.pairedPeerTimeoutMs<=0)throw Error(`Paired peer timeout must be greater than zero`)}get secret(){return this.currentSecret}onError(e){return this.errorListeners.add(e),()=>this.errorListeners.delete(e)}onPaired(e){return this.pairedListeners.add(e),()=>this.pairedListeners.delete(e)}onUnpaired(e){return this.unpairedListeners.add(e),()=>this.unpairedListeners.delete(e)}onSecretChanged(e){return this.secretChangedListeners.add(e),()=>this.secretChangedListeners.delete(e)}async start(){await this.components.registrar.handle(this.config.protocol,this.handleProtocol.bind(this),{runOnLimitedConnection:!0})}async stop(){await this.components.registrar.unhandle(this.config.protocol),this.pairingTimeouts.forEach(e=>clearTimeout(e)),this.pairingTimeouts.clear()}isPaired(e){return this.pairingTimeouts.has(e.toString())}refresh(e){let t=e.toString(),n=this.pairingTimeouts.get(t);n!==void 0&&(clearTimeout(n),this.pairingTimeouts.set(t,this.createPeerTimeout(e)))}async pair(e,t={}){let n,r;try{let i=await this.openStream(e.addresses,t);if(n=i.stream,!i.connection)throw Error(`Could not determine remote peer`);this.addPairedPeer(i.connection.remotePeer)&&(r=i.connection.remotePeer);let a=await F(n,I(e.secret,this.config.name,this.addresses()),t);if(t.signal?.throwIfAborted(),n=void 0,!a.ok)throw Error(a.error);return r&&(this.rotateSecret(),this.notifyPairedPeer(r,a.name)),i.connection.remotePeer}catch(e){throw r&&this.removePairedPeer(r,!1),this.handleError(e,n)}}async unpair(e){if(!this.removePairedPeer(e))return;let t={signal:AbortSignal.timeout(1e4)},n;try{let r=await this.openStream(e,t);n=r.stream;let i=await F(r.stream,{type:`unpair`},t);if(n=void 0,!i.ok)throw Error(i.error)}catch(e){this.handleError(e,n)}}async handleProtocol(e,t){let n;try{let r=(0,u.lpStream)(e,{maxDataLength:M}),i=z((await r.read()).subarray());if(i.type===`unpair`){this.removePairedPeer(t.remotePeer),await R(r,e,{ok:!0});return}if(i.secret!==this.secret||!this.canPair(t.remotePeer)){await R(r,e,{ok:!1,error:`Pairing rejected`});return}let a=V(i.addresses,t.remotePeer);this.rotateSecret(),this.addPairedPeer(t.remotePeer)&&(n=t.remotePeer),await R(r,e,{ok:!0,name:this.config.name}),n&&(this.notifyPairedPeer(n,i.name),this.openFallbackConnection(a))}catch(t){n&&this.removePairedPeer(n,!1),this.handleError(t,e)}}addPairedPeer(e){let t=e.toString(),n=this.pairingTimeouts.get(t);return n!==void 0&&clearTimeout(n),this.pairingTimeouts.set(t,this.createPeerTimeout(e)),n===void 0}notifyPairedPeer(e,t){this.pairedListeners.forEach(n=>n(e,t))}addresses(){return this.components.addressManager?.getAddresses()??[]}async openFallbackConnection(e){if(e.length!==0)try{await this.components.connectionManager.openConnection(e)}catch{}}rotateSecret(){this.currentSecret=U(),this.secretChangedListeners.forEach(e=>e(this.currentSecret))}removePairedPeer(e,t=!0){let n=e.toString(),r=this.pairingTimeouts.get(n);return r!==void 0&&(clearTimeout(r),this.pairingTimeouts.delete(n),t&&this.unpairedListeners.forEach(t=>t(e)),!0)}createPeerTimeout(e){return setTimeout(()=>{this.removePairedPeer(e)},this.config.pairedPeerTimeoutMs)}async openStream(e,t){let n;return{stream:await this.components.connectionManager.openStream(e,this.config.protocol,{runOnLimitedConnection:!0,signal:t.signal,onProgress(e){e.type===`connection:opened-stream`&&(n=e.detail.connection)}}),connection:n}}handleError(e,t){let n=W(e,`Pairing operation failed`);return t?.abort(n),this.errorListeners.forEach(e=>e(n)),n}};function P(e,t){return n=>new class extends N{canPair(e){return t.call(this,e)}}(n,e)}async function F(e,t,n){let r=(0,u.lpStream)(e,{maxDataLength:M});await r.write(l.ccc.bytesFrom(JSON.stringify(t),`utf8`),{signal:n.signal}),await e.close(n);let i=H((await r.read(n)).subarray());return await e.closeRead(n),i}function I(e,t,n){let r=n.map(e=>e.toString()),i={type:`pair`,secret:e,name:t,addresses:r};for(;r.length>0&&!L(i);)r.pop();if(L(i)||delete i.addresses,L(i)||delete i.name,!L(i))throw Error(`Pairing request is too large`);return i}function L(e){return l.ccc.bytesFrom(JSON.stringify(e),`utf8`).byteLength<=M}async function R(e,t,n){await e.write(l.ccc.bytesFrom(JSON.stringify(n),`utf8`)),await t.close()}function z(e){let t=JSON.parse(l.ccc.bytesTo(e,`utf8`));if(t.type===`pair`&&typeof t.secret==`string`&&t.secret)return{type:`pair`,secret:t.secret,name:typeof t.name==`string`?t.name:void 0,addresses:B(t.addresses)};if(t.type===`unpair`)return{type:`unpair`};throw Error(`Invalid pairing request`)}function B(e){if(Array.isArray(e))return e.filter(e=>typeof e==`string`&&e.length>0)}function V(e,t){return(e??[]).flatMap(e=>{try{let n=(0,d.multiaddr)(e);return n.getComponents().filter(({code:e})=>e===d.CODE_P2P).at(-1)?.value===t.toString()?[n]:[]}catch{return[]}})}function H(e){let t=JSON.parse(l.ccc.bytesTo(e,`utf8`));if(t.ok===!0)return{ok:!0,name:typeof t.name==`string`?t.name:void 0};if(t.ok===!1&&typeof t.error==`string`)return{ok:!1,error:t.error};throw Error(`Invalid pairing response`)}function U(){return l.ccc.bytesTo(crypto.getRandomValues(new Uint8Array(16)),`base64url`)}function W(e,t){return e instanceof Error?e:Error(t)}var G=o({JsonRpcService:()=>p,JsonRpcTransportLibp2p:()=>y,PairingEndpointRoleError:()=>w,PairingService:()=>N,decodePairingEndpoint:()=>E,encodePairingEndpoint:()=>T,jsonRpcService:()=>v,pairingService:()=>P});exports.JsonRpcService=p,exports.JsonRpcTransportLibp2p=y,exports.PairingEndpointRoleError=w,exports.PairingService=N,exports.decodePairingEndpoint=E,exports.encodePairingEndpoint=T,exports.jsonRpcService=v,exports.pairingService=P,Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return G}});
2
2
  //# sourceMappingURL=barrel.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"barrel.js","names":["DEFAULT_MAX_MESSAGE_LENGTH","lpStream","ccc","asJsonRpcError","JsonRpcError","ccc","lpStream","ccc","lp","multiaddr","lpStream","ccc"],"sources":["../src/jsonRpcService.ts","../src/jsonRpcTransport.ts","../src/pairingEndpoint.ts","../src/pairingService.ts","../src/barrel.ts"],"sourcesContent":["import { ccc, JsonRpcError } from \"@ckb-ccc/core\";\nimport type { Connection, PeerId, Stream } from \"@libp2p/interface\";\nimport type { Registrar } from \"@libp2p/interface-internal\";\nimport { lpStream } from \"@libp2p/utils\";\n\nconst DEFAULT_MAX_MESSAGE_LENGTH = 1024 * 1024;\n\nexport type JsonRpcServiceComponents = {\n registrar: Registrar;\n};\n\nexport type JsonRpcServiceConfig = {\n protocol: string;\n maxMessageLength?: number;\n};\n\nexport type JsonRpcRequest = {\n peerId: PeerId;\n payload: ccc.JsonRpcPayload;\n};\n\nexport type JsonRpcRequestHandler<\n Components extends JsonRpcServiceComponents = JsonRpcServiceComponents,\n> = (this: JsonRpcService<Components>, request: JsonRpcRequest) => unknown;\n\nexport abstract class JsonRpcService<\n Components extends JsonRpcServiceComponents = JsonRpcServiceComponents,\n> {\n private readonly errorListeners = new Set<(error: Error) => void>();\n readonly maxMessageLength: number;\n\n constructor(\n readonly components: Components,\n readonly config: JsonRpcServiceConfig,\n ) {\n const maxMessageLength =\n config.maxMessageLength ?? DEFAULT_MAX_MESSAGE_LENGTH;\n if (!Number.isSafeInteger(maxMessageLength) || maxMessageLength <= 0) {\n throw new Error(\n \"Maximum JSON-RPC message length must be a positive integer\",\n );\n }\n\n this.maxMessageLength = maxMessageLength;\n }\n\n protected abstract handleRequest(request: JsonRpcRequest): unknown;\n\n onError(listener: (error: Error) => void) {\n this.errorListeners.add(listener);\n return () => this.errorListeners.delete(listener);\n }\n\n async start() {\n await this.components.registrar.handle(\n this.config.protocol,\n this.handleProtocol.bind(this),\n { runOnLimitedConnection: true },\n );\n }\n\n async stop() {\n await this.components.registrar.unhandle(this.config.protocol);\n }\n\n private async handleProtocol(stream: Stream, connection: Connection) {\n let requestAborted = false;\n\n try {\n const rpcStream = lpStream(stream, {\n maxDataLength: this.maxMessageLength,\n });\n const payload = parseJsonRpcRequest(\n ccc.bytesTo((await rpcStream.read()).subarray(), \"utf8\"),\n );\n\n let response: ccc.JsonRpcResponse;\n try {\n response = {\n jsonrpc: \"2.0\",\n id: payload.id,\n result: await this.handleRequest({\n peerId: connection.remotePeer,\n payload,\n }),\n };\n } catch (cause) {\n requestAborted = isAbortError(cause);\n const error = toJsonRpcError(cause);\n response = {\n jsonrpc: \"2.0\",\n id: payload.id,\n error: {\n code: error.code,\n message: error.message,\n data: error.data,\n },\n };\n }\n\n await rpcStream.write(ccc.bytesFrom(JSON.stringify(response), \"utf8\"));\n await stream.close();\n } catch (cause) {\n if (requestAborted) {\n return;\n }\n this.handleError(cause, stream);\n }\n }\n\n private handleError(cause: unknown, stream?: Stream) {\n const error = asJsonRpcError(cause);\n\n stream?.abort(error);\n this.errorListeners.forEach((listener) => listener(error));\n return error;\n }\n}\n\nfunction toJsonRpcError(cause: unknown) {\n if (cause instanceof JsonRpcError) {\n return cause;\n }\n\n return new JsonRpcError({\n code: -32603,\n message: asJsonRpcError(cause).message,\n });\n}\n\nfunction parseJsonRpcRequest(data: string) {\n const payload = JSON.parse(data) as Partial<ccc.JsonRpcPayload>;\n if (\n payload?.jsonrpc !== \"2.0\" ||\n (typeof payload.id !== \"number\" && typeof payload.id !== \"string\") ||\n typeof payload.method !== \"string\"\n ) {\n throw new Error(\"Invalid JSON-RPC request\");\n }\n\n return payload as ccc.JsonRpcPayload;\n}\n\nfunction asJsonRpcError(cause: unknown) {\n return cause instanceof Error ? cause : new Error(\"JSON-RPC request failed\");\n}\n\nfunction isAbortError(cause: unknown) {\n return (\n cause instanceof Error &&\n (cause.name === \"AbortError\" || cause.name === \"TimeoutError\")\n );\n}\n\nexport function jsonRpcService<\n Components extends JsonRpcServiceComponents = JsonRpcServiceComponents,\n>(\n config: JsonRpcServiceConfig,\n handler: JsonRpcRequestHandler<Components>,\n): (components: Components) => JsonRpcService<Components> {\n return (components: Components) =>\n new (class extends JsonRpcService<Components> {\n protected handleRequest(request: JsonRpcRequest) {\n return handler.call(this, request);\n }\n })(components, config);\n}\n","import { ccc } from \"@ckb-ccc/core\";\nimport type { Libp2p, PeerId, Stream } from \"@libp2p/interface\";\nimport { lpStream } from \"@libp2p/utils\";\n\nconst DEFAULT_MAX_MESSAGE_LENGTH = 1024 * 1024;\nconst DEFAULT_TIMEOUT = 120_000;\n\nexport type JsonRpcTransportLibp2pConfig = {\n protocol: string;\n maxMessageLength?: number;\n timeout?: number;\n signal?: AbortSignal;\n onResponse?: (response: ccc.JsonRpcResponse) => void;\n};\n\nexport class JsonRpcTransportLibp2p implements ccc.JsonRpcTransport {\n private readonly maxMessageLength: number;\n private readonly timeout: number;\n\n constructor(\n private readonly node: Libp2p,\n readonly peerId: PeerId,\n private readonly config: JsonRpcTransportLibp2pConfig,\n ) {\n const maxMessageLength =\n config.maxMessageLength ?? DEFAULT_MAX_MESSAGE_LENGTH;\n if (!Number.isSafeInteger(maxMessageLength) || maxMessageLength <= 0) {\n throw new Error(\n \"Maximum JSON-RPC message length must be a positive integer\",\n );\n }\n\n this.maxMessageLength = maxMessageLength;\n\n const timeout = config.timeout ?? DEFAULT_TIMEOUT;\n if (!Number.isSafeInteger(timeout) || timeout <= 0) {\n throw new Error(\"JSON-RPC request timeout must be a positive integer\");\n }\n\n this.timeout = timeout;\n }\n\n async request(payload: ccc.JsonRpcPayload): Promise<ccc.JsonRpcResponse> {\n const timeoutSignal = AbortSignal.timeout(this.timeout);\n const signal = this.config.signal\n ? ccc.abortSignalAny([this.config.signal, timeoutSignal])\n : timeoutSignal;\n\n let stream: Stream | undefined;\n let response: ccc.JsonRpcResponse;\n try {\n // dialProtocol handles the signal, so no separate entry check is needed.\n stream = await this.node.dialProtocol(this.peerId, this.config.protocol, {\n runOnLimitedConnection: true,\n signal,\n });\n\n const rpcStream = lpStream(stream, {\n maxDataLength: this.maxMessageLength,\n });\n\n await rpcStream.write(ccc.bytesFrom(JSON.stringify(payload), \"utf8\"), {\n signal,\n });\n await stream.close({ signal });\n\n response = JSON.parse(\n ccc.bytesTo((await rpcStream.read({ signal })).subarray(), \"utf8\"),\n ) as ccc.JsonRpcResponse;\n await stream.closeRead({ signal });\n signal.throwIfAborted();\n } catch (cause) {\n const error = asJsonRpcError(cause);\n stream?.abort(error);\n throw error;\n }\n\n this.config.onResponse?.(response);\n return response;\n }\n}\n\nfunction asJsonRpcError(cause: unknown) {\n return cause instanceof Error ? cause : new Error(\"JSON-RPC request failed\");\n}\n","import { ccc } from \"@ckb-ccc/core\";\nimport { multiaddr, type Multiaddr } from \"@multiformats/multiaddr\";\nimport * as lp from \"it-length-prefixed\";\nimport type { PairingTarget } from \"./pairingService.js\";\n\nconst MAX_DECOMPRESSED_ADDRESSES_LENGTH = 16 * 1024;\nconst MAX_ADDRESSES = 16;\nconst PAIRING_PARAMETER_NAMES = [\"addresses\", \"role\", \"secret\"] as const;\n\nexport class PairingEndpointRoleError extends Error {\n constructor(\n readonly expectedRole: string,\n readonly actualRole: string | undefined,\n ) {\n super(\n `Expected a ${expectedRole} pairing endpoint, received ${actualRole ? `role \"${actualRole}\"` : \"an endpoint without a role\"}`,\n );\n this.name = \"PairingEndpointRoleError\";\n }\n}\n\nexport async function encodePairingEndpoint(\n endpointUrl: string,\n addresses: readonly Multiaddr[],\n secret: string,\n role?: string,\n) {\n if (addresses.length === 0) {\n throw new Error(\"Pairing endpoint requires at least one address\");\n }\n if (addresses.length > MAX_ADDRESSES) {\n throw new Error(\n `Pairing endpoint supports at most ${MAX_ADDRESSES} addresses`,\n );\n }\n\n const addressBytes = encodeAddresses(addresses);\n if (addressBytes.byteLength > MAX_DECOMPRESSED_ADDRESSES_LENGTH) {\n throw new Error(\"Pairing endpoint address data is too large\");\n }\n\n const compressedAddressBytes = await transformBytes(\n addressBytes,\n new CompressionStream(\"deflate\"),\n );\n\n const encodedAddresses = ccc.bytesTo(compressedAddressBytes, \"base64url\");\n\n const url = new URL(endpointUrl);\n const { params, route } = fragmentParameters(url);\n\n PAIRING_PARAMETER_NAMES.forEach((name) => {\n params.delete(name);\n });\n if (role) {\n params.set(\"role\", role);\n }\n params.set(\"addresses\", encodedAddresses);\n params.set(\"secret\", secret);\n url.hash = `${route}?${params.toString()}`;\n\n return url.toString();\n}\n\nexport async function decodePairingEndpoint(\n endpoint: string,\n expectedRole?: string,\n): Promise<PairingTarget> {\n const url = new URL(endpoint.trim());\n const { params } = fragmentParameters(url);\n if (expectedRole) {\n const actualRole = params.get(\"role\")?.trim() || undefined;\n if (actualRole !== expectedRole) {\n throw new PairingEndpointRoleError(expectedRole, actualRole);\n }\n }\n const compressedAddresses = params.get(\"addresses\")?.trim();\n const secret = params.get(\"secret\")?.trim();\n\n if (!compressedAddresses || !secret) {\n throw new Error(\"Pairing endpoint is incomplete\");\n }\n\n const addresses = await decodeCompressedAddresses(compressedAddresses);\n\n return { addresses, secret };\n}\n\nfunction fragmentParameters(url: URL) {\n const fragment = url.hash.slice(1);\n const separator = fragment.indexOf(\"?\");\n if (separator === -1) {\n return { params: new URLSearchParams(), route: fragment };\n }\n\n return {\n params: new URLSearchParams(fragment.slice(separator + 1)),\n route: fragment.slice(0, separator),\n };\n}\n\nfunction encodeAddresses(addresses: readonly Multiaddr[]): Uint8Array {\n return ccc.bytesConcat(\n ...lp.encode(addresses.map((address) => address.bytes)),\n );\n}\n\nasync function decodeCompressedAddresses(value: string): Promise<Multiaddr[]> {\n try {\n const compressedBytes = ccc.bytesFrom(value, \"base64url\");\n const bytes = await transformBytes(\n compressedBytes,\n new DecompressionStream(\"deflate\"),\n MAX_DECOMPRESSED_ADDRESSES_LENGTH,\n );\n const addresses: Multiaddr[] = [];\n for (const address of lp.decode([bytes], {\n maxDataLength: MAX_DECOMPRESSED_ADDRESSES_LENGTH,\n })) {\n if (addresses.length >= MAX_ADDRESSES) {\n throw new Error(\"Too many addresses\");\n }\n addresses.push(multiaddr(address.subarray()));\n }\n\n if (addresses.length === 0) {\n throw new Error(\"Missing addresses\");\n }\n return addresses;\n } catch (cause) {\n throw new Error(\"Pairing endpoint contains invalid compressed addresses\", {\n cause,\n });\n }\n}\n\nasync function transformBytes(\n bytes: Uint8Array,\n transform: CompressionStream | DecompressionStream,\n maxOutputLength?: number,\n) {\n const readable =\n maxOutputLength === undefined\n ? transform.readable\n : transform.readable.pipeThrough(limitBytes(maxOutputLength));\n const output = new Response(readable).arrayBuffer().then(\n (buffer) => ({ buffer, ok: true }) as const,\n (error: unknown) => ({ error, ok: false }) as const,\n );\n const writer = transform.writable.getWriter();\n try {\n await writer.write(Uint8Array.from(bytes));\n await writer.close();\n } catch (cause) {\n const result = await output;\n if (!result.ok) {\n throw result.error;\n }\n throw cause;\n }\n\n const result = await output;\n if (!result.ok) {\n throw result.error;\n }\n return new Uint8Array(result.buffer);\n}\n\nfunction limitBytes(maxLength: number) {\n let length = 0;\n\n return new TransformStream<Uint8Array, Uint8Array>({\n transform(chunk, controller) {\n if (chunk.byteLength > maxLength - length) {\n throw new Error(\"Decompressed address data is too large\");\n }\n length += chunk.byteLength;\n controller.enqueue(chunk);\n },\n });\n}\n","import { ccc } from \"@ckb-ccc/core\";\nimport type {\n AbortOptions,\n Connection,\n PeerId,\n Stream,\n} from \"@libp2p/interface\";\nimport type { ConnectionManager, Registrar } from \"@libp2p/interface-internal\";\nimport { lpStream } from \"@libp2p/utils\";\nimport type { Multiaddr } from \"@multiformats/multiaddr\";\n\nconst MAX_PAIRING_MESSAGE_LENGTH = 1024;\nconst UNPAIR_TIMEOUT_MS = 10_000;\n\nexport type PairingServiceComponents = {\n connectionManager: ConnectionManager;\n registrar: Registrar;\n};\n\nexport type PairingServiceConfig = {\n /** Optional self-reported label shared with the paired peer. */\n name?: string;\n protocol: string;\n pairedPeerTimeoutMs: number;\n};\n\nexport type PairingTarget = {\n addresses: Multiaddr[];\n secret: string;\n};\n\nexport type PairingGuard<\n Components extends PairingServiceComponents = PairingServiceComponents,\n> = (this: PairingService<Components>, peerId: PeerId) => boolean;\n\ntype PairingRequest =\n { type: \"pair\"; secret: string; name?: string } | { type: \"unpair\" };\n\ntype PairingResponse =\n { ok: true; name?: string } | { ok: false; error: string };\n\nexport abstract class PairingService<\n Components extends PairingServiceComponents = PairingServiceComponents,\n> {\n private currentSecret = createSecret();\n private readonly errorListeners = new Set<(error: Error) => void>();\n private readonly pairedListeners = new Set<\n (peerId: PeerId, name?: string) => void\n >();\n private readonly unpairedListeners = new Set<(peerId: PeerId) => void>();\n private readonly secretChangedListeners = new Set<(secret: string) => void>();\n private readonly pairingTimeouts = new Map<\n string,\n ReturnType<typeof setTimeout>\n >();\n\n constructor(\n readonly components: Components,\n readonly config: PairingServiceConfig,\n ) {\n if (config.pairedPeerTimeoutMs <= 0) {\n throw new Error(\"Paired peer timeout must be greater than zero\");\n }\n }\n\n protected abstract canPair(peerId: PeerId): boolean;\n\n /** The credential to include in the currently issued pairing endpoint. */\n get secret() {\n return this.currentSecret;\n }\n\n onError(listener: (error: Error) => void) {\n this.errorListeners.add(listener);\n return () => this.errorListeners.delete(listener);\n }\n\n onPaired(listener: (peerId: PeerId, name?: string) => void) {\n this.pairedListeners.add(listener);\n return () => this.pairedListeners.delete(listener);\n }\n\n onUnpaired(listener: (peerId: PeerId) => void) {\n this.unpairedListeners.add(listener);\n return () => this.unpairedListeners.delete(listener);\n }\n\n onSecretChanged(listener: (secret: string) => void) {\n this.secretChangedListeners.add(listener);\n return () => this.secretChangedListeners.delete(listener);\n }\n\n async start() {\n await this.components.registrar.handle(\n this.config.protocol,\n this.handleProtocol.bind(this),\n { runOnLimitedConnection: true },\n );\n }\n\n async stop() {\n await this.components.registrar.unhandle(this.config.protocol);\n\n this.pairingTimeouts.forEach((timeout) => clearTimeout(timeout));\n this.pairingTimeouts.clear();\n }\n\n isPaired(peerId: PeerId) {\n return this.pairingTimeouts.has(peerId.toString());\n }\n\n refresh(peerId: PeerId) {\n const key = peerId.toString();\n const timeout = this.pairingTimeouts.get(key);\n if (timeout === undefined) {\n return;\n }\n\n clearTimeout(timeout);\n this.pairingTimeouts.set(key, this.createPeerTimeout(peerId));\n }\n\n async pair(\n target: PairingTarget,\n options: AbortOptions = {},\n ): Promise<PeerId> {\n let stream: Stream | undefined;\n let addedPeer: PeerId | undefined;\n\n try {\n const opened = await this.openStream(target.addresses, options);\n stream = opened.stream;\n\n if (!opened.connection) {\n throw new Error(\"Could not determine remote peer\");\n }\n\n if (this.addPairedPeer(opened.connection.remotePeer)) {\n addedPeer = opened.connection.remotePeer;\n }\n\n const response = await exchange(\n stream,\n {\n type: \"pair\",\n secret: target.secret,\n name: this.config.name,\n },\n options,\n );\n options.signal?.throwIfAborted();\n stream = undefined;\n\n if (!response.ok) {\n throw new Error(response.error);\n }\n\n if (addedPeer) {\n this.rotateSecret();\n this.notifyPairedPeer(addedPeer, response.name);\n }\n return opened.connection.remotePeer;\n } catch (cause) {\n if (addedPeer) {\n this.removePairedPeer(addedPeer, false);\n }\n throw this.handleError(cause, stream);\n }\n }\n\n async unpair(peerId: PeerId) {\n if (!this.removePairedPeer(peerId)) {\n return;\n }\n\n const options = { signal: AbortSignal.timeout(UNPAIR_TIMEOUT_MS) };\n\n let stream: Stream | undefined;\n try {\n const opened = await this.openStream(peerId, options);\n stream = opened.stream;\n const response = await exchange(\n opened.stream,\n { type: \"unpair\" },\n options,\n );\n stream = undefined;\n if (!response.ok) {\n throw new Error(response.error);\n }\n } catch (cause) {\n this.handleError(cause, stream);\n }\n }\n\n private async handleProtocol(stream: Stream, connection: Connection) {\n let addedPeer: PeerId | undefined;\n\n try {\n const pairingStream = lpStream(stream, {\n maxDataLength: MAX_PAIRING_MESSAGE_LENGTH,\n });\n const request = parsePairingRequest(\n (await pairingStream.read()).subarray(),\n );\n\n if (request.type === \"unpair\") {\n this.removePairedPeer(connection.remotePeer);\n await writeResponse(pairingStream, stream, { ok: true });\n return;\n }\n\n if (\n request.secret !== this.secret ||\n !this.canPair(connection.remotePeer)\n ) {\n await writeResponse(pairingStream, stream, {\n ok: false,\n error: \"Pairing rejected\",\n });\n return;\n }\n\n // Consume the credential before the next asynchronous boundary so two\n // concurrent requests cannot both pair with the same endpoint.\n this.rotateSecret();\n\n if (this.addPairedPeer(connection.remotePeer)) {\n addedPeer = connection.remotePeer;\n }\n await writeResponse(pairingStream, stream, {\n ok: true,\n name: this.config.name,\n });\n if (addedPeer) {\n this.notifyPairedPeer(addedPeer, request.name);\n }\n } catch (cause) {\n if (addedPeer) {\n this.removePairedPeer(addedPeer, false);\n }\n this.handleError(cause, stream);\n }\n }\n\n private addPairedPeer(peerId: PeerId) {\n const key = peerId.toString();\n const timeout = this.pairingTimeouts.get(key);\n if (timeout !== undefined) {\n clearTimeout(timeout);\n }\n\n this.pairingTimeouts.set(key, this.createPeerTimeout(peerId));\n return timeout === undefined;\n }\n\n private notifyPairedPeer(peerId: PeerId, name?: string) {\n this.pairedListeners.forEach((listener) => listener(peerId, name));\n }\n\n private rotateSecret() {\n this.currentSecret = createSecret();\n this.secretChangedListeners.forEach((listener) =>\n listener(this.currentSecret),\n );\n }\n\n private removePairedPeer(peerId: PeerId, notify = true) {\n const key = peerId.toString();\n const timeout = this.pairingTimeouts.get(key);\n if (timeout === undefined) {\n return false;\n }\n\n clearTimeout(timeout);\n this.pairingTimeouts.delete(key);\n\n if (notify) {\n this.unpairedListeners.forEach((listener) => listener(peerId));\n }\n return true;\n }\n\n private createPeerTimeout(peerId: PeerId) {\n return setTimeout(() => {\n this.removePairedPeer(peerId);\n }, this.config.pairedPeerTimeoutMs);\n }\n\n private async openStream(\n target: PeerId | Multiaddr | Multiaddr[],\n options: AbortOptions,\n ) {\n let connection: Connection | undefined;\n const stream = await this.components.connectionManager.openStream(\n target,\n this.config.protocol,\n {\n runOnLimitedConnection: true,\n signal: options.signal,\n onProgress(event) {\n if (event.type === \"connection:opened-stream\") {\n connection = event.detail.connection;\n }\n },\n },\n );\n\n return { stream, connection };\n }\n\n private handleError(cause: unknown, stream?: Stream) {\n const error = asError(cause, \"Pairing operation failed\");\n\n stream?.abort(error);\n this.errorListeners.forEach((listener) => listener(error));\n return error;\n }\n}\n\nexport function pairingService<\n Components extends PairingServiceComponents = PairingServiceComponents,\n>(\n config: PairingServiceConfig,\n canPair: PairingGuard<Components>,\n): (components: Components) => PairingService<Components> {\n return (components: Components) =>\n new (class extends PairingService<Components> {\n protected canPair(peerId: PeerId) {\n return canPair.call(this, peerId);\n }\n })(components, config);\n}\n\nasync function exchange(\n stream: Stream,\n request: PairingRequest,\n options: AbortOptions,\n) {\n const pairingStream = lpStream(stream, {\n maxDataLength: MAX_PAIRING_MESSAGE_LENGTH,\n });\n\n await pairingStream.write(ccc.bytesFrom(JSON.stringify(request), \"utf8\"), {\n signal: options.signal,\n });\n await stream.close(options);\n\n const response = parsePairingResponse(\n (await pairingStream.read(options)).subarray(),\n );\n await stream.closeRead(options);\n return response;\n}\n\nasync function writeResponse(\n pairingStream: ReturnType<typeof lpStream>,\n stream: Stream,\n response: PairingResponse,\n) {\n await pairingStream.write(ccc.bytesFrom(JSON.stringify(response), \"utf8\"));\n await stream.close();\n}\n\nfunction parsePairingRequest(data: Uint8Array): PairingRequest {\n const request = JSON.parse(\n ccc.bytesTo(data, \"utf8\"),\n ) as Partial<PairingRequest>;\n if (\n request.type === \"pair\" &&\n typeof request.secret === \"string\" &&\n request.secret\n ) {\n return {\n type: \"pair\",\n secret: request.secret,\n name: typeof request.name === \"string\" ? request.name : undefined,\n };\n }\n\n if (request.type === \"unpair\") {\n return { type: \"unpair\" };\n }\n\n throw new Error(\"Invalid pairing request\");\n}\n\nfunction parsePairingResponse(data: Uint8Array): PairingResponse {\n const response = JSON.parse(\n ccc.bytesTo(data, \"utf8\"),\n ) as Partial<PairingResponse>;\n if (response.ok === true) {\n return {\n ok: true,\n name: typeof response.name === \"string\" ? response.name : undefined,\n };\n }\n if (response.ok === false && typeof response.error === \"string\") {\n return { ok: false, error: response.error };\n }\n\n throw new Error(\"Invalid pairing response\");\n}\n\nfunction createSecret() {\n return ccc.bytesTo(crypto.getRandomValues(new Uint8Array(16)), \"base64url\");\n}\n\nfunction asError(cause: unknown, fallback: string) {\n return cause instanceof Error ? cause : new Error(fallback);\n}\n","export * from \"./jsonRpcService.js\";\nexport * from \"./jsonRpcTransport.js\";\nexport * from \"./pairingEndpoint.js\";\nexport * from \"./pairingService.js\";\n"],"mappings":"qzBAyBA,IAAsB,EAAtB,KAEE,CAIA,YACE,EACA,EACA,CAFS,KAAA,WAAA,EACA,KAAA,OAAA,EALuB,KAAA,eAAA,IAAI,IAOpC,IAAM,EACJ,EAAO,kBAAoBA,QAC7B,GAAI,CAAC,OAAO,cAAc,CAAgB,GAAK,GAAoB,EACjE,MAAU,MACR,4DACF,EAGF,KAAK,iBAAmB,CAC1B,CAIA,QAAQ,EAAkC,CAExC,OADA,KAAK,eAAe,IAAI,CAAQ,MACnB,KAAK,eAAe,OAAO,CAAQ,CAClD,CAEA,MAAM,OAAQ,CACZ,MAAM,KAAK,WAAW,UAAU,OAC9B,KAAK,OAAO,SACZ,KAAK,eAAe,KAAK,IAAI,EAC7B,CAAE,uBAAwB,EAAK,CACjC,CACF,CAEA,MAAM,MAAO,CACX,MAAM,KAAK,WAAW,UAAU,SAAS,KAAK,OAAO,QAAQ,CAC/D,CAEA,MAAc,eAAe,EAAgB,EAAwB,CACnE,IAAI,EAAiB,GAErB,GAAI,CACF,IAAM,GAAA,EAAYC,EAAAA,SAAAA,CAAS,EAAQ,CACjC,cAAe,KAAK,gBACtB,CAAC,EACK,EAAU,EACdC,EAAAA,IAAI,SAAS,MAAM,EAAU,KAAK,EAAA,CAAG,SAAS,EAAG,MAAM,CACzD,EAEI,EACJ,GAAI,CACF,EAAW,CACT,QAAS,MACT,GAAI,EAAQ,GACZ,OAAQ,MAAM,KAAK,cAAc,CAC/B,OAAQ,EAAW,WACnB,SACF,CAAC,CACH,CACF,OAAS,EAAO,CACd,EAAiB,EAAa,CAAK,EACnC,IAAM,EAAQ,EAAe,CAAK,EAClC,EAAW,CACT,QAAS,MACT,GAAI,EAAQ,GACZ,MAAO,CACL,KAAM,EAAM,KACZ,QAAS,EAAM,QACf,KAAM,EAAM,IACd,CACF,CACF,CAEA,MAAM,EAAU,MAAMA,EAAAA,IAAI,UAAU,KAAK,UAAU,CAAQ,EAAG,MAAM,CAAC,EACrE,MAAM,EAAO,MAAM,CACrB,OAAS,EAAO,CACd,GAAI,EACF,OAEF,KAAK,YAAY,EAAO,CAAM,CAChC,CACF,CAEA,YAAoB,EAAgB,EAAiB,CACnD,IAAM,EAAQC,EAAe,CAAK,EAIlC,OAFA,GAAQ,MAAM,CAAK,EACnB,KAAK,eAAe,QAAS,GAAa,EAAS,CAAK,CAAC,EAClD,CACT,CACF,EAEA,SAAS,EAAe,EAAgB,CAKtC,OAJI,aAAiBC,EAAAA,aACZ,EAGF,IAAIA,EAAAA,aAAa,CACtB,KAAM,OACN,QAASD,EAAe,CAAK,CAAC,CAAC,OACjC,CAAC,CACH,CAEA,SAAS,EAAoB,EAAc,CACzC,IAAM,EAAU,KAAK,MAAM,CAAI,EAC/B,GACE,GAAS,UAAY,OACpB,OAAO,EAAQ,IAAO,UAAY,OAAO,EAAQ,IAAO,UACzD,OAAO,EAAQ,QAAW,SAE1B,MAAU,MAAM,0BAA0B,EAG5C,OAAO,CACT,CAEA,SAASA,EAAe,EAAgB,CACtC,OAAO,aAAiB,MAAQ,EAAY,MAAM,yBAAyB,CAC7E,CAEA,SAAS,EAAa,EAAgB,CACpC,OACE,aAAiB,QAChB,EAAM,OAAS,cAAgB,EAAM,OAAS,eAEnD,CAEA,SAAgB,EAGd,EACA,EACwD,CACxD,MAAQ,IACN,IAAK,cAAc,CAA2B,CAC5C,cAAwB,EAAyB,CAC/C,OAAO,EAAQ,KAAK,KAAM,CAAO,CACnC,CACF,EAAG,EAAY,CAAM,CACzB,CCvJA,IAAa,EAAb,KAAoE,CAIlE,YACE,EACA,EACA,EACA,CAHiB,KAAA,KAAA,EACR,KAAA,OAAA,EACQ,KAAA,OAAA,EAEjB,IAAM,EACJ,EAAO,kBAAoB,QAC7B,GAAI,CAAC,OAAO,cAAc,CAAgB,GAAK,GAAoB,EACjE,MAAU,MACR,4DACF,EAGF,KAAK,iBAAmB,EAExB,IAAM,EAAU,EAAO,SAAW,KAClC,GAAI,CAAC,OAAO,cAAc,CAAO,GAAK,GAAW,EAC/C,MAAU,MAAM,qDAAqD,EAGvE,KAAK,QAAU,CACjB,CAEA,MAAM,QAAQ,EAA2D,CACvE,IAAM,EAAgB,YAAY,QAAQ,KAAK,OAAO,EAChD,EAAS,KAAK,OAAO,OACvBE,EAAAA,IAAI,eAAe,CAAC,KAAK,OAAO,OAAQ,CAAa,CAAC,EACtD,EAEA,EACA,EACJ,GAAI,CAEF,EAAS,MAAM,KAAK,KAAK,aAAa,KAAK,OAAQ,KAAK,OAAO,SAAU,CACvE,uBAAwB,GACxB,QACF,CAAC,EAED,IAAM,GAAA,EAAYC,EAAAA,SAAAA,CAAS,EAAQ,CACjC,cAAe,KAAK,gBACtB,CAAC,EAED,MAAM,EAAU,MAAMD,EAAAA,IAAI,UAAU,KAAK,UAAU,CAAO,EAAG,MAAM,EAAG,CACpE,QACF,CAAC,EACD,MAAM,EAAO,MAAM,CAAE,QAAO,CAAC,EAE7B,EAAW,KAAK,MACdA,EAAAA,IAAI,SAAS,MAAM,EAAU,KAAK,CAAE,QAAO,CAAC,EAAA,CAAG,SAAS,EAAG,MAAM,CACnE,EACA,MAAM,EAAO,UAAU,CAAE,QAAO,CAAC,EACjC,EAAO,eAAe,CACxB,OAAS,EAAO,CACd,IAAM,EAAQ,EAAe,CAAK,EAElC,MADA,GAAQ,MAAM,CAAK,EACb,CACR,CAGA,OADA,KAAK,OAAO,aAAa,CAAQ,EAC1B,CACT,CACF,EAEA,SAAS,EAAe,EAAgB,CACtC,OAAO,aAAiB,MAAQ,EAAY,MAAM,yBAAyB,CAC7E,CC/EA,MAAM,EAAoC,MAEpC,EAA0B,CAAC,YAAa,OAAQ,QAAQ,EAE9D,IAAa,EAAb,cAA8C,KAAM,CAClD,YACE,EACA,EACA,CACA,MACE,cAAc,EAAa,8BAA8B,EAAa,SAAS,EAAW,GAAK,8BACjG,EALS,KAAA,aAAA,EACA,KAAA,WAAA,EAKT,KAAK,KAAO,0BACd,CACF,EAEA,eAAsB,EACpB,EACA,EACA,EACA,EACA,CACA,GAAI,EAAU,SAAW,EACvB,MAAU,MAAM,gDAAgD,EAElE,GAAI,EAAU,OAAS,GACrB,MAAU,MACR,gDACF,EAGF,IAAM,EAAe,EAAgB,CAAS,EAC9C,GAAI,EAAa,WAAa,EAC5B,MAAU,MAAM,4CAA4C,EAG9D,IAAM,EAAyB,MAAM,EACnC,EACA,IAAI,kBAAkB,SAAS,CACjC,EAEM,EAAmBE,EAAAA,IAAI,QAAQ,EAAwB,WAAW,EAElE,EAAM,IAAI,IAAI,CAAW,EACzB,CAAE,SAAQ,SAAU,EAAmB,CAAG,EAYhD,OAVA,EAAwB,QAAS,GAAS,CACxC,EAAO,OAAO,CAAI,CACpB,CAAC,EACG,GACF,EAAO,IAAI,OAAQ,CAAI,EAEzB,EAAO,IAAI,YAAa,CAAgB,EACxC,EAAO,IAAI,SAAU,CAAM,EAC3B,EAAI,KAAO,GAAG,EAAM,GAAG,EAAO,SAAS,IAEhC,EAAI,SAAS,CACtB,CAEA,eAAsB,EACpB,EACA,EACwB,CAExB,GAAM,CAAE,UAAW,EAAmB,IADtB,IAAI,EAAS,KAAK,CACM,CAAC,EACzC,GAAI,EAAc,CAChB,IAAM,EAAa,EAAO,IAAI,MAAM,CAAC,EAAE,KAAK,GAAK,IAAA,GACjD,GAAI,IAAe,EACjB,MAAM,IAAI,EAAyB,EAAc,CAAU,CAE/D,CACA,IAAM,EAAsB,EAAO,IAAI,WAAW,CAAC,EAAE,KAAK,EACpD,EAAS,EAAO,IAAI,QAAQ,CAAC,EAAE,KAAK,EAE1C,GAAI,CAAC,GAAuB,CAAC,EAC3B,MAAU,MAAM,gCAAgC,EAKlD,MAAO,CAAE,UAAA,MAFe,EAA0B,CAAmB,EAEjD,QAAO,CAC7B,CAEA,SAAS,EAAmB,EAAU,CACpC,IAAM,EAAW,EAAI,KAAK,MAAM,CAAC,EAC3B,EAAY,EAAS,QAAQ,GAAG,EAKtC,OAJI,IAAc,GACT,CAAE,OAAQ,IAAI,gBAAmB,MAAO,CAAS,EAGnD,CACL,OAAQ,IAAI,gBAAgB,EAAS,MAAM,EAAY,CAAC,CAAC,EACzD,MAAO,EAAS,MAAM,EAAG,CAAS,CACpC,CACF,CAEA,SAAS,EAAgB,EAA6C,CACpE,OAAOA,EAAAA,IAAI,YACT,GAAGC,EAAG,OAAO,EAAU,IAAK,GAAY,EAAQ,KAAK,CAAC,CACxD,CACF,CAEA,eAAe,EAA0B,EAAqC,CAC5E,GAAI,CAEF,IAAM,EAAQ,MAAM,EADID,EAAAA,IAAI,UAAU,EAAO,WAE7B,EACd,IAAI,oBAAoB,SAAS,EACjC,CACF,EACM,EAAyB,CAAC,EAChC,IAAK,IAAM,KAAWC,EAAG,OAAO,CAAC,CAAK,EAAG,CACvC,cAAe,CACjB,CAAC,EAAG,CACF,GAAI,EAAU,QAAU,GACtB,MAAU,MAAM,oBAAoB,EAEtC,EAAU,MAAA,EAAKC,EAAAA,UAAAA,CAAU,EAAQ,SAAS,CAAC,CAAC,CAC9C,CAEA,GAAI,EAAU,SAAW,EACvB,MAAU,MAAM,mBAAmB,EAErC,OAAO,CACT,OAAS,EAAO,CACd,MAAU,MAAM,yDAA0D,CACxE,OACF,CAAC,CACH,CACF,CAEA,eAAe,EACb,EACA,EACA,EACA,CACA,IAAM,EACJ,IAAoB,IAAA,GAChB,EAAU,SACV,EAAU,SAAS,YAAY,EAAW,CAAe,CAAC,EAC1D,EAAS,IAAI,SAAS,CAAQ,CAAC,CAAC,YAAY,CAAC,CAAC,KACjD,IAAY,CAAE,SAAQ,GAAI,EAAK,GAC/B,IAAoB,CAAE,QAAO,GAAI,EAAM,EAC1C,EACM,EAAS,EAAU,SAAS,UAAU,EAC5C,GAAI,CACF,MAAM,EAAO,MAAM,WAAW,KAAK,CAAK,CAAC,EACzC,MAAM,EAAO,MAAM,CACrB,OAAS,EAAO,CACd,IAAM,EAAS,MAAM,EAIrB,MAHK,EAAO,GAGN,EAFE,EAAO,KAGjB,CAEA,IAAM,EAAS,MAAM,EACrB,GAAI,CAAC,EAAO,GACV,MAAM,EAAO,MAEf,OAAO,IAAI,WAAW,EAAO,MAAM,CACrC,CAEA,SAAS,EAAW,EAAmB,CACrC,IAAI,EAAS,EAEb,OAAO,IAAI,gBAAwC,CACjD,UAAU,EAAO,EAAY,CAC3B,GAAI,EAAM,WAAa,EAAY,EACjC,MAAU,MAAM,wCAAwC,EAE1D,GAAU,EAAM,WAChB,EAAW,QAAQ,CAAK,CAC1B,CACF,CAAC,CACH,CCzKA,MAAM,EAA6B,KA8BnC,IAAsB,EAAtB,KAEE,CAaA,YACE,EACA,EACA,CACA,GAHS,KAAA,WAAA,EACA,KAAA,OAAA,EAda,KAAA,cAAA,EAAa,EACH,KAAA,eAAA,IAAI,IACH,KAAA,gBAAA,IAAI,IAGF,KAAA,kBAAA,IAAI,IACC,KAAA,uBAAA,IAAI,IACX,KAAA,gBAAA,IAAI,IASjC,EAAO,qBAAuB,EAChC,MAAU,MAAM,+CAA+C,CAEnE,CAKA,IAAI,QAAS,CACX,OAAO,KAAK,aACd,CAEA,QAAQ,EAAkC,CAExC,OADA,KAAK,eAAe,IAAI,CAAQ,MACnB,KAAK,eAAe,OAAO,CAAQ,CAClD,CAEA,SAAS,EAAmD,CAE1D,OADA,KAAK,gBAAgB,IAAI,CAAQ,MACpB,KAAK,gBAAgB,OAAO,CAAQ,CACnD,CAEA,WAAW,EAAoC,CAE7C,OADA,KAAK,kBAAkB,IAAI,CAAQ,MACtB,KAAK,kBAAkB,OAAO,CAAQ,CACrD,CAEA,gBAAgB,EAAoC,CAElD,OADA,KAAK,uBAAuB,IAAI,CAAQ,MAC3B,KAAK,uBAAuB,OAAO,CAAQ,CAC1D,CAEA,MAAM,OAAQ,CACZ,MAAM,KAAK,WAAW,UAAU,OAC9B,KAAK,OAAO,SACZ,KAAK,eAAe,KAAK,IAAI,EAC7B,CAAE,uBAAwB,EAAK,CACjC,CACF,CAEA,MAAM,MAAO,CACX,MAAM,KAAK,WAAW,UAAU,SAAS,KAAK,OAAO,QAAQ,EAE7D,KAAK,gBAAgB,QAAS,GAAY,aAAa,CAAO,CAAC,EAC/D,KAAK,gBAAgB,MAAM,CAC7B,CAEA,SAAS,EAAgB,CACvB,OAAO,KAAK,gBAAgB,IAAI,EAAO,SAAS,CAAC,CACnD,CAEA,QAAQ,EAAgB,CACtB,IAAM,EAAM,EAAO,SAAS,EACtB,EAAU,KAAK,gBAAgB,IAAI,CAAG,EACxC,IAAY,IAAA,KAIhB,aAAa,CAAO,EACpB,KAAK,gBAAgB,IAAI,EAAK,KAAK,kBAAkB,CAAM,CAAC,EAC9D,CAEA,MAAM,KACJ,EACA,EAAwB,CAAC,EACR,CACjB,IAAI,EACA,EAEJ,GAAI,CACF,IAAM,EAAS,MAAM,KAAK,WAAW,EAAO,UAAW,CAAO,EAG9D,GAFA,EAAS,EAAO,OAEZ,CAAC,EAAO,WACV,MAAU,MAAM,iCAAiC,EAG/C,KAAK,cAAc,EAAO,WAAW,UAAU,IACjD,EAAY,EAAO,WAAW,YAGhC,IAAM,EAAW,MAAM,EACrB,EACA,CACE,KAAM,OACN,OAAQ,EAAO,OACf,KAAM,KAAK,OAAO,IACpB,EACA,CACF,EAIA,GAHA,EAAQ,QAAQ,eAAe,EAC/B,EAAS,IAAA,GAEL,CAAC,EAAS,GACZ,MAAU,MAAM,EAAS,KAAK,EAOhC,OAJI,IACF,KAAK,aAAa,EAClB,KAAK,iBAAiB,EAAW,EAAS,IAAI,GAEzC,EAAO,WAAW,UAC3B,OAAS,EAAO,CAId,MAHI,GACF,KAAK,iBAAiB,EAAW,EAAK,EAElC,KAAK,YAAY,EAAO,CAAM,CACtC,CACF,CAEA,MAAM,OAAO,EAAgB,CAC3B,GAAI,CAAC,KAAK,iBAAiB,CAAM,EAC/B,OAGF,IAAM,EAAU,CAAE,OAAQ,YAAY,QAAQ,GAAiB,CAAE,EAE7D,EACJ,GAAI,CACF,IAAM,EAAS,MAAM,KAAK,WAAW,EAAQ,CAAO,EACpD,EAAS,EAAO,OAChB,IAAM,EAAW,MAAM,EACrB,EAAO,OACP,CAAE,KAAM,QAAS,EACjB,CACF,EAEA,GADA,EAAS,IAAA,GACL,CAAC,EAAS,GACZ,MAAU,MAAM,EAAS,KAAK,CAElC,OAAS,EAAO,CACd,KAAK,YAAY,EAAO,CAAM,CAChC,CACF,CAEA,MAAc,eAAe,EAAgB,EAAwB,CACnE,IAAI,EAEJ,GAAI,CACF,IAAM,GAAA,EAAgBC,EAAAA,SAAAA,CAAS,EAAQ,CACrC,cAAe,CACjB,CAAC,EACK,EAAU,GACb,MAAM,EAAc,KAAK,EAAA,CAAG,SAAS,CACxC,EAEA,GAAI,EAAQ,OAAS,SAAU,CAC7B,KAAK,iBAAiB,EAAW,UAAU,EAC3C,MAAM,EAAc,EAAe,EAAQ,CAAE,GAAI,EAAK,CAAC,EACvD,MACF,CAEA,GACE,EAAQ,SAAW,KAAK,QACxB,CAAC,KAAK,QAAQ,EAAW,UAAU,EACnC,CACA,MAAM,EAAc,EAAe,EAAQ,CACzC,GAAI,GACJ,MAAO,kBACT,CAAC,EACD,MACF,CAIA,KAAK,aAAa,EAEd,KAAK,cAAc,EAAW,UAAU,IAC1C,EAAY,EAAW,YAEzB,MAAM,EAAc,EAAe,EAAQ,CACzC,GAAI,GACJ,KAAM,KAAK,OAAO,IACpB,CAAC,EACG,GACF,KAAK,iBAAiB,EAAW,EAAQ,IAAI,CAEjD,OAAS,EAAO,CACV,GACF,KAAK,iBAAiB,EAAW,EAAK,EAExC,KAAK,YAAY,EAAO,CAAM,CAChC,CACF,CAEA,cAAsB,EAAgB,CACpC,IAAM,EAAM,EAAO,SAAS,EACtB,EAAU,KAAK,gBAAgB,IAAI,CAAG,EAM5C,OALI,IAAY,IAAA,IACd,aAAa,CAAO,EAGtB,KAAK,gBAAgB,IAAI,EAAK,KAAK,kBAAkB,CAAM,CAAC,EACrD,IAAY,IAAA,EACrB,CAEA,iBAAyB,EAAgB,EAAe,CACtD,KAAK,gBAAgB,QAAS,GAAa,EAAS,EAAQ,CAAI,CAAC,CACnE,CAEA,cAAuB,CACrB,KAAK,cAAgB,EAAa,EAClC,KAAK,uBAAuB,QAAS,GACnC,EAAS,KAAK,aAAa,CAC7B,CACF,CAEA,iBAAyB,EAAgB,EAAS,GAAM,CACtD,IAAM,EAAM,EAAO,SAAS,EACtB,EAAU,KAAK,gBAAgB,IAAI,CAAG,EAW5C,OAVI,IAAY,IAAA,KAIhB,aAAa,CAAO,EACpB,KAAK,gBAAgB,OAAO,CAAG,EAE3B,GACF,KAAK,kBAAkB,QAAS,GAAa,EAAS,CAAM,CAAC,EAExD,GACT,CAEA,kBAA0B,EAAgB,CACxC,OAAO,eAAiB,CACtB,KAAK,iBAAiB,CAAM,CAC9B,EAAG,KAAK,OAAO,mBAAmB,CACpC,CAEA,MAAc,WACZ,EACA,EACA,CACA,IAAI,EAeJ,MAAO,CAAE,OAAA,MAdY,KAAK,WAAW,kBAAkB,WACrD,EACA,KAAK,OAAO,SACZ,CACE,uBAAwB,GACxB,OAAQ,EAAQ,OAChB,WAAW,EAAO,CACZ,EAAM,OAAS,6BACjB,EAAa,EAAM,OAAO,WAE9B,CACF,CACF,EAEiB,YAAW,CAC9B,CAEA,YAAoB,EAAgB,EAAiB,CACnD,IAAM,EAAQ,EAAQ,EAAO,0BAA0B,EAIvD,OAFA,GAAQ,MAAM,CAAK,EACnB,KAAK,eAAe,QAAS,GAAa,EAAS,CAAK,CAAC,EAClD,CACT,CACF,EAEA,SAAgB,EAGd,EACA,EACwD,CACxD,MAAQ,IACN,IAAK,cAAc,CAA2B,CAC5C,QAAkB,EAAgB,CAChC,OAAO,EAAQ,KAAK,KAAM,CAAM,CAClC,CACF,EAAG,EAAY,CAAM,CACzB,CAEA,eAAe,EACb,EACA,EACA,EACA,CACA,IAAM,GAAA,EAAgBA,EAAAA,SAAAA,CAAS,EAAQ,CACrC,cAAe,CACjB,CAAC,EAED,MAAM,EAAc,MAAMC,EAAAA,IAAI,UAAU,KAAK,UAAU,CAAO,EAAG,MAAM,EAAG,CACxE,OAAQ,EAAQ,MAClB,CAAC,EACD,MAAM,EAAO,MAAM,CAAO,EAE1B,IAAM,EAAW,GACd,MAAM,EAAc,KAAK,CAAO,EAAA,CAAG,SAAS,CAC/C,EAEA,OADA,MAAM,EAAO,UAAU,CAAO,EACvB,CACT,CAEA,eAAe,EACb,EACA,EACA,EACA,CACA,MAAM,EAAc,MAAMA,EAAAA,IAAI,UAAU,KAAK,UAAU,CAAQ,EAAG,MAAM,CAAC,EACzE,MAAM,EAAO,MAAM,CACrB,CAEA,SAAS,EAAoB,EAAkC,CAC7D,IAAM,EAAU,KAAK,MACnBA,EAAAA,IAAI,QAAQ,EAAM,MAAM,CAC1B,EACA,GACE,EAAQ,OAAS,QACjB,OAAO,EAAQ,QAAW,UAC1B,EAAQ,OAER,MAAO,CACL,KAAM,OACN,OAAQ,EAAQ,OAChB,KAAM,OAAO,EAAQ,MAAS,SAAW,EAAQ,KAAO,IAAA,EAC1D,EAGF,GAAI,EAAQ,OAAS,SACnB,MAAO,CAAE,KAAM,QAAS,EAG1B,MAAU,MAAM,yBAAyB,CAC3C,CAEA,SAAS,EAAqB,EAAmC,CAC/D,IAAM,EAAW,KAAK,MACpBA,EAAAA,IAAI,QAAQ,EAAM,MAAM,CAC1B,EACA,GAAI,EAAS,KAAO,GAClB,MAAO,CACL,GAAI,GACJ,KAAM,OAAO,EAAS,MAAS,SAAW,EAAS,KAAO,IAAA,EAC5D,EAEF,GAAI,EAAS,KAAO,IAAS,OAAO,EAAS,OAAU,SACrD,MAAO,CAAE,GAAI,GAAO,MAAO,EAAS,KAAM,EAG5C,MAAU,MAAM,0BAA0B,CAC5C,CAEA,SAAS,GAAe,CACtB,OAAOA,EAAAA,IAAI,QAAQ,OAAO,gBAAgB,IAAI,WAAW,EAAE,CAAC,EAAG,WAAW,CAC5E,CAEA,SAAS,EAAQ,EAAgB,EAAkB,CACjD,OAAO,aAAiB,MAAQ,EAAY,MAAM,CAAQ,CAC5D"}
1
+ {"version":3,"file":"barrel.js","names":["DEFAULT_MAX_MESSAGE_LENGTH","lpStream","ccc","asJsonRpcError","JsonRpcError","ccc","lpStream","ccc","lp","multiaddr","lpStream","ccc","multiaddr","CODE_P2P"],"sources":["../src/jsonRpcService.ts","../src/jsonRpcTransport.ts","../src/pairingEndpoint.ts","../src/pairingService.ts","../src/barrel.ts"],"sourcesContent":["import { ccc, JsonRpcError } from \"@ckb-ccc/core\";\nimport type { Connection, PeerId, Stream } from \"@libp2p/interface\";\nimport type { Registrar } from \"@libp2p/interface-internal\";\nimport { lpStream } from \"@libp2p/utils\";\n\nconst DEFAULT_MAX_MESSAGE_LENGTH = 1024 * 1024;\n\nexport type JsonRpcServiceComponents = {\n registrar: Registrar;\n};\n\nexport type JsonRpcServiceConfig = {\n protocol: string;\n maxMessageLength?: number;\n};\n\nexport type JsonRpcRequest = {\n connection: Connection;\n peerId: PeerId;\n payload: ccc.JsonRpcPayload;\n};\n\nexport type JsonRpcRequestHandler<\n Components extends JsonRpcServiceComponents = JsonRpcServiceComponents,\n> = (this: JsonRpcService<Components>, request: JsonRpcRequest) => unknown;\n\nexport abstract class JsonRpcService<\n Components extends JsonRpcServiceComponents = JsonRpcServiceComponents,\n> {\n private readonly errorListeners = new Set<(error: Error) => void>();\n readonly maxMessageLength: number;\n\n constructor(\n readonly components: Components,\n readonly config: JsonRpcServiceConfig,\n ) {\n const maxMessageLength =\n config.maxMessageLength ?? DEFAULT_MAX_MESSAGE_LENGTH;\n if (!Number.isSafeInteger(maxMessageLength) || maxMessageLength <= 0) {\n throw new Error(\n \"Maximum JSON-RPC message length must be a positive integer\",\n );\n }\n\n this.maxMessageLength = maxMessageLength;\n }\n\n protected abstract handleRequest(request: JsonRpcRequest): unknown;\n\n onError(listener: (error: Error) => void) {\n this.errorListeners.add(listener);\n return () => this.errorListeners.delete(listener);\n }\n\n async start() {\n await this.components.registrar.handle(\n this.config.protocol,\n this.handleProtocol.bind(this),\n { runOnLimitedConnection: true },\n );\n }\n\n async stop() {\n await this.components.registrar.unhandle(this.config.protocol);\n }\n\n private async handleProtocol(stream: Stream, connection: Connection) {\n let requestAborted = false;\n\n try {\n const rpcStream = lpStream(stream, {\n maxDataLength: this.maxMessageLength,\n });\n const payload = parseJsonRpcRequest(\n ccc.bytesTo((await rpcStream.read()).subarray(), \"utf8\"),\n );\n\n let response: ccc.JsonRpcResponse;\n try {\n response = {\n jsonrpc: \"2.0\",\n id: payload.id,\n result: await this.handleRequest({\n connection,\n peerId: connection.remotePeer,\n payload,\n }),\n };\n } catch (cause) {\n requestAborted = isAbortError(cause);\n const error = toJsonRpcError(cause);\n response = {\n jsonrpc: \"2.0\",\n id: payload.id,\n error: {\n code: error.code,\n message: error.message,\n data: error.data,\n },\n };\n }\n\n await rpcStream.write(ccc.bytesFrom(JSON.stringify(response), \"utf8\"));\n await stream.close();\n } catch (cause) {\n if (requestAborted) {\n return;\n }\n this.handleError(cause, stream);\n }\n }\n\n private handleError(cause: unknown, stream?: Stream) {\n const error = asJsonRpcError(cause);\n\n stream?.abort(error);\n this.errorListeners.forEach((listener) => listener(error));\n return error;\n }\n}\n\nfunction toJsonRpcError(cause: unknown) {\n if (cause instanceof JsonRpcError) {\n return cause;\n }\n\n return new JsonRpcError({\n code: -32603,\n message: asJsonRpcError(cause).message,\n });\n}\n\nfunction parseJsonRpcRequest(data: string) {\n const payload = JSON.parse(data) as Partial<ccc.JsonRpcPayload>;\n if (\n payload?.jsonrpc !== \"2.0\" ||\n (typeof payload.id !== \"number\" && typeof payload.id !== \"string\") ||\n typeof payload.method !== \"string\"\n ) {\n throw new Error(\"Invalid JSON-RPC request\");\n }\n\n return payload as ccc.JsonRpcPayload;\n}\n\nfunction asJsonRpcError(cause: unknown) {\n return cause instanceof Error ? cause : new Error(\"JSON-RPC request failed\");\n}\n\nfunction isAbortError(cause: unknown) {\n return (\n cause instanceof Error &&\n (cause.name === \"AbortError\" || cause.name === \"TimeoutError\")\n );\n}\n\nexport function jsonRpcService<\n Components extends JsonRpcServiceComponents = JsonRpcServiceComponents,\n>(\n config: JsonRpcServiceConfig,\n handler: JsonRpcRequestHandler<Components>,\n): (components: Components) => JsonRpcService<Components> {\n return (components: Components) =>\n new (class extends JsonRpcService<Components> {\n protected handleRequest(request: JsonRpcRequest) {\n return handler.call(this, request);\n }\n })(components, config);\n}\n","import { ccc } from \"@ckb-ccc/core\";\nimport type { Connection, Libp2p, PeerId, Stream } from \"@libp2p/interface\";\nimport { lpStream } from \"@libp2p/utils\";\n\nconst DEFAULT_MAX_MESSAGE_LENGTH = 1024 * 1024;\nconst DEFAULT_TIMEOUT = 120_000;\n\nexport type JsonRpcTransportLibp2pConfig = {\n protocol: string;\n maxMessageLength?: number;\n timeout?: number;\n signal?: AbortSignal;\n onResponse?: (response: ccc.JsonRpcResponse) => void;\n};\n\nexport class JsonRpcTransportLibp2p implements ccc.JsonRpcTransport {\n private readonly maxMessageLength: number;\n private readonly timeout: number;\n\n constructor(\n private readonly node: Libp2p,\n readonly peerId: PeerId,\n private readonly config: JsonRpcTransportLibp2pConfig,\n ) {\n const maxMessageLength =\n config.maxMessageLength ?? DEFAULT_MAX_MESSAGE_LENGTH;\n if (!Number.isSafeInteger(maxMessageLength) || maxMessageLength <= 0) {\n throw new Error(\n \"Maximum JSON-RPC message length must be a positive integer\",\n );\n }\n\n this.maxMessageLength = maxMessageLength;\n\n const timeout = config.timeout ?? DEFAULT_TIMEOUT;\n if (!Number.isSafeInteger(timeout) || timeout <= 0) {\n throw new Error(\"JSON-RPC request timeout must be a positive integer\");\n }\n\n this.timeout = timeout;\n }\n\n async request(payload: ccc.JsonRpcPayload): Promise<ccc.JsonRpcResponse> {\n const timeoutSignal = AbortSignal.timeout(this.timeout);\n const signal = this.config.signal\n ? ccc.abortSignalAny([this.config.signal, timeoutSignal])\n : timeoutSignal;\n\n let stream: Stream | undefined;\n let response: ccc.JsonRpcResponse;\n try {\n stream = await this.openStream(signal);\n\n const rpcStream = lpStream(stream, {\n maxDataLength: this.maxMessageLength,\n });\n\n await rpcStream.write(ccc.bytesFrom(JSON.stringify(payload), \"utf8\"), {\n signal,\n });\n await stream.close({ signal });\n\n response = JSON.parse(\n ccc.bytesTo((await rpcStream.read({ signal })).subarray(), \"utf8\"),\n ) as ccc.JsonRpcResponse;\n await stream.closeRead({ signal });\n signal.throwIfAborted();\n } catch (cause) {\n const error = asJsonRpcError(cause);\n stream?.abort(error);\n throw error;\n }\n\n this.config.onResponse?.(response);\n return response;\n }\n\n private async openStream(signal: AbortSignal) {\n for (const connection of this.connections()) {\n try {\n return await connection.newStream(this.config.protocol, {\n runOnLimitedConnection: true,\n signal,\n });\n } catch {\n signal.throwIfAborted();\n }\n }\n\n return this.node.dialProtocol(this.peerId, this.config.protocol, {\n runOnLimitedConnection: true,\n signal,\n });\n }\n\n private connections() {\n return this.node\n .getConnections(this.peerId)\n .filter(({ status }) => status === \"open\")\n .sort(compareConnections);\n }\n}\n\nfunction compareConnections(a: Connection, b: Connection) {\n if (a.direct !== b.direct) {\n return a.direct ? -1 : 1;\n }\n if (a.rtt === undefined) {\n return b.rtt === undefined ? 0 : 1;\n }\n return b.rtt === undefined ? -1 : a.rtt - b.rtt;\n}\n\nfunction asJsonRpcError(cause: unknown) {\n return cause instanceof Error ? cause : new Error(\"JSON-RPC request failed\");\n}\n","import { ccc } from \"@ckb-ccc/core\";\nimport { multiaddr, type Multiaddr } from \"@multiformats/multiaddr\";\nimport * as lp from \"it-length-prefixed\";\nimport type { PairingTarget } from \"./pairingService.js\";\n\nconst MAX_DECOMPRESSED_ADDRESSES_LENGTH = 16 * 1024;\nconst MAX_ADDRESSES = 16;\nconst PAIRING_PARAMETER_NAMES = [\"addresses\", \"role\", \"secret\"] as const;\n\nexport class PairingEndpointRoleError extends Error {\n constructor(\n readonly expectedRole: string,\n readonly actualRole: string | undefined,\n ) {\n super(\n `Expected a ${expectedRole} pairing endpoint, received ${actualRole ? `role \"${actualRole}\"` : \"an endpoint without a role\"}`,\n );\n this.name = \"PairingEndpointRoleError\";\n }\n}\n\nexport async function encodePairingEndpoint(\n endpointUrl: string,\n addresses: readonly Multiaddr[],\n secret: string,\n role?: string,\n) {\n if (addresses.length === 0) {\n throw new Error(\"Pairing endpoint requires at least one address\");\n }\n if (addresses.length > MAX_ADDRESSES) {\n throw new Error(\n `Pairing endpoint supports at most ${MAX_ADDRESSES} addresses`,\n );\n }\n\n const addressBytes = encodeAddresses(addresses);\n if (addressBytes.byteLength > MAX_DECOMPRESSED_ADDRESSES_LENGTH) {\n throw new Error(\"Pairing endpoint address data is too large\");\n }\n\n const compressedAddressBytes = await transformBytes(\n addressBytes,\n new CompressionStream(\"deflate\"),\n );\n\n const encodedAddresses = ccc.bytesTo(compressedAddressBytes, \"base64url\");\n\n const url = new URL(endpointUrl);\n const { params, route } = fragmentParameters(url);\n\n PAIRING_PARAMETER_NAMES.forEach((name) => {\n params.delete(name);\n });\n if (role) {\n params.set(\"role\", role);\n }\n params.set(\"addresses\", encodedAddresses);\n params.set(\"secret\", secret);\n url.hash = `${route}?${params.toString()}`;\n\n return url.toString();\n}\n\nexport async function decodePairingEndpoint(\n endpoint: string,\n expectedRole?: string,\n): Promise<PairingTarget> {\n const url = new URL(endpoint.trim());\n const { params } = fragmentParameters(url);\n if (expectedRole) {\n const actualRole = params.get(\"role\")?.trim() || undefined;\n if (actualRole !== expectedRole) {\n throw new PairingEndpointRoleError(expectedRole, actualRole);\n }\n }\n const compressedAddresses = params.get(\"addresses\")?.trim();\n const secret = params.get(\"secret\")?.trim();\n\n if (!compressedAddresses || !secret) {\n throw new Error(\"Pairing endpoint is incomplete\");\n }\n\n const addresses = await decodeCompressedAddresses(compressedAddresses);\n\n return { addresses, secret };\n}\n\nfunction fragmentParameters(url: URL) {\n const fragment = url.hash.slice(1);\n const separator = fragment.indexOf(\"?\");\n if (separator === -1) {\n return { params: new URLSearchParams(), route: fragment };\n }\n\n return {\n params: new URLSearchParams(fragment.slice(separator + 1)),\n route: fragment.slice(0, separator),\n };\n}\n\nfunction encodeAddresses(addresses: readonly Multiaddr[]): Uint8Array {\n return ccc.bytesConcat(\n ...lp.encode(addresses.map((address) => address.bytes)),\n );\n}\n\nasync function decodeCompressedAddresses(value: string): Promise<Multiaddr[]> {\n try {\n const compressedBytes = ccc.bytesFrom(value, \"base64url\");\n const bytes = await transformBytes(\n compressedBytes,\n new DecompressionStream(\"deflate\"),\n MAX_DECOMPRESSED_ADDRESSES_LENGTH,\n );\n const addresses: Multiaddr[] = [];\n for (const address of lp.decode([bytes], {\n maxDataLength: MAX_DECOMPRESSED_ADDRESSES_LENGTH,\n })) {\n if (addresses.length >= MAX_ADDRESSES) {\n throw new Error(\"Too many addresses\");\n }\n addresses.push(multiaddr(address.subarray()));\n }\n\n if (addresses.length === 0) {\n throw new Error(\"Missing addresses\");\n }\n return addresses;\n } catch (cause) {\n throw new Error(\"Pairing endpoint contains invalid compressed addresses\", {\n cause,\n });\n }\n}\n\nasync function transformBytes(\n bytes: Uint8Array,\n transform: CompressionStream | DecompressionStream,\n maxOutputLength?: number,\n) {\n const readable =\n maxOutputLength === undefined\n ? transform.readable\n : transform.readable.pipeThrough(limitBytes(maxOutputLength));\n const output = new Response(readable).arrayBuffer().then(\n (buffer) => ({ buffer, ok: true }) as const,\n (error: unknown) => ({ error, ok: false }) as const,\n );\n const writer = transform.writable.getWriter();\n try {\n await writer.write(Uint8Array.from(bytes));\n await writer.close();\n } catch (cause) {\n const result = await output;\n if (!result.ok) {\n throw result.error;\n }\n throw cause;\n }\n\n const result = await output;\n if (!result.ok) {\n throw result.error;\n }\n return new Uint8Array(result.buffer);\n}\n\nfunction limitBytes(maxLength: number) {\n let length = 0;\n\n return new TransformStream<Uint8Array, Uint8Array>({\n transform(chunk, controller) {\n if (chunk.byteLength > maxLength - length) {\n throw new Error(\"Decompressed address data is too large\");\n }\n length += chunk.byteLength;\n controller.enqueue(chunk);\n },\n });\n}\n","import { ccc } from \"@ckb-ccc/core\";\nimport type {\n AbortOptions,\n Connection,\n PeerId,\n Stream,\n} from \"@libp2p/interface\";\nimport type {\n AddressManager,\n ConnectionManager,\n Registrar,\n} from \"@libp2p/interface-internal\";\nimport { lpStream } from \"@libp2p/utils\";\nimport { CODE_P2P, multiaddr, type Multiaddr } from \"@multiformats/multiaddr\";\n\nconst MAX_PAIRING_MESSAGE_LENGTH = 1024;\nconst UNPAIR_TIMEOUT_MS = 10_000;\n\nexport type PairingServiceComponents = {\n addressManager?: Pick<AddressManager, \"getAddresses\">;\n connectionManager: ConnectionManager;\n registrar: Registrar;\n};\n\nexport type PairingServiceConfig = {\n /** Optional self-reported label shared with the paired peer. */\n name?: string;\n protocol: string;\n pairedPeerTimeoutMs: number;\n};\n\nexport type PairingTarget = {\n addresses: Multiaddr[];\n secret: string;\n};\n\nexport type PairingGuard<\n Components extends PairingServiceComponents = PairingServiceComponents,\n> = (this: PairingService<Components>, peerId: PeerId) => boolean;\n\ntype PairingRequest =\n | { type: \"pair\"; secret: string; name?: string; addresses?: string[] }\n | { type: \"unpair\" };\n\ntype PairingResponse =\n { ok: true; name?: string } | { ok: false; error: string };\n\nexport abstract class PairingService<\n Components extends PairingServiceComponents = PairingServiceComponents,\n> {\n private currentSecret = createSecret();\n private readonly errorListeners = new Set<(error: Error) => void>();\n private readonly pairedListeners = new Set<\n (peerId: PeerId, name?: string) => void\n >();\n private readonly unpairedListeners = new Set<(peerId: PeerId) => void>();\n private readonly secretChangedListeners = new Set<(secret: string) => void>();\n private readonly pairingTimeouts = new Map<\n string,\n ReturnType<typeof setTimeout>\n >();\n\n constructor(\n readonly components: Components,\n readonly config: PairingServiceConfig,\n ) {\n if (config.pairedPeerTimeoutMs <= 0) {\n throw new Error(\"Paired peer timeout must be greater than zero\");\n }\n }\n\n protected abstract canPair(peerId: PeerId): boolean;\n\n /** The credential to include in the currently issued pairing endpoint. */\n get secret() {\n return this.currentSecret;\n }\n\n onError(listener: (error: Error) => void) {\n this.errorListeners.add(listener);\n return () => this.errorListeners.delete(listener);\n }\n\n onPaired(listener: (peerId: PeerId, name?: string) => void) {\n this.pairedListeners.add(listener);\n return () => this.pairedListeners.delete(listener);\n }\n\n onUnpaired(listener: (peerId: PeerId) => void) {\n this.unpairedListeners.add(listener);\n return () => this.unpairedListeners.delete(listener);\n }\n\n onSecretChanged(listener: (secret: string) => void) {\n this.secretChangedListeners.add(listener);\n return () => this.secretChangedListeners.delete(listener);\n }\n\n async start() {\n await this.components.registrar.handle(\n this.config.protocol,\n this.handleProtocol.bind(this),\n { runOnLimitedConnection: true },\n );\n }\n\n async stop() {\n await this.components.registrar.unhandle(this.config.protocol);\n\n this.pairingTimeouts.forEach((timeout) => clearTimeout(timeout));\n this.pairingTimeouts.clear();\n }\n\n isPaired(peerId: PeerId) {\n return this.pairingTimeouts.has(peerId.toString());\n }\n\n refresh(peerId: PeerId) {\n const key = peerId.toString();\n const timeout = this.pairingTimeouts.get(key);\n if (timeout === undefined) {\n return;\n }\n\n clearTimeout(timeout);\n this.pairingTimeouts.set(key, this.createPeerTimeout(peerId));\n }\n\n async pair(\n target: PairingTarget,\n options: AbortOptions = {},\n ): Promise<PeerId> {\n let stream: Stream | undefined;\n let addedPeer: PeerId | undefined;\n\n try {\n const opened = await this.openStream(target.addresses, options);\n stream = opened.stream;\n\n if (!opened.connection) {\n throw new Error(\"Could not determine remote peer\");\n }\n\n if (this.addPairedPeer(opened.connection.remotePeer)) {\n addedPeer = opened.connection.remotePeer;\n }\n\n const response = await exchange(\n stream,\n createPairRequest(target.secret, this.config.name, this.addresses()),\n options,\n );\n options.signal?.throwIfAborted();\n stream = undefined;\n\n if (!response.ok) {\n throw new Error(response.error);\n }\n\n if (addedPeer) {\n this.rotateSecret();\n this.notifyPairedPeer(addedPeer, response.name);\n }\n return opened.connection.remotePeer;\n } catch (cause) {\n if (addedPeer) {\n this.removePairedPeer(addedPeer, false);\n }\n throw this.handleError(cause, stream);\n }\n }\n\n async unpair(peerId: PeerId) {\n if (!this.removePairedPeer(peerId)) {\n return;\n }\n\n const options = { signal: AbortSignal.timeout(UNPAIR_TIMEOUT_MS) };\n\n let stream: Stream | undefined;\n try {\n const opened = await this.openStream(peerId, options);\n stream = opened.stream;\n const response = await exchange(\n opened.stream,\n { type: \"unpair\" },\n options,\n );\n stream = undefined;\n if (!response.ok) {\n throw new Error(response.error);\n }\n } catch (cause) {\n this.handleError(cause, stream);\n }\n }\n\n private async handleProtocol(stream: Stream, connection: Connection) {\n let addedPeer: PeerId | undefined;\n\n try {\n const pairingStream = lpStream(stream, {\n maxDataLength: MAX_PAIRING_MESSAGE_LENGTH,\n });\n const request = parsePairingRequest(\n (await pairingStream.read()).subarray(),\n );\n\n if (request.type === \"unpair\") {\n this.removePairedPeer(connection.remotePeer);\n await writeResponse(pairingStream, stream, { ok: true });\n return;\n }\n\n if (\n request.secret !== this.secret ||\n !this.canPair(connection.remotePeer)\n ) {\n await writeResponse(pairingStream, stream, {\n ok: false,\n error: \"Pairing rejected\",\n });\n return;\n }\n\n const addresses = parseAddresses(\n request.addresses,\n connection.remotePeer,\n );\n\n // Consume the credential before the next asynchronous boundary so two\n // concurrent requests cannot both pair with the same endpoint.\n this.rotateSecret();\n\n if (this.addPairedPeer(connection.remotePeer)) {\n addedPeer = connection.remotePeer;\n }\n await writeResponse(pairingStream, stream, {\n ok: true,\n name: this.config.name,\n });\n if (addedPeer) {\n this.notifyPairedPeer(addedPeer, request.name);\n void this.openFallbackConnection(addresses);\n }\n } catch (cause) {\n if (addedPeer) {\n this.removePairedPeer(addedPeer, false);\n }\n this.handleError(cause, stream);\n }\n }\n\n private addPairedPeer(peerId: PeerId) {\n const key = peerId.toString();\n const timeout = this.pairingTimeouts.get(key);\n if (timeout !== undefined) {\n clearTimeout(timeout);\n }\n\n this.pairingTimeouts.set(key, this.createPeerTimeout(peerId));\n return timeout === undefined;\n }\n\n private notifyPairedPeer(peerId: PeerId, name?: string) {\n this.pairedListeners.forEach((listener) => listener(peerId, name));\n }\n\n private addresses() {\n return this.components.addressManager?.getAddresses() ?? [];\n }\n\n private async openFallbackConnection(addresses: Multiaddr[]) {\n if (addresses.length === 0) {\n return;\n }\n\n try {\n await this.components.connectionManager.openConnection(addresses);\n } catch {\n // The original pairing connection remains usable when the fallback fails.\n }\n }\n\n private rotateSecret() {\n this.currentSecret = createSecret();\n this.secretChangedListeners.forEach((listener) =>\n listener(this.currentSecret),\n );\n }\n\n private removePairedPeer(peerId: PeerId, notify = true) {\n const key = peerId.toString();\n const timeout = this.pairingTimeouts.get(key);\n if (timeout === undefined) {\n return false;\n }\n\n clearTimeout(timeout);\n this.pairingTimeouts.delete(key);\n\n if (notify) {\n this.unpairedListeners.forEach((listener) => listener(peerId));\n }\n return true;\n }\n\n private createPeerTimeout(peerId: PeerId) {\n return setTimeout(() => {\n this.removePairedPeer(peerId);\n }, this.config.pairedPeerTimeoutMs);\n }\n\n private async openStream(\n target: PeerId | Multiaddr | Multiaddr[],\n options: AbortOptions,\n ) {\n let connection: Connection | undefined;\n const stream = await this.components.connectionManager.openStream(\n target,\n this.config.protocol,\n {\n runOnLimitedConnection: true,\n signal: options.signal,\n onProgress(event) {\n if (event.type === \"connection:opened-stream\") {\n connection = event.detail.connection;\n }\n },\n },\n );\n\n return { stream, connection };\n }\n\n private handleError(cause: unknown, stream?: Stream) {\n const error = asError(cause, \"Pairing operation failed\");\n\n stream?.abort(error);\n this.errorListeners.forEach((listener) => listener(error));\n return error;\n }\n}\n\nexport function pairingService<\n Components extends PairingServiceComponents = PairingServiceComponents,\n>(\n config: PairingServiceConfig,\n canPair: PairingGuard<Components>,\n): (components: Components) => PairingService<Components> {\n return (components: Components) =>\n new (class extends PairingService<Components> {\n protected canPair(peerId: PeerId) {\n return canPair.call(this, peerId);\n }\n })(components, config);\n}\n\nasync function exchange(\n stream: Stream,\n request: PairingRequest,\n options: AbortOptions,\n) {\n const pairingStream = lpStream(stream, {\n maxDataLength: MAX_PAIRING_MESSAGE_LENGTH,\n });\n\n await pairingStream.write(ccc.bytesFrom(JSON.stringify(request), \"utf8\"), {\n signal: options.signal,\n });\n await stream.close(options);\n\n const response = parsePairingResponse(\n (await pairingStream.read(options)).subarray(),\n );\n await stream.closeRead(options);\n return response;\n}\n\nfunction createPairRequest(\n secret: string,\n name: string | undefined,\n addresses: Multiaddr[],\n): PairingRequest {\n const addressStrings = addresses.map((address) => address.toString());\n const request: PairingRequest = {\n type: \"pair\",\n secret,\n name,\n addresses: addressStrings,\n };\n while (addressStrings.length > 0 && !pairingRequestFits(request)) {\n addressStrings.pop();\n }\n\n if (!pairingRequestFits(request)) {\n delete request.addresses;\n }\n if (!pairingRequestFits(request)) {\n delete request.name;\n }\n if (!pairingRequestFits(request)) {\n throw new Error(\"Pairing request is too large\");\n }\n return request;\n}\n\nfunction pairingRequestFits(request: PairingRequest) {\n return (\n ccc.bytesFrom(JSON.stringify(request), \"utf8\").byteLength <=\n MAX_PAIRING_MESSAGE_LENGTH\n );\n}\n\nasync function writeResponse(\n pairingStream: ReturnType<typeof lpStream>,\n stream: Stream,\n response: PairingResponse,\n) {\n await pairingStream.write(ccc.bytesFrom(JSON.stringify(response), \"utf8\"));\n await stream.close();\n}\n\nfunction parsePairingRequest(data: Uint8Array): PairingRequest {\n const request = JSON.parse(\n ccc.bytesTo(data, \"utf8\"),\n ) as Partial<PairingRequest>;\n if (\n request.type === \"pair\" &&\n typeof request.secret === \"string\" &&\n request.secret\n ) {\n return {\n type: \"pair\",\n secret: request.secret,\n name: typeof request.name === \"string\" ? request.name : undefined,\n addresses: parseAddressStrings(request.addresses),\n };\n }\n\n if (request.type === \"unpair\") {\n return { type: \"unpair\" };\n }\n\n throw new Error(\"Invalid pairing request\");\n}\n\nfunction parseAddressStrings(value: unknown): string[] | undefined {\n if (!Array.isArray(value)) {\n return undefined;\n }\n return value.filter(\n (address): address is string =>\n typeof address === \"string\" && address.length > 0,\n );\n}\n\nfunction parseAddresses(values: string[] | undefined, peerId: PeerId) {\n return (values ?? []).flatMap((value) => {\n try {\n const address = multiaddr(value);\n const peer = address\n .getComponents()\n .filter(({ code }) => code === CODE_P2P)\n .at(-1)?.value;\n return peer === peerId.toString() ? [address] : [];\n } catch {\n return [];\n }\n });\n}\n\nfunction parsePairingResponse(data: Uint8Array): PairingResponse {\n const response = JSON.parse(\n ccc.bytesTo(data, \"utf8\"),\n ) as Partial<PairingResponse>;\n if (response.ok === true) {\n return {\n ok: true,\n name: typeof response.name === \"string\" ? response.name : undefined,\n };\n }\n if (response.ok === false && typeof response.error === \"string\") {\n return { ok: false, error: response.error };\n }\n\n throw new Error(\"Invalid pairing response\");\n}\n\nfunction createSecret() {\n return ccc.bytesTo(crypto.getRandomValues(new Uint8Array(16)), \"base64url\");\n}\n\nfunction asError(cause: unknown, fallback: string) {\n return cause instanceof Error ? cause : new Error(fallback);\n}\n","export * from \"./jsonRpcService.js\";\nexport * from \"./jsonRpcTransport.js\";\nexport * from \"./pairingEndpoint.js\";\nexport * from \"./pairingService.js\";\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ckb-ccc/libp2p",
3
- "version": "0.0.2",
3
+ "version": "0.1.0",
4
4
  "description": "CCC support for libp2p",
5
5
  "author": "Hanssen0 <0@hanssen0.com>",
6
6
  "license": "MIT",
@@ -45,7 +45,7 @@
45
45
  "@libp2p/utils": "^7.4.0",
46
46
  "@multiformats/multiaddr": "^13.0.3",
47
47
  "it-length-prefixed": "^11.0.1",
48
- "@ckb-ccc/core": "1.20.0"
48
+ "@ckb-ccc/core": "1.20.1"
49
49
  },
50
50
  "types": "./dist.commonjs/index.d.ts",
51
51
  "scripts": {
@@ -15,6 +15,7 @@ export type JsonRpcServiceConfig = {
15
15
  };
16
16
 
17
17
  export type JsonRpcRequest = {
18
+ connection: Connection;
18
19
  peerId: PeerId;
19
20
  payload: ccc.JsonRpcPayload;
20
21
  };
@@ -80,6 +81,7 @@ export abstract class JsonRpcService<
80
81
  jsonrpc: "2.0",
81
82
  id: payload.id,
82
83
  result: await this.handleRequest({
84
+ connection,
83
85
  peerId: connection.remotePeer,
84
86
  payload,
85
87
  }),
@@ -1,5 +1,5 @@
1
1
  import { ccc } from "@ckb-ccc/core";
2
- import type { Libp2p, PeerId, Stream } from "@libp2p/interface";
2
+ import type { Connection, Libp2p, PeerId, Stream } from "@libp2p/interface";
3
3
  import { lpStream } from "@libp2p/utils";
4
4
 
5
5
  const DEFAULT_MAX_MESSAGE_LENGTH = 1024 * 1024;
@@ -49,11 +49,7 @@ export class JsonRpcTransportLibp2p implements ccc.JsonRpcTransport {
49
49
  let stream: Stream | undefined;
50
50
  let response: ccc.JsonRpcResponse;
51
51
  try {
52
- // dialProtocol handles the signal, so no separate entry check is needed.
53
- stream = await this.node.dialProtocol(this.peerId, this.config.protocol, {
54
- runOnLimitedConnection: true,
55
- signal,
56
- });
52
+ stream = await this.openStream(signal);
57
53
 
58
54
  const rpcStream = lpStream(stream, {
59
55
  maxDataLength: this.maxMessageLength,
@@ -78,6 +74,41 @@ export class JsonRpcTransportLibp2p implements ccc.JsonRpcTransport {
78
74
  this.config.onResponse?.(response);
79
75
  return response;
80
76
  }
77
+
78
+ private async openStream(signal: AbortSignal) {
79
+ for (const connection of this.connections()) {
80
+ try {
81
+ return await connection.newStream(this.config.protocol, {
82
+ runOnLimitedConnection: true,
83
+ signal,
84
+ });
85
+ } catch {
86
+ signal.throwIfAborted();
87
+ }
88
+ }
89
+
90
+ return this.node.dialProtocol(this.peerId, this.config.protocol, {
91
+ runOnLimitedConnection: true,
92
+ signal,
93
+ });
94
+ }
95
+
96
+ private connections() {
97
+ return this.node
98
+ .getConnections(this.peerId)
99
+ .filter(({ status }) => status === "open")
100
+ .sort(compareConnections);
101
+ }
102
+ }
103
+
104
+ function compareConnections(a: Connection, b: Connection) {
105
+ if (a.direct !== b.direct) {
106
+ return a.direct ? -1 : 1;
107
+ }
108
+ if (a.rtt === undefined) {
109
+ return b.rtt === undefined ? 0 : 1;
110
+ }
111
+ return b.rtt === undefined ? -1 : a.rtt - b.rtt;
81
112
  }
82
113
 
83
114
  function asJsonRpcError(cause: unknown) {
@@ -5,14 +5,19 @@ import type {
5
5
  PeerId,
6
6
  Stream,
7
7
  } from "@libp2p/interface";
8
- import type { ConnectionManager, Registrar } from "@libp2p/interface-internal";
8
+ import type {
9
+ AddressManager,
10
+ ConnectionManager,
11
+ Registrar,
12
+ } from "@libp2p/interface-internal";
9
13
  import { lpStream } from "@libp2p/utils";
10
- import type { Multiaddr } from "@multiformats/multiaddr";
14
+ import { CODE_P2P, multiaddr, type Multiaddr } from "@multiformats/multiaddr";
11
15
 
12
16
  const MAX_PAIRING_MESSAGE_LENGTH = 1024;
13
17
  const UNPAIR_TIMEOUT_MS = 10_000;
14
18
 
15
19
  export type PairingServiceComponents = {
20
+ addressManager?: Pick<AddressManager, "getAddresses">;
16
21
  connectionManager: ConnectionManager;
17
22
  registrar: Registrar;
18
23
  };
@@ -34,7 +39,8 @@ export type PairingGuard<
34
39
  > = (this: PairingService<Components>, peerId: PeerId) => boolean;
35
40
 
36
41
  type PairingRequest =
37
- { type: "pair"; secret: string; name?: string } | { type: "unpair" };
42
+ | { type: "pair"; secret: string; name?: string; addresses?: string[] }
43
+ | { type: "unpair" };
38
44
 
39
45
  type PairingResponse =
40
46
  { ok: true; name?: string } | { ok: false; error: string };
@@ -141,11 +147,7 @@ export abstract class PairingService<
141
147
 
142
148
  const response = await exchange(
143
149
  stream,
144
- {
145
- type: "pair",
146
- secret: target.secret,
147
- name: this.config.name,
148
- },
150
+ createPairRequest(target.secret, this.config.name, this.addresses()),
149
151
  options,
150
152
  );
151
153
  options.signal?.throwIfAborted();
@@ -221,6 +223,11 @@ export abstract class PairingService<
221
223
  return;
222
224
  }
223
225
 
226
+ const addresses = parseAddresses(
227
+ request.addresses,
228
+ connection.remotePeer,
229
+ );
230
+
224
231
  // Consume the credential before the next asynchronous boundary so two
225
232
  // concurrent requests cannot both pair with the same endpoint.
226
233
  this.rotateSecret();
@@ -234,6 +241,7 @@ export abstract class PairingService<
234
241
  });
235
242
  if (addedPeer) {
236
243
  this.notifyPairedPeer(addedPeer, request.name);
244
+ void this.openFallbackConnection(addresses);
237
245
  }
238
246
  } catch (cause) {
239
247
  if (addedPeer) {
@@ -258,6 +266,22 @@ export abstract class PairingService<
258
266
  this.pairedListeners.forEach((listener) => listener(peerId, name));
259
267
  }
260
268
 
269
+ private addresses() {
270
+ return this.components.addressManager?.getAddresses() ?? [];
271
+ }
272
+
273
+ private async openFallbackConnection(addresses: Multiaddr[]) {
274
+ if (addresses.length === 0) {
275
+ return;
276
+ }
277
+
278
+ try {
279
+ await this.components.connectionManager.openConnection(addresses);
280
+ } catch {
281
+ // The original pairing connection remains usable when the fallback fails.
282
+ }
283
+ }
284
+
261
285
  private rotateSecret() {
262
286
  this.currentSecret = createSecret();
263
287
  this.secretChangedListeners.forEach((listener) =>
@@ -353,6 +377,41 @@ async function exchange(
353
377
  return response;
354
378
  }
355
379
 
380
+ function createPairRequest(
381
+ secret: string,
382
+ name: string | undefined,
383
+ addresses: Multiaddr[],
384
+ ): PairingRequest {
385
+ const addressStrings = addresses.map((address) => address.toString());
386
+ const request: PairingRequest = {
387
+ type: "pair",
388
+ secret,
389
+ name,
390
+ addresses: addressStrings,
391
+ };
392
+ while (addressStrings.length > 0 && !pairingRequestFits(request)) {
393
+ addressStrings.pop();
394
+ }
395
+
396
+ if (!pairingRequestFits(request)) {
397
+ delete request.addresses;
398
+ }
399
+ if (!pairingRequestFits(request)) {
400
+ delete request.name;
401
+ }
402
+ if (!pairingRequestFits(request)) {
403
+ throw new Error("Pairing request is too large");
404
+ }
405
+ return request;
406
+ }
407
+
408
+ function pairingRequestFits(request: PairingRequest) {
409
+ return (
410
+ ccc.bytesFrom(JSON.stringify(request), "utf8").byteLength <=
411
+ MAX_PAIRING_MESSAGE_LENGTH
412
+ );
413
+ }
414
+
356
415
  async function writeResponse(
357
416
  pairingStream: ReturnType<typeof lpStream>,
358
417
  stream: Stream,
@@ -375,6 +434,7 @@ function parsePairingRequest(data: Uint8Array): PairingRequest {
375
434
  type: "pair",
376
435
  secret: request.secret,
377
436
  name: typeof request.name === "string" ? request.name : undefined,
437
+ addresses: parseAddressStrings(request.addresses),
378
438
  };
379
439
  }
380
440
 
@@ -385,6 +445,31 @@ function parsePairingRequest(data: Uint8Array): PairingRequest {
385
445
  throw new Error("Invalid pairing request");
386
446
  }
387
447
 
448
+ function parseAddressStrings(value: unknown): string[] | undefined {
449
+ if (!Array.isArray(value)) {
450
+ return undefined;
451
+ }
452
+ return value.filter(
453
+ (address): address is string =>
454
+ typeof address === "string" && address.length > 0,
455
+ );
456
+ }
457
+
458
+ function parseAddresses(values: string[] | undefined, peerId: PeerId) {
459
+ return (values ?? []).flatMap((value) => {
460
+ try {
461
+ const address = multiaddr(value);
462
+ const peer = address
463
+ .getComponents()
464
+ .filter(({ code }) => code === CODE_P2P)
465
+ .at(-1)?.value;
466
+ return peer === peerId.toString() ? [address] : [];
467
+ } catch {
468
+ return [];
469
+ }
470
+ });
471
+ }
472
+
388
473
  function parsePairingResponse(data: Uint8Array): PairingResponse {
389
474
  const response = JSON.parse(
390
475
  ccc.bytesTo(data, "utf8"),