nostr-arena 0.1.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.
Files changed (71) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +188 -0
  3. package/dist/__tests__/callbacks.test.d.ts +28 -0
  4. package/dist/__tests__/callbacks.test.d.ts.map +1 -0
  5. package/dist/__tests__/callbacks.test.js +140 -0
  6. package/dist/__tests__/callbacks.test.js.map +1 -0
  7. package/dist/__tests__/config.test.d.ts +18 -0
  8. package/dist/__tests__/config.test.d.ts.map +1 -0
  9. package/dist/__tests__/config.test.js +43 -0
  10. package/dist/__tests__/config.test.js.map +1 -0
  11. package/dist/__tests__/error-handling.test.d.ts +14 -0
  12. package/dist/__tests__/error-handling.test.d.ts.map +1 -0
  13. package/dist/__tests__/error-handling.test.js +199 -0
  14. package/dist/__tests__/error-handling.test.js.map +1 -0
  15. package/dist/__tests__/protocol.test.d.ts +7 -0
  16. package/dist/__tests__/protocol.test.d.ts.map +1 -0
  17. package/dist/__tests__/protocol.test.js +257 -0
  18. package/dist/__tests__/protocol.test.js.map +1 -0
  19. package/dist/__tests__/state-flow.test.d.ts +37 -0
  20. package/dist/__tests__/state-flow.test.d.ts.map +1 -0
  21. package/dist/__tests__/state-flow.test.js +160 -0
  22. package/dist/__tests__/state-flow.test.js.map +1 -0
  23. package/dist/__tests__/timing.test.d.ts +20 -0
  24. package/dist/__tests__/timing.test.d.ts.map +1 -0
  25. package/dist/__tests__/timing.test.js +73 -0
  26. package/dist/__tests__/timing.test.js.map +1 -0
  27. package/dist/core/Arena.d.ts +164 -0
  28. package/dist/core/Arena.d.ts.map +1 -0
  29. package/dist/core/Arena.js +634 -0
  30. package/dist/core/Arena.js.map +1 -0
  31. package/dist/core/NostrClient.d.ts +108 -0
  32. package/dist/core/NostrClient.d.ts.map +1 -0
  33. package/dist/core/NostrClient.js +225 -0
  34. package/dist/core/NostrClient.js.map +1 -0
  35. package/dist/core/index.d.ts +8 -0
  36. package/dist/core/index.d.ts.map +1 -0
  37. package/dist/core/index.js +7 -0
  38. package/dist/core/index.js.map +1 -0
  39. package/dist/index.d.ts +35 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +37 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/proxy.d.ts +36 -0
  44. package/dist/proxy.d.ts.map +1 -0
  45. package/dist/proxy.js +98 -0
  46. package/dist/proxy.js.map +1 -0
  47. package/dist/react/index.d.ts +7 -0
  48. package/dist/react/index.d.ts.map +1 -0
  49. package/dist/react/index.js +6 -0
  50. package/dist/react/index.js.map +1 -0
  51. package/dist/react/useArena.d.ts +74 -0
  52. package/dist/react/useArena.d.ts.map +1 -0
  53. package/dist/react/useArena.js +224 -0
  54. package/dist/react/useArena.js.map +1 -0
  55. package/dist/retry.d.ts +54 -0
  56. package/dist/retry.d.ts.map +1 -0
  57. package/dist/retry.js +82 -0
  58. package/dist/retry.js.map +1 -0
  59. package/dist/testing/MockArena.d.ts +84 -0
  60. package/dist/testing/MockArena.d.ts.map +1 -0
  61. package/dist/testing/MockArena.js +156 -0
  62. package/dist/testing/MockArena.js.map +1 -0
  63. package/dist/testing/index.d.ts +6 -0
  64. package/dist/testing/index.d.ts.map +1 -0
  65. package/dist/testing/index.js +6 -0
  66. package/dist/testing/index.js.map +1 -0
  67. package/dist/types.d.ts +183 -0
  68. package/dist/types.d.ts.map +1 -0
  69. package/dist/types.js +55 -0
  70. package/dist/types.js.map +1 -0
  71. package/package.json +84 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 kako-jun
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,188 @@
1
+ # nostr-arena
2
+
3
+ Nostr-based real-time battle room for multiplayer games. No server required.
4
+
5
+ ## Features
6
+
7
+ - **P2P Matchmaking**: Create and join rooms via shareable URLs
8
+ - **Real-time State Sync**: Send game state to opponents with automatic throttling
9
+ - **Connection Health**: Heartbeat and disconnect detection
10
+ - **Reconnection**: Automatic reconnection from localStorage
11
+ - **Rematch**: Built-in rematch flow
12
+ - **Framework Agnostic**: Core classes work anywhere, React hooks included
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install nostr-arena nostr-tools
18
+ ```
19
+
20
+ ## Quick Start
21
+
22
+ ### React
23
+
24
+ ```tsx
25
+ import { useArena } from 'nostr-arena/react';
26
+
27
+ interface MyGameState {
28
+ score: number;
29
+ position: { x: number; y: number };
30
+ }
31
+
32
+ function Game() {
33
+ const { roomState, opponent, createRoom, joinRoom, sendState, leaveRoom } =
34
+ useArena<MyGameState>({
35
+ gameId: 'my-game',
36
+ });
37
+
38
+ const handleCreate = async () => {
39
+ const url = await createRoom();
40
+ // Share this URL with opponent
41
+ navigator.clipboard.writeText(url);
42
+ };
43
+
44
+ const handleMove = (x: number, y: number) => {
45
+ sendState({ score: 100, position: { x, y } });
46
+ };
47
+
48
+ return (
49
+ <div>
50
+ <p>Status: {roomState.status}</p>
51
+ {roomState.status === 'idle' && <button onClick={handleCreate}>Create Room</button>}
52
+ {opponent && <p>Opponent score: {opponent.gameState?.score ?? 0}</p>}
53
+ </div>
54
+ );
55
+ }
56
+ ```
57
+
58
+ ### Vanilla JavaScript
59
+
60
+ ```typescript
61
+ import { Arena } from 'nostr-arena';
62
+
63
+ interface MyGameState {
64
+ score: number;
65
+ }
66
+
67
+ const room = new Arena<MyGameState>({
68
+ gameId: 'my-game',
69
+ relays: ['wss://relay.damus.io'],
70
+ });
71
+
72
+ // Register event callbacks (chainable)
73
+ room
74
+ .onOpponentJoin((pubkey) => {
75
+ console.log('Opponent joined:', pubkey);
76
+ })
77
+ .onOpponentState((state) => {
78
+ console.log('Opponent score:', state.score);
79
+ })
80
+ .onOpponentDisconnect(() => {
81
+ console.log('Opponent disconnected');
82
+ });
83
+
84
+ // Create a room
85
+ room.connect();
86
+ const url = await room.create();
87
+ console.log('Share this URL:', url);
88
+
89
+ // Send state updates
90
+ room.sendState({ score: 100 });
91
+
92
+ // Game over
93
+ room.sendGameOver('win', 500);
94
+
95
+ // Cleanup
96
+ room.disconnect();
97
+ ```
98
+
99
+ ## API
100
+
101
+ ### ArenaConfig
102
+
103
+ ```typescript
104
+ interface ArenaConfig {
105
+ gameId: string; // Required: unique game identifier
106
+ relays?: string[]; // Nostr relay URLs (default: public relays)
107
+ roomExpiry?: number; // Room expiration in ms (default: 600000 = 10 min)
108
+ heartbeatInterval?: number; // Heartbeat interval in ms (default: 3000)
109
+ disconnectThreshold?: number; // Disconnect threshold in ms (default: 10000)
110
+ stateThrottle?: number; // State update throttle in ms (default: 100)
111
+ }
112
+ ```
113
+
114
+ ### Room States
115
+
116
+ | Status | Description |
117
+ | -------- | ------------------------------------- |
118
+ | idle | No room active |
119
+ | creating | Creating a new room |
120
+ | waiting | Waiting for opponent to join |
121
+ | joining | Joining an existing room |
122
+ | ready | Both players connected, ready to play |
123
+ | playing | Game in progress |
124
+ | finished | Game ended |
125
+
126
+ ### Events (Callbacks)
127
+
128
+ | Event | Parameters | Description |
129
+ | ------------------ | -------------------------------- | -------------------------- |
130
+ | opponentJoin | (publicKey: string) | Opponent joined the room |
131
+ | opponentState | (state: TGameState) | Opponent sent state update |
132
+ | opponentDisconnect | () | Opponent disconnected |
133
+ | opponentGameOver | (reason: string, score?: number) | Opponent game over |
134
+ | rematchRequested | () | Opponent requested rematch |
135
+ | rematchStart | (newSeed: number) | Rematch starting |
136
+ | error | (error: Error) | Error occurred |
137
+
138
+ ## Node.js / Proxy Support
139
+
140
+ For Node.js environments or when you need proxy support, call `configureProxy()` before creating any rooms:
141
+
142
+ ```typescript
143
+ import { configureProxy, Arena } from 'nostr-arena';
144
+
145
+ // Call once at startup
146
+ configureProxy();
147
+
148
+ // Now create rooms as usual
149
+ const room = new Arena({ gameId: 'my-game' });
150
+ ```
151
+
152
+ This function:
153
+
154
+ - Configures the `ws` package for Node.js WebSocket support
155
+ - Reads proxy URL from environment variables: `HTTPS_PROXY`, `HTTP_PROXY`, or `ALL_PROXY`
156
+ - No-op in browser environments (browsers handle proxies at OS level)
157
+
158
+ **Required packages for Node.js:**
159
+
160
+ ```bash
161
+ npm install ws # Required for Node.js
162
+ npm install https-proxy-agent # Required for proxy support
163
+ ```
164
+
165
+ ## Testing
166
+
167
+ ```typescript
168
+ import { MockArena } from 'nostr-arena/testing';
169
+
170
+ const mock = new MockArena<MyGameState>({ gameId: 'test' });
171
+
172
+ // Simulate opponent actions
173
+ mock.simulateOpponentJoin('pubkey123');
174
+ mock.simulateOpponentState({ score: 100 });
175
+ mock.simulateOpponentDisconnect();
176
+ ```
177
+
178
+ ## How It Works
179
+
180
+ 1. **Room Creation**: Host publishes a replaceable event (kind 30078) with room info
181
+ 2. **Joining**: Guest fetches room event, sends join notification (kind 25000)
182
+ 3. **State Sync**: Players send ephemeral events (kind 25000) with game state
183
+ 4. **Heartbeat**: Periodic heartbeat events detect disconnections
184
+ 5. **Cleanup**: Ephemeral events are not stored by relays (no garbage)
185
+
186
+ ## License
187
+
188
+ MIT
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Callbacks Tests
3
+ *
4
+ * Source: README.md - Events (Callbacks) table
5
+ *
6
+ * | Event | Parameters | Description |
7
+ * | ------------------ | -------------------------------- | -------------------------- |
8
+ * | opponentJoin | (publicKey: string) | Opponent joined the room |
9
+ * | opponentState | (state: TGameState) | Opponent sent state update |
10
+ * | opponentDisconnect | () | Opponent disconnected |
11
+ * | opponentGameOver | (reason: string, score?: number) | Opponent game over |
12
+ * | rematchRequested | () | Opponent requested rematch |
13
+ * | rematchStart | (newSeed: number) | Rematch starting |
14
+ * | error | (error: Error) | Error occurred |
15
+ *
16
+ * Source: architecture.md - Arena API
17
+ *
18
+ * All callback methods return `this` for chaining:
19
+ * - onOpponentJoin(callback): this
20
+ * - onOpponentState(callback): this
21
+ * - onOpponentDisconnect(callback): this
22
+ * - onOpponentGameOver(callback): this
23
+ * - onRematchRequested(callback): this
24
+ * - onRematchStart(callback): this
25
+ * - onError(callback): this
26
+ */
27
+ export {};
28
+ //# sourceMappingURL=callbacks.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"callbacks.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/callbacks.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG"}
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Callbacks Tests
3
+ *
4
+ * Source: README.md - Events (Callbacks) table
5
+ *
6
+ * | Event | Parameters | Description |
7
+ * | ------------------ | -------------------------------- | -------------------------- |
8
+ * | opponentJoin | (publicKey: string) | Opponent joined the room |
9
+ * | opponentState | (state: TGameState) | Opponent sent state update |
10
+ * | opponentDisconnect | () | Opponent disconnected |
11
+ * | opponentGameOver | (reason: string, score?: number) | Opponent game over |
12
+ * | rematchRequested | () | Opponent requested rematch |
13
+ * | rematchStart | (newSeed: number) | Rematch starting |
14
+ * | error | (error: Error) | Error occurred |
15
+ *
16
+ * Source: architecture.md - Arena API
17
+ *
18
+ * All callback methods return `this` for chaining:
19
+ * - onOpponentJoin(callback): this
20
+ * - onOpponentState(callback): this
21
+ * - onOpponentDisconnect(callback): this
22
+ * - onOpponentGameOver(callback): this
23
+ * - onRematchRequested(callback): this
24
+ * - onRematchStart(callback): this
25
+ * - onError(callback): this
26
+ */
27
+ import { describe, it, expect, beforeEach } from 'vitest';
28
+ import { MockArena } from '../testing/MockArena';
29
+ describe('Callbacks (README.md, architecture.md)', () => {
30
+ let room;
31
+ beforeEach(() => {
32
+ room = new MockArena({ gameId: 'test-game' });
33
+ });
34
+ describe('opponentJoin: (publicKey: string)', () => {
35
+ it('should be called when opponent joins', async () => {
36
+ let receivedPubkey = null;
37
+ room.on('opponentJoin', (pubkey) => {
38
+ receivedPubkey = pubkey;
39
+ });
40
+ await room.create();
41
+ room.simulateOpponentJoin('opponent-abc123');
42
+ expect(receivedPubkey).toBe('opponent-abc123');
43
+ });
44
+ });
45
+ describe('opponentState: (state: TGameState)', () => {
46
+ it('should be called with game state when opponent sends state', async () => {
47
+ let receivedState = null;
48
+ room.on('opponentState', (state) => {
49
+ receivedState = state;
50
+ });
51
+ await room.create();
52
+ room.simulateOpponentJoin('opponent');
53
+ room.simulateOpponentState({ score: 250 });
54
+ expect(receivedState).toEqual({ score: 250 });
55
+ });
56
+ });
57
+ describe('opponentDisconnect: ()', () => {
58
+ it('should be called when opponent disconnects', async () => {
59
+ let disconnectCalled = false;
60
+ room.on('opponentDisconnect', () => {
61
+ disconnectCalled = true;
62
+ });
63
+ await room.create();
64
+ room.simulateOpponentJoin('opponent');
65
+ room.simulateOpponentDisconnect();
66
+ expect(disconnectCalled).toBe(true);
67
+ });
68
+ });
69
+ describe('opponentGameOver: (reason: string, score?: number)', () => {
70
+ it('should be called with reason when opponent ends game', async () => {
71
+ let receivedReason = null;
72
+ room.on('opponentGameOver', (reason) => {
73
+ receivedReason = reason;
74
+ });
75
+ await room.create();
76
+ room.simulateOpponentJoin('opponent');
77
+ room.simulateOpponentGameOver('surrender');
78
+ expect(receivedReason).toBe('surrender');
79
+ });
80
+ it('should receive optional score parameter', async () => {
81
+ let receivedScore;
82
+ room.on('opponentGameOver', (_reason, score) => {
83
+ receivedScore = score;
84
+ });
85
+ await room.create();
86
+ room.simulateOpponentJoin('opponent');
87
+ room.simulateOpponentGameOver('win', 1000);
88
+ expect(receivedScore).toBe(1000);
89
+ });
90
+ });
91
+ describe('rematchRequested: ()', () => {
92
+ it('should be called when opponent requests rematch', async () => {
93
+ let rematchRequested = false;
94
+ room.on('rematchRequested', () => {
95
+ rematchRequested = true;
96
+ });
97
+ await room.create();
98
+ room.simulateOpponentJoin('opponent');
99
+ room.sendGameOver('win');
100
+ room.simulateRematchRequested();
101
+ expect(rematchRequested).toBe(true);
102
+ });
103
+ });
104
+ describe('rematchStart: (newSeed: number)', () => {
105
+ it('should be called with new seed when rematch starts', async () => {
106
+ let receivedSeed = null;
107
+ room.on('rematchStart', (seed) => {
108
+ receivedSeed = seed;
109
+ });
110
+ await room.create();
111
+ room.simulateOpponentJoin('opponent');
112
+ room.sendGameOver('win');
113
+ room.simulateRematchRequested();
114
+ room.acceptRematch();
115
+ expect(receivedSeed).not.toBeNull();
116
+ expect(typeof receivedSeed).toBe('number');
117
+ });
118
+ });
119
+ describe('Chaining (architecture.md)', () => {
120
+ /**
121
+ * Source: architecture.md - Arena API
122
+ * All callback methods return `this` for chaining
123
+ */
124
+ it('on() should return this for chaining', () => {
125
+ const result = room.on('opponentJoin', () => { });
126
+ expect(result).toBe(room);
127
+ });
128
+ it('multiple callbacks can be chained', () => {
129
+ const result = room
130
+ .on('opponentJoin', () => { })
131
+ .on('opponentState', () => { })
132
+ .on('opponentDisconnect', () => { })
133
+ .on('opponentGameOver', () => { })
134
+ .on('rematchRequested', () => { })
135
+ .on('rematchStart', () => { });
136
+ expect(result).toBe(room);
137
+ });
138
+ });
139
+ });
140
+ //# sourceMappingURL=callbacks.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"callbacks.test.js","sourceRoot":"","sources":["../../src/__tests__/callbacks.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * ArenaConfig Tests
3
+ *
4
+ * Source: README.md - ArenaConfig section
5
+ *
6
+ * ```typescript
7
+ * interface ArenaConfig {
8
+ * gameId: string; // Required: unique game identifier
9
+ * relays?: string[]; // Nostr relay URLs (default: public relays)
10
+ * roomExpiry?: number; // Room expiration in ms (default: 600000 = 10 min)
11
+ * heartbeatInterval?: number; // Heartbeat interval in ms (default: 3000)
12
+ * disconnectThreshold?: number; // Disconnect threshold in ms (default: 10000)
13
+ * stateThrottle?: number; // State update throttle in ms (default: 100)
14
+ * }
15
+ * ```
16
+ */
17
+ export {};
18
+ //# sourceMappingURL=config.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/config.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * ArenaConfig Tests
3
+ *
4
+ * Source: README.md - ArenaConfig section
5
+ *
6
+ * ```typescript
7
+ * interface ArenaConfig {
8
+ * gameId: string; // Required: unique game identifier
9
+ * relays?: string[]; // Nostr relay URLs (default: public relays)
10
+ * roomExpiry?: number; // Room expiration in ms (default: 600000 = 10 min)
11
+ * heartbeatInterval?: number; // Heartbeat interval in ms (default: 3000)
12
+ * disconnectThreshold?: number; // Disconnect threshold in ms (default: 10000)
13
+ * stateThrottle?: number; // State update throttle in ms (default: 100)
14
+ * }
15
+ * ```
16
+ */
17
+ import { describe, it, expect } from 'vitest';
18
+ import { DEFAULT_CONFIG } from '../types';
19
+ describe('ArenaConfig defaults (README.md)', () => {
20
+ // "relays?: string[] // Nostr relay URLs (default: public relays)"
21
+ it('relays should default to public relays', () => {
22
+ expect(DEFAULT_CONFIG.relays).toBeDefined();
23
+ expect(Array.isArray(DEFAULT_CONFIG.relays)).toBe(true);
24
+ expect(DEFAULT_CONFIG.relays.length).toBeGreaterThan(0);
25
+ });
26
+ // "roomExpiry?: number // Room expiration in ms (default: 600000 = 10 min)"
27
+ it('roomExpiry should default to 600000ms (10 min)', () => {
28
+ expect(DEFAULT_CONFIG.roomExpiry).toBe(600000);
29
+ });
30
+ // "heartbeatInterval?: number // Heartbeat interval in ms (default: 3000)"
31
+ it('heartbeatInterval should default to 3000ms', () => {
32
+ expect(DEFAULT_CONFIG.heartbeatInterval).toBe(3000);
33
+ });
34
+ // "disconnectThreshold?: number // Disconnect threshold in ms (default: 10000)"
35
+ it('disconnectThreshold should default to 10000ms', () => {
36
+ expect(DEFAULT_CONFIG.disconnectThreshold).toBe(10000);
37
+ });
38
+ // "stateThrottle?: number // State update throttle in ms (default: 100)"
39
+ it('stateThrottle should default to 100ms', () => {
40
+ expect(DEFAULT_CONFIG.stateThrottle).toBe(100);
41
+ });
42
+ });
43
+ //# sourceMappingURL=config.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.test.js","sourceRoot":"","sources":["../../src/__tests__/config.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE1C,QAAQ,CAAC,kCAAkC,EAAE,GAAG,EAAE;IAChD,mEAAmE;IACnE,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAChD,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;QAC5C,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxD,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,4EAA4E;IAC5E,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACxD,MAAM,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,2EAA2E;IAC3E,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,CAAC,cAAc,CAAC,iBAAiB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,gFAAgF;IAChF,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,CAAC,cAAc,CAAC,mBAAmB,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,yEAAyE;IACzE,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Error Handling Tests
3
+ *
4
+ * Source: architecture.md - Error Handling section
5
+ *
6
+ * Errors are reported via `onError` callback:
7
+ *
8
+ * Common errors:
9
+ * - `"Room not found"` - Room doesn't exist or expired
10
+ * - `"Room has expired"` - Room older than 10 minutes
11
+ * - `"NostrClient not connected"` - Called method before connect()
12
+ */
13
+ export {};
14
+ //# sourceMappingURL=error-handling.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-handling.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/error-handling.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG"}