@leofcoin/peernet 1.2.1 → 1.2.3
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 +225 -31
- package/exports/browser/{browser-CfYI-6aD.js → browser-CWWW0kSj.js} +17 -2
- package/exports/browser/{browser-Qcpp3EKK.js → browser-DMVbMScf.js} +17 -2
- package/exports/browser/{client-CWkdUcxK.js → client-B2RpD9kv.js} +4 -4
- package/exports/browser/{index-DTbjK0sK.js → index-BoQ4uWm2.js} +17 -2
- package/exports/browser/{messages-C507MMRx.js → messages-DiCEP9k9.js} +2 -2
- package/exports/browser/{peernet-CXod4Dva.js → peernet-BTtebeW3.js} +123 -45
- package/exports/browser/peernet.d.ts +22 -0
- package/exports/browser/peernet.js +1 -1
- package/exports/browser/proto/file.proto.d.ts +1 -1
- package/exports/{messages-CsDqTaBh.js → messages-C9lYBAhD.js} +1 -1
- package/exports/peernet.js +56 -2
- package/exports/types/peernet.d.ts +22 -0
- package/exports/types/proto/file.proto.d.ts +1 -1
- package/package.json +8 -6
- package/src/peernet.ts +58 -1
- package/src/proto/file.proto.js +1 -1
- package/test/peernet.test.js +259 -0
- package/test-output.log +84 -0
- package/tsconfig.json +2 -1
package/README.md
CHANGED
|
@@ -1,72 +1,266 @@
|
|
|
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
|
-
|
|
15
|
-
|
|
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
|
-
|
|
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
|
-
|
|
62
|
+
### Password Prompt
|
|
63
|
+
For password-protected identities, use the provided prompt:
|
|
54
64
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
73
|
+
## In-Memory Broadcasts: API and Usage
|
|
74
|
+
|
|
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>
|
|
63
85
|
```
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
+
});
|
|
67
113
|
```
|
|
68
|
-
|
|
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.
|
|
135
|
+
|
|
136
|
+
### Usage Example
|
|
137
|
+
|
|
138
|
+
```js
|
|
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
|
+
});
|
|
151
|
+
|
|
152
|
+
// Another peer can request the data by hash
|
|
153
|
+
// (handled internally by peernet.handleData)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Detailed Example
|
|
157
|
+
|
|
158
|
+
```js
|
|
159
|
+
// 1. Broadcast a file in-memory
|
|
160
|
+
const hash = await peernet.broadcast('/example', {
|
|
161
|
+
content: new TextEncoder().encode('Hello Peernet!')
|
|
162
|
+
});
|
|
163
|
+
console.log('Broadcasted hash:', hash);
|
|
164
|
+
|
|
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
|
|
175
|
+
const mockPeer = {
|
|
176
|
+
connected: true,
|
|
177
|
+
send: async (data, id) => {
|
|
178
|
+
// Handle the received data (for demo/testing)
|
|
179
|
+
const DataResponseProto = globalThis.peernet.protos['peernet-data-response'];
|
|
180
|
+
const decodedProto = await new DataResponseProto(data);
|
|
181
|
+
await decodedProto.decode();
|
|
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
|
+
}
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
const proto = { decoded: { hash } };
|
|
191
|
+
await peernet.handleData(mockPeer, 'test-id', proto);
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Data Flow Diagram
|
|
195
|
+
|
|
196
|
+
```
|
|
197
|
+
[You/Peer] --(broadcast)--> [Peernet Node: In-Memory Map]
|
|
198
|
+
^ |
|
|
199
|
+
| |
|
|
200
|
+
+------(handleData request)--------+
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
- `broadcast()` stores the data in-memory and returns a hash.
|
|
204
|
+
- `handleData()` retrieves the data by hash and sends it to the requesting peer.
|
|
205
|
+
|
|
206
|
+
- The broadcasted data is stored in-memory and is not persisted to disk.
|
|
207
|
+
- Retrieval is by hash, matching the value returned from `broadcast()`.
|
|
208
|
+
- The feature is useful for ephemeral data, rapid prototyping, or testing.
|
|
209
|
+
- If the process restarts, the in-memory data is lost.
|
|
210
|
+
|
|
211
|
+
### Testing
|
|
212
|
+
|
|
213
|
+
Automated tests verify that broadcasting and retrieving in-memory data works as expected, including content integrity and hash matching.
|
|
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
|
+
|
|
236
|
+
## Development
|
|
237
|
+
|
|
238
|
+
> **Note:** You need to install [jsproject](https://www.npmjs.com/package/@vandeurenglenn/project)
|
|
239
|
+
|
|
69
240
|
```sh
|
|
70
|
-
npm
|
|
241
|
+
npm i -g @vandeurenglenn/project
|
|
71
242
|
```
|
|
72
243
|
|
|
244
|
+
### Scripts
|
|
245
|
+
|
|
246
|
+
- **Watch:**
|
|
247
|
+
```sh
|
|
248
|
+
npm run watch
|
|
249
|
+
```
|
|
250
|
+
- **Build:**
|
|
251
|
+
```sh
|
|
252
|
+
npm run build
|
|
253
|
+
```
|
|
254
|
+
- **Test:**
|
|
255
|
+
```sh
|
|
256
|
+
npm test
|
|
257
|
+
```
|
|
258
|
+
- **Serve HTML Demo:**
|
|
259
|
+
```sh
|
|
260
|
+
npm run demo
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## License
|
|
264
|
+
|
|
265
|
+
MIT
|
|
266
|
+
|
|
@@ -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__*/
|
|
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__*/
|
|
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,
|
|
1
|
+
import { L as LittlePubSub, d as deflate_1, i as inflate_1, c as createDebugger } from './peernet-BTtebeW3.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-
|
|
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-
|
|
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-
|
|
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__*/
|
|
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-
|
|
1
|
+
import { F as FormatInterface } from './peernet-BTtebeW3.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
|
|
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
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if
|
|
56
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
if (!this.hasSubscribers(event))
|
|
67
|
+
const subscriber = this.subscribers.get(event);
|
|
68
|
+
if (subscriber === undefined)
|
|
62
69
|
return;
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
73
|
-
if (
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const oldValue =
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
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,
|
|
110
|
+
this.unsubscribe(event, handler, options);
|
|
93
111
|
};
|
|
94
|
-
this.subscribe(event,
|
|
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-
|
|
8383
|
+
} = await import(/* webpackChunkName: "messages" */ './messages-DiCEP9k9.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-
|
|
8477
|
+
const importee = await import('./client-B2RpD9kv.js');
|
|
8453
8478
|
/**
|
|
8454
8479
|
* @access public
|
|
8455
8480
|
* @type {PeernetClient}
|
|
@@ -8520,10 +8545,62 @@ 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
|
+
* @param {string} path - The path or identifier for the content being broadcasted
|
|
8561
|
+
* @param {{content?: Uint8Array, links?: any[]}} data - The data to broadcast
|
|
8562
|
+
|
|
8563
|
+
* @returns {Promise<string>} The hash that can be shared for direct download
|
|
8564
|
+
*/
|
|
8565
|
+
async broadcast(path, { content, links }) {
|
|
8566
|
+
let protoInput;
|
|
8567
|
+
if (content)
|
|
8568
|
+
protoInput = { path, content };
|
|
8569
|
+
else if (links)
|
|
8570
|
+
protoInput = { path, links };
|
|
8571
|
+
const protoNode = await new globalThis.peernet.protos['peernet-file'](protoInput);
|
|
8572
|
+
const hash = await protoNode.hash();
|
|
8573
|
+
const encoded = await protoNode.encoded;
|
|
8574
|
+
if (!this._inMemoryBroadcasts)
|
|
8575
|
+
this._inMemoryBroadcasts = new Map();
|
|
8576
|
+
this._inMemoryBroadcasts.set(hash, encoded);
|
|
8577
|
+
await this.publish('broadcast', { hash, from: this.id });
|
|
8578
|
+
return hash;
|
|
8579
|
+
}
|
|
8523
8580
|
async handleData(peer, id, proto) {
|
|
8524
8581
|
let { hash, store } = proto.decoded;
|
|
8525
8582
|
let data;
|
|
8526
8583
|
try {
|
|
8584
|
+
if (this._inMemoryBroadcasts && this._inMemoryBroadcasts.has(hash)) {
|
|
8585
|
+
data = this._inMemoryBroadcasts.get(hash);
|
|
8586
|
+
let resolvedHash = hash;
|
|
8587
|
+
if (typeof hash === 'function') {
|
|
8588
|
+
resolvedHash = await hash();
|
|
8589
|
+
}
|
|
8590
|
+
// Decode the stored proto to extract the content or links
|
|
8591
|
+
const FileProto = globalThis.peernet.protos['peernet-file'];
|
|
8592
|
+
const fileProto = await new FileProto(data);
|
|
8593
|
+
await fileProto.decode();
|
|
8594
|
+
const { content, links } = fileProto.decoded;
|
|
8595
|
+
console.log(links);
|
|
8596
|
+
data = await new globalThis.peernet.protos['peernet-data-response']({
|
|
8597
|
+
hash: resolvedHash,
|
|
8598
|
+
data: links || content
|
|
8599
|
+
});
|
|
8600
|
+
const node = await this.prepareMessage(data);
|
|
8601
|
+
await this.sendMessage(peer, id, node.encoded);
|
|
8602
|
+
return;
|
|
8603
|
+
}
|
|
8527
8604
|
store = globalThis[`${store}Store`] || (await this.whichStore([...this.stores], hash));
|
|
8528
8605
|
if (store && !store.private) {
|
|
8529
8606
|
data = await store.get(hash);
|
|
@@ -8533,7 +8610,7 @@ class Peernet {
|
|
|
8533
8610
|
data
|
|
8534
8611
|
});
|
|
8535
8612
|
const node = await this.prepareMessage(data);
|
|
8536
|
-
this.sendMessage(peer, id, node.encoded);
|
|
8613
|
+
await this.sendMessage(peer, id, node.encoded);
|
|
8537
8614
|
}
|
|
8538
8615
|
}
|
|
8539
8616
|
else {
|
|
@@ -8541,6 +8618,7 @@ class Peernet {
|
|
|
8541
8618
|
}
|
|
8542
8619
|
}
|
|
8543
8620
|
catch (error) {
|
|
8621
|
+
console.error('handleData: error', error);
|
|
8544
8622
|
return this.requestData(hash, store);
|
|
8545
8623
|
}
|
|
8546
8624
|
}
|