# Transcodes AI Integration Guide --- ## [Table of Contents] ### Agent directives & rules - [Global Agent Directives] — security, parameter resolution, video references - [Global Implementation Rules] — try-catch, loading state, await - **[Default Integration Method] — CDN static (HTML ``) is the ONLY default. npm SDK is a fallback.** - [Code Contract] — mandatory rules every code block must satisfy - [Exhaustive Modal API List] — only 3 modals exist - [Quick Use Cases] — when to call which modal - [Common Session Rule] — session requirements per API - [Blocking Parameter Rule] — stop and ask for resource / action (IDP modal) when unknown - [Placeholder Resolution Policy] — RESOLVED_* are required inputs, not final values - [Index & Tags] — keyword → section mapping ### MCP Server Integration - MCP Server setup — Claude Desktop, Cursor, Codex - Available MCP tools — members, roles, resources, audit logs, permissions - Migration Guide — moving existing users and roles to Transcodes ### How It Works & Architecture - Core security model — WebAuthn, DPoP, browser memory tokens, RP ID binding - System Context: Disney World Scenario — entity mapping and authentication flow analogy ### Section 1 — PWA Setup (manifest + sw.js) — CDN static - PWA — HTML + service worker (required, CDN static, `defer`) - DO NOT list — prohibited actions - Required wiring (single canonical snippet — `` + `` to the ``. - **REQUIRED ATTRIBUTE:** The script tag **MUST** use `defer`. Do NOT use `type="module"`, `crossorigin`, or `async`. ### 2. TypeScript Rule (Mandatory for CDN Static) - **`transcodes.d.ts` is MANDATORY** for any TypeScript project using the CDN static method. - **DO NOT ASSUME IT EXISTS.** Verify its existence. If missing, you MUST create it by fetching from `https://www.transcodes.io/instructions/types`. - **`tsconfig.json` MUST BE UPDATED.** You MUST add the directory containing `transcodes.d.ts` to **BOTH** `include` AND `typeRoots`. Skipping this will cause a compile error. ### 3. Framework Access Pattern Rule (CSR vs SSR) - **CSR (Vite, CRA, Vue SPA):** Use the bare `transcodes.*` global everywhere. - **SSR/RSC (Next.js, Remix, Astro, SvelteKit):** You **MUST** guard SDK calls. Calling `transcodes.*` at the module top-level or in server renders will throw a `ReferenceError`. - **Rule:** Only call `transcodes.*` inside browser-only paths (`useEffect`, event handlers) OR guard it with `if (typeof window !== 'undefined' && window.transcodes)`. ### 4. Code Contract Rule (Duplicate, see Rule 7) - **ALWAYS** wrap API invocations in a `try-catch` block. - **ALWAYS** use `await` for async SDK methods. ### 5. Parameter Resolution Rule - If a required parameter (like `projectId`, `resource`, or `action`) is missing, **STOP AND ASK** the user. Do NOT hallucinate or guess security parameters. - If Transcodes MCP is available, use it to resolve resources/roles first. --- ### [Default Integration Method — CDN STATIC IS THE ONLY DEFAULT] 1. **Always default to CDN static integration.** Add two tags to the HTML `` and create one `public/sw.js`: ```html ``` ```javascript // public/sw.js — entire file, one line importScripts('https://cdn.transcodes.link/{RESOLVED_PROJECT_ID}/sw.js'); ``` 2. **The script tag MUST use `defer`.** Do NOT use `type="module"`. Do NOT add `crossorigin` / `crossOrigin`. Do NOT use `async`. Do NOT use Next.js `` only. 3. **CDN static REQUIRES `transcodes.d.ts` for any TypeScript project. Skipping any sub-step here is a compile error.** This is a strict 3-step procedure — execute it as a sequence, do not assume any step is already done: **Step 3a — Check if `transcodes.d.ts` exists in the repo** (e.g. `types/transcodes.d.ts`, `src/types/transcodes.d.ts`, or anywhere on the `tsconfig.json` `include` path). - **If the file is MISSING → you MUST create it** by fetching from `https://www.transcodes.io/instructions/types` (or download/copy the same file from the Transcodes Console). Do not skip this step because the file "should be there" — verify, then create if absent: ```bash mkdir -p types curl -o types/transcodes.d.ts https://www.transcodes.io/instructions/types ``` - **If the file EXISTS → still confirm it is up-to-date** by re-running `curl` (or comparing the version comment). **Step 3b — TypeScript projects MUST update `tsconfig.json`** so the compiler picks up the file. **Without this edit, every `transcodes.*` (or `window.transcodes.*`) call site will throw a TypeScript compile error** (`Cannot find name 'transcodes'` for the bare form, or `Property 'transcodes' does not exist on type 'Window & typeof globalThis'` for the explicit form). Add the directory containing `transcodes.d.ts` to **both** `include` AND `typeRoots`: ```jsonc { "compilerOptions": { "typeRoots": ["./node_modules/@types", "./types"] }, "include": ["src", "types"] } ``` **Step 3c — Verify by running `tsc --noEmit`** (or your framework's type-check command). If `window.transcodes` is still untyped, `tsconfig.json` was not edited correctly — go back to Step 3b. For non-TypeScript (plain JS) projects you can skip Step 3b/3c, but Step 3a is still recommended so editors can offer autocomplete via JSDoc. 4. **Do NOT call `init()` on the CDN static path.** `webworker.js` bootstraps `window.transcodes` automatically — `projectId` is encoded in the URL. 5. **Do NOT install `@bigstrider/transcodes-sdk` by default.** The npm SDK is a **fallback only** for the rare case where CDN static is impossible. Before falling back, ASK the user explicitly: > "I default to CDN static integration (``** - Do NOT add `crossorigin` / `crossOrigin` to the `webworker.js` `` in ``. **No `type="module"`. No `crossorigin`.** **CDN host:** Production typically uses `https://cdn.transcodes.link`. Some environments use `https://dev.transcodes.link` — resolve `CDN_BASE` from Console / env (e.g. `NEXT_PUBLIC_TRANSCODES_CDN_BASE`). **TypeScript (CDN) — MANDATORY (3 strict steps):** 1. **Verify `transcodes.d.ts` exists** in the repo. If it is MISSING, create it now from `https://www.transcodes.io/instructions/types` (do not skip — the file will not appear by itself): ```bash mkdir -p types && curl -o types/transcodes.d.ts https://www.transcodes.io/instructions/types ``` 2. **Edit `tsconfig.json`** — add the directory to **both** `include` and `typeRoots`. **Without this edit, every `transcodes.*` call site is a TypeScript compile error** (`Cannot find name 'transcodes'`). 3. **Run `npx tsc --noEmit`** to confirm no type errors remain. See **CDN TypeScript setup** for the full procedure and the exact `tsconfig.json` snippet. **Do not** call `init()` on the CDN static path. #### React (Vite / CRA) / Vue.js Add to `index.html` ``, then create `public/sw.js`: ```html ``` `public/sw.js` (entire file — one line): ```javascript importScripts('https://cdn.transcodes.link/{RESOLVED_PROJECT_ID}/sw.js'); ``` #### Next.js App Router Use a **native ` ``` After the deferred script runs, all APIs are available via `window.transcodes` (or just `transcodes`). **Why this is the default:** No bundler config, no `init()` race conditions, no SSR pitfalls, no extra dependency, single source of truth (the CDN URL), instant SDK updates without redeploying your app. ### Option B — `@bigstrider/transcodes-sdk` (npm) — FALLBACK ONLY **Do not pick this unless the user explicitly asks for it.** Before generating npm code, ASK once: > "I default to CDN static (`` in ``. Don't use `type="module"`. Don't use `crossorigin`. Don't use `next/script`. Don't install `@bigstrider/transcodes-sdk` unless the user explicitly requests the npm fallback. ### React + Vite — CDN static (DEFAULT) > **Access pattern:** Vite is CSR-only — bare `transcodes.*` and explicit `window.transcodes.*` are equivalent. Use the bare form everywhere for brevity. See **Framework-specific access pattern** below. Put the script in **`index.html` ``** (native ` ``` ```bash # .env VITE_TRANSCODES_PROJECT_ID=proj_abc123xyz ``` **Then complete the TypeScript wiring (mandatory — skipping this is a compile error):** ```bash # 1. Create types/transcodes.d.ts if it does not already exist mkdir -p types curl -o types/transcodes.d.ts https://www.transcodes.io/instructions/types ``` ```jsonc // 2. tsconfig.json — add types/ to BOTH typeRoots and include { "compilerOptions": { "typeRoots": ["./node_modules/@types", "./types"] }, "include": ["src", "types"] } ``` ```bash # 3. Verify npx tsc --noEmit ``` ### Next.js App Router — CDN static (DEFAULT) > **Access pattern (CRITICAL):** Next.js renders `'use client'` components **on the server first** during SSR/RSC. Calling `transcodes.*` (or `window.transcodes.*`) at module top level or directly in a render body throws `ReferenceError` at SSR — **and `tsc` will not warn you**. Defer every SDK call into `useEffect`, an event handler, or behind a `typeof window !== 'undefined' && window.transcodes` guard. See **Framework-specific access pattern** below. Use a **native ` ``` **Then complete the TypeScript wiring (mandatory — skipping this is a compile error):** ```bash # 1. Create types/transcodes.d.ts if it does not already exist mkdir -p types curl -o types/transcodes.d.ts https://www.transcodes.io/instructions/types ``` ```jsonc // 2. tsconfig.json — add types/ to BOTH typeRoots and include { "compilerOptions": { "typeRoots": ["./node_modules/@types", "./types"] }, "include": ["src", "types"] } ``` ```bash # 3. Verify npx tsc --noEmit ``` --- ### Framework-specific access pattern — when to use `transcodes` vs `window.transcodes` Both `transcodes.*` (bare) and `window.transcodes.*` (explicit) resolve to the **same object at runtime** in the browser, and both are typed by `transcodes.d.ts` (`var transcodes: Window['transcodes']`). **The choice depends entirely on where the code runs.** > ⚠️ **TypeScript will NOT catch SSR-time absence.** The `.d.ts` declares the global as always-present. The type checker happily green-lights both forms even when they would throw `ReferenceError` at runtime on the server. This makes the framework decision below a **runtime concern that the type system cannot enforce** — you must reason about it explicitly. | Framework / runtime | Module top-level / RSC / first SSR render | `useEffect` / event handler / `onClick` | Recommended access form | | ---------------------------------------- | ----------------------------------------------------- | --------------------------------------------------- | ---------------------------------------------------------------------- | | **Vite** (React, Vue, Svelte) — CSR only | ✅ both work — no SSR pass | ✅ both work | Use bare `transcodes.*` everywhere. Equivalent and shorter. | | **Create React App** / Parcel — CSR only | ✅ both work | ✅ both work | Use bare `transcodes.*` everywhere. | | **Next.js App Router** (RSC + SSR) | ❌ both throw `ReferenceError` (renders in Node first) | ✅ both work (browser only) | Inside `'use client'` + `useEffect` / handler: bare `transcodes.*`. Outside that scope: guard with `typeof window !== 'undefined' && window.transcodes`. | | **Next.js Pages Router** (`getServerSideProps`, `getStaticProps`) | ❌ both throw on the server | ✅ both work in `useEffect` / handlers | Same as App Router. Never call SDK in `getServerSideProps` / `getStaticProps`. | | **Remix / React Router (Framework Mode)** | ❌ both throw in loaders / actions / SSR | ✅ both work in component effects | Same as Next.js — gate behind effects or feature-detection. | | **Astro / Qwik** (islands + SSR) | ❌ both throw at SSR | ✅ both work inside client islands | Use bare `transcodes.*` inside client-only directives (`client:load`, `client:idle`, etc.). | **Decision rule for code samples:** 1. **Pure CSR project (Vite / CRA / Vue SPA / static HTML)** → use bare `transcodes.*` everywhere. Concise and equivalent. 2. **Any framework with SSR / RSC / static-generation phase (Next.js / Remix / Astro / SvelteKit)** → bare `transcodes.*` is fine **only inside browser-only code paths** (`useEffect`, `useLayoutEffect`, event handlers, components after `'use client'` + post-mount). Anywhere else, use the explicit `window.transcodes` form **with an SSR guard** so `tsc` and bundler don't mask the runtime hazard. **Idiomatic SSR-safe access patterns:** ```tsx // Next.js App Router — 'use client' + useEffect (preferred) 'use client'; import { useEffect } from 'react'; export function LoginButton() { // ❌ WRONG — runs at first SSR pass, transcodes is undefined on the server // const isAuth = await transcodes.token.isAuthenticated(); useEffect(() => { // ✅ OK — useEffect only runs in the browser transcodes.token.isAuthenticated().then((isAuth) => { // ... }); }, []); return ; } ``` ```ts // Anywhere outside an effect / handler in an SSR framework — feature-detect first. // The window.transcodes form pairs naturally with `typeof window !== 'undefined'`. function isTranscodesReady(): boolean { return typeof window !== 'undefined' && Boolean(window.transcodes); } if (isTranscodesReady()) { // Inside the guarded block, bare `transcodes.*` is safe to call. await transcodes.token.isAuthenticated(); } ``` ```ts // AVOID — the bare form silently breaks SSR because TS doesn't see the gap. // In Next.js / Remix / Astro this throws at SSR build / render time. const isAuth = await transcodes.token.isAuthenticated(); // ❌ at module top level ``` **Why prefer `window.transcodes` for the guard but `transcodes.*` for the call?** - `typeof window !== 'undefined' && window.transcodes` is the canonical SSR feature-detection idiom — every Next.js / Remix codebase has it. Reviewers immediately recognise the intent. - After the guard establishes browser context, switching to bare `transcodes.*` for the actual calls keeps the call sites short and matches the rest of the documentation. - TypeScript narrows neither form (the `.d.ts` always promises `transcodes` exists), so the guard is purely a **runtime safety contract**, not a type-narrowing trick. --- ### npm fallback variants (only when user explicitly opts out of CDN static) Generate these only after the user confirms they cannot use CDN static. #### Next.js App Router — `@bigstrider/transcodes-sdk` (FALLBACK) ```tsx // app/providers.tsx — 'use client' 'use client'; import { init } from '@bigstrider/transcodes-sdk'; import { useEffect } from 'react'; export function TranscodesProvider({ children }) { useEffect(() => { init({ projectId: process.env.NEXT_PUBLIC_TRANSCODES_PROJECT_ID! }); }, []); return <>{children}; } ``` #### Vue 3 + Vite — `@bigstrider/transcodes-sdk` (FALLBACK) ```typescript // main.ts import { init } from '@bigstrider/transcodes-sdk'; await init({ projectId: import.meta.env.VITE_TRANSCODES_PROJECT_ID }); ``` --- ## Quick start example (CDN static — DEFAULT) **This is the default. Generate this — not an npm/`init()` variant — unless the user explicitly asks otherwise.** ```html

