dignity.js 0.3.0 → 0.5.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.
Files changed (43) hide show
  1. package/README.md +142 -4
  2. package/dist/dignity.cjs.js +768 -20
  3. package/dist/dignity.cjs.js.map +4 -4
  4. package/dist/dignity.esm.js +768 -20
  5. package/dist/dignity.esm.js.map +3 -3
  6. package/dist/dignity.min.js +18 -18
  7. package/docs/assets/dignity.esm.js +11205 -0
  8. package/docs/assets/docs.js +47 -0
  9. package/docs/assets/favicon.svg +8 -0
  10. package/docs/assets/highlight/github-dark.min.css +10 -0
  11. package/docs/assets/highlight/github.min.css +10 -0
  12. package/docs/assets/highlight/highlight.min.js +1244 -0
  13. package/docs/assets/styles.css +449 -38
  14. package/docs/chess/assets/chess-app.js +58022 -0
  15. package/docs/chess/assets/chess-app.js.map +7 -0
  16. package/docs/chess/assets/chess.css +584 -0
  17. package/docs/chess/favicon.ico +0 -0
  18. package/docs/chess/index.html +16 -0
  19. package/docs/chess/src/App.jsx +128 -0
  20. package/docs/chess/src/components/Board3D.jsx +364 -0
  21. package/docs/chess/src/components/GameView.jsx +847 -0
  22. package/docs/chess/src/components/JoinGate.jsx +68 -0
  23. package/docs/chess/src/components/LinkPanel.jsx +132 -0
  24. package/docs/chess/src/components/Lobby.jsx +154 -0
  25. package/docs/chess/src/components/MovePanel.jsx +123 -0
  26. package/docs/chess/src/lib/audio.js +50 -0
  27. package/docs/chess/src/lib/dignitySetup.js +42 -0
  28. package/docs/chess/src/lib/links.js +124 -0
  29. package/docs/chess/src/lib/localGames.js +160 -0
  30. package/docs/chess/src/lib/p2pDebug.js +192 -0
  31. package/docs/chess/src/main.jsx +5 -0
  32. package/docs/favicon.ico +0 -0
  33. package/docs/index.html +605 -81
  34. package/docs/openapi-like.json +74 -7
  35. package/examples/decentralized-chess-lite.js +52 -30
  36. package/package.json +30 -4
  37. package/src/core/dignity-p2p.js +466 -15
  38. package/src/index.js +8 -0
  39. package/src/network/peerjs-network.js +234 -0
  40. package/src/persistence/indexeddb-persistence.js +184 -0
  41. package/src/react/index.js +256 -0
  42. package/src/signaling/parse-peerjs-url.js +24 -0
  43. package/src/signaling/peerjs-signaling-provider.js +2 -8
