tickcli-js 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.0 (unreleased)
3
+ ## 0.1.1
4
+
5
+ A "Leave" button in the top bar of multiplayer games: out of the room at any time (mid-match with others, a second tap within 3 s confirms) into a new private room of your own, and the seat is freed at once. With `Game({ exit: "/" })` or `<meta name="tickcli-exit" content="/">` it reads "Exit" and goes to that URL. Fix: after leaving, a page opened from an invite link (`#room=CODE`) no longer goes back to that room.
6
+
7
+ ## 0.1.0
4
8
 
5
9
  Games hosted anywhere: `tickcli-js/remote` (`createRemote`, `createRemoteServer`, `originAllowed`). A deployment is a public id with the sites allowed to use it; the page pastes one tag (`/c/<id>.js`: relay, room.js and game.js set up for it), the server checks the page's Origin at the socket upgrade: https sites, http only for localhost when listed (`http://localhost:*`), one kind of deployment. Guests keep a random 128-bit key in the page's storage, sent as the socket's first message (no cookie). Each deployment is its own namespace with optional caps (rooms, players, KB a second) and `revoke(id)`. Hub: `roomsPerScope` option and `scopeStats(scope)`. A refused page shows the server's reason (game.js). e2e `remote.ts` (two browsers on another site).
6
10
 
package/client/game.js CHANGED
@@ -9,6 +9,8 @@
9
9
  aspect: 1, // board width / height (1 = square)
10
10
  dpad: "auto", // "auto" (touch screens only) | "always" | "never"
11
11
  step: 1 / 60, // seconds per update call
12
+ exit: "/", // multiplayer: an "Exit" button in the top bar leaves the room and goes to this URL (also <meta name="tickcli-exit"
13
+ // content="/"> in the page). Without it the button reads "Leave": out of this room into a new private one (lobby)
12
14
  scores: false, // true = at game over, hand the score to window.gameScores (a high-score board the host app provides, if any)
13
15
  scoresBetter: "high", // "low" when a smaller score wins (a time)
14
16
  setup(g) { g.score = 0; ... }, // start or restart: reset ALL state here (multiplayer: the host may also set starting g.shared values here)
@@ -98,7 +100,7 @@
98
100
  // A box that scrolls or clips starts its own touch-action, so the overlays and the lobby say again that a quick double tap never zooms.
99
101
  ".tg-ov,.tg-lob{touch-action:pan-y}.tg button{touch-action:manipulation}.tg-pad button,.tg-acts button{touch-action:none}.tg input{user-select:text;-webkit-user-select:text;-webkit-touch-callout:default}" +
100
102
  ".tg-bar{flex:none;display:flex;align-items:center;gap:12px;padding:8px 12px;font-size:15px;border-bottom:1px solid rgba(148,163,184,.25)}" +
101
- ".tg-bar b{font-size:17px;margin-right:auto}.tg-bar span{opacity:.8;font-variant-numeric:tabular-nums}.tg-bar button{min-height:36px;min-width:36px;border-radius:10px;border:1px solid rgba(148,163,184,.35);background:transparent;color:inherit;font:inherit;cursor:pointer}" +
103
+ ".tg-bar b{font-size:17px;margin-right:auto}.tg-bar span{opacity:.8;font-variant-numeric:tabular-nums}.tg-bar button.lv{padding:0 12px}.tg-bar button{min-height:36px;min-width:36px;border-radius:10px;border:1px solid rgba(148,163,184,.35);background:transparent;color:inherit;font:inherit;cursor:pointer}" +
102
104
  ".tg-area{flex:1 1 auto;min-height:0;display:flex;align-items:center;justify-content:center;padding:8px}" +
103
105
  ".tg-board{position:relative;display:block;container-type:size}.tg-board canvas{display:block;width:100%;height:100%;border-radius:10px;background:rgba(15,23,42,.35)}" +
104
106
  ".tg-ov{position:absolute;inset:0;display:none;flex-direction:column;align-items:center;gap:clamp(4px,2.5cqmin,10px);text-align:center;padding:clamp(6px,4cqmin,16px);border-radius:10px;background:rgba(8,12,24,.78);color:#f1f5f9;overflow:hidden}" +
