@leofcoin/peernet 1.2.2 → 1.2.4

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
@@ -70,20 +70,84 @@ const password = await passwordPrompt();
70
70
  const node = await new Peernet(config, password);
71
71
  ```
72
72
 
73
- ## In-Memory Broadcasts
73
+ ## In-Memory Broadcasts: API and Usage
74
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.
75
+ **What is it?**
76
+ The `broadcast` method allows you to share files or folders with peers, storing them in-memory (not persisted to disk). Data is available for direct retrieval by hash until the process restarts.
77
+
78
+ **API:**
79
+ ```typescript
80
+ // For files:
81
+ peernet.broadcast(path: string, { content: Uint8Array }): Promise<string>
82
+
83
+ // For folders:
84
+ peernet.broadcast(path: string, { links: Array<{ path: string, hash: string }> }): Promise<string>
85
+ ```
86
+
87
+ **Parameters:**
88
+ - `path` (string): The virtual path or identifier for the file or folder.
89
+ - `content` (Uint8Array): The file data (for files).
90
+ - `links` (Array): An array of `{ path, hash }` objects (for folders).
91
+
92
+ **Returns:**
93
+ A `Promise` that resolves to a hash string, which can be used to retrieve the data.
94
+
95
+ ---
96
+
97
+ ### Examples
98
+
99
+ **Broadcast a file:**
100
+ ```js
101
+ const hash = await peernet.broadcast('/hello.txt', {
102
+ content: new TextEncoder().encode('Hello world!')
103
+ });
104
+ ```
105
+
106
+ **Broadcast a folder:**
107
+ ```js
108
+ const folderHash = await peernet.broadcast('/my-folder', {
109
+ links: [
110
+ { path: '/hello.txt', hash: hash }
111
+ ]
112
+ });
113
+ ```
114
+
115
+ **Retrieve data by hash (peer side):**
116
+ ```js
117
+ const proto = { decoded: { hash } };
118
+ await peernet.handleData(peer, 'some-id', proto);
119
+ // The peer will receive either .data (for files) or .links (for folders)
120
+ ```
121
+
122
+ ---
123
+
124
+ ### Notes
125
+
126
+ - If you broadcast a folder, only the `links` property is required; `content` should be omitted.
127
+ - If you broadcast a file, only the `content` property is required; `links` should be omitted.
128
+ - The hash returned is unique to the content or folder structure.
129
+ - Data is lost when the process restarts.
130
+
131
+ ### In-Memory Broadcasts
132
+
133
+
134
+ Peernet supports in-memory data sharing for fast, temporary distribution of files or folders between peers. This feature allows you to broadcast a file (with content) or a folder (with just links), which is then available for retrieval by other peers (or yourself) until the process is restarted or the in-memory cache is cleared.
76
135
 
77
136
  ### Usage Example
78
137
 
79
138
  ```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);
139
+ // Broadcast a file in-memory
140
+ const hash = await peernet.broadcast('/example', {
141
+ content: new TextEncoder().encode('hello world')
142
+ });
143
+
144
+ // Broadcast a folder (just links) in-memory
145
+ const folderHash = await peernet.broadcast('/folder', {
146
+ links: [
147
+ { path: '/file1', hash: 'hash1' },
148
+ { path: '/file2', hash: 'hash2' }
149
+ ]
150
+ });
87
151
 
88
152
  // Another peer can request the data by hash
89
153
  // (handled internally by peernet.handleData)
@@ -92,16 +156,22 @@ const hash = await peernet.broadcast(fileObj);
92
156
  ### Detailed Example
93
157
 