Not signed in

``` --- ## CDN TypeScript setup — MANDATORY The CDN static path is the default integration method, and **`transcodes.d.ts` is a required part of it for any TypeScript project.** Without it, `window.transcodes` is `any` and the SDK call sites do not type-check — worse, with `strict` mode enabled the project will fail to compile (`Property 'transcodes' does not exist on type 'Window'`). **Do not assume the file is already in the repo. Verify, then create it if missing.** ### Step 1 — Check `transcodes.d.ts` exists; if not, create it Search the repo for an existing `transcodes.d.ts` (typical locations: `types/transcodes.d.ts`, `src/types/transcodes.d.ts`, or any path on `tsconfig.json` `include`). - **If the file is MISSING → create it now.** This is mandatory, not optional. The canonical source is `https://www.transcodes.io/instructions/types` (you can also download/copy the same file from the Transcodes Console): ```bash mkdir -p types curl -o types/transcodes.d.ts https://www.transcodes.io/instructions/types ``` - **If the file EXISTS → confirm it is current** by re-running the `curl` (it overwrites the local copy with the latest CDN version). Stale `.d.ts` files cause subtle "method not found" errors when the SDK adds new APIs. ### Step 2 — Edit `tsconfig.json` (TypeScript projects only — REQUIRED) **Skipping this step causes compile errors.** The TypeScript compiler will not pick up `transcodes.d.ts` unless its directory is on `include` and/or `typeRoots`. Update `tsconfig.json` like this: ```jsonc { "compilerOptions": { // include the project-local types directory in addition to node_modules/@types "typeRoots": ["./node_modules/@types", "./types"] }, // ensure the types/ directory is part of the compilation (not just typeRoots) "include": ["src", "types"] } ``` If you used a different folder name in Step 1 (e.g. `src/types/`), match it in both `typeRoots` and `include`. For Next.js projects, add the same paths to the **root** `tsconfig.json`. Do **not** rely on auto-generated `next-env.d.ts` to pick up `transcodes.d.ts` — it will not. ### Step 3 — Verify the compiler is happy Run a no-emit type check to confirm the wiring: ```bash npx tsc --noEmit ``` If `window.transcodes` is still untyped, `tsconfig.json` was edited incorrectly — re-check that the directory containing `transcodes.d.ts` appears in **both** `include` AND `typeRoots`, and that the file path is exactly `/transcodes.d.ts`. After Steps 1–3, `window.transcodes`, `transcodes.openAuthLoginModal`, `transcodes.token.getCurrentMember()`, etc. are fully typed. ### Notes for the CDN static path - **Do NOT call `init()`.** `webworker.js` bootstraps `window.transcodes` automatically. The `.d.ts` may still declare `init` (it is shared with the npm package) — ignore that export on the CDN static path. - Re-run `curl` periodically (or as part of CI) so the local `transcodes.d.ts` stays in sync with the CDN. ### npm fallback only When the user has explicitly chosen `@bigstrider/transcodes-sdk`, TypeScript types ship with the package and **no** extra `transcodes.d.ts` file is needed (and no `tsconfig.json` edit either). This exemption applies only to the npm fallback — not to the default CDN static path. --- ## React AuthContext pattern (recommended) The standard pattern for React apps. The structure is identical for both integrations — only the imports change. **Default to the CDN static version**: read everything off the bare `transcodes` global (typed via `transcodes.d.ts`). The npm import shape below is shown for the fallback case only. **CDN static (DEFAULT) — replace the import block with:** ```tsx // src/context/AuthContext.tsx — CDN static // transcodes.d.ts must be installed (see "CDN TypeScript setup") const sdkIsAuthenticated = () => transcodes.token.isAuthenticated(); const sdkLogin = (params: { webhookNotification?: boolean }) => transcodes.openAuthLoginModal(params); const sdkConsole = () => transcodes.openAuthConsoleModal(); const sdkIdp = (params: { resource: string; action: 'create' | 'read' | 'update' | 'delete' }) => transcodes.openAuthIdpModal(params); const sdkSignOut = () => transcodes.token.signOut(); const on = transcodes.on.bind(transcodes); // ...rest of AuthProvider below is identical ``` **npm fallback — only when the user has confirmed Option B:** ```tsx // src/context/AuthContext.tsx import { createContext, useEffect, useState, type ReactNode } from 'react'; import { isAuthenticated as sdkIsAuthenticated, openAuthLoginModal as sdkLogin, openAuthConsoleModal as sdkConsole, openAuthIdpModal as sdkIdp, signOut as sdkSignOut, on, } from '@bigstrider/transcodes-sdk'; interface AuthContextValue { isAuthenticated: boolean; isLoading: boolean; memberId: string | null; openAuthLoginModal: () => Promise; openAuthConsoleModal: () => Promise; openAuthIdpModal: (params: { resource: string; action: 'create' | 'read' | 'update' | 'delete'; }) => Promise; signOut: () => Promise; } export const AuthContext = createContext({ /* defaults */ } as AuthContextValue); export function AuthProvider({ children }: { children: ReactNode }) { const [isAuth, setIsAuth] = useState(false); const [isLoading, setIsLoading] = useState(true); const [memberId, setMemberId] = useState(null); useEffect(() => { sdkIsAuthenticated().then((auth) => { setIsAuth(auth); setIsLoading(false); }); const unsubscribe = on('AUTH_STATE_CHANGED', ({ isAuthenticated, member }) => { setIsAuth(isAuthenticated); setMemberId(member?.id ?? null); }); return () => unsubscribe(); }, []); return ( { const result = await sdkLogin({}); if (result.success) setMemberId(result.payload[0]?.member?.id ?? null); }, openAuthConsoleModal: async () => { await sdkConsole(); }, openAuthIdpModal: async (params) => { await sdkIdp(params); }, signOut: async () => { await sdkSignOut(); setIsAuth(false); setMemberId(null); }, }}> {children} ); } ``` Wrap your app in `` and consume with `use(AuthContext)` in components. --- ## Quick start example (`@bigstrider/transcodes-sdk` — React) — npm FALLBACK **Generate this only when the user has explicitly chosen the npm SDK.** For the default CDN static path, use the bare `transcodes` global directly (see Quick start example above). ```tsx // src/App.tsx 'use client'; // Next.js import { openAuthLoginModal, on, getCurrentMember } from '@bigstrider/transcodes-sdk'; import { useEffect, useState } from 'react'; import type { Member } from '@bigstrider/transcodes-sdk'; export default function App() { const [member, setMember] = useState(null); useEffect(() => { getCurrentMember().then((r) => setMember(r.success ? r.member : null)); const unsub = on('AUTH_STATE_CHANGED', (p) => setMember(p.member)); return unsub; }, []); const handleLogin = async () => { const result = await openAuthLoginModal({}); if (result.success) setMember(result.payload[0].member); }; return member ?

