cairn-p2p 0.2.0 → 0.3.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/dist/index.cjs CHANGED
@@ -1319,6 +1319,67 @@ var MessageQueue = class {
1319
1319
  }
1320
1320
  };
1321
1321
 
1322
+ // src/transport/libp2p-node.ts
1323
+ function defaultTransportConfig() {
1324
+ return {
1325
+ quicEnabled: true,
1326
+ tcpEnabled: true,
1327
+ websocketEnabled: true,
1328
+ webtransportEnabled: true,
1329
+ webrtcEnabled: true,
1330
+ circuitRelayEnabled: true,
1331
+ perTransportTimeoutMs: 1e4,
1332
+ stunServers: [
1333
+ "stun:stun.l.google.com:19302",
1334
+ "stun:stun1.l.google.com:19302"
1335
+ ],
1336
+ turnServers: []
1337
+ };
1338
+ }
1339
+ function isNodeEnvironment() {
1340
+ return typeof globalThis.process !== "undefined" && typeof globalThis.process.versions !== "undefined" && typeof globalThis.process.versions.node !== "undefined";
1341
+ }
1342
+ async function createCairnNode(options) {
1343
+ const config = { ...defaultTransportConfig(), ...options?.config };
1344
+ const { createLibp2p } = await import("libp2p");
1345
+ const { yamux } = await import("@libp2p/yamux");
1346
+ const { noise } = await import("@chainsafe/libp2p-noise");
1347
+ const transports = [];
1348
+ if (isNodeEnvironment()) {
1349
+ if (config.tcpEnabled) {
1350
+ const { tcp } = await import("@libp2p/tcp");
1351
+ transports.push(tcp());
1352
+ }
1353
+ if (config.websocketEnabled) {
1354
+ const { webSockets } = await import("@libp2p/websockets");
1355
+ transports.push(webSockets());
1356
+ }
1357
+ } else {
1358
+ if (config.webrtcEnabled) {
1359
+ const { webRTC } = await import("@libp2p/webrtc");
1360
+ transports.push(webRTC());
1361
+ }
1362
+ if (config.websocketEnabled) {
1363
+ const { webSockets } = await import("@libp2p/websockets");
1364
+ transports.push(webSockets());
1365
+ }
1366
+ if (config.webtransportEnabled) {
1367
+ const { webTransport } = await import("@libp2p/webtransport");
1368
+ transports.push(webTransport());
1369
+ }
1370
+ }
1371
+ if (config.circuitRelayEnabled) {
1372
+ const { circuitRelayTransport } = await import("@libp2p/circuit-relay-v2");
1373
+ transports.push(circuitRelayTransport());
1374
+ }
1375
+ const node = await createLibp2p({
1376
+ transports,
1377
+ streamMuxers: [yamux()],
1378
+ connectionEncrypters: [noise()]
1379
+ });
1380
+ return node;
1381
+ }
1382
+
1322
1383
  // src/node.ts
1323
1384
  var APP_MSG_TYPE_MIN = 61440;
1324
1385
  var APP_MSG_TYPE_MAX = 65535;
