From 972e4cf05baa40967de1759ccf2e9aec5e4dd131 Mon Sep 17 00:00:00 2001 From: Ola Adebayo Date: Thu, 19 Mar 2026 01:42:05 +0000 Subject: [PATCH] feat: add README.md for @rep-protocol/next plugin; enhance keys management in keys.ts --- plugins/next/README.md | 138 +++++++++++++++++++++++++++++++++++++++ plugins/next/src/keys.ts | 29 ++++++-- 2 files changed, 160 insertions(+), 7 deletions(-) create mode 100644 plugins/next/README.md diff --git a/plugins/next/README.md b/plugins/next/README.md new file mode 100644 index 0000000..d1e343b --- /dev/null +++ b/plugins/next/README.md @@ -0,0 +1,138 @@ +# @rep-protocol/next + +Next.js plugin for the [Runtime Environment Protocol (REP)](https://github.com/RuachTech/rep). Injects REP environment variables during development without needing the Go gateway. + +In production, this plugin does nothing — the REP gateway handles variable injection. + +## Install + +```bash +pnpm add @rep-protocol/next +# or +npm install @rep-protocol/next +``` + +Peer dependencies: `next >= 14`, `react >= 18`. + +## Setup + +### 1. Add `` to your root layout + +`RepScript` is a React Server Component that injects a `` injection. +- The dev session-key endpoint has no rate limiting or single-use semantics — production deployments must use the REP gateway. + +## Troubleshooting + +### `OperationError: The operation failed for an operation-specific reason` + +Key mismatch between `RepScript` and the session-key route. This happens if the bundler creates separate module instances for each entry point. The fix (applied in v0.1.12+) uses `globalThis` with `Symbol.for` to ensure a single key store across all bundles. Update to the latest version. + +### `REPError: SENSITIVE variable "X" not found in payload` + +The variable isn't set in your `.env.local`. If it's optional, catch the error: + +```ts +const getOptionalKey = (): Promise => + rep.getSecure("OPTIONAL_KEY").catch(() => ""); +``` + +### `404` on `/rep/session-key` during development + +Either the API route or the rewrite is missing. Ensure both are set up (steps 2 and 3 above). + +## License + +Apache-2.0 diff --git a/plugins/next/src/keys.ts b/plugins/next/src/keys.ts index 2b1a920..2576701 100644 --- a/plugins/next/src/keys.ts +++ b/plugins/next/src/keys.ts @@ -1,26 +1,41 @@ import { generateKeys, type Keys } from './crypto.js'; /** - * Module-scoped singleton for ephemeral cryptographic keys. + * Process-wide singleton for ephemeral cryptographic keys. + * + * Uses globalThis so that the singleton survives even when bundlers (tsup, + * Turbopack, webpack) duplicate the module across CJS entry points. This + * is the standard Next.js singleton pattern (same approach Prisma uses). * * In Next.js dev, the server is a long-running Node.js process, so keys * persist across requests within the same server lifecycle. New keys are * generated on server restart — matching the gateway's behavior. * - * RepScript and the session-key route handler both import this module, + * RepScript and the session-key route handler both call getOrCreateKeys(), * ensuring they share the same keys (encryption key used for the blob * matches the key returned by /rep/session-key). */ -let _keys: Keys | null = null; + +const GLOBAL_KEY = Symbol.for('__rep_next_keys__'); + +function getGlobal(): { keys: Keys | null } { + const g = globalThis as unknown as Record; + if (!g[GLOBAL_KEY]) { + g[GLOBAL_KEY] = { keys: null }; + } + return g[GLOBAL_KEY]!; +} export function getOrCreateKeys(): Keys { - if (!_keys) { - _keys = generateKeys(); + const store = getGlobal(); + if (!store.keys) { + store.keys = generateKeys(); } - return _keys; + return store.keys; } /** @internal For testing only. */ export function _resetKeys(): void { - _keys = null; + const store = getGlobal(); + store.keys = null; }