@leofcoin/peernet 1.2.30 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -0
- package/docs/automatic-transport.md +37 -0
- package/exports/browser/{browser-DvU1xNFS.js → browser-CCfR9KOM.js} +1 -1
- package/exports/browser/{client-86Tnax66.js → client-C_pBoi1g.js} +254 -78
- package/exports/browser/{identity-CYVPDmkf.js → identity-Cf8KKnho.js} +511 -391
- package/exports/browser/identity.js +1 -1
- package/exports/browser/{index-BD0Anx7_.js → index-dTQVgkpf.js} +1 -1
- package/exports/browser/{messages-D7JxkkDW.js → messages-BGHaihSi.js} +2 -2
- package/exports/browser/{peernet-C4rOvyyU.js → peernet-Db43ag2D.js} +68 -45
- package/exports/browser/peernet.js +2 -2
- package/exports/store.js +0 -3
- package/package.json +10 -6
- package/.esdoc.json +0 -10
- package/.gitattributes +0 -2
- package/.github/workflows/release.yml +0 -34
- package/.prettierrc +0 -7
- package/.travis.yml +0 -27
- package/BREAKING_CHANGES.md +0 -35
- package/deploy.js +0 -8
- package/exports/browser/browser-D-r0O9Qn.js +0 -53
- package/exports/browser/browser-_hiyXwPp.js +0 -147
- package/exports/browser/client-BFxJts6j.js +0 -2224
- package/exports/browser/client-BVhUamQG.js +0 -2200
- package/exports/browser/client-BzvWzzLv.js +0 -2224
- package/exports/browser/client-C9GBz2Tf.js +0 -2224
- package/exports/browser/client-CQzuu6At.js +0 -2224
- package/exports/browser/client-CVkFejdx.js +0 -2224
- package/exports/browser/client-Cj_UUO8W.js +0 -2224
- package/exports/browser/client-CrGaeBjj.js +0 -2224
- package/exports/browser/client-DDC8C510.js +0 -2224
- package/exports/browser/client-ERocgKET.js +0 -2224
- package/exports/browser/client-a8rws3cu.js +0 -2224
- package/exports/browser/identity-ChyyZSve.js +0 -8397
- package/exports/browser/index-Bxr5Iztg.js +0 -37
- package/exports/browser/messages-BFCmg6Vb.js +0 -212
- package/exports/browser/messages-BHIC2mQu.js +0 -212
- package/exports/browser/messages-CimzJ4wr.js +0 -210
- package/exports/browser/messages-Cjoa_LGO.js +0 -210
- package/exports/browser/messages-DH0tgNM9.js +0 -207
- package/exports/browser/messages-DVHcNvs_.js +0 -212
- package/exports/browser/messages-DqL_g2qw.js +0 -210
- package/exports/browser/messages-UKnuelZ7.js +0 -207
- package/exports/browser/messages-XgZZUMma.js +0 -212
- package/exports/browser/messages-kfFyctPL.js +0 -210
- package/exports/browser/messages-psROnfRi.js +0 -212
- package/exports/browser/peernet-B9VL4Xwe.js +0 -9522
- package/exports/browser/peernet-C-HNdrcd.js +0 -9528
- package/exports/browser/peernet-Ca1iM2tN.js +0 -9336
- package/exports/browser/peernet-CbI0-jFn.js +0 -9651
- package/exports/browser/peernet-CtHScDyh.js +0 -9524
- package/exports/browser/peernet-DfuxUK3u.js +0 -9525
- package/exports/browser/peernet-DiEcNDLD.js +0 -9655
- package/exports/browser/peernet-DtuRXOzP.js +0 -9653
- package/exports/browser/peernet-r_9IyzTe.js +0 -9336
- package/exports/browser/peernet-rcD5XL1y.js +0 -9590
- package/exports/browser/peernet-rw8HJGOz.js +0 -9597
- package/exports/messages-CHKVVTVE.js +0 -204
- package/exports/messages-DaUtFfBh.js +0 -207
- package/index.html +0 -19
- package/rollup.config.js +0 -143
- package/src/dht/dht.ts +0 -142
- package/src/discovery/peer-discovery.js +0 -75
- package/src/errors/errors.js +0 -12
- package/src/file-transfer.ts +0 -194
- package/src/handlers/data.js +0 -15
- package/src/handlers/message.js +0 -34
- package/src/identity.ts +0 -172
- package/src/messages/chat.ts +0 -13
- package/src/messages/data-response.ts +0 -13
- package/src/messages/data.ts +0 -17
- package/src/messages/dht-response.ts +0 -13
- package/src/messages/dht.ts +0 -21
- package/src/messages/file-link.ts +0 -17
- package/src/messages/file.ts +0 -17
- package/src/messages/peer-response.ts +0 -13
- package/src/messages/peer.ts +0 -13
- package/src/messages/peernet.ts +0 -13
- package/src/messages/ps.ts +0 -13
- package/src/messages/request.ts +0 -13
- package/src/messages/response.ts +0 -13
- package/src/messages.js +0 -12
- package/src/peer-info.js +0 -9
- package/src/peernet.ts +0 -1113
- package/src/prompts/password/browser.js +0 -1
- package/src/prompts/password/node.js +0 -6
- package/src/prompts/password.js +0 -1
- package/src/proto/chat-message.proto.js +0 -6
- package/src/proto/data-response.proto.js +0 -4
- package/src/proto/data.proto.js +0 -4
- package/src/proto/dht-response.proto.js +0 -4
- package/src/proto/dht.proto.js +0 -4
- package/src/proto/file-link.proto.js +0 -5
- package/src/proto/file.proto.js +0 -10
- package/src/proto/peer-response.proto.js +0 -3
- package/src/proto/peer.proto.js +0 -3
- package/src/proto/peernet.proto.js +0 -7
- package/src/proto/ps.proto.js +0 -4
- package/src/proto/request.proto.js +0 -4
- package/src/proto/response.proto.js +0 -3
- package/src/types.ts +0 -25
- package/src/utils/utils.js +0 -77
- package/test/all.js +0 -5
- package/test/client.js +0 -15
- package/test/codec.js +0 -56
- package/test/file-transfer.test.js +0 -81
- package/test/fresh-empty-store.js +0 -15
- package/test/hash.js +0 -14
- package/test/index.js +0 -6
- package/test/lastBlock.js +0 -7
- package/test/messages.js +0 -26
- package/test/peer-data-exchange.test.js +0 -52
- package/test/peer-file-exchange.test.js +0 -78
- package/test/peernet.js +0 -17
- package/test/peernet.test.js +0 -440
- package/test-output.log +0 -84
- package/test.js +0 -63
- package/test2.js +0 -13
- package/test3.js +0 -16
- package/test4.js +0 -7
- package/tsconfig.json +0 -13
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { protoFor } from './../utils/utils.js'
|
|
2
|
-
|
|
3
|
-
export default class PeerDiscovery {
|
|
4
|
-
constructor(id) {
|
|
5
|
-
this.id = id
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
_getPeerId(id) {
|
|
9
|
-
if (!peernet.peerMap || (peernet.peerMap && peernet.peerMap.size === 0)) return false
|
|
10
|
-
|
|
11
|
-
for (const entry of [...peernet.peerMap.entries()]) {
|
|
12
|
-
for (const _id of entry[1]) {
|
|
13
|
-
if (_id === id) return entry[0]
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
async discover(peer) {
|
|
19
|
-
let id = this._getPeerId(peer.id)
|
|
20
|
-
if (id) return id
|
|
21
|
-
const data = await new peernet.protos['peernet-peer']({ id: this.id })
|
|
22
|
-
const node = await peernet.prepareMessage(peer.id, data.encoded)
|
|
23
|
-
|
|
24
|
-
let response = await peer.request(node.encoded)
|
|
25
|
-
response = await protoFor(response)
|
|
26
|
-
response = await new peernet.protos['peernet-peer-response'](response.decoded.data)
|
|
27
|
-
|
|
28
|
-
id = response.decoded.id
|
|
29
|
-
if (id === this.id) return
|
|
30
|
-
|
|
31
|
-
if (!peernet.peerMap.has(id)) peernet.peerMap.set(id, [peer.id])
|
|
32
|
-
else {
|
|
33
|
-
const connections = peernet.peerMap.get(id)
|
|
34
|
-
if (connections.indexOf(peer.id) === -1) {
|
|
35
|
-
connections.push(peer.id)
|
|
36
|
-
peernet.peerMap.set(peer.id, connections)
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
return id
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async discoverHandler(message, peer) {
|
|
43
|
-
const { id, proto } = message
|
|
44
|
-
// if (typeof message.data === 'string') message.data = Buffer.from(message.data)
|
|
45
|
-
if (proto.name === 'peernet-peer') {
|
|
46
|
-
const from = proto.decoded.id
|
|
47
|
-
if (from === this.id) return
|
|
48
|
-
|
|
49
|
-
if (!peernet.peerMap.has(from)) peernet.peerMap.set(from, [peer.id])
|
|
50
|
-
else {
|
|
51
|
-
const connections = peernet.peerMap.get(from)
|
|
52
|
-
if (connections.indexOf(peer.id) === -1) {
|
|
53
|
-
connections.push(peer.id)
|
|
54
|
-
peernet.peerMap.set(from, connections)
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
const data = await new peernet.protos['peernet-peer-response']({ id: this.id })
|
|
58
|
-
const node = await peernet.prepareMessage(from, data.encoded)
|
|
59
|
-
|
|
60
|
-
peer.write(Buffer.from(JSON.stringify({ id, data: node.encoded })))
|
|
61
|
-
} else if (proto.name === 'peernet-peer-response') {
|
|
62
|
-
const from = proto.decoded.id
|
|
63
|
-
if (from === this.id) return
|
|
64
|
-
|
|
65
|
-
if (!peernet.peerMap.has(from)) peernet.peerMap.set(from, [peer.id])
|
|
66
|
-
else {
|
|
67
|
-
const connections = peernet.peerMap.get(from)
|
|
68
|
-
if (connections.indexOf(peer.id) === -1) {
|
|
69
|
-
connections.push(peer.id)
|
|
70
|
-
peernet.peerMap.set(from, connections)
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
package/src/errors/errors.js
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export const encapsulatedError = () => {
|
|
2
|
-
return new Error('Nodes/Data should be send encapsulated by peernet-message')
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
export const dhtError = (proto) => {
|
|
6
|
-
const text = `Received proto ${proto.name} expected peernet-dht-response`
|
|
7
|
-
return new Error(`Routing error: ${text}`)
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export const nothingFoundError = (hash) => {
|
|
11
|
-
return new Error(`nothing found for ${hash}`)
|
|
12
|
-
}
|
package/src/file-transfer.ts
DELETED
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
export type FileTransferState = 'idle' | 'running' | 'paused' | 'completed' | 'cancelled' | 'failed'
|
|
2
|
-
|
|
3
|
-
export type FileTransferProgress = {
|
|
4
|
-
hash: string
|
|
5
|
-
state: FileTransferState
|
|
6
|
-
completedChunks: number
|
|
7
|
-
totalChunks: number
|
|
8
|
-
transferredBytes: number
|
|
9
|
-
totalBytes: number
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
type ProgressListener = (progress: FileTransferProgress) => void
|
|
13
|
-
|
|
14
|
-
/** A resumable download. Completed chunks remain cached on the transfer instance. */
|
|
15
|
-
export default class FileTransfer {
|
|
16
|
-
readonly hash: string
|
|
17
|
-
state: FileTransferState = 'idle'
|
|
18
|
-
result: Promise<Uint8Array>
|
|
19
|
-
error?: unknown
|
|
20
|
-
|
|
21
|
-
#fetch: (hash: string, index?: number) => Promise<Uint8Array | undefined>
|
|
22
|
-
#decode: (data: Uint8Array) => Promise<any>
|
|
23
|
-
#verify: (data: Uint8Array, hash: string) => Promise<boolean>
|
|
24
|
-
#pin?: (hash: string, data: Uint8Array) => Promise<any>
|
|
25
|
-
#verifyManifest: boolean
|
|
26
|
-
#concurrency: number
|
|
27
|
-
#stopped = false
|
|
28
|
-
#listeners = new Set<ProgressListener>()
|
|
29
|
-
#chunks = new Map<number, Uint8Array>()
|
|
30
|
-
#resumeWaiters: Array<() => void> = []
|
|
31
|
-
#resolve!: (value: Uint8Array) => void
|
|
32
|
-
#reject!: (reason?: unknown) => void
|
|
33
|
-
#totalChunks = 0
|
|
34
|
-
#totalBytes = 0
|
|
35
|
-
#started = false
|
|
36
|
-
|
|
37
|
-
constructor(options: {
|
|
38
|
-
hash: string
|
|
39
|
-
fetch: (hash: string, index?: number) => Promise<Uint8Array | undefined>
|
|
40
|
-
decode: (data: Uint8Array) => Promise<any>
|
|
41
|
-
verify: (data: Uint8Array, hash: string) => Promise<boolean>
|
|
42
|
-
pin?: (hash: string, data: Uint8Array) => Promise<any>
|
|
43
|
-
verifyManifest?: boolean
|
|
44
|
-
concurrency?: number
|
|
45
|
-
}) {
|
|
46
|
-
this.hash = options.hash
|
|
47
|
-
this.#fetch = options.fetch
|
|
48
|
-
this.#decode = options.decode
|
|
49
|
-
this.#verify = options.verify
|
|
50
|
-
this.#pin = options.pin
|
|
51
|
-
this.#verifyManifest = options.verifyManifest !== false
|
|
52
|
-
this.#concurrency = options.concurrency ?? 4
|
|
53
|
-
if (!Number.isSafeInteger(this.#concurrency) || this.#concurrency <= 0)
|
|
54
|
-
throw new TypeError('concurrency must be a positive integer')
|
|
55
|
-
this.result = new Promise((resolve, reject) => {
|
|
56
|
-
this.#resolve = resolve
|
|
57
|
-
this.#reject = reject
|
|
58
|
-
})
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
get progress(): FileTransferProgress {
|
|
62
|
-
let transferredBytes = 0
|
|
63
|
-
for (const chunk of this.#chunks.values()) transferredBytes += chunk.length
|
|
64
|
-
return {
|
|
65
|
-
hash: this.hash,
|
|
66
|
-
state: this.state,
|
|
67
|
-
completedChunks: this.#chunks.size,
|
|
68
|
-
totalChunks: this.#totalChunks,
|
|
69
|
-
transferredBytes,
|
|
70
|
-
totalBytes: this.#totalBytes
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
onProgress(listener: ProgressListener): () => void {
|
|
75
|
-
this.#listeners.add(listener)
|
|
76
|
-
listener(this.progress)
|
|
77
|
-
return () => this.#listeners.delete(listener)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
start(): this {
|
|
81
|
-
if (!this.#started) {
|
|
82
|
-
this.#started = true
|
|
83
|
-
this.state = 'running'
|
|
84
|
-
this.#emit()
|
|
85
|
-
void this.#run()
|
|
86
|
-
}
|
|
87
|
-
return this
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
pause(): void {
|
|
91
|
-
if (this.state === 'running') {
|
|
92
|
-
this.state = 'paused'
|
|
93
|
-
this.#emit()
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
resume(): void {
|
|
98
|
-
if (this.state !== 'paused') return
|
|
99
|
-
this.state = 'running'
|
|
100
|
-
const waiters = this.#resumeWaiters.splice(0)
|
|
101
|
-
for (const resolve of waiters) resolve()
|
|
102
|
-
this.#emit()
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
cancel(): void {
|
|
106
|
-
if (this.state === 'completed' || this.state === 'failed' || this.state === 'cancelled') return
|
|
107
|
-
this.state = 'cancelled'
|
|
108
|
-
this.#stopped = true
|
|
109
|
-
const error = new Error(`Transfer ${this.hash} was cancelled`)
|
|
110
|
-
this.error = error
|
|
111
|
-
const waiters = this.#resumeWaiters.splice(0)
|
|
112
|
-
for (const resolve of waiters) resolve()
|
|
113
|
-
this.#reject(error)
|
|
114
|
-
this.#emit()
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
async #waitUntilRunning(): Promise<void> {
|
|
118
|
-
if (this.state === 'paused') await new Promise<void>((resolve) => this.#resumeWaiters.push(resolve))
|
|
119
|
-
if (this.state === 'cancelled') throw this.error
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
async #run(): Promise<void> {
|
|
123
|
-
try {
|
|
124
|
-
const manifestData = await this.#fetch(this.hash)
|
|
125
|
-
if (!manifestData) throw new Error(`Unable to download ${this.hash}`)
|
|
126
|
-
if (this.#verifyManifest && !(await this.#verify(manifestData, this.hash)))
|
|
127
|
-
throw new Error(`Manifest hash mismatch for ${this.hash}`)
|
|
128
|
-
const manifest = await this.#decode(manifestData)
|
|
129
|
-
if (this.#pin) await this.#pin(this.hash, manifestData)
|
|
130
|
-
|
|
131
|
-
if (!manifest.chunked) {
|
|
132
|
-
if (manifest.links?.length) throw new Error(`${this.hash} is a directory`)
|
|
133
|
-
const content = manifest.content || new Uint8Array()
|
|
134
|
-
this.#totalChunks = 1
|
|
135
|
-
this.#totalBytes = content.length
|
|
136
|
-
this.#chunks.set(0, content)
|
|
137
|
-
this.state = 'completed'
|
|
138
|
-
this.#resolve(content)
|
|
139
|
-
this.#emit()
|
|
140
|
-
return
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const links = manifest.links || []
|
|
144
|
-
this.#totalChunks = links.length
|
|
145
|
-
this.#totalBytes =
|
|
146
|
-
Number(manifest.size) || links.reduce((sum: number, link: { size?: number }) => sum + (Number(link.size) || 0), 0)
|
|
147
|
-
this.#emit()
|
|
148
|
-
|
|
149
|
-
let nextIndex = 0
|
|
150
|
-
const worker = async () => {
|
|
151
|
-
while (!this.#stopped) {
|
|
152
|
-
await this.#waitUntilRunning()
|
|
153
|
-
const index = nextIndex++
|
|
154
|
-
if (index >= links.length) return
|
|
155
|
-
if (this.#chunks.has(index)) continue
|
|
156
|
-
const encoded = await this.#fetch(links[index].hash, index)
|
|
157
|
-
if (this.#stopped) return
|
|
158
|
-
if (!encoded) throw new Error(`Missing chunk ${index + 1}/${links.length}`)
|
|
159
|
-
if (!(await this.#verify(encoded, links[index].hash))) throw new Error(`Hash mismatch for chunk ${index + 1}`)
|
|
160
|
-
const chunkNode = await this.#decode(encoded)
|
|
161
|
-
const content = chunkNode.content || new Uint8Array()
|
|
162
|
-
this.#chunks.set(index, content)
|
|
163
|
-
if (this.#pin) await this.#pin(links[index].hash, encoded)
|
|
164
|
-
this.#emit()
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
await Promise.all(Array.from({ length: Math.min(this.#concurrency, links.length) }, () => worker()))
|
|
168
|
-
|
|
169
|
-
const output = new Uint8Array(this.#totalBytes)
|
|
170
|
-
let offset = 0
|
|
171
|
-
for (let index = 0; index < this.#totalChunks; index++) {
|
|
172
|
-
const chunk = this.#chunks.get(index)
|
|
173
|
-
if (!chunk) throw new Error(`Missing downloaded chunk ${index + 1}`)
|
|
174
|
-
output.set(chunk, offset)
|
|
175
|
-
offset += chunk.length
|
|
176
|
-
}
|
|
177
|
-
this.state = 'completed'
|
|
178
|
-
this.#resolve(offset === output.length ? output : output.slice(0, offset))
|
|
179
|
-
this.#emit()
|
|
180
|
-
} catch (error) {
|
|
181
|
-
if (this.state === 'cancelled') return
|
|
182
|
-
this.state = 'failed'
|
|
183
|
-
this.#stopped = true
|
|
184
|
-
this.error = error
|
|
185
|
-
this.#reject(error)
|
|
186
|
-
this.#emit()
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
#emit(): void {
|
|
191
|
-
const progress = this.progress
|
|
192
|
-
for (const listener of this.#listeners) listener(progress)
|
|
193
|
-
}
|
|
194
|
-
}
|
package/src/handlers/data.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { protoFor } from './../utils/utils.js'
|
|
2
|
-
|
|
3
|
-
const dataHandler = async (message) => {
|
|
4
|
-
if (!message) return
|
|
5
|
-
|
|
6
|
-
try {
|
|
7
|
-
const { data, id, from, peer } = message
|
|
8
|
-
const proto = await protoFor(data)
|
|
9
|
-
peernet._protoHandler({ id, proto }, peernet.connections[from] || peer, from)
|
|
10
|
-
} catch (error) {
|
|
11
|
-
console.error(error)
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export default dataHandler
|
package/src/handlers/message.js
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
export default class MessageHandler {
|
|
2
|
-
constructor(network) {
|
|
3
|
-
this.network = network
|
|
4
|
-
}
|
|
5
|
-
/**
|
|
6
|
-
* hash and sign message
|
|
7
|
-
*
|
|
8
|
-
* @param {object} message
|
|
9
|
-
* @param {Buffer} message.from peer id
|
|
10
|
-
* @param {Buffer} message.to peer id
|
|
11
|
-
* @param {string} message.data Peernet message
|
|
12
|
-
* (PeernetMessage excluded) encoded as a string
|
|
13
|
-
* @return message
|
|
14
|
-
*/
|
|
15
|
-
async hashAndSignMessage(message) {
|
|
16
|
-
const hash = await message.peernetHash
|
|
17
|
-
message.decoded.signature = globalThis.identity.sign(hash.buffer)
|
|
18
|
-
return message
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* @param {String} from - peer id
|
|
23
|
-
* @param {String} to - peer id
|
|
24
|
-
* @param {String|PeernetMessage} data - data encoded message string
|
|
25
|
-
* or the messageNode itself
|
|
26
|
-
*/
|
|
27
|
-
async prepareMessage(message) {
|
|
28
|
-
if (message.keys.includes('signature')) {
|
|
29
|
-
message = await this.hashAndSignMessage(message)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return message
|
|
33
|
-
}
|
|
34
|
-
}
|
package/src/identity.ts
DELETED
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
import MultiWallet from '@leofcoin/multi-wallet'
|
|
2
|
-
import base58 from '@vandeurenglenn/base58'
|
|
3
|
-
import type { base58String } from '@vandeurenglenn/base58'
|
|
4
|
-
import { encrypt, decrypt } from '@leofcoin/identity-utils'
|
|
5
|
-
import QrScanner from 'qr-scanner'
|
|
6
|
-
import qrcode from 'qrcode'
|
|
7
|
-
|
|
8
|
-
type StoredAccount = [name: string, externalAddress: string, internalAddress: string]
|
|
9
|
-
type AccountWallet = {
|
|
10
|
-
address: Promise<string>
|
|
11
|
-
sign: (hash: Uint8Array) => Promise<Uint8Array>
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const accountDerivationSchemes = [
|
|
15
|
-
{
|
|
16
|
-
name: 'current',
|
|
17
|
-
accountIndex: (index: number) => index + 1,
|
|
18
|
-
addressIndex: 1
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
name: 'legacy-v1',
|
|
22
|
-
accountIndex: (index: number) => index,
|
|
23
|
-
addressIndex: 0
|
|
24
|
-
}
|
|
25
|
-
] as const
|
|
26
|
-
|
|
27
|
-
export const resolveAccountWallets = async (
|
|
28
|
-
wallet: MultiWallet,
|
|
29
|
-
accounts: StoredAccount[]
|
|
30
|
-
): Promise<Map<string, AccountWallet>> => {
|
|
31
|
-
for (const scheme of accountDerivationSchemes) {
|
|
32
|
-
const wallets = new Map<string, AccountWallet>()
|
|
33
|
-
let matches = true
|
|
34
|
-
|
|
35
|
-
for (const [index, [, externalAddress, internalAddress]] of accounts.entries()) {
|
|
36
|
-
const account = wallet.account(scheme.accountIndex(index))
|
|
37
|
-
const external = (await account.external(scheme.addressIndex)) as unknown as AccountWallet
|
|
38
|
-
const internal = (await account.internal(scheme.addressIndex)) as unknown as AccountWallet
|
|
39
|
-
|
|
40
|
-
if ((await external.address) !== externalAddress || (await internal.address) !== internalAddress) {
|
|
41
|
-
matches = false
|
|
42
|
-
break
|
|
43
|
-
}
|
|
44
|
-
wallets.set(externalAddress, external)
|
|
45
|
-
wallets.set(internalAddress, internal)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (matches) return wallets
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
throw new Error('stored accounts do not match this identity under a supported derivation scheme')
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export default class Identity {
|
|
55
|
-
#wallet: MultiWallet
|
|
56
|
-
#accountWallets = new Map<string, AccountWallet>()
|
|
57
|
-
network
|
|
58
|
-
id: string
|
|
59
|
-
selectedAccount: string
|
|
60
|
-
|
|
61
|
-
constructor(network: string) {
|
|
62
|
-
this.network = network
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
get accounts(): Promise<[[name: string, externalAddress: string, internalAddress: string]]> {
|
|
66
|
-
return this.getAccounts()
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async getAccounts(): Promise<[[name: string, externalAddress: string, internalAddress: string]]> {
|
|
70
|
-
let accounts = await globalThis.walletStore.get('accounts')
|
|
71
|
-
accounts = new TextDecoder().decode(accounts)
|
|
72
|
-
return JSON.parse(accounts)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async load(password?: string, fresh = false): Promise<void> {
|
|
76
|
-
if (password && password.includes('.txt')) {
|
|
77
|
-
const { readFile } = await import('fs/promises')
|
|
78
|
-
try {
|
|
79
|
-
password = (await readFile(password)).toString()
|
|
80
|
-
} catch (error) {
|
|
81
|
-
console.error(error)
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
if (!password) {
|
|
85
|
-
// @ts-ignore
|
|
86
|
-
const importee: { default: () => Promise<string> } = await import('./prompts/password.js')
|
|
87
|
-
password = await importee.default()
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// LevelDB opens lazily. Touch both stores before freshIdentity clears them;
|
|
91
|
-
// clearing a brand-new, unopened store throws LEVEL_DATABASE_NOT_OPEN.
|
|
92
|
-
const [accountExists] = await Promise.all([
|
|
93
|
-
globalThis.accountStore.has('public'),
|
|
94
|
-
globalThis.walletStore.has('identity')
|
|
95
|
-
])
|
|
96
|
-
if (accountExists && !fresh) {
|
|
97
|
-
const pub = await globalThis.accountStore.get('public')
|
|
98
|
-
this.id = JSON.parse(new TextDecoder().decode(pub)).walletId
|
|
99
|
-
const selected = await globalThis.walletStore.get('selected-account')
|
|
100
|
-
this.selectedAccount = new TextDecoder().decode(selected)
|
|
101
|
-
} else {
|
|
102
|
-
const importee = await import(/* webpackChunkName: "generate-account" */ '@leofcoin/generate-account')
|
|
103
|
-
const { identity, accounts } = await importee.default(password, this.network)
|
|
104
|
-
if (fresh) {
|
|
105
|
-
await globalThis.accountStore.clear()
|
|
106
|
-
await globalThis.walletStore.clear()
|
|
107
|
-
}
|
|
108
|
-
await globalThis.accountStore.put('public', JSON.stringify({ walletId: identity.walletId }))
|
|
109
|
-
|
|
110
|
-
await globalThis.walletStore.put('version', String(1))
|
|
111
|
-
await globalThis.walletStore.put('accounts', JSON.stringify(accounts))
|
|
112
|
-
await globalThis.walletStore.put('selected-account', accounts[0][1])
|
|
113
|
-
await globalThis.walletStore.put('identity', JSON.stringify(identity))
|
|
114
|
-
|
|
115
|
-
this.selectedAccount = accounts[0][1]
|
|
116
|
-
this.id = identity.walletId
|
|
117
|
-
}
|
|
118
|
-
const identity = JSON.parse(new TextDecoder().decode(await globalThis.walletStore.get('identity')))
|
|
119
|
-
this.#wallet = new MultiWallet(this.network)
|
|
120
|
-
const multiWIF = await decrypt(password, base58.decode(identity.multiWIF))
|
|
121
|
-
await this.#wallet.fromMultiWif(multiWIF)
|
|
122
|
-
await this.#loadAccountWallets()
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
async #loadAccountWallets() {
|
|
126
|
-
const accounts = await this.getAccounts()
|
|
127
|
-
this.#accountWallets = await resolveAccountWallets(this.#wallet, accounts)
|
|
128
|
-
if (!this.#accountWallets.has(this.selectedAccount)) {
|
|
129
|
-
throw new Error(`selected account ${this.selectedAccount} is not part of this identity`)
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
selectAccount(account: string) {
|
|
134
|
-
if (!this.#accountWallets.has(account)) throw new Error(`unknown identity account ${account}`)
|
|
135
|
-
this.selectedAccount = account
|
|
136
|
-
return walletStore.put('selected-account', account)
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
sign(hash: Uint8Array) {
|
|
140
|
-
const wallet = this.#accountWallets.get(this.selectedAccount)
|
|
141
|
-
if (!wallet) throw new Error(`no signer available for selected account ${this.selectedAccount}`)
|
|
142
|
-
return wallet.sign(hash.subarray(0, 32))
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
lock(password: string) {
|
|
146
|
-
this.#wallet.lock(password)
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
unlock(password: string) {
|
|
150
|
-
this.#wallet.unlock(password)
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
async export(password: string) {
|
|
154
|
-
return this.#wallet.export(password)
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
async import(password, encrypted: base58String) {
|
|
158
|
-
await this.#wallet.import(password, encrypted)
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
async exportQR(password: string) {
|
|
162
|
-
const exported = await this.export(password)
|
|
163
|
-
return globalThis.navigator
|
|
164
|
-
? await qrcode.toDataURL(exported)
|
|
165
|
-
: await qrcode.toString(exported, { type: 'terminal' })
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
async importQR(image: File | Blob, password: string) {
|
|
169
|
-
const multiWIF = await QrScanner.default.scanImage(image)
|
|
170
|
-
return this.import(password, multiWIF)
|
|
171
|
-
}
|
|
172
|
-
}
|
package/src/messages/chat.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import proto from './../proto/chat-message.proto.js'
|
|
2
|
-
import { FormatInterface } from '@leofcoin/codec-format-interface'
|
|
3
|
-
|
|
4
|
-
export default class ChatMessage extends FormatInterface {
|
|
5
|
-
get messageName(): string {
|
|
6
|
-
return 'ChatMessage'
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
constructor(buffer: any) {
|
|
10
|
-
const name = 'chat-message'
|
|
11
|
-
super(buffer, proto, { name })
|
|
12
|
-
}
|
|
13
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import proto from './../proto/data-response.proto.js'
|
|
2
|
-
import { FormatInterface } from '@leofcoin/codec-format-interface'
|
|
3
|
-
|
|
4
|
-
export default class DataMessageResponse extends FormatInterface {
|
|
5
|
-
get messageName(): string {
|
|
6
|
-
return 'PeernetDataMessageResponse'
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
constructor(data: any) {
|
|
10
|
-
const name = 'peernet-data-response'
|
|
11
|
-
super(data, proto, { name })
|
|
12
|
-
}
|
|
13
|
-
}
|
package/src/messages/data.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import proto from './../proto/data.proto.js'
|
|
2
|
-
import { FormatInterface } from '@leofcoin/codec-format-interface'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* @extends {CodecFormat}
|
|
6
|
-
*/
|
|
7
|
-
export default class DataMessage extends FormatInterface {
|
|
8
|
-
get messageName(): string {
|
|
9
|
-
return 'PeernetDataMessage'
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* @param {Buffer|String|Object|DataMessage} data - The data needed to create the DataMessage
|
|
13
|
-
*/
|
|
14
|
-
constructor(data: any) {
|
|
15
|
-
super(data, proto, { name: 'peernet-data' })
|
|
16
|
-
}
|
|
17
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import proto from './../proto/dht-response.proto.js'
|
|
2
|
-
import { FormatInterface } from '@leofcoin/codec-format-interface'
|
|
3
|
-
|
|
4
|
-
export default class DHTMessageResponse extends FormatInterface {
|
|
5
|
-
get messageName(): string {
|
|
6
|
-
return 'PeernetDHTMessageResponse'
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
constructor(data: any) {
|
|
10
|
-
const name = 'peernet-dht-response'
|
|
11
|
-
super(data, proto, { name })
|
|
12
|
-
}
|
|
13
|
-
}
|
package/src/messages/dht.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import proto from './../proto/dht.proto.js'
|
|
2
|
-
import { FormatInterface } from '@leofcoin/codec-format-interface'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* @example `
|
|
6
|
-
new DHTMessage(hash, store)
|
|
7
|
-
// store = optional if not set, peernet checks every store
|
|
8
|
-
let message = new DHTMessage('hashmvbs124xcfd...', 'transaction')
|
|
9
|
-
message = new DHTMessage('hashmvbs124xcfd...', 'block')
|
|
10
|
-
`
|
|
11
|
-
*/
|
|
12
|
-
export default class DHTMessage extends FormatInterface {
|
|
13
|
-
get messageName(): string {
|
|
14
|
-
return 'PeernetDHTMessage'
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
constructor(data: any) {
|
|
18
|
-
const name = 'peernet-dht'
|
|
19
|
-
super(data, proto, { name })
|
|
20
|
-
}
|
|
21
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import proto from '../proto/file-link.proto.js'
|
|
2
|
-
import { FormatInterface } from '@leofcoin/codec-format-interface'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* @extends {CodecFormat}
|
|
6
|
-
*/
|
|
7
|
-
export default class PeernetFileLink extends FormatInterface {
|
|
8
|
-
get messageName(): string {
|
|
9
|
-
return 'PeernetFileLink'
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* @param {Buffer|String|Object|DataMessage} data - The data needed to create the DataMessage
|
|
13
|
-
*/
|
|
14
|
-
constructor(data: any) {
|
|
15
|
-
super(data, proto, { name: 'peernet-file-link' })
|
|
16
|
-
}
|
|
17
|
-
}
|
package/src/messages/file.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import proto from './../proto/file.proto.js'
|
|
2
|
-
import { FormatInterface } from '@leofcoin/codec-format-interface'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* @extends {CodecFormat}
|
|
6
|
-
*/
|
|
7
|
-
export default class PeernetFile extends FormatInterface {
|
|
8
|
-
get messageName(): string {
|
|
9
|
-
return 'PeernetFile'
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* @param {Buffer|String|Object|DataMessage} data - The data needed to create the DataMessage
|
|
13
|
-
*/
|
|
14
|
-
constructor(data: any) {
|
|
15
|
-
super(data, proto, { name: 'peernet-file' })
|
|
16
|
-
}
|
|
17
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import proto from '../proto/peer-response.proto.js'
|
|
2
|
-
import { FormatInterface } from '@leofcoin/codec-format-interface'
|
|
3
|
-
|
|
4
|
-
export default class PeerMessageResponse extends FormatInterface {
|
|
5
|
-
get messageName(): string {
|
|
6
|
-
return 'PeernetPeerMessageResponse'
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
constructor(data: any) {
|
|
10
|
-
const name = 'peernet-peer-response'
|
|
11
|
-
super(data, proto, { name })
|
|
12
|
-
}
|
|
13
|
-
}
|
package/src/messages/peer.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import proto from '../proto/peer.proto.js'
|
|
2
|
-
import { FormatInterface } from '@leofcoin/codec-format-interface'
|
|
3
|
-
|
|
4
|
-
export default class PeerMessage extends FormatInterface {
|
|
5
|
-
get messageName(): string {
|
|
6
|
-
return 'PeernetPeerMessage'
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
constructor(data: any) {
|
|
10
|
-
const name = 'peernet-peer'
|
|
11
|
-
super(data, proto, { name })
|
|
12
|
-
}
|
|
13
|
-
}
|
package/src/messages/peernet.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import proto from './../proto/peernet.proto.js'
|
|
2
|
-
import { FormatInterface } from '@leofcoin/codec-format-interface'
|
|
3
|
-
|
|
4
|
-
export default class PeernetMessage extends FormatInterface {
|
|
5
|
-
get messageName(): string {
|
|
6
|
-
return 'PeernetMessage'
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
constructor(buffer: any) {
|
|
10
|
-
const name = 'peernet-message'
|
|
11
|
-
super(buffer, proto, { name })
|
|
12
|
-
}
|
|
13
|
-
}
|
package/src/messages/ps.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import proto from './../proto/ps.proto.js'
|
|
2
|
-
import { FormatInterface } from '@leofcoin/codec-format-interface'
|
|
3
|
-
|
|
4
|
-
export default class PsMessage extends FormatInterface {
|
|
5
|
-
get messageName(): string {
|
|
6
|
-
return 'PsMessage'
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
constructor(buffer: any) {
|
|
10
|
-
const name = 'peernet-ps'
|
|
11
|
-
super(buffer, proto, { name })
|
|
12
|
-
}
|
|
13
|
-
}
|