p2p-lockstep-kit-session 0.1.2 → 0.1.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/dist/session/index.d.ts +362 -0
- package/dist/session/index.js +1198 -0
- package/dist/session/index.js.map +1 -0
- package/package.json +5 -3
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
import { NetworkClient } from 'p2p-lockstep-kit-network';
|
|
2
|
+
|
|
3
|
+
type SessionMessageType = 'READY' | 'START' | 'MOVE' | 'UNDO' | 'RESTART' | 'APPROVE' | 'REJECT' | 'REJOIN' | 'SYNC_REQUEST' | 'SYNC_STATE' | 'OFFLINE' | 'ONLINE' | 'GAME_OVER';
|
|
4
|
+
type SessionMessage = {
|
|
5
|
+
type: SessionMessageType;
|
|
6
|
+
from?: string;
|
|
7
|
+
seq?: number;
|
|
8
|
+
sid?: string;
|
|
9
|
+
turn?: number;
|
|
10
|
+
stateHash?: string;
|
|
11
|
+
payload?: any;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type CommandOrigin = 'local' | 'remote';
|
|
15
|
+
type BusMessageType = SessionMessageType | 'OFFLINE' | 'ONLINE' | 'GAME_OVER';
|
|
16
|
+
type BusMessage = Omit<SessionMessage, 'type'> & {
|
|
17
|
+
type: BusMessageType;
|
|
18
|
+
};
|
|
19
|
+
type CommandListener = (message: BusMessage) => Promise<void> | void;
|
|
20
|
+
declare class CommandBus {
|
|
21
|
+
private handlers;
|
|
22
|
+
private processingQueue;
|
|
23
|
+
emit(type: BusMessageType, payload?: unknown, from?: CommandOrigin): void;
|
|
24
|
+
register(type: BusMessageType, handler: CommandListener): void;
|
|
25
|
+
dispatch(message: BusMessage): void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Network client wrapper that bridges NetworkClient with CommandBus
|
|
30
|
+
* Handles message encoding/decoding and connection state monitoring
|
|
31
|
+
*/
|
|
32
|
+
declare class NetClient {
|
|
33
|
+
private readonly client;
|
|
34
|
+
private readonly bus;
|
|
35
|
+
private localPeerId;
|
|
36
|
+
private remotePeerId;
|
|
37
|
+
private isConnected;
|
|
38
|
+
private connectionChangeListener;
|
|
39
|
+
private mediaStateListener;
|
|
40
|
+
constructor(client: NetworkClient, bus: CommandBus, peerId: string | null);
|
|
41
|
+
/**
|
|
42
|
+
* Send a message to the remote peer
|
|
43
|
+
* Drops message if not connected and logs warning
|
|
44
|
+
*/
|
|
45
|
+
send(message: SessionMessage): void;
|
|
46
|
+
/**
|
|
47
|
+
* Update local and remote peer IDs
|
|
48
|
+
*/
|
|
49
|
+
setPeerIds(ids: {
|
|
50
|
+
local?: string | null;
|
|
51
|
+
remote?: string | null;
|
|
52
|
+
}): void;
|
|
53
|
+
/**
|
|
54
|
+
* Get current peer IDs
|
|
55
|
+
*/
|
|
56
|
+
getPeerIds(): {
|
|
57
|
+
local: string | null;
|
|
58
|
+
remote: string | null;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Check if currently connected to peer
|
|
62
|
+
*/
|
|
63
|
+
getIsConnected(): boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Monitor connection state changes
|
|
66
|
+
* @param handler Called when connection state changes (true=connected, false=disconnected)
|
|
67
|
+
*/
|
|
68
|
+
onConnectionChange(handler: (isConnected: boolean) => void): void;
|
|
69
|
+
/**
|
|
70
|
+
* Monitor remote media stream state changes
|
|
71
|
+
* @param handler Called when remote media becomes available or unavailable
|
|
72
|
+
*/
|
|
73
|
+
onMediaStateChange(handler: (active: boolean) => void): void;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
type SessionState = 'idle' | 'ready' | 'could_start' | 'turn' | 'remote_turn' | 'approving' | 'waiting_approval' | 'syncing' | 'offline';
|
|
77
|
+
type SessionEvent = 'REMOTE_READY' | 'READY' | 'START' | 'REMOTE_START' | 'MOVE' | 'REMOTE_MOVE' | 'UNDO' | 'REMOTE_UNDO' | 'RESTART' | 'REMOTE_RESTART' | 'APPROVE' | 'REJECT' | 'GAME_OVER' | 'REJOIN' | 'SYNC' | 'SYNC_COMPLETE' | 'OFFLINE' | 'ONLINE';
|
|
78
|
+
|
|
79
|
+
interface GameStateSnapshot {
|
|
80
|
+
localState: SessionState;
|
|
81
|
+
remoteState: SessionState;
|
|
82
|
+
turn: number;
|
|
83
|
+
history: TurnEntry[];
|
|
84
|
+
lastStart: PlayerLabel | null;
|
|
85
|
+
pendingAction: 'undo' | 'restart' | null;
|
|
86
|
+
connected: boolean;
|
|
87
|
+
}
|
|
88
|
+
interface GameEvent {
|
|
89
|
+
type: 'READY' | 'START' | 'MOVE' | 'GAME_OVER' | 'UNDO' | 'RESTART' | 'OFFLINE' | 'ONLINE' | 'SYNC' | 'ERROR';
|
|
90
|
+
payload?: any;
|
|
91
|
+
from?: 'local' | 'remote';
|
|
92
|
+
timestamp?: number;
|
|
93
|
+
}
|
|
94
|
+
interface IGameObserver {
|
|
95
|
+
onStateChange(snapshot: GameStateSnapshot): void;
|
|
96
|
+
onGameEvent(event: GameEvent): void;
|
|
97
|
+
onConnectionChange?(connected: boolean): void;
|
|
98
|
+
onError?(error: {
|
|
99
|
+
message: string;
|
|
100
|
+
context?: any;
|
|
101
|
+
}): void;
|
|
102
|
+
}
|
|
103
|
+
interface IStateObserver {
|
|
104
|
+
onStateChanged?(): void;
|
|
105
|
+
onHistoryChanged?(): void;
|
|
106
|
+
onGameReset?(): void;
|
|
107
|
+
}
|
|
108
|
+
interface IGamePlugin {
|
|
109
|
+
validateMove(move: unknown, gameState: GameState): ValidationResult;
|
|
110
|
+
checkWin(gameState: GameState, history: TurnEntry[]): PlayerLabel | null;
|
|
111
|
+
initialize?(): void;
|
|
112
|
+
cleanup?(): void;
|
|
113
|
+
}
|
|
114
|
+
interface ValidationResult {
|
|
115
|
+
valid: boolean;
|
|
116
|
+
reason?: string;
|
|
117
|
+
}
|
|
118
|
+
interface GameState {
|
|
119
|
+
history: TurnEntry[];
|
|
120
|
+
localState: 'turn' | 'remote_turn' | string;
|
|
121
|
+
remoteState: 'turn' | 'remote_turn' | string;
|
|
122
|
+
turn: number;
|
|
123
|
+
lastStart: PlayerLabel | null;
|
|
124
|
+
}
|
|
125
|
+
declare class DefaultGamePlugin implements IGamePlugin {
|
|
126
|
+
validateMove(): ValidationResult;
|
|
127
|
+
checkWin(): PlayerLabel | null;
|
|
128
|
+
}
|
|
129
|
+
declare class StateObserverManager {
|
|
130
|
+
private observers;
|
|
131
|
+
subscribe(observer: IStateObserver): void;
|
|
132
|
+
unsubscribe(observer: IStateObserver): void;
|
|
133
|
+
notifyStateChanged(): void;
|
|
134
|
+
notifyHistoryChanged(): void;
|
|
135
|
+
notifyGameReset(): void;
|
|
136
|
+
}
|
|
137
|
+
declare class GameStateObserver {
|
|
138
|
+
private observers;
|
|
139
|
+
private currentSnapshot;
|
|
140
|
+
subscribe(observer: IGameObserver): () => void;
|
|
141
|
+
unsubscribe(observer: IGameObserver): void;
|
|
142
|
+
notifyStateChange(snapshot: GameStateSnapshot): void;
|
|
143
|
+
notifyGameEvent(event: GameEvent): void;
|
|
144
|
+
notifyConnectionChange(connected: boolean): void;
|
|
145
|
+
notifyError(error: {
|
|
146
|
+
message: string;
|
|
147
|
+
context?: any;
|
|
148
|
+
}): void;
|
|
149
|
+
getSnapshot(): GameStateSnapshot | null;
|
|
150
|
+
getObserverCount(): number;
|
|
151
|
+
}
|
|
152
|
+
declare function buildGameStateSnapshot(state: State, connected?: boolean): GameStateSnapshot;
|
|
153
|
+
declare class UINotificationAdapter implements IStateObserver {
|
|
154
|
+
private stateRef;
|
|
155
|
+
private uiObserver;
|
|
156
|
+
private lastNotificationTime;
|
|
157
|
+
private notificationThrottleMs;
|
|
158
|
+
constructor(stateRef: State, uiObserver: GameStateObserver);
|
|
159
|
+
onStateChanged(): void;
|
|
160
|
+
onHistoryChanged(): void;
|
|
161
|
+
onGameReset(): void;
|
|
162
|
+
emitEvent(event: Omit<GameEvent, 'timestamp'>): void;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
type TurnEntry = {
|
|
166
|
+
turn: number;
|
|
167
|
+
player: 'local' | 'remote';
|
|
168
|
+
move?: any;
|
|
169
|
+
};
|
|
170
|
+
type PlayerLabel = 'local' | 'remote';
|
|
171
|
+
declare class State {
|
|
172
|
+
private local;
|
|
173
|
+
private remote;
|
|
174
|
+
private readonly localId;
|
|
175
|
+
private remoteId;
|
|
176
|
+
private readonly history;
|
|
177
|
+
private pendingAction;
|
|
178
|
+
private pendingUndoCount;
|
|
179
|
+
private resumeTurn;
|
|
180
|
+
private lastStart;
|
|
181
|
+
private gamePlugin;
|
|
182
|
+
private stateObserverManager;
|
|
183
|
+
constructor(id: string | null, remoteId: string | null);
|
|
184
|
+
/**
|
|
185
|
+
* Register an internal observer (like plugin pattern)
|
|
186
|
+
* Use this to connect State mutations to UI updates
|
|
187
|
+
*/
|
|
188
|
+
subscribeStateObserver(observer: IStateObserver): void;
|
|
189
|
+
getId(): string | null;
|
|
190
|
+
getremoteId(): string | null;
|
|
191
|
+
setremoteId(id: string): void;
|
|
192
|
+
getState(player: PlayerLabel): SessionState;
|
|
193
|
+
getTurnCount(): number;
|
|
194
|
+
getHistory(): TurnEntry[];
|
|
195
|
+
replaceHistory(entries: TurnEntry[]): void;
|
|
196
|
+
clearHistory(): void;
|
|
197
|
+
pushHistory(entry: TurnEntry): void;
|
|
198
|
+
popHistory(): TurnEntry | null;
|
|
199
|
+
canAction(player: PlayerLabel, action: SessionEvent): boolean;
|
|
200
|
+
/**
|
|
201
|
+
* Dispatch an action and automatically determine target state if unique
|
|
202
|
+
* Only use explicit 'to' parameter for ambiguous transitions (APPROVE, REJECT, etc.)
|
|
203
|
+
*
|
|
204
|
+
* For most actions (READY, MOVE, START, etc.), there's only one valid transition,
|
|
205
|
+
* so we automatically find and apply it.
|
|
206
|
+
*/
|
|
207
|
+
dispatch(player: PlayerLabel, action: SessionEvent, to?: SessionState): void;
|
|
208
|
+
setPendingAction(action: 'undo' | 'restart' | null): void;
|
|
209
|
+
getPendingAction(): "undo" | "restart" | null;
|
|
210
|
+
setPendingUndoCount(count: 1 | 2 | null): void;
|
|
211
|
+
getPendingUndoCount(): 1 | 2 | null;
|
|
212
|
+
setLastStart(player: PlayerLabel | null): void;
|
|
213
|
+
getLastStart(): PlayerLabel | null;
|
|
214
|
+
setResumeTurn(player: PlayerLabel | null): void;
|
|
215
|
+
getResumeTurn(): PlayerLabel | null;
|
|
216
|
+
private getPlayerFsm;
|
|
217
|
+
/**
|
|
218
|
+
* Save game state snapshot for undo/restart operations
|
|
219
|
+
*/
|
|
220
|
+
private gameSnapshot;
|
|
221
|
+
saveGameSnapshot(snapshot: unknown): void;
|
|
222
|
+
getGameSnapshot(): unknown;
|
|
223
|
+
clearGameSnapshot(): void;
|
|
224
|
+
/**
|
|
225
|
+
* Check if there's a pending action (undo/restart)
|
|
226
|
+
*/
|
|
227
|
+
hasPendingAction(): boolean;
|
|
228
|
+
/**
|
|
229
|
+
* Clear all pending states (called after approval/rejection)
|
|
230
|
+
*/
|
|
231
|
+
clearPendingStates(): void;
|
|
232
|
+
/**
|
|
233
|
+
* Initialize undo request with undo count and current turn holder
|
|
234
|
+
*/
|
|
235
|
+
initializeUndoRequest(undoCount: 1 | 2, resumeTurn: PlayerLabel): void;
|
|
236
|
+
/**
|
|
237
|
+
* Initialize restart request with resume turn
|
|
238
|
+
*/
|
|
239
|
+
initializeRestartRequest(resumeTurn: PlayerLabel): void;
|
|
240
|
+
/**
|
|
241
|
+
* Check if pending action is undo
|
|
242
|
+
*/
|
|
243
|
+
isPendingUndo(): boolean;
|
|
244
|
+
/**
|
|
245
|
+
* Check if pending action is restart
|
|
246
|
+
*/
|
|
247
|
+
isPendingRestart(): boolean;
|
|
248
|
+
/**
|
|
249
|
+
* Apply undo by popping history N times
|
|
250
|
+
*/
|
|
251
|
+
applyUndo(count?: 1 | 2): void;
|
|
252
|
+
/**
|
|
253
|
+
* Reset game state to initial (for restart)
|
|
254
|
+
*/
|
|
255
|
+
resetGame(): void;
|
|
256
|
+
/**
|
|
257
|
+
* Save start player for rejoin flow
|
|
258
|
+
*/
|
|
259
|
+
recordStartPlayer(player: PlayerLabel): void;
|
|
260
|
+
/**
|
|
261
|
+
* Get move to undo from history
|
|
262
|
+
*/
|
|
263
|
+
getLastMove(): TurnEntry | null;
|
|
264
|
+
/**
|
|
265
|
+
* Dispatch APPROVE action with automatic target state resolution
|
|
266
|
+
* Multiple valid transitions exist - use state context to determine target
|
|
267
|
+
*/
|
|
268
|
+
dispatchApprove(): void;
|
|
269
|
+
/**
|
|
270
|
+
* Dispatch REJECT action with automatic target state resolution
|
|
271
|
+
* Multiple valid transitions exist - use resumeTurn to determine who continues
|
|
272
|
+
*/
|
|
273
|
+
dispatchReject(): void;
|
|
274
|
+
/**
|
|
275
|
+
* Dispatch START action with automatic target state resolution
|
|
276
|
+
* Determines who plays first based on starter parameter
|
|
277
|
+
*/
|
|
278
|
+
dispatchStart(firstPlayer: PlayerLabel): void;
|
|
279
|
+
/**
|
|
280
|
+
* Dispatch SYNC_COMPLETE with automatic target state resolution
|
|
281
|
+
* Based on who should have the turn after sync
|
|
282
|
+
*/
|
|
283
|
+
dispatchSyncComplete(nextPlayer: PlayerLabel): void;
|
|
284
|
+
/**
|
|
285
|
+
* Set the game plugin for rule validation and win checking
|
|
286
|
+
* @param plugin Implementation of IGamePlugin
|
|
287
|
+
*/
|
|
288
|
+
setGamePlugin(plugin: IGamePlugin): void;
|
|
289
|
+
/**
|
|
290
|
+
* Get current game plugin
|
|
291
|
+
*/
|
|
292
|
+
getGamePlugin(): IGamePlugin;
|
|
293
|
+
/**
|
|
294
|
+
* Validate a move using the game plugin
|
|
295
|
+
* Called by move handler to check if move is legal
|
|
296
|
+
* @param move The move data to validate
|
|
297
|
+
* @returns Validation result with reason if invalid
|
|
298
|
+
*/
|
|
299
|
+
validateMove(move: unknown): ValidationResult;
|
|
300
|
+
/**
|
|
301
|
+
* Check if game has ended (someone won)
|
|
302
|
+
* Called by move handler after move is applied
|
|
303
|
+
* @returns Winner (local/remote) or null if game continues
|
|
304
|
+
*/
|
|
305
|
+
checkWin(): PlayerLabel | null;
|
|
306
|
+
/**
|
|
307
|
+
* Cleanup when game ends (for plugin to reset internal state)
|
|
308
|
+
*/
|
|
309
|
+
cleanupGame(): void;
|
|
310
|
+
/**
|
|
311
|
+
* Build game state for plugin
|
|
312
|
+
* @private
|
|
313
|
+
*/
|
|
314
|
+
private buildGameState;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
interface ISessionActions {
|
|
318
|
+
ready(): void;
|
|
319
|
+
start(): void;
|
|
320
|
+
move(data: unknown): void;
|
|
321
|
+
undo(): void;
|
|
322
|
+
restart(): void;
|
|
323
|
+
approve(): void;
|
|
324
|
+
reject(): void;
|
|
325
|
+
rejoin(sid: string): void;
|
|
326
|
+
}
|
|
327
|
+
declare class LocalActionsAPI implements ISessionActions {
|
|
328
|
+
private bus;
|
|
329
|
+
constructor(bus: CommandBus);
|
|
330
|
+
ready(): void;
|
|
331
|
+
start(): void;
|
|
332
|
+
move(data: unknown): void;
|
|
333
|
+
undo(): void;
|
|
334
|
+
restart(): void;
|
|
335
|
+
approve(): void;
|
|
336
|
+
reject(): void;
|
|
337
|
+
rejoin(sid: string): void;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Create a new game session with state management and networking
|
|
342
|
+
* @param sid Session ID for rejoining (optional)
|
|
343
|
+
* @param networkClient Custom network client (optional, creates default if not provided)
|
|
344
|
+
* @returns Session manager with bus, state, observer, net, and send method
|
|
345
|
+
*
|
|
346
|
+
* @example
|
|
347
|
+
* const session = createSession();
|
|
348
|
+
* // UI automatically updates when state changes - no manual observer calls needed!
|
|
349
|
+
* session.observer.subscribe(myUIObserver);
|
|
350
|
+
* session.bus.emit('READY', undefined, 'local');
|
|
351
|
+
* await session.net.connect(remotePeerId);
|
|
352
|
+
*/
|
|
353
|
+
declare const createSession: (networkClient: NetworkClient, sid?: string) => {
|
|
354
|
+
bus: CommandBus;
|
|
355
|
+
state: State;
|
|
356
|
+
observer: GameStateObserver;
|
|
357
|
+
net: NetClient;
|
|
358
|
+
actions: LocalActionsAPI;
|
|
359
|
+
send: (message: SessionMessage) => void;
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
export { DefaultGamePlugin, type GameEvent, type GameState, GameStateObserver, type GameStateSnapshot, type IGameObserver, type IGamePlugin, type ISessionActions, type IStateObserver, StateObserverManager, UINotificationAdapter, type ValidationResult, buildGameStateSnapshot, createSession };
|