@1upmonster/versus 0.1.0

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 1upmonster
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.
@@ -0,0 +1,65 @@
1
+ import type { Match, MatchFoundPayload, VersusConfig } from "@1upmonster/types";
2
+ import { Room } from "./room.js";
3
+ export interface VersusClientOptions {
4
+ baseUrl: string;
5
+ /** Player session JWT from auth login */
6
+ token: string;
7
+ /** Game API key (1up_...) issued by the game developer — required for player-facing
8
+ * operations (matchmake, leaveQueue); omit for admin/CLI use. */
9
+ gameApiKey?: string;
10
+ }
11
+ /**
12
+ * Represents a pending match proposal. Players must accept or decline
13
+ * before the accept deadline. The Room is only created after all players accept.
14
+ */
15
+ export declare class MatchProposal {
16
+ readonly matchId: string;
17
+ readonly opponents: MatchFoundPayload["opponents"];
18
+ readonly acceptDeadline: number;
19
+ private readonly ws;
20
+ private readonly wsBase;
21
+ private readonly pingInterval;
22
+ constructor(payload: MatchFoundPayload, ws: WebSocket, pingInterval: ReturnType<typeof setInterval>, wsBase: string);
23
+ /**
24
+ * Accept the match proposal. Resolves with a connected Room once all
25
+ * players have accepted and the server has created the room.
26
+ * Rejects if the match is declined or times out before all accept.
27
+ */
28
+ accept(): Promise<Room>;
29
+ /**
30
+ * Decline the match proposal. The Queue will re-queue the other players
31
+ * and apply a re-queue penalty to this player.
32
+ */
33
+ decline(): void;
34
+ /**
35
+ * Abandon the proposal without sending accept or decline — cleans up the
36
+ * WebSocket and ping interval so they don't leak. Equivalent to navigating
37
+ * away without responding. The server will time out the proposal via the
38
+ * accept window alarm. (GAP-32)
39
+ */
40
+ abandon(): void;
41
+ }
42
+ export declare class VersusClient {
43
+ private readonly baseUrl;
44
+ private readonly wsBase;
45
+ private readonly headers;
46
+ private readonly gameApiKey;
47
+ constructor(options: VersusClientOptions);
48
+ /**
49
+ * Join the matchmaking queue and wait for a match proposal.
50
+ *
51
+ * Returns a MatchProposal when a match is found. The game should show
52
+ * accept/decline UI, then call proposal.accept() or proposal.decline().
53
+ * proposal.accept() resolves with a Room once all players confirm.
54
+ *
55
+ * @param gameId - The game to queue for
56
+ * @param onQueued - Optional callback fired once successfully in queue
57
+ */
58
+ matchmake(gameId: string, onQueued?: () => void): Promise<MatchProposal>;
59
+ leaveQueue(gameId: string): Promise<void>;
60
+ getConfig(gameId: string): Promise<VersusConfig>;
61
+ setConfig(gameId: string, config: Partial<VersusConfig>): Promise<VersusConfig>;
62
+ listMatches(gameId: string): Promise<Match[]>;
63
+ inspectMatch(matchId: string): Promise<Match>;
64
+ }
65
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,KAAK,EAEL,iBAAiB,EAEjB,YAAY,EACb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,yCAAyC;IACzC,KAAK,EAAE,MAAM,CAAC;IACd;sEACkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,qBAAa,aAAa;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,iBAAiB,CAAC,WAAW,CAAC,CAAC;IACnD,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAEhC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAY;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAiC;gBAG5D,OAAO,EAAE,iBAAiB,EAC1B,EAAE,EAAE,SAAS,EACb,YAAY,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,EAC5C,MAAM,EAAE,MAAM;IAUhB;;;;OAIG;IACH,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAgDvB;;;OAGG;IACH,OAAO,IAAI,IAAI;IAMf;;;;;OAKG;IACH,OAAO,IAAI,IAAI;CAIhB;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyB;IACjD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;gBAExB,OAAO,EAAE,mBAAmB;IAWxC;;;;;;;;;OASG;IACG,SAAS,CACb,MAAM,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE,MAAM,IAAI,GACpB,OAAO,CAAC,aAAa,CAAC;IA2EnB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQzC,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAQhD,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC;IAU/E,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAQ7C,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;CAOpD"}
package/dist/client.js ADDED
@@ -0,0 +1,216 @@
1
+ import { Room } from "./room.js";
2
+ /**
3
+ * Represents a pending match proposal. Players must accept or decline
4
+ * before the accept deadline. The Room is only created after all players accept.
5
+ */
6
+ export class MatchProposal {
7
+ matchId;
8
+ opponents;
9
+ acceptDeadline;
10
+ ws;
11
+ wsBase;
12
+ pingInterval;
13
+ constructor(payload, ws, pingInterval, wsBase) {
14
+ this.matchId = payload.matchId;
15
+ this.opponents = payload.opponents;
16
+ this.acceptDeadline = payload.acceptDeadline;
17
+ this.ws = ws;
18
+ this.pingInterval = pingInterval;
19
+ this.wsBase = wsBase;
20
+ }
21
+ /**
22
+ * Accept the match proposal. Resolves with a connected Room once all
23
+ * players have accepted and the server has created the room.
24
+ * Rejects if the match is declined or times out before all accept.
25
+ */
26
+ accept() {
27
+ return new Promise((resolve, reject) => {
28
+ const onMessage = (e) => {
29
+ const msg = JSON.parse(e.data);
30
+ if (msg.type === "match_ready") {
31
+ cleanup();
32
+ const payload = msg.payload;
33
+ // Use the server-provided roomUrl rather than reconstructing it locally (GAP-21)
34
+ const roomWsUrl = payload.roomUrl.replace(/^http/, "ws");
35
+ resolve(new Room(roomWsUrl, payload.roomToken));
36
+ }
37
+ if (msg.type === "match_declined") {
38
+ cleanup();
39
+ const payload = msg.payload;
40
+ reject(new Error(`Match cancelled: ${payload.reason} by ${payload.walletPubkey}`));
41
+ }
42
+ };
43
+ const onClose = (e) => {
44
+ cleanup();
45
+ if (!e.wasClean) {
46
+ reject(new Error("Queue WebSocket closed unexpectedly"));
47
+ }
48
+ };
49
+ const onError = () => {
50
+ cleanup();
51
+ reject(new Error("Queue WebSocket error during acceptance"));
52
+ };
53
+ const cleanup = () => {
54
+ clearInterval(this.pingInterval);
55
+ this.ws.removeEventListener("message", onMessage);
56
+ this.ws.removeEventListener("close", onClose);
57
+ this.ws.removeEventListener("error", onError);
58
+ this.ws.close();
59
+ };
60
+ this.ws.addEventListener("message", onMessage);
61
+ this.ws.addEventListener("close", onClose);
62
+ this.ws.addEventListener("error", onError);
63
+ this.ws.send(JSON.stringify({ type: "accept_match" }));
64
+ });
65
+ }
66
+ /**
67
+ * Decline the match proposal. The Queue will re-queue the other players
68
+ * and apply a re-queue penalty to this player.
69
+ */
70
+ decline() {
71
+ clearInterval(this.pingInterval);
72
+ this.ws.send(JSON.stringify({ type: "decline_match" }));
73
+ this.ws.close();
74
+ }
75
+ /**
76
+ * Abandon the proposal without sending accept or decline — cleans up the
77
+ * WebSocket and ping interval so they don't leak. Equivalent to navigating
78
+ * away without responding. The server will time out the proposal via the
79
+ * accept window alarm. (GAP-32)
80
+ */
81
+ abandon() {
82
+ clearInterval(this.pingInterval);
83
+ this.ws.close(1000, "Abandoned");
84
+ }
85
+ }
86
+ export class VersusClient {
87
+ baseUrl;
88
+ wsBase;
89
+ headers;
90
+ gameApiKey;
91
+ constructor(options) {
92
+ this.baseUrl = options.baseUrl;
93
+ this.wsBase = options.baseUrl.replace(/^http/, "ws");
94
+ this.gameApiKey = options.gameApiKey ?? "";
95
+ this.headers = {
96
+ "Content-Type": "application/json",
97
+ Authorization: `Bearer ${options.token}`,
98
+ ...(options.gameApiKey ? { "X-Api-Key": options.gameApiKey } : {}),
99
+ };
100
+ }
101
+ /**
102
+ * Join the matchmaking queue and wait for a match proposal.
103
+ *
104
+ * Returns a MatchProposal when a match is found. The game should show
105
+ * accept/decline UI, then call proposal.accept() or proposal.decline().
106
+ * proposal.accept() resolves with a Room once all players confirm.
107
+ *
108
+ * @param gameId - The game to queue for
109
+ * @param onQueued - Optional callback fired once successfully in queue
110
+ */
111
+ async matchmake(gameId, onQueued) {
112
+ // Step 1: Join the queue via HTTP
113
+ const res = await fetch(`${this.baseUrl}/versus/${gameId}/queue`, {
114
+ method: "POST",
115
+ headers: this.headers,
116
+ });
117
+ if (!res.ok)
118
+ throw new Error(await res.text());
119
+ onQueued?.();
120
+ // Step 2: Open WebSocket to receive queue events
121
+ return new Promise((resolve, reject) => {
122
+ const wsUrl = `${this.wsBase}/versus/${gameId}/queue/ws`;
123
+ const token = this.headers["Authorization"].replace("Bearer ", "");
124
+ const ws = new WebSocket(`${wsUrl}?token=${encodeURIComponent(token)}&apiKey=${encodeURIComponent(this.gameApiKey)}`);
125
+ // Reject if the connection never opens (GAP-31)
126
+ const connectTimeout = setTimeout(() => {
127
+ clearInterval(pingInterval);
128
+ ws.close();
129
+ reject(new Error("Queue WebSocket connection timed out"));
130
+ }, 10_000);
131
+ ws.addEventListener("open", () => clearTimeout(connectTimeout));
132
+ const pingInterval = setInterval(() => {
133
+ if (ws.readyState === WebSocket.OPEN) {
134
+ ws.send(JSON.stringify({ type: "ping" }));
135
+ }
136
+ }, 5000);
137
+ const onMessage = (e) => {
138
+ const msg = JSON.parse(e.data);
139
+ if (msg.type === "match_found") {
140
+ // Stop listening for queue-level events here;
141
+ // MatchProposal takes over the WS from this point
142
+ clearTimeout(connectTimeout);
143
+ ws.removeEventListener("message", onMessage);
144
+ ws.removeEventListener("error", onError);
145
+ ws.removeEventListener("close", onClose);
146
+ const payload = msg.payload;
147
+ resolve(new MatchProposal(payload, ws, pingInterval, this.wsBase));
148
+ }
149
+ if (msg.type === "queue_expired") {
150
+ clearTimeout(connectTimeout);
151
+ clearInterval(pingInterval);
152
+ ws.close();
153
+ reject(new Error("Queue expired: no match found within the time limit"));
154
+ }
155
+ };
156
+ const onError = () => {
157
+ clearTimeout(connectTimeout);
158
+ clearInterval(pingInterval);
159
+ reject(new Error("Queue WebSocket connection failed"));
160
+ };
161
+ const onClose = (e) => {
162
+ clearTimeout(connectTimeout);
163
+ clearInterval(pingInterval);
164
+ if (!e.wasClean) {
165
+ reject(new Error("Queue WebSocket closed unexpectedly"));
166
+ }
167
+ };
168
+ ws.addEventListener("message", onMessage);
169
+ ws.addEventListener("error", onError);
170
+ ws.addEventListener("close", onClose);
171
+ });
172
+ }
173
+ async leaveQueue(gameId) {
174
+ const res = await fetch(`${this.baseUrl}/versus/${gameId}/queue/leave`, {
175
+ method: "POST",
176
+ headers: this.headers,
177
+ });
178
+ if (!res.ok)
179
+ throw new Error(await res.text());
180
+ }
181
+ async getConfig(gameId) {
182
+ const res = await fetch(`${this.baseUrl}/versus/${gameId}/config`, {
183
+ headers: this.headers,
184
+ });
185
+ if (!res.ok)
186
+ throw new Error(await res.text());
187
+ return res.json();
188
+ }
189
+ async setConfig(gameId, config) {
190
+ const res = await fetch(`${this.baseUrl}/versus/${gameId}/config`, {
191
+ method: "PUT",
192
+ headers: this.headers,
193
+ body: JSON.stringify(config),
194
+ });
195
+ if (!res.ok)
196
+ throw new Error(await res.text());
197
+ return res.json();
198
+ }
199
+ async listMatches(gameId) {
200
+ const res = await fetch(`${this.baseUrl}/versus/${gameId}/matches`, {
201
+ headers: this.headers,
202
+ });
203
+ if (!res.ok)
204
+ throw new Error(await res.text());
205
+ return res.json();
206
+ }
207
+ async inspectMatch(matchId) {
208
+ const res = await fetch(`${this.baseUrl}/versus/matches/${matchId}`, {
209
+ headers: this.headers,
210
+ });
211
+ if (!res.ok)
212
+ throw new Error(await res.text());
213
+ return res.json();
214
+ }
215
+ }
216
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export { VersusClient } from "./client.js";
2
+ export { Room } from "./room.js";
3
+ export type { VersusClientOptions } from "./client.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,YAAY,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { VersusClient } from "./client.js";
2
+ export { Room } from "./room.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC"}
package/dist/room.d.ts ADDED
@@ -0,0 +1,34 @@
1
+ import type { GameMessagePayload } from "@1upmonster/types";
2
+ /** Events that the Room DO WebSocket actually emits */
3
+ type RoomEventMap = {
4
+ room_ready: void;
5
+ game_message: GameMessagePayload;
6
+ opponent_disconnected: void;
7
+ match_expired: void;
8
+ error: {
9
+ message: string;
10
+ };
11
+ close: void;
12
+ };
13
+ type RoomEventListener<K extends keyof RoomEventMap> = (payload: RoomEventMap[K]) => void;
14
+ export declare class Room {
15
+ private readonly token;
16
+ private ws;
17
+ private listeners;
18
+ private pingInterval;
19
+ constructor(wsUrl: string, token: string);
20
+ on<K extends keyof RoomEventMap>(event: K, listener: RoomEventListener<K>): this;
21
+ off<K extends keyof RoomEventMap>(event: K, listener: RoomEventListener<K>): this;
22
+ /** Signal ready once connected to room */
23
+ ready(): void;
24
+ /** Send a game-defined message to all players in the room */
25
+ broadcast(data: unknown): void;
26
+ leave(): void;
27
+ private send;
28
+ private handleMessage;
29
+ private emit;
30
+ private startPing;
31
+ private stopPing;
32
+ }
33
+ export {};
34
+ //# sourceMappingURL=room.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"room.d.ts","sourceRoot":"","sources":["../src/room.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,kBAAkB,EAGnB,MAAM,mBAAmB,CAAC;AAE3B,uDAAuD;AACvD,KAAK,YAAY,GAAG;IAClB,UAAU,EAAE,IAAI,CAAC;IACjB,YAAY,EAAE,kBAAkB,CAAC;IACjC,qBAAqB,EAAE,IAAI,CAAC;IAC5B,aAAa,EAAE,IAAI,CAAC;IACpB,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3B,KAAK,EAAE,IAAI,CAAC;CACb,CAAC;AAEF,KAAK,iBAAiB,CAAC,CAAC,SAAS,MAAM,YAAY,IAAI,CACrD,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,KACrB,IAAI,CAAC;AAEV,qBAAa,IAAI;IAKY,OAAO,CAAC,QAAQ,CAAC,KAAK;IAJjD,OAAO,CAAC,EAAE,CAAY;IACtB,OAAO,CAAC,SAAS,CAAoD;IACrE,OAAO,CAAC,YAAY,CAA+C;gBAEvD,KAAK,EAAE,MAAM,EAAmB,KAAK,EAAE,MAAM;IAUzD,EAAE,CAAC,CAAC,SAAS,MAAM,YAAY,EAC7B,KAAK,EAAE,CAAC,EACR,QAAQ,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAC7B,IAAI;IAMP,GAAG,CAAC,CAAC,SAAS,MAAM,YAAY,EAC9B,KAAK,EAAE,CAAC,EACR,QAAQ,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAC7B,IAAI;IAKP,0CAA0C;IAC1C,KAAK,IAAI,IAAI;IAIb,6DAA6D;IAC7D,SAAS,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;IAI9B,KAAK,IAAI,IAAI;IAKb,OAAO,CAAC,IAAI;IAMZ,OAAO,CAAC,aAAa;IAWrB,OAAO,CAAC,IAAI;IAIZ,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,QAAQ;CAMjB"}
package/dist/room.js ADDED
@@ -0,0 +1,69 @@
1
+ export class Room {
2
+ token;
3
+ ws;
4
+ listeners = new Map();
5
+ pingInterval = null;
6
+ constructor(wsUrl, token) {
7
+ this.token = token;
8
+ this.ws = new WebSocket(`${wsUrl}?token=${encodeURIComponent(token)}`);
9
+ this.ws.addEventListener("message", (e) => this.handleMessage(e));
10
+ this.ws.addEventListener("close", () => {
11
+ this.stopPing();
12
+ this.emit("close", undefined);
13
+ });
14
+ this.ws.addEventListener("open", () => this.startPing());
15
+ }
16
+ on(event, listener) {
17
+ if (!this.listeners.has(event))
18
+ this.listeners.set(event, new Set());
19
+ this.listeners.get(event).add(listener);
20
+ return this;
21
+ }
22
+ off(event, listener) {
23
+ this.listeners.get(event)?.delete(listener);
24
+ return this;
25
+ }
26
+ /** Signal ready once connected to room */
27
+ ready() {
28
+ this.send({ type: "ready" });
29
+ }
30
+ /** Send a game-defined message to all players in the room */
31
+ broadcast(data) {
32
+ this.send({ type: "game_message", payload: data });
33
+ }
34
+ leave() {
35
+ this.stopPing();
36
+ this.ws.close();
37
+ }
38
+ send(msg) {
39
+ if (this.ws.readyState === WebSocket.OPEN) {
40
+ this.ws.send(JSON.stringify(msg));
41
+ }
42
+ }
43
+ handleMessage(e) {
44
+ let msg;
45
+ try {
46
+ msg = JSON.parse(e.data);
47
+ }
48
+ catch {
49
+ return;
50
+ }
51
+ const type = msg.type;
52
+ this.emit(type, msg.payload);
53
+ }
54
+ emit(event, payload) {
55
+ this.listeners.get(event)?.forEach((fn) => fn(payload));
56
+ }
57
+ startPing() {
58
+ this.pingInterval = setInterval(() => {
59
+ this.send({ type: "ping" });
60
+ }, 5000);
61
+ }
62
+ stopPing() {
63
+ if (this.pingInterval !== null) {
64
+ clearInterval(this.pingInterval);
65
+ this.pingInterval = null;
66
+ }
67
+ }
68
+ }
69
+ //# sourceMappingURL=room.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"room.js","sourceRoot":"","sources":["../src/room.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=client.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.test.d.ts","sourceRoot":"","sources":["../../src/test/client.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,290 @@
1
+ import { describe, expect, it, beforeEach, afterEach, vi } from "vitest";
2
+ import { VersusClient, MatchProposal } from "../client.js";
3
+ import { Room } from "../room.js";
4
+ import { FakeWebSocket } from "./fake-ws.js";
5
+ // ---------------------------------------------------------------------------
6
+ // Test setup
7
+ // ---------------------------------------------------------------------------
8
+ const BASE_URL = "https://api.test.internal";
9
+ const TOKEN = "test-session-jwt";
10
+ const API_KEY = "1up_testapikey";
11
+ function makeClient() {
12
+ return new VersusClient({ baseUrl: BASE_URL, token: TOKEN, gameApiKey: API_KEY });
13
+ }
14
+ let queueWs;
15
+ let roomWs;
16
+ let wsCallCount = 0;
17
+ function stubWebSocket() {
18
+ wsCallCount = 0;
19
+ vi.stubGlobal("WebSocket", class extends FakeWebSocket {
20
+ constructor(url) {
21
+ super(url);
22
+ wsCallCount++;
23
+ if (wsCallCount === 1) {
24
+ queueWs = this; // first WS = queue WS
25
+ }
26
+ else {
27
+ roomWs = this; // second WS = room WS (created by Room constructor)
28
+ }
29
+ }
30
+ });
31
+ }
32
+ function stubFetch(ok = true, body) {
33
+ vi.stubGlobal("fetch", vi.fn().mockResolvedValue({
34
+ ok,
35
+ text: vi.fn().mockResolvedValue(ok ? "" : "Error"),
36
+ json: vi.fn().mockResolvedValue(body ?? {}),
37
+ }));
38
+ }
39
+ afterEach(() => {
40
+ vi.restoreAllMocks();
41
+ vi.useRealTimers();
42
+ });
43
+ // ---------------------------------------------------------------------------
44
+ // matchmake()
45
+ // ---------------------------------------------------------------------------
46
+ describe("VersusClient — matchmake()", () => {
47
+ beforeEach(() => {
48
+ stubWebSocket();
49
+ stubFetch(true);
50
+ });
51
+ it("POSTs to /versus/:gameId/queue with correct headers", async () => {
52
+ // Resolve with match_found quickly
53
+ const matchmakePromise = makeClient().matchmake("game1");
54
+ // Let the async fetch complete
55
+ await vi.waitFor(() => expect(queueWs).toBeDefined());
56
+ const fetchMock = vi.mocked(globalThis.fetch);
57
+ const [url, init] = fetchMock.mock.calls[0];
58
+ expect(url).toBe(`${BASE_URL}/versus/game1/queue`);
59
+ expect(init.headers["Authorization"]).toBe(`Bearer ${TOKEN}`);
60
+ expect(init.headers["X-Api-Key"]).toBe(API_KEY);
61
+ expect(init.method).toBe("POST");
62
+ // Resolve the promise to avoid unhandled rejection
63
+ queueWs.receive(JSON.stringify({
64
+ type: "match_found",
65
+ payload: {
66
+ matchId: "m1",
67
+ opponents: [{ walletPubkey: "p2", elo: 1000 }],
68
+ acceptDeadline: Date.now() + 15000,
69
+ },
70
+ }));
71
+ await matchmakePromise;
72
+ });
73
+ it("opens queue WebSocket with token and apiKey params", async () => {
74
+ const matchmakePromise = makeClient().matchmake("game1");
75
+ await vi.waitFor(() => expect(queueWs).toBeDefined());
76
+ expect(queueWs.url).toContain(`/versus/game1/queue/ws`);
77
+ expect(queueWs.url).toContain(`token=`);
78
+ expect(queueWs.url).toContain(`apiKey=`);
79
+ queueWs.receive(JSON.stringify({
80
+ type: "match_found",
81
+ payload: { matchId: "m1", opponents: [], acceptDeadline: Date.now() + 15000 },
82
+ }));
83
+ await matchmakePromise;
84
+ });
85
+ it("calls onQueued callback after successful HTTP join", async () => {
86
+ const onQueued = vi.fn();
87
+ const matchmakePromise = makeClient().matchmake("game1", onQueued);
88
+ await vi.waitFor(() => expect(onQueued).toHaveBeenCalled());
89
+ queueWs.receive(JSON.stringify({
90
+ type: "match_found",
91
+ payload: { matchId: "m1", opponents: [], acceptDeadline: Date.now() + 15000 },
92
+ }));
93
+ await matchmakePromise;
94
+ });
95
+ it("resolves with MatchProposal on match_found", async () => {
96
+ const matchmakePromise = makeClient().matchmake("game1");
97
+ await vi.waitFor(() => expect(queueWs).toBeDefined());
98
+ const payload = {
99
+ matchId: "match-abc",
100
+ opponents: [{ walletPubkey: "p2", elo: 1050 }],
101
+ acceptDeadline: Date.now() + 15000,
102
+ };
103
+ queueWs.receive(JSON.stringify({ type: "match_found", payload }));
104
+ const proposal = await matchmakePromise;
105
+ expect(proposal).toBeInstanceOf(MatchProposal);
106
+ expect(proposal.matchId).toBe("match-abc");
107
+ expect(proposal.opponents).toEqual(payload.opponents);
108
+ expect(proposal.acceptDeadline).toBe(payload.acceptDeadline);
109
+ });
110
+ it("rejects on queue_expired", async () => {
111
+ const matchmakePromise = makeClient().matchmake("game1");
112
+ await vi.waitFor(() => expect(queueWs).toBeDefined());
113
+ queueWs.receive(JSON.stringify({ type: "queue_expired" }));
114
+ await expect(matchmakePromise).rejects.toThrow(/expired/i);
115
+ });
116
+ it("rejects on unexpected WebSocket close", async () => {
117
+ const matchmakePromise = makeClient().matchmake("game1");
118
+ await vi.waitFor(() => expect(queueWs).toBeDefined());
119
+ queueWs.serverClose(); // wasClean=false
120
+ await expect(matchmakePromise).rejects.toThrow();
121
+ });
122
+ it("rejects when HTTP join fails", async () => {
123
+ vi.stubGlobal("fetch", vi.fn().mockResolvedValue({ ok: false, text: vi.fn().mockResolvedValue("Queue full") }));
124
+ await expect(makeClient().matchmake("game1")).rejects.toThrow("Queue full");
125
+ });
126
+ });
127
+ // ---------------------------------------------------------------------------
128
+ // MatchProposal.accept()
129
+ // ---------------------------------------------------------------------------
130
+ describe("MatchProposal — accept()", () => {
131
+ beforeEach(() => {
132
+ stubWebSocket();
133
+ stubFetch(true);
134
+ });
135
+ async function getProposal() {
136
+ const matchmakePromise = makeClient().matchmake("game1");
137
+ await vi.waitFor(() => expect(queueWs).toBeDefined());
138
+ queueWs.open(); // WS must be OPEN for accept()/decline() to send messages
139
+ queueWs.receive(JSON.stringify({
140
+ type: "match_found",
141
+ payload: { matchId: "m1", opponents: [], acceptDeadline: Date.now() + 15000 },
142
+ }));
143
+ return matchmakePromise;
144
+ }
145
+ it("sends accept_match message to the queue WS", async () => {
146
+ const proposal = await getProposal();
147
+ const sentBefore = queueWs.sent.length;
148
+ const acceptPromise = proposal.accept();
149
+ // accept_match should have been sent synchronously in the Promise executor
150
+ expect(queueWs.sent.length).toBeGreaterThan(sentBefore);
151
+ const lastSent = JSON.parse(queueWs.sent[queueWs.sent.length - 1]);
152
+ expect(lastSent).toEqual({ type: "accept_match" });
153
+ // Resolve with match_ready
154
+ queueWs.receive(JSON.stringify({
155
+ type: "match_ready",
156
+ payload: { matchId: "m1", roomToken: "room-jwt", roomUrl: "wss://api.test.internal/versus/rooms/m1" },
157
+ }));
158
+ await acceptPromise;
159
+ });
160
+ it("resolves with a Room instance on match_ready", async () => {
161
+ const proposal = await getProposal();
162
+ const acceptPromise = proposal.accept();
163
+ queueWs.receive(JSON.stringify({
164
+ type: "match_ready",
165
+ payload: {
166
+ matchId: "m1",
167
+ roomToken: "room-jwt-token",
168
+ roomUrl: "wss://api.test.internal/versus/rooms/m1",
169
+ },
170
+ }));
171
+ const room = await acceptPromise;
172
+ expect(room).toBeInstanceOf(Room);
173
+ // Token is passed in the URL — Worker validates it before forwarding to the Room DO
174
+ expect(roomWs?.url ?? queueWs.url).toContain("room-jwt-token");
175
+ });
176
+ it("rejects on match_declined", async () => {
177
+ const proposal = await getProposal();
178
+ const acceptPromise = proposal.accept();
179
+ queueWs.receive(JSON.stringify({
180
+ type: "match_declined",
181
+ payload: { reason: "declined", walletPubkey: "p2" },
182
+ }));
183
+ await expect(acceptPromise).rejects.toThrow(/cancelled/i);
184
+ });
185
+ });
186
+ // ---------------------------------------------------------------------------
187
+ // MatchProposal.decline()
188
+ // ---------------------------------------------------------------------------
189
+ describe("MatchProposal — decline()", () => {
190
+ beforeEach(() => {
191
+ stubWebSocket();
192
+ stubFetch(true);
193
+ });
194
+ it("sends decline_match and closes the queue WS", async () => {
195
+ const matchmakePromise = makeClient().matchmake("game1");
196
+ await vi.waitFor(() => expect(queueWs).toBeDefined());
197
+ queueWs.open(); // WS must be OPEN for decline() to send
198
+ queueWs.receive(JSON.stringify({
199
+ type: "match_found",
200
+ payload: { matchId: "m1", opponents: [], acceptDeadline: Date.now() + 15000 },
201
+ }));
202
+ const proposal = await matchmakePromise;
203
+ proposal.decline();
204
+ const sentMsgs = queueWs.sent.map((s) => JSON.parse(s));
205
+ expect(sentMsgs).toContainEqual({ type: "decline_match" });
206
+ expect(queueWs.readyState).toBe(FakeWebSocket.CLOSED);
207
+ });
208
+ });
209
+ // ---------------------------------------------------------------------------
210
+ // MatchProposal.abandon() — resource leak cleanup (GAP-32)
211
+ // ---------------------------------------------------------------------------
212
+ describe("MatchProposal — abandon() (GAP-32)", () => {
213
+ beforeEach(() => {
214
+ stubWebSocket();
215
+ stubFetch(true);
216
+ });
217
+ async function getProposal() {
218
+ const matchmakePromise = makeClient().matchmake("game1");
219
+ await vi.waitFor(() => expect(queueWs).toBeDefined());
220
+ queueWs.open();
221
+ queueWs.receive(JSON.stringify({
222
+ type: "match_found",
223
+ payload: { matchId: "m1", opponents: [], acceptDeadline: Date.now() + 15000 },
224
+ }));
225
+ return matchmakePromise;
226
+ }
227
+ it("closes the WebSocket on abandon()", async () => {
228
+ const proposal = await getProposal();
229
+ proposal.abandon();
230
+ expect(queueWs.readyState).toBe(FakeWebSocket.CLOSED);
231
+ });
232
+ it("does not send accept_match or decline_match on abandon()", async () => {
233
+ const proposal = await getProposal();
234
+ const sentBefore = queueWs.sent.length;
235
+ proposal.abandon();
236
+ const newMsgs = queueWs.sent.slice(sentBefore).map((s) => JSON.parse(s));
237
+ expect(newMsgs.map((m) => m.type)).not.toContain("accept_match");
238
+ expect(newMsgs.map((m) => m.type)).not.toContain("decline_match");
239
+ });
240
+ });
241
+ // ---------------------------------------------------------------------------
242
+ // leaveQueue()
243
+ // ---------------------------------------------------------------------------
244
+ describe("VersusClient — leaveQueue()", () => {
245
+ it("POSTs to /versus/:gameId/queue/leave with correct headers", async () => {
246
+ stubFetch(true);
247
+ await makeClient().leaveQueue("game1");
248
+ const fetchMock = vi.mocked(globalThis.fetch);
249
+ const [url, init] = fetchMock.mock.calls[0];
250
+ expect(url).toBe(`${BASE_URL}/versus/game1/queue/leave`);
251
+ expect(init.method).toBe("POST");
252
+ expect(init.headers["Authorization"]).toBe(`Bearer ${TOKEN}`);
253
+ });
254
+ });
255
+ // ---------------------------------------------------------------------------
256
+ // getConfig / setConfig / listMatches / inspectMatch
257
+ // ---------------------------------------------------------------------------
258
+ describe("VersusClient — REST helpers", () => {
259
+ beforeEach(() => {
260
+ vi.stubGlobal("fetch", vi.fn().mockResolvedValue({
261
+ ok: true,
262
+ json: vi.fn().mockResolvedValue({}),
263
+ }));
264
+ });
265
+ it("getConfig: GET /versus/:gameId/config with auth headers", async () => {
266
+ await makeClient().getConfig("g1");
267
+ const [url, init] = vi.mocked(fetch).mock.calls[0];
268
+ expect(url).toBe(`${BASE_URL}/versus/g1/config`);
269
+ expect(init.method).toBeUndefined(); // default GET
270
+ expect(init.headers["Authorization"]).toBe(`Bearer ${TOKEN}`);
271
+ });
272
+ it("setConfig: PUT /versus/:gameId/config", async () => {
273
+ await makeClient().setConfig("g1", { queueTtl: 60 });
274
+ const [url, init] = vi.mocked(fetch).mock.calls[0];
275
+ expect(url).toBe(`${BASE_URL}/versus/g1/config`);
276
+ expect(init.method).toBe("PUT");
277
+ expect(JSON.parse(init.body)).toMatchObject({ queueTtl: 60 });
278
+ });
279
+ it("listMatches: GET /versus/:gameId/matches", async () => {
280
+ await makeClient().listMatches("g1");
281
+ const [url] = vi.mocked(fetch).mock.calls[0];
282
+ expect(url).toBe(`${BASE_URL}/versus/g1/matches`);
283
+ });
284
+ it("inspectMatch: GET /versus/matches/:matchId", async () => {
285
+ await makeClient().inspectMatch("match-123");
286
+ const [url] = vi.mocked(fetch).mock.calls[0];
287
+ expect(url).toBe(`${BASE_URL}/versus/matches/match-123`);
288
+ });
289
+ });
290
+ //# sourceMappingURL=client.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.test.js","sourceRoot":"","sources":["../../src/test/client.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Hand-rolled FakeWebSocket for unit-testing Room and VersusClient.
3
+ *
4
+ * Usage:
5
+ * vi.stubGlobal("WebSocket", FakeWebSocket);
6
+ * // construct Room / VersusClient — they will get a FakeWebSocket instance
7
+ * fakeWs.open(); // trigger the "open" event
8
+ * fakeWs.receive(data); // inject a "message" event
9
+ * fakeWs.serverClose(); // inject a dirty "close" event (wasClean=false)
10
+ * fakeWs.sent // inspect outbound messages
11
+ */
12
+ export declare class FakeWebSocket extends EventTarget {
13
+ static readonly CONNECTING = 0;
14
+ static readonly OPEN = 1;
15
+ static readonly CLOSING = 2;
16
+ static readonly CLOSED = 3;
17
+ readonly CONNECTING = 0;
18
+ readonly OPEN = 1;
19
+ readonly CLOSING = 2;
20
+ readonly CLOSED = 3;
21
+ readyState: number;
22
+ readonly url: string;
23
+ /** All payloads passed to send() while OPEN. */
24
+ sent: string[];
25
+ constructor(url: string | URL);
26
+ send(data: string): void;
27
+ close(_code?: number, _reason?: string): void;
28
+ /** Test helper: fire the "open" event and move to OPEN state. */
29
+ open(): void;
30
+ /** Test helper: inject an incoming message from the server. */
31
+ receive(data: string): void;
32
+ /** Test helper: inject an unclean "close" event (wasClean=false). */
33
+ serverClose(code?: number, reason?: string): void;
34
+ }
35
+ //# sourceMappingURL=fake-ws.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fake-ws.d.ts","sourceRoot":"","sources":["../../src/test/fake-ws.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,qBAAa,aAAc,SAAQ,WAAW;IAC5C,MAAM,CAAC,QAAQ,CAAC,UAAU,KAAK;IAC/B,MAAM,CAAC,QAAQ,CAAC,IAAI,KAAK;IACzB,MAAM,CAAC,QAAQ,CAAC,OAAO,KAAK;IAC5B,MAAM,CAAC,QAAQ,CAAC,MAAM,KAAK;IAE3B,QAAQ,CAAC,UAAU,KAAK;IACxB,QAAQ,CAAC,IAAI,KAAK;IAClB,QAAQ,CAAC,OAAO,KAAK;IACrB,QAAQ,CAAC,MAAM,KAAK;IAEpB,UAAU,EAAE,MAAM,CAA4B;IAC9C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,gDAAgD;IAChD,IAAI,EAAE,MAAM,EAAE,CAAM;gBAER,GAAG,EAAE,MAAM,GAAG,GAAG;IAK7B,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAMxB,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI;IAM7C,iEAAiE;IACjE,IAAI,IAAI,IAAI;IAKZ,+DAA+D;IAC/D,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAI3B,qEAAqE;IACrE,WAAW,CAAC,IAAI,SAAO,EAAE,MAAM,SAAK,GAAG,IAAI;CAK5C"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Hand-rolled FakeWebSocket for unit-testing Room and VersusClient.
3
+ *
4
+ * Usage:
5
+ * vi.stubGlobal("WebSocket", FakeWebSocket);
6
+ * // construct Room / VersusClient — they will get a FakeWebSocket instance
7
+ * fakeWs.open(); // trigger the "open" event
8
+ * fakeWs.receive(data); // inject a "message" event
9
+ * fakeWs.serverClose(); // inject a dirty "close" event (wasClean=false)
10
+ * fakeWs.sent // inspect outbound messages
11
+ */
12
+ export class FakeWebSocket extends EventTarget {
13
+ static CONNECTING = 0;
14
+ static OPEN = 1;
15
+ static CLOSING = 2;
16
+ static CLOSED = 3;
17
+ CONNECTING = 0;
18
+ OPEN = 1;
19
+ CLOSING = 2;
20
+ CLOSED = 3;
21
+ readyState = FakeWebSocket.CONNECTING;
22
+ url;
23
+ /** All payloads passed to send() while OPEN. */
24
+ sent = [];
25
+ constructor(url) {
26
+ super();
27
+ this.url = url.toString();
28
+ }
29
+ send(data) {
30
+ if (this.readyState === FakeWebSocket.OPEN) {
31
+ this.sent.push(data);
32
+ }
33
+ }
34
+ close(_code, _reason) {
35
+ if (this.readyState === FakeWebSocket.CLOSED)
36
+ return;
37
+ this.readyState = FakeWebSocket.CLOSED;
38
+ this.dispatchEvent(new CloseEvent("close", { wasClean: true }));
39
+ }
40
+ /** Test helper: fire the "open" event and move to OPEN state. */
41
+ open() {
42
+ this.readyState = FakeWebSocket.OPEN;
43
+ this.dispatchEvent(new Event("open"));
44
+ }
45
+ /** Test helper: inject an incoming message from the server. */
46
+ receive(data) {
47
+ this.dispatchEvent(new MessageEvent("message", { data }));
48
+ }
49
+ /** Test helper: inject an unclean "close" event (wasClean=false). */
50
+ serverClose(code = 1006, reason = "") {
51
+ if (this.readyState === FakeWebSocket.CLOSED)
52
+ return;
53
+ this.readyState = FakeWebSocket.CLOSED;
54
+ this.dispatchEvent(new CloseEvent("close", { wasClean: false, code, reason }));
55
+ }
56
+ }
57
+ //# sourceMappingURL=fake-ws.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fake-ws.js","sourceRoot":"","sources":["../../src/test/fake-ws.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,OAAO,aAAc,SAAQ,WAAW;IAC5C,MAAM,CAAU,UAAU,GAAG,CAAC,CAAC;IAC/B,MAAM,CAAU,IAAI,GAAG,CAAC,CAAC;IACzB,MAAM,CAAU,OAAO,GAAG,CAAC,CAAC;IAC5B,MAAM,CAAU,MAAM,GAAG,CAAC,CAAC;IAElB,UAAU,GAAG,CAAC,CAAC;IACf,IAAI,GAAG,CAAC,CAAC;IACT,OAAO,GAAG,CAAC,CAAC;IACZ,MAAM,GAAG,CAAC,CAAC;IAEpB,UAAU,GAAW,aAAa,CAAC,UAAU,CAAC;IACrC,GAAG,CAAS;IACrB,gDAAgD;IAChD,IAAI,GAAa,EAAE,CAAC;IAEpB,YAAY,GAAiB;QAC3B,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC,QAAQ,EAAE,CAAC;IAC5B,CAAC;IAED,IAAI,CAAC,IAAY;QACf,IAAI,IAAI,CAAC,UAAU,KAAK,aAAa,CAAC,IAAI,EAAE,CAAC;YAC3C,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IAED,KAAK,CAAC,KAAc,EAAE,OAAgB;QACpC,IAAI,IAAI,CAAC,UAAU,KAAK,aAAa,CAAC,MAAM;YAAE,OAAO;QACrD,IAAI,CAAC,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC;QACvC,IAAI,CAAC,aAAa,CAAC,IAAI,UAAU,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAClE,CAAC;IAED,iEAAiE;IACjE,IAAI;QACF,IAAI,CAAC,UAAU,GAAG,aAAa,CAAC,IAAI,CAAC;QACrC,IAAI,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IACxC,CAAC;IAED,+DAA+D;IAC/D,OAAO,CAAC,IAAY;QAClB,IAAI,CAAC,aAAa,CAAC,IAAI,YAAY,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAC5D,CAAC;IAED,qEAAqE;IACrE,WAAW,CAAC,IAAI,GAAG,IAAI,EAAE,MAAM,GAAG,EAAE;QAClC,IAAI,IAAI,CAAC,UAAU,KAAK,aAAa,CAAC,MAAM;YAAE,OAAO;QACrD,IAAI,CAAC,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC;QACvC,IAAI,CAAC,aAAa,CAAC,IAAI,UAAU,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;IACjF,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=room.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"room.test.d.ts","sourceRoot":"","sources":["../../src/test/room.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,181 @@
1
+ import { describe, expect, it, beforeEach, afterEach, vi } from "vitest";
2
+ import { Room } from "../room.js";
3
+ import { FakeWebSocket } from "./fake-ws.js";
4
+ // ---------------------------------------------------------------------------
5
+ // Helpers
6
+ // ---------------------------------------------------------------------------
7
+ let fakeWs;
8
+ /** Stubs global WebSocket to capture the FakeWebSocket instance. */
9
+ function stubWs() {
10
+ vi.stubGlobal("WebSocket", class extends FakeWebSocket {
11
+ constructor(url) {
12
+ super(url);
13
+ fakeWs = this;
14
+ }
15
+ });
16
+ }
17
+ afterEach(() => {
18
+ vi.restoreAllMocks();
19
+ vi.useRealTimers();
20
+ });
21
+ // ---------------------------------------------------------------------------
22
+ // Constructor
23
+ // ---------------------------------------------------------------------------
24
+ describe("Room — constructor", () => {
25
+ beforeEach(stubWs);
26
+ it("opens WebSocket with ?token= in URL", () => {
27
+ new Room("wss://test.internal/versus/rooms/m1", "my-room-token");
28
+ // Token must appear in the URL — Worker validates it before forwarding to DO
29
+ expect(fakeWs.url).toContain("token=my-room-token");
30
+ });
31
+ it("starts pinging after open (every 5s)", () => {
32
+ vi.useFakeTimers();
33
+ new Room("wss://test.internal/versus/rooms/m1", "tok");
34
+ fakeWs.open(); // trigger "open" event → starts ping
35
+ // No auth frame — token is in the URL
36
+ expect(fakeWs.sent).toHaveLength(0);
37
+ vi.advanceTimersByTime(5001);
38
+ // first ping
39
+ expect(fakeWs.sent).toHaveLength(1);
40
+ expect(JSON.parse(fakeWs.sent[0])).toEqual({ type: "ping" });
41
+ vi.advanceTimersByTime(5000);
42
+ // second ping
43
+ expect(fakeWs.sent).toHaveLength(2);
44
+ });
45
+ it("stops pinging after close", () => {
46
+ vi.useFakeTimers();
47
+ new Room("wss://test.internal/versus/rooms/m1", "tok");
48
+ fakeWs.open();
49
+ vi.advanceTimersByTime(5001);
50
+ expect(fakeWs.sent).toHaveLength(1); // ping
51
+ fakeWs.close();
52
+ vi.advanceTimersByTime(10_000);
53
+ // No more pings after close
54
+ expect(fakeWs.sent).toHaveLength(1); // still 1 ping
55
+ });
56
+ });
57
+ // ---------------------------------------------------------------------------
58
+ // on / off
59
+ // ---------------------------------------------------------------------------
60
+ describe("Room — on/off", () => {
61
+ beforeEach(stubWs);
62
+ it("listener fires on matching event", () => {
63
+ const room = new Room("wss://test.internal/versus/rooms/m1", "tok");
64
+ fakeWs.open();
65
+ const calls = [];
66
+ room.on("game_message", (p) => calls.push(p));
67
+ fakeWs.receive(JSON.stringify({ type: "game_message", payload: { from: "p2", data: { x: 1 } } }));
68
+ expect(calls).toHaveLength(1);
69
+ expect(calls[0]).toEqual({ from: "p2", data: { x: 1 } });
70
+ });
71
+ it("off removes only the specified listener", () => {
72
+ const room = new Room("wss://test.internal/versus/rooms/m1", "tok");
73
+ fakeWs.open();
74
+ const calls1 = [];
75
+ const calls2 = [];
76
+ const listener1 = (p) => calls1.push(p);
77
+ room.on("room_ready", listener1);
78
+ room.on("room_ready", (p) => calls2.push(p));
79
+ room.off("room_ready", listener1);
80
+ fakeWs.receive(JSON.stringify({ type: "room_ready" }));
81
+ expect(calls1).toHaveLength(0);
82
+ expect(calls2).toHaveLength(1);
83
+ });
84
+ });
85
+ // ---------------------------------------------------------------------------
86
+ // ready()
87
+ // ---------------------------------------------------------------------------
88
+ describe("Room — ready()", () => {
89
+ beforeEach(stubWs);
90
+ it("sends {type:'ready'} when WS is OPEN", () => {
91
+ const room = new Room("wss://test.internal/versus/rooms/m1", "tok");
92
+ fakeWs.open();
93
+ room.ready();
94
+ expect(fakeWs.sent.map((s) => JSON.parse(s))).toContainEqual({ type: "ready" });
95
+ });
96
+ it("does not send when WS is not OPEN", () => {
97
+ const room = new Room("wss://test.internal/versus/rooms/m1", "tok");
98
+ // fakeWs starts in CONNECTING state — auth is only sent on open event
99
+ room.ready();
100
+ expect(fakeWs.sent).toHaveLength(0);
101
+ });
102
+ });
103
+ // ---------------------------------------------------------------------------
104
+ // broadcast(data)
105
+ // ---------------------------------------------------------------------------
106
+ describe("Room — broadcast()", () => {
107
+ beforeEach(stubWs);
108
+ it("sends {type:'game_message', payload: data}", () => {
109
+ const room = new Room("wss://test.internal/versus/rooms/m1", "tok");
110
+ fakeWs.open(); // starts ping interval, no auth frame
111
+ room.broadcast({ move: "up" });
112
+ const sentMsgs = fakeWs.sent.map((s) => JSON.parse(s));
113
+ expect(sentMsgs).toContainEqual({ type: "game_message", payload: { move: "up" } });
114
+ });
115
+ });
116
+ // ---------------------------------------------------------------------------
117
+ // leave()
118
+ // ---------------------------------------------------------------------------
119
+ describe("Room — leave()", () => {
120
+ beforeEach(stubWs);
121
+ it("closes the WebSocket and stops pinging", () => {
122
+ vi.useFakeTimers();
123
+ const room = new Room("wss://test.internal/versus/rooms/m1", "tok");
124
+ fakeWs.open(); // starts ping interval
125
+ vi.advanceTimersByTime(5001);
126
+ // 1 ping (no auth frame)
127
+ expect(fakeWs.sent).toHaveLength(1);
128
+ room.leave();
129
+ expect(fakeWs.readyState).toBe(FakeWebSocket.CLOSED);
130
+ vi.advanceTimersByTime(10_000);
131
+ expect(fakeWs.sent).toHaveLength(1); // no more pings
132
+ });
133
+ });
134
+ // ---------------------------------------------------------------------------
135
+ // Incoming message routing
136
+ // ---------------------------------------------------------------------------
137
+ describe("Room — incoming message routing", () => {
138
+ beforeEach(stubWs);
139
+ it("routes room_ready to listener with no payload", () => {
140
+ const room = new Room("wss://test.internal/versus/rooms/m1", "tok");
141
+ fakeWs.open();
142
+ const calls = [];
143
+ room.on("room_ready", (p) => calls.push(p));
144
+ fakeWs.receive(JSON.stringify({ type: "room_ready" }));
145
+ expect(calls).toHaveLength(1);
146
+ });
147
+ it("routes game_message payload to listener", () => {
148
+ const room = new Room("wss://test.internal/versus/rooms/m1", "tok");
149
+ fakeWs.open();
150
+ const calls = [];
151
+ room.on("game_message", (p) => calls.push(p));
152
+ fakeWs.receive(JSON.stringify({ type: "game_message", payload: { from: "p2", data: 42 } }));
153
+ expect(calls[0]).toEqual({ from: "p2", data: 42 });
154
+ });
155
+ it("routes opponent_disconnected to listener", () => {
156
+ const room = new Room("wss://test.internal/versus/rooms/m1", "tok");
157
+ fakeWs.open();
158
+ const calls = [];
159
+ room.on("opponent_disconnected", (p) => calls.push(p));
160
+ fakeWs.receive(JSON.stringify({ type: "opponent_disconnected" }));
161
+ expect(calls).toHaveLength(1);
162
+ });
163
+ it("routes match_expired to listener", () => {
164
+ const room = new Room("wss://test.internal/versus/rooms/m1", "tok");
165
+ fakeWs.open();
166
+ const calls = [];
167
+ room.on("match_expired", (p) => calls.push(p));
168
+ fakeWs.receive(JSON.stringify({ type: "match_expired" }));
169
+ expect(calls).toHaveLength(1);
170
+ });
171
+ it("silently ignores malformed JSON", () => {
172
+ const room = new Room("wss://test.internal/versus/rooms/m1", "tok");
173
+ fakeWs.open();
174
+ const errors = [];
175
+ room.on("error", (e) => errors.push(e));
176
+ expect(() => fakeWs.receive("not json at all {{{{")).not.toThrow();
177
+ // No error events should be emitted for JSON parse failures
178
+ expect(errors).toHaveLength(0);
179
+ });
180
+ });
181
+ //# sourceMappingURL=room.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"room.test.js","sourceRoot":"","sources":["../../src/test/room.test.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@1upmonster/versus",
3
+ "version": "0.1.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "import": "./dist/index.js",
9
+ "types": "./dist/index.d.ts"
10
+ }
11
+ },
12
+ "publishConfig": {
13
+ "access": "public"
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "dependencies": {
19
+ "@1upmonster/sdk": "0.1.0",
20
+ "@1upmonster/types": "0.1.0"
21
+ },
22
+ "devDependencies": {
23
+ "typescript": "^5.7.2",
24
+ "vitest": "^3.0.0"
25
+ },
26
+ "scripts": {
27
+ "build": "tsc",
28
+ "typecheck": "tsc --noEmit",
29
+ "test": "vitest run",
30
+ "test:watch": "vitest",
31
+ "clean": "rm -rf dist"
32
+ }
33
+ }