clone

a takuzu puzzle field for agents to explore tile by tile, with a referee and a replayable spectator view

main
mosaic / site / islands / Login.tsx
Login.tsx 2.7K
1// login / logout, right of "copy prompt". shoo.js (in the page head)2// runs the sign-in; after the callback it holds a signed id_token, which3// this island exchanges for the session cookie at /api/session. Logged in,4// the button reads "logout".5import { useEffect, useState } from "preact/hooks";67type ShooGlobal = {8 startSignIn(opts?: { returnTo?: string; requestPii?: boolean }): void;9 getIdentity(): { userId: string; token: string } | null;10 clearIdentity(): void;11};1213function shoo(): ShooGlobal | undefined {14 return (globalThis as unknown as { Shoo?: ShooGlobal }).Shoo;15}1617export default function Login(props: { returnTo?: string }) {18 const [me, setMe] = useState<19 { id: string; name: string | null } | null | undefined20 >(21 undefined,22 );23 useEffect(() => {24 (async () => {25 const r = await fetch("/api/me", { credentials: "same-origin" });26 if (r.ok) {27 setMe((await r.json()).account);28 return;29 }30 // just back from shoo: exchange the id_token for a cookie31 const id = shoo()?.getIdentity?.();32 if (id?.token) {33 const s = await fetch("/api/session", {34 method: "POST",35 headers: { "content-type": "application/json" },36 body: JSON.stringify({ id_token: id.token }),37 credentials: "same-origin",38 });39 if (s.ok) {40 setMe((await s.json()).account);41 // back from shoo: return to where the user was; a claim link42 // re-renders server side once the cookie exists43 if (location.pathname === "/shoo/callback") {44 location.replace(props.returnTo ?? "/");45 } else if (location.pathname === "/link") location.reload();46 return;47 }48 }49 setMe(null);50 })().catch(() => setMe(null));51 }, []);52 if (me === undefined) {53 return <span class="btn" style="visibility:hidden">login</span>;54 }55 if (me) {56 return (57 <button58 type="button"59 class="btn"60 title={me.name ? `signed in as ${me.name}` : "signed in"}61 onClick={async () => {62 await fetch("/api/logout", {63 method: "POST",64 credentials: "same-origin",65 });66 shoo()?.clearIdentity?.();67 setMe(null);68 if (location.pathname === "/link") location.reload();69 }}70 >71 logout72 </button>73 );74 }75 return (76 <button77 type="button"78 class="btn"79 onClick={() => {80 const s = shoo();81 if (!s) {82 alert("sign-in is not available (shoo.js did not load)");83 return;84 }85 s.startSignIn({86 returnTo: props.returnTo ?? location.pathname + location.search,87 });88 }}89 >90 login91 </button>92 );93}