@leofcoin/peernet 0.10.8 → 0.11.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/LICENSE +1 -1
- package/README.md +49 -49
- package/coverage/lcov-report/block-navigation.js +8 -0
- package/coverage/lcov-report/codec-format-interface.js.html +224 -120
- package/coverage/lcov-report/dht-response.js.html +44 -39
- package/coverage/lcov-report/index.html +39 -64
- package/coverage/lcov-report/sorter.js +26 -0
- package/coverage/lcov.info +164 -424
- package/dist/browser/326.peernet.js +29 -0
- package/dist/browser/peernet.js +84258 -95769
- package/dist/commonjs/client-1a1f75e6.js +324 -0
- package/dist/commonjs/{codec-6367213c.js → codec-8c8c652f.js} +198 -188
- package/dist/commonjs/codec-format-interface.js +169 -152
- package/dist/commonjs/codec.js +4 -4
- package/dist/commonjs/dht-response.js +13 -13
- package/dist/commonjs/dht.js +24 -24
- package/dist/commonjs/hash.js +151 -141
- package/dist/commonjs/{http-a94c5a81.js → http-4bc6caeb.js} +19 -15
- package/dist/commonjs/peernet-message.js +15 -15
- package/dist/commonjs/peernet.js +1901 -1794
- package/dist/commonjs/request.js +13 -13
- package/dist/commonjs/response.js +13 -13
- package/dist/module/peernet.js +2462 -2348
- package/index.html +5 -7
- package/package.json +22 -14
- package/rollup.config.js +33 -5
- package/rollup0.config.js +7 -0
- package/src/client.js +75 -75
- package/src/codec/codec-format-interface.js +172 -155
- package/src/codec/codec.js +124 -114
- package/src/codec/codecs.js +79 -79
- package/src/dht/dht.js +121 -121
- package/src/discovery/peer-discovery.js +75 -75
- package/src/handlers/message.js +50 -52
- package/src/hash/hash.js +155 -145
- package/src/http/client/http-client.js +44 -44
- package/src/messages/chat-message.js +14 -14
- package/src/messages/data-response.js +14 -14
- package/src/messages/data.js +18 -18
- package/src/messages/dht-response.js +14 -15
- package/src/messages/dht.js +25 -25
- package/src/messages/peer-response.js +14 -14
- package/src/messages/peer.js +14 -14
- package/src/messages/peernet-message.js +14 -14
- package/src/messages/ps.js +14 -14
- package/src/messages/request.js +14 -14
- package/src/messages/response.js +14 -14
- package/src/peer.js +67 -67
- package/src/peernet.js +614 -697
- package/src/proto/chat-message.proto.js +7 -7
- package/src/proto/peernet.proto.js +2 -2
- package/src/utils/utils.js +78 -78
- package/test/codec.js +3 -2
- package/test/messages.js +7 -4
- package/test.js +11 -4
- package/webpack.config.js +41 -0
- package/coverage/lcov-report/codec.js.html +0 -677
- package/coverage/lcov-report/hash.js.html +0 -551
- package/debug.log +0 -3
- package/dist/browser/peernet.js.tmp-browserify-14074318104595318069 +0 -0
- package/dist/browser/peernet.js.tmp-browserify-45407634493269122267 +0 -0
- package/dist/browser/peernet.js.tmp-browserify-53722389064799025427 +0 -0
- package/dist/browser/peernet.js.tmp-browserify-96323030449218949300 +0 -0
- package/dist/codec/codec-format-interface.js +0 -433
- package/dist/codec/codec.js +0 -199
- package/dist/commonjs/codec-73adfc0f.js +0 -205
- package/dist/commonjs/http-2c603501.js +0 -324
- package/dist/commonjs/http-42a6e555.js +0 -324
- package/dist/commonjs/http-43f4fafe.js +0 -324
- package/dist/commonjs/peernet-message-b6925673.js +0 -32
- package/dist/hash/hash.js +0 -340
- package/dist/messages/dht-response.js +0 -454
- package/dist/messages/dht.js +0 -453
- package/dist/messages/peernet.js +0 -456
- package/dist/module/http-273664bd.js +0 -317
- package/dist/module/http-8fe3c0d7.js +0 -317
- package/dist/module/http-c780c991.js +0 -317
- package/dist/module/http-f13e0d77.js +0 -317
package/dist/commonjs/peernet.js
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var sha256 = require('crypto-js/sha256');
|
|
5
|
-
var P2P = require('p2pt');
|
|
6
|
-
var LeofcoinStorage$1 = require('@leofcoin/storage');
|
|
7
|
-
var websocket = require('websocket');
|
|
8
|
-
var http$1 = require('http');
|
|
9
|
-
var Koa = require('koa');
|
|
3
|
+
var LeofcoinStorage = require('@leofcoin/storage');
|
|
10
4
|
var peernetMessage = require('./peernet-message.js');
|
|
11
5
|
var dht = require('./dht.js');
|
|
12
6
|
var dhtResponse = require('./dht-response.js');
|
|
@@ -14,786 +8,624 @@ var protons = require('protons');
|
|
|
14
8
|
var codecFormatInterface = require('./codec-format-interface.js');
|
|
15
9
|
var request = require('./request.js');
|
|
16
10
|
var response = require('./response.js');
|
|
17
|
-
var fetch
|
|
18
|
-
var codec = require('./codec-
|
|
11
|
+
var fetch = require('node-fetch');
|
|
12
|
+
var codec = require('./codec-8c8c652f.js');
|
|
19
13
|
var hash = require('./hash.js');
|
|
20
|
-
var
|
|
21
|
-
require('
|
|
22
|
-
require('
|
|
23
|
-
require('
|
|
24
|
-
require('
|
|
25
|
-
require('
|
|
14
|
+
var generateAccount = require('@leofcoin/generate-account');
|
|
15
|
+
var bs58check = require('bs58check');
|
|
16
|
+
var bip39 = require('bip39');
|
|
17
|
+
var bip32 = require('bip32');
|
|
18
|
+
var createKeccakHash = require('keccak');
|
|
19
|
+
var secp256k1 = require('secp256k1');
|
|
20
|
+
var ecc = require('tiny-secp256k1');
|
|
21
|
+
var MultiSignature = require('multi-signature');
|
|
22
|
+
var varint = require('varint');
|
|
23
|
+
var AES = require('crypto-js/aes.js');
|
|
24
|
+
require('crypto-js/sha512.js');
|
|
25
|
+
var ENC = require('crypto-js/enc-utf8.js');
|
|
26
|
+
require('@vandeurenglenn/base32');
|
|
27
|
+
require('@vandeurenglenn/base58');
|
|
28
|
+
require('@vandeurenglenn/is-hex');
|
|
26
29
|
|
|
27
30
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
28
31
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
var
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
var
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
this.bw = {
|
|
42
|
-
up: 0,
|
|
43
|
-
down: 0,
|
|
44
|
-
};
|
|
45
|
-
this.id = id;
|
|
46
|
-
this.connection = connection;
|
|
47
|
-
|
|
48
|
-
this.connection.on('data', (message) => {
|
|
49
|
-
this.bw.down += message.length;
|
|
50
|
-
pubsub.publish('peernet.data', JSON.parse(message.toString()));
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
request(data) {
|
|
55
|
-
return new Promise((resolve, reject) => {
|
|
56
|
-
const id = Math.random().toString(36).slice(-12);
|
|
57
|
-
data = Buffer.from(JSON.stringify({id, data}));
|
|
58
|
-
const _onData = (message) => {
|
|
59
|
-
if (message.id !== id) return
|
|
60
|
-
|
|
61
|
-
resolve(message.data);
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
pubsub.subscribe('peernet.data', _onData);
|
|
65
|
-
|
|
66
|
-
// cleanup subscriptions
|
|
67
|
-
setTimeout(() => {
|
|
68
|
-
pubsub.unsubscribe('peernet.data', _onData);
|
|
69
|
-
}, 5000);
|
|
70
|
-
|
|
71
|
-
this.write(data);
|
|
32
|
+
function _interopNamespace(e) {
|
|
33
|
+
if (e && e.__esModule) return e;
|
|
34
|
+
var n = Object.create(null);
|
|
35
|
+
if (e) {
|
|
36
|
+
Object.keys(e).forEach(function (k) {
|
|
37
|
+
if (k !== 'default') {
|
|
38
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
39
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
40
|
+
enumerable: true,
|
|
41
|
+
get: function () { return e[k]; }
|
|
42
|
+
});
|
|
43
|
+
}
|
|
72
44
|
});
|
|
73
45
|
}
|
|
46
|
+
n["default"] = e;
|
|
47
|
+
return Object.freeze(n);
|
|
48
|
+
}
|
|
74
49
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
50
|
+
var LeofcoinStorage__default = /*#__PURE__*/_interopDefaultLegacy(LeofcoinStorage);
|
|
51
|
+
var protons__default = /*#__PURE__*/_interopDefaultLegacy(protons);
|
|
52
|
+
var fetch__default = /*#__PURE__*/_interopDefaultLegacy(fetch);
|
|
53
|
+
var generateAccount__default = /*#__PURE__*/_interopDefaultLegacy(generateAccount);
|
|
54
|
+
var bs58check__default = /*#__PURE__*/_interopDefaultLegacy(bs58check);
|
|
55
|
+
var bs58check__namespace = /*#__PURE__*/_interopNamespace(bs58check);
|
|
56
|
+
var bip32__namespace = /*#__PURE__*/_interopNamespace(bip32);
|
|
57
|
+
var createKeccakHash__default = /*#__PURE__*/_interopDefaultLegacy(createKeccakHash);
|
|
58
|
+
var ecc__default = /*#__PURE__*/_interopDefaultLegacy(ecc);
|
|
59
|
+
var MultiSignature__default = /*#__PURE__*/_interopDefaultLegacy(MultiSignature);
|
|
60
|
+
var varint__default = /*#__PURE__*/_interopDefaultLegacy(varint);
|
|
61
|
+
var AES__default = /*#__PURE__*/_interopDefaultLegacy(AES);
|
|
62
|
+
var ENC__default = /*#__PURE__*/_interopDefaultLegacy(ENC);
|
|
63
|
+
|
|
64
|
+
/* socket-request-client version 1.6.1 */
|
|
65
|
+
|
|
66
|
+
class LittlePubSub {
|
|
67
|
+
constructor(verbose = true) {
|
|
68
|
+
this.subscribers = {};
|
|
69
|
+
this.verbose = verbose;
|
|
70
|
+
}
|
|
71
|
+
subscribe(event, handler, context) {
|
|
72
|
+
if (typeof context === 'undefined') {
|
|
73
|
+
context = handler;
|
|
74
|
+
}
|
|
75
|
+
this.subscribers[event] = this.subscribers[event] || { handlers: [], value: null};
|
|
76
|
+
this.subscribers[event].handlers.push(handler.bind(context));
|
|
91
77
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
pubsub.unsubscribe(event, this._events[event]);
|
|
78
|
+
unsubscribe(event, handler, context) {
|
|
79
|
+
if (typeof context === 'undefined') {
|
|
80
|
+
context = handler;
|
|
96
81
|
}
|
|
97
|
-
this.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
this.
|
|
82
|
+
if (this.subscribers[event]) {
|
|
83
|
+
const index = this.subscribers[event].handlers.indexOf(handler.bind(context));
|
|
84
|
+
this.subscribers[event].handlers.splice(index);
|
|
85
|
+
if (this.subscribers[event].handlers.length === 0) delete this.subscribers[event];
|
|
101
86
|
}
|
|
102
|
-
this.connection.destroy();
|
|
103
87
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
globalThis.connections = new Map();
|
|
112
|
-
globalThis.recentConnections = new Map();
|
|
113
|
-
globalThis.pubsub = globalThis.pubsub || new PubSub__default["default"]({verbose: false});
|
|
114
|
-
|
|
115
|
-
class PeernetClient {
|
|
116
|
-
constructor(options = {}) {
|
|
117
|
-
if (!options.id) options.id = Buffer.from('00000000000000000000000000000000');
|
|
118
|
-
if (!options.networkVersion) options.networkVersion = 'v0.1.0';
|
|
119
|
-
if (!options.networkName) options.networkName = 'peernet';
|
|
120
|
-
this.id = options.id;
|
|
121
|
-
|
|
122
|
-
this.topic = Buffer.from(sha256__default["default"](`${options.networkName}-${options.networkVersion}`).toString());
|
|
123
|
-
|
|
124
|
-
const trackers = [
|
|
125
|
-
'wss://star.leofcoin.org',
|
|
126
|
-
'wss://tracker.openwebtorrent.com',
|
|
127
|
-
// 'wss://tracker.sloppyta.co:443/announce',
|
|
128
|
-
];
|
|
129
|
-
this.p2p = new P2P__default["default"](trackers, this.topic.slice(0, 20));
|
|
130
|
-
this.p2p.on('peerconnect', (peer) => {
|
|
131
|
-
peer = new PeernetPeer(peer.id, peer);
|
|
132
|
-
connections.set(peer.id, peer);
|
|
133
|
-
pubsub.publish('peer:discovered', peer);
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
this.p2p.on('peerclose', (peer) => {
|
|
137
|
-
// TODO: close peernetPeer
|
|
138
|
-
const peernetPeer = connections.get(peer.id);
|
|
139
|
-
if (peernetPeer) {
|
|
140
|
-
peernetPeer.close();
|
|
88
|
+
publish(event, change) {
|
|
89
|
+
if (this.subscribers[event]) {
|
|
90
|
+
if (this.verbose || this.subscribers[event].value !== change) {
|
|
91
|
+
this.subscribers[event].value = change;
|
|
92
|
+
this.subscribers[event].handlers.forEach(handler => {
|
|
93
|
+
handler(change, this.subscribers[event].value);
|
|
94
|
+
});
|
|
141
95
|
}
|
|
142
|
-
connections.delete(peer.id);
|
|
143
|
-
pubsub.publish('peer:disconnected', peer);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
this.p2p.start();
|
|
147
|
-
|
|
148
|
-
if (globalThis.process) {
|
|
149
|
-
process.on('SIGINT', async () => {
|
|
150
|
-
console.log('Caught interrupt signal');
|
|
151
|
-
this.close();
|
|
152
|
-
setTimeout(async () => {
|
|
153
|
-
process.exit();
|
|
154
|
-
}, 100);
|
|
155
|
-
});
|
|
156
|
-
} else {
|
|
157
|
-
globalThis.onbeforeunload = () => {
|
|
158
|
-
this.close();
|
|
159
|
-
};
|
|
160
96
|
}
|
|
161
|
-
//
|
|
162
|
-
// this.sw.on('close', () => {
|
|
163
|
-
// })
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
close() {
|
|
167
|
-
return this.p2p.destroy()
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
_peers() {
|
|
171
|
-
return this.p2p.getPeers()
|
|
172
97
|
}
|
|
173
98
|
}
|
|
174
99
|
|
|
175
|
-
var
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
return
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
return
|
|
195
|
-
}
|
|
196
|
-
if (method === 'remove') {
|
|
197
|
-
await globalThis[name].remove(key, value);
|
|
198
|
-
return send('ok')
|
|
199
|
-
}
|
|
200
|
-
value = await globalThis[name].get(key);
|
|
201
|
-
return send(value)
|
|
202
|
-
} catch (e) {
|
|
203
|
-
return error(e)
|
|
204
|
-
}
|
|
205
|
-
},
|
|
206
|
-
getConfig: async (params, {send, error}) => {
|
|
207
|
-
try {
|
|
208
|
-
const config = await api.getConfig(params);
|
|
209
|
-
send(config);
|
|
210
|
-
} catch (e) {
|
|
211
|
-
error(e);
|
|
212
|
-
}
|
|
213
|
-
},
|
|
214
|
-
setMinerConfig: async (params, {send, error}) => {
|
|
215
|
-
try {
|
|
216
|
-
await api.setMinerConfig(params);
|
|
217
|
-
send('ok');
|
|
218
|
-
} catch (e) {
|
|
219
|
-
error(e);
|
|
100
|
+
var clientApi = _pubsub => {
|
|
101
|
+
const subscribe = (topic, cb) => {
|
|
102
|
+
_pubsub.subscribe(topic, cb);
|
|
103
|
+
};
|
|
104
|
+
const unsubscribe = (topic, cb) => {
|
|
105
|
+
_pubsub.unsubscribe(topic, cb);
|
|
106
|
+
};
|
|
107
|
+
const publish = (topic, value) => {
|
|
108
|
+
_pubsub.publish(topic, value);
|
|
109
|
+
};
|
|
110
|
+
const _connectionState = (state) => {
|
|
111
|
+
switch (state) {
|
|
112
|
+
case 0:
|
|
113
|
+
return 'connecting'
|
|
114
|
+
case 1:
|
|
115
|
+
return 'open'
|
|
116
|
+
case 2:
|
|
117
|
+
return 'closing'
|
|
118
|
+
case 3:
|
|
119
|
+
return 'closed'
|
|
220
120
|
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
121
|
+
};
|
|
122
|
+
const request = (client, request) => {
|
|
123
|
+
return new Promise((resolve, reject) => {
|
|
124
|
+
const state = _connectionState(client.readyState);
|
|
125
|
+
if (state !== 'open') return reject(`coudn't send request to ${client.id}, no open connection found.`)
|
|
126
|
+
request.id = Math.random().toString(36).slice(-12);
|
|
127
|
+
const handler = result => {
|
|
128
|
+
if (result && result.error) return reject(result.error)
|
|
129
|
+
resolve({result, id: request.id, handler});
|
|
130
|
+
unsubscribe(request.id, handler);
|
|
131
|
+
};
|
|
132
|
+
subscribe(request.id, handler);
|
|
133
|
+
send(client, request);
|
|
134
|
+
});
|
|
135
|
+
};
|
|
136
|
+
const send = async (client, request) => {
|
|
137
|
+
return client.send(JSON.stringify(request))
|
|
138
|
+
};
|
|
139
|
+
const pubsub = client => {
|
|
140
|
+
return {
|
|
141
|
+
publish: (topic = 'pubsub', value) => {
|
|
142
|
+
return send(client, {url: 'pubsub', params: { topic, value }})
|
|
143
|
+
},
|
|
144
|
+
subscribe: (topic = 'pubsub', cb) => {
|
|
145
|
+
subscribe(topic, cb);
|
|
146
|
+
return send(client, {url: 'pubsub', params: { topic, subscribe: true }})
|
|
147
|
+
},
|
|
148
|
+
unsubscribe: (topic = 'pubsub', cb) => {
|
|
149
|
+
unsubscribe(topic, cb);
|
|
150
|
+
return send(client, {url: 'pubsub', params: { topic, unsubscribe: true }})
|
|
151
|
+
},
|
|
152
|
+
subscribers: _pubsub.subscribers
|
|
228
153
|
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
154
|
+
};
|
|
155
|
+
const server = (client) => {
|
|
156
|
+
return {
|
|
157
|
+
uptime: async () => {
|
|
158
|
+
try {
|
|
159
|
+
const { result, id, handler } = await request(client, {url: 'uptime'});
|
|
160
|
+
unsubscribe(id, handler);
|
|
161
|
+
return result
|
|
162
|
+
} catch (e) {
|
|
163
|
+
throw e
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
ping: async () => {
|
|
167
|
+
try {
|
|
168
|
+
const now = new Date().getTime();
|
|
169
|
+
const { result, id, handler } = await request(client, {url: 'ping'});
|
|
170
|
+
unsubscribe(id, handler);
|
|
171
|
+
return (Number(result) - now)
|
|
172
|
+
} catch (e) {
|
|
173
|
+
throw e
|
|
174
|
+
}
|
|
175
|
+
}
|
|
240
176
|
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
177
|
+
};
|
|
178
|
+
const peernet = (client) => {
|
|
179
|
+
return {
|
|
180
|
+
join: async (params) => {
|
|
181
|
+
try {
|
|
182
|
+
params.join = true;
|
|
183
|
+
const requested = { url: 'peernet', params };
|
|
184
|
+
const { result, id, handler } = await request(client, requested);
|
|
185
|
+
unsubscribe(id, handler);
|
|
186
|
+
return result
|
|
187
|
+
} catch (e) {
|
|
188
|
+
throw e
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
leave: async (params) => {
|
|
192
|
+
try {
|
|
193
|
+
params.join = false;
|
|
194
|
+
const requested = { url: 'peernet', params };
|
|
195
|
+
const { result, id, handler } = await request(client, requested);
|
|
196
|
+
unsubscribe(id, handler);
|
|
197
|
+
return result
|
|
198
|
+
} catch (e) {
|
|
199
|
+
throw e
|
|
200
|
+
}
|
|
201
|
+
}
|
|
248
202
|
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
} catch (e) {
|
|
265
|
-
console.log(e);
|
|
266
|
-
error(e);
|
|
203
|
+
};
|
|
204
|
+
return { send, request, pubsub, server, subscribe, unsubscribe, publish, peernet }
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
if (!globalThis.PubSub) globalThis.PubSub = LittlePubSub;
|
|
208
|
+
if (!globalThis.pubsub) globalThis.pubsub = new LittlePubSub({verbose: false});
|
|
209
|
+
const socketRequestClient = (url, protocols = 'echo-protocol', options = { retry: false }) => {
|
|
210
|
+
const { retry } = options;
|
|
211
|
+
const api = clientApi(pubsub);
|
|
212
|
+
let tries = 0;
|
|
213
|
+
const onerror = error => {
|
|
214
|
+
if (pubsub.subscribers['error']) {
|
|
215
|
+
pubsub.publish('error', error);
|
|
216
|
+
} else {
|
|
217
|
+
console.error(error);
|
|
267
218
|
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
219
|
+
};
|
|
220
|
+
const onmessage = message => {
|
|
221
|
+
const {value, url, status, id} = JSON.parse(message.data.toString());
|
|
222
|
+
const publisher = id ? id : url;
|
|
223
|
+
if (status === 200) {
|
|
224
|
+
pubsub.publish(publisher, value);
|
|
225
|
+
} else {
|
|
226
|
+
pubsub.publish(publisher, {error: value});
|
|
275
227
|
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
228
|
+
};
|
|
229
|
+
const clientConnection = client => {
|
|
230
|
+
const startTime = new Date().getTime();
|
|
231
|
+
return {
|
|
232
|
+
client,
|
|
233
|
+
request: async req => {
|
|
234
|
+
const { result, id, handler } = await api.request(client, req);
|
|
235
|
+
pubsub.unsubscribe(id, handler);
|
|
236
|
+
return result
|
|
237
|
+
},
|
|
238
|
+
send: req => api.send(client, req),
|
|
239
|
+
subscribe: api.subscribe,
|
|
240
|
+
unsubscribe: api.unsubscribe,
|
|
241
|
+
subscribers: api.subscribers,
|
|
242
|
+
publish: api.publish,
|
|
243
|
+
pubsub: api.pubsub(client),
|
|
244
|
+
uptime: () => {
|
|
245
|
+
const now = new Date().getTime();
|
|
246
|
+
return (now - startTime)
|
|
247
|
+
},
|
|
248
|
+
peernet: api.peernet(client),
|
|
249
|
+
server: api.server(client),
|
|
250
|
+
close: exit => {
|
|
251
|
+
client.onclose = message => {
|
|
252
|
+
if (exit) process.exit();
|
|
253
|
+
};
|
|
254
|
+
client.close();
|
|
255
|
+
}
|
|
287
256
|
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
257
|
+
};
|
|
258
|
+
return new Promise((resolve, reject) => {
|
|
259
|
+
const init = () => {
|
|
260
|
+
let ws;
|
|
261
|
+
if (typeof process === 'object' && !globalThis.WebSocket) {
|
|
262
|
+
ws = require('websocket').w3cwebsocket;
|
|
263
|
+
} else {
|
|
264
|
+
ws = WebSocket;
|
|
265
|
+
}
|
|
266
|
+
const client = new ws(url, protocols);
|
|
267
|
+
client.onmessage = onmessage;
|
|
268
|
+
client.onerror = onerror;
|
|
269
|
+
client.onopen = () => {
|
|
270
|
+
tries = 0;
|
|
271
|
+
resolve(clientConnection(client));
|
|
272
|
+
};
|
|
273
|
+
client.onclose = message => {
|
|
274
|
+
tries++;
|
|
275
|
+
if (!retry) return reject(options)
|
|
276
|
+
if (tries > 5) {
|
|
277
|
+
console.log(`${protocols} Client Closed`);
|
|
278
|
+
console.error(`could not connect to - ${url}/`);
|
|
279
|
+
return resolve(clientConnection(client))
|
|
280
|
+
}
|
|
281
|
+
if (message.code === 1006) {
|
|
282
|
+
console.log('Retrying in 10 seconds');
|
|
283
|
+
setTimeout(() => {
|
|
284
|
+
return init();
|
|
285
|
+
}, retry);
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
};
|
|
289
|
+
return init();
|
|
290
|
+
});
|
|
293
291
|
};
|
|
294
292
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
293
|
+
class Peer {
|
|
294
|
+
#connection
|
|
295
|
+
#ready = false
|
|
296
|
+
#connecting = false
|
|
297
|
+
#connected = false
|
|
298
|
+
#channelReady = false
|
|
299
|
+
#destroying = false
|
|
300
|
+
#destroyed = false
|
|
301
|
+
#isNegotiating = false
|
|
302
|
+
#firstNegotiation = true
|
|
303
|
+
#iceComplete = false
|
|
304
|
+
#remoteTracks = []
|
|
305
|
+
#remoteStreams = []
|
|
306
|
+
#pendingCandidates = []
|
|
307
|
+
#senderMap = new Map()
|
|
308
|
+
#iceCompleteTimer
|
|
309
|
+
#channel
|
|
310
|
+
|
|
311
|
+
get connection() {
|
|
312
|
+
return this.#connection
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
get connected() {
|
|
316
|
+
return this.#connected
|
|
317
|
+
}
|
|
300
318
|
|
|
301
319
|
/**
|
|
302
|
-
* @
|
|
303
|
-
*
|
|
304
|
-
* @param {object} connection socket connection
|
|
305
|
-
* @param {string} route the route to handle
|
|
306
|
-
*/
|
|
307
|
-
var socketConnection = (request, protocol, origin) => {
|
|
308
|
-
if (origin && !originIsAllowed(request.origin, origin)) {
|
|
309
|
-
// Make sure we only accept requests from an allowed origin
|
|
310
|
-
request.reject();
|
|
311
|
-
fullLog(`Connection from origin ${request.origin} rejected.`);
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
// console.log(request);
|
|
315
|
-
const connection = request.accept(protocol, request.origin);
|
|
316
|
-
fullLog(`Connection accepted @${protocol}`);
|
|
317
|
-
return connection;
|
|
318
|
-
};
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* @module socketResponse
|
|
322
|
-
*
|
|
323
|
-
* @param {object} connection socket connection
|
|
324
|
-
* @param {string} url the request url
|
|
320
|
+
* @params {Object} options
|
|
321
|
+
* @params {string} options.channelName - this peerid : otherpeer id
|
|
325
322
|
*/
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
const socketRequestServer = (options, routes = {}) => {
|
|
339
|
-
// if (!routes && !routes.port && options) routes = options;
|
|
340
|
-
// else if (!options && !routes) return console.error('no routes defined');
|
|
341
|
-
|
|
342
|
-
let {httpServer, httpsServer, port, protocol, credentials, origin, pubsub } = options;
|
|
343
|
-
if (!pubsub) pubsub = new PubSub__default["default"]({verbose: false});
|
|
344
|
-
if (!port) port = 6000;
|
|
345
|
-
const connections = [];
|
|
346
|
-
let connection;
|
|
347
|
-
const startTime = new Date().getTime();
|
|
348
|
-
// default routes
|
|
349
|
-
if (!routes.ping) routes.ping = (response) => response.send(new Date().getTime());
|
|
350
|
-
if (!routes.uptime) routes.uptime = (response) => {
|
|
351
|
-
const now = new Date().getTime();
|
|
352
|
-
response.send(now - startTime);
|
|
323
|
+
constructor(options = {}) {
|
|
324
|
+
this._in = this._in.bind(this);
|
|
325
|
+
this.offerOptions = options.offerOptions;
|
|
326
|
+
this.initiator = options.initiator;
|
|
327
|
+
this.streams = options.streams;
|
|
328
|
+
this.socketClient = options.socketClient;
|
|
329
|
+
this.id = options.id;
|
|
330
|
+
this.to = options.to;
|
|
331
|
+
this.bw = {
|
|
332
|
+
up: 0,
|
|
333
|
+
down: 0
|
|
353
334
|
};
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
// }));
|
|
386
|
-
// }
|
|
387
|
-
pubsub.publish(topic, params.value);
|
|
388
|
-
response.send('ok', 200);
|
|
389
|
-
};
|
|
390
|
-
|
|
391
|
-
}
|
|
392
|
-
globalThis.peerMap = new Map();
|
|
393
|
-
if (!routes.peernet) {
|
|
394
|
-
routes.peernet = (params, response) => {
|
|
395
|
-
if (params.join) {
|
|
396
|
-
peerMap.set(params.peerId, params.address);
|
|
397
|
-
response.send([...peerMap.values()]);
|
|
398
|
-
for (const connection of connections) {
|
|
399
|
-
if (connection !== response.connection)
|
|
400
|
-
socketResponse(connection, 'peernet', 'peernet').send({discovered: params.address});
|
|
401
|
-
}
|
|
402
|
-
return
|
|
403
|
-
}
|
|
404
|
-
if (!params.join) {
|
|
405
|
-
peerMap.delete(params.peerId);
|
|
406
|
-
return response.send()
|
|
335
|
+
|
|
336
|
+
this.channelName = options.channelName || Buffer.from(Math.random().toString(36).slice(-12)).toString('hex');
|
|
337
|
+
console.log(this.channelName);
|
|
338
|
+
this.options = options;
|
|
339
|
+
this.#init();
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
set socketClient(value) {
|
|
343
|
+
// this.socketClient?.pubsub.unsubscribe('signal', this._in)
|
|
344
|
+
this._socketClient = value;
|
|
345
|
+
this._socketClient.pubsub.subscribe('signal', this._in);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
get socketClient() {
|
|
349
|
+
return this._socketClient
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
send(message) {
|
|
353
|
+
this.bw.up += message.length;
|
|
354
|
+
this.channel.send(message);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
request(data) {
|
|
358
|
+
return new Promise((resolve, reject) => {
|
|
359
|
+
const id = Math.random().toString(36).slice(-12);
|
|
360
|
+
data = new TextEncoder().encode(JSON.stringify({id, data}));
|
|
361
|
+
const _onData = message => {
|
|
362
|
+
message = JSON.parse(new TextDecoder().decode(message.data));
|
|
363
|
+
if (message.id === id) {
|
|
364
|
+
resolve(message.data);
|
|
365
|
+
pubsub.unsubscribe('peer:data', _onData);
|
|
407
366
|
}
|
|
408
367
|
};
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
368
|
+
|
|
369
|
+
pubsub.subscribe('peer:data', _onData);
|
|
370
|
+
|
|
371
|
+
// cleanup subscriptions
|
|
372
|
+
setTimeout(() => {
|
|
373
|
+
pubsub.unsubscribe('peer:data', _onData);
|
|
374
|
+
}, 5000);
|
|
375
|
+
|
|
376
|
+
this.send(data);
|
|
418
377
|
});
|
|
419
378
|
}
|
|
420
379
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
380
|
+
async #init() {
|
|
381
|
+
try {
|
|
382
|
+
const iceServers = [{
|
|
383
|
+
urls: 'stun:stun.l.google.com:19302' // Google's public STUN server
|
|
384
|
+
}];
|
|
425
385
|
|
|
426
|
-
|
|
386
|
+
this.#connection = new wrtc.RTCPeerConnection();
|
|
387
|
+
this.#connection.onicecandidate = ({ candidate }) => {
|
|
388
|
+
if (candidate) {
|
|
389
|
+
this.address = candidate.address;
|
|
390
|
+
this.port = candidate.port;
|
|
391
|
+
this.protocol = candidate.protocol;
|
|
392
|
+
this.ipFamily = this.address.includes('::') ? 'ipv6': 'ipv4';
|
|
393
|
+
this._sendMessage({candidate});
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
// if (this.initiator) this.#connection.onnegotiationneeded = () => {
|
|
397
|
+
// console.log('create offer');
|
|
398
|
+
this.#connection.ondatachannel = (message) => {
|
|
399
|
+
message.channel.onopen = () => {
|
|
400
|
+
this.#connected = true;
|
|
401
|
+
pubsub.publish('peer:connected', this);
|
|
402
|
+
};
|
|
403
|
+
message.channel.onclose = () => console.log('close');
|
|
404
|
+
message.channel.onmessage = (message) => {
|
|
405
|
+
if (message.to) {
|
|
406
|
+
if (message.to === this.id) pubsub.publish('peer:data', message);
|
|
407
|
+
} else pubsub.publish('peer:data', message);
|
|
408
|
+
this.bw.down += message.length;
|
|
409
|
+
};
|
|
410
|
+
this.channel = message.channel;
|
|
411
|
+
};
|
|
412
|
+
if (this.initiator) {
|
|
427
413
|
|
|
428
|
-
|
|
429
|
-
|
|
414
|
+
this.channel = this.#connection.createDataChannel('messageChannel');
|
|
415
|
+
this.channel.onopen = () => {
|
|
416
|
+
this.#connected = true;
|
|
417
|
+
pubsub.publish('peer:connected', this);
|
|
418
|
+
// this.channel.send('hi')
|
|
419
|
+
};
|
|
420
|
+
this.channel.onclose = () => this.close.bind(this);
|
|
430
421
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
}
|
|
443
|
-
const { route, params, url, id, customEvent } = JSON.parse(data);
|
|
444
|
-
// ignore api when customEvent is defined
|
|
445
|
-
if (customEvent) return;
|
|
446
|
-
if (routes[url]) {
|
|
447
|
-
if (!params) return routes[url](socketResponse(connection, url, id));
|
|
448
|
-
return routes[url](params, socketResponse(connection, url, id));
|
|
422
|
+
this.channel.onmessage = (message) => {
|
|
423
|
+
if (message.to) {
|
|
424
|
+
if (message.to === this.id) pubsub.publish('peer:data', message);
|
|
425
|
+
} else pubsub.publish('peer:data', message);
|
|
426
|
+
this.bw.down += message.length;
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
const offer = await this.#connection.createOffer();
|
|
430
|
+
await this.#connection.setLocalDescription(offer);
|
|
431
|
+
|
|
432
|
+
this._sendMessage({'sdp': this.#connection.localDescription});
|
|
449
433
|
}
|
|
450
|
-
|
|
451
|
-
};
|
|
434
|
+
// }
|
|
452
435
|
|
|
453
|
-
|
|
454
|
-
|
|
436
|
+
} catch (e) {
|
|
437
|
+
console.log(e);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
455
440
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
if (!config.protocol) config.protocol = 'peernet-v0.1.0';
|
|
465
|
-
if (!config.port) config.port = 2000;
|
|
466
|
-
if (!config.host) config.host = '127.0.0.1';
|
|
467
|
-
|
|
468
|
-
const app = new Koa__default["default"]();
|
|
469
|
-
|
|
470
|
-
app.use(async (ctx) => {
|
|
471
|
-
const url = ctx.url.split('/api/')[1];
|
|
472
|
-
if (url === 'version') ctx.body = {client: '@peernet/api/http', version};
|
|
473
|
-
});
|
|
474
|
-
|
|
475
|
-
const httpServer = http$1.createServer(app.callback());
|
|
476
|
-
|
|
477
|
-
config.httpServer = httpServer;
|
|
478
|
-
|
|
479
|
-
httpServer.listen(config.port, () => {
|
|
480
|
-
console.log(`listening on ${config.port}`);
|
|
481
|
-
});
|
|
482
|
-
|
|
483
|
-
return socketRequestServer(config, api$1)
|
|
484
|
-
};
|
|
485
|
-
|
|
486
|
-
var clientApi = _pubsub => {
|
|
487
|
-
|
|
488
|
-
const subscribe = (topic, cb) => {
|
|
489
|
-
_pubsub.subscribe(topic, cb);
|
|
490
|
-
};
|
|
491
|
-
|
|
492
|
-
const unsubscribe = (topic, cb) => {
|
|
493
|
-
_pubsub.unsubscribe(topic, cb);
|
|
494
|
-
};
|
|
495
|
-
|
|
496
|
-
const publish = (topic, value) => {
|
|
497
|
-
_pubsub.publish(topic, value);
|
|
498
|
-
};
|
|
499
|
-
|
|
500
|
-
const _connectionState = (state) => {
|
|
501
|
-
switch (state) {
|
|
502
|
-
case 0:
|
|
503
|
-
return 'connecting'
|
|
504
|
-
case 1:
|
|
505
|
-
return 'open'
|
|
506
|
-
case 2:
|
|
507
|
-
return 'closing'
|
|
508
|
-
case 3:
|
|
509
|
-
return 'closed'
|
|
510
|
-
}
|
|
511
|
-
};
|
|
512
|
-
/**
|
|
513
|
-
* @param {string} type
|
|
514
|
-
* @param {string} name
|
|
515
|
-
* @param {object} params
|
|
516
|
-
*/
|
|
517
|
-
const request = (client, request) => {
|
|
518
|
-
return new Promise((resolve, reject) => {
|
|
519
|
-
|
|
520
|
-
const state = _connectionState(client.readyState);
|
|
521
|
-
if (state !== 'open') return reject(`coudn't send request to ${client.id}, no open connection found.`)
|
|
522
|
-
|
|
523
|
-
request.id = Math.random().toString(36).slice(-12);
|
|
524
|
-
const handler = result => {
|
|
525
|
-
if (result && result.error) return reject(result.error)
|
|
526
|
-
resolve({result, id: request.id, handler});
|
|
527
|
-
};
|
|
528
|
-
subscribe(request.id, handler);
|
|
529
|
-
send(client, request);
|
|
530
|
-
});
|
|
531
|
-
};
|
|
532
|
-
|
|
533
|
-
const send = async (client, request) => {
|
|
534
|
-
return client.send(JSON.stringify(request))
|
|
535
|
-
};
|
|
441
|
+
_sendMessage(message) {
|
|
442
|
+
this.socketClient.send({url: 'signal', params: {
|
|
443
|
+
to: this.to,
|
|
444
|
+
from: this.id,
|
|
445
|
+
channelName: this.options.channelName,
|
|
446
|
+
...message
|
|
447
|
+
}});
|
|
448
|
+
}
|
|
536
449
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
return send(client, {url: 'pubsub', params: { topic, unsubscribe: true }})
|
|
549
|
-
},
|
|
550
|
-
subscribers: _pubsub.subscribers
|
|
450
|
+
async _in(message, data) {
|
|
451
|
+
// message = JSON.parse(message);
|
|
452
|
+
if (message.to !== this.id) return
|
|
453
|
+
// if (data.videocall) return this._startStream(true, false); // start video and audio stream
|
|
454
|
+
// if (data.call) return this._startStream(true, true); // start audio stream
|
|
455
|
+
if (message.candidate) {
|
|
456
|
+
this.remoteAddress = message.candidate.address;
|
|
457
|
+
this.remotePort = message.candidate.port;
|
|
458
|
+
this.remoteProtocol = message.candidate.protocol;
|
|
459
|
+
this.remoteIpFamily = this.remoteAddress?.includes('::') ? 'ipv6': 'ipv4';
|
|
460
|
+
return this.#connection.addIceCandidate(new wrtc.RTCIceCandidate(message.candidate));
|
|
551
461
|
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
unsubscribe(id, handler);
|
|
560
|
-
return result
|
|
561
|
-
} catch (e) {
|
|
562
|
-
throw e
|
|
462
|
+
try {
|
|
463
|
+
if (message.sdp) {
|
|
464
|
+
if (message.sdp.type === 'offer') {
|
|
465
|
+
await this.#connection.setRemoteDescription(new wrtc.RTCSessionDescription(message.sdp));
|
|
466
|
+
const answer = await this.#connection.createAnswer();
|
|
467
|
+
await this.#connection.setLocalDescription(answer);
|
|
468
|
+
this._sendMessage({'sdp': this.#connection.localDescription});
|
|
563
469
|
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
try {
|
|
567
|
-
const now = new Date().getTime();
|
|
568
|
-
const { result, id, handler } = await request(client, {url: 'ping'});
|
|
569
|
-
unsubscribe(id, handler);
|
|
570
|
-
return (Number(result) - now)
|
|
571
|
-
} catch (e) {
|
|
572
|
-
throw e
|
|
470
|
+
if (message.sdp.type === 'answer') {
|
|
471
|
+
await this.#connection.setRemoteDescription(new wrtc.RTCSessionDescription(message.sdp));
|
|
573
472
|
}
|
|
473
|
+
}
|
|
474
|
+
} catch (e) {
|
|
475
|
+
console.log(e);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
close() {
|
|
480
|
+
this.channel?.close();
|
|
481
|
+
this.#connection?.close();
|
|
482
|
+
|
|
483
|
+
this.socketClient.pubsub.unsubscribe('signal', this._in);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
class Client {
|
|
488
|
+
#peerConnection
|
|
489
|
+
#connections = {}
|
|
490
|
+
#stars = {}
|
|
491
|
+
|
|
492
|
+
get connections() {
|
|
493
|
+
return { ...this.#connections }
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
constructor(id, identifiers = ['peernet-v0.1.0'], stars = []) {
|
|
497
|
+
this.id = id || Math.random().toString(36).slice(-12);
|
|
498
|
+
if (!Array.isArray(identifiers)) identifiers = [identifiers];
|
|
499
|
+
this.peerJoined = this.peerJoined.bind(this);
|
|
500
|
+
this.peerLeft = this.peerLeft.bind(this);
|
|
501
|
+
this.starLeft = this.starLeft.bind(this);
|
|
502
|
+
this.starJoined = this.starJoined.bind(this);
|
|
503
|
+
|
|
504
|
+
this._init(identifiers, stars);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
async _init(identifiers, stars = []) {
|
|
508
|
+
if (stars.length === 0) {
|
|
509
|
+
stars.push('wss://star.leofcoin.org');
|
|
510
|
+
stars.push('ws://localhost:44444');
|
|
511
|
+
}
|
|
512
|
+
this.identifiers = identifiers;
|
|
513
|
+
this.starsConfig = stars;
|
|
514
|
+
// reconnectJob()
|
|
515
|
+
|
|
516
|
+
globalThis.wrtc = await Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require('wrtc')); });
|
|
517
|
+
for (const star of stars) {
|
|
518
|
+
try {
|
|
519
|
+
this.socketClient = await socketRequestClient(star, identifiers[0]);
|
|
520
|
+
const id = await this.socketClient.request({url: 'id', params: {from: this.id}});
|
|
521
|
+
this.socketClient.peerId = id;
|
|
522
|
+
this.#stars[id] = this.socketClient;
|
|
523
|
+
} catch (e) {
|
|
524
|
+
if (stars.indexOf(star) === stars.length -1 && !this.socketClient) throw new Error(`No star available to connect`);
|
|
574
525
|
}
|
|
575
526
|
}
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
527
|
+
const peers = await this.socketClient.peernet.join({id: this.id});
|
|
528
|
+
for (const id of peers) {
|
|
529
|
+
if (id !== this.id && !this.#connections[id]) this.#connections[id] = new Peer({channelName: `${id}:${this.id}`, socketClient: this.socketClient, id: this.id, to: id});
|
|
530
|
+
}
|
|
531
|
+
this.setupListeners();
|
|
532
|
+
|
|
533
|
+
pubsub.subscribe('peer:connected', (peer) => {
|
|
534
|
+
// peer.send(JSON.stringify({data: 'hello', from: this.id, to: peer.to}))
|
|
535
|
+
console.log({peer: peer.to});
|
|
536
|
+
console.log({id: peer.id});
|
|
537
|
+
console.log({id: this.id});
|
|
538
|
+
});
|
|
539
|
+
// pubsub.subscribe('peer:data', (data) => console.log({data}))
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
setupListeners() {
|
|
543
|
+
this.socketClient.subscribe('peer:joined', this.peerJoined);
|
|
544
|
+
this.socketClient.subscribe('peer:left', this.peerLeft);
|
|
545
|
+
this.socketClient.subscribe('star:left', this.starLeft);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
starJoined(id) {
|
|
549
|
+
if (this.#stars[id]) {
|
|
550
|
+
this.#stars[id].close();
|
|
551
|
+
delete this.#stars[id];
|
|
552
|
+
}
|
|
553
|
+
console.log(`star ${id} joined`);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
async starLeft(id) {
|
|
557
|
+
if (this.#stars[id]) {
|
|
558
|
+
this.#stars[id].close();
|
|
559
|
+
delete this.#stars[id];
|
|
560
|
+
}
|
|
561
|
+
if (this.socketClient?.peerId === id) {
|
|
562
|
+
|
|
563
|
+
this.socketClient.unsubscribe('peer:joined', this.peerJoined);
|
|
564
|
+
this.socketClient.unsubscribe('peer:left', this.peerLeft);
|
|
565
|
+
this.socketClient.unsubscribe('star:left', this.starLeft);
|
|
566
|
+
this.socketClient.close();
|
|
567
|
+
this.socketClient = undefined;
|
|
568
|
+
|
|
569
|
+
for (const star of this.starsConfig) {
|
|
592
570
|
try {
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
const
|
|
596
|
-
|
|
597
|
-
|
|
571
|
+
this.socketClient = await socketRequestClient(star, this.identifiers[0]);
|
|
572
|
+
if (!this.socketClient?.client?._connection.connected) return
|
|
573
|
+
const id = await this.socketClient.request({url: 'id', params: {from: this.id}});
|
|
574
|
+
this.#stars[id] = this.socketClient;
|
|
575
|
+
|
|
576
|
+
this.socketClient.peerId = id;
|
|
577
|
+
|
|
578
|
+
const peers = await this.socketClient.peernet.join({id: this.id});
|
|
579
|
+
this.setupListeners();
|
|
580
|
+
for (const id of peers) {
|
|
581
|
+
if (id !== this.id) {
|
|
582
|
+
// close connection
|
|
583
|
+
if (this.#connections[id]) {
|
|
584
|
+
if (this.#connections[id].connected) await this.#connections[id].close();
|
|
585
|
+
delete this.#connections[id];
|
|
586
|
+
}
|
|
587
|
+
// reconnect
|
|
588
|
+
if (id !== this.id) this.#connections[id] = new Peer({channelName: `${id}:${this.id}`, socketClient: this.socketClient, id: this.id, to: id});
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
}
|
|
598
592
|
} catch (e) {
|
|
599
|
-
|
|
593
|
+
console.log(e);
|
|
594
|
+
if (this.starsConfig.indexOf(star) === this.starsConfig.length -1 && !this.socketClient) throw new Error(`No star available to connect`);
|
|
600
595
|
}
|
|
601
596
|
}
|
|
602
597
|
}
|
|
603
|
-
};
|
|
604
|
-
|
|
605
|
-
return { send, request, pubsub, server, subscribe, unsubscribe, publish, peernet }
|
|
606
|
-
};
|
|
607
|
-
|
|
608
|
-
const socketRequestClient = (url, protocols = 'echo-protocol', options = { retry: false, pubsub: false }) => {
|
|
609
|
-
let { pubsub, retry } = options;
|
|
610
|
-
if (!pubsub) pubsub = new PubSub__default["default"]({verbose: false});
|
|
611
|
-
|
|
612
|
-
const api = clientApi(pubsub);
|
|
613
|
-
|
|
614
|
-
let tries = 0;
|
|
615
|
-
|
|
616
|
-
const onerror = error => {
|
|
617
|
-
if (pubsub.subscribers['error']) {
|
|
618
|
-
pubsub.publish('error', error);
|
|
619
|
-
} else {
|
|
620
|
-
console.error(error);
|
|
621
|
-
}
|
|
622
|
-
};
|
|
623
598
|
|
|
624
|
-
|
|
625
|
-
const {value, url, status, id} = JSON.parse(message.data.toString());
|
|
626
|
-
const publisher = id ? id : url;
|
|
627
|
-
if (status === 200) {
|
|
628
|
-
pubsub.publish(publisher, value);
|
|
629
|
-
} else {
|
|
630
|
-
pubsub.publish(publisher, {error: value});
|
|
631
|
-
}
|
|
599
|
+
console.log(`star ${id} left`);
|
|
632
600
|
|
|
633
|
-
};
|
|
634
601
|
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
pubsub.unsubscribe(id, handler);
|
|
642
|
-
return result
|
|
643
|
-
},
|
|
644
|
-
send: req => api.send(client, req),
|
|
645
|
-
subscribe: api.subscribe,
|
|
646
|
-
unsubscribe: api.unsubscribe,
|
|
647
|
-
subscribers: api.subscribers,
|
|
648
|
-
publish: api.publish,
|
|
649
|
-
pubsub: api.pubsub(client),
|
|
650
|
-
uptime: () => {
|
|
651
|
-
const now = new Date().getTime();
|
|
652
|
-
return (now - startTime)
|
|
653
|
-
},
|
|
654
|
-
peernet: api.peernet(client),
|
|
655
|
-
server: api.server(client),
|
|
656
|
-
close: exit => {
|
|
657
|
-
client.onclose = message => {
|
|
658
|
-
if (exit) process.exit();
|
|
659
|
-
};
|
|
660
|
-
client.close();
|
|
661
|
-
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
peerLeft(id) {
|
|
605
|
+
if (this.#connections[id]) {
|
|
606
|
+
this.#connections[id].close();
|
|
607
|
+
delete this.#connections[id];
|
|
662
608
|
}
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
return new Promise((resolve, reject) => {
|
|
666
|
-
const init = () => {
|
|
667
|
-
let ws;
|
|
668
|
-
if (typeof process === 'object') {
|
|
669
|
-
ws = require('websocket').w3cwebsocket;
|
|
670
|
-
} else {
|
|
671
|
-
ws = WebSocket;
|
|
672
|
-
}
|
|
673
|
-
const client = new ws(url, protocols);
|
|
609
|
+
console.log(`peer ${id} left`);
|
|
610
|
+
}
|
|
674
611
|
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
return resolve(clientConnection(client))
|
|
688
|
-
}
|
|
689
|
-
if (message.code === 1006) {
|
|
690
|
-
console.log('Retrying in 10 seconds');
|
|
691
|
-
setTimeout(() => {
|
|
692
|
-
return init();
|
|
693
|
-
}, retry);
|
|
694
|
-
}
|
|
695
|
-
};
|
|
696
|
-
};
|
|
697
|
-
return init();
|
|
698
|
-
});
|
|
699
|
-
};
|
|
700
|
-
|
|
701
|
-
class HttpClientApi$1 {
|
|
702
|
-
constructor(config = {}) {
|
|
703
|
-
if (!config.apiPath) config.apiPath = 'api';
|
|
704
|
-
|
|
705
|
-
const address = `ws://${config.host}:${config.port}`;
|
|
706
|
-
|
|
707
|
-
this.apiUrl = (url) => `${address}/${url}`;
|
|
708
|
-
return (async () => {
|
|
709
|
-
this.client = await socketRequestClient(address, config.protocol, {pubsub: config.pubsub, retry: 3000});
|
|
710
|
-
return this
|
|
711
|
-
})()
|
|
712
|
-
}
|
|
612
|
+
peerJoined(id, signal) {
|
|
613
|
+
if (this.#connections[id]) {
|
|
614
|
+
if (this.#connections[id].connected) this.#connections[id].close();
|
|
615
|
+
delete this.#connections[id];
|
|
616
|
+
}
|
|
617
|
+
// RTCPeerConnection
|
|
618
|
+
this.#connections[id] = new Peer({initiator: true, channelName: `${this.id}:${id}`, socketClient: this.socketClient, id: this.id, to: id});
|
|
619
|
+
console.log(`peer ${id} joined`);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
}
|
|
713
624
|
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
if (obj) {
|
|
719
|
-
method = 'POST';
|
|
720
|
-
headers['Content-Type'] = 'application/json';
|
|
721
|
-
body = JSON.stringify(obj);
|
|
722
|
-
}
|
|
723
|
-
let response = await this.client.request(url, {headers, body, method});
|
|
724
|
-
const type = response.headers.get('content-type').split(';')[0];
|
|
725
|
-
if (type==='application/json') response = await response.json();
|
|
726
|
-
return response
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
async put(url, obj) {
|
|
730
|
-
const headers = {};
|
|
731
|
-
let body = {};
|
|
732
|
-
if (obj) {
|
|
733
|
-
headers['Content-Type'] = 'application/json';
|
|
734
|
-
body = JSON.stringify(obj);
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
let response = await fetch(this.apiUrl(url), {method: 'PUT', headers, body});
|
|
738
|
-
const type = response.headers.get('content-type').split(';')[0];
|
|
739
|
-
if (type==='application/json') response = await response.json();
|
|
740
|
-
return response
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
class HttpClientApi extends HttpClientApi$1 {
|
|
745
|
-
constructor(config = {}) {
|
|
746
|
-
config.apiPath = 'api';
|
|
747
|
-
return (async () => {
|
|
748
|
-
await super(config);
|
|
749
|
-
|
|
750
|
-
this.properties = {
|
|
751
|
-
wallet: 'get',
|
|
752
|
-
version: 'get',
|
|
753
|
-
addresses: 'get',
|
|
754
|
-
config: 'get',
|
|
755
|
-
account: 'get',
|
|
756
|
-
accounts: 'get',
|
|
757
|
-
transaction: 'any',
|
|
758
|
-
transactions: 'get',
|
|
759
|
-
block: 'get',
|
|
760
|
-
blocks: 'get',
|
|
761
|
-
};
|
|
762
|
-
this.keys = Object.keys(this.properties);
|
|
763
|
-
return this
|
|
764
|
-
})()
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
async request(url, data) {
|
|
768
|
-
return await this.client.request({url, params: data})
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
async ready() {
|
|
772
|
-
return await this.request('ready')
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
async version() {
|
|
776
|
-
return await this.request('version')
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
async account(index) {
|
|
780
|
-
return await this.request('account', {index})
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
var httpClient = (config = {}) => {
|
|
785
|
-
if (typeof config !== 'object') config = {};
|
|
786
|
-
if (!config.protocol) config.protocol = 'peernet-v0.1.0';
|
|
787
|
-
if (!config.port) config.port = 1000;
|
|
788
|
-
if (!config.host) config.host = '127.0.0.1';
|
|
789
|
-
|
|
790
|
-
return new HttpClientApi(config)
|
|
791
|
-
};
|
|
792
|
-
|
|
793
|
-
class LeofcoinStorageClient {
|
|
794
|
-
constructor(name, root) {
|
|
795
|
-
this.name = name;
|
|
796
|
-
this.root = root;
|
|
625
|
+
class LeofcoinStorageClient {
|
|
626
|
+
constructor(name, root) {
|
|
627
|
+
this.name = name;
|
|
628
|
+
this.root = root;
|
|
797
629
|
}
|
|
798
630
|
|
|
799
631
|
async get(key) {
|
|
@@ -835,19 +667,19 @@ message PeernetDataMessage {
|
|
|
835
667
|
}
|
|
836
668
|
`;
|
|
837
669
|
|
|
838
|
-
/**
|
|
839
|
-
* @extends {CodecFormat}
|
|
840
|
-
*/
|
|
841
|
-
class DataMessage extends codecFormatInterface {
|
|
842
|
-
get keys() {
|
|
843
|
-
return ['hash', 'store']
|
|
844
|
-
}
|
|
845
|
-
/**
|
|
846
|
-
* @param {Buffer|String|Object|DataMessage} data - The data needed to create the DataMessage
|
|
847
|
-
*/
|
|
848
|
-
constructor(data) {
|
|
849
|
-
super(data, protons__default["default"](proto$5).PeernetDataMessage, {name: 'peernet-data'});
|
|
850
|
-
}
|
|
670
|
+
/**
|
|
671
|
+
* @extends {CodecFormat}
|
|
672
|
+
*/
|
|
673
|
+
class DataMessage extends codecFormatInterface {
|
|
674
|
+
get keys() {
|
|
675
|
+
return ['hash', 'store']
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* @param {Buffer|String|Object|DataMessage} data - The data needed to create the DataMessage
|
|
679
|
+
*/
|
|
680
|
+
constructor(data) {
|
|
681
|
+
super(data, protons__default["default"](proto$5).PeernetDataMessage, {name: 'peernet-data'});
|
|
682
|
+
}
|
|
851
683
|
}
|
|
852
684
|
|
|
853
685
|
var proto$4 = `
|
|
@@ -857,15 +689,15 @@ message PsMessage {
|
|
|
857
689
|
required bytes topic = 2;
|
|
858
690
|
}`;
|
|
859
691
|
|
|
860
|
-
class PsMessage extends codecFormatInterface {
|
|
861
|
-
get keys() {
|
|
862
|
-
return ['data', 'topic']
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
constructor(buffer) {
|
|
866
|
-
const name = 'peernet-ps';
|
|
867
|
-
super(buffer, protons__default["default"](proto$4).PsMessage, {name});
|
|
868
|
-
}
|
|
692
|
+
class PsMessage extends codecFormatInterface {
|
|
693
|
+
get keys() {
|
|
694
|
+
return ['data', 'topic']
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
constructor(buffer) {
|
|
698
|
+
const name = 'peernet-ps';
|
|
699
|
+
super(buffer, protons__default["default"](proto$4).PsMessage, {name});
|
|
700
|
+
}
|
|
869
701
|
}
|
|
870
702
|
|
|
871
703
|
var proto$3 = `
|
|
@@ -875,15 +707,15 @@ message PeernetPeerMessage {
|
|
|
875
707
|
}
|
|
876
708
|
`;
|
|
877
709
|
|
|
878
|
-
class PeerMessage extends codecFormatInterface {
|
|
879
|
-
get keys() {
|
|
880
|
-
return ['id']
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
constructor(data) {
|
|
884
|
-
const name = 'peernet-peer';
|
|
885
|
-
super(data, protons__default["default"](proto$3).PeernetPeerMessage, {name});
|
|
886
|
-
}
|
|
710
|
+
class PeerMessage extends codecFormatInterface {
|
|
711
|
+
get keys() {
|
|
712
|
+
return ['id']
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
constructor(data) {
|
|
716
|
+
const name = 'peernet-peer';
|
|
717
|
+
super(data, protons__default["default"](proto$3).PeernetPeerMessage, {name});
|
|
718
|
+
}
|
|
887
719
|
}
|
|
888
720
|
|
|
889
721
|
var proto$2 = `
|
|
@@ -893,15 +725,15 @@ message PeernetPeerMessageResponse {
|
|
|
893
725
|
}
|
|
894
726
|
`;
|
|
895
727
|
|
|
896
|
-
class PeerMessageResponse extends codecFormatInterface {
|
|
897
|
-
get keys() {
|
|
898
|
-
return ['id']
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
constructor(data) {
|
|
902
|
-
const name = 'peernet-peer-response';
|
|
903
|
-
super(data, protons__default["default"](proto$2).PeernetPeerMessageResponse, {name});
|
|
904
|
-
}
|
|
728
|
+
class PeerMessageResponse extends codecFormatInterface {
|
|
729
|
+
get keys() {
|
|
730
|
+
return ['id']
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
constructor(data) {
|
|
734
|
+
const name = 'peernet-peer-response';
|
|
735
|
+
super(data, protons__default["default"](proto$2).PeernetPeerMessageResponse, {name});
|
|
736
|
+
}
|
|
905
737
|
}
|
|
906
738
|
|
|
907
739
|
var proto$1 = `
|
|
@@ -912,1058 +744,1333 @@ message PeernetDataMessageResponse {
|
|
|
912
744
|
}
|
|
913
745
|
`;
|
|
914
746
|
|
|
915
|
-
class DataMessageResponse extends codecFormatInterface {
|
|
916
|
-
get keys() {
|
|
917
|
-
return ['hash', 'data']
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
constructor(data) {
|
|
921
|
-
const name = 'peernet-data-response';
|
|
922
|
-
super(data, protons__default["default"](proto$1).PeernetDataMessageResponse, {name});
|
|
923
|
-
}
|
|
747
|
+
class DataMessageResponse extends codecFormatInterface {
|
|
748
|
+
get keys() {
|
|
749
|
+
return ['hash', 'data']
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
constructor(data) {
|
|
753
|
+
const name = 'peernet-data-response';
|
|
754
|
+
super(data, protons__default["default"](proto$1).PeernetDataMessageResponse, {name});
|
|
755
|
+
}
|
|
924
756
|
}
|
|
925
757
|
|
|
926
|
-
var proto = `
|
|
927
|
-
message ChatMessage {
|
|
928
|
-
required string value = 1;
|
|
929
|
-
required string author = 2;
|
|
930
|
-
required uint64 timestamp = 3;
|
|
931
|
-
repeated string files = 4;
|
|
758
|
+
var proto = `
|
|
759
|
+
message ChatMessage {
|
|
760
|
+
required string value = 1;
|
|
761
|
+
required string author = 2;
|
|
762
|
+
required uint64 timestamp = 3;
|
|
763
|
+
repeated string files = 4;
|
|
932
764
|
}`;
|
|
933
765
|
|
|
934
|
-
class ChatMessage extends codecFormatInterface {
|
|
935
|
-
get keys() {
|
|
936
|
-
return ['author', 'value', 'timestamp', 'files']
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
constructor(buffer) {
|
|
940
|
-
const name = 'chat-message';
|
|
941
|
-
super(buffer, protons__default["default"](proto).ChatMessage, {name});
|
|
942
|
-
}
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
const debug = (log) => {
|
|
946
|
-
if (globalThis.DEBUG || globalThis.debug) console.log(`%c ${log}`, 'color: #0080ff;');
|
|
947
|
-
};
|
|
948
|
-
|
|
949
|
-
const protoFor = (data) => {
|
|
950
|
-
if (!Buffer.isBuffer(data)) data = Buffer.from(data);
|
|
951
|
-
const codec$1 = new codec.PeernetCodec(data);
|
|
952
|
-
if (!codec$1.name) throw new Error('proto not found')
|
|
953
|
-
const Proto = globalThis.peernet.protos[codec$1.name];
|
|
954
|
-
if (!Proto) throw (new Error(`No proto defined for ${codec$1.name}`))
|
|
955
|
-
return new Proto(data)
|
|
956
|
-
};
|
|
957
|
-
|
|
958
|
-
/**
|
|
959
|
-
* wether or not a peernet daemon is active
|
|
960
|
-
* @return {Boolean}
|
|
961
|
-
*/
|
|
962
|
-
const hasDaemon = async () => {
|
|
963
|
-
try {
|
|
964
|
-
let response = await fetch__default["default"]('http://127.0.0.1:1000/api/version');
|
|
965
|
-
response = await response.json();
|
|
966
|
-
return Boolean(response.client === '@peernet/api/http')
|
|
967
|
-
} catch (e) {
|
|
968
|
-
return false
|
|
969
|
-
}
|
|
970
|
-
};
|
|
971
|
-
|
|
972
|
-
const https = () => {
|
|
973
|
-
if (!globalThis.location) return false;
|
|
974
|
-
return Boolean(globalThis.location.protocol === 'https:')
|
|
975
|
-
};
|
|
976
|
-
|
|
977
|
-
/**
|
|
978
|
-
* Get current environment
|
|
979
|
-
* @return {String} current environment [node, electron, browser]
|
|
980
|
-
*/
|
|
981
|
-
const environment = () => {
|
|
982
|
-
const _navigator = globalThis.navigator;
|
|
983
|
-
if (!_navigator) {
|
|
984
|
-
return 'node'
|
|
985
|
-
} else if (_navigator && /electron/i.test(_navigator.userAgent)) {
|
|
986
|
-
return 'electron'
|
|
987
|
-
} else {
|
|
988
|
-
return 'browser'
|
|
989
|
-
}
|
|
990
|
-
};
|
|
991
|
-
|
|
992
|
-
/**
|
|
993
|
-
* * Get current environment
|
|
994
|
-
* @return {Object} result
|
|
995
|
-
* @property {Boolean} reult.daemon whether or not daemon is running
|
|
996
|
-
* @property {Boolean} reult.environment Current environment
|
|
997
|
-
*/
|
|
998
|
-
const target = async () => {
|
|
999
|
-
let daemon = false;
|
|
1000
|
-
const env = await environment();
|
|
1001
|
-
if (!https()) daemon = await hasDaemon();
|
|
1002
|
-
|
|
1003
|
-
return {daemon, environment: env}
|
|
1004
|
-
};
|
|
1005
|
-
|
|
1006
|
-
class PeerDiscovery {
|
|
1007
|
-
constructor(id) {
|
|
1008
|
-
this.id = id;
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
_getPeerId(id) {
|
|
1012
|
-
if (!peernet.peerMap || peernet.peerMap && peernet.peerMap.size === 0) return false
|
|
1013
|
-
|
|
1014
|
-
for (const entry of [...peernet.peerMap.entries()]) {
|
|
1015
|
-
for (const _id of entry[1]) {
|
|
1016
|
-
if (_id === id) return entry[0]
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
|
|
1021
|
-
async discover(peer) {
|
|
1022
|
-
let id = this._getPeerId(peer.id);
|
|
1023
|
-
if (id) return id
|
|
1024
|
-
const data = new peernet.protos['peernet-peer']({id: this.id});
|
|
1025
|
-
const node = await peernet.prepareMessage(peer.id, data.encoded);
|
|
1026
|
-
|
|
1027
|
-
let response = await peer.request(node.encoded);
|
|
1028
|
-
response = protoFor(response);
|
|
1029
|
-
response = new peernet.protos['peernet-peer-response'](response.decoded.data);
|
|
1030
|
-
|
|
1031
|
-
id = response.decoded.id;
|
|
1032
|
-
if (id === this.id) return;
|
|
1033
|
-
|
|
1034
|
-
if (!peernet.peerMap.has(id)) peernet.peerMap.set(id, [peer.id]);
|
|
1035
|
-
else {
|
|
1036
|
-
const connections = peernet.peerMap.get(id);
|
|
1037
|
-
if (connections.indexOf(peer.id) === -1) {
|
|
1038
|
-
connections.push(peer.id);
|
|
1039
|
-
peernet.peerMap.set(peer.id, connections);
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
return id
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
async discoverHandler(message, peer) {
|
|
1046
|
-
const {id, proto} = message;
|
|
1047
|
-
// if (typeof message.data === 'string') message.data = Buffer.from(message.data)
|
|
1048
|
-
if (proto.name === 'peernet-peer') {
|
|
1049
|
-
const from = proto.decoded.id;
|
|
1050
|
-
if (from === this.id) return;
|
|
1051
|
-
|
|
1052
|
-
if (!peernet.peerMap.has(from)) peernet.peerMap.set(from, [peer.id]);
|
|
1053
|
-
else {
|
|
1054
|
-
const connections = peernet.peerMap.get(from);
|
|
1055
|
-
if (connections.indexOf(peer.id) === -1) {
|
|
1056
|
-
connections.push(peer.id);
|
|
1057
|
-
peernet.peerMap.set(from, connections);
|
|
1058
|
-
}
|
|
1059
|
-
}
|
|
1060
|
-
const data = new peernet.protos['peernet-peer-response']({id: this.id});
|
|
1061
|
-
const node = await peernet.prepareMessage(from, data.encoded);
|
|
1062
|
-
|
|
1063
|
-
peer.write(Buffer.from(JSON.stringify({id, data: node.encoded})));
|
|
1064
|
-
} else if (proto.name === 'peernet-peer-response') {
|
|
1065
|
-
const from = proto.decoded.id;
|
|
1066
|
-
if (from === this.id) return;
|
|
1067
|
-
|
|
1068
|
-
if (!peernet.peerMap.has(from)) peernet.peerMap.set(from, [peer.id]);
|
|
1069
|
-
else {
|
|
1070
|
-
const connections = peernet.peerMap.get(from);
|
|
1071
|
-
if (connections.indexOf(peer.id) === -1) {
|
|
1072
|
-
connections.push(peer.id);
|
|
1073
|
-
peernet.peerMap.set(from, connections);
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
766
|
+
class ChatMessage extends codecFormatInterface {
|
|
767
|
+
get keys() {
|
|
768
|
+
return ['author', 'value', 'timestamp', 'files']
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
constructor(buffer) {
|
|
772
|
+
const name = 'chat-message';
|
|
773
|
+
super(buffer, protons__default["default"](proto).ChatMessage, {name});
|
|
774
|
+
}
|
|
1078
775
|
}
|
|
1079
776
|
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
const
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
777
|
+
const debug = (log) => {
|
|
778
|
+
if (globalThis.DEBUG || globalThis.debug) console.log(`%c ${log}`, 'color: #0080ff;');
|
|
779
|
+
};
|
|
780
|
+
|
|
781
|
+
const protoFor = (data) => {
|
|
782
|
+
if (!Buffer.isBuffer(data)) data = Buffer.from(data);
|
|
783
|
+
const codec$1 = new codec.PeernetCodec(data);
|
|
784
|
+
if (!codec$1.name) throw new Error('proto not found')
|
|
785
|
+
const Proto = globalThis.peernet.protos[codec$1.name];
|
|
786
|
+
if (!Proto) throw (new Error(`No proto defined for ${codec$1.name}`))
|
|
787
|
+
return new Proto(data)
|
|
788
|
+
};
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* wether or not a peernet daemon is active
|
|
792
|
+
* @return {Boolean}
|
|
793
|
+
*/
|
|
794
|
+
const hasDaemon = async () => {
|
|
795
|
+
try {
|
|
796
|
+
let response = await fetch__default["default"]('http://127.0.0.1:1000/api/version');
|
|
797
|
+
response = await response.json();
|
|
798
|
+
return Boolean(response.client === '@peernet/api/http')
|
|
799
|
+
} catch (e) {
|
|
800
|
+
return false
|
|
801
|
+
}
|
|
802
|
+
};
|
|
803
|
+
|
|
804
|
+
const https = () => {
|
|
805
|
+
if (!globalThis.location) return false;
|
|
806
|
+
return Boolean(globalThis.location.protocol === 'https:')
|
|
807
|
+
};
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* Get current environment
|
|
811
|
+
* @return {String} current environment [node, electron, browser]
|
|
812
|
+
*/
|
|
813
|
+
const environment = () => {
|
|
814
|
+
const _navigator = globalThis.navigator;
|
|
815
|
+
if (!_navigator) {
|
|
816
|
+
return 'node'
|
|
817
|
+
} else if (_navigator && /electron/i.test(_navigator.userAgent)) {
|
|
818
|
+
return 'electron'
|
|
819
|
+
} else {
|
|
820
|
+
return 'browser'
|
|
821
|
+
}
|
|
822
|
+
};
|
|
823
|
+
|
|
824
|
+
/**
|
|
825
|
+
* * Get current environment
|
|
826
|
+
* @return {Object} result
|
|
827
|
+
* @property {Boolean} reult.daemon whether or not daemon is running
|
|
828
|
+
* @property {Boolean} reult.environment Current environment
|
|
829
|
+
*/
|
|
830
|
+
const target = async () => {
|
|
831
|
+
let daemon = false;
|
|
832
|
+
const env = await environment();
|
|
833
|
+
if (!https()) daemon = await hasDaemon();
|
|
834
|
+
|
|
835
|
+
return {daemon, environment: env}
|
|
1125
836
|
};
|
|
1126
837
|
|
|
1127
|
-
class
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
838
|
+
class PeerDiscovery {
|
|
839
|
+
constructor(id) {
|
|
840
|
+
this.id = id;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
_getPeerId(id) {
|
|
844
|
+
if (!peernet.peerMap || peernet.peerMap && peernet.peerMap.size === 0) return false
|
|
845
|
+
|
|
846
|
+
for (const entry of [...peernet.peerMap.entries()]) {
|
|
847
|
+
for (const _id of entry[1]) {
|
|
848
|
+
if (_id === id) return entry[0]
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
async discover(peer) {
|
|
854
|
+
let id = this._getPeerId(peer.id);
|
|
855
|
+
if (id) return id
|
|
856
|
+
const data = new peernet.protos['peernet-peer']({id: this.id});
|
|
857
|
+
const node = await peernet.prepareMessage(peer.id, data.encoded);
|
|
858
|
+
|
|
859
|
+
let response = await peer.request(node.encoded);
|
|
860
|
+
response = protoFor(response);
|
|
861
|
+
response = new peernet.protos['peernet-peer-response'](response.decoded.data);
|
|
862
|
+
|
|
863
|
+
id = response.decoded.id;
|
|
864
|
+
if (id === this.id) return;
|
|
865
|
+
|
|
866
|
+
if (!peernet.peerMap.has(id)) peernet.peerMap.set(id, [peer.id]);
|
|
867
|
+
else {
|
|
868
|
+
const connections = peernet.peerMap.get(id);
|
|
869
|
+
if (connections.indexOf(peer.id) === -1) {
|
|
870
|
+
connections.push(peer.id);
|
|
871
|
+
peernet.peerMap.set(peer.id, connections);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
return id
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
async discoverHandler(message, peer) {
|
|
878
|
+
const {id, proto} = message;
|
|
879
|
+
// if (typeof message.data === 'string') message.data = Buffer.from(message.data)
|
|
880
|
+
if (proto.name === 'peernet-peer') {
|
|
881
|
+
const from = proto.decoded.id;
|
|
882
|
+
if (from === this.id) return;
|
|
883
|
+
|
|
884
|
+
if (!peernet.peerMap.has(from)) peernet.peerMap.set(from, [peer.id]);
|
|
885
|
+
else {
|
|
886
|
+
const connections = peernet.peerMap.get(from);
|
|
887
|
+
if (connections.indexOf(peer.id) === -1) {
|
|
888
|
+
connections.push(peer.id);
|
|
889
|
+
peernet.peerMap.set(from, connections);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
const data = new peernet.protos['peernet-peer-response']({id: this.id});
|
|
893
|
+
const node = await peernet.prepareMessage(from, data.encoded);
|
|
894
|
+
|
|
895
|
+
peer.write(Buffer.from(JSON.stringify({id, data: node.encoded})));
|
|
896
|
+
} else if (proto.name === 'peernet-peer-response') {
|
|
897
|
+
const from = proto.decoded.id;
|
|
898
|
+
if (from === this.id) return;
|
|
899
|
+
|
|
900
|
+
if (!peernet.peerMap.has(from)) peernet.peerMap.set(from, [peer.id]);
|
|
901
|
+
else {
|
|
902
|
+
const connections = peernet.peerMap.get(from);
|
|
903
|
+
if (connections.indexOf(peer.id) === -1) {
|
|
904
|
+
connections.push(peer.id);
|
|
905
|
+
peernet.peerMap.set(from, connections);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
}
|
|
1198
910
|
}
|
|
1199
911
|
|
|
1200
912
|
/**
|
|
1201
|
-
*
|
|
1202
|
-
* @
|
|
913
|
+
* Keep history of fetched address and ptr
|
|
914
|
+
* @property {Object} address
|
|
915
|
+
* @property {Object} ptr
|
|
1203
916
|
*/
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
917
|
+
const lastFetched = {
|
|
918
|
+
address: {
|
|
919
|
+
value: undefined,
|
|
920
|
+
timestamp: 0,
|
|
921
|
+
},
|
|
922
|
+
ptr: {
|
|
923
|
+
value: undefined,
|
|
924
|
+
timestamp: 0,
|
|
925
|
+
},
|
|
926
|
+
};
|
|
927
|
+
|
|
928
|
+
const getAddress = async () => {
|
|
929
|
+
const {address} = lastFetched;
|
|
930
|
+
const now = Math.round(new Date().getTime() / 1000);
|
|
931
|
+
if (now - address.timestamp > 1200000) {
|
|
932
|
+
address.value = await fetch__default["default"]('https://icanhazip.com/');
|
|
933
|
+
address.value = await address.value.text();
|
|
934
|
+
address.timestamp = Math.round(new Date().getTime() / 1000);
|
|
935
|
+
lastFetched.address = address;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
return address.value
|
|
939
|
+
};
|
|
940
|
+
|
|
941
|
+
const degreesToRadians = (degrees) => {
|
|
942
|
+
return degrees * Math.PI / 180;
|
|
943
|
+
};
|
|
944
|
+
|
|
945
|
+
const distanceInKmBetweenEarthCoordinates = (lat1, lon1, lat2, lon2) => {
|
|
946
|
+
const earthRadiusKm = 6371;
|
|
947
|
+
|
|
948
|
+
const dLat = degreesToRadians(lat2-lat1);
|
|
949
|
+
const dLon = degreesToRadians(lon2-lon1);
|
|
950
|
+
|
|
951
|
+
lat1 = degreesToRadians(lat1);
|
|
952
|
+
lat2 = degreesToRadians(lat2);
|
|
953
|
+
const a = Math.sin(dLat/2) * Math.sin(dLat/2) +
|
|
954
|
+
Math.sin(dLon/2) * Math.sin(dLon/2) * Math.cos(lat1) * Math.cos(lat2);
|
|
955
|
+
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
|
|
956
|
+
return earthRadiusKm * c;
|
|
957
|
+
};
|
|
958
|
+
|
|
959
|
+
class DhtEarth {
|
|
960
|
+
/**
|
|
961
|
+
*
|
|
962
|
+
*/
|
|
963
|
+
constructor() {
|
|
964
|
+
this.providerMap = new Map();
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
/**
|
|
968
|
+
* @param {Object} address
|
|
969
|
+
* @return {Object} {latitude: lat, longitude: lon}
|
|
970
|
+
*/
|
|
971
|
+
async getCoordinates(address) {
|
|
972
|
+
// const {address} = parseAddress(provider)
|
|
973
|
+
const request = `https://whereis.leofcoin.org/?ip=${address}`;
|
|
974
|
+
let response = await fetch__default["default"](request);
|
|
975
|
+
response = await response.json();
|
|
976
|
+
const {lat, lon} = response;
|
|
977
|
+
return {latitude: lat, longitude: lon}
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
/**
|
|
981
|
+
* @param {Object} peer
|
|
982
|
+
* @param {Object} provider
|
|
983
|
+
* @return {Object} {provider, distance}
|
|
984
|
+
*/
|
|
985
|
+
async getDistance(peer, provider) {
|
|
986
|
+
const {latitude, longitude} = await this.getCoordinates(provider.address);
|
|
987
|
+
return {provider, distance: distanceInKmBetweenEarthCoordinates(peer.latitude, peer.longitude, latitude, longitude)}
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
/**
|
|
991
|
+
* @param {Array} providers
|
|
992
|
+
* @return {Object} closestPeer
|
|
993
|
+
*/
|
|
994
|
+
async closestPeer(providers) {
|
|
995
|
+
let all = [];
|
|
996
|
+
const address = await getAddress();
|
|
997
|
+
const peerLoc = await this.getCoordinates(address);
|
|
998
|
+
|
|
999
|
+
for (const provider of providers) {
|
|
1000
|
+
if (provider.address === '127.0.0.1') all.push({provider, distance: 0});
|
|
1001
|
+
else all.push(this.getDistance(peerLoc, provider));
|
|
1237
1002
|
}
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
/**
|
|
1245
|
-
*
|
|
1246
|
-
*
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
* @param {String} to - peer id
|
|
1266
|
-
* @param {String|PeernetMessage} data - data encoded message string
|
|
1267
|
-
* or the messageNode itself
|
|
1268
|
-
*/
|
|
1269
|
-
async prepareMessage(from, to, data) {
|
|
1270
|
-
if (!Buffer.isBuffer(from)) from = new Buffer.from(from);
|
|
1271
|
-
if (!Buffer.isBuffer(to)) to = new Buffer.from(to);
|
|
1272
|
-
if (data.encoded) data = data.encoded;
|
|
1273
|
-
|
|
1274
|
-
const message = {
|
|
1275
|
-
from,
|
|
1276
|
-
to,
|
|
1277
|
-
data,
|
|
1278
|
-
};
|
|
1279
|
-
const signature = await this.hashAndSignMessage(message);
|
|
1280
|
-
const node = new peernetMessage({
|
|
1281
|
-
...message,
|
|
1282
|
-
signature,
|
|
1283
|
-
});
|
|
1284
|
-
|
|
1285
|
-
return node
|
|
1286
|
-
}
|
|
1003
|
+
|
|
1004
|
+
all = await Promise.all(all);
|
|
1005
|
+
all = all.sort((previous, current) => previous.distance - current.distance);
|
|
1006
|
+
return all[0].provider;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
/**
|
|
1010
|
+
* @param {String} hash
|
|
1011
|
+
* @return {Array} providers
|
|
1012
|
+
*/
|
|
1013
|
+
providersFor(hash) {
|
|
1014
|
+
return this.providerMap.get(hash);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
/**
|
|
1018
|
+
* @param {String} address
|
|
1019
|
+
* @param {String} hash
|
|
1020
|
+
* @return {Array} providers
|
|
1021
|
+
*/
|
|
1022
|
+
async addProvider(address, hash) {
|
|
1023
|
+
let providers = [];
|
|
1024
|
+
if (this.providerMap.has(hash)) providers = this.providerMap.get(hash);
|
|
1025
|
+
|
|
1026
|
+
providers = new Set([...providers, address]);
|
|
1027
|
+
this.providerMap.set(hash, providers);
|
|
1028
|
+
return providers;
|
|
1029
|
+
}
|
|
1287
1030
|
}
|
|
1288
1031
|
|
|
1289
|
-
|
|
1290
|
-
|
|
1032
|
+
var testnets = {
|
|
1033
|
+
'leofcoin:olivia': {
|
|
1034
|
+
messagePrefix: '\u0019Leofcoin Signed Message:',
|
|
1035
|
+
pubKeyHash: 0x73, // o
|
|
1036
|
+
scriptHash: 0x76, // p
|
|
1037
|
+
multiTxHash: 0x8b4125, // omtx
|
|
1038
|
+
payments: {
|
|
1039
|
+
version: 0,
|
|
1040
|
+
unspent: 0x1fa443d7 // ounsp
|
|
1041
|
+
},
|
|
1042
|
+
wif: 0x7D, // s
|
|
1043
|
+
multiCodec: 0x7c4,
|
|
1044
|
+
bip32: { public: 0x13BBF2D5, private: 0x13BBCBC5 }
|
|
1045
|
+
},
|
|
1046
|
+
'bitcoin:testnet': {
|
|
1047
|
+
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
|
1048
|
+
bech32: 'tb',
|
|
1049
|
+
pubKeyHash: 0x6f,
|
|
1050
|
+
scriptHash: 0xc4,
|
|
1051
|
+
wif: 0xef,
|
|
1052
|
+
bip32: {
|
|
1053
|
+
public: 0x043587cf,
|
|
1054
|
+
private: 0x04358394
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1291
1058
|
};
|
|
1292
1059
|
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1060
|
+
// https://en.bitcoin.it/wiki/List_of_address_prefixes
|
|
1061
|
+
/**
|
|
1062
|
+
* Main network
|
|
1063
|
+
* @return {messagePrefix, pubKeyHash, scriptHash, wif, bip32}
|
|
1064
|
+
*/
|
|
1065
|
+
const leofcoin = {
|
|
1066
|
+
messagePrefix: '\u0019Leofcoin Signed Message:',
|
|
1067
|
+
pubKeyHash: 0x30, // L
|
|
1068
|
+
scriptHash: 0x37, // P
|
|
1069
|
+
multiTxHash: 0x3adeed, // Lmtx
|
|
1070
|
+
payments: {
|
|
1071
|
+
version: 0,
|
|
1072
|
+
unspent: 0x0d6e0327 // Lunsp
|
|
1073
|
+
},
|
|
1074
|
+
coin_type: 640,
|
|
1075
|
+
wif: 0x3F, // S
|
|
1076
|
+
multiCodec: 0x3c4,
|
|
1077
|
+
bip32: { public: 0x13BBF2D4, private: 0x13BBCBC4 },
|
|
1078
|
+
testnet: testnets['leofcoin:olivia']
|
|
1079
|
+
};
|
|
1080
|
+
|
|
1081
|
+
const bitcoin = {
|
|
1082
|
+
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
|
1083
|
+
bech32: 'bc',
|
|
1084
|
+
pubKeyHash: 0x00,
|
|
1085
|
+
scriptHash: 0x05,
|
|
1086
|
+
wif: 0x80,
|
|
1087
|
+
coin_type: 0,
|
|
1088
|
+
bip32: {
|
|
1089
|
+
public: 0x0488b21e, private: 0x0488ade4
|
|
1090
|
+
},
|
|
1091
|
+
testnet: testnets['bitcoin:testnet']
|
|
1092
|
+
};
|
|
1093
|
+
|
|
1094
|
+
const litecoin = {
|
|
1095
|
+
messagePrefix: '\x19Litecoin Signed Message:\n',
|
|
1096
|
+
pubKeyHash: 0x30,
|
|
1097
|
+
scriptHash: 0x32,
|
|
1098
|
+
wif: 0xb0,
|
|
1099
|
+
bip32: {
|
|
1100
|
+
public: 0x019da462,
|
|
1101
|
+
private: 0x019d9cfe
|
|
1102
|
+
}
|
|
1103
|
+
};
|
|
1104
|
+
|
|
1105
|
+
const ethereum = {
|
|
1106
|
+
messagePrefix: '\x19Ethereum Signed Message:\n',
|
|
1107
|
+
pubKeyHash: 0x30,
|
|
1108
|
+
scriptHash: 0x32,
|
|
1109
|
+
bip32: {
|
|
1110
|
+
private: 0x0488ADE4, public: 0x0488B21E
|
|
1111
|
+
},
|
|
1112
|
+
coin_type: 60,
|
|
1113
|
+
wif: 0x45,//E
|
|
1114
|
+
multiCodec: 0x3c5
|
|
1115
|
+
};
|
|
1116
|
+
|
|
1117
|
+
/**
|
|
1118
|
+
* Our & supported networks
|
|
1119
|
+
* @return {leofcoin, olivia}
|
|
1120
|
+
*/
|
|
1121
|
+
var networks = {
|
|
1122
|
+
leofcoin,
|
|
1123
|
+
bitcoin,
|
|
1124
|
+
litecoin,
|
|
1125
|
+
ethereum
|
|
1296
1126
|
};
|
|
1297
1127
|
|
|
1298
|
-
const
|
|
1299
|
-
|
|
1128
|
+
const fromNetworkString = network => {
|
|
1129
|
+
const parts = network.split(':');
|
|
1130
|
+
network = networks[parts[0]];
|
|
1131
|
+
if (parts[1]) {
|
|
1132
|
+
if (network[parts[1]]) network = network[parts[1]];
|
|
1133
|
+
|
|
1134
|
+
network.coin_type = 1;
|
|
1135
|
+
}
|
|
1136
|
+
return network;
|
|
1300
1137
|
};
|
|
1301
1138
|
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1139
|
+
const { encode: encode$1, decode: decode$1 } = bs58check__default["default"];
|
|
1140
|
+
class HDWallet {
|
|
1141
|
+
|
|
1142
|
+
get chainCodeBuffer() {
|
|
1143
|
+
return this.ifNotLocked(() => this.hdnode.chainCode)
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
get chainCode() {
|
|
1147
|
+
return this.ifNotLocked(() => this.chainCodeBuffer.toString('hex'))
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
get privateKeyBuffer() {
|
|
1151
|
+
return this.ifNotLocked(() => this.hdnode.privateKey)
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
get privateKey() {
|
|
1155
|
+
return this.ifNotLocked(() => this.privateKeyBuffer.toString('hex'))
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
get publicKeyBuffer() {
|
|
1159
|
+
return this.ifNotLocked(() => this.hdnode.publicKey)
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
get publicKey() {
|
|
1163
|
+
return this.ifNotLocked(() => this.publicKeyBuffer.toString('hex'))
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
get address() {
|
|
1167
|
+
// override testnet coin_type
|
|
1168
|
+
let coin_type = this.hdnode.network.coin_type;
|
|
1169
|
+
if (coin_type === 1) {
|
|
1170
|
+
if (this.networkName?.split(':')[0] === 'ethereum') coin_type = 60;
|
|
1171
|
+
if (this.networkName?.split(':')[0] === 'leofcoin') coin_type = 640;
|
|
1172
|
+
}
|
|
1173
|
+
if (coin_type === 60 || coin_type === 640) {
|
|
1174
|
+
let buffer = ecc__default["default"].pointFromScalar(this.hdnode.__D, false);
|
|
1175
|
+
buffer = Buffer.from(secp256k1.publicKeyConvert(buffer, false)).slice(1);
|
|
1176
|
+
let hash = createKeccakHash__default["default"]('keccak256').update(buffer).digest();
|
|
1177
|
+
return hash.slice(-20).toString('hex')
|
|
1178
|
+
}
|
|
1179
|
+
return encode$1(this.neutered.publicKeyBuffer)
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
get accountAddress() {
|
|
1183
|
+
return this.ifNotLocked(() => encode$1(this.hdnode.publicKeyBuffer))
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
get isTestnet() {
|
|
1187
|
+
if (typeof network === 'string')
|
|
1188
|
+
this.hdnode.network = fromNetworkString(network);
|
|
1189
|
+
|
|
1190
|
+
return Boolean(this.hdnode.network.coin_type === 1)
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
constructor(network, hdnode) {
|
|
1194
|
+
if (typeof network === 'string') {
|
|
1195
|
+
this.networkName = network;
|
|
1196
|
+
this.network = fromNetworkString(network);
|
|
1197
|
+
} else if (typeof network === 'object')
|
|
1198
|
+
this.network = network;
|
|
1199
|
+
|
|
1200
|
+
if (hdnode) this.defineHDNode(hdnode);
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
ifNotLocked(fn, params) {
|
|
1204
|
+
if (!this.locked) return fn(params);
|
|
1205
|
+
return null
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
defineHDNode(value) {
|
|
1209
|
+
Object.defineProperty(this, 'hdnode', {
|
|
1210
|
+
configurable: false,
|
|
1211
|
+
writable: false,
|
|
1212
|
+
value: value
|
|
1213
|
+
});
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
validateNetwork(network) {
|
|
1217
|
+
if (!network && !this.network) return console.error(`expected network to be defined`);
|
|
1218
|
+
if (!network && this.network) network = this.network;
|
|
1219
|
+
if (typeof network === 'string') network = fromNetworkString(network);
|
|
1220
|
+
if (typeof network !== 'object') return console.error('network not found');
|
|
1221
|
+
return network;
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
async generate(network) {
|
|
1225
|
+
network = this.validateNetwork(network);
|
|
1226
|
+
const mnemonic = bip39.generateMnemonic();
|
|
1227
|
+
const seed = await bip39.mnemonicToSeed(mnemonic);
|
|
1228
|
+
this.defineHDNode(bip32__namespace.fromSeed(seed, network));
|
|
1229
|
+
return mnemonic; // userpw
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
/**
|
|
1233
|
+
* recover using mnemonic (recovery word list)
|
|
1234
|
+
*/
|
|
1235
|
+
async recover(mnemonic, network) {
|
|
1236
|
+
network = this.validateNetwork(network);
|
|
1237
|
+
const seed = await bip39.mnemonicToSeed(mnemonic);
|
|
1238
|
+
this.defineHDNode(bip32__namespace.fromSeed(seed, network));
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
load(base58, network) {
|
|
1242
|
+
network = this.validateNetwork(network);
|
|
1243
|
+
this.defineHDNode(bip32__namespace.fromBase58(base58, network));
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
save() {
|
|
1247
|
+
return this.hdnode.toBase58();
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
fromAddress(address, chainCode, network) {
|
|
1251
|
+
network = this.validateNetwork(network);
|
|
1252
|
+
// if (network.coin_type === 60) {
|
|
1253
|
+
// address = Buffer.from(address, 'hex')
|
|
1254
|
+
// } else {
|
|
1255
|
+
address = decode$1(address);
|
|
1256
|
+
// }
|
|
1257
|
+
|
|
1258
|
+
if (!chainCode || chainCode && !Buffer.isBuffer(chainCode)) chainCode = address.slice(1);
|
|
1259
|
+
this.defineHDNode(bip32__namespace.fromPublicKey(address, chainCode, network));
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
fromPublicKey(hex, chainCode, network) {
|
|
1263
|
+
network = this.validateNetwork(network);
|
|
1264
|
+
if (!Buffer.isBuffer(hex)) hex = Buffer.from(hex, 'hex');
|
|
1265
|
+
if (!chainCode || chainCode && !Buffer.isBuffer(chainCode)) chainCode = hex.slice(1);
|
|
1266
|
+
this.defineHDNode(bip32__namespace.fromPublicKey(hex, chainCode, network));
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1390
1269
|
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1270
|
+
const { encode, decode } = bs58check__namespace;
|
|
1271
|
+
|
|
1272
|
+
// TODO: multihash addresses
|
|
1273
|
+
class HDAccount {
|
|
1274
|
+
/**
|
|
1275
|
+
* @param {number} depth - acount depth
|
|
1276
|
+
*/
|
|
1277
|
+
constructor(node, depth = 0) {
|
|
1278
|
+
this.node = node;
|
|
1279
|
+
this.depth = depth;
|
|
1280
|
+
this._prefix = `m/44'/${node.network.coin_type}'/${depth}'/`;
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
/**
|
|
1284
|
+
* @param {number} index - address index
|
|
1285
|
+
*/
|
|
1286
|
+
internal(index = 0) {
|
|
1287
|
+
return this.node.derivePath(`${this._prefix}1/${index}`)
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
/**
|
|
1291
|
+
* @param {number} index - address index
|
|
1292
|
+
*/
|
|
1293
|
+
external(index = 0) {
|
|
1294
|
+
return this.node.derivePath(`${this._prefix}0/${index}`)
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
class MultiWallet extends HDWallet {
|
|
1299
|
+
constructor(network, hdnode) {
|
|
1300
|
+
const networkName = network;
|
|
1301
|
+
super(network, hdnode);
|
|
1302
|
+
if (typeof networkName === 'string') this.networkName = networkName;
|
|
1303
|
+
this.multiCodec = this.network.multiCodec;
|
|
1304
|
+
this.version = 0x00;
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
get id() {
|
|
1308
|
+
const buffer = Buffer.concat([
|
|
1309
|
+
Buffer.from(varint__default["default"].encode(this.multiCodec)),
|
|
1310
|
+
Buffer.from(this.account(0).node.neutered.publicKey, 'hex')
|
|
1311
|
+
]);
|
|
1312
|
+
return encode(buffer)
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
get multiWIF() {
|
|
1316
|
+
return this.ifNotLocked(() => this.encode())
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
get neutered() {
|
|
1320
|
+
const neutered = this.ifNotLocked(() => new MultiWallet(this.network, this.hdnode.neutered()));
|
|
1321
|
+
if (neutered) this._neutered = neutered;
|
|
1322
|
+
return this._neutered
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
fromId(id) {
|
|
1326
|
+
let buffer = decode(id);
|
|
1327
|
+
varint__default["default"].decode(buffer);
|
|
1328
|
+
buffer = buffer.slice(varint__default["default"].decode.bytes);
|
|
1329
|
+
this.fromPublicKey(buffer, null, this.network);
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
lock(key, multiWIF) {
|
|
1333
|
+
if (!multiWIF) multiWIF = this.multiWIF;
|
|
1334
|
+
this.encrypted = AES__default["default"].encrypt(multiWIF.toString('hex'), key).toString();
|
|
1335
|
+
this.locked = true;
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
unlock(key, encrypted) {
|
|
1339
|
+
if (!encrypted) encrypted = this.encrypted;
|
|
1340
|
+
this.import(AES__default["default"].decrypt(encrypted, key).toString(ENC__default["default"]));
|
|
1341
|
+
this.locked = false;
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
export() {
|
|
1345
|
+
return this.encode();
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
/**
|
|
1349
|
+
* encodes the multiWIF and loads wallet from bs58
|
|
1350
|
+
*
|
|
1351
|
+
* @param {multiWIF} multiWIF - note a multiWIF is not the same as a wif
|
|
1352
|
+
*/
|
|
1353
|
+
import(multiWIF) {
|
|
1354
|
+
const { bs58, version, multiCodec } = this.decode(multiWIF);
|
|
1355
|
+
this.network = Object.values(networks).reduce((p, c) => {
|
|
1356
|
+
if (c.multiCodec===multiCodec) return c
|
|
1357
|
+
else if (c.testnet && c.testnet.multiCodec === multiCodec) return c.testnet
|
|
1358
|
+
else return p
|
|
1359
|
+
}, networks['leofcoin']);
|
|
1360
|
+
this.load(bs58, this.network);
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
/**
|
|
1364
|
+
* @return base58Check encoded string
|
|
1365
|
+
*/
|
|
1366
|
+
encode() {
|
|
1367
|
+
const buffer = Buffer.concat([
|
|
1368
|
+
Buffer.from(varint__default["default"].encode(this.version)),
|
|
1369
|
+
Buffer.from(varint__default["default"].encode(this.multiCodec)),
|
|
1370
|
+
decode(this.save())
|
|
1371
|
+
]);
|
|
1372
|
+
return encode(buffer);
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
decode(bs58) {
|
|
1376
|
+
let buffer = decode(bs58);
|
|
1377
|
+
const version = varint__default["default"].decode(buffer);
|
|
1378
|
+
buffer = buffer.slice(varint__default["default"].decode.bytes);
|
|
1379
|
+
const multiCodec = varint__default["default"].decode(buffer);
|
|
1380
|
+
buffer = buffer.slice(varint__default["default"].decode.bytes);
|
|
1381
|
+
bs58 = encode(buffer);
|
|
1382
|
+
if (version !== this.version) throw TypeError('Invalid version');
|
|
1383
|
+
if (this.multiCodec !== multiCodec) throw TypeError('Invalid multiCodec');
|
|
1384
|
+
return { version, multiCodec, bs58 };
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
sign(hash) {
|
|
1388
|
+
return new MultiSignature__default["default"](this.version, this.network.multiCodec)
|
|
1389
|
+
.sign(hash, this.privateKeyBuffer);
|
|
1390
|
+
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
verify(multiSignature, hash) {
|
|
1394
|
+
return new MultiSignature__default["default"](this.version, this.network.multiCodec)
|
|
1395
|
+
.verify(multiSignature, hash, this.publicKeyBuffer)
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
/**
|
|
1399
|
+
* @param {number} account - account to return chain for
|
|
1400
|
+
* @return { internal(addressIndex), external(addressIndex) }
|
|
1401
|
+
*/
|
|
1402
|
+
account(index) {
|
|
1403
|
+
return new HDAccount(new MultiWallet(this.network, this.hdnode), index);
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
/**
|
|
1407
|
+
* m / purpose' / coin_type' / account' / change / aadress_index
|
|
1408
|
+
*
|
|
1409
|
+
* see https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
|
|
1410
|
+
*/
|
|
1411
|
+
derivePath(path) {
|
|
1412
|
+
return new MultiWallet(this.network, this.hdnode.derivePath(path))
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
derive(index) {
|
|
1416
|
+
return new MultiWallet(this.network, this.hdnode.derive(index));
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1415
1419
|
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1420
|
+
class MessageHandler {
|
|
1421
|
+
constructor(network) {
|
|
1422
|
+
this.network = network;
|
|
1423
|
+
}
|
|
1424
|
+
/**
|
|
1425
|
+
* hash and sign message
|
|
1426
|
+
*
|
|
1427
|
+
* @param {object} message
|
|
1428
|
+
* @param {Buffer} message.from peer id
|
|
1429
|
+
* @param {Buffer} message.to peer id
|
|
1430
|
+
* @param {string} message.data Peernet message
|
|
1431
|
+
* (PeernetMessage excluded) encoded as a string
|
|
1432
|
+
* @return signature
|
|
1433
|
+
*/
|
|
1434
|
+
async hashAndSignMessage(message) {
|
|
1435
|
+
const hasher = new hash(message, {name: 'peernet-message'});
|
|
1436
|
+
const identity = await walletStore.get('identity');
|
|
1437
|
+
|
|
1438
|
+
const wallet = new MultiWallet(this.network);
|
|
1439
|
+
wallet.import(identity.multiWIF);
|
|
1440
|
+
return wallet.sign(hasher.hash.slice(0, 32))
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
/**
|
|
1444
|
+
* @param {String} from - peer id
|
|
1445
|
+
* @param {String} to - peer id
|
|
1446
|
+
* @param {String|PeernetMessage} data - data encoded message string
|
|
1447
|
+
* or the messageNode itself
|
|
1448
|
+
*/
|
|
1449
|
+
async prepareMessage(from, to, data, id) {
|
|
1450
|
+
if (data.encoded) data = data.encoded;
|
|
1451
|
+
|
|
1452
|
+
const message = {
|
|
1453
|
+
from,
|
|
1454
|
+
to,
|
|
1455
|
+
data,
|
|
1456
|
+
};
|
|
1457
|
+
const signature = await this.hashAndSignMessage(message);
|
|
1458
|
+
const node = new peernetMessage({
|
|
1459
|
+
...message,
|
|
1460
|
+
signature,
|
|
1461
|
+
});
|
|
1462
|
+
|
|
1463
|
+
return node
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1438
1466
|
|
|
1439
|
-
|
|
1440
|
-
|
|
1467
|
+
const encapsulatedError = () => {
|
|
1468
|
+
return new Error('Nodes/Data should be send encapsulated by peernet-message')
|
|
1469
|
+
};
|
|
1441
1470
|
|
|
1442
|
-
|
|
1471
|
+
const dhtError = (proto) => {
|
|
1472
|
+
const text = `Received proto ${proto.name} expected peernet-dht-response`;
|
|
1473
|
+
return new Error(`Routing error: ${text}`)
|
|
1474
|
+
};
|
|
1443
1475
|
|
|
1444
|
-
|
|
1445
|
-
|
|
1476
|
+
const nothingFoundError = (hash) => {
|
|
1477
|
+
return new Error(`nothing found for ${hash}`)
|
|
1478
|
+
};
|
|
1446
1479
|
|
|
1480
|
+
globalThis.leofcoin = globalThis.leofcoin || {};
|
|
1481
|
+
globalThis.globalSub = globalThis.globalSub || new PubSub({verbose: true});
|
|
1482
|
+
|
|
1483
|
+
/**
|
|
1484
|
+
* @access public
|
|
1485
|
+
* @example
|
|
1486
|
+
* const peernet = new Peernet();
|
|
1487
|
+
*/
|
|
1488
|
+
class Peernet {
|
|
1489
|
+
/**
|
|
1490
|
+
* @access public
|
|
1491
|
+
* @param {Object} options
|
|
1492
|
+
* @param {String} options.network - desired network
|
|
1493
|
+
* @param {String} options.root - path to root directory
|
|
1494
|
+
* @param {String} options.storePrefix - prefix for datatores (lfc)
|
|
1495
|
+
*
|
|
1496
|
+
* @return {Promise} instance of Peernet
|
|
1497
|
+
*
|
|
1498
|
+
* @example
|
|
1499
|
+
* const peernet = new Peernet({network: 'leofcoin', root: '.leofcoin'});
|
|
1500
|
+
*/
|
|
1501
|
+
constructor(options = {}) {
|
|
1502
|
+
this._discovered = [];
|
|
1503
|
+
/**
|
|
1504
|
+
* @property {String} network - current network
|
|
1505
|
+
*/
|
|
1506
|
+
this.network = options.network || 'leofcoin';
|
|
1507
|
+
const parts = this.network.split(':');
|
|
1508
|
+
|
|
1509
|
+
if (!options.storePrefix) options.storePrefix = 'lfc';
|
|
1510
|
+
if (!options.port) options.port = 2000;
|
|
1511
|
+
if (!options.root) {
|
|
1512
|
+
if (parts[1]) options.root = `.${parts[0]}/peernet/${parts[1]}`;
|
|
1513
|
+
else options.root = `.${this.network}/peernet`;
|
|
1514
|
+
}
|
|
1515
|
+
globalThis.peernet = this;
|
|
1516
|
+
this.bw = {
|
|
1517
|
+
up: 0,
|
|
1518
|
+
down: 0,
|
|
1519
|
+
};
|
|
1520
|
+
return this._init(options)
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
get defaultStores() {
|
|
1524
|
+
return ['account', 'wallet', 'block', 'transaction', 'chain', 'data', 'message']
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
addProto(name, proto) {
|
|
1528
|
+
if (!this.protos[name]) this.protos[name] = proto;
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
addCodec(name, codec) {
|
|
1532
|
+
if (!this.codecs[name]) this.codecs[name] = codec;
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
async addStore(name, prefix, root, isPrivate = true) {
|
|
1536
|
+
if (name === 'block' || name === 'transaction' || name === 'chain' ||
|
|
1537
|
+
name === 'data' || name === 'message') isPrivate = false;
|
|
1538
|
+
|
|
1539
|
+
let Storage;
|
|
1540
|
+
if (this.hasDaemon) {
|
|
1541
|
+
Storage = LeofcoinStorageClient;
|
|
1542
|
+
} else {
|
|
1543
|
+
Storage = globalThis.LeofcoinStorage?.default ? globalThis.LeofcoinStorage.default : LeofcoinStorage__default["default"];
|
|
1544
|
+
}
|
|
1545
|
+
globalThis[`${name}Store`] = globalThis[`${name}Store`] ||
|
|
1546
|
+
await new Storage(`${prefix}-${name}`, root);
|
|
1547
|
+
|
|
1548
|
+
globalThis[`${name}Store`].private = isPrivate;
|
|
1549
|
+
if (!isPrivate) this.stores.push(name);
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
|
|
1553
|
+
/**
|
|
1554
|
+
* @see MessageHandler
|
|
1555
|
+
*/
|
|
1556
|
+
prepareMessage(to, data) {
|
|
1557
|
+
return this._messageHandler.prepareMessage(this.id, to, data)
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
/**
|
|
1561
|
+
* @access public
|
|
1562
|
+
*
|
|
1563
|
+
* @return {Array} peerId
|
|
1564
|
+
*/
|
|
1565
|
+
get peers() {
|
|
1566
|
+
return [...connections.values()]
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
/**
|
|
1570
|
+
* @private
|
|
1571
|
+
*
|
|
1572
|
+
* @param {Object} options
|
|
1573
|
+
* @param {String} options.root - path to root directory
|
|
1574
|
+
*
|
|
1575
|
+
* @return {Promise} instance of Peernet
|
|
1576
|
+
*/
|
|
1577
|
+
async _init(options) {
|
|
1578
|
+
// peernetDHT aka closesPeer by coordinates
|
|
1579
|
+
/**
|
|
1580
|
+
* @type {Object}
|
|
1581
|
+
* @property {Object} peer Instance of Peer
|
|
1582
|
+
*/
|
|
1583
|
+
this.dht = new DhtEarth();
|
|
1584
|
+
/**
|
|
1585
|
+
* @type {Map}
|
|
1586
|
+
* @property {Object} peer Instance of Peer
|
|
1587
|
+
*/
|
|
1588
|
+
this.peerMap = new Map();
|
|
1589
|
+
this.stores = [];
|
|
1590
|
+
this.requestProtos = {};
|
|
1591
|
+
this.storePrefix = options.storePrefix;
|
|
1592
|
+
this.root = options.root;
|
|
1593
|
+
|
|
1594
|
+
/**
|
|
1595
|
+
* proto Object containing protos
|
|
1596
|
+
* @type {Object}
|
|
1597
|
+
* @property {PeernetMessage} protos[peernet-message] messageNode
|
|
1598
|
+
* @property {DHTMessage} protos[peernet-dht] messageNode
|
|
1599
|
+
* @property {DHTMessageResponse} protos[peernet-dht-response] messageNode
|
|
1600
|
+
* @property {DataMessage} protos[peernet-data] messageNode
|
|
1601
|
+
* @property {DataMessageResponse} protos[peernet-data-response] messageNode
|
|
1602
|
+
*/
|
|
1603
|
+
globalThis.peernet.protos = {
|
|
1604
|
+
'peernet-request': request,
|
|
1605
|
+
'peernet-response': response,
|
|
1606
|
+
'peernet-peer': PeerMessage,
|
|
1607
|
+
'peernet-peer-response': PeerMessageResponse,
|
|
1608
|
+
'peernet-message': peernetMessage,
|
|
1609
|
+
'peernet-dht': dht,
|
|
1610
|
+
'peernet-dht-response': dhtResponse,
|
|
1611
|
+
'peernet-data': DataMessage,
|
|
1612
|
+
'peernet-data-response': DataMessageResponse,
|
|
1613
|
+
'peernet-ps': PsMessage,
|
|
1614
|
+
'chat-message': ChatMessage,
|
|
1615
|
+
};
|
|
1616
|
+
|
|
1617
|
+
this.protos = globalThis.peernet.protos;
|
|
1618
|
+
this.codecs = codec.codecs;
|
|
1619
|
+
|
|
1620
|
+
this._messageHandler = new MessageHandler(this.network);
|
|
1621
|
+
|
|
1622
|
+
const {daemon, environment} = await target();
|
|
1623
|
+
this.hasDaemon = daemon;
|
|
1624
|
+
|
|
1447
1625
|
if (this.hasDaemon) {
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
wallet
|
|
1467
|
-
|
|
1468
|
-
wallet.
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
}
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
this.peerId = id;
|
|
1482
|
-
|
|
1483
|
-
pubsub.subscribe('peer:
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
if (
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
if (
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
this.
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
//
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
return
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
}
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
*
|
|
1874
|
-
*
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
return
|
|
1887
|
-
}
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
async put(hash, data, store = 'data') {
|
|
1897
|
-
store = globalThis[`${store}Store`];
|
|
1898
|
-
return store.put(hash, data)
|
|
1899
|
-
}
|
|
1900
|
-
|
|
1901
|
-
/**
|
|
1902
|
-
* @param {String} hash
|
|
1903
|
-
* @return {Boolean}
|
|
1904
|
-
*/
|
|
1905
|
-
async has(hash) {
|
|
1906
|
-
const store = await this.whichStore([...this.stores], hash);
|
|
1907
|
-
if (store) {
|
|
1908
|
-
if (store.private) return false
|
|
1909
|
-
else return true
|
|
1910
|
-
}
|
|
1911
|
-
return false
|
|
1912
|
-
}
|
|
1913
|
-
|
|
1914
|
-
/**
|
|
1915
|
-
*
|
|
1916
|
-
* @param {String} topic
|
|
1917
|
-
* @param {String|Object|Array|Boolean|Buffer} data
|
|
1918
|
-
*/
|
|
1919
|
-
async publish(topic, data) {
|
|
1920
|
-
// globalSub.publish(topic, data)
|
|
1921
|
-
if (!Buffer.isBuffer(topic)) topic = Buffer.from(topic);
|
|
1922
|
-
if (!Buffer.isBuffer(data)) data = Buffer.from(data);
|
|
1923
|
-
const id = Math.random().toString(36).slice(-12);
|
|
1924
|
-
data = new PsMessage({data, topic});
|
|
1925
|
-
for (const peer of this.peers) {
|
|
1926
|
-
if (peer.connection._connected) {
|
|
1927
|
-
if (peer.id.toString() !== this.peerId.toString()) {
|
|
1928
|
-
const node = await this.prepareMessage(peer.id, data.encoded);
|
|
1929
|
-
peer.write(Buffer.from(JSON.stringify({id, data: node.encoded})));
|
|
1930
|
-
}
|
|
1931
|
-
} else {
|
|
1932
|
-
this.removePeer(peer);
|
|
1933
|
-
}
|
|
1934
|
-
// TODO: if peer subscribed
|
|
1935
|
-
}
|
|
1936
|
-
}
|
|
1937
|
-
|
|
1938
|
-
createHash(data, name) {
|
|
1939
|
-
return new hash(data, {name})
|
|
1940
|
-
}
|
|
1941
|
-
|
|
1942
|
-
/**
|
|
1943
|
-
*
|
|
1944
|
-
* @param {String} topic
|
|
1945
|
-
* @param {Method} cb
|
|
1946
|
-
*/
|
|
1947
|
-
async subscribe(topic, cb) {
|
|
1948
|
-
// TODO: if peer subscribed
|
|
1949
|
-
globalSub.subscribe(topic, cb);
|
|
1950
|
-
}
|
|
1951
|
-
|
|
1952
|
-
async removePeer(peer) {
|
|
1953
|
-
connections.delete(peer.id);
|
|
1954
|
-
}
|
|
1955
|
-
|
|
1956
|
-
get Buffer() {
|
|
1957
|
-
return Buffer
|
|
1958
|
-
}
|
|
1959
|
-
// async block(index) {
|
|
1960
|
-
// const _values = []
|
|
1961
|
-
// for (const peer of this.peers) {
|
|
1962
|
-
// const value = await peer.request({type: 'block', index})
|
|
1963
|
-
// console.log(value);
|
|
1964
|
-
// }
|
|
1965
|
-
//
|
|
1966
|
-
// }
|
|
1626
|
+
const httpClient = await Promise.resolve().then(function () { return require('./client-1a1f75e6.js'); });
|
|
1627
|
+
globalThis.peernet.client = await httpClient.default({
|
|
1628
|
+
protocol: 'peernet-v0.1.0', host: '127.0.0.1', port: options.port
|
|
1629
|
+
});
|
|
1630
|
+
} else {
|
|
1631
|
+
const http = await Promise.resolve().then(function () { return require('./http-4bc6caeb.js'); });
|
|
1632
|
+
if (environment !== 'browser') http.default(options);
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
for (const store of this.defaultStores) {
|
|
1636
|
+
await this.addStore(store, options.storePrefix, options.root);
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
try {
|
|
1640
|
+
const pub = await accountStore.get('public');
|
|
1641
|
+
this.id = pub.walletId;
|
|
1642
|
+
} catch (e) {
|
|
1643
|
+
if (e.code === 'ERR_NOT_FOUND') {
|
|
1644
|
+
const wallet = {};
|
|
1645
|
+
const {identity, accounts, config} = await generateAccount__default["default"](this.network);
|
|
1646
|
+
wallet.identity = identity;
|
|
1647
|
+
wallet.accounts = accounts;
|
|
1648
|
+
wallet.version = 1;
|
|
1649
|
+
walletStore.put(wallet);
|
|
1650
|
+
await accountStore.put('config', config);
|
|
1651
|
+
await accountStore.put('public', {walletId: wallet.identity.walletId});
|
|
1652
|
+
|
|
1653
|
+
this.id = wallet.identity.walletId;
|
|
1654
|
+
} else {
|
|
1655
|
+
throw e
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
this._peerHandler = new PeerDiscovery(this.id);
|
|
1659
|
+
this.peerId = this.id;
|
|
1660
|
+
|
|
1661
|
+
pubsub.subscribe('peer:connected', async (peer) => {
|
|
1662
|
+
console.log(peer);
|
|
1663
|
+
// console.log({connected: peer.id, as: this._getPeerId(peer.id) });
|
|
1664
|
+
// peer.on('peernet.data', async (message) => {
|
|
1665
|
+
// const id = message.id
|
|
1666
|
+
// message = new PeernetMessage(Buffer.from(message.data.data))
|
|
1667
|
+
// const proto = protoFor(message.decoded.data)
|
|
1668
|
+
// this._protoHandler({id, proto}, peer)
|
|
1669
|
+
// })
|
|
1670
|
+
});
|
|
1671
|
+
|
|
1672
|
+
pubsub.subscribe('peer:data', async message => {
|
|
1673
|
+
if (!message.data) return
|
|
1674
|
+
const {id, data} = JSON.parse(new TextDecoder().decode(message.data));
|
|
1675
|
+
const uint8Array = new Uint8Array(Object.keys(data).length);
|
|
1676
|
+
for (var i = 0; i < Object.keys(data).length; i++) {
|
|
1677
|
+
uint8Array[i] = data[i];
|
|
1678
|
+
}
|
|
1679
|
+
message = new peernetMessage(uint8Array);
|
|
1680
|
+
const proto = protoFor(message.decoded.data);
|
|
1681
|
+
|
|
1682
|
+
const from = message.decoded.from;
|
|
1683
|
+
this._protoHandler({id, proto}, this.client.connections[from], from);
|
|
1684
|
+
});
|
|
1685
|
+
|
|
1686
|
+
/**
|
|
1687
|
+
* @access public
|
|
1688
|
+
* @type {PeernetClient}
|
|
1689
|
+
*/
|
|
1690
|
+
this.client = new Client(this.id);
|
|
1691
|
+
if (globalThis.onbeforeunload) {
|
|
1692
|
+
globalThis.addEventListener('beforeunload', async () => this.client.close());
|
|
1693
|
+
}
|
|
1694
|
+
return this
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
_getPeerId(id) {
|
|
1698
|
+
for (const entry of [...this.peerMap.entries()]) {
|
|
1699
|
+
for (const _id of entry[1]) {
|
|
1700
|
+
if (_id === id) return entry[0]
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
addRequestHandler(name, method) {
|
|
1706
|
+
this.requestProtos[name] = method;
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
/**
|
|
1710
|
+
* @private
|
|
1711
|
+
*
|
|
1712
|
+
* @param {Buffer} message - peernet message
|
|
1713
|
+
* @param {PeernetPeer} peer - peernet peer
|
|
1714
|
+
*/
|
|
1715
|
+
async _protoHandler(message, peer, from) {
|
|
1716
|
+
const {id, proto} = message;
|
|
1717
|
+
this.bw.down += proto.encoded.length;
|
|
1718
|
+
|
|
1719
|
+
if (proto.name === 'peernet-dht') {
|
|
1720
|
+
let { hash, store } = proto.decoded;
|
|
1721
|
+
let has;
|
|
1722
|
+
|
|
1723
|
+
if (!store) {
|
|
1724
|
+
has = await this.has(hash);
|
|
1725
|
+
} else {
|
|
1726
|
+
store = globalThis[`${store}Store`];
|
|
1727
|
+
if (store.private) has = false;
|
|
1728
|
+
else has = await store.has(hash);
|
|
1729
|
+
}
|
|
1730
|
+
const data = new dhtResponse({hash, has});
|
|
1731
|
+
const node = await this.prepareMessage(from, data.encoded);
|
|
1732
|
+
|
|
1733
|
+
peer.send(new TextEncoder().encode(JSON.stringify({id, data: node.encoded})));
|
|
1734
|
+
this.bw.up += node.encoded.length;
|
|
1735
|
+
} else if (proto.name === 'peernet-data') {
|
|
1736
|
+
let { hash, store } = proto.decoded;
|
|
1737
|
+
let data;
|
|
1738
|
+
if (!store) {
|
|
1739
|
+
store = await this.whichStore([...this.stores], hash);
|
|
1740
|
+
} else {
|
|
1741
|
+
store = globalThis[`${store}Store`];
|
|
1742
|
+
}
|
|
1743
|
+
if (store && !store.private) {
|
|
1744
|
+
data = await store.get(hash);
|
|
1745
|
+
|
|
1746
|
+
if (data) {
|
|
1747
|
+
data = new DataMessageResponse({hash, data: data.decoded ? new TextEncoder().encode(JSON.stringify(data.decoded)) : data});
|
|
1748
|
+
|
|
1749
|
+
const node = await this.prepareMessage(from, data.encoded);
|
|
1750
|
+
peer.send(new TextEncoder().encode(JSON.stringify({id, data: node.encoded})));
|
|
1751
|
+
this.bw.up += node.encoded.length;
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
} else if (proto.name === 'peernet-request') {
|
|
1756
|
+
// TODO: make dynamic
|
|
1757
|
+
// exposeddevapi[proto.decoded.request](proto.decoded.params)
|
|
1758
|
+
const method = this.requestProtos[proto.decoded.request];
|
|
1759
|
+
if (method) {
|
|
1760
|
+
const data = await method();
|
|
1761
|
+
const node = await this.prepareMessage(from, data.encoded);
|
|
1762
|
+
peer.send(new TextEncoder().encode(JSON.stringify({id, data: node.encoded})));
|
|
1763
|
+
|
|
1764
|
+
this.bw.up += node.encoded.length;
|
|
1765
|
+
}
|
|
1766
|
+
} else if (proto.name === 'peernet-ps' &&
|
|
1767
|
+
this._getPeerId(peer.id) !== this.id.toString()) {
|
|
1768
|
+
globalSub.publish(proto.decoded.topic.toString(), proto.decoded.data.toString());
|
|
1769
|
+
}
|
|
1770
|
+
// }
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
/**
|
|
1774
|
+
* performs a walk and resolves first encounter
|
|
1775
|
+
*
|
|
1776
|
+
* @param {String} hash
|
|
1777
|
+
*/
|
|
1778
|
+
async walk(hash) {
|
|
1779
|
+
if (!hash) throw new Error('hash expected, received undefined')
|
|
1780
|
+
const data = new dht({hash});
|
|
1781
|
+
this.client.id;
|
|
1782
|
+
for (const peer of this.peers) {
|
|
1783
|
+
const node = await this.prepareMessage(peer.id, data.encoded);
|
|
1784
|
+
|
|
1785
|
+
const result = await peer.request(node.encoded);
|
|
1786
|
+
|
|
1787
|
+
let proto = protoFor(result.data);
|
|
1788
|
+
|
|
1789
|
+
if (proto.name !== 'peernet-message') throw encapsulatedError()
|
|
1790
|
+
const from = proto.decoded.from;
|
|
1791
|
+
proto = protoFor(proto.decoded.data);
|
|
1792
|
+
|
|
1793
|
+
if (proto.name !== 'peernet-dht-response') throw dhtError(proto.name)
|
|
1794
|
+
|
|
1795
|
+
// TODO: give ip and port (just used for location)
|
|
1796
|
+
if (!peer.connection.remoteAddress || !peer.connection.localAddress) {
|
|
1797
|
+
peer.connection.remoteFamily = 'ipv4';
|
|
1798
|
+
peer.connection.remoteAddress = '127.0.0.1';
|
|
1799
|
+
peer.connection.remotePort = '0000';
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
const peerInfo = {
|
|
1803
|
+
family: peer.connection.remoteFamily || peer.connection.localFamily,
|
|
1804
|
+
address: peer.connection.remoteAddress || peer.connection.localAddress,
|
|
1805
|
+
port: peer.connection.remotePort || peer.connection.localPort,
|
|
1806
|
+
id: from,
|
|
1807
|
+
};
|
|
1808
|
+
|
|
1809
|
+
if (proto.decoded.has) this.dht.addProvider(peerInfo, proto.decoded.hash);
|
|
1810
|
+
}
|
|
1811
|
+
return
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
/**
|
|
1815
|
+
* Override DHT behavior, try's finding the content three times
|
|
1816
|
+
*
|
|
1817
|
+
* @param {String} hash
|
|
1818
|
+
*/
|
|
1819
|
+
async providersFor(hash) {
|
|
1820
|
+
let providers = await this.dht.providersFor(hash);
|
|
1821
|
+
// walk the network to find a provider
|
|
1822
|
+
if (!providers || providers.length === 0) {
|
|
1823
|
+
await this.walk(hash);
|
|
1824
|
+
providers = await this.dht.providersFor(hash);
|
|
1825
|
+
// second walk the network to find a provider
|
|
1826
|
+
if (!providers || providers.length === 0) {
|
|
1827
|
+
await this.walk(hash);
|
|
1828
|
+
providers = await this.dht.providersFor(hash);
|
|
1829
|
+
}
|
|
1830
|
+
// last walk
|
|
1831
|
+
if (!providers || providers.length === 0) {
|
|
1832
|
+
await this.walk(hash);
|
|
1833
|
+
providers = await this.dht.providersFor(hash);
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
// undefined if no providers given
|
|
1837
|
+
return providers
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
get block() {
|
|
1841
|
+
return {
|
|
1842
|
+
get: async (hash) => {
|
|
1843
|
+
const data = await blockStore.has(hash);
|
|
1844
|
+
if (data) return await blockStore.get(hash)
|
|
1845
|
+
return this.requestData(hash, 'block')
|
|
1846
|
+
},
|
|
1847
|
+
put: async (hash, data) => {
|
|
1848
|
+
if (await blockStore.has(hash)) return
|
|
1849
|
+
return await blockStore.put(hash, data)
|
|
1850
|
+
},
|
|
1851
|
+
has: async (hash) => await blockStore.has(hash, 'block'),
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
get transaction() {
|
|
1856
|
+
return {
|
|
1857
|
+
get: async (hash) => {
|
|
1858
|
+
const data = await transactionStore.has(hash);
|
|
1859
|
+
if (data) return await transactionStore.get(hash)
|
|
1860
|
+
return this.requestData(hash, 'transaction')
|
|
1861
|
+
},
|
|
1862
|
+
put: async (hash, data) => {
|
|
1863
|
+
if (await transactionStore.has(hash)) return
|
|
1864
|
+
return await transactionStore.put(hash, data)
|
|
1865
|
+
},
|
|
1866
|
+
has: async (hash) => await transactionStore.has(hash),
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
async requestData(hash, store) {
|
|
1871
|
+
const providers = await this.providersFor(hash);
|
|
1872
|
+
if (!providers || providers.size === 0) throw nothingFoundError(hash)
|
|
1873
|
+
debug(`found ${providers.size} provider(s) for ${hash}`);
|
|
1874
|
+
// get closest peer on earth
|
|
1875
|
+
const closestPeer = await this.dht.closestPeer(providers);
|
|
1876
|
+
// get peer instance by id
|
|
1877
|
+
if (!closestPeer || !closestPeer.id) return this.requestData(hash, store.name ? store.name : store)
|
|
1878
|
+
|
|
1879
|
+
const id = closestPeer.id.toString();
|
|
1880
|
+
if (this.peers) {
|
|
1881
|
+
let closest = this.peers.filter((peer) => {
|
|
1882
|
+
if (this._getPeerId(peer.id) === id) return peer
|
|
1883
|
+
});
|
|
1884
|
+
|
|
1885
|
+
let data = new DataMessage({hash, store: store.name ? store.name : store});
|
|
1886
|
+
|
|
1887
|
+
const node = await this.prepareMessage(id, data.encoded);
|
|
1888
|
+
if (closest[0]) data = await closest[0].request(node.encoded);
|
|
1889
|
+
else {
|
|
1890
|
+
closest = this.peers.filter((peer) => {
|
|
1891
|
+
if (peer.id.toString() === id) return peer
|
|
1892
|
+
});
|
|
1893
|
+
if (closest[0]) data = await closest[0].request(node.encoded);
|
|
1894
|
+
}
|
|
1895
|
+
if (data.data) {
|
|
1896
|
+
console.log(data.data);
|
|
1897
|
+
let proto = protoFor(data.data);
|
|
1898
|
+
proto = protoFor(proto.decoded.data);
|
|
1899
|
+
return proto.decoded.data
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
// this.put(hash, proto.decoded.data)
|
|
1903
|
+
}
|
|
1904
|
+
return null
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
|
|
1908
|
+
get message() {
|
|
1909
|
+
return {
|
|
1910
|
+
/**
|
|
1911
|
+
* Get content for given message hash
|
|
1912
|
+
*
|
|
1913
|
+
* @param {String} hash
|
|
1914
|
+
*/
|
|
1915
|
+
get: async (hash) => {
|
|
1916
|
+
debug(`get message ${hash}`);
|
|
1917
|
+
const message = await messageStore.has(hash);
|
|
1918
|
+
if (message) return await messageStore.get(hash)
|
|
1919
|
+
return this.requestData(hash, 'message')
|
|
1920
|
+
},
|
|
1921
|
+
/**
|
|
1922
|
+
* put message content
|
|
1923
|
+
*
|
|
1924
|
+
* @param {String} hash
|
|
1925
|
+
* @param {Buffer} message
|
|
1926
|
+
*/
|
|
1927
|
+
put: async (hash, message) => await messageStore.put(hash, message),
|
|
1928
|
+
/**
|
|
1929
|
+
* @param {String} hash
|
|
1930
|
+
* @return {Boolean}
|
|
1931
|
+
*/
|
|
1932
|
+
has: async (hash) => await messageStore.has(hash),
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
get data() {
|
|
1937
|
+
return {
|
|
1938
|
+
/**
|
|
1939
|
+
* Get content for given data hash
|
|
1940
|
+
*
|
|
1941
|
+
* @param {String} hash
|
|
1942
|
+
*/
|
|
1943
|
+
get: async (hash) => {
|
|
1944
|
+
debug(`get data ${hash}`);
|
|
1945
|
+
const data = await dataStore.has(hash);
|
|
1946
|
+
if (data) return await dataStore.get(hash)
|
|
1947
|
+
return this.requestData(hash, 'data')
|
|
1948
|
+
},
|
|
1949
|
+
/**
|
|
1950
|
+
* put data content
|
|
1951
|
+
*
|
|
1952
|
+
* @param {String} hash
|
|
1953
|
+
* @param {Buffer} data
|
|
1954
|
+
*/
|
|
1955
|
+
put: async (hash, data) => await dataStore.put(hash, data),
|
|
1956
|
+
/**
|
|
1957
|
+
* @param {String} hash
|
|
1958
|
+
* @return {Boolean}
|
|
1959
|
+
*/
|
|
1960
|
+
has: async (hash) => await dataStore.has(hash),
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
/**
|
|
1965
|
+
* goes trough given stores and tries to find data for given hash
|
|
1966
|
+
* @param {Array} stores
|
|
1967
|
+
* @param {string} hash
|
|
1968
|
+
*/
|
|
1969
|
+
async whichStore(stores, hash) {
|
|
1970
|
+
let store = stores.pop();
|
|
1971
|
+
store = globalThis[`${store}Store`];
|
|
1972
|
+
if (store) {
|
|
1973
|
+
const has = await store.has(hash);
|
|
1974
|
+
if (has) return store
|
|
1975
|
+
if (stores.length > 0) return this.whichStore(stores, hash)
|
|
1976
|
+
} else return null
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
/**
|
|
1980
|
+
* Get content for given hash
|
|
1981
|
+
*
|
|
1982
|
+
* @param {String} hash - the hash of the wanted data
|
|
1983
|
+
* @param {String} store - storeName to access
|
|
1984
|
+
*/
|
|
1985
|
+
async get(hash, store) {
|
|
1986
|
+
debug(`get ${hash}`);
|
|
1987
|
+
let data;
|
|
1988
|
+
if (store) store = globalThis[`${store}Store`];
|
|
1989
|
+
if (!store) store = await this.whichStore([...this.stores], hash);
|
|
1990
|
+
if (store && await store.has(hash)) data = await store.get(hash);
|
|
1991
|
+
if (data) return data
|
|
1992
|
+
|
|
1993
|
+
return this.requestData(hash, store.name ? store.name : store)
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
/**
|
|
1997
|
+
* put content
|
|
1998
|
+
*
|
|
1999
|
+
* @param {String} hash
|
|
2000
|
+
* @param {Buffer} data
|
|
2001
|
+
* @param {String} store - storeName to access
|
|
2002
|
+
*/
|
|
2003
|
+
async put(hash, data, store = 'data') {
|
|
2004
|
+
store = globalThis[`${store}Store`];
|
|
2005
|
+
return store.put(hash, data)
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
/**
|
|
2009
|
+
* @param {String} hash
|
|
2010
|
+
* @return {Boolean}
|
|
2011
|
+
*/
|
|
2012
|
+
async has(hash) {
|
|
2013
|
+
const store = await this.whichStore([...this.stores], hash);
|
|
2014
|
+
if (store) {
|
|
2015
|
+
if (store.private) return false
|
|
2016
|
+
else return true
|
|
2017
|
+
}
|
|
2018
|
+
return false
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
/**
|
|
2022
|
+
*
|
|
2023
|
+
* @param {String} topic
|
|
2024
|
+
* @param {String|Object|Array|Boolean|Buffer} data
|
|
2025
|
+
*/
|
|
2026
|
+
async publish(topic, data) {
|
|
2027
|
+
// globalSub.publish(topic, data)
|
|
2028
|
+
if (topic instanceof Uint8Array === false) topic = new TextEncoder().encode(topic);
|
|
2029
|
+
if (data instanceof Uint8Array === false) data = new TextEncoder().encode(JSON.stringify(data));
|
|
2030
|
+
const id = Math.random().toString(36).slice(-12);
|
|
2031
|
+
data = new PsMessage({data, topic});
|
|
2032
|
+
for (const peer of this.peers) {
|
|
2033
|
+
if (peer.connection._connected) {
|
|
2034
|
+
if (peer.id.toString() !== this.peerId.toString()) {
|
|
2035
|
+
const node = await this.prepareMessage(peer.id, data.encoded);
|
|
2036
|
+
peer.send(new TextEncoder().encode(JSON.stringify({id, data: node.encoded})));
|
|
2037
|
+
}
|
|
2038
|
+
} else {
|
|
2039
|
+
this.removePeer(peer);
|
|
2040
|
+
}
|
|
2041
|
+
// TODO: if peer subscribed
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
createHash(data, name) {
|
|
2046
|
+
return new hash(data, {name})
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
/**
|
|
2050
|
+
*
|
|
2051
|
+
* @param {String} topic
|
|
2052
|
+
* @param {Method} cb
|
|
2053
|
+
*/
|
|
2054
|
+
async subscribe(topic, cb) {
|
|
2055
|
+
// TODO: if peer subscribed
|
|
2056
|
+
globalSub.subscribe(topic, cb);
|
|
2057
|
+
}
|
|
2058
|
+
|
|
2059
|
+
async removePeer(peer) {
|
|
2060
|
+
connections.delete(peer.id);
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
get Buffer() {
|
|
2064
|
+
return Buffer
|
|
2065
|
+
}
|
|
2066
|
+
// async block(index) {
|
|
2067
|
+
// const _values = []
|
|
2068
|
+
// for (const peer of this.peers) {
|
|
2069
|
+
// const value = await peer.request({type: 'block', index})
|
|
2070
|
+
// console.log(value);
|
|
2071
|
+
// }
|
|
2072
|
+
//
|
|
2073
|
+
// }
|
|
1967
2074
|
}
|
|
1968
2075
|
|
|
1969
2076
|
module.exports = Peernet;
|