-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(react-server): action redirect headers and context (#254)
- Loading branch information
Showing
18 changed files
with
299 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
40 changes: 40 additions & 0 deletions
40
packages/react-server/examples/basic/src/routes/test/session/_action.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
"use server"; | ||
|
||
import { getActionContext, redirect } from "@hiogawa/react-server/server"; | ||
import { tinyassert } from "@hiogawa/utils"; | ||
import { getSession, setSession } from "./utils"; | ||
|
||
export async function signin(formData: FormData) { | ||
// TODO: return error on invalid input | ||
const name = formData.get("name"); | ||
tinyassert(typeof name === "string"); | ||
|
||
throw redirect("/test/session", { | ||
headers: { | ||
"set-cookie": setSession({ name }), | ||
}, | ||
}); | ||
} | ||
|
||
export async function signout() { | ||
throw redirect("/test/session", { | ||
headers: { | ||
"set-cookie": setSession({}), | ||
}, | ||
}); | ||
} | ||
|
||
let counter = 0; | ||
|
||
export function getCounter() { | ||
return counter; | ||
} | ||
|
||
export async function incrementCounter(formData: FormData) { | ||
const ctx = getActionContext(formData); | ||
const session = getSession(ctx.request); | ||
if (!session?.name) { | ||
throw redirect("/test/session/signin"); | ||
} | ||
counter += Number(formData.get("delta")); | ||
} |
10 changes: 10 additions & 0 deletions
10
packages/react-server/examples/basic/src/routes/test/session/layout.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import type { LayoutProps } from "@hiogawa/react-server/server"; | ||
|
||
export default function Layout(props: LayoutProps) { | ||
return ( | ||
<div className="flex flex-col gap-2 p-2"> | ||
<h3 className="font-bold">Session Demo</h3> | ||
<div>{props.children}</div> | ||
</div> | ||
); | ||
} |
49 changes: 49 additions & 0 deletions
49
packages/react-server/examples/basic/src/routes/test/session/page.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { Link } from "@hiogawa/react-server/client"; | ||
import type { PageProps } from "@hiogawa/react-server/server"; | ||
import { getCounter, incrementCounter, signout } from "./_action"; | ||
import { getSession } from "./utils"; | ||
|
||
export default function Page(props: PageProps) { | ||
const session = getSession(props.request); | ||
return ( | ||
<div className="flex flex-col gap-4 p-3 max-w-sm"> | ||
<form className="flex items-center gap-2" action={incrementCounter}> | ||
<p>Counter: {getCounter()}</p> | ||
<button | ||
className="antd-btn antd-btn-default px-2" | ||
name="delta" | ||
value={-1} | ||
> | ||
-1 | ||
</button> | ||
<button | ||
className="antd-btn antd-btn-default px-2" | ||
name="delta" | ||
value={+1} | ||
> | ||
+1 | ||
</button> | ||
<span className="text-colorTextLabel text-sm">(signin required)</span> | ||
</form> | ||
{session?.name && ( | ||
<div className="flex items-center gap-3"> | ||
<p>Hello, {session.name}!</p> | ||
<form action={signout}> | ||
<button className="antd-btn antd-btn-default px-2">Signout</button> | ||
</form> | ||
</div> | ||
)} | ||
{!session?.name && ( | ||
<div className="flex items-center gap-3 "> | ||
<p>Hi, anonymous user!</p> | ||
<Link | ||
className="antd-btn antd-btn-default px-2" | ||
href="/test/session/signin" | ||
> | ||
Signin | ||
</Link> | ||
</div> | ||
)} | ||
</div> | ||
); | ||
} |
22 changes: 22 additions & 0 deletions
22
packages/react-server/examples/basic/src/routes/test/session/signin/page.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { type PageProps, redirect } from "@hiogawa/react-server/server"; | ||
import { signin } from "../_action"; | ||
import { getSession } from "../utils"; | ||
|
||
export default function Page(props: PageProps) { | ||
const session = getSession(props.request); | ||
if (session?.name) { | ||
throw redirect("/test/session"); | ||
} | ||
|
||
return ( | ||
<form className="flex flex-col gap-2 p-2 max-w-sm" action={signin}> | ||
<input | ||
className="antd-input px-2" | ||
name="name" | ||
placeholder="Input name..." | ||
required | ||
/> | ||
<button className="antd-btn antd-btn-primary">Signin</button> | ||
</form> | ||
); | ||
} |
39 changes: 39 additions & 0 deletions
39
packages/react-server/examples/basic/src/routes/test/session/utils.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { objectHas, tinyassert } from "@hiogawa/utils"; | ||
import * as cookieLib from "cookie"; | ||
|
||
// mini cookie session utils | ||
|
||
type SessionData = { | ||
name?: string; | ||
}; | ||
|
||
const SESSION_KEY = "__session"; | ||
|
||
export function getSession(request: Request): SessionData | undefined { | ||
const raw = request.headers.get("cookie"); | ||
if (raw) { | ||
const parsed = cookieLib.parse(raw); | ||
const token = parsed[SESSION_KEY]; | ||
if (token) { | ||
try { | ||
const data = JSON.parse(decodeURIComponent(token)); | ||
tinyassert(objectHas(data, "name")); | ||
tinyassert(typeof data.name === "string"); | ||
return { name: data.name }; | ||
} catch (e) {} | ||
} | ||
} | ||
} | ||
|
||
export function setSession(data: SessionData) { | ||
const token = encodeURIComponent(JSON.stringify(data)); | ||
const cookie = cookieLib.serialize(SESSION_KEY, token, { | ||
httpOnly: true, | ||
secure: true, | ||
sameSite: "lax", | ||
path: "/", | ||
maxAge: 14 * 24 * 60 * 60, // two week | ||
}); | ||
tinyassert(cookie.length < 2 ** 12, "too large cookie session"); | ||
return cookie; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.