@mblaney/holster 1.0.1 → 1.0.3
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/package.json +1 -1
- package/src/holster.js +4 -3
- package/src/store.js +60 -12
- package/src/user.js +102 -90
- package/src/wire.js +30 -5
package/package.json
CHANGED
package/src/holster.js
CHANGED
|
@@ -229,7 +229,7 @@ const Holster = opt => {
|
|
|
229
229
|
// The context has been resolved but it does not include the requested
|
|
230
230
|
// node, which requires one more lookup.
|
|
231
231
|
ctx.chain.push({item: null, soul: null})
|
|
232
|
-
api(ctxid).next(null, request.get, cb)
|
|
232
|
+
api(ctxid).next(null, request.get, cb, request._opt)
|
|
233
233
|
return false
|
|
234
234
|
}
|
|
235
235
|
|
|
@@ -361,7 +361,8 @@ const Holster = opt => {
|
|
|
361
361
|
return
|
|
362
362
|
}
|
|
363
363
|
|
|
364
|
-
// Resolve the current context before putting data.
|
|
364
|
+
// Resolve the current context before putting data. (Note that set is
|
|
365
|
+
// not passed to resolve because it's already been applied above.)
|
|
365
366
|
const {item, soul} = resolve({put: data}, _ack)
|
|
366
367
|
if (!soul) return
|
|
367
368
|
|
|
@@ -639,7 +640,7 @@ const Holster = opt => {
|
|
|
639
640
|
|
|
640
641
|
// When there's a callback need to resolve the context first.
|
|
641
642
|
const _done = done(ctxid)
|
|
642
|
-
const {soul} = resolve({get: lex}, _done)
|
|
643
|
+
const {soul} = resolve({get: lex, _opt: _opt}, _done)
|
|
643
644
|
if (soul) get(lex, soul, _done, _opt)
|
|
644
645
|
}
|
|
645
646
|
}
|
package/src/store.js
CHANGED
|
@@ -35,11 +35,11 @@ const fileSystem = opt => {
|
|
|
35
35
|
})
|
|
36
36
|
},
|
|
37
37
|
put: (file, data, cb) => {
|
|
38
|
-
var random = Math.random().toString(36).slice(-9)
|
|
39
38
|
// Don't put tmp files under dir so that they're not listed.
|
|
40
|
-
var tmp = file + "." + random + ".tmp"
|
|
39
|
+
var tmp = file + "." + utils.text.random(9) + ".tmp"
|
|
41
40
|
fs.writeFile(tmp, data, err => {
|
|
42
41
|
if (err) {
|
|
42
|
+
console.log("fs.writeFile error:", err)
|
|
43
43
|
cb(err)
|
|
44
44
|
return
|
|
45
45
|
}
|
|
@@ -90,7 +90,7 @@ const fileSystem = opt => {
|
|
|
90
90
|
|
|
91
91
|
return {
|
|
92
92
|
get: (file, cb) => {
|
|
93
|
-
|
|
93
|
+
const _get = (file, cb) => {
|
|
94
94
|
const tx = db.transaction([dir], "readonly")
|
|
95
95
|
const req = tx.objectStore(dir).get(file)
|
|
96
96
|
req.onerror = () => {
|
|
@@ -99,12 +99,28 @@ const fileSystem = opt => {
|
|
|
99
99
|
req.onsuccess = () => {
|
|
100
100
|
cb(null, req.result)
|
|
101
101
|
}
|
|
102
|
-
} else {
|
|
103
|
-
cb("error indexedDB not available")
|
|
104
102
|
}
|
|
103
|
+
if (db) {
|
|
104
|
+
_get(file, cb)
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let retry = 0
|
|
109
|
+
const interval = setInterval(() => {
|
|
110
|
+
if (db) {
|
|
111
|
+
clearInterval(interval)
|
|
112
|
+
_get(file, cb)
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (retry++ > 5) {
|
|
117
|
+
clearInterval(interval)
|
|
118
|
+
cb("error indexedDB not available")
|
|
119
|
+
}
|
|
120
|
+
}, 1000)
|
|
105
121
|
},
|
|
106
122
|
put: (file, data, cb) => {
|
|
107
|
-
|
|
123
|
+
const _put = (file, data, cb) => {
|
|
108
124
|
const tx = db.transaction([dir], "readwrite")
|
|
109
125
|
const req = tx.objectStore(dir).put(data, file)
|
|
110
126
|
req.onerror = () => {
|
|
@@ -113,12 +129,28 @@ const fileSystem = opt => {
|
|
|
113
129
|
req.onsuccess = () => {
|
|
114
130
|
cb(null)
|
|
115
131
|
}
|
|
116
|
-
} else {
|
|
117
|
-
cb("error indexedDB not available")
|
|
118
132
|
}
|
|
133
|
+
if (db) {
|
|
134
|
+
_put(file, data, cb)
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let retry = 0
|
|
139
|
+
const interval = setInterval(() => {
|
|
140
|
+
if (db) {
|
|
141
|
+
clearInterval(interval)
|
|
142
|
+
_put(file, data, cb)
|
|
143
|
+
return
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (retry++ > 5) {
|
|
147
|
+
clearInterval(interval)
|
|
148
|
+
cb("error indexedDB not available")
|
|
149
|
+
}
|
|
150
|
+
}, 1000)
|
|
119
151
|
},
|
|
120
152
|
list: cb => {
|
|
121
|
-
|
|
153
|
+
const _list = cb => {
|
|
122
154
|
const tx = db.transaction([dir], "readonly")
|
|
123
155
|
const req = tx.objectStore(dir).getAllKeys()
|
|
124
156
|
req.onerror = () => console.log("error getting keys for", dir)
|
|
@@ -126,10 +158,26 @@ const fileSystem = opt => {
|
|
|
126
158
|
req.result.forEach(cb)
|
|
127
159
|
cb()
|
|
128
160
|
}
|
|
129
|
-
} else {
|
|
130
|
-
console.log("error indexedDB not available")
|
|
131
|
-
cb()
|
|
132
161
|
}
|
|
162
|
+
if (db) {
|
|
163
|
+
_list(cb)
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
let retry = 0
|
|
168
|
+
const interval = setInterval(() => {
|
|
169
|
+
if (db) {
|
|
170
|
+
clearInterval(interval)
|
|
171
|
+
_list(cb)
|
|
172
|
+
return
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (retry++ > 5) {
|
|
176
|
+
clearInterval(interval)
|
|
177
|
+
console.log("error indexedDB not available")
|
|
178
|
+
cb()
|
|
179
|
+
}
|
|
180
|
+
}, 1000)
|
|
133
181
|
},
|
|
134
182
|
}
|
|
135
183
|
}
|
package/src/user.js
CHANGED
|
@@ -27,53 +27,57 @@ const User = (opt, wire) => {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
const pub = pubs.shift()
|
|
30
|
-
wire.get(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
wire.get(
|
|
31
|
+
{"#": pub},
|
|
32
|
+
async msg => {
|
|
33
|
+
if (msg.err) {
|
|
34
|
+
done(`error getting ${pub}: ${msg.err}`)
|
|
35
|
+
return
|
|
36
|
+
}
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
const data = msg.put && msg.put[pub]
|
|
39
|
+
if (!data || !data.auth) return next()
|
|
38
40
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
const auth = JSON.parse(data.auth)
|
|
42
|
+
const work = await SEA.work(password, auth.salt)
|
|
43
|
+
const dec = await SEA.decrypt(auth.enc, work)
|
|
44
|
+
if (!dec) return next()
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
username: username,
|
|
46
|
-
pub: data.pub,
|
|
47
|
-
epub: data.epub,
|
|
48
|
-
priv: dec.priv,
|
|
49
|
-
epriv: dec.epriv,
|
|
50
|
-
}
|
|
51
|
-
if (newPassword !== "") {
|
|
52
|
-
// Encrypt private key using new password and a new salt.
|
|
53
|
-
const salt = utils.text.random(64)
|
|
54
|
-
const work = await SEA.work(newPassword, salt)
|
|
55
|
-
const enc = await SEA.encrypt(dec, work)
|
|
56
|
-
// Put all fields so that verification applies to the whole node.
|
|
57
|
-
const update = {
|
|
46
|
+
user.is = {
|
|
58
47
|
username: username,
|
|
59
48
|
pub: data.pub,
|
|
60
49
|
epub: data.epub,
|
|
61
|
-
|
|
50
|
+
priv: dec.priv,
|
|
51
|
+
epriv: dec.epriv,
|
|
62
52
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
53
|
+
if (newPassword !== "") {
|
|
54
|
+
// Encrypt private key using new password and a new salt.
|
|
55
|
+
const salt = utils.text.random(64)
|
|
56
|
+
const work = await SEA.work(newPassword, salt)
|
|
57
|
+
const enc = await SEA.encrypt(dec, work)
|
|
58
|
+
// Put all fields so that verification applies to the whole node.
|
|
59
|
+
const update = {
|
|
60
|
+
username: username,
|
|
61
|
+
pub: data.pub,
|
|
62
|
+
epub: data.epub,
|
|
63
|
+
auth: JSON.stringify({enc: enc, salt: salt}),
|
|
70
64
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
65
|
+
const signed = await SEA.sign(update, user.is)
|
|
66
|
+
const graph = utils.graph(pub, signed.m, signed.s, data.pub)
|
|
67
|
+
wire.put(graph, err => {
|
|
68
|
+
if (err) {
|
|
69
|
+
done(`error putting ${update} on ${pub}: ${err}`)
|
|
70
|
+
} else {
|
|
71
|
+
done(null)
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
return
|
|
75
|
+
}
|
|
74
76
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
return done(null)
|
|
78
|
+
},
|
|
79
|
+
{wait: 1000},
|
|
80
|
+
)
|
|
77
81
|
}
|
|
78
82
|
|
|
79
83
|
if (retries > 9) {
|
|
@@ -82,21 +86,25 @@ const User = (opt, wire) => {
|
|
|
82
86
|
}
|
|
83
87
|
|
|
84
88
|
const soul = "~@" + username
|
|
85
|
-
wire.get(
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (!data) return retry(username)
|
|
89
|
+
wire.get(
|
|
90
|
+
{"#": soul},
|
|
91
|
+
async msg => {
|
|
92
|
+
if (msg.err) {
|
|
93
|
+
done(`error getting ${soul}: ${msg.err}`)
|
|
94
|
+
return
|
|
95
|
+
}
|
|
93
96
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
const data = msg.put && msg.put[soul]
|
|
98
|
+
if (!data) return retry(username)
|
|
99
|
+
|
|
100
|
+
delete msg.put[soul]._
|
|
101
|
+
// Usernames aren't guaranteed to be unique, so store the list and then
|
|
102
|
+
// try each of them until one is successful with the given password.
|
|
103
|
+
pubs = Object.keys(data)
|
|
104
|
+
next()
|
|
105
|
+
},
|
|
106
|
+
{wait: 1000},
|
|
107
|
+
)
|
|
100
108
|
}
|
|
101
109
|
|
|
102
110
|
const user = {
|
|
@@ -124,53 +132,57 @@ const User = (opt, wire) => {
|
|
|
124
132
|
creating = true
|
|
125
133
|
|
|
126
134
|
const soul = "~@" + username
|
|
127
|
-
wire.get(
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
creating = false
|
|
136
|
-
ack("Username already exists")
|
|
137
|
-
return
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const salt = utils.text.random(64)
|
|
141
|
-
const work = await SEA.work(password, salt)
|
|
142
|
-
const pair = await SEA.pair()
|
|
143
|
-
const priv = {priv: pair.priv, epriv: pair.epriv}
|
|
144
|
-
const enc = await SEA.encrypt(priv, work)
|
|
145
|
-
const data = {
|
|
146
|
-
username: username,
|
|
147
|
-
pub: pair.pub,
|
|
148
|
-
epub: pair.epub,
|
|
149
|
-
auth: JSON.stringify({enc: enc, salt: salt}),
|
|
150
|
-
}
|
|
135
|
+
wire.get(
|
|
136
|
+
{"#": soul},
|
|
137
|
+
async msg => {
|
|
138
|
+
if (msg.err) {
|
|
139
|
+
creating = false
|
|
140
|
+
ack(`error getting ${soul}: ${msg.err}`)
|
|
141
|
+
return
|
|
142
|
+
}
|
|
151
143
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
wire.put(graph, err => {
|
|
156
|
-
creating = false
|
|
157
|
-
if (err) {
|
|
158
|
-
ack(`error putting ${data} on ${pub}: ${err}`)
|
|
144
|
+
if (msg.put && msg.put[soul]) {
|
|
145
|
+
creating = false
|
|
146
|
+
ack("Username already exists")
|
|
159
147
|
return
|
|
160
148
|
}
|
|
161
149
|
|
|
162
|
-
const
|
|
163
|
-
|
|
150
|
+
const salt = utils.text.random(64)
|
|
151
|
+
const work = await SEA.work(password, salt)
|
|
152
|
+
const pair = await SEA.pair()
|
|
153
|
+
const priv = {priv: pair.priv, epriv: pair.epriv}
|
|
154
|
+
const enc = await SEA.encrypt(priv, work)
|
|
155
|
+
const data = {
|
|
156
|
+
username: username,
|
|
157
|
+
pub: pair.pub,
|
|
158
|
+
epub: pair.epub,
|
|
159
|
+
auth: JSON.stringify({enc: enc, salt: salt}),
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const pub = "~" + pair.pub
|
|
163
|
+
const signed = await SEA.sign(data, pair)
|
|
164
|
+
const graph = utils.graph(pub, signed.m, signed.s, pair.pub)
|
|
165
|
+
wire.put(graph, err => {
|
|
166
|
+
creating = false
|
|
164
167
|
if (err) {
|
|
165
|
-
ack(`error putting ${
|
|
168
|
+
ack(`error putting ${data} on ${pub}: ${err}`)
|
|
166
169
|
return
|
|
167
170
|
}
|
|
168
171
|
|
|
169
|
-
|
|
170
|
-
|
|
172
|
+
const rel = {[pub]: {"#": pub}}
|
|
173
|
+
wire.put(utils.graph(soul, rel), err => {
|
|
174
|
+
if (err) {
|
|
175
|
+
ack(`error putting ${rel} on ${soul}: ${err}`)
|
|
176
|
+
return
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Return null on success.
|
|
180
|
+
if (cb) cb(null)
|
|
181
|
+
})
|
|
171
182
|
})
|
|
172
|
-
}
|
|
173
|
-
|
|
183
|
+
},
|
|
184
|
+
{wait: 1000},
|
|
185
|
+
)
|
|
174
186
|
},
|
|
175
187
|
auth: (username, password, cb) => {
|
|
176
188
|
const ack = err => {
|
package/src/wire.js
CHANGED
|
@@ -220,8 +220,22 @@ const Wire = opt => {
|
|
|
220
220
|
|
|
221
221
|
const send = (data, isBinary) => {
|
|
222
222
|
clients().forEach(client => {
|
|
223
|
-
if (client.readyState === WebSocket.OPEN) {
|
|
223
|
+
if (client && client.readyState === WebSocket.OPEN) {
|
|
224
224
|
client.send(data, {binary: isBinary})
|
|
225
|
+
} else {
|
|
226
|
+
let retry = 0
|
|
227
|
+
const interval = setInterval(() => {
|
|
228
|
+
if (client && client.readyState === WebSocket.OPEN) {
|
|
229
|
+
clearInterval(interval)
|
|
230
|
+
client.send(data, {binary: isBinary})
|
|
231
|
+
return
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (retry++ > 5) {
|
|
235
|
+
clearInterval(interval)
|
|
236
|
+
console.log("websocket not available")
|
|
237
|
+
}
|
|
238
|
+
}, 1000)
|
|
225
239
|
}
|
|
226
240
|
})
|
|
227
241
|
}
|
|
@@ -257,7 +271,19 @@ const Wire = opt => {
|
|
|
257
271
|
if (peer && peer.readyState === WebSocket.OPEN) {
|
|
258
272
|
peer.send(data)
|
|
259
273
|
} else {
|
|
260
|
-
|
|
274
|
+
let retry = 0
|
|
275
|
+
const interval = setInterval(() => {
|
|
276
|
+
if (peer && peer.readyState === WebSocket.OPEN) {
|
|
277
|
+
clearInterval(interval)
|
|
278
|
+
peer.send(data)
|
|
279
|
+
return
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (retry++ > 5) {
|
|
283
|
+
clearInterval(interval)
|
|
284
|
+
console.log("websocket not available")
|
|
285
|
+
}
|
|
286
|
+
}, 1000)
|
|
261
287
|
}
|
|
262
288
|
})
|
|
263
289
|
}
|
|
@@ -265,10 +291,9 @@ const Wire = opt => {
|
|
|
265
291
|
opt.peers = [`ws://localhost:${opt.port || 8765}`]
|
|
266
292
|
}
|
|
267
293
|
opt.peers.forEach(peer => {
|
|
268
|
-
let ws = new WebSocket(peer)
|
|
269
|
-
peers.push(ws)
|
|
270
294
|
const start = () => {
|
|
271
|
-
|
|
295
|
+
let ws = new WebSocket(peer)
|
|
296
|
+
peers.push(ws)
|
|
272
297
|
ws.onclose = c => {
|
|
273
298
|
if (peers.indexOf(ws) !== -1) {
|
|
274
299
|
peers.splice(peers.indexOf(ws), 1)
|