@zaisma/game-server 1.12.0 → 1.14.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/bench/brand/favicon-32.png +0 -0
- package/bench/brand/favicon.ico +0 -0
- package/bench/brand/favicon.svg +1 -1
- package/bench/glass.js +8955 -757
- package/bin/local-play-runner.js +27 -1
- package/bin/local-play.js +25 -0
- package/bin/node-parts.js +5 -1
- package/bin/node.d.ts +10 -1
- package/bin/node.js +167 -36
- package/package.json +1 -1
- package/sdk/zaisma.d.ts +80 -8
- package/sdk/zaisma.js +77 -18
- package/src/chat.d.ts +9 -4
- package/src/chat.js +20 -4
- package/src/index.d.ts +2 -1
- package/src/index.js +2 -1
- package/src/internal.d.ts +1 -0
- package/src/internal.js +1 -0
- package/src/outside-players.d.ts +65 -0
- package/src/outside-players.js +162 -0
- package/src/pass.d.ts +7 -2
- package/src/pass.js +1 -1
- package/src/requests.d.ts +45 -4
- package/src/requests.js +15 -8
- package/src/room-calls.d.ts +16 -0
- package/src/room-calls.js +31 -1
- package/src/room.d.ts +155 -4
- package/src/room.js +137 -4
package/sdk/zaisma.d.ts
CHANGED
|
@@ -26,7 +26,9 @@
|
|
|
26
26
|
*
|
|
27
27
|
* Player passes, for rooms and your own servers, need the game played on its zaisma.com page, which
|
|
28
28
|
* checks the browser on Play: joining fails with `error.code` "unchecked" when it couldn't, or when the
|
|
29
|
-
* game was opened anywhere else
|
|
29
|
+
* game was opened anywhere else. A game played off Zaisma (exported, on another site, wrapped for a store)
|
|
30
|
+
* plays online against your own game server only: call {@link Zaisma.setUpOffZaisma} first, and give
|
|
31
|
+
* joinRoom and server.request a serverUrl; without them they fail with "not_on_zaisma".
|
|
30
32
|
*
|
|
31
33
|
* @example
|
|
32
34
|
* const room = await Zaisma.joinRoom(); // joins ?room=CODE if the link has one, else makes a room
|
|
@@ -140,8 +142,9 @@ declare namespace Zaisma {
|
|
|
140
142
|
* refused (the game server's canJoin said no; the message is its refusalMessage), hosting_paused (the
|
|
141
143
|
* game's hosting is paused this month: its free allowance or its developer's budget or credit is used). No player pass: unchecked, or account_needed for a
|
|
142
144
|
* guest when the game needs accounts (see Zaisma.player.getPass()). The
|
|
143
|
-
* call is set up wrong: unknown_option, bad_room_code, bad_server_url, bad_transport, not_on_zaisma (the game
|
|
144
|
-
*
|
|
145
|
+
* call is set up wrong: unknown_option, bad_room_code, bad_server_url, bad_transport, not_on_zaisma (the game is played
|
|
146
|
+
* off Zaisma without a serverUrl, or before {@link setUpOffZaisma}). Off Zaisma, bad_player_token: your server
|
|
147
|
+
* didn't take the playerToken (get the player a new one and call setUpOffZaisma again). Or connection_closed (the connection closed first, say the game left or the player
|
|
145
148
|
* joined from another tab) and request_failed (Zaisma didn't answer). {@link ErrorCode} has them all.
|
|
146
149
|
*
|
|
147
150
|
* @example
|
|
@@ -154,6 +157,40 @@ declare namespace Zaisma {
|
|
|
154
157
|
*/
|
|
155
158
|
function joinRoom(joinOptions?: JoinOptions): Promise<Room>;
|
|
156
159
|
|
|
160
|
+
/**
|
|
161
|
+
* Set up a game played off Zaisma (exported, on another site, wrapped for a store) to play online against
|
|
162
|
+
* your own game server. Off Zaisma there's no Zaisma pass, so joinRoom and server.request with a serverUrl
|
|
163
|
+
* send the playerToken in its place, and your server says who the player is (startServer's identifyPlayer):
|
|
164
|
+
* a Sign in with Zaisma ID token (same player ID and name as on Zaisma), your game's own token, or null for
|
|
165
|
+
* a guest when your server lets guests in (outsideGuestsEnabled). They join with kind "outside_zaisma",
|
|
166
|
+
* in the same rooms as players from your Zaisma page.
|
|
167
|
+
*
|
|
168
|
+
* On a Zaisma page it changes nothing and resolves { isOffZaisma: false }, so the same code runs in both
|
|
169
|
+
* places. Call it again to replace the token (a new sign-in, or signing out); rooms use the newest one when
|
|
170
|
+
* they reconnect. Off Zaisma there's no Zaisma bar, saves, buy sheet, friends or room lists.
|
|
171
|
+
*
|
|
172
|
+
* Fails with error.code unknown_option, bad_game_slug (not a game slug) and bad_player_token (not text up to 8 KB, or null).
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* await Zaisma.setUpOffZaisma({ gameSlug: "star-raiders", playerToken: idToken });
|
|
176
|
+
* const room = await Zaisma.joinRoom({ serverUrl: "wss://play.example.com" });
|
|
177
|
+
*/
|
|
178
|
+
function setUpOffZaisma(setUpOptions: SetUpOffZaismaOptions): Promise<OffZaismaSetup>;
|
|
179
|
+
|
|
180
|
+
/** Options for {@link setUpOffZaisma}. */
|
|
181
|
+
interface SetUpOffZaismaOptions {
|
|
182
|
+
/** Your game's slug, as its Zaisma link names it, like "star-raiders": the address your server's rooms and requests take. */
|
|
183
|
+
gameSlug: string;
|
|
184
|
+
/** The player's token for your own server, text up to 8 KB: a Sign in with Zaisma ID token or your game's own. Null or left out for a guest. */
|
|
185
|
+
playerToken?: string | null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** What {@link setUpOffZaisma} answers. */
|
|
189
|
+
interface OffZaismaSetup {
|
|
190
|
+
/** False on a Zaisma page (nothing changed); true elsewhere, where joins and requests now carry the playerToken. */
|
|
191
|
+
isOffZaisma: boolean;
|
|
192
|
+
}
|
|
193
|
+
|
|
157
194
|
/** Room browsing: the game's listed rooms, which show on its Zaisma page and to the game itself. */
|
|
158
195
|
const rooms: Rooms;
|
|
159
196
|
|
|
@@ -982,8 +1019,9 @@ declare namespace Zaisma {
|
|
|
982
1019
|
* seconds; you see why with the game server's errors), hosting_paused (the game's hosting
|
|
983
1020
|
* on Zaisma is paused this month: its free allowance or its developer's budget or credit is used), too_big, bad_request (not plain JSON), slow_down
|
|
984
1021
|
* (over about 120 a minute from one address), bad_pass, account_needed, unchecked,
|
|
985
|
-
* game_unavailable, bad_origin, busy, bad_server_url, not_on_zaisma (the game
|
|
986
|
-
*
|
|
1022
|
+
* game_unavailable, bad_origin, busy, bad_server_url, not_on_zaisma (the game is played off
|
|
1023
|
+
* Zaisma without a serverUrl, or before {@link Zaisma.setUpOffZaisma}), bad_player_token (off Zaisma,
|
|
1024
|
+
* your server didn't take the playerToken), unknown_option and request_failed.
|
|
987
1025
|
*
|
|
988
1026
|
* @example
|
|
989
1027
|
* const top = await Zaisma.server.request({ type: "leaderboard" });
|
|
@@ -1347,6 +1385,15 @@ declare namespace Zaisma {
|
|
|
1347
1385
|
* room.on("versionUpdate", ({ appliesAt }) => showBanner(`Updating at ${new Date(appliesAt).toLocaleTimeString()}`));
|
|
1348
1386
|
*/
|
|
1349
1387
|
versionUpdate: (versionUpdate: VersionUpdate) => void;
|
|
1388
|
+
/**
|
|
1389
|
+
* A room message reached this room and its game server has no onRoomMessage, so every player's game hears
|
|
1390
|
+
* it: from another room of the game (senderKind "room", fromRoomCode), its game server outside rooms
|
|
1391
|
+
* ("server") or the developer (the API and MCP, "developer"), like an announcement or a live event starting.
|
|
1392
|
+
* Zaisma shows nothing: your game decides. A game server with onRoomMessage takes it instead.
|
|
1393
|
+
* @example
|
|
1394
|
+
* room.on("roomMessage", ({ message }) => { if (message.kind === "blood_moon") startBloodMoon(); });
|
|
1395
|
+
*/
|
|
1396
|
+
roomMessage?: (roomMessage: RoomMessage) => void;
|
|
1350
1397
|
/**
|
|
1351
1398
|
* Another player's chat message reached this player ({@link Room.chat}). Messages from players this
|
|
1352
1399
|
* player muted or blocked never come. hasSwearing and hasSlur flag what Zaisma allows: hide or blur
|
|
@@ -1438,6 +1485,18 @@ declare namespace Zaisma {
|
|
|
1438
1485
|
answerTimeoutSec: number;
|
|
1439
1486
|
}
|
|
1440
1487
|
|
|
1488
|
+
/** A room message, as the room's "roomMessage" event hands it over. */
|
|
1489
|
+
interface RoomMessage {
|
|
1490
|
+
/** The message as sent: plain JSON, up to 16 KB. */
|
|
1491
|
+
message: unknown;
|
|
1492
|
+
/** Who sent it: another room's game server, the game server outside rooms, or the developer. */
|
|
1493
|
+
senderKind: "room" | "server" | "developer";
|
|
1494
|
+
/** The code of the room that sent it, or null unless senderKind is "room". */
|
|
1495
|
+
fromRoomCode: string | null;
|
|
1496
|
+
/** When it was sent, an ISO time. */
|
|
1497
|
+
sentAt: string;
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1441
1500
|
/** A match moving to a version published "now" (the room's "versionUpdate" event). */
|
|
1442
1501
|
interface VersionUpdate {
|
|
1443
1502
|
/** The version it moves to, like "version_k2v9q4x7m1p3w8r5t6y0". */
|
|
@@ -1467,8 +1526,11 @@ declare namespace Zaisma {
|
|
|
1467
1526
|
awayReason: "idle" | "hidden" | "lost" | null;
|
|
1468
1527
|
}
|
|
1469
1528
|
|
|
1470
|
-
/**
|
|
1471
|
-
|
|
1529
|
+
/**
|
|
1530
|
+
* A signed-in Zaisma account, a guest, or a player your own server let in from outside Zaisma
|
|
1531
|
+
* (outside_zaisma: a game played off Zaisma, {@link setUpOffZaisma}). More values may come; treat an unknown one as "guest".
|
|
1532
|
+
*/
|
|
1533
|
+
type PlayerKind = "guest" | "account" | "outside_zaisma";
|
|
1472
1534
|
|
|
1473
1535
|
/** What anyone sees of an account on its profile, zaisma.com/@username ({@link player.getProfile}). */
|
|
1474
1536
|
interface PlayerProfile {
|
|
@@ -1606,6 +1668,7 @@ declare namespace Zaisma {
|
|
|
1606
1668
|
* - game_not_open_to_player: the account is younger than the game's minimum age (set on the dashboard); the game never learns its age.
|
|
1607
1669
|
* - game_unavailable: the game can't be played right now.
|
|
1608
1670
|
* - bad_pass: the player pass wasn't accepted (the SDK gets a new one and tries once more).
|
|
1671
|
+
* - bad_player_token: off Zaisma, your own server didn't take the playerToken ({@link setUpOffZaisma}); the game gets the player a new one.
|
|
1609
1672
|
* - bad_origin: the connection didn't come from the game's own page.
|
|
1610
1673
|
* - too_big: a message over 16 KB, or joinData over 4 KB.
|
|
1611
1674
|
* - too_fast: more messages than the room takes (about 30 a second).
|
|
@@ -1621,6 +1684,8 @@ declare namespace Zaisma {
|
|
|
1621
1684
|
* - chat_not_allowed, chat_empty, chat_too_long, chat_too_fast: {@link Room.chat} refused (they reject its promise, not the "error" event); chat_not_here: the room's server doesn't take chat.
|
|
1622
1685
|
*
|
|
1623
1686
|
* More codes may come within v1: handle one you don't know like an error you can show (its message).
|
|
1687
|
+
*
|
|
1688
|
+
* More values may come; treat an unknown one as "request_failed".
|
|
1624
1689
|
*/
|
|
1625
1690
|
type RoomErrorCode =
|
|
1626
1691
|
| "room_full"
|
|
@@ -1633,6 +1698,7 @@ declare namespace Zaisma {
|
|
|
1633
1698
|
| "game_not_open_to_player"
|
|
1634
1699
|
| "game_unavailable"
|
|
1635
1700
|
| "bad_pass"
|
|
1701
|
+
| "bad_player_token"
|
|
1636
1702
|
| "bad_origin"
|
|
1637
1703
|
| "too_big"
|
|
1638
1704
|
| "too_fast"
|
|
@@ -1658,6 +1724,7 @@ declare namespace Zaisma {
|
|
|
1658
1724
|
* - bad_request: not plain JSON with the player's pass.
|
|
1659
1725
|
* - too_big: a request over 16 KB.
|
|
1660
1726
|
* - bad_pass: the player pass wasn't accepted (the SDK gets a new one and tries once more).
|
|
1727
|
+
* - bad_player_token: off Zaisma, your own server didn't take the playerToken ({@link setUpOffZaisma}); the game gets the player a new one.
|
|
1661
1728
|
* - account_needed: the player is a guest, and the game needs a Zaisma account for multiplayer or the whole game.
|
|
1662
1729
|
* - game_not_open_to_player: the account is younger than the game's minimum age (set on the dashboard); the game never learns its age.
|
|
1663
1730
|
* - game_unavailable: the game can't be played right now.
|
|
@@ -1667,11 +1734,14 @@ declare namespace Zaisma {
|
|
|
1667
1734
|
* - slow_down: too many requests from this address; try again in a minute.
|
|
1668
1735
|
* - bad_origin: the request didn't come from the game's own page.
|
|
1669
1736
|
* - busy: Zaisma couldn't answer right now; try again soon.
|
|
1737
|
+
*
|
|
1738
|
+
* More values may come; treat an unknown one as "request_failed".
|
|
1670
1739
|
*/
|
|
1671
1740
|
type RequestErrorCode =
|
|
1672
1741
|
| "bad_request"
|
|
1673
1742
|
| "too_big"
|
|
1674
1743
|
| "bad_pass"
|
|
1744
|
+
| "bad_player_token"
|
|
1675
1745
|
| "account_needed"
|
|
1676
1746
|
| "game_not_open_to_player"
|
|
1677
1747
|
| "game_unavailable"
|
|
@@ -1690,7 +1760,8 @@ declare namespace Zaisma {
|
|
|
1690
1760
|
* - zaisma_rooms_not_used: the game doesn't use Zaisma rooms.
|
|
1691
1761
|
* - bad_report: a report whose reason isn't a {@link ReportReason}, or whose player ID isn't one.
|
|
1692
1762
|
* - bad_origin: the call didn't come from the game's own page.
|
|
1693
|
-
* - not_on_zaisma: the game
|
|
1763
|
+
* - not_on_zaisma: the game is played off Zaisma, so it has no player passes, sign-in or room lists; joinRoom and server.request need {@link setUpOffZaisma} and a serverUrl.
|
|
1764
|
+
* - bad_game_slug: {@link setUpOffZaisma}'s gameSlug isn't a game slug: 1 to 40 lowercase letters, digits or hyphens.
|
|
1694
1765
|
* - sign_in_declined: the player chose Not now when the game asked them to sign in ({@link player.signIn}) or showed the "Keep your progress" card ({@link player.offerAccount}).
|
|
1695
1766
|
* - account_offer_too_soon: the "Keep your progress" card already showed this visit, or the player chose Not now in the last 3 days ({@link player.offerAccount}).
|
|
1696
1767
|
* - cloud_saves_off: the game has cloud saves off, so it has no saves on players' accounts ({@link saves}).
|
|
@@ -1731,6 +1802,7 @@ declare namespace Zaisma {
|
|
|
1731
1802
|
| "bad_report"
|
|
1732
1803
|
| "bad_origin"
|
|
1733
1804
|
| "not_on_zaisma"
|
|
1805
|
+
| "bad_game_slug"
|
|
1734
1806
|
| "sign_in_declined"
|
|
1735
1807
|
| "account_offer_too_soon"
|
|
1736
1808
|
| "cloud_saves_off"
|
package/sdk/zaisma.js
CHANGED
|
@@ -6,8 +6,11 @@
|
|
|
6
6
|
* together; test/node/sdk-types.test.ts checks that they match.
|
|
7
7
|
*
|
|
8
8
|
* On Zaisma, the games Worker defines __ZAISMA__ before this file with the rooms address, the game and
|
|
9
|
-
* the site
|
|
10
|
-
* game
|
|
9
|
+
* the site; serverUrl points a game played there at your own server. Played anywhere else (exported, on
|
|
10
|
+
* another site, wrapped for a store), a game plays online against your own server only: after
|
|
11
|
+
* Zaisma.setUpOffZaisma({ gameSlug, playerToken }), joinRoom and server.request with a serverUrl send the
|
|
12
|
+
* playerToken in place of a Zaisma pass, and your server says who that is (its identifyPlayer). Saves, the buy
|
|
13
|
+
* sheet, friends and room lists stay on Zaisma, and so does the Zaisma bar.
|
|
11
14
|
*/
|
|
12
15
|
(function () {
|
|
13
16
|
"use strict";
|
|
@@ -18,6 +21,7 @@
|
|
|
18
21
|
var OWN_SERVER_URL = /^(wss|https):\/\/[^/]/;
|
|
19
22
|
|
|
20
23
|
// What each call takes. Anything else is refused, so a misspelled or old option never goes quietly unused.
|
|
24
|
+
var SET_UP_OFF_ZAISMA_OPTIONS = ["gameSlug", "playerToken"];
|
|
21
25
|
var JOIN_OPTIONS = ["roomCode", "serverUrl", "transport", "isListed", "friendJoinsEnabled", "name", "labels", "joinData", "idleTimeoutSec", "idleCheckEnabled", "isSpectator"];
|
|
22
26
|
// quickJoin finds Zaisma rooms, so it takes no serverUrl.
|
|
23
27
|
var QUICK_JOIN_OPTIONS = ["isListed", "friendJoinsEnabled", "name", "labels", "joinData", "idleTimeoutSec", "idleCheckEnabled", "region"];
|
|
@@ -33,7 +37,7 @@
|
|
|
33
37
|
/** A fast lane that isn't up in this long gives way to the WebSocket, for the rest of the room's life. */
|
|
34
38
|
var FAST_LANE_CONNECT_TIMEOUT_MS = 3000;
|
|
35
39
|
var ROOM_INFO_FIELDS = ["name", "labels"];
|
|
36
|
-
var ROOM_EVENTS = ["message", "bytes", "state", "players", "connectionStatus", "ended", "ending", "endingCancelled", "kicked", "settings", "move", "moved", "idle", "away", "back", "muted", "error", "versionUpdate", "chat"];
|
|
40
|
+
var ROOM_EVENTS = ["message", "bytes", "state", "players", "connectionStatus", "ended", "ending", "endingCancelled", "kicked", "settings", "move", "moved", "idle", "away", "back", "muted", "error", "versionUpdate", "chat", "roomMessage"];
|
|
37
41
|
/** Chat (room.chat): the longest message, the lines a room keeps in memory for reports, and how many of them a report carries. */
|
|
38
42
|
var MAXIMUM_CHAT_CHARACTER_COUNT = 2000;
|
|
39
43
|
var KEPT_CHAT_LINE_COUNT = 50;
|
|
@@ -76,6 +80,13 @@
|
|
|
76
80
|
var DEFAULT_SMOOTHING_DELAY_MS = 100;
|
|
77
81
|
var KEPT_VIEWS_DURATION_MS = 1000;
|
|
78
82
|
|
|
83
|
+
/** A game's slug, as its Zaisma link names it. */
|
|
84
|
+
var GAME_SLUG = /^[a-z0-9-]{1,40}$/;
|
|
85
|
+
/** The longest playerToken a game off Zaisma sends its own server, as UTF-8. */
|
|
86
|
+
var MAXIMUM_PLAYER_TOKEN_SIZE_BYTES = 8 * 1024;
|
|
87
|
+
/** Off Zaisma, after Zaisma.setUpOffZaisma: { gameSlug, playerToken } for joins and requests to the game's own server. */
|
|
88
|
+
var offZaisma = null;
|
|
89
|
+
|
|
79
90
|
/** An Error with a code, as the SDK's errors all have. */
|
|
80
91
|
function codedError(code, message) {
|
|
81
92
|
var error = new Error(message);
|
|
@@ -88,6 +99,36 @@
|
|
|
88
99
|
return codedError("not_on_zaisma", message);
|
|
89
100
|
}
|
|
90
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Off Zaisma, the game's own server can't take a Zaisma pass: what to do instead, or null when it can go ahead
|
|
104
|
+
* (on Zaisma, or set up off it). serverUrl: the call's own server, which off Zaisma it needs.
|
|
105
|
+
*/
|
|
106
|
+
function offZaismaRefusal(serverUrl, call) {
|
|
107
|
+
if (config.rooms) return null;
|
|
108
|
+
if (!serverUrl) return notOnZaisma("Zaisma's rooms serve games played on Zaisma; off Zaisma, give serverUrl, the address of your own game server, to " + call + ".");
|
|
109
|
+
if (!offZaisma) return notOnZaisma("Off Zaisma, call Zaisma.setUpOffZaisma({ gameSlug, playerToken }) first: your own server knows the player by the playerToken, and serverUrl points " + call + " at it.");
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Set up a game played off Zaisma (zaisma.d.ts: Zaisma.setUpOffZaisma). On a Zaisma page it changes nothing,
|
|
115
|
+
* so the same code runs in both places; elsewhere it keeps the game's slug and its player's token for joins
|
|
116
|
+
* and requests to the game's own server. Called again, it replaces the token (a new sign-in, or signing out).
|
|
117
|
+
*/
|
|
118
|
+
function setUpOffZaisma(setUpOptions) {
|
|
119
|
+
var refused = unknownOption(setUpOptions, SET_UP_OFF_ZAISMA_OPTIONS, "Zaisma.setUpOffZaisma");
|
|
120
|
+
if (refused) return Promise.reject(refused);
|
|
121
|
+
if (config.rooms) return Promise.resolve({ isOffZaisma: false });
|
|
122
|
+
setUpOptions = setUpOptions || {};
|
|
123
|
+
var gameSlug = setUpOptions.gameSlug;
|
|
124
|
+
if (typeof gameSlug !== "string" || !GAME_SLUG.test(gameSlug)) return Promise.reject(codedError("bad_game_slug", "gameSlug is your game's slug, as its Zaisma link names it: 1 to 40 lowercase letters, digits or hyphens, like \"my-game\"."));
|
|
125
|
+
var playerToken = setUpOptions.playerToken === undefined ? null : setUpOptions.playerToken;
|
|
126
|
+
var isToken = typeof playerToken === "string" && playerToken.length > 0 && new TextEncoder().encode(playerToken).length <= MAXIMUM_PLAYER_TOKEN_SIZE_BYTES;
|
|
127
|
+
if (playerToken !== null && !isToken) return Promise.reject(codedError("bad_player_token", "playerToken is the player's token for your own server, as text up to 8 KB (a Sign in with Zaisma ID token, or your game's own), or null for a guest."));
|
|
128
|
+
offZaisma = { gameSlug: gameSlug, playerToken: playerToken };
|
|
129
|
+
return Promise.resolve({ isOffZaisma: true });
|
|
130
|
+
}
|
|
131
|
+
|
|
91
132
|
/** The error for options a call doesn't take, naming the ones it does, or null when they're all fine. */
|
|
92
133
|
function unknownOption(given, valid, call) {
|
|
93
134
|
if (given === undefined || given === null) return null;
|
|
@@ -435,6 +476,8 @@
|
|
|
435
476
|
// What this connection uses now: "webtransport" once the fast lane opens, else "websocket".
|
|
436
477
|
this.transport = "websocket";
|
|
437
478
|
this._pass = setup.pass;
|
|
479
|
+
// Off Zaisma: the hello carries the playerToken Zaisma.setUpOffZaisma holds now, so a reconnect uses a refreshed one.
|
|
480
|
+
this._isOffZaisma = setup.isOffZaisma === true;
|
|
438
481
|
this._made = { isListed: this.isListed, friendJoinsEnabled: setup.friendJoinsEnabled === false ? false : undefined, name: setup.name, labels: setup.labels };
|
|
439
482
|
this._joinData = setup.joinData === undefined ? null : setup.joinData;
|
|
440
483
|
this._serverUrl = setup.serverUrl;
|
|
@@ -653,7 +696,7 @@
|
|
|
653
696
|
};
|
|
654
697
|
|
|
655
698
|
// Codes that mean this room won't take the player, so joining fails instead of trying again.
|
|
656
|
-
var REFUSED = ["room_full", "room_closed", "ended", "bad_pass", "too_big", "game_unavailable", "kept_out", "refused", "hosting_paused", "account_needed", "game_not_open_to_player", "blocked_player_here", "other_version", "spectators_full", "room_not_running", "already_playing", "bad_origin"];
|
|
699
|
+
var REFUSED = ["room_full", "room_closed", "ended", "bad_pass", "bad_player_token", "too_big", "game_unavailable", "kept_out", "refused", "hosting_paused", "account_needed", "game_not_open_to_player", "blocked_player_here", "other_version", "spectators_full", "room_not_running", "already_playing", "bad_origin"];
|
|
657
700
|
|
|
658
701
|
/**
|
|
659
702
|
* Whether a Zaisma room on another version (other_version) has the page reload the game at it: on the
|
|
@@ -685,7 +728,9 @@
|
|
|
685
728
|
room.transport = isFastLane ? "webtransport" : "websocket";
|
|
686
729
|
room._pingAtMs = room._pongAtMs = room._heardAtMs = Date.now();
|
|
687
730
|
room._probeIntervalSec = null;
|
|
688
|
-
var hello = { type: "hello", protocolVersionNumber: PROTOCOL_VERSION_NUMBER,
|
|
731
|
+
var hello = { type: "hello", protocolVersionNumber: PROTOCOL_VERSION_NUMBER, room: room._made, joinData: room._joinData, idleTimeoutSec: room._clientIdle.idleTimeoutSec, idleCheckEnabled: room._clientIdle.idleCheckEnabled };
|
|
732
|
+
if (room._isOffZaisma) hello.playerToken = offZaisma.playerToken;
|
|
733
|
+
else hello.pass = room._pass;
|
|
689
734
|
// quickJoin's first try at a room: rather another room than one with someone this player blocked.
|
|
690
735
|
if (room._avoidsBlockedPlayers) hello.avoidBlockedPlayers = true;
|
|
691
736
|
// Any quickJoin: Zaisma learns the game joins players by itself, for its Rooms tab.
|
|
@@ -795,6 +840,10 @@
|
|
|
795
840
|
} else if (message.type === "ending_cancelled") {
|
|
796
841
|
room.endsAt = null;
|
|
797
842
|
room._emit("endingCancelled");
|
|
843
|
+
} else if (message.type === "room_message") {
|
|
844
|
+
// A room message for a room whose game server has no onRoomMessage: another room, the game server or the developer sent it.
|
|
845
|
+
var roomMessage = message.roomMessage || {};
|
|
846
|
+
room._emit("roomMessage", { message: roomMessage.message, senderKind: roomMessage.senderKind, fromRoomCode: roomMessage.fromRoomCode || null, sentAt: roomMessage.sentAt });
|
|
798
847
|
} else if (message.type === "version_update") {
|
|
799
848
|
var versionUpdate = { versionId: message.versionId, appliesAt: message.appliesAt, isReloading: message.isReloading === true };
|
|
800
849
|
if (message.step === "now") room._isReloadingForVersion = versionUpdate.isReloading;
|
|
@@ -1523,7 +1572,9 @@
|
|
|
1523
1572
|
|
|
1524
1573
|
function connect(roomCode, joinOptions, isRetry) {
|
|
1525
1574
|
var serverUrl = joinOptions.serverUrl ? String(joinOptions.serverUrl).replace(/\/+$/, "") : null;
|
|
1526
|
-
|
|
1575
|
+
var offRefusal = offZaismaRefusal(serverUrl, "Zaisma.joinRoom");
|
|
1576
|
+
if (offRefusal) return Promise.reject(offRefusal);
|
|
1577
|
+
var isOffZaisma = !config.rooms;
|
|
1527
1578
|
var idleTimeoutSec = joinOptions.idleTimeoutSec;
|
|
1528
1579
|
if (idleTimeoutSec !== undefined && !(typeof idleTimeoutSec === "number" && idleTimeoutSec >= MINIMUM_IDLE_TIMEOUT_SEC && idleTimeoutSec <= MAXIMUM_IDLE_TIMEOUT_SEC)) return Promise.reject(codedError("bad_idle_timeout", "idleTimeoutSec runs from " + MINIMUM_IDLE_TIMEOUT_SEC + " to " + MAXIMUM_IDLE_TIMEOUT_SEC + "."));
|
|
1529
1580
|
if (serverUrl && !SERVER_URL.test(serverUrl)) return Promise.reject(codedError("bad_server_url", "serverUrl takes a full address starting with wss://, ws://, https:// or http://, like \"wss://play.example.com\"."));
|
|
@@ -1535,12 +1586,14 @@
|
|
|
1535
1586
|
: serverUrl
|
|
1536
1587
|
? Promise.resolve(randomRoomCode())
|
|
1537
1588
|
: post("/v1/rooms", { gameSlug: config.game }).then(function (answer) { return answer.roomCode; });
|
|
1538
|
-
|
|
1589
|
+
// Off Zaisma there's no pass: the hello carries the playerToken instead.
|
|
1590
|
+
return Promise.all([roomCodeReady, isOffZaisma ? null : getPass()]).then(function (ready) {
|
|
1539
1591
|
var base = (serverUrl || config.rooms).replace(/^http/, "ws");
|
|
1540
|
-
var path = "/v1/rooms/" + config.game + "/" + ready[0];
|
|
1592
|
+
var path = "/v1/rooms/" + (isOffZaisma ? offZaisma.gameSlug : config.game) + "/" + ready[0];
|
|
1541
1593
|
var room = new Room({
|
|
1542
1594
|
roomCode: ready[0],
|
|
1543
|
-
pass: ready[1].pass,
|
|
1595
|
+
pass: isOffZaisma ? null : ready[1].pass,
|
|
1596
|
+
isOffZaisma: isOffZaisma,
|
|
1544
1597
|
url: base + path,
|
|
1545
1598
|
// The fast lane reaches only the game's own secure servers: Zaisma's rooms take the WebSocket, whatever transport says.
|
|
1546
1599
|
fastLaneUrl: joinOptions.transport === "webtransport" && serverUrl && OWN_SERVER_URL.test(serverUrl) ? serverUrl.replace(/^wss:/, "https:") + path : null,
|
|
@@ -1562,8 +1615,8 @@
|
|
|
1562
1615
|
room._ready = { resolve: resolve, reject: reject };
|
|
1563
1616
|
room._connect();
|
|
1564
1617
|
}).catch(function (error) {
|
|
1565
|
-
// A pass this tab kept from before Zaisma changed its keys: get a new one and try once more.
|
|
1566
|
-
if (error.code !== "bad_pass" || isRetry) throw error;
|
|
1618
|
+
// A pass this tab kept from before Zaisma changed its keys: get a new one and try once more. Off Zaisma, only the game can get a new token.
|
|
1619
|
+
if (error.code !== "bad_pass" || isRetry || isOffZaisma) throw error;
|
|
1567
1620
|
try { sessionStorage.removeItem(passKey); } catch (storageError) { /* fine */ }
|
|
1568
1621
|
return connect(room.roomCode, joinOptions, true);
|
|
1569
1622
|
});
|
|
@@ -1766,13 +1819,15 @@
|
|
|
1766
1819
|
|
|
1767
1820
|
/**
|
|
1768
1821
|
* A request to the game's own game server, its onRequest, outside any room: what it answered
|
|
1769
|
-
* (zaisma.d.ts: Zaisma.server.request). The player's pass goes with it, so the server knows who's asking
|
|
1822
|
+
* (zaisma.d.ts: Zaisma.server.request). The player's pass goes with it, so the server knows who's asking;
|
|
1823
|
+
* off Zaisma, the playerToken Zaisma.setUpOffZaisma holds.
|
|
1770
1824
|
*/
|
|
1771
1825
|
function serverRequest(request, requestOptions, isRetry) {
|
|
1772
1826
|
var refused = unknownOption(requestOptions, REQUEST_OPTIONS, "Zaisma.server.request");
|
|
1773
1827
|
if (refused) return Promise.reject(refused);
|
|
1774
1828
|
var serverUrl = requestOptions && requestOptions.serverUrl ? String(requestOptions.serverUrl).replace(/\/+$/, "") : null;
|
|
1775
|
-
|
|
1829
|
+
var offRefusal = offZaismaRefusal(serverUrl, "Zaisma.server.request");
|
|
1830
|
+
if (offRefusal) return Promise.reject(offRefusal);
|
|
1776
1831
|
if (serverUrl && !SERVER_URL.test(serverUrl)) return Promise.reject(codedError("bad_server_url", "serverUrl takes a full address starting with https://, http://, wss:// or ws://, like \"https://play.example.com\"."));
|
|
1777
1832
|
var requestText;
|
|
1778
1833
|
try {
|
|
@@ -1783,13 +1838,17 @@
|
|
|
1783
1838
|
if (requestText === undefined) return Promise.reject(codedError("bad_request", "A request is plain JSON: numbers, text, true or false, null, lists and plain objects, like { type: \"buy\", itemId: \"sword\" }."));
|
|
1784
1839
|
if (new TextEncoder().encode(requestText).length > MAXIMUM_REQUEST_SIZE_BYTES) return Promise.reject(codedError("too_big", "Requests are up to 16 KB as JSON."));
|
|
1785
1840
|
var base = (serverUrl || config.rooms).replace(/^ws/, "http");
|
|
1786
|
-
|
|
1787
|
-
|
|
1841
|
+
var isOffZaisma = !config.rooms;
|
|
1842
|
+
// Off Zaisma there's no pass: the playerToken goes in its place.
|
|
1843
|
+
var sender = isOffZaisma ? Promise.resolve({ playerToken: offZaisma.playerToken }) : getPass().then(function (mine) { return { pass: mine.pass }; });
|
|
1844
|
+
return sender.then(function (who) {
|
|
1845
|
+
who.request = JSON.parse(requestText);
|
|
1846
|
+
return fetch(base + "/v1/requests/" + (isOffZaisma ? offZaisma.gameSlug : config.game), { method: "POST", headers: { "Content-Type": "text/plain" }, body: JSON.stringify(who) }).then(answered, requestFailed);
|
|
1788
1847
|
}).then(function (answer) {
|
|
1789
1848
|
return answer.answer;
|
|
1790
1849
|
}, function (error) {
|
|
1791
|
-
// A pass this tab kept from before Zaisma changed its keys: get a new one and try once more.
|
|
1792
|
-
if (error.code !== "bad_pass" || isRetry) throw error;
|
|
1850
|
+
// A pass this tab kept from before Zaisma changed its keys: get a new one and try once more. Off Zaisma, only the game can get a new token.
|
|
1851
|
+
if (error.code !== "bad_pass" || isRetry || isOffZaisma) throw error;
|
|
1793
1852
|
try { sessionStorage.removeItem(passKey); } catch (storageError) { /* fine */ }
|
|
1794
1853
|
return serverRequest(request, requestOptions, true);
|
|
1795
1854
|
});
|
|
@@ -2694,5 +2753,5 @@
|
|
|
2694
2753
|
});
|
|
2695
2754
|
|
|
2696
2755
|
var friends = { list: listFriends, setRoom: setOwnRoom, sendRequest: sendFriendRequest };
|
|
2697
|
-
window.Zaisma = { sdkVersionNumber: 1, gameSlug: config.game || null, player: player, joinRoom: joinRoom, rooms: rooms, server: server, invite: invite, checkChat: checkChat, openPlayersPanel: openPlayersPanel, links: links, items: items, friends: friends, saves: saves, versions: versions, page: zaismaPage };
|
|
2756
|
+
window.Zaisma = { sdkVersionNumber: 1, gameSlug: config.game || null, player: player, setUpOffZaisma: setUpOffZaisma, joinRoom: joinRoom, rooms: rooms, server: server, invite: invite, checkChat: checkChat, openPlayersPanel: openPlayersPanel, links: links, items: items, friends: friends, saves: saves, versions: versions, page: zaismaPage };
|
|
2698
2757
|
})();
|
package/src/chat.d.ts
CHANGED
|
@@ -9,6 +9,11 @@
|
|
|
9
9
|
* checkChat sends the players' passes (as your server received them) and the sender's last few messages to
|
|
10
10
|
* Zaisma (POST <issuer>/v1/chat/check), so a phone number sent a few digits at a time is caught too.
|
|
11
11
|
*
|
|
12
|
+
* A player your server let in from outside Zaisma (kind outside_zaisma) has no pass: give null in their place.
|
|
13
|
+
* Zaisma counts them as strangers of unknown age, so a message between one and an under-18 account is stopped
|
|
14
|
+
* (not_allowed_to_chat); between them and adults it's checked as between adults. A message among players from
|
|
15
|
+
* outside Zaisma only is allowed without asking Zaisma: none of its players is in it, so your own rules apply.
|
|
16
|
+
*
|
|
12
17
|
* It's for your own server only. In Zaisma rooms the room filters the chat it delivers itself, so a game
|
|
13
18
|
* server running there never sees passes, and checkChat throws a ChatCheckError (own_server_only).
|
|
14
19
|
*/
|
|
@@ -36,10 +41,10 @@ export interface ChatCheck {
|
|
|
36
41
|
export interface ChatCheckOptions {
|
|
37
42
|
/** The message. */
|
|
38
43
|
text: string;
|
|
39
|
-
/** The sender's player pass, the signed token as your server received it. */
|
|
40
|
-
senderPass: string;
|
|
41
|
-
/** The passes of everyone it would reach, up to 50. */
|
|
42
|
-
receiverPasses: string[];
|
|
44
|
+
/** The sender's player pass, the signed token as your server received it; null for a player from outside Zaisma. */
|
|
45
|
+
senderPass: string | null;
|
|
46
|
+
/** The passes of everyone it would reach, up to 50; null for each player from outside Zaisma. */
|
|
47
|
+
receiverPasses: (string | null)[];
|
|
43
48
|
/** Check chat between adults too, as for under-18s; each message Zaisma's AI looks at counts on your hosting meter. False by default. */
|
|
44
49
|
isAdultFilterEnabled?: boolean;
|
|
45
50
|
}
|
package/src/chat.js
CHANGED
|
@@ -9,6 +9,11 @@
|
|
|
9
9
|
* checkChat sends the players' passes (as your server received them) and the sender's last few messages to
|
|
10
10
|
* Zaisma (POST <issuer>/v1/chat/check), so a phone number sent a few digits at a time is caught too.
|
|
11
11
|
*
|
|
12
|
+
* A player your server let in from outside Zaisma (kind outside_zaisma) has no pass: give null in their place.
|
|
13
|
+
* Zaisma counts them as strangers of unknown age, so a message between one and an under-18 account is stopped
|
|
14
|
+
* (not_allowed_to_chat); between them and adults it's checked as between adults. A message among players from
|
|
15
|
+
* outside Zaisma only is allowed without asking Zaisma: none of its players is in it, so your own rules apply.
|
|
16
|
+
*
|
|
12
17
|
* It's for your own server only. In Zaisma rooms the room filters the chat it delivers itself, so a game
|
|
13
18
|
* server running there never sees passes, and checkChat throws a ChatCheckError (own_server_only).
|
|
14
19
|
*/
|
|
@@ -84,15 +89,26 @@ function rememberText(senderPlayerId, text, nowMs) {
|
|
|
84
89
|
export async function checkChat({ text, senderPass, receiverPasses }) {
|
|
85
90
|
// Read apart from the destructuring, which is v1's signature as it's locked (test/contracts).
|
|
86
91
|
const isAdultFilterEnabled = arguments[0]?.isAdultFilterEnabled === true;
|
|
87
|
-
|
|
88
|
-
if (typeof claims.sub !== "string" || !isZaismaIssuer(claims.iss) || !Array.isArray(receiverPasses))
|
|
92
|
+
if (!Array.isArray(receiverPasses) || !receiverPasses.every((pass) => pass === null || typeof pass === "string") || !(senderPass === null || typeof senderPass === "string"))
|
|
89
93
|
return UNCHECKED;
|
|
90
94
|
const message = String(text ?? "");
|
|
91
95
|
if (message.length > CHAT_CHECK_LIMITS.maximumMessageCharacterCount)
|
|
92
96
|
return TOO_LONG;
|
|
97
|
+
// Among players from outside Zaisma only: no Zaisma player is in it, so the server's own rules apply, and Zaisma never hears of it.
|
|
98
|
+
const firstPass = [senderPass, ...receiverPasses].find((pass) => typeof pass === "string");
|
|
99
|
+
if (firstPass === undefined)
|
|
100
|
+
return { isAllowed: true, reason: null, message: null, hasSwearing: false, hasSlur: false };
|
|
101
|
+
// Where to ask comes from a pass, the sender's when they have one: they all come from the same issuer for the same game.
|
|
102
|
+
const claims = readClaims(firstPass);
|
|
103
|
+
const senderClaims = senderPass === null ? null : readClaims(senderPass);
|
|
104
|
+
if (typeof claims.sub !== "string" || !isZaismaIssuer(claims.iss) || (senderClaims && typeof senderClaims.sub !== "string"))
|
|
105
|
+
return UNCHECKED;
|
|
93
106
|
const nowMs = Date.now();
|
|
94
|
-
|
|
95
|
-
|
|
107
|
+
// A sender from outside Zaisma has no player ID Zaisma knows, so their last messages aren't sent along.
|
|
108
|
+
const senderPlayerId = senderClaims ? senderClaims.sub : null;
|
|
109
|
+
const recentTexts = senderPlayerId ? (recentBySender.get(senderPlayerId) ?? []).filter((sent) => nowMs - sent.sentAtMs < CHAT_CHECK_LIMITS.recentTextDurationMs).map((sent) => sent.text) : [];
|
|
110
|
+
if (senderPlayerId)
|
|
111
|
+
rememberText(senderPlayerId, message, nowMs);
|
|
96
112
|
try {
|
|
97
113
|
const response = await fetch(`${claims.iss}/v1/chat/check`, {
|
|
98
114
|
method: "POST",
|
package/src/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { BUSY_CLOSE_CODE, ENDED_CLOSE_CODE, GAME_SERVER_OPTIONS, GameServerError, NO_ANSWER_CLOSE_CODE, REMOVED_CLOSE_CODE, ROOM_LIMITS, SEAT_GONE_CLOSE_CODE, VERSION_UPDATE_CLOSE_CODE, defineGameServer, type Audience, type AwayInfo, type AwayMode, type AwayReason, type BroadcastOptions, type Delivery, type GameServer, type GameServerErrorCode, type HandlerResult, type IdleCheck, type JoinAnswer, type ChatAnswer, type ChatInfo, type JoinInfo, type KickOptions, type LeaveInfo, type LeaveReason, type MoveOptions, type MoveResult, type Player, type Purchase, type Refusal, type ReserveOptions, type RestoreInfo, type Room, type RoomErrorCode, type RoomInfo, type SendOptions, type VersionUpdate, } from "./room.ts";
|
|
1
|
+
export { BUSY_CLOSE_CODE, ENDED_CLOSE_CODE, GAME_SERVER_OPTIONS, GameServerError, NO_ANSWER_CLOSE_CODE, REMOVED_CLOSE_CODE, ROOM_LIMITS, ROOM_MESSAGE_LIMITS, ROOM_MESSAGE_SENDER_KINDS, SEAT_GONE_CLOSE_CODE, VERSION_UPDATE_CLOSE_CODE, defineGameServer, type Audience, type AwayInfo, type AwayMode, type AwayReason, type BroadcastOptions, type Delivery, type GameServer, type GameServerErrorCode, type HandlerResult, type IdleCheck, type JoinAnswer, type ChatAnswer, type ChatInfo, type JoinInfo, type KickOptions, type LiveRoom, type LeaveInfo, type LeaveReason, type MoveOptions, type MoveResult, type Player, type Purchase, type Refusal, type ReserveOptions, type RestoreInfo, type Room, type RoomErrorCode, type RoomInfo, type RoomMessage, type RoomMessageSenderKind, type RoomPlayer, type RoomSelection, type SendOptions, type VersionUpdate, } from "./room.ts";
|
|
2
2
|
export { REQUEST_LIMITS, type RequestErrorCode, type Server } from "./requests.ts";
|
|
3
3
|
export { SCHEDULE_LIMITS, type Schedule, type ScheduledRun } from "./schedules.ts";
|
|
4
4
|
export { WEBHOOK_LIMITS, verifyWebhook, type WebhookError, type WebhookErrorCode, type WebhookEvent, type WebhookEventKind, type WebhookHeaders } from "./webhooks.ts";
|
|
@@ -7,4 +7,5 @@ export { OUTSIDE_CALL_LIMITS, OutsideCallError, type OutsideCallErrorCode } from
|
|
|
7
7
|
export { PASS_ISSUER_URL, PASS_TOKEN_TYPE, PASS_VERSION_NUMBER, PassError, RECEIPT_ISSUER_URL, RECEIPT_TOKEN_TYPE, RECEIPT_VERSION_NUMBER, ReceiptError, canChat, passesAreFriends, passesHaveBlockBetween, verifyPass, verifyReceipt, type PassClaims, type PassErrorCode, type PassExpectations, type PlayerKind, type PublicKey, type ReceiptClaims, type ReceiptErrorCode, type ReceiptExpectations, type WhoCanChat } from "./pass.ts";
|
|
8
8
|
export { CHAT_CHECK_LIMITS, CHAT_ROOM_LIMITS, ChatCheckError, checkChat, type ChatCheck, type ChatCheckOptions, type ChatCheckReason } from "./chat.ts";
|
|
9
9
|
export { guestName, isRoomCode } from "./names.ts";
|
|
10
|
+
export { guestPlayer, signInWithZaismaPlayers, type IdentifyPlayer, type OutsidePlayer, type SignInWithZaismaPlayersOptions } from "./outside-players.ts";
|
|
10
11
|
export { relay } from "./relay.ts";
|
package/src/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// What @zaisma/game-server gives developers, named one by one: from v1 it only grows (test/contracts).
|
|
2
2
|
// Zaisma's own parts of the package, never locked, are in ./internal.
|
|
3
|
-
export { BUSY_CLOSE_CODE, ENDED_CLOSE_CODE, GAME_SERVER_OPTIONS, GameServerError, NO_ANSWER_CLOSE_CODE, REMOVED_CLOSE_CODE, ROOM_LIMITS, SEAT_GONE_CLOSE_CODE, VERSION_UPDATE_CLOSE_CODE, defineGameServer, } from "./room.js";
|
|
3
|
+
export { BUSY_CLOSE_CODE, ENDED_CLOSE_CODE, GAME_SERVER_OPTIONS, GameServerError, NO_ANSWER_CLOSE_CODE, REMOVED_CLOSE_CODE, ROOM_LIMITS, ROOM_MESSAGE_LIMITS, ROOM_MESSAGE_SENDER_KINDS, SEAT_GONE_CLOSE_CODE, VERSION_UPDATE_CLOSE_CODE, defineGameServer, } from "./room.js";
|
|
4
4
|
export { REQUEST_LIMITS } from "./requests.js";
|
|
5
5
|
export { SCHEDULE_LIMITS } from "./schedules.js";
|
|
6
6
|
export { WEBHOOK_LIMITS, verifyWebhook } from "./webhooks.js";
|
|
@@ -9,4 +9,5 @@ export { OUTSIDE_CALL_LIMITS, OutsideCallError } from "./outside-calls.js";
|
|
|
9
9
|
export { PASS_ISSUER_URL, PASS_TOKEN_TYPE, PASS_VERSION_NUMBER, PassError, RECEIPT_ISSUER_URL, RECEIPT_TOKEN_TYPE, RECEIPT_VERSION_NUMBER, ReceiptError, canChat, passesAreFriends, passesHaveBlockBetween, verifyPass, verifyReceipt } from "./pass.js";
|
|
10
10
|
export { CHAT_CHECK_LIMITS, CHAT_ROOM_LIMITS, ChatCheckError, checkChat } from "./chat.js";
|
|
11
11
|
export { guestName, isRoomCode } from "./names.js";
|
|
12
|
+
export { guestPlayer, signInWithZaismaPlayers } from "./outside-players.js";
|
|
12
13
|
export { relay } from "./relay.js";
|
package/src/internal.d.ts
CHANGED
package/src/internal.js
CHANGED
|
@@ -7,6 +7,7 @@ export * from "./database.js";
|
|
|
7
7
|
export * from "./dump.js";
|
|
8
8
|
export * from "./requests.js";
|
|
9
9
|
export * from "./outside-calls.js";
|
|
10
|
+
export * from "./outside-players.js";
|
|
10
11
|
export * from "./schedules.js";
|
|
11
12
|
export * from "./webhooks.js";
|
|
12
13
|
export * from "./pass.js";
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Players from outside Zaisma, for a game server on your own server: a game played anywhere but its Zaisma
|
|
3
|
+
* page (exported, on another site, wrapped for a store) has no Zaisma pass, so its game sends a playerToken
|
|
4
|
+
* instead (Zaisma.setUpOffZaisma) and your server says who that is with startServer's identifyPlayer. Such a
|
|
5
|
+
* player joins with kind "outside_zaisma", in the same rooms as players from the Zaisma page.
|
|
6
|
+
*
|
|
7
|
+
* signInWithZaismaPlayers makes an identifyPlayer for players signed in with Zaisma (Sign in with Zaisma's ID
|
|
8
|
+
* tokens): the same player ID and name as on Zaisma. guestPlayer is who a player with no token is, when
|
|
9
|
+
* startServer's outsideGuestsEnabled lets them in: a temporary ID and a generated name, like Zaisma's guests.
|
|
10
|
+
*/
|
|
11
|
+
import { type PublicKey } from "./pass.ts";
|
|
12
|
+
/** Who a player from outside Zaisma is, as identifyPlayer answers: an ID your server keeps for them, and the name others see. */
|
|
13
|
+
export interface OutsidePlayer {
|
|
14
|
+
/** 1 to 100 characters: a Zaisma player ID for a player signed in with Zaisma, or your own accounts' IDs. */
|
|
15
|
+
id: string;
|
|
16
|
+
/** 1 to 40 characters, as other players see it. */
|
|
17
|
+
name: string;
|
|
18
|
+
}
|
|
19
|
+
/** startServer's identifyPlayer: who a playerToken is, or null when it isn't one your server takes. */
|
|
20
|
+
export type IdentifyPlayer = (playerToken: string, connection: {
|
|
21
|
+
origin: string | null;
|
|
22
|
+
}) => Promise<OutsidePlayer | null>;
|
|
23
|
+
export interface SignInWithZaismaPlayersOptions {
|
|
24
|
+
/** Your sign-in apps' IDs (sign_in_app_…), 1 to 20: an ID token for any other app is refused. */
|
|
25
|
+
signInAppIds: string[];
|
|
26
|
+
/** Where Sign in with Zaisma runs, the ID tokens' iss: https://zaisma.com unless a local preview of Zaisma. */
|
|
27
|
+
siteUrl?: string;
|
|
28
|
+
/** async (isRefreshing) => the ID token keys; by default from <siteUrl>/.well-known/jwks.json, fetched again once on a key it doesn't know. */
|
|
29
|
+
loadKeys?: (isRefreshing: boolean) => Promise<PublicKey[]>;
|
|
30
|
+
/** async (playerId) => an account's public profile or null, for a token with no name (signed in without the profile scope). */
|
|
31
|
+
loadProfile?: (playerId: string) => Promise<{
|
|
32
|
+
name: string;
|
|
33
|
+
} | null>;
|
|
34
|
+
}
|
|
35
|
+
export declare const OUTSIDE_PLAYER_LIMITS: {
|
|
36
|
+
readonly maximumIdCharacterCount: 100;
|
|
37
|
+
readonly maximumSignInAppCount: 20;
|
|
38
|
+
/** How far an ID token's exp may be past, for clocks that differ a little. */
|
|
39
|
+
readonly clockLeewaySec: 60;
|
|
40
|
+
/** How long the keys and profiles are kept before they're fetched again. */
|
|
41
|
+
readonly keysKeptMs: 3600000;
|
|
42
|
+
readonly profileKeptMs: 300000;
|
|
43
|
+
/** A refresh for a key ID it doesn't know waits this long after the last fetch, so made-up key IDs can't make it fetch on every token. */
|
|
44
|
+
readonly minimumRefreshIntervalMs: 30000;
|
|
45
|
+
readonly maximumProfileCount: 10000;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* An identifyPlayer for players signed in with Zaisma: the game signs them in with Sign in with Zaisma and
|
|
49
|
+
* sends the ID token as its playerToken. The token is checked like any OpenID Connect ID token: RS256 only,
|
|
50
|
+
* signed by Zaisma's published key, its iss the siteUrl, its aud one of signInAppIds, not expired. The player
|
|
51
|
+
* is { id, name }: the account's Zaisma player ID, the same as on Zaisma, and its name from the token (or its
|
|
52
|
+
* public profile when the token has none). Answers null for any token that doesn't check out, and for an
|
|
53
|
+
* account deleted since; throws only when Zaisma's keys or profiles can't be reached (the runner answers busy).
|
|
54
|
+
*
|
|
55
|
+
* startServer({ gameServer, gameSlug, identifyPlayer: signInWithZaismaPlayers({ signInAppIds: ["sign_in_app_8f3k2m"] }) });
|
|
56
|
+
*/
|
|
57
|
+
export declare function signInWithZaismaPlayers(options: SignInWithZaismaPlayersOptions): IdentifyPlayer;
|
|
58
|
+
/** A player with no token, when startServer's outsideGuestsEnabled lets them in: a temporary player ID and a generated name, as Zaisma's guests have. */
|
|
59
|
+
export declare function guestPlayer(): OutsidePlayer;
|
|
60
|
+
/** What identifyPlayer answered, checked for the runner: the player, with their name tidied, or what was wrong with it. */
|
|
61
|
+
export declare function checkedOutsidePlayer(given: unknown): {
|
|
62
|
+
player: OutsidePlayer;
|
|
63
|
+
} | {
|
|
64
|
+
mistake: string;
|
|
65
|
+
};
|