From 1e7cf7bb4a33e8372f8dab017eb98aea31f4e871 Mon Sep 17 00:00:00 2001 From: Ritesh Singh Date: Fri, 6 Mar 2026 01:28:57 +0530 Subject: [PATCH 1/5] docs(ai-docs): add docs for meetings widget --- packages/meetings/ai-docs/AGENTS.md | 287 ++++++++ packages/meetings/ai-docs/ARCHITECTURE.md | 777 ++++++++++++++++++++++ 2 files changed, 1064 insertions(+) create mode 100644 packages/meetings/ai-docs/AGENTS.md create mode 100644 packages/meetings/ai-docs/ARCHITECTURE.md diff --git a/packages/meetings/ai-docs/AGENTS.md b/packages/meetings/ai-docs/AGENTS.md new file mode 100644 index 000000000..f7d150041 --- /dev/null +++ b/packages/meetings/ai-docs/AGENTS.md @@ -0,0 +1,287 @@ +# Meetings Widget + +## Overview + +The Meetings Widget provides a full-featured Webex meeting experience as an embeddable component. It orchestrates three external repositories — `webex-js-sdk` for backend communication, `sdk-component-adapter` for reactive data binding, and `components` for the React UI. + +**Widget:** Meetings + +**Location:** `widgets/packages/meetings/` + +--- + +## Why and What is This Used For? + +### Purpose + +The Meetings Widget lets consuming applications embed a complete meeting experience without building any meeting logic themselves. It handles the entire lifecycle — from SDK initialization through meeting creation, joining, in-meeting controls, and leaving — by composing existing components and adapters together. + +### Key Capabilities + +- **Join Meetings** — Connect to a meeting via URL, SIP address, or Personal Meeting Room +- **Audio Controls** — Mute and unmute microphone with transitional states +- **Video Controls** — Start and stop camera with device switching +- **Screen Sharing** — Share screen, window, or tab with other participants +- **Member Roster** — View list of meeting participants +- **Device Settings** — Switch between cameras, microphones, and speakers +- **Guest/Host Authentication** — Password-protected meetings with host key support +- **Waiting for Host** — Automatic transition when host starts the meeting + +--- + +## Examples and Use Cases + +### Getting Started + +#### Basic Usage (React) + +```jsx +import Webex from 'webex'; +import WebexSDKAdapter from '@webex/sdk-component-adapter'; +import {WebexMeeting, AdapterContext} from '@webex/components'; + +function MeetingsWidget({accessToken, meetingDestination}) { + const [adapter, setAdapter] = useState(null); + const [meetingID, setMeetingID] = useState(null); + + useEffect(() => { + const webex = Webex.init({ + credentials: { access_token: accessToken } + }); + const sdkAdapter = new WebexSDKAdapter(webex); + + sdkAdapter.connect().then(() => { + setAdapter(sdkAdapter); + return sdkAdapter.meetingsAdapter.createMeeting(meetingDestination); + }).then((meeting) => { + setMeetingID(meeting.ID); + }); + + return () => sdkAdapter.disconnect(); + }, [accessToken, meetingDestination]); + + if (!adapter || !meetingID) return
Loading...
; + + return ( + + + + ); +} +``` + +### Common Use Cases + +#### 1. Password-Protected Meeting + +When a meeting requires a password, the `WebexMeeting` component detects `passwordRequired` from the adapter observable and renders the `WebexMeetingGuestAuthentication` modal. The user enters the password, and `JoinControl.action()` passes it to the SDK. + +**Key Points:** + +- `passwordRequired` is a boolean on the adapter meeting observable +- The component handles guest vs host authentication flows +- Wrong password triggers `invalidPassword` flag on the observable + +#### 2. Pre-Join Media Preview + +Before joining, the interstitial screen shows local media preview. The user can mute audio, stop video, or open settings before entering the meeting. + +**Key Points:** + +- `WebexInterstitialMeeting` renders when `state === 'NOT_JOINED'` +- Controls available pre-join: `mute-audio`, `mute-video`, `settings`, `join-meeting` +- `JoinControl.display()` shows a hint like "Unmuted, video on" based on current state + +#### 3. Device Switching Mid-Meeting + +During an active meeting, users can switch cameras, microphones, or speakers through the settings panel. + +**Key Points:** + +- `SettingsControl.action()` opens the `WebexSettings` modal +- `SwitchCameraControl.action({ meetingID, cameraId })` calls `switchCamera(meetingID, cameraId)` on the adapter +- The adapter acquires a new media stream with the selected device and emits an updated `localVideo.stream` + +#### 4. Screen Sharing + +The share button triggers the browser's native screen picker. The SDK handles `getDisplayMedia()` and negotiates the share stream with the backend. + +**Key Points:** + +- `ShareControl` checks `navigator.mediaDevices.getDisplayMedia` availability +- If unsupported, the control renders as DISABLED +- The adapter emits `localShare.stream` with the display stream when sharing starts + +--- + +## Three-Repository Architecture + +```mermaid +graph LR + subgraph "Widget" + W[Meetings Widget] + end + + subgraph "components" + C[WebexMeeting & UI] + end + + subgraph "sdk-component-adapter" + A[MeetingsSDKAdapter] + end + + subgraph "webex-js-sdk" + S[Webex Instance] + end + + W -->|renders| C + W -->|creates| A + W -->|initializes| S + C -->|uses via AdapterContext| A + A -->|wraps| S + + style W fill:#e1f5ff,color:#000 + style C fill:#d4edda,color:#000 + style A fill:#fff4e1,color:#000 + style S fill:#ffe1e1,color:#000 +``` + + + + +| Repository | Role | Key Exports Used | +| ----------------------- | ----------------------------------------- | ------------------------------------------------------------------- | +| `webex-js-sdk` | Core SDK for Webex backend communication | `Webex.init()`, `webex.meetings`, meeting methods | +| `sdk-component-adapter` | Reactive adapter layer (RxJS observables) | `WebexSDKAdapter`, `MeetingsSDKAdapter`, all Control classes | +| `components` | React UI components + hooks | `WebexMeeting`, `AdapterContext`, `useMeeting`, `useMeetingControl` | + + +--- + +## Dependencies + +**Note:** For exact versions, see [package.json](../package.json) + +### Runtime Dependencies + + +| Package | Purpose | +| ------------------------------ | ----------------------------------------------------- | +| `webex` | Core Webex JavaScript SDK for backend communication | +| `@webex/sdk-component-adapter` | Reactive adapter that wraps SDK into RxJS observables | +| `@webex/components` | React UI components for meeting views and controls | + + +### Peer Dependencies + + +| Package | Purpose | +| ----------- | ------------------- | +| `react` | React framework | +| `react-dom` | React DOM rendering | + + +--- + +## API Reference + +### WebexMeeting Component Props + + +| Prop | Type | Required | Default | Description | +| ---------------------- | ------------- | -------- | ------- | -------------------------------------------- | +| `meetingID` | `string` | No | — | The meeting ID returned by `createMeeting()` | +| `meetingPasswordOrPin` | `string` | No | — | Password or host pin for protected meetings | +| `participantName` | `string` | No | — | Display name for guest participants | +| `controls` | `Function` | No | — | Function returning control IDs to render | +| `layout` | `string` | No | — | Meeting layout variant | +| `logo` | `JSX.Element` | No | — | Custom logo for loading state | +| `className` | `string` | No | — | CSS class for the root element | +| `style` | `object` | No | — | Inline styles for the root element | + + +The `WebexMeeting` component also requires an `AdapterContext.Provider` ancestor with a valid `WebexSDKAdapter` instance as its value. + +### Hooks (from `components`) + + +| Hook | Parameters | Returns | Description | +| ------------------------------------------- | --------------------------------- | ---------------------------------------------- | ------------------------------------------------------------------- | +| `useMeeting(meetingID)` | `meetingID: string` | Meeting object (see ARCHITECTURE.md for shape) | Subscribes to the adapter's meeting observable | +| `useMeetingControl(type, meetingID)` | `type: string, meetingID: string` | `[action, display]` (array) | Returns action function and display state for a control | +| `useMeetingDestination(meetingDestination)` | `meetingDestination: string` | Meeting object | Creates a meeting from destination and subscribes to its observable | + + +### WebexSDKAdapter Methods (top-level adapter) + + +| Method | Returns | Description | +| -------------- | --------------- | --------------------------------------------------------------------------------------------------------------- | +| `connect()` | `Promise` | Calls `sdk.internal.device.register()` → `sdk.internal.mercury.connect()` → `meetingsAdapter.connect()` | +| `disconnect()` | `Promise` | Calls `meetingsAdapter.disconnect()` → `sdk.internal.mercury.disconnect()` → `sdk.internal.device.unregister()` | + + +### MeetingsSDKAdapter Methods + + +| Method | Parameters | Returns | Description | +| ------------------------------------ | ------------------------------------------------------ | --------------------- | ------------------------------------------------------- | +| `connect()` | — | `Promise` | Calls `meetings.register()` + `meetings.syncMeetings()` | +| `disconnect()` | — | `Promise` | Calls `meetings.unregister()` | +| `createMeeting(destination)` | `destination: string` | `Observable` | Creates a meeting from URL, SIP, or PMR | +| `joinMeeting(ID, options)` | `ID: string, { password?, name?, hostKey?, captcha? }` | `Promise` | Joins the meeting | +| `leaveMeeting(ID)` | `ID: string` | `Promise` | Leaves and cleans up the meeting | +| `handleLocalAudio(ID)` | `ID: string` | `Promise` | Toggles audio mute/unmute | +| `handleLocalVideo(ID)` | `ID: string` | `Promise` | Toggles video on/off | +| `handleLocalShare(ID)` | `ID: string` | `Promise` | Toggles screen share on/off | +| `toggleRoster(ID)` | `ID: string` | `void` | Toggles member roster panel (client-side only) | +| `toggleSettings(ID)` | `ID: string` | `Promise` | Toggles settings modal; applies device changes on close | +| `switchCamera(ID, cameraID)` | `ID, cameraID: string` | `Promise` | Switches to a different camera device | +| `switchMicrophone(ID, microphoneID)` | `ID, microphoneID: string` | `Promise` | Switches to a different microphone | +| `switchSpeaker(ID, speakerID)` | `ID, speakerID: string` | `Promise` | Switches to a different speaker (client-side only) | + + +### Control Action Parameters + +All control `action()` methods take a **destructured object**, not a plain string. + + +| Control | `action()` Parameters | Adapter Method Called | +| ------------------------- | ------------------------------------------------------ | -------------------------------------------- | +| `AudioControl` | `{ meetingID }` | `handleLocalAudio(meetingID)` | +| `VideoControl` | `{ meetingID }` | `handleLocalVideo(meetingID)` | +| `ShareControl` | `{ meetingID }` | `handleLocalShare(meetingID)` | +| `JoinControl` | `{ meetingID, meetingPasswordOrPin, participantName }` | `joinMeeting(meetingID, { password, name })` | +| `ExitControl` | `{ meetingID }` | `leaveMeeting(meetingID)` | +| `RosterControl` | `{ meetingID }` | `toggleRoster(meetingID)` | +| `SettingsControl` | `{ meetingID }` | `toggleSettings(meetingID)` | +| `SwitchCameraControl` | `{ meetingID, cameraId }` | `switchCamera(meetingID, cameraId)` | +| `SwitchMicrophoneControl` | `{ meetingID, microphoneId }` | `switchMicrophone(meetingID, microphoneId)` | +| `SwitchSpeakerControl` | `{ meetingID, speakerId }` | `switchSpeaker(meetingID, speakerId)` | + + +### Control IDs for WebexMeetingControlBar + + +| Control ID | Class | Type | Available | +| ------------------- | ------------------------- | ----------- | --------------------- | +| `mute-audio` | `AudioControl` | TOGGLE | Pre-join + In-meeting | +| `mute-video` | `VideoControl` | TOGGLE | Pre-join + In-meeting | +| `share-screen` | `ShareControl` | TOGGLE | In-meeting only | +| `join-meeting` | `JoinControl` | JOIN | Pre-join only | +| `leave-meeting` | `ExitControl` | CANCEL | In-meeting only | +| `member-roster` | `RosterControl` | TOGGLE | In-meeting only | +| `settings` | `SettingsControl` | TOGGLE | Pre-join + In-meeting | +| `switch-camera` | `SwitchCameraControl` | MULTISELECT | Settings panel | +| `switch-microphone` | `SwitchMicrophoneControl` | MULTISELECT | Settings panel | +| `switch-speaker` | `SwitchSpeakerControl` | MULTISELECT | Settings panel | + + +--- + +## Additional Resources + +For detailed architecture, event flows, data structures, and troubleshooting, see [ARCHITECTURE.md](./ARCHITECTURE.md). + +--- + diff --git a/packages/meetings/ai-docs/ARCHITECTURE.md b/packages/meetings/ai-docs/ARCHITECTURE.md new file mode 100644 index 000000000..f9f7c5f06 --- /dev/null +++ b/packages/meetings/ai-docs/ARCHITECTURE.md @@ -0,0 +1,777 @@ +# Meetings Widget - Architecture + +## Component Overview + +The Meetings Widget composes three external repositories into an embeddable meeting experience. The widget initializes `webex-js-sdk`, wraps it with `sdk-component-adapter`, and renders `components` repo UI via `AdapterContext`. + +### Layer Architecture + +```mermaid +graph TB + subgraph "Widget Layer" + W[Widget Entry Point] + end + + subgraph "UI Layer (components repo)" + WM[WebexMeeting] + WIM[WebexInterstitialMeeting] + WIN[WebexInMeeting] + MCB[WebexMeetingControlBar] + WLM[WebexLocalMedia] + WRM[WebexRemoteMedia] + end + + subgraph "Adapter Layer (sdk-component-adapter)" + ADAPT[MeetingsSDKAdapter] + AC[AudioControl] + VC[VideoControl] + SC[ShareControl] + JC[JoinControl] + EC[ExitControl] + end + + subgraph "SDK Layer (webex-js-sdk)" + SDK[Webex Instance] + end + + subgraph "Backend" + BE[Backend] + end + + W -->|creates| SDK + W -->|creates| ADAPT + W -->|AdapterContext| WM + WM --> WIM + WM --> WIN + WM --> MCB + WIN --> WLM + WIN --> WRM + MCB --> AC & VC & SC & JC & EC + AC & VC & SC & JC & EC --> ADAPT + ADAPT --> SDK + SDK --> BE + + style W fill:#e1f5ff,color:#000 + style ADAPT fill:#fff4e1,color:#000 + style SDK fill:#ffe1e1,color:#000 + style BE fill:#f0f0f0,color:#000 +``` + + + +### Component Table + + +| Component | Source | Purpose | Data Source | +| --------------------------------- | ------------------------------------------------------------ | ----------------------------------------------------------------- | ------------------------------------------ | +| `WebexMeeting` | `components/src/components/WebexMeeting/` | Master orchestrator — renders correct view based on meeting state | `useMeeting(meetingID)` | +| `WebexInterstitialMeeting` | `components/src/components/WebexInterstitialMeeting/` | Pre-join lobby with local media preview | state=NOT_JOINED | +| `WebexInMeeting` | `components/src/components/WebexInMeeting/` | Active meeting view with remote + local media | state=JOINED | +| `WebexWaitingForHost` | `components/src/components/WebexWaitingForHost/` | Waiting room when host hasn't started | state is else (not JOINED/NOT_JOINED/LEFT) | +| `WebexMeetingControlBar` | `components/src/components/WebexMeetingControlBar/` | Renders meeting control buttons | Maps control IDs to Control classes | +| `WebexMeetingControl` | `components/src/components/WebexMeetingControl/` | Individual control button | `useMeetingControl(controlID)` | +| `WebexLocalMedia` | `components/src/components/WebexLocalMedia/` | Local camera preview | `localVideo.stream` | +| `WebexRemoteMedia` | `components/src/components/WebexRemoteMedia/` | Remote participant video | `remoteVideo` / `remoteShare` | +| `WebexMemberRoster` | `components/src/components/WebexMemberRoster/` | Participant list panel | `showRoster` flag | +| `WebexSettings` | `components/src/components/WebexSettings/` | Audio/video device settings modal | `settings.visible` flag | +| `WebexMeetingGuestAuthentication` | `components/src/components/WebexMeetingGuestAuthentication/` | Guest password entry | `passwordRequired` flag | +| `WebexMeetingHostAuthentication` | `components/src/components/WebexMeetingHostAuthentication/` | Host pin entry | `passwordRequired` flag | + + +--- + +## SDK Integration + + +| Area | SDK Methods | Adapter Methods | Control Class | +| ----------------- | ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | ------------------------- | +| Initialization | `Webex.init()`, `device.register()`, `mercury.connect()` | `sdkAdapter.connect()` → calls `meetings.register()` + `syncMeetings()` | — | +| Meeting creation | `webex.meetings.create(destination)` | `adapter.meetingsAdapter.createMeeting(dest)` | — | +| Join | `sdkMeeting.verifyPassword()`, `sdkMeeting.join({ pin, moderator, alias })` | `adapter.meetingsAdapter.joinMeeting(ID, options)` | `JoinControl` | +| Leave | `sdkMeeting.leave()` | `adapter.meetingsAdapter.leaveMeeting(ID)` (also calls `removeMedia`) | `ExitControl` | +| Mute/Unmute Audio | `sdkMeeting.muteAudio()`, `sdkMeeting.unmuteAudio()` | `adapter.meetingsAdapter.handleLocalAudio(ID)` | `AudioControl` | +| Mute/Unmute Video | `sdkMeeting.muteVideo()`, `sdkMeeting.unmuteVideo()` | `adapter.meetingsAdapter.handleLocalVideo(ID)` | `VideoControl` | +| Screen Share | `sdkMeeting.getMediaStreams()`, `sdkMeeting.updateShare()` * | `adapter.meetingsAdapter.handleLocalShare(ID)` | `ShareControl` | +| Toggle Roster | — (client-side) | `adapter.meetingsAdapter.toggleRoster(ID)` | `RosterControl` | +| Toggle Settings | `sdkMeeting.updateVideo()`, `sdkMeeting.updateAudio()` (on close, if joined) | `adapter.meetingsAdapter.toggleSettings(ID)` | `SettingsControl` | +| Switch Camera | `sdkMeeting.getMediaStreams()` * | `adapter.switchCamera(ID, cameraID)` | `SwitchCameraControl` | +| Switch Microphone | `sdkMeeting.getMediaStreams()` * | `adapter.switchMicrophone(ID, microphoneID)` | `SwitchMicrophoneControl` | +| Switch Speaker | — (client-side, updates meeting state only) | `adapter.switchSpeaker(ID, speakerID)` | `SwitchSpeakerControl` | +| Cleanup | `meetings.unregister()`, `mercury.disconnect()`, `device.unregister()` | `sdkAdapter.disconnect()` → calls `meetingsAdapter.disconnect()` then SDK cleanup | — | + + +** `getMediaStreams()` and `updateShare()` are the SDK methods invoked by the adapter source code. In newer SDK versions, equivalent functionality is provided by `media.getUserMedia()`, `addMedia()`, `publishStreams()`, and `updateMedia()`.* + +--- + +## Data Flow + +### Outbound (User Action → Backend) + +``` +User clicks control button + → Component (WebexMeetingControl) + → useMeetingControl hook + → Control.action({ meetingID }) + → sdk-component-adapter method + → webex-js-sdk meeting method + → Backend (REST/WebSocket) +``` + +### Inbound (Backend → UI Update) + +``` +Backend processes request + → WebSocket event delivered to webex-js-sdk + → sdk-component-adapter detects change + → RxJS BehaviorSubject emits new meeting state + → useMeeting hook receives update + → Component re-renders +``` + +--- + +## Adapter Meeting Object (from `createMeeting` + runtime updates) + +This is the real shape emitted by `adapter.meetingsAdapter.getMeeting(ID)`: + +``` +{ + ID: string + title: string + state: 'NOT_JOINED' | 'JOINED' | 'LEFT' + + localAudio: { + stream: MediaStream | null + permission: string | null // 'ALLOWED' | 'ERROR' | null + muting: boolean | undefined // true = muting in progress, false = unmuting, undefined = idle + } + localVideo: { + stream: MediaStream | null + permission: string | null + muting: boolean | undefined + error: string | null // e.g. 'Video not supported on iOS 15.1' + } + localShare: { + stream: MediaStream | null + } + + remoteAudio: MediaStream | null + remoteVideo: MediaStream | null + remoteShare: MediaStream | null + + disabledLocalAudio: MediaStream | null // stores the stream when audio is muted + disabledLocalVideo: MediaStream | null // stores the stream when video is muted + + showRoster: boolean | null + settings: { + visible: boolean + preview: { + audio: MediaStream | null + video: MediaStream | null + } + } + + passwordRequired: boolean + requiredCaptcha: object + cameraID: string | null + microphoneID: string | null + speakerID: string +} +``` + +--- + +## Event Flows + +### 1. SDK Initialization + +```mermaid +sequenceDiagram + participant User + participant Component as WebexMeeting + participant Adapter as sdk-component-adapter + participant SDK as webex-js-sdk + participant Backend + + User->>Component: Mount widget with accessToken + Component->>SDK: Webex.init({ credentials: { access_token } }) + Component->>Adapter: new WebexSDKAdapter(webex) + Adapter->>Adapter: Create MeetingsSDKAdapter(webex) with controls + + Component->>Adapter: sdkAdapter.connect() + Adapter->>SDK: sdk.internal.device.register() + SDK->>Backend: Register device + Backend-->>SDK: Device registered + Adapter->>SDK: sdk.internal.mercury.connect() + SDK->>Backend: Open WebSocket + Backend-->>SDK: WebSocket connected + Adapter->>SDK: webex.meetings.register() + syncMeetings() + SDK-->>Adapter: Meetings ready + + Component->>Component: Render with AdapterContext.Provider +``` + + + +--- + +### 2. Meeting Creation & Interstitial + +```mermaid +sequenceDiagram + participant User + participant Component as WebexMeeting + participant Adapter as sdk-component-adapter + participant SDK as webex-js-sdk + participant Backend + + User->>Component: Provide meeting destination (URL/SIP/PMR) + Component->>Adapter: createMeeting(destination) + Adapter->>SDK: webex.meetings.create(destination) + SDK->>Backend: Resolve meeting info, check active sessions, get user profile + Backend-->>SDK: Meeting info (title, sipUri), user profile + + Note over SDK: Meeting object created with state=NOT_JOINED + + SDK-->>Adapter: Meeting object + Adapter->>Adapter: Create meeting observable (RxJS) + Adapter-->>Component: meetingID + + Component->>Component: Render WebexInterstitialMeeting + Component->>Component: Show local media preview + Component->>Component: Show controls [mute-audio, mute-video, settings, join-meeting] +``` + + + +--- + +### 3. Join Meeting + +```mermaid +sequenceDiagram + participant User + participant Component as WebexMeetingControlBar + participant Adapter as JoinControl + participant SDK as webex-js-sdk + participant Backend + + User->>Component: Click "Join Meeting" button + Component->>Adapter: action({ meetingID, meetingPasswordOrPin, participantName }) + Adapter->>Adapter: joinMeeting(ID, { password, name }) + + alt Password Required + Adapter->>SDK: sdkMeeting.verifyPassword(password, captcha) + SDK->>Backend: Verify password + Backend-->>SDK: Verified + end + + Adapter->>SDK: sdkMeeting.join({ pin, moderator, alias }) + SDK->>Backend: Join meeting session + Backend-->>SDK: Session joined, media connections ready + + SDK->>SDK: Negotiate media (SDP offer/answer) + SDK->>Backend: Send local media description + Backend-->>SDK: Media established (audio + video active) + + SDK-->>Adapter: Meeting state updated + Adapter->>Adapter: Emit observable { state: JOINED } + Adapter-->>Component: Observable emits + + Component->>Component: Transition: WebexInterstitialMeeting → WebexInMeeting + Component->>Component: Update controls [mute-audio, mute-video, share-screen, member-roster, settings, leave-meeting] +``` + + + +--- + +### 4. Mute / Unmute Audio + +```mermaid +sequenceDiagram + participant User + participant Component as WebexMeetingControlBar + participant Adapter as AudioControl + participant SDK as webex-js-sdk + participant Backend + + Note over User: Audio is currently UNMUTED + + User->>Component: Click microphone button + Component->>Adapter: action({ meetingID }) + Adapter->>Adapter: handleLocalAudio(ID) + Adapter->>Adapter: Set localAudio.muting = true + Adapter->>SDK: sdkMeeting.muteAudio() + SDK->>Backend: Update media state (audio → receive-only) + Backend-->>SDK: Confirmed + + Adapter->>Adapter: Emit { disabledLocalAudio: stream, localAudio.stream: null } + Adapter-->>Component: display() emits { icon: microphone-muted, text: Unmute, state: ACTIVE } + Component->>Component: Re-render with muted icon + + Note over User: Audio is now MUTED — click again to unmute + + User->>Component: Click microphone button + Component->>Adapter: action({ meetingID }) + Adapter->>Adapter: handleLocalAudio(ID) + Adapter->>Adapter: Set localAudio.muting = false + Adapter->>SDK: sdkMeeting.unmuteAudio() + SDK->>Backend: Update media state (audio → send+receive) + Backend-->>SDK: Confirmed + + Adapter->>Adapter: Emit { disabledLocalAudio: null, localAudio.stream: stream } + Adapter-->>Component: display() emits { icon: microphone, text: Mute, state: INACTIVE } + Component->>Component: Re-render with unmuted icon +``` + + + +--- + +### 5. Start / Stop Video + +```mermaid +sequenceDiagram + participant User + participant Component as WebexMeetingControlBar + participant Adapter as VideoControl + participant SDK as webex-js-sdk + participant Backend + + Note over User: Video is currently ON + + User->>Component: Click camera button + Component->>Adapter: action({ meetingID }) + Adapter->>Adapter: handleLocalVideo(ID) + Adapter->>Adapter: Set localVideo.muting = true + Adapter->>SDK: sdkMeeting.muteVideo() + SDK->>Backend: Update media state (video → receive-only) + Backend-->>SDK: Confirmed + + Adapter->>Adapter: Emit { disabledLocalVideo: stream, localVideo.stream: null } + Adapter-->>Component: display() emits { icon: camera-muted, text: Start video, state: ACTIVE } + + Note over User: Video is now OFF — click again to start + + User->>Component: Click camera button + Component->>Adapter: action({ meetingID }) + Adapter->>Adapter: handleLocalVideo(ID) + Adapter->>Adapter: Set localVideo.muting = false + Adapter->>SDK: sdkMeeting.unmuteVideo() + SDK->>Backend: Update media state (video → send+receive) + Backend-->>SDK: Confirmed + + Adapter->>Adapter: Emit { disabledLocalVideo: null, localVideo.stream: stream } + Adapter-->>Component: display() emits { icon: camera, text: Stop video, state: INACTIVE } +``` + + + +--- + +### 6. Start / Stop Screen Share + +```mermaid +sequenceDiagram + participant User + participant Component as WebexMeetingControlBar + participant Adapter as ShareControl + participant SDK as webex-js-sdk + participant Backend + + User->>Component: Click share screen button + Component->>Adapter: action({ meetingID }) + Adapter->>Adapter: handleLocalShare(ID) + Adapter->>SDK: sdkMeeting.getMediaStreams({ sendShare: true }) + SDK->>User: Browser screen picker dialog (getDisplayMedia) + User->>SDK: Select screen/window/tab + SDK-->>Adapter: [, localShareStream] + Adapter->>SDK: sdkMeeting.updateShare({ stream, sendShare: true, receiveShare: true }) + SDK->>Backend: Update media state (share → send+receive) + Backend-->>SDK: Confirmed + + Adapter->>Adapter: Emit { localShare.stream: localShareStream } + Adapter-->>Component: display() emits { text: Stop sharing, state: ACTIVE } + + Note over User: Sharing active — click again to stop + + User->>Component: Click stop sharing + Component->>Adapter: action({ meetingID }) + Adapter->>Adapter: handleLocalShare(ID) + Adapter->>Adapter: stopStream(localShare.stream) + Adapter->>SDK: sdkMeeting.updateShare({ sendShare: false, receiveShare: true }) + SDK->>Backend: Update media state (share → receive-only) + Backend-->>SDK: Confirmed + + Adapter->>Adapter: Emit { localShare.stream: null } + Adapter-->>Component: display() emits { text: Start sharing, state: INACTIVE } +``` + + + +--- + +### 7. Toggle Member Roster + +```mermaid +sequenceDiagram + participant User + participant Component as WebexMeeting + participant Adapter as RosterControl + + Note over Adapter: Client-side only — no Backend call + + User->>Component: Click roster button + Component->>Adapter: action({ meetingID }) + Adapter->>Adapter: toggleRoster(ID) + Adapter->>Adapter: meeting.showRoster = !meeting.showRoster + Adapter->>Adapter: Emit observable { showRoster: true } + Adapter-->>Component: Observable emits + Component->>Component: Render WebexMemberRoster panel + + User->>Component: Click roster button (close) + Component->>Adapter: action({ meetingID }) + Adapter->>Adapter: toggleRoster(ID) + Adapter->>Adapter: Emit { showRoster: false } + Adapter-->>Component: Observable emits + Component->>Component: Remove WebexMemberRoster panel +``` + + + +--- + +### 8. Toggle Settings & Switch Camera + +```mermaid +sequenceDiagram + participant User + participant Component as WebexMeeting + participant Adapter as sdk-component-adapter + participant SDK as webex-js-sdk + + User->>Component: Click settings button + Component->>Adapter: SettingsControl.action({ meetingID }) + Adapter->>Adapter: toggleSettings(ID) + Adapter->>Adapter: Clone current streams to settings.preview + Adapter->>Adapter: Emit { settings.visible: true } + Adapter-->>Component: Observable emits + Component->>Component: Open WebexSettings modal + + Note over User: User selects a different camera + + User->>Component: Select new camera from dropdown + Component->>Adapter: SwitchCameraControl.action({ meetingID, cameraId }) + Adapter->>Adapter: switchCamera(ID, cameraId) + Adapter->>SDK: sdkMeeting.getMediaStreams({ sendVideo: true }, { video: { deviceId } }) + SDK->>SDK: getUserMedia with new deviceId + SDK-->>Adapter: New video MediaStream + Adapter->>Adapter: Emit { settings.preview.video: newStream, cameraID } + Adapter-->>Component: Settings preview re-renders with new camera + + User->>Component: Close settings modal + Component->>Adapter: SettingsControl.action({ meetingID }) + Adapter->>Adapter: toggleSettings(ID) + Adapter->>Adapter: Replace meeting streams with preview streams + + alt Meeting is joined + Adapter->>SDK: sdkMeeting.updateVideo({ stream, receiveVideo, sendVideo }) + Adapter->>SDK: sdkMeeting.updateAudio({ stream, receiveAudio, sendAudio }) + end + + Adapter->>Adapter: Emit { settings.visible: false } + Component->>Component: Close modal +``` + + + +--- + +### 9. Leave Meeting + +```mermaid +sequenceDiagram + participant User + participant Component as WebexMeetingControlBar + participant Adapter as ExitControl + participant SDK as webex-js-sdk + participant Backend + + User->>Component: Click leave meeting button + Component->>Adapter: action({ meetingID }) + Adapter->>Adapter: leaveMeeting(ID) + Adapter->>Adapter: removeMedia(ID) — stop all local streams + Adapter->>SDK: sdkMeeting.leave() + SDK->>Backend: Leave session + Backend-->>SDK: Confirmed + + SDK-->>Adapter: Meeting state updated + Adapter->>Adapter: Emit { state: LEFT } + Adapter-->>Component: Observable emits + + Component->>Component: Show "You've successfully left the meeting" +``` + + + +--- + +### 10. Guest/Host Authentication + +```mermaid +sequenceDiagram + participant User + participant Component as WebexMeeting + participant Adapter as JoinControl + participant SDK as webex-js-sdk + participant Backend + + Note over Component: Meeting has passwordRequired=true + + Component->>Component: Detect passwordRequired from observable + Component->>Component: Open WebexMeetingGuestAuthentication modal + + User->>Component: Enter password, click "Join as Guest" + Component->>Adapter: action({ meetingID, meetingPasswordOrPin: password }) + Adapter->>SDK: joinMeeting(ID, { password }) + SDK->>Backend: Verify password and join + Backend-->>SDK: Result + + alt Password Correct + SDK-->>Adapter: state → JOINED + Adapter-->>Component: Observable emits + Component->>Component: Close auth modal, show in-meeting view + else Password Incorrect + SDK-->>Adapter: Error / invalidPassword flag + Adapter-->>Component: Observable emits { invalidPassword: true } + Component->>Component: Show error in auth modal + end + + Note over User: Alternative: "I'm the host" + + User->>Component: Click "I'm the host" + Component->>Component: Switch to WebexMeetingHostAuthentication modal + User->>Component: Enter host pin, click "Start Meeting" + Component->>Adapter: action({ meetingID, meetingPasswordOrPin: hostPin }) + Adapter->>SDK: joinMeeting(ID, { hostKey: hostPin }) +``` + + + +--- + +### 11. Waiting for Host + +```mermaid +sequenceDiagram + participant User + participant Component as WebexMeeting + participant Adapter as sdk-component-adapter + participant SDK as webex-js-sdk + participant Backend + + Note over Component: Meeting joined but host not yet present + + Component->>Component: state is not JOINED, NOT_JOINED, or LEFT + Component->>Component: Render WebexWaitingForHost + Component->>User: Show "Waiting for the host to start the meeting" + + Note over Backend: Host joins the meeting + + Backend-->>SDK: WebSocket event — host joined, meeting started + SDK-->>Adapter: Meeting state updated + Adapter->>Adapter: Emit { state: JOINED } + Adapter-->>Component: Observable emits + + Component->>Component: Transition: WebexWaitingForHost → WebexInMeeting +``` + + + +--- + +## Meeting State Machine + +```mermaid +stateDiagram-v2 + [*] --> NOT_JOINED: SDK + Adapter ready, meeting created + + NOT_JOINED --> JOINED: User joins (JoinControl) + + JOINED --> LEFT: User leaves (ExitControl) + + LEFT --> [*]: Widget unmounts +``` + + + +*These are the three states emitted by the adapter's meeting observable. The `WebexMeeting` component also handles a falsy state (loading) and an else catch-all (WebexWaitingForHost).* + +--- + +## Control Display States (from actual source) + +### AudioControl + + +| State | Icon | Text | Tooltip | Control State | +| ------------ | ------------------ | ------------- | ----------------------- | ------------- | +| unmuted | `microphone` | Mute | Mute audio | INACTIVE | +| muted | `microphone-muted` | Unmute | Unmute audio | ACTIVE | +| muting | `microphone` | Muting... | Muting audio | DISABLED | +| unmuting | `microphone-muted` | Unmuting... | Unmuting audio | DISABLED | +| noMicrophone | `microphone-muted` | No microphone | No microphone available | DISABLED | + + +### VideoControl + + +| State | Icon | Text | Tooltip | Control State | +| -------- | -------------- | ----------- | ------------------- | ------------- | +| unmuted | `camera` | Stop video | Stop video | INACTIVE | +| muted | `camera-muted` | Start video | Start video | ACTIVE | +| muting | `camera` | Stopping... | Stopping video | DISABLED | +| unmuting | `camera-muted` | Starting... | Starting video | DISABLED | +| noCamera | `camera-muted` | No camera | No camera available | DISABLED | + + +### ShareControl + + +| State | Icon | Text | Tooltip | Control State | Type | +| ------------ | ------------------------------ | ------------- | -------------------------- | ------------- | ------ | +| inactive | `share-screen-presence-stroke` | Start sharing | Start sharing content | INACTIVE | TOGGLE | +| active | `share-screen-presence-stroke` | Stop sharing | Stop sharing content | ACTIVE | TOGGLE | +| notSupported | `share-screen-presence-stroke` | Start sharing | Share screen not supported | DISABLED | TOGGLE | + + +### JoinControl + + +| Text | Tooltip | Hint | Control State | Type | +| ------------ | ------------ | ------------------------------- | --------------------------------- | ---- | +| Join meeting | Join meeting | {Muted/Unmuted}, {video on/off} | ACTIVE (if NOT_JOINED) / DISABLED | JOIN | + + +### ExitControl + +Renders as a CANCEL type button. + +--- + +## Troubleshooting Guide + +### 1. Widget Stuck on Loading + +**Symptoms:** Loading state never resolves, no meeting UI appears + +**Possible Causes:** + +- Invalid or expired access token +- Network connectivity to backend +- Device registration failure + +**Solutions:** + +- Verify the access token is valid and not expired +- Check network connectivity (browser dev tools network tab) +- Check browser console for SDK error messages + +--- + +### 2. Audio/Video Not Working After Join + +**Symptoms:** Joined meeting but no audio/video, controls show "No camera" or "No microphone" + +**Possible Causes:** + +- Browser denied `getUserMedia` permissions +- Media negotiation (SDP/ROAP) failed +- Media server unreachable + +**Solutions:** + +- Check browser permission prompts for camera/microphone +- Verify `getUserMedia` works in browser console +- Check for errors in SDK logs + +--- + +### 3. Screen Share Not Available + +**Symptoms:** Share button disabled, shows "Share screen not supported" + +**Possible Causes:** + +- Browser doesn't support `getDisplayMedia` +- Running over HTTP instead of HTTPS +- `navigator.mediaDevices.getDisplayMedia` is undefined + +**Solutions:** + +- Verify HTTPS is being used +- Check browser compatibility +- `ShareControl` checks `navigator.mediaDevices.getDisplayMedia` availability before enabling + +--- + +### 4. Meeting State Not Updating + +**Symptoms:** UI doesn't change after control actions + +**Possible Causes:** + +- WebSocket connection dropped +- Observable subscription lost +- Adapter not emitting updates + +**Solutions:** + +- Check WebSocket status in network tab +- Verify the observable subscription is active +- Look for WebSocket events in the network inspector + +--- + +### 5. Multiple Meeting Instances Created + +**Symptoms:** Widget creates duplicate meetings + +**Possible Causes:** + +- React strict mode causing double initialization +- Missing cleanup on prop changes +- Missing dependency array in useEffect + +**Solutions:** + +- Use a ref to track initialization state +- Implement proper cleanup in useEffect return +- Guard against re-initialization + +--- + +### 6. AdapterContext Not Provided + +**Symptoms:** Components crash with "Cannot read property of undefined" + +**Possible Causes:** + +- `AdapterContext.Provider` not wrapping `WebexMeeting` +- Adapter not yet initialized when components render + +**Solutions:** + +- Ensure `` wraps all components +- Wait for adapter to be ready before rendering + +--- + +## Related Documentation + +- [Agent Documentation](./AGENTS.md) - Widget usage and API reference + +--- + From b7796b048a075e14496dbf90a79017ef054e9c86 Mon Sep 17 00:00:00 2001 From: Ritesh Singh Date: Fri, 6 Mar 2026 01:40:57 +0530 Subject: [PATCH 2/5] docs(ai-docs): add docs for meetings widget --- packages/{meetings => @webex/widgets}/ai-docs/AGENTS.md | 0 packages/{meetings => @webex/widgets}/ai-docs/ARCHITECTURE.md | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename packages/{meetings => @webex/widgets}/ai-docs/AGENTS.md (100%) rename packages/{meetings => @webex/widgets}/ai-docs/ARCHITECTURE.md (100%) diff --git a/packages/meetings/ai-docs/AGENTS.md b/packages/@webex/widgets/ai-docs/AGENTS.md similarity index 100% rename from packages/meetings/ai-docs/AGENTS.md rename to packages/@webex/widgets/ai-docs/AGENTS.md diff --git a/packages/meetings/ai-docs/ARCHITECTURE.md b/packages/@webex/widgets/ai-docs/ARCHITECTURE.md similarity index 100% rename from packages/meetings/ai-docs/ARCHITECTURE.md rename to packages/@webex/widgets/ai-docs/ARCHITECTURE.md From 6630b60c5d23ada9ff2933d257a3ba662186dd5e Mon Sep 17 00:00:00 2001 From: Ritesh Singh Date: Fri, 6 Mar 2026 10:14:30 +0530 Subject: [PATCH 3/5] docs(ai-docs): update AGENTS.md --- packages/@webex/widgets/ai-docs/AGENTS.md | 110 ++++++++++++++-------- 1 file changed, 70 insertions(+), 40 deletions(-) diff --git a/packages/@webex/widgets/ai-docs/AGENTS.md b/packages/@webex/widgets/ai-docs/AGENTS.md index f7d150041..0f8606aa6 100644 --- a/packages/@webex/widgets/ai-docs/AGENTS.md +++ b/packages/@webex/widgets/ai-docs/AGENTS.md @@ -6,7 +6,9 @@ The Meetings Widget provides a full-featured Webex meeting experience as an embe **Widget:** Meetings -**Location:** `widgets/packages/meetings/` +**Package:** `@webex/widgets` + +**Location:** `packages/@webex/widgets` --- @@ -35,40 +37,46 @@ The Meetings Widget lets consuming applications embed a complete meeting experie #### Basic Usage (React) +The widget handles SDK initialization, adapter creation, meeting creation, and all internal wiring via the `withAdapter` and `withMeeting` HOCs. Consumers just import and render with props: + ```jsx -import Webex from 'webex'; -import WebexSDKAdapter from '@webex/sdk-component-adapter'; -import {WebexMeeting, AdapterContext} from '@webex/components'; +import {WebexMeetingsWidget} from '@webex/widgets'; -function MeetingsWidget({accessToken, meetingDestination}) { - const [adapter, setAdapter] = useState(null); - const [meetingID, setMeetingID] = useState(null); +function App() { + return ( + + ); +} +``` - useEffect(() => { - const webex = Webex.init({ - credentials: { access_token: accessToken } - }); - const sdkAdapter = new WebexSDKAdapter(webex); +#### With All Optional Props - sdkAdapter.connect().then(() => { - setAdapter(sdkAdapter); - return sdkAdapter.meetingsAdapter.createMeeting(meetingDestination); - }).then((meeting) => { - setMeetingID(meeting.ID); - }); +```jsx + +``` - return () => sdkAdapter.disconnect(); - }, [accessToken, meetingDestination]); +#### What Happens Internally - if (!adapter || !meetingID) return
Loading...
; +When `WebexMeetingsWidget` mounts, the `withAdapter` HOC: - return ( - - - - ); -} -``` +1. Creates a `Webex` instance using the `accessToken` prop +2. Wraps it in a `WebexSDKAdapter` +3. Calls `adapter.connect()` (registers device, opens WebSocket, syncs meetings) +4. Provides the adapter via `AdapterContext` + +The `withMeeting` HOC then creates a meeting from `meetingDestination` and passes the meeting object as a prop. The widget renders the appropriate view based on meeting state. ### Common Use Cases @@ -185,22 +193,44 @@ graph LR ## API Reference -### WebexMeeting Component Props +### WebexMeetingsWidget Props (Public API) + +These are the props consumers pass when using the widget. The widget handles SDK/adapter setup internally. + + +| Prop | Type | Required | Default | Description | +| --------------------------- | ---------- | -------- | ----------- | -------------------------------------------------------------- | +| `accessToken` | `string` | **Yes** | — | Webex access token for authentication | +| `meetingDestination` | `string` | **Yes** | — | Meeting URL, SIP address, email, or Personal Meeting Room link | +| `meetingPasswordOrPin` | `string` | No | `''` | Password or host pin for protected meetings | +| `participantName` | `string` | No | `''` | Display name for guest participants | +| `fedramp` | `bool` | No | `false` | Enable FedRAMP-compliant environment | +| `layout` | `string` | No | `'Grid'` | Remote video layout (`Grid`, `Stack`, `Overlay`, `Prominent`, `Focus`) | +| `controls` | `Function` | No | `undefined` | Function returning control IDs to render | +| `controlsCollapseRangeStart`| `number` | No | `undefined` | Zero-based index of the first collapsible control | +| `controlsCollapseRangeEnd` | `number` | No | `undefined` | Zero-based index before the last collapsible control | +| `className` | `string` | No | `''` | Custom CSS class for the root element | +| `style` | `object` | No | `{}` | Inline styles for the root element | + + +**Source:** `src/widgets/WebexMeetings/WebexMeetings.jsx` (see `WebexMeetingsWidget.propTypes` and `WebexMeetingsWidget.defaultProps`) + +### Internal Component Props (WebexMeeting from @webex/components) +These are passed internally by `WebexMeetingsWidget` to the `WebexMeeting` component from `@webex/components`. Consumers do not interact with these directly. -| Prop | Type | Required | Default | Description | -| ---------------------- | ------------- | -------- | ------- | -------------------------------------------- | -| `meetingID` | `string` | No | — | The meeting ID returned by `createMeeting()` | -| `meetingPasswordOrPin` | `string` | No | — | Password or host pin for protected meetings | -| `participantName` | `string` | No | — | Display name for guest participants | -| `controls` | `Function` | No | — | Function returning control IDs to render | -| `layout` | `string` | No | — | Meeting layout variant | -| `logo` | `JSX.Element` | No | — | Custom logo for loading state | -| `className` | `string` | No | — | CSS class for the root element | -| `style` | `object` | No | — | Inline styles for the root element | +| Prop | Type | Description | +| ---------------------- | ------------- | -------------------------------------------------------------- | +| `meetingID` | `string` | Injected by `withMeeting` HOC from `meetingDestination` | +| `meetingPasswordOrPin` | `string` | Forwarded from widget prop | +| `participantName` | `string` | Forwarded from widget prop | +| `controls` | `Function` | Forwarded from widget prop | +| `layout` | `string` | Forwarded from widget prop | +| `logo` | `JSX.Element` | Hard-coded `` SVG | +| `className` | `string` | Always `'webex-meetings-widget__content'` | -The `WebexMeeting` component also requires an `AdapterContext.Provider` ancestor with a valid `WebexSDKAdapter` instance as its value. +The `WebexMeeting` component receives its adapter via `AdapterContext.Provider`, which is set up by the `withAdapter` HOC wrapping the widget. ### Hooks (from `components`) From e59ce0216ec2e4750410a59a5f0cfe5ecbe2e7ea Mon Sep 17 00:00:00 2001 From: Ritesh Singh Date: Thu, 12 Mar 2026 09:56:47 +0530 Subject: [PATCH 4/5] ai-docs(docs): update agents.md --- packages/@webex/widgets/ai-docs/AGENTS.md | 27 ++++++++++------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/packages/@webex/widgets/ai-docs/AGENTS.md b/packages/@webex/widgets/ai-docs/AGENTS.md index 0f8606aa6..cd0f3e79d 100644 --- a/packages/@webex/widgets/ai-docs/AGENTS.md +++ b/packages/@webex/widgets/ai-docs/AGENTS.md @@ -273,21 +273,18 @@ The `WebexMeeting` component receives its adapter via `AdapterContext.Provider`, ### Control Action Parameters -All control `action()` methods take a **destructured object**, not a plain string. - - -| Control | `action()` Parameters | Adapter Method Called | -| ------------------------- | ------------------------------------------------------ | -------------------------------------------- | -| `AudioControl` | `{ meetingID }` | `handleLocalAudio(meetingID)` | -| `VideoControl` | `{ meetingID }` | `handleLocalVideo(meetingID)` | -| `ShareControl` | `{ meetingID }` | `handleLocalShare(meetingID)` | -| `JoinControl` | `{ meetingID, meetingPasswordOrPin, participantName }` | `joinMeeting(meetingID, { password, name })` | -| `ExitControl` | `{ meetingID }` | `leaveMeeting(meetingID)` | -| `RosterControl` | `{ meetingID }` | `toggleRoster(meetingID)` | -| `SettingsControl` | `{ meetingID }` | `toggleSettings(meetingID)` | -| `SwitchCameraControl` | `{ meetingID, cameraId }` | `switchCamera(meetingID, cameraId)` | -| `SwitchMicrophoneControl` | `{ meetingID, microphoneId }` | `switchMicrophone(meetingID, microphoneId)` | -| `SwitchSpeakerControl` | `{ meetingID, speakerId }` | `switchSpeaker(meetingID, speakerId)` | +| Control | Adapter Method Called | +| ------------------------- | -------------------------------------------- | +| `AudioControl` | `handleLocalAudio(meetingID)` | +| `VideoControl` | `handleLocalVideo(meetingID)` | +| `ShareControl` | `handleLocalShare(meetingID)` | +| `JoinControl` | `joinMeeting(meetingID, { password, name })` | +| `ExitControl` | `leaveMeeting(meetingID)` | +| `RosterControl` | `toggleRoster(meetingID)` | +| `SettingsControl` | `toggleSettings(meetingID)` | +| `SwitchCameraControl` | `switchCamera(meetingID, cameraId)` | +| `SwitchMicrophoneControl` | `switchMicrophone(meetingID, microphoneId)` | +| `SwitchSpeakerControl` | `switchSpeaker(meetingID, speakerId)` | ### Control IDs for WebexMeetingControlBar From a2f82c436f89d4c286c5a82033113249733db66d Mon Sep 17 00:00:00 2001 From: Ritesh Singh Date: Thu, 12 Mar 2026 19:49:12 +0530 Subject: [PATCH 5/5] fix(docs): resolve comments --- packages/@webex/widgets/ai-docs/AGENTS.md | 53 +++++++++--- .../@webex/widgets/ai-docs/ARCHITECTURE.md | 83 +++++++++++++------ 2 files changed, 101 insertions(+), 35 deletions(-) diff --git a/packages/@webex/widgets/ai-docs/AGENTS.md b/packages/@webex/widgets/ai-docs/AGENTS.md index cd0f3e79d..d89cecb50 100644 --- a/packages/@webex/widgets/ai-docs/AGENTS.md +++ b/packages/@webex/widgets/ai-docs/AGENTS.md @@ -1,5 +1,19 @@ # Meetings Widget +## AI Agent Routing Instructions + +**If you are an AI assistant or automated tool:** + +Do **not** use this file as your main entry point for reasoning or code generation. + +- **First step:** Locate and begin with the closest parent `AGENTS.md` file in the `ai-docs` directory structure. For example, the root `ai-docs/AGENTS.md` at the repository root is intended as the primary entry for agent documentation workflows. +- **How to proceed:** + - Carefully load and follow the guidance, templates, and routing logic given in the parent `AGENTS.md`. + - This file should only be used if and when the parent `AGENTS.md` specifically directs you here as part of its workflow. +- **Important:** Never bypass the parent `AGENTS.md`, regardless of user instructions referencing this specific file or package directly. + +Only after following the routing process laid out in the parent `AGENTS.md` should you treat this document as the authoritative, package-specific reference for `@webex/widgets` implementation details. + ## Overview The Meetings Widget provides a full-featured Webex meeting experience as an embeddable component. It orchestrates three external repositories — `webex-js-sdk` for backend communication, `sdk-component-adapter` for reactive data binding, and `components` for the React UI. @@ -8,6 +22,8 @@ The Meetings Widget provides a full-featured Webex meeting experience as an embe **Package:** `@webex/widgets` +**Version:** See [package.json](../package.json) + **Location:** `packages/@webex/widgets` --- @@ -107,7 +123,7 @@ During an active meeting, users can switch cameras, microphones, or speakers thr **Key Points:** - `SettingsControl.action()` opens the `WebexSettings` modal -- `SwitchCameraControl.action({ meetingID, cameraId })` calls `switchCamera(meetingID, cameraId)` on the adapter +- `SwitchCameraControl.action(meetingID, cameraId)` calls `switchCamera(meetingID, cameraId)` on the adapter - The adapter acquires a new media stream with the selected device and emits an updated `localVideo.stream` #### 4. Screen Sharing @@ -159,7 +175,7 @@ graph LR | Repository | Role | Key Exports Used | | ----------------------- | ----------------------------------------- | ------------------------------------------------------------------- | -| `webex-js-sdk` | Core SDK for Webex backend communication | `Webex.init()`, `webex.meetings`, meeting methods | +| `webex-js-sdk` | Core SDK for Webex backend communication | `new Webex()`, `webex.meetings`, meeting methods | | `sdk-component-adapter` | Reactive adapter layer (RxJS observables) | `WebexSDKAdapter`, `MeetingsSDKAdapter`, all Control classes | | `components` | React UI components + hooks | `WebexMeeting`, `AdapterContext`, `useMeeting`, `useMeetingControl` | @@ -173,20 +189,23 @@ graph LR ### Runtime Dependencies -| Package | Purpose | -| ------------------------------ | ----------------------------------------------------- | -| `webex` | Core Webex JavaScript SDK for backend communication | -| `@webex/sdk-component-adapter` | Reactive adapter that wraps SDK into RxJS observables | -| `@webex/components` | React UI components for meeting views and controls | +| Package | Purpose | +| ---------------------------------- | ----------------------------------------------------- | +| `webex` | Core Webex JavaScript SDK for backend communication | +| `@webex/sdk-component-adapter` | Reactive adapter that wraps SDK into RxJS observables | +| `@webex/components` | React UI components for meeting views and controls | +| `@webex/component-adapter-interfaces` | Interface definitions for component adapters | ### Peer Dependencies -| Package | Purpose | -| ----------- | ------------------- | -| `react` | React framework | -| `react-dom` | React DOM rendering | +| Package | Purpose | +| ------------- | -------------------------- | +| `react` | React framework | +| `react-dom` | React DOM rendering | +| `prop-types` | React prop type checking | +| `webex` | Core Webex SDK (peer) | --- @@ -304,6 +323,16 @@ The `WebexMeeting` component receives its adapter via `AdapterContext.Provider`, | `switch-speaker` | `SwitchSpeakerControl` | MULTISELECT | Settings panel | +--- + +## Installation + +```bash +# Install as a standalone package +yarn add @webex/widgets +npm install @webex/widgets +``` + --- ## Additional Resources @@ -312,3 +341,5 @@ For detailed architecture, event flows, data structures, and troubleshooting, se --- +_Last Updated: 2026-03-12_ + diff --git a/packages/@webex/widgets/ai-docs/ARCHITECTURE.md b/packages/@webex/widgets/ai-docs/ARCHITECTURE.md index f9f7c5f06..6c7190b00 100644 --- a/packages/@webex/widgets/ai-docs/ARCHITECTURE.md +++ b/packages/@webex/widgets/ai-docs/ARCHITECTURE.md @@ -59,6 +59,35 @@ graph TB +### File Structure + +``` +packages/@webex/widgets/ +├── src/ +│ ├── index.js # Package exports +│ └── widgets/ +│ └── WebexMeetings/ +│ ├── WebexMeetings.jsx # Widget component (main source) +│ ├── WebexMeetings.css # Widget styles +│ ├── WebexLogo.jsx # SVG logo component +│ ├── webex-logo.svg # Logo asset +│ └── README.md # Component README +├── tests/ +│ ├── WebexMeetings/ +│ │ └── WebexMeetings.test.jsx # Unit tests +│ ├── WebexMeeting.e2e.js # E2E tests +│ ├── pages/ +│ │ ├── MeetingWidget.page.js # Page object for E2E +│ │ └── Samples.page.js # Samples page object +│ └── util.js # Test utilities +├── demo/ # Demo app +├── ai-docs/ +│ ├── AGENTS.md # Usage, API, examples +│ └── ARCHITECTURE.md # This file +├── jest.config.js # Jest configuration +└── package.json # Package manifest +``` + ### Component Table @@ -85,7 +114,7 @@ graph TB | Area | SDK Methods | Adapter Methods | Control Class | | ----------------- | ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | ------------------------- | -| Initialization | `Webex.init()`, `device.register()`, `mercury.connect()` | `sdkAdapter.connect()` → calls `meetings.register()` + `syncMeetings()` | — | +| Initialization | `new Webex()`, `device.register()`, `mercury.connect()` | `sdkAdapter.connect()` → calls `meetings.register()` + `syncMeetings()` | — | | Meeting creation | `webex.meetings.create(destination)` | `adapter.meetingsAdapter.createMeeting(dest)` | — | | Join | `sdkMeeting.verifyPassword()`, `sdkMeeting.join({ pin, moderator, alias })` | `adapter.meetingsAdapter.joinMeeting(ID, options)` | `JoinControl` | | Leave | `sdkMeeting.leave()` | `adapter.meetingsAdapter.leaveMeeting(ID)` (also calls `removeMedia`) | `ExitControl` | @@ -112,7 +141,7 @@ graph TB User clicks control button → Component (WebexMeetingControl) → useMeetingControl hook - → Control.action({ meetingID }) + → Control.action(meetingID) → sdk-component-adapter method → webex-js-sdk meeting method → Backend (REST/WebSocket) @@ -143,12 +172,12 @@ This is the real shape emitted by `adapter.meetingsAdapter.getMeeting(ID)`: localAudio: { stream: MediaStream | null - permission: string | null // 'ALLOWED' | 'ERROR' | null + permission: string | null // 'ASKING' | 'ALLOWED' | 'ERROR' | null muting: boolean | undefined // true = muting in progress, false = unmuting, undefined = idle } localVideo: { stream: MediaStream | null - permission: string | null + permission: string | null // 'ASKING' | 'ALLOWED' | 'ERROR' | null muting: boolean | undefined error: string | null // e.g. 'Video not supported on iOS 15.1' } @@ -195,7 +224,7 @@ sequenceDiagram participant Backend User->>Component: Mount widget with accessToken - Component->>SDK: Webex.init({ credentials: { access_token } }) + Component->>SDK: new Webex({ credentials: { access_token } }) Component->>Adapter: new WebexSDKAdapter(webex) Adapter->>Adapter: Create MeetingsSDKAdapter(webex) with controls @@ -258,7 +287,7 @@ sequenceDiagram participant Backend User->>Component: Click "Join Meeting" button - Component->>Adapter: action({ meetingID, meetingPasswordOrPin, participantName }) + Component->>Adapter: action(meetingID) Adapter->>Adapter: joinMeeting(ID, { password, name }) alt Password Required @@ -300,7 +329,7 @@ sequenceDiagram Note over User: Audio is currently UNMUTED User->>Component: Click microphone button - Component->>Adapter: action({ meetingID }) + Component->>Adapter: action(meetingID) Adapter->>Adapter: handleLocalAudio(ID) Adapter->>Adapter: Set localAudio.muting = true Adapter->>SDK: sdkMeeting.muteAudio() @@ -314,7 +343,7 @@ sequenceDiagram Note over User: Audio is now MUTED — click again to unmute User->>Component: Click microphone button - Component->>Adapter: action({ meetingID }) + Component->>Adapter: action(meetingID) Adapter->>Adapter: handleLocalAudio(ID) Adapter->>Adapter: Set localAudio.muting = false Adapter->>SDK: sdkMeeting.unmuteAudio() @@ -343,7 +372,7 @@ sequenceDiagram Note over User: Video is currently ON User->>Component: Click camera button - Component->>Adapter: action({ meetingID }) + Component->>Adapter: action(meetingID) Adapter->>Adapter: handleLocalVideo(ID) Adapter->>Adapter: Set localVideo.muting = true Adapter->>SDK: sdkMeeting.muteVideo() @@ -356,7 +385,7 @@ sequenceDiagram Note over User: Video is now OFF — click again to start User->>Component: Click camera button - Component->>Adapter: action({ meetingID }) + Component->>Adapter: action(meetingID) Adapter->>Adapter: handleLocalVideo(ID) Adapter->>Adapter: Set localVideo.muting = false Adapter->>SDK: sdkMeeting.unmuteVideo() @@ -382,7 +411,7 @@ sequenceDiagram participant Backend User->>Component: Click share screen button - Component->>Adapter: action({ meetingID }) + Component->>Adapter: action(meetingID) Adapter->>Adapter: handleLocalShare(ID) Adapter->>SDK: sdkMeeting.getMediaStreams({ sendShare: true }) SDK->>User: Browser screen picker dialog (getDisplayMedia) @@ -398,7 +427,7 @@ sequenceDiagram Note over User: Sharing active — click again to stop User->>Component: Click stop sharing - Component->>Adapter: action({ meetingID }) + Component->>Adapter: action(meetingID) Adapter->>Adapter: handleLocalShare(ID) Adapter->>Adapter: stopStream(localShare.stream) Adapter->>SDK: sdkMeeting.updateShare({ sendShare: false, receiveShare: true }) @@ -424,7 +453,7 @@ sequenceDiagram Note over Adapter: Client-side only — no Backend call User->>Component: Click roster button - Component->>Adapter: action({ meetingID }) + Component->>Adapter: action(meetingID) Adapter->>Adapter: toggleRoster(ID) Adapter->>Adapter: meeting.showRoster = !meeting.showRoster Adapter->>Adapter: Emit observable { showRoster: true } @@ -432,7 +461,7 @@ sequenceDiagram Component->>Component: Render WebexMemberRoster panel User->>Component: Click roster button (close) - Component->>Adapter: action({ meetingID }) + Component->>Adapter: action(meetingID) Adapter->>Adapter: toggleRoster(ID) Adapter->>Adapter: Emit { showRoster: false } Adapter-->>Component: Observable emits @@ -453,7 +482,7 @@ sequenceDiagram participant SDK as webex-js-sdk User->>Component: Click settings button - Component->>Adapter: SettingsControl.action({ meetingID }) + Component->>Adapter: SettingsControl.action(meetingID) Adapter->>Adapter: toggleSettings(ID) Adapter->>Adapter: Clone current streams to settings.preview Adapter->>Adapter: Emit { settings.visible: true } @@ -463,7 +492,7 @@ sequenceDiagram Note over User: User selects a different camera User->>Component: Select new camera from dropdown - Component->>Adapter: SwitchCameraControl.action({ meetingID, cameraId }) + Component->>Adapter: SwitchCameraControl.action(meetingID, cameraId) Adapter->>Adapter: switchCamera(ID, cameraId) Adapter->>SDK: sdkMeeting.getMediaStreams({ sendVideo: true }, { video: { deviceId } }) SDK->>SDK: getUserMedia with new deviceId @@ -472,7 +501,7 @@ sequenceDiagram Adapter-->>Component: Settings preview re-renders with new camera User->>Component: Close settings modal - Component->>Adapter: SettingsControl.action({ meetingID }) + Component->>Adapter: SettingsControl.action(meetingID) Adapter->>Adapter: toggleSettings(ID) Adapter->>Adapter: Replace meeting streams with preview streams @@ -500,7 +529,7 @@ sequenceDiagram participant Backend User->>Component: Click leave meeting button - Component->>Adapter: action({ meetingID }) + Component->>Adapter: action(meetingID) Adapter->>Adapter: leaveMeeting(ID) Adapter->>Adapter: removeMedia(ID) — stop all local streams Adapter->>SDK: sdkMeeting.leave() @@ -534,7 +563,7 @@ sequenceDiagram Component->>Component: Open WebexMeetingGuestAuthentication modal User->>Component: Enter password, click "Join as Guest" - Component->>Adapter: action({ meetingID, meetingPasswordOrPin: password }) + Component->>Adapter: action(meetingID) Adapter->>SDK: joinMeeting(ID, { password }) SDK->>Backend: Verify password and join Backend-->>SDK: Result @@ -554,7 +583,7 @@ sequenceDiagram User->>Component: Click "I'm the host" Component->>Component: Switch to WebexMeetingHostAuthentication modal User->>Component: Enter host pin, click "Start Meeting" - Component->>Adapter: action({ meetingID, meetingPasswordOrPin: hostPin }) + Component->>Adapter: action(meetingID) Adapter->>SDK: joinMeeting(ID, { hostKey: hostPin }) ``` @@ -743,13 +772,13 @@ Renders as a CANCEL type button. - React strict mode causing double initialization - Missing cleanup on prop changes -- Missing dependency array in useEffect +- `componentWillUnmount` not disconnecting adapter or cleaning up observers **Solutions:** -- Use a ref to track initialization state -- Implement proper cleanup in useEffect return -- Guard against re-initialization +- Use an instance property to track initialization state +- Implement proper cleanup in `componentWillUnmount` +- Guard against re-initialization in `componentDidMount` --- @@ -772,6 +801,12 @@ Renders as a CANCEL type button. ## Related Documentation - [Agent Documentation](./AGENTS.md) - Widget usage and API reference +- [React Patterns](../../../ai-docs/patterns/react-patterns.md) - Component patterns +- [TypeScript Patterns](../../../ai-docs/patterns/typescript-patterns.md) - Type safety and naming conventions +- [Web Component Patterns](../../../ai-docs/patterns/web-component-patterns.md) - r2wc patterns +- [Testing Patterns](../../../ai-docs/patterns/testing-patterns.md) - Jest, RTL, Playwright guidelines --- +_Last Updated: 2026-03-12_ +