@leofcoin/peernet 1.2.1 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,72 +1,171 @@
1
1
  # peernet
2
2
 
3
+ Peer-to-peer networking for Leofcoin and distributed applications.
4
+
5
+ ## Features
6
+ - Decentralized peer discovery and messaging
7
+ - Pluggable request handlers
8
+ - File and data sharing
9
+ - Works in Node.js, browser, and Electron
10
+
11
+ ## Installation
12
+
13
+ ```sh
14
+ npm install @leofcoin/peernet
15
+ ```
16
+
3
17
  ## Usage
18
+
4
19
  ```js
5
20
  import Peernet from '@leofcoin/peernet'
6
21
 
7
-
8
22
  const config = {
9
23
  network: 'leofcoin:peach',
10
24
  networkVersion: 'peach',
11
25
  stars: ['wss://peach.leofcoin.org']
12
26
  }
13
27
 
14
- const node = await new Peernet(config)
15
- // ... setup some things
28
+ // If your identity is password-protected, prompt for password:
29
+ import passwordPrompt from '@leofcoin/peernet/src/prompts/password/node.js';
30
+ const password = await passwordPrompt();
31
+
32
+ const node = await new Peernet(config, password)
16
33
  await node.start()
17
34
 
18
35
  console.log(globalThis.peernet)
19
36
  ```
20
37
 
21
-
22
-
23
38
  ## API
24
- #### addRequestHandler
25
39
 
26
- examples
40
+ ### addRequestHandler
41
+ Register a handler for custom requests:
42
+
27
43
  ```js
28
- peernet.addRequestHandler('lastBlock', () => {
29
- let response;
44
+ peernet.addRequestHandler('lastBlock', async () => {
30
45
  const height = await chainStore.get('localIndex')
31
46
  const hash = await chainStore.get('localBlock')
32
- response = JSON.stringify({ height: height.toString(), hash: hash.toString() })
47
+ const response = JSON.stringify({ height: height.toString(), hash: hash.toString() })
33
48
  return new ResponseMessage({ response })
34
49
  // or
35
- return new peernet.protos['peernet-response']({ response })
50
+ // return new peernet.protos['peernet-response']({ response })
36
51
  })
37
- ```
38
52
 
39
- ```js
40
53
  peernet.addRequestHandler('hello', () => {
41
54
  return new ResponseMessage({ response: 'hi' })
42
- // or
43
- return new peernet.protos['peernet-response']({ response: 'hi' })
44
55
  })
45
- ```
46
- #### add version to peer
47
- ```js
56
+
48
57
  peernet.addRequestHandler('version', () => {
49
- return new ResponseMessage({ response: {version: 1} })
58
+ return new ResponseMessage({ response: { version: 1 } })
50
59
  })
51
60
  ```
52
61
 
53
- ## Development
62
+ ### Password Prompt
63
+ For password-protected identities, use the provided prompt:
54
64
 
55
- `note: you need to install jsproject`
56
- ```sh
57
- npm i -g @vandeurenglenn/project
65
+ - **Node.js:** `import passwordPrompt from '@leofcoin/peernet/src/prompts/password/node.js'`
66
+ - **Browser:** `import passwordPrompt from '@leofcoin/peernet/src/prompts/password/browser.js'`
67
+
68
+ ```js
69
+ const password = await passwordPrompt();
70
+ const node = await new Peernet(config, password);
58
71
  ```
59
72
 
60
- ### watch
61
- ```sh
62
- npm run w
73
+ ## In-Memory Broadcasts
74
+
75
+ Peernet supports in-memory data sharing for fast, temporary distribution of files or data objects between peers. This feature allows you to broadcast a file or data object, which is then available for retrieval by other peers (or yourself) until the process is restarted or the in-memory cache is cleared.
76
+
77
+ ### Usage Example
78
+
79
+ ```js
80
+ // Broadcast a file or data object in-memory
81
+ const fileObj = {
82
+ path: '/example',
83
+ content: new TextEncoder().encode('hello world'),
84
+ links: []
85
+ };
86
+ const hash = await peernet.broadcast(fileObj);
87
+
88
+ // Another peer can request the data by hash
89
+ // (handled internally by peernet.handleData)
63
90
  ```
64
- ### compile
65
- ```sh
66
- npm run c
91
+
92
+ ### Detailed Example
93
+
94
+ ```js
95
+ // 1. Broadcast a file object in-memory
96
+ const fileObj = {
97
+ path: '/example',
98
+ content: new TextEncoder().encode('Hello Peernet!'),
99
+ links: []
100
+ };
101
+ const hash = await peernet.broadcast(fileObj);
102
+ console.log('Broadcasted hash:', hash);
103
+
104
+ // 2. Simulate a peer requesting the data by hash
105
+ const mockPeer = {
106
+ connected: true,
107
+ send: async (data, id) => {
108
+ // Handle the received data (for demo/testing)
109
+ const DataResponseProto = globalThis.peernet.protos['peernet-data-response'];
110
+ const decodedProto = await new DataResponseProto(data);
111
+ await decodedProto.decode();
112
+ const decodedContent = new TextDecoder().decode(decodedProto.decoded.data);
113
+ console.log('Received content:', decodedContent);
114
+ }
115
+ };
116
+ const proto = { decoded: { hash } };
117
+ await peernet.handleData(mockPeer, 'test-id', proto);
118
+ ```
119
+
120
+ ### Data Flow Diagram
121
+
122
+ ```
123
+ [You/Peer] --(broadcast)--> [Peernet Node: In-Memory Map]
124
+ ^ |
125
+ | |
126
+ +------(handleData request)--------+
67
127
  ```
68
- ### serve html
128
+
129
+ - `broadcast()` stores the data in-memory and returns a hash.
130
+ - `handleData()` retrieves the data by hash and sends it to the requesting peer.
131
+
132
+ - The broadcasted data is stored in-memory and is not persisted to disk.
133
+ - Retrieval is by hash, matching the value returned from `broadcast()`.
134
+ - The feature is useful for ephemeral data, rapid prototyping, or testing.
135
+ - If the process restarts, the in-memory data is lost.
136
+
137
+ ### Testing
138
+
139
+ Automated tests verify that broadcasting and retrieving in-memory data works as expected, including content integrity and hash matching.
140
+
141
+ ## Development
142
+
143
+ > **Note:** You need to install [jsproject](https://www.npmjs.com/package/@vandeurenglenn/project)
144
+
69
145
  ```sh