@@ -1,19 +1,43 @@
1
1
  {
2
2
  "name": "dignity.js",
3
- "version": "0.1.0",
3
+ "version": "0.5.1",
4
4
  "description": "REST-like object API over peer-to-peer replication",
5
5
  "resources": {
6
6
  "collections/{collection}/{id}": {
7
7
  "create": {
8
8
  "method": "create(collection, data, options)",
9
- "owner": "actor that creates the object"
9
+ "owner": "actor that creates the object",
10
+ "options": {
11
+ "id": "optional stable id",
12
+ "collaborators": "optional peer id list",
13
+ "broadcastScope": "scoped broadcast password namespace",
14
+ "connectToPeers": "optional; defaults to collaborators on PeerJS mesh"
15
+ }
10
16
  },
11
17
  "read": {
12
18
  "method": "read(collection, id)"
13
19
  },
14
20
  "update": {
15
- "method": "update(collection, id, patch)",
16
- "authorization": "owner-only"
21
+ "method": "update(collection, id, patch, options)",
22
+ "authorization": "owner or collaborator",
23
+ "options": {
24
+ "expectedVersion": "optional number; throws VERSION_CONFLICT when mismatched",
25
+ "broadcastScope": "optional scoped broadcast password namespace",
26
+ "collaborators": "owner may replace collaborator list",
27
+ "connectToPeers": "optional; defaults to owner + collaborators"
28
+ }
29
+ },
30
+ "updateWithRetry": {
31
+ "method": "updateWithRetry(collection, id, patchFn, options)",
32
+ "description": "read-modify-write helper with automatic retry on version conflicts"
33
+ },
34
+ "pushRecordSnapshot": {
35
+ "method": "pushRecordSnapshot(collection, id, options)",
36
+ "description": "broadcast full record for late joiners who missed the initial create"
37
+ },
38
+ "getRecordPeerIds": {
39
+ "method": "getRecordPeerIds(collection, id, options)",
40
+ "description": "returns owner + collaborator peer ids for connectToPeers"
17
41
  },
18
42
  "delete": {
19
43
  "method": "remove(collection, id)",
@@ -24,8 +48,46 @@
24
48
  "list": {
25
49
  "method": "list(collection, options)"
26
50
  }
51
+ },
52
+ "peers": {
53
+ "connectToPeer": "open PeerJS data channel to peer id",
54
+ "getConnectionStats": "{ openCount, peerIds }",
55
+ "ensureConnectedToPeers": "connect to many peers before broadcast",
56
+ "joinDiscovery": "scoped presence; options.bootstrapPeerIds connects before announce",
57
+ "broadcastMessage": "custom app messages; options.connectToPeers"
27
58
  }
28
59
  },
60
+ "events": {
61
+ "change": "object create/update/delete/snapshot applied",
62
+ "conflict": "local or remote version mismatch",
63
+ "warning": "orphan-operation, peer-connect-failed, presence failures",
64
+ "peerdiscovered": "peer joined discovery scope",
65
+ "peerleft": "peer left or timed out",
66
+ "message": "custom decrypted message received"
67
+ },
68
+ "persistence": {
69
+ "IndexedDBPersistence": {
70
+ "method": "attach(node)",
71
+ "options": [
72
+ "dbName",
73
+ "storeName",
74
+ "collections"
75
+ ]
76
+ }
77
+ },
78
+ "react": {
79
+ "entrypoint": "dignity.js/react",
80
+ "hooks": [
81
+ "useDignity",
82
+ "useCollection",
83
+ "useObject",
84
+ "usePeers",
85
+ "useDiscovery",
86
+ "useConnectionStats",
87
+ "useRoom",
88
+ "useMessages"
89
+ ]
90
+ },
29
91
  "signaling": {
30
92
  "defaults": {
31
93
  "cloudflare": "enabled by default",
@@ -45,13 +107,18 @@
45
107
  "signingEnabled": true,
46
108
  "encryptionEnabled": true,
47
109
  "powEnabled": true,
48
- "powTargetMs": 1000
110
+ "powSteps": 22,
111
+ "powTargetMs": 1000,
112
+ "kdfIterations": 100000,
113
+ "banDurationMs": 172800000
49
114
  },
50
115
  "broadcast": {
51
- "encryption": "aes-256-gcm with appPassword-derived key"
116
+ "encryption": "AES secretbox with PBKDF2-SHA256 derived key",
117
+ "scopePasswords": "broadcastPasswords map keyed by broadcastScope",
118
+ "legacyKdf": "single-hash fallback accepted for older peers"
52
119
  },
53
120
  "direct": {
54
- "encryption": "aes-256-gcm payload + rsa-oaep wrapped session key"
121
+ "encryption": "NaCl box (X25519 + XSalsa20-Poly1305) to recipient public key"
55
122
  },
56
123
  "pow": {
57
124
  "algorithm": "Sloth VDF",
@@ -11,6 +11,7 @@ function initialBoard() {
11
11
 
12
12
  async function runDemo() {
13
13
  const hub = new InMemoryNetworkHub();
14
+ const scope = 'room:chess-lite';
14
15
 
15
16
  const host = new DignityP2P({
16
17
  nodeId: 'host',
@@ -21,8 +22,27 @@ async function runDemo() {
21
22
  }
22
23
  });
23
24
 
24
- const observer = new DignityP2P({
25
- nodeId: 'observer',
25
+ await host.start();
26
+ await host.joinDiscovery(scope, {
27
+ metadata: { nickname: 'host', role: 'owner' },
28
+ heartbeatIntervalMs: 100000,
29
+ ttlMs: 30000
30
+ });
31
+
32
+ // Host creates before joiner is online — joiner never receives the create op.
33
+ await host.create(
34
+ 'matches',
35
+ {
36
+ type: 'chess-lite',
37
+ board: initialBoard(),
38
+ moveHistory: [],
39
+ status: 'waiting'
40
+ },
41
+ { id: 'match-1', broadcastScope: scope }
42
+ );
43
+
44
+ const joiner = new DignityP2P({
45
+ nodeId: 'joiner',
26
46
  networkAdapter: new InMemoryNetworkAdapter(hub),
27
47
  security: {
28
48
  appPassword: 'demo-shared-password',
@@ -30,38 +50,40 @@ async function runDemo() {
30
50
  }
31
51
  });
32
52
 
33
- await host.start();
34
- await observer.start();
35
- await host.joinDiscovery('room:chess-lite', {
36
- metadata: { nickname: 'host', role: 'owner' },
37
- heartbeatIntervalMs: 100000,
38
- ttlMs: 30000
39
- });
40
- await observer.joinDiscovery('room:chess-lite', {
41
- metadata: { nickname: 'observer', role: 'viewer' },
53
+ await joiner.start();
54
+ await joiner.joinDiscovery(scope, {
55
+ metadata: { nickname: 'joiner', role: 'player' },
56
+ bootstrapPeerIds: ['host'],
42
57
  heartbeatIntervalMs: 100000,
43
58
  ttlMs: 30000
44
59
  });
45
60
 
46
- console.log(
47
- '\nPeers visible from host in room:chess-lite:',
48
- host.listPeers('room:chess-lite', { includeSelf: false }).map((peer) => peer.peerId)
49
- );
61
+ console.log('\nJoiner before snapshot:', joiner.read('matches', 'match-1'));
50
62
 
51
- await host.create(
63
+ const warnings = [];
64
+ joiner.on('warning', (event) => warnings.push(event));
65
+
66
+ await host.update(
52
67
  'matches',
68
+ 'match-1',
69
+ { status: 'playing', blackPlayerId: 'joiner' },
53
70
  {
54
- type: 'chess-lite',
55
- board: initialBoard(),
56
- moveHistory: []
57
- },
58
- { id: 'match-1', broadcastScope: 'room:chess-lite' }
71
+ broadcastScope: scope,
72
+ collaborators: ['host', 'joiner']
73
+ }
59
74
  );
60
75
 
76
+ console.log('\nJoiner after update (still missing create):', joiner.read('matches', 'match-1'));
77
+ console.log('Orphan warnings:', warnings.filter((event) => event.type === 'orphan-operation').length);
78
+
79
+ await host.pushRecordSnapshot('matches', 'match-1', {
80
+ broadcastScope: scope,
81
+ connectToPeers: ['joiner']
82
+ });
83
+
61
84
  const scriptedMoves = [
62
85
  { from: 'a2', to: 'a4', piece: 'whitePawnA' },
63
- { from: 'a7', to: 'a5', piece: 'blackPawnA' },
64
- { from: 'e1', to: 'e2', piece: 'whiteKing' }
86
+ { from: 'a7', to: 'a5', piece: 'blackPawnA' }
65
87
  ];
66
88
 
67
89
  for (const move of scriptedMoves) {
@@ -77,23 +99,23 @@ async function runDemo() {
77
99
  moveHistory,
78
100
  lastMove: move
79
101
  },
80
- { broadcastScope: 'room:chess-lite' }
102
+ { broadcastScope: scope }
81
103
  );
82
104
  }
83
105
 
84
106
  const hostState = host.read('matches', 'match-1');
85
- const observerState = observer.read('matches', 'match-1');
107
+ const joinerState = joiner.read('matches', 'match-1');
86
108
 
87
109
  console.log('\nHost state:');
88
110
  console.log(JSON.stringify(hostState.data, null, 2));
89
111
 
90
- console.log('\nObserver replicated state:');
91
- console.log(JSON.stringify(observerState.data, null, 2));
112
+ console.log('\nJoiner replicated state (after snapshot + moves):');
113
+ console.log(JSON.stringify(joinerState.data, null, 2));
92
114
 
93
- await host.leaveDiscovery('room:chess-lite');
94
- await observer.leaveDiscovery('room:chess-lite');
115
+ await host.leaveDiscovery(scope);
116
+ await joiner.leaveDiscovery(scope);
95
117
  await host.stop();
96
- await observer.stop();
118
+ await joiner.stop();
97
119
  }
98
120
 
99
121
  runDemo().catch((error) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dignity.js",
3
- "version": "0.3.0",
3
+ "version": "0.5.1",
4
4
  "description": "P2P object API for decentralized JavaScript applications",
5
5
  "main": "dist/dignity.cjs.js",
6
6
  "module": "dist/dignity.esm.js",
@@ -12,6 +12,11 @@
12
12
  "require": "./dist/dignity.cjs.js",
13
13
  "import": "./dist/dignity.esm.js",
14
14
  "default": "./dist/dignity.esm.js"
15
+ },
16
+ "./react": {
17
+ "require": "./src/react/index.js",
18
+ "import": "./src/react/index.js",
19
+ "default": "./src/react/index.js"
15
20
  }
16
21
  },
17
22
  "files": [
@@ -26,8 +31,13 @@
26
31
  "test:cloudflare-live": "RUN_CLOUDFLARE_LIVE_TESTS=1 jest tests/integration/cloudflare-signaling-live.test.js --runInBand",
27
32
  "test:pow-calibrate": "jest tests/unit/sloth-vdf-timing.test.js --runInBand",
28
33
  "build": "node scripts/build.js",
29
- "docs:serve": "npx http-server docs -p 4173 -o",
30
- "docs:check": "node -e \"require('fs').accessSync('docs/index.html')\"",
34
+ "build:chess": "node scripts/build-chess-demo.js",
35
+ "docs:favicon": "node scripts/generate-favicon.js",
36
+ "docs:build": "npm run build:chess",
37
+ "docs:dev": "node scripts/serve-docs.js",
38
+ "docs:serve": "node scripts/serve-docs.js",
39
+ "docs:stop": "node scripts/stop-docs.js",
40
+ "docs:check": "node -e \"const fs=require('fs');['docs/index.html','docs/favicon.ico','docs/chess/index.html','docs/chess/favicon.ico','docs/assets/favicon.svg','docs/chess/assets/chess-app.js','docs/assets/highlight/highlight.min.js','docs/assets/highlight/github.min.css','docs/assets/highlight/github-dark.min.css'].forEach(p=>fs.accessSync(p));\"",
31
41
  "example:tictactoe": "node examples/decentralized-tictactoe.js",
32
42
  "example:chess": "node examples/decentralized-chess-lite.js",
33
43
  "prepublishOnly": "npm test && npm run build"
@@ -40,9 +50,25 @@
40
50
  "objects"
41
51
  ],
42
52
  "license": "Apache 2.0",
53
+ "peerDependencies": {
54
+ "react": ">=18"
55
+ },
56
+ "peerDependenciesMeta": {
57
+ "react": {
58
+ "optional": true
59
+ }
60
+ },
43
61
  "devDependencies": {
62
+ "@testing-library/react": "^16.3.0",
63
+ "chess.js": "^1.4.0",
44
64
  "esbuild": "^0.28.0",
45
- "jest": "^29.7.0"
65
+ "fake-indexeddb": "^6.0.0",
66
+ "http-server": "^14.1.1",
67
+ "jest": "^29.7.0",
68
+ "jest-environment-jsdom": "^29.7.0",
69
+ "react": "^19.1.0",
70
+ "react-dom": "^19.1.0",
71
+ "three": "^0.184.0"
46
72
  },
47
73
  "dependencies": {
48
74
  "peerjs": "^1.5.5",