@cero-base/core 1.0.1 → 1.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cero-base/core",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "cero p2p primitives — identity, storage, network, database, blobs, rpc, pairing.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
package/src/lib/errors.js CHANGED
@@ -55,6 +55,14 @@ export class CeroError extends Error {
55
55
  static CLOSED(resource) {
56
56
  return new CeroError('CLOSED', `${resource} is closed`)
57
57
  }
58
+ /**
59
+ * A channel-stamped storage was opened under a different channel. Deliberately
60
+ * does NOT name the stored channel — that label gates the network, so leaking it
61
+ * in an error would hint at a realm the caller isn't on.
62
+ */
63
+ static CHANNEL_MISMATCH() {
64
+ return new CeroError('CHANNEL_MISMATCH', 'storage belongs to a different channel')
65
+ }
58
66
  /**
59
67
  * Operation before `ready()` resolved.
60
68
  *
@@ -3,6 +3,7 @@ import ProtomuxWakeup from 'protomux-wakeup'
3
3
  import ReadyResource from 'ready-resource'
4
4
  import safetyCatch from 'safety-catch'
5
5
  import b4a from 'b4a'
6
+ import { hash } from 'hypercore-crypto'
6
7
 
7
8
  import { ACTIVE, PASSIVE } from '../lib/constants.js'
8
9
  import { CeroError } from '../lib/errors.js'
@@ -14,6 +15,7 @@ import { Discovery } from './discovery.js'
14
15
  * @property {Array<{ host: string, port: number }>} [bootstrap] Custom DHT bootstrap nodes.
15
16
  * @property {(remotePublicKey: Uint8Array, payload: any) => boolean} [firewall] Incoming-connection filter.
16
17
  * @property {Uint8Array[]} [relayThrough] Relay public keys to tunnel through.
18
+ * @property {string} [channel] Optional network-isolation label; only same-channel peers meet.
17
19
  *
18
20
  * @typedef {{ replicate: (stream: any) => any }} Replicable
19
21
  */
@@ -24,12 +26,13 @@ import { Discovery } from './discovery.js'
24
26
  */
25
27
  export class Network extends ReadyResource {
26
28
  /** @param {NetworkOpts} [opts] */
27
- constructor({ identity, bootstrap, firewall, relayThrough } = {}) {
29
+ constructor({ identity, bootstrap, firewall, relayThrough, channel } = {}) {
28
30
  super()
29
31
  this.identity = identity || null
30
32
  this.bootstrap = bootstrap || null
31
33
  this.firewall = firewall || null
32
34
  this.relayThrough = relayThrough || null
35
+ this.channel = channel || null
33
36
 
34
37
  this.swarm = null
35
38
  this.wakeup = new ProtomuxWakeup()
@@ -63,6 +66,14 @@ export class Network extends ReadyResource {
63
66
 
64
67
  this.swarm = new Hyperswarm(opts)
65
68
 
69
+ if (this.channel) {
70
+ const channel = this.channel
71
+ const join = this.swarm.join.bind(this.swarm)
72
+ const leave = this.swarm.leave.bind(this.swarm)
73
+ this.swarm.join = (topic, opts) => join(channelTopic(topic, channel), opts)
74
+ this.swarm.leave = (topic) => leave(channelTopic(topic, channel))
75
+ }
76
+
66
77
  this.swarm.on('connection', (stream, info) => {
67
78
  if (this.closing || this.closed) {
68
79
  stream.destroy()
@@ -226,6 +237,12 @@ function replicateInto(core, stream) {
226
237
  }
227
238
  }
228
239
 
240
+ // A channel re-namespaces every swarm topic so only same-channel peers meet.
241
+ // No channel → identity (unchanged, back-compat).
242
+ export function channelTopic(topic, channel) {
243
+ return channel ? hash([topic, b4a.from(channel)]) : topic
244
+ }
245
+
229
246
  function isTopic(x) {
230
247
  return b4a.isBuffer(x) && x.length === 32
231
248
  }
@@ -29,6 +29,12 @@ export class CeroError extends Error {
29
29
  * @param {string} resource
30
30
  */
31
31
  static CLOSED(resource: string): CeroError;
32
+ /**
33
+ * A channel-stamped storage was opened under a different channel. Deliberately
34
+ * does NOT name the stored channel — that label gates the network, so leaking it
35
+ * in an error would hint at a realm the caller isn't on.
36
+ */
37
+ static CHANNEL_MISMATCH(): CeroError;
32
38
  /**
33
39
  * Operation before `ready()` resolved.
34
40
  *
@@ -1,9 +1,11 @@
1
+ export function channelTopic(topic: any, channel: any): any;
1
2
  /**
2
3
  * @typedef {object} NetworkOpts
3
4
  * @property {import('../identity/index.js').Identity} [identity] Long-lived keypair used as the swarm identity.
4
5
  * @property {Array<{ host: string, port: number }>} [bootstrap] Custom DHT bootstrap nodes.
5
6
  * @property {(remotePublicKey: Uint8Array, payload: any) => boolean} [firewall] Incoming-connection filter.
6
7
  * @property {Uint8Array[]} [relayThrough] Relay public keys to tunnel through.
8
+ * @property {string} [channel] Optional network-isolation label; only same-channel peers meet.
7
9
  *
8
10
  * @typedef {{ replicate: (stream: any) => any }} Replicable
9
11
  */
@@ -13,7 +15,7 @@
13
15
  */
14
16
  export class Network extends ReadyResource {
15
17
  /** @param {NetworkOpts} [opts] */
16
- constructor({ identity, bootstrap, firewall, relayThrough }?: NetworkOpts);
18
+ constructor({ identity, bootstrap, firewall, relayThrough, channel }?: NetworkOpts);
17
19
  identity: import("../index.js").Identity;
18
20
  bootstrap: {
19
21
  host: string;
@@ -21,6 +23,7 @@ export class Network extends ReadyResource {
21
23
  }[];
22
24
  firewall: (remotePublicKey: Uint8Array, payload: any) => boolean;
23
25
  relayThrough: Uint8Array<ArrayBufferLike>[];
26
+ channel: string;
24
27
  swarm: any;
25
28
  wakeup: any;
26
29
  _replicateables: Set<any>;
@@ -107,6 +110,10 @@ export type NetworkOpts = {
107
110
  * Relay public keys to tunnel through.
108
111
  */
109
112
  relayThrough?: Uint8Array[];
113
+ /**
114
+ * Optional network-isolation label; only same-channel peers meet.
115
+ */
116
+ channel?: string;
110
117
  };
111
118
  export type Replicable = {
112
119
  replicate: (stream: any) => any;