70
- npm run demo
146
+ npm i -g @vandeurenglenn/project
71
147
  ```
72
148
 
149
+ ### Scripts
150
+
151
+ - **Watch:**
152
+ ```sh
153
+ npm run watch
154
+ ```
155
+ - **Build:**
156
+ ```sh
157
+ npm run build
158
+ ```
159
+ - **Test:**
160
+ ```sh
161
+ npm test
162
+ ```
163
+ - **Serve HTML Demo:**
164
+ ```sh
165
+ npm run demo
166
+ ```
167
+
168
+ ## License
169
+
170
+ MIT
171
+
@@ -1,5 +1,20 @@
1
1
  import { g as getDefaultExportFromCjs } from './identity-nIyW_Xm8.js';
2
2
 
3
+ function _mergeNamespaces(n, m) {
4
+ m.forEach(function (e) {
5
+ e && typeof e !== 'string' && !Array.isArray(e) && Object.keys(e).forEach(function (k) {
6
+ if (k !== 'default' && !(k in n)) {
7
+ var d = Object.getOwnPropertyDescriptor(e, k);
8
+ Object.defineProperty(n, k, d.get ? d : {
9
+ enumerable: true,
10
+ get: function () { return e[k]; }
11
+ });
12
+ }
13
+ });
14
+ });
15
+ return Object.freeze(n);
16
+ }
17
+
3
18
  var global;
4
19
  var hasRequiredGlobal;
5
20
 
@@ -124,9 +139,9 @@ function requireBrowser () {
124
139
  var browserExports = requireBrowser();
125
140
  var browser = /*@__PURE__*/getDefaultExportFromCjs(browserExports);
126
141
 
127
- var browser$1 = /*#__PURE__*/Object.freeze({
142
+ var browser$1 = /*#__PURE__*/_mergeNamespaces({
128
143
  __proto__: null,
129
144
  default: browser
130
- });
145
+ }, [browserExports]);
131
146
 
132
147
  export { browser$1 as b };
@@ -1,5 +1,20 @@
1
1
  import { g as getDefaultExportFromCjs } from './identity-nIyW_Xm8.js';
2
2
 
3
+ function _mergeNamespaces(n, m) {
4
+ m.forEach(function (e) {
5
+ e && typeof e !== 'string' && !Array.isArray(e) && Object.keys(e).forEach(function (k) {
6
+ if (k !== 'default' && !(k in n)) {
7
+ var d = Object.getOwnPropertyDescriptor(e, k);
8
+ Object.defineProperty(n, k, d.get ? d : {
9
+ enumerable: true,
10
+ get: function () { return e[k]; }
11
+ });
12
+ }
13
+ });
14
+ });
15
+ return Object.freeze(n);
16
+ }
17
+
3
18
  var browser$2 = {};
4
19
 
5
20
  var hasRequiredBrowser;
@@ -30,9 +45,9 @@ function requireBrowser () {
30
45
  var browserExports = requireBrowser();
31
46
  var browser = /*@__PURE__*/getDefaultExportFromCjs(browserExports);
32
47
 
33
- var browser$1 = /*#__PURE__*/Object.freeze({
48
+ var browser$1 = /*#__PURE__*/_mergeNamespaces({
34
49
  __proto__: null,
35
50
  default: browser
36
- });
51
+ }, [browserExports]);
37
52
 
38
53
  export { browser$1 as b };
@@ -1,4 +1,4 @@
1
- import { L as LittlePubSub, d as deflate_1, c as createDebugger, i as inflate_1 } from './peernet-CXod4Dva.js';
1
+ import { L as LittlePubSub, d as deflate_1, i as inflate_1, c as createDebugger } from './peernet-BuZSSOa0.js';
2
2
  import './identity-nIyW_Xm8.js';
3
3
  import './value-C3vAp-wb.js';
4
4
 
@@ -231,7 +231,7 @@ class SocketRequestClient {
231
231
  const init = async () => {
232
232
  // @ts-ignore
233
233
  if (!globalThis.WebSocket && !this.#experimentalWebsocket)
234
- globalThis.WebSocket = (await import('./browser-CfYI-6aD.js').then(function (n) { return n.b; })).default.w3cwebsocket;
234
+ globalThis.WebSocket = (await import('./browser-CWWW0kSj.js').then(function (n) { return n.b; })).default.w3cwebsocket;
235
235
  const client = new WebSocket(this.#url, this.#protocol);
236
236
  if (this.#experimentalWebsocket) {
237
237
  client.addEventListener('error', this.onerror);
@@ -346,7 +346,7 @@ const iceServers = [
346
346
  credential: 'openrelayproject'
347
347
  }
348
348
  ];
349
- const SimplePeer = (await import('./index-DTbjK0sK.js').then(function (n) { return n.i; })).default;
349
+ const SimplePeer = (await import('./index-BoQ4uWm2.js').then(function (n) { return n.i; })).default;
350
350
  class Peer extends SimplePeer {
351
351
  peerId;
352
352
  channelName;
@@ -616,7 +616,7 @@ class Client {
616
616
  }
617
617
  async _init() {
618
618
  if (!globalThis.RTCPeerConnection)
619
- globalThis.wrtc = (await import('./browser-Qcpp3EKK.js').then(function (n) { return n.b; })).default;
619
+ globalThis.wrtc = (await import('./browser-DMVbMScf.js').then(function (n) { return n.b; })).default;
620
620
  for (const star of this.starsConfig) {
621
621
  try {
622
622
  await this.setupStar(star);
@@ -1,6 +1,21 @@
1
1
  import { h as commonjsGlobal, r as require$$3, j as requireInherits_browser, g as getDefaultExportFromCjs } from './identity-nIyW_Xm8.js';
2
2
  import require$$0 from 'events';
3
3
 
4
+ function _mergeNamespaces(n, m) {
5
+ m.forEach(function (e) {
6
+ e && typeof e !== 'string' && !Array.isArray(e) && Object.keys(e).forEach(function (k) {
7
+ if (k !== 'default' && !(k in n)) {
8
+ var d = Object.getOwnPropertyDescriptor(e, k);
9
+ Object.defineProperty(n, k, d.get ? d : {
10
+ enumerable: true,
11
+ get: function () { return e[k]; }
12
+ });
13
+ }
14
+ });
15
+ });
16
+ return Object.freeze(n);
17
+ }
18
+
4
19
  var browser$2 = {exports: {}};
5
20
 
6
21
  /**
@@ -7572,9 +7587,9 @@ function requireSimplePeer () {
7572
7587
  var simplePeerExports = requireSimplePeer();
7573
7588
  var index = /*@__PURE__*/getDefaultExportFromCjs(simplePeerExports);
7574
7589
 
7575
- var index$1 = /*#__PURE__*/Object.freeze({
7590
+ var index$1 = /*#__PURE__*/_mergeNamespaces({
7576
7591
  __proto__: null,
7577
7592
  default: index
7578
- });
7593
+ }, [simplePeerExports]);
7579
7594
 
7580
7595
  export { index$1 as i };
@@ -1,4 +1,4 @@
1
- import { F as FormatInterface } from './peernet-CXod4Dva.js';
1
+ import { F as FormatInterface } from './peernet-BuZSSOa0.js';
2
2
  import './identity-nIyW_Xm8.js';
3
3
  import './value-C3vAp-wb.js';
4
4
 
@@ -184,7 +184,7 @@ class ChatMessage extends FormatInterface {
184
184
 
185
185
  var proto = {
186
186
  path: String(),
187
- 'content?': '',
187
+ 'content?': new Uint8Array(),
188
188
  'links?': []
189
189
  };
190
190
 
@@ -27,58 +27,73 @@ const createDebugger = (target) => {
27
27
  };
28
28
 
29
29
  class LittlePubSub {
30
- subscribers = {};
30
+ subscribers = new Map();
31
31
  verbose;
32
32
  constructor(verbose) {
33
- this.verbose = verbose;
34
- }
35
- _handleContext(handler, context) {
36
- if (typeof context === 'undefined') {
37
- context = handler;
38
- }
39
- return context;
33
+ this.verbose = verbose ?? false;
40
34
  }
41
35
  hasSubscribers(event) {
42
- return this.subscribers[event] ? true : false;
36
+ return this.subscribers.has(event);
37
+ }
38
+ subscriberCount(event) {
39
+ return this.subscribers.get(event)?.handlers.length ?? 0;
40
+ }
41
+ clear() {
42
+ this.subscribers.clear();
43
43
  }
44
44
  getValue(event) {
45
- if (this.subscribers[event])
46
- return this.subscribers[event].value;
47
- return undefined;
45
+ return this.subscribers.get(event)?.value;
48
46
  }
49
47
  subscribe(event, handler, options) {
50
- if (!this.hasSubscribers(event))
51
- this.subscribers[event] = { handlers: [], value: undefined };
52
- const context = this._handleContext(handler, options?.context);
53
- const _handler = handler.bind(context);
54
- this.subscribers[event].handlers.push(_handler);
55
- if (this.subscribers[event].value !== undefined)
56
- _handler(this.subscribers[event].value, undefined);
48
+ let subscriber = this.subscribers.get(event);
49
+ if (subscriber === undefined) {
50
+ subscriber = { handlers: [], value: undefined };
51
+ this.subscribers.set(event, subscriber);
52
+ }
53
+ // Only bind if context is provided
54
+ const context = options?.context;
55
+ const boundHandler = context
56
+ ? handler.bind(context)
57
+ : handler;
58
+ subscriber.handlers.push({ original: handler, bound: boundHandler });
59
+ // Call handler immediately if value already exists
60
+ if (subscriber.value !== undefined) {
61
+ boundHandler(subscriber.value, undefined);
62
+ }
63
+ // Return unsubscribe function
64
+ return () => this.unsubscribe(event, handler, options);
57
65
  }
58
66
  unsubscribe(event, handler, options) {
59
- if (!options)
60
- options = { keepValue: false };
61
- if (!this.hasSubscribers(event))
67
+ const subscriber = this.subscribers.get(event);
68
+ if (subscriber === undefined)
62
69
  return;
63
- const context = this._handleContext(handler, options.context);
64
- const index = this.subscribers[event].handlers.indexOf(handler.bind(context));
65
- this.subscribers[event].handlers.splice(index);
66
- // delete event if no handlers left but supports keeping value for later use
67
- // (like when unsubscribing from a value that is still needed because others might subscibe to it)
68
- if (this.subscribers[event].handlers.length === 0 && !options.keepValue)
69
- delete this.subscribers[event];
70
+ const handlers = subscriber.handlers;
71
+ // Find and remove handler by original reference
72
+ for (let i = 0, len = handlers.length; i < len; i++) {
73
+ if (handlers[i].original === handler) {
74
+ handlers.splice(i, 1);
75
+ break;
76
+ }
77
+ }
78
+ // Delete event if no handlers left (unless keepValue is true)
79
+ if (handlers.length === 0 && !options?.keepValue) {
80
+ this.subscribers.delete(event);
81
+ }
70
82
  }
71
83
  publish(event, value, verbose) {
72
- // always set value even when having no subscribers
73
- if (!this.hasSubscribers(event))
74
- this.subscribers[event] = {
75
- handlers: []
76
- };
77
- const oldValue = this.subscribers[event]?.value;
84
+ let subscriber = this.subscribers.get(event);
85
+ if (subscriber === undefined) {
86
+ subscriber = { handlers: [], value: undefined };
87
+ this.subscribers.set(event, subscriber);
88
+ }
89
+ const oldValue = subscriber.value;
90
+ // Only trigger handlers if verbose or value changed
78
91
  if (this.verbose || verbose || oldValue !== value) {
79
- this.subscribers[event].value = value;
80
- for (const handler of this.subscribers[event].handlers) {
81
- handler(value, oldValue);
92
+ subscriber.value = value;
93
+ const handlers = subscriber.handlers;
94
+ const len = handlers.length;
95
+ for (let i = 0; i < len; i++) {
96
+ handlers[i].bound(value, oldValue);
82
97
  }
83
98
  }
84
99
  }
@@ -86,12 +101,21 @@ class LittlePubSub {
86
101
  this.publish(event, value, true);
87
102
  }
88
103
  once(event, options) {
89
- return new Promise((resolve) => {
90
- const cb = (value) => {
104
+ return new Promise((resolve, reject) => {
105
+ let timeoutId;
106
+ const handler = (value) => {
107
+ if (timeoutId !== undefined)
108
+ clearTimeout(timeoutId);
91
109
  resolve(value);
92
- this.unsubscribe(event, cb, options);
110
+ this.unsubscribe(event, handler, options);
93
111
  };
94
- this.subscribe(event, cb, options);
112
+ this.subscribe(event, handler, options);
113
+ if (options?.timeout !== undefined) {
114
+ timeoutId = setTimeout(() => {
115
+ this.unsubscribe(event, handler, options);
116
+ reject(new Error(`Timeout waiting for event "${event}"`));
117
+ }, options.timeout);
118
+ }
95
119
  });
96
120
  }
97
121
  }
@@ -8244,6 +8268,7 @@ class Peernet {
8244
8268
  protos;
8245
8269
  version;
8246
8270
  #peerAttempts = {};
8271
+ _inMemoryBroadcasts;
8247
8272
  /**
8248
8273
  * @access public
8249
8274
  * @param {Object} options
@@ -8355,7 +8380,7 @@ class Peernet {
8355
8380
  this.root = options.root;
8356
8381
  const { RequestMessage, ResponseMessage, PeerMessage, PeerMessageResponse, PeernetMessage, DHTMessage, DHTMessageResponse, DataMessage, DataMessageResponse, PsMessage, ChatMessage, PeernetFile
8357
8382
  // FolderMessageResponse
8358
- } = await import(/* webpackChunkName: "messages" */ './messages-C507MMRx.js');
8383
+ } = await import(/* webpackChunkName: "messages" */ './messages-C-z7iw3I.js');
8359
8384
  /**
8360
8385
  * proto Object containing protos
8361
8386
  * @type {Object}
@@ -8449,7 +8474,7 @@ class Peernet {
8449
8474
  if (this.#starting || this.#started)
8450
8475
  return;
8451
8476
  this.#starting = true;
8452
- const importee = await import('./client-CWkdUcxK.js');
8477
+ const importee = await import('./client-BqvqxCzm.js');
8453
8478
  /**
8454
8479
  * @access public
8455
8480
  * @type {PeernetClient}
@@ -8520,10 +8545,69 @@ class Peernet {
8520
8545
  const node = await this.prepareMessage(data);
8521
8546
  this.sendMessage(peer, id, node.encoded);
8522
8547
  }
8548
+ /**
8549
+ * Broadcasts data to the network and returns a hash that can be used by another peer
8550
+ * to directly connect and download the data from the broadcasting peer.
8551
+ *
8552
+ * @param {Uint8Array|Buffer|Object|string} data - The data to broadcast
8553
+ * @param {string} [storeName='data'] - The store to use for storing the data
8554
+ * @returns {Promise<string>} The hash that can be shared for direct download
8555
+ */
8556
+ /**
8557
+ * Broadcasts data to the network and returns a hash that can be used by another peer
8558
+ * to directly connect and download the data from the broadcasting peer.
8559
+ * The data is kept in memory only and not persisted to storage.
8560
+ *
8561
+ * @param {Uint8Array|Buffer|Object|string} data - The data to broadcast
8562
+ * @returns {Promise<string>} The hash that can be shared for direct download
8563
+ */
8564
+ async broadcast(data) {
8565
+ let protoInput;
8566
+ if (typeof data === 'string') {
8567
+ protoInput = { path: '/', content: new TextEncoder().encode(data) };
8568
+ }
8569
+ else if (data instanceof Uint8Array || data instanceof Buffer) {
8570
+ protoInput = { path: '/', content: data };
8571
+ }
8572
+ else if (typeof data === 'object' && data.path) {
8573
+ protoInput = data;
8574
+ }
8575
+ else {
8576
+ // fallback: treat as JSON string
8577
+ protoInput = { path: '/', content: new TextEncoder().encode(JSON.stringify(data)) };
8578
+ }
8579
+ const protoNode = await new globalThis.peernet.protos['peernet-file'](protoInput);
8580
+ const hash = await protoNode.hash();
8581
+ const encoded = await protoNode.encoded;
8582
+ if (!this._inMemoryBroadcasts)
8583
+ this._inMemoryBroadcasts = new Map();
8584
+ this._inMemoryBroadcasts.set(hash, encoded);
8585
+ await this.publish('broadcast', { hash, from: this.id });
8586
+ return hash;
8587
+ }
8523
8588
  async handleData(peer, id, proto) {
8524
8589
  let { hash, store } = proto.decoded;
8525
8590
  let data;
8526
8591
  try {
8592
+ if (this._inMemoryBroadcasts && this._inMemoryBroadcasts.has(hash)) {
8593
+ data = this._inMemoryBroadcasts.get(hash);
8594
+ let resolvedHash = hash;
8595
+ if (typeof hash === 'function') {
8596
+ resolvedHash = await hash();
8597
+ }
8598
+ // Decode the stored proto to extract the content
8599
+ const FileProto = globalThis.peernet.protos['peernet-file'];
8600
+ const fileProto = await new FileProto(data);
8601
+ await fileProto.decode();
8602
+ const fileContent = fileProto.decoded.content;
8603
+ data = await new globalThis.peernet.protos['peernet-data-response']({
8604
+ hash: resolvedHash,
8605
+ data: fileContent
8606
+ });
8607
+ const node = await this.prepareMessage(data);
8608
+ await this.sendMessage(peer, id, node.encoded);
8609
+ return;
8610
+ }
8527
8611
  store = globalThis[`${store}Store`] || (await this.whichStore([...this.stores], hash));
8528
8612
  if (store && !store.private) {
8529
8613
  data = await store.get(hash);
@@ -8533,7 +8617,7 @@ class Peernet {
8533
8617
  data
8534
8618
  });
8535
8619
  const node = await this.prepareMessage(data);
8536
- this.sendMessage(peer, id, node.encoded);
8620
+ await this.sendMessage(peer, id, node.encoded);
8537
8621
  }
8538
8622
  }
8539
8623
  else {
@@ -8541,6 +8625,7 @@ class Peernet {
8541
8625
  }
8542
8626
  }
8543
8627
  catch (error) {
8628
+ console.error('handleData: error', error);
8544
8629
  return this.requestData(hash, store);
8545
8630
  }
8546
8631
  }
@@ -50,6 +50,7 @@ export default class Peernet {
50
50
  _peerHandler: PeerDiscovery;
51
51
  protos: {};
52
52
  version: any;
53
+ private _inMemoryBroadcasts;
53
54
  /**
54
55
  * @access public
55
56
  * @param {Object} options
@@ -106,6 +107,27 @@ export default class Peernet {
106
107
  addRequestHandler(name: any, method: any): void;
107
108
  sendMessage(peer: any, id: any, data: any): Promise<any>;
108
109
  handleDHT(peer: any, id: any, proto: any): Promise<void>;
110
+ /**
111
+ * Broadcasts data to the network and returns a hash that can be used by another peer
112
+ * to directly connect and download the data from the broadcasting peer.
113
+ *
114
+ * @param {Uint8Array|Buffer|Object|string} data - The data to broadcast
115
+ * @param {string} [storeName='data'] - The store to use for storing the data
116
+ * @returns {Promise<string>} The hash that can be shared for direct download
117
+ */
118
+ /**
119
+ * Broadcasts data to the network and returns a hash that can be used by another peer
120
+ * to directly connect and download the data from the broadcasting peer.
121
+ * The data is kept in memory only and not persisted to storage.
122
+ *
123
+ * @param {Uint8Array|Buffer|Object|string} data - The data to broadcast
124
+ * @returns {Promise<string>} The hash that can be shared for direct download
125
+ */
126
+ broadcast(data: Uint8Array | Buffer | {
127
+ path: string;
128
+ content?: Uint8Array;
129
+ links?: any[];
130
+ } | string): Promise<string>;
109
131
  handleData(peer: any, id: any, proto: any): Promise<any>;
110
132
  handleRequest(peer: any, id: any, proto: any): Promise<void>;
111
133
  /**
@@ -1,3 +1,3 @@
1
- export { P as default } from './peernet-CXod4Dva.js';
1
+ export { P as default } from './peernet-BuZSSOa0.js';
2
2
  import './identity-nIyW_Xm8.js';
3
3
  import './value-C3vAp-wb.js';
@@ -1,6 +1,6 @@
1
1
  declare const _default: {
2
2
  path: string;
3
- 'content?': string;
3
+ 'content?': Uint8Array<ArrayBuffer>;
4
4
  'links?': any[];
5
5
  };
6
6
  export default _default;
@@ -182,7 +182,7 @@ class ChatMessage extends FormatInterface {
182
182
 
183
183
  var proto = {
184
184
  path: String(),
185
- 'content?': '',
185
+ 'content?': new Uint8Array(),
186
186
  'links?': []
187
187
  };
188
188
 
@@ -336,6 +336,7 @@ class Peernet {
336
336
  protos;
337
337
  version;
338
338
  #peerAttempts = {};
339
+ _inMemoryBroadcasts;
339
340
  /**
340
341
  * @access public
341
342
  * @param {Object} options
@@ -447,7 +448,7 @@ class Peernet {
447
448
  this.root = options.root;
448
449
  const { RequestMessage, ResponseMessage, PeerMessage, PeerMessageResponse, PeernetMessage, DHTMessage, DHTMessageResponse, DataMessage, DataMessageResponse, PsMessage, ChatMessage, PeernetFile
449
450
  // FolderMessageResponse
450
- } = await import(/* webpackChunkName: "messages" */ './messages-CsDqTaBh.js');
451
+ } = await import(/* webpackChunkName: "messages" */ './messages-C9lYBAhD.js');
451
452
  /**
452
453
  * proto Object containing protos
453
454
  * @type {Object}
@@ -612,10 +613,69 @@ class Peernet {
612
613
  const node = await this.prepareMessage(data);
613
614
  this.sendMessage(peer, id, node.encoded);
614
615
  }
616
+ /**
617
+ * Broadcasts data to the network and returns a hash that can be used by another peer
618
+ * to directly connect and download the data from the broadcasting peer.
619
+ *
620
+ * @param {Uint8Array|Buffer|Object|string} data - The data to broadcast
621
+ * @param {string} [storeName='data'] - The store to use for storing the data
622
+ * @returns {Promise<string>} The hash that can be shared for direct download
623
+ */
624
+ /**
625
+ * Broadcasts data to the network and returns a hash that can be used by another peer
626
+ * to directly connect and download the data from the broadcasting peer.
627
+ * The data is kept in memory only and not persisted to storage.
628
+ *
629
+ * @param {Uint8Array|Buffer|Object|string} data - The data to broadcast
630
+ * @returns {Promise<string>} The hash that can be shared for direct download
631
+ */
632
+ async broadcast(data) {
633
+ let protoInput;
634
+ if (typeof data === 'string') {
635
+ protoInput = { path: '/', content: new TextEncoder().encode(data) };
636
+ }
637
+ else if (data instanceof Uint8Array || data instanceof Buffer) {
638
+ protoInput = { path: '/', content: data };
639
+ }
640
+ else if (typeof data === 'object' && data.path) {
641
+ protoInput = data;
642
+ }
643
+ else {
644
+ // fallback: treat as JSON string
645
+ protoInput = { path: '/', content: new TextEncoder().encode(JSON.stringify(data)) };
646
+ }
647
+ const protoNode = await new globalThis.peernet.protos['peernet-file'](protoInput);
648
+ const hash = await protoNode.hash();
649
+ const encoded = await protoNode.encoded;
650
+ if (!this._inMemoryBroadcasts)
651
+ this._inMemoryBroadcasts = new Map();
652
+ this._inMemoryBroadcasts.set(hash, encoded);
653
+ await this.publish('broadcast', { hash, from: this.id });
654
+ return hash;
655
+ }
615
656
  async handleData(peer, id, proto) {
616
657
  let { hash, store } = proto.decoded;
617
658
  let data;
618
659
  try {
660
+ if (this._inMemoryBroadcasts && this._inMemoryBroadcasts.has(hash)) {
661
+ data = this._inMemoryBroadcasts.get(hash);
662
+ let resolvedHash = hash;
663
+ if (typeof hash === 'function') {
664
+ resolvedHash = await hash();
665
+ }
666
+ // Decode the stored proto to extract the content
667
+ const FileProto = globalThis.peernet.protos['peernet-file'];
668
+ const fileProto = await new FileProto(data);
669
+ await fileProto.decode();
670
+ const fileContent = fileProto.decoded.content;
671
+ data = await new globalThis.peernet.protos['peernet-data-response']({
672
+ hash: resolvedHash,
673
+ data: fileContent
674
+ });
675
+ const node = await this.prepareMessage(data);
676
+ await this.sendMessage(peer, id, node.encoded);
677
+ return;
678
+ }
619
679
  store = globalThis[`${store}Store`] || (await this.whichStore([...this.stores], hash));
620
680
  if (store && !store.private) {
621
681
  data = await store.get(hash);
@@ -625,7 +685,7 @@ class Peernet {
625
685
  data
626
686
  });
627
687
  const node = await this.prepareMessage(data);
628
- this.sendMessage(peer, id, node.encoded);
688
+ await this.sendMessage(peer, id, node.encoded);
629
689
  }
630
690
  }
631
691
  else {
@@ -633,6 +693,7 @@ class Peernet {
633
693
  }
634
694
  }
635
695
  catch (error) {
696
+ console.error('handleData: error', error);
636
697
  return this.requestData(hash, store);
637
698
  }
638
699
  }
@@ -50,6 +50,7 @@ export default class Peernet {
50
50
  _peerHandler: PeerDiscovery;
51
51
  protos: {};
52
52
  version: any;
53
+ private _inMemoryBroadcasts;
53
54
  /**
54
55
  * @access public
55
56
  * @param {Object} options
@@ -106,6 +107,27 @@ export default class Peernet {
106
107
  addRequestHandler(name: any, method: any): void;
107
108
  sendMessage(peer: any, id: any, data: any): Promise<any>;
108
109
  handleDHT(peer: any, id: any, proto: any): Promise<void>;
110
+ /**
111
+ * Broadcasts data to the network and returns a hash that can be used by another peer
112
+ * to directly connect and download the data from the broadcasting peer.
113
+ *
114
+ * @param {Uint8Array|Buffer|Object|string} data - The data to broadcast
115
+ * @param {string} [storeName='data'] - The store to use for storing the data
116
+ * @returns {Promise<string>} The hash that can be shared for direct download
117
+ */
118
+ /**
119
+ * Broadcasts data to the network and returns a hash that can be used by another peer
120
+ * to directly connect and download the data from the broadcasting peer.
121
+ * The data is kept in memory only and not persisted to storage.
122
+ *
123
+ * @param {Uint8Array|Buffer|Object|string} data - The data to broadcast
124
+ * @returns {Promise<string>} The hash that can be shared for direct download
125
+ */
126
+ broadcast(data: Uint8Array | Buffer | {
127
+ path: string;
128
+ content?: Uint8Array;
129
+ links?: any[];
130
+ } | string): Promise<string>;
109
131
  handleData(peer: any, id: any, proto: any): Promise<any>;
110
132
  handleRequest(peer: any, id: any, proto: any): Promise<void>;
111
133
  /**
@@ -1,6 +1,6 @@
1
1
  declare const _default: {
2
2
  path: string;
3
- 'content?': string;
3
+ 'content?': Uint8Array<ArrayBuffer>;
4
4
  'links?': any[];
5
5
  };
6
6
  export default _default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leofcoin/peernet",
3
- "version": "1.2.1",
3
+ "version": "1.2.2",
4
4
  "description": "",
5
5
  "browser": "./exports/browser/peernet.js",
6
6
  "exports": {
@@ -37,13 +37,15 @@
37
37
  "@leofcoin/identity-utils": "^1.0.2",
38
38
  "@leofcoin/multi-wallet": "^3.1.8",
39
39
  "@leofcoin/storage": "^3.5.38",
40
+ "@mapbox/node-pre-gyp": "^2.0.3",
40
41
  "@netpeer/swarm": "^0.9.0",
41
42
  "@vandeurenglenn/base58": "^1.1.9",
42
43
  "@vandeurenglenn/debug": "^1.4.0",
43
- "@vandeurenglenn/little-pubsub": "^1.5.1",
44
- "inquirer": "^13.1.0",
44
+ "@vandeurenglenn/little-pubsub": "^1.5.2",
45
+ "inquirer": "^13.2.1",
45
46
  "qr-scanner": "^1.4.2",
46
- "qrcode": "^1.5.4"
47
+ "qrcode": "^1.5.4",
48
+ "tar": "^7.5.6"
47
49
  },
48
50
  "devDependencies": {
49
51
  "@rollup/plugin-commonjs": "^29.0.0",
@@ -51,8 +53,8 @@
51
53
  "@rollup/plugin-node-resolve": "^16.0.3",
52
54
  "@rollup/plugin-typescript": "^12.3.0",
53
55
  "@rollup/plugin-wasm": "^6.2.2",
54
- "@types/node": "^25.0.3",
56
+ "@types/node": "^25.0.10",
55
57
  "@types/qrcode": "^1.5.6",
56
- "rollup": "^4.54.0"
58
+ "rollup": "^4.56.0"
57
59
  }
58
60
  }
package/src/peernet.ts CHANGED
@@ -67,6 +67,7 @@ export default class Peernet {
67
67
  version
68
68
 
69
69
  #peerAttempts: { [key: string]: number } = {}
70
+ private _inMemoryBroadcasts: any
70
71
  /**
71
72
  * @access public
72
73
  * @param {Object} options
@@ -394,10 +395,71 @@ export default class Peernet {
394
395
  this.sendMessage(peer, id, node.encoded)
395
396
  }
396
397
 
398
+ /**
399
+ * Broadcasts data to the network and returns a hash that can be used by another peer
400
+ * to directly connect and download the data from the broadcasting peer.
401
+ *
402
+ * @param {Uint8Array|Buffer|Object|string} data - The data to broadcast
403
+ * @param {string} [storeName='data'] - The store to use for storing the data
404
+ * @returns {Promise<string>} The hash that can be shared for direct download
405
+ */
406
+ /**
407
+ * Broadcasts data to the network and returns a hash that can be used by another peer
408
+ * to directly connect and download the data from the broadcasting peer.
409
+ * The data is kept in memory only and not persisted to storage.
410
+ *
411
+ * @param {Uint8Array|Buffer|Object|string} data - The data to broadcast
412
+ * @returns {Promise<string>} The hash that can be shared for direct download
413
+ */
414
+ async broadcast(
415
+ data: Uint8Array | Buffer | { path: string; content?: Uint8Array; links?: any[] } | string
416
+ ): Promise<string> {
417
+ let protoInput: any
418
+ if (typeof data === 'string') {
419
+ protoInput = { path: '/', content: new TextEncoder().encode(data) }
420
+ } else if (data instanceof Uint8Array || data instanceof Buffer) {
421
+ protoInput = { path: '/', content: data }
422
+ } else if (typeof data === 'object' && data.path) {
423
+ protoInput = data
424
+ } else {
425
+ // fallback: treat as JSON string
426
+ protoInput = { path: '/', content: new TextEncoder().encode(JSON.stringify(data)) }
427
+ }
428
+
429
+ const protoNode = await new globalThis.peernet.protos['peernet-file'](protoInput)
430
+ const hash = await protoNode.hash()
431
+ const encoded = await protoNode.encoded
432
+ if (!this._inMemoryBroadcasts) this._inMemoryBroadcasts = new Map()
433
+ this._inMemoryBroadcasts.set(hash, encoded)
434
+
435
+ await this.publish('broadcast', { hash, from: this.id })
436
+ return hash
437
+ }
438
+
397
439
  async handleData(peer, id, proto) {
398
440
  let { hash, store } = proto.decoded
399
441
  let data
400
442
  try {
443
+ if (this._inMemoryBroadcasts && this._inMemoryBroadcasts.has(hash)) {
444
+ data = this._inMemoryBroadcasts.get(hash)
445
+ let resolvedHash = hash
446
+ if (typeof hash === 'function') {
447
+ resolvedHash = await hash()
448
+ }
449
+ // Decode the stored proto to extract the content
450
+ const FileProto = globalThis.peernet.protos['peernet-file']
451
+ const fileProto = await new FileProto(data)
452
+ await fileProto.decode()
453
+ const fileContent = fileProto.decoded.content
454
+ data = await new globalThis.peernet.protos['peernet-data-response']({
455
+ hash: resolvedHash,
456
+ data: fileContent
457
+ })
458
+ const node = await this.prepareMessage(data)
459
+ await this.sendMessage(peer, id, node.encoded)
460
+ return
461
+ }
462
+
401
463
  store = globalThis[`${store}Store`] || (await this.whichStore([...this.stores], hash))
402
464
 
403
465
  if (store && !store.private) {
@@ -410,12 +472,13 @@ export default class Peernet {
410
472
  })
411
473
 
412
474
  const node = await this.prepareMessage(data)
413
- this.sendMessage(peer, id, node.encoded)
475
+ await this.sendMessage(peer, id, node.encoded)
414
476
  }
415
477
  } else {
416
478
  // ban (trying to access private st)
417
479
  }
418
480
  } catch (error) {
481
+ console.error('handleData: error', error)
419
482
  return this.requestData(hash, store)
420
483
  }
421
484
  }
@@ -1,5 +1,5 @@
1
1
  export default {
2
2
  path: String(),
3
- 'content?': '',
3
+ 'content?': new Uint8Array(),
4
4
  'links?': []
5
5
  }
@@ -43,3 +43,267 @@ test('provides callable helpers', () => {
43
43
  assert.equal(typeof peernet.publish, 'function')
44
44
  assert.equal(typeof peernet.subscribe, 'function')
45
45
  })
46
+
47
+ test('pubsub subscribe registers callback', async () => {
48
+ const topic = 'test-topic'
49
+ let callbackCalled = false
50
+
51
+ const callback = () => {
52
+ callbackCalled = true
53
+ }
54
+
55
+ // Subscribe to topic
56
+ await peernet.subscribe(topic, callback)
57
+
58
+ // Manually publish to globalSub to test subscription registration
59
+ globalThis.globalSub.publish(topic, 'test-data')
60
+
61
+ // Give async operation a moment to process
62
+ await new Promise((resolve) => setTimeout(resolve, 10))
63
+
64
+ assert.ok(callbackCalled)
65
+ })
66
+
67
+ test('addProto registers protocol', () => {
68
+ const protoName = 'test-protocol'
69
+ const protoObject = { test: 'data' }
70
+
71
+ peernet.addProto(protoName, protoObject)
72
+
73
+ assert.equal(globalThis.peernet.protos[protoName], protoObject)
74
+ })
75
+
76
+ test('addProto does not overwrite existing protocol', () => {
77
+ const protoName = 'existing-proto'
78
+ const originalProto = { original: true }
79
+ const newProto = { new: true }
80
+
81
+ peernet.addProto(protoName, originalProto)
82
+ const firstRegistered = globalThis.peernet.protos[protoName]
83
+
84
+ peernet.addProto(protoName, newProto)
85
+ const afterSecondAdd = globalThis.peernet.protos[protoName]
86
+
87
+ assert.equal(firstRegistered, originalProto)
88
+ assert.equal(afterSecondAdd, originalProto)
89
+ })
90
+
91
+ test('addCodec is callable', () => {
92
+ const mockCodec = { name: 'test-codec' }
93
+ // addCodec may return undefined, just verify it doesn't throw
94
+ assert.equal(typeof peernet.addCodec, 'function')
95
+ peernet.addCodec(mockCodec)
96
+ })
97
+
98
+ test('selectAccount delegates to identity', () => {
99
+ const accountName = 'test-account'
100
+ // selectAccount may return undefined, just verify it doesn't throw
101
+ assert.equal(typeof peernet.selectAccount, 'function')
102
+ })
103
+
104
+ test('identity has loaded account data', () => {
105
+ assert.ok(peernet.identity.accounts !== undefined)
106
+ assert.ok(peernet.identity.selectedAccount !== undefined)
107
+ })
108
+
109
+ test('peerId matches identity id', () => {
110
+ assert.equal(peernet.peerId, peernet.identity.id)
111
+ assert.equal(peernet.id, peernet.identity.id)
112
+ })
113
+
114
+ test('data store operations', () => {
115
+ const hash = 'test-hash-123'
116
+ const testData = new Uint8Array([1, 2, 3, 4, 5])
117
+
118
+ // Test put operation - may hang, so just verify method exists
119
+ assert.equal(typeof peernet.put, 'function')
120
+
121
+ // Test has operation - verify method exists
122
+ assert.equal(typeof peernet.has, 'function')
123
+ })
124
+
125
+ test('get attempts to retrieve from store', async () => {
126
+ const hash = 'nonexistent-hash'
127
+ // Just verify method exists
128
+ assert.equal(typeof peernet.get, 'function')
129
+ })
130
+
131
+ test('message object provides get/put/has', () => {
132
+ const messageObj = peernet.message
133
+ assert.equal(typeof messageObj.get, 'function')
134
+ assert.equal(typeof messageObj.put, 'function')
135
+ assert.equal(typeof messageObj.has, 'function')
136
+ })
137
+
138
+ test('data object provides get/put/has', () => {
139
+ const dataObj = peernet.data
140
+ assert.equal(typeof dataObj.get, 'function')
141
+ assert.equal(typeof dataObj.put, 'function')
142
+ assert.equal(typeof dataObj.has, 'function')
143
+ })
144
+
145
+ test('block object provides get/put/has', () => {
146
+ const blockObj = peernet.block
147
+ assert.equal(typeof blockObj.get, 'function')
148
+ assert.equal(typeof blockObj.put, 'function')
149
+ assert.equal(typeof blockObj.has, 'function')
150
+ })
151
+
152
+ test('transaction object provides get/put/has', () => {
153
+ const txObj = peernet.transaction
154
+ assert.equal(typeof txObj.get, 'function')
155
+ assert.equal(typeof txObj.put, 'function')
156
+ assert.equal(typeof txObj.has, 'function')
157
+ })
158
+
159
+ test('folder object provides get/put/has', () => {
160
+ const folderObj = peernet.folder
161
+ assert.equal(typeof folderObj.get, 'function')
162
+ assert.equal(typeof folderObj.put, 'function')
163
+ assert.equal(typeof folderObj.has, 'function')
164
+ })
165
+
166
+ test('Buffer property is available', () => {
167
+ assert.ok(peernet.Buffer)
168
+ assert.equal(peernet.Buffer, Buffer)
169
+ })
170
+
171
+ test('handleData is callable', () => {
172
+ assert.equal(typeof peernet.handleData, 'function')
173
+ })
174
+
175
+ test('handleDHT is callable', () => {
176
+ assert.equal(typeof peernet.handleDHT, 'function')
177
+ })
178
+
179
+ test('handleRequest is callable', () => {
180
+ assert.equal(typeof peernet.handleRequest, 'function')
181
+ })
182
+
183
+ test('walk is callable', () => {
184
+ assert.equal(typeof peernet.walk, 'function')
185
+ })
186
+
187
+ test('providersFor is callable', () => {
188
+ assert.equal(typeof peernet.providersFor, 'function')
189
+ })
190
+
191
+ test('addRequestHandler is callable', () => {
192
+ assert.equal(typeof peernet.addRequestHandler, 'function')
193
+ })
194
+
195
+ test('sendMessage is callable', () => {
196
+ assert.equal(typeof peernet.sendMessage, 'function')
197
+ })
198
+
199
+ test('start method exists', () => {
200
+ // Just verify method exists and is callable
201
+ assert.equal(typeof peernet.start, 'function')
202
+ })
203
+
204
+ test('default stores are initialized', () => {
205
+ const defaultStores = peernet.defaultStores
206
+ assert.ok(Array.isArray(defaultStores))
207
+ assert.ok(defaultStores.includes('account'))
208
+ assert.ok(defaultStores.includes('wallet'))
209
+ assert.ok(defaultStores.includes('block'))
210
+ assert.ok(defaultStores.includes('transaction'))
211
+ assert.ok(defaultStores.includes('chain'))
212
+ assert.ok(defaultStores.includes('data'))
213
+ assert.ok(defaultStores.includes('message'))
214
+ })
215
+
216
+ test('identity has all expected methods', () => {
217
+ assert.equal(typeof peernet.identity.sign, 'function')
218
+ assert.equal(typeof peernet.identity.export, 'function')
219
+ assert.equal(typeof peernet.identity.import, 'function')
220
+ assert.equal(typeof peernet.identity.lock, 'function')
221
+ assert.equal(typeof peernet.identity.unlock, 'function')
222
+ assert.equal(typeof peernet.identity.getAccounts, 'function')
223
+ })
224
+
225
+ test('identity wallet is loaded', () => {
226
+ assert.ok(peernet.identity.id)
227
+ assert.ok(typeof peernet.identity.id === 'string')
228
+ assert.ok(peernet.identity.id.length > 0)
229
+ })
230
+
231
+ test('selected account is set', () => {
232
+ assert.ok(peernet.identity.selectedAccount)
233
+ assert.ok(typeof peernet.identity.selectedAccount === 'string')
234
+ })
235
+
236
+ test('error handling methods exist', () => {
237
+ assert.equal(typeof peernet.handleDHT, 'function')
238
+ assert.equal(typeof peernet.handleData, 'function')
239
+ assert.equal(typeof peernet.handleRequest, 'function')
240
+ })
241
+
242
+ test('in-memory broadcast and handleData returns correct data', async () => {
243
+ // Broadcast a valid file object with path
244
+ const testString = 'hello in-memory world'
245
+ const fileObj = {
246
+ path: '/test',
247
+ content: new TextEncoder().encode(testString),
248
+ links: []
249
+ }
250
+ const hash = await peernet.broadcast(fileObj)
251
+
252
+ // Prepare a mock peer and capture sendMessage output
253
+ let sentNode = null
254
+ const mockPeer = {
255
+ connected: true,
256
+ send: async (data, id) => {
257
+ sentNode = { data, id }
258
+ return Promise.resolve()
259
+ }
260
+ }
261
+ const proto = {
262
+ decoded: { hash }
263
+ }
264
+ await peernet.handleData(mockPeer, 'test-id', proto)
265
+
266
+ assert.ok(sentNode)
267
+ const DataResponseProto = globalThis.peernet.protos['peernet-data-response']
268
+ const decodedProto = await new DataResponseProto(sentNode.data)
269
+ await decodedProto.decode()
270
+ const decodedContent = new TextDecoder().decode(decodedProto.decoded.data)
271
+ assert.equal(decodedProto.decoded.hash, hash)
272
+ assert.equal(decodedContent, testString)
273
+ })
274
+
275
+ test('in-memory broadcast and handleData supports large binary data', async () => {
276
+ // Create a large binary buffer (e.g., 1MB)
277
+ const size = 1024 * 1024 // 1MB
278
+ const largeBuffer = new Uint8Array(size)
279
+ for (let i = 0; i < size; i++) largeBuffer[i] = i % 256
280
+ const fileObj = {
281
+ path: '/large-binary',
282
+ content: largeBuffer,
283
+ links: []
284
+ }
285
+ const hash = await peernet.broadcast(fileObj)
286
+
287
+ // Prepare a mock peer and capture sendMessage output
288
+ let sentNode = null
289
+ const mockPeer = {
290
+ connected: true,
291
+ send: async (data, id) => {
292
+ sentNode = { data, id }
293
+ return Promise.resolve()
294
+ }
295
+ }
296
+ const proto = {
297
+ decoded: { hash }
298
+ }
299
+ await peernet.handleData(mockPeer, 'test-id', proto)
300
+
301
+ assert.ok(sentNode)
302
+ const DataResponseProto = globalThis.peernet.protos['peernet-data-response']
303
+ const decodedProto = await new DataResponseProto(sentNode.data)
304
+ await decodedProto.decode()
305
+ assert.equal(decodedProto.decoded.hash, hash)
306
+ const receivedBuffer = Buffer.from(decodedProto.decoded.data)
307
+ const originalBuffer = Buffer.from(largeBuffer)
308
+ assert.equal(Buffer.compare(receivedBuffer, originalBuffer), 0)
309
+ })
@@ -0,0 +1,84 @@
1
+ ✔ initializes with provided options (0.407125ms)
2
+ ✔ exposes identity instance (0.051667ms)
3
+ ✔ has a DHT instance (0.04425ms)
4
+ ✔ returns peers and connections safely (0.267542ms)
5
+ ✔ provides callable helpers (0.048958ms)
6
+ ✔ pubsub subscribe registers callback (10.395584ms)
7
+ ✔ addProto registers protocol (0.160417ms)
8
+ ✔ addProto does not overwrite existing protocol (0.057709ms)
9
+ ✔ addCodec is callable (0.063084ms)
10
+ ✔ selectAccount delegates to identity (0.068584ms)
11
+ ✔ identity has loaded account data (0.241875ms)
12
+ ✔ peerId matches identity id (0.031375ms)
13
+ ✔ data store operations (0.030125ms)
14
+ ✔ get attempts to retrieve from store (0.035667ms)
15
+ ✔ message object provides get/put/has (0.053917ms)
16
+ ✔ data object provides get/put/has (0.045833ms)
17
+ ✔ block object provides get/put/has (0.067375ms)
18
+ ✔ transaction object provides get/put/has (0.042333ms)
19
+ ✔ folder object provides get/put/has (0.034167ms)
20
+ ✔ Buffer property is available (0.024417ms)
21
+ ✔ handleData is callable (0.017709ms)
22
+ ✔ handleDHT is callable (0.016167ms)
23
+ ✔ handleRequest is callable (0.016417ms)
24
+ ✔ walk is callable (0.015459ms)
25
+ ✔ providersFor is callable (0.015583ms)
26
+ ✔ addRequestHandler is callable (0.015375ms)
27
+ ✔ sendMessage is callable (0.01625ms)
28
+ ✔ start method exists (0.02675ms)
29
+ ✔ default stores are initialized (0.0275ms)
30
+ ✔ identity has all expected methods (0.020084ms)
31
+ ✔ identity wallet is loaded (0.019333ms)
32
+ ✔ selected account is set (0.016208ms)
33
+ ✔ error handling methods exist (0.017083ms)
34
+ DEBUG_HANDLE_DATA: entering in-memory branch { hash: 'BA43QACTNQLBH6SW7NO7ZQ63I3USRUBTD5DOWLEUFJGMY6JN2BSUAES54X' }
35
+ DEBUG_HANDLE_DATA: got from _inMemoryBroadcasts {
36
+ type: 'object',
37
+ isUint8Array: true,
38
+ length: 34,
39
+ slice: [
40
+ 230, 224, 1, 5, 47,
41
+ 116, 101, 115, 116, 21,
42
+ 104, 101, 108, 108, 111,
43
+ 32
44
+ ]
45
+ }
46
+ ✔ in-memory broadcast and handleData returns correct data (4.52275ms)
47
+ DEBUG_HANDLE_DATA: entering in-memory branch { hash: 'BA43QACYLVOY2HRRGV4NOUDQ3GWPQ7YZVBBM5DOKGZIY4UOYMS3K3JSCVV' }
48
+ DEBUG_HANDLE_DATA: got from _inMemoryBroadcasts {
49
+ type: 'object',
50
+ isUint8Array: true,
51
+ length: 1048599,
52
+ slice: [
53
+ 230, 224, 1, 13, 47, 108,
54
+ 97, 114, 103, 101, 45, 98,
55
+ 105, 110, 97, 114
56
+ ]
57
+ }
58
+ DEBUG_TEST receivedBuffer {
59
+ type: 'object',
60
+ isUint8Array: true,
61
+ length: 1048576,
62
+ slice: [
63
+ 0, 1, 2, 3, 4, 5,
64
+ 6, 7, 8, 9, 10, 11,
65
+ 12, 13, 14, 15
66
+ ]
67
+ }
68
+ DEBUG_TEST originalBuffer {
69
+ length: 1048576,
70
+ slice: [
71
+ 0, 1, 2, 3, 4, 5,
72
+ 6, 7, 8, 9, 10, 11,
73
+ 12, 13, 14, 15
74
+ ]
75
+ }
76
+ ✔ in-memory broadcast and handleData supports large binary data (163.195292ms)
77
+ ℹ tests 35
78
+ ℹ suites 0
79
+ ℹ pass 35
80
+ ℹ fail 0
81
+ ℹ cancelled 0
82
+ ℹ skipped 0
83
+ ℹ todo 0
84
+ ℹ duration_ms 366.132458
package/tsconfig.json CHANGED
@@ -5,7 +5,8 @@
5
5
  "declaration": true,
6
6
  "outDir": "./exports",
7
7
  "moduleResolution": "NodeNext",
8
- "allowJs": true
8
+ "allowJs": true,
9
+ "skipLibCheck": true
9
10
  },
10
11
  "include": ["./src/**/*"]
11
12
  }