Seats are banked in a cookie the server sets, and a lost seat comes back by name to the address it was last used from
A seat lived in the browser's storage alone, and Safari clears that after a week away, or at a word from the person on another device. Now every seat taken is banked as well in an HttpOnly cookie the server writes, which such clearing leaves alone, and the hall takes its seats back from the bank on arrival. For a browser that lost even the cookie, the ledger notes the address each seat was last used from, as a salted hash, at most once in ten minutes, and a name given in the hall or at the duel gets its seats back from the same address, with a new token; where the address differs, two people at the duel share it, or the host gave the seat to a bot, the duel is only counted, and the host's phrase or the keeper's link is the way back. The keeper's script finds a name across duels. The seat endpoints answer only this site's own pages, under a limit of their own. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CCLa2PaEeCj8njQZSWGqHx
This commit is contained in:
1 parent
b8f65a74d3
commit
29a68f4d9d
8 files changed
+443
-13
No files matched your search
@@ -3,7 +3,7 @@
|
||||
import TallyBoard from './Tally.svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { loadFamily, type FamilyGame } from '$lib/family';
|
||||
import { allSeats, api, doubtSeat, forgetSeat, rememberSeat, ServerError, trustSeat, type Report, type Tally } from '$lib/game/client';
|
||||
import { restoreSeats, allSeats, api, doubtSeat, forgetSeat, rememberSeat, ServerError, trustSeat, type Report, type Tally } from '$lib/game/client';
|
||||
import { Duel, duel, NAME_MAX, playerName, rememberName } from '$lib/game/duel.svelte';
|
||||
import { unreadTalk } from '$lib/game/talk';
|
||||
import type { RoomView } from '$lib/game/view';
|
||||
@@ -13,6 +13,14 @@
|
||||
let busy = $state(false);
|
||||
let error = $state('');
|
||||
let seats = $state(allSeats());
|
||||
// The bank is asked once on arrival: a browser whose storage was cleared has its seats again before it misses them.
|
||||
$effect(() => {
|
||||
void restoreSeats()
|
||||
.then((found) => {
|
||||
if (found) seats = allSeats();
|
||||
})
|
||||
.catch(() => {});
|
||||
});
|
||||
let games = $state<Record<string, RoomView | null>>({});
|
||||
let aboutOpen = $state(false);
|
||||
$effect(() => {
|
||||
@@ -62,6 +70,30 @@
|
||||
let answers = $state<Record<string, string>>({});
|
||||
let answerError = $state('');
|
||||
|
||||
let finding = $state(false);
|
||||
/** What a search for lost seats turned up, in words. */
|
||||
let found = $state('');
|
||||
/** Seats under this name at duels still in play: those last used from here are taken back; the rest are named, to ask the host. */
|
||||
async function findSeats() {
|
||||
finding = true;
|
||||
error = '';
|
||||
found = '';
|
||||
try {
|
||||
rememberName(name);
|
||||
const r = await api.findSeats(name.trim());
|
||||
for (const s of r.seated) rememberSeat(s.roomId, { seat: s.seat, token: s.token });
|
||||
if (r.seated.length) seats = allSeats();
|
||||
const back = r.seated.length ? `Seated again at ${r.seated.map((s) => s.roomId).join(', ')}.` : '';
|
||||
const n = r.elsewhere;
|
||||
const ask = n ? ` ${n === 1 ? 'One more table' : `${n} more duels`} in play ${n === 1 ? 'has' : 'have'} a seat under your name, last used from another address, or given to a bot by the host: ask the host there for a transfer phrase, or write to the keeper.` : '';
|
||||
found = back + ask || 'No duel still in play has a seat under that name.';
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'The search did not go through.';
|
||||
} finally {
|
||||
finding = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch the reports for every held seat; re-runs when the held seats change.
|
||||
$effect(() => {
|
||||
const held = seats;
|
||||
@@ -249,6 +281,8 @@
|
||||
<input type="text" bind:value={phrase} placeholder="the four-word phrase" autocomplete="off" aria-label="transfer phrase" />
|
||||
<button type="submit" class="quiet" disabled={claiming || phrase.trim().split(/[\s-]+/).length < 4}>Claim</button>
|
||||
</form>
|
||||
<p class="muted small lost">Lost your duels? If this browser forgot them, <button type="button" class="link" disabled={!ready || finding} onclick={findSeats}>find the seats under your name</button>.</p>
|
||||
{#if found}<p class="muted small">{found}</p>{/if}
|
||||
{#if error}<p class="warning">{error}</p>{/if}
|
||||
</div>
|
||||
|
||||
|
||||
Reference in new issue
Block a user