@zaisma/game-server 1.4.0 → 1.6.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/README.md +2 -0
- package/bench/brand/.assetsignore +4 -0
- package/bench/brand/README.md +51 -0
- package/bench/brand/_headers +13 -0
- package/bench/brand/app-icon-1024.png +0 -0
- package/bench/brand/app-icon-192.png +0 -0
- package/bench/brand/app-icon-512.png +0 -0
- package/bench/brand/apple-touch-icon.png +0 -0
- package/bench/brand/cover-guide/coin-cascade-busy-fine.webp +0 -0
- package/bench/brand/cover-guide/gem-grid-cover.webp +0 -0
- package/bench/brand/cover-guide/gem-grid-screenshot.webp +0 -0
- package/bench/brand/cover-guide/good-arena.webp +0 -0
- package/bench/brand/cover-guide/good-four-in-a-row.webp +0 -0
- package/bench/brand/cover-guide/lava-leap-extra-text.webp +0 -0
- package/bench/brand/cover-guide/lava-leap-fake-reward.webp +0 -0
- package/bench/brand/cover-guide/lava-leap-fine.webp +0 -0
- package/bench/brand/cover-guide/lava-leap-overlay.webp +0 -0
- package/bench/brand/cover-guide/screenshot-fine.webp +0 -0
- package/bench/brand/cover-guide/screenshot-not-fine.webp +0 -0
- package/bench/brand/design/covers/ashen-crown.webp +0 -0
- package/bench/brand/design/covers/frostline.webp +0 -0
- package/bench/brand/design/covers/glasswing.webp +0 -0
- package/bench/brand/design/covers/hollow-tide.webp +0 -0
- package/bench/brand/design/covers/iron-vigil.webp +0 -0
- package/bench/brand/design/covers/kestrel.webp +0 -0
- package/bench/brand/design/covers/neon-drift.webp +0 -0
- package/bench/brand/design/covers/verdant.webp +0 -0
- package/bench/brand/favicon-32.png +0 -0
- package/bench/brand/favicon.ico +0 -0
- package/bench/brand/favicon.svg +1 -0
- package/bench/brand/flags/LICENSE +21 -0
- package/bench/brand/flags/ad.png +0 -0
- package/bench/brand/flags/ae.png +0 -0
- package/bench/brand/flags/af.png +0 -0
- package/bench/brand/flags/ag.png +0 -0
- package/bench/brand/flags/ai.png +0 -0
- package/bench/brand/flags/al.png +0 -0
- package/bench/brand/flags/am.png +0 -0
- package/bench/brand/flags/ao.png +0 -0
- package/bench/brand/flags/aq.png +0 -0
- package/bench/brand/flags/ar.png +0 -0
- package/bench/brand/flags/as.png +0 -0
- package/bench/brand/flags/at.png +0 -0
- package/bench/brand/flags/au.png +0 -0
- package/bench/brand/flags/aw.png +0 -0
- package/bench/brand/flags/ax.png +0 -0
- package/bench/brand/flags/az.png +0 -0
- package/bench/brand/flags/ba.png +0 -0
- package/bench/brand/flags/bb.png +0 -0
- package/bench/brand/flags/bd.png +0 -0
- package/bench/brand/flags/be.png +0 -0
- package/bench/brand/flags/bf.png +0 -0
- package/bench/brand/flags/bg.png +0 -0
- package/bench/brand/flags/bh.png +0 -0
- package/bench/brand/flags/bi.png +0 -0
- package/bench/brand/flags/bj.png +0 -0
- package/bench/brand/flags/bl.png +0 -0
- package/bench/brand/flags/bm.png +0 -0
- package/bench/brand/flags/bn.png +0 -0
- package/bench/brand/flags/bo.png +0 -0
- package/bench/brand/flags/bq.png +0 -0
- package/bench/brand/flags/br.png +0 -0
- package/bench/brand/flags/bs.png +0 -0
- package/bench/brand/flags/bt.png +0 -0
- package/bench/brand/flags/bv.png +0 -0
- package/bench/brand/flags/bw.png +0 -0
- package/bench/brand/flags/by.png +0 -0
- package/bench/brand/flags/bz.png +0 -0
- package/bench/brand/flags/ca.png +0 -0
- package/bench/brand/flags/cc.png +0 -0
- package/bench/brand/flags/cd.png +0 -0
- package/bench/brand/flags/cf.png +0 -0
- package/bench/brand/flags/cg.png +0 -0
- package/bench/brand/flags/ch.png +0 -0
- package/bench/brand/flags/ci.png +0 -0
- package/bench/brand/flags/ck.png +0 -0
- package/bench/brand/flags/cl.png +0 -0
- package/bench/brand/flags/cm.png +0 -0
- package/bench/brand/flags/cn.png +0 -0
- package/bench/brand/flags/co.png +0 -0
- package/bench/brand/flags/cp.png +0 -0
- package/bench/brand/flags/cr.png +0 -0
- package/bench/brand/flags/cu.png +0 -0
- package/bench/brand/flags/cv.png +0 -0
- package/bench/brand/flags/cw.png +0 -0
- package/bench/brand/flags/cx.png +0 -0
- package/bench/brand/flags/cy.png +0 -0
- package/bench/brand/flags/cz.png +0 -0
- package/bench/brand/flags/de.png +0 -0
- package/bench/brand/flags/dg.png +0 -0
- package/bench/brand/flags/dj.png +0 -0
- package/bench/brand/flags/dk.png +0 -0
- package/bench/brand/flags/dm.png +0 -0
- package/bench/brand/flags/do.png +0 -0
- package/bench/brand/flags/dz.png +0 -0
- package/bench/brand/flags/ec.png +0 -0
- package/bench/brand/flags/ee.png +0 -0
- package/bench/brand/flags/eg.png +0 -0
- package/bench/brand/flags/eh.png +0 -0
- package/bench/brand/flags/er.png +0 -0
- package/bench/brand/flags/es.png +0 -0
- package/bench/brand/flags/et.png +0 -0
- package/bench/brand/flags/eu.png +0 -0
- package/bench/brand/flags/fi.png +0 -0
- package/bench/brand/flags/fj.png +0 -0
- package/bench/brand/flags/fk.png +0 -0
- package/bench/brand/flags/fm.png +0 -0
- package/bench/brand/flags/fo.png +0 -0
- package/bench/brand/flags/fr.png +0 -0
- package/bench/brand/flags/ga.png +0 -0
- package/bench/brand/flags/gb.png +0 -0
- package/bench/brand/flags/gd.png +0 -0
- package/bench/brand/flags/ge.png +0 -0
- package/bench/brand/flags/gf.png +0 -0
- package/bench/brand/flags/gg.png +0 -0
- package/bench/brand/flags/gh.png +0 -0
- package/bench/brand/flags/gi.png +0 -0
- package/bench/brand/flags/gl.png +0 -0
- package/bench/brand/flags/gm.png +0 -0
- package/bench/brand/flags/gn.png +0 -0
- package/bench/brand/flags/gp.png +0 -0
- package/bench/brand/flags/gq.png +0 -0
- package/bench/brand/flags/gr.png +0 -0
- package/bench/brand/flags/gs.png +0 -0
- package/bench/brand/flags/gt.png +0 -0
- package/bench/brand/flags/gu.png +0 -0
- package/bench/brand/flags/gw.png +0 -0
- package/bench/brand/flags/gy.png +0 -0
- package/bench/brand/flags/hk.png +0 -0
- package/bench/brand/flags/hm.png +0 -0
- package/bench/brand/flags/hn.png +0 -0
- package/bench/brand/flags/hr.png +0 -0
- package/bench/brand/flags/ht.png +0 -0
- package/bench/brand/flags/hu.png +0 -0
- package/bench/brand/flags/ic.png +0 -0
- package/bench/brand/flags/id.png +0 -0
- package/bench/brand/flags/ie.png +0 -0
- package/bench/brand/flags/il.png +0 -0
- package/bench/brand/flags/im.png +0 -0
- package/bench/brand/flags/in.png +0 -0
- package/bench/brand/flags/io.png +0 -0
- package/bench/brand/flags/iq.png +0 -0
- package/bench/brand/flags/ir.png +0 -0
- package/bench/brand/flags/is.png +0 -0
- package/bench/brand/flags/it.png +0 -0
- package/bench/brand/flags/je.png +0 -0
- package/bench/brand/flags/jm.png +0 -0
- package/bench/brand/flags/jo.png +0 -0
- package/bench/brand/flags/jp.png +0 -0
- package/bench/brand/flags/ke.png +0 -0
- package/bench/brand/flags/kg.png +0 -0
- package/bench/brand/flags/kh.png +0 -0
- package/bench/brand/flags/ki.png +0 -0
- package/bench/brand/flags/km.png +0 -0
- package/bench/brand/flags/kn.png +0 -0
- package/bench/brand/flags/kp.png +0 -0
- package/bench/brand/flags/kr.png +0 -0
- package/bench/brand/flags/kw.png +0 -0
- package/bench/brand/flags/ky.png +0 -0
- package/bench/brand/flags/kz.png +0 -0
- package/bench/brand/flags/la.png +0 -0
- package/bench/brand/flags/lb.png +0 -0
- package/bench/brand/flags/lc.png +0 -0
- package/bench/brand/flags/li.png +0 -0
- package/bench/brand/flags/lk.png +0 -0
- package/bench/brand/flags/lr.png +0 -0
- package/bench/brand/flags/ls.png +0 -0
- package/bench/brand/flags/lt.png +0 -0
- package/bench/brand/flags/lu.png +0 -0
- package/bench/brand/flags/lv.png +0 -0
- package/bench/brand/flags/ly.png +0 -0
- package/bench/brand/flags/ma.png +0 -0
- package/bench/brand/flags/mc.png +0 -0
- package/bench/brand/flags/md.png +0 -0
- package/bench/brand/flags/me.png +0 -0
- package/bench/brand/flags/mf.png +0 -0
- package/bench/brand/flags/mg.png +0 -0
- package/bench/brand/flags/mh.png +0 -0
- package/bench/brand/flags/mk.png +0 -0
- package/bench/brand/flags/ml.png +0 -0
- package/bench/brand/flags/mm.png +0 -0
- package/bench/brand/flags/mn.png +0 -0
- package/bench/brand/flags/mo.png +0 -0
- package/bench/brand/flags/mp.png +0 -0
- package/bench/brand/flags/mq.png +0 -0
- package/bench/brand/flags/mr.png +0 -0
- package/bench/brand/flags/ms.png +0 -0
- package/bench/brand/flags/mt.png +0 -0
- package/bench/brand/flags/mu.png +0 -0
- package/bench/brand/flags/mv.png +0 -0
- package/bench/brand/flags/mw.png +0 -0
- package/bench/brand/flags/mx.png +0 -0
- package/bench/brand/flags/my.png +0 -0
- package/bench/brand/flags/mz.png +0 -0
- package/bench/brand/flags/na.png +0 -0
- package/bench/brand/flags/nc.png +0 -0
- package/bench/brand/flags/ne.png +0 -0
- package/bench/brand/flags/nf.png +0 -0
- package/bench/brand/flags/ng.png +0 -0
- package/bench/brand/flags/ni.png +0 -0
- package/bench/brand/flags/nl.png +0 -0
- package/bench/brand/flags/no.png +0 -0
- package/bench/brand/flags/np.png +0 -0
- package/bench/brand/flags/nr.png +0 -0
- package/bench/brand/flags/nu.png +0 -0
- package/bench/brand/flags/nz.png +0 -0
- package/bench/brand/flags/om.png +0 -0
- package/bench/brand/flags/pa.png +0 -0
- package/bench/brand/flags/pc.png +0 -0
- package/bench/brand/flags/pe.png +0 -0
- package/bench/brand/flags/pf.png +0 -0
- package/bench/brand/flags/pg.png +0 -0
- package/bench/brand/flags/ph.png +0 -0
- package/bench/brand/flags/pk.png +0 -0
- package/bench/brand/flags/pl.png +0 -0
- package/bench/brand/flags/pm.png +0 -0
- package/bench/brand/flags/pn.png +0 -0
- package/bench/brand/flags/pr.png +0 -0
- package/bench/brand/flags/ps.png +0 -0
- package/bench/brand/flags/pt.png +0 -0
- package/bench/brand/flags/pw.png +0 -0
- package/bench/brand/flags/py.png +0 -0
- package/bench/brand/flags/qa.png +0 -0
- package/bench/brand/flags/re.png +0 -0
- package/bench/brand/flags/ro.png +0 -0
- package/bench/brand/flags/rs.png +0 -0
- package/bench/brand/flags/ru.png +0 -0
- package/bench/brand/flags/rw.png +0 -0
- package/bench/brand/flags/sa.png +0 -0
- package/bench/brand/flags/sb.png +0 -0
- package/bench/brand/flags/sc.png +0 -0
- package/bench/brand/flags/sd.png +0 -0
- package/bench/brand/flags/se.png +0 -0
- package/bench/brand/flags/sg.png +0 -0
- package/bench/brand/flags/sh.png +0 -0
- package/bench/brand/flags/si.png +0 -0
- package/bench/brand/flags/sj.png +0 -0
- package/bench/brand/flags/sk.png +0 -0
- package/bench/brand/flags/sl.png +0 -0
- package/bench/brand/flags/sm.png +0 -0
- package/bench/brand/flags/sn.png +0 -0
- package/bench/brand/flags/so.png +0 -0
- package/bench/brand/flags/sr.png +0 -0
- package/bench/brand/flags/ss.png +0 -0
- package/bench/brand/flags/st.png +0 -0
- package/bench/brand/flags/sv.png +0 -0
- package/bench/brand/flags/sx.png +0 -0
- package/bench/brand/flags/sy.png +0 -0
- package/bench/brand/flags/sz.png +0 -0
- package/bench/brand/flags/tc.png +0 -0
- package/bench/brand/flags/td.png +0 -0
- package/bench/brand/flags/tf.png +0 -0
- package/bench/brand/flags/tg.png +0 -0
- package/bench/brand/flags/th.png +0 -0
- package/bench/brand/flags/tj.png +0 -0
- package/bench/brand/flags/tk.png +0 -0
- package/bench/brand/flags/tl.png +0 -0
- package/bench/brand/flags/tm.png +0 -0
- package/bench/brand/flags/tn.png +0 -0
- package/bench/brand/flags/to.png +0 -0
- package/bench/brand/flags/tr.png +0 -0
- package/bench/brand/flags/tt.png +0 -0
- package/bench/brand/flags/tv.png +0 -0
- package/bench/brand/flags/tw.png +0 -0
- package/bench/brand/flags/tz.png +0 -0
- package/bench/brand/flags/ua.png +0 -0
- package/bench/brand/flags/ug.png +0 -0
- package/bench/brand/flags/um.png +0 -0
- package/bench/brand/flags/un.png +0 -0
- package/bench/brand/flags/us.png +0 -0
- package/bench/brand/flags/uy.png +0 -0
- package/bench/brand/flags/uz.png +0 -0
- package/bench/brand/flags/va.png +0 -0
- package/bench/brand/flags/vc.png +0 -0
- package/bench/brand/flags/ve.png +0 -0
- package/bench/brand/flags/vg.png +0 -0
- package/bench/brand/flags/vi.png +0 -0
- package/bench/brand/flags/vn.png +0 -0
- package/bench/brand/flags/vu.png +0 -0
- package/bench/brand/flags/wf.png +0 -0
- package/bench/brand/flags/ws.png +0 -0
- package/bench/brand/flags/xk.png +0 -0
- package/bench/brand/flags/xx.png +0 -0
- package/bench/brand/flags/ye.png +0 -0
- package/bench/brand/flags/yt.png +0 -0
- package/bench/brand/flags/za.png +0 -0
- package/bench/brand/flags/zm.png +0 -0
- package/bench/brand/flags/zw.png +0 -0
- package/bench/brand/fonts/OFL.txt +93 -0
- package/bench/brand/fonts/jost.7726a5cd.woff2 +0 -0
- package/bench/brand/loader/gif/zaisma-loader-black-128.gif +0 -0
- package/bench/brand/loader/gif/zaisma-loader-black-512.gif +0 -0
- package/bench/brand/loader/gif/zaisma-loader-white-128.gif +0 -0
- package/bench/brand/loader/gif/zaisma-loader-white-512.gif +0 -0
- package/bench/brand/loader/png/zaisma-loader-black-128.png +0 -0
- package/bench/brand/loader/png/zaisma-loader-black-512.png +0 -0
- package/bench/brand/loader/png/zaisma-loader-white-128.png +0 -0
- package/bench/brand/loader/png/zaisma-loader-white-512.png +0 -0
- package/bench/brand/loader/webp/zaisma-loader-black-128.webp +0 -0
- package/bench/brand/loader/webp/zaisma-loader-black-512.webp +0 -0
- package/bench/brand/loader/webp/zaisma-loader-white-128.webp +0 -0
- package/bench/brand/loader/webp/zaisma-loader-white-512.webp +0 -0
- package/bench/brand/loader/zaisma-loader-black.svg +1 -0
- package/bench/brand/loader/zaisma-loader-white.svg +1 -0
- package/bench/brand/loader/zaisma-loader.svg +1 -0
- package/bench/brand/sign-in/city-day-phone.webp +0 -0
- package/bench/brand/sign-in/city-day.webp +0 -0
- package/bench/brand/sign-in/city-night-phone.webp +0 -0
- package/bench/brand/sign-in/city-night.webp +0 -0
- package/bench/brand/zaisma-app-icon.svg +1 -0
- package/bench/brand/zaisma-mark-black-2048.png +0 -0
- package/bench/brand/zaisma-mark-black.svg +1 -0
- package/bench/brand/zaisma-mark-metal-2048.png +0 -0
- package/bench/brand/zaisma-mark-metal.svg +1 -0
- package/bench/brand/zaisma-mark-small-black.svg +1 -0
- package/bench/brand/zaisma-mark-small-white.svg +1 -0
- package/bench/brand/zaisma-mark-tricolor.svg +1 -0
- package/bench/brand/zaisma-mark-white-2048.png +0 -0
- package/bench/brand/zaisma-mark-white.svg +1 -0
- package/bench/brand/zaisma-wordmark-black-2400.png +0 -0
- package/bench/brand/zaisma-wordmark-black.svg +1 -0
- package/bench/brand/zaisma-wordmark-metal-2400.png +0 -0
- package/bench/brand/zaisma-wordmark-metal.svg +1 -0
- package/bench/brand/zaisma-wordmark-white-2400.png +0 -0
- package/bench/brand/zaisma-wordmark-white.svg +1 -0
- package/bench/glass.js +46486 -0
- package/bin/local-play-bench.d.ts +8 -0
- package/bin/local-play-bench.js +218 -0
- package/bin/local-play-certificate.js +99 -0
- package/bin/local-play-runner.js +43 -0
- package/bin/local-play.d.ts +41 -0
- package/bin/local-play.js +1573 -0
- package/bin/node.js +99 -5
- package/package.json +1 -1
- package/sdk/picture.js +97 -0
- package/sdk/relay.js +269 -0
- package/sdk/saves.js +884 -0
- package/sdk/zaisma.d.ts +1810 -0
- package/sdk/zaisma.js +2459 -0
- package/src/chat.d.ts +23 -8
- package/src/chat.js +21 -11
- package/src/index.d.ts +2 -2
- package/src/index.js +1 -1
- package/src/pass.d.ts +9 -8
- package/src/pass.js +10 -8
- package/src/qr-code.d.ts +20 -0
- package/src/qr-code.js +319 -0
- package/src/room.d.ts +148 -3
- package/src/room.js +204 -3
package/bin/node.js
CHANGED
|
@@ -22,7 +22,7 @@ import { createServer } from "node:http";
|
|
|
22
22
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
23
23
|
import { resolve } from "node:path";
|
|
24
24
|
import { realpathSync } from "node:fs";
|
|
25
|
-
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_TUNING, WEBHOOK_LIMITS, GameServerError, RoomCore, restoreInfoOf, passesAreFriends, passesHaveBlockBetween, answerRequest, startSchedules, checkRoomData, clientIdle, defineGameServer, errorMessage, isRoomCode, readRequestBody, relay, requestError, runWebhook, tidyRoomInfo, verifyPass, verifyReceipt, verifyWebhook } from "../src/internal.js";
|
|
25
|
+
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_TUNING, WEBHOOK_LIMITS, GameServerError, RoomCore, chatSettingsOf, checkChat, readChatMessage, restoreInfoOf, passesAreFriends, passesHaveBlockBetween, answerRequest, startSchedules, checkRoomData, clientIdle, defineGameServer, errorMessage, isRoomCode, readRequestBody, relay, requestError, runWebhook, tidyRoomInfo, verifyPass, verifyReceipt, verifyWebhook } from "../src/internal.js";
|
|
26
26
|
import { OutsideCallError } from "../src/outside-calls.js";
|
|
27
27
|
import { checkedStartServerOptions, localDatabase, localRoomStates, passKeysFrom, profilesFrom, receiptKeysFrom, secretsFromEnvironment } from "./node-parts.js";
|
|
28
28
|
|
|
@@ -290,10 +290,11 @@ export function startServer(options) {
|
|
|
290
290
|
saved ? { state: saved.state, room: saved.savedRoom, ...(definition.ticksPerSec || saved.matchPlayers?.length ? { restoreInfo: restoreInfoOf(saved.savedAtMs, Date.now(), null, saved.isSavedEmpty), lostPlayers: saved.matchPlayers } : {}) } : { start },
|
|
291
291
|
);
|
|
292
292
|
if (saved) core.savedAtMs = saved.savedAtMs;
|
|
293
|
-
// passes: each player's checked pass, newest, for the blocks and friendships between them (passesHaveBlockBetween, passesAreFriends)
|
|
293
|
+
// passes: each player's checked pass, newest, for the blocks and friendships between them (passesHaveBlockBetween, passesAreFriends);
|
|
294
|
+
// passTexts: the same passes as the players sent them, for Zaisma's chat filter (checkChat).
|
|
294
295
|
// savedStateKey: what the room last saved (its state and its match's players), so an unchanged one isn't written again.
|
|
295
296
|
// seatedAtMs: when each player came into this room on their own, for the game's one-room rule (the oldest seat closes first).
|
|
296
|
-
room = { core, sockets, passes: new Map(), seatedAtMs: new Map(), timer: null, probeTimer: null, waiting: false, savedStateKey: saved ? stateKeyOf(saved.stateJson, saved.matchPlayers, saved.savedRoom.isKept === true) : null, hasReportedStateSize: false };
|
|
297
|
+
room = { core, sockets, passes: new Map(), passTexts: new Map(), seatedAtMs: new Map(), timer: null, probeTimer: null, waiting: false, savedStateKey: saved ? stateKeyOf(saved.stateJson, saved.matchPlayers, saved.savedRoom.isKept === true) : null, hasReportedStateSize: false };
|
|
297
298
|
rooms.set(code, room);
|
|
298
299
|
return room;
|
|
299
300
|
}
|
|
@@ -402,6 +403,24 @@ export function startServer(options) {
|
|
|
402
403
|
}
|
|
403
404
|
}
|
|
404
405
|
|
|
406
|
+
/**
|
|
407
|
+
* A player's chat message (room.chat): the room checks it and asks the game server's onChat (startChat),
|
|
408
|
+
* Zaisma's filter looks (checkChat, with the passes of those who may read it), and it goes to them
|
|
409
|
+
* (deliverChat) or the sender hears why not (stopChat). Everyone it may reach is in the sender's chat
|
|
410
|
+
* group, so the first CHAT_CHECK_LIMITS.maximumReceiverCount of them stand for the rest.
|
|
411
|
+
*/
|
|
412
|
+
async function chatIn(room, playerId, chat) {
|
|
413
|
+
const started = await room.core.startChat(playerId, chat);
|
|
414
|
+
if (!started) return;
|
|
415
|
+
let check = { isAllowed: true, reason: null, message: null, hasSwearing: false, hasSlur: false };
|
|
416
|
+
if (started.receiverPlayerIds.length) {
|
|
417
|
+
const receiverPasses = started.receiverPlayerIds.map((id) => room.passTexts.get(id)).filter((pass) => typeof pass === "string");
|
|
418
|
+
check = await checkChat({ text: started.text, senderPass: room.passTexts.get(playerId) ?? "", receiverPasses, isAdultFilterEnabled: definition.adultChatFilterEnabled === true });
|
|
419
|
+
}
|
|
420
|
+
if (check.isAllowed) room.core.deliverChat(playerId, chat.chatNumber, started.receiverPlayerIds, { text: started.text, hasSwearing: check.hasSwearing, hasSlur: check.hasSlur });
|
|
421
|
+
else room.core.stopChat(playerId, chat.chatNumber, check.reason ?? "unchecked", check.message ?? "");
|
|
422
|
+
}
|
|
423
|
+
|
|
405
424
|
/** Keep a room for players on their way, even if it empties meanwhile. */
|
|
406
425
|
function wait(code, room, durationMs) {
|
|
407
426
|
room.waiting = true;
|
|
@@ -489,6 +508,9 @@ export function startServer(options) {
|
|
|
489
508
|
if (text === "ping") return ws.send("pong");
|
|
490
509
|
if (playerId) {
|
|
491
510
|
const room = rooms.get(code);
|
|
511
|
+
// Chat goes through Zaisma's filter (checkChat) on its way, so the room takes it before the game's messages.
|
|
512
|
+
const chat = text === null ? null : readChatMessage(text);
|
|
513
|
+
if (chat && room) return chatIn(room, playerId, chat);
|
|
492
514
|
// Bytes (a binary frame) go to the game server's onBytes, under the same limits as messages.
|
|
493
515
|
const errorCode = text === null ? room?.core.receiveBytes(playerId, binaryBytes(received)) : room?.core.receive(playerId, text);
|
|
494
516
|
if (errorCode) ws.send(errorMessage(errorCode));
|
|
@@ -537,9 +559,16 @@ export function startServer(options) {
|
|
|
537
559
|
room.sockets.set(player.id, ws);
|
|
538
560
|
// Blocks between accounts come in their passes: this player's and those of everyone the room remembers.
|
|
539
561
|
// Spectators send nothing, so nothing of theirs reaches a player to block.
|
|
562
|
+
// Who the player may chat with, from their pass: the room keeps it, and the game never sees it. Spectators send nothing, chat included.
|
|
563
|
+
room.core.setChatSettings(player.id, isSpectator ? null : chatSettingsOf(claims));
|
|
540
564
|
if (!isSpectator) {
|
|
541
565
|
room.passes.set(player.id, claims);
|
|
542
|
-
|
|
566
|
+
room.passTexts.set(player.id, pass);
|
|
567
|
+
for (const id of room.passes.keys()) {
|
|
568
|
+
if (room.core.known.has(id) || id === player.id) continue;
|
|
569
|
+
room.passes.delete(id);
|
|
570
|
+
room.passTexts.delete(id);
|
|
571
|
+
}
|
|
543
572
|
room.core.setAccountBlocks(player.id, [...room.passes].filter(([id, other]) => id !== player.id && passesHaveBlockBetween(claims, other)).map(([id]) => id));
|
|
544
573
|
room.core.setFriends(player.id, [...room.passes].filter(([id, other]) => id !== player.id && passesAreFriends(claims, other)).map(([id]) => id));
|
|
545
574
|
}
|
|
@@ -691,7 +720,7 @@ function binaryBytes(received) {
|
|
|
691
720
|
return new Uint8Array(received.buffer.slice(received.byteOffset, received.byteOffset + received.byteLength));
|
|
692
721
|
}
|
|
693
722
|
|
|
694
|
-
const USAGE = "Usage: zaisma-game-server <server.ts | relay> --game <slug> [--port 8080] [--origin <url>,<url>] [--issuer-url https://rooms.zaisma.com] [--database-file zaisma-database.sqlite] [--room-state-file zaisma-room-states.sqlite]";
|
|
723
|
+
const USAGE = "Usage: zaisma-game-server <server.ts | relay> --game <slug> [--port 8080] [--origin <url>,<url>] [--issuer-url https://rooms.zaisma.com] [--database-file zaisma-database.sqlite] [--room-state-file zaisma-room-states.sqlite]\n or: zaisma-game-server play-locally [folder] [--port 4800] [--game <slug>] [--on-network] [--no-open] [--json]";
|
|
695
724
|
const FLAGS = ["game", "port", "origin", "issuer-url", "database-file", "room-state-file"];
|
|
696
725
|
|
|
697
726
|
/** The command line's flags by name, or null for one this runner doesn't take (or one missing its value). */
|
|
@@ -705,8 +734,73 @@ function flagsFrom(argumentList) {
|
|
|
705
734
|
return flags;
|
|
706
735
|
}
|
|
707
736
|
|
|
737
|
+
/** play-locally's flags: whether each takes a value. */
|
|
738
|
+
const PLAY_LOCALLY_FLAGS = { port: true, game: true, "on-network": false, "no-open": false, json: false };
|
|
739
|
+
|
|
740
|
+
/** play-locally [folder] and its flags, or null with the refusal printed: said plainly, before anything listens. */
|
|
741
|
+
function playLocallyArguments(argumentList) {
|
|
742
|
+
const refuse = (message, fix) => {
|
|
743
|
+
console.error(`${message} ${fix} (bad_option)`);
|
|
744
|
+
return null;
|
|
745
|
+
};
|
|
746
|
+
const flags = {};
|
|
747
|
+
let folder = null;
|
|
748
|
+
for (let i = 0; i < argumentList.length; i++) {
|
|
749
|
+
const given = argumentList[i];
|
|
750
|
+
if (!given.startsWith("--")) {
|
|
751
|
+
if (folder !== null) return refuse(`play-locally takes one folder, and got ${folder} and ${given}.`, "Give the folder of your game, the one with index.html at the top.");
|
|
752
|
+
folder = given;
|
|
753
|
+
continue;
|
|
754
|
+
}
|
|
755
|
+
const name = given.slice(2);
|
|
756
|
+
if (!(name in PLAY_LOCALLY_FLAGS)) return refuse(`${given} isn't a play-locally flag.`, `play-locally takes: ${Object.keys(PLAY_LOCALLY_FLAGS).map((each) => `--${each}`).join(", ")}.`);
|
|
757
|
+
if (!PLAY_LOCALLY_FLAGS[name]) {
|
|
758
|
+
flags[name] = true;
|
|
759
|
+
continue;
|
|
760
|
+
}
|
|
761
|
+
const flagValue = argumentList[++i];
|
|
762
|
+
if (flagValue === undefined || flagValue.startsWith("--")) return refuse(`${given} needs a value.`, name === "port" ? "Give the first of four ports, like --port 4800." : "Give your game's slug, like --game sky-hopper.");
|
|
763
|
+
flags[name] = flagValue;
|
|
764
|
+
}
|
|
765
|
+
const portNumber = flags.port === undefined ? 4800 : Number(flags.port);
|
|
766
|
+
if (!Number.isInteger(portNumber) || portNumber < 1 || portNumber > 65532) return refuse("--port isn't a whole number from 1 to 65532.", "Give the first of four ports, like --port 4800: the bench, the game's site, the stand-in rooms and your game server take it and the next three.");
|
|
767
|
+
if (flags.game !== undefined && !/^[a-z0-9-]{1,40}$/.test(flags.game)) return refuse("--game isn't a game's slug.", "A slug is 1 to 40 lowercase letters, digits or hyphens, like sky-hopper.");
|
|
768
|
+
return { folder: folder ?? ".", portNumber, gameSlug: flags.game ?? null, isOnNetwork: flags["on-network"] === true, isOpening: flags["no-open"] !== true, isJson: flags.json === true };
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/** zaisma-game-server play-locally: your game and its game server on this computer, with a test bench (bin/local-play.js). */
|
|
772
|
+
async function playLocally(argumentList) {
|
|
773
|
+
const given = playLocallyArguments(argumentList);
|
|
774
|
+
if (!given) process.exit(1);
|
|
775
|
+
const { startLocalPlay, terminalQr } = await import("./local-play.js");
|
|
776
|
+
const say = given.isJson ? (line) => console.error(line) : (line) => console.log(line);
|
|
777
|
+
let running;
|
|
778
|
+
try {
|
|
779
|
+
running = await startLocalPlay({ gameFolder: given.folder, port: given.portNumber, isOnNetwork: given.isOnNetwork, gameSlug: given.gameSlug, log: say });
|
|
780
|
+
} catch (error) {
|
|
781
|
+
console.error(error instanceof GameServerError ? `${error.message} ${error.fix ?? ""} (${error.code})` : error);
|
|
782
|
+
process.exit(1);
|
|
783
|
+
}
|
|
784
|
+
for (const signal of ["SIGTERM", "SIGINT"]) process.once(signal, () => running.close().then(() => process.exit(0)));
|
|
785
|
+
if (given.isJson) console.log(JSON.stringify({ bench_url: running.benchUrl, game_site_url: running.gameSiteUrl, rooms_url: running.roomsUrl, network_url: running.networkUrl }));
|
|
786
|
+
say(`Playing ${given.gameSlug ?? "your game"} on this computer. The test bench is at ${running.benchUrl}`);
|
|
787
|
+
say("Each player plays on Zaisma's own game page. Add a player in the dev bar to play with yourself in another tab. A change to a game file reloads the game, and a change to the game server starts it again with its rooms kept.");
|
|
788
|
+
say("Nothing here talks to Zaisma. What the bench keeps is in .zaisma-local/ in the game's folder: add it to your .gitignore. Press Ctrl-C to stop.");
|
|
789
|
+
if (running.networkUrl) {
|
|
790
|
+
say(`On a phone on the same Wi-Fi: ${running.networkUrl}`);
|
|
791
|
+
say(terminalQr(running.networkUrl));
|
|
792
|
+
say("Your phone will say the connection isn't private, because the bench signs its own certificate. Continue to the site.");
|
|
793
|
+
}
|
|
794
|
+
if (given.isOpening) {
|
|
795
|
+
const { spawn } = await import("node:child_process");
|
|
796
|
+
const opener = process.platform === "darwin" ? ["open", [running.benchUrl]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", running.benchUrl]] : ["xdg-open", [running.benchUrl]];
|
|
797
|
+
spawn(opener[0], opener[1], { stdio: "ignore", detached: true }).on("error", () => {}).unref();
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
708
801
|
// Run as a command. An npm bin is a symlink to this file, so both sides are resolved before they're compared.
|
|
709
802
|
async function runCommand() {
|
|
803
|
+
if (process.argv[2] === "play-locally") return playLocally(process.argv.slice(3));
|
|
710
804
|
const file = process.argv[2];
|
|
711
805
|
const flags = file ? flagsFrom(process.argv.slice(3)) : null;
|
|
712
806
|
if (!file || file.startsWith("--") || !flags?.game) {
|
package/package.json
CHANGED
package/sdk/picture.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Report pictures. Zaisma adds this to every game page. When a player opens Report on the Zaisma
|
|
3
|
+
* game page, the page asks for one picture of the game as it is right now: the game's biggest
|
|
4
|
+
* canvas, copied straight after the game draws a frame (so WebGL games work too), shrunk and
|
|
5
|
+
* handed back (unless it's all one color). Nothing is taken unless the Zaisma page asks, and nothing leaves the player's
|
|
6
|
+
* browser unless they send the report with the picture. Games that don't draw on a canvas give
|
|
7
|
+
* no picture. Plain JavaScript, no dependencies, MIT licensed.
|
|
8
|
+
*/
|
|
9
|
+
(function () {
|
|
10
|
+
"use strict";
|
|
11
|
+
var site = typeof __ZAISMA_PICTURE__ === "object" && __ZAISMA_PICTURE__ ? __ZAISMA_PICTURE__.site : "";
|
|
12
|
+
if (!site || window.parent === window) return;
|
|
13
|
+
var MAXIMUM_SIDE_PIXEL_COUNT = 1280;
|
|
14
|
+
var waiting = [];
|
|
15
|
+
|
|
16
|
+
function biggestCanvas() {
|
|
17
|
+
var best = null;
|
|
18
|
+
var bestPixelCount = 0;
|
|
19
|
+
var all = document.getElementsByTagName("canvas");
|
|
20
|
+
for (var i = 0; i < all.length; i++) {
|
|
21
|
+
var pixelCount = all[i].width * all[i].height;
|
|
22
|
+
if (pixelCount > bestPixelCount && all[i].getClientRects().length) {
|
|
23
|
+
best = all[i];
|
|
24
|
+
bestPixelCount = pixelCount;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return best;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// A canvas that's all one color (a title screen drawn in HTML over it, a game still loading) shows nothing worth sending.
|
|
31
|
+
function blank(canvas) {
|
|
32
|
+
var small = document.createElement("canvas");
|
|
33
|
+
small.width = 32;
|
|
34
|
+
small.height = 18;
|
|
35
|
+
var canvasContext = small.getContext("2d");
|
|
36
|
+
canvasContext.drawImage(canvas, 0, 0, 32, 18);
|
|
37
|
+
var pixelBytes = canvasContext.getImageData(0, 0, 32, 18).data;
|
|
38
|
+
var darkestByte = 255, lightestByte = 0;
|
|
39
|
+
for (var i = 0; i < pixelBytes.length; i += 4) {
|
|
40
|
+
var lightnessByte = (pixelBytes[i] * 3 + pixelBytes[i + 1] * 6 + pixelBytes[i + 2]) / 10;
|
|
41
|
+
if (lightnessByte < darkestByte) darkestByte = lightnessByte;
|
|
42
|
+
if (lightnessByte > lightestByte) lightestByte = lightnessByte;
|
|
43
|
+
}
|
|
44
|
+
return lightestByte - darkestByte < 10;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function picture() {
|
|
48
|
+
try {
|
|
49
|
+
var from = biggestCanvas();
|
|
50
|
+
if (!from) return null;
|
|
51
|
+
var longestSidePixelCount = Math.max(from.width, from.height);
|
|
52
|
+
var shrunkSidePixelCount = Math.min(longestSidePixelCount, MAXIMUM_SIDE_PIXEL_COUNT);
|
|
53
|
+
var to = document.createElement("canvas");
|
|
54
|
+
to.width = Math.max(1, Math.round((from.width * shrunkSidePixelCount) / longestSidePixelCount));
|
|
55
|
+
to.height = Math.max(1, Math.round((from.height * shrunkSidePixelCount) / longestSidePixelCount));
|
|
56
|
+
var canvasContext = to.getContext("2d");
|
|
57
|
+
canvasContext.fillStyle = "#000";
|
|
58
|
+
canvasContext.fillRect(0, 0, to.width, to.height);
|
|
59
|
+
canvasContext.drawImage(from, 0, 0, to.width, to.height);
|
|
60
|
+
return blank(to) ? null : to.toDataURL("image/jpeg", 0.8);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
return null; // A canvas showing pictures from another site can't be copied.
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function answer() {
|
|
67
|
+
if (!waiting.length) return;
|
|
68
|
+
var pictureUrl = picture();
|
|
69
|
+
waiting.splice(0).forEach(function (id) {
|
|
70
|
+
window.parent.postMessage({ type: "zaisma:picture", id: id, picture: pictureUrl }, site);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Copy inside the game's own frame callback: after it has drawn, before the browser clears a WebGL canvas.
|
|
75
|
+
var nativeFrame = window.requestAnimationFrame;
|
|
76
|
+
if (nativeFrame) {
|
|
77
|
+
window.requestAnimationFrame = function (callback) {
|
|
78
|
+
return nativeFrame.call(window, function (frameTimeMs) {
|
|
79
|
+
try {
|
|
80
|
+
callback(frameTimeMs);
|
|
81
|
+
} finally {
|
|
82
|
+
answer();
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
window.addEventListener("message", function (event) {
|
|
89
|
+
if (event.origin !== site || event.source !== window.parent || !event.data || event.data.type !== "zaisma:picture-request") return;
|
|
90
|
+
var id = String(event.data.id);
|
|
91
|
+
waiting.push(id);
|
|
92
|
+
// A game that isn't drawing right now (paused, between turns) is copied straight away.
|
|
93
|
+
setTimeout(function () {
|
|
94
|
+
if (waiting.indexOf(id) !== -1) answer();
|
|
95
|
+
}, 200);
|
|
96
|
+
});
|
|
97
|
+
})();
|
package/sdk/relay.js
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Zaisma's relay. Zaisma adds this first to every game page. A connection between two players'
|
|
3
|
+
* browsers (WebRTC, what PeerJS and Godot use) would show each the other's internet address, so
|
|
4
|
+
* every one goes through Cloudflare's relay (TURN) instead: players only ever see Cloudflare's
|
|
5
|
+
* address, and connections also get through school and office networks where direct ones fail.
|
|
6
|
+
* The game's own WebRTC code works unchanged: this wraps RTCPeerConnection so each connection uses
|
|
7
|
+
* only the relay, with Zaisma's relay servers, whatever servers the game asks for (the game's own
|
|
8
|
+
* relay stays when it's one of the game's listed servers). Plain JavaScript, no dependencies, MIT licensed.
|
|
9
|
+
*/
|
|
10
|
+
(function (window) {
|
|
11
|
+
"use strict";
|
|
12
|
+
var config = typeof __ZAISMA_RELAY__ === "object" && __ZAISMA_RELAY__ ? __ZAISMA_RELAY__ : {};
|
|
13
|
+
var NAMES = ["RTCPeerConnection", "webkitRTCPeerConnection"];
|
|
14
|
+
var ownRelayHosts = (config.ownRelayHosts || []).map(function (host) { return String(host).toLowerCase(); });
|
|
15
|
+
/** Ask again this long after the relay couldn't be had, not on every connection. */
|
|
16
|
+
var RETRY_AFTER_MS = 60 * 1000;
|
|
17
|
+
/** Credentials with less left than this are fetched anew. */
|
|
18
|
+
var RENEW_BEFORE_MS = 10 * 60 * 1000;
|
|
19
|
+
var storageKey = "zaisma:relay:" + config.game;
|
|
20
|
+
|
|
21
|
+
/** What this window wrapped: our getters (so nothing is wrapped twice), and each original's wrapper. */
|
|
22
|
+
var getters = new WeakSet();
|
|
23
|
+
var wrappers = new WeakMap();
|
|
24
|
+
/** The configuration the game asked each connection for, and the relay servers it was given. */
|
|
25
|
+
var askedConfigurations = new WeakMap();
|
|
26
|
+
var givenServers = new WeakMap();
|
|
27
|
+
|
|
28
|
+
// ---- Zaisma's relay servers for this tab ----
|
|
29
|
+
|
|
30
|
+
var kept = null; // { iceServers, expiresAtMs }
|
|
31
|
+
var fetching = null;
|
|
32
|
+
var failedAtMs = 0;
|
|
33
|
+
var hasWarned = false;
|
|
34
|
+
|
|
35
|
+
function warn(message) {
|
|
36
|
+
if (hasWarned) return;
|
|
37
|
+
hasWarned = true;
|
|
38
|
+
window.console.warn("Zaisma: connections between players go only through Zaisma's relay, so they don't connect right now. " + message);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function isFresh(credentials) {
|
|
42
|
+
return Boolean(credentials && credentials.iceServers && credentials.expiresAtMs - Date.now() > RENEW_BEFORE_MS);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function storedCredentials() {
|
|
46
|
+
try {
|
|
47
|
+
var stored = JSON.parse(window.sessionStorage.getItem(storageKey) || "null");
|
|
48
|
+
return isFresh(stored) ? stored : null;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
return null; // storage off: fetch new ones
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The game page's key to player passes, which also gives relay credentials (the SDK asks for it the same way). */
|
|
55
|
+
function roomKey() {
|
|
56
|
+
return new Promise(function (resolve, reject) {
|
|
57
|
+
if (!config.site || !config.rooms || window.parent === window) return reject(new Error("Connections between players work when the game is played on Zaisma."));
|
|
58
|
+
var timerNumber = window.setTimeout(function () {
|
|
59
|
+
window.removeEventListener("message", onMessage);
|
|
60
|
+
reject(new Error("Zaisma couldn't check this browser. Reload the game to try again."));
|
|
61
|
+
}, 30000);
|
|
62
|
+
function onMessage(event) {
|
|
63
|
+
if (event.origin !== config.site || event.source !== window.parent || !event.data || event.data.type !== "zaisma:room-key") return;
|
|
64
|
+
window.removeEventListener("message", onMessage);
|
|
65
|
+
window.clearTimeout(timerNumber);
|
|
66
|
+
if (typeof event.data.key === "string") resolve(event.data.key);
|
|
67
|
+
else reject(new Error("Zaisma couldn't check this browser, or this game doesn't use online play. Reload the game to try again."));
|
|
68
|
+
}
|
|
69
|
+
window.addEventListener("message", onMessage);
|
|
70
|
+
window.parent.postMessage({ type: "zaisma:room-key" }, config.site);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Zaisma's relay servers, as iceServers: [] when there are none to be had (connections then don't connect, rather than go direct). */
|
|
75
|
+
function relayServers() {
|
|
76
|
+
if (isFresh(kept)) return Promise.resolve(kept.iceServers);
|
|
77
|
+
var stored = storedCredentials();
|
|
78
|
+
if (stored) {
|
|
79
|
+
kept = stored;
|
|
80
|
+
return Promise.resolve(kept.iceServers);
|
|
81
|
+
}
|
|
82
|
+
if (Date.now() - failedAtMs < RETRY_AFTER_MS) return Promise.resolve([]);
|
|
83
|
+
if (!fetching) {
|
|
84
|
+
fetching = roomKey()
|
|
85
|
+
.then(function (key) {
|
|
86
|
+
// text/plain keeps this a simple request, so the browser needs no preflight.
|
|
87
|
+
return window.fetch(config.rooms + "/v1/relay", { method: "POST", headers: { "Content-Type": "text/plain" }, body: JSON.stringify({ gameSlug: config.game, roomKey: key }) });
|
|
88
|
+
})
|
|
89
|
+
.then(function (response) {
|
|
90
|
+
return response.json().then(function (answer) {
|
|
91
|
+
if (!response.ok || !answer || !Array.isArray(answer.iceServers)) throw new Error((answer && answer.error && answer.error.message) || "Zaisma's relay didn't answer.");
|
|
92
|
+
return answer;
|
|
93
|
+
});
|
|
94
|
+
})
|
|
95
|
+
.then(
|
|
96
|
+
function (answer) {
|
|
97
|
+
fetching = null;
|
|
98
|
+
kept = { iceServers: answer.iceServers, expiresAtMs: Date.parse(answer.expiresAt) };
|
|
99
|
+
try { window.sessionStorage.setItem(storageKey, JSON.stringify(kept)); } catch (error) { /* fine */ }
|
|
100
|
+
return kept.iceServers;
|
|
101
|
+
},
|
|
102
|
+
function (error) {
|
|
103
|
+
fetching = null;
|
|
104
|
+
failedAtMs = Date.now();
|
|
105
|
+
warn(error && error.message ? error.message : "");
|
|
106
|
+
return [];
|
|
107
|
+
},
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
return fetching;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ---- What each connection is given ----
|
|
114
|
+
|
|
115
|
+
/** The game's own relay servers among its listed servers: turn: and turns: addresses on those hosts, nothing else. */
|
|
116
|
+
function ownRelays(iceServers) {
|
|
117
|
+
var out = [];
|
|
118
|
+
(Array.isArray(iceServers) ? iceServers : []).forEach(function (server) {
|
|
119
|
+
if (!server || typeof server !== "object") return;
|
|
120
|
+
var urls = [].concat(server.urls !== undefined ? server.urls : server.url !== undefined ? server.url : []).filter(function (address) {
|
|
121
|
+
var host = /^turns?:(\[[^\]]+\]|[^:?\/]+)/i.exec(String(address));
|
|
122
|
+
return host !== null && ownRelayHosts.indexOf(host[1].toLowerCase()) !== -1;
|
|
123
|
+
});
|
|
124
|
+
if (urls.length) out.push({ urls: urls, username: server.username, credential: server.credential });
|
|
125
|
+
});
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** A configuration from base that only relays, through Zaisma's servers and the game's own relay. */
|
|
130
|
+
function relayedConfiguration(base, asked, servers) {
|
|
131
|
+
var out = {};
|
|
132
|
+
for (var name in base) if (Object.prototype.hasOwnProperty.call(base, name)) out[name] = base[name];
|
|
133
|
+
out.iceTransportPolicy = "relay";
|
|
134
|
+
out.iceServers = servers.concat(ownRelays(asked.iceServers));
|
|
135
|
+
// Gathering ahead of time waits until there are servers to gather from.
|
|
136
|
+
if (!servers.length) out.iceCandidatePoolSize = 0;
|
|
137
|
+
else if (asked.iceCandidatePoolSize !== undefined) out.iceCandidatePoolSize = asked.iceCandidatePoolSize;
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function define(target, name, method) {
|
|
142
|
+
Object.defineProperty(target, name, { value: method, writable: true, configurable: true, enumerable: false });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** The wrapper for one window's RTCPeerConnection, and its prototype's calls made to relay first. */
|
|
146
|
+
function wrap(Native) {
|
|
147
|
+
var proto = Native.prototype;
|
|
148
|
+
var nativeSetConfiguration = proto.setConfiguration;
|
|
149
|
+
var nativeGetConfiguration = proto.getConfiguration;
|
|
150
|
+
|
|
151
|
+
function RTCPeerConnection(configuration) {
|
|
152
|
+
if (!new.target) throw new TypeError("Failed to construct 'RTCPeerConnection': Please use the 'new' operator.");
|
|
153
|
+
var asked = configuration && typeof configuration === "object" ? configuration : {};
|
|
154
|
+
var servers = isFresh(kept) ? kept.iceServers : [];
|
|
155
|
+
var parts = Array.prototype.slice.call(arguments);
|
|
156
|
+
parts[0] = relayedConfiguration(asked, asked, servers);
|
|
157
|
+
var connection = Reflect.construct(Native, parts, new.target);
|
|
158
|
+
askedConfigurations.set(connection, asked);
|
|
159
|
+
if (servers.length) givenServers.set(connection, servers);
|
|
160
|
+
// Fetched now, so they're there by the game's first offer or answer.
|
|
161
|
+
if (!servers.length) relayServers();
|
|
162
|
+
return connection;
|
|
163
|
+
}
|
|
164
|
+
RTCPeerConnection.prototype = proto;
|
|
165
|
+
Object.setPrototypeOf(RTCPeerConnection, Native);
|
|
166
|
+
define(proto, "constructor", RTCPeerConnection);
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Before a connection gathers addresses (its first offer or answer): Zaisma's relay servers, and
|
|
170
|
+
* a check that it only relays. Covers every connection in this window, however it was made.
|
|
171
|
+
*/
|
|
172
|
+
function relayFirst(connection) {
|
|
173
|
+
return relayServers().then(function (servers) {
|
|
174
|
+
if (connection.signalingState === "closed") return; // the call itself says so
|
|
175
|
+
var current = nativeGetConfiguration.call(connection);
|
|
176
|
+
// Servers it already has stay when new ones couldn't be had.
|
|
177
|
+
var use = servers.length ? servers : givenServers.get(connection) || [];
|
|
178
|
+
if (givenServers.get(connection) !== use || current.iceTransportPolicy !== "relay") {
|
|
179
|
+
try {
|
|
180
|
+
nativeSetConfiguration.call(connection, relayedConfiguration(current, askedConfigurations.get(connection) || current, use));
|
|
181
|
+
givenServers.set(connection, use);
|
|
182
|
+
} catch (error) {
|
|
183
|
+
/* checked just below */
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (nativeGetConfiguration.call(connection).iceTransportPolicy !== "relay") {
|
|
187
|
+
throw new DOMException("On Zaisma, connections between players go only through Zaisma's relay, and this one couldn't be set to.", "InvalidStateError");
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
["createOffer", "createAnswer", "setLocalDescription"].forEach(function (name) {
|
|
192
|
+
var native = proto[name];
|
|
193
|
+
if (typeof native !== "function") return;
|
|
194
|
+
define(proto, name, function () {
|
|
195
|
+
var connection = this;
|
|
196
|
+
var parts = arguments;
|
|
197
|
+
return relayFirst(connection).then(function () { return native.apply(connection, parts); });
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
if (typeof nativeSetConfiguration === "function") {
|
|
201
|
+
// A game changing its servers keeps Zaisma's relay.
|
|
202
|
+
define(proto, "setConfiguration", function (configuration) {
|
|
203
|
+
var asked = configuration && typeof configuration === "object" ? configuration : {};
|
|
204
|
+
askedConfigurations.set(this, asked);
|
|
205
|
+
var servers = givenServers.get(this) || [];
|
|
206
|
+
return nativeSetConfiguration.call(this, relayedConfiguration(asked, asked, servers));
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
wrappers.set(Native, RTCPeerConnection);
|
|
210
|
+
return RTCPeerConnection;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ---- Every window the game can reach ----
|
|
214
|
+
|
|
215
|
+
function install(target) {
|
|
216
|
+
try {
|
|
217
|
+
if (!target) return;
|
|
218
|
+
NAMES.forEach(function (name) {
|
|
219
|
+
var descriptor = Object.getOwnPropertyDescriptor(target, name);
|
|
220
|
+
if (!descriptor || (descriptor.get && getters.has(descriptor.get))) return;
|
|
221
|
+
var Native = target[name];
|
|
222
|
+
if (typeof Native !== "function") return;
|
|
223
|
+
var Relayed = wrappers.get(Native) || wrap(Native);
|
|
224
|
+
var current = Relayed;
|
|
225
|
+
var get = function () { return current; };
|
|
226
|
+
getters.add(get);
|
|
227
|
+
// Libraries like webrtc-adapter put their own wrapper around this one; nothing puts the original back.
|
|
228
|
+
Object.defineProperty(target, name, { configurable: true, enumerable: false, get: get, set: function (assigned) { current = assigned === Native ? Relayed : assigned; } });
|
|
229
|
+
});
|
|
230
|
+
watchFrames(target);
|
|
231
|
+
} catch (error) {
|
|
232
|
+
/* another site's window: nothing of it is reachable from here */
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** A frame the game makes has its own RTCPeerConnection: it's wrapped the moment the game reaches into the frame. */
|
|
237
|
+
function watchFrames(target) {
|
|
238
|
+
["HTMLIFrameElement", "HTMLFrameElement", "HTMLObjectElement"].forEach(function (name) {
|
|
239
|
+
var Element = target[name];
|
|
240
|
+
if (typeof Element !== "function") return;
|
|
241
|
+
["contentWindow", "contentDocument"].forEach(function (property) {
|
|
242
|
+
var descriptor = Object.getOwnPropertyDescriptor(Element.prototype, property);
|
|
243
|
+
if (!descriptor || !descriptor.get || getters.has(descriptor.get)) return;
|
|
244
|
+
var nativeGet = descriptor.get;
|
|
245
|
+
var get = function () {
|
|
246
|
+
var reached = nativeGet.call(this);
|
|
247
|
+
if (reached) install(property === "contentDocument" ? reached.defaultView : reached);
|
|
248
|
+
return reached;
|
|
249
|
+
};
|
|
250
|
+
getters.add(get);
|
|
251
|
+
Object.defineProperty(Element.prototype, property, { configurable: true, enumerable: descriptor.enumerable, get: get });
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
var nativeOpen = target.open;
|
|
255
|
+
if (typeof nativeOpen === "function" && !getters.has(nativeOpen)) {
|
|
256
|
+
var open = function () {
|
|
257
|
+
var opened = nativeOpen.apply(this, arguments);
|
|
258
|
+
install(opened);
|
|
259
|
+
return opened;
|
|
260
|
+
};
|
|
261
|
+
getters.add(open);
|
|
262
|
+
target.open = open;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
install(window);
|
|
267
|
+
// A game that plays online gets its relay servers while it loads, so its first connection doesn't wait.
|
|
268
|
+
if (config.fetchEarly && NAMES.some(function (name) { return typeof window[name] === "function"; })) relayServers();
|
|
269
|
+
})(window);
|