@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.
Files changed (349) hide show
  1. package/README.md +2 -0
  2. package/bench/brand/.assetsignore +4 -0
  3. package/bench/brand/README.md +51 -0
  4. package/bench/brand/_headers +13 -0
  5. package/bench/brand/app-icon-1024.png +0 -0
  6. package/bench/brand/app-icon-192.png +0 -0
  7. package/bench/brand/app-icon-512.png +0 -0
  8. package/bench/brand/apple-touch-icon.png +0 -0
  9. package/bench/brand/cover-guide/coin-cascade-busy-fine.webp +0 -0
  10. package/bench/brand/cover-guide/gem-grid-cover.webp +0 -0
  11. package/bench/brand/cover-guide/gem-grid-screenshot.webp +0 -0
  12. package/bench/brand/cover-guide/good-arena.webp +0 -0
  13. package/bench/brand/cover-guide/good-four-in-a-row.webp +0 -0
  14. package/bench/brand/cover-guide/lava-leap-extra-text.webp +0 -0
  15. package/bench/brand/cover-guide/lava-leap-fake-reward.webp +0 -0
  16. package/bench/brand/cover-guide/lava-leap-fine.webp +0 -0
  17. package/bench/brand/cover-guide/lava-leap-overlay.webp +0 -0
  18. package/bench/brand/cover-guide/screenshot-fine.webp +0 -0
  19. package/bench/brand/cover-guide/screenshot-not-fine.webp +0 -0
  20. package/bench/brand/design/covers/ashen-crown.webp +0 -0
  21. package/bench/brand/design/covers/frostline.webp +0 -0
  22. package/bench/brand/design/covers/glasswing.webp +0 -0
  23. package/bench/brand/design/covers/hollow-tide.webp +0 -0
  24. package/bench/brand/design/covers/iron-vigil.webp +0 -0
  25. package/bench/brand/design/covers/kestrel.webp +0 -0
  26. package/bench/brand/design/covers/neon-drift.webp +0 -0
  27. package/bench/brand/design/covers/verdant.webp +0 -0
  28. package/bench/brand/favicon-32.png +0 -0
  29. package/bench/brand/favicon.ico +0 -0
  30. package/bench/brand/favicon.svg +1 -0
  31. package/bench/brand/flags/LICENSE +21 -0
  32. package/bench/brand/flags/ad.png +0 -0
  33. package/bench/brand/flags/ae.png +0 -0
  34. package/bench/brand/flags/af.png +0 -0
  35. package/bench/brand/flags/ag.png +0 -0
  36. package/bench/brand/flags/ai.png +0 -0
  37. package/bench/brand/flags/al.png +0 -0
  38. package/bench/brand/flags/am.png +0 -0
  39. package/bench/brand/flags/ao.png +0 -0
  40. package/bench/brand/flags/aq.png +0 -0
  41. package/bench/brand/flags/ar.png +0 -0
  42. package/bench/brand/flags/as.png +0 -0
  43. package/bench/brand/flags/at.png +0 -0
  44. package/bench/brand/flags/au.png +0 -0
  45. package/bench/brand/flags/aw.png +0 -0
  46. package/bench/brand/flags/ax.png +0 -0
  47. package/bench/brand/flags/az.png +0 -0
  48. package/bench/brand/flags/ba.png +0 -0
  49. package/bench/brand/flags/bb.png +0 -0
  50. package/bench/brand/flags/bd.png +0 -0
  51. package/bench/brand/flags/be.png +0 -0
  52. package/bench/brand/flags/bf.png +0 -0
  53. package/bench/brand/flags/bg.png +0 -0
  54. package/bench/brand/flags/bh.png +0 -0
  55. package/bench/brand/flags/bi.png +0 -0
  56. package/bench/brand/flags/bj.png +0 -0
  57. package/bench/brand/flags/bl.png +0 -0
  58. package/bench/brand/flags/bm.png +0 -0
  59. package/bench/brand/flags/bn.png +0 -0
  60. package/bench/brand/flags/bo.png +0 -0
  61. package/bench/brand/flags/bq.png +0 -0
  62. package/bench/brand/flags/br.png +0 -0
  63. package/bench/brand/flags/bs.png +0 -0
  64. package/bench/brand/flags/bt.png +0 -0
  65. package/bench/brand/flags/bv.png +0 -0
  66. package/bench/brand/flags/bw.png +0 -0
  67. package/bench/brand/flags/by.png +0 -0
  68. package/bench/brand/flags/bz.png +0 -0
  69. package/bench/brand/flags/ca.png +0 -0
  70. package/bench/brand/flags/cc.png +0 -0
  71. package/bench/brand/flags/cd.png +0 -0
  72. package/bench/brand/flags/cf.png +0 -0
  73. package/bench/brand/flags/cg.png +0 -0
  74. package/bench/brand/flags/ch.png +0 -0
  75. package/bench/brand/flags/ci.png +0 -0
  76. package/bench/brand/flags/ck.png +0 -0
  77. package/bench/brand/flags/cl.png +0 -0
  78. package/bench/brand/flags/cm.png +0 -0
  79. package/bench/brand/flags/cn.png +0 -0
  80. package/bench/brand/flags/co.png +0 -0
  81. package/bench/brand/flags/cp.png +0 -0
  82. package/bench/brand/flags/cr.png +0 -0
  83. package/bench/brand/flags/cu.png +0 -0
  84. package/bench/brand/flags/cv.png +0 -0
  85. package/bench/brand/flags/cw.png +0 -0
  86. package/bench/brand/flags/cx.png +0 -0
  87. package/bench/brand/flags/cy.png +0 -0
  88. package/bench/brand/flags/cz.png +0 -0
  89. package/bench/brand/flags/de.png +0 -0
  90. package/bench/brand/flags/dg.png +0 -0
  91. package/bench/brand/flags/dj.png +0 -0
  92. package/bench/brand/flags/dk.png +0 -0
  93. package/bench/brand/flags/dm.png +0 -0
  94. package/bench/brand/flags/do.png +0 -0
  95. package/bench/brand/flags/dz.png +0 -0
  96. package/bench/brand/flags/ec.png +0 -0
  97. package/bench/brand/flags/ee.png +0 -0
  98. package/bench/brand/flags/eg.png +0 -0
  99. package/bench/brand/flags/eh.png +0 -0
  100. package/bench/brand/flags/er.png +0 -0
  101. package/bench/brand/flags/es.png +0 -0
  102. package/bench/brand/flags/et.png +0 -0
  103. package/bench/brand/flags/eu.png +0 -0
  104. package/bench/brand/flags/fi.png +0 -0
  105. package/bench/brand/flags/fj.png +0 -0
  106. package/bench/brand/flags/fk.png +0 -0
  107. package/bench/brand/flags/fm.png +0 -0
  108. package/bench/brand/flags/fo.png +0 -0
  109. package/bench/brand/flags/fr.png +0 -0
  110. package/bench/brand/flags/ga.png +0 -0
  111. package/bench/brand/flags/gb.png +0 -0
  112. package/bench/brand/flags/gd.png +0 -0
  113. package/bench/brand/flags/ge.png +0 -0
  114. package/bench/brand/flags/gf.png +0 -0
  115. package/bench/brand/flags/gg.png +0 -0
  116. package/bench/brand/flags/gh.png +0 -0
  117. package/bench/brand/flags/gi.png +0 -0
  118. package/bench/brand/flags/gl.png +0 -0
  119. package/bench/brand/flags/gm.png +0 -0
  120. package/bench/brand/flags/gn.png +0 -0
  121. package/bench/brand/flags/gp.png +0 -0
  122. package/bench/brand/flags/gq.png +0 -0
  123. package/bench/brand/flags/gr.png +0 -0
  124. package/bench/brand/flags/gs.png +0 -0
  125. package/bench/brand/flags/gt.png +0 -0
  126. package/bench/brand/flags/gu.png +0 -0
  127. package/bench/brand/flags/gw.png +0 -0
  128. package/bench/brand/flags/gy.png +0 -0
  129. package/bench/brand/flags/hk.png +0 -0
  130. package/bench/brand/flags/hm.png +0 -0
  131. package/bench/brand/flags/hn.png +0 -0
  132. package/bench/brand/flags/hr.png +0 -0
  133. package/bench/brand/flags/ht.png +0 -0
  134. package/bench/brand/flags/hu.png +0 -0
  135. package/bench/brand/flags/ic.png +0 -0
  136. package/bench/brand/flags/id.png +0 -0
  137. package/bench/brand/flags/ie.png +0 -0
  138. package/bench/brand/flags/il.png +0 -0
  139. package/bench/brand/flags/im.png +0 -0
  140. package/bench/brand/flags/in.png +0 -0
  141. package/bench/brand/flags/io.png +0 -0
  142. package/bench/brand/flags/iq.png +0 -0
  143. package/bench/brand/flags/ir.png +0 -0
  144. package/bench/brand/flags/is.png +0 -0
  145. package/bench/brand/flags/it.png +0 -0
  146. package/bench/brand/flags/je.png +0 -0
  147. package/bench/brand/flags/jm.png +0 -0
  148. package/bench/brand/flags/jo.png +0 -0
  149. package/bench/brand/flags/jp.png +0 -0
  150. package/bench/brand/flags/ke.png +0 -0
  151. package/bench/brand/flags/kg.png +0 -0
  152. package/bench/brand/flags/kh.png +0 -0
  153. package/bench/brand/flags/ki.png +0 -0
  154. package/bench/brand/flags/km.png +0 -0
  155. package/bench/brand/flags/kn.png +0 -0
  156. package/bench/brand/flags/kp.png +0 -0
  157. package/bench/brand/flags/kr.png +0 -0
  158. package/bench/brand/flags/kw.png +0 -0
  159. package/bench/brand/flags/ky.png +0 -0
  160. package/bench/brand/flags/kz.png +0 -0
  161. package/bench/brand/flags/la.png +0 -0
  162. package/bench/brand/flags/lb.png +0 -0
  163. package/bench/brand/flags/lc.png +0 -0
  164. package/bench/brand/flags/li.png +0 -0
  165. package/bench/brand/flags/lk.png +0 -0
  166. package/bench/brand/flags/lr.png +0 -0
  167. package/bench/brand/flags/ls.png +0 -0
  168. package/bench/brand/flags/lt.png +0 -0
  169. package/bench/brand/flags/lu.png +0 -0
  170. package/bench/brand/flags/lv.png +0 -0
  171. package/bench/brand/flags/ly.png +0 -0
  172. package/bench/brand/flags/ma.png +0 -0
  173. package/bench/brand/flags/mc.png +0 -0
  174. package/bench/brand/flags/md.png +0 -0
  175. package/bench/brand/flags/me.png +0 -0
  176. package/bench/brand/flags/mf.png +0 -0
  177. package/bench/brand/flags/mg.png +0 -0
  178. package/bench/brand/flags/mh.png +0 -0
  179. package/bench/brand/flags/mk.png +0 -0
  180. package/bench/brand/flags/ml.png +0 -0
  181. package/bench/brand/flags/mm.png +0 -0
  182. package/bench/brand/flags/mn.png +0 -0
  183. package/bench/brand/flags/mo.png +0 -0
  184. package/bench/brand/flags/mp.png +0 -0
  185. package/bench/brand/flags/mq.png +0 -0
  186. package/bench/brand/flags/mr.png +0 -0
  187. package/bench/brand/flags/ms.png +0 -0
  188. package/bench/brand/flags/mt.png +0 -0
  189. package/bench/brand/flags/mu.png +0 -0
  190. package/bench/brand/flags/mv.png +0 -0
  191. package/bench/brand/flags/mw.png +0 -0
  192. package/bench/brand/flags/mx.png +0 -0
  193. package/bench/brand/flags/my.png +0 -0
  194. package/bench/brand/flags/mz.png +0 -0
  195. package/bench/brand/flags/na.png +0 -0
  196. package/bench/brand/flags/nc.png +0 -0
  197. package/bench/brand/flags/ne.png +0 -0
  198. package/bench/brand/flags/nf.png +0 -0
  199. package/bench/brand/flags/ng.png +0 -0
  200. package/bench/brand/flags/ni.png +0 -0
  201. package/bench/brand/flags/nl.png +0 -0
  202. package/bench/brand/flags/no.png +0 -0
  203. package/bench/brand/flags/np.png +0 -0
  204. package/bench/brand/flags/nr.png +0 -0
  205. package/bench/brand/flags/nu.png +0 -0
  206. package/bench/brand/flags/nz.png +0 -0
  207. package/bench/brand/flags/om.png +0 -0
  208. package/bench/brand/flags/pa.png +0 -0
  209. package/bench/brand/flags/pc.png +0 -0
  210. package/bench/brand/flags/pe.png +0 -0
  211. package/bench/brand/flags/pf.png +0 -0
  212. package/bench/brand/flags/pg.png +0 -0
  213. package/bench/brand/flags/ph.png +0 -0
  214. package/bench/brand/flags/pk.png +0 -0
  215. package/bench/brand/flags/pl.png +0 -0
  216. package/bench/brand/flags/pm.png +0 -0
  217. package/bench/brand/flags/pn.png +0 -0
  218. package/bench/brand/flags/pr.png +0 -0
  219. package/bench/brand/flags/ps.png +0 -0
  220. package/bench/brand/flags/pt.png +0 -0
  221. package/bench/brand/flags/pw.png +0 -0
  222. package/bench/brand/flags/py.png +0 -0
  223. package/bench/brand/flags/qa.png +0 -0
  224. package/bench/brand/flags/re.png +0 -0
  225. package/bench/brand/flags/ro.png +0 -0
  226. package/bench/brand/flags/rs.png +0 -0
  227. package/bench/brand/flags/ru.png +0 -0
  228. package/bench/brand/flags/rw.png +0 -0
  229. package/bench/brand/flags/sa.png +0 -0
  230. package/bench/brand/flags/sb.png +0 -0
  231. package/bench/brand/flags/sc.png +0 -0
  232. package/bench/brand/flags/sd.png +0 -0
  233. package/bench/brand/flags/se.png +0 -0
  234. package/bench/brand/flags/sg.png +0 -0
  235. package/bench/brand/flags/sh.png +0 -0
  236. package/bench/brand/flags/si.png +0 -0
  237. package/bench/brand/flags/sj.png +0 -0
  238. package/bench/brand/flags/sk.png +0 -0
  239. package/bench/brand/flags/sl.png +0 -0
  240. package/bench/brand/flags/sm.png +0 -0
  241. package/bench/brand/flags/sn.png +0 -0
  242. package/bench/brand/flags/so.png +0 -0
  243. package/bench/brand/flags/sr.png +0 -0
  244. package/bench/brand/flags/ss.png +0 -0
  245. package/bench/brand/flags/st.png +0 -0
  246. package/bench/brand/flags/sv.png +0 -0
  247. package/bench/brand/flags/sx.png +0 -0
  248. package/bench/brand/flags/sy.png +0 -0
  249. package/bench/brand/flags/sz.png +0 -0
  250. package/bench/brand/flags/tc.png +0 -0
  251. package/bench/brand/flags/td.png +0 -0
  252. package/bench/brand/flags/tf.png +0 -0
  253. package/bench/brand/flags/tg.png +0 -0
  254. package/bench/brand/flags/th.png +0 -0
  255. package/bench/brand/flags/tj.png +0 -0
  256. package/bench/brand/flags/tk.png +0 -0
  257. package/bench/brand/flags/tl.png +0 -0
  258. package/bench/brand/flags/tm.png +0 -0
  259. package/bench/brand/flags/tn.png +0 -0
  260. package/bench/brand/flags/to.png +0 -0
  261. package/bench/brand/flags/tr.png +0 -0
  262. package/bench/brand/flags/tt.png +0 -0
  263. package/bench/brand/flags/tv.png +0 -0
  264. package/bench/brand/flags/tw.png +0 -0
  265. package/bench/brand/flags/tz.png +0 -0
  266. package/bench/brand/flags/ua.png +0 -0
  267. package/bench/brand/flags/ug.png +0 -0
  268. package/bench/brand/flags/um.png +0 -0
  269. package/bench/brand/flags/un.png +0 -0
  270. package/bench/brand/flags/us.png +0 -0
  271. package/bench/brand/flags/uy.png +0 -0
  272. package/bench/brand/flags/uz.png +0 -0
  273. package/bench/brand/flags/va.png +0 -0
  274. package/bench/brand/flags/vc.png +0 -0
  275. package/bench/brand/flags/ve.png +0 -0
  276. package/bench/brand/flags/vg.png +0 -0
  277. package/bench/brand/flags/vi.png +0 -0
  278. package/bench/brand/flags/vn.png +0 -0
  279. package/bench/brand/flags/vu.png +0 -0
  280. package/bench/brand/flags/wf.png +0 -0
  281. package/bench/brand/flags/ws.png +0 -0
  282. package/bench/brand/flags/xk.png +0 -0
  283. package/bench/brand/flags/xx.png +0 -0
  284. package/bench/brand/flags/ye.png +0 -0
  285. package/bench/brand/flags/yt.png +0 -0
  286. package/bench/brand/flags/za.png +0 -0
  287. package/bench/brand/flags/zm.png +0 -0
  288. package/bench/brand/flags/zw.png +0 -0
  289. package/bench/brand/fonts/OFL.txt +93 -0
  290. package/bench/brand/fonts/jost.7726a5cd.woff2 +0 -0
  291. package/bench/brand/loader/gif/zaisma-loader-black-128.gif +0 -0
  292. package/bench/brand/loader/gif/zaisma-loader-black-512.gif +0 -0
  293. package/bench/brand/loader/gif/zaisma-loader-white-128.gif +0 -0
  294. package/bench/brand/loader/gif/zaisma-loader-white-512.gif +0 -0
  295. package/bench/brand/loader/png/zaisma-loader-black-128.png +0 -0
  296. package/bench/brand/loader/png/zaisma-loader-black-512.png +0 -0
  297. package/bench/brand/loader/png/zaisma-loader-white-128.png +0 -0
  298. package/bench/brand/loader/png/zaisma-loader-white-512.png +0 -0
  299. package/bench/brand/loader/webp/zaisma-loader-black-128.webp +0 -0
  300. package/bench/brand/loader/webp/zaisma-loader-black-512.webp +0 -0
  301. package/bench/brand/loader/webp/zaisma-loader-white-128.webp +0 -0
  302. package/bench/brand/loader/webp/zaisma-loader-white-512.webp +0 -0
  303. package/bench/brand/loader/zaisma-loader-black.svg +1 -0
  304. package/bench/brand/loader/zaisma-loader-white.svg +1 -0
  305. package/bench/brand/loader/zaisma-loader.svg +1 -0
  306. package/bench/brand/sign-in/city-day-phone.webp +0 -0
  307. package/bench/brand/sign-in/city-day.webp +0 -0
  308. package/bench/brand/sign-in/city-night-phone.webp +0 -0
  309. package/bench/brand/sign-in/city-night.webp +0 -0
  310. package/bench/brand/zaisma-app-icon.svg +1 -0
  311. package/bench/brand/zaisma-mark-black-2048.png +0 -0
  312. package/bench/brand/zaisma-mark-black.svg +1 -0
  313. package/bench/brand/zaisma-mark-metal-2048.png +0 -0
  314. package/bench/brand/zaisma-mark-metal.svg +1 -0
  315. package/bench/brand/zaisma-mark-small-black.svg +1 -0
  316. package/bench/brand/zaisma-mark-small-white.svg +1 -0
  317. package/bench/brand/zaisma-mark-tricolor.svg +1 -0
  318. package/bench/brand/zaisma-mark-white-2048.png +0 -0
  319. package/bench/brand/zaisma-mark-white.svg +1 -0
  320. package/bench/brand/zaisma-wordmark-black-2400.png +0 -0
  321. package/bench/brand/zaisma-wordmark-black.svg +1 -0
  322. package/bench/brand/zaisma-wordmark-metal-2400.png +0 -0
  323. package/bench/brand/zaisma-wordmark-metal.svg +1 -0
  324. package/bench/brand/zaisma-wordmark-white-2400.png +0 -0
  325. package/bench/brand/zaisma-wordmark-white.svg +1 -0
  326. package/bench/glass.js +46486 -0
  327. package/bin/local-play-bench.d.ts +8 -0
  328. package/bin/local-play-bench.js +218 -0
  329. package/bin/local-play-certificate.js +99 -0
  330. package/bin/local-play-runner.js +43 -0
  331. package/bin/local-play.d.ts +41 -0
  332. package/bin/local-play.js +1573 -0
  333. package/bin/node.js +99 -5
  334. package/package.json +1 -1
  335. package/sdk/picture.js +97 -0
  336. package/sdk/relay.js +269 -0
  337. package/sdk/saves.js +884 -0
  338. package/sdk/zaisma.d.ts +1810 -0
  339. package/sdk/zaisma.js +2459 -0
  340. package/src/chat.d.ts +23 -8
  341. package/src/chat.js +21 -11
  342. package/src/index.d.ts +2 -2
  343. package/src/index.js +1 -1
  344. package/src/pass.d.ts +9 -8
  345. package/src/pass.js +10 -8
  346. package/src/qr-code.d.ts +20 -0
  347. package/src/qr-code.js +319 -0
  348. package/src/room.d.ts +148 -3
  349. package/src/room.js +204 -3
