@leofcoin/peernet 1.2.0 → 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,4 +1,19 @@
1
- import { g as getDefaultExportFromCjs } from './identity-B6BHwSTU.js';
1
+ import { g as getDefaultExportFromCjs } from './identity-nIyW_Xm8.js';
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
+ }
2
17
 
3
18
  var global;
4
19
  var hasRequiredGlobal;
@@ -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,4 +1,19 @@
1
- import { g as getDefaultExportFromCjs } from './identity-B6BHwSTU.js';
1
+ import { g as getDefaultExportFromCjs } from './identity-nIyW_Xm8.js';
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
+ }
2
17
 
3
18
  var browser$2 = {};
4
19
 
@@ -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,5 +1,5 @@
1
- import { L as LittlePubSub, d as deflate_1, c as createDebugger, i as inflate_1 } from './peernet-fcX5oKJJ.js';
2
- import './identity-B6BHwSTU.js';
1
+ import { L as LittlePubSub, d as deflate_1, i as inflate_1, c as createDebugger } from './peernet-BuZSSOa0.js';
2
+ import './identity-nIyW_Xm8.js';
3
3
  import './value-C3vAp-wb.js';
4
4
 
5
5
  class Api {
@@ -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-DQlwTLRn.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-DYdP5D9L.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-BHbuEZJu.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);
@@ -17150,7 +17150,7 @@ class Identity {
17150
17150
  this.selectedAccount = new TextDecoder().decode(selected);
17151
17151
  }
17152
17152
  else {
17153
- const importee = await import(/* webpackChunkName: "generate-account" */ '@leofcoin/generate-account');
17153
+ const importee = await import(/* webpackChunkName: "generate-account" */ './index-ChRjMyiM.js');
17154
17154
  const { identity, accounts } = await importee.default(password, this.network);
17155
17155
  await globalThis.accountStore.put('public', JSON.stringify({ walletId: identity.walletId }));
17156
17156
  await globalThis.walletStore.put('version', String(1));
@@ -17196,4 +17196,4 @@ class Identity {
17196
17196
  }
17197
17197
  }
17198
17198
 
17199
- export { Identity as I, base58$1 as a, base$1 as b, index$3 as c, index$2 as d, index$4 as e, commonjsGlobal as f, getDefaultExportFromCjs as g, requireInherits_browser as h, index$5 as i, require$$3 as r };
17199
+ export { Identity as I, MultiWallet as M, base58$1 as a, base$1 as b, index$3 as c, index$2 as d, index$4 as e, encrypt as f, getDefaultExportFromCjs as g, commonjsGlobal as h, index$5 as i, requireInherits_browser as j, require$$3 as r };
@@ -1 +1 @@
1
- export { I as default } from './identity-B6BHwSTU.js';
1
+ export { I as default } from './identity-nIyW_Xm8.js';
@@ -1,6 +1,21 @@
1
- import { f as commonjsGlobal, r as require$$3, h as requireInherits_browser, g as getDefaultExportFromCjs } from './identity-B6BHwSTU.js';
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 };
@@ -0,0 +1,36 @@
1
+ import { M as MultiWallet, f as encrypt, a as base58$1 } from './identity-nIyW_Xm8.js';
2
+
3
+ /**
4
+ * @params {String} network
5
+ * @return {object} { identity, accounts, config }
6
+ */
7
+ var index = async (password, network) => {
8
+ if (!password)
9
+ throw new Error('wallets need to be password protected.');
10
+ let wallet = new MultiWallet(network);
11
+ /**
12
+ * @type {string}
13
+ */
14
+ let mnemonic = await wallet.generate(password);
15
+ wallet = new MultiWallet(network);
16
+ await wallet.recover(mnemonic, password, network);
17
+ mnemonic = new Uint8Array(await encrypt(password, mnemonic));
18
+ const multiWIF = new Uint8Array(await encrypt(password, await wallet.multiWIF));
19
+ /**
20
+ * @type {object}
21
+ */
22
+ const external = await wallet.account(1).external(1);
23
+ const externalAddress = await external.address;
24
+ const internal = await wallet.account(1).internal(1);
25
+ const internalAddress = await internal.address;
26
+ return {
27
+ identity: {
28
+ mnemonic: base58$1.encode(mnemonic),
29
+ multiWIF: base58$1.encode(multiWIF),
30
+ walletId: await external.id
31
+ },
32
+ accounts: [['main account', externalAddress, internalAddress]]
33
+ };
34
+ };
35
+
36
+ export { index as default };
@@ -1,5 +1,5 @@
1
- import { F as FormatInterface } from './peernet-fcX5oKJJ.js';
2
- import './identity-B6BHwSTU.js';
1
+ import { F as FormatInterface } from './peernet-BuZSSOa0.js';
2
+ import './identity-nIyW_Xm8.js';
3
3
  import './value-C3vAp-wb.js';
4
4
 
5
5
  var proto$b = {
@@ -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
 
@@ -1,4 +1,4 @@
1
- import { b as base, a as base58$1, i as index$2, c as index$3, d as index$4, e as index$5, I as Identity } from './identity-B6BHwSTU.js';
1
+ import { b as base, a as base58$1, i as index$2, c as index$3, d as index$4, e as index$5, I as Identity } from './identity-nIyW_Xm8.js';
2
2
  import { K as KeyPath, a as KeyValue } from './value-C3vAp-wb.js';
3
3
 
4
4
  const getTargets = () => Array.from([]);
@@ -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-CiR1YiV5.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-DCeU_UX5.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
  }