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.
- package/README.md +142 -4
- package/dist/dignity.cjs.js +768 -20
- package/dist/dignity.cjs.js.map +4 -4
- package/dist/dignity.esm.js +768 -20
- package/dist/dignity.esm.js.map +3 -3
- package/dist/dignity.min.js +18 -18
- package/docs/assets/dignity.esm.js +11205 -0
- package/docs/assets/docs.js +47 -0
- package/docs/assets/favicon.svg +8 -0
- package/docs/assets/highlight/github-dark.min.css +10 -0
- package/docs/assets/highlight/github.min.css +10 -0
- package/docs/assets/highlight/highlight.min.js +1244 -0
- package/docs/assets/styles.css +449 -38
- package/docs/chess/assets/chess-app.js +58022 -0
- package/docs/chess/assets/chess-app.js.map +7 -0
- package/docs/chess/assets/chess.css +584 -0
- package/docs/chess/favicon.ico +0 -0
- package/docs/chess/index.html +16 -0
- package/docs/chess/src/App.jsx +128 -0
- package/docs/chess/src/components/Board3D.jsx +364 -0
- package/docs/chess/src/components/GameView.jsx +847 -0
- package/docs/chess/src/components/JoinGate.jsx +68 -0
- package/docs/chess/src/components/LinkPanel.jsx +132 -0
- package/docs/chess/src/components/Lobby.jsx +154 -0
- package/docs/chess/src/components/MovePanel.jsx +123 -0
- package/docs/chess/src/lib/audio.js +50 -0
- package/docs/chess/src/lib/dignitySetup.js +42 -0
- package/docs/chess/src/lib/links.js +124 -0
- package/docs/chess/src/lib/localGames.js +160 -0
- package/docs/chess/src/lib/p2pDebug.js +192 -0
- package/docs/chess/src/main.jsx +5 -0
- package/docs/favicon.ico +0 -0
- package/docs/index.html +605 -81
- package/docs/openapi-like.json +74 -7
- package/examples/decentralized-chess-lite.js +52 -30
- package/package.json +30 -4
- package/src/core/dignity-p2p.js +466 -15
- package/src/index.js +8 -0
- package/src/network/peerjs-network.js +234 -0
- package/src/persistence/indexeddb-persistence.js +184 -0
- package/src/react/index.js +256 -0
- package/src/signaling/parse-peerjs-url.js +24 -0
- package/src/signaling/peerjs-signaling-provider.js +2 -8
package/docs/openapi-like.json
CHANGED
|
@@ -1,19 +1,43 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dignity.js",
|
|
3
|
-
"version": "0.1
|
|
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
|
|
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
|
-
"
|
|
110
|
+
"powSteps": 22,
|
|
111
|
+
"powTargetMs": 1000,
|
|
112
|
+
"kdfIterations": 100000,
|
|
113
|
+
"banDurationMs": 172800000
|
|
49
114
|
},
|
|
50
115
|
"broadcast": {
|
|
51
|
-
"encryption": "
|
|
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": "
|
|
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
|
-
|
|
25
|
-
|
|
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
|
|
34
|
-
await
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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:
|
|
102
|
+
{ broadcastScope: scope }
|
|
81
103
|
);
|
|
82
104
|
}
|
|
83
105
|
|
|
84
106
|
const hostState = host.read('matches', 'match-1');
|
|
85
|
-
const
|
|
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('\
|
|
91
|
-
console.log(JSON.stringify(
|
|
112
|
+
console.log('\nJoiner replicated state (after snapshot + moves):');
|
|
113
|
+
console.log(JSON.stringify(joinerState.data, null, 2));
|
|
92
114
|
|
|
93
|
-
await host.leaveDiscovery(
|
|
94
|
-
await
|
|
115
|
+
await host.leaveDiscovery(scope);
|
|
116
|
+
await joiner.leaveDiscovery(scope);
|
|
95
117
|
await host.stop();
|
|
96
|
-
await
|
|
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
|
+
"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
|
-
"
|
|
30
|
-
"docs:
|
|
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
|
-
"
|
|
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",
|