@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/package.json
CHANGED
package/src/ham.js
CHANGED
|
@@ -35,7 +35,14 @@ const Ham = (state, currentState, value, currentValue, signed = false) => {
|
|
|
35
35
|
return {incoming: true}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
Ham.mix = async (
|
|
38
|
+
Ham.mix = async (
|
|
39
|
+
change,
|
|
40
|
+
graph,
|
|
41
|
+
hasNode,
|
|
42
|
+
secure,
|
|
43
|
+
listen,
|
|
44
|
+
maxGraphSize = MAX_GRAPH_SIZE,
|
|
45
|
+
) => {
|
|
39
46
|
if (!change || typeof change !== "object") {
|
|
40
47
|
throw new TypeError("change must be an object")
|
|
41
48
|
}
|
|
@@ -216,7 +223,7 @@ Ham.mix = async (change, graph, hasNode, secure, listen) => {
|
|
|
216
223
|
}
|
|
217
224
|
|
|
218
225
|
const souls = Object.keys(graph)
|
|
219
|
-
if (souls.length >
|
|
226
|
+
if (souls.length > maxGraphSize) {
|
|
220
227
|
// Sort by oldest state timestamp and remove oldest entries
|
|
221
228
|
const soulsByAge = souls
|
|
222
229
|
.map(soul => {
|
|
@@ -225,7 +232,7 @@ Ham.mix = async (change, graph, hasNode, secure, listen) => {
|
|
|
225
232
|
return {soul, maxState}
|
|
226
233
|
})
|
|
227
234
|
.sort((a, b) => a.maxState - b.maxState)
|
|
228
|
-
const remove = soulsByAge.slice(0, souls.length -
|
|
235
|
+
const remove = soulsByAge.slice(0, souls.length - maxGraphSize)
|
|
229
236
|
remove.forEach(({soul}) => {
|
|
230
237
|
delete graph[soul]
|
|
231
238
|
if (hasNode) hasNode.delete(soul)
|
package/src/holster.js
CHANGED
|
@@ -64,28 +64,30 @@ const Holster = opt => {
|
|
|
64
64
|
delete msg.put[soul][utils.userPublicKey]
|
|
65
65
|
delete msg.put[soul][utils.userSignature]
|
|
66
66
|
// Resolve any rels on the node before returning to the user.
|
|
67
|
-
await Promise.all(
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
67
|
+
await Promise.all(
|
|
68
|
+
Object.keys(msg.put[soul]).map(async key => {
|
|
69
|
+
const id = utils.rel.is(msg.put[soul][key])
|
|
70
|
+
if (!id) return
|
|
71
|
+
const attemptRead = async (retries = 0) => {
|
|
72
|
+
const data = await new Promise(res => {
|
|
73
|
+
const _ctxid = utils.text.random()
|
|
74
|
+
const ctx = allctx.get(ctxid)
|
|
75
|
+
allctx.set(_ctxid, {
|
|
76
|
+
chain: [{item: null, soul: id}],
|
|
77
|
+
user: ctx ? ctx.user : null,
|
|
78
|
+
})
|
|
79
|
+
api(_ctxid).next(null, res, _opt)
|
|
77
80
|
})
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
if (data !== null || retries >= 5) {
|
|
82
|
+
return data
|
|
83
|
+
}
|
|
84
|
+
// Data not ready, retry after delay
|
|
85
|
+
await new Promise(resolve => setTimeout(resolve, 50))
|
|
86
|
+
return attemptRead(retries + 1)
|
|
82
87
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
msg.put[soul][key] = await attemptRead()
|
|
88
|
-
}))
|
|
88
|
+
msg.put[soul][key] = await attemptRead()
|
|
89
|
+
}),
|
|
90
|
+
)
|
|
89
91
|
ack(msg.put[soul])
|
|
90
92
|
} else {
|
|
91
93
|
// No data callback.
|
|
@@ -281,7 +283,11 @@ const Holster = opt => {
|
|
|
281
283
|
if (request._get) cb(null)
|
|
282
284
|
} else if (get && node) {
|
|
283
285
|
const sv = node._ && node._[">"]
|
|
284
|
-
if (
|
|
286
|
+
if (
|
|
287
|
+
sv &&
|
|
288
|
+
Object.keys(sv).length > 0 &&
|
|
289
|
+
typeof sv[item] === "undefined"
|
|
290
|
+
) {
|
|
285
291
|
// State vector has other properties but not this one — never written.
|
|
286
292
|
if (cb) cb(null)
|
|
287
293
|
} else {
|
|
@@ -666,8 +672,11 @@ const Holster = opt => {
|
|
|
666
672
|
chain: [{item: null, soul: id}],
|
|
667
673
|
user: ctx ? ctx.user : null,
|
|
668
674
|
})
|
|
669
|
-
api(_ctxid).next(
|
|
670
|
-
|
|
675
|
+
api(_ctxid).next(
|
|
676
|
+
null,
|
|
677
|
+
res,
|
|
678
|
+
retries === 0 ? utils.obj.put(_opt, "fast", true) : _opt,
|
|
679
|
+
)
|
|
671
680
|
})
|
|
672
681
|
if (data !== null || retries >= 5) {
|
|
673
682
|
retryCount = 0 // Reset retry counter on success
|
|
@@ -693,8 +702,10 @@ const Holster = opt => {
|
|
|
693
702
|
// miss and never fire again if the data doesn't change while
|
|
694
703
|
// the listener is attached, so we must poll for initial load.
|
|
695
704
|
const retry = () => {
|
|
696
|
-
const delay =
|
|
697
|
-
|
|
705
|
+
const delay = Math.min(
|
|
706
|
+
retryDelay * Math.pow(2, retryCount),
|
|
707
|
+
30000,
|
|
708
|
+
)
|
|
698
709
|
retryCount++
|
|
699
710
|
retryTimer = setTimeout(() => {
|
|
700
711
|
wire.get(
|
package/src/radisk.js
CHANGED
|
@@ -34,7 +34,7 @@ const Radisk = opt => {
|
|
|
34
34
|
if (!opt.write) opt.write = 1 // Wait time before write in milliseconds
|
|
35
35
|
if (!opt.size) opt.size = 1024 * 1024 // File size on disk, default 1MB
|
|
36
36
|
if (!opt.memoryLimit) opt.memoryLimit = 500 // Memory limit in MB
|
|
37
|
-
if (typeof opt.cache === "undefined") opt.cache = true
|
|
37
|
+
if (typeof opt.cache === "undefined") opt.cache = true // Used in tests
|
|
38
38
|
|
|
39
39
|
if (!opt.store) {
|
|
40
40
|
opt.log(
|
|
@@ -55,16 +55,6 @@ const Radisk = opt => {
|
|
|
55
55
|
return
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
// Performance logging
|
|
59
|
-
const perfLog = (operation, startTime, key, size) => {
|
|
60
|
-
const duration = Date.now() - startTime
|
|
61
|
-
if (duration > 3000) {
|
|
62
|
-
console.log(
|
|
63
|
-
`[RADISK-SLOW] ${operation}: ${duration}ms for ${key}${size ? ` (${size} bytes)` : ""}`,
|
|
64
|
-
)
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
58
|
// Memory monitoring and cache cleanup
|
|
69
59
|
const checkMemoryUsage = () => {
|
|
70
60
|
if (typeof process === "undefined" || !process.memoryUsage) return
|
|
@@ -129,7 +119,6 @@ const Radisk = opt => {
|
|
|
129
119
|
}
|
|
130
120
|
|
|
131
121
|
pendingReads.set(key, [cb])
|
|
132
|
-
const readStart = Date.now()
|
|
133
122
|
// If radisk.read genuinely hangs (fd leak, stuck I/O), recover
|
|
134
123
|
// callbacks as a cache miss so callers can fall through to the wire path.
|
|
135
124
|
// 30s is long enough to distinguish a hang from a slow-but-healthy read.
|
|
@@ -142,7 +131,6 @@ const Radisk = opt => {
|
|
|
142
131
|
}, 30000)
|
|
143
132
|
return radisk.read(key, (err, result) => {
|
|
144
133
|
clearTimeout(timeout)
|
|
145
|
-
perfLog("read", readStart, key)
|
|
146
134
|
const callbacks = pendingReads.get(key) || []
|
|
147
135
|
pendingReads.delete(key)
|
|
148
136
|
callbacks.forEach(callback => callback(err, result))
|
|
@@ -175,7 +163,6 @@ const Radisk = opt => {
|
|
|
175
163
|
return (radisk.thrash.more = true)
|
|
176
164
|
}
|
|
177
165
|
|
|
178
|
-
const thrashStart = Date.now()
|
|
179
166
|
clearTimeout(radisk.batch.timeout)
|
|
180
167
|
radisk.thrash.more = false
|
|
181
168
|
radisk.thrash.ing = true
|
|
@@ -191,7 +178,6 @@ const Radisk = opt => {
|
|
|
191
178
|
// file needs to be split.
|
|
192
179
|
if (++i > 1) return
|
|
193
180
|
|
|
194
|
-
perfLog("thrash", thrashStart, `batch-${batch.ed}`)
|
|
195
181
|
if (err) opt.log(err)
|
|
196
182
|
batch.acks.forEach(cb => cb(err))
|
|
197
183
|
radisk.thrash.at = null
|
|
@@ -279,7 +265,7 @@ const Radisk = opt => {
|
|
|
279
265
|
limit: "",
|
|
280
266
|
done: false,
|
|
281
267
|
count: 0,
|
|
282
|
-
halfKey: "",
|
|
268
|
+
halfKey: "", // soul ID at the ~opt.size/2 mark for a balanced split
|
|
283
269
|
halfOffset: 0, // write.text.length before that entry
|
|
284
270
|
each: (value, key, k, pre) => {
|
|
285
271
|
// each is called for all keys, but stop adding to write.text when
|
|
@@ -309,8 +295,7 @@ const Radisk = opt => {
|
|
|
309
295
|
) {
|
|
310
296
|
const fullKey = pre.join("") + k
|
|
311
297
|
const end = fullKey.indexOf(enq)
|
|
312
|
-
write.halfKey =
|
|
313
|
-
end === -1 ? fullKey : fullKey.substring(0, end)
|
|
298
|
+
write.halfKey = end === -1 ? fullKey : fullKey.substring(0, end)
|
|
314
299
|
write.halfOffset = write.text.length
|
|
315
300
|
}
|
|
316
301
|
|
|
@@ -322,8 +307,7 @@ const Radisk = opt => {
|
|
|
322
307
|
const fullKey = pre.join("") + k
|
|
323
308
|
const end = fullKey.indexOf(enq)
|
|
324
309
|
write.limit =
|
|
325
|
-
write.halfKey ||
|
|
326
|
-
(end === -1 ? fullKey : fullKey.substring(0, end))
|
|
310
|
+
write.halfKey || (end === -1 ? fullKey : fullKey.substring(0, end))
|
|
327
311
|
// Cannot split if the key is the same as the current file name.
|
|
328
312
|
if (write.limit !== file) {
|
|
329
313
|
// Truncate to the balanced split point — text after halfOffset is
|
|
@@ -350,7 +334,6 @@ const Radisk = opt => {
|
|
|
350
334
|
Radix.map(rad, write.each, true)
|
|
351
335
|
// There is always accumulated write.text to store once write.each has
|
|
352
336
|
// finished.
|
|
353
|
-
const writeStart = Date.now()
|
|
354
337
|
opt.store.put(file, write.text, err => {
|
|
355
338
|
// If a split occurred the cached radix tree still contains entries that
|
|
356
339
|
// were moved to the sub-file. Invalidate so the next save.mix re-reads
|
|
@@ -359,7 +342,6 @@ const Radisk = opt => {
|
|
|
359
342
|
cache.delete(file)
|
|
360
343
|
fileListCache = null
|
|
361
344
|
}
|
|
362
|
-
perfLog("file-write", writeStart, file, write.text.length)
|
|
363
345
|
cb(err)
|
|
364
346
|
})
|
|
365
347
|
}
|
|
@@ -371,17 +353,26 @@ const Radisk = opt => {
|
|
|
371
353
|
|
|
372
354
|
// Binary search for largest filename <= soul in a sorted array.
|
|
373
355
|
const findFile = files => {
|
|
374
|
-
let lo = 0,
|
|
356
|
+
let lo = 0,
|
|
357
|
+
hi = files.length - 1,
|
|
358
|
+
result = null
|
|
375
359
|
while (lo <= hi) {
|
|
376
360
|
const mid = (lo + hi) >> 1
|
|
377
|
-
if (files[mid] <= soul) {
|
|
378
|
-
|
|
361
|
+
if (files[mid] <= soul) {
|
|
362
|
+
result = files[mid]
|
|
363
|
+
lo = mid + 1
|
|
364
|
+
} else {
|
|
365
|
+
hi = mid - 1
|
|
366
|
+
}
|
|
379
367
|
}
|
|
380
368
|
return result
|
|
381
369
|
}
|
|
382
370
|
|
|
383
371
|
const read = file => {
|
|
384
|
-
if (!file) {
|
|
372
|
+
if (!file) {
|
|
373
|
+
cb("no file found", u)
|
|
374
|
+
return
|
|
375
|
+
}
|
|
385
376
|
if (opt.cache && cache.has(file)) {
|
|
386
377
|
return cb(u, cache.get(file)(key))
|
|
387
378
|
}
|
|
@@ -396,12 +387,19 @@ const Radisk = opt => {
|
|
|
396
387
|
}
|
|
397
388
|
|
|
398
389
|
const now = Date.now()
|
|
399
|
-
if (
|
|
390
|
+
if (
|
|
391
|
+
opt.cache &&
|
|
392
|
+
fileListCache &&
|
|
393
|
+
now - fileListCacheTime < FILE_LIST_CACHE_TTL
|
|
394
|
+
) {
|
|
400
395
|
read(findFile(fileListCache, soul))
|
|
401
396
|
} else {
|
|
402
397
|
const files = []
|
|
403
398
|
opt.store.list(file => {
|
|
404
|
-
if (file) {
|
|
399
|
+
if (file) {
|
|
400
|
+
files.push(file)
|
|
401
|
+
return
|
|
402
|
+
}
|
|
405
403
|
files.sort()
|
|
406
404
|
if (opt.cache) {
|
|
407
405
|
fileListCache = files
|
package/src/store.js
CHANGED
|
@@ -197,7 +197,7 @@ const Store = opt => {
|
|
|
197
197
|
const radisk = Radisk(opt)
|
|
198
198
|
|
|
199
199
|
return {
|
|
200
|
-
get: (lex, cb
|
|
200
|
+
get: (lex, cb) => {
|
|
201
201
|
if (!lex || !utils.obj.is(lex)) {
|
|
202
202
|
cb("lex required")
|
|
203
203
|
return
|
|
@@ -207,41 +207,54 @@ const Store = opt => {
|
|
|
207
207
|
return
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
if (!_opt) _opt = {}
|
|
211
|
-
|
|
212
210
|
var soul = lex["#"]
|
|
213
|
-
var key =
|
|
211
|
+
var key = typeof lex["."] === "string" ? lex["."] : ""
|
|
214
212
|
var node
|
|
215
213
|
var signatures = {}
|
|
216
|
-
const each = (value,
|
|
217
|
-
|
|
218
|
-
if (key !== utils.userPublicKey && !utils.match(lex["."], key)) return
|
|
214
|
+
const each = (value, k) => {
|
|
215
|
+
if (k !== utils.userPublicKey && !utils.match(lex["."], k)) return
|
|
219
216
|
|
|
220
217
|
if (!node) node = {_: {"#": soul, ">": {}}}
|
|
221
|
-
node[
|
|
222
|
-
node._[">"][
|
|
218
|
+
node[k] = value[0]
|
|
219
|
+
node._[">"][k] = value[1]
|
|
223
220
|
// If signature is present, store it in _["s"]
|
|
224
221
|
if (value.length === 3 && value[2]) {
|
|
225
222
|
signatures[value[1]] = value[2]
|
|
226
223
|
}
|
|
227
224
|
}
|
|
228
225
|
|
|
226
|
+
// When doing a targeted property lookup, also fetch userPublicKey in
|
|
227
|
+
// parallel so ham.js can verify signatures regardless of secure mode.
|
|
228
|
+
// Full-soul scans (key="") already surface it via Radix.map.
|
|
229
|
+
const needsPubKey = key !== "" && key !== utils.userPublicKey
|
|
230
|
+
let pending = needsPubKey ? 2 : 1
|
|
231
|
+
let cbErr = null
|
|
232
|
+
const finish = err => {
|
|
233
|
+
if (err && !cbErr) cbErr = err
|
|
234
|
+
if (--pending > 0) return
|
|
235
|
+
const graph = {[soul]: node}
|
|
236
|
+
if (node && Object.keys(signatures).length > 0) {
|
|
237
|
+
node._["s"] = signatures
|
|
238
|
+
}
|
|
239
|
+
cb(cbErr, graph)
|
|
240
|
+
}
|
|
241
|
+
|
|
229
242
|
radisk(soul + enq + key, (err, value) => {
|
|
230
|
-
let graph
|
|
231
243
|
if (utils.obj.is(value)) {
|
|
232
244
|
Radix.map(value, each)
|
|
233
245
|
if (!node) each(value, key)
|
|
234
|
-
graph = {[soul]: node}
|
|
235
246
|
} else if (value) {
|
|
236
247
|
each(value, key)
|
|
237
|
-
graph = {[soul]: node}
|
|
238
248
|
}
|
|
239
|
-
|
|
240
|
-
if (graph && graph[soul] && Object.keys(signatures).length > 0) {
|
|
241
|
-
graph[soul]._["s"] = signatures
|
|
242
|
-
}
|
|
243
|
-
cb(err, graph)
|
|
249
|
+
finish(err)
|
|
244
250
|
})
|
|
251
|
+
|
|
252
|
+
if (needsPubKey) {
|
|
253
|
+
radisk(soul + enq + utils.userPublicKey, (err, value) => {
|
|
254
|
+
if (value && !utils.obj.is(value)) each(value, utils.userPublicKey)
|
|
255
|
+
finish(err)
|
|
256
|
+
})
|
|
257
|
+
}
|
|
245
258
|
},
|
|
246
259
|
put: (graph, cb) => {
|
|
247
260
|
if (!graph) {
|
package/src/user.js
CHANGED
|
@@ -28,54 +28,51 @@ const User = (opt, wire) => {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
const pub = pubs.shift()
|
|
31
|
-
wire.get(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return
|
|
37
|
-
}
|
|
31
|
+
wire.get({"#": pub, ".": ["auth", "pub", "epub"]}, async msg => {
|
|
32
|
+
if (msg.err) {
|
|
33
|
+
done(`error getting ${pub}: ${msg.err}`)
|
|
34
|
+
return
|
|
35
|
+
}
|
|
38
36
|
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
const data = msg.put && msg.put[pub]
|
|
38
|
+
if (!data || !data.auth) return next()
|
|
41
39
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
const auth = JSON.parse(data.auth)
|
|
41
|
+
const work = await SEA.work(password, auth.salt)
|
|
42
|
+
const dec = await SEA.decrypt(auth.enc, work)
|
|
43
|
+
if (!dec) return next()
|
|
46
44
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
45
|
+
user.is = {
|
|
46
|
+
username: username,
|
|
47
|
+
pub: data.pub,
|
|
48
|
+
epub: data.epub,
|
|
49
|
+
priv: dec.priv,
|
|
50
|
+
epriv: dec.epriv,
|
|
51
|
+
}
|
|
52
|
+
if (newPassword !== "") {
|
|
53
|
+
// Encrypt private key using new password and a new salt.
|
|
54
|
+
const salt = utils.text.random(64)
|
|
55
|
+
const work = await SEA.work(newPassword, salt)
|
|
56
|
+
const enc = await SEA.encrypt(dec, work)
|
|
57
|
+
// Only update the auth property with the new password encryption.
|
|
58
|
+
const update = {
|
|
59
|
+
auth: JSON.stringify({enc: enc, salt: salt}),
|
|
53
60
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
const timestamp = Date.now()
|
|
62
|
+
const sig = await SEA.signTimestamp(timestamp, user.is)
|
|
63
|
+
const graph = utils.graph(pub, update, sig, data.pub, timestamp)
|
|
64
|
+
wire.put(graph, err => {
|
|
65
|
+
if (err) {
|
|
66
|
+
done(`error putting ${update} on ${pub}: ${err}`)
|
|
67
|
+
} else {
|
|
68
|
+
done(null)
|
|
62
69
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
wire.put(graph, err => {
|
|
67
|
-
if (err) {
|
|
68
|
-
done(`error putting ${update} on ${pub}: ${err}`)
|
|
69
|
-
} else {
|
|
70
|
-
done(null)
|
|
71
|
-
}
|
|
72
|
-
})
|
|
73
|
-
return
|
|
74
|
-
}
|
|
70
|
+
})
|
|
71
|
+
return
|
|
72
|
+
}
|
|
75
73
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
)
|
|
74
|
+
done(null)
|
|
75
|
+
})
|
|
79
76
|
}
|
|
80
77
|
|
|
81
78
|
if (retries > 9) {
|
|
@@ -84,24 +81,21 @@ const User = (opt, wire) => {
|
|
|
84
81
|
}
|
|
85
82
|
|
|
86
83
|
const soul = "~@" + username
|
|
87
|
-
wire.get(
|
|
88
|
-
{
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
return
|
|
93
|
-
}
|
|
84
|
+
wire.get({"#": soul}, async msg => {
|
|
85
|
+
if (msg.err) {
|
|
86
|
+
done(`error getting ${soul}: ${msg.err}`)
|
|
87
|
+
return
|
|
88
|
+
}
|
|
94
89
|
|
|
95
|
-
|
|
96
|
-
|
|
90
|
+
const data = msg.put && msg.put[soul]
|
|
91
|
+
if (!data) return retry(username)
|
|
97
92
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
)
|
|
93
|
+
delete msg.put[soul]._
|
|
94
|
+
// Usernames aren't guaranteed to be unique, so store the list and then
|
|
95
|
+
// try each of them until one is successful with the given password.
|
|
96
|
+
pubs = Object.keys(data)
|
|
97
|
+
next()
|
|
98
|
+
})
|
|
105
99
|
}
|
|
106
100
|
|
|
107
101
|
const user = {
|
|
@@ -129,58 +123,55 @@ const User = (opt, wire) => {
|
|
|
129
123
|
creating = true
|
|
130
124
|
|
|
131
125
|
const soul = "~@" + username
|
|
132
|
-
wire.get(
|
|
133
|
-
{
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
return
|
|
139
|
-
}
|
|
126
|
+
wire.get({"#": soul}, async msg => {
|
|
127
|
+
if (msg.err) {
|
|
128
|
+
creating = false
|
|
129
|
+
ack(`error getting ${soul}: ${msg.err}`)
|
|
130
|
+
return
|
|
131
|
+
}
|
|
140
132
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
133
|
+
if (msg.put && msg.put[soul]) {
|
|
134
|
+
creating = false
|
|
135
|
+
ack("Username already exists")
|
|
136
|
+
return
|
|
137
|
+
}
|
|
146
138
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
139
|
+
const salt = utils.text.random(64)
|
|
140
|
+
const work = await SEA.work(password, salt)
|
|
141
|
+
const pair = await SEA.pair()
|
|
142
|
+
const priv = {priv: pair.priv, epriv: pair.epriv}
|
|
143
|
+
const enc = await SEA.encrypt(priv, work)
|
|
144
|
+
const data = {
|
|
145
|
+
username: username,
|
|
146
|
+
pub: pair.pub,
|
|
147
|
+
epub: pair.epub,
|
|
148
|
+
auth: JSON.stringify({enc: enc, salt: salt}),
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const pub = "~" + pair.pub
|
|
152
|
+
// Sign the timestamp for new account
|
|
153
|
+
const timestamp = Date.now()
|
|
154
|
+
const sig = await SEA.signTimestamp(timestamp, pair)
|
|
155
|
+
const graph = utils.graph(pub, data, sig, pair.pub, timestamp)
|
|
156
|
+
wire.put(graph, err => {
|
|
157
|
+
creating = false
|
|
158
|
+
if (err) {
|
|
159
|
+
ack(`error putting ${data} on ${pub}: ${err}`)
|
|
160
|
+
return
|
|
157
161
|
}
|
|
158
162
|
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
const timestamp = Date.now()
|
|
162
|
-
const sig = await SEA.signTimestamp(timestamp, pair)
|
|
163
|
-
const graph = utils.graph(pub, data, sig, pair.pub, timestamp)
|
|
164
|
-
wire.put(graph, err => {
|
|
165
|
-
creating = false
|
|
163
|
+
const rel = {[pub]: {"#": pub}}
|
|
164
|
+
wire.put(utils.graph(soul, rel), err => {
|
|
166
165
|
if (err) {
|
|
167
|
-
ack(`error putting ${
|
|
166
|
+
ack(`error putting ${rel} on ${soul}: ${err}`)
|
|
168
167
|
return
|
|
169
168
|
}
|
|
170
169
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if (err) {
|
|
174
|
-
ack(`error putting ${rel} on ${soul}: ${err}`)
|
|
175
|
-
return
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Return null on success.
|
|
179
|
-
if (cb) cb(null)
|
|
180
|
-
})
|
|
170
|
+
// Return null on success.
|
|
171
|
+
if (cb) cb(null)
|
|
181
172
|
})
|
|
182
|
-
}
|
|
183
|
-
)
|
|
173
|
+
})
|
|
174
|
+
})
|
|
184
175
|
},
|
|
185
176
|
auth: (username, password, cb) => {
|
|
186
177
|
const ack = err => {
|