corestore 6.0.1-alpha.11 → 6.0.1-alpha.14

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.
@@ -1,23 +1,22 @@
1
- name: Test on Node.js
2
-
1
+ name: Build Status
3
2
  on:
4
3
  push:
5
4
  branches:
6
- - main
5
+ - master
7
6
  pull_request:
8
7
  branches:
9
- - main
8
+ - master
10
9
  jobs:
11
10
  build:
12
11
  strategy:
13
12
  matrix:
14
- node-version: [12.x, 14.x, 16.x]
13
+ node-version: [lts/*]
15
14
  os: [ubuntu-latest, macos-latest, windows-latest]
16
15
  runs-on: ${{ matrix.os }}
17
16
  steps:
18
17
  - uses: actions/checkout@v2
19
18
  - name: Use Node.js ${{ matrix.node-version }}
20
- uses: actions/setup-node@v1
19
+ uses: actions/setup-node@v2
21
20
  with:
22
21
  node-version: ${{ matrix.node-version }}
23
22
  - run: npm install
package/index.js CHANGED
@@ -25,12 +25,43 @@ module.exports = class Corestore extends EventEmitter {
25
25
  this._namespace = opts._namespace || DEFAULT_NAMESPACE
26
26
  this._replicationStreams = opts._streams || []
27
27
  this._streamSessions = opts._streamSessions || new Map()
28
+ this._sessions = new Set() // sessions for THIS namespace
29
+
30
+ this._findingPeersCount = 0
31
+ this._findingPeers = []
28
32
 
29
33
  this._opening = opts._opening ? opts._opening.then(() => this._open()) : this._open()
30
34
  this._opening.catch(safetyCatch)
31
35
  this.ready = () => this._opening
32
36
  }
33
37
 
38
+ findingPeers () {
39
+ let done = false
40
+ this._incFindingPeers()
41
+
42
+ return () => {
43
+ if (done) return
44
+ done = true
45
+ this._decFindingPeers()
46
+ }
47
+ }
48
+
49
+ _incFindingPeers () {
50
+ if (++this._findingPeersCount !== 1) return
51
+
52
+ for (const core of this._sessions) {
53
+ this._findingPeers.push(core.findingPeers())
54
+ }
55
+ }
56
+
57
+ _decFindingPeers () {
58
+ if (--this._findingPeersCount !== 0) return
59
+
60
+ while (this._findingPeers.length > 0) {
61
+ this._findingPeers.pop()()
62
+ }
63
+ }
64
+
34
65
  async _open () {
35
66
  if (this.keys) {
36
67
  this.keys = await this.keys // opts.keys can be a Promise that resolves to a KeyManager
@@ -43,7 +74,7 @@ module.exports = class Corestore extends EventEmitter {
43
74
  if (opts._discoveryKey) {
44
75
  return {
45
76
  keyPair: null,
46
- sign: null,
77
+ auth: null,
47
78
  discoveryKey: opts._discoveryKey
48
79
  }
49
80
  }
@@ -54,16 +85,17 @@ module.exports = class Corestore extends EventEmitter {
54
85
  secretKey: opts.secretKey
55
86
  },
56
87
  sign: opts.sign,
88
+ auth: opts.auth,
57
89
  discoveryKey: crypto.discoveryKey(opts.publicKey)
58
90
  }
59
91
  }
60
- const { publicKey, sign } = await this.keys.createHypercoreKeyPair(opts.name, this._namespace)
92
+ const { publicKey, auth } = await this.keys.createHypercoreKeyPair(opts.name, this._namespace)
61
93
  return {
62
94
  keyPair: {
63
95
  publicKey,
64
96
  secretKey: null
65
97
  },
66
- sign,
98
+ auth,
67
99
  discoveryKey: crypto.discoveryKey(publicKey)
68
100
  }
69
101
  }
@@ -80,11 +112,11 @@ module.exports = class Corestore extends EventEmitter {
80
112
  if (!name) return
81
113
 
82
114
  const namespace = this._getPrereadyUserData(core, USERDATA_NAMESPACE_KEY)
83
- const { publicKey, sign } = await this.keys.createHypercoreKeyPair(b4a.toString(name), namespace)
115
+ const { publicKey, auth } = await this.keys.createHypercoreKeyPair(b4a.toString(name), namespace)
84
116
  if (!b4a.equals(publicKey, core.key)) throw new Error('Stored core key does not match the provided name')
85
117
 
86
- // TODO: Should Hypercore expose a helper for this, or should preready return keypair/sign?
87
- core.sign = sign
118
+ // TODO: Should Hypercore expose a helper for this, or should preready return keypair/auth?
119
+ core.auth = auth
88
120
  core.key = publicKey
89
121
  core.writable = true
90
122
  }
@@ -92,12 +124,12 @@ module.exports = class Corestore extends EventEmitter {
92
124
  async _preload (opts) {
93
125
  await this.ready()
94
126
 
95
- const { discoveryKey, keyPair, sign } = await this._generateKeys(opts)
127
+ const { discoveryKey, keyPair, auth } = await this._generateKeys(opts)
96
128
  const id = b4a.toString(discoveryKey, 'hex')
97
129
 
98
130
  while (this.cores.has(id)) {
99
131
  const existing = this.cores.get(id)
100
- if (existing.opened && !existing.closing) return { from: existing, keyPair, sign }
132
+ if (existing.opened && !existing.closing) return { from: existing, keyPair, auth }
101
133
  if (!existing.opened) {
102
134
  await existing.ready().catch(safetyCatch)
103
135
  } else if (existing.closing) {
@@ -119,7 +151,7 @@ module.exports = class Corestore extends EventEmitter {
119
151
  autoClose: true,
120
152
  encryptionKey: opts.encryptionKey || null,
121
153
  userData,
122
- sign: null,
154
+ auth,
123
155
  createIfMissing: !opts._discoveryKey,
124
156
  keyPair: keyPair && keyPair.publicKey
125
157
  ? {
@@ -144,7 +176,7 @@ module.exports = class Corestore extends EventEmitter {
144
176
  this.cores.delete(id)
145
177
  })
146
178
 
147
- return { from: core, keyPair, sign }
179
+ return { from: core, keyPair, auth }
148
180
  }
149
181
 
150
182
  get (opts = {}) {
@@ -154,6 +186,19 @@ module.exports = class Corestore extends EventEmitter {
154
186
  name: null,
155
187
  preload: () => this._preload(opts)
156
188
  })
189
+
190
+ this._sessions.add(core)
191
+ if (this._findingPeersCount > 0) {
192
+ this._findingPeers.push(core.findingPeers())
193
+ }
194
+
195
+ core.once('close', () => {
196
+ // technically better to also clear _findingPeers if we added it,
197
+ // but the lifecycle for those are pretty short so prob not worth the complexity
198
+ // as _decFindingPeers clear them all.
199
+ this._sessions.delete(core)
200
+ })
201
+
157
202
  return core
158
203
  }
159
204
 
@@ -201,7 +246,7 @@ module.exports = class Corestore extends EventEmitter {
201
246
 
202
247
  async _close () {
203
248
  await this._opening
204
- if (!this._namespace.equals(DEFAULT_NAMESPACE)) return // namespaces should not release resources on close
249
+ if (!b4a.equals(this._namespace, DEFAULT_NAMESPACE)) return // namespaces should not release resources on close
205
250
  const closePromises = []
206
251
  for (const core of this.cores.values()) {
207
252
  closePromises.push(core.close())
package/lib/keys.js CHANGED
@@ -28,7 +28,12 @@ module.exports = class KeyManager {
28
28
  const keyPair = {
29
29
  publicKey: b4a.allocUnsafe(sodium.crypto_sign_PUBLICKEYBYTES),
30
30
  _secretKey: b4a.alloc(sodium.crypto_sign_SECRETKEYBYTES),
31
- sign: (msg) => this._sign(keyPair, msg)
31
+ auth: {
32
+ sign: (msg) => this._sign(keyPair, msg),
33
+ verify: (signable, signature) => {
34
+ return sodium.crypto_sign_detached(signature, signable, keyPair.publicKey)
35
+ }
36
+ }
32
37
  }
33
38
 
34
39
  sodium.crypto_sign_seed_keypair(keyPair.publicKey, keyPair._secretKey, this.createSecret(name, token))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "corestore",
3
- "version": "6.0.1-alpha.11",
3
+ "version": "6.0.1-alpha.14",
4
4
  "description": "A Hypercore factory that simplifies managing collections of cores.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -22,9 +22,8 @@
22
22
  "devDependencies": {
23
23
  "brittle": "^1.6.0",
24
24
  "random-access-file": "^2.2.0",
25
- "random-access-memory": "^3.1.2",
26
- "standardx": "^7.0.0",
27
- "tmp-promise": "^3.0.2"
25
+ "random-access-memory": "^4.0.0",
26
+ "standardx": "^7.0.0"
28
27
  },
29
28
  "dependencies": {
30
29
  "b4a": "^1.3.1",
package/test/all.js CHANGED
@@ -1,7 +1,8 @@
1
1
  const { test, configure } = require('brittle')
2
2
  const crypto = require('hypercore-crypto')
3
3
  const ram = require('random-access-memory')
4
- const tmp = require('tmp-promise')
4
+ const os = require('os')
5
+ const path = require('path')
5
6
 
6
7
  const Corestore = require('..')
7
8
 
@@ -99,8 +100,8 @@ test('replicating cores created after replication begins', async function (t) {
99
100
  })
100
101
 
101
102
  test('replicating cores using discovery key hook', async function (t) {
102
- const dir = await tmp.dir({ unsafeCleanup: true })
103
- let store1 = new Corestore(dir.path)
103
+ const dir = tmpdir()
104
+ let store1 = new Corestore(dir)
104
105
  const store2 = new Corestore(ram)
105
106
 
106
107
  const core = store1.get({ name: 'main' })
@@ -108,15 +109,13 @@ test('replicating cores using discovery key hook', async function (t) {
108
109
  const key = core.key
109
110
 
110
111
  await store1.close()
111
- store1 = new Corestore(dir.path)
112
+ store1 = new Corestore(dir)
112
113
 
113
114
  const s = store1.replicate(true, { live: true })
114
115
  s.pipe(store2.replicate(false, { live: true })).pipe(s)
115
116
 
116
117
  const core2 = store2.get(key)
117
118
  t.alike(await core2.get(0), Buffer.from('hello'))
118
-
119
- await dir.cleanup()
120
119
  })
121
120
 
122
121
  test('nested namespaces', async function (t) {
@@ -144,9 +143,9 @@ test('core uncached when all sessions close', async function (t) {
144
143
  })
145
144
 
146
145
  test('writable core loaded from name userData', async function (t) {
147
- const dir = await tmp.dir({ unsafeCleanup: true })
146
+ const dir = tmpdir()
148
147
 
149
- let store = new Corestore(dir.path)
148
+ let store = new Corestore(dir)
150
149
  let core = store.get({ name: 'main' })
151
150
  await core.ready()
152
151
  const key = core.key
@@ -156,7 +155,7 @@ test('writable core loaded from name userData', async function (t) {
156
155
  t.is(core.length, 1)
157
156
 
158
157
  await store.close()
159
- store = new Corestore(dir.path)
158
+ store = new Corestore(dir)
160
159
  core = store.get(key)
161
160
  await core.ready()
162
161
 
@@ -165,25 +164,21 @@ test('writable core loaded from name userData', async function (t) {
165
164
  t.is(core.length, 2)
166
165
  t.alike(await core.get(0), Buffer.from('hello'))
167
166
  t.alike(await core.get(1), Buffer.from('world'))
168
-
169
- await dir.cleanup()
170
167
  })
171
168
 
172
169
  test('storage locking', async function (t) {
173
- const dir = await tmp.dir({ unsafeCleanup: true })
170
+ const dir = tmpdir()
174
171
 
175
- const store1 = new Corestore(dir.path)
172
+ const store1 = new Corestore(dir)
176
173
  await store1.ready()
177
174
 
178
- const store2 = new Corestore(dir.path)
175
+ const store2 = new Corestore(dir)
179
176
  try {
180
177
  await store2.ready()
181
178
  t.fail('dir should have been locked')
182
179
  } catch {
183
180
  t.pass('dir was locked')
184
181
  }
185
-
186
- await dir.cleanup()
187
182
  })
188
183
 
189
184
  test('closing a namespace does not close cores', async function (t) {
@@ -205,3 +200,53 @@ test('closing a namespace does not close cores', async function (t) {
205
200
  t.ok(core1.closed)
206
201
  t.ok(core2.closed)
207
202
  })
203
+
204
+ test('findingPeers', async function (t) {
205
+ t.plan(6)
206
+
207
+ const store = new Corestore(ram)
208
+
209
+ const ns1 = store.namespace('ns1')
210
+ const ns2 = store.namespace('ns2')
211
+
212
+ const a = ns1.get(Buffer.alloc(32).fill('a'))
213
+ const b = ns2.get(Buffer.alloc(32).fill('b'))
214
+
215
+ const done = ns1.findingPeers()
216
+
217
+ let aUpdated = false
218
+ let bUpdated = false
219
+ let cUpdated = false
220
+
221
+ const c = ns1.get(Buffer.alloc(32).fill('c'))
222
+
223
+ a.update().then(function (bool) {
224
+ aUpdated = true
225
+ })
226
+
227
+ b.update().then(function (bool) {
228
+ bUpdated = true
229
+ })
230
+
231
+ c.update().then(function (bool) {
232
+ cUpdated = true
233
+ })
234
+
235
+ await new Promise(resolve => setImmediate(resolve))
236
+
237
+ t.is(aUpdated, false)
238
+ t.is(bUpdated, true)
239
+ t.is(cUpdated, false)
240
+
241
+ done()
242
+
243
+ await new Promise(resolve => setImmediate(resolve))
244
+
245
+ t.is(aUpdated, true)
246
+ t.is(bUpdated, true)
247
+ t.is(cUpdated, true)
248
+ })
249
+
250
+ function tmpdir () {
251
+ return path.join(os.tmpdir(), 'corestore-' + Math.random().toString(16).slice(2))
252
+ }
package/test/keys.js CHANGED
@@ -41,7 +41,7 @@ test('short user-provided token will throw', async t => {
41
41
  })
42
42
 
43
43
  test('persistent storage regenerates keys correctly', async t => {
44
- const testPath = p.resolve(__dirname, 'test-data')
44
+ const testPath = p.join(__dirname, 'test-data')
45
45
 
46
46
  const keys1 = await KeyManager.fromStorage((name) => raf(testPath, { directory: testPath }))
47
47
  const kp1 = await keys1.createHypercoreKeyPair('core1')
@@ -51,6 +51,9 @@ test('persistent storage regenerates keys correctly', async t => {
51
51
 
52
52
  t.alike(kp1.publicKey, kp2.publicKey)
53
53
 
54
+ await keys1.close()
55
+ await keys2.close()
56
+
54
57
  await fs.promises.rm(testPath, { recursive: true })
55
58
  })
56
59