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.
@@ -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
- }
@@ -1,5 +0,0 @@
1
- import React from 'react';
2
- import { createRoot } from 'react-dom/client';
3
- import App from './App.jsx';
4
-
5
- createRoot(document.getElementById('root')).render(<App />);