@leofcoin/peernet 0.10.7 → 0.11.1

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