Hello, {member.email}

: ; } ``` --- ## Step-up MFA example (CDN static — DEFAULT) Use before any sensitive action. This example shows the **default CDN static** flow via the bare `transcodes` global. The npm SDK variant uses named imports (see API 4 above) and should only be used if the user explicitly chose Option B. **CDN step-up with all mandatory steps:** ```typescript // CDN step-up implementation // Resolve resource/action from MCP / Console first. If unavailable, ask the user and stop. async function deleteUser(userId: string) { try { const resource = resolvedResource; // resolved via get_resources or explicit user input const action = resolvedAction; // resolved from the protected operation // 1. Request step-up verification const mfa = await transcodes.openAuthIdpModal({ resource, action, }); // SUCCESS CHECKS before using sid if (!mfa.success || !mfa.payload[0]?.success) return; // Send session ID to YOUR backend const sessionId = mfa.payload[0].sid; await fetch(`/api/users/${userId}`, { method: 'DELETE', headers: { Authorization: `Bearer ${await transcodes.token.getAccessToken()}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ sessionId }), }); // YOUR backend calls: GET https://api.transcodesapis.com/v1/auth/temp-session/step-up/{sessionId} // with header `x-transcodes-token: ` to verify before executing the action. // 4. Audit log success await transcodes.trackUserAction({ tag: 'user:delete', severity: 'high', status: true, metadata: { userId }, }); } catch (err) { // 5. Audit log failure await transcodes.trackUserAction({ tag: 'user:delete', severity: 'high', status: false, error: (err as Error).message, metadata: { userId }, }); } } ``` --- ## Server-side JWT verification When you create an Authentication Cluster, you receive a `public_key.json` file (EC P-256 / ES256). Use this key to verify JWTs issued by Transcodes on your server. No JWKS endpoint or remote key fetch is needed. Flow: 1. Client calls `await getAccessToken()` to get the JWT 2. Client sends the token to your backend via `Authorization: Bearer ` 3. Your backend verifies the token locally using `public_key.json` `public_key.json` format (download from Console → Authentication Cluster): ```json { "kty": "EC", "x": "...", "y": "...", "crv": "P-256", "alg": "ES256", "kid": "..." } ``` If you regenerate the public key in the Console, the previous key becomes invalid. Update `public_key.json` on your server immediately. ### Node.js / Next.js (jose) ```typescript // npm install jose import { importJWK, jwtVerify, type JWTPayload } from 'jose'; // Paste your public_key.json content here, or import from a file const TRANSCODES_PUBLIC_JWK = { kty: 'EC', x: '...', y: '...', crv: 'P-256', alg: 'ES256', kid: '...', }; let _publicKey: Awaited> | null = null; async function getPublicKey() { if (!_publicKey) { _publicKey = await importJWK(TRANSCODES_PUBLIC_JWK, 'ES256'); } return _publicKey; } async function verifyToken(token: string): Promise { const publicKey = await getPublicKey(); const { payload } = await jwtVerify(token, publicKey); return payload; } ``` Express middleware: ```typescript app.use(async (req, res, next) => { const auth = req.headers.authorization; if (!auth?.startsWith('Bearer ')) return res.status(401).json({ error: 'No token' }); try { req.member = await verifyToken(auth.slice(7)); next(); } catch { res.status(401).json({ error: 'Invalid or expired token' }); } }); ``` Next.js middleware / route handler: ```typescript import { importJWK, jwtVerify, type JWTPayload } from 'jose'; import { NextRequest, NextResponse } from 'next/server'; const TRANSCODES_PUBLIC_JWK = { /* paste public_key.json */ }; let _publicKey: Awaited> | null = null; async function getPublicKey() { if (!_publicKey) _publicKey = await importJWK(TRANSCODES_PUBLIC_JWK, 'ES256'); return _publicKey; } export async function verifyAuth(req: NextRequest): Promise<{ payload: JWTPayload } | NextResponse> { const authHeader = req.headers.get('authorization'); if (!authHeader?.startsWith('Bearer ')) { return NextResponse.json({ error: 'No token' }, { status: 401 }); } try { const publicKey = await getPublicKey(); const { payload } = await jwtVerify(authHeader.slice(7), publicKey); return { payload }; } catch { return NextResponse.json({ error: 'Invalid or expired token' }, { status: 401 }); } } ``` ### Python (PyJWT) ```python # pip install pyjwt cryptography import jwt PUBLIC_KEY_JWK = { "kty": "EC", "x": "...", "y": "...", "crv": "P-256", "alg": "ES256", "kid": "...", } _public_key = jwt.algorithms.ECAlgorithm.from_jwk(PUBLIC_KEY_JWK) def verify_token(token: str) -> dict: return jwt.decode(token, _public_key, algorithms=["ES256"]) ``` ## CSP (Content Security Policy) At minimum, allow the CDN for the SDK script. The exact API domains for `connect-src` and `frame-src` are listed in your Transcodes Console → Authentication Cluster → Installation Guide. ```html ``` --- ## Release Checklist Use this checklist before outputting final code: 0. **Integration method = CDN static** — `` in HTML ``, plus `` for PWA, plus `public/sw.js` (one-line `importScripts`). The script tag has `defer` only — **no `type="module"`, no `crossorigin`, no `async`**. The npm SDK (`@bigstrider/transcodes-sdk`) is used **only** if the user explicitly opted out of CDN static. 0a. **`transcodes.d.ts` is installed AND `tsconfig.json` is wired** for any TypeScript project on the CDN static path. All three sub-steps must be done — verify, do not assume: - `transcodes.d.ts` exists in the repo (e.g. `types/transcodes.d.ts`). If it was missing, you fetched it from `https://www.transcodes.io/instructions/types` (not from memory). - `tsconfig.json` includes the directory in **both** `include` AND `typeRoots`. **Skipping this is a guaranteed compile error.** - `npx tsc --noEmit` passes (or the project's equivalent type-check command). 0b. **No `init()` call** on the CDN static path. (`init({ projectId })` appears only in confirmed npm fallback code.) 1. `projectId`, JWT issuer, and server-side Transcodes token (`x-transcodes-token`) are resolved or explicitly requested from the user. 2. Only the canonical modal APIs are used: - `openAuthLoginModal({ webhookNotification? })` - `openAuthConsoleModal()` - `openAuthIdpModal({ resource, action, forceStepUp?, webhookNotification? })` 3. All async SDK methods use `await`. 4. Every modal result checks outer `result.success` before reading `payload`. 5. `openAuthIdpModal()` checks both outer `result.success` and inner `result.payload[0]?.success` before using `sid`. 6. Step-up flows send `sessionId` to the app backend, and the backend verifies it with: - `GET https://api.transcodesapis.com/v1/auth/temp-session/step-up/{sessionId}` - header `x-transcodes-token: ` (server-only JWT issued from the Console; different from `TRANSCODES_TOKEN` used by MCP) 7. Protected modals are preceded by `await isAuthenticated()`. 8. Event subscriptions return and use `unsubscribe` on cleanup. 9. Audit log tags use `entity:action` format. 10. Sensitive operations log both success and failure. 11. `resource` is resolved via `get_resources` or explicit user input; never guessed. 12. Final code contains no unresolved placeholders. --- ## [Agent Rule — .transcodes Integration Record] ### After completing Transcodes frontend integration When the initial installation and configuration of Transcodes is finished, **you MUST create a `.transcodes/` folder** in the project root and generate a documentation file that records how each Transcodes API was used. **Create:** `.transcodes/integration.md` The file must contain: 1. **Project info** — Project ID, integration method (`CDN static` is the default; record `npm` only if the user explicitly chose it), framework 2. **openAuthLoginModal** — where it is called, which component/page, any options used (e.g. `webhookNotification`) 3. **openAuthConsoleModal** — where it is called, which component/page 4. **openAuthIdpModal** — where it is called, which `resource` and `action` values were used, whether `forceStepUp` is enabled 5. **Transcodes MCP** — if used: which tools (e.g. members, roles, audit logs), and how the project is configured 6. **trackUserAction** — every call site, the `tag` used, `severity`, what `metadata` is passed 7. **Events** — which events are subscribed to (`AUTH_STATE_CHANGED`, `TOKEN_EXPIRED`, etc.) and where 8. **Server-side** — whether JWT verification is implemented, which endpoint, which library Format each entry as a table or bullet list with: **file path**, **function/component name**, **parameters**, and **purpose**. Example structure: ``` # Transcodes Integration Record ## Project - Project ID: proj_abc123 - Method: CDN static (`` in `` (React SPA and Next.js — no `next/script`, no `type="module"`, no `crossorigin`); fetch **https://www.transcodes.io/instructions/types**, save as `transcodes.d.ts`, wire `tsconfig.json`; the CDN static path needs no `init()`. - **When to show**: When the user is setting up the default CDN static integration or asks where to put the script tag. - **Link**: [Watch Video](https://player.mux.com/PQCWSkHiBYjhWkJXlsHu7w5Ha9U2oWnDGtAjbaCo1Go?metadata-video-title=pwa_auth_installation&video-title=pwa_auth_installation) - **PWA Installation Simulation** - **Description**: Simulates the user experience of installing the PWA. - **When to show**: When the user wants to understand the install prompt flow. - **Link**: [Watch Video](https://player.mux.com/HAp2zjaue756ndCwdQ02PsJGjZk01Ft75r101hhDSeE93Y?metadata-video-title=pwa_installation&video-title=pwa_installation) - **Rich Install Screenshots Setup** - **Description**: How to upload and configure screenshots that appear in the PWA install prompt. - **When to show**: When the user asks how to add images to the install prompt or configure rich install UI. - **Link**: [Watch Video](https://player.mux.com/blZ00ZnVCN00RMow02DL4fMwCBTdjSXNtJRPbO7jEFm4jU?metadata-video-title=pwa_screenshot&video-title=pwa_screenshot) - **Widget Component Configuration** - **Description**: How to configure the floating PWA install widget. - **When to show**: When the user asks about the install button or widget configuration. - **Link**: [Watch Video](https://player.mux.com/QfE008xqjTzHLjoGabizmqU00fdb7ZGrwve1QgpFeJU6g?metadata-video-title=pwa_widget&video-title=pwa_widget) - **Manifest Configuration** - **Description**: How to set up the PWA manifest details (name, short name, theme color, display mode) in the Console. - **When to show**: When the user asks how to change the app name, colors, or display settings for the PWA. - **Link**: [Watch Video](https://player.mux.com/JEm5F02GQfAWlNWxujqt8B9ScTXSCvs00RzraM9YHF01Ds?metadata-video-title=pwa_configuration&video-title=pwa_configuration) - **Custom Icon Setup** - **Description**: How to upload a base icon and have Transcodes automatically generate all required manifest icon sizes. - **When to show**: When the user asks how to set the app icon for the home screen. - **Link**: [Watch Video](https://player.mux.com/W2Gs4TJThlEkOW3m017RtP00ZsbkP01ZP6y7JoCrkTe5no?metadata-video-title=pwa_icon&video-title=pwa_icon) - **PWA Cache Settings** - **Description**: How to configure offline caching strategies via the Transcodes Console. - **When to show**: When the user asks about offline support or caching static assets. - **Link**: [Watch Video](https://player.mux.com/39ieyO856vgEacy0136q3qHEQEEQaokRl9vgPS8hizgE?metadata-video-title=pwa_cache&video-title=pwa_cache) --- # Troubleshooting Use this section when something is not working. Each entry maps **symptom → cause → fix**. --- ## PWA / sw.js ### GET /sw.js → 404 - **Cause**: `public/sw.js` does not exist in the repo, but the browser is requesting it because Transcodes PWA wiring is (or was) active. - **Diagnosis**: Check if `public/sw.js` is present. Check browser DevTools → Application → Service Workers for stale registrations. - **Fix**: Create `public/sw.js` with exactly one line: ```javascript importScripts('https://cdn.transcodes.link/{RESOLVED_PROJECT_ID}/sw.js'); ``` - **If PWA is not needed**: Remove `` and `` in `` + `transcodes.d.ts`). The npm SDK is a fallback used only when the user explicitly opts out of CDN static — for example, an environment with no controllable HTML ``. - **If you already wired npm without asking**: Switch to CDN static unless the user has confirmed they need npm. ### `window.transcodes` is undefined - **Cause 1**: `` inside ``. Gate usage on `window.transcodes` existing, on `DOMContentLoaded`, or subscribe to `AUTH_STATE_CHANGED` instead of reading on load. ### `ReferenceError: transcodes is not defined` (or `window is not defined`) on the server - **Symptom**: Build/runtime error during Next.js SSR/RSC, Remix loader, Astro SSR, SvelteKit prerender, or `next build` — the stack trace points at `transcodes.*` or `window.transcodes.*`. - **Cause**: The SDK call ran in a server context (Node) where neither the `transcodes` global nor `window` exists. Common triggers: - Bare `transcodes.token.isAuthenticated()` at the top of a `'use client'` file (still rendered on the server during SSR pass). - SDK call in a Server Component / RSC body. - SDK call in `getServerSideProps`, `getStaticProps`, Remix `loader` / `action`, SvelteKit `+page.server.ts`, or Astro frontmatter. - SDK call in module top-level of any file imported by an SSR route. - **Why TypeScript missed it**: `transcodes.d.ts` declares `var transcodes: Window['transcodes']` as always-present. The type system has no way to know the global is missing on the server. - **Fix**: - **Move the call into a browser-only path** — `useEffect`, `useLayoutEffect`, an event handler, or a post-mount callback. - **Or guard the call**: `if (typeof window !== 'undefined' && window.transcodes) { ... }` — works in module top-level too. - For Next.js, ensure the file starts with `'use client'` AND the call is inside `useEffect` (not the render body). - See **Framework-specific access pattern** for the per-framework matrix. ### Script tag has `type="module"` or `crossorigin` / `crossOrigin` - **Cause**: Old documentation or muscle memory from the prior recommendation. - **Fix**: Remove both attributes. The canonical tag is `` — `defer` is the only attribute besides `src`. ### TypeScript compile error: `Property 'transcodes' does not exist on type 'Window & typeof globalThis'` - **Cause A**: `transcodes.d.ts` is not in the repo at all. **This file is mandatory for the CDN static path — you must create it if it is missing.** - **Cause B**: `transcodes.d.ts` exists, but `tsconfig.json` was never edited, so the compiler does not pick it up. - **Cause C**: `tsconfig.json` was edited but the directory is on `typeRoots` only OR `include` only — both are required. - **Fix (sequence — execute every step, do not skip)**: 1. `mkdir -p types && curl -o types/transcodes.d.ts https://www.transcodes.io/instructions/types` 2. Edit `tsconfig.json` so `types` appears in **both** `include` AND `typeRoots`: ```jsonc { "compilerOptions": { "typeRoots": ["./node_modules/@types", "./types"] }, "include": ["src", "types"] } ``` 3. `npx tsc --noEmit` — if the error is gone, you are done. - See **CDN TypeScript setup** for the canonical procedure. ### TypeScript error: `window.transcodes` has no type / implicit `any` (non-strict mode) - **Cause**: Same as above (missing `transcodes.d.ts` or unedited `tsconfig.json`). Without `strict`/`noImplicitAny` the compiler does not crash, but every SDK call site falls back to `any` — you lose autocomplete and type checks. - **Fix**: Same 3-step sequence as the compile error above. ### `init is not a function` or calling `init()` breaks the CDN static flow - **Cause**: `init()` is only for the `@bigstrider/transcodes-sdk` (npm) fallback path. The CDN static path bootstraps automatically — `init()` must NOT be called. - **Fix**: Remove the `init()` call. Confirm the integration is CDN static (a `