Skip to content

Commit

Permalink
Add new session, kvSession and cookieSession middlewares
Browse files Browse the repository at this point in the history
  • Loading branch information
sergiodxa committed Jul 12, 2023
1 parent 6361176 commit d197a1d
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 3 deletions.
2 changes: 2 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ module.exports = {

"prefer-let/prefer-let": 2,

"unicorn/prevent-abbreviations": "off",

"import/order": [
"error",
{
Expand Down
12 changes: 12 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,18 @@
"types": "./build/cloudflare.d.ts",
"default": "./build/cloudflare.js"
},
"./cookie-session": {
"types": "./build/cookie-session.d.ts",
"default": "./build/cookie-session.js"
},
"./kv-session": {
"types": "./build/kv-session.d.ts",
"default": "./build/kv-session.js"
},
"./session": {
"types": "./build/session.d.ts",
"default": "./build/session.js"
},
"./package.json": "./package.json"
},
"scripts": {
Expand Down
4 changes: 2 additions & 2 deletions src/cloudflare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { Context, MiddlewareHandler, Env, Input } from "hono";

import { createRequestHandler } from "@remix-run/cloudflare";

export interface createPagesFunctionHandlerParameters<
export interface RemixMiddlewareOptions<
E extends Env = Record<string, never>,
P extends string = "",
I extends Input = Record<string, never>,
Expand All @@ -23,7 +23,7 @@ export function remix<
mode,
build,
getLoadContext = (context) => context.env as unknown as AppLoadContext,
}: createPagesFunctionHandlerParameters<E, P, I>): MiddlewareHandler {
}: RemixMiddlewareOptions<E, P, I>): MiddlewareHandler {
return async function middleware(context) {
let requestHandler = createRequestHandler(build, mode);
let loadContext = getLoadContext(context);
Expand Down
63 changes: 63 additions & 0 deletions src/middlewares/cookie-session.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import {
CookieOptions,
SessionData,
createCookieSessionStorage,
} from "@remix-run/cloudflare";
import { Context, Hono, MiddlewareHandler } from "hono";

import { session } from "./session";

type GetSecretsFunction<Secret extends string> = (
context: Context<{ Bindings: BindingsObject<Secret> }>,
) => string[];

type BindingsObject<Secret extends string> = {
[K in Secret]: string;
};

export function kvSession<
SecretBinding extends string,
Data = SessionData,
FlashData = Data,
>(options: {
autoCommit?: boolean;
cookie: Omit<CookieOptions, "secrets"> & {
name: string;
secrets: GetSecretsFunction<SecretBinding>;
};
}): MiddlewareHandler {
return session<
{ Bindings: BindingsObject<SecretBinding> },
"",
Record<string, unknown>,
Data,
FlashData
>({
autoCommit: options.autoCommit,
createSessionStorage(context) {
let secrets = options.cookie.secrets(context);

if (secrets.length === 0) {
throw new ReferenceError("The secrets for the kvSession are not set.");
}

return createCookieSessionStorage<Data, FlashData>({
cookie: { ...options.cookie, secrets },
});
},
});
}

new Hono().use(
"*",
kvSession({
autoCommit: true,
cookie: {
name: "__session",
httpOnly: true,
secrets(context) {
return [context.env.SECRET];
},
},
}),
);
70 changes: 70 additions & 0 deletions src/middlewares/kv-session.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import {
CookieOptions,
SessionData,
createWorkersKVSessionStorage,
} from "@remix-run/cloudflare";
import { Context, Hono, MiddlewareHandler } from "hono";

import { session } from "./session";

type GetSecretsFunction<KV extends string, Secret extends string> = (
context: Context<{ Bindings: BindingsObject<KV, Secret> }>,
) => string[];

type BindingsObject<KV extends string, Secret extends string> = {
[K in KV | Secret]: K extends KV ? KVNamespace : string;
};

export function kvSession<
KVBinding extends string,
SecretBinding extends string,
Data = SessionData,
FlashData = Data,
>(options: {
autoCommit?: boolean;
cookie: Omit<CookieOptions, "secrets"> & {
name: string;
secrets: GetSecretsFunction<KVBinding, SecretBinding>;
};
binding: KVBinding;
}): MiddlewareHandler {
return session<
{ Bindings: BindingsObject<KVBinding, SecretBinding> },
"",
Record<string, unknown>,
Data,
FlashData
>({
autoCommit: options.autoCommit,
createSessionStorage(context) {
if (!(options.binding in context.env)) {
throw new ReferenceError("The binding for the kvSession is not set.");
}

let secrets = options.cookie.secrets(context);

if (secrets.length === 0) {
throw new ReferenceError("The secrets for the kvSession are not set.");
}

return createWorkersKVSessionStorage<Data, FlashData>({
kv: context.env[options.binding] as KVNamespace,
cookie: { ...options.cookie, secrets },
});
},
});
}

new Hono().use(
"*",
kvSession({
autoCommit: true,
cookie: {
name: "session",
secrets(context) {
return [context.env.SECRET];
},
},
binding: "KV_BINDING",
}),
);
55 changes: 55 additions & 0 deletions src/middlewares/session.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Session, SessionData, SessionStorage } from "@remix-run/cloudflare";
import { Context, Env, Input, MiddlewareHandler } from "hono";

const sessionStorageSymbol = Symbol();
const sessionSymbol = Symbol();

export function session<
E extends Env = Record<string, never>,
P extends string = "",
I extends Input = Record<string, never>,
Data = SessionData,
FlashData = Data,
>(options: {
autoCommit?: boolean;
createSessionStorage(env: Context<E, P, I>): SessionStorage<Data, FlashData>;
}): MiddlewareHandler {
return async function middleware(context, next) {
let sessionStorage = options.createSessionStorage(context.env);

context.set(sessionStorageSymbol, sessionStorage);

// If autoCommit is disabled, we just create the SessionStorage and make it
// available with context.get(sessionStorageSymbol), then call next() and
// return.
if (!options.autoCommit) return await next();

// If autoCommit is enabled, we get the Session from the request.
let session = await sessionStorage.getSession(
context.req.raw.headers.get("cookie"),
);

// And make it available with context.get(sessionSymbol).
context.set(sessionSymbol, session);

// Then we call next() to let the rest of the middlewares run.
await next();

// Finally, we commit the session before the response is sent.
context.header("set-cookie", await sessionStorage.commitSession(session), {
append: true,
});
};
}

export function getSessionStorage<Data = SessionData, FlashData = Data>(
context: Context,
): SessionStorage<Data, FlashData> {
return context.get(sessionStorageSymbol);
}

export function getSession<Data = SessionData, FlashData = Data>(
context: Context,
): Session<Data, FlashData> {
return context.get(sessionSymbol);
}
6 changes: 5 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
"target": "ES2019",
"strict": true,
"skipLibCheck": true,
"declaration": true
"declaration": true,
"baseUrl": ".", // This must be specified if "paths" is specified
"paths": {
"remix-hono/cloudflare": ["./build/cloudflare"] // relative to "baseUrl"
}
},
"exclude": ["node_modules"],
"include": ["src/**/*.ts", "@cloudflare/workers-types"]
Expand Down

0 comments on commit d197a1d

Please sign in to comment.