dignity.js 0.5.2 → 0.5.3
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 +12 -21
- package/package.json +15 -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
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
const SESSIONS_KEY = 'dignity-chess-sessions';
|
|
2
|
-
const COLLECTION = 'chess-matches';
|
|
3
|
-
const DB_NAME = 'dignity';
|
|
4
|
-
const STORE_NAME = 'records';
|
|
5
|
-
|
|
6
|
-
export function loadLocalGameSessions() {
|
|
7
|
-
try {
|
|
8
|
-
const raw = localStorage.getItem(SESSIONS_KEY);
|
|
9
|
-
const parsed = raw ? JSON.parse(raw) : [];
|
|
10
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
11
|
-
} catch (error) {
|
|
12
|
-
return [];
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function saveLocalGameSession(session) {
|
|
17
|
-
if (!session?.gameId || !session?.roomKey) {
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const sessions = loadLocalGameSessions();
|
|
22
|
-
const index = sessions.findIndex((entry) => entry.gameId === session.gameId);
|
|
23
|
-
const next = {
|
|
24
|
-
...sessions[index],
|
|
25
|
-
...session,
|
|
26
|
-
updatedAt: session.updatedAt || Date.now()
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
if (index >= 0) {
|
|
30
|
-
sessions[index] = next;
|
|
31
|
-
} else {
|
|
32
|
-
sessions.unshift(next);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const trimmed = sessions
|
|
36
|
-
.sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0))
|
|
37
|
-
.slice(0, 40);
|
|
38
|
-
|
|
39
|
-
localStorage.setItem(SESSIONS_KEY, JSON.stringify(trimmed));
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function loadChessRecordsFromIndexedDB() {
|
|
43
|
-
if (typeof indexedDB === 'undefined') {
|
|
44
|
-
return Promise.resolve([]);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return new Promise((resolve) => {
|
|
48
|
-
const request = indexedDB.open(DB_NAME, 1);
|
|
49
|
-
|
|
50
|
-
request.onerror = () => resolve([]);
|
|
51
|
-
request.onsuccess = () => {
|
|
52
|
-
const db = request.result;
|
|
53
|
-
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
54
|
-
db.close();
|
|
55
|
-
resolve([]);
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const transaction = db.transaction(STORE_NAME, 'readonly');
|
|
60
|
-
const store = transaction.objectStore(STORE_NAME);
|
|
61
|
-
const getAll = store.getAll();
|
|
62
|
-
|
|
63
|
-
getAll.onsuccess = () => {
|
|
64
|
-
const records = (getAll.result || []).filter(
|
|
65
|
-
(record) => record.collection === COLLECTION && !record.deletedAt
|
|
66
|
-
);
|
|
67
|
-
resolve(records);
|
|
68
|
-
db.close();
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
getAll.onerror = () => {
|
|
72
|
-
resolve([]);
|
|
73
|
-
db.close();
|
|
74
|
-
};
|
|
75
|
-
};
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function mergeSessionWithRecord(session, record) {
|
|
80
|
-
if (!record?.data) {
|
|
81
|
-
return session;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return {
|
|
85
|
-
...session,
|
|
86
|
-
status: record.data.status || session.status || 'waiting',
|
|
87
|
-
winner: record.data.winner ?? session.winner ?? null,
|
|
88
|
-
moveCount: Array.isArray(record.data.moveHistory) ? record.data.moveHistory.length : session.moveCount || 0,
|
|
89
|
-
whitePlayerId: record.data.whitePlayerId || session.whitePlayerId || null,
|
|
90
|
-
blackPlayerId: record.data.blackPlayerId || session.blackPlayerId || null,
|
|
91
|
-
updatedAt: record.updatedAt || session.updatedAt || Date.now()
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export async function listLocalGames() {
|
|
96
|
-
const sessions = loadLocalGameSessions();
|
|
97
|
-
const records = await loadChessRecordsFromIndexedDB();
|
|
98
|
-
const recordById = new Map(records.map((record) => [record.id, record]));
|
|
99
|
-
|
|
100
|
-
const merged = sessions.map((session) => mergeSessionWithRecord(session, recordById.get(session.gameId)));
|
|
101
|
-
|
|
102
|
-
const active = merged.filter((game) => game.status === 'waiting' || game.status === 'playing');
|
|
103
|
-
const finished = merged.filter((game) => game.status === 'finished');
|
|
104
|
-
|
|
105
|
-
return { active, finished };
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export function sessionResumeHash(session) {
|
|
109
|
-
const role = session.role === 'host' ? 'host' : 'resume';
|
|
110
|
-
const params = new URLSearchParams({
|
|
111
|
-
game: session.gameId,
|
|
112
|
-
room: session.roomKey,
|
|
113
|
-
role,
|
|
114
|
-
resume: session.resumeToken || ''
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
if (session.hostPeer) {
|
|
118
|
-
params.set('host', session.hostPeer);
|
|
119
|
-
}
|
|
120
|
-
if (session.role === 'host' && session.joinToken) {
|
|
121
|
-
params.set('join', session.joinToken);
|
|
122
|
-
}
|
|
123
|
-
if (session.role === 'host' && session.watchToken) {
|
|
124
|
-
params.set('watch', session.watchToken);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return params.toString();
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
export function formatGameStatus(game) {
|
|
131
|
-
if (game.status === 'waiting') {
|
|
132
|
-
return 'Waiting for opponent';
|
|
133
|
-
}
|
|
134
|
-
if (game.status === 'playing') {
|
|
135
|
-
return `${game.moveCount || 0} move(s) · in progress`;
|
|
136
|
-
}
|
|
137
|
-
if (game.winner === 'draw') {
|
|
138
|
-
return 'Draw';
|
|
139
|
-
}
|
|
140
|
-
if (game.winner === 'w') {
|
|
141
|
-
return 'White wins';
|
|
142
|
-
}
|
|
143
|
-
if (game.winner === 'b') {
|
|
144
|
-
return 'Black wins';
|
|
145
|
-
}
|
|
146
|
-
return 'Finished';
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
export function formatRoleLabel(game) {
|
|
150
|
-
if (game.role === 'host') {
|
|
151
|
-
return 'You · White (host)';
|
|
152
|
-
}
|
|
153
|
-
if (game.role === 'join') {
|
|
154
|
-
return 'You · Black';
|
|
155
|
-
}
|
|
156
|
-
if (game.role === 'watch') {
|
|
157
|
-
return 'Spectator';
|
|
158
|
-
}
|
|
159
|
-
return 'Resume';
|
|
160
|
-
}
|
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
const PREFIX = '[chess-p2p]';
|
|
2
|
-
|
|
3
|
-
function ts() {
|
|
4
|
-
return new Date().toISOString().slice(11, 23);
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function p2pLog(label, detail) {
|
|
8
|
-
if (detail === undefined) {
|
|
9
|
-
console.log(`${PREFIX} ${ts()} ${label}`);
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
12
|
-
console.log(`${PREFIX} ${ts()} ${label}`, detail);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function p2pWarn(label, detail) {
|
|
16
|
-
if (detail === undefined) {
|
|
17
|
-
console.warn(`${PREFIX} ${ts()} ${label}`);
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
console.warn(`${PREFIX} ${ts()} ${label}`, detail);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function p2pError(label, detail) {
|
|
24
|
-
if (detail === undefined) {
|
|
25
|
-
console.error(`${PREFIX} ${ts()} ${label}`);
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
console.error(`${PREFIX} ${ts()} ${label}`, detail);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function connectionSnapshot(node) {
|
|
32
|
-
const adapter = node?.networkAdapter;
|
|
33
|
-
if (!adapter) {
|
|
34
|
-
return { adapter: 'none' };
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const openIds = [];
|
|
38
|
-
if (adapter.connections instanceof Map) {
|
|
39
|
-
for (const [peerId, conn] of adapter.connections.entries()) {
|
|
40
|
-
if (conn?.open) {
|
|
41
|
-
openIds.push(peerId);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return {
|
|
47
|
-
signalingUrl: adapter.url || null,
|
|
48
|
-
localPeerId: adapter.nodeId || node?.nodeId || null,
|
|
49
|
-
openConnectionCount: node?.getConnectionStats?.()?.openCount ?? adapter.getOpenConnectionCount?.() ?? openIds.length,
|
|
50
|
-
openPeerIds: node?.getConnectionStats?.()?.peerIds ?? openIds,
|
|
51
|
-
peerJsReady: Boolean(adapter.peer)
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function attachNodeDebugListeners(node, context = {}) {
|
|
56
|
-
if (!node || node.__chessDebugAttached) {
|
|
57
|
-
return () => undefined;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
node.__chessDebugAttached = true;
|
|
61
|
-
const role = context.role || '?';
|
|
62
|
-
|
|
63
|
-
p2pLog(`debug attached (${role})`, {
|
|
64
|
-
nodeId: node.nodeId,
|
|
65
|
-
scope: context.scope,
|
|
66
|
-
gameId: context.gameId
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
const handlers = {
|
|
70
|
-
warning: (event) => p2pWarn('node warning', event),
|
|
71
|
-
securityerror: (event) => p2pError('security error', {
|
|
72
|
-
senderId: event?.senderId,
|
|
73
|
-
code: event?.error?.code,
|
|
74
|
-
message: event?.error?.message
|
|
75
|
-
}),
|
|
76
|
-
messageignored: (event) => p2pWarn('message ignored', event),
|
|
77
|
-
peerdiscovered: (event) => p2pLog('peer discovered', event),
|
|
78
|
-
peerleft: (event) => p2pLog('peer left', event),
|
|
79
|
-
peerbanned: (event) => p2pError('peer banned', event),
|
|
80
|
-
conflict: (event) => p2pWarn('sync conflict', event),
|
|
81
|
-
change: (event) => {
|
|
82
|
-
if (event?.collection === context.collection) {
|
|
83
|
-
p2pLog('game record changed', {
|
|
84
|
-
id: event.id,
|
|
85
|
-
kind: event.kind,
|
|
86
|
-
version: event.version
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
},
|
|
90
|
-
message: (event) => {
|
|
91
|
-
if (event?.type === 'claim-seat' || event?.type === 'operation') {
|
|
92
|
-
p2pLog('incoming message', {
|
|
93
|
-
type: event.type,
|
|
94
|
-
senderId: event.senderId,
|
|
95
|
-
payload: event.payload
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
for (const [eventName, handler] of Object.entries(handlers)) {
|
|
102
|
-
node.on(eventName, handler);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (node.networkAdapter?.peer) {
|
|
106
|
-
const peer = node.networkAdapter.peer;
|
|
107
|
-
peer.on('error', (err) => {
|
|
108
|
-
p2pError('PeerJS error', {
|
|
109
|
-
type: err?.type,
|
|
110
|
-
message: err?.message || String(err)
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
peer.on('disconnected', () => p2pWarn('PeerJS disconnected from signaling server'));
|
|
114
|
-
peer.on('close', () => p2pWarn('PeerJS peer closed'));
|
|
115
|
-
peer.on('connection', (conn) => {
|
|
116
|
-
p2pLog('PeerJS inbound connection', { from: conn.peer });
|
|
117
|
-
conn.on('open', () => p2pLog('PeerJS data channel open (inbound)', { peer: conn.peer }));
|
|
118
|
-
conn.on('close', () => p2pWarn('PeerJS data channel closed (inbound)', { peer: conn.peer }));
|
|
119
|
-
conn.on('error', (err) => p2pError('PeerJS data channel error (inbound)', {
|
|
120
|
-
peer: conn.peer,
|
|
121
|
-
message: err?.message || String(err)
|
|
122
|
-
}));
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return () => {
|
|
127
|
-
for (const [eventName, handler] of Object.entries(handlers)) {
|
|
128
|
-
node.off(eventName, handler);
|
|
129
|
-
}
|
|
130
|
-
delete node.__chessDebugAttached;
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
export function dumpJoinState({
|
|
135
|
-
route,
|
|
136
|
-
node,
|
|
137
|
-
nodeId,
|
|
138
|
-
scope,
|
|
139
|
-
status,
|
|
140
|
-
error,
|
|
141
|
-
joined,
|
|
142
|
-
roomConnected,
|
|
143
|
-
connectionCount,
|
|
144
|
-
remoteHostPeer,
|
|
145
|
-
peers,
|
|
146
|
-
game,
|
|
147
|
-
myColor
|
|
148
|
-
}) {
|
|
149
|
-
const snap = connectionSnapshot(node);
|
|
150
|
-
const payload = {
|
|
151
|
-
role: route.role,
|
|
152
|
-
localNodeId: nodeId,
|
|
153
|
-
nodeRunningId: node?.nodeId,
|
|
154
|
-
hostPeerTarget: remoteHostPeer,
|
|
155
|
-
routeHostParam: route.hostPeer,
|
|
156
|
-
gameId: route.gameId,
|
|
157
|
-
scope,
|
|
158
|
-
dignityStatus: status,
|
|
159
|
-
dignityError: error?.message || null,
|
|
160
|
-
joinedDiscovery: joined,
|
|
161
|
-
roomConnected,
|
|
162
|
-
connectionCount,
|
|
163
|
-
peerJs: snap,
|
|
164
|
-
peers: peers.map((p) => ({
|
|
165
|
-
peerId: p.peerId,
|
|
166
|
-
role: p.metadata?.role,
|
|
167
|
-
nickname: p.metadata?.nickname,
|
|
168
|
-
joinToken: p.metadata?.joinToken ? `${p.metadata.joinToken.slice(0, 6)}…` : null
|
|
169
|
-
})),
|
|
170
|
-
game: game ? {
|
|
171
|
-
status: game.data?.status,
|
|
172
|
-
whitePlayerId: game.data?.whitePlayerId,
|
|
173
|
-
blackPlayerId: game.data?.blackPlayerId,
|
|
174
|
-
joinTokenUsed: game.data?.joinTokenUsed,
|
|
175
|
-
joinTokenMatch: route.joinToken === game.data?.joinToken,
|
|
176
|
-
version: game.version
|
|
177
|
-
} : null,
|
|
178
|
-
myColor,
|
|
179
|
-
webrtc: typeof RTCPeerConnection !== 'undefined' ? 'available' : 'missing'
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
p2pLog('STATE SNAPSHOT', payload);
|
|
183
|
-
return payload;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
export function installGlobalDebug(dumpFn) {
|
|
187
|
-
if (typeof window === 'undefined') {
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
window.__chessP2pDump = dumpFn;
|
|
191
|
-
p2pLog('manual dump: run __chessP2pDump() in console');
|
|
192
|
-
}
|