筆記
如何對 remix cookie 加上一點 TypeScript
根據 remix 官方的Cookie 範例,透過 createCookie 建立 cookieHelper 後,執行 cookieHelper.parse() 得到的回傳內容,其型別會是 any。但我想要在設定 request headers Set-Cookie 和取得 response headers Cookie 時借助一點 TypeScript 的力量,於是有了以下兩個功能 get 和 setAndSerialize:
import { createCookie } from "@remix-run/node";
export const cookieHelper = createCookie("remixCookieJar");
const validKeys = ["bravoToken", "superToken"] as const;
type CookieKey = (typeof validKeys)[number];
export const get = async (key: CookieKey, cookies: string | null) => { const jar = await cookieHelper.parse(cookies); if (!jar) return ""; return (jar[key] as string) || "";};
export const setAndSerialize = async ( key: CookieKey, value: string, cookies: string | null,) => { const jar = await cookieHelper.parse(cookies); if (!jar) return ""; jar[key] = value; return await cookieHelper.serialize(jar);};說明:
get: 目的是確保使用者從 cookie 取值時不會打錯字,比如目前只開放取用陣列const validKeys = ['bravoToken', 'superToken'] as const;提到的兩種鍵setAndSerialize: 目的是確保使用者不會在對 cookie 賦值時打錯字,並順便回傳序列化後的資料
操作示範如下。首先在 remix action 使用 setAndSerialize 對 cookie 設定登入後回傳的 auth token,並透過 Set-Cookie 加進 response header 中:
import type { ActionFunctionArgs } from "@remix-run/node";import { redirect } from "@remix-run/node";import { Form } from "@remix-run/react";import { setAndSerialize } from "my-remix-app/app/cookies.server.ts";
export default function LoginPage() { return ( <Form method="post"> <button type="submit">Submit</button> </Form> );}
export async function action({ request }: ActionFunctionArgs) { // do some login work... const authToken = "..."; // assign auth token to cookie const headers = { "Set-Cookie": await setAndSerialize( "superToken", authToken, request.headers.get("Cookie"), ), }; return redirect("/result", { headers });}而當我需要取得 auth token 時,就在 remix loader 使用 get 來取得 cookie 中的對應內容,並回傳的資料型別必定為字串,不需要再進行額外的型別驗證。可參考下方範例:
import type { LoaderFunctionArgs } from "@remix-run/node";import { json } from "@remix-run/node";import { useLoaderData } from "@remix-run/react";import { get } from "my-remix-app/app/cookies.server.ts";
export async function loader({ request }: LoaderFunctionArgs) { const superToken = await get("superToken", request.headers.get("Cookie")); return json({ superToken });}
export default function ResultPage() { const { superToken } = useLoaderData<typeof loader>(); return <p>{superToken ? superToken : "no super token"}</p>;}省事 🌚 害怕打錯字就是督促我多寫點抽象層的原動力。
如何在 remix 中操作第三方 cookie
最近在弄的專案會需要和同 domain 的不同產品共用 cookie,非 remix 建立的 cookie 就沒辦法透過 createCookie 的 .get() / .set() 操作了。需要另覓他法。而考量到我需要在 remix loader 內(後端)處理 cookie,故選擇使用支援 Node.Js 環境的 cookie 套件。包一層抽象的程式碼如下:
import cookie from "cookie";
export const get3rdPartyCookie = async ( key: string, cookies: string | null,) => { const jar = cookie.parse(cookies || ""); return jar[key] || "";};實際使用範例如下:
import type { LoaderFunctionArgs } from "@remix-run/node";import { json } from "@remix-run/node";import { useLoaderData } from "@remix-run/react";import { get3rdPartyCookie } from "my-remix-app/app/cookies.server.ts";
export async function loader({ request }: LoaderFunctionArgs) { const otherCookie = await get3rdPartyCookie( "notRemixCookieJar", request.headers.get("Cookie"), ); return json({ otherCookie });}