remix-hono
Version:
Hono middlewares for Remix
134 lines (112 loc) • 3.54 kB
text/typescript
import type {
Fetcher,
KVNamespace,
RequestInit,
} from "@cloudflare/workers-types";
import { createWorkersKVSessionStorage } from "@react-router/cloudflare";
import type { Context } from "hono";
import { createMiddleware } from "hono/factory";
import { cacheHeader } from "pretty-cache-header";
import {
type CookieOptions,
type SessionData,
createCookieSessionStorage,
} from "react-router";
import { session } from "./session.js";
interface StaticAssetsOptions {
cache?: Parameters<typeof cacheHeader>[0];
}
export function staticAssets(options: StaticAssetsOptions = {}) {
return createMiddleware(async (c, next) => {
let binding = c.env?.ASSETS as Fetcher | undefined;
if (!binding) throw new ReferenceError("The binding ASSETS is not set.");
let response: Response;
c.req.raw.headers.delete("if-none-match");
try {
response = (await binding.fetch(
c.req.url,
c.req.raw.clone() as unknown as RequestInit,
)) as unknown as globalThis.Response;
// If the request failed, we just call the next middleware
if (response.status >= 400) return await next();
response = new Response(response.body, response);
// If cache options are configured, we set the cache-control header
if (options.cache) {
response.headers.set("cache-control", cacheHeader(options.cache));
}
return response;
} catch {
return await next();
}
});
}
/* Middleware for sessions with Worker KV */
type WorkerKVBindingsObject<KV extends string, Secret extends string> = {
[K in KV | Secret]: K extends KV ? KVNamespace : string;
};
type GetWorkerKVSecretsFunction<KV extends string, Secret extends string> = (
c: Context<{ Bindings: WorkerKVBindingsObject<KV, Secret> }>,
) => string[];
export function workerKVSession<
KVBinding extends string,
SecretBinding extends string,
Data = SessionData,
FlashData = Data,
>(options: {
autoCommit?: boolean;
cookie: Omit<CookieOptions, "secrets"> & {
name: string;
secrets: GetWorkerKVSecretsFunction<KVBinding, SecretBinding>;
};
binding: KVBinding;
}) {
return session<Data, FlashData>({
autoCommit: options.autoCommit,
createSessionStorage(c) {
if (!(options.binding in c.env)) {
throw new ReferenceError("The binding for the kvSession is not set.");
}
let secrets = options.cookie.secrets(c);
if (secrets.filter(Boolean).length === 0) {
throw new ReferenceError("The secrets for the kvSession are not set.");
}
return createWorkersKVSessionStorage<Data, FlashData>({
kv: c.env[options.binding] as KVNamespace,
cookie: { ...options.cookie, secrets },
});
},
});
}
/* Middleware for sessions with Cookies */
type CookieBindingsObject<Secret extends string> = {
[K in Secret]: string;
};
type GetCookieSecretsFunction<Secret extends string> = (
c: Context<{ Bindings: CookieBindingsObject<Secret> }>,
) => string[];
export function cookieSession<
SecretBinding extends string,
Data = SessionData,
FlashData = Data,
>(options: {
autoCommit?: boolean;
cookie: Omit<CookieOptions, "secrets"> & {
name: string;
secrets: GetCookieSecretsFunction<SecretBinding>;
};
}) {
return session<Data, FlashData>({
autoCommit: options.autoCommit,
createSessionStorage(c) {
let secrets = options.cookie.secrets(c);
if (secrets.filter(Boolean).length === 0) {
throw new ReferenceError(
"The secrets for the cookieSession are not set.",
);
}
return createCookieSessionStorage<Data, FlashData>({
cookie: { ...options.cookie, secrets },
});
},
});
}