@mblaney/holster 0.3.0 → 1.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/build/holster.js +3 -3
- package/build/holster.js.map +3 -3
- package/examples/user.js +3 -3
- package/package.json +1 -1
- package/src/get.js +17 -10
- package/src/ham.js +18 -20
- package/src/holster.js +214 -91
- package/src/sea.js +16 -4
- package/src/user.js +21 -22
- package/src/utils.js +13 -6
- package/src/wire.js +21 -15
- package/test/holster.get.test.js +32 -2
- package/test/holster.on.test.js +46 -5
- package/test/holster.put.test.js +19 -2
- package/test/holster.secure.test.js +1 -9
- package/test/holster.user.get.test.js +27 -4
- package/test/holster.user.lex.test.js +2 -2
- package/test/holster.user.on.test.js +58 -4
- package/test/holster.user.put.test.js +121 -59
- package/test/wire.test.js +7 -8
package/src/holster.js
CHANGED
|
@@ -14,8 +14,6 @@ const Holster = opt => {
|
|
|
14
14
|
const map = new Map()
|
|
15
15
|
// Allow concurrent calls to the api by storing each context.
|
|
16
16
|
const allctx = new Map()
|
|
17
|
-
// Allow user queries by public key
|
|
18
|
-
let publicKey
|
|
19
17
|
|
|
20
18
|
const ok = data => {
|
|
21
19
|
return (
|
|
@@ -37,21 +35,26 @@ const Holster = opt => {
|
|
|
37
35
|
const keys = []
|
|
38
36
|
for (const [key, value] of Object.entries(data)) {
|
|
39
37
|
if (key === "_") {
|
|
40
|
-
return "error underscore cannot be used as
|
|
38
|
+
return "error underscore cannot be used as a property name"
|
|
41
39
|
}
|
|
42
40
|
if (utils.obj.is(value) || ok(value)) {
|
|
43
41
|
keys.push(key)
|
|
44
42
|
continue
|
|
45
43
|
}
|
|
46
|
-
|
|
44
|
+
if (typeof value === "undefined") {
|
|
45
|
+
return `error undefined ${key} cannot be converted to a graph`
|
|
46
|
+
}
|
|
47
|
+
const error = JSON.stringify({[key]: value})
|
|
48
|
+
return `error ${error} cannot be converted to a graph`
|
|
47
49
|
}
|
|
48
50
|
if (keys.length !== 0) return keys
|
|
49
51
|
}
|
|
50
|
-
|
|
52
|
+
const error = JSON.stringify(data)
|
|
53
|
+
return `error ${error} cannot be converted to a graph`
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
const api = ctxid => {
|
|
54
|
-
const get = (lex, soul, ack,
|
|
57
|
+
const get = (lex, soul, ack, _opt) => {
|
|
55
58
|
wire.get(
|
|
56
59
|
utils.obj.put(lex, "#", soul),
|
|
57
60
|
async msg => {
|
|
@@ -59,6 +62,7 @@ const Holster = opt => {
|
|
|
59
62
|
if (msg.put && msg.put[soul]) {
|
|
60
63
|
delete msg.put[soul]._
|
|
61
64
|
delete msg.put[soul][utils.userPublicKey]
|
|
65
|
+
delete msg.put[soul][utils.userSignature]
|
|
62
66
|
// Resolve any rels on the node before returning to the user.
|
|
63
67
|
for (const key of Object.keys(msg.put[soul])) {
|
|
64
68
|
const id = utils.rel.is(msg.put[soul][key])
|
|
@@ -77,19 +81,18 @@ const Holster = opt => {
|
|
|
77
81
|
ack(null)
|
|
78
82
|
}
|
|
79
83
|
},
|
|
80
|
-
|
|
84
|
+
_opt,
|
|
81
85
|
)
|
|
82
86
|
}
|
|
83
87
|
|
|
84
|
-
const graph = async (soul, data, cb) => {
|
|
85
|
-
if (
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const signed = await SEA.sign(data, user.is)
|
|
89
|
-
return utils.graph(soul, signed.m, signed.s, user.is.pub)
|
|
88
|
+
const graph = async (soul, data, userctx, cb) => {
|
|
89
|
+
if (userctx) {
|
|
90
|
+
const signed = await SEA.sign(data, userctx)
|
|
91
|
+
return utils.graph(soul, signed.m, signed.s, userctx.pub)
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
if (opt.secure) {
|
|
95
|
+
if (!cb) cb = console.log
|
|
93
96
|
cb(`error putting data on ${soul}: user required in secure mode`)
|
|
94
97
|
return null
|
|
95
98
|
}
|
|
@@ -99,17 +102,27 @@ const Holster = opt => {
|
|
|
99
102
|
|
|
100
103
|
const done = data => {
|
|
101
104
|
const ctx = allctx.get(ctxid)
|
|
102
|
-
if (ctx && typeof ctx.cb !== "undefined")
|
|
103
|
-
|
|
105
|
+
if (ctx && typeof ctx.cb !== "undefined") {
|
|
106
|
+
// Use a timeout so that the context can be removed before data is
|
|
107
|
+
// returned to the callback (allows nested get calls).
|
|
108
|
+
setTimeout(() => ctx.cb(data), 1)
|
|
109
|
+
} else if (data) {
|
|
110
|
+
console.log("error no callback for data", data, "ctx", ctx)
|
|
111
|
+
}
|
|
104
112
|
// A context updated by "on" should only be removed by "off".
|
|
105
113
|
if (ctx && !ctx.on) allctx.delete(ctxid)
|
|
106
114
|
}
|
|
107
115
|
|
|
108
116
|
const resolve = (request, cb) => {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
117
|
+
if (!request) {
|
|
118
|
+
console.log("error resolve request parameter required")
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const get = typeof request.get !== "undefined"
|
|
123
|
+
const put = typeof request.put !== "undefined"
|
|
124
|
+
const on = typeof request.on !== "undefined"
|
|
125
|
+
const off = typeof request.off !== "undefined"
|
|
113
126
|
|
|
114
127
|
let found = false
|
|
115
128
|
const ctx = allctx.get(ctxid)
|
|
@@ -124,34 +137,45 @@ const Holster = opt => {
|
|
|
124
137
|
// Found a soul that needs resolving, need the previous context
|
|
125
138
|
// (ie the parent node) to find a soul relation for it.
|
|
126
139
|
const {item, soul} = ctx.chain[i - 1]
|
|
127
|
-
|
|
140
|
+
// If this is a user context or using secure mode then need to get the
|
|
141
|
+
// whole node for verification.
|
|
142
|
+
const lex =
|
|
143
|
+
ctx.user || opt.secure ? {"#": soul} : {"#": soul, ".": item}
|
|
144
|
+
wire.get(lex, async msg => {
|
|
128
145
|
if (msg.err) {
|
|
129
|
-
|
|
146
|
+
if (ctx.user || opt.secure) {
|
|
147
|
+
console.log(`error getting ${soul}: ${msg.err}`)
|
|
148
|
+
} else {
|
|
149
|
+
console.log(`error getting ${item} on ${soul}: ${msg.err}`)
|
|
150
|
+
}
|
|
130
151
|
if (cb) cb(null)
|
|
131
152
|
return
|
|
132
153
|
}
|
|
133
154
|
|
|
134
|
-
|
|
155
|
+
let node = msg.put && msg.put[soul]
|
|
135
156
|
if (node && typeof node[item] !== "undefined") {
|
|
136
157
|
let id = utils.rel.is(node[item])
|
|
137
158
|
if (id) {
|
|
138
159
|
ctx.chain[i].soul = id
|
|
139
|
-
|
|
140
|
-
allctx.set(ctxid, {chain: ctx.chain, cb: ctx.cb})
|
|
160
|
+
allctx.set(ctxid, {...ctx})
|
|
141
161
|
// Call api again using the updated context.
|
|
142
|
-
if (get)
|
|
143
|
-
|
|
144
|
-
else if (
|
|
145
|
-
|
|
162
|
+
if (get) {
|
|
163
|
+
api(ctxid).next(null, request.get, cb, request._opt)
|
|
164
|
+
} else if (put) {
|
|
165
|
+
api(ctxid).put(request.put, cb)
|
|
166
|
+
} else if (on) {
|
|
167
|
+
api(ctxid).on(request.on, cb, request._get, request._opt)
|
|
168
|
+
} else if (off) {
|
|
169
|
+
api(ctxid).off(cb)
|
|
170
|
+
}
|
|
146
171
|
} else if (get) {
|
|
147
|
-
// Request was not for a node, return
|
|
148
|
-
// soul.
|
|
172
|
+
// Request was not for a node, return property on current soul.
|
|
149
173
|
cb(node[item])
|
|
150
174
|
} else if (put) {
|
|
151
175
|
// Request was chained before put, so rel doesn't exist yet.
|
|
152
176
|
id = utils.text.random()
|
|
153
|
-
|
|
154
|
-
const g = await graph(soul,
|
|
177
|
+
node[item] = utils.rel.ify(id)
|
|
178
|
+
const g = await graph(soul, node, ctx.user, cb)
|
|
155
179
|
if (g === null) return
|
|
156
180
|
|
|
157
181
|
wire.put(g, err => {
|
|
@@ -164,16 +188,31 @@ const Holster = opt => {
|
|
|
164
188
|
api(ctxid).put(request.put, cb)
|
|
165
189
|
})
|
|
166
190
|
} else if (on) {
|
|
167
|
-
|
|
191
|
+
// Allow listening to a node that doesn't exist.
|
|
168
192
|
cb(null)
|
|
169
193
|
} else if (off) {
|
|
170
|
-
|
|
194
|
+
// Allow stop listening to a node that doesn't exist.
|
|
171
195
|
if (cb) cb(null)
|
|
172
196
|
}
|
|
173
197
|
} else if (put) {
|
|
174
|
-
|
|
198
|
+
// Request was chained before put, so rel doesn't exist yet.
|
|
199
|
+
const id = utils.text.random()
|
|
200
|
+
if (!node) node = {}
|
|
201
|
+
node[item] = utils.rel.ify(id)
|
|
202
|
+
const g = await graph(soul, node, ctx.user, cb)
|
|
203
|
+
if (g === null) return
|
|
204
|
+
|
|
205
|
+
wire.put(g, err => {
|
|
206
|
+
if (err) {
|
|
207
|
+
cb(`error putting ${item} on ${soul}: ${err}`)
|
|
208
|
+
return
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
ctx.chain[i].soul = id
|
|
212
|
+
api(ctxid).put(request.put, cb)
|
|
213
|
+
})
|
|
175
214
|
} else {
|
|
176
|
-
|
|
215
|
+
// Allow querying a node that doesn't exist.
|
|
177
216
|
if (cb) cb(null)
|
|
178
217
|
}
|
|
179
218
|
})
|
|
@@ -195,41 +234,40 @@ const Holster = opt => {
|
|
|
195
234
|
}
|
|
196
235
|
|
|
197
236
|
return {
|
|
198
|
-
get: (key, lex, cb,
|
|
237
|
+
get: (key, lex, cb, _opt) => {
|
|
199
238
|
if (typeof lex === "function") {
|
|
239
|
+
_opt = cb
|
|
200
240
|
cb = lex
|
|
201
241
|
lex = null
|
|
202
242
|
}
|
|
203
243
|
if (key === null || key === "" || key === "_") {
|
|
244
|
+
console.log("error please provide a key")
|
|
204
245
|
if (cb) cb(null)
|
|
205
246
|
return
|
|
206
247
|
}
|
|
207
248
|
|
|
208
249
|
// lex requires a callback as it's not included in the chain below.
|
|
209
|
-
if (lex &&
|
|
250
|
+
if (lex && typeof cb !== "function") {
|
|
210
251
|
console.log("error lex requires a callback function")
|
|
211
252
|
return
|
|
212
253
|
}
|
|
213
254
|
|
|
214
255
|
// Top level keys are added to a root node so their values don't need
|
|
215
|
-
// to be objects
|
|
216
|
-
// publicKey or user.ctx is set.
|
|
217
|
-
const root = publicKey ? publicKey : user.ctx ? user.ctx : "root"
|
|
218
|
-
|
|
256
|
+
// to be objects.
|
|
219
257
|
ctxid = utils.text.random()
|
|
220
|
-
allctx.set(ctxid, {chain: [{item: key, soul: root}], cb: cb})
|
|
258
|
+
allctx.set(ctxid, {chain: [{item: key, soul: "root"}], cb: cb})
|
|
221
259
|
if (!cb) return api(ctxid)
|
|
222
260
|
|
|
223
261
|
// When there's a callback need to resolve the context first.
|
|
224
|
-
const {soul} = resolve({get: lex}, done)
|
|
225
|
-
if (soul) get(lex, soul, done,
|
|
262
|
+
const {soul} = resolve({get: lex, _opt: _opt}, done)
|
|
263
|
+
if (soul) get(lex, soul, done, _opt)
|
|
226
264
|
},
|
|
227
|
-
next: (key, lex, cb,
|
|
265
|
+
next: (key, lex, cb, _opt) => {
|
|
228
266
|
const ack = data => {
|
|
229
267
|
cb ? cb(data) : done(data)
|
|
230
268
|
}
|
|
231
|
-
|
|
232
269
|
if (typeof lex === "function") {
|
|
270
|
+
_opt = cb
|
|
233
271
|
cb = lex
|
|
234
272
|
lex = null
|
|
235
273
|
}
|
|
@@ -245,7 +283,7 @@ const Holster = opt => {
|
|
|
245
283
|
}
|
|
246
284
|
|
|
247
285
|
// lex requires a callback as it's not included in the chain below.
|
|
248
|
-
if (lex &&
|
|
286
|
+
if (lex && typeof cb !== "function") {
|
|
249
287
|
console.log("error lex requires a callback function")
|
|
250
288
|
return
|
|
251
289
|
}
|
|
@@ -266,8 +304,8 @@ const Holster = opt => {
|
|
|
266
304
|
if (!ctx.cb) return api(ctxid)
|
|
267
305
|
|
|
268
306
|
// When there's a callback need to resolve the context first.
|
|
269
|
-
const {soul} = resolve({get: lex}, ack)
|
|
270
|
-
if (soul) get(lex, soul, ack,
|
|
307
|
+
const {soul} = resolve({get: lex, _opt: _opt}, ack)
|
|
308
|
+
if (soul) get(lex, soul, ack, _opt)
|
|
271
309
|
},
|
|
272
310
|
put: (data, set, cb) => {
|
|
273
311
|
if (typeof set === "function") {
|
|
@@ -311,17 +349,23 @@ const Holster = opt => {
|
|
|
311
349
|
// When result is true data is a property to put on the current soul.
|
|
312
350
|
// Need to check if item is a rel and also set the node to null. (This
|
|
313
351
|
// applies for any update from a rel to a property, not just null.)
|
|
314
|
-
|
|
352
|
+
// If using secure mode need to get the whole node for verification.
|
|
353
|
+
const lex =
|
|
354
|
+
ctx.user || opt.secure ? {"#": soul} : {"#": soul, ".": item}
|
|
355
|
+
wire.get(lex, async msg => {
|
|
315
356
|
if (msg.err) {
|
|
316
|
-
|
|
357
|
+
ack(`error getting ${soul}: ${msg.err}`)
|
|
317
358
|
return
|
|
318
359
|
}
|
|
319
360
|
|
|
320
|
-
|
|
361
|
+
let node = msg.put && msg.put[soul]
|
|
362
|
+
const current = node && node[item]
|
|
321
363
|
const id = utils.rel.is(current)
|
|
322
364
|
if (!id) {
|
|
323
365
|
// Not a rel, can just put the data.
|
|
324
|
-
|
|
366
|
+
if (!node) node = {}
|
|
367
|
+
node[item] = data
|
|
368
|
+
const g = await graph(soul, node, ctx.user, ack)
|
|
325
369
|
if (g === null) return
|
|
326
370
|
|
|
327
371
|
wire.put(g, ack)
|
|
@@ -330,23 +374,29 @@ const Holster = opt => {
|
|
|
330
374
|
|
|
331
375
|
wire.get({"#": id}, async msg => {
|
|
332
376
|
if (msg.err) {
|
|
333
|
-
|
|
377
|
+
ack(`error getting ${id}: ${msg.err}`)
|
|
334
378
|
return
|
|
335
379
|
}
|
|
336
380
|
|
|
337
381
|
if (!msg.put || !msg.put[id]) {
|
|
338
|
-
|
|
382
|
+
ack(`error ${id} not found`)
|
|
339
383
|
return
|
|
340
384
|
}
|
|
341
385
|
|
|
342
|
-
delete msg.put[id]._
|
|
343
386
|
// null each of the properties on the node before putting data.
|
|
344
387
|
for (const key of Object.keys(msg.put[id])) {
|
|
345
|
-
if (
|
|
388
|
+
if (
|
|
389
|
+
key === "_" ||
|
|
390
|
+
key === utils.userPublicKey ||
|
|
391
|
+
key === utils.userSignature
|
|
392
|
+
) {
|
|
393
|
+
continue
|
|
394
|
+
}
|
|
346
395
|
|
|
347
396
|
const err = await new Promise(res => {
|
|
348
397
|
const _ctxid = utils.text.random()
|
|
349
|
-
|
|
398
|
+
const chain = [{item: key, soul: id}]
|
|
399
|
+
allctx.set(_ctxid, {chain: chain, user: ctx.user})
|
|
350
400
|
api(_ctxid).put(null, res)
|
|
351
401
|
})
|
|
352
402
|
if (err) {
|
|
@@ -354,7 +404,9 @@ const Holster = opt => {
|
|
|
354
404
|
return
|
|
355
405
|
}
|
|
356
406
|
}
|
|
357
|
-
|
|
407
|
+
if (!node) node = {}
|
|
408
|
+
node[item] = data
|
|
409
|
+
const g = await graph(soul, node, ctx.user, ack)
|
|
358
410
|
if (g === null) return
|
|
359
411
|
|
|
360
412
|
wire.put(g, ack)
|
|
@@ -365,18 +417,23 @@ const Holster = opt => {
|
|
|
365
417
|
|
|
366
418
|
// Otherwise put the data using the keys returned in result.
|
|
367
419
|
// Need to check if a rel has already been added on the current node.
|
|
368
|
-
|
|
420
|
+
// If using secure mode need to get the whole node for verification.
|
|
421
|
+
const lex =
|
|
422
|
+
ctx.user || opt.secure ? {"#": soul} : {"#": soul, ".": item}
|
|
423
|
+
wire.get(lex, async msg => {
|
|
369
424
|
if (msg.err) {
|
|
370
425
|
ack(`error getting ${soul}.${item}: ${msg.err}`)
|
|
371
426
|
return
|
|
372
427
|
}
|
|
373
428
|
|
|
374
|
-
|
|
429
|
+
let node = msg.put && msg.put[soul]
|
|
430
|
+
const current = node && node[item]
|
|
375
431
|
const id = utils.rel.is(current)
|
|
376
432
|
if (!id) {
|
|
377
433
|
// The current rel doesn't exist, so add it first.
|
|
378
|
-
|
|
379
|
-
|
|
434
|
+
if (!node) node = {}
|
|
435
|
+
node[item] = utils.rel.ify(utils.text.random())
|
|
436
|
+
const g = await graph(soul, node, ctx.user, ack)
|
|
380
437
|
if (g === null) return
|
|
381
438
|
|
|
382
439
|
wire.put(g, err => {
|
|
@@ -385,27 +442,26 @@ const Holster = opt => {
|
|
|
385
442
|
} else {
|
|
386
443
|
const _ctxid = utils.text.random()
|
|
387
444
|
const chain = [{item: item, soul: soul}]
|
|
388
|
-
// Pass the previous context's callback
|
|
389
|
-
allctx.set(_ctxid, {chain: chain, cb: ctx.cb})
|
|
445
|
+
// Pass on the previous context's callback and user flag here.
|
|
446
|
+
allctx.set(_ctxid, {chain: chain, user: ctx.user, cb: ctx.cb})
|
|
390
447
|
api(_ctxid).put(data)
|
|
391
448
|
}
|
|
392
449
|
})
|
|
393
450
|
return
|
|
394
451
|
}
|
|
395
452
|
|
|
396
|
-
|
|
397
|
-
const update = {}
|
|
453
|
+
const update = []
|
|
398
454
|
for (const key of result) {
|
|
399
455
|
const err = await new Promise(res => {
|
|
400
|
-
if (utils.obj.is(data[key])) {
|
|
456
|
+
if (utils.obj.is(data[key]) && !utils.rel.is(data[key])) {
|
|
401
457
|
// Use the current rel as the context for nested objects.
|
|
402
458
|
const _ctxid = utils.text.random()
|
|
403
|
-
|
|
459
|
+
const chain = [{item: key, soul: id}]
|
|
460
|
+
allctx.set(_ctxid, {chain: chain, user: ctx.user})
|
|
404
461
|
api(_ctxid).put(data[key], res)
|
|
405
462
|
} else {
|
|
406
|
-
put
|
|
407
|
-
|
|
408
|
-
update[key] = data[key]
|
|
463
|
+
// Group the rest of the updates for put below.
|
|
464
|
+
update.push(key)
|
|
409
465
|
res(null)
|
|
410
466
|
}
|
|
411
467
|
})
|
|
@@ -414,23 +470,43 @@ const Holster = opt => {
|
|
|
414
470
|
return
|
|
415
471
|
}
|
|
416
472
|
}
|
|
417
|
-
if (put) {
|
|
418
|
-
const g = await graph(id, update, ack)
|
|
419
|
-
if (g === null) return
|
|
420
473
|
|
|
421
|
-
|
|
422
|
-
} else {
|
|
423
|
-
// Callback on behalf of nested objects.
|
|
474
|
+
if (update.length === 0) {
|
|
424
475
|
ack(null)
|
|
476
|
+
return
|
|
425
477
|
}
|
|
478
|
+
|
|
479
|
+
// The nested objects created above will also have rels on the parent
|
|
480
|
+
// object, so fetch the node so the rest of the updates can be added.
|
|
481
|
+
wire.get({"#": id}, async msg => {
|
|
482
|
+
if (msg.err) {
|
|
483
|
+
ack(`error getting ${id}: ${msg.err}`)
|
|
484
|
+
return
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
let node = msg.put && msg.put[id]
|
|
488
|
+
if (!node) node = {}
|
|
489
|
+
update.forEach(key => {
|
|
490
|
+
node[key] = data[key]
|
|
491
|
+
})
|
|
492
|
+
const g = await graph(id, node, ctx.user, ack)
|
|
493
|
+
if (g === null) return
|
|
494
|
+
|
|
495
|
+
wire.put(g, ack)
|
|
496
|
+
})
|
|
426
497
|
})
|
|
427
498
|
},
|
|
428
|
-
on: (lex, cb) => {
|
|
499
|
+
on: (lex, cb, _get, _opt) => {
|
|
429
500
|
if (typeof lex === "function") {
|
|
501
|
+
_opt = _get
|
|
502
|
+
_get = cb
|
|
430
503
|
cb = lex
|
|
431
504
|
lex = null
|
|
432
505
|
}
|
|
433
|
-
if (
|
|
506
|
+
if (typeof cb !== "function") {
|
|
507
|
+
console.log("error on() requires a callback function")
|
|
508
|
+
return
|
|
509
|
+
}
|
|
434
510
|
|
|
435
511
|
if (!ctxid) {
|
|
436
512
|
console.log("error please provide a key using get(key)")
|
|
@@ -439,7 +515,7 @@ const Holster = opt => {
|
|
|
439
515
|
}
|
|
440
516
|
|
|
441
517
|
// Resolve the current context before adding event listener.
|
|
442
|
-
const {item, soul} = resolve({on: lex}, cb)
|
|
518
|
+
const {item, soul} = resolve({on: lex, _get: _get, _opt: _opt}, cb)
|
|
443
519
|
if (!soul) return
|
|
444
520
|
|
|
445
521
|
// Flag that this context is set from on and shouldn't be removed.
|
|
@@ -463,7 +539,7 @@ const Holster = opt => {
|
|
|
463
539
|
if (lex) lex = utils.obj.put(lex, "#", soul)
|
|
464
540
|
else lex = {"#": soul, ".": item}
|
|
465
541
|
}
|
|
466
|
-
wire.on(lex, map.get(cb))
|
|
542
|
+
wire.on(lex, map.get(cb), _get, _opt)
|
|
467
543
|
})
|
|
468
544
|
},
|
|
469
545
|
off: cb => {
|
|
@@ -492,14 +568,61 @@ const Holster = opt => {
|
|
|
492
568
|
allctx.delete(ctxid)
|
|
493
569
|
})
|
|
494
570
|
},
|
|
495
|
-
user:
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
571
|
+
user: () => {
|
|
572
|
+
if (!user.get) {
|
|
573
|
+
// Return the combined Holster and User APIs.
|
|
574
|
+
Object.assign(user, api())
|
|
575
|
+
// Need to provide a user specific get() function to know if user
|
|
576
|
+
// context should be checked.
|
|
577
|
+
user.get = (keys, lex, cb, _opt) => {
|
|
578
|
+
if (typeof lex === "function") {
|
|
579
|
+
_opt = cb
|
|
580
|
+
cb = lex
|
|
581
|
+
lex = null
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
let pub = null
|
|
585
|
+
let key = null
|
|
586
|
+
if (user.is) pub = user.is.pub
|
|
587
|
+
if (typeof keys === "string") {
|
|
588
|
+
key = keys
|
|
589
|
+
} else if (keys instanceof Array) {
|
|
590
|
+
if (keys.length === 2) {
|
|
591
|
+
pub = keys[0]
|
|
592
|
+
key = keys[1]
|
|
593
|
+
} else if (keys.length === 1) {
|
|
594
|
+
key = keys[0]
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
if (!pub) {
|
|
598
|
+
console.log("error please log in or provide a public key")
|
|
599
|
+
if (cb) cb(null)
|
|
600
|
+
return
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
if (key === null || key === "" || key === "_") {
|
|
604
|
+
console.log("error please provide a key")
|
|
605
|
+
if (cb) cb(null)
|
|
606
|
+
return
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// lex requires a callback as it's not included in the chain below.
|
|
610
|
+
if (lex && !cb) {
|
|
611
|
+
console.log("error lex requires a callback function")
|
|
612
|
+
return
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
ctxid = utils.text.random()
|
|
616
|
+
const chain = [{item: key, soul: "~" + pub}]
|
|
617
|
+
allctx.set(ctxid, {chain: chain, user: user.is, cb: cb})
|
|
618
|
+
if (!cb) return api(ctxid)
|
|
619
|
+
|
|
620
|
+
// When there's a callback need to resolve the context first.
|
|
621
|
+
const {soul} = resolve({get: lex}, done)
|
|
622
|
+
if (soul) get(lex, soul, done, _opt)
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
return user
|
|
503
626
|
},
|
|
504
627
|
// Allow the wire spec to be used via holster.
|
|
505
628
|
wire: wire,
|
package/src/sea.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {userPublicKey} from "./utils.js"
|
|
1
|
+
import {userPublicKey, userSignature} from "./utils.js"
|
|
2
2
|
import * as utils from "./sea-utils.js"
|
|
3
3
|
import SafeBuffer from "./buffer.js"
|
|
4
4
|
|
|
@@ -117,8 +117,10 @@ const SEA = {
|
|
|
117
117
|
} else {
|
|
118
118
|
// Allow data to be passed in with graph meta data,
|
|
119
119
|
// (which should not be part of signature, so not verified).
|
|
120
|
-
for (const k of Object.keys(signed.m)) {
|
|
121
|
-
if (k !== "_" && k != userPublicKey
|
|
120
|
+
for (const k of Object.keys(signed.m).sort()) {
|
|
121
|
+
if (k !== "_" && k != userPublicKey && k != userSignature) {
|
|
122
|
+
msg[k] = signed.m[k]
|
|
123
|
+
}
|
|
122
124
|
}
|
|
123
125
|
}
|
|
124
126
|
const hash = await utils.sha256(msg)
|
|
@@ -139,7 +141,17 @@ const SEA = {
|
|
|
139
141
|
return null
|
|
140
142
|
}
|
|
141
143
|
|
|
142
|
-
|
|
144
|
+
let msg = {}
|
|
145
|
+
if (typeof data === "string") {
|
|
146
|
+
msg = data
|
|
147
|
+
} else {
|
|
148
|
+
const check = utils.parse(data)
|
|
149
|
+
for (const k of Object.keys(check).sort()) {
|
|
150
|
+
if (k !== "_" && k != userPublicKey && k != userSignature) {
|
|
151
|
+
msg[k] = check[k]
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
143
155
|
const hash = await utils.sha256(msg)
|
|
144
156
|
const jwk = utils.jwk(pair.pub, pair.priv)
|
|
145
157
|
const alg = {name: "ECDSA", namedCurve: "P-256"}
|
package/src/user.js
CHANGED
|
@@ -48,20 +48,23 @@ const User = (opt, wire) => {
|
|
|
48
48
|
priv: dec.priv,
|
|
49
49
|
epriv: dec.epriv,
|
|
50
50
|
}
|
|
51
|
-
// Holster API uses ctx to set the root node for user data.
|
|
52
|
-
user.ctx = "~" + data.pub
|
|
53
|
-
|
|
54
51
|
if (newPassword !== "") {
|
|
55
52
|
// Encrypt private key using new password and a new salt.
|
|
56
53
|
const salt = utils.text.random(64)
|
|
57
54
|
const work = await SEA.work(newPassword, salt)
|
|
58
55
|
const enc = await SEA.encrypt(dec, work)
|
|
59
|
-
|
|
60
|
-
const
|
|
56
|
+
// Put all fields so that verification applies to the whole node.
|
|
57
|
+
const update = {
|
|
58
|
+
username: username,
|
|
59
|
+
pub: data.pub,
|
|
60
|
+
epub: data.epub,
|
|
61
|
+
auth: JSON.stringify({enc: enc, salt: salt}),
|
|
62
|
+
}
|
|
63
|
+
const signed = await SEA.sign(update, user.is)
|
|
61
64
|
const graph = utils.graph(pub, signed.m, signed.s, data.pub)
|
|
62
65
|
wire.put(graph, err => {
|
|
63
66
|
if (err) {
|
|
64
|
-
done(`error putting ${
|
|
67
|
+
done(`error putting ${update} on ${pub}: ${err}`)
|
|
65
68
|
} else {
|
|
66
69
|
done(null)
|
|
67
70
|
}
|
|
@@ -236,40 +239,37 @@ const User = (opt, wire) => {
|
|
|
236
239
|
if (typeof globalThis.localStorage !== "undefined") {
|
|
237
240
|
globalThis.localStorage.setItem("user.is", JSON.stringify(user.is))
|
|
238
241
|
}
|
|
242
|
+
if (typeof globalThis.sessionStorage !== "undefined") {
|
|
243
|
+
globalThis.sessionStorage.removeItem("user.is")
|
|
244
|
+
}
|
|
239
245
|
return
|
|
240
246
|
}
|
|
241
247
|
|
|
242
248
|
if (typeof globalThis.sessionStorage !== "undefined") {
|
|
243
249
|
globalThis.sessionStorage.setItem("user.is", JSON.stringify(user.is))
|
|
244
250
|
}
|
|
251
|
+
if (typeof globalThis.localStorage !== "undefined") {
|
|
252
|
+
globalThis.localStorage.removeItem("user.is")
|
|
253
|
+
}
|
|
245
254
|
},
|
|
246
|
-
recall:
|
|
247
|
-
if (localStorage) {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
user.ctx = "~" + user.is.pub
|
|
253
|
-
} else {
|
|
254
|
-
console.log("User credentials not stored in local storage")
|
|
255
|
-
}
|
|
255
|
+
recall: () => {
|
|
256
|
+
if (typeof globalThis.localStorage !== "undefined") {
|
|
257
|
+
const is = globalThis.localStorage.getItem("user.is")
|
|
258
|
+
if (is) {
|
|
259
|
+
user.is = JSON.parse(is)
|
|
260
|
+
return
|
|
256
261
|
}
|
|
257
|
-
return
|
|
258
262
|
}
|
|
259
263
|
|
|
260
264
|
if (typeof globalThis.sessionStorage !== "undefined") {
|
|
261
265
|
const is = globalThis.sessionStorage.getItem("user.is")
|
|
262
266
|
if (is) {
|
|
263
267
|
user.is = JSON.parse(is)
|
|
264
|
-
user.ctx = "~" + user.is.pub
|
|
265
|
-
} else {
|
|
266
|
-
console.log("User credentials not stored in session storage")
|
|
267
268
|
}
|
|
268
269
|
}
|
|
269
270
|
},
|
|
270
271
|
leave: () => {
|
|
271
272
|
user.is = null
|
|
272
|
-
user.ctx = null
|
|
273
273
|
if (typeof globalThis.localStorage !== "undefined") {
|
|
274
274
|
globalThis.localStorage.removeItem("user.is")
|
|
275
275
|
}
|
|
@@ -316,7 +316,6 @@ const User = (opt, wire) => {
|
|
|
316
316
|
}
|
|
317
317
|
|
|
318
318
|
user.is = null
|
|
319
|
-
user.ctx = null
|
|
320
319
|
// Return null on success. Note currently not updating ~@username,
|
|
321
320
|
// not sure if allowing username re-user is a good idea anyway?
|
|
322
321
|
if (cb) cb(null)
|