diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml index d1f7c1a..e40aefe 100644 --- a/.github/workflows/deploy-staging.yml +++ b/.github/workflows/deploy-staging.yml @@ -38,9 +38,22 @@ jobs: env: CLOUDFLARE_ENV: staging # for vite.config.ts to figure out which environment to build for VITE_ENV: staging - VITE_ORG_ID: ${{ secrets.VITE_ORG_ID_STAGING }} - VITE_API_KEY: ${{ secrets.VITE_API_KEY_STAGING }} - VITE_BASE_URL: staging.realtime.cloudflare.com + + - name: Configure RealtimeKit API key + run: pnpm wrangler secret put REALTIMEKIT_API_KEY --env staging <<< "$REALTIMEKIT_API_KEY" + working-directory: ./demo-app + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + REALTIMEKIT_API_KEY: ${{ secrets.REALTIMEKIT_API_KEY_STAGING }} + + - name: Configure RealtimeKit org ID + run: pnpm wrangler secret put REALTIMEKIT_ORG_ID --env staging <<< "$REALTIMEKIT_ORG_ID" + working-directory: ./demo-app + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + REALTIMEKIT_ORG_ID: ${{ secrets.REALTIMEKIT_ORG_ID_STAGING }} - name: Deploy to Cloudflare Workers uses: cloudflare/wrangler-action@v4 @@ -52,6 +65,3 @@ jobs: env: VITE_ENV: staging CLOUDFLARE_ENV: staging # for vite.config.ts to figure out which environment to deploy to - VITE_API_KEY: ${{ secrets.VITE_API_KEY_STAGING }} - VITE_ORG_ID: ${{ secrets.VITE_ORG_ID_STAGING }} - VITE_BASE_URL: staging.realtime.cloudflare.com \ No newline at end of file diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index f68a9e6..c2929ad 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -32,26 +32,28 @@ jobs: run: pnpm install --frozen-lockfile working-directory: ./ - - name: Debug env vars - run: | - echo "VITE_BASE_URL is set: ${{ secrets.VITE_BASE_URL != '' }}" - echo "VITE_API_KEY is set: ${{ secrets.VITE_API_KEY != '' }}" - echo "VITE_ORG_ID is set: ${{ secrets.VITE_ORG_ID != '' }}" - echo "VITE_BASE_URL length: ${#VITE_BASE_URL}" - env: - VITE_BASE_URL: realtime.cloudflare.com - VITE_API_KEY: ${{ secrets.VITE_API_KEY }} - VITE_ORG_ID: ${{ secrets.VITE_ORG_ID }} - - name: Build run: pnpm run build working-directory: ./demo-app env: CLOUDFLARE_ENV: production # for vite.config.ts to figure out which environment to build for VITE_ENV: production - VITE_ORG_ID: ${{ secrets.VITE_ORG_ID }} - VITE_API_KEY: ${{ secrets.VITE_API_KEY }} - VITE_BASE_URL: realtime.cloudflare.com + + - name: Configure RealtimeKit API key + run: pnpm wrangler secret put REALTIMEKIT_API_KEY --env production <<< "$REALTIMEKIT_API_KEY" + working-directory: ./demo-app + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + REALTIMEKIT_API_KEY: ${{ secrets.REALTIMEKIT_API_KEY }} + + - name: Configure RealtimeKit org ID + run: pnpm wrangler secret put REALTIMEKIT_ORG_ID --env production <<< "$REALTIMEKIT_ORG_ID" + working-directory: ./demo-app + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + REALTIMEKIT_ORG_ID: ${{ secrets.REALTIMEKIT_ORG_ID }} - name: Deploy to Cloudflare Workers uses: cloudflare/wrangler-action@v4 @@ -63,6 +65,3 @@ jobs: env: VITE_ENV: production CLOUDFLARE_ENV: production # for vite.config.ts to figure out which environment to deploy to - VITE_API_KEY: ${{ secrets.VITE_API_KEY }} - VITE_ORG_ID: ${{ secrets.VITE_ORG_ID }} - VITE_BASE_URL: realtime.cloudflare.com \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index a4d8378..7112d64 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -31,7 +31,7 @@ realtimekit-web-examples/ | Update SDK versions | `/update-cloudflare-realtime-deps.sh` | Fetches from npm; pass `--env staging\|prod` | | Gallery example catalog | `demo-app/app/utils/react.ts`, `angular.ts`, `vanilla.ts` | Hardcoded; new examples won't appear without editing | | CI / deploy config | `.github/workflows/` | Per-framework staging + prod deploy workflows | -| API credentials | `demo-app/.env.example` | `VITE_ORG_ID`, `VITE_API_KEY`, `VITE_BASE_URL`, `VITE_ENV` | +| API credentials | `demo-app/.dev.vars.example` | `REALTIMEKIT_ORG_ID`, `REALTIMEKIT_API_KEY`, `REALTIMEKIT_BASE_URL` | ## COMMANDS @@ -71,6 +71,6 @@ cd demo-app && pnpm dev - `pnpm-workspace.yaml` has a stale path: `recording-sdk-app-examples/react-examples/*` (actual dir: `recording-sdk-app-samples`) — this workspace glob is broken - `demo-app/README.md` contains only "TODO" — consult root `README.md` -- `VITE_BASE_URL=realtime.cloudflare.com` in `.env.example` is a legacy URL (pre-Cloudflare branding) — verify the actual endpoint in CI secrets +- `REALTIMEKIT_BASE_URL=realtime.cloudflare.com` is the server-side API base hostname for production; public SDK example builds use `VITE_BASE_URL` with staging using `staging.realtime.cloudflare.com` - Node version unspecified in the repo; CI uses Node 20 — match locally - `@dyteinternals/utils` in `recording-sdk-app-samples/react-examples/recording-with-watermark/package.json` is a vestigial upstream dependency diff --git a/angular-examples/.env.examples b/angular-examples/.env.examples index 1caf2e1..219bb08 100644 --- a/angular-examples/.env.examples +++ b/angular-examples/.env.examples @@ -1,10 +1,5 @@ -# API Credentials -VITE_ORG_ID= -VITE_API_KEY= - # API Base URL VITE_BASE_URL=realtime.cloudflare.com - VITE_ENV=production -# VITE_ENV=production \ No newline at end of file +# VITE_ENV=production diff --git a/angular-examples/examples/create-your-own-ui/src/app/app.component.ts b/angular-examples/examples/create-your-own-ui/src/app/app.component.ts index b119ec9..5c71172 100644 --- a/angular-examples/examples/create-your-own-ui/src/app/app.component.ts +++ b/angular-examples/examples/create-your-own-ui/src/app/app.component.ts @@ -35,9 +35,13 @@ export class AppComponent implements OnInit { if (!this.meeting) { try { // Initialize RealtimeKit client + const baseURI = searchParams.get('baseURI') || environment.baseUrl; + const logInConsole = searchParams.get('logInConsole') === 'true'; + const meeting = await RealtimeKitClient.init({ authToken, - baseURI: environment.baseUrl, + baseURI, + modules: { devTools: { logs: logInConsole } }, }); this.meeting = meeting; diff --git a/angular-examples/examples/default-meeting-ui/src/app/app.component.ts b/angular-examples/examples/default-meeting-ui/src/app/app.component.ts index 2a718d4..6167515 100644 --- a/angular-examples/examples/default-meeting-ui/src/app/app.component.ts +++ b/angular-examples/examples/default-meeting-ui/src/app/app.component.ts @@ -25,9 +25,13 @@ export class AppComponent { return; } + const baseURI = searchParams.get('baseURI') || environment.baseUrl; + const logInConsole = searchParams.get('logInConsole') === 'true'; + const meeting = await RealtimeKitClient.init({ authToken, - baseURI: environment.baseUrl, + baseURI, + modules: { devTools: { logs: logInConsole } }, }); this.$meetingEl.meeting = meeting; diff --git a/angular-examples/examples/with-video-transformer/src/app/app.component.ts b/angular-examples/examples/with-video-transformer/src/app/app.component.ts index accf2b6..71aa37c 100644 --- a/angular-examples/examples/with-video-transformer/src/app/app.component.ts +++ b/angular-examples/examples/with-video-transformer/src/app/app.component.ts @@ -28,9 +28,13 @@ export class AppComponent { return; } + const baseURI = searchParams.get('baseURI') || environment.baseUrl; + const logInConsole = searchParams.get('logInConsole') === 'true'; + const meeting = await RealtimeKitClient.init({ authToken, - baseURI: environment.baseUrl, + baseURI, + modules: { devTools: { logs: logInConsole } }, }); this.$meetingEl.meeting = meeting; diff --git a/demo-app/.dev.vars.example b/demo-app/.dev.vars.example new file mode 100644 index 0000000..e1ccd2e --- /dev/null +++ b/demo-app/.dev.vars.example @@ -0,0 +1,3 @@ +REALTIMEKIT_ORG_ID=your-org-id-here +REALTIMEKIT_API_KEY=your-api-key-here +REALTIMEKIT_BASE_URL=realtime.cloudflare.com diff --git a/demo-app/.env.example b/demo-app/.env.example index b79e522..3d3e203 100644 --- a/demo-app/.env.example +++ b/demo-app/.env.example @@ -1,9 +1,2 @@ -# Dyte API Credentials -# Please use this ORG ID in order for presets to work -VITE_ORG_ID=05dc4990-76b0-44f8-adc9-42d27cd9f68f -VITE_API_KEY=your-api-key-here - -# Dyte API Base URL -VITE_BASE_URL=realtime.cloudflare.com - +# Browser-exposed environment only. Do not put API credentials in VITE_* vars. VITE_ENV=production diff --git a/demo-app/.gitignore b/demo-app/.gitignore index f71e499..de3464c 100644 --- a/demo-app/.gitignore +++ b/demo-app/.gitignore @@ -33,4 +33,5 @@ html-examples *.tsbuildinfo .env.production -.env.staging \ No newline at end of file +.env.staging +.dev.vars diff --git a/demo-app/AGENTS.md b/demo-app/AGENTS.md index 68233e8..e323fb6 100644 --- a/demo-app/AGENTS.md +++ b/demo-app/AGENTS.md @@ -22,6 +22,7 @@ demo-app/ │ └── app.ts # Cloudflare Worker entry point (SSR) ├── worker-configuration.d.ts # Auto-generated Wrangler env bindings — do not edit manually ├── .env.example +├── .dev.vars.example ├── vite.config.ts └── tsconfig.cloudflare.json # Primary TS config; includes .react-router/types/**/* ``` @@ -52,7 +53,7 @@ demo-app/ ## COMMANDS ```bash -pnpm dev # Local dev (requires .env with valid VITE_ORG_ID + VITE_API_KEY) +pnpm dev # Local dev (requires .dev.vars with valid REALTIMEKIT_ORG_ID + REALTIMEKIT_API_KEY) pnpm build # Production build pnpm deploy # Build + wrangler deploy pnpm typecheck # wrangler types + react-router typegen + tsc @@ -61,7 +62,7 @@ pnpm cf-typegen # Regenerate worker-configuration.d.ts ## NOTES -- `.env.example` includes a real shared demo `VITE_ORG_ID` — intentional for preset compatibility -- `VITE_BASE_URL=dyte.io` in `.env.example` is a legacy URL — verify actual endpoint in CI secrets +- `.env.example` is browser-exposed and must not include API credentials +- `.dev.vars.example` documents local Worker runtime credentials for meeting creation - `demo-app/README.md` is blank ("TODO") — this file is the authoritative reference -- Stack: React 18 + React Router 7 + Tailwind CSS v4 + `@cloudflare/vite-plugin` + Wrangler \ No newline at end of file +- Stack: React 18 + React Router 7 + Tailwind CSS v4 + `@cloudflare/vite-plugin` + Wrangler diff --git a/demo-app/README.md b/demo-app/README.md index 042035f..60511a5 100644 --- a/demo-app/README.md +++ b/demo-app/README.md @@ -21,20 +21,21 @@ This application lists, filters, and launches all SDK examples. It creates meeti ### Environment Setup -Copy `.env.example` to `.env` and fill in your credentials: +Copy `.env.example` to `.env` for browser-safe Vite values, and copy `.dev.vars.example` to `.dev.vars` for Worker runtime credentials: ```bash cp .env.example .env +cp .dev.vars.example .dev.vars ``` Required environment variables: | Variable | Description | |----------|-------------| -| `VITE_ORG_ID` | Your RealtimeKit organization ID | -| `VITE_API_KEY` | Your RealtimeKit API key | -| `VITE_BASE_URL` | RealtimeKit API base URL (e.g., `https://api.cloudflare.com/client/v2` or legacy `https://api.dyte.io/v2`) | | `VITE_ENV` | Environment identifier (e.g., `staging`, `production`) | +| `REALTIMEKIT_ORG_ID` | Your RealtimeKit organization ID, stored in `.dev.vars` locally or Worker secrets in deploys | +| `REALTIMEKIT_API_KEY` | Your RealtimeKit API key, stored in `.dev.vars` locally or Worker secrets in deploys | +| `REALTIMEKIT_BASE_URL` | RealtimeKit API base hostname (e.g., `realtime.cloudflare.com`) | ### Development @@ -42,7 +43,7 @@ Required environment variables: # Install dependencies pnpm install -# Start dev server (requires .env with valid credentials) +# Start dev server (requires .dev.vars with valid credentials) pnpm dev ``` @@ -79,7 +80,8 @@ demo-app/ ├── worker-configuration.d.ts # Auto-generated Wrangler env bindings ├── vite.config.ts ├── wrangler.jsonc -└── .env.example +├── .env.example +└── .dev.vars.example ``` ## Adding Examples to the Gallery @@ -129,7 +131,7 @@ The gallery catalog is **hardcoded** (not auto-discovered). To add a new example - **Type errors after route changes**: Run `pnpm typecheck` to regenerate `.react-router/types/` - **Wrangler env binding errors**: Run `pnpm cf-typegen` to update `worker-configuration.d.ts` -- **Meeting creation fails**: Verify your `.env` contains valid `VITE_ORG_ID` and `VITE_API_KEY` +- **Meeting creation fails**: Verify your `.dev.vars` contains valid `REALTIMEKIT_ORG_ID` and `REALTIMEKIT_API_KEY` ## Related diff --git a/demo-app/app/api/index.ts b/demo-app/app/api/index.ts deleted file mode 100644 index 478aef6..0000000 --- a/demo-app/app/api/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { createMeeting } from "./meeting"; -import { addParticipant } from "./participant"; - -export { - createMeeting, - addParticipant -} \ No newline at end of file diff --git a/demo-app/app/api/meeting.ts b/demo-app/app/api/meeting.ts index 856e487..d340937 100644 --- a/demo-app/app/api/meeting.ts +++ b/demo-app/app/api/meeting.ts @@ -1,34 +1,27 @@ -export const createMeeting = async ({ - meetingName, - recordOnStart, - aiSummary -}: { - meetingName: string; - recordOnStart: boolean; - aiSummary: boolean; -}) => { - const apiKey = import.meta.env.VITE_API_KEY; - const orgId = import.meta.env.VITE_ORG_ID; - const baseUrl = `https://api.${import.meta.env.VITE_BASE_URL}/v2`; - const authHeader = btoa(`${orgId}:${apiKey}`); - const response = await fetch(`${baseUrl}/meetings`, { - method: "POST", - headers: { - "Authorization": `Basic ${authHeader}`, - "Content-Type": "application/json" - }, - body: JSON.stringify({ - title: meetingName ?? "RealtimeKit Meeting", - record_on_start: recordOnStart, - summarize_on_end: aiSummary - }) - }) - - const data = await response.json() as { message: string, data: { id: string } }; - - if (!response.ok) { - throw new Error(data.message || `API request failed with status ${response.status}`); - } - - return data; -} +import type { ActionFunctionArgs } from "react-router"; +import { apiError, getAuthHeaders, getRealtimeKitConfig } from "./realtimekit.server"; + +export const action = async ({ context, request }: ActionFunctionArgs) => { + try { + const { meetingName, recordOnStart, aiSummary } = await request.json() as { + meetingName: string; + recordOnStart: boolean; + aiSummary: boolean; + }; + const config = getRealtimeKitConfig(context); + const response = await fetch(`${config.baseUrl}/meetings`, { + method: "POST", + headers: getAuthHeaders(config), + body: JSON.stringify({ + title: meetingName ?? "RealtimeKit Meeting", + record_on_start: recordOnStart, + summarize_on_end: aiSummary, + }), + }); + const data = await response.json(); + + return Response.json(data, { status: response.status }); + } catch (error) { + return apiError(error); + } +}; diff --git a/demo-app/app/api/participant.ts b/demo-app/app/api/participant.ts index 67dfc8b..13942c3 100644 --- a/demo-app/app/api/participant.ts +++ b/demo-app/app/api/participant.ts @@ -1,34 +1,32 @@ -export const addParticipant = async ({ - name, - meetingId, - presetName -}: { - name: string; - meetingId: string; - presetName: string; -}) => { - const apiKey = import.meta.env.VITE_API_KEY; - const orgId = import.meta.env.VITE_ORG_ID; - const baseUrl = `https://api.${import.meta.env.VITE_BASE_URL}/v2` - const authHeader = btoa(`${orgId}:${apiKey}`); - const response = await fetch(`${baseUrl}/meetings/${meetingId}/participants`, { - method: "POST", - headers: { - "Authorization": `Basic ${authHeader}`, - "Content-Type": "application/json" - }, - body: JSON.stringify({ - name, - preset_name: presetName, - custom_participant_id: `${(Math.random() * 1000).toString()}-${name}` - }) - }) - - const data = await response.json() as { message: string, data: { token: string } }; - - if (!response.ok) { - throw new Error(data.message || `API request failed with status ${response.status}`); +import type { ActionFunctionArgs } from "react-router"; +import { apiError, getAuthHeaders, getRealtimeKitConfig } from "./realtimekit.server"; + +export const action = async ({ context, request }: ActionFunctionArgs) => { + try { + const { name, meetingId, presetName } = await request.json() as { + name: string; + meetingId: string; + presetName: string; + }; + const normalizedMeetingId = meetingId?.trim(); + if (!normalizedMeetingId || !/^[A-Za-z0-9_-]+$/.test(normalizedMeetingId)) { + return Response.json({ message: "Invalid meetingId" }, { status: 400 }); } - - return data; -} + + const config = getRealtimeKitConfig(context); + const response = await fetch(`${config.baseUrl}/meetings/${encodeURIComponent(normalizedMeetingId)}/participants`, { + method: "POST", + headers: getAuthHeaders(config), + body: JSON.stringify({ + name, + preset_name: presetName, + custom_participant_id: `${(Math.random() * 1000).toString()}-${name}`, + }), + }); + const data = await response.json(); + + return Response.json(data, { status: response.status }); + } catch (error) { + return apiError(error); + } +}; diff --git a/demo-app/app/api/preset.ts b/demo-app/app/api/preset.ts index 2e38679..b63276f 100644 --- a/demo-app/app/api/preset.ts +++ b/demo-app/app/api/preset.ts @@ -1,32 +1,24 @@ -export const getPresets = async () => { - const apiKey = import.meta.env.VITE_API_KEY; - const orgId = import.meta.env.VITE_ORG_ID; - const baseUrl = `https://api.${import.meta.env.VITE_BASE_URL}/v2`; - const authHeader = btoa(`${orgId}:${apiKey}`); - const response = await fetch(`${baseUrl}/presets`, { - method: "GET", - headers: { - "Authorization": `Basic ${authHeader}`, - "Content-Type": "application/json" - } - }) - const data = await response.json() as { - success: boolean, - data: { - created_at: string; - updated_at: string; - name: string; - id: string; - }[], - paging: { - end_offset: number, - start_offset: number, - total_count: number, - }}; +import type { LoaderFunctionArgs } from "react-router"; +import { apiError, getAuthHeaders, getRealtimeKitConfig } from "./realtimekit.server"; - if (!response.ok) { - throw new Error(`API request failed with status ${response.status}`); - } +export const loader = async ({ context }: LoaderFunctionArgs) => { + try { + const config = getRealtimeKitConfig(context); + const response = await fetch(`${config.baseUrl}/presets`, { + method: "GET", + headers: getAuthHeaders(config), + }); + const data = await response.json() as { + data: { + created_at: string; + updated_at: string; + name: string; + id: string; + }[]; + }; - return data.data; -}; \ No newline at end of file + return Response.json(response.ok ? data.data : data, { status: response.status }); + } catch (error) { + return apiError(error); + } +}; diff --git a/demo-app/app/api/realtimekit.server.ts b/demo-app/app/api/realtimekit.server.ts new file mode 100644 index 0000000..753731f --- /dev/null +++ b/demo-app/app/api/realtimekit.server.ts @@ -0,0 +1,45 @@ +import type { AppLoadContext } from "react-router"; + +type RuntimeEnv = Record; + +type RealtimeKitConfig = { + apiKey: string; + orgId: string; + baseUrl: string; +}; + +const getRuntimeEnv = (context: AppLoadContext): RuntimeEnv => { + return context.cloudflare.env as unknown as RuntimeEnv; +}; + +export const getRealtimeKitConfig = (context: AppLoadContext) => { + const env = getRuntimeEnv(context); + const apiKey = env.REALTIMEKIT_API_KEY; + const orgId = env.REALTIMEKIT_ORG_ID; + const baseUrl = env.REALTIMEKIT_BASE_URL ?? "realtime.cloudflare.com"; + + if (!apiKey || !orgId) { + throw new Error("RealtimeKit credentials are not configured"); + } + + return { + apiKey, + orgId, + baseUrl: `https://api.${baseUrl}/v2`, + }; +}; + +export const getAuthHeaders = ({ apiKey, orgId }: RealtimeKitConfig) => { + const authHeader = btoa(`${orgId}:${apiKey}`); + + return { + Authorization: `Basic ${authHeader}`, + "Content-Type": "application/json", + }; +}; + +export const apiError = (error: unknown) => { + console.error("RealtimeKit API route failed", error); + + return Response.json({ message: "Request failed" }, { status: 500 }); +}; diff --git a/demo-app/app/pages/meeting.tsx b/demo-app/app/pages/meeting.tsx index 5ab83b4..e79b6b7 100644 --- a/demo-app/app/pages/meeting.tsx +++ b/demo-app/app/pages/meeting.tsx @@ -1,19 +1,67 @@ import { useEffect, useMemo, useState } from "react"; import { useLocation } from "react-router"; -import { createMeeting } from "~/api"; -import { getPresets } from "~/api/preset"; import { Icon } from "~/components/icons"; import type { Usecase } from "~/context"; import { useSharedState } from "~/context/hook"; import { generateUrl, getGuestPreset, getPresetName } from "~/utils/utils"; +type Preset = { + created_at: string; + updated_at: string; + name: string; + id: string; +}; + +type CreateMeetingResponse = { + message?: string; + data: { id: string }; +}; + +const createMeeting = async ({ + meetingName, + recordOnStart, + aiSummary, +}: { + meetingName: string; + recordOnStart: boolean; + aiSummary: boolean; +}) => { + const response = await fetch("/api/meetings", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ meetingName, recordOnStart, aiSummary }), + }); + const data = await response.json() as CreateMeetingResponse; + + if (!response.ok) { + throw new Error(data.message || `API request failed with status ${response.status}`); + } + + return data; +}; + +const getPresets = async () => { + const response = await fetch("/api/presets"); + const data = await response.json() as Preset[] | { message?: string }; + + if (!response.ok) { + throw new Error( + "message" in data && data.message + ? data.message + : `API request failed with status ${response.status}`, + ); + } + + return data as Preset[]; +}; const fromBase64Url = (input: string) => { - const b64 = input.replace(/-/g, "+").replace(/_/g, "/") + "===".slice((input.length + 3) % 4); + const b64 = + input.replace(/-/g, "+").replace(/_/g, "/") + + "===".slice((input.length + 3) % 4); const json = decodeURIComponent(escape(atob(b64))); return json; -} - +}; const env = import.meta.env.VITE_ENV; const defaultBaseURL = @@ -21,13 +69,12 @@ const defaultBaseURL = ? "react-examples.realtime.cloudflare.com" : "react-examples.staging.realtime.cloudflare.com"; - const defaultPayload = { - name: 'default-meeting-ui', - framework: 'react', - usecase: 'video', - url: `https://${defaultBaseURL}/default-meeting-ui`, -} + name: "plugins", + framework: "react", + usecase: "all", + url: `https://${defaultBaseURL}/plugins`, +}; type Mode = "create" | "join"; type LoadingState = "loaded" | "loading" | "errored"; @@ -43,9 +90,9 @@ const Meeting = () => { const [authURL, setAuthURL] = useState(null); const [loading, setLoading] = useState(false); const [loadingState, setLoadingState] = useState("loading"); - const [presets, setPresets] = useState>>([]); + const [presets, setPresets] = useState([]); - const {url, preset, name} = useMemo(() => { + const { url, preset, name } = useMemo(() => { const searchParams = new URLSearchParams(location.search); const stateParam = searchParams.get("state"); let presetParam = searchParams.get("preset"); @@ -55,12 +102,19 @@ const Meeting = () => { payload = JSON.parse(fromBase64Url(stateParam)); } if (!presetParam) { - presetParam = mode === "create" ? getPresetName(payload.usecase as Usecase) : getGuestPreset(payload.usecase as Usecase) as string; + presetParam = + mode === "create" + ? getPresetName(payload.usecase as Usecase) + : (getGuestPreset(payload.usecase as Usecase) as string); } return { - ...payload, - preset: presetParam, - name: payload.name.replaceAll("-", " ").split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ") + ...payload, + preset: presetParam, + name: payload.name + .replaceAll("-", " ") + .split(" ") + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(" "), }; }, [location.search]); @@ -73,7 +127,7 @@ const Meeting = () => { setAuthURL(`${url}?authToken=${authToken}`); } setLoading(false); - }, [url, location.search]) + }, [url, location.search]); const [form, setForm] = useState({ recordOnStart: false, @@ -86,18 +140,17 @@ const Meeting = () => { useEffect(() => { const searchParams = new URLSearchParams(location.search); - const meetingId = searchParams.get('meetingId'); + const meetingId = searchParams.get("meetingId"); if (meetingId) { - setForm(prev => ({ ...prev, meetingId })); + setForm((prev) => ({ ...prev, meetingId })); setMode("join"); } }, [location.search]); useEffect(() => { if (!preset) return; - setForm((prev) => ({ ...prev, preset })) - }, [preset]) - + setForm((prev) => ({ ...prev, preset })); + }, [preset]); const isValid = useMemo(() => { if (mode === "create") return form.yourName.trim().length > 0; @@ -107,7 +160,7 @@ const Meeting = () => { const joinMeeting = async () => { setLoading(true); let meetingId = form.meetingId; - if (!meetingId || mode === 'create') { + if (!meetingId || mode === "create") { const meeting = await createMeeting({ meetingName: form.meetingName, recordOnStart: form.recordOnStart, @@ -116,60 +169,63 @@ const Meeting = () => { meetingId = meeting.data.id; } const searchParams = new URLSearchParams(location.search); - searchParams.set('meetingId', meetingId); - window.history.replaceState(null, '', `${location.pathname}?${searchParams.toString()}`); + searchParams.set("meetingId", meetingId); + window.history.replaceState( + null, + "", + `${location.pathname}?${searchParams.toString()}`, + ); const _url = await generateUrl({ name: form.yourName, meetingId, presetName: form.preset, sampleName: name, - url + url, }); setLoading(false); setAuthURL(_url); }; useEffect(() => { - getPresets() - .then((resp) => { - setPresets(resp); - }) + getPresets().then((resp) => { + setPresets(resp); + }); }, []); - - if (authURL) { - return
- {loadingState === "loading" && ( -
-
-            Loading...
-          
-
- )} -