From 60417ed307f6bedb82ecbb9702331b79e2b6249f Mon Sep 17 00:00:00 2001 From: Ravindra Singh Rathor Date: Thu, 28 May 2026 18:07:48 +0530 Subject: [PATCH 01/20] ci(release): removed buggy base url default replacement --- html-examples/scripts/set-env.sh | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/html-examples/scripts/set-env.sh b/html-examples/scripts/set-env.sh index 1769f4b..e6cda09 100755 --- a/html-examples/scripts/set-env.sh +++ b/html-examples/scripts/set-env.sh @@ -3,15 +3,17 @@ EXAMPLES_DIR="examples" -# Read from .env file in html-examples root -ENV_FILE="$(dirname "$0")/../.env" -if [ -f "$ENV_FILE" ]; then - BASE_URL=$(grep '^VITE_BASE_URL=' "$ENV_FILE" | cut -d= -f2-) +# Priority: env var > .env file > default +if [ -n "${VITE_BASE_URL:-}" ]; then + BASE_URL="$VITE_BASE_URL" +else + ENV_FILE="$(dirname "$0")/../.env" + if [ -f "$ENV_FILE" ]; then + BASE_URL=$(grep -m1 '^VITE_BASE_URL=' "$ENV_FILE" | sed 's/^VITE_BASE_URL=//') + fi + BASE_URL="${BASE_URL:-realtime.cloudflare.com}" fi -# Fallback to env var or default -BASE_URL="${BASE_URL:-${VITE_BASE_URL:-https://realtime.cloudflare.com}}" - echo "Replacing import.meta.env.VITE_BASE_URL with '${BASE_URL}' in source HTML files..." find "$EXAMPLES_DIR" -name "*.html" -type f | while read html_file; do From 81c5a4d7107e07f4f31340a94f4cb83f799e75e6 Mon Sep 17 00:00:00 2001 From: Ravindra Singh Rathor Date: Wed, 3 Jun 2026 15:14:00 +0530 Subject: [PATCH 02/20] fix(react-build): fixed typescript issue with react-draggable, hardcoded version --- pnpm-lock.yaml | 17 +++++++++++++++-- react-examples/examples/facetime/package.json | 2 +- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b656ee0..f156be6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -919,8 +919,8 @@ importers: specifier: ^18.2.0 version: 18.3.1(react@18.3.1) react-draggable: - specifier: ^4.4.5 - version: 4.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: 4.4.5 + version: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-feather: specifier: ^2.0.10 version: 2.0.10(react@18.3.1) @@ -7148,6 +7148,12 @@ packages: peerDependencies: react: ^18.3.1 + react-draggable@4.4.5: + resolution: {integrity: sha512-OMHzJdyJbYTZo4uQE393fHcqqPYsEtkjfMgvCHr6rejT+Ezn4OZbNyGH50vv+SunC1RMvwOTSWkEODQLzw1M9g==} + peerDependencies: + react: '>= 16.3.0' + react-dom: '>= 16.3.0' + react-draggable@4.5.0: resolution: {integrity: sha512-VC+HBLEZ0XJxnOxVAZsdRi8rD04Iz3SiiKOoYzamjylUcju/hP9np/aZdLHf/7WOD268WMoNJMvYfB5yAK45cw==} peerDependencies: @@ -14762,6 +14768,13 @@ snapshots: react: 18.3.1 scheduler: 0.23.2 + react-draggable@4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + clsx: 1.2.1 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-draggable@4.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: clsx: 2.1.1 diff --git a/react-examples/examples/facetime/package.json b/react-examples/examples/facetime/package.json index 0a23f15..e1a6fbd 100644 --- a/react-examples/examples/facetime/package.json +++ b/react-examples/examples/facetime/package.json @@ -13,7 +13,7 @@ "clsx": "^1.2.1", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-draggable": "^4.4.5", + "react-draggable": "4.4.5", "react-feather": "^2.0.10", "@cloudflare/realtimekit": "^1.5.1", "@cloudflare/realtimekit-ui": "^1.2.0", From de7252e1ba03c4c9729f54b2614ea0c2369b40c0 Mon Sep 17 00:00:00 2001 From: Ravindra Singh Rathor Date: Wed, 3 Jun 2026 15:43:08 +0530 Subject: [PATCH 03/20] fix(active-speaker-ui): removed default plugin support --- .../src/components/Controlbar.tsx | 8 +--- .../src/components/MainArea.tsx | 44 +------------------ .../active-speaker-ui/src/lib/const.ts | 16 ------- 3 files changed, 4 insertions(+), 64 deletions(-) diff --git a/react-examples/examples/active-speaker-ui/src/components/Controlbar.tsx b/react-examples/examples/active-speaker-ui/src/components/Controlbar.tsx index 61de0cb..cc46292 100644 --- a/react-examples/examples/active-speaker-ui/src/components/Controlbar.tsx +++ b/react-examples/examples/active-speaker-ui/src/components/Controlbar.tsx @@ -11,8 +11,8 @@ import { RtkPluginsToggle, RtkControlbarButton, } from '@cloudflare/realtimekit-react-ui'; -import { useRealtimeKitMeeting, useRealtimeKitSelector } from '@cloudflare/realtimekit-react'; -import HOST_PRESET, { iconPack, saveWhiteboard, WHITEBOARD_ID } from '../lib/const'; +import { useRealtimeKitMeeting } from '@cloudflare/realtimekit-react'; +import HOST_PRESET, { iconPack } from '../lib/const'; export default function Controlbar() { const { meeting } = useRealtimeKitMeeting(); @@ -22,16 +22,12 @@ export default function Controlbar() { const isDarkMode = useMeetingStore((s) => s.darkMode); const toggleDarkMode = useMeetingStore((s) => s.toggleDarkMode); const setStates = useMeetingStore((s) => s.setStates); - const whiteboardPlugin = useRealtimeKitSelector(m => m.plugins.active.get(WHITEBOARD_ID)) const buttonSize = size === 'lg' ? 'lg' : 'sm'; const isHost = meeting.self.presetName === HOST_PRESET; const leaveMeeting = async () => { - if (whiteboardPlugin?.active) { - await saveWhiteboard(whiteboardPlugin); - } setStates({ activeLeaveConfirmation: true, }) diff --git a/react-examples/examples/active-speaker-ui/src/components/MainArea.tsx b/react-examples/examples/active-speaker-ui/src/components/MainArea.tsx index 25ba34b..46cb292 100644 --- a/react-examples/examples/active-speaker-ui/src/components/MainArea.tsx +++ b/react-examples/examples/active-speaker-ui/src/components/MainArea.tsx @@ -12,7 +12,7 @@ import { useRealtimeKitMeeting, useRealtimeKitSelector } from '@cloudflare/realt import type { RTKParticipant, RTKPlugin, RTKSelf } from '@cloudflare/realtimekit'; import clsx from 'clsx'; import { useState, useEffect, useRef } from 'react'; -import HOST_PRESET, { iconPack, saveWhiteboard, WHITEBOARD_ID } from '../lib/const'; +import HOST_PRESET, { iconPack } from '../lib/const'; type ActiveTab = | { type: 'plugin'; plugin: RTKPlugin } @@ -34,12 +34,9 @@ function ActiveSpeakerView({ const showTabBar = screenshares.length + plugins.length > 1; const size = useMeetingStore((s) => s.size); - const whiteboardPlugin = useRealtimeKitSelector(m => m.plugins.active.get(WHITEBOARD_ID)) const [states, setStates] = useMeetingStore((s) => [s.states, s.setStates]); const activeTab = useRealtimeKitSelector((m) => m.meta.selfActiveTab); - const isHost = meeting.self.presetName === HOST_PRESET; - const isDarkMode = useMeetingStore((s) => s.darkMode); useEffect(() => { if (activeTab) { @@ -129,45 +126,8 @@ function ActiveSpeakerView({ setSelectedTab(tab); }; - const setConfig = () => { - const hostId = - isHost - ? meeting.self.id - : meeting.participants.joined.toArray().find(x => x.presetName === HOST_PRESET)?.id; - - whiteboardPlugin?.sendData({ - eventName: 'config', - data: { - eventName: 'config', - follow: hostId, - role: isHost ? 'editor' : 'viewer', - infiniteCanvas: false, - darkMode: isDarkMode, - exportMode: 'pdf', - } - }) - } - - // NOTE(ishita1805): Set whiteboard config on launch - useEffect(() => { - if (!whiteboardPlugin) return; - setConfig(); - whiteboardPlugin.on('ready', setConfig); - return () => { - whiteboardPlugin.off('ready', setConfig); - } - }, [whiteboardPlugin]) - - // NOTE(ishita1805): Update whiteboard config when dark mode is toggled - useEffect(() => { - setConfig(); - }, [isDarkMode]) - // NOTE(ishita1805): Save whiteboard before closing const closePlugin = async (plugin: RTKPlugin) => { - if (plugin.id === whiteboardPlugin?.id) { - await saveWhiteboard(whiteboardPlugin); - } plugin.deactivate(); } @@ -208,7 +168,7 @@ function ActiveSpeakerView({ key={plugin.id} onClick={() => setActiveTab({ type: 'plugin', plugin })} > - + {plugin.name} ))} diff --git a/react-examples/examples/active-speaker-ui/src/lib/const.ts b/react-examples/examples/active-speaker-ui/src/lib/const.ts index 1588181..57bd9b1 100644 --- a/react-examples/examples/active-speaker-ui/src/lib/const.ts +++ b/react-examples/examples/active-speaker-ui/src/lib/const.ts @@ -1,25 +1,9 @@ import { defaultIconPack } from '@cloudflare/realtimekit-react-ui'; -import type { RTKPlugin } from '@cloudflare/realtimekit'; const HOST_PRESET = 'Video Call Host Demo'; -export const WHITEBOARD_ID = "ae79b269-24ca-4f8a-8112-f96084c8c19a"; export const iconPack = { ...defaultIconPack, 'light': '', 'dark': '', join_stage: '', } -export const saveWhiteboard = async (whiteboardPlugin: RTKPlugin | undefined) => { - if (!whiteboardPlugin) return; - await new Promise((resolve) => { - whiteboardPlugin.removeAllListeners('board-saved' as any); - whiteboardPlugin.on('board-saved' as any, (data: any) => { - console.log('board saved:', data); - resolve(true); - }) - whiteboardPlugin.sendData({ - eventName: 'save-board', - data: { eventName: 'save-board' } - }) - }); - } export default HOST_PRESET; From 47496603a9fd3e9c07b3e3b9a1c63824c5067890 Mon Sep 17 00:00:00 2001 From: Mayank Sharma Date: Fri, 5 Jun 2026 16:49:46 +0530 Subject: [PATCH 04/20] fix: remove supportsConnectedMeetings references from react-examples --- .../src/rtk-state-listeners.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/react-examples/examples/screenshare-focused-ui-with-custom-addons/src/rtk-state-listeners.tsx b/react-examples/examples/screenshare-focused-ui-with-custom-addons/src/rtk-state-listeners.tsx index f3bee7e..d777175 100644 --- a/react-examples/examples/screenshare-focused-ui-with-custom-addons/src/rtk-state-listeners.tsx +++ b/react-examples/examples/screenshare-focused-ui-with-custom-addons/src/rtk-state-listeners.tsx @@ -96,11 +96,7 @@ export class RtkStateListenersUtils{ this.meeting.self.addListener('roomLeft', this.roomLeftListener); this.meeting.self.addListener('mediaPermissionUpdate', this.mediaPermissionUpdateListener); this.meeting.stage.addListener('stageRequestApproved', this.joinStateAcceptedListener); - - if (this.meeting.connectedMeetings.supportsConnectedMeetings) { - this.meeting.connectedMeetings.once('changingMeeting', this.handleChangingMeeting); - } - + this.meeting.connectedMeetings.once('changingMeeting', this.handleChangingMeeting); } cleanupRtkEventListeners(){ From 6aa3a3ecbe6817ebc3a2a38999513e555601ae1f Mon Sep 17 00:00:00 2001 From: ikabra Date: Mon, 8 Jun 2026 17:58:50 +0530 Subject: [PATCH 05/20] feat: add a sample with a whiteboard custom plugin --- demo-app/app/pages/meeting.tsx | 288 +++++++++++------- demo-app/app/utils/react.ts | 12 + demo-app/worker-configuration.d.ts | 3 +- react-examples/examples/plugins/.gitignore | 23 ++ react-examples/examples/plugins/index.html | 12 + react-examples/examples/plugins/package.json | 27 ++ react-examples/examples/plugins/src/App.tsx | 77 +++++ react-examples/examples/plugins/src/index.css | 0 react-examples/examples/plugins/src/main.tsx | 9 + .../examples/plugins/src/vite-env.d.ts | 1 + react-examples/examples/plugins/tsconfig.json | 21 ++ .../examples/plugins/tsconfig.node.json | 9 + .../examples/plugins/vite.config.ts | 8 + 13 files changed, 371 insertions(+), 119 deletions(-) create mode 100644 react-examples/examples/plugins/.gitignore create mode 100644 react-examples/examples/plugins/index.html create mode 100644 react-examples/examples/plugins/package.json create mode 100644 react-examples/examples/plugins/src/App.tsx create mode 100644 react-examples/examples/plugins/src/index.css create mode 100644 react-examples/examples/plugins/src/main.tsx create mode 100644 react-examples/examples/plugins/src/vite-env.d.ts create mode 100644 react-examples/examples/plugins/tsconfig.json create mode 100644 react-examples/examples/plugins/tsconfig.node.json create mode 100644 react-examples/examples/plugins/vite.config.ts diff --git a/demo-app/app/pages/meeting.tsx b/demo-app/app/pages/meeting.tsx index 5ab83b4..10c56ce 100644 --- a/demo-app/app/pages/meeting.tsx +++ b/demo-app/app/pages/meeting.tsx @@ -7,13 +7,13 @@ import type { Usecase } from "~/context"; import { useSharedState } from "~/context/hook"; import { generateUrl, getGuestPreset, getPresetName } from "~/utils/utils"; - 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 +21,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 +42,11 @@ 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< + Awaited> + >([]); - 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 +56,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 +81,7 @@ const Meeting = () => { setAuthURL(`${url}?authToken=${authToken}`); } setLoading(false); - }, [url, location.search]) + }, [url, location.search]); const [form, setForm] = useState({ recordOnStart: false, @@ -86,18 +94,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 +114,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 +123,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...
-          
-
- )} -