@loova/game-sdk 0.1.0-alpha.2 → 0.1.0-alpha.4

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 CHANGED
@@ -17,12 +17,16 @@ The protocol source of truth is
17
17
  Protobuf-ES bindings are committed under `src/gen`; published packages do not
18
18
  need `buf` or `protoc` at runtime.
19
19
 
20
- ## Runtime client
20
+ ## Stable realtime core
21
21
 
22
22
  ```ts
23
- import { InputAction, createLoovaGameSDK } from "@loova/game-sdk";
23
+ import {
24
+ TypedProfileClient,
25
+ createJsonCodec,
26
+ createLoovaRealtimeClient,
27
+ } from "@loova/game-sdk";
24
28
 
25
- const sdk = createLoovaGameSDK({
29
+ const sdk = createLoovaRealtimeClient({
26
30
  apiUrl: "https://api.loova.example",
27
31
  });
28
32
 
@@ -37,58 +41,44 @@ await sdk.connect({
37
41
  roomId: session.roomId,
38
42
  });
39
43
 
40
- sdk.sendInput({
44
+ const shooter = new TypedProfileClient(sdk, {
45
+ schemaId: "loova/shooter/v1",
46
+ commands: createJsonCodec<ShooterCommand>(),
47
+ states: createJsonCodec<ShooterState>(),
48
+ events: createJsonCodec<ShooterEvent>(),
49
+ });
50
+
51
+ shooter.send("input", {
41
52
  moveX: 1,
42
53
  moveY: 0,
43
54
  aimX: 1,
44
55
  aimY: 0,
45
- actions: InputAction.Fire,
56
+ actions: 1,
46
57
  });
47
58
  ```
48
59
 
49
- Anonymous public games can join directly on the same SDK-owned WebSocket:
50
-
51
- ```ts
52
- const welcome = await sdk.connectAnonymous({
53
- websocketUrl: "wss://realtime.loova.ai/ws",
54
- gameId: "my-game",
55
- releaseId: "v2",
56
- name: "Player One",
57
- });
58
- ```
59
-
60
- The SDK sends `{"t":"join","name":"Player One"}`, consumes the JSON
61
- `welcome`, and then continues with Protocol v1 binary messages on that same
62
- connection. Game code must not create a parallel WebSocket.
63
-
64
60
  `createRoom()` and `joinRoom()` use the trusted platform HTTP API. Tickets must
65
61
  never be created inside an untrusted browser game.
66
62
 
67
63
  ## Events
68
64
 
69
65
  ```ts
70
- sdk.onPlayerJoin(({ player }) => console.log("join", player.playerId));
71
- sdk.onSnapshot(({ serverTick, players }) => render(players, serverTick));
66
+ sdk.onPlayerJoin(({ playerId }) => console.log("join", playerId));
72
67
  sdk.onPlayerLeave(({ playerId }) => console.log("left", playerId));
73
- sdk.onDamage(({ playerId, damage }) => showDamage(playerId, damage));
74
- sdk.onDeath(({ playerId, killerPlayerId }) => showDeath(playerId, killerPlayerId));
75
68
  sdk.onMatchEnd(({ winnerPlayerId }) => showWinner(winnerPlayerId));
69
+
70
+ shooter.onState(({ state, revision }) => render(state, revision));
71
+ shooter.onEvent(({ eventType, event }) => showEffect(eventType, event));
76
72
  ```
77
73
 
78
74
  Every subscription returns an unsubscribe function.
79
75
 
80
- ## Prediction and interpolation
76
+ ## Profile boundary
81
77
 
