@naviedu/room-platform-react
v0.0.64
Published
Embeddable React UI and runtime for NaviEdu Room Platform rooms. The package owns the default room experience, LiveKit connection, media controls, speaker requests, private rooms, responsive layout, and optional chat and whiteboard surfaces.
Readme
@naviedu/room-platform-react
Embeddable React UI and runtime for NaviEdu Room Platform rooms. The package owns the default room experience, LiveKit connection, media controls, speaker requests, private rooms, responsive layout, and optional chat and whiteboard surfaces.
Requirements
- React 18 or 19
- A Room Platform launch ticket issued by the host backend
- A public Room Platform API base URL
- A browser running in a secure context when camera, microphone, or screen sharing is required
The launch ticket is short-lived and must be obtained server-side. Do not put backend credentials or long-lived signing secrets in the browser bundle.
Installation
npm install @naviedu/room-platform-reactreact and react-dom are peer dependencies. Room Platform's published
runtime dependencies are installed automatically with the package.
Basic usage
Render RoomPlatformRoom with the launch ticket returned by your backend:
'use client'
import { type RoomLaunchTicketRenewalReason, RoomPlatformRoom } from '@naviedu/room-platform-react'
type RoomTicketResponse = { launchToken: string }
export function Room({ launchToken }: { launchToken: string }) {
async function renewLaunchTicket(_reason: RoomLaunchTicketRenewalReason): Promise<RoomTicketResponse> {
const response = await fetch('/api/room-platform/launch-ticket', {
method: 'POST',
credentials: 'include',
})
if (!response.ok) throw new Error('Unable to renew the Room Platform launch ticket')
return (await response.json()) as RoomTicketResponse
}
return (
<RoomPlatformRoom
apiBaseUrl="https://api.example.com/room-platform"
launchToken={launchToken}
onLaunchTicketRequired={renewLaunchTicket}
onExit={() => window.history.back()}
/>
)
}RoomPlatformRoom renders its default room UI in an isolated Shadow Root and installs the package stylesheet itself. Do not import @naviedu/room-platform-react/styles.css for the default component.
RoomPlatformProvider is advanced composition only; a consumer using it instead of RoomPlatformRoom owns its own DOM and stylesheet boundary.
apiBaseUrl is the URL prefix before /rooms. The package calls these routes
relative to it:
POST /rooms/exchangeGET /rooms/:roomId/control-statePOST /rooms/:roomId/actions
For example, with apiBaseUrl="https://api.example.com/room-platform", the
exchange request is sent to
https://api.example.com/room-platform/rooms/exchange.
The package renders a full viewport room (100dvh). Mount it in a page or
layout that allows the room to occupy the viewport.
RoomPlatformRoom props
| Prop | Required | Description |
| ------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| apiBaseUrl | Yes | Room Platform API prefix. Do not append /rooms. |
| launchToken | Yes | Initial server-issued launch ticket. |
| onLaunchTicketRequired | Yes | Returns { launchToken } when the current ticket or access session must be renewed. |
| extensions | No | Host-provided top-bar, workspace, sidebar, monitor, and participant extensions. |
| primaryCameraPlacement | No | overlay (default) keeps the practice camera over the workspace; sidebar renders the local primary camera in the Room Platform sidebar. |
| onError | No | Receives connection, media, and action errors. |
| onLifecycleEvent | No | Receives exchange, renewal, and reconnect lifecycle events. |
| onExit | No | Called when the user chooses to leave the room. |
| children | No | Replaces the default workspace while keeping the provider and room runtime. |
The default teaching stage selects the available participant camera carrying
the canonical room:moderate capability. Screen share remains primary and the
moderator camera becomes its overlay. If no moderator camera is available, the
stage keeps its waiting state and never promotes a student camera. The local
sidebar preview is hidden only when that selected moderator camera is local;
its existing desktop breakpoint remains unchanged.
The public room keeps one canonical local camera track. It may be preview-only
before publication, and preview visibility never grants media:publish_video.
Camera placement does not change camera permissions, publication, or LiveKit
track lifecycle.
Error and lifecycle callbacks
<RoomPlatformRoom
// ...required props
onError={({ error, recoverable, scope }) => {
console.error(`[room-platform:${scope}]`, error)
if (!recoverable) {
// Show the host application's unavailable-room state.
}
}}
onLifecycleEvent={(event) => {
// Send telemetry to the host backend if needed.
console.info('Room Platform lifecycle:', event)
}}
/>Lifecycle events are exchange.succeeded, exchange.failed,
renewal.succeeded, renewal.failed, reconnect.succeeded, and
reconnect.failed.
Chat and whiteboard boundary
Chat and Whiteboard are owned by the Room Platform exchange. The host only
passes the launch ticket and public Room Platform API base URL shown above;
there is no public integrations prop.
After the launch ticket exchange succeeds and LiveKit connects, the package hydrates the Chat and Whiteboard projection returned by Room Platform. The projection contains only the short-lived, already-authorized runtime data needed by the package. On renewal, the complete projection is replaced.
Each integration degrades independently. An available: false Chat or
Whiteboard projection disables only that surface; LiveKit, the other
integration, and private-room invite, accept, and media flows remain usable.
LiveKit exchange/connection failure is the only fatal case for mounting the
room integrations.
Consumers must not provide or derive Chat/Whiteboard credentials, internal or service endpoints, integration room IDs, or permission objects. Do not expose app/HMAC secrets or call Chat/Whiteboard services directly from the host. The Room Platform exchange is the trust boundary and supplies the public runtime projection after server-side authorization.
Whiteboard uses the exchanged Room Platform roomId for its room context and
is created lazily only when Whiteboard is available and a screen share starts.
Stopping a share deactivates its board; every new share receives a new
shareSessionKey. No board is mounted before sharing begins.
Extensions
Extensions keep host-specific UI outside the shared room implementation. Every
extension receives RoomPlatformExtensionProps:
Host extensions with independent framework CSS must own their own DOM and stylesheet boundary.
import type { RoomPlatformExtensionProps } from '@naviedu/room-platform-react'
function HostToolbar({ actions, capabilities, connectionStatus, roomId }: RoomPlatformExtensionProps) {
const canModerate = capabilities.includes('room:moderate')
return (
<div>
<span>{connectionStatus}</span>
<span>{roomId}</span>
{canModerate ? (
<button type="button" onClick={() => void actions.perform('speak.request')}>
Request to speak
</button>
) : null}
</div>
)
}
;<RoomPlatformRoom
// ...required props
extensions={{
topBarTrailing: HostToolbar,
sidebarTabs: [{ id: 'materials', title: 'Materials', component: HostMaterialsTab }],
mainWorkspace: {
component: HostWorkspace,
},
}}
/>Available extension slots are:
topBarLeadingandtopBarTrailingmainWorkspacefor the practice workspace overlaysidebarTabsparticipantTabindefaultorreplacemodemonitorwith a host-provided participant roster and eligibility flag
actions.perform sends a Room Platform action and accepts an optional string
payload. Check actions.hasCapability(...) before rendering privileged
controls, and use actions.isPending(...) to disable controls during an
in-flight action.
Advanced composition
Use RoomPlatformProvider and useRoomPlatform when the host needs to render
a custom room surface instead of RoomPlatformRoom's default workspace:
import { RoomPlatformProvider, type RoomPlatformProviderProps, useRoomPlatform } from '@naviedu/room-platform-react'
function CustomRoomSurface() {
const { localMedia, setLocalMediaEnabled, status } = useRoomPlatform()
return (
<button
type="button"
disabled={status !== 'connected'}
onClick={() => void setLocalMediaEnabled('camera', !localMedia.camera)}
>
{localMedia.camera ? 'Turn camera off' : 'Turn camera on'}
</button>
)
}
export function CustomRoom(props: {
apiBaseUrl: string
launchToken: string
onLaunchTicketRequired: RoomPlatformProviderProps['onLaunchTicketRequired']
}) {
return (
<RoomPlatformProvider {...props}>
<CustomRoomSurface />
</RoomPlatformProvider>
)
}Use RoomPlatformRoom unless a custom surface is required. It composes the
provider, LiveKit media runtime, private-room runtime, default stage, controls,
and sidebar for you.
Publishing and local verification
From the repository root:
yarn nx run @naviedu/room-platform-react:buildThe publishable package is written to
dist/libs/features/room-platform-react. Inspect it before publishing:
cd dist/libs/features/room-platform-react
npm pack --dry-run
npm publish --access publicThe package embeds the required shared UI source during bundling, so consumers
do not need private workspace aliases such as @navi/ui or @navi/utils.