@@ -0,0 +1,1810 @@
1
+ /**
2
+ * The Zaisma SDK, typed: this file is its reference. One script tag, from the game's top folder,
3
+ * gives a browser game its players (Zaisma accounts and guests) and multiplayer rooms:
4
+ *
5
+ * <script src="_zaisma/v1.js"></script>
6
+ *
7
+ * Plain JavaScript, no dependencies, MIT licensed, and every part is optional.
8
+ *
9
+ * v1 only gains things: calls, options, fields and values are added, never renamed or removed. Page
10
+ * messages, storage keys and members whose names start with `_` are not part of v1.
11
+ *
12
+ * For editor and compiler checks, save this file beside your game's folder, not in it, since uploads
13
+ * take no .ts files. On Zaisma it's served beside the SDK as _zaisma/v1.d.ts; in Zaisma's repository
14
+ * it's packages/sdk/zaisma.d.ts. Then put `/// <reference path="../zaisma.d.ts" />` (and in JavaScript,
15
+ * `// @ts-check`) at the top of your script, or list it in jsconfig.json or tsconfig.json. The
16
+ * global `Zaisma` is then typed, and its types are `Zaisma.Room`, `Zaisma.JoinOptions` and so on (in
17
+ * JavaScript, give `let room = null` a JSDoc `@type {Zaisma.Room | null}`, as the example games do).
18
+ *
19
+ * Names follow Zaisma's naming guide: whole words, the unit in the name (`idleTimeoutSec`), and is, has
20
+ * or can for yes or no. An option or event the SDK doesn't take is refused with the list of the ones it
21
+ * does (`error.code` "unknown_option" or "unknown_event"), so a misspelled or old one never goes
22
+ * quietly unused. Errors carry `error.code` ({@link Zaisma.ErrorCode}).
23
+ *
24
+ * Within v1, codes and events only grow: handle unknown ones (a `default` branch), and a new code or event
25
+ * never breaks your game.
26
+ *
27
+ * Player passes, for rooms and your own servers, need the game played on its zaisma.com page, which
28
+ * checks the browser on Play: joining fails with `error.code` "unchecked" when it couldn't, or when the
29
+ * game was opened anywhere else, and "not_on_zaisma" when the game isn't hosted on Zaisma at all.
30
+ *
31
+ * @example
32
+ * const room = await Zaisma.joinRoom(); // joins ?room=CODE if the link has one, else makes a room
33
+ * room.me; room.players; room.roomCode; room.inviteUrl;
34
+ * room.on("message", (message) => {}); // from the game server
35
+ * room.on("players", (players) => {});
36
+ * room.on("connectionStatus", (connectionStatus) => {});
37
+ * room.on("ended", ({ message, endReason }) => {});
38
+ * room.send({ move: "left" });
39
+ * room.leave();
40
+ * await Zaisma.invite(room); // share sheet on phones, copies the link elsewhere
41
+ */
42
+ declare namespace Zaisma {
43
+ /** The SDK's version: 1 for _zaisma/v1.js. */
44
+ const sdkVersionNumber: 1;
45
+
46
+ /** The game's slug, as in zaisma.com/g/<slug>, or null outside Zaisma. */
47
+ const gameSlug: string | null;
48
+
49
+ /** This tab's player: their pass, profiles, and asking a guest to sign in. */
50
+ const player: {
51
+ /**
52
+ * This tab's player, with their player pass: the signed-in Zaisma account, with the same player ID in
53
+ * every game, or a guest. joinRoom gets one by itself; call this to let the player into a server of
54
+ * your own. A guest's is kept for the tab, so a guest who reloads stays the same guest, and renewed when
55
+ * it has under 10 minutes left or the player signs in; an account gets a new one each time the game
56
+ * loads, for whoever is signed in then. Fails with error.code "unchecked" when the
57
+ * game isn't played on its zaisma.com page, "not_on_zaisma" when it isn't hosted on Zaisma, and
58
+ * "account_needed" for a guest when the game needs an account for multiplayer or the whole game
59
+ * (its account_requirement setting): ask them to sign in with Zaisma.player.signIn().
60
+ *
61
+ * @example
62
+ * const mine = await Zaisma.player.getPass(); // { id, name, kind, pass, expiresAt }
63
+ * socket.send(JSON.stringify({ pass: mine.pass }));
64
+ */
65
+ getPass(): Promise<PlayerWithPass>;
66
+
67
+ /**
68
+ * An account's public profile by player ID, what anyone sees of it, like a name for a leaderboard
69
+ * row. Fails with error.code "no_player" for a guest's ID or a deleted account's, and
70
+ * "bad_player_id" for anything that isn't a player ID.
71
+ *
72
+ * @example
73
+ * const profile = await Zaisma.player.getProfile("player_8f3k2m");
74
+ * scoreRow.textContent = profile.name; // the username: nobody can pass as someone else
75
+ */
76
+ getProfile(playerId: string): Promise<PlayerProfile>;
77
+
78
+ /**
79
+ * Ask a guest to sign in with their Zaisma account, without leaving the game. Zaisma's page shows a
80
+ * small card beside the game ("Sign in" or make a free account, and "Not now"); signing in opens a
81
+ * new tab, so the game keeps running. Resolves with the account once the player has signed in, and
82
+ * at once when they already are. From then on Zaisma.player.getPass() and joinRoom give the account's pass;
83
+ * rooms the player is already in keep them as the guest they joined as, so join again to play as
84
+ * the account. Call it when the player presses something that needs an account, like Play online
85
+ * in a game whose multiplayer needs one (joinRoom failed with "account_needed"), or Save to
86
+ * leaderboard. A second call while the card is up waits for the same answer. Fails with error.code
87
+ * "sign_in_declined" when the player chose Not now, and "not_on_zaisma" when the game isn't played
88
+ * on its zaisma.com page. There's no time limit: the player may take a few minutes.
89
+ *
90
+ * @example
91
+ * try {
92
+ * room = await Zaisma.joinRoom();
93
+ * } catch (error) {
94
+ * if (error.code !== "account_needed") throw error;
95
+ * await Zaisma.player.signIn(); // { id, name, kind: "account" }
96
+ * room = await Zaisma.joinRoom();
97
+ * }
98
+ */
99
+ signIn(): Promise<Player>;
100
+
101
+ /**
102
+ * Show Zaisma's "Keep your progress" card at the moment the game picks, like right after the player
103
+ * beat a level or found something rare. It's the same card a guest gets by itself after 5 minutes of
104
+ * play (account_offer_enabled; turn that off to pick every moment yourself): it offers a free account
105
+ * so their progress is kept, beside the game, and making one opens a new tab, so the game keeps
106
+ * running. Because it's Zaisma's card, Zaisma's rule on how often holds, however the game asks: at
107
+ * most once a visit, and not for 3 days after the player chose Not now. Resolves with the account
108
+ * once the player has one, and at once when they're signed in already. A second call while the card
109
+ * is up waits for the same answer. There's no time limit: the player may take a few minutes.
110
+ *
111
+ * Fails with error.code "account_offer_too_soon" when the card already showed this visit or the
112
+ * player chose Not now in the last 3 days (the game can't see the card, so it hears it here; show your
113
+ * own message if you like), "sign_in_declined" when the player chose Not now, "cloud_saves_off" when
114
+ * the game has cloud saves off (no progress for the card to keep: ask with Zaisma.player.signIn()
115
+ * instead), "cloud_saves_not_here" when cloud saves don't work where it's played (a tester link, or a
116
+ * Zaisma without accounts), and "not_on_zaisma" when the game isn't played on its zaisma.com page.
117
+ *
118
+ * @example
119
+ * async function levelBeaten() {
120
+ * try {
121
+ * const account = await Zaisma.player.offerAccount(); // { id, name, kind: "account" }
122
+ * showNote("Your progress follows you now, " + account.name + ".");
123
+ * } catch (error) {
124
+ * if (error.code !== "account_offer_too_soon" && error.code !== "sign_in_declined") throw error;
125
+ * }
126
+ * }
127
+ */
128
+ offerAccount(): Promise<Player>;
129
+ };
130
+
131
+ /**
132
+ * Join a room: the one joinOptions.roomCode names, else the one the page's link points to
133
+ * (?room=CODE, on the same server: {@link Rooms.linkedServerUrl}), else a new room. Resolves once the
134
+ * room has let the player in, so room.me and room.players are set. On your own servers (serverUrl),
135
+ * it also tells Zaisma which room a signed-in player is in, and that they left it, so their friends'
136
+ * Join works there too ({@link Friends.setRoom}).
137
+ *
138
+ * Fails with an error whose code says why and whose message says it in words. The room won't take
139
+ * the player: full, closed (to new players), ended, kept_out (removed for the rest of the match),
140
+ * refused (the game server's canJoin said no; the message is its refusalMessage), hosting_paused (the
141
+ * game's hosting is paused this month: its free allowance or its developer's budget or credit is used). No player pass: unchecked, or account_needed for a
142
+ * guest when the game needs accounts (see Zaisma.player.getPass()). The
143
+ * call is set up wrong: unknown_option, bad_room_code, bad_server_url, not_on_zaisma (the game isn't hosted
144
+ * on Zaisma, serverUrl or not; serverUrl points a game hosted on Zaisma at your own server). Or connection_closed (the connection closed first, say the game left or the player
145
+ * joined from another tab) and request_failed (Zaisma didn't answer). {@link ErrorCode} has them all.
146
+ *
147
+ * @example
148
+ * const room = await Zaisma.joinRoom();
149
+ * Zaisma.joinRoom({ joinData: { character: "knight" } }); // for the game server's canJoin and onJoin
150
+ * Zaisma.joinRoom({ isListed: true }); // others find it in room lists
151
+ * Zaisma.joinRoom({ name: "Ana's castle", labels: { Mode: "Classic", Map: "Docks" } });
152
+ * Zaisma.joinRoom({ idleTimeoutSec: 300 }); // ask "Still there?" after 5 minutes without input
153
+ * Zaisma.joinRoom({ serverUrl: "wss://play.example.com" }); // your own game server
154
+ */
155
+ function joinRoom(joinOptions?: JoinOptions): Promise<Room>;
156
+
157
+ /** Room browsing: the game's listed rooms, which show on its Zaisma page and to the game itself. */
158
+ const rooms: Rooms;
159
+
160
+ /** The game's own game server outside any room: requests to its onRequest. */
161
+ const server: Server;
162
+
163
+ /**
164
+ * Invite friends. On Zaisma it opens the page's Share, the same as the game bar's Share button: the
165
+ * room's QR code, link and code, Copy and the share sheet ("opened", or "failed" when the page couldn't
166
+ * open it). On your own site it opens the share sheet on phones, otherwise copies the room's invite link
167
+ * (the game's page without a room). Call it from a button in your game, like on a "waiting for players"
168
+ * screen: it opens only right after the player taps or presses a key in the game, so call it from that
169
+ * tap's handler, and it fails with error.code "needs_player_tap" otherwise. On a page that isn't
170
+ * Zaisma's, around a game hosted on Zaisma, it fails with "not_on_zaisma".
171
+ *
172
+ * @example
173
+ * if ((await Zaisma.invite(room)) === "copied") inviteButton.textContent = "Link copied";
174
+ */
175
+ function invite(room?: Room | null): Promise<InviteStatus>;
176
+
177
+ /**
178
+ * Zaisma's chat check: the same check Zaisma runs on its side on every chat message to or from an
179
+ * under-18 account. It stops contact details in any disguise, moving to another app or meeting up,
180
+ * personal details and sexual content, and answers with the words to show the sender. Swearing and
181
+ * slurs are flagged (hasSwearing, hasSlur) but allowed: blocking them is your game's call. For chat
182
+ * between players use {@link Room.chat}, which runs this check on its way; this one is for other text
183
+ * players type. Checks the whole text, up to 2000 characters; a longer one is refused (too_long), never cut.
184
+ *
185
+ * @example
186
+ * const check = await Zaisma.checkChat(typedMessage);
187
+ * if (!check.isAllowed) showError(check.message);
188
+ * else if (check.hasSwearing) showError("No swearing in this game."); // your call
189
+ * else send(check.text);
190
+ */
191
+ function checkChat(text: string): Promise<ChatCheck>;
192
+
193
+ /**
194
+ * The Players panel, where a player can mute, block and report the others and see their friends. On
195
+ * Zaisma it opens the game bar's own Players, the same as its button (leaving full screen if the
196
+ * game is in it), so a Players button in your game, in your game's style, does exactly what the bar's
197
+ * does. On your own site, where there's no bar, it draws Zaisma's ready-made panel.
198
+ * A game with its own netcode offers them in its own menu. Or build your own panel
199
+ * with room.mute, room.block and room.report.
200
+ */
201
+ function openPlayersPanel(room: Room): void;
202
+
203
+ /**
204
+ * The signed-in player's Zaisma friends who are playing this game right now, for your own list of
205
+ * them: to join a friend's match, watch it or invite them ({@link Friends.list}). A game never sees
206
+ * friends who aren't playing it, or who's online.
207
+ */
208
+ const friends: Friends;
209
+
210
+ /** The player's friends playing this game ({@link friends}). */
211
+ interface Friends {
212
+ /**
213
+ * The player's friends playing this game right now, in any of its rooms or none, as each friend's
214
+ * settings let the player see them: someone who hides what they're playing or appears offline isn't
215
+ * listed. Each comes with the room to join them in when the player could join it, and otherwise why
216
+ * not, the same reasons Join gives on zaisma.com/friends. Kept for 10 seconds, so call it whenever
217
+ * your list opens. Fails with error.code "account_needed" for a guest (ask them to sign in with
218
+ * {@link player.signIn}), and "not_on_zaisma" when the game isn't hosted on Zaisma.
219
+ *
220
+ * @example
221
+ * const { friends } = await Zaisma.friends.list();
222
+ * for (const friend of friends) {
223
+ * if (friend.roomCode) addRow(friend.name, () => Zaisma.joinRoom({ roomCode: friend.roomCode, serverUrl: friend.serverUrl ?? undefined }));
224
+ * else addRow(friend.name, null, friend.joinRefusal); // like "room_full" or "friend_not_in_room": see FriendJoinRefusal
225
+ * }
226
+ */
227
+ list(): Promise<FriendList>;
228
+ /**
229
+ * Say which room on your own servers the player is in, or null when they left it, so their friends'
230
+ * Join on Zaisma takes them there: a link to your game's page with ?room=CODE&server=… that
231
+ * joinRoom({ serverUrl }) follows (see {@link Rooms.linkedServerUrl}). joinRoom({ serverUrl }) does this by
232
+ * itself, so call it only when your game has its own netcode. The server must be one of the game's
233
+ * own servers, own_server_urls (error.code "unlisted_server" otherwise), and Zaisma rooms need
234
+ * none of this. The SDK says it again every 2 minutes while the page is open, and a room it stops
235
+ * saying drops off after 5. For a guest it does nothing: guests have no friends. Your server keeps
236
+ * its own door: a friend's Join is only a room link.
237
+ *
238
+ * @example
239
+ * await Zaisma.friends.setRoom({ serverUrl: "wss://play.example.com", roomCode: "K7Q2MX" });
240
+ * await Zaisma.friends.setRoom(null); // left the match
241
+ */
242
+ setRoom(room: OwnServerRoom | null): Promise<void>;
243
+ }
244
+
245
+ /** The player's friends playing this game ({@link Friends.list}). */
246
+ interface FriendList {
247
+ /** By name. Empty when none of them are playing it. */
248
+ friends: FriendInGame[];
249
+ }
250
+
251
+ /** A friend playing this game. */
252
+ interface FriendInGame {
253
+ /** Their player ID, the same in every game, as in room.players. */
254
+ id: string;
255
+ /** The name to show for them: their username. */
256
+ name: string;
257
+ username: string;
258
+ /** The room to join them in, when the player could join it; null otherwise, with joinRefusal. */
259
+ roomCode: string | null;
260
+ /** Your own server that room is on, for joinRoom's serverUrl; null for a Zaisma room, or with no room. */
261
+ serverUrl: string | null;
262
+ /** Why the player can't join them, or null when they can. */
263
+ joinRefusal: FriendJoinRefusal | null;
264
+ }
265
+
266
+ /**
267
+ * Why the player can't join a friend, the same reasons Join gives on zaisma.com/friends, in this order:
268
+ * - game_not_taking_joins: your game turned Join off (friend_joins_enabled false in its settings).
269
+ * - friend_not_taking_joins: the friend's own setting doesn't let this player join them.
270
+ * - private_room: your game turned Join off for their room ({@link Room.setFriendJoinsEnabled}).
271
+ * - room_closed: their room is closed to new players.
272
+ * - room_full: their room has no space (players and held seats).
273
+ * - spectators_full: their room takes no more spectators, for Watch.
274
+ * - friend_not_in_room: they're playing, but not in a room your game told Zaisma of (a Zaisma room, or one on your own servers through {@link Friends.setRoom}).
275
+ *
276
+ * More reasons may come within v1: show a plain "can't join" for one you don't know.
277
+ */
278
+ type FriendJoinRefusal = "game_not_taking_joins" | "friend_not_taking_joins" | "private_room" | "room_closed" | "room_full" | "spectators_full" | "friend_not_in_room";
279
+
280
+ /** A room on the game's own servers ({@link Friends.setRoom}). */
281
+ interface OwnServerRoom {
282
+ /** The server, starting with wss:// or https://, like "wss://play.example.com": one of the game's own servers (own_server_urls). */
283
+ serverUrl: string;
284
+ /** Its room code: six characters like "K7Q2MX", from 2 to 9 and A to Z without I or O. */
285
+ roomCode: string;
286
+ /** false: friends' Join shows grayed as "Private room" instead of taking them there. Default true. */
287
+ friendJoinsEnabled?: boolean;
288
+ }
289
+
290
+ /**
291
+ * The game's registered links: the sites on its details that it opens, like its store, Patreon,
292
+ * Steam page or Discord ({@link Links.open}). The game can open no other sites.
293
+ */
294
+ const links: Links;
295
+
296
+ /** The game's registered links ({@link links}). */
297
+ interface Links {
298
+ /**
299
+ * Open one of the links registered on the game's details, by its link_name, in a new tab. Call it
300
+ * from the player's tap or key press, like a Support us button: links open only right after one, so
301
+ * nothing ever opens by itself (error.code "needs_player_tap" otherwise). On Zaisma, the page shows a
302
+ * card over the game's top corner: "You're leaving Zaisma", your game's name, the full address, Open
303
+ * and Stay. The game keeps running behind it. Resolves with "opened" when a new tab opened, and
304
+ * "stayed" when the player chose Stay or the link can't open for them: guests and under-18 accounts
305
+ * open only links on trusted store, payment and video sites, never chat apps like Discord, and see
306
+ * "This link can't be opened here." instead. The game hears the same "stayed" either way, so it never
307
+ * learns why. Your dashboard and get_game say who each link opens for (who_can_open).
308
+ *
309
+ * Add a path and query under the link's address, so a store can tell what was bought and by whom:
310
+ * the link https://kit.itch.io/sky-hopper with path "/buy" opens https://kit.itch.io/sky-hopper/buy.
311
+ * Another site, or a path outside the link's, never opens.
312
+ *
313
+ * On your own site there's no Zaisma page to look the link up, so give its address there as
314
+ * ownSiteUrl, and it opens straight away. On Zaisma, the registered address opens, whatever
315
+ * ownSiteUrl says.
316
+ *
317
+ * Fails with error.code: unknown_link (no link by that name on the game), bad_link_path,
318
+ * bad_link_query (also when the link's own address already sets that name), bad_own_site_url,
319
+ * needs_player_tap, not_on_zaisma (outside Zaisma without ownSiteUrl), unknown_option, slow_down and
320
+ * request_failed.
321
+ *
322
+ * @example
323
+ * supportButton.onclick = () => Zaisma.links.open("patreon");
324
+ * buyButton.onclick = async () => {
325
+ * const linkStatus = await Zaisma.links.open("store", { path: "/buy", queryParameters: { item: "sword", player: playerId } });
326
+ * if (linkStatus === "opened") showNote("Finish buying in the new tab.");
327
+ * };
328
+ * Zaisma.links.open("store", { ownSiteUrl: "https://kit.itch.io/sky-hopper" }); // also works on your own site
329
+ */
330
+ open(linkName: string, linkOptions?: LinkOptions): Promise<LinkStatus>;
331
+ }
332
+
333
+ /**
334
+ * The game's items: what the player owns (from their player pass, so it matches what the game's server
335
+ * sees) and what the game sells, priced in the player's currency. Developers set items with set_item;
336
+ * buying comes with Zaisma's buy sheet.
337
+ */
338
+ const items: Items;
339
+
340
+ /** The game's items ({@link items}). */
341
+ interface Items {
342
+ /**
343
+ * Whether the player owns a permanent item (bought or granted, not refunded), by its item_name, as their
344
+ * player pass says: a new pass (at most a day old) brings new items. false for guests, who own nothing.
345
+ * Fails with error.code bad_item_name, not_on_zaisma, unchecked, account_needed and request_failed, as
346
+ * Zaisma.player.getPass() does.
347
+ *
348
+ * @example
349
+ * if (await Zaisma.items.owns("skin_red-dragon")) unlockSkin("red-dragon");
350
+ */
351
+ owns(itemName: string): Promise<boolean>;
352
+ /** Every permanent item the player owns, by item_name; [] for guests. Fails as {@link Items.owns} does. */
353
+ owned(): Promise<string[]>;
354
+ /**
355
+ * The items the game sells, at the price this player pays: in their currency when the game set a price
356
+ * for it (picked by the player's country), else the main price. Show priceText beside anything sold for
357
+ * coins or gems too: real prices are always shown. Fails with error.code not_on_zaisma, bad_origin,
358
+ * game_unavailable and request_failed.
359
+ *
360
+ * @example
361
+ * for (const item of await Zaisma.items.list()) addShopRow(item.title, item.priceText, item.isOwned);
362
+ */
363
+ list(): Promise<ItemForSale[]>;
364
+ /**
365
+ * Buy an item through Zaisma's buy sheet: on the game's zaisma.com page it slides up over the game, which
366
+ * keeps running; on your own site a zaisma.com window opens. The player pays you through your own Stripe
367
+ * account (get_stripe_account), with no cut. Call it from a tap or key press handler. Every player outcome
368
+ * resolves: "bought" (the player's pass owns it at once; pass purchase.receipt to your server to check),
369
+ * "cancelled" (nothing was charged: the player closed the sheet, or already owned the item) or "failed"
370
+ * (declined, or still settling when the sheet closed: a "purchase" event follows if it completes later).
371
+ * Fails with error.code needs_player_tap, bad_item_name, no_item, no_game, not_for_sale,
372
+ * stripe_not_connected, stripe_cannot_take_payments, bad_item_count, bad_game_account_id, unknown_option,
373
+ * not_on_zaisma and request_failed: your mistakes, never the player's; for the ones Zaisma finds,
374
+ * error.message ends with the fix (finish setting up Stripe, connect_stripe_account, set_item).
375
+ *
376
+ * @example
377
+ * buyButton.onclick = async () => {
378
+ * const bought = await Zaisma.items.buy("revive");
379
+ * if (bought.status === "bought") revivePlayer();
380
+ * };
381
+ */
382
+ buy(itemName: string, buyOptions?: BuyOptions): Promise<BuyResult>;
383
+ /**
384
+ * Listen for the buy sheet opening and closing, and for a purchase on its way to the game (the
385
+ * {@link ItemsEvents}). Purchases come when the game's purchase
386
+ * settings say purchase_delivery "game": the game calls {@link Items.markHandled} first, then hands it
387
+ * over, so a second tab, which gets "already_handled", skips it and it comes once. Each one waiting comes once the game listens, and each new one after the
388
+ * buy sheet completes it. A wrong name fails with error.code "unknown_event". With "game_server",
389
+ * the game server's onPurchase takes purchases instead, and "purchase" and "refund" never fire.
390
+ *
391
+ * @example
392
+ * Zaisma.items.on("purchase", async (purchase) => {
393
+ * await Zaisma.items.markHandled(purchase.id); // already_handled: another tab took it
394
+ * if (purchase.itemName === "revive") addRevives(purchase.itemCount);
395
+ * });
396
+ */
397
+ on<EventName extends keyof ItemsEvents>(event: EventName, listener: ItemsEvents[EventName]): this;
398
+ /** Stop listening. */
399
+ off<EventName extends keyof ItemsEvents>(event: EventName, listener: ItemsEvents[EventName]): this;
400
+ /**
401
+ * The player's purchases the game hasn't marked handled, oldest first: each
402
+ * completed one, and each taken back (purchaseStatus "refunded" or "charged_back") after it was
403
+ * handled; at most 50 purchases and 50 refunds at a time. [] for guests, who buy nothing. Fails as {@link Items.owns} does, and with "slow_down".
404
+ */
405
+ unhandled(): Promise<Purchase[]>;
406
+ /**
407
+ * Mark a purchase handed over, so it doesn't come again (or, for one taken back, its refund). Fails
408
+ * with error.code "bad_purchase_id", "no_purchase" (not this player's, or not paid), "already_handled"
409
+ * (another tab or device took it first: don't hand it over twice), "handled_by_server" (purchase_delivery
410
+ * isn't "game": the game server or the developer's own server marks it), "slow_down", and as
411
+ * {@link Items.owns} does.
412
+ *
413
+ * @example
414
+ * await Zaisma.items.markHandled(purchase.id);
415
+ */
416
+ markHandled(purchaseId: string): Promise<void>;
417
+ }
418
+
419
+ /** The events of {@link items}, each with what its listener gets. */
420
+ interface ItemsEvents {
421
+ /** A completed purchase the game hasn't handled yet: mark it handled, then add what it bought. */
422
+ purchase: (purchase: Purchase) => void;
423
+ /**
424
+ * A purchase taken back (refunded or charged back) after the game handled it: the game decides what to
425
+ * take back, after marking it handled. A permanent item leaves {@link Items.owned} by itself.
426
+ */
427
+ refund: (purchase: Purchase) => void;
428
+ /** The buy sheet opened over the game, which keeps running: pause it here if it shouldn't. */
429
+ sheetOpen: (sheet: SheetEvent) => void;
430
+ /** The buy sheet closed, bought or not; {@link Items.buy} says how it ended. */
431
+ sheetClose: (sheet: SheetEvent) => void;
432
+ }
433
+
434
+ /** Which item the buy sheet is for ({@link ItemsEvents}). */
435
+ interface SheetEvent {
436
+ itemName: string;
437
+ }
438
+
439
+ /** How {@link Items.buy} buys. */
440
+ interface BuyOptions {
441
+ /** How many of a consumable item, from 1 (1 unless you say). A permanent item is always 1. */
442
+ itemCount?: number;
443
+ /** Your game's own account ID for the player, kept on the purchase and its receipt; null unless you say. */
444
+ gameAccountId?: string | null;
445
+ }
446
+
447
+ /** How a buy ended ({@link Items.buy}). */
448
+ interface BuyResult {
449
+ /**
450
+ * bought: the player paid. cancelled: nothing was charged (the player closed the sheet before paying, or
451
+ * already owned the item). failed: the payment was declined, or was still settling when the sheet closed;
452
+ * if it completes later, the "purchase" event fires for it.
453
+ */
454
+ status: "bought" | "cancelled" | "failed";
455
+ /**
456
+ * The same {@link Purchase} the "purchase" event hands over, with its receipt when bought. On failed it's
457
+ * the purchase when there is one (purchaseStatus "pending" or "failed", receipt ""); null on cancelled.
458
+ */
459
+ purchase: Purchase | null;
460
+ }
461
+
462
+ /** A purchase on its way to the game ({@link ItemsEvents}), and the one a {@link BuyResult} carries, as the API's purchase in camelCase. */
463
+ interface Purchase {
464
+ /** Like "purchase_8f2k1x". */
465
+ id: string;
466
+ itemName: string;
467
+ itemKind: "permanent" | "consumable";
468
+ /** How many of the item: 1 for permanent items. */
469
+ itemCount: number;
470
+ /** sale: bought on the buy sheet. grant: given by the developer (grant_item). */
471
+ purchaseSource: "sale" | "grant";
472
+ /** "pending" and "failed" appear only on a failed {@link BuyResult}'s purchase; the events carry the rest. */
473
+ purchaseStatus: "completed" | "refunded" | "charged_back" | "pending" | "failed";
474
+ /** The game's own account ID for the player, when the game gave one to buy or grant_item; else null. */
475
+ gameAccountId: string | null;
476
+ /** What it paid, in the currency's minor units (cents); null for a grant. */
477
+ paidMinorUnits: number | null;
478
+ /** Like "EUR"; null for a grant. */
479
+ currencyCode: string | null;
480
+ /** When it was bought or granted, an ISO time. */
481
+ purchasedAt: string | null;
482
+ /**
483
+ * When the item was taken back, an ISO time: a full refund or a chargeback (purchaseStatus "refunded" or
484
+ * "charged_back"); null otherwise. A partial refund leaves the player owning the item and shows only in
485
+ * the API's refunded_minor_units, so test purchaseStatus, not just refundedAt, for what the player owns.
486
+ */
487
+ refundedAt: string | null;
488
+ /** The signed receipt, for a server of the game's own to check (verifyReceipt in @zaisma/game-server). "" on a failed {@link BuyResult}'s purchase, which never completed. */
489
+ receipt: string;
490
+ }
491
+
492
+ /** One item the game sells, for this player ({@link Items.list}). */
493
+ interface ItemForSale {
494
+ itemName: string;
495
+ title: string;
496
+ /** "" for none. */
497
+ description: string;
498
+ /** permanent: owned for good, on every device. consumable: delivered once, then the game keeps count. */
499
+ itemKind: "permanent" | "consumable";
500
+ itemCategory: "cosmetic" | "gameplay" | "content" | "currency";
501
+ /** The price this player pays, in the currency's smallest unit (cents; yen are whole), tax included. */
502
+ priceMinorUnits: number;
503
+ /** Lowercase ISO 4217, like "usd" or "brl". */
504
+ currencyCode: string;
505
+ /** The price as the player reads it, like "$1.99" or "R$ 59,90". */
506
+ priceText: string;
507
+ /** The player owns it (a permanent item), by their pass. */
508
+ isOwned: boolean;
509
+ }
510
+
511
+ /**
512
+ * The player's cloud saves, for games that draw their own save screens. With cloud saves on, Zaisma
513
+ * copies the game's own browser saves (localStorage and IndexedDB) to a signed-in player's account
514
+ * with no code, and keeps 5 earlier versions. When two devices played on, the progress played most
515
+ * recently wins and the other is kept as an earlier version: listen for "conflict" to offer the
516
+ * player the other one. When you change a player's save through the API or MCP, the game reloads with
517
+ * it, unless it listens for "developerChange" and rereads its storage instead. Guests' progress stays
518
+ * in their browser, so the calls fail with "account_needed" for them.
519
+ *
520
+ * Every call fails with error.code "account_needed" for a guest, "cloud_saves_off" when the game has
521
+ * cloud saves off, "cloud_saves_not_here" when they don't work where it's played (a tester link, or a
522
+ * Zaisma without accounts), "not_on_zaisma" when the game isn't played on its zaisma.com page,
523
+ * "slow_down" and "request_failed".
524
+ */
525
+ const saves: Saves;
526
+
527
+ /**
528
+ * A version the developer published with match_update_mode "now", for a game that isn't in a match
529
+ * (single-player, or in a menu): it's never forced on the player. Listen for "update" and call
530
+ * update.apply() when it suits the game, like at a checkpoint or back at the main menu: the page
531
+ * reloads the game at the new version, with its save. Without a listener, Zaisma offers Update beside
532
+ * the game, and the player gets it then, or the next time they open the game. Matches hear theirs from
533
+ * the room instead (the room's "versionUpdate" event).
534
+ * @example
535
+ * Zaisma.versions.on("update", (update) => {
536
+ * if (isAtMainMenu()) update.apply();
537
+ * else pendingUpdate = update; // apply it when the player is back at the menu
538
+ * });
539
+ */
540
+ const versions: Versions;
541
+
542
+ /** A version published "now" ({@link versions}). */
543
+ interface Versions {
544
+ /** Listen for "update". A wrong name fails with error.code "unknown_event". */
545
+ on(event: "update", listener: (update: PendingUpdate) => void): Versions;
546
+ off(event: "update", listener: (update: PendingUpdate) => void): Versions;
547
+ }
548
+
549
+ /** A newer version is out ({@link versions}): apply() reloads the game at it, keeping the save. */
550
+ interface PendingUpdate {
551
+ /** The new version, like "version_k2v9q4x7m1p3w8r5t6y0". */
552
+ versionId: string;
553
+ apply(): void;
554
+ }
555
+
556
+ /** The player's cloud saves ({@link saves}). */
557
+ interface Saves {
558
+ /**
559
+ * Listen for an event (the {@link SavesEvents}). A wrong name fails with error.code "unknown_event".
560
+ * Listen as the game starts: a conflict settled while the game was opening waits for the game to
561
+ * listen, then arrives at once.
562
+ *
563
+ * @example
564
+ * Zaisma.saves.on("conflict", (conflict) => {});
565
+ * Zaisma.saves.on("developerChange", (change) => {});
566
+ */
567
+ on<EventName extends keyof SavesEvents>(event: EventName, listener: SavesEvents[EventName]): this;
568
+ /** Stop listening. */
569
+ off<EventName extends keyof SavesEvents>(event: EventName, listener: SavesEvents[EventName]): this;
570
+ /**
571
+ * The player's earlier versions in this game, newest first: up to 5, with the one the save is now
572
+ * marked isCurrentSave. Each says why it was kept ({@link SaveCopyReason}).
573
+ *
574
+ * @example
575
+ * const { saveCopies } = await Zaisma.saves.listCopies();
576
+ * for (const copy of saveCopies) addRow(new Date(copy.savedAt).toLocaleString(), copy.copyReason);
577
+ */
578
+ listCopies(): Promise<SaveCopyList>;
579
+ /**
580
+ * One earlier version with what it holds: its localStorage items and IndexedDB databases, so the game
581
+ * can show what's in it ("Level 7, 1,200 coins"). Writing its localStorageItems into localStorage
582
+ * puts that progress in place without a reload, and the next save copies it to the account, but
583
+ * keeps no earlier version of the progress it replaced: {@link Saves.restoreCopy} does. Fails with
584
+ * error.code "bad_save_copy_id" for anything that isn't a save copy ID, and "no_save_copy" when the
585
+ * player has no such copy (copies past the newest 5 are gone).
586
+ *
587
+ * @example
588
+ * const copy = await Zaisma.saves.getCopy(copyId);
589
+ * levelText.textContent = "Level " + (copy.localStorageItems.level ?? "1");
590
+ */
591
+ getCopy(saveCopyId: string): Promise<SaveCopyContents>;
592
+ /**
593
+ * Make an earlier version the player's save again, as the player's own change. The save it replaces
594
+ * is kept first as an earlier version (copyReason "restore"), so going back can be undone. It
595
+ * reloads the game with the restored progress, the way a game that keeps its state in memory needs:
596
+ * resolves just before the reload. Fails with error.code "bad_save_copy_id", "no_save_copy" and the
597
+ * errors every saves call has.
598
+ *
599
+ * @example
600
+ * useOtherButton.onclick = () => Zaisma.saves.restoreCopy(conflict.keptSaveCopy.id);
601
+ */
602
+ restoreCopy(saveCopyId: string): Promise<void>;
603
+ }
604
+
605
+ /** The events of {@link saves}, each with what its listener gets. */
606
+ interface SavesEvents {
607
+ /**
608
+ * Two progresses differed (two devices, or a guest who signed in to an account with progress of its
609
+ * own), and Zaisma settled it: the progress played most recently won and is what the game opened
610
+ * with, and the other was kept as an earlier version. Offer the player the other one if you like.
611
+ * It fires on the device whose save was kept or refused, where the choice happened, never on a
612
+ * device that only takes up newer progress (like coming back to a tab after another device played
613
+ * on): that device's earlier save is still in {@link Saves.listCopies}.
614
+ *
615
+ * @example
616
+ * Zaisma.saves.on("conflict", async (conflict) => {
617
+ * const other = await Zaisma.saves.getCopy(conflict.keptSaveCopy.id);
618
+ * const where = conflict.winnerKind === "this_device" ? "another device" : "this device";
619
+ * if (confirm("Use your progress from " + where + " instead (level " + other.localStorageItems.level + ")?")) {
620
+ * await Zaisma.saves.restoreCopy(conflict.keptSaveCopy.id); // the game reloads with it
621
+ * }
622
+ * });
623
+ */
624
+ conflict: (conflict: SaveConflict) => void;
625
+ /**
626
+ * You changed the player's save through the API or MCP while they played. Listening means the game
627
+ * takes it in place: Zaisma has already written it into the game's localStorage and IndexedDB, so
628
+ * reread your state from there (without listening, the game reloads instead). The player's progress
629
+ * since their last save is kept as an earlier version.
630
+ *
631
+ * @example
632
+ * Zaisma.saves.on("developerChange", () => {
633
+ * state.coins = Number(localStorage.getItem("coins") ?? 0);
634
+ * state.level = Number(localStorage.getItem("level") ?? 1);
635
+ * redraw();
636
+ * });
637
+ */
638
+ developerChange: (change: DeveloperChange) => void;
639
+ }
640
+
641
+ /** The player's earlier versions ({@link Saves.listCopies}). */
642
+ interface SaveCopyList {
643
+ /** Newest first: up to 5. */
644
+ saveCopies: SaveCopy[];
645
+ }
646
+
647
+ /** One of the player's earlier versions ({@link Saves.listCopies}). */
648
+ interface SaveCopy {
649
+ /** Its ID, like "save_copy_k2v9q4x7m1p3w8r5t6y0": what getCopy and restoreCopy take. */
650
+ id: string;
651
+ /** When this progress was saved, as an ISO time in UTC. */
652
+ savedAt: string;
653
+ /** Why it was kept. */
654
+ copyReason: SaveCopyReason;
655
+ /** How big it is, in bytes. */
656
+ sizeBytes: number;
657
+ /** It's the same progress as the save now. */
658
+ isCurrentSave: boolean;
659
+ /**
660
+ * The game version that saved it, like "version_k2v9q4x7m1p3w8r5t6y0", as the game's page reported it;
661
+ * null for a save you changed, replaced or reset through the API or MCP, and for one from before Zaisma kept it;
662
+ * a restored save has the restored copy's.
663
+ */
664
+ versionId: string | null;
665
+ }
666
+
667
+ /** An earlier version with what it holds ({@link Saves.getCopy}). */
668
+ interface SaveCopyContents extends SaveCopy {
669
+ /** The game's localStorage as it was: every key with its text. */
670
+ localStorageItems: Record<string, string>;
671
+ /**
672
+ * The game's IndexedDB databases as it was ([] when it keeps none), in Zaisma's save format, with
673
+ * records whose binary data and dates are tagged values. restoreCopy puts them back.
674
+ */
675
+ indexeddbDatabases: IndexeddbDatabase[];
676
+ }
677
+
678
+ /**
679
+ * One IndexedDB database in a save, in Zaisma's save format, the same as the developer API's
680
+ * get_save_copy answers. Its names (keyPath, autoIncrement, unique, multiEntry, version) are IndexedDB's own.
681
+ */
682
+ interface IndexeddbDatabase {
683
+ name: string;
684
+ /** The database's IndexedDB version: a whole number from 1. */
685
+ version: number;
686
+ stores: IndexeddbStore[];
687
+ }
688
+
689
+ /** One object store in an {@link IndexeddbDatabase}. */
690
+ interface IndexeddbStore {
691
+ name: string;
692
+ /** Where each record's key is in its value, or null when keys are kept beside the values. */
693
+ keyPath: string | string[] | null;
694
+ autoIncrement: boolean;
695
+ indexes: IndexeddbIndex[];
696
+ /** Each record as [key, value], in Zaisma's save format: binary data, dates and the like are tagged values. */
697
+ records: [unknown, unknown][];
698
+ }
699
+
700
+ /** One index on an {@link IndexeddbStore}. */
701
+ interface IndexeddbIndex {
702
+ name: string;
703
+ keyPath: string | string[];
704
+ unique: boolean;
705
+ multiEntry: boolean;
706
+ }
707
+
708
+ /**
709
+ * Why an earlier version was kept: hourly (a routine copy while playing, at most one an hour),
710
+ * account_lost (the account's progress, replaced by a device's played more recently), smaller (the
711
+ * save shrank to under half, usually a reset), restore (the player went back to an earlier version:
712
+ * restoreCopy), device_lost (a device's progress lost to the account's, played more recently) or
713
+ * developer (you changed, reset or restored the save through the API or MCP).
714
+ * More values may come; treat an unknown one as no reason shown.
715
+ */
716
+ type SaveCopyReason = "hourly" | "account_lost" | "smaller" | "restore" | "device_lost" | "developer";
717
+
718
+ /** A conflict Zaisma settled ({@link SavesEvents.conflict}). */
719
+ interface SaveConflict {
720
+ /**
721
+ * Whose progress won: "this_device", played more recently, or "account", the account's, which the game
722
+ * opened with. More values may come; treat an unknown one as "account".
723
+ */
724
+ winnerKind: "this_device" | "account";
725
+ /** Where the other progress is kept. */
726
+ keptSaveCopy: SaveCopy;
727
+ }
728
+
729
+ /** A change you made to the player's save ({@link SavesEvents.developerChange}). */
730
+ interface DeveloperChange {
731
+ /** When you changed it, as an ISO time in UTC. */
732
+ savedAt: string;
733
+ /** Where the player's progress since their last save is kept, or null when there was none to keep. */
734
+ keptSaveCopy: SaveCopy | null;
735
+ }
736
+
737
+ /** What {@link Links.open} takes. Every option is optional. */
738
+ interface LinkOptions {
739
+ /** A path added under the link's address: starts with /, like "/buy", with no ?, #, . or .. parts. Up to 500 characters. */
740
+ path?: string;
741
+ /**
742
+ * Query parameters added to the link's address, like { item: "sword", player: "player_8f3k2m" }:
743
+ * up to 20, each text of up to 500 characters (numbers as String(3)). Never one the link's own address sets.
744
+ * The whole opened address is at most 2,000 characters and each parameter name at most 100.
745
+ */
746
+ queryParameters?: Record<string, string>;
747
+ /**
748
+ * The link's whole address when the game runs on your own site, like "https://kit.itch.io/sky-hopper",
749
+ * where it opens straight away. On Zaisma the address registered for the link opens instead, so keep the two the same.
750
+ */
751
+ ownSiteUrl?: string;
752
+ }
753
+
754
+ /**
755
+ * What happened when the game opened a link: opened (in a new tab), or stayed (the player chose Stay, or
756
+ * the link can't open for them). More values may come; treat an unknown one as "stayed".
757
+ */
758
+ type LinkStatus = "opened" | "stayed";
759
+
760
+ /** What {@link joinRoom} takes. Every option is optional. */
761
+ interface JoinOptions {
762
+ /**
763
+ * A room's code, like "K7Q2MX": six characters today; they may grow up to 12, so don't depend on the
764
+ * length. By default, the code in the page's link (?room=), else a new room.
765
+ */
766
+ roomCode?: string;
767
+ /**
768
+ * Your own game server's full address, like "wss://play.example.com" (wss://, ws://, https:// or
769
+ * http://), instead of Zaisma rooms. Add it to the game's own servers on Zaisma first, so the game
770
+ * is allowed to connect to it.
771
+ */
772
+ serverUrl?: string;
773
+ /**
774
+ * true: a new room that shows in room lists, for others to find. By default false: a room only its
775
+ * code and link reach, like a private match ({@link Rooms.quickJoin} lists its new rooms unless given
776
+ * false). Counts only when this player makes the room.
777
+ */
778
+ isListed?: boolean;
779
+ /**
780
+ * false: a new room friends can't Join from Zaisma, shown grayed as "Private room" (codes and links
781
+ * still work). Counts only when this player makes the room.
782
+ */
783
+ friendJoinsEnabled?: boolean;
784
+ /** A new room's name, like "Ana's castle": up to 40 characters. Counts only when this player makes the room. */
785
+ name?: string;
786
+ /**
787
+ * A new room's labels, which players can filter rooms by, like { Mode: "Classic", Map: "Docks" }: up
788
+ * to 4, names up to 16 characters and values up to 24. Counts only when this player makes the room.
789
+ */
790
+ labels?: Record<string, string>;
791
+ /**
792
+ * What the player brings into the room, for the game server's canJoin and onJoin, like the character
793
+ * they picked: plain JSON, up to 4 KB. It comes from the player's browser, so check it like any message.
794
+ */
795
+ joinData?: unknown;
796
+ /**
797
+ * Seconds without input before the player is asked "Still there?", from 30 to 86400 (anything else is
798
+ * refused with error.code "bad_idle_timeout"). By default, the game server's idleTimeoutSec (600, 10 minutes).
799
+ */
800
+ idleTimeoutSec?: number;
801
+ /**
802
+ * false: never ask "Still there?" in this game. The room's own check still applies, except where the
803
+ * game server's idleCheck is "client", as with the relay.
804
+ */
805
+ idleCheckEnabled?: boolean;
806
+ /**
807
+ * true: watch the room instead of playing, where the game server takes spectators (its
808
+ * maximumSpectatorCount). A spectator gets what the room sends everyone, sends nothing ({@link Room.send}
809
+ * throws error.code "spectator_cannot_send"), doesn't count as playing and is never asked "Still there?".
810
+ * A closed or full room can still be watched; a room with nobody playing can't (error.code "room_not_running"),
811
+ * and nor can one whose spectators are all there (error.code "spectators_full"), and a player already
812
+ * playing in it can't watch it too (error.code "already_playing"). Zaisma's Watch opens
813
+ * the game with {@link Rooms.isLinkedAsSpectator} true.
814
+ *
815
+ * @example
816
+ * const room = await Zaisma.joinRoom({ isSpectator: Zaisma.rooms.isLinkedAsSpectator });
817
+ */
818
+ isSpectator?: boolean;
819
+ }
820
+
821
+ /**
822
+ * A continent code: Africa, Antarctica, Asia, Europe, North America, Oceania, South America. A region
823
+ * the SDK doesn't take is refused with error.code "bad_region".
824
+ */
825
+ type Region = "AF" | "AN" | "AS" | "EU" | "NA" | "OC" | "SA";
826
+
827
+ /**
828
+ * What {@link Rooms.quickJoin} takes: joinRoom's options but roomCode, isSpectator and serverUrl (it
829
+ * finds Zaisma rooms), and region.
830
+ */
831
+ interface QuickJoinOptions extends Omit<JoinOptions, "roomCode" | "isSpectator" | "serverUrl"> {
832
+ /** Look for a room in this region only, like "EU", as in {@link RoomList.regions}. */
833
+ region?: Region;
834
+ /** Look for a room with these labels, like { Mode: "Classic" }; a new room gets them. */
835
+ labels?: Record<string, string>;
836
+ }
837
+
838
+ /** What {@link Rooms.list} takes. */
839
+ interface ListOptions {
840
+ /** Rooms in this region only, like "EU", as in {@link RoomList.regions}. */
841
+ region?: Region;
842
+ /** Rooms with these labels only, like { Mode: "Classic" }. */
843
+ labels?: Record<string, string>;
844
+ }
845
+
846
+ /** What {@link Move.join} takes: joinRoom's options but roomCode and serverUrl, which are the move's own, and isSpectator (moves are for players). */
847
+ interface MoveJoinOptions extends Omit<JoinOptions, "roomCode" | "serverUrl" | "isSpectator"> {}
848
+
849
+ /** What {@link Room.kick} takes. */
850
+ interface KickOptions {
851
+ /** Keep them out for the rest of the match. */
852
+ keepOut?: boolean;
853
+ }
854
+
855
+ /** What {@link Room.setInfo} takes. Leave a field out to keep it; null clears it. */
856
+ interface RoomInfo {
857
+ /** The room's name, like "Ana's castle": up to 40 characters. */
858
+ name?: string | null;
859
+ /** The room's labels, like { Mode: "Classic", Map: "Docks" }: up to 4, names up to 16 characters and values up to 24. */
860
+ labels?: Record<string, string> | null;
861
+ }
862
+
863
+ /** Room browsing: the game's listed rooms, which show on its Zaisma page and to the game itself. */
864
+ interface Rooms {
865
+ /**
866
+ * The room code the player's link points to (?room=CODE), or null. Check it to show a menu or a
867
+ * character picker first, then join with what the player picked: joinRoom and quickJoin both follow a link to a Zaisma room.
868
+ */
869
+ readonly linkedRoomCode: string | null;
870
+ /**
871
+ * The game's own server the player's link points to (?server=, beside ?room=), or null: a friend's
872
+ * Join into a room on your own servers ({@link Friends.setRoom}). Only joinRoom({ serverUrl }) follows a
873
+ * link that names a server (the same one); quickJoin follows only links to Zaisma rooms, which name none. A game on
874
+ * its own servers joins with joinRoom({ serverUrl, roomCode: Zaisma.rooms.linkedRoomCode }), using this
875
+ * as serverUrl. Zaisma passes it on only when it's one of the game's own servers (own_server_urls).
876
+ */
877
+ readonly linkedServerUrl: string | null;
878
+ /**
879
+ * true when the player's link is a Watch (?watch=1 beside ?room=), from Zaisma's Watch beside Join: they
880
+ * came to watch the linked room, not to play. Join it with joinRoom({ isSpectator: true }).
881
+ */
882
+ readonly isLinkedAsSpectator: boolean;
883
+ /**
884
+ * The game's listed rooms on Zaisma, for a game's own lobby or server browser: rooms with space
885
+ * first (the player's country, then region, then the busiest), up to 50. Never who's in them.
886
+ * Fails with error.code "not_on_zaisma" in a game that isn't hosted on Zaisma.
887
+ *
888
+ * @example
889
+ * const { rooms, playingPlayerCount, regions } = await Zaisma.rooms.list();
890
+ * Zaisma.rooms.list({ region: "EU", labels: { Mode: "Classic" } }); // one region, or rooms with these labels
891
+ */
892
+ list(listOptions?: ListOptions): Promise<RoomList>;
893
+ /**
894
+ * Drop the player into a match: the room their link points to if it has space, otherwise a listed
895
+ * room with space (nearby and busy first), otherwise a new room. Rooms with someone the player
896
+ * blocked come after the others, and before a new room; the linked room is the player's own choice.
897
+ *
898
+ * @example
899
+ * const room = await Zaisma.rooms.quickJoin();
900
+ * Zaisma.rooms.quickJoin({ region: "EU", labels: { Mode: "Classic" } }); // a room like that with space, else a new one with those labels
901
+ */
902
+ quickJoin(quickJoinOptions?: QuickJoinOptions): Promise<Room>;
903
+ }
904
+
905
+ /** The game's own game server outside any room ({@link server}). */
906
+ interface Server {
907
+ /**
908
+ * Send a request to the game's own game server, its onRequest(player, request, server), outside
909
+ * any room, and get back what it answers: for a leaderboard, a shop or a daily reward, in
910
+ * single-player games too. The player's pass goes with it, so the server knows who's asking and
911
+ * the player can't pretend to be someone else. The request is plain JSON, up to 16 KB; say what
912
+ * it's for in its type, like a room message. On Zaisma, the game server is the one in your
913
+ * upload's zaisma-server/ folder (or zaisma-server.js); give serverUrl for your own.
914
+ *
915
+ * Fails with error.code: no_request_handler (the game server has no onRequest), game_server_error
916
+ * (onRequest threw, answered something that isn't plain JSON or over 1 MB, or took over 10
917
+ * seconds; you see why with the game server's errors), hosting_paused (the game's hosting
918
+ * on Zaisma is paused this month: its free allowance or its developer's budget or credit is used), too_big, bad_request (not plain JSON), slow_down
919
+ * (over about 120 a minute from one address), bad_pass, account_needed, unchecked,
920
+ * game_unavailable, bad_origin, busy, bad_server_url, not_on_zaisma (the game isn't hosted
921
+ * on Zaisma, serverUrl or not), unknown_option and request_failed.
922
+ *
923
+ * @example
924
+ * const top = await Zaisma.server.request({ type: "leaderboard" });
925
+ * const { coinCount } = await Zaisma.server.request({ type: "claimDailyReward" });
926
+ * Zaisma.server.request({ type: "buy", itemId: "sword" }, { serverUrl: "https://play.example.com" }); // your own server
927
+ */
928
+ request(request: unknown, requestOptions?: RequestOptions): Promise<unknown>;
929
+ }
930
+
931
+ /** How {@link Room.send} and {@link Room.sendBytes} send. */
932
+ interface SendOptions {
933
+ /**
934
+ * "reliable" (the default) reaches the game server in order. "latest_only" may be dropped: while
935
+ * this player's connection is backed up, a newer send with the same latestKey replaces it, and the
936
+ * newest goes once it catches up. For input sent every frame, like where the player aims. Anything
937
+ * else throws error.code "bad_send_option".
938
+ */
939
+ delivery?: "reliable" | "latest_only";
940
+ /**
941
+ * For "latest_only", and needed there: which sends replace each other, up to 40 characters, like
942
+ * "position". latest_only without one throws error.code "bad_send_option".
943
+ */
944
+ latestKey?: string;
945
+ }
946
+
947
+ /**
948
+ * One change in a state update: [path, value] sets what's at path to value, [path] removes it, and an
949
+ * empty path replaces the whole state. path is keys of objects and indexes of arrays, from the top.
950
+ */
951
+ type StateChange = [path: (string | number)[]] | [path: (string | number)[], value: unknown];
952
+
953
+ /** Options for {@link Server.request}. */
954
+ interface RequestOptions {
955
+ /** Your own game server's address, like "https://play.example.com", instead of Zaisma's. The Node runner answers requests on the same port as its rooms. */
956
+ serverUrl?: string;
957
+ }
958
+
959
+ /** A room the player is in: one live match, running one copy of the game server. */
960
+ interface Room {
961
+ /**
962
+ * The room's code, like "K7Q2MX": six characters today; they may grow up to 12, so don't depend on the
963
+ * length. It changes when the game server moves the player (the "moved" event).
964
+ */
965
+ readonly roomCode: string;
966
+ /** This player. */
967
+ readonly me: Player;
968
+ /**
969
+ * Players in the room right now, in join order, this player included, each with why they aren't
970
+ * playing (awayReason), like a player whose connection dropped and who has a few seconds to come back.
971
+ */
972
+ readonly players: readonly ListedPlayer[];
973
+ /** The host's player ID, when the game server gives the host controls (the relay does), else null. */
974
+ readonly hostPlayerId: string | null;
975
+ /** This player is watching, not playing ({@link JoinOptions.isSpectator}): they're never in players. */
976
+ readonly isSpectator: boolean;
977
+ /** How many are watching the room; the "settings" event says when it changes. */
978
+ readonly spectatorCount: number;
979
+ /** Where the socket stands; the "connectionStatus" event says when it changes. Away is awayReason, not this. */
980
+ readonly connectionStatus: ConnectionStatus;
981
+ /**
982
+ * Why this player is out of the match: "idle" (no input until the "Still there?" ran out) or "hidden"
983
+ * (the game hidden too long), from the "away" event until "back"; null while they play.
984
+ */
985
+ readonly awayReason: "idle" | "hidden" | null;
986
+ /**
987
+ * The room's link on Zaisma, like "https://zaisma.com/g/sky-hopper?room=K7Q2MX", with &server= for a
988
+ * room on your own server, or null outside Zaisma.
989
+ */
990
+ readonly inviteUrl: string | null;
991
+ /** Shown in room lists. A room that isn't still works by code and link. */
992
+ readonly isListed: boolean;
993
+ /** Taking new players. A closed room lets back only players who were in it. */
994
+ readonly isOpen: boolean;
995
+ /** Zaisma's Join (friends on zaisma.com, Zaisma.friends) offers this room; when false it's grayed as "Private room". */
996
+ readonly friendJoinsEnabled: boolean;
997
+ /** The room's name, like "Ana's castle", or null. */
998
+ readonly name: string | null;
999
+ /** The room's labels, like { Mode: "Classic", Map: "Docks" }. */
1000
+ readonly labels: Readonly<Record<string, string>>;
1001
+ /**
1002
+ * Whether this player can chat at all ({@link Room.chat}): a Zaisma account whose chat setting isn't
1003
+ * nobody, playing (not watching). False for guests: hide your chat box, and offer preset phrases or emotes.
1004
+ */
1005
+ readonly canChat: boolean;
1006
+ /**
1007
+ * When Zaisma will end this match, an ISO time, or null: set once the game's hosting pauses for the
1008
+ * month, an hour after the pause (the "ending" event). Never set in a room on your own server.
1009
+ */
1010
+ readonly endsAt: string | null;
1011
+ /**
1012
+ * This player's round trip to the room in milliseconds, smoothed and measured by the room; null until
1013
+ * the first measurement, right after joining. The game server reads the same number with
1014
+ * room.pingDurationMs(playerId).
1015
+ *
1016
+ * @example
1017
+ * // A ping readout in the corner
1018
+ * pingText.textContent = room.pingDurationMs === null ? "" : room.pingDurationMs + " ms";
1019
+ */
1020
+ readonly pingDurationMs: number | null;
1021
+ /**
1022
+ * This player's view of the game server's state, when it has stateSyncEnabled: the game server's
1023
+ * state, or what its stateView answers for this player. null until the first update, which comes as
1024
+ * the player joins; after that it changes with each update (the "state" event). Read it, don't change it.
1025
+ *
1026
+ * @example
1027
+ * function draw() {
1028
+ * const world = room.state;
1029
+ * if (world) for (const [id, body] of Object.entries(world.bodies)) drawBody(id, body);
1030
+ * requestAnimationFrame(draw);
1031
+ * }
1032
+ */
1033
+ readonly state: unknown | null;
1034
+ /**
1035
+ * The state as it was smoothingOptions.delayMs behind the room's clock (100 unless you say, up to 1000),
1036
+ * between the two updates around then: numbers blended in a straight line (integers in between come back
1037
+ * as fractions), anything else as the older update had it, and what's in only one of them as it is. So
1038
+ * positions move evenly however the updates arrive, a moment behind; an angle that wraps around blends
1039
+ * the long way, so blend those yourself. A copy, yours to keep; null until the first update. Anything else
1040
+ * for delayMs throws error.code "bad_delay", and other options "unknown_option". Predicting this player's
1041
+ * own moves stays your game's.
1042
+ *
1043
+ * @example
1044
+ * function draw() {
1045
+ * const world = room.smoothedState(); // or room.smoothedState({ delayMs: 150 })
1046
+ * if (world) for (const body of Object.values(world.bodies)) drawBody(body.x, body.y);
1047
+ * requestAnimationFrame(draw);
1048
+ * }
1049
+ */
1050
+ smoothedState(smoothingOptions?: SmoothingOptions): unknown | null;
1051
+
1052
+ /**
1053
+ * The room's clock now, in epoch milliseconds, estimated from the room's probes (Date.now() until the
1054
+ * first one): the same clock as the game server's room.serverTimeMs(), so a time it sends means the
1055
+ * same moment for every player.
1056
+ *
1057
+ * @example
1058
+ * // The game server sent { type: "countdown", startsAtMs: room.serverTimeMs() + 3000 }
1059
+ * room.on("message", (message) => {
1060
+ * if (message.type === "countdown") startsInMs = message.startsAtMs - room.serverTimeMs();
1061
+ * });
1062
+ */
1063
+ serverTimeMs(): number;
1064
+ /**
1065
+ * Send a message to the game server: plain JSON, up to 16 KB (more throws error.code "too_big"), about
1066
+ * 30 a second at most. Send input when it changes, not every frame. While connectionStatus isn't "open"
1067
+ * or the player is away, reliable sends wait and go in order once the room is back, up to 60: past that
1068
+ * the oldest is dropped, and an "error" event says "send_queue_full" once. With
1069
+ * { delivery: "latest_only", latestKey }, a message may be dropped for a newer one (SendOptions). A
1070
+ * spectator can't send: it throws error.code "spectator_cannot_send". Returns false only when the room
1071
+ * is gone (ended, kicked or left), so nothing will ever send.
1072
+ *
1073
+ * @example
1074
+ * room.send({ type: "fire" });
1075
+ * // Where the player aims, as it changes: only the newest matters.
1076
+ * room.send({ type: "aim", angle }, { delivery: "latest_only", latestKey: "aim" });
1077
+ */
1078
+ send(message: unknown, sendOptions?: SendOptions): boolean;
1079
+ /**
1080
+ * Send bytes to the game server (its onBytes), like a compact input: an ArrayBuffer or any typed
1081
+ * array, up to 16 KB (more throws error.code "too_big"), counted with messages (about 30 a second at
1082
+ * most). Anything else throws error.code "bad_bytes", and a spectator's throws "spectator_cannot_send".
1083
+ * Waits like room.send while the room isn't reachable, takes the same sendOptions, and returns false
1084
+ * only when the room is gone.
1085
+ */
1086
+ sendBytes(bytes: ArrayBuffer | ArrayBufferView, sendOptions?: SendOptions): boolean;
1087
+ /** Leave the room for good. */
1088
+ leave(): void;
1089
+ /**
1090
+ * Show or hide the room in room lists. From the room's host when the game uses the relay; with a
1091
+ * game server of your own, the server decides (its room.setListed). The "settings" event says when it changed.
1092
+ */
1093
+ setListed(isListed: boolean): void;
1094
+ /**
1095
+ * Open or close the room to new players; players who were in it can still come back. From the
1096
+ * room's host when the game uses the relay; with a game server of your own, the server decides (its
1097
+ * room.setOpen). The "settings" event says when it changed.
1098
+ */
1099
+ setOpen(isOpen: boolean): void;
1100
+ /**
1101
+ * Turn Zaisma's Join on or off for this room: off, a player's friends see Join grayed as "Private
1102
+ * room" on zaisma.com and in {@link Friends.list}. It isn't a lock: a code or link still reaches the
1103
+ * room, so close it (setOpen) or answer false from canJoin to keep players out. From the room's host
1104
+ * when the game uses the relay; with a game server of your own, the server decides (its
1105
+ * room.setFriendJoinsEnabled). The "settings" event says when it changed.
1106
+ */
1107
+ setFriendJoinsEnabled(isEnabled: boolean): void;
1108
+ /**
1109
+ * Name and label the room. From the room's host when the game uses the relay; with a game server of
1110
+ * your own, the server decides (its room.setInfo). Names and labels players type show on Zaisma's
1111
+ * pages only if they pass Zaisma's filter for its pages, which blocks swearing too.
1112
+ */
1113
+ setInfo(nameAndLabels: RoomInfo): void;
1114
+ /**
1115
+ * The host removes a player, when the game server gives the host controls (the relay does). They're
1116
+ * told the host removed them; keepOut keeps them out for the rest of the match.
1117
+ */
1118
+ kick(playerId: string, kickOptions?: KickOptions): void;
1119
+ /**
1120
+ * Whether to hide a player's emotes and phrases: this player muted or blocked them, or there's a
1121
+ * block between them either way (never which one made it, so a blocked player is never told).
1122
+ */
1123
+ isMuted(playerId: string): boolean;
1124
+ /** Mute a player for this room. The "muted" event says the list changed. */
1125
+ mute(playerId: string): void;
1126
+ /** Unmute a player muted for this room. A block stays: {@link Room.unblock} lifts one. */
1127
+ unmute(playerId: string): void;
1128
+ /**
1129
+ * Lift a block this session made of a player. A block between two accounts is undone only on
1130
+ * zaisma.com/friends, never from a game.
1131
+ */
1132
+ unblock(playerId: string): void;
1133
+ /**
1134
+ * Block a player in the room: a wall both ways, so neither one's emotes and phrases reach the other,
1135
+ * and the game server's room.hasBlockBetween says so. When both are Zaisma accounts, Zaisma asks the
1136
+ * player beside the game first, and the block then lasts in every game until they undo it on
1137
+ * zaisma.com/friends; it ends a friendship, and the blocked player isn't told. Any other block (a
1138
+ * guest's, or of a guest) lasts for the session. Resolves true once they're blocked, or false if the
1139
+ * player changed their mind. A block never removes anyone from the room.
1140
+ */
1141
+ block(playerId: string): Promise<boolean>;
1142
+ /**
1143
+ * Report a player to Zaisma, with what happened in the player's own words if they gave any (details).
1144
+ * The last chat lines (up to 20) they sent this player, or this player sent them, go with it from this
1145
+ * screen; nothing else about the reporting player is sent.
1146
+ */
1147
+ report(playerId: string, reason: ReportReason, details?: string): Promise<ReportAnswer>;
1148
+ /**
1149
+ * Send a chat message: through the room to the players in it (or chatOptions.toPlayerIds of them) who
1150
+ * may chat with this player, on Zaisma's rooms or your own server's runner. It reaches only players in
1151
+ * this player's chat group whose settings let them chat with this player, never anyone with a block
1152
+ * between them, and never spectators. Your game server's onChat may stop it or send it to fewer
1153
+ * players first; then Zaisma's chat filter checks it on its way (messages to and from under-18s
1154
+ * always, between adults when your game server turns it on), and Zaisma keeps none of it. Draw the
1155
+ * chat yourself: the "chat" event brings the others' messages; show this player's own when it resolves.
1156
+ *
1157
+ * Resolves { sentAt } once it went, even when nobody could read it (the sender is never told who
1158
+ * can't). Rejects with error.code chat_stopped (error.reason says why, like contact_details or
1159
+ * stopped_by_game, and error.message is what to show the player), chat_not_allowed (a guest, or chat
1160
+ * set to nobody: check {@link Room.canChat} first), chat_empty, chat_too_long (over 2000 characters),
1161
+ * chat_too_fast (over 20 in 10 seconds) or connection_closed.
1162
+ *
1163
+ * @example
1164
+ * room.chat(input.value).then(({ sentAt }) => addLine(room.me.name, input.value), (error) => showError(error.message));
1165
+ */
1166
+ chat(text: string, chatOptions?: ChatOptions): Promise<ChatSent>;
1167
+
1168
+ /**
1169
+ * Listen for an event. Listening is optional: without "move" and "back" listeners, the SDK follows
1170
+ * a move and rejoins by itself; with one, your game decides when.
1171
+ *
1172
+ * @example
1173
+ * room.on("message", (message) => {});
1174
+ * room.on("players", (players) => {});
1175
+ * room.on("move", (move) => { showLoading(); move.follow(); });
1176
+ */
1177
+ on<EventName extends keyof RoomEvents>(event: EventName, listener: RoomEvents[EventName]): this;
1178
+ /** Stop listening. */
1179
+ off<EventName extends keyof RoomEvents>(event: EventName, listener: RoomEvents[EventName]): this;
1180
+ }
1181
+
1182
+ /** A room's events, each with what its listener gets. */
1183
+ interface RoomEvents {
1184
+ /**
1185
+ * A message from the game server: the game's own, as it sent it, and the player it came from when the
1186
+ * game server passed one player's message on (its fromPlayerId; the relay always does), or null.
1187
+ */
1188
+ message: (message: any, fromPlayerId: string | null) => void;
1189
+ /**
1190
+ * Bytes from the game server (its room.broadcastBytes or room.sendBytes): fromPlayerId is the player
1191
+ * they came from when the game server passed one player's bytes on (the relay always does), or null.
1192
+ */
1193
+ bytes: (bytes: Uint8Array, fromPlayerId: string | null) => void;
1194
+ /**
1195
+ * A state update, when the game server has stateSyncEnabled: state is room.state now, and changes
1196
+ * what changed since the last update, or null after a full copy (as the player joins, and after their
1197
+ * connection was backed up). Most games read room.state or room.smoothedState() as they draw instead.
1198
+ * @example
1199
+ * room.on("state", (state, changes) => { scoreText.textContent = String(state.scores[room.me.id]); });
1200
+ */
1201
+ state: (state: any, changes: StateChange[] | null) => void;
1202
+ /**
1203
+ * Players joined, left, walked away or came back, or their connection dropped or came back; the same
1204
+ * list as room.players.
1205
+ * @example
1206
+ * // Freeze a player whose connection dropped, and show them reconnecting, until they're back.
1207
+ * room.on("players", (players) => {
1208
+ * for (const player of players) {
1209
+ * const body = bodies.get(player.id);
1210
+ * if (body) body.isFrozen = player.awayReason === "lost";
1211
+ * }
1212
+ * });
1213
+ */
1214
+ players: (players: readonly ListedPlayer[]) => void;
1215
+ /** Where the socket stands, as room.connectionStatus. */
1216
+ connectionStatus: (connectionStatus: ConnectionStatus) => void;
1217
+ /** The match ended, with its message and why (endReason). The connection closes. */
1218
+ ended: (ended: Ended) => void;
1219
+ /**
1220
+ * Zaisma will end this match at endsAt (an ISO time, also room.endsAt), because the game's hosting on
1221
+ * Zaisma rooms paused for the month: tell your players and save what should last.
1222
+ * "ended" follows then.
1223
+ */
1224
+ ending: (ending: Ending) => void;
1225
+ /** The game server or the host removed the player from the room. The connection closes. */
1226
+ kicked: (kicked: Kicked) => void;
1227
+ /** The room's listing, Join switch, name or labels changed, or how many watch: room.isListed, room.isOpen, room.friendJoinsEnabled, room.name, room.labels and room.spectatorCount have the new ones. */
1228
+ settings: (settings: RoomSettingsChange) => void;
1229
+ /**
1230
+ * The game server is sending the player to another room (its room.reserve or room.moveTo). Without
1231
+ * a listener the room follows by itself; with one, call move.follow() when your game is ready, or
1232
+ * leave and move.join() it as a new room object.
1233
+ */
1234
+ move: (move: Move) => void;
1235
+ /** This room object moved to another room: same object, new code. A "players" event with the new list follows. */
1236
+ moved: (moved: Moved) => void;
1237
+ /**
1238
+ * "Still there?": idleStatus "asking" when it's time to ask, after idleTimeoutSec without input, with
1239
+ * answerTimeoutSec to answer. Listening replaces Zaisma's own "Still there?": a card beside the game
1240
+ * on its zaisma.com page, and elsewhere a small plate in the game (restyle the plate with CSS on
1241
+ * #zaisma-still-there instead, if you like). Then "answered" (any input), or "timed_out" as the room
1242
+ * is told they walked away.
1243
+ */
1244
+ idle: (idle: Idle) => void;
1245
+ /**
1246
+ * The player walked away: out of the match, with their seat held for seatHoldDurationSec, unless the
1247
+ * game server decides (its onAway). Any input brings them back.
1248
+ */
1249
+ away: (away: Away) => void;
1250
+ /**
1251
+ * The player came back after walking away. isSeatHeld true: they're in their held seat again, after
1252
+ * the room let them back in. false: the seat ran out, and listening turns off rejoining by itself, so
1253
+ * call back.rejoin() to rejoin their old room (it fails if the room is full or over).
1254
+ */
1255
+ back: (back: Back) => void;
1256
+ /** Whose emotes and phrases to hide changed: every player ID room.isMuted is true for. */
1257
+ muted: (playerIds: string[]) => void;
1258
+ /**
1259
+ * The room sent an error, like too_fast, or the SDK has one for the room, like send_queue_full. Joining
1260
+ * fails with the same code when the room won't take the player.
1261
+ */
1262
+ error: (error: RoomError) => void;
1263
+ /**
1264
+ * The developer published a version with match_update_mode "now", and this match moves to it at
1265
+ * versionUpdate.appliesAt at the latest (sooner if the game server switches at a good moment). Show a
1266
+ * countdown or save; when it switches, this room object reconnects by itself, or, when the game's
1267
+ * files changed (isReloading), the page reloads the game at the new version and joinRoom() with no
1268
+ * code brings the player back into this room.
1269
+ * @example
1270
+ * room.on("versionUpdate", ({ appliesAt }) => showBanner(`Updating at ${new Date(appliesAt).toLocaleTimeString()}`));
1271
+ */
1272
+ versionUpdate: (versionUpdate: VersionUpdate) => void;
1273
+ /**
1274
+ * Another player's chat message reached this player ({@link Room.chat}). Messages from players this
1275
+ * player muted or blocked never come. hasSwearing and hasSlur flag what Zaisma allows: hide or blur
1276
+ * them if your game doesn't. (Optional here only so games written for v1's events still fit.)
1277
+ */
1278
+ chat?: (chatMessage: ChatMessage) => void;
1279
+ }
1280
+
1281
+ /** What {@link Room.chat} takes. */
1282
+ interface ChatOptions {
1283
+ /** Send it to only these players in the room (like a team), from room.players. All players when left out. */
1284
+ toPlayerIds?: string[];
1285
+ }
1286
+
1287
+ /** {@link Room.chat}'s answer once the message went. */
1288
+ interface ChatSent {
1289
+ /** When the room sent it, as an ISO time. */
1290
+ sentAt: string;
1291
+ }
1292
+
1293
+ /** A chat message another player sent ({@link Room.chat}), as the "chat" event brings it. */
1294
+ interface ChatMessage {
1295
+ fromPlayerId: string;
1296
+ text: string;
1297
+ /** It swears: Zaisma allows it; hide it if your game doesn't. */
1298
+ hasSwearing: boolean;
1299
+ /** It has a slur: Zaisma allows it too; your game and its moderators decide. */
1300
+ hasSlur: boolean;
1301
+ /** When the room sent it, as an ISO time. */
1302
+ sentAt: string;
1303
+ }
1304
+
1305
+ /**
1306
+ * The codes {@link Room.chat} rejects with: chat_stopped (the game server's onChat or Zaisma's filter
1307
+ * stopped it: error.reason and error.message say why), chat_not_allowed, chat_empty, chat_too_long,
1308
+ * chat_too_fast, bad_player_id, busy (try again in a moment), ended, spectator_cannot_send and
1309
+ * connection_closed. More codes may come within v1: show the error's message.
1310
+ */
1311
+ type ChatErrorCode = "chat_stopped" | "chat_not_allowed" | "chat_empty" | "chat_too_long" | "chat_too_fast" | "chat_not_here" | "bad_player_id" | "busy" | "ended" | "spectator_cannot_send" | "connection_closed";
1312
+
1313
+ /**
1314
+ * Why {@link Room.chat} was stopped (error.reason with chat_stopped): "stopped_by_game" (your game
1315
+ * server's onChat), the filter's {@link ChatCheckReason}s, "chat_paused" (the sender's chat is paused
1316
+ * for now) or "not_allowed_to_chat". More values may come.
1317
+ */
1318
+ type ChatStoppedReason = "stopped_by_game" | ChatCheckReason | "chat_paused" | "not_allowed_to_chat";
1319
+
1320
+ /** What {@link Room.smoothedState} takes. */
1321
+ interface SmoothingOptions {
1322
+ /** How far behind the room's clock to draw, in milliseconds, from 0 to 1000. By default 100. */
1323
+ delayMs?: number;
1324
+ }
1325
+
1326
+ /** Why a match ended (the "ended" event). More may come within v1: treat one you don't know like "game". */
1327
+ type EndReason = "game" | "no_players" | "hosting_paused" | "kept_room_deleted" | "game_unavailable" | "game_server_stopped";
1328
+
1329
+ /** The match ended (the room's "ended" event). */
1330
+ interface Ended {
1331
+ /** What the game server said with room.end, or Zaisma's words when it ended the match; "" when none. */
1332
+ message: string;
1333
+ /**
1334
+ * Why: "game" (the game server's room.end), "no_players" (a spectator's room emptied of players),
1335
+ * "hosting_paused" (the game's hosting paused for the month), "kept_room_deleted" (the developer deleted
1336
+ * the kept room), "game_unavailable" (the game was taken down or made private) or "game_server_stopped"
1337
+ * (the game server failed and couldn't go on).
1338
+ */
1339
+ endReason: EndReason;
1340
+ }
1341
+
1342
+ /** Zaisma will end the match (the room's "ending" event). */
1343
+ interface Ending {
1344
+ /** When, an ISO time, also room.endsAt. */
1345
+ endsAt: string;
1346
+ }
1347
+
1348
+ /** This room object moved (the room's "moved" event). */
1349
+ interface Moved {
1350
+ /** The room it's in now. */
1351
+ roomCode: string;
1352
+ /** The room it left. */
1353
+ fromRoomCode: string;
1354
+ }
1355
+
1356
+ /** The "Still there?" question (the room's "idle" event). */
1357
+ interface Idle {
1358
+ /** "asking": show the question. "answered": the player gave input. "timed_out": no answer, and the room hears they walked away. */
1359
+ idleStatus: "asking" | "answered" | "timed_out";
1360
+ /** How long the player has to answer, in seconds: 30. */
1361
+ answerTimeoutSec: number;
1362
+ }
1363
+
1364
+ /** A match moving to a version published "now" (the room's "versionUpdate" event). */
1365
+ interface VersionUpdate {
1366
+ /** The version it moves to, like "version_k2v9q4x7m1p3w8r5t6y0". */
1367
+ versionId: string;
1368
+ /** When it switches at the latest, an ISO time. */
1369
+ appliesAt: string;
1370
+ /** The game's own files changed, so the page reloads the game at the new version; false: it only reconnects. */
1371
+ isReloading: boolean;
1372
+ }
1373
+
1374
+ /** A player, as the room shows them to everyone. */
1375
+ interface Player {
1376
+ /** Their player ID, like "player_8f3k2m". An account's is the same in every game; a guest's is temporary. */
1377
+ id: string;
1378
+ /** The name shown for them: the account's name, or a guest's like "Swift Otter 42". */
1379
+ name: string;
1380
+ /** A signed-in Zaisma account, or a guest. */
1381
+ kind: PlayerKind;
1382
+ }
1383
+
1384
+ /** A player in the room's list (room.players): who they are, and why they aren't playing. */
1385
+ interface ListedPlayer extends Player {
1386
+ /**
1387
+ * Why this player isn't playing: "idle" or "hidden" (the game server kept them, onAway), or "lost"
1388
+ * (their connection dropped and they have reconnectTimeoutSec to come back); null while they play.
1389
+ */
1390
+ awayReason: "idle" | "hidden" | "lost" | null;
1391
+ }
1392
+
1393
+ /** A signed-in Zaisma account, or a guest. More values may come; treat an unknown one as "guest". */
1394
+ type PlayerKind = "guest" | "account";
1395
+
1396
+ /** What anyone sees of an account on its profile, zaisma.com/@username ({@link player.getProfile}). */
1397
+ interface PlayerProfile {
1398
+ /** The account's player ID, the same in every game. */
1399
+ id: string;
1400
+ /** The name to show for them in games: their username, so nobody can pass as someone else. */
1401
+ name: string;
1402
+ /** Their username: unique ignoring case, 3 to 20 letters, numbers and underscores. It can change; the ID never does. */
1403
+ username: string;
1404
+ /** The name they chose to show beside @username on their profile, once checked; null when none. */
1405
+ displayName: string | null;
1406
+ /** What they wrote about themselves, once checked; null when none or when their profile is private. */
1407
+ bio: string | null;
1408
+ /** Links they chose to show (only adults can), up to 3; [] otherwise or when their profile is private. */
1409
+ linkUrls: string[];
1410
+ /** Their profile page. */
1411
+ profileUrl: string;
1412
+ /** True when they show their profile to friends only: strangers then see only the name, username and published games. */
1413
+ isProfilePrivate: boolean;
1414
+ /** Games they made that anyone can find, newest first. */
1415
+ publishedGames: ProfileGame[];
1416
+ /** Games they played lately, newest first, when they show those to anyone; [] otherwise. */
1417
+ playedGames: (ProfileGame & { playedPeriod: PlayedPeriod })[];
1418
+ /** The game they're playing right now, when they show that to anyone; null when they aren't, don't show it, appear offline, or the game isn't out yet. */
1419
+ playingNow: ProfileGame | null;
1420
+ }
1421
+
1422
+ /** A game on a profile. */
1423
+ interface ProfileGame {
1424
+ slug: string;
1425
+ title: string;
1426
+ /** The game's page on Zaisma. */
1427
+ pageUrl: string;
1428
+ }
1429
+
1430
+ /** Roughly when a player last played a game: a profile never shows a time. More values may come; treat an unknown one as "earlier". */
1431
+ type PlayedPeriod = "today" | "this_week" | "this_month" | "earlier";
1432
+
1433
+ /** This tab's player and their player pass (Zaisma.player.getPass()). */
1434
+ interface PlayerWithPass extends Player {
1435
+ /**
1436
+ * The player pass: a standard EdDSA JWT (header typ "pass+jwt") any server can check against the
1437
+ * keys at https://rooms.zaisma.com/.well-known/jwks.json. It proves only who the player is: its sub
1438
+ * is the player's ID, kind says guest or account, aud is the game, pass_version_number is 1. A
1439
+ * guest's pass also carries their name; an account's name comes from its profile
1440
+ * (https://rooms.zaisma.com/v1/players/<id>). An account's pass says who it can chat with:
1441
+ * who_can_chat (nobody, friends or anyone, the player's own setting) and, when anyone, an opaque
1442
+ * chat_group_id (chat_group_…) that differs in every game. Zaisma never tells a game anyone's age; a
1443
+ * game could guess which chat group is the younger one from its size. It carries its friends and
1444
+ * blocks, never as a list of players anyone can read: player_tag_id, its own opaque tag in this game
1445
+ * (player_tag_…), friend_player_tag_ids, its friends' tags (up to 200), and block_player_tag_ids, the
1446
+ * tags of the accounts it has a block with, either way (up to 100). Your own server checks it with
1447
+ * verifyPass, then two players with passesAreFriends(first, second), canChat(first, second) before
1448
+ * passing chat between them, and passesHaveBlockBetween(first, second) before matching them
1449
+ * together, all from @zaisma/game-server; guests never chat and are never anyone's friend.
1450
+ */
1451
+ pass: string;
1452
+ /** When the pass runs out, as an ISO time in UTC. */
1453
+ expiresAt: string;
1454
+ }
1455
+
1456
+ /**
1457
+ * Where a room's socket stands: "connecting" (joining, or rejoining after back.rejoin()), "open",
1458
+ * "reconnecting" (it dropped and the SDK is connecting again) or "closed" (for good, or while the player
1459
+ * is away with their seat gone or their connection dropped, until they're back). Whether the player is
1460
+ * in the match is {@link Room.awayReason}, not this.
1461
+ */
1462
+ type ConnectionStatus = "connecting" | "open" | "reconnecting" | "closed";
1463
+
1464
+ /** Whether a room shows in room lists, takes new players and is offered by Zaisma's Join, and its name and labels. */
1465
+ interface RoomSettings {
1466
+ isListed: boolean;
1467
+ isOpen: boolean;
1468
+ friendJoinsEnabled: boolean;
1469
+ name: string | null;
1470
+ labels: Record<string, string>;
1471
+ }
1472
+
1473
+ /** The room's settings changed, or how many watch (the room's "settings" event). */
1474
+ interface RoomSettingsChange extends RoomSettings {
1475
+ spectatorCount: number;
1476
+ }
1477
+
1478
+ /** Why the player was removed from the room, and whether they're kept out for the rest of the match. */
1479
+ interface Kicked {
1480
+ /** Why, in words to show the player. */
1481
+ message: string;
1482
+ isKeptOut: boolean;
1483
+ }
1484
+
1485
+ /** The game server is sending the player to another room ({@link RoomEvents.move}). */
1486
+ interface Move {
1487
+ /** The room they're being sent to. */
1488
+ roomCode: string;
1489
+ /** Move this room object there: the old connection closes and the new one opens. */
1490
+ follow(): void;
1491
+ /** Join it as a new room object instead, on the same server as this one: leave this one first. */
1492
+ join(moveJoinOptions?: MoveJoinOptions): Promise<Room>;
1493
+ }
1494
+
1495
+ /** The player walked away ({@link RoomEvents.away}). */
1496
+ interface Away {
1497
+ /** "idle": no input and no answer to "Still there?". "hidden": the game was hidden for 2 minutes in a real-time room. */
1498
+ awayReason: "idle" | "hidden";
1499
+ /** How long the room holds their seat, in seconds. */
1500
+ seatHoldDurationSec: number;
1501
+ }
1502
+
1503
+ /** The player is back after walking away ({@link RoomEvents.back}). */
1504
+ interface Back {
1505
+ /** Their room. */
1506
+ roomCode: string;
1507
+ /** true: they're in their held seat again, and rejoin does nothing. false: the seat ran out, so call rejoin. */
1508
+ isSeatHeld: boolean;
1509
+ /** Rejoin their old room when the seat ran out. Fails if it's full or over. */
1510
+ rejoin(): void;
1511
+ }
1512
+
1513
+ /** An error the room sent. */
1514
+ interface RoomError {
1515
+ code: RoomErrorCode;
1516
+ /** What happened, in words. */
1517
+ message: string;
1518
+ }
1519
+
1520
+ /**
1521
+ * The codes a room's errors have, in its "error" event and when joining fails:
1522
+ * - room_full: the room has no space.
1523
+ * - room_closed: the room is closed to new players.
1524
+ * - ended: the match is over.
1525
+ * - kept_out: the player was removed for the rest of the match.
1526
+ * - refused: the game server's canJoin turned them away.
1527
+ * - hosting_paused: the game's hosting on Zaisma is paused this month (its free allowance, or its developer's budget or credit, is used); new rooms open again when the month resets (UTC) or its developer adds credit, and a running match takes no new players and ends within the hour.
1528
+ * - account_needed: the player is a guest, and the game needs a Zaisma account for multiplayer or the whole game.
1529
+ * - game_not_open_to_player: the account is younger than the game's minimum age (set on the dashboard); the game never learns its age.
1530
+ * - game_unavailable: the game can't be played right now.
1531
+ * - bad_pass: the player pass wasn't accepted (the SDK gets a new one and tries once more).
1532
+ * - bad_origin: the connection didn't come from the game's own page.
1533
+ * - too_big: a message over 16 KB, or joinData over 4 KB.
1534
+ * - too_fast: more messages than the room takes (about 30 a second).
1535
+ * - bad_message, no_hello, not_allowed: a message the room couldn't read or use, didn't expect, or doesn't allow from this player.
1536
+ * - busy: the room couldn't take the player right now; try again soon.
1537
+ * - room_not_running: the room isn't running, like one with nobody playing to watch, or one a move named that isn't there.
1538
+ * - blocked_player_here: quickJoin's first try at a room with someone the player blocked; quickJoin tries the others first, and you won't see it.
1539
+ * - other_version: the room runs another version of the game. On its Zaisma page the game reloads at the room's version and joins it, and you won't see it; you see it outside Zaisma, through a tester link, or when that version is no longer kept.
1540
+ * - spectators_full: the room takes no more spectators (its game server's maximumSpectatorCount, 0 when it takes none).
1541
+ * - already_playing: a player in the room tried to watch it too.
1542
+ * - spectator_cannot_send: a spectator tried to send to the room ({@link Room.send}, {@link Room.sendBytes}).
1543
+ * - send_queue_full: the SDK's own: more than 60 reliable sends waited while the room wasn't reachable, so the oldest were dropped. Send less while connectionStatus isn't open, or use latest_only for updates that replace each other.
1544
+ * - chat_not_allowed, chat_empty, chat_too_long, chat_too_fast: {@link Room.chat} refused (they reject its promise, not the "error" event); chat_not_here: the room's server doesn't take chat.
1545
+ *
1546
+ * More codes may come within v1: handle one you don't know like an error you can show (its message).
1547
+ */
1548
+ type RoomErrorCode =
1549
+ | "room_full"
1550
+ | "room_closed"
1551
+ | "ended"
1552
+ | "kept_out"
1553
+ | "refused"
1554
+ | "hosting_paused"
1555
+ | "account_needed"
1556
+ | "game_not_open_to_player"
1557
+ | "game_unavailable"
1558
+ | "bad_pass"
1559
+ | "bad_origin"
1560
+ | "too_big"
1561
+ | "too_fast"
1562
+ | "bad_message"
1563
+ | "no_hello"
1564
+ | "not_allowed"
1565
+ | "busy"
1566
+ | "room_not_running"
1567
+ | "blocked_player_here"
1568
+ | "other_version"
1569
+ | "spectators_full"
1570
+ | "already_playing"
1571
+ | "spectator_cannot_send"
1572
+ | "send_queue_full"
1573
+ | "chat_not_here"
1574
+ | "chat_not_allowed"
1575
+ | "chat_empty"
1576
+ | "chat_too_long"
1577
+ | "chat_too_fast";
1578
+
1579
+ /**
1580
+ * The codes a request's errors have ({@link Server.request}), from Zaisma or the Node runner alike:
1581
+ * - bad_request: not plain JSON with the player's pass.
1582
+ * - too_big: a request over 16 KB.
1583
+ * - bad_pass: the player pass wasn't accepted (the SDK gets a new one and tries once more).
1584
+ * - account_needed: the player is a guest, and the game needs a Zaisma account for multiplayer or the whole game.
1585
+ * - game_not_open_to_player: the account is younger than the game's minimum age (set on the dashboard); the game never learns its age.
1586
+ * - game_unavailable: the game can't be played right now.
1587
+ * - no_request_handler: the game's server has no onRequest.
1588
+ * - game_server_error: onRequest threw, answered something that isn't plain JSON or over 1 MB, or took over 10 seconds.
1589
+ * - hosting_paused: the game's hosting on Zaisma is paused this month; requests work again when the month resets (UTC) or its developer adds credit.
1590
+ * - slow_down: too many requests from this address; try again in a minute.
1591
+ * - bad_origin: the request didn't come from the game's own page.
1592
+ * - busy: Zaisma couldn't answer right now; try again soon.
1593
+ */
1594
+ type RequestErrorCode =
1595
+ | "bad_request"
1596
+ | "too_big"
1597
+ | "bad_pass"
1598
+ | "account_needed"
1599
+ | "game_not_open_to_player"
1600
+ | "game_unavailable"
1601
+ | "no_request_handler"
1602
+ | "game_server_error"
1603
+ | "hosting_paused"
1604
+ | "slow_down"
1605
+ | "bad_origin"
1606
+ | "busy";
1607
+
1608
+ /**
1609
+ * The codes the SDK's errors have: a room's ({@link RoomErrorCode}), and:
1610
+ * - unchecked: no player pass, because the game isn't played on its zaisma.com page or Zaisma couldn't check the browser.
1611
+ * - unknown_option, unknown_event: a call the SDK doesn't take; the message lists what it does.
1612
+ * - slow_down: too many calls; try again in a minute.
1613
+ * - zaisma_rooms_not_used: the game doesn't use Zaisma rooms.
1614
+ * - bad_report: a report whose reason isn't a {@link ReportReason}, or whose player ID isn't one.
1615
+ * - bad_origin: the call didn't come from the game's own page.
1616
+ * - not_on_zaisma: the game isn't hosted on Zaisma, so it has no player passes, sign-in or room lists, and joinRoom needs a serverUrl.
1617
+ * - sign_in_declined: the player chose Not now when the game asked them to sign in ({@link player.signIn}) or showed the "Keep your progress" card ({@link player.offerAccount}).
1618
+ * - account_offer_too_soon: the "Keep your progress" card already showed this visit, or the player chose Not now in the last 3 days ({@link player.offerAccount}).
1619
+ * - cloud_saves_off: the game has cloud saves off, so it has no saves on players' accounts ({@link saves}).
1620
+ * - cloud_saves_not_here: cloud saves don't work where the game is played: a tester link, or a Zaisma without accounts. Save in the browser as usual.
1621
+ * - bad_save_copy_id: not a save copy ID; they come from {@link Saves.listCopies}, like "save_copy_k2v9q4x7m1p3w8r5t6y0".
1622
+ * - no_save_copy: the player has no earlier version with that ID in this game.
1623
+ * - bad_room_code: not a room code, which is six characters like "K7Q2MX", from 2 to 9 and A to Z without I or O.
1624
+ * - bad_server_url: a serverUrl that isn't a full wss://, ws://, https:// or http:// address ({@link Friends.setRoom} takes only wss:// and https://).
1625
+ * - unlisted_server: {@link Friends.setRoom} named a server that isn't one of the game's own servers (own_server_urls).
1626
+ * - connection_closed: the connection to the room closed before it let the player in; the message says why.
1627
+ * - bad_bytes: {@link Room.sendBytes} was given something other than an ArrayBuffer or a typed array.
1628
+ * - bad_send_option: a send's delivery or latestKey isn't one it takes; latest_only needs a latestKey.
1629
+ * - bad_delay: {@link Room.smoothedState}'s delayMs isn't from 0 to 1000.
1630
+ * - bad_region: a region that isn't a continent code ({@link Region}).
1631
+ * - bad_idle_timeout: an idleTimeoutSec that isn't from 30 to 86400.
1632
+ * - request_failed: Zaisma didn't answer (no connection, or a problem on Zaisma's side); try again.
1633
+ * - no_game: {@link Items.buy} named a game that isn't open to play here.
1634
+ * - chat_stopped, chat_not_allowed, chat_empty, chat_too_long, chat_too_fast, chat_not_here: {@link Room.chat} didn't send ({@link ChatErrorCode}).
1635
+ * - And a request's ({@link RequestErrorCode}).
1636
+ *
1637
+ * More values may come; treat an unknown one as "request_failed".
1638
+ */
1639
+ type ErrorCode =
1640
+ | RoomErrorCode
1641
+ | RequestErrorCode
1642
+ | "chat_stopped"
1643
+ | "chat_not_allowed"
1644
+ | "chat_empty"
1645
+ | "chat_too_long"
1646
+ | "chat_too_fast"
1647
+ | "chat_not_here"
1648
+ | "unchecked"
1649
+ | "unknown_option"
1650
+ | "unknown_event"
1651
+ | "slow_down"
1652
+ | "zaisma_rooms_not_used"
1653
+ | "bad_report"
1654
+ | "bad_origin"
1655
+ | "not_on_zaisma"
1656
+ | "sign_in_declined"
1657
+ | "account_offer_too_soon"
1658
+ | "cloud_saves_off"
1659
+ | "cloud_saves_not_here"
1660
+ | "bad_save_copy_id"
1661
+ | "no_save_copy"
1662
+ | "no_player"
1663
+ | "bad_player_id"
1664
+ | "bad_room_code"
1665
+ | "bad_bytes"
1666
+ | "bad_send_option"
1667
+ | "bad_delay"
1668
+ | "bad_region"
1669
+ | "bad_idle_timeout"
1670
+ | "bad_server_url"
1671
+ | "unlisted_server"
1672
+ | "unknown_link"
1673
+ | "bad_link_path"
1674
+ | "bad_item_name"
1675
+ | "bad_purchase_id"
1676
+ | "no_purchase"
1677
+ | "already_handled"
1678
+ | "handled_by_server"
1679
+ | "bad_link_query"
1680
+ | "bad_own_site_url"
1681
+ | "needs_player_tap"
1682
+ | "no_game"
1683
+ | "no_item"
1684
+ | "not_for_sale"
1685
+ | "stripe_not_connected"
1686
+ | "stripe_cannot_take_payments"
1687
+ | "bad_item_count"
1688
+ | "bad_game_account_id"
1689
+ | "connection_closed"
1690
+ | "request_failed";
1691
+
1692
+ /**
1693
+ * An error from the SDK: its code says what went wrong, for the game to branch on, and its message
1694
+ * says it in one sentence, with what to fix.
1695
+ *
1696
+ * @example
1697
+ * try { room = await Zaisma.joinRoom(); }
1698
+ * catch (error) {
1699
+ * switch (error.code) {
1700
+ * case "room_full": showRoomFull(); break;
1701
+ * default: showError(error.message); // codes only grow within v1, so keep a default
1702
+ * }
1703
+ * }
1704
+ */
1705
+ interface SdkError extends Error {
1706
+ code: ErrorCode;
1707
+ }
1708
+
1709
+ /**
1710
+ * What {@link invite} did: "opened" Zaisma's Share on a Zaisma page, or "failed" when the page couldn't
1711
+ * open it; elsewhere "shared" from the share sheet, "copied" the link, "cancelled" the share sheet, or
1712
+ * "failed" to do either. More values may come; treat an unknown one as "failed".
1713
+ */
1714
+ type InviteStatus = "opened" | "shared" | "copied" | "cancelled" | "failed";
1715
+
1716
+ /** What {@link checkChat} found. */
1717
+ interface ChatCheck {
1718
+ /** Whether it can be sent. */
1719
+ isAllowed: boolean;
1720
+ /** Why not, null when it's allowed ({@link ChatCheckReason}). */
1721
+ reason: ChatCheckReason | null;
1722
+ /** The words to show the sender when it's stopped (never about age or the other player); null when it's allowed. */
1723
+ message: string | null;
1724
+ /** Whether it swears. Zaisma allows swearing; block it yourself if your game doesn't. */
1725
+ hasSwearing: boolean;
1726
+ /** Whether it has a slur. Zaisma allows it too; your game and its moderators decide. */
1727
+ hasSlur: boolean;
1728
+ /** The text tidied up: hidden characters and extra spaces gone, line breaks kept (empty when it's too_long). */
1729
+ text: string;
1730
+ }
1731
+
1732
+ /**
1733
+ * Why {@link checkChat} stopped a message: "contact_details" (a link, handle, email, phone number or
1734
+ * contact app in any disguise, or moving to another app or meeting up), "personal_details" (where
1735
+ * someone lives, their school, their full name, photos), "sexual" or "too_long" (over 2000 characters).
1736
+ * "slur" and "unchecked" are v1's and no longer given: slurs are flagged (hasSlur), and what the AI can't
1737
+ * look at goes after the rules. The game server's ChatCheckReason has two more, "chat_paused" and
1738
+ * "not_allowed_to_chat", which only a room can give. More values may come; treat an unknown one as a
1739
+ * stop: show message.
1740
+ */
1741
+ type ChatCheckReason = "contact_details" | "personal_details" | "slur" | "sexual" | "unchecked" | "too_long";
1742
+
1743
+ /** What happened, in a player report. Zaisma looks at real_life_threat (a threat that reaches past the game: their real details, finding them, a plan), personal_details (sharing someone's), following_after_block and sexual (behavior); harassment, cheating and other go to the game's developer. */
1744
+ type ReportReason = "real_life_threat" | "personal_details" | "following_after_block" | "sexual" | "harassment" | "cheating" | "other";
1745
+
1746
+ /** Zaisma's answer to a report. */
1747
+ interface ReportAnswer {
1748
+ isReported: true;
1749
+ }
1750
+
1751
+ /** The game's listed rooms ({@link Rooms.list}). */
1752
+ interface RoomList {
1753
+ /** Rooms with space first (the player's country, then region, then the busiest), then full and closed ones. Up to 50, in the region asked for. */
1754
+ rooms: ListedRoom[];
1755
+ /** Everyone playing in the game's rooms everywhere, unlisted rooms too (for "playing now"). */
1756
+ playingPlayerCount: number;
1757
+ /** The game's rooms with someone playing, everywhere, unlisted ones too. */
1758
+ playingRoomCount: number;
1759
+ /** Listed rooms that are closed to new players, everywhere. */
1760
+ listedClosedRoomCount: number;
1761
+ /** Listed rooms a player could join right now, everywhere. */
1762
+ listedJoinableRoomCount: number;
1763
+ /** Listed rooms taking players, full ones too, in the region and with the labels asked for: the ones listed plus the ones past them (moreOpenRoomCount). */
1764
+ matchingOpenRoomCount: number;
1765
+ /** The regions with listed rooms, busiest first, for a region filter, like { region: "EU", regionName: "Europe", roomCount: 12, playerCount: 48 }. */
1766
+ regions: { region: Region; regionName: string; roomCount: number; playerCount: number }[];
1767
+ /** The region the rooms are filtered to, or null for everywhere. */
1768
+ region: Region | null;
1769
+ /** Open rooms in that region past the ones listed. */
1770
+ moreOpenRoomCount: number;
1771
+ /** Labels worth filtering by: ones several rooms have, with a handful of values. */
1772
+ filters: { label: string; values: string[] }[];
1773
+ }
1774
+
1775
+ /** A room in a room list. Never who's in it. */
1776
+ interface ListedRoom {
1777
+ roomCode: string;
1778
+ /** Players in the match, not counting players who walked away. */
1779
+ playerCount: number;
1780
+ /** Seats held for players coming back or on their way: a room has space while playerCount + heldSeatCount < maximumPlayerCount. */
1781
+ heldSeatCount: number;
1782
+ maximumPlayerCount: number;
1783
+ /** Spectators watching the room. */
1784
+ spectatorCount: number;
1785
+ /** Spectators the room takes (its game server's maximumSpectatorCount): 0 when nobody can watch. Room to watch while spectatorCount < maximumSpectatorCount, full or closed rooms too. */
1786
+ maximumSpectatorCount: number;
1787
+ /** false: closed, so only players who were in it can come back. */
1788
+ isOpen: boolean;
1789
+ /**
1790
+ * Where the room runs: the country its first player connected from, and its region (a continent),
1791
+ * like { countryCode: "DE", countryName: "Germany", region: "EU" }; null when unknown.
1792
+ */
1793
+ location: { countryCode: string; countryName: string; region: Region | null } | null;
1794
+ /** In the same region as the player asking. */
1795
+ isNear: boolean;
1796
+ /** The room's name, or null. */
1797
+ name: string | null;
1798
+ /** The room's labels. */
1799
+ labels: Record<string, string>;
1800
+ /** Kept by the game server (room.setKept): a world that stays listed with nobody in it, like a server waiting for players. */
1801
+ isKept: boolean;
1802
+ /** The room's link on Zaisma, to join it or pass it on. */
1803
+ inviteUrl: string;
1804
+ }
1805
+ }
1806
+
1807
+ interface Window {
1808
+ /** The Zaisma SDK: see the Zaisma namespace. */
1809
+ Zaisma: typeof Zaisma;
1810
+ }