@zaisma/game-server 1.13.0 → 1.15.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/glass.js +8655 -448
- package/bin/node-parts.js +5 -1
- package/bin/node.d.ts +10 -1
- package/bin/node.js +70 -31
- package/package.json +1 -1
- package/sdk/zaisma.d.ts +59 -8
- package/sdk/zaisma.js +72 -17
- package/src/chat.d.ts +9 -4
- package/src/chat.js +20 -4
- package/src/index.d.ts +1 -0
- package/src/index.js +1 -0
- 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 +17 -2
- package/src/requests.js +15 -8
- package/src/room.d.ts +11 -3
- package/src/room.js +12 -2
package/bin/node-parts.js
CHANGED
|
@@ -289,7 +289,7 @@ export function secretsFromEnvironment() {
|
|
|
289
289
|
return Object.freeze(Object.fromEntries(Object.entries(process.env).filter(([, environmentValue]) => typeof environmentValue === "string")));
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
-
const OPTION_NAMES = ["gameServer", "gameSlug", "port", "origins", "issuerUrl", "loadPassKeys", "loadReceiptKeys", "loadProfile", "database", "secrets", "webhookSigningSecret", "roomStateFile", "zaismaKey", "developerApiUrl", "webtransportPort", "certificatePath", "certificateKeyPath"];
|
|
292
|
+
const OPTION_NAMES = ["gameServer", "gameSlug", "port", "origins", "issuerUrl", "loadPassKeys", "loadReceiptKeys", "loadProfile", "database", "secrets", "webhookSigningSecret", "roomStateFile", "zaismaKey", "developerApiUrl", "webtransportPort", "certificatePath", "certificateKeyPath", "identifyPlayer", "outsideGuestsEnabled"];
|
|
293
293
|
|
|
294
294
|
/**
|
|
295
295
|
* Check what startServer was given: unknown options and options of the wrong kind are a GameServerError,
|
|
@@ -325,6 +325,10 @@ export function checkedStartServerOptions(given, issuerUrlDefault) {
|
|
|
325
325
|
if (options.roomStateFile !== undefined && options.roomStateFile !== null && !isText(options.roomStateFile)) throw bad("roomStateFile", "a file path", "Give a file path as text, like \"zaisma-room-states.sqlite\", or null to keep no states.");
|
|
326
326
|
if (options.zaismaKey !== undefined && !isText(options.zaismaKey)) throw bad("zaismaKey", "an API key", "Give an API key with purchase_access write for this game as text, or leave it out to use ZAISMA_KEY.");
|
|
327
327
|
if (options.developerApiUrl !== undefined && (!isText(options.developerApiUrl) || !URL.canParse(options.developerApiUrl))) throw bad("developerApiUrl", "a URL", 'Give the address of Zaisma\'s developer API, like "https://zaisma.com/api/dev".');
|
|
328
|
+
// Players from outside Zaisma (games played off their Zaisma page): who a playerToken is, and whether players with none come in as guests.
|
|
329
|
+
if (options.identifyPlayer !== undefined && !isFunction(options.identifyPlayer)) throw bad("identifyPlayer", "a function", "Give async (playerToken, { origin }) => ({ id, name }) or null, like signInWithZaismaPlayers({ signInAppIds }) makes.");
|
|
330
|
+
options.outsideGuestsEnabled ??= false;
|
|
331
|
+
if (typeof options.outsideGuestsEnabled !== "boolean") throw bad("outsideGuestsEnabled", "true or false", "Give true to let players with no playerToken in as guests, or false (the default).");
|
|
328
332
|
// The fast lane (WebTransport): its UDP port and the certificate it holds itself, all three or none.
|
|
329
333
|
const fastLaneFields = ["webtransportPort", "certificatePath", "certificateKeyPath"];
|
|
330
334
|
const missingFastLaneFields = fastLaneFields.filter((field) => options[field] === undefined);
|
package/bin/node.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Server } from "node:http";
|
|
2
|
-
import type { GameDatabase, GameServer, PublicKey } from "../src/index.js";
|
|
2
|
+
import type { GameDatabase, GameServer, IdentifyPlayer, PublicKey } from "../src/index.js";
|
|
3
3
|
|
|
4
4
|
/** What startServer takes. Only gameServer and gameSlug are required; the rest have the defaults named here. */
|
|
5
5
|
export interface StartServerOptions {
|
|
@@ -41,6 +41,15 @@ export interface StartServerOptions {
|
|
|
41
41
|
certificatePath?: string;
|
|
42
42
|
/** The fast lane's certificate key, a PEM file (Let's Encrypt's privkey.pem). */
|
|
43
43
|
certificateKeyPath?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Who a player from outside Zaisma is: a game played off its Zaisma page sends a playerToken in place of a pass
|
|
46
|
+
* (Zaisma.setUpOffZaisma), and this answers { id, name } for it, or null to refuse (bad_player_token). They join
|
|
47
|
+
* with kind "outside_zaisma". signInWithZaismaPlayers({ signInAppIds }) makes one for Sign in with Zaisma; your
|
|
48
|
+
* own checks your game's own accounts. One that throws answers busy. None by default: only Zaisma passes.
|
|
49
|
+
*/
|
|
50
|
+
identifyPlayer?: IdentifyPlayer;
|
|
51
|
+
/** Let players from outside Zaisma with no token (playerToken: null) in as guests (guestPlayer): false by default. */
|
|
52
|
+
outsideGuestsEnabled?: boolean;
|
|
44
53
|
}
|
|
45
54
|
|
|
46
55
|
export interface RunningServer {
|
package/bin/node.js
CHANGED
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
//
|
|
8
8
|
// Players connect to wss://your-server/v1/rooms/<game>/<code>, the same address
|
|
9
9
|
// layout as Zaisma rooms, and prove who they are with Zaisma player passes,
|
|
10
|
-
// checked against the keys Zaisma publishes.
|
|
10
|
+
// checked against the keys Zaisma publishes. A game played off its Zaisma page sends a playerToken instead
|
|
11
|
+
// (Zaisma.setUpOffZaisma), and identifyPlayer says who that is (signInWithZaismaPlayers for players signed in
|
|
12
|
+
// with Zaisma, or your own accounts); outsideGuestsEnabled lets players with none in as guests. Put it behind HTTPS (Caddy, nginx,
|
|
11
13
|
// a Cloudflare Tunnel) and add the address to your game's own servers (own_server_urls, or zaisma own-servers).
|
|
12
14
|
// The game's requests (onRequest) come to POST /v1/requests/<game> on the same port, and its
|
|
13
15
|
// schedules (onSchedule) run while it's up, at the minutes their crons give in UTC. A Zaisma webhook
|
|
@@ -24,7 +26,7 @@ import { createServer } from "node:http";
|
|
|
24
26
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
25
27
|
import { resolve } from "node:path";
|
|
26
28
|
import { readFileSync, realpathSync } from "node:fs";
|
|
27
|
-
import { BUSY_CLOSE_CODE, ENDED_CLOSE_CODE, ERROR_TEXT, NO_ANSWER_CLOSE_CODE, PASS_ISSUER_URL, PassError, ReceiptError, REMOVED_CLOSE_CODE, REQUEST_ERROR_HTTP_STATUS, REQUEST_LIMITS, ROOM_LIMITS, ROOM_MESSAGE_LIMITS, ROOM_TUNING, WEBHOOK_LIMITS, GameServerError, RoomCore, chatSettingsOf, checkChat, readChatMessage, restoreInfoOf, passesAreFriends, passesHaveBlockBetween, answerRequest, startSchedules, checkRoomData, clientIdle, defineGameServer, errorMessage, newServer, isRoomCode, readRequestBody, relay, requestError, runWebhook, tidyRoomInfo, verifyPass, verifyReceipt, verifyWebhook } from "../src/internal.js";
|
|
29
|
+
import { BUSY_CLOSE_CODE, ENDED_CLOSE_CODE, ERROR_TEXT, NO_ANSWER_CLOSE_CODE, PASS_ISSUER_URL, OUTSIDE_ZAISMA_CHAT_SETTINGS, PassError, ReceiptError, REMOVED_CLOSE_CODE, REQUEST_ERROR_HTTP_STATUS, REQUEST_LIMITS, ROOM_LIMITS, ROOM_MESSAGE_LIMITS, ROOM_TUNING, WEBHOOK_LIMITS, GameServerError, RoomCore, chatSettingsOf, checkChat, readChatMessage, restoreInfoOf, passesAreFriends, passesHaveBlockBetween, answerRequest, startSchedules, checkRoomData, checkedOutsidePlayer, clientIdle, defineGameServer, errorMessage, guestPlayer, newServer, isPlayerToken, isRoomCode, readRequestBody, relay, requestError, runWebhook, tidyRoomInfo, verifyPass, verifyReceipt, verifyWebhook } from "../src/internal.js";
|
|
28
30
|
import { OutsideCallError } from "../src/outside-calls.js";
|
|
29
31
|
import { startFastLane } from "./fast-lane.js";
|
|
30
32
|
import { checkedStartServerOptions, localDatabase, localRoomStates, passKeysFrom, profilesFrom, receiptKeysFrom, secretsFromEnvironment } from "./node-parts.js";
|
|
@@ -74,11 +76,15 @@ const CATCH_UP_MS = 50;
|
|
|
74
76
|
* the ZAISMA_KEY environment variable) and developerApiUrl (Zaisma's developer API; by default https://zaisma.com/api/dev).
|
|
75
77
|
* webtransportPort (a UDP port), certificatePath and certificateKeyPath (the server's certificate and its key,
|
|
76
78
|
* PEM files), all three or none, also serve the fast lane (WebTransport) beside the WebSocket.
|
|
79
|
+
* identifyPlayer (async (playerToken, { origin }) => ({ id, name }) or null; none by default) says who a player from
|
|
80
|
+
* outside Zaisma is by the playerToken their game sent (signInWithZaismaPlayers makes one for Sign in with Zaisma),
|
|
81
|
+
* and outsideGuestsEnabled (false by default) lets players with no token (playerToken: null) in as guests (guestPlayer).
|
|
82
|
+
* Both join with kind "outside_zaisma".
|
|
77
83
|
* Answers { close(), httpServer, fastLaneReady }: close() saves every room first and finishes once the server
|
|
78
84
|
* has stopped; fastLaneReady settles once the fast lane listens (at once without one), or rejects saying why it can't.
|
|
79
85
|
*/
|
|
80
86
|
export function startServer(options) {
|
|
81
|
-
const { gameServer, gameSlug, port: portNumber, origins, issuerUrl, loadPassKeys = passKeysFrom(`${issuerUrl}/.well-known/jwks.json`), loadReceiptKeys = receiptKeysFrom(`${issuerUrl}/.well-known/receipt-jwks.json`), loadProfile = profilesFrom(issuerUrl), database = localDatabase("zaisma-database.sqlite"), secrets = secretsFromEnvironment(), webhookSigningSecret = process.env.ZAISMA_WEBHOOK_SIGNING_SECRET, roomStateFile = "zaisma-room-states.sqlite", zaismaKey = process.env.ZAISMA_KEY, developerApiUrl = "https://zaisma.com/api/dev", webtransportPort, certificatePath, certificateKeyPath } = checkedStartServerOptions(options, PASS_ISSUER_URL);
|
|
87
|
+
const { gameServer, gameSlug, port: portNumber, origins, issuerUrl, loadPassKeys = passKeysFrom(`${issuerUrl}/.well-known/jwks.json`), loadReceiptKeys = receiptKeysFrom(`${issuerUrl}/.well-known/receipt-jwks.json`), loadProfile = profilesFrom(issuerUrl), database = localDatabase("zaisma-database.sqlite"), secrets = secretsFromEnvironment(), webhookSigningSecret = process.env.ZAISMA_WEBHOOK_SIGNING_SECRET, roomStateFile = "zaisma-room-states.sqlite", zaismaKey = process.env.ZAISMA_KEY, developerApiUrl = "https://zaisma.com/api/dev", webtransportPort, certificatePath, certificateKeyPath, identifyPlayer, outsideGuestsEnabled } = checkedStartServerOptions(options, PASS_ISSUER_URL);
|
|
82
88
|
const definition = defineGameServer(gameServer);
|
|
83
89
|
// Exactly once needs Zaisma's record of what's handled: without a key, purchases would come twice, so it doesn't start.
|
|
84
90
|
if ((definition.onPurchase || definition.onRefund) && !zaismaKey) {
|
|
@@ -125,7 +131,7 @@ export function startServer(options) {
|
|
|
125
131
|
* account's name from its public profile, and the pass's claims, for blocks between players. Null
|
|
126
132
|
* for an account deleted since; PassError for a bad pass.
|
|
127
133
|
*/
|
|
128
|
-
async function
|
|
134
|
+
async function passPlayer(pass) {
|
|
129
135
|
const claims = await verifyPass(pass, await loadPassKeys(), { gameSlug, issuerUrl }).catch(async (error) => {
|
|
130
136
|
// A new key may have been published since we cached them.
|
|
131
137
|
if (error instanceof PassError && error.code === "unknown_key") return verifyPass(pass, await loadPassKeys(true), { gameSlug, issuerUrl });
|
|
@@ -139,8 +145,43 @@ export function startServer(options) {
|
|
|
139
145
|
return name ? { player: { id: claims.sub, name, kind: claims.kind === "account" ? "account" : "guest", ...owned }, claims } : null;
|
|
140
146
|
}
|
|
141
147
|
|
|
142
|
-
|
|
143
|
-
|
|
148
|
+
/**
|
|
149
|
+
* Who a hello or request is from: by a Zaisma pass, or, from a game played off Zaisma, by the playerToken it
|
|
150
|
+
* sent, as identifyPlayer says, or as a guest when there's none and outsideGuestsEnabled. Answers { player, claims }
|
|
151
|
+
* (claims null for a player from outside Zaisma, who has no pass), or { code } when they can't come in.
|
|
152
|
+
*/
|
|
153
|
+
async function playerFrom({ pass, playerToken, origin }) {
|
|
154
|
+
if (typeof pass === "string") {
|
|
155
|
+
try {
|
|
156
|
+
return (await passPlayer(pass)) ?? { code: "bad_pass" };
|
|
157
|
+
} catch (error) {
|
|
158
|
+
if (error instanceof PassError) return { code: "bad_pass" };
|
|
159
|
+
// Zaisma couldn't be reached for the account's name: the player tries again, nothing crashes.
|
|
160
|
+
console.error("Couldn't check a player's pass:", error);
|
|
161
|
+
return { code: "busy" };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const outside = (player) => ({ player: { ...player, kind: "outside_zaisma" }, claims: null });
|
|
165
|
+
if (playerToken === null) return outsideGuestsEnabled ? outside(guestPlayer()) : { code: "bad_player_token", message: "This server takes players signed in, not guests: the game gives a playerToken." };
|
|
166
|
+
if (!identifyPlayer) return { code: "bad_player_token", message: "This server takes players from Zaisma's pages only." };
|
|
167
|
+
let identified;
|
|
168
|
+
try {
|
|
169
|
+
identified = await identifyPlayer(playerToken, { origin: origin ?? null });
|
|
170
|
+
} catch (error) {
|
|
171
|
+
console.error("Game server error in identifyPlayer:", error);
|
|
172
|
+
return { code: "busy" };
|
|
173
|
+
}
|
|
174
|
+
if (identified === null) return { code: "bad_player_token" };
|
|
175
|
+
const checked = checkedOutsidePlayer(identified);
|
|
176
|
+
if ("mistake" in checked) {
|
|
177
|
+
console.error(`Game server error in identifyPlayer: ${checked.mistake}`);
|
|
178
|
+
return { code: "bad_player_token" };
|
|
179
|
+
}
|
|
180
|
+
return outside(checked.player);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Requests from the game to onRequest, outside any room: POST /v1/requests/<game> with { pass, request }, or
|
|
184
|
+
// { playerToken, request } from a game played off Zaisma, sent as text/plain JSON so browsers need no preflight. Answers { answer } or { error: { code, message } }.
|
|
144
185
|
function answerHttp(request, response) {
|
|
145
186
|
if (request.url === "/v1/webhooks") return answerWebhook(request, response);
|
|
146
187
|
const origin = request.headers.origin;
|
|
@@ -171,16 +212,10 @@ export function startServer(options) {
|
|
|
171
212
|
request.on("end", async () => {
|
|
172
213
|
try {
|
|
173
214
|
if (isTooBig) return fail("too_big");
|
|
174
|
-
const body = readRequestBody(text);
|
|
215
|
+
const body = readRequestBody(text, true);
|
|
175
216
|
if ("code" in body) return fail(body.code);
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
checked = await playerFrom(body.pass);
|
|
179
|
-
} catch (error) {
|
|
180
|
-
if (error instanceof PassError) return fail("bad_pass");
|
|
181
|
-
throw error;
|
|
182
|
-
}
|
|
183
|
-
if (!checked) return fail("bad_pass");
|
|
217
|
+
const checked = await playerFrom({ ...body, origin });
|
|
218
|
+
if (checked.code) return fail(checked.code, checked.message);
|
|
184
219
|
const outcome = await answerRequest(definition, checked.player, body.request, server);
|
|
185
220
|
if ("answer" in outcome) return reply(200, { answer: outcome.answer });
|
|
186
221
|
if (outcome.code === "game_server_error") console.error("Game server error in onRequest:", outcome.failure);
|
|
@@ -512,8 +547,10 @@ export function startServer(options) {
|
|
|
512
547
|
if (!started) return;
|
|
513
548
|
let check = { isAllowed: true, reason: null, message: null, hasSwearing: false, hasSlur: false };
|
|
514
549
|
if (started.receiverPlayerIds.length) {
|
|
515
|
-
|
|
516
|
-
|
|
550
|
+
// A player from outside Zaisma goes as null: Zaisma stops their chat with under-18 accounts, and doesn't hear chat among them alone.
|
|
551
|
+
const passOf = (id) => (room.core.players.get(id)?.kind === "outside_zaisma" ? null : (room.passTexts.get(id) ?? ""));
|
|
552
|
+
const receiverPasses = started.receiverPlayerIds.map(passOf);
|
|
553
|
+
check = await checkChat({ text: started.text, senderPass: passOf(playerId), receiverPasses, isAdultFilterEnabled: definition.adultChatFilterEnabled === true });
|
|
517
554
|
}
|
|
518
555
|
if (check.isAllowed) room.core.deliverChat(playerId, chat.chatNumber, started.receiverPlayerIds, { text: started.text, hasSwearing: check.hasSwearing, hasSlur: check.hasSlur });
|
|
519
556
|
else room.core.stopChat(playerId, chat.chatNumber, check.reason ?? "unchecked", check.message ?? "");
|
|
@@ -621,13 +658,17 @@ export function startServer(options) {
|
|
|
621
658
|
return;
|
|
622
659
|
}
|
|
623
660
|
let pass;
|
|
661
|
+
let playerToken;
|
|
624
662
|
let made = {};
|
|
625
663
|
// Come to watch, not to play (joinRoom({ isSpectator: true })).
|
|
626
664
|
let isSpectator = false;
|
|
627
665
|
try {
|
|
628
666
|
const hello = JSON.parse(text ?? "");
|
|
629
|
-
|
|
630
|
-
|
|
667
|
+
// A Zaisma pass, or from a game played off Zaisma, its playerToken (null for a guest).
|
|
668
|
+
const isOutside = hello.pass === undefined && isPlayerToken(hello.playerToken);
|
|
669
|
+
if (hello.type !== "hello" || (typeof hello.pass !== "string" && !isOutside)) return refuse("no_hello");
|
|
670
|
+
if (isOutside) playerToken = hello.playerToken;
|
|
671
|
+
else pass = hello.pass;
|
|
631
672
|
isSpectator = hello.isSpectator === true;
|
|
632
673
|
if (hello.room && typeof hello.room === "object") made = { ...(hello.room.isListed === true ? { isListed: true } : {}), ...(hello.room.friendJoinsEnabled === false ? { friendJoinsEnabled: false } : {}), ...tidyRoomInfo(hello.room) };
|
|
633
674
|
made.clientIdle = clientIdle(hello);
|
|
@@ -637,16 +678,8 @@ export function startServer(options) {
|
|
|
637
678
|
} catch {
|
|
638
679
|
return refuse("no_hello");
|
|
639
680
|
}
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
checked = await playerFrom(pass);
|
|
643
|
-
} catch (error) {
|
|
644
|
-
if (error instanceof PassError) return refuse("bad_pass");
|
|
645
|
-
// Zaisma couldn't be reached for the account's name: the player tries again, nothing crashes.
|
|
646
|
-
console.error("Couldn't check a player's pass:", error);
|
|
647
|
-
return refuse("busy");
|
|
648
|
-
}
|
|
649
|
-
if (!checked) return refuse("bad_pass");
|
|
681
|
+
const checked = await playerFrom({ pass, playerToken, origin });
|
|
682
|
+
if (checked.code) return refuse(checked.code, checked.message);
|
|
650
683
|
const { player, claims } = checked;
|
|
651
684
|
clearTimeout(helloTimer);
|
|
652
685
|
// A spectator watches a match: one with nobody in it isn't running, and a spectator never starts one.
|
|
@@ -663,8 +696,14 @@ export function startServer(options) {
|
|
|
663
696
|
// Blocks between accounts come in their passes: this player's and those of everyone the room remembers.
|
|
664
697
|
// Spectators send nothing, so nothing of theirs reaches a player to block.
|
|
665
698
|
// Who the player may chat with, from their pass: the room keeps it, and the game never sees it. Spectators send nothing, chat included.
|
|
666
|
-
|
|
667
|
-
|
|
699
|
+
// A player from outside Zaisma has no pass: no account blocks or friends, and chat as OUTSIDE_ZAISMA_CHAT_SETTINGS says.
|
|
700
|
+
room.core.setChatSettings(player.id, isSpectator ? null : claims ? chatSettingsOf(claims) : OUTSIDE_ZAISMA_CHAT_SETTINGS);
|
|
701
|
+
if (!isSpectator && !claims) {
|
|
702
|
+
room.passes.delete(player.id);
|
|
703
|
+
room.passTexts.delete(player.id);
|
|
704
|
+
room.core.setAccountBlocks(player.id, []);
|
|
705
|
+
room.core.setFriends(player.id, []);
|
|
706
|
+
} else if (!isSpectator) {
|
|
668
707
|
room.passes.set(player.id, claims);
|
|
669
708
|
room.passTexts.set(player.id, pass);
|
|
670
709
|
for (const id of room.passes.keys()) {
|
package/package.json
CHANGED
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" });
|
|
@@ -1488,8 +1526,11 @@ declare namespace Zaisma {
|
|
|
1488
1526
|
awayReason: "idle" | "hidden" | "lost" | null;
|
|
1489
1527
|
}
|
|
1490
1528
|
|
|
1491
|
-
/**
|
|
1492
|
-
|
|
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";
|
|
1493
1534
|
|
|
1494
1535
|
/** What anyone sees of an account on its profile, zaisma.com/@username ({@link player.getProfile}). */
|
|
1495
1536
|
interface PlayerProfile {
|
|
@@ -1627,6 +1668,7 @@ declare namespace Zaisma {
|
|
|
1627
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.
|
|
1628
1669
|
* - game_unavailable: the game can't be played right now.
|
|
1629
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.
|
|
1630
1672
|
* - bad_origin: the connection didn't come from the game's own page.
|
|
1631
1673
|
* - too_big: a message over 16 KB, or joinData over 4 KB.
|
|
1632
1674
|
* - too_fast: more messages than the room takes (about 30 a second).
|
|
@@ -1642,6 +1684,8 @@ declare namespace Zaisma {
|
|
|
1642
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.
|
|
1643
1685
|
*
|
|
1644
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".
|
|
1645
1689
|
*/
|
|
1646
1690
|
type RoomErrorCode =
|
|
1647
1691
|
| "room_full"
|
|
@@ -1654,6 +1698,7 @@ declare namespace Zaisma {
|
|
|
1654
1698
|
| "game_not_open_to_player"
|
|
1655
1699
|
| "game_unavailable"
|
|
1656
1700
|
| "bad_pass"
|
|
1701
|
+
| "bad_player_token"
|
|
1657
1702
|
| "bad_origin"
|
|
1658
1703
|
| "too_big"
|
|
1659
1704
|
| "too_fast"
|
|
@@ -1679,6 +1724,7 @@ declare namespace Zaisma {
|
|
|
1679
1724
|
* - bad_request: not plain JSON with the player's pass.
|
|
1680
1725
|
* - too_big: a request over 16 KB.
|
|
1681
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.
|
|
1682
1728
|
* - account_needed: the player is a guest, and the game needs a Zaisma account for multiplayer or the whole game.
|
|
1683
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.
|
|
1684
1730
|
* - game_unavailable: the game can't be played right now.
|
|
@@ -1688,11 +1734,14 @@ declare namespace Zaisma {
|
|
|
1688
1734
|
* - slow_down: too many requests from this address; try again in a minute.
|
|
1689
1735
|
* - bad_origin: the request didn't come from the game's own page.
|
|
1690
1736
|
* - busy: Zaisma couldn't answer right now; try again soon.
|
|
1737
|
+
*
|
|
1738
|
+
* More values may come; treat an unknown one as "request_failed".
|
|
1691
1739
|
*/
|
|
1692
1740
|
type RequestErrorCode =
|
|
1693
1741
|
| "bad_request"
|
|
1694
1742
|
| "too_big"
|
|
1695
1743
|
| "bad_pass"
|
|
1744
|
+
| "bad_player_token"
|
|
1696
1745
|
| "account_needed"
|
|
1697
1746
|
| "game_not_open_to_player"
|
|
1698
1747
|
| "game_unavailable"
|
|
@@ -1711,7 +1760,8 @@ declare namespace Zaisma {
|
|
|
1711
1760
|
* - zaisma_rooms_not_used: the game doesn't use Zaisma rooms.
|
|
1712
1761
|
* - bad_report: a report whose reason isn't a {@link ReportReason}, or whose player ID isn't one.
|
|
1713
1762
|
* - bad_origin: the call didn't come from the game's own page.
|
|
1714
|
-
* - 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.
|
|
1715
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}).
|
|
1716
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}).
|
|
1717
1767
|
* - cloud_saves_off: the game has cloud saves off, so it has no saves on players' accounts ({@link saves}).
|
|
@@ -1752,6 +1802,7 @@ declare namespace Zaisma {
|
|
|
1752
1802
|
| "bad_report"
|
|
1753
1803
|
| "bad_origin"
|
|
1754
1804
|
| "not_on_zaisma"
|
|
1805
|
+
| "bad_game_slug"
|
|
1755
1806
|
| "sign_in_declined"
|
|
1756
1807
|
| "account_offer_too_soon"
|
|
1757
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"];
|
|
@@ -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.
|
|
@@ -1527,7 +1572,9 @@
|
|
|
1527
1572
|
|
|
1528
1573
|
function connect(roomCode, joinOptions, isRetry) {
|
|
1529
1574
|
var serverUrl = joinOptions.serverUrl ? String(joinOptions.serverUrl).replace(/\/+$/, "") : null;
|
|
1530
|
-
|
|
1575
|
+
var offRefusal = offZaismaRefusal(serverUrl, "Zaisma.joinRoom");
|
|
1576
|
+
if (offRefusal) return Promise.reject(offRefusal);
|
|
1577
|
+
var isOffZaisma = !config.rooms;
|
|
1531
1578
|
var idleTimeoutSec = joinOptions.idleTimeoutSec;
|
|
1532
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 + "."));
|
|
1533
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\"."));
|
|
@@ -1539,12 +1586,14 @@
|
|
|
1539
1586
|
: serverUrl
|
|
1540
1587
|
? Promise.resolve(randomRoomCode())
|
|
1541
1588
|
: post("/v1/rooms", { gameSlug: config.game }).then(function (answer) { return answer.roomCode; });
|
|
1542
|
-
|
|
1589
|
+
// Off Zaisma there's no pass: the hello carries the playerToken instead.
|
|
1590
|
+
return Promise.all([roomCodeReady, isOffZaisma ? null : getPass()]).then(function (ready) {
|
|
1543
1591
|
var base = (serverUrl || config.rooms).replace(/^http/, "ws");
|
|
1544
|
-
var path = "/v1/rooms/" + config.game + "/" + ready[0];
|
|
1592
|
+
var path = "/v1/rooms/" + (isOffZaisma ? offZaisma.gameSlug : config.game) + "/" + ready[0];
|
|
1545
1593
|
var room = new Room({
|
|
1546
1594
|
roomCode: ready[0],
|
|
1547
|
-
pass: ready[1].pass,
|
|
1595
|
+
pass: isOffZaisma ? null : ready[1].pass,
|
|
1596
|
+
isOffZaisma: isOffZaisma,
|
|
1548
1597
|
url: base + path,
|
|
1549
1598
|
// The fast lane reaches only the game's own secure servers: Zaisma's rooms take the WebSocket, whatever transport says.
|
|
1550
1599
|
fastLaneUrl: joinOptions.transport === "webtransport" && serverUrl && OWN_SERVER_URL.test(serverUrl) ? serverUrl.replace(/^wss:/, "https:") + path : null,
|
|
@@ -1566,8 +1615,8 @@
|
|
|
1566
1615
|
room._ready = { resolve: resolve, reject: reject };
|
|
1567
1616
|
room._connect();
|
|
1568
1617
|
}).catch(function (error) {
|
|
1569
|
-
// A pass this tab kept from before Zaisma changed its keys: get a new one and try once more.
|
|
1570
|
-
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;
|
|
1571
1620
|
try { sessionStorage.removeItem(passKey); } catch (storageError) { /* fine */ }
|
|
1572
1621
|
return connect(room.roomCode, joinOptions, true);
|
|
1573
1622
|
});
|
|
@@ -1770,13 +1819,15 @@
|
|
|
1770
1819
|
|
|
1771
1820
|
/**
|
|
1772
1821
|
* A request to the game's own game server, its onRequest, outside any room: what it answered
|
|
1773
|
-
* (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.
|
|
1774
1824
|
*/
|
|
1775
1825
|
function serverRequest(request, requestOptions, isRetry) {
|
|
1776
1826
|
var refused = unknownOption(requestOptions, REQUEST_OPTIONS, "Zaisma.server.request");
|
|
1777
1827
|
if (refused) return Promise.reject(refused);
|
|
1778
1828
|
var serverUrl = requestOptions && requestOptions.serverUrl ? String(requestOptions.serverUrl).replace(/\/+$/, "") : null;
|
|
1779
|
-
|
|
1829
|
+
var offRefusal = offZaismaRefusal(serverUrl, "Zaisma.server.request");
|
|
1830
|
+
if (offRefusal) return Promise.reject(offRefusal);
|
|
1780
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\"."));
|
|
1781
1832
|
var requestText;
|
|
1782
1833
|
try {
|
|
@@ -1787,13 +1838,17 @@
|
|
|
1787
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\" }."));
|
|
1788
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."));
|
|
1789
1840
|
var base = (serverUrl || config.rooms).replace(/^ws/, "http");
|
|
1790
|
-
|
|
1791
|
-
|
|
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);
|
|
1792
1847
|
}).then(function (answer) {
|
|
1793
1848
|
return answer.answer;
|
|
1794
1849
|
}, function (error) {
|
|
1795
|
-
// A pass this tab kept from before Zaisma changed its keys: get a new one and try once more.
|
|
1796
|
-
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;
|
|
1797
1852
|
try { sessionStorage.removeItem(passKey); } catch (storageError) { /* fine */ }
|
|
1798
1853
|
return serverRequest(request, requestOptions, true);
|
|
1799
1854
|
});
|
|
@@ -2698,5 +2753,5 @@
|
|
|
2698
2753
|
});
|
|
2699
2754
|
|
|
2700
2755
|
var friends = { list: listFriends, setRoom: setOwnRoom, sendRequest: sendFriendRequest };
|
|
2701
|
-
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 };
|
|
2702
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
|
}
|