@mblaney/holster 1.0.27 → 1.0.29

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mblaney/holster",
3
- "version": "1.0.27",
3
+ "version": "1.0.29",
4
4
  "description": "Holster is a service that allows synchronising data between devices using Node, Deno, Bun or the browser.",
5
5
  "type": "module",
6
6
  "main": "index.js",
package/src/get.js CHANGED
@@ -32,6 +32,20 @@ const Get = (lex, graph) => {
32
32
  signatures[key] = graph[soul]._["s"][key]
33
33
  }
34
34
  }
35
+
36
+ // Always include userPublicKey for verification, regardless of filter
37
+ if (userPublicKey && typeof graph[soul][userPublicKey] !== "undefined") {
38
+ node[userPublicKey] = graph[soul][userPublicKey]
39
+ node._[">"][userPublicKey] = graph[soul]._[">"][userPublicKey]
40
+ const pkState = graph[soul]._[">"][userPublicKey]
41
+ if (graph[soul]._["s"]) {
42
+ if (graph[soul]._["s"][pkState]) {
43
+ signatures[pkState] = graph[soul]._["s"][pkState]
44
+ } else if (graph[soul]._["s"][userPublicKey]) {
45
+ signatures[userPublicKey] = graph[soul]._["s"][userPublicKey]
46
+ }
47
+ }
48
+ }
35
49
  } else {
36
50
  for (const key of Object.keys(graph[soul])) {
37
51
  // Always include userPublicKey for verification, regardless of filter
package/src/ham.js CHANGED
@@ -130,9 +130,6 @@ Ham.mix = async (change, graph, secure, listen) => {
130
130
  for (const key of Object.keys(node)) {
131
131
  if (key === "_") continue
132
132
 
133
- // Skip metadata fields (including userSignature for old data)
134
- if (key === utils.userSignature || key === utils.userPublicKey) continue
135
-
136
133
  // Properties without signatures in this update are left unchanged
137
134
  if (validProperties.has(soul) && !validProperties.get(soul).has(key)) {
138
135
  continue
package/src/holster.js CHANGED
@@ -67,12 +67,15 @@ const Holster = opt => {
67
67
  for (const key of Object.keys(msg.put[soul])) {
68
68
  const id = utils.rel.is(msg.put[soul][key])
69
69
  if (id) {
70
- // Due to message queuing, the related node might not be available yet
71
70
  // Retry with delays if we get null
72
71
  const attemptRead = async (retries = 0) => {
73
72
  const data = await new Promise(res => {
74
73
  const _ctxid = utils.text.random()
75
- allctx.set(_ctxid, {chain: [{item: null, soul: id}]})
74
+ const ctx = allctx.get(ctxid)
75
+ allctx.set(_ctxid, {
76
+ chain: [{item: null, soul: id}],
77
+ user: ctx ? ctx.user : null,
78
+ })
76
79
  api(_ctxid).next(null, res, _opt)
77
80
  })
78
81
  if (data !== null || retries >= 5) {
@@ -157,11 +160,7 @@ const Holster = opt => {
157
160
  {"#": soul, ".": item},
158
161
  async msg => {
159
162
  if (msg.err) {
160
- if (ctx.user || opt.secure) {
161
- console.log(`error getting ${soul}: ${msg.err}`)
162
- } else {
163
- console.log(`error getting ${item} on ${soul}: ${msg.err}`)
164
- }
163
+ console.log(`error getting ${item} on ${soul}: ${msg.err}`)
165
164
  if (cb) cb(null)
166
165
  return
167
166
  }
@@ -230,7 +229,7 @@ const Holster = opt => {
230
229
  if (cb) cb(null)
231
230
  }
232
231
  },
233
- request._opt,
232
+ {...request._opt, secure: ctx.user || opt.secure},
234
233
  )
235
234
  // Callback has been passed to next soul lookup or called above, so
236
235
  // return false as the calling code should not continue.
@@ -382,66 +381,70 @@ const Holster = opt => {
382
381
  // When result is true data is a property to put on the current soul.
383
382
  // Need to check if item is a rel and also set the node to null. (This
384
383
  // applies for any update from a rel to a property, not just null.)
385
- wire.get({"#": soul, ".": item}, async msg => {
386
- if (msg.err) {
387
- _ack(`error getting ${soul}: ${msg.err}`)
388
- return
389
- }
390
-
391
- let node = msg.put && msg.put[soul]
392
- const current = node && node[item]
393
- const id = utils.rel.is(current)
394
- if (!id) {
395
- // Not a rel, can just put the data.
396
- if (!node) node = {}
397
- node[item] = data
398
- const g = await graph(soul, node, ctx.user, _ack)
399
- if (g === null) return
400
-
401
- wire.put(g, _ack)
402
- return
403
- }
404
-
405
- wire.get({"#": id}, async msg => {
384
+ wire.get(
385
+ {"#": soul, ".": item},
386
+ async msg => {
406
387
  if (msg.err) {
407
- _ack(`error getting ${id}: ${msg.err}`)
388
+ _ack(`error getting ${soul}: ${msg.err}`)
408
389
  return
409
390
  }
410
391
 
411
- if (!msg.put || !msg.put[id]) {
412
- _ack(`error ${id} not found`)
392
+ let node = msg.put && msg.put[soul]
393
+ const current = node && node[item]
394
+ const id = utils.rel.is(current)
395
+ if (!id) {
396
+ // Not a rel, can just put the data.
397
+ if (!node) node = {}
398
+ node[item] = data
399
+ const g = await graph(soul, node, ctx.user, _ack)
400
+ if (g === null) return
401
+
402
+ wire.put(g, _ack)
413
403
  return
414
404
  }
415
405
 
416
- // null each of the properties on the node before putting data.
417
- for (const key of Object.keys(msg.put[id])) {
418
- if (
419
- key === "_" ||
420
- key === utils.userPublicKey ||
421
- key === utils.userSignature
422
- ) {
423
- continue
406
+ wire.get({"#": id}, async msg => {
407
+ if (msg.err) {
408
+ _ack(`error getting ${id}: ${msg.err}`)
409
+ return
424
410
  }
425
411
 
426
- const err = await new Promise(res => {
427
- const _ctxid = utils.text.random()
428
- const chain = [{item: key, soul: id}]
429
- allctx.set(_ctxid, {chain: chain, user: ctx.user})
430
- api(_ctxid).put(null, res)
431
- })
432
- if (err) {
433
- _ack(err)
412
+ if (!msg.put || !msg.put[id]) {
413
+ _ack(`error ${id} not found`)
434
414
  return
435
415
  }
436
- }
437
- if (!node) node = {}
438
- node[item] = data
439
- const g = await graph(soul, node, ctx.user, _ack)
440
- if (g === null) return
441
416
 
442
- wire.put(g, _ack)
443
- })
444
- })
417
+ // null each of the properties on the node before putting data.
418
+ for (const key of Object.keys(msg.put[id])) {
419
+ if (
420
+ key === "_" ||
421
+ key === utils.userPublicKey ||
422
+ key === utils.userSignature
423
+ ) {
424
+ continue
425
+ }
426
+
427
+ const err = await new Promise(res => {
428
+ const _ctxid = utils.text.random()
429
+ const chain = [{item: key, soul: id}]
430
+ allctx.set(_ctxid, {chain: chain, user: ctx.user})
431
+ api(_ctxid).put(null, res)
432
+ })
433
+ if (err) {
434
+ _ack(err)
435
+ return
436
+ }
437
+ }
438
+ if (!node) node = {}
439
+ node[item] = data
440
+ const g = await graph(soul, node, ctx.user, _ack)
441
+ if (g === null) return
442
+
443
+ wire.put(g, _ack)
444
+ })
445
+ },
446
+ {secure: ctx.user || opt.secure},
447
+ )
445
448
  return
446
449
  }
447
450
 
@@ -449,88 +452,97 @@ const Holster = opt => {
449
452
  // Need to check if a rel has already been added on the current node.
450
453
  // Use attemptRead to retry if data hasn't propagated yet.
451
454
  const attemptRead = (retries = 0) => {
452
- wire.get({"#": soul, ".": item}, async msg => {
453
- if (msg.err) {
454
- _ack(`error getting ${soul}.${item}: ${msg.err}`)
455
- return
456
- }
455
+ wire.get(
456
+ {"#": soul, ".": item},
457
+ async msg => {
458
+ if (msg.err) {
459
+ _ack(`error getting ${soul}.${item}: ${msg.err}`)
460
+ return
461
+ }
457
462
 
458
- let node = msg.put && msg.put[soul]
459
- const current = node && node[item]
463
+ let node = msg.put && msg.put[soul]
464
+ const current = node && node[item]
460
465
 
461
- // If we didn't find the item but still have retries, retry
462
- if (!current && retries < 5) {
463
- await new Promise(r => setTimeout(r, 50))
464
- return attemptRead(retries + 1)
465
- }
466
+ // If we didn't find the item but still have retries, retry
467
+ if (!current && retries < 5) {
468
+ await new Promise(r => setTimeout(r, 50))
469
+ return attemptRead(retries + 1)
470
+ }
466
471
 
467
- const id = utils.rel.is(current)
468
- if (!id) {
469
- // The current rel doesn't exist, so add it first.
470
- if (!node) node = {}
471
- node[item] = utils.rel.ify(utils.text.random())
472
- const g = await graph(soul, node, ctx.user, _ack)
473
- if (g === null) return
472
+ const id = utils.rel.is(current)
473
+ if (!id) {
474
+ // The current rel doesn't exist, so add it first.
475
+ if (!node) node = {}
476
+ node[item] = utils.rel.ify(utils.text.random())
477
+ const g = await graph(soul, node, ctx.user, _ack)
478
+ if (g === null) return
474
479
 
475
- wire.put(g, err => {
480
+ wire.put(g, err => {
481
+ if (err) {
482
+ _ack(`error putting ${item} on ${soul}: ${err}`)
483
+ } else {
484
+ const _ctxid = utils.text.random()
485
+ const chain = [{item: item, soul: soul}]
486
+ // Pass on the previous context's callback and user flag here.
487
+ allctx.set(_ctxid, {
488
+ chain: chain,
489
+ user: ctx.user,
490
+ cb: ctx.cb,
491
+ })
492
+ api(_ctxid).put(data)
493
+ }
494
+ })
495
+ return
496
+ }
497
+
498
+ const update = []
499
+ for (const key of result) {
500
+ const err = await new Promise(res => {
501
+ if (utils.obj.is(data[key]) && !utils.rel.is(data[key])) {
502
+ // Use the current rel as the context for nested objects.
503
+ const _ctxid = utils.text.random()
504
+ const chain = [{item: key, soul: id}]
505
+ allctx.set(_ctxid, {chain: chain, user: ctx.user})
506
+ api(_ctxid).put(data[key], res)
507
+ } else {
508
+ // Group the rest of the updates for put below.
509
+ update.push(key)
510
+ res(null)
511
+ }
512
+ })
476
513
  if (err) {
477
- _ack(`error putting ${item} on ${soul}: ${err}`)
478
- } else {
479
- const _ctxid = utils.text.random()
480
- const chain = [{item: item, soul: soul}]
481
- // Pass on the previous context's callback and user flag here.
482
- allctx.set(_ctxid, {chain: chain, user: ctx.user, cb: ctx.cb})
483
- api(_ctxid).put(data)
514
+ _ack(err, ctxid)
515
+ return
484
516
  }
485
- })
486
- return
487
- }
517
+ }
488
518
 
489
- const update = []
490
- for (const key of result) {
491
- const err = await new Promise(res => {
492
- if (utils.obj.is(data[key]) && !utils.rel.is(data[key])) {
493
- // Use the current rel as the context for nested objects.
494
- const _ctxid = utils.text.random()
495
- const chain = [{item: key, soul: id}]
496
- allctx.set(_ctxid, {chain: chain, user: ctx.user})
497
- api(_ctxid).put(data[key], res)
498
- } else {
499
- // Group the rest of the updates for put below.
500
- update.push(key)
501
- res(null)
502
- }
503
- })
504
- if (err) {
505
- _ack(err, ctxid)
519
+ if (update.length === 0) {
520
+ _ack(null)
506
521
  return
507
522
  }
508
- }
509
523
 
510
- if (update.length === 0) {
511
- _ack(null)
512
- return
513
- }
524
+ // The nested objects created above will also have rels on the
525
+ // parent object, so fetch the node so that the rest of the
526
+ // updates can be added.
527
+ wire.get({"#": id}, async msg => {
528
+ if (msg.err) {
529
+ _ack(`error getting ${id}: ${msg.err}`)
530
+ return
531
+ }
514
532
 
515
- // The nested objects created above will also have rels on the parent
516
- // object, so fetch the node so the rest of the updates can be added.
517
- wire.get({"#": id}, async msg => {
518
- if (msg.err) {
519
- _ack(`error getting ${id}: ${msg.err}`)
520
- return
521
- }
533
+ let node = msg.put && msg.put[id]
534
+ if (!node) node = {}
535
+ update.forEach(key => {
536
+ node[key] = data[key]
537
+ })
538
+ const g = await graph(id, node, ctx.user, _ack)
539
+ if (g === null) return
522
540
 
523
- let node = msg.put && msg.put[id]
524
- if (!node) node = {}
525
- update.forEach(key => {
526
- node[key] = data[key]
541
+ wire.put(g, _ack)
527
542
  })
528
- const g = await graph(id, node, ctx.user, _ack)
529
- if (g === null) return
530
-
531
- wire.put(g, _ack)
532
- })
533
- })
543
+ },
544
+ {secure: ctx.user || opt.secure},
545
+ )
534
546
  }
535
547
  attemptRead()
536
548
  },
@@ -556,8 +568,15 @@ const Holster = opt => {
556
568
  const {item, soul} = resolve({on: lex, _get: _get, _opt: _opt}, cb)
557
569
  if (!soul) return
558
570
 
571
+ // Get the context to check if it has user info
572
+ const ctx = allctx.get(ctxid)
573
+
559
574
  // Flag that this context is set from on and shouldn't be removed.
560
- allctx.set(ctxid, {chain: [{item: item, soul: soul}], on: true})
575
+ allctx.set(ctxid, {
576
+ chain: [{item: item, soul: soul}],
577
+ on: true,
578
+ user: ctx ? ctx.user : null,
579
+ })
561
580
  // Map the user's callback because it can also be passed to off,
562
581
  // so need a reference to it to compare them.
563
582
  // Create a new context for each listener invocation to avoid mutation
@@ -574,7 +593,10 @@ const Holster = opt => {
574
593
  const attemptRead = async (retries = 0) => {
575
594
  const data = await new Promise(res => {
576
595
  const _ctxid = utils.text.random()
577
- allctx.set(_ctxid, {chain: [{item: null, soul: id}]})
596
+ allctx.set(_ctxid, {
597
+ chain: [{item: null, soul: id}],
598
+ user: ctx ? ctx.user : null,
599
+ })
578
600
  api(_ctxid).next(null, res, _opt)
579
601
  })
580
602
  if (data !== null || retries >= 5) {
@@ -591,7 +613,7 @@ const Holster = opt => {
591
613
  cb(current !== undefined ? current : null)
592
614
  }
593
615
  },
594
- _opt,
616
+ {..._opt, secure: ctx.user || opt.secure},
595
617
  )
596
618
  })
597
619
 
@@ -637,7 +659,7 @@ const Holster = opt => {
637
659
  map.get(cb)()
638
660
  }
639
661
  },
640
- _opt,
662
+ {..._opt, secure: ctx.user || opt.secure},
641
663
  )
642
664
  },
643
665
  off: cb => {
package/src/sea.js CHANGED
@@ -264,7 +264,6 @@ const SEA = {
264
264
 
265
265
  const propSig = propertySignatures[k]
266
266
  if (!propSig) {
267
- console.log(`warning: property '${k}' missing signature`)
268
267
  continue
269
268
  }
270
269
 
package/src/store.js CHANGED
@@ -211,7 +211,7 @@ const Store = opt => {
211
211
  const radisk = Radisk(opt)
212
212
 
213
213
  return {
214
- get: (lex, cb) => {
214
+ get: (lex, cb, _opt) => {
215
215
  if (!lex || !utils.obj.is(lex)) {
216
216
  cb("lex required")
217
217
  return
@@ -221,8 +221,10 @@ const Store = opt => {
221
221
  return
222
222
  }
223
223
 
224
+ if (!_opt) _opt = {}
225
+
224
226
  var soul = lex["#"]
225
- var key = typeof lex["."] === "string" ? lex["."] : ""
227
+ var key = !_opt.secure && typeof lex["."] === "string" ? lex["."] : ""
226
228
  var node
227
229
  var signatures = {}
228
230
  const each = (value, key) => {
package/src/wire.js CHANGED
@@ -197,46 +197,6 @@ const Wire = opt => {
197
197
  const rateLimiter = createRateLimiter(isTestEnv)
198
198
  const connectionManager = createConnectionManager(opt.maxConnections || 1000)
199
199
 
200
- // The check function is required because user data must provide a public key
201
- // so that it can be verified. The public key might verify the provided
202
- // signature but not actually match the user under which the data is being
203
- // stored. To avoid this, the current data on a soul needs to be checked to
204
- // make sure the stored public key matches the one provided with the update.
205
- const check = async (data, send, cb) => {
206
- for (const soul of Object.keys(data)) {
207
- const msg = await new Promise(res => {
208
- getWithCallback({"#": soul}, res, send)
209
- })
210
- if (msg.err) {
211
- if (cb) cb(msg.err)
212
- return false
213
- }
214
-
215
- const node = data[soul]
216
- const key = utils.userPublicKey
217
- // If there is no current node then the data is ok to write without
218
- // matching public keys, as the provided soul also needs a rel on the
219
- // parent node which then also requires checking. Otherwise publc keys
220
- // need to match for existing data.
221
- if (!msg.put || !msg.put[soul] || msg.put[soul][key] === node[key]) {
222
- continue
223
- }
224
-
225
- // If a soul exists but does not have a public key, then one should not be
226
- // added because the node is not user data. The above check fails in this
227
- // case if a public key is provided. Note that this is only an error case
228
- // if called via the API, which is when a callback is provided here.
229
- // (The wire spec can fetch and put data on the wire without a signature
230
- // or public key and this can be ignored.)
231
- if (cb) {
232
- cb(`error in wire check public key does not match for soul: ${soul}`)
233
- }
234
- return false
235
- }
236
-
237
- return true
238
- }
239
-
240
200
  const get = (msg, send) => {
241
201
  const ack = Get(msg.get, graph)
242
202
  if (ack) {
@@ -248,16 +208,22 @@ const Wire = opt => {
248
208
  }),
249
209
  )
250
210
  } else {
251
- store.get(msg.get, (err, ack) => {
252
- send(
253
- JSON.stringify({
254
- "#": dup.track(utils.text.random(9)),
255
- "@": msg["#"],
256
- put: ack,
257
- err: err,
258
- }),
259
- )
260
- })
211
+ // Always pass secure to ensure public key is fetched. User data may be
212
+ // requested that requires the public key for verification.
213
+ store.get(
214
+ msg.get,
215
+ (err, ack) => {
216
+ send(
217
+ JSON.stringify({
218
+ "#": dup.track(utils.text.random(9)),
219
+ "@": msg["#"],
220
+ put: ack,
221
+ err: err,
222
+ }),
223
+ )
224
+ },
225
+ {secure: true},
226
+ )
261
227
  }
262
228
  }
263
229
 
@@ -275,8 +241,6 @@ const Wire = opt => {
275
241
  return
276
242
  }
277
243
 
278
- if (!(await check(update.now, send))) return
279
-
280
244
  store.put(update.now, err => {
281
245
  send(
282
246
  JSON.stringify({
@@ -318,36 +282,40 @@ const Wire = opt => {
318
282
  return
319
283
  }
320
284
 
321
- store.get(lex, (err, ack) => {
322
- if (ack) {
323
- // Also send request on the wire to check for updates.
324
- const sendResult = send(request)
325
- if (sendResult && sendResult.err) {
326
- cb({err: sendResult.err})
285
+ store.get(
286
+ lex,
287
+ (err, ack) => {
288
+ if (ack) {
289
+ // Also send request on the wire to check for updates.
290
+ const sendResult = send(request)
291
+ if (sendResult && sendResult.err) {
292
+ cb({err: sendResult.err})
293
+ return
294
+ }
295
+ cb({put: ack, err: err})
327
296
  return
328
297
  }
329
- cb({put: ack, err: err})
330
- return
331
- }
332
298
 
333
- if (err) console.log(err)
299
+ if (err) console.log(err)
334
300
 
335
- queue[track] = cb
301
+ queue[track] = cb
336
302
 
337
- // Store timeout config to start after message is sent from queue
338
- pendingTimeouts.set(track, {
339
- lex: lex,
340
- wait: _opt.wait || 100,
341
- })
303
+ // Store timeout config to start after message is sent from queue
304
+ pendingTimeouts.set(track, {
305
+ lex: lex,
306
+ wait: _opt.wait || 100,
307
+ })
342
308
 
343
- const sendResult = send(request)
344
- if (sendResult && sendResult.err) {
345
- cb({err: sendResult.err})
346
- delete queue[track]
347
- pendingTimeouts.delete(track)
348
- return
349
- }
350
- })
309
+ const sendResult = send(request)
310
+ if (sendResult && sendResult.err) {
311
+ cb({err: sendResult.err})
312
+ delete queue[track]
313
+ pendingTimeouts.delete(track)
314
+ return
315
+ }
316
+ },
317
+ _opt,
318
+ )
351
319
  }
352
320
 
353
321
  const api = send => {
@@ -374,8 +342,6 @@ const Wire = opt => {
374
342
  return
375
343
  }
376
344
 
377
- if (!(await check(update.now, send, cb))) return
378
-
379
345
  // Seed pendingReferences with any new references from API calls
380
346
  for (const [soul, node] of Object.entries(update.now)) {
381
347
  if (node && typeof node === "object") {