@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 +130 -31
- package/exports/browser/{browser-DQlwTLRn.js → browser-CWWW0kSj.js} +18 -3
- package/exports/browser/{browser-BHbuEZJu.js → browser-DMVbMScf.js} +18 -3
- package/exports/browser/{client-DCeU_UX5.js → client-BqvqxCzm.js} +5 -5
- package/exports/browser/{identity-B6BHwSTU.js → identity-nIyW_Xm8.js} +2 -2
- package/exports/browser/identity.js +1 -1
- package/exports/browser/{index-DYdP5D9L.js → index-BoQ4uWm2.js} +18 -3
- package/exports/browser/index-ChRjMyiM.js +36 -0
- package/exports/browser/{messages-CiR1YiV5.js → messages-C-z7iw3I.js} +3 -3
- package/exports/browser/{peernet-fcX5oKJJ.js → peernet-BuZSSOa0.js} +131 -46
- package/exports/browser/peernet.d.ts +22 -0
- package/exports/browser/peernet.js +2 -2
- package/exports/browser/proto/file.proto.d.ts +1 -1
- package/exports/{messages-CsDqTaBh.js → messages-C9lYBAhD.js} +1 -1
- package/exports/peernet.js +63 -2
- package/exports/types/peernet.d.ts +22 -0
- package/exports/types/proto/file.proto.d.ts +1 -1
- package/package.json +9 -6
- package/src/peernet.ts +64 -1
- package/src/proto/file.proto.js +1 -1
- package/test/peernet.test.js +264 -0
- package/test-output.log +84 -0
- package/tsconfig.json +2 -1
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
|
-
|
|
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
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
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
|
|
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-
|
|
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__*/
|
|
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-
|
|
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__*/
|
|
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,
|
|
2
|
-
import './identity-
|
|
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-
|
|
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);
|
|
@@ -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" */ '
|
|
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,
|
|
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-
|
|
1
|
+
export { I as default } from './identity-nIyW_Xm8.js';
|
|
@@ -1,6 +1,21 @@
|
|
|
1
|
-
import {
|
|
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 };
|
|
@@ -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-
|
|
2
|
-
import './identity-
|
|
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-
|
|
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
|
|
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-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-
|
|
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
|
}
|