@mblaney/holster 2.0.1 → 2.1.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 +37 -6
- package/build/holster.js +3 -3
- package/build/holster.js.map +3 -3
- package/dist/holster.js +3 -3
- package/dist/holster.js.map +3 -3
- package/package.json +1 -1
- package/src/ham.js +10 -3
- package/src/holster.js +36 -25
- package/src/radisk.js +26 -28
- package/src/store.js +30 -17
- package/src/user.js +92 -101
- package/src/wire.js +325 -125
- package/ts/src/array.ts +10 -13
- package/ts/src/buffer.ts +40 -20
- package/ts/src/get.ts +5 -6
- package/ts/src/ham.ts +47 -28
- package/ts/src/holster.ts +277 -132
- package/ts/src/index.ts +14 -15
- package/ts/src/radisk.ts +122 -51
- package/ts/src/radix.ts +50 -34
- package/ts/src/schemas.ts +44 -24
- package/ts/src/sea-utils.ts +7 -11
- package/ts/src/sea.ts +51 -54
- package/ts/src/store.ts +76 -58
- package/ts/src/user.ts +136 -113
- package/ts/src/utils.ts +36 -11
- package/ts/src/wire.ts +477 -248
package/ts/src/sea-utils.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import SafeBuffer from "./buffer.ts"
|
|
7
|
-
import type {
|
|
7
|
+
import type {JWK} from "./schemas.ts"
|
|
8
8
|
|
|
9
9
|
const isNode = typeof process !== "undefined" && process.versions?.node != null
|
|
10
10
|
|
|
@@ -64,23 +64,20 @@ export const jwk = (pub: string, priv?: string): JWK => {
|
|
|
64
64
|
*/
|
|
65
65
|
export const sha256 = async (data: unknown): Promise<SeaArray> => {
|
|
66
66
|
const hash = await subtle.digest(
|
|
67
|
-
{
|
|
68
|
-
new TextEncoder().encode(stringify(data))
|
|
67
|
+
{name: "SHA-256"},
|
|
68
|
+
new TextEncoder().encode(stringify(data)),
|
|
69
69
|
)
|
|
70
70
|
return SafeBuffer.from(new Uint8Array(hash))
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
|
-
* Derive AES key from password and salt
|
|
74
|
+
* Derive AES key from password and salt
|
|
75
75
|
*/
|
|
76
|
-
export const aeskey = async (
|
|
77
|
-
key: string,
|
|
78
|
-
salt: SeaArray
|
|
79
|
-
) => {
|
|
76
|
+
export const aeskey = async (key: string, salt: SeaArray) => {
|
|
80
77
|
const combined = key + salt.toString("utf8")
|
|
81
78
|
const hashResult = await sha256(combined)
|
|
82
79
|
const jwkKey = keyToJwk(hashResult)
|
|
83
|
-
return await subtle.importKey("jwk", jwkKey, {
|
|
80
|
+
return await subtle.importKey("jwk", jwkKey, {name: "AES-GCM"}, false, [
|
|
84
81
|
"encrypt",
|
|
85
82
|
"decrypt",
|
|
86
83
|
])
|
|
@@ -95,9 +92,8 @@ const keyToJwk = (key: SeaArray): JWK => {
|
|
|
95
92
|
.replace(/\+/g, "-")
|
|
96
93
|
.replace(/\//g, "_")
|
|
97
94
|
.replace(/=/g, "")
|
|
98
|
-
return {
|
|
95
|
+
return {kty: "oct", k: k, ext: false, alg: "A256GCM"}
|
|
99
96
|
}
|
|
100
97
|
|
|
101
98
|
// Re-export SeaArray type for convenience
|
|
102
99
|
import type SeaArray from "./array.ts"
|
|
103
|
-
|
package/ts/src/sea.ts
CHANGED
|
@@ -3,14 +3,10 @@
|
|
|
3
3
|
* Cryptographic functions for signing, verification, encryption, and decryption
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import {userPublicKey, userSignature} from "./utils.ts"
|
|
7
7
|
import * as utils from "./sea-utils.ts"
|
|
8
8
|
import SafeBuffer from "./buffer.ts"
|
|
9
|
-
import type {
|
|
10
|
-
UserPair,
|
|
11
|
-
EncryptedData,
|
|
12
|
-
SignedData,
|
|
13
|
-
} from "./schemas.ts"
|
|
9
|
+
import type {UserPair, EncryptedData, SignedData} from "./schemas.ts"
|
|
14
10
|
|
|
15
11
|
type Callback<T> = (result: T) => void
|
|
16
12
|
|
|
@@ -24,7 +20,7 @@ const SEA = {
|
|
|
24
20
|
pair: async (cb?: Callback<UserPair>): Promise<UserPair> => {
|
|
25
21
|
// ECDSA keys for signing/verifying
|
|
26
22
|
const ecdsa = await utils.subtle
|
|
27
|
-
.generateKey({
|
|
23
|
+
.generateKey({name: "ECDSA", namedCurve: "P-256"}, true, [
|
|
28
24
|
"sign",
|
|
29
25
|
"verify",
|
|
30
26
|
])
|
|
@@ -38,7 +34,7 @@ const SEA = {
|
|
|
38
34
|
|
|
39
35
|
// ECDH keys for encryption/decryption
|
|
40
36
|
const ecdh = await utils.subtle
|
|
41
|
-
.generateKey({
|
|
37
|
+
.generateKey({name: "ECDH", namedCurve: "P-256"}, true, ["deriveKey"])
|
|
42
38
|
.then(async keys => {
|
|
43
39
|
const pub = await utils.subtle.exportKey("jwk", keys.publicKey)
|
|
44
40
|
return {
|
|
@@ -63,14 +59,14 @@ const SEA = {
|
|
|
63
59
|
encrypt: async (
|
|
64
60
|
data: unknown,
|
|
65
61
|
pair: Partial<UserPair> | null,
|
|
66
|
-
cb?: Callback<EncryptedData | null
|
|
62
|
+
cb?: Callback<EncryptedData | null>,
|
|
67
63
|
): Promise<EncryptedData | null> => {
|
|
68
64
|
if (!pair || !pair.epriv) {
|
|
69
65
|
if (cb) cb(null)
|
|
70
66
|
return null
|
|
71
67
|
}
|
|
72
68
|
|
|
73
|
-
const rand = {
|
|
69
|
+
const rand = {s: utils.random(9), iv: utils.random(15)}
|
|
74
70
|
const ct = await utils.aeskey(pair.epriv, rand.s).then(aes => {
|
|
75
71
|
return utils.subtle.encrypt(
|
|
76
72
|
{
|
|
@@ -78,7 +74,7 @@ const SEA = {
|
|
|
78
74
|
iv: new Uint8Array(rand.iv),
|
|
79
75
|
},
|
|
80
76
|
aes,
|
|
81
|
-
new TextEncoder().encode(utils.stringify(data))
|
|
77
|
+
new TextEncoder().encode(utils.stringify(data)),
|
|
82
78
|
)
|
|
83
79
|
})
|
|
84
80
|
const enc: EncryptedData = {
|
|
@@ -96,7 +92,7 @@ const SEA = {
|
|
|
96
92
|
decrypt: async (
|
|
97
93
|
enc: Partial<EncryptedData> | null,
|
|
98
94
|
pair: Partial<UserPair> | null,
|
|
99
|
-
cb?: Callback<unknown | null
|
|
95
|
+
cb?: Callback<unknown | null>,
|
|
100
96
|
): Promise<unknown | null> => {
|
|
101
97
|
if (!enc || !enc.ct || !enc.iv || !enc.s || !pair || !pair.epriv) {
|
|
102
98
|
if (cb) cb(null)
|
|
@@ -117,7 +113,7 @@ const SEA = {
|
|
|
117
113
|
tagLength: 128,
|
|
118
114
|
},
|
|
119
115
|
aes,
|
|
120
|
-
new Uint8Array(data.ct)
|
|
116
|
+
new Uint8Array(data.ct),
|
|
121
117
|
)
|
|
122
118
|
})
|
|
123
119
|
const dec = utils.parse(new TextDecoder("utf8").decode(ct))
|
|
@@ -136,7 +132,7 @@ const SEA = {
|
|
|
136
132
|
verify: async (
|
|
137
133
|
data: string | SignedData,
|
|
138
134
|
pair: Partial<UserPair> | null,
|
|
139
|
-
cb?: Callback<unknown | null
|
|
135
|
+
cb?: Callback<unknown | null>,
|
|
140
136
|
): Promise<unknown | null> => {
|
|
141
137
|
if (!pair || !pair.pub) {
|
|
142
138
|
if (cb) cb(null)
|
|
@@ -149,9 +145,9 @@ const SEA = {
|
|
|
149
145
|
const key = await utils.subtle.importKey(
|
|
150
146
|
"jwk",
|
|
151
147
|
utils.jwk(pair.pub),
|
|
152
|
-
{
|
|
148
|
+
{name: "ECDSA", namedCurve: "P-256"},
|
|
153
149
|
false,
|
|
154
|
-
["verify"]
|
|
150
|
+
["verify"],
|
|
155
151
|
)
|
|
156
152
|
|
|
157
153
|
let msg: string | Record<string, unknown> = {}
|
|
@@ -162,13 +158,13 @@ const SEA = {
|
|
|
162
158
|
// (which should not be part of signature, so not verified)
|
|
163
159
|
for (const k of Object.keys(signed.m).sort()) {
|
|
164
160
|
if (k !== "_" && k !== userPublicKey && k !== userSignature) {
|
|
165
|
-
(msg as Record<string, unknown>)[k] = signed.m[k]
|
|
161
|
+
;(msg as Record<string, unknown>)[k] = signed.m[k]
|
|
166
162
|
}
|
|
167
163
|
}
|
|
168
164
|
}
|
|
169
165
|
const hash = await utils.sha256(msg)
|
|
170
166
|
const sig = new Uint8Array(SafeBuffer.from(signed.s, "base64"))
|
|
171
|
-
const alg = {
|
|
167
|
+
const alg = {name: "ECDSA", hash: {name: "SHA-256"}}
|
|
172
168
|
if (await utils.subtle.verify(alg, key, sig, new Uint8Array(hash))) {
|
|
173
169
|
const verified = utils.parse(signed.m as string)
|
|
174
170
|
if (cb) cb(verified)
|
|
@@ -185,7 +181,7 @@ const SEA = {
|
|
|
185
181
|
sign: async (
|
|
186
182
|
data: string | Record<string, unknown>,
|
|
187
183
|
pair: Partial<UserPair> | null,
|
|
188
|
-
cb?: Callback<SignedData | null
|
|
184
|
+
cb?: Callback<SignedData | null>,
|
|
189
185
|
): Promise<SignedData | null> => {
|
|
190
186
|
if (!pair || !pair.pub || !pair.priv) {
|
|
191
187
|
if (cb) cb(null)
|
|
@@ -199,21 +195,21 @@ const SEA = {
|
|
|
199
195
|
const check = utils.parse(data as never) as Record<string, unknown>
|
|
200
196
|
for (const k of Object.keys(check).sort()) {
|
|
201
197
|
if (k !== "_" && k !== userPublicKey && k !== userSignature) {
|
|
202
|
-
(msg as Record<string, unknown>)[k] = check[k]
|
|
198
|
+
;(msg as Record<string, unknown>)[k] = check[k]
|
|
203
199
|
}
|
|
204
200
|
}
|
|
205
201
|
}
|
|
206
202
|
const hash = await utils.sha256(msg)
|
|
207
203
|
const jwk = utils.jwk(pair.pub, pair.priv)
|
|
208
|
-
const alg = {
|
|
204
|
+
const alg = {name: "ECDSA", namedCurve: "P-256"}
|
|
209
205
|
const sig = await utils.subtle
|
|
210
206
|
.importKey("jwk", jwk, alg, false, ["sign"])
|
|
211
207
|
.then(key =>
|
|
212
208
|
utils.subtle.sign(
|
|
213
|
-
{
|
|
209
|
+
{name: "ECDSA", hash: {name: "SHA-256"}},
|
|
214
210
|
key,
|
|
215
|
-
new Uint8Array(hash)
|
|
216
|
-
)
|
|
211
|
+
new Uint8Array(hash),
|
|
212
|
+
),
|
|
217
213
|
)
|
|
218
214
|
const signed: SignedData = {
|
|
219
215
|
m: msg,
|
|
@@ -230,7 +226,7 @@ const SEA = {
|
|
|
230
226
|
signTimestamp: async (
|
|
231
227
|
timestamp: number,
|
|
232
228
|
pair: Partial<UserPair> | null,
|
|
233
|
-
cb?: Callback<string | null
|
|
229
|
+
cb?: Callback<string | null>,
|
|
234
230
|
): Promise<string | null> => {
|
|
235
231
|
if (!pair || !pair.pub || !pair.priv) {
|
|
236
232
|
if (cb) cb(null)
|
|
@@ -241,16 +237,16 @@ const SEA = {
|
|
|
241
237
|
const timestampStr = timestamp.toString()
|
|
242
238
|
const hash = await utils.sha256(timestampStr)
|
|
243
239
|
const jwk = utils.jwk(pair.pub, pair.priv)
|
|
244
|
-
const alg = {
|
|
240
|
+
const alg = {name: "ECDSA", namedCurve: "P-256"}
|
|
245
241
|
|
|
246
242
|
const sig = await utils.subtle
|
|
247
243
|
.importKey("jwk", jwk, alg, false, ["sign"])
|
|
248
244
|
.then(key =>
|
|
249
245
|
utils.subtle.sign(
|
|
250
|
-
{
|
|
246
|
+
{name: "ECDSA", hash: {name: "SHA-256"}},
|
|
251
247
|
key,
|
|
252
|
-
new Uint8Array(hash)
|
|
253
|
-
)
|
|
248
|
+
new Uint8Array(hash),
|
|
249
|
+
),
|
|
254
250
|
)
|
|
255
251
|
|
|
256
252
|
const signature = SafeBuffer.from(sig, "binary").toString("base64")
|
|
@@ -266,7 +262,7 @@ const SEA = {
|
|
|
266
262
|
timestamp: number,
|
|
267
263
|
signature: string,
|
|
268
264
|
publicKey: string,
|
|
269
|
-
cb?: Callback<boolean
|
|
265
|
+
cb?: Callback<boolean>,
|
|
270
266
|
): Promise<boolean> => {
|
|
271
267
|
if (!publicKey || !timestamp || !signature) {
|
|
272
268
|
if (cb) cb(false)
|
|
@@ -281,25 +277,27 @@ const SEA = {
|
|
|
281
277
|
const key = await utils.subtle.importKey(
|
|
282
278
|
"jwk",
|
|
283
279
|
utils.jwk(publicKey),
|
|
284
|
-
{
|
|
280
|
+
{name: "ECDSA", namedCurve: "P-256"},
|
|
285
281
|
false,
|
|
286
|
-
["verify"]
|
|
282
|
+
["verify"],
|
|
287
283
|
)
|
|
288
284
|
|
|
289
285
|
const sig = new Uint8Array(SafeBuffer.from(signature, "base64"))
|
|
290
|
-
const alg = {
|
|
286
|
+
const alg = {name: "ECDSA", hash: {name: "SHA-256"}}
|
|
291
287
|
|
|
292
288
|
const isValid = await utils.subtle.verify(
|
|
293
289
|
alg,
|
|
294
290
|
key,
|
|
295
291
|
sig,
|
|
296
|
-
new Uint8Array(hash)
|
|
292
|
+
new Uint8Array(hash),
|
|
297
293
|
)
|
|
298
294
|
|
|
299
295
|
if (cb) cb(isValid)
|
|
300
296
|
return isValid
|
|
301
297
|
} catch (err) {
|
|
302
|
-
console.log(
|
|
298
|
+
console.log(
|
|
299
|
+
`error verifying timestamp signature: ${(err as Error).message}`,
|
|
300
|
+
)
|
|
303
301
|
if (cb) cb(false)
|
|
304
302
|
return false
|
|
305
303
|
}
|
|
@@ -311,13 +309,13 @@ const SEA = {
|
|
|
311
309
|
work: async (
|
|
312
310
|
data: unknown,
|
|
313
311
|
salt?: unknown,
|
|
314
|
-
cb?: Callback<{
|
|
315
|
-
): Promise<{
|
|
312
|
+
cb?: Callback<{epriv: string}>,
|
|
313
|
+
): Promise<{epriv: string}> => {
|
|
316
314
|
let _cb = cb
|
|
317
315
|
let _salt = salt
|
|
318
316
|
|
|
319
317
|
if (typeof salt === "function") {
|
|
320
|
-
_cb = salt as Callback<{
|
|
318
|
+
_cb = salt as Callback<{epriv: string}>
|
|
321
319
|
_salt = undefined
|
|
322
320
|
}
|
|
323
321
|
if (typeof _salt === "undefined") _salt = utils.random(9)
|
|
@@ -325,19 +323,19 @@ const SEA = {
|
|
|
325
323
|
const key = await utils.subtle.importKey(
|
|
326
324
|
"raw",
|
|
327
325
|
new TextEncoder().encode(utils.stringify(data)),
|
|
328
|
-
{
|
|
326
|
+
{name: "PBKDF2"},
|
|
329
327
|
false,
|
|
330
|
-
["deriveBits"]
|
|
328
|
+
["deriveBits"],
|
|
331
329
|
)
|
|
332
330
|
const alg = {
|
|
333
331
|
name: "PBKDF2",
|
|
334
332
|
iterations: 100000,
|
|
335
333
|
salt: new TextEncoder().encode(_salt as string),
|
|
336
|
-
hash: {
|
|
334
|
+
hash: {name: "SHA-256"},
|
|
337
335
|
}
|
|
338
336
|
const work = await utils.subtle.deriveBits(alg, key, 512)
|
|
339
337
|
// Use "pair" format so that work can be used as epriv by decrypt
|
|
340
|
-
const pair = {
|
|
338
|
+
const pair = {epriv: SafeBuffer.from(work, "binary").toString("base64")}
|
|
341
339
|
if (_cb) _cb(pair)
|
|
342
340
|
return pair
|
|
343
341
|
},
|
|
@@ -348,41 +346,40 @@ const SEA = {
|
|
|
348
346
|
secret: async (
|
|
349
347
|
to: Partial<UserPair> | null,
|
|
350
348
|
from: Partial<UserPair> | null,
|
|
351
|
-
cb?: Callback<{
|
|
352
|
-
): Promise<{
|
|
349
|
+
cb?: Callback<{epriv: string} | null>,
|
|
350
|
+
): Promise<{epriv: string} | null> => {
|
|
353
351
|
if (!to || !to.epub || !from || !from.epub || !from.epriv) {
|
|
354
352
|
if (cb) cb(null)
|
|
355
353
|
return null
|
|
356
354
|
}
|
|
357
355
|
|
|
358
|
-
const alg = {
|
|
356
|
+
const alg = {name: "ECDH", namedCurve: "P-256"}
|
|
359
357
|
const pub = utils.jwk(to.epub)
|
|
360
358
|
const pubKey = await utils.subtle.importKey("jwk", pub, alg, true, [])
|
|
361
359
|
const priv = utils.jwk(from.epub, from.epriv)
|
|
362
360
|
// utils.jwk provides default key_ops but it shouldn't be used here
|
|
363
|
-
delete (priv as {
|
|
361
|
+
delete (priv as {key_ops?: unknown}).key_ops
|
|
364
362
|
const derived = await utils.subtle
|
|
365
363
|
.importKey("jwk", priv, alg, false, ["deriveBits"])
|
|
366
364
|
.then(async key => {
|
|
367
365
|
const derivedBits = await utils.subtle.deriveBits(
|
|
368
|
-
{
|
|
366
|
+
{public: pubKey, name: "ECDH", namedCurve: "P-256"} as never,
|
|
369
367
|
key,
|
|
370
|
-
256
|
|
368
|
+
256,
|
|
371
369
|
)
|
|
372
370
|
const derivedKey = await utils.subtle.importKey(
|
|
373
371
|
"raw",
|
|
374
372
|
new Uint8Array(derivedBits),
|
|
375
|
-
{
|
|
373
|
+
{name: "AES-GCM", length: 256},
|
|
376
374
|
true,
|
|
377
|
-
["encrypt", "decrypt"]
|
|
375
|
+
["encrypt", "decrypt"],
|
|
378
376
|
)
|
|
379
|
-
return utils.subtle.exportKey("jwk", derivedKey).then(({
|
|
377
|
+
return utils.subtle.exportKey("jwk", derivedKey).then(({k}) => k!)
|
|
380
378
|
})
|
|
381
379
|
// Use "pair" format so that secret can be used as epriv by encrypt
|
|
382
|
-
if (cb) cb({
|
|
383
|
-
return {
|
|
380
|
+
if (cb) cb({epriv: derived})
|
|
381
|
+
return {epriv: derived}
|
|
384
382
|
},
|
|
385
383
|
}
|
|
386
384
|
|
|
387
385
|
export default SEA
|
|
388
|
-
|
package/ts/src/store.ts
CHANGED
|
@@ -18,9 +18,7 @@ import type {
|
|
|
18
18
|
const isNode = typeof process !== "undefined" && process.versions?.node != null
|
|
19
19
|
|
|
20
20
|
// Dynamic import for Node.js fs module - won't execute in browser/service worker
|
|
21
|
-
const fs = isNode
|
|
22
|
-
? await import(/*webpackIgnore: true*/ "node:fs")
|
|
23
|
-
: undefined
|
|
21
|
+
const fs = isNode ? await import(/*webpackIgnore: true*/ "node:fs") : undefined
|
|
24
22
|
|
|
25
23
|
// ASCII character for enquiry
|
|
26
24
|
const enq = String.fromCharCode(5)
|
|
@@ -41,39 +39,49 @@ const fileSystem = (opt: StoreOptions): FileSystemInterface => {
|
|
|
41
39
|
|
|
42
40
|
return {
|
|
43
41
|
get: (file, cb) => {
|
|
44
|
-
fs!.readFile(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
42
|
+
fs!.readFile(
|
|
43
|
+
dir + "/" + file,
|
|
44
|
+
(err: NodeJS.ErrnoException | null, data?: Buffer) => {
|
|
45
|
+
if (err) {
|
|
46
|
+
if ((err as {code?: string}).code === "ENOENT") {
|
|
47
|
+
cb()
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
console.log("fs.readFile error:", err)
|
|
49
51
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
})
|
|
52
|
+
const dataStr = data ? data.toString() : undefined
|
|
53
|
+
cb(err as never, dataStr)
|
|
54
|
+
},
|
|
55
|
+
)
|
|
55
56
|
},
|
|
56
57
|
put: (file, data, cb) => {
|
|
57
58
|
const tmp = file + "." + utils.text.random(9) + ".tmp"
|
|
58
|
-
fs!.writeFile(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
59
|
+
fs!.writeFile(
|
|
60
|
+
tmp,
|
|
61
|
+
data as string,
|
|
62
|
+
(err: NodeJS.ErrnoException | null) => {
|
|
63
|
+
if (err) {
|
|
64
|
+
console.log("fs.writeFile error:", err)
|
|
65
|
+
cb(err as never)
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
fs!.rename(tmp, dir + "/" + file, cb as never)
|
|
69
|
+
},
|
|
70
|
+
)
|
|
66
71
|
},
|
|
67
72
|
list: cb => {
|
|
68
|
-
fs!.readdir(
|
|
69
|
-
|
|
70
|
-
|
|
73
|
+
fs!.readdir(
|
|
74
|
+
dir,
|
|
75
|
+
(err: NodeJS.ErrnoException | null, files?: string[]) => {
|
|
76
|
+
if (err) {
|
|
77
|
+
console.log("fs.readdir error:", err)
|
|
78
|
+
cb()
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
if (files) files.forEach(cb)
|
|
71
82
|
cb()
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (files) files.forEach(cb)
|
|
75
|
-
cb()
|
|
76
|
-
})
|
|
83
|
+
},
|
|
84
|
+
)
|
|
77
85
|
},
|
|
78
86
|
}
|
|
79
87
|
}
|
|
@@ -122,7 +130,10 @@ const fileSystem = (opt: StoreOptions): FileSystemInterface => {
|
|
|
122
130
|
|
|
123
131
|
return {
|
|
124
132
|
get: (file, cb) => {
|
|
125
|
-
const _get = (
|
|
133
|
+
const _get = (
|
|
134
|
+
file: string,
|
|
135
|
+
cb: (err?: string | null, data?: string) => void,
|
|
136
|
+
) => {
|
|
126
137
|
const tx = db.transaction([dir], "readonly")
|
|
127
138
|
const req = tx.objectStore(dir).get(file)
|
|
128
139
|
req.onerror = () => {
|
|
@@ -146,7 +157,7 @@ const fileSystem = (opt: StoreOptions): FileSystemInterface => {
|
|
|
146
157
|
const _put = (
|
|
147
158
|
file: string,
|
|
148
159
|
data: string,
|
|
149
|
-
cb: (err?: string | null) => void
|
|
160
|
+
cb: (err?: string | null) => void,
|
|
150
161
|
) => {
|
|
151
162
|
const tx = db.transaction([dir], "readwrite")
|
|
152
163
|
const req = tx.objectStore(dir).put(data, file)
|
|
@@ -209,11 +220,7 @@ const fileSystem = (opt: StoreOptions): FileSystemInterface => {
|
|
|
209
220
|
}
|
|
210
221
|
|
|
211
222
|
export interface StoreInterface {
|
|
212
|
-
get: (
|
|
213
|
-
lex: Lex,
|
|
214
|
-
cb: (err?: string | null, graph?: Graph) => void,
|
|
215
|
-
_opt?: { secure?: boolean }
|
|
216
|
-
) => void
|
|
223
|
+
get: (lex: Lex, cb: (err?: string | null, graph?: Graph) => void) => void
|
|
217
224
|
put: (graph: Graph, cb: (err?: string | null) => void) => void
|
|
218
225
|
}
|
|
219
226
|
|
|
@@ -227,7 +234,7 @@ const Store = (opt?: StoreOptions): StoreInterface => {
|
|
|
227
234
|
const radisk = Radisk(options as never)
|
|
228
235
|
|
|
229
236
|
return {
|
|
230
|
-
get: (lex, cb
|
|
237
|
+
get: (lex, cb) => {
|
|
231
238
|
if (!lex || !utils.obj.is(lex)) {
|
|
232
239
|
cb("lex required")
|
|
233
240
|
return
|
|
@@ -237,42 +244,56 @@ const Store = (opt?: StoreOptions): StoreInterface => {
|
|
|
237
244
|
return
|
|
238
245
|
}
|
|
239
246
|
|
|
240
|
-
const opts = _opt || {}
|
|
241
247
|
const soul = lex["#"]
|
|
242
|
-
const key =
|
|
243
|
-
!opts.secure && typeof lex["."] === "string" ? lex["."] : ""
|
|
248
|
+
const key = typeof lex["."] === "string" ? lex["."] : ""
|
|
244
249
|
let node: GraphNode | undefined
|
|
245
250
|
const signatures: Record<string, string> = {}
|
|
246
251
|
|
|
247
|
-
const each = (value: EncodedValue,
|
|
248
|
-
|
|
249
|
-
if (key !== utils.userPublicKey && !utils.match(lex["."], key)) return
|
|
252
|
+
const each = (value: EncodedValue, k: string): void => {
|
|
253
|
+
if (k !== utils.userPublicKey && !utils.match(lex["."], k)) return
|
|
250
254
|
|
|
251
|
-
if (!node) node = {
|
|
252
|
-
node[
|
|
253
|
-
node._[">"][
|
|
255
|
+
if (!node) node = {_: {"#": soul, ">": {}}}
|
|
256
|
+
node[k] = value[0]
|
|
257
|
+
node._[">"][k] = value[1]
|
|
254
258
|
// If signature is present, store it in _["s"]
|
|
255
259
|
if (value.length === 3 && value[2]) {
|
|
256
260
|
signatures[value[1].toString()] = value[2]
|
|
257
261
|
}
|
|
258
262
|
}
|
|
259
263
|
|
|
264
|
+
// When doing a targeted property lookup, also fetch userPublicKey in
|
|
265
|
+
// parallel so ham.js can verify signatures regardless of secure mode.
|
|
266
|
+
// Full-soul scans (key="") already surface it via Radix.map.
|
|
267
|
+
const needsPubKey = key !== "" && key !== utils.userPublicKey
|
|
268
|
+
let pending = needsPubKey ? 2 : 1
|
|
269
|
+
let cbErr: string | null = null
|
|
270
|
+
const finish = (err: string | null | undefined): void => {
|
|
271
|
+
if (err && !cbErr) cbErr = err
|
|
272
|
+
if (--pending > 0) return
|
|
273
|
+
const graph: Graph = {[soul]: node!}
|
|
274
|
+
if (node && Object.keys(signatures).length > 0) {
|
|
275
|
+
node._["s"] = signatures
|
|
276
|
+
}
|
|
277
|
+
cb(cbErr, graph)
|
|
278
|
+
}
|
|
279
|
+
|
|
260
280
|
radisk(soul + enq + key, (err, value) => {
|
|
261
|
-
let graph: Graph | undefined
|
|
262
281
|
if (utils.obj.is(value)) {
|
|
263
282
|
Radix.map(value as never, each)
|
|
264
283
|
if (!node) each(value as never, key)
|
|
265
|
-
graph = { [soul]: node! }
|
|
266
284
|
} else if (value) {
|
|
267
285
|
each(value as EncodedValue, key)
|
|
268
|
-
graph = { [soul]: node! }
|
|
269
|
-
}
|
|
270
|
-
// Only add _["s"] if we found any signatures
|
|
271
|
-
if (graph && graph[soul] && Object.keys(signatures).length > 0) {
|
|
272
|
-
graph[soul]._["s"] = signatures
|
|
273
286
|
}
|
|
274
|
-
|
|
287
|
+
finish(err)
|
|
275
288
|
})
|
|
289
|
+
|
|
290
|
+
if (needsPubKey) {
|
|
291
|
+
radisk(soul + enq + utils.userPublicKey, (err, value) => {
|
|
292
|
+
if (value && !utils.obj.is(value))
|
|
293
|
+
each(value as EncodedValue, utils.userPublicKey)
|
|
294
|
+
finish(err)
|
|
295
|
+
})
|
|
296
|
+
}
|
|
276
297
|
},
|
|
277
298
|
put: (graph, cb) => {
|
|
278
299
|
if (!graph) {
|
|
@@ -307,9 +328,7 @@ const Store = (opt?: StoreOptions): StoreInterface => {
|
|
|
307
328
|
const value = node[key]!
|
|
308
329
|
const state = node._[">"][key]!
|
|
309
330
|
const sig = node._["s"] && node._["s"][state]
|
|
310
|
-
const data: EncodedValue = sig
|
|
311
|
-
? [value, state, sig]
|
|
312
|
-
: [value, state]
|
|
331
|
+
const data: EncodedValue = sig ? [value, state, sig] : [value, state]
|
|
313
332
|
radisk(soul + enq + key, data, ack)
|
|
314
333
|
})
|
|
315
334
|
})
|
|
@@ -318,4 +337,3 @@ const Store = (opt?: StoreOptions): StoreInterface => {
|
|
|
318
337
|
}
|
|
319
338
|
|
|
320
339
|
export default Store
|
|
321
|
-
|