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.
- package/LICENSE +21 -0
- package/README.md +188 -0
- package/dist/__tests__/callbacks.test.d.ts +28 -0
- package/dist/__tests__/callbacks.test.d.ts.map +1 -0
- package/dist/__tests__/callbacks.test.js +140 -0
- package/dist/__tests__/callbacks.test.js.map +1 -0
- package/dist/__tests__/config.test.d.ts +18 -0
- package/dist/__tests__/config.test.d.ts.map +1 -0
- package/dist/__tests__/config.test.js +43 -0
- package/dist/__tests__/config.test.js.map +1 -0
- package/dist/__tests__/error-handling.test.d.ts +14 -0
- package/dist/__tests__/error-handling.test.d.ts.map +1 -0
- package/dist/__tests__/error-handling.test.js +199 -0
- package/dist/__tests__/error-handling.test.js.map +1 -0
- package/dist/__tests__/protocol.test.d.ts +7 -0
- package/dist/__tests__/protocol.test.d.ts.map +1 -0
- package/dist/__tests__/protocol.test.js +257 -0
- package/dist/__tests__/protocol.test.js.map +1 -0
- package/dist/__tests__/state-flow.test.d.ts +37 -0
- package/dist/__tests__/state-flow.test.d.ts.map +1 -0
- package/dist/__tests__/state-flow.test.js +160 -0
- package/dist/__tests__/state-flow.test.js.map +1 -0
- package/dist/__tests__/timing.test.d.ts +20 -0
- package/dist/__tests__/timing.test.d.ts.map +1 -0
- package/dist/__tests__/timing.test.js +73 -0
- package/dist/__tests__/timing.test.js.map +1 -0
- package/dist/core/Arena.d.ts +164 -0
- package/dist/core/Arena.d.ts.map +1 -0
- package/dist/core/Arena.js +634 -0
- package/dist/core/Arena.js.map +1 -0
- package/dist/core/NostrClient.d.ts +108 -0
- package/dist/core/NostrClient.d.ts.map +1 -0
- package/dist/core/NostrClient.js +225 -0
- package/dist/core/NostrClient.js.map +1 -0
- package/dist/core/index.d.ts +8 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +7 -0
- package/dist/core/index.js.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/proxy.d.ts +36 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.js +98 -0
- package/dist/proxy.js.map +1 -0
- package/dist/react/index.d.ts +7 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +6 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/useArena.d.ts +74 -0
- package/dist/react/useArena.d.ts.map +1 -0
- package/dist/react/useArena.js +224 -0
- package/dist/react/useArena.js.map +1 -0
- package/dist/retry.d.ts +54 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +82 -0
- package/dist/retry.js.map +1 -0
- package/dist/testing/MockArena.d.ts +84 -0
- package/dist/testing/MockArena.d.ts.map +1 -0
- package/dist/testing/MockArena.js +156 -0
- package/dist/testing/MockArena.js.map +1 -0
- package/dist/testing/index.d.ts +6 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +6 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/types.d.ts +183 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +55 -0
- package/dist/types.js.map +1 -0
- package/package.json +84 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* nostr-battle-room - MockArena
|
|
3
|
+
* Testing utilities for battle room
|
|
4
|
+
*/
|
|
5
|
+
import { INITIAL_ROOM_STATE, generateSeed, generateRoomId } from '../types';
|
|
6
|
+
/**
|
|
7
|
+
* MockArena - For testing without real Nostr connections
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* const mock = new MockArena<MyGameState>({ gameId: 'test' });
|
|
12
|
+
*
|
|
13
|
+
* // Simulate opponent joining
|
|
14
|
+
* mock.simulateOpponentJoin('pubkey123');
|
|
15
|
+
*
|
|
16
|
+
* // Simulate opponent state update
|
|
17
|
+
* mock.simulateOpponentState({ score: 100 });
|
|
18
|
+
*
|
|
19
|
+
* // Simulate opponent disconnect
|
|
20
|
+
* mock.simulateOpponentDisconnect();
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export class MockArena {
|
|
24
|
+
constructor(_config) {
|
|
25
|
+
this._roomState = { ...INITIAL_ROOM_STATE };
|
|
26
|
+
this._opponent = null;
|
|
27
|
+
this.callbacks = {};
|
|
28
|
+
// Config is accepted but not used in mock
|
|
29
|
+
}
|
|
30
|
+
get roomState() {
|
|
31
|
+
return this._roomState;
|
|
32
|
+
}
|
|
33
|
+
get opponent() {
|
|
34
|
+
return this._opponent;
|
|
35
|
+
}
|
|
36
|
+
get isConnected() {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
get publicKey() {
|
|
40
|
+
return 'mock-public-key';
|
|
41
|
+
}
|
|
42
|
+
on(event, callback) {
|
|
43
|
+
const callbackKey = `on${event.charAt(0).toUpperCase()}${event.slice(1)}`;
|
|
44
|
+
this.callbacks[callbackKey] = callback;
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
47
|
+
connect() {
|
|
48
|
+
// No-op
|
|
49
|
+
}
|
|
50
|
+
disconnect() {
|
|
51
|
+
// No-op
|
|
52
|
+
}
|
|
53
|
+
async create() {
|
|
54
|
+
const roomId = generateRoomId();
|
|
55
|
+
this._roomState = {
|
|
56
|
+
roomId,
|
|
57
|
+
status: 'waiting',
|
|
58
|
+
isHost: true,
|
|
59
|
+
seed: generateSeed(),
|
|
60
|
+
createdAt: Date.now(),
|
|
61
|
+
};
|
|
62
|
+
return `https://example.com/battle/${roomId}`;
|
|
63
|
+
}
|
|
64
|
+
async join(roomId) {
|
|
65
|
+
this._roomState = {
|
|
66
|
+
roomId,
|
|
67
|
+
status: 'ready',
|
|
68
|
+
isHost: false,
|
|
69
|
+
seed: generateSeed(),
|
|
70
|
+
createdAt: Date.now(),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
leave() {
|
|
74
|
+
this._roomState = { ...INITIAL_ROOM_STATE };
|
|
75
|
+
this._opponent = null;
|
|
76
|
+
}
|
|
77
|
+
sendState(_state) {
|
|
78
|
+
// No-op in mock
|
|
79
|
+
}
|
|
80
|
+
sendGameOver(_reason, _finalScore) {
|
|
81
|
+
this._roomState = { ...this._roomState, status: 'finished' };
|
|
82
|
+
}
|
|
83
|
+
requestRematch() {
|
|
84
|
+
this._roomState = { ...this._roomState, rematchRequested: true };
|
|
85
|
+
}
|
|
86
|
+
acceptRematch() {
|
|
87
|
+
const newSeed = generateSeed();
|
|
88
|
+
this._roomState = {
|
|
89
|
+
...this._roomState,
|
|
90
|
+
seed: newSeed,
|
|
91
|
+
status: 'ready',
|
|
92
|
+
rematchRequested: false,
|
|
93
|
+
};
|
|
94
|
+
if (this._opponent) {
|
|
95
|
+
this._opponent = { ...this._opponent, gameState: null, rematchRequested: false };
|
|
96
|
+
}
|
|
97
|
+
this.callbacks.onRematchStart?.(newSeed);
|
|
98
|
+
}
|
|
99
|
+
// ==========================================================================
|
|
100
|
+
// Simulation methods (for testing)
|
|
101
|
+
// ==========================================================================
|
|
102
|
+
/**
|
|
103
|
+
* Simulate an opponent joining the room
|
|
104
|
+
*/
|
|
105
|
+
simulateOpponentJoin(publicKey = 'opponent-pubkey') {
|
|
106
|
+
this._opponent = {
|
|
107
|
+
publicKey,
|
|
108
|
+
gameState: null,
|
|
109
|
+
isConnected: true,
|
|
110
|
+
lastHeartbeat: Date.now(),
|
|
111
|
+
rematchRequested: false,
|
|
112
|
+
};
|
|
113
|
+
this._roomState = { ...this._roomState, status: 'ready' };
|
|
114
|
+
this.callbacks.onOpponentJoin?.(publicKey);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Simulate opponent state update
|
|
118
|
+
*/
|
|
119
|
+
simulateOpponentState(state) {
|
|
120
|
+
if (this._opponent) {
|
|
121
|
+
this._opponent = {
|
|
122
|
+
...this._opponent,
|
|
123
|
+
gameState: state,
|
|
124
|
+
lastHeartbeat: Date.now(),
|
|
125
|
+
isConnected: true,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
this.callbacks.onOpponentState?.(state);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Simulate opponent disconnection
|
|
132
|
+
*/
|
|
133
|
+
simulateOpponentDisconnect() {
|
|
134
|
+
if (this._opponent) {
|
|
135
|
+
this._opponent = { ...this._opponent, isConnected: false };
|
|
136
|
+
}
|
|
137
|
+
this.callbacks.onOpponentDisconnect?.();
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Simulate opponent game over
|
|
141
|
+
*/
|
|
142
|
+
simulateOpponentGameOver(reason, finalScore) {
|
|
143
|
+
this._roomState = { ...this._roomState, status: 'finished' };
|
|
144
|
+
this.callbacks.onOpponentGameOver?.(reason, finalScore);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Simulate opponent rematch request
|
|
148
|
+
*/
|
|
149
|
+
simulateRematchRequested() {
|
|
150
|
+
if (this._opponent) {
|
|
151
|
+
this._opponent = { ...this._opponent, rematchRequested: true };
|
|
152
|
+
}
|
|
153
|
+
this.callbacks.onRematchRequested?.();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=MockArena.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MockArena.js","sourceRoot":"","sources":["../../src/testing/MockArena.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* nostr-battle-room - Types
|
|
3
|
+
* Generic types for Nostr-based multiplayer game rooms
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Configuration for Arena
|
|
7
|
+
*/
|
|
8
|
+
export interface ArenaConfig {
|
|
9
|
+
/** Unique identifier for the game (e.g., 'sasso', 'tetris') */
|
|
10
|
+
gameId: string;
|
|
11
|
+
/** Nostr relay URLs */
|
|
12
|
+
relays?: string[];
|
|
13
|
+
/** Room expiration time in ms (default: 600000 = 10 minutes) */
|
|
14
|
+
roomExpiry?: number;
|
|
15
|
+
/** Heartbeat interval in ms (default: 3000 = 3 seconds) */
|
|
16
|
+
heartbeatInterval?: number;
|
|
17
|
+
/** Disconnect threshold in ms (default: 10000 = 10 seconds) */
|
|
18
|
+
disconnectThreshold?: number;
|
|
19
|
+
/** State update throttle in ms (default: 100) */
|
|
20
|
+
stateThrottle?: number;
|
|
21
|
+
/** Join timeout in ms (default: 30000 = 30 seconds) */
|
|
22
|
+
joinTimeout?: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Default configuration values
|
|
26
|
+
*/
|
|
27
|
+
export declare const DEFAULT_CONFIG: Required<Omit<ArenaConfig, 'gameId'>>;
|
|
28
|
+
/**
|
|
29
|
+
* Room status
|
|
30
|
+
*/
|
|
31
|
+
export type RoomStatus = 'idle' | 'creating' | 'waiting' | 'joining' | 'ready' | 'playing' | 'finished';
|
|
32
|
+
/**
|
|
33
|
+
* Room state (game-agnostic)
|
|
34
|
+
*/
|
|
35
|
+
export interface RoomState {
|
|
36
|
+
roomId: string | null;
|
|
37
|
+
status: RoomStatus;
|
|
38
|
+
isHost: boolean;
|
|
39
|
+
seed: number;
|
|
40
|
+
createdAt?: number;
|
|
41
|
+
rematchRequested?: boolean;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Initial room state
|
|
45
|
+
*/
|
|
46
|
+
export declare const INITIAL_ROOM_STATE: RoomState;
|
|
47
|
+
/**
|
|
48
|
+
* Base opponent information (game-agnostic)
|
|
49
|
+
*/
|
|
50
|
+
export interface OpponentBase {
|
|
51
|
+
publicKey: string;
|
|
52
|
+
isConnected: boolean;
|
|
53
|
+
lastHeartbeat?: number;
|
|
54
|
+
rematchRequested?: boolean;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Opponent state with generic game state
|
|
58
|
+
*/
|
|
59
|
+
export interface OpponentState<TGameState = Record<string, unknown>> extends OpponentBase {
|
|
60
|
+
gameState: TGameState;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Nostr event kinds used by the library
|
|
64
|
+
*/
|
|
65
|
+
export declare const NOSTR_KINDS: {
|
|
66
|
+
/** Replaceable event for room metadata */
|
|
67
|
+
readonly ROOM: 30078;
|
|
68
|
+
/** Ephemeral event for game state (not stored by relays) */
|
|
69
|
+
readonly EPHEMERAL: 25000;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Nostr event structure
|
|
73
|
+
*/
|
|
74
|
+
export interface NostrEvent {
|
|
75
|
+
id?: string;
|
|
76
|
+
pubkey?: string;
|
|
77
|
+
created_at?: number;
|
|
78
|
+
kind: number;
|
|
79
|
+
tags: string[][];
|
|
80
|
+
content: string;
|
|
81
|
+
sig?: string;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Room creation event
|
|
85
|
+
*/
|
|
86
|
+
export interface RoomEventContent {
|
|
87
|
+
type: 'room';
|
|
88
|
+
status: 'waiting' | 'playing' | 'finished';
|
|
89
|
+
seed: number;
|
|
90
|
+
hostPubkey: string;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Join event (player joining a room)
|
|
94
|
+
*/
|
|
95
|
+
export interface JoinEventContent {
|
|
96
|
+
type: 'join';
|
|
97
|
+
playerPubkey: string;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Generic game state event
|
|
101
|
+
*/
|
|
102
|
+
export interface StateEventContent<TGameState = Record<string, unknown>> {
|
|
103
|
+
type: 'state';
|
|
104
|
+
gameState: TGameState;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Game over event
|
|
108
|
+
*/
|
|
109
|
+
export interface GameOverEventContent {
|
|
110
|
+
type: 'gameover';
|
|
111
|
+
reason: string;
|
|
112
|
+
finalScore?: number;
|
|
113
|
+
winner?: string;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Rematch event
|
|
117
|
+
*/
|
|
118
|
+
export interface RematchEventContent {
|
|
119
|
+
type: 'rematch';
|
|
120
|
+
action: 'request' | 'accept';
|
|
121
|
+
newSeed?: number;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Heartbeat event
|
|
125
|
+
*/
|
|
126
|
+
export interface HeartbeatEventContent {
|
|
127
|
+
type: 'heartbeat';
|
|
128
|
+
timestamp: number;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* All possible event content types
|
|
132
|
+
*/
|
|
133
|
+
export type BattleEventContent<TGameState = Record<string, unknown>> = RoomEventContent | JoinEventContent | StateEventContent<TGameState> | GameOverEventContent | RematchEventContent | HeartbeatEventContent;
|
|
134
|
+
/**
|
|
135
|
+
* Event names for Arena
|
|
136
|
+
*/
|
|
137
|
+
export type ArenaEventName = 'opponentJoin' | 'opponentState' | 'opponentDisconnect' | 'opponentGameOver' | 'rematchRequested' | 'rematchStart' | 'error';
|
|
138
|
+
/**
|
|
139
|
+
* Event callbacks for Arena
|
|
140
|
+
*/
|
|
141
|
+
export interface ArenaCallbacks<TGameState = Record<string, unknown>> {
|
|
142
|
+
/** Called when opponent joins the room */
|
|
143
|
+
onOpponentJoin?: (publicKey: string) => void;
|
|
144
|
+
/** Called when opponent's game state is updated */
|
|
145
|
+
onOpponentState?: (state: TGameState) => void;
|
|
146
|
+
/** Called when opponent disconnects */
|
|
147
|
+
onOpponentDisconnect?: () => void;
|
|
148
|
+
/** Called when opponent sends game over */
|
|
149
|
+
onOpponentGameOver?: (reason: string, finalScore?: number) => void;
|
|
150
|
+
/** Called when opponent requests rematch */
|
|
151
|
+
onRematchRequested?: () => void;
|
|
152
|
+
/** Called when rematch is accepted (by either party) */
|
|
153
|
+
onRematchStart?: (newSeed: number) => void;
|
|
154
|
+
/** Called on any error */
|
|
155
|
+
onError?: (error: Error) => void;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Data stored in localStorage for reconnection
|
|
159
|
+
*/
|
|
160
|
+
export interface StoredRoomData {
|
|
161
|
+
roomId: string;
|
|
162
|
+
isHost: boolean;
|
|
163
|
+
seed: number;
|
|
164
|
+
createdAt: number;
|
|
165
|
+
opponentPubkey?: string;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Function to unsubscribe from events
|
|
169
|
+
*/
|
|
170
|
+
export type Unsubscribe = () => void;
|
|
171
|
+
/**
|
|
172
|
+
* Generate room tag from game ID and room ID
|
|
173
|
+
*/
|
|
174
|
+
export declare function createRoomTag(gameId: string, roomId: string): string;
|
|
175
|
+
/**
|
|
176
|
+
* Generate a random seed
|
|
177
|
+
*/
|
|
178
|
+
export declare function generateSeed(): number;
|
|
179
|
+
/**
|
|
180
|
+
* Generate a unique room ID
|
|
181
|
+
*/
|
|
182
|
+
export declare function generateRoomId(): string;
|
|
183
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,+DAA+D;IAC/D,MAAM,EAAE,MAAM,CAAC;IAEf,uBAAuB;IACvB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAElB,gEAAgE;IAChE,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,2DAA2D;IAC3D,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B,+DAA+D;IAC/D,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAE7B,iDAAiD;IACjD,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,uDAAuD;IACvD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,CAOhE,CAAC;AAMF;;GAEG;AACH,MAAM,MAAM,UAAU,GAClB,MAAM,GACN,UAAU,GACV,SAAS,GACT,SAAS,GACT,OAAO,GACP,SAAS,GACT,UAAU,CAAC;AAEf;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,UAAU,CAAC;IACnB,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;GAEG;AACH,eAAO,MAAM,kBAAkB,EAAE,SAKhC,CAAC;AAMF;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,aAAa,CAAC,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAE,SAAQ,YAAY;IACvF,SAAS,EAAE,UAAU,CAAC;CACvB;AAMD;;GAEG;AACH,eAAO,MAAM,WAAW;IACtB,0CAA0C;;IAE1C,4DAA4D;;CAEpD,CAAC;AAKX;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAMD;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,UAAU,CAAC;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB,CAAC,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACrE,IAAI,EAAE,OAAO,CAAC;IACd,SAAS,EAAE,UAAU,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,UAAU,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,WAAW,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,MAAM,kBAAkB,CAAC,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAC/D,gBAAgB,GAChB,gBAAgB,GAChB,iBAAiB,CAAC,UAAU,CAAC,GAC7B,oBAAoB,GACpB,mBAAmB,GACnB,qBAAqB,CAAC;AAM1B;;GAEG;AACH,MAAM,MAAM,cAAc,GACtB,cAAc,GACd,eAAe,GACf,oBAAoB,GACpB,kBAAkB,GAClB,kBAAkB,GAClB,cAAc,GACd,OAAO,CAAC;AAEZ;;GAEG;AACH,MAAM,WAAW,cAAc,CAAC,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAClE,0CAA0C;IAC1C,cAAc,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAE7C,mDAAmD;IACnD,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IAE9C,uCAAuC;IACvC,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;IAElC,2CAA2C;IAC3C,kBAAkB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAEnE,4CAA4C;IAC5C,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IAEhC,wDAAwD;IACxD,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAE3C,0BAA0B;IAC1B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAMD;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAMD;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC;AAErC;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEpE;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAEvC"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* nostr-battle-room - Types
|
|
3
|
+
* Generic types for Nostr-based multiplayer game rooms
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Default configuration values
|
|
7
|
+
*/
|
|
8
|
+
export const DEFAULT_CONFIG = {
|
|
9
|
+
relays: ['wss://relay.damus.io', 'wss://nos.lol', 'wss://relay.nostr.band'],
|
|
10
|
+
roomExpiry: 600000,
|
|
11
|
+
heartbeatInterval: 3000,
|
|
12
|
+
disconnectThreshold: 10000,
|
|
13
|
+
stateThrottle: 100,
|
|
14
|
+
joinTimeout: 30000,
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Initial room state
|
|
18
|
+
*/
|
|
19
|
+
export const INITIAL_ROOM_STATE = {
|
|
20
|
+
roomId: null,
|
|
21
|
+
status: 'idle',
|
|
22
|
+
isHost: false,
|
|
23
|
+
seed: 0,
|
|
24
|
+
};
|
|
25
|
+
// =============================================================================
|
|
26
|
+
// Nostr Types
|
|
27
|
+
// =============================================================================
|
|
28
|
+
/**
|
|
29
|
+
* Nostr event kinds used by the library
|
|
30
|
+
*/
|
|
31
|
+
export const NOSTR_KINDS = {
|
|
32
|
+
/** Replaceable event for room metadata */
|
|
33
|
+
ROOM: 30078,
|
|
34
|
+
/** Ephemeral event for game state (not stored by relays) */
|
|
35
|
+
EPHEMERAL: 25000,
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Generate room tag from game ID and room ID
|
|
39
|
+
*/
|
|
40
|
+
export function createRoomTag(gameId, roomId) {
|
|
41
|
+
return `${gameId}-room-${roomId}`;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Generate a random seed
|
|
45
|
+
*/
|
|
46
|
+
export function generateSeed() {
|
|
47
|
+
return Math.floor(Math.random() * 0x7fffffff);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Generate a unique room ID
|
|
51
|
+
*/
|
|
52
|
+
export function generateRoomId() {
|
|
53
|
+
return `${Date.now().toString(36)}-${Math.random().toString(36).substr(2, 9)}`;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAgCH;;GAEG;AACH,MAAM,CAAC,MAAM,cAAc,GAA0C;IACnE,MAAM,EAAE,CAAC,sBAAsB,EAAE,eAAe,EAAE,wBAAwB,CAAC;IAC3E,UAAU,EAAE,MAAM;IAClB,iBAAiB,EAAE,IAAI;IACvB,mBAAmB,EAAE,KAAK;IAC1B,aAAa,EAAE,GAAG;IAClB,WAAW,EAAE,KAAK;CACnB,CAAC;AA8BF;;GAEG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAc;IAC3C,MAAM,EAAE,IAAI;IACZ,MAAM,EAAE,MAAM;IACd,MAAM,EAAE,KAAK;IACb,IAAI,EAAE,CAAC;CACR,CAAC;AAuBF,gFAAgF;AAChF,cAAc;AACd,gFAAgF;AAEhF;;GAEG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,0CAA0C;IAC1C,IAAI,EAAE,KAAK;IACX,4DAA4D;IAC5D,SAAS,EAAE,KAAK;CACR,CAAC;AAwJX;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,MAAc,EAAE,MAAc;IAC1D,OAAO,GAAG,MAAM,SAAS,MAAM,EAAE,CAAC;AACpC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY;IAC1B,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,UAAU,CAAC,CAAC;AAChD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc;IAC5B,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;AACjF,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nostr-arena",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Nostr-based real-time multiplayer game arena. No server required.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
},
|
|
14
|
+
"./react": {
|
|
15
|
+
"import": "./dist/react/index.js",
|
|
16
|
+
"types": "./dist/react/index.d.ts"
|
|
17
|
+
},
|
|
18
|
+
"./testing": {
|
|
19
|
+
"import": "./dist/testing/index.js",
|
|
20
|
+
"types": "./dist/testing/index.d.ts"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsc",
|
|
28
|
+
"dev": "tsc --watch",
|
|
29
|
+
"clean": "rm -rf dist",
|
|
30
|
+
"prepare": "npm run build",
|
|
31
|
+
"test": "vitest run",
|
|
32
|
+
"test:watch": "vitest"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"nostr",
|
|
36
|
+
"multiplayer",
|
|
37
|
+
"game",
|
|
38
|
+
"battle",
|
|
39
|
+
"matchmaking",
|
|
40
|
+
"p2p",
|
|
41
|
+
"realtime",
|
|
42
|
+
"websocket",
|
|
43
|
+
"decentralized"
|
|
44
|
+
],
|
|
45
|
+
"author": {
|
|
46
|
+
"name": "kako-jun",
|
|
47
|
+
"url": "https://github.com/kako-jun"
|
|
48
|
+
},
|
|
49
|
+
"license": "MIT",
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "git+https://github.com/kako-jun/nostr-arena.git"
|
|
53
|
+
},
|
|
54
|
+
"homepage": "https://github.com/kako-jun/nostr-arena#readme",
|
|
55
|
+
"bugs": {
|
|
56
|
+
"url": "https://github.com/kako-jun/nostr-arena/issues"
|
|
57
|
+
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"nostr-tools": "^2.0.0",
|
|
60
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
61
|
+
"ws": "^8.0.0",
|
|
62
|
+
"https-proxy-agent": "^7.0.0"
|
|
63
|
+
},
|
|
64
|
+
"peerDependenciesMeta": {
|
|
65
|
+
"react": {
|
|
66
|
+
"optional": true
|
|
67
|
+
},
|
|
68
|
+
"ws": {
|
|
69
|
+
"optional": true
|
|
70
|
+
},
|
|
71
|
+
"https-proxy-agent": {
|
|
72
|
+
"optional": true
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"devDependencies": {
|
|
76
|
+
"typescript": "~5.6.2",
|
|
77
|
+
"@types/react": "^19.0.0",
|
|
78
|
+
"@types/ws": "^8.5.0",
|
|
79
|
+
"ws": "^8.18.0",
|
|
80
|
+
"https-proxy-agent": "^7.0.0",
|
|
81
|
+
"vitest": "^2.0.0",
|
|
82
|
+
"nostr-tools": "^2.10.0"
|
|
83
|
+
}
|
|
84
|
+
}
|