@@ -305,6 +307,25 @@
305
307
  var xBtn = document.createElement("button"); xBtn.className = "x"; xBtn.title = "End game"; xBtn.setAttribute("aria-label", "End game"); xBtn.textContent = "End"; xBtn.hidden = true;
306
308
  pBtn.parentNode.insertBefore(xBtn, pBtn);
307
309
  var xArm = 0;
310
+ // Leave / Exit: out of this room at any time. With an exit URL (Game({exit}) or <meta name="tickcli-exit">) it goes there; without one the
311
+ // player lands in a new private room of their own (the lobby: other open rooms, join with a code, play alone). Mid-match with others, a second
312
+ // tap within 3 s confirms, so a stray tap does not drop someone out of a running game.
313
+ var exitMeta = document.querySelector('meta[name="tickcli-exit"]');
314
+ var EXIT = typeof o.exit === "string" && o.exit ? o.exit : exitMeta && exitMeta.content ? exitMeta.content : "";
315
+ var lBtn = document.createElement("button"); lBtn.className = "lv"; var lWord = EXIT ? "Exit" : "Leave";
316
+ lBtn.textContent = lWord; lBtn.title = EXIT ? "Leave this room and exit" : "Leave this room"; lBtn.setAttribute("aria-label", lBtn.title); lBtn.hidden = !R;
317
+ pBtn.parentNode.insertBefore(lBtn, pBtn);
318
+ var lArm = 0;
319
+ lBtn.addEventListener("click", function () {
320
+ var busy = g.state === "playing" && g.players.length > 1;
321
+ if (busy && Date.now() - lArm > 3000) { lArm = Date.now(); lBtn.textContent = lWord + "?"; setTimeout(function () { if (Date.now() - lArm >= 3000) lBtn.textContent = lWord; }, 3100); return; }
322
+ lArm = 0; lBtn.textContent = lWord;
323
+ if (m.solo && !EXIT) return;
324
+ if (EXIT) { try { if (S.code) R.leave(); } catch (e) { /* closed already */ } location.href = EXIT; return; }
325
+ if (S.code) R.leave(); // free the seat at once (switching rooms alone would hold it for seatHoldMs)
326
+ try { if (/room=/.test(location.hash)) history.replaceState(null, "", location.pathname + location.search); } catch (e) { /* sandboxed */ }
327
+ join({ priv: true });
328
+ });
308
329
  xBtn.addEventListener("click", function () {
309
330
  if (Date.now() - xArm > 3000) { xArm = Date.now(); xBtn.textContent = "End game?"; setTimeout(function () { if (Date.now() - xArm >= 3000) xBtn.textContent = "End"; }, 3100); return; }
310
331
  xArm = 0; xBtn.textContent = "End"; if (g.state === "playing" && g.isHost) m.end();
package/client/relay.js CHANGED
@@ -135,7 +135,7 @@
135
135
  var text; try { text = JSON.stringify(Object.assign({}, d.m, { v: 1 })); } catch (x) { return; }
136
136
  if (bytes(text) > MAX_BYTES) toGame({ k: "msg", m: { t: "error", code: "too_big" } }); // the server would refuse it: say so, do not drop it silently
137
137
  else if (ws && ws.readyState === 1) ws.send(text);
138
- if (d.m.t === "leave") { join = null; closeSocket(); }
138
+ if (d.m.t === "leave") { join = null; code0 = null; closeSocket(); } // left on purpose: the page's #room= code no longer applies
139
139
  if (d.m.t === "name" && o.onName && typeof d.m.name === "string") try { o.onName(d.m.name); } catch (x) { /* storage blocked */ }
140
140
  } else if (d.k === "invite-anyone" && typeof o.inviteAnyone === "function") {
141
141
  try { o.inviteAnyone(); } catch (x) { /* the host app's own problem */ } // the app shows its own way to let anyone in (a public link)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tickcli-js",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Live rooms for browser games: one player's browser runs the rules, a small Node hub keeps the shared state, membership and order. No game code on the server.",
5
5
  "license": "MIT",
6
6
  "repository": {