dignity.js 0.5.2 → 0.5.4
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 +48 -21
- package/dist/dignity.cjs.js +92 -50
- package/dist/dignity.cjs.js.map +3 -3
- package/dist/dignity.esm.js +92 -50
- package/dist/dignity.esm.js.map +3 -3
- package/dist/dignity.min.js +5 -5
- package/docs/openapi-like.json +31 -4
- package/examples/decentralized-chess-lite.js +9 -0
- package/package.json +15 -2
- package/src/core/dignity-p2p.js +35 -6
- package/src/persistence/indexeddb-persistence.js +2 -0
- package/src/security/sloth-vdf.js +11 -5
- package/src/signaling/websocket-signaling-provider.js +11 -2
- package/docs/chess/assets/chess-app.js +0 -58022
- package/docs/chess/assets/chess-app.js.map +0 -7
- package/docs/chess/assets/chess.css +0 -584
- package/docs/chess/favicon.ico +0 -0
- package/docs/chess/index.html +0 -16
- package/docs/chess/src/App.jsx +0 -128
- package/docs/chess/src/components/Board3D.jsx +0 -364
- package/docs/chess/src/components/GameView.jsx +0 -847
- package/docs/chess/src/components/JoinGate.jsx +0 -68
- package/docs/chess/src/components/LinkPanel.jsx +0 -132
- package/docs/chess/src/components/Lobby.jsx +0 -154
- package/docs/chess/src/components/MovePanel.jsx +0 -123
- package/docs/chess/src/lib/audio.js +0 -50
- package/docs/chess/src/lib/dignitySetup.js +0 -42
- package/docs/chess/src/lib/links.js +0 -124
- package/docs/chess/src/lib/localGames.js +0 -160
- package/docs/chess/src/lib/p2pDebug.js +0 -192
- package/docs/chess/src/main.jsx +0 -5
package/docs/openapi-like.json
CHANGED
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
17
|
"read": {
|
|
18
|
-
"method": "read(collection, id)"
|
|
18
|
+
"method": "read(collection, id)",
|
|
19
|
+
"returns": "active record or null; see recordShape"
|
|
19
20
|
},
|
|
20
21
|
"update": {
|
|
21
22
|
"method": "update(collection, id, patch, options)",
|
|
@@ -33,7 +34,8 @@
|
|
|
33
34
|
},
|
|
34
35
|
"pushRecordSnapshot": {
|
|
35
36
|
"method": "pushRecordSnapshot(collection, id, options)",
|
|
36
|
-
"description": "broadcast full record for late joiners who missed the initial create"
|
|
37
|
+
"description": "broadcast full record for late joiners who missed the initial create",
|
|
38
|
+
"returns": "active record; see recordShape"
|
|
37
39
|
},
|
|
38
40
|
"getRecordPeerIds": {
|
|
39
41
|
"method": "getRecordPeerIds(collection, id, options)",
|
|
@@ -46,7 +48,8 @@
|
|
|
46
48
|
},
|
|
47
49
|
"collections/{collection}": {
|
|
48
50
|
"list": {
|
|
49
|
-
"method": "list(collection, options)"
|
|
51
|
+
"method": "list(collection, options)",
|
|
52
|
+
"returns": "active records with hash; deleted stubs omit hash when includeDeleted is true"
|
|
50
53
|
}
|
|
51
54
|
},
|
|
52
55
|
"peers": {
|
|
@@ -60,11 +63,35 @@
|
|
|
60
63
|
"events": {
|
|
61
64
|
"change": "object create/update/delete/snapshot applied",
|
|
62
65
|
"conflict": "local or remote version mismatch",
|
|
63
|
-
"warning": "orphan-operation, peer-connect-failed, presence failures",
|
|
66
|
+
"warning": "orphan-operation, peer-connect-failed, presence failures, content-hash-mismatch",
|
|
64
67
|
"peerdiscovered": "peer joined discovery scope",
|
|
65
68
|
"peerleft": "peer left or timed out",
|
|
66
69
|
"message": "custom decrypted message received"
|
|
67
70
|
},
|
|
71
|
+
"recordShape": {
|
|
72
|
+
"active": {
|
|
73
|
+
"id": "string",
|
|
74
|
+
"ownerId": "string",
|
|
75
|
+
"collaboratorIds": [
|
|
76
|
+
"peer-id"
|
|
77
|
+
],
|
|
78
|
+
"data": "application payload object",
|
|
79
|
+
"hash": "sha512:<hex>; computed from canonicalized data only",
|
|
80
|
+
"createdAt": "unix ms timestamp",
|
|
81
|
+
"updatedAt": "unix ms timestamp",
|
|
82
|
+
"version": "monotonic integer"
|
|
83
|
+
},
|
|
84
|
+
"deletedStub": {
|
|
85
|
+
"id": "string",
|
|
86
|
+
"ownerId": "string",
|
|
87
|
+
"deletedAt": "unix ms timestamp",
|
|
88
|
+
"version": "monotonic integer"
|
|
89
|
+
},
|
|
90
|
+
"notes": [
|
|
91
|
+
"hash uses stableStringify(data) so object key order does not change the digest",
|
|
92
|
+
"restoreRecord recomputes hash locally and emits warning.type=content-hash-mismatch on mismatch"
|
|
93
|
+
]
|
|
94
|
+
},
|
|
68
95
|
"persistence": {
|
|
69
96
|
"IndexedDBPersistence": {
|
|
70
97
|
"method": "attach(node)",
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node.js chess example.
|
|
3
|
+
*
|
|
4
|
+
* Run this example with:
|
|
5
|
+
* npm run example:chess
|
|
6
|
+
*
|
|
7
|
+
* For the browser-based demo, see:
|
|
8
|
+
* docs/chess/
|
|
9
|
+
*/
|
|
1
10
|
const { DignityP2P, InMemoryNetworkHub, InMemoryNetworkAdapter } = require('../src');
|
|
2
11
|
|
|
3
12
|
function initialBoard() {
|
package/package.json
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dignity.js",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
4
4
|
"description": "P2P object API for decentralized JavaScript applications",
|
|
5
|
+
"homepage": "https://jose-compu.github.io/dignity.js/",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/jose-compu/dignity.js.git"
|
|
9
|
+
},
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/jose-compu/dignity.js/issues"
|
|
12
|
+
},
|
|
5
13
|
"main": "dist/dignity.cjs.js",
|
|
6
14
|
"module": "dist/dignity.esm.js",
|
|
7
15
|
"browser": "dist/dignity.esm.js",
|
|
@@ -20,9 +28,14 @@
|
|
|
20
28
|
}
|
|
21
29
|
},
|
|
22
30
|
"files": [
|
|
31
|
+
"README.md",
|
|
32
|
+
"LICENSE",
|
|
23
33
|
"dist",
|
|
24
34
|
"src",
|
|
25
|
-
"docs",
|
|
35
|
+
"docs/index.html",
|
|
36
|
+
"docs/openapi-like.json",
|
|
37
|
+
"docs/favicon.ico",
|
|
38
|
+
"docs/assets",
|
|
26
39
|
"examples"
|
|
27
40
|
],
|
|
28
41
|
"scripts": {
|
package/src/core/dignity-p2p.js
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
|
+
const nacl = require('tweetnacl');
|
|
2
|
+
const naclUtil = require('tweetnacl-util');
|
|
1
3
|
const EventEmitter = require('../utils/event-emitter');
|
|
2
|
-
const { MessageSecurityService } = require('../security/message-security-service');
|
|
4
|
+
const { MessageSecurityService, stableStringify } = require('../security/message-security-service');
|
|
5
|
+
|
|
6
|
+
function computeContentHash(data) {
|
|
7
|
+
const canonical = stableStringify(data || {});
|
|
8
|
+
const bytes = naclUtil.decodeUTF8(canonical);
|
|
9
|
+
const hash = nacl.hash(bytes);
|
|
10
|
+
const hex = Array.from(hash, (b) => b.toString(16).padStart(2, '0')).join('');
|
|
11
|
+
return `sha512:${hex}`;
|
|
12
|
+
}
|
|
3
13
|
|
|
4
14
|
/**
|
|
5
15
|
* Core node API for replicated object collections.
|
|
@@ -101,6 +111,8 @@ class DignityP2P extends EventEmitter {
|
|
|
101
111
|
return null;
|
|
102
112
|
}
|
|
103
113
|
|
|
114
|
+
const normalizedData = { ...(record.data || {}) };
|
|
115
|
+
|
|
104
116
|
return {
|
|
105
117
|
id: record.id,
|
|
106
118
|
ownerId: record.ownerId,
|
|
@@ -108,7 +120,8 @@ class DignityP2P extends EventEmitter {
|
|
|
108
120
|
createdAt: record.createdAt,
|
|
109
121
|
updatedAt: record.updatedAt,
|
|
110
122
|
version: record.version,
|
|
111
|
-
|
|
123
|
+
hash: record.hash || computeContentHash(normalizedData),
|
|
124
|
+
data: normalizedData
|
|
112
125
|
};
|
|
113
126
|
}
|
|
114
127
|
|
|
@@ -204,7 +217,7 @@ class DignityP2P extends EventEmitter {
|
|
|
204
217
|
ownerId: this.nodeId,
|
|
205
218
|
collaboratorIds,
|
|
206
219
|
timestamp,
|
|
207
|
-
payload: { ...data }
|
|
220
|
+
payload: { ...(data || {}) }
|
|
208
221
|
};
|
|
209
222
|
|
|
210
223
|
this.applyOperation(operation);
|
|
@@ -855,11 +868,24 @@ class DignityP2P extends EventEmitter {
|
|
|
855
868
|
return false;
|
|
856
869
|
}
|
|
857
870
|
|
|
871
|
+
const restoredData = { ...(record.data || {}) };
|
|
872
|
+
const computedHash = computeContentHash(restoredData);
|
|
873
|
+
if (record.hash && record.hash !== computedHash) {
|
|
874
|
+
this.emit('warning', {
|
|
875
|
+
type: 'content-hash-mismatch',
|
|
876
|
+
collection: collectionName,
|
|
877
|
+
id: record.id,
|
|
878
|
+
advertisedHash: record.hash,
|
|
879
|
+
computedHash
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
|
|
858
883
|
collection.set(record.id, {
|
|
859
884
|
id: record.id,
|
|
860
885
|
ownerId: record.ownerId,
|
|
861
886
|
collaboratorIds: this.normalizeCollaboratorIds(record.collaboratorIds),
|
|
862
|
-
data:
|
|
887
|
+
data: restoredData,
|
|
888
|
+
hash: computedHash,
|
|
863
889
|
createdAt: record.createdAt,
|
|
864
890
|
updatedAt: record.updatedAt,
|
|
865
891
|
deletedAt: record.deletedAt || null,
|
|
@@ -881,7 +907,8 @@ class DignityP2P extends EventEmitter {
|
|
|
881
907
|
id: raw.id,
|
|
882
908
|
ownerId: raw.ownerId,
|
|
883
909
|
collaboratorIds: Array.isArray(raw.collaboratorIds) ? [...raw.collaboratorIds] : [],
|
|
884
|
-
data: { ...raw.data },
|
|
910
|
+
data: { ...(raw.data || {}) },
|
|
911
|
+
hash: raw.hash || computeContentHash(raw.data || {}),
|
|
885
912
|
createdAt: raw.createdAt,
|
|
886
913
|
updatedAt: raw.updatedAt,
|
|
887
914
|
deletedAt: raw.deletedAt || null,
|
|
@@ -917,7 +944,8 @@ class DignityP2P extends EventEmitter {
|
|
|
917
944
|
id: operation.id,
|
|
918
945
|
ownerId: operation.ownerId,
|
|
919
946
|
collaboratorIds: this.normalizeCollaboratorIds(operation.collaboratorIds),
|
|
920
|
-
data: { ...operation.payload },
|
|
947
|
+
data: { ...(operation.payload || {}) },
|
|
948
|
+
hash: computeContentHash(operation.payload || {}),
|
|
921
949
|
createdAt: operation.timestamp,
|
|
922
950
|
updatedAt: operation.timestamp,
|
|
923
951
|
deletedAt: null,
|
|
@@ -1035,6 +1063,7 @@ class DignityP2P extends EventEmitter {
|
|
|
1035
1063
|
...current.data,
|
|
1036
1064
|
...operation.payload
|
|
1037
1065
|
};
|
|
1066
|
+
current.hash = computeContentHash(current.data);
|
|
1038
1067
|
|
|
1039
1068
|
if (Array.isArray(operation.collaboratorIds) && operation.actorId === current.ownerId) {
|
|
1040
1069
|
current.collaboratorIds = this.normalizeCollaboratorIds(operation.collaboratorIds);
|
|
@@ -73,6 +73,7 @@ class IndexedDBPersistence {
|
|
|
73
73
|
ownerId: record.ownerId,
|
|
74
74
|
collaboratorIds: Array.isArray(record.collaboratorIds) ? [...record.collaboratorIds] : [],
|
|
75
75
|
data: { ...record.data },
|
|
76
|
+
hash: record.hash || null,
|
|
76
77
|
createdAt: record.createdAt,
|
|
77
78
|
updatedAt: record.updatedAt,
|
|
78
79
|
deletedAt: record.deletedAt,
|
|
@@ -143,6 +144,7 @@ class IndexedDBPersistence {
|
|
|
143
144
|
ownerId: stored.ownerId,
|
|
144
145
|
collaboratorIds: stored.collaboratorIds,
|
|
145
146
|
data: stored.data,
|
|
147
|
+
hash: stored.hash || null,
|
|
146
148
|
createdAt: stored.createdAt,
|
|
147
149
|
updatedAt: stored.updatedAt,
|
|
148
150
|
deletedAt: stored.deletedAt,
|
|
@@ -6,6 +6,12 @@ class SlothPermutation {
|
|
|
6
6
|
static p = BigInt(
|
|
7
7
|
'170082004324204494273811327264862981553264701145937538369570764779791492622392118654022654452947093285873855529044371650895045691292912712699015605832276411308653107069798639938826015099738961427172366594187783204437869906954750443653318078358839409699824714551430573905637228307966826784684174483831608534979'
|
|
8
8
|
);
|
|
9
|
+
// precompute values for optimization:
|
|
10
|
+
// (p - 1) / 2
|
|
11
|
+
static pHalf = (SlothPermutation.p - BigInt(1)) >> BigInt(1);
|
|
12
|
+
// (p + 1) / 4
|
|
13
|
+
// p ≡ 3 (mod 4) ⇒ (p+1) divisible by 4
|
|
14
|
+
static pQuarter = (SlothPermutation.p + BigInt(1)) >> BigInt(2);
|
|
9
15
|
|
|
10
16
|
fastPow(base, exponent, modulus) {
|
|
11
17
|
if (modulus === BigInt(1)) {
|
|
@@ -17,11 +23,11 @@ class SlothPermutation {
|
|
|
17
23
|
let powExponent = exponent;
|
|
18
24
|
|
|
19
25
|
while (powExponent > 0) {
|
|
20
|
-
if (powExponent
|
|
26
|
+
if ((powExponent & BigInt(1)) === BigInt(1)) {
|
|
21
27
|
result = (result * powBase) % modulus;
|
|
22
28
|
}
|
|
23
29
|
|
|
24
|
-
powExponent = powExponent
|
|
30
|
+
powExponent = powExponent >> BigInt(1);
|
|
25
31
|
powBase = (powBase * powBase) % modulus;
|
|
26
32
|
}
|
|
27
33
|
|
|
@@ -29,7 +35,7 @@ class SlothPermutation {
|
|
|
29
35
|
}
|
|
30
36
|
|
|
31
37
|
quadRes(x) {
|
|
32
|
-
return this.fastPow(x,
|
|
38
|
+
return this.fastPow(x, SlothPermutation.pHalf, SlothPermutation.p) === BigInt(1);
|
|
33
39
|
}
|
|
34
40
|
|
|
35
41
|
modSqrtOp(x) {
|
|
@@ -37,10 +43,10 @@ class SlothPermutation {
|
|
|
37
43
|
let value = x;
|
|
38
44
|
|
|
39
45
|
if (this.quadRes(value)) {
|
|
40
|
-
y = this.fastPow(value,
|
|
46
|
+
y = this.fastPow(value, SlothPermutation.pQuarter, SlothPermutation.p);
|
|
41
47
|
} else {
|
|
42
48
|
value = (-value + SlothPermutation.p) % SlothPermutation.p;
|
|
43
|
-
y = this.fastPow(value,
|
|
49
|
+
y = this.fastPow(value, SlothPermutation.pQuarter, SlothPermutation.p);
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
return y;
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
function randomBase36(length) {
|
|
2
|
+
let value = '';
|
|
3
|
+
while (value.length < length) {
|
|
4
|
+
const chunk = Math.random().toString(36).slice(2);
|
|
5
|
+
value += chunk.length > 0 ? chunk : '0';
|
|
6
|
+
}
|
|
7
|
+
return value.slice(0, length);
|
|
8
|
+
}
|
|
9
|
+
|
|
1
10
|
class WebSocketSignalingProvider {
|
|
2
11
|
constructor({ id, url, WebSocketImpl, priority = 0 }) {
|
|
3
12
|
if (!url) {
|
|
@@ -50,8 +59,8 @@ class WebSocketSignalingProvider {
|
|
|
50
59
|
return this.url;
|
|
51
60
|
}
|
|
52
61
|
|
|
53
|
-
const connectionId = `dignityjs_${
|
|
54
|
-
const token =
|
|
62
|
+
const connectionId = `dignityjs_${randomBase36(10)}`;
|
|
63
|
+
const token = randomBase36(10);
|
|
55
64
|
const hasQuery = this.url.includes('?');
|
|
56
65
|
const hasId = /[?&]id=/.test(this.url);
|
|
57
66
|
const hasToken = /[?&]token=/.test(this.url);
|