@cero-base/cero 1.14.1 → 1.15.1

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": "@cero-base/cero",
3
- "version": "1.14.1",
3
+ "version": "1.15.1",
4
4
  "description": "The ideal p2p API — everything is a handle, handles contain refs, refs contain rows.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -95,13 +95,13 @@
95
95
  "test:node": "ls test/*.test.js | xargs -P1 -n1 brittle-node"
96
96
  },
97
97
  "dependencies": {
98
- "@cero-base/core": "^1.14.1",
98
+ "@cero-base/core": "^1.15.1",
99
99
  "b4a": "^1.8.1",
100
100
  "bare-abort-controller": "^1.1.2",
101
101
  "bare-crypto": "^1.15.3",
102
102
  "bare-fs": "^4.8.1",
103
103
  "bare-path": "^3.1.1",
104
- "ble-swarm": "^2.2.0",
104
+ "ble-swarm": "^2.3.0",
105
105
  "compact-encoding": "^3.3.2",
106
106
  "corestore": "^7.12.2",
107
107
  "hrpc": "^4.3.1",
@@ -732,12 +732,14 @@ export class Handle extends ReadyResource {
732
732
  async _pair(invite, type, { routes, timeout } = {}, target = null) {
733
733
  const deadline = timeout || TIMEOUT
734
734
 
735
- // Idempotent: if the invite targets a handle we already have AND we are
736
- // still a member of it, return it — without pairing, so there's no
737
- // waiting on a peer to confirm. If our member row is gone (we were
738
- // removed), the stored writer is revoked and the old session can never
739
- // become writable again holding a fresh invite is exactly the
740
- // re-admission path, so fall through to a real pairing instead.
735
+ // Idempotent: if the invite targets a handle we already have AND our
736
+ // stored writer is still admitted, return it — without pairing, so
737
+ // there's no waiting on a peer to confirm. A removed writer's core is
738
+ // frozen: the old session can never become writable again, and reusing
739
+ // its keypair would only get it re-removed. Holding a fresh invite is
740
+ // exactly the re-admission path, so fall through to a real pairing
741
+ // which mints a fresh keypair the host admits. Our member row can outlive
742
+ // our writer (one revoked device), so the device row is the real test.
741
743
  if (target) {
742
744
  const { data: joined } = await this.store.get('handles')
743
745
  const existing = joined.find(
@@ -746,7 +748,8 @@ export class Handle extends ReadyResource {
746
748
  if (existing) {
747
749
  const known = await this._load(type, existing.id)
748
750
  const { data: me } = await known.store.get('members', this.identity.id)
749
- if (me) return known
751
+ const { data: device } = await known.store.get('devices', toId(known.store.writerKey))
752
+ if (me && device) return known
750
753
  await known.close().catch(safetyCatch)
751
754
  }
752
755
  }
package/src/index.js CHANGED
@@ -64,6 +64,7 @@ export { t, schema } from './lib/spec.js'
64
64
  * @property {string | null} [name] Friendly device name persisted on the identity claim.
65
65
  * @property {boolean} [isMobile] Marks this device as mobile.
66
66
  * @property {Array<{ host: string, port: number }>} [bootstrap] Custom DHT bootstrap nodes.
67
+ * @property {number[]} [backoffs] Swarm reconnect backoff tiers in ms (testing/tuning).
67
68
  * @property {string} [channel] Optional network-isolation label; only same-channel peers connect.
68
69
  * @property {Array<string | Uint8Array>} [mirrors] Blind-peer public keys. Rooms and files are mirrored through them so peers sync even when never online at the same time. Mirrors hold only encrypted blocks — they never read your data.
69
70
  * @property {Uint8Array} [key] Pre-existing database key (skip bootstrap).
@@ -127,6 +128,7 @@ export async function cero(dir, spec, opts = {}) {
127
128
 
128
129
  network = new Network({
129
130
  bootstrap: opts.bootstrap,
131
+ backoffs: opts.backoffs,
130
132
  channel: opts.channel,
131
133
  store,
132
134
  mirrors: opts.mirrors
@@ -155,6 +157,21 @@ export async function cero(dir, spec, opts = {}) {
155
157
  })
156
158
  await me.ready()
157
159
 
160
+ // a supplied or stored identity may already have history elsewhere —
161
+ // authoring genesis twice forks the seed-derived writer core (a writable
162
+ // core has one author, ever). Give discovery a moment; any replicated
163
+ // history means the caller wanted { recovery: true }. Offline reuse is
164
+ // undetectable — this catches the reachable-peer case loudly.
165
+ if (!writer && !opts.key && !opts.recovery && !fresh) {
166
+ const until = Date.now() + 1500
167
+ while (Date.now() < until) {
168
+ if (network.connections.size > 0) await me.store.bee.update().catch(safetyCatch)
169
+ if (me.store.length > 0 || me.store.bee.local.length > 0) {
170
+ throw CeroError.CONFLICT('identity already has history — open with { recovery: true }')
171
+ }
172
+ await new Promise((r) => setTimeout(r, 100))
173
+ }
174
+ }
158
175
  if (!writer && me.store.length === 0 && (!opts.key || opts.recovery)) {
159
176
  const result = await me.bootstrap({
160
177
  name: opts.name || null,
package/types/index.d.ts CHANGED
@@ -10,6 +10,7 @@
10
10
  * @property {string | null} [name] Friendly device name persisted on the identity claim.
11
11
  * @property {boolean} [isMobile] Marks this device as mobile.
12
12
  * @property {Array<{ host: string, port: number }>} [bootstrap] Custom DHT bootstrap nodes.
13
+ * @property {number[]} [backoffs] Swarm reconnect backoff tiers in ms (testing/tuning).
13
14
  * @property {string} [channel] Optional network-isolation label; only same-channel peers connect.
14
15
  * @property {Array<string | Uint8Array>} [mirrors] Blind-peer public keys. Rooms and files are mirrored through them so peers sync even when never online at the same time. Mirrors hold only encrypted blocks — they never read your data.
15
16
  * @property {Uint8Array} [key] Pre-existing database key (skip bootstrap).
@@ -99,6 +100,10 @@ export type CeroOpts = {
99
100
  host: string;
100
101
  port: number;
101
102
  }>;
103
+ /**
104
+ * Swarm reconnect backoff tiers in ms (testing/tuning).
105
+ */
106
+ backoffs?: number[];
102
107
  /**
103
108
  * Optional network-isolation label; only same-channel peers connect.
104
109
  */