dignity.js 0.6.0 → 0.7.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 +97 -1
- package/dist/dignity.cjs.js +802 -16
- package/dist/dignity.cjs.js.map +4 -4
- package/dist/dignity.esm.js +795 -9
- package/dist/dignity.esm.js.map +3 -3
- package/dist/dignity.min.js +9 -9
- package/docs/assets/dignity.esm.js +928 -30
- package/docs/assets/playground-demos.js +342 -0
- package/docs/assets/playground.css +277 -0
- package/docs/assets/playground.js +248 -0
- package/docs/assets/styles.css +18 -2
- package/docs/index.html +23 -3
- package/docs/openapi-like.json +1 -1
- package/package.json +5 -3
- package/src/core/dignity-p2p.js +229 -4
- package/src/index.js +25 -1
- package/src/security/derive-key-pair.js +147 -0
- package/src/security/identity-rotation.js +427 -0
- package/src/security/message-security-service.js +94 -4
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { PLAYGROUND_DEMOS } from './playground-demos.js';
|
|
2
|
+
|
|
3
|
+
const dignityPromise = import('./dignity.esm.js').then((mod) => mod.default || mod);
|
|
4
|
+
|
|
5
|
+
const els = {
|
|
6
|
+
featureSelect: document.getElementById('feature-select'),
|
|
7
|
+
featureDesc: document.getElementById('feature-desc'),
|
|
8
|
+
editor: document.getElementById('code-editor'),
|
|
9
|
+
highlightCode: document.getElementById('code-highlight'),
|
|
10
|
+
highlightPre: document.querySelector('.playground-editor-highlight'),
|
|
11
|
+
runBtn: document.getElementById('run-btn'),
|
|
12
|
+
resetBtn: document.getElementById('reset-btn'),
|
|
13
|
+
clearBtn: document.getElementById('clear-btn'),
|
|
14
|
+
output: document.getElementById('output'),
|
|
15
|
+
status: document.getElementById('run-status')
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
let highlightTimer = null;
|
|
19
|
+
|
|
20
|
+
let activeCleanup = null;
|
|
21
|
+
let running = false;
|
|
22
|
+
|
|
23
|
+
function getDemoById(id) {
|
|
24
|
+
return PLAYGROUND_DEMOS.find((demo) => demo.id === id) || PLAYGROUND_DEMOS[0];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function populateFeatureSelect() {
|
|
28
|
+
els.featureSelect.innerHTML = PLAYGROUND_DEMOS.map(
|
|
29
|
+
(demo) => `<option value="${demo.id}">${demo.title}</option>`
|
|
30
|
+
).join('');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function syncEditorScroll() {
|
|
34
|
+
if (!els.highlightPre) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
els.highlightPre.scrollTop = els.editor.scrollTop;
|
|
38
|
+
els.highlightPre.scrollLeft = els.editor.scrollLeft;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function updateHighlight() {
|
|
42
|
+
if (!els.highlightCode || typeof hljs === 'undefined') {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const source = els.editor.value;
|
|
47
|
+
els.highlightCode.textContent = source.endsWith('\n') ? source : `${source}\n`;
|
|
48
|
+
|
|
49
|
+
if (typeof hljs.highlight === 'function') {
|
|
50
|
+
const { value } = hljs.highlight(source, { language: 'javascript' });
|
|
51
|
+
els.highlightCode.innerHTML = value;
|
|
52
|
+
els.highlightCode.classList.add('hljs', 'language-javascript');
|
|
53
|
+
} else {
|
|
54
|
+
hljs.highlightElement(els.highlightCode);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
syncEditorScroll();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function scheduleHighlight() {
|
|
61
|
+
if (highlightTimer) {
|
|
62
|
+
cancelAnimationFrame(highlightTimer);
|
|
63
|
+
}
|
|
64
|
+
highlightTimer = requestAnimationFrame(() => {
|
|
65
|
+
highlightTimer = null;
|
|
66
|
+
updateHighlight();
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function loadDemo(demo, { pushHash = true } = {}) {
|
|
71
|
+
els.featureSelect.value = demo.id;
|
|
72
|
+
els.featureDesc.textContent = demo.description;
|
|
73
|
+
els.editor.value = demo.code;
|
|
74
|
+
scheduleHighlight();
|
|
75
|
+
if (pushHash) {
|
|
76
|
+
history.replaceState(null, '', `#${demo.id}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function appendOutputLine(line, className = '') {
|
|
81
|
+
const row = document.createElement('div');
|
|
82
|
+
row.className = `playground-output__line${className ? ` ${className}` : ''}`;
|
|
83
|
+
row.textContent = line;
|
|
84
|
+
els.output.appendChild(row);
|
|
85
|
+
els.output.scrollTop = els.output.scrollHeight;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function clearOutput() {
|
|
89
|
+
els.output.innerHTML = '';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function setStatus(text, kind = '') {
|
|
93
|
+
els.status.textContent = text;
|
|
94
|
+
els.status.className = `playground-toolbar__status${kind ? ` is-${kind}` : ''}`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function createHelpers() {
|
|
98
|
+
const tracked = [];
|
|
99
|
+
const cleanupFns = [];
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
fastSecurity(overrides = {}) {
|
|
103
|
+
return {
|
|
104
|
+
appPassword: 'playground-app-password',
|
|
105
|
+
powEnabled: false,
|
|
106
|
+
signingEnabled: false,
|
|
107
|
+
encryptionEnabled: false,
|
|
108
|
+
...overrides
|
|
109
|
+
};
|
|
110
|
+
},
|
|
111
|
+
sleep(ms = 30) {
|
|
112
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
113
|
+
},
|
|
114
|
+
track(...nodes) {
|
|
115
|
+
tracked.push(...nodes);
|
|
116
|
+
},
|
|
117
|
+
onCleanup(fn) {
|
|
118
|
+
cleanupFns.push(fn);
|
|
119
|
+
},
|
|
120
|
+
async cleanupAll() {
|
|
121
|
+
for (const fn of cleanupFns.splice(0)) {
|
|
122
|
+
try {
|
|
123
|
+
await fn();
|
|
124
|
+
} catch (_ignored) {
|
|
125
|
+
// ignore cleanup errors
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
for (const node of tracked.splice(0)) {
|
|
129
|
+
try {
|
|
130
|
+
if (node && typeof node.stop === 'function') {
|
|
131
|
+
await node.stop();
|
|
132
|
+
}
|
|
133
|
+
} catch (_ignored) {
|
|
134
|
+
// ignore stop errors
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function runCleanup() {
|
|
142
|
+
if (typeof activeCleanup === 'function') {
|
|
143
|
+
await activeCleanup();
|
|
144
|
+
activeCleanup = null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function runCode() {
|
|
149
|
+
if (running) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
running = true;
|
|
154
|
+
els.runBtn.disabled = true;
|
|
155
|
+
clearOutput();
|
|
156
|
+
setStatus('Running…', 'running');
|
|
157
|
+
|
|
158
|
+
await runCleanup();
|
|
159
|
+
|
|
160
|
+
const helpers = createHelpers();
|
|
161
|
+
activeCleanup = () => helpers.cleanupAll();
|
|
162
|
+
|
|
163
|
+
const log = (...args) => {
|
|
164
|
+
const line = args
|
|
165
|
+
.map((value) => {
|
|
166
|
+
if (typeof value === 'string') {
|
|
167
|
+
return value;
|
|
168
|
+
}
|
|
169
|
+
try {
|
|
170
|
+
return JSON.stringify(value);
|
|
171
|
+
} catch (_ignored) {
|
|
172
|
+
return String(value);
|
|
173
|
+
}
|
|
174
|
+
})
|
|
175
|
+
.join(' ');
|
|
176
|
+
appendOutputLine(line);
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
const dignity = await dignityPromise;
|
|
181
|
+
const userCode = els.editor.value;
|
|
182
|
+
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;
|
|
183
|
+
const runner = new AsyncFunction('dignity', 'log', 'helpers', `"use strict";\n${userCode}`);
|
|
184
|
+
await runner(dignity, log, helpers);
|
|
185
|
+
setStatus('Done', 'ok');
|
|
186
|
+
} catch (error) {
|
|
187
|
+
appendOutputLine(String(error?.stack || error?.message || error), 'is-error');
|
|
188
|
+
setStatus('Error', 'error');
|
|
189
|
+
} finally {
|
|
190
|
+
await runCleanup();
|
|
191
|
+
running = false;
|
|
192
|
+
els.runBtn.disabled = false;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function initEditor() {
|
|
197
|
+
els.editor.addEventListener('input', scheduleHighlight);
|
|
198
|
+
els.editor.addEventListener('scroll', syncEditorScroll);
|
|
199
|
+
|
|
200
|
+
els.editor.addEventListener('keydown', (event) => {
|
|
201
|
+
if (event.key !== 'Tab') {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
event.preventDefault();
|
|
205
|
+
const { selectionStart, selectionEnd, value } = els.editor;
|
|
206
|
+
const next = `${value.slice(0, selectionStart)} ${value.slice(selectionEnd)}`;
|
|
207
|
+
els.editor.value = next;
|
|
208
|
+
els.editor.selectionStart = els.editor.selectionEnd = selectionStart + 2;
|
|
209
|
+
scheduleHighlight();
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function initFromHash() {
|
|
214
|
+
const id = window.location.hash.replace('#', '');
|
|
215
|
+
const demo = id ? getDemoById(id) : PLAYGROUND_DEMOS[0];
|
|
216
|
+
loadDemo(demo, { pushHash: Boolean(id) });
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
populateFeatureSelect();
|
|
220
|
+
initFromHash();
|
|
221
|
+
initEditor();
|
|
222
|
+
updateHighlight();
|
|
223
|
+
|
|
224
|
+
els.featureSelect.addEventListener('change', () => {
|
|
225
|
+
loadDemo(getDemoById(els.featureSelect.value));
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
els.runBtn.addEventListener('click', () => {
|
|
229
|
+
runCode();
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
els.resetBtn.addEventListener('click', () => {
|
|
233
|
+
loadDemo(getDemoById(els.featureSelect.value), { pushHash: false });
|
|
234
|
+
clearOutput();
|
|
235
|
+
setStatus('Reset to example');
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
els.clearBtn.addEventListener('click', () => {
|
|
239
|
+
clearOutput();
|
|
240
|
+
setStatus('');
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
window.addEventListener('hashchange', () => {
|
|
244
|
+
const id = window.location.hash.replace('#', '');
|
|
245
|
+
if (id) {
|
|
246
|
+
loadDemo(getDemoById(id), { pushHash: false });
|
|
247
|
+
}
|
|
248
|
+
});
|
package/docs/assets/styles.css
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
--green: #1a7f37;
|
|
15
15
|
--green-bg: #dafbe1;
|
|
16
16
|
--sidebar-width: 280px;
|
|
17
|
-
--header-height:
|
|
17
|
+
--header-height: 64px;
|
|
18
18
|
--content-max: 920px;
|
|
19
19
|
--radius: 8px;
|
|
20
20
|
--shadow: 0 1px 3px rgba(27, 31, 36, 0.08);
|
|
@@ -89,6 +89,22 @@ pre code.hljs {
|
|
|
89
89
|
padding: 16px 18px;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
.playground-cta {
|
|
93
|
+
display: inline-block;
|
|
94
|
+
font-weight: 600;
|
|
95
|
+
padding: 10px 16px;
|
|
96
|
+
border-radius: var(--radius);
|
|
97
|
+
background: var(--accent-soft);
|
|
98
|
+
border: 1px solid var(--accent);
|
|
99
|
+
text-decoration: none;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.playground-cta:hover {
|
|
103
|
+
background: var(--accent);
|
|
104
|
+
color: #fff;
|
|
105
|
+
text-decoration: none;
|
|
106
|
+
}
|
|
107
|
+
|
|
92
108
|
/* Header */
|
|
93
109
|
.site-header {
|
|
94
110
|
position: sticky;
|
|
@@ -119,7 +135,7 @@ pre code.hljs {
|
|
|
119
135
|
}
|
|
120
136
|
|
|
121
137
|
.site-header__brand img {
|
|
122
|
-
height:
|
|
138
|
+
height: 54px;
|
|
123
139
|
width: auto;
|
|
124
140
|
}
|
|
125
141
|
|
package/docs/index.html
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
-
<meta name="description" content="dignity.js v0.
|
|
6
|
+
<meta name="description" content="dignity.js v0.7.1 — REST-like P2P object API for decentralized JavaScript applications." />
|
|
7
7
|
<title>dignity.js · Documentation</title>
|
|
8
8
|
<link rel="icon" href="./assets/favicon.svg" type="image/svg+xml" />
|
|
9
9
|
<link rel="icon" href="./favicon.ico" sizes="32x32" />
|
|
@@ -16,11 +16,12 @@
|
|
|
16
16
|
<a class="site-header__brand" href="#overview">
|
|
17
17
|
<img src="./assets/dignity-logo.svg" alt="" width="344" height="80" />
|
|
18
18
|
<!-- <span>dignity.js</span> -->
|
|
19
|
-
<span class="site-header__version">v0.
|
|
19
|
+
<span class="site-header__version">v0.7.1</span>
|
|
20
20
|
</a>
|
|
21
21
|
<div class="site-header__links">
|
|
22
22
|
<a href="https://www.npmjs.com/package/dignity.js" target="_blank" rel="noopener noreferrer">npm</a>
|
|
23
23
|
<a href="https://github.com/jose-compu/dignity.js" target="_blank" rel="noopener noreferrer">GitHub</a>
|
|
24
|
+
<a href="./playground/index.html">Playground</a>
|
|
24
25
|
<a href="./chess/index.html">3D Chess</a>
|
|
25
26
|
<a href="./openapi-like.json">API JSON</a>
|
|
26
27
|
</div>
|
|
@@ -36,6 +37,7 @@
|
|
|
36
37
|
<a href="#installation">Installation</a>
|
|
37
38
|
<a href="#tutorial">Tutorial</a>
|
|
38
39
|
<a href="#quick-start">Quick start</a>
|
|
40
|
+
<a href="./playground/index.html">Live playground</a>
|
|
39
41
|
<a href="#browser">Browser usage</a>
|
|
40
42
|
</nav>
|
|
41
43
|
</div>
|
|
@@ -294,6 +296,17 @@ await bob.stop();</code></pre>
|
|
|
294
296
|
</div>
|
|
295
297
|
</section>
|
|
296
298
|
|
|
299
|
+
<section id="playground" class="section">
|
|
300
|
+
<h2>Live playground</h2>
|
|
301
|
+
<p>
|
|
302
|
+
Try dignity.js features in the browser: pick a scenario, edit the code on the left,
|
|
303
|
+
and see output on the right. Uses the bundled ESM build with an in-memory mesh — no install required.
|
|
304
|
+
</p>
|
|
305
|
+
<p>
|
|
306
|
+
<a class="playground-cta" href="./playground/index.html">Open live playground →</a>
|
|
307
|
+
</p>
|
|
308
|
+
</section>
|
|
309
|
+
|
|
297
310
|
<section id="browser" class="section">
|
|
298
311
|
<h2>Browser usage</h2>
|
|
299
312
|
<p>Pre-built bundles are published to npm and available via CDN:</p>
|
|
@@ -578,6 +591,8 @@ node.getPeerGroupStats();
|
|
|
578
591
|
</div>
|
|
579
592
|
|
|
580
593
|
<h3>Roadmap</h3>
|
|
594
|
+
<p><strong>v0.7.1 (current)</strong> — live docs playground, demo fixes, syntax highlighting.</p>
|
|
595
|
+
<p><strong>v0.7.0 (shipped)</strong> — credential-derived keys, identity rotation, PeerGroup hardening, stress harness.</p>
|
|
581
596
|
<p><strong>v0.6.0 (shipped)</strong> — core gossip, chess spectators, unit + e2e tests.</p>
|
|
582
597
|
<p><strong>v0.6.x polish</strong> — <code>subscribeObjectFeed</code> wrapper, connection LRU trim, IndexedDB joined-group persistence, React <code>usePeerGroup</code> hook, 50+ node integration test.</p>
|
|
583
598
|
<p><strong>Future</strong> — publisher rate limits, partial snapshot / delta feeds, cross-group fan-in metrics.</p>
|
|
@@ -932,6 +947,11 @@ const pool = createDefaultSignalingPool({
|
|
|
932
947
|
<td>Replicated move history with compact board model</td>
|
|
933
948
|
<td><code>npm run example:chess</code></td>
|
|
934
949
|
</tr>
|
|
950
|
+
<tr>
|
|
951
|
+
<td><a href="./playground/index.html"><code>docs/playground/</code></a></td>
|
|
952
|
+
<td>Live browser playground — editable in-memory demos per feature</td>
|
|
953
|
+
<td><code>npm run docs:serve</code></td>
|
|
954
|
+
</tr>
|
|
935
955
|
<tr>
|
|
936
956
|
<td><a href="./chess/index.html"><code>docs/chess/</code></a></td>
|
|
937
957
|
<td>3D browser chess — PeerJS mesh, dual-signed resume links, IndexedDB, React hooks</td>
|
|
@@ -965,7 +985,7 @@ npm run example:chess</code></pre>
|
|
|
965
985
|
|
|
966
986
|
<footer class="site-footer">
|
|
967
987
|
<p>
|
|
968
|
-
dignity.js v0.
|
|
988
|
+
dignity.js v0.7.1 ·
|
|
969
989
|
<a href="https://github.com/jose-compu/dignity.js/blob/main/LICENSE">Apache 2.0</a> ·
|
|
970
990
|
<a href="https://github.com/jose-compu/dignity.js">GitHub</a> ·
|
|
971
991
|
<a href="https://www.npmjs.com/package/dignity.js">npm</a>
|
package/docs/openapi-like.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dignity.js",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "P2P object API for decentralized JavaScript applications",
|
|
5
5
|
"homepage": "https://jose-compu.github.io/dignity.js/",
|
|
6
6
|
"repository": {
|
|
@@ -42,6 +42,8 @@
|
|
|
42
42
|
"test": "jest --coverage",
|
|
43
43
|
"test:unit": "jest tests/unit --runInBand",
|
|
44
44
|
"test:peer-group": "jest tests/unit/peer-group-gossip.test.js tests/unit/peer-group-helpers.test.js tests/integration/peer-group-gossip-e2e.test.js --runInBand --coverage=false",
|
|
45
|
+
"test:stress-peer-group": "RUN_STRESS_TESTS=1 jest tests/stress/peer-group-scale.test.js --runInBand --coverage=false",
|
|
46
|
+
"stress:peer-group": "node scripts/stress-peer-group.js",
|
|
45
47
|
"test:cloudflare-live": "RUN_CLOUDFLARE_LIVE_TESTS=1 jest tests/integration/cloudflare-signaling-live.test.js --runInBand",
|
|
46
48
|
"test:pow-calibrate": "RUN_POW_CALIBRATE=1 jest tests/unit/sloth-vdf-timing.test.js --runInBand --coverage=false",
|
|
47
49
|
"build": "node scripts/build.js",
|
|
@@ -51,7 +53,7 @@
|
|
|
51
53
|
"docs:dev": "node scripts/serve-docs.js",
|
|
52
54
|
"docs:serve": "node scripts/serve-docs.js",
|
|
53
55
|
"docs:stop": "node scripts/stop-docs.js",
|
|
54
|
-
"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));\"",
|
|
56
|
+
"docs:check": "node -e \"const fs=require('fs');['docs/index.html','docs/favicon.ico','docs/playground/index.html','docs/assets/playground.js','docs/assets/playground-demos.js','docs/assets/playground.css','docs/chess/index.html','docs/chess/favicon.ico','docs/assets/favicon.svg','docs/chess/assets/chess-app.js','docs/assets/dignity.esm.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));\"",
|
|
55
57
|
"example:tictactoe": "node examples/decentralized-tictactoe.js",
|
|
56
58
|
"example:chess": "node examples/decentralized-chess-lite.js",
|
|
57
59
|
"prepublishOnly": "npm test && npm run build"
|
|
@@ -63,7 +65,7 @@
|
|
|
63
65
|
"rest",
|
|
64
66
|
"objects"
|
|
65
67
|
],
|
|
66
|
-
"license": "Apache
|
|
68
|
+
"license": "Apache-2.0",
|
|
67
69
|
"peerDependencies": {
|
|
68
70
|
"react": ">=18"
|
|
69
71
|
},
|