94
158
  ```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);
159
+ // 1. Broadcast a file in-memory
160
+ const hash = await peernet.broadcast('/example', {
161
+ content: new TextEncoder().encode('Hello Peernet!')
162
+ });
102
163
  console.log('Broadcasted hash:', hash);
103
164
 
104
- // 2. Simulate a peer requesting the data by hash
165
+ // 2. Broadcast a folder in-memory
166
+ const folderHash = await peernet.broadcast('/folder', {
167
+ links: [
168
+ { path: '/file1', hash: 'hash1' },
169
+ { path: '/file2', hash: 'hash2' }
170
+ ]
171
+ });
172
+ console.log('Broadcasted folder hash:', folderHash);
173
+
174
+ // 3. Simulate a peer requesting the data by hash
105
175
  const mockPeer = {
106
176
  connected: true,
107
177
  send: async (data, id) => {
@@ -109,8 +179,12 @@ const mockPeer = {
109
179
  const DataResponseProto = globalThis.peernet.protos['peernet-data-response'];
110
180
  const decodedProto = await new DataResponseProto(data);
111
181
  await decodedProto.decode();
112
- const decodedContent = new TextDecoder().decode(decodedProto.decoded.data);
113
- console.log('Received content:', decodedContent);
182
+ if (decodedProto.decoded.data) {
183
+ const decodedContent = new TextDecoder().decode(decodedProto.decoded.data);
184
+ console.log('Received content:', decodedContent);
185
+ } else if (decodedProto.decoded.links) {
186
+ console.log('Received folder links:', decodedProto.decoded.links);
187
+ }
114
188
  }
115
189
  };
116
190
  const proto = { decoded: { hash } };
@@ -138,6 +212,27 @@ await peernet.handleData(mockPeer, 'test-id', proto);
138
212
 
139
213
  Automated tests verify that broadcasting and retrieving in-memory data works as expected, including content integrity and hash matching.
140
214
 
215
+ ## Folder Support (Files with Just Links)
216
+
217
+
218
+ Peernet supports "folders" as special file objects that contain only links to other files, without any content. This allows you to represent directory structures or collections of files.
219
+
220
+ **Example: Broadcasting a Folder**
221
+ ```js
222
+ const folderHash = await peernet.broadcast('/folder', {
223
+ links: [
224
+ { path: '/file1', hash: 'hash1' },
225
+ { path: '/file2', hash: 'hash2' }
226
+ ]
227
+ });
228
+ ```
229
+
230
+ **Retrieving a Folder**
231
+ When a folder is retrieved (e.g., via `handleData`), its `links` array will be preserved, and `content` will be empty or undefined.
232
+
233
+ **Test Coverage**
234
+ Automated tests verify that broadcasting and retrieving folders works as expected, ensuring the links are preserved and content is empty or undefined.
235
+
141
236
  ## Development
142
237
 
143
238
  > **Note:** You need to install [jsproject](https://www.npmjs.com/package/@vandeurenglenn/project)
@@ -1,4 +1,4 @@
1
- import { L as LittlePubSub, d as deflate_1, i as inflate_1, c as createDebugger } from './peernet-BuZSSOa0.js';
1
+ import { L as LittlePubSub, d as deflate_1, i as inflate_1, c as createDebugger } from './peernet-D6QlSJ6B.js';
2
2
  import './identity-nIyW_Xm8.js';
3
3
  import './value-C3vAp-wb.js';
4
4
 
@@ -599,6 +599,8 @@ class Client {
599
599
  url: 'join',
600
600
  params: { version: this.version, peerId: this.peerId }
601
601
  });
602
+ globalThis.pubsub.publishVerbose('star:connected', star);
603
+ debug(`setupStar ${star} succeeded`);
602
604
  return this.#stars[star];
603
605
  }
604
606
  catch (e) {
@@ -1,4 +1,4 @@
1
- import { F as FormatInterface } from './peernet-BuZSSOa0.js';
1
+ import { F as FormatInterface } from './peernet-D6QlSJ6B.js';
2
2
  import './identity-nIyW_Xm8.js';
3
3
  import './value-C3vAp-wb.js';
4
4
 
@@ -8380,7 +8380,7 @@ class Peernet {
8380
8380
  this.root = options.root;
8381
8381
  const { RequestMessage, ResponseMessage, PeerMessage, PeerMessageResponse, PeernetMessage, DHTMessage, DHTMessageResponse, DataMessage, DataMessageResponse, PsMessage, ChatMessage, PeernetFile
8382
8382
  // FolderMessageResponse
8383
- } = await import(/* webpackChunkName: "messages" */ './messages-C-z7iw3I.js');
8383
+ } = await import(/* webpackChunkName: "messages" */ './messages-BVmbvFj4.js');
8384
8384
  /**
8385
8385
  * proto Object containing protos
8386
8386
  * @type {Object}
@@ -8474,7 +8474,7 @@ class Peernet {
8474
8474
  if (this.#starting || this.#started)
8475
8475
  return;
8476
8476
  this.#starting = true;
8477
- const importee = await import('./client-BqvqxCzm.js');
8477
+ const importee = await import('./client-7chRAqOy.js');
8478
8478
  /**
8479
8479
  * @access public
8480
8480
  * @type {PeernetClient}
@@ -8557,25 +8557,17 @@ class Peernet {
8557
8557
  * Broadcasts data to the network and returns a hash that can be used by another peer
8558
8558
  * to directly connect and download the data from the broadcasting peer.
8559
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
8560
+ * @param {string} path - The path or identifier for the content being broadcasted
8561
+ * @param {{content?: Uint8Array, links?: any[]}} data - The data to broadcast
8562
+
8562
8563
  * @returns {Promise<string>} The hash that can be shared for direct download
8563
8564
  */
8564
- async broadcast(data) {
8565
+ async broadcast(path, { content, links }) {
8565
8566
  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
- }
8567
+ if (content)
8568
+ protoInput = { path, content };
8569
+ else if (links)
8570
+ protoInput = { path, links };
8579
8571
  const protoNode = await new globalThis.peernet.protos['peernet-file'](protoInput);
8580
8572
  const hash = await protoNode.hash();
8581
8573
  const encoded = await protoNode.encoded;
@@ -8595,14 +8587,15 @@ class Peernet {
8595
8587
  if (typeof hash === 'function') {
8596
8588
  resolvedHash = await hash();
8597
8589
  }
8598
- // Decode the stored proto to extract the content
8590
+ // Decode the stored proto to extract the content or links
8599
8591
  const FileProto = globalThis.peernet.protos['peernet-file'];
8600
8592
  const fileProto = await new FileProto(data);
8601
8593
  await fileProto.decode();
8602
- const fileContent = fileProto.decoded.content;
8594
+ const { content, links } = fileProto.decoded;
8595
+ console.log(links);
8603
8596
  data = await new globalThis.peernet.protos['peernet-data-response']({
8604
8597
  hash: resolvedHash,
8605
- data: fileContent
8598
+ data: links || content
8606
8599
  });
8607
8600
  const node = await this.prepareMessage(data);
8608
8601
  await this.sendMessage(peer, id, node.encoded);
@@ -119,15 +119,15 @@ export default class Peernet {
119
119
  * Broadcasts data to the network and returns a hash that can be used by another peer
120
120
  * to directly connect and download the data from the broadcasting peer.
121
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
122
+ * @param {string} path - The path or identifier for the content being broadcasted
123
+ * @param {{content?: Uint8Array, links?: any[]}} data - The data to broadcast
124
+
124
125
  * @returns {Promise<string>} The hash that can be shared for direct download
125
126
  */
126
- broadcast(data: Uint8Array | Buffer | {
127
- path: string;
127
+ broadcast(path: string, { content, links }: {
128
128
  content?: Uint8Array;
129
129
  links?: any[];
130
- } | string): Promise<string>;
130
+ }): Promise<string>;
131
131
  handleData(peer: any, id: any, proto: any): Promise<any>;
132
132
  handleRequest(peer: any, id: any, proto: any): Promise<void>;
133
133
  /**
@@ -1,3 +1,3 @@
1
- export { P as default } from './peernet-BuZSSOa0.js';
1
+ export { P as default } from './peernet-D6QlSJ6B.js';
2
2
  import './identity-nIyW_Xm8.js';
3
3
  import './value-C3vAp-wb.js';
@@ -625,25 +625,17 @@ class Peernet {
625
625
  * Broadcasts data to the network and returns a hash that can be used by another peer
626
626
  * to directly connect and download the data from the broadcasting peer.
627
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
628
+ * @param {string} path - The path or identifier for the content being broadcasted
629
+ * @param {{content?: Uint8Array, links?: any[]}} data - The data to broadcast
630
+
630
631
  * @returns {Promise<string>} The hash that can be shared for direct download
631
632
  */
632
- async broadcast(data) {
633
+ async broadcast(path, { content, links }) {
633
634
  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
- }
635
+ if (content)
636
+ protoInput = { path, content };
637
+ else if (links)
638
+ protoInput = { path, links };
647
639
  const protoNode = await new globalThis.peernet.protos['peernet-file'](protoInput);
648
640
  const hash = await protoNode.hash();
649
641
  const encoded = await protoNode.encoded;
@@ -663,14 +655,15 @@ class Peernet {
663
655
  if (typeof hash === 'function') {
664
656
  resolvedHash = await hash();
665
657
  }
666
- // Decode the stored proto to extract the content
658
+ // Decode the stored proto to extract the content or links
667
659
  const FileProto = globalThis.peernet.protos['peernet-file'];
668
660
  const fileProto = await new FileProto(data);
669
661
  await fileProto.decode();
670
- const fileContent = fileProto.decoded.content;
662
+ const { content, links } = fileProto.decoded;
663
+ console.log(links);
671
664
  data = await new globalThis.peernet.protos['peernet-data-response']({
672
665
  hash: resolvedHash,
673
- data: fileContent
666
+ data: links || content
674
667
  });
675
668
  const node = await this.prepareMessage(data);
676
669
  await this.sendMessage(peer, id, node.encoded);
@@ -119,15 +119,15 @@ export default class Peernet {
119
119
  * Broadcasts data to the network and returns a hash that can be used by another peer
120
120
  * to directly connect and download the data from the broadcasting peer.
121
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
122
+ * @param {string} path - The path or identifier for the content being broadcasted
123
+ * @param {{content?: Uint8Array, links?: any[]}} data - The data to broadcast
124
+
124
125
  * @returns {Promise<string>} The hash that can be shared for direct download
125
126
  */
126
- broadcast(data: Uint8Array | Buffer | {
127
- path: string;
127
+ broadcast(path: string, { content, links }: {
128
128
  content?: Uint8Array;
129
129
  links?: any[];
130
- } | string): Promise<string>;
130
+ }): Promise<string>;
131
131
  handleData(peer: any, id: any, proto: any): Promise<any>;
132
132
  handleRequest(peer: any, id: any, proto: any): Promise<void>;
133
133
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leofcoin/peernet",
3
- "version": "1.2.2",
3
+ "version": "1.2.4",
4
4
  "description": "",
5
5
  "browser": "./exports/browser/peernet.js",
6
6
  "exports": {
@@ -38,14 +38,14 @@
38
38
  "@leofcoin/multi-wallet": "^3.1.8",
39
39
  "@leofcoin/storage": "^3.5.38",
40
40
  "@mapbox/node-pre-gyp": "^2.0.3",
41
- "@netpeer/swarm": "^0.9.0",
41
+ "@netpeer/swarm": "^0.9.1",
42
42
  "@vandeurenglenn/base58": "^1.1.9",
43
43
  "@vandeurenglenn/debug": "^1.4.0",
44
44
  "@vandeurenglenn/little-pubsub": "^1.5.2",
45
- "inquirer": "^13.2.1",
45
+ "inquirer": "^13.2.2",
46
46
  "qr-scanner": "^1.4.2",
47
47
  "qrcode": "^1.5.4",
48
- "tar": "^7.5.6"
48
+ "tar": "^7.5.7"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@rollup/plugin-commonjs": "^29.0.0",
@@ -53,8 +53,8 @@
53
53
  "@rollup/plugin-node-resolve": "^16.0.3",
54
54
  "@rollup/plugin-typescript": "^12.3.0",
55
55
  "@rollup/plugin-wasm": "^6.2.2",
56
- "@types/node": "^25.0.10",
56
+ "@types/node": "^25.2.0",
57
57
  "@types/qrcode": "^1.5.6",
58
- "rollup": "^4.56.0"
58
+ "rollup": "^4.57.1"
59
59
  }
60
60
  }
package/src/peernet.ts CHANGED
@@ -407,24 +407,15 @@ export default class Peernet {
407
407
  * Broadcasts data to the network and returns a hash that can be used by another peer
408
408
  * to directly connect and download the data from the broadcasting peer.
409
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
410
+ * @param {string} path - The path or identifier for the content being broadcasted
411
+ * @param {{content?: Uint8Array, links?: any[]}} data - The data to broadcast
412
+
412
413
  * @returns {Promise<string>} The hash that can be shared for direct download
413
414
  */
414
- async broadcast(
415
- data: Uint8Array | Buffer | { path: string; content?: Uint8Array; links?: any[] } | string
416
- ): Promise<string> {
415
+ async broadcast(path: string, { content, links }: { content?: Uint8Array; links?: any[] }): Promise<string> {
417
416
  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
- }
417
+ if (content) protoInput = { path, content }
418
+ else if (links) protoInput = { path, links }
428
419
 
429
420
  const protoNode = await new globalThis.peernet.protos['peernet-file'](protoInput)
430
421
  const hash = await protoNode.hash()
@@ -446,15 +437,18 @@ export default class Peernet {
446
437
  if (typeof hash === 'function') {
447
438
  resolvedHash = await hash()
448
439
  }
449
- // Decode the stored proto to extract the content
440
+ // Decode the stored proto to extract the content or links
450
441
  const FileProto = globalThis.peernet.protos['peernet-file']
451
442
  const fileProto = await new FileProto(data)
452
443
  await fileProto.decode()
453
- const fileContent = fileProto.decoded.content
444
+ const { content, links } = fileProto.decoded
445
+ console.log(links)
446
+
454
447
  data = await new globalThis.peernet.protos['peernet-data-response']({
455
448
  hash: resolvedHash,
456
- data: fileContent
449
+ data: links || content
457
450
  })
451
+
458
452
  const node = await this.prepareMessage(data)
459
453
  await this.sendMessage(peer, id, node.encoded)
460
454
  return
@@ -242,12 +242,10 @@ test('error handling methods exist', () => {
242
242
  test('in-memory broadcast and handleData returns correct data', async () => {
243
243
  // Broadcast a valid file object with path
244
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)
245
+ const path = '/test'
246
+ const content = new TextEncoder().encode(testString)
247
+
248
+ const hash = await peernet.broadcast(path, { content })
251
249
 
252
250
  // Prepare a mock peer and capture sendMessage output
253
251
  let sentNode = null
@@ -277,12 +275,9 @@ test('in-memory broadcast and handleData supports large binary data', async () =
277
275
  const size = 1024 * 1024 // 1MB
278
276
  const largeBuffer = new Uint8Array(size)
279
277
  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)
278
+ const path = '/large-binary'
279
+ const content = largeBuffer
280
+ const hash = await peernet.broadcast(path, { content })
286
281
 
287
282
  // Prepare a mock peer and capture sendMessage output
288
283
  let sentNode = null