@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 +42 -45
- package/dist/core-client.d.ts +76 -0
- package/dist/core-client.d.ts.map +1 -0
- package/dist/core-client.js +393 -0
- package/dist/core-client.js.map +1 -0
- package/dist/gen/realtime/v1/realtime_pb.d.ts +156 -9
- package/dist/gen/realtime/v1/realtime_pb.d.ts.map +1 -1
- package/dist/gen/realtime/v1/realtime_pb.js +51 -17
- package/dist/gen/realtime/v1/realtime_pb.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/profile-client.d.ts +49 -0
- package/dist/profile-client.d.ts.map +1 -0
- package/dist/profile-client.js +54 -0
- package/dist/profile-client.js.map +1 -0
- package/dist/runtime-client.d.ts.map +1 -1
- package/dist/runtime-client.js +9 -19
- package/dist/runtime-client.js.map +1 -1
- package/package.json +1 -1
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
|
-
##
|
|
20
|
+
## Stable realtime core
|
|
21
21
|
|
|
22
22
|
```ts
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
TypedProfileClient,
|
|
25
|
+
createJsonCodec,
|
|
26
|
+
createLoovaRealtimeClient,
|
|
27
|
+
} from "@loova/game-sdk";
|
|
24
28
|
|
|
25
|
-
const sdk =
|
|
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
|
|
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:
|
|
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(({
|
|
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
|
-
##
|
|
76
|
+
## Profile boundary
|
|
81
77
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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":"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"}
|
|
@@ -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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"}
|