@sloev/trystero-corvax 0.0.0-stage → 0.1.17

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.
Files changed (3) hide show
  1. package/README.md +26 -2
  2. package/index.js +96 -0
  3. package/package.json +32 -4
package/README.md CHANGED
@@ -1,3 +1,27 @@
1
- # Temporary Holding Version
1
+ # @sloev/trystero-corvax
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [Trystero](https://github.com/dmotz/trystero) signalling over [CORVAX](https://github.com/sloev/CORVAX): no relay server, nothing to configure.
4
+
5
+ ```sh
6
+ npm i @trystero-p2p/core @sloev/trystero-corvax
7
+ ```
8
+
9
+ ```js
10
+ import { joinRoom } from '@sloev/trystero-corvax'
11
+
12
+ const room = joinRoom({ appId: 'my-app' }, 'lobby')
13
+ room.onPeerJoin(({ peerId }) => console.log('joined', peerId))
14
+ ```
15
+
16
+ Browsers without a bundler: `import { joinRoom } from 'https://cdn.jsdelivr.net/npm/@sloev/trystero-corvax/+esm'`.
17
+
18
+ **How it signals** (peer to peer):
19
+
20
+ - **Members:** CORVAX topic presence on the room's topic: signed leases kept by many storage peers, not one server.
21
+ - **Announce:** flooded over the room's CORVAX mesh, and sealed straight to each member through that member's own inbox anchors.
22
+ - **Offers, answers, candidates:** over a CORVAX mesh link to that peer when there is one (no anchor at all), otherwise to its inboxes. Senders are verified by signature.
23
+ - **Once a room's mesh exists, it no longer needs any anchor:** reconnects re-signal over the mesh.
24
+
25
+ Trystero encrypts its signals with the room key, and CORVAX seals them again for the one recipient. Joining took 0.3 s between two Chromium tabs in our tests.
26
+
27
+ Options: `joinRoom({ appId, corvax: { peer, ...Peer.create options } }, roomId)`. Pass `corvax.peer` to share an existing CORVAX peer.
package/index.js ADDED
@@ -0,0 +1,96 @@
1
+ // @sloev/trystero-corvax: a Trystero signalling transport on CORVAX, peer to peer (SPEC §10).
2
+ // room members → CORVAX topic presence on the room's root topic (signed leases on storage peers, any peer)
3
+ // announce → a topic broadcast over the room's CORVAX mesh, and a sealed message to each member's inboxes
4
+ // signal → over a CORVAX mesh link to that peer when there is one (no anchor), else to its inboxes
5
+ // Trystero encrypts its signals with the room key; CORVAX seals them again for the one recipient.
6
+ import { createTopicStrategy } from '@trystero-p2p/core'
7
+ import { Peer } from '@sloev/corvax'
8
+
9
+ export { clearUserIdentity, createUserIdentity, exportUserIdentity, importUserIdentity, roles, scale } from '@trystero-p2p/core'
10
+
11
+ const peers = new Map() // options key → Promise<Peer>: one CORVAX peer per tab or process
12
+ const TAG = 'tr1'
13
+
14
+ // The CORVAX peer behind this transport: one per tab (sessionStorage seed), or pass your own as corvax.peer.
15
+ export function corvaxPeer ({ peer, ...options } = {}) {
16
+ if (peer) return Promise.resolve(peer)
17
+ const key = JSON.stringify(options)
18
+ if (!peers.has(key)) peers.set(key, (async () => {
19
+ let seed = null
20
+ try { seed = sessionStorage.getItem('corvax.trystero') } catch {}
21
+ const p = await Peer.create({ seed, ...options })
22
+ try { sessionStorage.setItem('corvax.trystero', p.seed) } catch {}
23
+ wire(p)
24
+ return p
25
+ })())
26
+ return peers.get(key)
27
+ }
28
+
29
+ // Per CORVAX peer: Trystero topic → handlers; peer topic → CORVAX address (learned from signed messages).
30
+ function wire (p) {
31
+ if (p.trystero) return p.trystero
32
+ const s = p.trystero = { handlers: new Map(), routes: new Map(), rooms: new Map(), early: [] }
33
+ const deliver = (from, body) => {
34
+ if (body?.[TAG] !== 1 || typeof body.t !== 'string' || typeof body.st !== 'string') return
35
+ s.routes.set(body.st, from) // reply path: their topic → their verified address
36
+ const fns = s.handlers.get(body.t)
37
+ if (fns?.size) for (const fn of fns) fn(body.t, body.m)
38
+ else s.early = [...s.early.filter(e => Date.now() - e.at < 10e3), { at: Date.now(), body }].slice(-64) // before we subscribed
39
+ }
40
+ p.on(({ from, body }) => deliver(from, body))
41
+ s.deliver = deliver
42
+ return s
43
+ }
44
+
45
+ // A CORVAX topic per Trystero root topic: members (presence), a mesh (links), and the last announce to repeat.
46
+ async function room (p, s, root) {
47
+ if (s.rooms.has(root)) return s.rooms.get(root)
48
+ const r = { members: new Set(), announce: null, topic: await p.topic('trystero:' + root) }
49
+ s.rooms.set(root, r)
50
+ r.off = r.topic.subscribe({
51
+ presence: e => {
52
+ if (e.presence !== 'recent') return r.members.delete(e.address)
53
+ if (r.members.has(e.address)) return
54
+ r.members.add(e.address)
55
+ if (r.announce) send(p, e.address, r.announce) // a newcomer hears from us at once, not in 60 s
56
+ },
57
+ message: m => s.deliver(m.from, m.body) // announces flooded over the room's mesh
58
+ })
59
+ await r.topic.publish()
60
+ return r
61
+ }
62
+
63
+ const send = (p, address, body) => p.topics.sendVia(address, body) || p.send(address, body).catch(() => {})
64
+
65
+ export const joinRoom = createTopicStrategy({
66
+ init: config => corvaxPeer(config.corvax),
67
+
68
+ subscribeTopic: async (p, topic, onMessage, { kind, rootTopic }) => {
69
+ const s = wire(p)
70
+ if (!s.handlers.has(topic)) s.handlers.set(topic, new Set())
71
+ s.handlers.get(topic).add(onMessage)
72
+ for (const e of s.early.filter(e => e.body.t === topic && Date.now() - e.at < 10e3)) onMessage(topic, e.body.m)
73
+ s.early = s.early.filter(e => e.body.t !== topic)
74
+ if (kind === 'root') await room(p, s, rootTopic)
75
+ return () => {
76
+ s.handlers.get(topic)?.delete(onMessage)
77
+ if (kind !== 'root' || s.handlers.get(topic)?.size) return
78
+ const r = s.rooms.get(rootTopic)
79
+ s.rooms.delete(rootTopic)
80
+ r?.off(); r?.topic.close().catch(() => {})
81
+ }
82
+ },
83
+
84
+ publishTopic: async (p, topic, msg, { kind, rootTopic, selfTopic }) => {
85
+ const s = wire(p), body = { [TAG]: 1, t: topic, st: selfTopic, m: msg }
86
+ if (kind === 'announce') {
87
+ const r = await room(p, s, rootTopic)
88
+ r.announce = body
89
+ r.topic.broadcast(body).catch(() => {}) // over the mesh, if we have links
90
+ for (const address of r.members) send(p, address, body) // and straight to each member
91
+ return
92
+ }
93
+ const address = s.routes.get(topic) // signals answer someone we heard from
94
+ if (address) send(p, address, body)
95
+ }
96
+ })
package/package.json CHANGED
@@ -1,6 +1,34 @@
1
1
  {
2
2
  "name": "@sloev/trystero-corvax",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.17",
4
+ "description": "Trystero signalling over CORVAX: peer to peer, no relay server; rooms keep re-signalling over their own mesh.",
5
+ "type": "module",
6
+ "exports": "./index.js",
7
+ "files": [
8
+ "index.js",
9
+ "README.md"
10
+ ],
11
+ "dependencies": {
12
+ "@sloev/corvax": "^0.1.17"
13
+ },
14
+ "peerDependencies": {
15
+ "@trystero-p2p/core": ">=0.27.0 <0.28.0"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/sloev/CORVAX.git",
20
+ "directory": "trystero"
21
+ },
22
+ "homepage": "https://corvax.sloev.workers.dev/",
23
+ "keywords": [
24
+ "trystero",
25
+ "webrtc",
26
+ "p2p",
27
+ "signalling",
28
+ "corvax"
29
+ ],
30
+ "license": "MIT",
31
+ "publishConfig": {
32
+ "access": "public"
33
+ }
34
+ }