82
- - Sent inputs are retained in a bounded input buffer.
83
- - `FullSnapshot` acknowledges processed input sequences.
84
- - `ServerCorrection` resets the local player to authoritative state and replays
85
- unacknowledged inputs.
86
- - `getPredictedLocalPlayer()` returns the predicted local state.
87
- - `getInterpolatedPlayers()` returns remote state sampled behind realtime by the
88
- configured interpolation delay.
89
-
90
- Prediction is visual only. The SDK never sends predicted positions, health,
91
- hits, deaths, or match outcomes back to the server.
78
+ SDK Core transports opaque `Command`, `GameState`, and `ProfileEvent` payloads.
79
+ It does not know about movement, health, weapons, cards, or physics. A typed
80
+ profile client supplies codecs for one schema ID. Prediction and interpolation
81
+ belong in optional profile adapters and remain presentation-only.
92
82
 
93
83
  ## Heartbeat and reconnect
94
84
 
@@ -98,16 +88,9 @@ The first frame on the replacement socket is `Reconnect` with the last received
98
88
  server sequence. Without a reconnect token, the SDK closes instead of replaying
99
89
  the one-time Auth ticket.
100
90
 
101
- ## Two-player demo
102
-
103
- ```bash
104
- npm run build
105
- npm run demo:build
106
- python3 -m http.server 4173
107
- ```
108
-
109
- Open `http://localhost:4173/demo/`, enter two one-time tickets for the same
110
- room, and connect. Space fires for player A; Enter fires for player B.
91
+ `LoovaGameClient` remains exported temporarily for source migration but targets
92
+ the retired shooter-specific wire messages. New games must use
93
+ `LoovaRealtimeClient`.
111
94
 
112
95
  ## Protocol stability
113
96
 
@@ -117,3 +100,17 @@ room, and connect. Space fires for player A; Enter fires for player B.
117
100
  reused.
118
101
  - Breaking protocol changes require a new protobuf package and protocol major
119
102
  version.
