@zaisma/game-server 1.9.0 → 1.11.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 +4249 -1885
- package/bin/local-play.js +65 -0
- package/package.json +1 -1
- package/sdk/zaisma.d.ts +53 -0
- package/sdk/zaisma.js +79 -2
- package/src/qr-code.d.ts +5 -4
- package/src/qr-code.js +11 -10
- package/src/room.d.ts +11 -0
- package/src/room.js +16 -0
package/bin/local-play.js
CHANGED
|
@@ -145,6 +145,8 @@ function defaultState() {
|
|
|
145
145
|
// Each player's guest, while they play as one: { id, name }.
|
|
146
146
|
guests: {},
|
|
147
147
|
friends: [],
|
|
148
|
+
// Friend requests waiting between bench accounts: { from, to, createdAtMs }.
|
|
149
|
+
friendRequests: [],
|
|
148
150
|
blocks: [],
|
|
149
151
|
ownedItems: {},
|
|
150
152
|
items: [],
|
|
@@ -629,6 +631,8 @@ export async function startLocalPlay(givenOptions) {
|
|
|
629
631
|
if (rest === "" && method === "DELETE") {
|
|
630
632
|
state.accounts = state.accounts.filter((each) => each.id !== account.id);
|
|
631
633
|
state.friends = state.friends.filter((pair) => !pair.includes(account.id));
|
|
634
|
+
state.friendRequests = (state.friendRequests ?? []).filter((each) => each.from !== account.id && each.to !== account.id);
|
|
635
|
+
state.acceptedFriendRequests = (state.acceptedFriendRequests ?? []).filter((each) => each.from !== account.id && each.to !== account.id);
|
|
632
636
|
state.blocks = state.blocks.filter((block) => block.blocker !== account.id && block.blocked !== account.id);
|
|
633
637
|
state.purchases = state.purchases.filter((purchase) => purchase.accountId !== account.id);
|
|
634
638
|
delete state.ownedItems[account.id];
|
|
@@ -951,6 +955,67 @@ export async function startLocalPlay(givenOptions) {
|
|
|
951
955
|
}
|
|
952
956
|
return reply(200, { player: { player_id: target.playerId, name: target.username }, is_friend: friendIdsOf(account.id).includes(target.id), is_blocked: state.blocks.some((block) => block.blocker === account.id && block.blocked === target.id) });
|
|
953
957
|
}
|
|
958
|
+
// Friend requests between bench accounts, as Zaisma's game page sends and answers them (apps/site/src/friend-requests.ts).
|
|
959
|
+
if (path === "/friends/requests" || path.startsWith("/friends/requests/")) {
|
|
960
|
+
if (!account) return reply(401, { error: { code: "not_signed_in", message: "Sign in to add friends." } });
|
|
961
|
+
const requests = (state.friendRequests ??= []);
|
|
962
|
+
const isBlockedPair = (other) => state.blocks.some((block) => isPair([block.blocker, block.blocked], account.id, other.id));
|
|
963
|
+
const isFriend = (other) => friendIdsOf(account.id).includes(other.id);
|
|
964
|
+
const waitingFrom = (from, to) => requests.find((each) => each.from === from && each.to === to);
|
|
965
|
+
const makeFriends = (other) => {
|
|
966
|
+
// The sender hears of the accept on their next look, for a day, as on Zaisma.
|
|
967
|
+
(state.acceptedFriendRequests ??= []).push({ from: other.id, to: account.id, answeredAtMs: Date.now() });
|
|
968
|
+
state.friendRequests = requests.filter((each) => !isPair([each.from, each.to], account.id, other.id));
|
|
969
|
+
if (!isFriend(other)) state.friends.push([account.id, other.id]);
|
|
970
|
+
saveState();
|
|
971
|
+
log(`${account.username} and ${other.username} are friends now.`);
|
|
972
|
+
};
|
|
973
|
+
if (request.method === "GET" && path === "/friends/requests") {
|
|
974
|
+
const accepted = (state.acceptedFriendRequests ?? []).filter((each) => each.from === account.id && each.answeredAtMs > Date.now() - 86400000 && isFriend({ id: each.to })).map((each) => ({ to: accountOf(each.to), answeredAtMs: each.answeredAtMs })).filter((each) => each.to);
|
|
975
|
+
return reply(200, {
|
|
976
|
+
requests: requests.filter((each) => each.to === account.id).map((each) => ({ from: accountOf(each.from), createdAtMs: each.createdAtMs })).filter((each) => each.from).map((each) => ({ player_id: each.from.playerId, name: each.from.username, created_at_ms: each.createdAtMs, game_slug: gameSlug })).reverse(),
|
|
977
|
+
accepted: accepted.map((each) => ({ player_id: each.to.playerId, name: each.to.username, answered_at_ms: each.answeredAtMs })).reverse(),
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
const aboutMatch = /^\/friends\/requests\/about\/(player_[a-z0-9]{1,40})$/.exec(path);
|
|
981
|
+
if (request.method === "GET" && aboutMatch) {
|
|
982
|
+
const other = accountOfPlayer(aboutMatch[1]);
|
|
983
|
+
if (!other || other.id === account.id) return reply(200, { player: null, can_send: false, has_sent: false, is_friend: false });
|
|
984
|
+
const hasSent = Boolean(waitingFrom(account.id, other.id));
|
|
985
|
+
const canSend = !isFriend(other) && !hasSent && !isBlockedPair(other) && other.ageBand === account.ageBand && (Boolean(waitingFrom(other.id, account.id)) || glass.takesFriendRequests(other.ageBand, other.socialMode));
|
|
986
|
+
return reply(200, { player: { player_id: other.playerId, name: other.username }, can_send: canSend, has_sent: hasSent, is_friend: isFriend(other) });
|
|
987
|
+
}
|
|
988
|
+
if (isPost && path === "/friends/requests") {
|
|
989
|
+
const body = await readJson(request);
|
|
990
|
+
const other = typeof body.player_id === "string" ? accountOfPlayer(body.player_id) : null;
|
|
991
|
+
if (!other || other.id === account.id) return reply(409, { error: { code: "not_an_account", message: "Only players signed in to Zaisma take friend requests." } });
|
|
992
|
+
if (isFriend(other)) return reply(409, { error: { code: "already_friends", message: "You're friends already." } });
|
|
993
|
+
if (waitingFrom(other.id, account.id)) {
|
|
994
|
+
makeFriends(other);
|
|
995
|
+
return reply(200, { status: "friends" });
|
|
996
|
+
}
|
|
997
|
+
if (waitingFrom(account.id, other.id)) return reply(409, { error: { code: "already_sent", message: "Your request is waiting for them." } });
|
|
998
|
+
if (isBlockedPair(other) || other.ageBand !== account.ageBand || !glass.takesFriendRequests(other.ageBand, other.socialMode)) return reply(409, { error: { code: "not_taking_requests", message: "They aren't taking friend requests from you." } });
|
|
999
|
+
requests.push({ from: account.id, to: other.id, createdAtMs: Date.now() });
|
|
1000
|
+
saveState();
|
|
1001
|
+
log(`${account.username} sent ${other.username} a friend request.`);
|
|
1002
|
+
return reply(200, { status: "sent" });
|
|
1003
|
+
}
|
|
1004
|
+
const answerMatch = /^\/friends\/requests\/(player_[a-z0-9]{1,40})\/(accept|turn-down)$/.exec(path);
|
|
1005
|
+
if (isPost && answerMatch) {
|
|
1006
|
+
await readText(request);
|
|
1007
|
+
const other = accountOfPlayer(answerMatch[1]);
|
|
1008
|
+
if (!other || !waitingFrom(other.id, account.id)) return reply(404, { error: { code: "request_not_found", message: "That request isn't waiting any more." } });
|
|
1009
|
+
if (answerMatch[2] === "accept") makeFriends(other);
|
|
1010
|
+
else {
|
|
1011
|
+
state.friendRequests = requests.filter((each) => !(each.from === other.id && each.to === account.id));
|
|
1012
|
+
saveState();
|
|
1013
|
+
log(`${account.username} turned down ${other.username}'s friend request.`);
|
|
1014
|
+
}
|
|
1015
|
+
return reply(200, { answer: answerMatch[2] === "accept" ? "accepted" : "turned_down" });
|
|
1016
|
+
}
|
|
1017
|
+
return reply(404, { error: { code: "not_found", message: "Nothing here." } });
|
|
1018
|
+
}
|
|
954
1019
|
// A report goes to Zaisma's moderators, or the game's developer: here the terminal says what it would send.
|
|
955
1020
|
if (isPost && path === `${game}/report`) {
|
|
956
1021
|
const body = await readJson(request);
|
package/package.json
CHANGED
package/sdk/zaisma.d.ts
CHANGED
|
@@ -240,8 +240,35 @@ declare namespace Zaisma {
|
|
|
240
240
|
* await Zaisma.friends.setRoom(null); // left the match
|
|
241
241
|
*/
|
|
242
242
|
setRoom(room: OwnServerRoom | null): Promise<void>;
|
|
243
|
+
/**
|
|
244
|
+
* Offer the player to send another player a friend request, from your own scoreboard, lobby or menu.
|
|
245
|
+
* Zaisma asks the player first on its page ("Send Mia a friend request?", with the name Zaisma knows
|
|
246
|
+
* them by), so nothing is sent without them, and the other player gets it over their game's corner and
|
|
247
|
+
* on zaisma.com/friends. Players can also send one from Players in Zaisma's bar, so a button of your
|
|
248
|
+
* own is optional. Resolves with what happened: "sent"; "cancelled" when the player said no or another
|
|
249
|
+
* question was up; "already_friends"; or "not_taking_requests" when the other player doesn't take them
|
|
250
|
+
* from this one (their settings, their age group, a block, or a guest), which never says which. Your game
|
|
251
|
+
* never hears of requests themselves, only that two players are friends (room.areFriends on your game server, and the
|
|
252
|
+
* passes' tags, from their next join). Fails with error.code "account_needed" for a guest (ask them to
|
|
253
|
+
* sign in with {@link player.signIn}), "bad_player_id" for something that isn't a player's id, and
|
|
254
|
+
* "not_on_zaisma" when the game isn't played on Zaisma.
|
|
255
|
+
*
|
|
256
|
+
* @example
|
|
257
|
+
* // A button beside each player on your end-of-match screen.
|
|
258
|
+
* const { status } = await Zaisma.friends.sendRequest(player.id);
|
|
259
|
+
* if (status === "sent") button.textContent = "Request sent";
|
|
260
|
+
*/
|
|
261
|
+
sendRequest(playerId: string): Promise<FriendRequestAnswer>;
|
|
243
262
|
}
|
|
244
263
|
|
|
264
|
+
/** What became of a friend request the game offered ({@link Friends.sendRequest}). */
|
|
265
|
+
interface FriendRequestAnswer {
|
|
266
|
+
status: FriendRequestStatus;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** "sent", "cancelled" (the player said no), "already_friends", or "not_taking_requests" (never why). */
|
|
270
|
+
type FriendRequestStatus = "sent" | "cancelled" | "already_friends" | "not_taking_requests";
|
|
271
|
+
|
|
245
272
|
/** The player's friends playing this game ({@link Friends.list}). */
|
|
246
273
|
interface FriendList {
|
|
247
274
|
/** By name. Empty when none of them are playing it. */
|
|
@@ -539,6 +566,32 @@ declare namespace Zaisma {
|
|
|
539
566
|
*/
|
|
540
567
|
const versions: Versions;
|
|
541
568
|
|
|
569
|
+
/**
|
|
570
|
+
* Whether Zaisma's page covers the game right now, so a game can pause or hold the player's input while it does.
|
|
571
|
+
* coveredBy is "sheet" while one of Zaisma's sheets is open over the game (buying, Players, Report: the player is
|
|
572
|
+
* busy with it), "card" while one of its cards is over the game's corner (Still there?, Keep your progress; the
|
|
573
|
+
* game stays in view and playable), and null otherwise. Always null outside Zaisma's page (your own site, a
|
|
574
|
+
* local test). Listen for "coveredBy" to hear each change.
|
|
575
|
+
* @example
|
|
576
|
+
* Zaisma.page.on("coveredBy", (coveredBy) => {
|
|
577
|
+
* if (coveredBy === "sheet") pauseGame();
|
|
578
|
+
* else resumeGame();
|
|
579
|
+
* });
|
|
580
|
+
*/
|
|
581
|
+
const page: Page;
|
|
582
|
+
|
|
583
|
+
/** Whether Zaisma's page covers the game ({@link page}). */
|
|
584
|
+
interface Page {
|
|
585
|
+
/** "sheet", "card" or null: what of Zaisma's is over the game now. Read-only. */
|
|
586
|
+
readonly coveredBy: CoveredBy;
|
|
587
|
+
/** Listen for "coveredBy": called with the new value each time it changes. A wrong name fails with error.code "unknown_event". */
|
|
588
|
+
on(event: "coveredBy", listener: (coveredBy: CoveredBy) => void): Page;
|
|
589
|
+
off(event: "coveredBy", listener: (coveredBy: CoveredBy) => void): Page;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/** What of Zaisma's covers the game ({@link page}): one of its sheets, one of its cards, or nothing. */
|
|
593
|
+
type CoveredBy = "sheet" | "card" | null;
|
|
594
|
+
|
|
542
595
|
/** A version published "now" ({@link versions}). */
|
|
543
596
|
interface Versions {
|
|
544
597
|
/** Listen for "update". A wrong name fails with error.code "unknown_event". */
|
package/sdk/zaisma.js
CHANGED
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
var SAVES_EVENTS = ["conflict", "developerChange"];
|
|
38
38
|
var ITEMS_EVENTS = ["purchase", "refund", "sheetOpen", "sheetClose"];
|
|
39
39
|
var VERSIONS_EVENTS = ["update"];
|
|
40
|
+
var PAGE_EVENTS = ["coveredBy"];
|
|
40
41
|
var BUY_OPTIONS = ["itemCount", "gameAccountId"];
|
|
41
42
|
/** The room messages this SDK speaks (zaisma.d.ts: the wire protocol), sent in the hello. */
|
|
42
43
|
var PROTOCOL_VERSION_NUMBER = 1;
|
|
@@ -617,6 +618,9 @@
|
|
|
617
618
|
} else if (message.type === "blocks") {
|
|
618
619
|
room._blocks = new Set(Array.isArray(message.playerIds) ? message.playerIds : []);
|
|
619
620
|
room._mutedChanged();
|
|
621
|
+
} else if (message.type === "friend_request") {
|
|
622
|
+
// Someone here sent this player a friend request, or accepted theirs: the Zaisma page looks now (never who).
|
|
623
|
+
if (onZaismaPage()) window.parent.postMessage({ type: "zaisma:friend-requests" }, config.site);
|
|
620
624
|
} else if (message.type === "message") {
|
|
621
625
|
room._emit("message", message.data, typeof message.fromPlayerId === "string" ? message.fromPlayerId : null);
|
|
622
626
|
} else if (message.type === "chat" || message.type === "chat_sent" || message.type === "chat_stopped" || (message.type === "error" && typeof message.chatNumber === "number")) {
|
|
@@ -2020,6 +2024,34 @@
|
|
|
2020
2024
|
});
|
|
2021
2025
|
}
|
|
2022
2026
|
|
|
2027
|
+
/*
|
|
2028
|
+
* Whether Zaisma's page covers the game: "sheet" while one of its sheets is open over it (buying, Players, Report), "card" while one of
|
|
2029
|
+
* its cards is over the game's corner, null otherwise, and always null outside Zaisma's page. The page says so whenever it changes
|
|
2030
|
+
* (zaisma:covered), and only the page is heard. A game can pause while a sheet covers it.
|
|
2031
|
+
*/
|
|
2032
|
+
var pageEvents = new Emitter(PAGE_EVENTS, "Zaisma.page has");
|
|
2033
|
+
var coveredBy = null;
|
|
2034
|
+
var zaismaPage = {
|
|
2035
|
+
on: function (event, listener) {
|
|
2036
|
+
pageEvents.on(event, listener);
|
|
2037
|
+
return zaismaPage;
|
|
2038
|
+
},
|
|
2039
|
+
off: function (event, listener) {
|
|
2040
|
+
pageEvents.off(event, listener);
|
|
2041
|
+
return zaismaPage;
|
|
2042
|
+
},
|
|
2043
|
+
};
|
|
2044
|
+
Object.defineProperty(zaismaPage, "coveredBy", { enumerable: true, get: function () { return coveredBy; } });
|
|
2045
|
+
if (config.site && window.parent !== window) {
|
|
2046
|
+
window.addEventListener("message", function (event) {
|
|
2047
|
+
if (event.origin !== config.site || event.source !== window.parent || !event.data || event.data.type !== "zaisma:covered") return;
|
|
2048
|
+
var now = event.data.coveredBy === "sheet" || event.data.coveredBy === "card" ? event.data.coveredBy : null;
|
|
2049
|
+
if (now === coveredBy) return;
|
|
2050
|
+
coveredBy = now;
|
|
2051
|
+
pageEvents._emit("coveredBy", now);
|
|
2052
|
+
});
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2023
2055
|
/*
|
|
2024
2056
|
* The player's cloud saves, for games that draw their own save screens. The game page on Zaisma holds
|
|
2025
2057
|
* the player's session and answers (apps/site/src/saves-client.ts); the game never talks to a server.
|
|
@@ -2457,6 +2489,51 @@
|
|
|
2457
2489
|
}
|
|
2458
2490
|
|
|
2459
2491
|
var rooms = { list: listRooms, quickJoin: quickJoin, linkedRoomCode: roomCodeFromPage(), linkedServerUrl: roomCodeFromPage() ? serverUrlFromPage() : null, isLinkedAsSpectator: watchFromPage() };
|
|
2460
|
-
|
|
2461
|
-
|
|
2492
|
+
/*
|
|
2493
|
+
* Friend requests (zaisma.d.ts: Friends.sendRequest): the Zaisma page asks the player before anything is
|
|
2494
|
+
* sent, with the name Zaisma knows the other player by, so no game sends one on a player's behalf. Once one
|
|
2495
|
+
* is sent, from here or the page's Players, the rooms the other player is in tell their game to have their
|
|
2496
|
+
* page look now; Zaisma's record is the request itself.
|
|
2497
|
+
*/
|
|
2498
|
+
var friendRequestAsk = null;
|
|
2499
|
+
var FRIEND_REQUEST_STATUSES = ["sent", "cancelled", "already_friends", "not_taking_requests"];
|
|
2500
|
+
function sendFriendRequest(playerId) {
|
|
2501
|
+
if (!onZaismaPage() || !config.rooms) return Promise.reject(notOnZaisma("Friend requests come from Zaisma, so they work only in games played on Zaisma."));
|
|
2502
|
+
if (typeof playerId !== "string" || !/^player_[a-z0-9]{1,40}$/.test(playerId)) return Promise.reject(codedError("bad_player_id", "Zaisma.friends.sendRequest takes a player's id from room.players or Zaisma.friends.list."));
|
|
2503
|
+
return getPass().then(function (mine) {
|
|
2504
|
+
if (mine.kind !== "account") throw codedError("account_needed", "Friend requests are for players signed in to Zaisma: ask the player to sign in first (Zaisma.player.signIn).");
|
|
2505
|
+
// One question at a time: another request while the page is asking is a no.
|
|
2506
|
+
if (friendRequestAsk) return { status: "cancelled" };
|
|
2507
|
+
friendRequestAsk = new Promise(function (resolve) {
|
|
2508
|
+
var isAsking = false;
|
|
2509
|
+
var timer = setTimeout(function () { if (!isAsking) settle("cancelled"); }, 3000);
|
|
2510
|
+
function settle(status) {
|
|
2511
|
+
removeEventListener("message", onMessage);
|
|
2512
|
+
clearTimeout(timer);
|
|
2513
|
+
friendRequestAsk = null;
|
|
2514
|
+
resolve({ status: status });
|
|
2515
|
+
}
|
|
2516
|
+
function onMessage(event) {
|
|
2517
|
+
var told = event.data;
|
|
2518
|
+
if (event.origin !== config.site || event.source !== window.parent || !told || told.type !== "zaisma:friend-request" || told.playerId !== playerId) return;
|
|
2519
|
+
if (told.status === "asking") isAsking = true;
|
|
2520
|
+
else if (FRIEND_REQUEST_STATUSES.indexOf(told.status) !== -1) settle(told.status);
|
|
2521
|
+
}
|
|
2522
|
+
addEventListener("message", onMessage);
|
|
2523
|
+
window.parent.postMessage({ type: "zaisma:friend-request", playerId: playerId }, config.site);
|
|
2524
|
+
});
|
|
2525
|
+
return friendRequestAsk;
|
|
2526
|
+
});
|
|
2527
|
+
}
|
|
2528
|
+
// A request sent (by the game's own button or the page's Players), or one accepted: the rooms the other player is in nudge their game.
|
|
2529
|
+
window.addEventListener("message", function (event) {
|
|
2530
|
+
var told = event.data;
|
|
2531
|
+
if (!config.site || event.origin !== config.site || event.source !== window.parent || !told || told.type !== "zaisma:friend-request" || (told.status !== "sent" && told.status !== "accepted") || typeof told.playerId !== "string") return;
|
|
2532
|
+
joined.forEach(function (room) {
|
|
2533
|
+
if (!room._done && !room.isSpectator && room.players.some(function (each) { return each.id === told.playerId; })) room._say({ type: "friend_request", playerId: told.playerId });
|
|
2534
|
+
});
|
|
2535
|
+
});
|
|
2536
|
+
|
|
2537
|
+
var friends = { list: listFriends, setRoom: setOwnRoom, sendRequest: sendFriendRequest };
|
|
2538
|
+
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 };
|
|
2462
2539
|
})();
|
package/src/qr-code.d.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* QR codes for game and room links (docs/DECISIONS.md, "QR codes"): players get one behind Share,
|
|
3
|
-
* developers on the dashboard and through the API and MCP. Byte mode, error correction level
|
|
4
|
-
*
|
|
3
|
+
* developers on the dashboard and through the API and MCP. Byte mode, error correction level H, so a
|
|
4
|
+
* small picture over its middle (a game's icon) still leaves it readable, versions 1 to 10 (up to 119
|
|
5
|
+
* bytes, far more than a link needs). Written here rather than taken from a
|
|
5
6
|
* package so the site's page and the API draw the same code with no dependency; it follows ISO/IEC
|
|
6
7
|
* 18004 the way Project Nayuki's reference encoder does.
|
|
7
8
|
*/
|
|
8
9
|
export declare const QR_CODE_LIMITS: {
|
|
9
|
-
/** Version 10 at level
|
|
10
|
-
readonly maximumTextBytes:
|
|
10
|
+
/** Version 10 at level H holds 119 bytes. */
|
|
11
|
+
readonly maximumTextBytes: 119;
|
|
11
12
|
/** Light modules around the code, as the standard asks. */
|
|
12
13
|
readonly quietZoneModuleCount: 4;
|
|
13
14
|
};
|
package/src/qr-code.js
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* QR codes for game and room links (docs/DECISIONS.md, "QR codes"): players get one behind Share,
|
|
3
|
-
* developers on the dashboard and through the API and MCP. Byte mode, error correction level
|
|
4
|
-
*
|
|
3
|
+
* developers on the dashboard and through the API and MCP. Byte mode, error correction level H, so a
|
|
4
|
+
* small picture over its middle (a game's icon) still leaves it readable, versions 1 to 10 (up to 119
|
|
5
|
+
* bytes, far more than a link needs). Written here rather than taken from a
|
|
5
6
|
* package so the site's page and the API draw the same code with no dependency; it follows ISO/IEC
|
|
6
7
|
* 18004 the way Project Nayuki's reference encoder does.
|
|
7
8
|
*/
|
|
8
9
|
export const QR_CODE_LIMITS = {
|
|
9
|
-
/** Version 10 at level
|
|
10
|
-
maximumTextBytes:
|
|
10
|
+
/** Version 10 at level H holds 119 bytes. */
|
|
11
|
+
maximumTextBytes: 119,
|
|
11
12
|
/** Light modules around the code, as the standard asks. */
|
|
12
13
|
quietZoneModuleCount: 4,
|
|
13
14
|
};
|
|
14
|
-
// Level
|
|
15
|
-
const ERROR_CORRECTION_CODEWORD_COUNTS_PER_BLOCK = [0,
|
|
16
|
-
const ERROR_CORRECTION_BLOCK_COUNTS = [0, 1, 1,
|
|
17
|
-
/** Error correction level
|
|
18
|
-
const
|
|
15
|
+
// Level H: error correction codewords per block and blocks, by version (index 0 unused).
|
|
16
|
+
const ERROR_CORRECTION_CODEWORD_COUNTS_PER_BLOCK = [0, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28];
|
|
17
|
+
const ERROR_CORRECTION_BLOCK_COUNTS = [0, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8];
|
|
18
|
+
/** Error correction level H (high)'s two bits in the format information. */
|
|
19
|
+
const HIGH_CORRECTION_FORMAT_NUMBER = 2;
|
|
19
20
|
const HIGHEST_VERSION_NUMBER = 10;
|
|
20
21
|
const isBitSet = (bitsNumber, bitIndex) => ((bitsNumber >>> bitIndex) & 1) !== 0;
|
|
21
22
|
function rawDataModuleCount(versionNumber) {
|
|
@@ -226,7 +227,7 @@ export function qrCodeModules(text) {
|
|
|
226
227
|
}
|
|
227
228
|
}));
|
|
228
229
|
const drawFormat = (maskIndex) => {
|
|
229
|
-
const formatNumber = (
|
|
230
|
+
const formatNumber = (HIGH_CORRECTION_FORMAT_NUMBER << 3) | maskIndex;
|
|
230
231
|
let remainderNumber = formatNumber;
|
|
231
232
|
for (let stepIndex = 0; stepIndex < 10; stepIndex++)
|
|
232
233
|
remainderNumber = (remainderNumber << 1) ^ ((remainderNumber >>> 9) * 0x537);
|
package/src/room.d.ts
CHANGED
|
@@ -892,6 +892,8 @@ export type ServerMessage = {
|
|
|
892
892
|
} | {
|
|
893
893
|
type: "blocks";
|
|
894
894
|
playerIds: string[];
|
|
895
|
+
} | {
|
|
896
|
+
type: "friend_request";
|
|
895
897
|
} | {
|
|
896
898
|
type: "chat";
|
|
897
899
|
fromPlayerId: string;
|
|
@@ -959,6 +961,9 @@ export type ClientMessage = {
|
|
|
959
961
|
} | {
|
|
960
962
|
type: "unblock";
|
|
961
963
|
playerId: string;
|
|
964
|
+
} | {
|
|
965
|
+
type: "friend_request";
|
|
966
|
+
playerId: string;
|
|
962
967
|
} | {
|
|
963
968
|
type: "probe_answer";
|
|
964
969
|
probeNumber: number;
|
|
@@ -1427,6 +1432,12 @@ export declare class RoomCore<S = any> {
|
|
|
1427
1432
|
private tellBlocks;
|
|
1428
1433
|
/** A player's game blocked a player in the room (a guest's block, or one just made), or took one back. */
|
|
1429
1434
|
private sessionBlock;
|
|
1435
|
+
/**
|
|
1436
|
+
* A player's game page just sent another player here a friend request, or accepted theirs (docs/DECISIONS.md,
|
|
1437
|
+
* "Friend requests in games"): the room tells that player's game to have their page look now, rather than at its next check-in.
|
|
1438
|
+
* Zaisma's record is the request; this says only "look", never who it's from, so a lying client gains nothing.
|
|
1439
|
+
*/
|
|
1440
|
+
private friendRequestTold;
|
|
1430
1441
|
/** Players in the match who haven't walked away: what room lists and "playing now" count. */
|
|
1431
1442
|
get playingCount(): number;
|
|
1432
1443
|
/**
|
package/src/room.js
CHANGED
|
@@ -1071,6 +1071,20 @@ export class RoomCore {
|
|
|
1071
1071
|
this.tellBlocks();
|
|
1072
1072
|
return null;
|
|
1073
1073
|
}
|
|
1074
|
+
/**
|
|
1075
|
+
* A player's game page just sent another player here a friend request, or accepted theirs (docs/DECISIONS.md,
|
|
1076
|
+
* "Friend requests in games"): the room tells that player's game to have their page look now, rather than at its next check-in.
|
|
1077
|
+
* Zaisma's record is the request; this says only "look", never who it's from, so a lying client gains nothing.
|
|
1078
|
+
*/
|
|
1079
|
+
friendRequestTold(playerId, message) {
|
|
1080
|
+
const otherPlayerId = message.playerId;
|
|
1081
|
+
if (typeof otherPlayerId !== "string" || otherPlayerId === playerId || !this.players.has(otherPlayerId))
|
|
1082
|
+
return "bad_message";
|
|
1083
|
+
if (this.hasBlockBetween(playerId, otherPlayerId))
|
|
1084
|
+
return null;
|
|
1085
|
+
this.sendText(otherPlayerId, JSON.stringify({ type: "friend_request" }));
|
|
1086
|
+
return null;
|
|
1087
|
+
}
|
|
1074
1088
|
/** Players in the match who haven't walked away: what room lists and "playing now" count. */
|
|
1075
1089
|
get playingCount() {
|
|
1076
1090
|
return this.players.size - this.kept.size;
|
|
@@ -1691,6 +1705,8 @@ export class RoomCore {
|
|
|
1691
1705
|
return this.sessionBlock(playerId, message, true);
|
|
1692
1706
|
case "unblock":
|
|
1693
1707
|
return this.sessionBlock(playerId, message, false);
|
|
1708
|
+
case "friend_request":
|
|
1709
|
+
return this.friendRequestTold(playerId, message);
|
|
1694
1710
|
default:
|
|
1695
1711
|
// A type this room doesn't know, maybe from a newer SDK: counted and limited above, then ignored.
|
|
1696
1712
|
return typeof message.type === "string" ? null : "bad_message";
|