@@ -1526,6 +1587,10 @@ var Node = class _Node {
1526
1587
  _closed = false;
1527
1588
  _identity = null;
1528
1589
  _pairedPeers = /* @__PURE__ */ new Set();
1590
+ /** The libp2p node instance (null until startTransport is called). */
1591
+ _libp2pNode = null;
1592
+ /** Listen addresses reported by the libp2p node. */
1593
+ _listenAddresses = [];
1529
1594
  constructor(config) {
1530
1595
  this._config = config;
1531
1596
  }
@@ -1565,6 +1630,40 @@ var Node = class _Node {
1565
1630
  node._identity = await IdentityKeypair.generate();
1566
1631
  return node;
1567
1632
  }
1633
+ /**
1634
+ * Create a node AND start the transport layer.
1635
+ * This is the recommended entry point for applications that need real
1636
+ * network connectivity.
1637
+ */
1638
+ static async createAndStart(config) {
1639
+ const node = await _Node.create(config);
1640
+ await node.startTransport();
1641
+ return node;
1642
+ }
1643
+ /**
1644
+ * Start the libp2p transport layer.
1645
+ *
1646
+ * Creates a libp2p node with environment-appropriate transports
1647
+ * (WebRTC + WebSocket in browser, TCP + WebSocket in Node.js),
1648
+ * starts listening, and populates listen addresses.
1649
+ *
1650
+ * Safe to skip in unit tests — the node works without transport.
1651
+ */
1652
+ async startTransport() {
1653
+ const libp2pNode = await createCairnNode();
1654
+ await libp2pNode.start();
1655
+ this._libp2pNode = libp2pNode;
1656
+ const addrs = libp2pNode.getMultiaddrs();
1657
+ this._listenAddresses = addrs.map((a) => a.toString());
1658
+ }
1659
+ /** Get the libp2p node (null if transport not started). */
1660
+ get libp2pNode() {
1661
+ return this._libp2pNode;
1662
+ }
1663
+ /** Get the node's listen addresses (available after startTransport). */
1664
+ get listenAddresses() {
1665
+ return [...this._listenAddresses];
1666
+ }
1568
1667
  /** Get the node configuration. */
1569
1668
  get config() {
1570
1669
  return this._config;
@@ -1614,11 +1713,12 @@ var Node = class _Node {
1614
1713
  const nonce = generateNonce();
1615
1714
  const now = Math.floor(Date.now() / 1e3);
1616
1715
  const ttlSec = Math.floor(this._config.reconnectionPolicy.pairingPayloadExpiry / 1e3);
1716
+ const hints = this._listenAddresses.length > 0 ? this._listenAddresses.map((addr) => ({ hintType: "multiaddr", value: addr })) : void 0;
1617
1717
  return {
1618
1718
  peerId: this._identity.peerId(),
1619
1719
  nonce,
1620
1720
  pakeCredential: nonce,
1621
- // use nonce as PAKE credential (same as Rust)
1721
+ hints,
1622
1722
  createdAt: now,
1623
1723
  expiresAt: now + ttlSec
1624
1724
  };
package/dist/index.d.cts CHANGED
@@ -1,3 +1,5 @@
1
+ import { Libp2p } from 'libp2p';
2
+
1
3
  /**
2
4
  * Storage adapter interface for persisting identity, per-peer state, and
3
5
  * per-session state. All values are opaque Uint8Array blobs.
@@ -460,6 +462,10 @@ declare class Node {
460
462
  private _closed;
461
463
  private _identity;
462
464
  private readonly _pairedPeers;
465
+ /** The libp2p node instance (null until startTransport is called). */
466
+ private _libp2pNode;
467
+ /** Listen addresses reported by the libp2p node. */
468
+ private _listenAddresses;
463
469
  private constructor();
464
470
  /** Create a new cairn peer node with zero-config defaults (Tier 0). */
465
471
  static create(config?: Partial<CairnConfig>): Promise<Node>;
@@ -470,6 +476,26 @@ declare class Node {
470
476
  * meshEnabled: true, relayWilling: true, relayCapacity: 100, etc.
471
477
  */
472
478
  static createServer(config?: Partial<CairnConfig>): Promise<Node>;
479
+ /**
480
+ * Create a node AND start the transport layer.
481
+ * This is the recommended entry point for applications that need real
482
+ * network connectivity.
483
+ */
484
+ static createAndStart(config?: Partial<CairnConfig>): Promise<Node>;
485
+ /**
486
+ * Start the libp2p transport layer.
487
+ *
488
+ * Creates a libp2p node with environment-appropriate transports
489
+ * (WebRTC + WebSocket in browser, TCP + WebSocket in Node.js),
490
+ * starts listening, and populates listen addresses.
491
+ *
492
+ * Safe to skip in unit tests — the node works without transport.
493
+ */
494
+ startTransport(): Promise<void>;
495
+ /** Get the libp2p node (null if transport not started). */
496
+ get libp2pNode(): Libp2p | null;
497
+ /** Get the node's listen addresses (available after startTransport). */
498
+ get listenAddresses(): string[];
473
499
  /** Get the node configuration. */
474
500
  get config(): ResolvedConfig;
475
501
  /** Whether this node has been closed. */
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { Libp2p } from 'libp2p';
2
+
1
3
  /**
2
4
  * Storage adapter interface for persisting identity, per-peer state, and
3
5
  * per-session state. All values are opaque Uint8Array blobs.
@@ -460,6 +462,10 @@ declare class Node {
460
462
  private _closed;
461
463
  private _identity;
462
464
  private readonly _pairedPeers;
465
+ /** The libp2p node instance (null until startTransport is called). */
466
+ private _libp2pNode;
467
+ /** Listen addresses reported by the libp2p node. */
468
+ private _listenAddresses;
463
469
  private constructor();
464
470
  /** Create a new cairn peer node with zero-config defaults (Tier 0). */
465
471
  static create(config?: Partial<CairnConfig>): Promise<Node>;
@@ -470,6 +476,26 @@ declare class Node {
470
476
  * meshEnabled: true, relayWilling: true, relayCapacity: 100, etc.
471
477
  */
472
478
  static createServer(config?: Partial<CairnConfig>): Promise<Node>;
479
+ /**
480
+ * Create a node AND start the transport layer.
481
+ * This is the recommended entry point for applications that need real
482
+ * network connectivity.
483
+ */
484
+ static createAndStart(config?: Partial<CairnConfig>): Promise<Node>;
485
+ /**
486
+ * Start the libp2p transport layer.
487
+ *
488
+ * Creates a libp2p node with environment-appropriate transports
489
+ * (WebRTC + WebSocket in browser, TCP + WebSocket in Node.js),
490
+ * starts listening, and populates listen addresses.
491
+ *
492
+ * Safe to skip in unit tests — the node works without transport.
493
+ */
494
+ startTransport(): Promise<void>;
495
+ /** Get the libp2p node (null if transport not started). */
496
+ get libp2pNode(): Libp2p | null;
497
+ /** Get the node's listen addresses (available after startTransport). */
498
+ get listenAddresses(): string[];
473
499
  /** Get the node configuration. */
474
500
  get config(): ResolvedConfig;
475
501
  /** Whether this node has been closed. */
package/dist/index.js CHANGED
@@ -1264,6 +1264,67 @@ var MessageQueue = class {
1264
1264
  }
1265
1265
  };
1266
1266
 
1267
+ // src/transport/libp2p-node.ts
1268
+ function defaultTransportConfig() {
1269
+ return {
1270
+ quicEnabled: true,
1271
+ tcpEnabled: true,
1272
+ websocketEnabled: true,
1273
+ webtransportEnabled: true,
1274
+ webrtcEnabled: true,
1275
+ circuitRelayEnabled: true,
1276
+ perTransportTimeoutMs: 1e4,
1277
+ stunServers: [
1278
+ "stun:stun.l.google.com:19302",
1279
+ "stun:stun1.l.google.com:19302"
1280
+ ],
1281
+ turnServers: []
1282
+ };
1283
+ }
1284
+ function isNodeEnvironment() {
1285
+ return typeof globalThis.process !== "undefined" && typeof globalThis.process.versions !== "undefined" && typeof globalThis.process.versions.node !== "undefined";
1286
+ }
1287
+ async function createCairnNode(options) {
1288
+ const config = { ...defaultTransportConfig(), ...options?.config };
1289
+ const { createLibp2p } = await import("libp2p");
1290
+ const { yamux } = await import("@libp2p/yamux");
1291
+ const { noise } = await import("@chainsafe/libp2p-noise");
1292
+ const transports = [];
1293
+ if (isNodeEnvironment()) {
1294
+ if (config.tcpEnabled) {
1295
+ const { tcp } = await import("@libp2p/tcp");
1296
+ transports.push(tcp());
1297
+ }
1298
+ if (config.websocketEnabled) {
1299
+ const { webSockets } = await import("@libp2p/websockets");
1300
+ transports.push(webSockets());
1301
+ }
1302
+ } else {
1303
+ if (config.webrtcEnabled) {
1304
+ const { webRTC } = await import("@libp2p/webrtc");
1305
+ transports.push(webRTC());
1306
+ }
1307
+ if (config.websocketEnabled) {
1308
+ const { webSockets } = await import("@libp2p/websockets");
1309
+ transports.push(webSockets());
1310
+ }
1311
+ if (config.webtransportEnabled) {
1312
+ const { webTransport } = await import("@libp2p/webtransport");
1313
+ transports.push(webTransport());
1314
+ }
1315
+ }
1316
+ if (config.circuitRelayEnabled) {
1317
+ const { circuitRelayTransport } = await import("@libp2p/circuit-relay-v2");
1318
+ transports.push(circuitRelayTransport());
1319
+ }
1320
+ const node = await createLibp2p({
1321
+ transports,
1322
+ streamMuxers: [yamux()],
1323
+ connectionEncrypters: [noise()]
1324
+ });
1325
+ return node;
1326
+ }
1327
+
1267
1328
  // src/node.ts
1268
1329
  var APP_MSG_TYPE_MIN = 61440;
1269
1330
  var APP_MSG_TYPE_MAX = 65535;
@@ -1471,6 +1532,10 @@ var Node = class _Node {
1471
1532
  _closed = false;
1472
1533
  _identity = null;
1473
1534
  _pairedPeers = /* @__PURE__ */ new Set();
1535
+ /** The libp2p node instance (null until startTransport is called). */
1536
+ _libp2pNode = null;
1537
+ /** Listen addresses reported by the libp2p node. */
1538
+ _listenAddresses = [];
1474
1539
  constructor(config) {
1475
1540
  this._config = config;
1476
1541
  }
@@ -1510,6 +1575,40 @@ var Node = class _Node {
1510
1575
  node._identity = await IdentityKeypair.generate();
1511
1576
  return node;
1512
1577
  }
1578
+ /**
1579
+ * Create a node AND start the transport layer.
1580
+ * This is the recommended entry point for applications that need real
1581
+ * network connectivity.
1582
+ */
1583
+ static async createAndStart(config) {
1584
+ const node = await _Node.create(config);
1585
+ await node.startTransport();
1586
+ return node;
1587
+ }
1588
+ /**
1589
+ * Start the libp2p transport layer.
1590
+ *
1591
+ * Creates a libp2p node with environment-appropriate transports
1592
+ * (WebRTC + WebSocket in browser, TCP + WebSocket in Node.js),
1593
+ * starts listening, and populates listen addresses.
1594
+ *
1595
+ * Safe to skip in unit tests — the node works without transport.
1596
+ */
1597
+ async startTransport() {
1598
+ const libp2pNode = await createCairnNode();
1599
+ await libp2pNode.start();
1600
+ this._libp2pNode = libp2pNode;
1601
+ const addrs = libp2pNode.getMultiaddrs();
1602
+ this._listenAddresses = addrs.map((a) => a.toString());
1603
+ }
1604
+ /** Get the libp2p node (null if transport not started). */
1605
+ get libp2pNode() {
1606
+ return this._libp2pNode;
1607
+ }
1608
+ /** Get the node's listen addresses (available after startTransport). */
1609
+ get listenAddresses() {
1610
+ return [...this._listenAddresses];
1611
+ }
1513
1612
  /** Get the node configuration. */
1514
1613
  get config() {
1515
1614
  return this._config;
@@ -1559,11 +1658,12 @@ var Node = class _Node {
1559
1658
  const nonce = generateNonce();
1560
1659
  const now = Math.floor(Date.now() / 1e3);
1561
1660
  const ttlSec = Math.floor(this._config.reconnectionPolicy.pairingPayloadExpiry / 1e3);
1661
+ const hints = this._listenAddresses.length > 0 ? this._listenAddresses.map((addr) => ({ hintType: "multiaddr", value: addr })) : void 0;
1562
1662
  return {
1563
1663
  peerId: this._identity.peerId(),
1564
1664
  nonce,
1565
1665
  pakeCredential: nonce,
1566
- // use nonce as PAKE credential (same as Rust)
1666
+ hints,
1567
1667
  createdAt: now,
1568
1668
  expiresAt: now + ttlSec
1569
1669
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cairn-p2p",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
package/src/node.ts CHANGED
@@ -33,6 +33,9 @@ import type { MessageEnvelope } from './protocol/envelope.js';
33
33
  import { DATA_MESSAGE } from './protocol/message-types.js';
34
34
  import { MessageQueue } from './session/message-queue.js';
35
35
  import type { EnqueueResult } from './session/message-queue.js';
36
+ import type { ConnectionHint } from './pairing/payload.js';
37
+ import type { Libp2p } from 'libp2p';
38
+ import { createCairnNode } from './transport/libp2p-node.js';
36
39
 
37
40
  /** Fully resolved configuration with no optional fields. */
38
41
  export interface ResolvedConfig {
@@ -333,6 +336,10 @@ export class Node {
333
336
  private _closed = false;
334
337
  private _identity: IdentityKeypair | null = null;
335
338
  private readonly _pairedPeers = new Set<string>();
339
+ /** The libp2p node instance (null until startTransport is called). */
340
+ private _libp2pNode: Libp2p | null = null;
341
+ /** Listen addresses reported by the libp2p node. */
342
+ private _listenAddresses: string[] = [];
336
343
 
337
344
  private constructor(config: ResolvedConfig) {
338
345
  this._config = config;
@@ -377,6 +384,46 @@ export class Node {
377
384
  return node;
378
385
  }
379
386
 
387
+ /**
388
+ * Create a node AND start the transport layer.
389
+ * This is the recommended entry point for applications that need real
390
+ * network connectivity.
391
+ */
392
+ static async createAndStart(config?: Partial<CairnConfig>): Promise<Node> {
393
+ const node = await Node.create(config);
394
+ await node.startTransport();
395
+ return node;
396
+ }
397
+
398
+ /**
399
+ * Start the libp2p transport layer.
400
+ *
401
+ * Creates a libp2p node with environment-appropriate transports
402
+ * (WebRTC + WebSocket in browser, TCP + WebSocket in Node.js),
403
+ * starts listening, and populates listen addresses.
404
+ *
405
+ * Safe to skip in unit tests — the node works without transport.
406
+ */
407
+ async startTransport(): Promise<void> {
408
+ const libp2pNode = await createCairnNode();
409
+ await libp2pNode.start();
410
+ this._libp2pNode = libp2pNode;
411
+
412
+ // Collect listen addresses
413
+ const addrs = libp2pNode.getMultiaddrs();
414
+ this._listenAddresses = addrs.map(a => a.toString());
415
+ }
416
+
417
+ /** Get the libp2p node (null if transport not started). */
418
+ get libp2pNode(): Libp2p | null {
419
+ return this._libp2pNode;
420
+ }
421
+
422
+ /** Get the node's listen addresses (available after startTransport). */
423
+ get listenAddresses(): string[] {
424
+ return [...this._listenAddresses];
425
+ }
426
+
380
427
  /** Get the node configuration. */
381
428
  get config(): ResolvedConfig {
382
429
  return this._config;
@@ -436,10 +483,17 @@ export class Node {
436
483
  const nonce = generateNonce();
437
484
  const now = Math.floor(Date.now() / 1000);
438
485
  const ttlSec = Math.floor(this._config.reconnectionPolicy.pairingPayloadExpiry / 1000);
486
+
487
+ // Include listen addresses as connection hints if transport is running
488
+ const hints: ConnectionHint[] | undefined = this._listenAddresses.length > 0
489
+ ? this._listenAddresses.map(addr => ({ hintType: 'multiaddr', value: addr }))
490
+ : undefined;
491
+
439
492
  return {
440
493
  peerId: this._identity.peerId(),
441
494
  nonce,
442
- pakeCredential: nonce, // use nonce as PAKE credential (same as Rust)
495
+ pakeCredential: nonce,
496
+ hints,
443
497
  createdAt: now,
444
498
  expiresAt: now + ttlSec,
445
499
  };