103
+
104
+
105
+ ## publish
106
+ npm run check
107
+ npm test
108
+ npm run build
109
+ npm pack --dry-run
110
+
111
+ npm pkg get name version
112
+
113
+ npm publish \
114
+ --tag alpha \
115
+ --access public \
116
+ --registry=https://registry.npmjs.org/
@@ -0,0 +1,76 @@
1
+ import { type Unsubscribe } from "./events.js";
2
+ import type { ProfileCommand, ProfileEventMessage, ProfileStateUpdate, ProfileTransport } from "./profile-client.js";
3
+ import { type CreateRoomOptions, type CreateRoomResult, type JoinRoomOptions, type JoinRoomResult } from "./room-api.js";
4
+ import { type WebSocketFactory } from "./websocket.js";
5
+ export declare const PROTOCOL_VERSION = 1;
6
+ export interface RealtimeConnectOptions {
7
+ websocketUrl: string;
8
+ ticket: string;
9
+ playerId: string;
10
+ roomId: string;
11
+ reconnectToken?: string;
12
+ }
13
+ export interface RealtimeReconnectOptions {
14
+ enabled?: boolean;
15
+ initialDelayMs?: number;
16
+ maximumDelayMs?: number;
17
+ maximumAttempts?: number;
18
+ }
19
+ export interface LoovaRealtimeClientOptions {
20
+ apiUrl: string;
21
+ protocolVersion?: number;
22
+ heartbeatIntervalMs?: number;
23
+ reconnect?: RealtimeReconnectOptions;
24
+ websocketFactory?: WebSocketFactory;
25
+ fetcher?: typeof fetch;
26
+ now?: () => number;
27
+ }
28
+ export interface WelcomeEvent {
29
+ roomId: string;
30
+ playerId: string;
31
+ tickRate: number;
32
+ snapshotRate: number;
33
+ simulationProfile: string;
34
+ }
35
+ export interface PresenceJoinEvent {
36
+ playerId: string;
37
+ }
38
+ export interface PresenceLeaveEvent {
39
+ playerId: string;
40
+ reason: number;
41
+ }
42
+ export interface RealtimeMatchEndEvent {
43
+ winnerPlayerId: string;
44
+ reason: number;
45
+ players: ReadonlyArray<{
46
+ playerId: string;
47
+ score: number;
48
+ winner: boolean;
49
+ }>;
50
+ }
51
+ export interface RealtimePongEvent {
52
+ nonce: bigint;
53
+ roundTripMs: number | undefined;
54
+ serverTimeMs: bigint;
55
+ }
56
+ type ConnectionState = "idle" | "connecting" | "connected" | "reconnecting" | "closed";
57
+ export declare class LoovaRealtimeClient implements ProfileTransport {
58
+ #private;
59
+ constructor(options: LoovaRealtimeClientOptions);
60
+ get state(): ConnectionState;
61
+ createRoom(options: CreateRoomOptions): Promise<CreateRoomResult>;
62
+ joinRoom(roomId: string, options: JoinRoomOptions): Promise<JoinRoomResult>;
63
+ connect(options: RealtimeConnectOptions): Promise<void>;
64
+ disconnect(code?: number, reason?: string): void;
65
+ sendCommand(command: ProfileCommand): bigint;
66
+ onWelcome(listener: (event: WelcomeEvent) => void): Unsubscribe;
67
+ onState(listener: (state: ProfileStateUpdate) => void): Unsubscribe;
68
+ onProfileEvent(listener: (event: ProfileEventMessage) => void): Unsubscribe;
69
+ onPlayerJoin(listener: (event: PresenceJoinEvent) => void): Unsubscribe;
70
+ onPlayerLeave(listener: (event: PresenceLeaveEvent) => void): Unsubscribe;
71
+ onMatchEnd(listener: (event: RealtimeMatchEndEvent) => void): Unsubscribe;
72
+ onPong(listener: (event: RealtimePongEvent) => void): Unsubscribe;
73
+ ping(): bigint;
74
+ }
75
+ export {};
76
+ //# sourceMappingURL=core-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core-client.d.ts","sourceRoot":"","sources":["../src/core-client.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAa7D,OAAO,KAAK,EACV,cAAc,EACd,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EACjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,cAAc,EACpB,MAAM,eAAe,CAAC;AACvB,OAAO,EAEL,KAAK,gBAAgB,EAEtB,MAAM,gBAAgB,CAAC;AAExB,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAElC,MAAM,WAAW,sBAAsB;IACrC,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,SAAS,CAAC,EAAE,wBAAwB,CAAC;IACrC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IACvB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,qBAAqB;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,aAAa,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CAC9E;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,KAAK,eAAe,GAAG,MAAM,GAAG,YAAY,GAAG,WAAW,GAAG,cAAc,GAAG,QAAQ,CAAC;AAYvF,qBAAa,mBAAoB,YAAW,gBAAgB;;gBA2B9C,OAAO,EAAE,0BAA0B;IAS/C,IAAI,KAAK,IAAI,eAAe,CAE3B;IAEK,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAIjE,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC;IAIjF,OAAO,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC;IAWvD,UAAU,CAAC,IAAI,SAAO,EAAE,MAAM,SAAsB,GAAG,IAAI;IAQ3D,WAAW,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM;IAe5C,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,GAAG,WAAW;IAI/D,OAAO,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,GAAG,WAAW;IAInE,cAAc,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,GAAG,WAAW;IAI3E,YAAY,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,GAAG,WAAW;IAIvE,aAAa,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,GAAG,WAAW;IAIzE,UAAU,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,IAAI,GAAG,WAAW;IAIzE,MAAM,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,GAAG,WAAW;IAIjE,IAAI,IAAI,MAAM;CAmQf"}
@@ -0,0 +1,393 @@
1
+ import { create, fromBinary, toBinary } from "@bufbuild/protobuf";
2
+ import { EventChannel } from "./events.js";
3
+ import { AuthSchema, ClientMessageSchema, CommandSchema, MessageType, PingSchema, ReconnectSchema, ServerMessageSchema, } from "./gen/realtime/v1/realtime_pb.js";
4
+ import { RoomApi, } from "./room-api.js";
5
+ import { browserWebSocketFactory, } from "./websocket.js";
6
+ export const PROTOCOL_VERSION = 1;
7
+ const MAX_PROFILE_PAYLOAD_BYTES = 64 * 1024;
8
+ const DEFAULT_RECONNECT = {
9
+ enabled: true,
10
+ initialDelayMs: 250,
11
+ maximumDelayMs: 5_000,
12
+ maximumAttempts: 8,
13
+ };
14
+ // LoovaRealtimeClient is the stable SDK core. It owns transport and lifecycle;
15
+ // profile codecs own every game-specific command, state, and event.
16
+ export class LoovaRealtimeClient {
17
+ #protocolVersion;
18
+ #heartbeatIntervalMs;
19
+ #reconnect;
20
+ #websocketFactory;
21
+ #now;
22
+ #rooms;
23
+ #welcome = new EventChannel();
24
+ #stateUpdate = new EventChannel();
25
+ #profileEvent = new EventChannel();
26
+ #playerJoin = new EventChannel();
27
+ #playerLeave = new EventChannel();
28
+ #matchEnd = new EventChannel();
29
+ #pong = new EventChannel();
30
+ #socket;
31
+ #connection;
32
+ #state = "idle";
33
+ #sequence = 0n;
34
+ #lastServerSequence = 0n;
35
+ #serverTick = 0n;
36
+ #heartbeatTimer;
37
+ #reconnectTimer;
38
+ #reconnectAttempts = 0;
39
+ #manualClose = false;
40
+ #pendingPings = new Map();
41
+ constructor(options) {
42
+ this.#protocolVersion = options.protocolVersion ?? PROTOCOL_VERSION;
43
+ this.#heartbeatIntervalMs = options.heartbeatIntervalMs ?? 10_000;
44
+ this.#reconnect = { ...DEFAULT_RECONNECT, ...options.reconnect };
45
+ this.#websocketFactory = options.websocketFactory ?? browserWebSocketFactory;
46
+ this.#now = options.now ?? Date.now;
47
+ this.#rooms = new RoomApi(options.apiUrl, options.fetcher);
48
+ }
49
+ get state() {
50
+ return this.#state;
51
+ }
52
+ async createRoom(options) {
53
+ return await this.#rooms.createRoom(options);
54
+ }
55
+ async joinRoom(roomId, options) {
56
+ return await this.#rooms.joinRoom(roomId, options);
57
+ }
58
+ connect(options) {
59
+ if (this.#state === "connecting" || this.#state === "connected") {
60
+ return Promise.reject(new Error("SDK is already connected"));
61
+ }
62
+ this.#connection = { ...options };
63
+ this.#manualClose = false;
64
+ this.#sequence = 0n;
65
+ this.#lastServerSequence = 0n;
66
+ return this.#open(false);
67
+ }
68
+ disconnect(code = 1000, reason = "client disconnect") {
69
+ this.#manualClose = true;
70
+ this.#state = "closed";
71
+ this.#clearTimers();
72
+ this.#socket?.close(code, reason);
73
+ this.#socket = undefined;
74
+ }
75
+ sendCommand(command) {
76
+ validateProfileCommand(command);
77
+ const sequence = this.#nextSequence();
78
+ this.#send(MessageType.COMMAND, sequence, {
79
+ case: "command",
80
+ value: create(CommandSchema, {
81
+ schemaId: command.schemaId,
82
+ commandType: command.commandType,
83
+ payload: command.payload,
84
+ clientTick: this.#serverTick,
85
+ }),
86
+ });
87
+ return sequence;
88
+ }
89
+ onWelcome(listener) {
90
+ return this.#welcome.subscribe(listener);
91
+ }
92
+ onState(listener) {
93
+ return this.#stateUpdate.subscribe(listener);
94
+ }
95
+ onProfileEvent(listener) {
96
+ return this.#profileEvent.subscribe(listener);
97
+ }
98
+ onPlayerJoin(listener) {
99
+ return this.#playerJoin.subscribe(listener);
100
+ }
101
+ onPlayerLeave(listener) {
102
+ return this.#playerLeave.subscribe(listener);
103
+ }
104
+ onMatchEnd(listener) {
105
+ return this.#matchEnd.subscribe(listener);
106
+ }
107
+ onPong(listener) {
108
+ return this.#pong.subscribe(listener);
109
+ }
110
+ ping() {
111
+ if (this.#state !== "connected") {
112
+ throw new Error("SDK is not connected");
113
+ }
114
+ return this.#sendPing();
115
+ }
116
+ #open(isReconnect) {
117
+ const connection = this.#connection;
118
+ if (!connection) {
119
+ return Promise.reject(new Error("connection options are missing"));
120
+ }
121
+ this.#state = isReconnect ? "reconnecting" : "connecting";
122
+ const socket = this.#websocketFactory(websocketURL(connection.websocketUrl, connection.roomId));
123
+ socket.binaryType = "arraybuffer";
124
+ this.#socket = socket;
125
+ return new Promise((resolve, reject) => {
126
+ let settled = false;
127
+ socket.onopen = () => {
128
+ if (isReconnect && connection.reconnectToken) {
129
+ this.#sendReconnect(connection);
130
+ }
131
+ else {
132
+ this.#sendAuth(connection);
133
+ }
134
+ };
135
+ socket.onmessage = (event) => {
136
+ try {
137
+ if (typeof event.data === "string") {
138
+ throw new Error("unexpected text server message");
139
+ }
140
+ const message = fromBinary(ServerMessageSchema, new Uint8Array(event.data));
141
+ this.#handleServerMessage(message);
142
+ if (!settled && message.payload.case === "welcome") {
143
+ settled = true;
144
+ this.#state = "connected";
145
+ this.#reconnectAttempts = 0;
146
+ this.#startHeartbeat();
147
+ resolve();
148
+ }
149
+ }
150
+ catch (error) {
151
+ if (!settled) {
152
+ settled = true;
153
+ reject(error);
154
+ }
155
+ socket.close(1002, "invalid server message");
156
+ }
157
+ };
158
+ socket.onerror = () => {
159
+ if (!settled) {
160
+ settled = true;
161
+ reject(new Error("WebSocket connection failed"));
162
+ }
163
+ };
164
+ socket.onclose = () => {
165
+ this.#stopHeartbeat();
166
+ if (!settled) {
167
+ settled = true;
168
+ reject(new Error("WebSocket closed before Welcome"));
169
+ }
170
+ if (!this.#manualClose) {
171
+ this.#scheduleReconnect();
172
+ }
173
+ };
174
+ });
175
+ }
176
+ #sendAuth(connection) {
177
+ const sequence = this.#nextSequence();
178
+ this.#send(MessageType.AUTH, sequence, {
179
+ case: "auth",
180
+ value: create(AuthSchema, {
181
+ ticket: connection.ticket,
182
+ playerId: connection.playerId,
183
+ roomId: connection.roomId,
184
+ protocolVersion: this.#protocolVersion,
185
+ }),
186
+ });
187
+ }
188
+ #sendReconnect(connection) {
189
+ const sequence = this.#nextSequence();
190
+ this.#send(MessageType.RECONNECT, sequence, {
191
+ case: "reconnect",
192
+ value: create(ReconnectSchema, {
193
+ reconnectToken: connection.reconnectToken ?? "",
194
+ playerId: connection.playerId,
195
+ roomId: connection.roomId,
196
+ lastReceivedSequence: this.#lastServerSequence,
197
+ }),
198
+ });
199
+ }
200
+ #send(messageType, sequence, payload) {
201
+ const socket = this.#socket;
202
+ if (!socket || socket.readyState !== socket.OPEN) {
203
+ throw new Error("WebSocket is not open");
204
+ }
205
+ socket.send(toBinary(ClientMessageSchema, create(ClientMessageSchema, {
206
+ protocolVersion: this.#protocolVersion,
207
+ messageType,
208
+ sequence,
209
+ serverTick: this.#serverTick,
210
+ payload,
211
+ })));
212
+ }
213
+ #handleServerMessage(message) {
214
+ if (message.protocolVersion !== this.#protocolVersion) {
215
+ throw new Error(`unsupported protocol version ${message.protocolVersion}`);
216
+ }
217
+ if (message.messageType !== expectedMessageType(message.payload.case)) {
218
+ throw new Error("server message type does not match payload");
219
+ }
220
+ if (message.sequence === 0n || message.sequence <= this.#lastServerSequence) {
221
+ throw new Error("server sequence is missing or out of order");
222
+ }
223
+ this.#lastServerSequence = message.sequence;
224
+ this.#serverTick = message.serverTick;
225
+ switch (message.payload.case) {
226
+ case "welcome":
227
+ this.#welcome.emit({
228
+ roomId: message.payload.value.roomId,
229
+ playerId: message.payload.value.playerId,
230
+ tickRate: message.payload.value.tickRate,
231
+ snapshotRate: message.payload.value.snapshotRate,
232
+ simulationProfile: message.payload.value.simulationProfile,
233
+ });
234
+ break;
235
+ case "gameState":
236
+ this.#stateUpdate.emit({
237
+ schemaId: message.payload.value.schemaId,
238
+ revision: message.payload.value.revision,
239
+ baseRevision: message.payload.value.baseRevision,
240
+ full: message.payload.value.full,
241
+ payload: message.payload.value.payload.slice(),
242
+ });
243
+ break;
244
+ case "profileEvent":
245
+ this.#profileEvent.emit({
246
+ schemaId: message.payload.value.schemaId,
247
+ eventType: message.payload.value.eventType,
248
+ eventId: message.payload.value.eventId,
249
+ payload: message.payload.value.payload.slice(),
250
+ });
251
+ break;
252
+ case "playerJoined":
253
+ this.#playerJoin.emit({ playerId: message.payload.value.playerId });
254
+ break;
255
+ case "playerLeft":
256
+ this.#playerLeave.emit({
257
+ playerId: message.payload.value.playerId,
258
+ reason: message.payload.value.reason,
259
+ });
260
+ break;
261
+ case "serverCorrection":
262
+ if (message.payload.value.authoritativeState) {
263
+ const state = message.payload.value.authoritativeState;
264
+ this.#stateUpdate.emit({
265
+ schemaId: state.schemaId,
266
+ revision: state.revision,
267
+ baseRevision: state.baseRevision,
268
+ full: state.full,
269
+ payload: state.payload.slice(),
270
+ });
271
+ }
272
+ break;
273
+ case "matchEnded":
274
+ this.#emitMatchEnd(message.payload.value);
275
+ break;
276
+ case "pong":
277
+ this.#emitPong(message.payload.value.nonce, message.payload.value.serverTimeMs);
278
+ break;
279
+ default:
280
+ throw new Error(`unsupported server payload ${String(message.payload.case)}`);
281
+ }
282
+ }
283
+ #emitMatchEnd(match) {
284
+ this.#matchEnd.emit({
285
+ winnerPlayerId: match.winnerPlayerId,
286
+ reason: match.reason,
287
+ players: match.players.map((player) => ({
288
+ playerId: player.playerId,
289
+ score: player.score,
290
+ winner: player.winner,
291
+ })),
292
+ });
293
+ }
294
+ #startHeartbeat() {
295
+ this.#stopHeartbeat();
296
+ this.#heartbeatTimer = setInterval(() => {
297
+ if (this.#state !== "connected")
298
+ return;
299
+ try {
300
+ this.#sendPing();
301
+ }
302
+ catch {
303
+ this.#socket?.close(1011, "heartbeat failed");
304
+ }
305
+ }, this.#heartbeatIntervalMs);
306
+ }
307
+ #scheduleReconnect() {
308
+ const connection = this.#connection;
309
+ if (!connection?.reconnectToken || !this.#reconnect.enabled ||
310
+ this.#reconnectAttempts >= this.#reconnect.maximumAttempts) {
311
+ this.#state = "closed";
312
+ return;
313
+ }
314
+ const delay = Math.min(this.#reconnect.initialDelayMs * 2 ** this.#reconnectAttempts, this.#reconnect.maximumDelayMs);
315
+ this.#reconnectAttempts += 1;
316
+ this.#state = "reconnecting";
317
+ this.#reconnectTimer = setTimeout(() => void this.#open(true).catch(() => { }), delay);
318
+ }
319
+ #nextSequence() {
320
+ this.#sequence += 1n;
321
+ return this.#sequence;
322
+ }
323
+ #sendPing() {
324
+ const sequence = this.#nextSequence();
325
+ const sentAt = this.#now();
326
+ this.#pendingPings.set(sequence, sentAt);
327
+ this.#send(MessageType.PING, sequence, {
328
+ case: "ping",
329
+ value: create(PingSchema, {
330
+ nonce: sequence,
331
+ clientTimeMs: BigInt(Math.trunc(sentAt)),
332
+ }),
333
+ });
334
+ return sequence;
335
+ }
336
+ #emitPong(nonce, serverTimeMs) {
337
+ const sentAt = this.#pendingPings.get(nonce);
338
+ this.#pendingPings.delete(nonce);
339
+ this.#pong.emit({
340
+ nonce,
341
+ roundTripMs: sentAt === undefined ? undefined : Math.max(0, this.#now() - sentAt),
342
+ serverTimeMs,
343
+ });
344
+ }
345
+ #stopHeartbeat() {
346
+ if (this.#heartbeatTimer !== undefined)
347
+ clearInterval(this.#heartbeatTimer);
348
+ this.#heartbeatTimer = undefined;
349
+ }
350
+ #clearTimers() {
351
+ this.#stopHeartbeat();
352
+ if (this.#reconnectTimer !== undefined)
353
+ clearTimeout(this.#reconnectTimer);
354
+ this.#reconnectTimer = undefined;
355
+ this.#pendingPings.clear();
356
+ }
357
+ }
358
+ function validateProfileCommand(command) {
359
+ if (!command.schemaId || !command.commandType) {
360
+ throw new TypeError("profile command schemaId and commandType are required");
361
+ }
362
+ if (!(command.payload instanceof Uint8Array) || command.payload.byteLength > MAX_PROFILE_PAYLOAD_BYTES) {
363
+ throw new RangeError(`profile command payload must be at most ${MAX_PROFILE_PAYLOAD_BYTES} bytes`);
364
+ }
365
+ }
366
+ function websocketURL(baseURL, roomId) {
367
+ const url = new URL(baseURL);
368
+ url.searchParams.set("roomId", roomId);
369
+ return url.toString();
370
+ }
371
+ function expectedMessageType(payloadCase) {
372
+ switch (payloadCase) {
373
+ case "welcome":
374
+ return MessageType.WELCOME;
375
+ case "gameState":
376
+ return MessageType.GAME_STATE;
377
+ case "profileEvent":
378
+ return MessageType.PROFILE_EVENT;
379
+ case "playerJoined":
380
+ return MessageType.PLAYER_JOINED;
381
+ case "playerLeft":
382
+ return MessageType.PLAYER_LEFT;
383
+ case "serverCorrection":
384
+ return MessageType.SERVER_CORRECTION;
385
+ case "matchEnded":
386
+ return MessageType.MATCH_ENDED;
387
+ case "pong":
388
+ return MessageType.PONG;
389
+ default:
390
+ return MessageType.UNSPECIFIED;
391
+ }
392
+ }
393
+ //# sourceMappingURL=core-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core-client.js","sourceRoot":"","sources":["../src/core-client.ts"],"names":[],"mappings":"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"}