@zaisma/game-server 1.21.0 → 1.23.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/design/game-views/forest-by-day.webp +0 -0
- package/bench/brand/design/game-views/racing-at-night.webp +0 -0
- package/bench/brand/manifest.webmanifest +1 -1
- package/bench/glass.js +3026 -2001
- package/package.json +1 -1
- package/sdk/zaisma.d.ts +2 -12
- package/sdk/zaisma.js +1 -130
- package/src/doors.d.ts +68 -0
- package/src/doors.js +24 -0
- package/src/internal.d.ts +1 -0
- package/src/internal.js +1 -0
- package/src/room.d.ts +64 -0
- package/src/room.js +35 -6
package/package.json
CHANGED
package/sdk/zaisma.d.ts
CHANGED
|
@@ -198,7 +198,7 @@ declare namespace Zaisma {
|
|
|
198
198
|
const server: Server;
|
|
199
199
|
|
|
200
200
|
/**
|
|
201
|
-
* Invite friends. On Zaisma it opens
|
|
201
|
+
* Invite friends. On Zaisma it opens Zaisma's invite sheet, the same as the Zaisma overlay's Share: the
|
|
202
202
|
* room's QR code, link and code, Copy and the share sheet ("opened", or "failed" when the page couldn't
|
|
203
203
|
* open it). On your own site it opens the share sheet on phones, otherwise copies the room's invite link
|
|
204
204
|
* (the game's page without a room). Call it from a button in your game, like on a "waiting for players"
|
|
@@ -227,16 +227,6 @@ declare namespace Zaisma {
|
|
|
227
227
|
*/
|
|
228
228
|
function checkChat(text: string): Promise<ChatCheck>;
|
|
229
229
|
|
|
230
|
-
/**
|
|
231
|
-
* The Players panel, where a player can mute, block and report the others and see their friends. On
|
|
232
|
-
* Zaisma it opens the game bar's own Players, the same as its button (leaving full screen if the
|
|
233
|
-
* game is in it), so a Players button in your game, in your game's style, does exactly what the bar's
|
|
234
|
-
* does. On your own site, where there's no bar, it draws Zaisma's ready-made panel.
|
|
235
|
-
* A game with its own netcode offers them in its own menu. Or build your own panel
|
|
236
|
-
* with room.mute, room.block and room.report.
|
|
237
|
-
*/
|
|
238
|
-
function openPlayersPanel(room: Room): void;
|
|
239
|
-
|
|
240
230
|
/**
|
|
241
231
|
* The signed-in player's Zaisma friends who are playing this game right now, for your own list of
|
|
242
232
|
* them: to join a friend's match, watch it or invite them ({@link Friends.list}). A game never sees
|
|
@@ -605,7 +595,7 @@ declare namespace Zaisma {
|
|
|
605
595
|
|
|
606
596
|
/**
|
|
607
597
|
* Whether Zaisma's page covers the game right now, so a game can pause or hold the player's input while it does.
|
|
608
|
-
* coveredBy is "sheet" while one of Zaisma's sheets is open over the game (buying,
|
|
598
|
+
* coveredBy is "sheet" while one of Zaisma's sheets is open over the game (buying, the Zaisma overlay, Report: the player is
|
|
609
599
|
* busy with it), "card" while one of its cards is over the game's corner (Still there?, Keep your progress; the
|
|
610
600
|
* game stays in view and playable), and null otherwise. Always null outside Zaisma's page (your own site, a
|
|
611
601
|
* local test). Listen for "coveredBy" to hear each change.
|
package/sdk/zaisma.js
CHANGED
|
@@ -2574,138 +2574,9 @@
|
|
|
2574
2574
|
});
|
|
2575
2575
|
});
|
|
2576
2576
|
|
|
2577
|
-
/** Hand a player report to the Zaisma page. If no Zaisma page answers (the game runs somewhere else), use our own form. */
|
|
2578
|
-
function handOff(room, reported, opened, fallback) {
|
|
2579
|
-
if (!config.site || window.parent === window) return fallback();
|
|
2580
|
-
var isAnswered = false;
|
|
2581
|
-
function listen(event) {
|
|
2582
|
-
if (event.origin === config.site && event.source === window.parent && event.data && event.data.type === "zaisma:report-open") {
|
|
2583
|
-
isAnswered = true;
|
|
2584
|
-
window.removeEventListener("message", listen);
|
|
2585
|
-
opened();
|
|
2586
|
-
}
|
|
2587
|
-
}
|
|
2588
|
-
window.addEventListener("message", listen);
|
|
2589
|
-
window.parent.postMessage({ type: "zaisma:report", code: room.roomCode, player: { id: reported.id, name: reported.name } }, config.site);
|
|
2590
|
-
setTimeout(function () {
|
|
2591
|
-
window.removeEventListener("message", listen);
|
|
2592
|
-
if (!isAnswered) fallback();
|
|
2593
|
-
}, 800);
|
|
2594
|
-
}
|
|
2595
|
-
|
|
2596
2577
|
// The first four go to Zaisma; the rest to the game's developer.
|
|
2597
2578
|
var REASONS = [["real_life_threat", "Threatening them in real life"], ["personal_details", "Sharing someone's personal details"], ["following_after_block", "Following me after I blocked them"], ["sexual", "Sexual behavior"], ["harassment", "Harassing or bullying"], ["cheating", "Cheating"], ["other", "Something else"]];
|
|
2598
2579
|
|
|
2599
|
-
/** A ready-made Players panel: mute, block and report. Games open it from their own menu. */
|
|
2600
|
-
function openPlayersPanel(room) {
|
|
2601
|
-
// On Zaisma, the page's own Players (the bar's), so a game's Players button opens the same one.
|
|
2602
|
-
if (onZaismaPage()) {
|
|
2603
|
-
window.parent.postMessage({ type: "zaisma:players-open" }, config.site);
|
|
2604
|
-
return;
|
|
2605
|
-
}
|
|
2606
|
-
var old = document.getElementById("zaisma-safety");
|
|
2607
|
-
if (old) old.remove();
|
|
2608
|
-
var wrap = document.createElement("div");
|
|
2609
|
-
wrap.id = "zaisma-safety";
|
|
2610
|
-
wrap.setAttribute("role", "dialog");
|
|
2611
|
-
wrap.setAttribute("aria-label", "Players");
|
|
2612
|
-
// Zaisma's Steel look: gunmetal plates with the cut corner.
|
|
2613
|
-
wrap.style.cssText = "position:fixed;inset:0;z-index:2147483647;background:rgba(4,5,7,.6);display:grid;place-items:center;font:15px/1.4 system-ui,-apple-system,sans-serif;color:#eceef0";
|
|
2614
|
-
var box = document.createElement("div");
|
|
2615
|
-
box.style.cssText = "background:linear-gradient(180deg,#262a31,#1b1e23);box-shadow:inset 1px 1px 0 rgba(255,255,255,.09),inset -1px -1px 0 rgba(0,0,0,.7),0 14px 32px rgba(0,0,0,.42);clip-path:polygon(0 0,calc(100% - 18px) 0,100% 18px,100% 100%,0 100%);padding:18px;width:min(420px,92vw);max-height:80vh;overflow:auto";
|
|
2616
|
-
wrap.appendChild(box);
|
|
2617
|
-
var buttonStyle = "font:inherit;font-size:13px;font-weight:600;min-height:36px;padding:0 12px;border:0;background:linear-gradient(180deg,#30353d,#22262c);box-shadow:inset 1px 1px 0 rgba(255,255,255,.13),inset -1px -1px 0 #000;clip-path:polygon(0 0,calc(100% - 7px) 0,100% 7px,100% 100%,0 100%);color:#eceef0;cursor:pointer";
|
|
2618
|
-
function close() { wrap.remove(); }
|
|
2619
|
-
wrap.addEventListener("click", function (event) { if (event.target === wrap) close(); });
|
|
2620
|
-
wrap.addEventListener("keydown", function (event) { if (event.key === "Escape") close(); });
|
|
2621
|
-
|
|
2622
|
-
function render(note) {
|
|
2623
|
-
box.textContent = "";
|
|
2624
|
-
var head = document.createElement("div");
|
|
2625
|
-
head.style.cssText = "display:flex;justify-content:space-between;align-items:center;margin-bottom:10px";
|
|
2626
|
-
var heading = document.createElement("strong");
|
|
2627
|
-
heading.textContent = "Players";
|
|
2628
|
-
var closeButton = document.createElement("button");
|
|
2629
|
-
closeButton.textContent = "Close";
|
|
2630
|
-
closeButton.style.cssText = buttonStyle;
|
|
2631
|
-
closeButton.onclick = close;
|
|
2632
|
-
head.append(heading, closeButton);
|
|
2633
|
-
box.appendChild(head);
|
|
2634
|
-
if (note) {
|
|
2635
|
-
var noteText = document.createElement("p");
|
|
2636
|
-
noteText.textContent = note;
|
|
2637
|
-
noteText.style.cssText = "margin:0 0 10px;color:#b9e7a6";
|
|
2638
|
-
box.appendChild(noteText);
|
|
2639
|
-
}
|
|
2640
|
-
var others = room.players.filter(function (each) { return !room.me || each.id !== room.me.id; });
|
|
2641
|
-
if (!others.length) {
|
|
2642
|
-
var none = document.createElement("p");
|
|
2643
|
-
none.textContent = "Nobody else is here.";
|
|
2644
|
-
none.style.color = "#969ba4";
|
|
2645
|
-
box.appendChild(none);
|
|
2646
|
-
}
|
|
2647
|
-
others.forEach(function (other) {
|
|
2648
|
-
var row = document.createElement("div");
|
|
2649
|
-
row.style.cssText = "display:flex;gap:6px;align-items:center;padding:10px 0;box-shadow:inset 0 1px 0 #000,inset 0 2px 0 rgba(255,255,255,.05)";
|
|
2650
|
-
// Only what this player did themselves shows: a block someone made of them never does.
|
|
2651
|
-
var isBlocked = hasBlocked(other.id);
|
|
2652
|
-
var isMuted = Boolean(room._muted && room._muted.has(other.id));
|
|
2653
|
-
var name = document.createElement("span");
|
|
2654
|
-
name.textContent = other.name + (isBlocked ? " (blocked)" : isMuted ? " (muted)" : "");
|
|
2655
|
-
name.style.flex = "1";
|
|
2656
|
-
row.appendChild(name);
|
|
2657
|
-
function button(text, onclick) {
|
|
2658
|
-
var each = document.createElement("button");
|
|
2659
|
-
each.textContent = text;
|
|
2660
|
-
each.style.cssText = buttonStyle;
|
|
2661
|
-
each.onclick = onclick;
|
|
2662
|
-
row.appendChild(each);
|
|
2663
|
-
}
|
|
2664
|
-
// A lasting block is undone on zaisma.com/friends; a session one here.
|
|
2665
|
-
if (!lastingBlocked.has(other.id)) button(isBlocked ? "Unblock" : isMuted ? "Unmute" : "Mute", function () { isBlocked || isMuted ? room.unmute(other.id) : room.mute(other.id); render(); });
|
|
2666
|
-
if (!isBlocked) {
|
|
2667
|
-
button("Block", function () {
|
|
2668
|
-
room.block(other.id).then(function (isDone) {
|
|
2669
|
-
render(!isDone ? undefined : lastingBlocked.has(other.id) ? other.name + " is blocked. Unblock them on your Zaisma friends page." : other.name + " is blocked for this session.");
|
|
2670
|
-
}, function () { render(); });
|
|
2671
|
-
});
|
|
2672
|
-
}
|
|
2673
|
-
button("Report", function () { handOff(room, other, close, function () { reportForm(other); }); });
|
|
2674
|
-
box.appendChild(row);
|
|
2675
|
-
});
|
|
2676
|
-
}
|
|
2677
|
-
|
|
2678
|
-
function reportForm(reported) {
|
|
2679
|
-
box.textContent = "";
|
|
2680
|
-
var heading = document.createElement("strong");
|
|
2681
|
-
heading.textContent = "Report " + reported.name;
|
|
2682
|
-
var select = document.createElement("select");
|
|
2683
|
-
select.style.cssText = "display:block;width:100%;margin:12px 0;min-height:40px;padding:0 10px;background:#0d0f12;color:#eceef0;border:1px solid #000;box-shadow:inset 0 2px 5px rgba(0,0,0,.65),0 1px 0 rgba(255,255,255,.06);font:inherit";
|
|
2684
|
-
REASONS.forEach(function (reason) { var option = document.createElement("option"); option.value = reason[0]; option.textContent = reason[1]; select.appendChild(option); });
|
|
2685
|
-
var note = document.createElement("p");
|
|
2686
|
-
note.textContent = "Zaisma reviews reports. Nothing about you is sent. They'll also be muted for you.";
|
|
2687
|
-
note.style.cssText = "margin:0 0 12px;color:#969ba4;font-size:13px";
|
|
2688
|
-
var send = document.createElement("button");
|
|
2689
|
-
send.textContent = "Send report";
|
|
2690
|
-
send.style.cssText = buttonStyle + ";background:linear-gradient(180deg,#eef0f2 0%,#b9bcc1 46%,#7d8086 100%);box-shadow:inset 1px 1px 0 #fff,inset -1px -1px 0 #000;color:#0b0b0c";
|
|
2691
|
-
var back = document.createElement("button");
|
|
2692
|
-
back.textContent = "Back";
|
|
2693
|
-
back.style.cssText = buttonStyle + ";margin-left:6px";
|
|
2694
|
-
back.onclick = function () { render(); };
|
|
2695
|
-
send.onclick = function () {
|
|
2696
|
-
send.disabled = true;
|
|
2697
|
-
room.mute(reported.id);
|
|
2698
|
-
room.report(reported.id, select.value).then(function () { render("Thanks. Your report was sent."); }, function (error) { render(error.message); });
|
|
2699
|
-
};
|
|
2700
|
-
box.append(heading, select, note, send, back);
|
|
2701
|
-
}
|
|
2702
|
-
|
|
2703
|
-
render();
|
|
2704
|
-
document.body.appendChild(wrap);
|
|
2705
|
-
wrap.tabIndex = -1;
|
|
2706
|
-
wrap.focus();
|
|
2707
|
-
}
|
|
2708
|
-
|
|
2709
2580
|
var rooms = { list: listRooms, quickJoin: quickJoin, linkedRoomCode: roomCodeFromPage(), linkedServerUrl: roomCodeFromPage() ? serverUrlFromPage() : null, isLinkedAsSpectator: watchFromPage() };
|
|
2710
2581
|
/*
|
|
2711
2582
|
* Friend requests (zaisma.d.ts: Friends.sendRequest): the Zaisma page asks the player before anything is
|
|
@@ -2753,5 +2624,5 @@
|
|
|
2753
2624
|
});
|
|
2754
2625
|
|
|
2755
2626
|
var friends = { list: listFriends, setRoom: setOwnRoom, sendRequest: sendFriendRequest };
|
|
2756
|
-
window.Zaisma = { sdkVersionNumber: 1, gameSlug: config.game || null, player: player, setUpOffZaisma: setUpOffZaisma, joinRoom: joinRoom, rooms: rooms, server: server, invite: invite, checkChat: checkChat,
|
|
2627
|
+
window.Zaisma = { sdkVersionNumber: 1, gameSlug: config.game || null, player: player, setUpOffZaisma: setUpOffZaisma, joinRoom: joinRoom, rooms: rooms, server: server, invite: invite, checkChat: checkChat, links: links, items: items, friends: friends, saves: saves, versions: versions, page: zaismaPage };
|
|
2757
2628
|
})();
|
package/src/doors.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Door contracts (docs/DOORS.md). Every way into Zaisma, of every kind, states who may call it, what it may
|
|
3
|
+
* reach and what it trusts from other doors, and goes through checkDoor before its own code runs. Here, in the
|
|
4
|
+
* game server package's internal part, so a room on a developer's own server goes through the same check;
|
|
5
|
+
* never in the package's published index.
|
|
6
|
+
*/
|
|
7
|
+
/** Callers the door's guard proves itself, from what it read of the request or the room. */
|
|
8
|
+
export type DoorCallerProvenByGuard =
|
|
9
|
+
/** Guests too. A door that shows a signed-in person more trusts `session` as well. */
|
|
10
|
+
"anyone"
|
|
11
|
+
/** A session that works. */
|
|
12
|
+
| "signed_in"
|
|
13
|
+
/** A session with a finished account, and no developer ban unless the door keeps it open to them. */
|
|
14
|
+
| "developer"
|
|
15
|
+
/** Cloudflare Access to /admin. */
|
|
16
|
+
| "admin"
|
|
17
|
+
/** Anyone in the room, spectators too. */
|
|
18
|
+
| "room_member"
|
|
19
|
+
/** A player in the room, not a spectator. */
|
|
20
|
+
| "room_player"
|
|
21
|
+
/** The room's host. */
|
|
22
|
+
| "room_host";
|
|
23
|
+
/** Callers a named function proves, because the proof takes the door's own data (a body's signature, a token's row). */
|
|
24
|
+
export type DoorCallerProvenByFunction =
|
|
25
|
+
/** The API router: an API key's access, or a session from Zaisma's own pages. */
|
|
26
|
+
"api_caller"
|
|
27
|
+
/** A tester or preview link's token in the address. */
|
|
28
|
+
| "link_holder"
|
|
29
|
+
/** A Sign in with Zaisma code or access token. */
|
|
30
|
+
| "token_holder"
|
|
31
|
+
/** Stripe or GitHub, by the signature on the body. */
|
|
32
|
+
| "signed_sender"
|
|
33
|
+
/** A game's own page, by the request's Origin. */
|
|
34
|
+
| "game_page"
|
|
35
|
+
/** A signed player pass. */
|
|
36
|
+
| "pass_holder";
|
|
37
|
+
export type DoorCaller = DoorCallerProvenByGuard | DoorCallerProvenByFunction;
|
|
38
|
+
/**
|
|
39
|
+
* What a door may touch. The first seven are the API's own words (ApiRoute's reaches, apps/site/src/developer.ts):
|
|
40
|
+
* `account`, `player_save`, `game`, `webhook`, `sign_in_app` and `forum_topic` are the caller's own or what the caller may read,
|
|
41
|
+
* and `nothing_private` is what anyone may see. The `any_` ones and `zaisma_settings` are only ever an admin's.
|
|
42
|
+
*/
|
|
43
|
+
export type DoorReach = "account" | "player_save" | "game" | "webhook" | "sign_in_app" | "forum_topic" | "nothing_private" | "any_game" | "any_account" | "zaisma_settings" | "room" | "purchase";
|
|
44
|
+
/** What a door takes from another door, as the security map names them (scripts/security-map/doors.mjs). */
|
|
45
|
+
export type DoorTrust = "session" | "api_key" | "oauth_code" | "oauth_access" | "tester_link" | "preview_link" | "player_pass" | "admin_access" | "player_id" | "stripe_signature" | "github_signature" | "game_origin" | "purchase_receipt" | "passkey" | "email_code";
|
|
46
|
+
/** The function that proves a caller; a test checks it exists. */
|
|
47
|
+
export interface DoorProof {
|
|
48
|
+
source_path: string;
|
|
49
|
+
symbol_name: string;
|
|
50
|
+
}
|
|
51
|
+
export interface DoorContractFields {
|
|
52
|
+
reaches: DoorReach;
|
|
53
|
+
trusts: readonly DoorTrust[];
|
|
54
|
+
}
|
|
55
|
+
/** A caller a function proves can't be stated without naming the function, so it doesn't build. */
|
|
56
|
+
export type DoorContract = DoorContractFields & ({
|
|
57
|
+
whoMayCall: DoorCallerProvenByGuard;
|
|
58
|
+
} | {
|
|
59
|
+
whoMayCall: DoorCallerProvenByFunction;
|
|
60
|
+
provenBy: DoorProof;
|
|
61
|
+
});
|
|
62
|
+
/**
|
|
63
|
+
* The one check every door goes through. `proven` is who the door's guard found the caller to be; a caller a
|
|
64
|
+
* named function proves passes here, and that function refuses them, before anything else of the door runs.
|
|
65
|
+
*/
|
|
66
|
+
export declare function checkDoor(contract: DoorContract, proven: readonly DoorCaller[]): boolean;
|
|
67
|
+
/** Where a door's path parts come from: `:name` parts, each a [^/]+ unless named here, and a final `*` for the rest. */
|
|
68
|
+
export declare function doorPathPattern(path: string, partPatterns?: Readonly<Record<string, string>>): RegExp;
|
package/src/doors.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Door contracts (docs/DOORS.md). Every way into Zaisma, of every kind, states who may call it, what it may
|
|
3
|
+
* reach and what it trusts from other doors, and goes through checkDoor before its own code runs. Here, in the
|
|
4
|
+
* game server package's internal part, so a room on a developer's own server goes through the same check;
|
|
5
|
+
* never in the package's published index.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* The one check every door goes through. `proven` is who the door's guard found the caller to be; a caller a
|
|
9
|
+
* named function proves passes here, and that function refuses them, before anything else of the door runs.
|
|
10
|
+
*/
|
|
11
|
+
export function checkDoor(contract, proven) {
|
|
12
|
+
return contract.whoMayCall === "anyone" || "provenBy" in contract || proven.includes(contract.whoMayCall);
|
|
13
|
+
}
|
|
14
|
+
/** Where a door's path parts come from: `:name` parts, each a [^/]+ unless named here, and a final `*` for the rest. */
|
|
15
|
+
export function doorPathPattern(path, partPatterns = {}) {
|
|
16
|
+
const parts = path.split("/").map((part) => {
|
|
17
|
+
if (part === "*")
|
|
18
|
+
return ".*";
|
|
19
|
+
if (part.startsWith(":"))
|
|
20
|
+
return `(?<${part.slice(1)}>${partPatterns[part.slice(1)] ?? "[^/]+"})`;
|
|
21
|
+
return part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
22
|
+
});
|
|
23
|
+
return new RegExp(`^${parts.join("/")}$`);
|
|
24
|
+
}
|
package/src/internal.d.ts
CHANGED
package/src/internal.js
CHANGED
package/src/room.d.ts
CHANGED
|
@@ -1334,6 +1334,70 @@ export interface StateSyncHooks {
|
|
|
1334
1334
|
export declare const NO_MESSAGE_TYPE = "(none)";
|
|
1335
1335
|
export declare const BYTES_MESSAGE_TYPE = "(bytes)";
|
|
1336
1336
|
export declare const ZAISMA_MESSAGE_TYPE = "(zaisma)";
|
|
1337
|
+
/**
|
|
1338
|
+
* Every message a room takes (docs/DOORS.md). receive's switch is over these names, so a new one without a row
|
|
1339
|
+
* doesn't build. A spectator may send only what's for room members. hello comes first and proves the pass; chat is the host's to take before receive (readChatMessage);
|
|
1340
|
+
* bytes is every binary message (receiveBytes). A type with no row, maybe from a newer SDK, is counted and ignored.
|
|
1341
|
+
*/
|
|
1342
|
+
export declare const ROOM_MESSAGE_DOORS: {
|
|
1343
|
+
readonly hello: {
|
|
1344
|
+
readonly whoMayCall: "pass_holder";
|
|
1345
|
+
readonly reaches: "room";
|
|
1346
|
+
readonly trusts: readonly ["player_pass"];
|
|
1347
|
+
readonly provenBy: {
|
|
1348
|
+
readonly source_path: "packages/game-server/src/pass.ts";
|
|
1349
|
+
readonly symbol_name: "verifyPass";
|
|
1350
|
+
};
|
|
1351
|
+
};
|
|
1352
|
+
readonly probe_answer: import("./doors.ts").DoorContractFields & {
|
|
1353
|
+
whoMayCall: import("./doors.ts").DoorCallerProvenByGuard;
|
|
1354
|
+
};
|
|
1355
|
+
readonly room: import("./doors.ts").DoorContractFields & {
|
|
1356
|
+
whoMayCall: import("./doors.ts").DoorCallerProvenByGuard;
|
|
1357
|
+
};
|
|
1358
|
+
readonly kick: import("./doors.ts").DoorContractFields & {
|
|
1359
|
+
whoMayCall: import("./doors.ts").DoorCallerProvenByGuard;
|
|
1360
|
+
};
|
|
1361
|
+
readonly here: import("./doors.ts").DoorContractFields & {
|
|
1362
|
+
whoMayCall: import("./doors.ts").DoorCallerProvenByGuard;
|
|
1363
|
+
};
|
|
1364
|
+
readonly idle: import("./doors.ts").DoorContractFields & {
|
|
1365
|
+
whoMayCall: import("./doors.ts").DoorCallerProvenByGuard;
|
|
1366
|
+
};
|
|
1367
|
+
readonly hidden: import("./doors.ts").DoorContractFields & {
|
|
1368
|
+
whoMayCall: import("./doors.ts").DoorCallerProvenByGuard;
|
|
1369
|
+
};
|
|
1370
|
+
readonly shown: import("./doors.ts").DoorContractFields & {
|
|
1371
|
+
whoMayCall: import("./doors.ts").DoorCallerProvenByGuard;
|
|
1372
|
+
};
|
|
1373
|
+
readonly back: import("./doors.ts").DoorContractFields & {
|
|
1374
|
+
whoMayCall: import("./doors.ts").DoorCallerProvenByGuard;
|
|
1375
|
+
};
|
|
1376
|
+
readonly message: import("./doors.ts").DoorContractFields & {
|
|
1377
|
+
whoMayCall: import("./doors.ts").DoorCallerProvenByGuard;
|
|
1378
|
+
};
|
|
1379
|
+
readonly bytes: import("./doors.ts").DoorContractFields & {
|
|
1380
|
+
whoMayCall: import("./doors.ts").DoorCallerProvenByGuard;
|
|
1381
|
+
};
|
|
1382
|
+
readonly chat: import("./doors.ts").DoorContractFields & {
|
|
1383
|
+
whoMayCall: import("./doors.ts").DoorCallerProvenByGuard;
|
|
1384
|
+
};
|
|
1385
|
+
readonly block: import("./doors.ts").DoorContractFields & {
|
|
1386
|
+
whoMayCall: import("./doors.ts").DoorCallerProvenByGuard;
|
|
1387
|
+
};
|
|
1388
|
+
readonly unblock: import("./doors.ts").DoorContractFields & {
|
|
1389
|
+
whoMayCall: import("./doors.ts").DoorCallerProvenByGuard;
|
|
1390
|
+
};
|
|
1391
|
+
readonly friend_request: import("./doors.ts").DoorContractFields & {
|
|
1392
|
+
whoMayCall: import("./doors.ts").DoorCallerProvenByGuard;
|
|
1393
|
+
};
|
|
1394
|
+
readonly "zaisma:purchase": {
|
|
1395
|
+
readonly whoMayCall: "room_player";
|
|
1396
|
+
readonly reaches: "purchase";
|
|
1397
|
+
readonly trusts: readonly ["player_pass", "purchase_receipt"];
|
|
1398
|
+
};
|
|
1399
|
+
};
|
|
1400
|
+
export type RoomMessageType = keyof typeof ROOM_MESSAGE_DOORS;
|
|
1337
1401
|
export declare const OTHER_MESSAGE_TYPE = "(other)";
|
|
1338
1402
|
/** State sync's updates (stateSyncEnabled). */
|
|
1339
1403
|
export declare const STATE_MESSAGE_TYPE = "(state)";
|
package/src/room.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { CHAT_CHECK_LIMITS, CHAT_ROOM_LIMITS } from "./chat.js";
|
|
10
10
|
import { DatabaseError } from "./database.js";
|
|
11
|
+
import { checkDoor } from "./doors.js";
|
|
11
12
|
import { isRoomCode, roomCode } from "./names.js";
|
|
12
13
|
import { readSchedules } from "./schedules.js";
|
|
13
14
|
import { roomCallChecks } from "./room-calls.js";
|
|
@@ -502,6 +503,35 @@ export const STATE_SYNC = Symbol("stateSync");
|
|
|
502
503
|
export const NO_MESSAGE_TYPE = "(none)";
|
|
503
504
|
export const BYTES_MESSAGE_TYPE = "(bytes)";
|
|
504
505
|
export const ZAISMA_MESSAGE_TYPE = "(zaisma)";
|
|
506
|
+
const ROOM_MEMBER = { whoMayCall: "room_member", reaches: "room", trusts: ["player_pass"] };
|
|
507
|
+
const ROOM_PLAYER = { whoMayCall: "room_player", reaches: "room", trusts: ["player_pass"] };
|
|
508
|
+
const ROOM_HOST = { whoMayCall: "room_host", reaches: "room", trusts: ["player_pass"] };
|
|
509
|
+
/**
|
|
510
|
+
* Every message a room takes (docs/DOORS.md). receive's switch is over these names, so a new one without a row
|
|
511
|
+
* doesn't build. A spectator may send only what's for room members. hello comes first and proves the pass; chat is the host's to take before receive (readChatMessage);
|
|
512
|
+
* bytes is every binary message (receiveBytes). A type with no row, maybe from a newer SDK, is counted and ignored.
|
|
513
|
+
*/
|
|
514
|
+
export const ROOM_MESSAGE_DOORS = {
|
|
515
|
+
hello: { whoMayCall: "pass_holder", reaches: "room", trusts: ["player_pass"], provenBy: { source_path: "packages/game-server/src/pass.ts", symbol_name: "verifyPass" } },
|
|
516
|
+
probe_answer: ROOM_MEMBER,
|
|
517
|
+
room: ROOM_HOST,
|
|
518
|
+
kick: ROOM_HOST,
|
|
519
|
+
here: ROOM_PLAYER,
|
|
520
|
+
idle: ROOM_PLAYER,
|
|
521
|
+
hidden: ROOM_PLAYER,
|
|
522
|
+
shown: ROOM_PLAYER,
|
|
523
|
+
back: ROOM_PLAYER,
|
|
524
|
+
message: ROOM_PLAYER,
|
|
525
|
+
bytes: ROOM_PLAYER,
|
|
526
|
+
chat: ROOM_PLAYER,
|
|
527
|
+
block: ROOM_PLAYER,
|
|
528
|
+
unblock: ROOM_PLAYER,
|
|
529
|
+
friend_request: ROOM_PLAYER,
|
|
530
|
+
"zaisma:purchase": { whoMayCall: "room_player", reaches: "purchase", trusts: ["player_pass", "purchase_receipt"] },
|
|
531
|
+
};
|
|
532
|
+
const isRoomMessageType = (type) => typeof type === "string" && Object.hasOwn(ROOM_MESSAGE_DOORS, type);
|
|
533
|
+
const PLAYER_PROVEN = ["room_member", "room_player"];
|
|
534
|
+
const HOST_PROVEN = ["room_member", "room_player", "room_host"];
|
|
505
535
|
export const OTHER_MESSAGE_TYPE = "(other)";
|
|
506
536
|
/** State sync's updates (stateSyncEnabled). */
|
|
507
537
|
export const STATE_MESSAGE_TYPE = "(state)";
|
|
@@ -1759,7 +1789,10 @@ export class RoomCore {
|
|
|
1759
1789
|
const presence = this.presence.get(playerId);
|
|
1760
1790
|
if (presence)
|
|
1761
1791
|
presence.seenAtMs = this.now();
|
|
1762
|
-
|
|
1792
|
+
const type = isRoomMessageType(message.type) ? message.type : null;
|
|
1793
|
+
if (type && !checkDoor(ROOM_MESSAGE_DOORS[type], this.hostPlayerId === playerId ? HOST_PROVEN : PLAYER_PROVEN))
|
|
1794
|
+
return "not_allowed";
|
|
1795
|
+
switch (type) {
|
|
1763
1796
|
case "room":
|
|
1764
1797
|
return this.hostChange(playerId, message);
|
|
1765
1798
|
case "kick":
|
|
@@ -1838,7 +1871,7 @@ export class RoomCore {
|
|
|
1838
1871
|
if (text.length <= ROOM_LIMITS.maximumGameMessageSizeBytes) {
|
|
1839
1872
|
try {
|
|
1840
1873
|
const message = JSON.parse(text);
|
|
1841
|
-
if (message?.type === "
|
|
1874
|
+
if (isRoomMessageType(message?.type) && ROOM_MESSAGE_DOORS[message.type].whoMayCall === "room_member" && this.answer(spectatorId, message.probeNumber))
|
|
1842
1875
|
return null;
|
|
1843
1876
|
}
|
|
1844
1877
|
catch {
|
|
@@ -1910,8 +1943,6 @@ export class RoomCore {
|
|
|
1910
1943
|
* text between strangers), so the kicked player is told the host removed them.
|
|
1911
1944
|
*/
|
|
1912
1945
|
hostKick(playerId, message) {
|
|
1913
|
-
if (this.hostPlayerId !== playerId)
|
|
1914
|
-
return "not_allowed";
|
|
1915
1946
|
const kickedId = message.playerId;
|
|
1916
1947
|
if (typeof kickedId !== "string" || kickedId === playerId || !this.players.has(kickedId))
|
|
1917
1948
|
return "bad_message";
|
|
@@ -1920,8 +1951,6 @@ export class RoomCore {
|
|
|
1920
1951
|
}
|
|
1921
1952
|
/** The host hides, shows, opens, closes, turns friends' Join on or off, names or labels the room, when the game server allows it. */
|
|
1922
1953
|
hostChange(playerId, message) {
|
|
1923
|
-
if (this.hostPlayerId !== playerId)
|
|
1924
|
-
return "not_allowed";
|
|
1925
1954
|
const asked = message.room;
|
|
1926
1955
|
if (!asked || typeof asked !== "object" || Array.isArray(asked))
|
|
1927
1956
|
return "bad_message";
|