From 354e58e50c80530f89d9427c32c80c66581ffda8 Mon Sep 17 00:00:00 2001 From: Ritesh Singh <133033102+riteshfyi@users.noreply.github.com> Date: Tue, 31 Mar 2026 09:22:54 +0530 Subject: [PATCH 01/21] docs(ai-docs): add docs for meetings widget (#642) --- packages/@webex/widgets/ai-docs/AGENTS.md | 357 +++++++ .../@webex/widgets/ai-docs/ARCHITECTURE.md | 873 ++++++++++++++++++ 2 files changed, 1230 insertions(+) create mode 100644 packages/@webex/widgets/ai-docs/AGENTS.md create mode 100644 packages/@webex/widgets/ai-docs/ARCHITECTURE.md diff --git a/packages/@webex/widgets/ai-docs/AGENTS.md b/packages/@webex/widgets/ai-docs/AGENTS.md new file mode 100644 index 000000000..6fab0e3e5 --- /dev/null +++ b/packages/@webex/widgets/ai-docs/AGENTS.md @@ -0,0 +1,357 @@ +# 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. + +- **How to proceed:** + - Carefully load and follow the guidance, templates, and routing logic given in this `AGENTS.md`. + +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. + +**Widget:** Meetings + +**Package:** `@webex/widgets` + +**Version:** See [package.json](../package.json) + +**Location:** `packages/@webex/widgets` + +--- + +## 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) + +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 {WebexMeetingsWidget} from '@webex/widgets'; + +function App() { + return ( + + ); +} +``` + +#### With All Optional Props + +```jsx + +``` + +#### What Happens Internally + +When `WebexMeetingsWidget` mounts, the `withAdapter` HOC: + +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 + +#### 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({ meetingID })` 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 | `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` | + + +--- + +## 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 | +| `@webex/component-adapter-interfaces` | Interface definitions for component adapters | + + +### Peer Dependencies + + +| Package | Purpose | +| ------------ | ------------------------ | +| `react` | React framework | +| `react-dom` | React DOM rendering | +| `prop-types` | React prop type checking | +| `webex` | Core Webex SDK (peer) | + + +--- + +## API Reference + +### 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 | 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 receives its adapter via `AdapterContext.Provider`, which is set up by the `withAdapter` HOC wrapping the widget. + +### Hooks + +**Source:** [`@webex/components`](https://github.com/webex/components) → [`src/components/hooks/`](https://github.com/webex/components/tree/master/src/components/hooks) + + +| 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) + +**Source:** [`@webex/sdk-component-adapter`](https://github.com/webex/sdk-component-adapter) → [`src/WebexSDKAdapter.js`](https://github.com/webex/sdk-component-adapter/blob/master/src/WebexSDKAdapter.js) + + +| 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 + +**Source:** [`@webex/sdk-component-adapter`](https://github.com/webex/sdk-component-adapter) → [`src/MeetingsSDKAdapter.js`](https://github.com/webex/sdk-component-adapter/blob/master/src/MeetingsSDKAdapter.js) + + +| 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` | `Promise` | 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 + +**Source:** [`@webex/sdk-component-adapter`](https://github.com/webex/sdk-component-adapter) → [`src/MeetingsSDKAdapter/controls/`](https://github.com/webex/sdk-component-adapter/tree/master/src/MeetingsSDKAdapter/controls) + +Each control's `action()` receives a destructured object from the [`useMeetingControl`](https://github.com/webex/components/blob/master/src/components/hooks/useMeetingControl.js) hook and calls the corresponding adapter method internally. + +| Control | File | Adapter Method Called | +| ------------------------- | ---------------------------- | -------------------------------------------- | +| `AudioControl` | `AudioControl.js` | `handleLocalAudio(meetingID)` | +| `VideoControl` | `VideoControl.js` | `handleLocalVideo(meetingID)` | +| `ShareControl` | `ShareControl.js` | `handleLocalShare(meetingID)` | +| `JoinControl` | `JoinControl.js` | `joinMeeting(meetingID, { password, name })` | +| `ExitControl` | `ExitControl.js` | `leaveMeeting(meetingID)` | +| `RosterControl` | `RosterControl.js` | `toggleRoster(meetingID)` | +| `SettingsControl` | `SettingsControl.js` | `toggleSettings(meetingID)` | +| `SwitchCameraControl` | `SwitchCameraControl.js` | `switchCamera(meetingID, cameraId)` | +| `SwitchMicrophoneControl` | `SwitchMicrophoneControl.js` | `switchMicrophone(meetingID, microphoneId)` | +| `SwitchSpeakerControl` | `SwitchSpeakerControl.js` | `switchSpeaker(meetingID, speakerId)` | + + +### Control IDs for WebexMeetingControlBar + +**Source:** Control IDs are registered in [`@webex/sdk-component-adapter`](https://github.com/webex/sdk-component-adapter) → [`src/MeetingsSDKAdapter.js`](https://github.com/webex/sdk-component-adapter/blob/master/src/MeetingsSDKAdapter.js) and rendered by [`WebexMeetingControlBar`](https://github.com/webex/components/tree/master/src/components/WebexMeetingControlBar) from `@webex/components`. The widget passes them via the `controls` prop. + +| Control ID | Class | Type | Available | +| ------------------- | ------------------------- | ----------- | --------------------- | +| `mute-audio` | `AudioControl` | BUTTON | Pre-join + In-meeting | +| `mute-video` | `VideoControl` | BUTTON | 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` | BUTTON | Pre-join + In-meeting | +| `switch-camera` | `SwitchCameraControl` | MULTISELECT | Settings panel | +| `switch-microphone` | `SwitchMicrophoneControl` | MULTISELECT | Settings panel | +| `switch-speaker` | `SwitchSpeakerControl` | MULTISELECT | Settings panel | + + +--- + +## Installation + +The widget declares `react`, `react-dom`, `prop-types`, and `webex` as **peer dependencies**. Consumers must install them alongside the widget at the versions specified in [`package.json`](../package.json) under `peerDependencies`. + +```bash +# yarn +yarn add @webex/widgets react@ react-dom@ prop-types@ webex@ + +# npm +npm install @webex/widgets react@ react-dom@ prop-types@ webex@ +``` + +> **Important:** Some peer dependencies require **exact versions** (no caret/tilde). Check [`package.json`](../package.json) for the precise version constraints before installing. Running these commands without version specifiers will install the latest versions, which may not satisfy the peer dependency requirements. + +## Additional Resources + +For detailed architecture, event flows, data structures, and troubleshooting, see [ARCHITECTURE.md](./ARCHITECTURE.md). + +--- + +*Last Updated: 2026-03-27* \ No newline at end of file diff --git a/packages/@webex/widgets/ai-docs/ARCHITECTURE.md b/packages/@webex/widgets/ai-docs/ARCHITECTURE.md new file mode 100644 index 000000000..aff2bbfd9 --- /dev/null +++ b/packages/@webex/widgets/ai-docs/ARCHITECTURE.md @@ -0,0 +1,873 @@ +# 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] + WFH[WebexWaitingForHost] + MCB[WebexMeetingControlBar] + WLM[WebexLocalMedia] + WRM[WebexRemoteMedia] + WMR[WebexMemberRoster] + WS[WebexSettings] + WGA[WebexMeetingGuestAuthentication] + WHA[WebexMeetingHostAuthentication] + WMI[WebexMeetingInfo] + WMA[WebexMediaAccess] + end + + subgraph "Adapter Layer (sdk-component-adapter)" + ADAPT[MeetingsSDKAdapter] + AC[AudioControl] + VC[VideoControl] + SC[ShareControl] + JC[JoinControl] + EC[ExitControl] + RC[RosterControl] + STC[SettingsControl] + SCC[SwitchCameraControl] + SMC[SwitchMicrophoneControl] + SSC[SwitchSpeakerControl] + 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 + W --> WMA + WM --> WIM + WM --> WIN + WM --> WFH + WM --> MCB + WM --> WMR + WM --> WS + WM --> WGA + WM --> WHA + WIN --> WLM + WIN --> WRM + WIN --> WMI + WIN --> WGA + WIN --> WHA + WIM --> WMI + WFH --> WMI + MCB --> AC & VC & SC & JC & EC & RC & STC + STC --> SCC & SMC & SSC + AC & VC & SC & JC & EC & RC & STC & SCC & SMC & SSC --> 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 +``` + + + +### 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 + +**Source:** All components below are from `[@webex/components](https://github.com/webex/components)` → `[src/components/](https://github.com/webex/components/tree/master/src/components)` + + +| Component | Folder | Purpose | Render Condition (parent decides) | Internal Data Source (own hooks) | +| --------------------------------- | ---------------------------------- | --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `WebexMeeting` | `WebexMeeting/` | Master orchestrator — renders correct view based on meeting state | Always (top-level) | `useMeeting(meetingID)` → `ID`, `localAudio`, `localVideo`, `state`, `showRoster`, `settings`, `passwordRequired` | +| `WebexInterstitialMeeting` | `WebexInterstitialMeeting/` | Pre-join lobby with local media preview | `state === NOT_JOINED` | `useMeeting(meetingID)` → `localVideo` | +| `WebexInMeeting` | `WebexInMeeting/` | Active meeting view with remote + local media | `state === JOINED` | `useMeeting(meetingID)` → `remoteShare`, `localShare`, `passwordRequired`, `state` | +| `WebexWaitingForHost` | `WebexWaitingForHost/` | Waiting room when host hasn't started (renders for `LOBBY` state) | `else` (not JOINED/NOT_JOINED/LEFT — typically `LOBBY`) | `useMeeting(meetingID)` → `ID`; uses `AdapterContext` → `leaveMeeting(ID)` | +| `WebexMeetingControlBar` | `WebexMeetingControlBar/` | Renders meeting control buttons | Always (when state is truthy and not LEFT) | `useMeeting(meetingID)` → `state`; computes `isActive = state === JOINED` to select controls | +| `WebexMeetingControl` | `WebexMeetingControl/` | Individual control button | Rendered by `WebexMeetingControlBar` | `useMeetingControl(type, meetingID)` → `[action, display]` | +| `WebexMeetingInfo` | `WebexMeetingInfo/` | Meeting title and time overlay | Rendered inside `WebexInMeeting`, `WebexInterstitialMeeting`, `WebexWaitingForHost` | `useMeeting(meetingID)` → `ID`, `startTime`, `endTime`, `title` | +| `WebexMediaAccess` | `WebexMediaAccess/` | Browser media permission prompt (camera/microphone) | `localAudio.permission === 'ASKING'` or `localVideo.permission === 'ASKING'` (in widget) | `useMeeting(meetingID)` → `ID`; uses `AdapterContext` → `ignoreVideoAccessPrompt` / `ignoreAudioAccessPrompt` | +| `WebexLocalMedia` | `WebexLocalMedia/` | Local camera/screen/preview video | Rendered inside `WebexInMeeting`, `WebexInterstitialMeeting`, `WebexWaitingForHost` | `useMeeting(meetingID)` → `localVideo`, `localShare`, `settings`; also `useMe()` → `ID` | +| `WebexRemoteMedia` | `WebexRemoteMedia/` | Remote participant video, audio, and share | Rendered inside `WebexInMeeting` | `useMeeting(meetingID)` → `remoteAudio`, `remoteVideo`, `remoteShare`, `error`, `speakerID`; also `useMembers()` | +| `WebexMemberRoster` | `WebexMemberRoster/` | Participant list panel | `showRoster === true` (in `WebexMeeting`) | `useMembers(destinationID, destinationType)`; `useMe()` → `orgID`. Does NOT use `useMeeting` | +| `WebexSettings` | `WebexSettings/` | Audio/video device settings modal (tabs) | `settings.visible === true` (in `WebexMeeting`) | None — delegates to `WebexAudioSettings` + `WebexVideoSettings` children | +| `WebexMeetingGuestAuthentication` | `WebexMeetingGuestAuthentication/` | Guest password entry (rendered in both `WebexMeeting` and `WebexInMeeting`) | `passwordRequired && !meetingPasswordOrPin && state === NOT_JOINED` | `useMeeting(meetingID)` → `ID`, `failureReason`, `invalidPassword`, `requiredCaptcha`; uses `AdapterContext` → `joinMeeting`, `clearInvalidPasswordFlag`, `refreshCaptcha` | +| `WebexMeetingHostAuthentication` | `WebexMeetingHostAuthentication/` | Host pin entry (rendered in both `WebexMeeting` and `WebexInMeeting`) | User clicks "I'm the host" in guest modal | `useMeeting(meetingID)` → `ID`, `invalidHostKey`; uses `AdapterContext` → `joinMeeting`, `clearInvalidHostKeyFlag` | + + +--- + +## SDK Integration + +**Repos:** [webex-js-sdk](https://github.com/webex/webex-js-sdk) · `[@webex/sdk-component-adapter](https://github.com/webex/sdk-component-adapter)` → `[src/MeetingsSDKAdapter.js](https://github.com/webex/sdk-component-adapter/blob/master/src/MeetingsSDKAdapter.js)`, `[src/MeetingsSDKAdapter/controls/](https://github.com/webex/sdk-component-adapter/tree/master/src/MeetingsSDKAdapter/controls)` + + +| Area | SDK Methods | Adapter Methods | Control Class | +| ----------------- | ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | ------------------------- | +| 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` | +| 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' | 'LOBBY' | 'JOINED' | 'LEFT' + + localAudio: { + stream: MediaStream | null + permission: string | null // 'ASKING' | 'ALLOWED' | 'DISMISSED' | 'DENIED' | 'DISABLED' | 'IGNORED' | 'ERROR' | null + muting: boolean | undefined // true = muting in progress, false = unmuting, undefined = idle + ignoreMediaAccessPrompt: Function | undefined // callback to dismiss the media access prompt and proceed without audio + } + localVideo: { + stream: MediaStream | null + permission: string | null // 'ASKING' | 'ALLOWED' | 'DISMISSED' | 'DENIED' | 'DISABLED' | 'IGNORED' | 'ERROR' | null + muting: boolean | undefined + error: string | null // e.g. 'Video not supported on iOS 15.1' + ignoreMediaAccessPrompt: Function | undefined // callback to dismiss the media access prompt and proceed without video + } + 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 + remoteShareStream: MediaStream | null // raw remote share stream (may differ from remoteShare timing) + remoteSharing: boolean // true when remote participant is sharing + + invalidPassword: boolean // true when entered password was wrong + invalidHostKey: boolean // true when entered host key was wrong + failureReason: string | undefined // reason from server when password verification fails + + cameraID: string | null + microphoneID: string | null + speakerID: string | null // '' on creation, null after removeMedia +} +``` + +--- + +## 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: new Webex({ 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 }) + 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 }) + 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 }) + Adapter->>SDK: joinMeeting(ID, { hostKey: hostPin }) +``` + + + +--- + +### 11. Waiting for Host (LOBBY State) + +```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 — state is LOBBY + + Component->>Component: state is LOBBY (falls into else catch-all) + 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: members:update event, self.state changes to JOINED + Adapter->>Adapter: Emit { state: JOINED } + Adapter-->>Component: Observable emits + + Component->>Component: Transition: WebexWaitingForHost → WebexInMeeting +``` + +*The `LOBBY` state is defined in `MeetingState` from `@webex/component-adapter-interfaces`. The adapter propagates `sdkMeeting.joinedWith.state` directly from SDK member updates, so when the SDK reports `LOBBY` (e.g., in waiting-room flows), it reaches the UI unfiltered. The `WebexMeeting` component renders `WebexWaitingForHost` for any state that is not `NOT_JOINED`, `JOINED`, or `LEFT` — which is where `LOBBY` lands.* + + + +--- + +## Meeting State Machine + +```mermaid +stateDiagram-v2 + [*] --> NOT_JOINED: SDK + Adapter ready, meeting created + + NOT_JOINED --> JOINED: User joins (JoinControl) + NOT_JOINED --> LOBBY: User joins but waiting for host admission + + LOBBY --> JOINED: Host admits user / host starts meeting + + JOINED --> LEFT: User leaves (ExitControl) + + LEFT --> [*]: Widget unmounts +``` + + + +*These are the four states defined by the `MeetingState` enum in `@webex/component-adapter-interfaces` and emitted by the adapter's meeting observable. The adapter propagates `self.state` from SDK member updates without filtering, so all four values (`NOT_JOINED`, `LOBBY`, `JOINED`, `LEFT`) can appear. The `WebexMeeting` component also handles a falsy state (loading) and uses an `else` catch-all that renders `WebexWaitingForHost` — this is where the `LOBBY` state lands.* + +--- + +## Control Display States + +**Source:** [`@webex/sdk-component-adapter`](https://github.com/webex/sdk-component-adapter) → [`src/MeetingsSDKAdapter/controls/`](https://github.com/webex/sdk-component-adapter/tree/master/src/MeetingsSDKAdapter/controls) + +### 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 | + + + *If `localVideo.error` is set (e.g. `'Video not supported on iOS 15.1'`), the tooltip shows the error string instead of "No camera available".* + +### 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 or SDK instances + +**Important:** SDK initialization (`new Webex()`, `new WebexSDKAdapter()`) and meeting creation do **not** happen in `WebexMeetingsWidget`'s lifecycle methods. They happen in the `withAdapter` and `withMeeting` HOC wrappers from `@webex/components` (see `src/widgets/WebexMeetings/WebexMeetings.jsx:259-278`). The widget class's own `componentDidMount`/`componentWillUnmount` only manages focus and accessibility wiring — patching those will not fix duplicate initialization. + +**Possible Causes:** + +- React strict mode causing `withAdapter`/`withMeeting` HOCs to mount twice +- Consumer re-rendering the widget with a new `accessToken` or `meetingDestination` prop, triggering the adapter factory again +- Missing cleanup in the HOC layer on unmount + +**Solutions:** + +- Investigate the `withAdapter` HOC in `@webex/components` — that is where `adapter.connect()`/`adapter.disconnect()` is managed +- Investigate the `withMeeting` HOC — that is where `createMeeting(destination)` is called +- Ensure the consumer does not remount `WebexMeetingsWidget` unnecessarily (e.g., by changing a React `key` prop) +- For React strict mode issues, the fix must be in the HOC layer (in `@webex/components`), not in this widget class + +--- + +### 6. SettingsControl Display State Never Toggles + +**Symptoms:** Settings button always shows INACTIVE state even after opening settings + +**Root Cause:** This is a known inconsistency in `sdk-component-adapter`. `SettingsControl.display()` reads `showSettings` from the meeting object, but `MeetingsSDKAdapter.toggleSettings()` writes to `settings.visible`. The `showSettings` property is never set by the adapter, so `display()` always sees `undefined` (falsy) and emits INACTIVE. + +**Impact:** The settings button icon/text never toggles visually, but the settings modal still opens/closes because `WebexSettings` in `@webex/components` reads `settings.visible` directly. + +**Workaround:** None needed for functionality — the modal works. The display state is cosmetic only. + +--- + +### 7. 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 +- [React Patterns](../../../../ai-docs/patterns/react-patterns.md) - Component patterns +- [TypeScript Patterns](../../../../ai-docs/patterns/typescript-patterns.md) - Type safety and naming conventions +- [Testing Patterns](../../../../ai-docs/patterns/testing-patterns.md) - Jest, RTL, Playwright guidelines + +--- + +*Last Updated: 2026-03-27* \ No newline at end of file From bb3bea7f90be652b3c163da9aa19e34fd346244f Mon Sep 17 00:00:00 2001 From: Ritesh Singh <133033102+riteshfyi@users.noreply.github.com> Date: Wed, 8 Apr 2026 15:46:14 +0530 Subject: [PATCH 02/21] fix(meetings): address version resolution (#664) --- package.json | 4 +- packages/@webex/widgets/package.json | 8 +- packages/@webex/widgets/webpack.config.js | 4 +- .../samples-meeting-app/package.json | 4 +- .../samples-meeting-app/webpack.config.js | 9 + yarn.lock | 978 +++++------------- 6 files changed, 310 insertions(+), 697 deletions(-) diff --git a/package.json b/package.json index 44a4242cd..0988a96c3 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ ], "packageManager": "yarn@4.5.1", "devDependencies": { + "@babel/plugin-transform-runtime": "^7.29.0", "@babel/preset-typescript": "7.25.9", "@playwright/test": "^1.51.1", "@semantic-release/changelog": "^6.0.3", @@ -18,6 +19,7 @@ "@semantic-release/git": "^10.0.1", "@semantic-release/github": "^11.0.1", "@testing-library/react-hooks": "^8.0.1", + "@types/babel__plugin-transform-runtime": "^7.9.5", "@webex/cli-tools": "0.0.0-next.2", "@webex/package-tools": "0.0.0-next.6", "babel-jest": "^29.7.0", @@ -70,4 +72,4 @@ "prepare": "husky", "package-tools": "webex-package-tools" } -} +} \ No newline at end of file diff --git a/packages/@webex/widgets/package.json b/packages/@webex/widgets/package.json index 3dec22ace..465b98c47 100644 --- a/packages/@webex/widgets/package.json +++ b/packages/@webex/widgets/package.json @@ -4,6 +4,9 @@ "engines": { "node": ">=20.13.1" }, + "installConfig": { + "hoistingLimits": "dependencies" + }, "main": "dist/webexWidgets.mjs", "module": "dist/webexWidgets.mjs", "scripts": { @@ -53,8 +56,11 @@ "@babel/plugin-transform-runtime": "^7.16.0", "@babel/preset-env": "^7.8.4", "@babel/preset-react": "^7.8.3", + "@babel/register": "^7.25.9", "@commitlint/cli": "^8.3.5", "@commitlint/config-conventional": "^8.3.4", + "@momentum-ui/core": "19.16.0", + "@momentum-ui/icons": "8.28.5", "@momentum-ui/react": "^23.21.4", "@semantic-release/changelog": "^6.0.0", "@semantic-release/git": "^10.0.0", @@ -117,7 +123,7 @@ "prop-types": "^15.7.2", "react": "18.3.1", "react-dom": "18.3.1", - "webex": "2.60.2" + "webex": "2.60.4" }, "babel": { "presets": [ diff --git a/packages/@webex/widgets/webpack.config.js b/packages/@webex/widgets/webpack.config.js index 74c652fb6..815552ca6 100644 --- a/packages/@webex/widgets/webpack.config.js +++ b/packages/@webex/widgets/webpack.config.js @@ -74,8 +74,8 @@ module.exports = function(env, argv) { use: [isDemo ? 'style-loader' : MiniCssExtractPlugin.loader, 'css-loader'], include: [ resolveMonorepoRoot('node_modules/@momentum-ui'), - resolveMonorepoRoot('node_modules/@webex/components'), - path.resolve(__dirname, 'packages'), + path.resolve(__dirname, 'node_modules/@webex/components'), + path.resolve(__dirname, 'node_modules/@momentum-ui'), ], }, { diff --git a/widgets-samples/samples-meeting-app/package.json b/widgets-samples/samples-meeting-app/package.json index ab49b6dfe..a8b59adc3 100644 --- a/widgets-samples/samples-meeting-app/package.json +++ b/widgets-samples/samples-meeting-app/package.json @@ -9,11 +9,11 @@ "serve": "webpack serve --mode development" }, "dependencies": { + "@momentum-ui/react": "^23.21.4", "@webex/widgets": "workspace:*", "prop-types": "^15.7.2", "react": "18.3.1", - "react-dom": "18.3.1", - "webex": "2.60.2" + "react-dom": "18.3.1" }, "devDependencies": { "@babel/core": "^7.25.2", diff --git a/widgets-samples/samples-meeting-app/webpack.config.js b/widgets-samples/samples-meeting-app/webpack.config.js index 7d1cdbe25..42ba1dc42 100644 --- a/widgets-samples/samples-meeting-app/webpack.config.js +++ b/widgets-samples/samples-meeting-app/webpack.config.js @@ -4,6 +4,8 @@ const {CleanWebpackPlugin} = require('clean-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const {version} = require('./package.json'); +const widgetsDir = path.dirname(require.resolve('@webex/widgets/package.json')); + module.exports = function(env, argv) { return { cache: true, @@ -18,6 +20,13 @@ module.exports = function(env, argv) { devtool: argv.mode === 'production' ? 'source-map' : 'inline-source-map', resolve: { extensions: ['.js', '.jsx'], + alias: { + react: path.dirname(require.resolve('react/package.json', { paths: [widgetsDir] })), + 'react-dom': path.dirname(require.resolve('react-dom/package.json', { paths: [widgetsDir] })), + 'prop-types': path.dirname(require.resolve('prop-types/package.json', { paths: [widgetsDir] })), + webex: path.dirname(require.resolve('webex/package', { paths: [widgetsDir] })), + '@webex/common': path.dirname(require.resolve('@webex/common/package.json', { paths: [widgetsDir] })), + }, fallback: { "buffer": require.resolve("buffer/"), "crypto": require.resolve("crypto-browserify"), diff --git a/yarn.lock b/yarn.lock index f3ca3f2b3..c28bac9ba 100644 --- a/yarn.lock +++ b/yarn.lock @@ -77,6 +77,17 @@ __metadata: languageName: node linkType: hard +"@babel/code-frame@npm:^7.28.6, @babel/code-frame@npm:^7.29.0": + version: 7.29.0 + resolution: "@babel/code-frame@npm:7.29.0" + dependencies: + "@babel/helper-validator-identifier": "npm:^7.28.5" + js-tokens: "npm:^4.0.0" + picocolors: "npm:^1.1.1" + checksum: 10c0/d34cc504e7765dfb576a663d97067afb614525806b5cad1a5cc1a7183b916fec8ff57fa233585e3926fd5a9e6b31aae6df91aa81ae9775fb7a28f658d3346f0d + languageName: node + linkType: hard + "@babel/compat-data@npm:^7.20.5, @babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.25.9": version: 7.25.9 resolution: "@babel/compat-data@npm:7.25.9" @@ -98,6 +109,13 @@ __metadata: languageName: node linkType: hard +"@babel/compat-data@npm:^7.28.6": + version: 7.29.0 + resolution: "@babel/compat-data@npm:7.29.0" + checksum: 10c0/08f348554989d23aa801bf1405aa34b15e841c0d52d79da7e524285c77a5f9d298e70e11d91cc578d8e2c9542efc586d50c5f5cf8e1915b254a9dcf786913a94 + languageName: node + linkType: hard + "@babel/core@npm:7.12.9": version: 7.12.9 resolution: "@babel/core@npm:7.12.9" @@ -252,6 +270,19 @@ __metadata: languageName: node linkType: hard +"@babel/generator@npm:^7.29.0": + version: 7.29.1 + resolution: "@babel/generator@npm:7.29.1" + dependencies: + "@babel/parser": "npm:^7.29.0" + "@babel/types": "npm:^7.29.0" + "@jridgewell/gen-mapping": "npm:^0.3.12" + "@jridgewell/trace-mapping": "npm:^0.3.28" + jsesc: "npm:^3.0.2" + checksum: 10c0/349086e6876258ef3fb2823030fee0f6c0eb9c3ebe35fc572e16997f8c030d765f636ddc6299edae63e760ea6658f8ee9a2edfa6d6b24c9a80c917916b973551 + languageName: node + linkType: hard + "@babel/helper-annotate-as-pure@npm:^7.18.6, @babel/helper-annotate-as-pure@npm:^7.25.9": version: 7.25.9 resolution: "@babel/helper-annotate-as-pure@npm:7.25.9" @@ -297,6 +328,19 @@ __metadata: languageName: node linkType: hard +"@babel/helper-compilation-targets@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/helper-compilation-targets@npm:7.28.6" + dependencies: + "@babel/compat-data": "npm:^7.28.6" + "@babel/helper-validator-option": "npm:^7.27.1" + browserslist: "npm:^4.24.0" + lru-cache: "npm:^5.1.1" + semver: "npm:^6.3.1" + checksum: 10c0/3fcdf3b1b857a1578e99d20508859dbd3f22f3c87b8a0f3dc540627b4be539bae7f6e61e49d931542fe5b557545347272bbdacd7f58a5c77025a18b745593a50 + languageName: node + linkType: hard + "@babel/helper-create-class-features-plugin@npm:^7.18.6, @babel/helper-create-class-features-plugin@npm:^7.21.0": version: 7.26.9 resolution: "@babel/helper-create-class-features-plugin@npm:7.26.9" @@ -377,6 +421,28 @@ __metadata: languageName: node linkType: hard +"@babel/helper-define-polyfill-provider@npm:^0.6.5, @babel/helper-define-polyfill-provider@npm:^0.6.8": + version: 0.6.8 + resolution: "@babel/helper-define-polyfill-provider@npm:0.6.8" + dependencies: + "@babel/helper-compilation-targets": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.28.6" + debug: "npm:^4.4.3" + lodash.debounce: "npm:^4.0.8" + resolve: "npm:^1.22.11" + peerDependencies: + "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 + checksum: 10c0/306a169f2cb285f368578219ef18ea9702860d3d02d64334f8d45ea38648be0b9e1edad8c8f732fa34bb4206ccbb9883c395570fd57ab7bbcf293bc5964c5b3a + languageName: node + linkType: hard + +"@babel/helper-globals@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/helper-globals@npm:7.28.0" + checksum: 10c0/5a0cd0c0e8c764b5f27f2095e4243e8af6fa145daea2b41b53c0c1414fe6ff139e3640f4e2207ae2b3d2153a1abd346f901c26c290ee7cb3881dd922d4ee9232 + languageName: node + linkType: hard + "@babel/helper-member-expression-to-functions@npm:^7.25.9": version: 7.25.9 resolution: "@babel/helper-member-expression-to-functions@npm:7.25.9" @@ -397,6 +463,16 @@ __metadata: languageName: node linkType: hard +"@babel/helper-module-imports@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/helper-module-imports@npm:7.28.6" + dependencies: + "@babel/traverse": "npm:^7.28.6" + "@babel/types": "npm:^7.28.6" + checksum: 10c0/b49d8d8f204d9dbfd5ac70c54e533e5269afb3cea966a9d976722b13e9922cc773a653405f53c89acb247d5aebdae4681d631a3ae3df77ec046b58da76eda2ac + languageName: node + linkType: hard + "@babel/helper-module-transforms@npm:^7.12.1, @babel/helper-module-transforms@npm:^7.25.2, @babel/helper-module-transforms@npm:^7.26.0": version: 7.26.0 resolution: "@babel/helper-module-transforms@npm:7.26.0" @@ -454,6 +530,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-plugin-utils@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/helper-plugin-utils@npm:7.28.6" + checksum: 10c0/3f5f8acc152fdbb69a84b8624145ff4f9b9f6e776cb989f9f968f8606eb7185c5c3cfcf3ba08534e37e1e0e1c118ac67080610333f56baa4f7376c99b5f1143d + languageName: node + linkType: hard + "@babel/helper-remap-async-to-generator@npm:^7.25.9": version: 7.25.9 resolution: "@babel/helper-remap-async-to-generator@npm:7.25.9" @@ -520,6 +603,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-string-parser@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-string-parser@npm:7.27.1" + checksum: 10c0/8bda3448e07b5583727c103560bcf9c4c24b3c1051a4c516d4050ef69df37bb9a4734a585fe12725b8c2763de0a265aa1e909b485a4e3270b7cfd3e4dbe4b602 + languageName: node + linkType: hard + "@babel/helper-validator-identifier@npm:^7.25.9": version: 7.25.9 resolution: "@babel/helper-validator-identifier@npm:7.25.9" @@ -527,6 +617,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-validator-identifier@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/helper-validator-identifier@npm:7.28.5" + checksum: 10c0/42aaebed91f739a41f3d80b72752d1f95fd7c72394e8e4bd7cdd88817e0774d80a432451bcba17c2c642c257c483bf1d409dd4548883429ea9493a3bc4ab0847 + languageName: node + linkType: hard + "@babel/helper-validator-option@npm:^7.24.7, @babel/helper-validator-option@npm:^7.24.8, @babel/helper-validator-option@npm:^7.25.9": version: 7.25.9 resolution: "@babel/helper-validator-option@npm:7.25.9" @@ -534,6 +631,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-validator-option@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-validator-option@npm:7.27.1" + checksum: 10c0/6fec5f006eba40001a20f26b1ef5dbbda377b7b68c8ad518c05baa9af3f396e780bdfded24c4eef95d14bb7b8fd56192a6ed38d5d439b97d10efc5f1a191d148 + languageName: node + linkType: hard + "@babel/helper-wrap-function@npm:^7.25.9": version: 7.25.9 resolution: "@babel/helper-wrap-function@npm:7.25.9" @@ -649,6 +753,17 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.28.6, @babel/parser@npm:^7.29.0": + version: 7.29.2 + resolution: "@babel/parser@npm:7.29.2" + dependencies: + "@babel/types": "npm:^7.29.0" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/e5a4e69e3ac7acdde995f37cf299a68458cfe7009dff66bd0962fd04920bef287201169006af365af479c08ff216bfefbb595e331f87f6ae7283858aebbc3317 + languageName: node + linkType: hard + "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.25.3, @babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.25.9": version: 7.25.9 resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.25.9" @@ -1752,6 +1867,22 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-runtime@npm:^7.29.0": + version: 7.29.0 + resolution: "@babel/plugin-transform-runtime@npm:7.29.0" + dependencies: + "@babel/helper-module-imports": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.28.6" + babel-plugin-polyfill-corejs2: "npm:^0.4.14" + babel-plugin-polyfill-corejs3: "npm:^0.13.0" + babel-plugin-polyfill-regenerator: "npm:^0.6.5" + semver: "npm:^6.3.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/05a451cb96a1e6ccfdd1a123773208615cd14cb156aa0aa99a448d86e4326b36b9ab2be8267037bd27644a5918dac88378b791d020b3c08a4fd8f3415621a006 + languageName: node + linkType: hard + "@babel/plugin-transform-shorthand-properties@npm:^7.12.1, @babel/plugin-transform-shorthand-properties@npm:^7.24.7, @babel/plugin-transform-shorthand-properties@npm:^7.25.9": version: 7.25.9 resolution: "@babel/plugin-transform-shorthand-properties@npm:7.25.9" @@ -2411,6 +2542,17 @@ __metadata: languageName: node linkType: hard +"@babel/template@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/template@npm:7.28.6" + dependencies: + "@babel/code-frame": "npm:^7.28.6" + "@babel/parser": "npm:^7.28.6" + "@babel/types": "npm:^7.28.6" + checksum: 10c0/66d87225ed0bc77f888181ae2d97845021838c619944877f7c4398c6748bcf611f216dfd6be74d39016af502bca876e6ce6873db3c49e4ac354c56d34d57e9f5 + languageName: node + linkType: hard + "@babel/traverse@npm:^7.12.9, @babel/traverse@npm:^7.13.0, @babel/traverse@npm:^7.26.5, @babel/traverse@npm:^7.26.8, @babel/traverse@npm:^7.26.9": version: 7.26.9 resolution: "@babel/traverse@npm:7.26.9" @@ -2441,6 +2583,21 @@ __metadata: languageName: node linkType: hard +"@babel/traverse@npm:^7.28.6": + version: 7.29.0 + resolution: "@babel/traverse@npm:7.29.0" + dependencies: + "@babel/code-frame": "npm:^7.29.0" + "@babel/generator": "npm:^7.29.0" + "@babel/helper-globals": "npm:^7.28.0" + "@babel/parser": "npm:^7.29.0" + "@babel/template": "npm:^7.28.6" + "@babel/types": "npm:^7.29.0" + debug: "npm:^4.3.1" + checksum: 10c0/f63ef6e58d02a9fbf3c0e2e5f1c877da3e0bc57f91a19d2223d53e356a76859cbaf51171c9211c71816d94a0e69efa2732fd27ffc0e1bbc84b636e60932333eb + languageName: node + linkType: hard + "@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.25.9, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4, @babel/types@npm:^7.7.0": version: 7.25.9 resolution: "@babel/types@npm:7.25.9" @@ -2481,6 +2638,16 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.28.6, @babel/types@npm:^7.29.0": + version: 7.29.0 + resolution: "@babel/types@npm:7.29.0" + dependencies: + "@babel/helper-string-parser": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.28.5" + checksum: 10c0/23cc3466e83bcbfab8b9bd0edaafdb5d4efdb88b82b3be6728bbade5ba2f0996f84f63b1c5f7a8c0d67efded28300898a5f930b171bb40b311bca2029c4e9b4f + languageName: node + linkType: hard + "@bcherny/json-schema-ref-parser@npm:10.0.5-fork": version: 10.0.5-fork resolution: "@bcherny/json-schema-ref-parser@npm:10.0.5-fork" @@ -3455,6 +3622,16 @@ __metadata: languageName: node linkType: hard +"@jridgewell/gen-mapping@npm:^0.3.12": + version: 0.3.13 + resolution: "@jridgewell/gen-mapping@npm:0.3.13" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.0" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/9a7d65fb13bd9aec1fbab74cda08496839b7e2ceb31f5ab922b323e94d7c481ce0fc4fd7e12e2610915ed8af51178bdc61e168e92a8c8b8303b030b03489b13b + languageName: node + linkType: hard + "@jridgewell/gen-mapping@npm:^0.3.5": version: 0.3.5 resolution: "@jridgewell/gen-mapping@npm:0.3.5" @@ -3497,6 +3674,13 @@ __metadata: languageName: node linkType: hard +"@jridgewell/sourcemap-codec@npm:^1.5.0": + version: 1.5.5 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" + checksum: 10c0/f9e538f302b63c0ebc06eecb1dd9918dd4289ed36147a0ddce35d6ea4d7ebbda243cda7b2213b6a5e1d8087a298d5cf630fb2bd39329cdecb82017023f6081a0 + languageName: node + linkType: hard + "@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.18, @jridgewell/trace-mapping@npm:^0.3.20, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25": version: 0.3.25 resolution: "@jridgewell/trace-mapping@npm:0.3.25" @@ -3507,6 +3691,16 @@ __metadata: languageName: node linkType: hard +"@jridgewell/trace-mapping@npm:^0.3.28": + version: 0.3.31 + resolution: "@jridgewell/trace-mapping@npm:0.3.31" + dependencies: + "@jridgewell/resolve-uri": "npm:^3.1.0" + "@jridgewell/sourcemap-codec": "npm:^1.4.14" + checksum: 10c0/4b30ec8cd56c5fd9a661f088230af01e0c1a3888d11ffb6b47639700f71225be21d1f7e168048d6d4f9449207b978a235c07c8f15c07705685d16dc06280e9d9 + languageName: node + linkType: hard + "@jsdevtools/ono@npm:^7.1.3": version: 7.1.3 resolution: "@jsdevtools/ono@npm:7.1.3" @@ -7666,7 +7860,7 @@ __metadata: languageName: node linkType: hard -"@types/babel__plugin-transform-runtime@npm:^7": +"@types/babel__plugin-transform-runtime@npm:^7, @types/babel__plugin-transform-runtime@npm:^7.9.5": version: 7.9.5 resolution: "@types/babel__plugin-transform-runtime@npm:7.9.5" checksum: 10c0/119d8f88188c63eb21bf150d294991d3470e6aa90dc8ec9f634197760af8acc981564e29af9087bfa8ad713b5b5419cae03047cd225fccb9977bb97adf25a871 @@ -9737,13 +9931,6 @@ __metadata: languageName: node linkType: hard -"@webex/common-timers@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/common-timers@npm:2.60.2" - checksum: 10c0/02213535a9a238587531e85dba3b20d4353560a9676d84200c753fcb5e574ad55af1952ba0ace4513830647e017c8a3ced580d685856dce750b5f7ab1dacbf7f - languageName: node - linkType: hard - "@webex/common-timers@npm:2.60.4": version: 2.60.4 resolution: "@webex/common-timers@npm:2.60.4" @@ -9783,21 +9970,6 @@ __metadata: languageName: node linkType: hard -"@webex/common@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/common@npm:2.60.2" - dependencies: - backoff: "npm:^2.5.0" - bowser: "npm:^2.11.0" - core-decorators: "npm:^0.20.0" - global: "npm:^4.4.0" - lodash: "npm:^4.17.21" - safe-buffer: "npm:^5.2.0" - urlsafe-base64: "npm:^1.0.0" - checksum: 10c0/f0e1b58202e05379f4e624f7392dbab10e661fdd398f5ec58e8218c1420dd8db6a5b2d00330ddee699fbfae621680d382579e51525995979f89e6488b0c3a445 - languageName: node - linkType: hard - "@webex/common@npm:2.60.4, @webex/common@npm:^2.60.4": version: 2.60.4 resolution: "@webex/common@npm:2.60.4" @@ -9910,15 +10082,6 @@ __metadata: languageName: node linkType: hard -"@webex/helper-html@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/helper-html@npm:2.60.2" - dependencies: - lodash: "npm:^4.17.21" - checksum: 10c0/20aeb758f942449d1c9dd26e92e460073ba0dabfd2578d96230da06dcd6fc5141f62f690b1cc88d6bd9a40df606aff97071697e8a4b2961fc46896a560f079d8 - languageName: node - linkType: hard - "@webex/helper-html@npm:2.60.4": version: 2.60.4 resolution: "@webex/helper-html@npm:2.60.4" @@ -9946,23 +10109,6 @@ __metadata: languageName: node linkType: hard -"@webex/helper-image@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/helper-image@npm:2.60.2" - dependencies: - "@webex/http-core": "npm:2.60.2" - "@webex/test-helper-chai": "npm:2.60.2" - "@webex/test-helper-file": "npm:2.60.2" - "@webex/test-helper-mocha": "npm:2.60.2" - exifr: "npm:^5.0.3" - gm: "npm:^1.23.1" - lodash: "npm:^4.17.21" - mime: "npm:^2.4.4" - safe-buffer: "npm:^5.2.0" - checksum: 10c0/8044fe167ba69ea2852229939202ff85c82913e9170aa5da61776846c2d3ff48a89b02b602beb64aa3a6717cd85761fb0cc06495bc47f04676a9fb7308e368c5 - languageName: node - linkType: hard - "@webex/helper-image@npm:2.60.4": version: 2.60.4 resolution: "@webex/helper-image@npm:2.60.4" @@ -10034,27 +10180,6 @@ __metadata: languageName: node linkType: hard -"@webex/http-core@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/http-core@npm:2.60.2" - dependencies: - "@webex/common": "npm:2.60.2" - "@webex/internal-plugin-device": "npm:2.60.2" - "@webex/test-helper-test-users": "npm:2.60.2" - "@webex/webex-core": "npm:2.60.2" - file-type: "npm:^16.0.1" - global: "npm:^4.4.0" - is-function: "npm:^1.0.1" - lodash: "npm:^4.17.21" - parse-headers: "npm:^2.0.2" - qs: "npm:^6.7.3" - request: "npm:^2.88.0" - safe-buffer: "npm:^5.2.0" - xtend: "npm:^4.0.2" - checksum: 10c0/071b6ef0ee4e843cb4f2ec38b047953ed4155ab0bfea1768fc490546f7327db888d060498ef66f18624efc52c5ffb1b7b6858da2959a4322750aa1f97295ec9b - languageName: node - linkType: hard - "@webex/http-core@npm:2.60.4": version: 2.60.4 resolution: "@webex/http-core@npm:2.60.4" @@ -10149,20 +10274,6 @@ __metadata: languageName: node linkType: hard -"@webex/internal-plugin-calendar@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/internal-plugin-calendar@npm:2.60.2" - dependencies: - "@webex/internal-plugin-conversation": "npm:2.60.2" - "@webex/internal-plugin-device": "npm:2.60.2" - "@webex/internal-plugin-encryption": "npm:2.60.2" - "@webex/webex-core": "npm:2.60.2" - lodash: "npm:^4.17.21" - uuid: "npm:^3.3.2" - checksum: 10c0/906b59379cdbdfb2a7302a616de0515ce19d3061e612c8b398436f1c708b97a762e83de6932d3bc2a7d6b663ba35b7e8f1f7153e4203fc3aeda2716cf8a66506 - languageName: node - linkType: hard - "@webex/internal-plugin-calendar@npm:2.60.4": version: 2.60.4 resolution: "@webex/internal-plugin-calendar@npm:2.60.4" @@ -10191,24 +10302,6 @@ __metadata: languageName: node linkType: hard -"@webex/internal-plugin-conversation@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/internal-plugin-conversation@npm:2.60.2" - dependencies: - "@webex/common": "npm:2.60.2" - "@webex/helper-html": "npm:2.60.2" - "@webex/helper-image": "npm:2.60.2" - "@webex/internal-plugin-encryption": "npm:2.60.2" - "@webex/internal-plugin-user": "npm:2.60.2" - "@webex/webex-core": "npm:2.60.2" - crypto-js: "npm:^4.1.1" - lodash: "npm:^4.17.21" - node-scr: "npm:^0.3.0" - uuid: "npm:^3.3.2" - checksum: 10c0/4d0e25a98ac3b43dc85735c8cbaf7cbc22e97bb426820d886a2ec62b609c6ad4a192cf03615f77c1214c38451d3ec977482c8a198186699431f260d812cb1cbc - languageName: node - linkType: hard - "@webex/internal-plugin-conversation@npm:2.60.4": version: 2.60.4 resolution: "@webex/internal-plugin-conversation@npm:2.60.4" @@ -10263,22 +10356,6 @@ __metadata: languageName: node linkType: hard -"@webex/internal-plugin-device@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/internal-plugin-device@npm:2.60.2" - dependencies: - "@webex/common": "npm:2.60.2" - "@webex/common-timers": "npm:2.60.2" - "@webex/http-core": "npm:2.60.2" - "@webex/internal-plugin-metrics": "npm:2.60.2" - "@webex/webex-core": "npm:2.60.2" - ampersand-collection: "npm:^2.0.2" - ampersand-state: "npm:^5.0.3" - lodash: "npm:^4.17.21" - checksum: 10c0/ed0b0271159bd10b17766c84ce14e7bb229c41aca95024a59ca5f58148e73a4c74d3e8ca6b66adf048f22d65dbe8110c4e13ef25a016b14aa2600a8285027aaa - languageName: node - linkType: hard - "@webex/internal-plugin-device@npm:2.60.4": version: 2.60.4 resolution: "@webex/internal-plugin-device@npm:2.60.4" @@ -10343,32 +10420,6 @@ __metadata: languageName: node linkType: hard -"@webex/internal-plugin-encryption@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/internal-plugin-encryption@npm:2.60.2" - dependencies: - "@webex/common": "npm:2.60.2" - "@webex/common-timers": "npm:2.60.2" - "@webex/http-core": "npm:2.60.2" - "@webex/internal-plugin-device": "npm:2.60.2" - "@webex/internal-plugin-mercury": "npm:2.60.2" - "@webex/test-helper-file": "npm:2.60.2" - "@webex/webex-core": "npm:2.60.2" - asn1js: "npm:^2.0.26" - debug: "npm:^4.3.4" - isomorphic-webcrypto: "npm:^2.3.8" - lodash: "npm:^4.17.21" - node-jose: "npm:^2.2.0" - node-kms: "npm:^0.4.0" - node-scr: "npm:^0.3.0" - pkijs: "npm:^2.1.84" - safe-buffer: "npm:^5.2.0" - uuid: "npm:^3.3.2" - valid-url: "npm:^1.0.9" - checksum: 10c0/2f2eb2428975cf188f56312eac25668899b2fe1b475b1983f8718a820fca0364eac267ef38c0b25d95c2a1290bcb520b1cae59ca0f0a191c0b971c361638628f - languageName: node - linkType: hard - "@webex/internal-plugin-encryption@npm:2.60.4": version: 2.60.4 resolution: "@webex/internal-plugin-encryption@npm:2.60.4" @@ -10447,18 +10498,6 @@ __metadata: languageName: node linkType: hard -"@webex/internal-plugin-feature@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/internal-plugin-feature@npm:2.60.2" - dependencies: - "@webex/internal-plugin-device": "npm:2.60.2" - "@webex/internal-plugin-mercury": "npm:2.60.2" - "@webex/webex-core": "npm:2.60.2" - lodash: "npm:^4.17.21" - checksum: 10c0/afcd6d51637688024b5f18bf2230bb4ef42f8636ac0e3303ef6354976f70e02653703248c60b3ac5da07db42fbdfe4377469d3dde5dfb5e5b0a70298fed49d15 - languageName: node - linkType: hard - "@webex/internal-plugin-feature@npm:2.60.4": version: 2.60.4 resolution: "@webex/internal-plugin-feature@npm:2.60.4" @@ -10502,20 +10541,6 @@ __metadata: languageName: node linkType: hard -"@webex/internal-plugin-locus@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/internal-plugin-locus@npm:2.60.2" - dependencies: - "@webex/internal-plugin-mercury": "npm:2.60.2" - "@webex/test-helper-chai": "npm:2.60.2" - "@webex/test-helper-mock-webex": "npm:2.60.2" - "@webex/webex-core": "npm:2.60.2" - lodash: "npm:^4.17.21" - uuid: "npm:^3.3.2" - checksum: 10c0/fe6edd14f8838f6393db2f39f583b7ef77f9b539d7eacbc832b68a3c965844d6b802a5c35efbf4bb0e8465d058029f49eaae6df503877f566d57525b5afa9259 - languageName: node - linkType: hard - "@webex/internal-plugin-locus@npm:2.60.4": version: 2.60.4 resolution: "@webex/internal-plugin-locus@npm:2.60.4" @@ -10544,23 +10569,6 @@ __metadata: languageName: node linkType: hard -"@webex/internal-plugin-lyra@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/internal-plugin-lyra@npm:2.60.2" - dependencies: - "@webex/common": "npm:2.60.2" - "@webex/internal-plugin-conversation": "npm:2.60.2" - "@webex/internal-plugin-encryption": "npm:2.60.2" - "@webex/internal-plugin-feature": "npm:2.60.2" - "@webex/internal-plugin-locus": "npm:2.60.2" - "@webex/internal-plugin-mercury": "npm:2.60.2" - "@webex/webex-core": "npm:2.60.2" - bowser: "npm:^2.11.0" - uuid: "npm:^3.3.2" - checksum: 10c0/77e2a7557e923fe1ef38656b358eec4ca20a950677fd8b2e2bd9e958a7f90b8d9e89a32f0a7e35147c7c16a123e602045009a04e111df4f25bb05fc8b6ffaa56 - languageName: node - linkType: hard - "@webex/internal-plugin-lyra@npm:2.60.4": version: 2.60.4 resolution: "@webex/internal-plugin-lyra@npm:2.60.4" @@ -10595,30 +10603,6 @@ __metadata: languageName: node linkType: hard -"@webex/internal-plugin-mercury@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/internal-plugin-mercury@npm:2.60.2" - dependencies: - "@webex/common": "npm:2.60.2" - "@webex/common-timers": "npm:2.60.2" - "@webex/internal-plugin-device": "npm:2.60.2" - "@webex/internal-plugin-feature": "npm:2.60.2" - "@webex/internal-plugin-metrics": "npm:2.60.2" - "@webex/test-helper-chai": "npm:2.60.2" - "@webex/test-helper-mocha": "npm:2.60.2" - "@webex/test-helper-mock-web-socket": "npm:2.60.2" - "@webex/test-helper-mock-webex": "npm:2.60.2" - "@webex/test-helper-refresh-callback": "npm:2.60.2" - "@webex/test-helper-test-users": "npm:2.60.2" - "@webex/webex-core": "npm:2.60.2" - backoff: "npm:^2.5.0" - lodash: "npm:^4.17.21" - uuid: "npm:^3.3.2" - ws: "npm:^8.2.2" - checksum: 10c0/2c3040ecf1851e59e7f64f64465fc8904324f159a42eea8754e3a5dbad496f762916bca777a8dc722400644c7ad8d3bc7b835239cbfdb4638c5c911c3a65e17a - languageName: node - linkType: hard - "@webex/internal-plugin-mercury@npm:2.60.4": version: 2.60.4 resolution: "@webex/internal-plugin-mercury@npm:2.60.4" @@ -10691,19 +10675,6 @@ __metadata: languageName: node linkType: hard -"@webex/internal-plugin-metrics@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/internal-plugin-metrics@npm:2.60.2" - dependencies: - "@webex/common": "npm:2.60.2" - "@webex/common-timers": "npm:2.60.2" - "@webex/test-helper-chai": "npm:2.60.2" - "@webex/test-helper-mock-webex": "npm:2.60.2" - "@webex/webex-core": "npm:2.60.2" - checksum: 10c0/f0fbc705ec090e39d448d2b5f3ad076125bc136ff72517b7ba776e89346ab39e9681d1bbcdd7f5eea08c945bb969e2d38891611e20e5f63d7de203c77e2a4afc - languageName: node - linkType: hard - "@webex/internal-plugin-metrics@npm:2.60.4": version: 2.60.4 resolution: "@webex/internal-plugin-metrics@npm:2.60.4" @@ -10750,22 +10721,6 @@ __metadata: languageName: node linkType: hard -"@webex/internal-plugin-presence@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/internal-plugin-presence@npm:2.60.2" - dependencies: - "@webex/internal-plugin-device": "npm:2.60.2" - "@webex/internal-plugin-mercury": "npm:2.60.2" - "@webex/test-helper-chai": "npm:2.60.2" - "@webex/test-helper-mocha": "npm:2.60.2" - "@webex/test-helper-mock-webex": "npm:2.60.2" - "@webex/test-helper-test-users": "npm:2.60.2" - "@webex/webex-core": "npm:2.60.2" - lodash: "npm:^4.17.21" - checksum: 10c0/a0b434e845d8ba5a135b80a8813b7060edd6012a91d5a69480cf082a5007c8632f073e287c6eff397b6d8dba603508fda0a402e4b55a03b951bd8cafab13e295 - languageName: node - linkType: hard - "@webex/internal-plugin-presence@npm:2.60.4": version: 2.60.4 resolution: "@webex/internal-plugin-presence@npm:2.60.4" @@ -10798,21 +10753,6 @@ __metadata: languageName: node linkType: hard -"@webex/internal-plugin-search@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/internal-plugin-search@npm:2.60.2" - dependencies: - "@webex/common": "npm:2.60.2" - "@webex/internal-plugin-conversation": "npm:2.60.2" - "@webex/internal-plugin-device": "npm:2.60.2" - "@webex/internal-plugin-encryption": "npm:2.60.2" - "@webex/webex-core": "npm:2.60.2" - lodash: "npm:^4.17.21" - uuid: "npm:^3.3.2" - checksum: 10c0/29a389910847d14a571a2daf165f3d7bf5407d3c61c0779888a596c90df03e7bd16a0d14b004ad4a458b9fd3a919a8963e127755aa3af57ba2321d961b5b2a9a - languageName: node - linkType: hard - "@webex/internal-plugin-search@npm:2.60.4": version: 2.60.4 resolution: "@webex/internal-plugin-search@npm:2.60.4" @@ -10843,23 +10783,6 @@ __metadata: languageName: node linkType: hard -"@webex/internal-plugin-support@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/internal-plugin-support@npm:2.60.2" - dependencies: - "@webex/internal-plugin-device": "npm:2.60.2" - "@webex/internal-plugin-search": "npm:2.60.2" - "@webex/test-helper-chai": "npm:2.60.2" - "@webex/test-helper-file": "npm:2.60.2" - "@webex/test-helper-mock-webex": "npm:2.60.2" - "@webex/test-helper-test-users": "npm:2.60.2" - "@webex/webex-core": "npm:2.60.2" - lodash: "npm:^4.17.21" - uuid: "npm:^3.3.2" - checksum: 10c0/b808287f48e0074b97dbcfb589c491f570cf3d7c177382c7fbb013dc7904e486f6110614c616328860f91db066ff30e2dde0d2c94154222133f6cecbe95e9069 - languageName: node - linkType: hard - "@webex/internal-plugin-support@npm:2.60.4": version: 2.60.4 resolution: "@webex/internal-plugin-support@npm:2.60.4" @@ -10908,22 +10831,6 @@ __metadata: languageName: node linkType: hard -"@webex/internal-plugin-user@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/internal-plugin-user@npm:2.60.2" - dependencies: - "@webex/common": "npm:2.60.2" - "@webex/internal-plugin-device": "npm:2.60.2" - "@webex/test-helper-chai": "npm:2.60.2" - "@webex/test-helper-mock-webex": "npm:2.60.2" - "@webex/test-helper-test-users": "npm:2.60.2" - "@webex/webex-core": "npm:2.60.2" - lodash: "npm:^4.17.21" - uuid: "npm:^3.3.2" - checksum: 10c0/aa9ccdfee4558524681ae19c002d87cd328301ad9d3566df2dacc3dc4940498af93772fc64b663e67838ecffb31f1b67d79b15fb17ce41a95902844b7abeeef3 - languageName: node - linkType: hard - "@webex/internal-plugin-user@npm:2.60.4": version: 2.60.4 resolution: "@webex/internal-plugin-user@npm:2.60.4" @@ -11026,25 +10933,6 @@ __metadata: languageName: node linkType: hard -"@webex/plugin-attachment-actions@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/plugin-attachment-actions@npm:2.60.2" - dependencies: - "@webex/common": "npm:2.60.2" - "@webex/internal-plugin-conversation": "npm:2.60.2" - "@webex/internal-plugin-mercury": "npm:2.60.2" - "@webex/plugin-logger": "npm:2.60.2" - "@webex/plugin-messages": "npm:2.60.2" - "@webex/plugin-people": "npm:2.60.2" - "@webex/test-helper-chai": "npm:2.60.2" - "@webex/test-helper-test-users": "npm:2.60.2" - "@webex/webex-core": "npm:2.60.2" - debug: "npm:^4.3.4" - lodash: "npm:^4.17.21" - checksum: 10c0/37bae86a7b056227d27b6574c5df14f41b99ed50ada323e1a484df2d1a8c18cd2fc91d3156b007d71c0c0c6c6eeab7ac55e979eb30ccc510029670c638de96d2 - languageName: node - linkType: hard - "@webex/plugin-attachment-actions@npm:2.60.4": version: 2.60.4 resolution: "@webex/plugin-attachment-actions@npm:2.60.4" @@ -11082,23 +10970,6 @@ __metadata: languageName: node linkType: hard -"@webex/plugin-authorization-browser@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/plugin-authorization-browser@npm:2.60.2" - dependencies: - "@webex/common": "npm:2.60.2" - "@webex/internal-plugin-device": "npm:2.60.2" - "@webex/plugin-authorization-node": "npm:2.60.2" - "@webex/storage-adapter-local-storage": "npm:2.60.2" - "@webex/storage-adapter-spec": "npm:2.60.2" - "@webex/webex-core": "npm:2.60.2" - jose: "npm:^4.13.1" - lodash: "npm:^4.17.21" - uuid: "npm:^3.3.2" - checksum: 10c0/b2426408d08566e9a6ceac6b4e25dc393b5c67ed97dfb7eec84557d0b6af771a2516badf67a9219782fe781451b93711b8cc2242fa3d2e5b126aad0175a6edc8 - languageName: node - linkType: hard - "@webex/plugin-authorization-browser@npm:2.60.4": version: 2.60.4 resolution: "@webex/plugin-authorization-browser@npm:2.60.4" @@ -11133,19 +11004,6 @@ __metadata: languageName: node linkType: hard -"@webex/plugin-authorization-node@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/plugin-authorization-node@npm:2.60.2" - dependencies: - "@webex/common": "npm:2.60.2" - "@webex/internal-plugin-device": "npm:2.60.2" - "@webex/webex-core": "npm:2.60.2" - jsonwebtoken: "npm:^9.0.0" - uuid: "npm:^3.3.2" - checksum: 10c0/7b113ae183c9b029daf411172335120f275a64f4319cce740b066206d4be3ff0107b61d61aab8b4ef8d95d1d03fc46f5e374edfbb047bfddc89c8a936feb1f38 - languageName: node - linkType: hard - "@webex/plugin-authorization-node@npm:2.60.4": version: 2.60.4 resolution: "@webex/plugin-authorization-node@npm:2.60.4" @@ -11172,16 +11030,6 @@ __metadata: languageName: node linkType: hard -"@webex/plugin-authorization@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/plugin-authorization@npm:2.60.2" - dependencies: - "@webex/plugin-authorization-browser": "npm:2.60.2" - "@webex/plugin-authorization-node": "npm:2.60.2" - checksum: 10c0/d856c829338b5107e78fda5aa74b1317c362d393290274b73de123f6c283299396fa884440f0d31c72ef29c51b08a5ac261e5b54b90ebd5f521dca628efe1117 - languageName: node - linkType: hard - "@webex/plugin-authorization@npm:2.60.4": version: 2.60.4 resolution: "@webex/plugin-authorization@npm:2.60.4" @@ -11202,24 +11050,6 @@ __metadata: languageName: node linkType: hard -"@webex/plugin-device-manager@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/plugin-device-manager@npm:2.60.2" - dependencies: - "@webex/internal-plugin-calendar": "npm:2.60.2" - "@webex/internal-plugin-device": "npm:2.60.2" - "@webex/internal-plugin-lyra": "npm:2.60.2" - "@webex/internal-plugin-search": "npm:2.60.2" - "@webex/plugin-authorization": "npm:2.60.2" - "@webex/plugin-logger": "npm:2.60.2" - "@webex/test-helper-chai": "npm:2.60.2" - "@webex/webex-core": "npm:2.60.2" - lodash: "npm:^4.17.21" - uuid: "npm:^3.3.2" - checksum: 10c0/0852b64df9d97a23e2e7cb0efa7957a9e48fbdd6b34306f837a87a02ecc1254d2f19e9800c11685e051cee74bb2aa8e6da553c87480838fbd0dcfc8921de9bd3 - languageName: node - linkType: hard - "@webex/plugin-device-manager@npm:2.60.4": version: 2.60.4 resolution: "@webex/plugin-device-manager@npm:2.60.4" @@ -11266,20 +11096,6 @@ __metadata: languageName: node linkType: hard -"@webex/plugin-logger@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/plugin-logger@npm:2.60.2" - dependencies: - "@webex/common": "npm:2.60.2" - "@webex/test-helper-chai": "npm:2.60.2" - "@webex/test-helper-mocha": "npm:2.60.2" - "@webex/test-helper-mock-webex": "npm:2.60.2" - "@webex/webex-core": "npm:2.60.2" - lodash: "npm:^4.17.21" - checksum: 10c0/2298b4d3b7e884fb83d9f7303cfd3edac1e8ceb8a5c961bcbfc9a701760f092746d87e7f638da35f814fed11b65b5867b4aeb2025159cf22ca2b88fd012e729c - languageName: node - linkType: hard - "@webex/plugin-logger@npm:2.60.4": version: 2.60.4 resolution: "@webex/plugin-logger@npm:2.60.4" @@ -11308,33 +11124,6 @@ __metadata: languageName: node linkType: hard -"@webex/plugin-meetings@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/plugin-meetings@npm:2.60.2" - dependencies: - "@webex/common": "npm:2.60.2" - "@webex/internal-media-core": "npm:0.0.7-beta" - "@webex/internal-plugin-device": "npm:2.60.2" - "@webex/internal-plugin-metrics": "npm:2.60.2" - "@webex/internal-plugin-support": "npm:2.60.2" - "@webex/internal-plugin-user": "npm:2.60.2" - "@webex/plugin-people": "npm:2.60.2" - "@webex/ts-sdp": "npm:1.0.1" - "@webex/webex-core": "npm:2.60.2" - bowser: "npm:^2.11.0" - btoa: "npm:^1.2.1" - dotenv: "npm:^4.0.0" - global: "npm:^4.4.0" - ip-anonymize: "npm:^0.1.0" - javascript-state-machine: "npm:^3.1.0" - lodash: "npm:^4.17.21" - sdp-transform: "npm:^2.12.0" - uuid: "npm:^3.3.2" - webrtc-adapter: "npm:^7.7.0" - checksum: 10c0/a041ac7e6b827653f940995be4aab99647eb532b213c0f977040520b7bc281855e048f7eba275dcae7bc265c46c14a189ba0d7a0a0ed27e3fc36dcabb61c4451 - languageName: node - linkType: hard - "@webex/plugin-meetings@npm:2.60.4": version: 2.60.4 resolution: "@webex/plugin-meetings@npm:2.60.4" @@ -11398,24 +11187,6 @@ __metadata: languageName: node linkType: hard -"@webex/plugin-memberships@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/plugin-memberships@npm:2.60.2" - dependencies: - "@webex/common": "npm:2.60.2" - "@webex/internal-plugin-conversation": "npm:2.60.2" - "@webex/internal-plugin-mercury": "npm:2.60.2" - "@webex/plugin-logger": "npm:2.60.2" - "@webex/plugin-messages": "npm:2.60.2" - "@webex/plugin-people": "npm:2.60.2" - "@webex/plugin-rooms": "npm:2.60.2" - "@webex/webex-core": "npm:2.60.2" - debug: "npm:^4.3.4" - lodash: "npm:^4.17.21" - checksum: 10c0/7c4c3ca3b44fcf6819fcb2bd4dd6e46bfe5e9f25e005a1ffb52801cda66d0455f168f0ba7fbabe1daa0354fe2c3c103202dd8347494db27402175e0713b090dd - languageName: node - linkType: hard - "@webex/plugin-memberships@npm:2.60.4": version: 2.60.4 resolution: "@webex/plugin-memberships@npm:2.60.4" @@ -11453,24 +11224,6 @@ __metadata: languageName: node linkType: hard -"@webex/plugin-messages@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/plugin-messages@npm:2.60.2" - dependencies: - "@webex/common": "npm:2.60.2" - "@webex/internal-plugin-conversation": "npm:2.60.2" - "@webex/internal-plugin-device": "npm:2.60.2" - "@webex/internal-plugin-mercury": "npm:2.60.2" - "@webex/plugin-logger": "npm:2.60.2" - "@webex/plugin-people": "npm:2.60.2" - "@webex/plugin-rooms": "npm:2.60.2" - "@webex/webex-core": "npm:2.60.2" - debug: "npm:^4.3.4" - lodash: "npm:^4.17.21" - checksum: 10c0/a891f77a061a58bcca5bd7d12e20d5870bbf24574c8cd3b965880219804d90647e216126da0747794111063c1db8c043835fa648843666c7e822ce1c76819b5b - languageName: node - linkType: hard - "@webex/plugin-messages@npm:2.60.4": version: 2.60.4 resolution: "@webex/plugin-messages@npm:2.60.4" @@ -11508,17 +11261,6 @@ __metadata: languageName: node linkType: hard -"@webex/plugin-people@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/plugin-people@npm:2.60.2" - dependencies: - "@webex/common": "npm:2.60.2" - "@webex/internal-plugin-mercury": "npm:2.60.2" - "@webex/webex-core": "npm:2.60.2" - checksum: 10c0/577ff54e6fa91992a4e70a092b0dfc1dbaadf1dcad5dd65a6d7fd7a9a3075bec2a1be78fc746991d6b6df31bd647a3fa599ff30a6749ba83522f04934444fe53 - languageName: node - linkType: hard - "@webex/plugin-people@npm:2.60.4": version: 2.60.4 resolution: "@webex/plugin-people@npm:2.60.4" @@ -11541,24 +11283,6 @@ __metadata: languageName: node linkType: hard -"@webex/plugin-rooms@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/plugin-rooms@npm:2.60.2" - dependencies: - "@webex/common": "npm:2.60.2" - "@webex/internal-plugin-conversation": "npm:2.60.2" - "@webex/internal-plugin-mercury": "npm:2.60.2" - "@webex/plugin-logger": "npm:2.60.2" - "@webex/plugin-memberships": "npm:2.60.2" - "@webex/plugin-messages": "npm:2.60.2" - "@webex/plugin-people": "npm:2.60.2" - "@webex/webex-core": "npm:2.60.2" - debug: "npm:^4.3.4" - lodash: "npm:^4.17.21" - checksum: 10c0/fb5cd4476258f6521a41a1c4926ee1ddc1ed38c98d60f3edbbd46631fccba687e9493a3c7e8f81318c622d926731378b34ab97e55cb204e577a6a10e7481469e - languageName: node - linkType: hard - "@webex/plugin-rooms@npm:2.60.4": version: 2.60.4 resolution: "@webex/plugin-rooms@npm:2.60.4" @@ -11594,19 +11318,6 @@ __metadata: languageName: node linkType: hard -"@webex/plugin-team-memberships@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/plugin-team-memberships@npm:2.60.2" - dependencies: - "@webex/internal-plugin-device": "npm:2.60.2" - "@webex/plugin-logger": "npm:2.60.2" - "@webex/plugin-rooms": "npm:2.60.2" - "@webex/plugin-teams": "npm:2.60.2" - "@webex/webex-core": "npm:2.60.2" - checksum: 10c0/72c9c9f5cc74f6ad754faff9cb9fe9ea632684697648a4ccd6f9fe8ea573ac162087715e977a0108cf7f73819f27ceb24808e9003ea2b41ca36673f52e5102b5 - languageName: node - linkType: hard - "@webex/plugin-team-memberships@npm:2.60.4": version: 2.60.4 resolution: "@webex/plugin-team-memberships@npm:2.60.4" @@ -11634,22 +11345,6 @@ __metadata: languageName: node linkType: hard -"@webex/plugin-teams@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/plugin-teams@npm:2.60.2" - dependencies: - "@webex/internal-plugin-device": "npm:2.60.2" - "@webex/plugin-logger": "npm:2.60.2" - "@webex/plugin-memberships": "npm:2.60.2" - "@webex/plugin-rooms": "npm:2.60.2" - "@webex/test-helper-chai": "npm:2.60.2" - "@webex/test-helper-test-users": "npm:2.60.2" - "@webex/webex-core": "npm:2.60.2" - lodash: "npm:^4.17.21" - checksum: 10c0/72a65b0795ae44024cbc300b38fe75abbc4b910b31b40715ea0f8de8fbf2666529223fcda0374cdf96ab5af5103327e5b64bcadf49310d3b76fdac5e6251af75 - languageName: node - linkType: hard - "@webex/plugin-teams@npm:2.60.4": version: 2.60.4 resolution: "@webex/plugin-teams@npm:2.60.4" @@ -11681,18 +11376,6 @@ __metadata: languageName: node linkType: hard -"@webex/plugin-webhooks@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/plugin-webhooks@npm:2.60.2" - dependencies: - "@webex/internal-plugin-device": "npm:2.60.2" - "@webex/plugin-logger": "npm:2.60.2" - "@webex/plugin-rooms": "npm:2.60.2" - "@webex/webex-core": "npm:2.60.2" - checksum: 10c0/357fa1b310163884f76c3b3e7c2162a15e1a53f6b96e74af0086d3525e5202cde6c0b695dfa0d416958c9147ce295ba64d3a885c055f913cc89c771a4c9e059d - languageName: node - linkType: hard - "@webex/plugin-webhooks@npm:2.60.4": version: 2.60.4 resolution: "@webex/plugin-webhooks@npm:2.60.4" @@ -11743,17 +11426,6 @@ __metadata: languageName: node linkType: hard -"@webex/storage-adapter-local-storage@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/storage-adapter-local-storage@npm:2.60.2" - dependencies: - "@webex/storage-adapter-spec": "npm:2.60.2" - "@webex/test-helper-mocha": "npm:2.60.2" - "@webex/webex-core": "npm:2.60.2" - checksum: 10c0/2da4f706b68140cf9ee16bca1ff5a9db6d18d091a5c155a54ec42143c05236fa07185d7a63e2e33885f6bf1300eb7a05351185290d15073dac6e2b2431050c7a - languageName: node - linkType: hard - "@webex/storage-adapter-local-storage@npm:2.60.4": version: 2.60.4 resolution: "@webex/storage-adapter-local-storage@npm:2.60.4" @@ -11776,15 +11448,6 @@ __metadata: languageName: node linkType: hard -"@webex/storage-adapter-spec@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/storage-adapter-spec@npm:2.60.2" - dependencies: - "@webex/test-helper-chai": "npm:2.60.2" - checksum: 10c0/c41a04b4aa0551798bfd2c628ba92ed43613cd7f100d52b773f90ea3b3201712fcd889dc25e4d47223f0ec2be24f1aaa848316a33cae19a7ca39b9d5ec54989e - languageName: node - linkType: hard - "@webex/storage-adapter-spec@npm:2.60.4": version: 2.60.4 resolution: "@webex/storage-adapter-spec@npm:2.60.4" @@ -11843,17 +11506,6 @@ __metadata: languageName: unknown linkType: soft -"@webex/test-helper-chai@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/test-helper-chai@npm:2.60.2" - dependencies: - "@webex/test-helper-file": "npm:2.60.2" - check-error: "npm:^1.0.2" - lodash: "npm:^4.17.21" - checksum: 10c0/afe306948e2597bda91a4f1cf53885fe1e700f2200b2728275a00c6909fd02b1cbae465c48e1e482ebb2b9db876788147b87b2097311ae0efd34c2f5cfb41100 - languageName: node - linkType: hard - "@webex/test-helper-chai@npm:2.60.4": version: 2.60.4 resolution: "@webex/test-helper-chai@npm:2.60.4" @@ -11887,19 +11539,6 @@ __metadata: languageName: node linkType: hard -"@webex/test-helper-file@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/test-helper-file@npm:2.60.2" - dependencies: - "@webex/common": "npm:2.60.2" - "@webex/test-helper-make-local-url": "npm:2.60.2" - es6-promise: "npm:^4.2.8" - file-type: "npm:^16.0.1" - xhr: "npm:^2.5.0" - checksum: 10c0/68198f745f166fc9bc0d40bd2eea3939b9f0c81b239c85cd99561ea19b439c60bc9635181abdd28583535babaaa9d567c645075bf831a0e17efd0da84f224134 - languageName: node - linkType: hard - "@webex/test-helper-file@npm:2.60.4": version: 2.60.4 resolution: "@webex/test-helper-file@npm:2.60.4" @@ -11939,13 +11578,6 @@ __metadata: languageName: node linkType: hard -"@webex/test-helper-make-local-url@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/test-helper-make-local-url@npm:2.60.2" - checksum: 10c0/52ccc4a1ad376a95a53f41aaf36ee81ddc93fe95af626109bcfcb45a2c1b66d5c5d4bb4aa2a6a430848ea18565d5ef2fffada69d4f87a9828bb0506dc6f36ff7 - languageName: node - linkType: hard - "@webex/test-helper-make-local-url@npm:2.60.4": version: 2.60.4 resolution: "@webex/test-helper-make-local-url@npm:2.60.4" @@ -11967,15 +11599,6 @@ __metadata: languageName: node linkType: hard -"@webex/test-helper-mocha@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/test-helper-mocha@npm:2.60.2" - dependencies: - bowser: "npm:^2.11.0" - checksum: 10c0/1fe884f9e9b03127f317a031a4f9dcd1735013811e767e49303e61ffe30ce27e3e61017c358430643ac5358db2e91ea73f846563e52ae2d20d89cbbe78453f86 - languageName: node - linkType: hard - "@webex/test-helper-mocha@npm:2.60.4": version: 2.60.4 resolution: "@webex/test-helper-mocha@npm:2.60.4" @@ -12003,13 +11626,6 @@ __metadata: languageName: node linkType: hard -"@webex/test-helper-mock-web-socket@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/test-helper-mock-web-socket@npm:2.60.2" - checksum: 10c0/1596c087b440dceec33ad0124c7b89b5f9d4c854ee694feaf22473f38773f0b16be4e86b59c7cf4a0882a534e6299f6fffd2fd0f2c693e02e95639b50d6ee1ab - languageName: node - linkType: hard - "@webex/test-helper-mock-web-socket@npm:2.60.4": version: 2.60.4 resolution: "@webex/test-helper-mock-web-socket@npm:2.60.4" @@ -12031,17 +11647,6 @@ __metadata: languageName: node linkType: hard -"@webex/test-helper-mock-webex@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/test-helper-mock-webex@npm:2.60.2" - dependencies: - ampersand-state: "npm:^5.0.3" - es6-promise: "npm:^4.2.8" - lodash: "npm:^4.17.21" - checksum: 10c0/e9669cc60ba9dd2db2729617bbfc696b595c8e0cc81ffe9662ba8e163de68c0a74e4cb2ebf0de5e553ac6d663312186695640de3c58c01d0d288fb7c520ea42e - languageName: node - linkType: hard - "@webex/test-helper-mock-webex@npm:2.60.4": version: 2.60.4 resolution: "@webex/test-helper-mock-webex@npm:2.60.4" @@ -12075,13 +11680,6 @@ __metadata: languageName: node linkType: hard -"@webex/test-helper-refresh-callback@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/test-helper-refresh-callback@npm:2.60.2" - checksum: 10c0/0bbffeb49eabfa2fea4f2ccfcedd0fd2061187db3ea3c7eb4d373fd5d315e19ab46d0e951ddfd0500ce5cfdfed54c6090f82da18e2ef85220624ad32c1998d8e - languageName: node - linkType: hard - "@webex/test-helper-refresh-callback@npm:2.60.4": version: 2.60.4 resolution: "@webex/test-helper-refresh-callback@npm:2.60.4" @@ -12103,15 +11701,6 @@ __metadata: languageName: node linkType: hard -"@webex/test-helper-retry@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/test-helper-retry@npm:2.60.2" - dependencies: - es6-promise: "npm:^4.2.8" - checksum: 10c0/fb17ade3006ef0f4d4fcf76c8ca99d76a98e8c2157ec34d01d6102fa657a5712e66d8453a325a9b87c0506d488bae79e46701de769f25a5af19c44bfb985f351 - languageName: node - linkType: hard - "@webex/test-helper-retry@npm:2.60.4": version: 2.60.4 resolution: "@webex/test-helper-retry@npm:2.60.4" @@ -12139,21 +11728,6 @@ __metadata: languageName: node linkType: hard -"@webex/test-helper-test-users@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/test-helper-test-users@npm:2.60.2" - dependencies: - "@ciscospark/test-users-legacy": "npm:^1.0.2" - "@webex/test-helper-retry": "npm:2.60.2" - "@webex/test-users": "npm:2.60.2" - lodash: "npm:^4.17.21" - dependenciesMeta: - "@ciscospark/test-users-legacy": - optional: true - checksum: 10c0/fa22ff049d67f985a68671c9d1a3ec7ddd6a66af599174b7290aeda8efb60b9cfbd0102ba8e1e9739067d410d5ac86a092728b2ef6fd9e5c6037c1ddb1e253af - languageName: node - linkType: hard - "@webex/test-helper-test-users@npm:2.60.4": version: 2.60.4 resolution: "@webex/test-helper-test-users@npm:2.60.4" @@ -12195,20 +11769,6 @@ __metadata: languageName: node linkType: hard -"@webex/test-users@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/test-users@npm:2.60.2" - dependencies: - "@webex/http-core": "npm:2.60.2" - "@webex/test-helper-mocha": "npm:2.60.2" - btoa: "npm:^1.2.1" - lodash: "npm:^4.17.21" - node-random-name: "npm:^1.0.1" - uuid: "npm:^3.3.2" - checksum: 10c0/359282a460405db9e71f3b1141adbd0948998a6235496b37b0a8cfe771a0b6b1d8a8c81ff2d5b35f2fa0349de46455919a1f2ba973a6b3b817988692917b5987 - languageName: node - linkType: hard - "@webex/test-users@npm:2.60.4": version: 2.60.4 resolution: "@webex/test-users@npm:2.60.4" @@ -12368,28 +11928,6 @@ __metadata: languageName: node linkType: hard -"@webex/webex-core@npm:2.60.2": - version: 2.60.2 - resolution: "@webex/webex-core@npm:2.60.2" - dependencies: - "@webex/common": "npm:2.60.2" - "@webex/common-timers": "npm:2.60.2" - "@webex/http-core": "npm:2.60.2" - "@webex/internal-plugin-device": "npm:2.60.2" - "@webex/plugin-logger": "npm:2.60.2" - "@webex/storage-adapter-spec": "npm:2.60.2" - ampersand-collection: "npm:^2.0.2" - ampersand-events: "npm:^2.0.2" - ampersand-state: "npm:^5.0.3" - core-decorators: "npm:^0.20.0" - crypto-js: "npm:^4.1.1" - jsonwebtoken: "npm:^9.0.0" - lodash: "npm:^4.17.21" - uuid: "npm:^3.3.2" - checksum: 10c0/14ca691a4b74a1d4f7176ff2b141403cc4e80802f22fcb137befa0d7b6c9e0a3de5326d89a70329f21f470aae605a85e239671cc9c0ffdc3dbed5d2d5e938b4e - languageName: node - linkType: hard - "@webex/webex-core@npm:2.60.4": version: 2.60.4 resolution: "@webex/webex-core@npm:2.60.4" @@ -12482,8 +12020,11 @@ __metadata: "@babel/plugin-transform-runtime": "npm:^7.16.0" "@babel/preset-env": "npm:^7.8.4" "@babel/preset-react": "npm:^7.8.3" + "@babel/register": "npm:^7.25.9" "@commitlint/cli": "npm:^8.3.5" "@commitlint/config-conventional": "npm:^8.3.4" + "@momentum-ui/core": "npm:19.16.0" + "@momentum-ui/icons": "npm:8.28.5" "@momentum-ui/react": "npm:^23.21.4" "@semantic-release/changelog": "npm:^6.0.0" "@semantic-release/git": "npm:^10.0.0" @@ -12549,7 +12090,7 @@ __metadata: prop-types: ^15.7.2 react: 18.3.1 react-dom: 18.3.1 - webex: 2.60.2 + webex: 2.60.4 languageName: unknown linkType: soft @@ -13897,6 +13438,19 @@ __metadata: languageName: node linkType: hard +"babel-plugin-polyfill-corejs2@npm:^0.4.14": + version: 0.4.17 + resolution: "babel-plugin-polyfill-corejs2@npm:0.4.17" + dependencies: + "@babel/compat-data": "npm:^7.28.6" + "@babel/helper-define-polyfill-provider": "npm:^0.6.8" + semver: "npm:^6.3.1" + peerDependencies: + "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 + checksum: 10c0/1284960ea403c63b0dd598f338666c4b17d489aefee30b4da6a7313eff1d91edffb0ccf26341a6e5d94231684b74e016eade66b3921ea112f8b0e4980fa08a5c + languageName: node + linkType: hard + "babel-plugin-polyfill-corejs3@npm:^0.1.0": version: 0.1.7 resolution: "babel-plugin-polyfill-corejs3@npm:0.1.7" @@ -13933,6 +13487,18 @@ __metadata: languageName: node linkType: hard +"babel-plugin-polyfill-corejs3@npm:^0.13.0": + version: 0.13.0 + resolution: "babel-plugin-polyfill-corejs3@npm:0.13.0" + dependencies: + "@babel/helper-define-polyfill-provider": "npm:^0.6.5" + core-js-compat: "npm:^3.43.0" + peerDependencies: + "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 + checksum: 10c0/5d8e228da425edc040d8c868486fd01ba10b0440f841156a30d9f8986f330f723e2ee61553c180929519563ef5b64acce2caac36a5a847f095d708dda5d8206d + languageName: node + linkType: hard + "babel-plugin-polyfill-regenerator@npm:^0.6.1": version: 0.6.2 resolution: "babel-plugin-polyfill-regenerator@npm:0.6.2" @@ -13944,6 +13510,17 @@ __metadata: languageName: node linkType: hard +"babel-plugin-polyfill-regenerator@npm:^0.6.5": + version: 0.6.8 + resolution: "babel-plugin-polyfill-regenerator@npm:0.6.8" + dependencies: + "@babel/helper-define-polyfill-provider": "npm:^0.6.8" + peerDependencies: + "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 + checksum: 10c0/7c8b2497c29fa880e0acdc8e7b93e29b81b154179b83beb0476eb2c4e7a78b6b42fc35c2554ca250c9bd6d39941eaf75416254b8592ce50979f9a12e1d51c049 + languageName: node + linkType: hard + "babel-plugin-syntax-object-rest-spread@npm:^6.8.0": version: 6.13.0 resolution: "babel-plugin-syntax-object-rest-spread@npm:6.13.0" @@ -14448,7 +14025,7 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.27.0": +"browserslist@npm:^4.27.0, browserslist@npm:^4.28.1": version: 4.28.1 resolution: "browserslist@npm:4.28.1" dependencies: @@ -16103,6 +15680,15 @@ __metadata: languageName: node linkType: hard +"core-js-compat@npm:^3.43.0": + version: 3.49.0 + resolution: "core-js-compat@npm:3.49.0" + dependencies: + browserslist: "npm:^4.28.1" + checksum: 10c0/546e64b7ce45f724825bc13c1347f35c0459a6e71c0dcccff3ec21fbff463f5b0b97fc1220e6d90302153863489301793276fe2bf96f46001ff555ead4140308 + languageName: node + linkType: hard + "core-js@npm:^2.4.0, core-js@npm:^2.5.0, core-js@npm:^2.6.12, core-js@npm:^2.6.5": version: 2.6.12 resolution: "core-js@npm:2.6.12" @@ -16987,6 +16573,18 @@ __metadata: languageName: node linkType: hard +"debug@npm:^4.4.3": + version: 4.4.3 + resolution: "debug@npm:4.4.3" + dependencies: + ms: "npm:^2.1.3" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10c0/d79136ec6c83ecbefd0f6a5593da6a9c91ec4d7ddc4b54c883d6e71ec9accb5f67a1a5e96d00a328196b5b5c86d365e98d8a3a70856aaf16b4e7b1985e67f5a6 + languageName: node + linkType: hard + "debuglog@npm:^1.0.1": version: 1.0.1 resolution: "debuglog@npm:1.0.1" @@ -22352,7 +21950,7 @@ __metadata: languageName: node linkType: hard -"is-core-module@npm:^2.12.1, is-core-module@npm:^2.16.0": +"is-core-module@npm:^2.12.1, is-core-module@npm:^2.16.0, is-core-module@npm:^2.16.1": version: 2.16.1 resolution: "is-core-module@npm:2.16.1" dependencies: @@ -30678,6 +30276,19 @@ __metadata: languageName: node linkType: hard +"resolve@npm:^1.22.11": + version: 1.22.11 + resolution: "resolve@npm:1.22.11" + dependencies: + is-core-module: "npm:^2.16.1" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/f657191507530f2cbecb5815b1ee99b20741ea6ee02a59c57028e9ec4c2c8d7681afcc35febbd554ac0ded459db6f2d8153382c53a2f266cee2575e512674409 + languageName: node + linkType: hard + "resolve@npm:^2.0.0-next.5": version: 2.0.0-next.5 resolution: "resolve@npm:2.0.0-next.5" @@ -30717,6 +30328,19 @@ __metadata: languageName: node linkType: hard +"resolve@patch:resolve@npm%3A^1.22.11#optional!builtin": + version: 1.22.11 + resolution: "resolve@patch:resolve@npm%3A1.22.11#optional!builtin::version=1.22.11&hash=c3c19d" + dependencies: + is-core-module: "npm:^2.16.1" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/ee5b182f2e37cb1165465e58c6abc797fec0a80b5ba3231607beb4677db0c9291ac010c47cf092b6daa2b7f518d69a0e21888e7e2b633f68d501a874212a8c63 + languageName: node + linkType: hard + "resolve@patch:resolve@npm%3A^2.0.0-next.5#optional!builtin": version: 2.0.0-next.5 resolution: "resolve@patch:resolve@npm%3A2.0.0-next.5#optional!builtin::version=2.0.0-next.5&hash=c3c19d" @@ -31092,6 +30716,7 @@ __metadata: "@babel/preset-env": "npm:^7.25.4" "@babel/preset-react": "npm:^7.24.7" "@babel/preset-typescript": "npm:^7.25.9" + "@momentum-ui/react": "npm:^23.21.4" "@types/babel__plugin-transform-runtime": "npm:^7" "@types/prop-types": "npm:^15" "@webex/widgets": "workspace:*" @@ -31116,7 +30741,6 @@ __metadata: url: "npm:^0.11.4" util: "npm:^0.12.5" vm-browserify: "npm:^1.1.2" - webex: "npm:2.60.2" webpack: "npm:^5.94.0" webpack-cli: "npm:^5.1.4" webpack-dev-server: "npm:^5.1.0" @@ -35101,6 +34725,7 @@ __metadata: version: 0.0.0-use.local resolution: "webex-widgets@workspace:." dependencies: + "@babel/plugin-transform-runtime": "npm:^7.29.0" "@babel/preset-typescript": "npm:7.25.9" "@playwright/test": "npm:^1.51.1" "@semantic-release/changelog": "npm:^6.0.3" @@ -35108,6 +34733,7 @@ __metadata: "@semantic-release/git": "npm:^10.0.1" "@semantic-release/github": "npm:^11.0.1" "@testing-library/react-hooks": "npm:^8.0.1" + "@types/babel__plugin-transform-runtime": "npm:^7.9.5" "@webex/cli-tools": "npm:0.0.0-next.2" "@webex/package-tools": "npm:0.0.0-next.6" babel-jest: "npm:^29.7.0" @@ -35136,36 +34762,6 @@ __metadata: languageName: unknown linkType: soft -"webex@npm:2.60.2": - version: 2.60.2 - resolution: "webex@npm:2.60.2" - dependencies: - "@babel/polyfill": "npm:^7.12.1" - "@babel/runtime-corejs2": "npm:^7.14.8" - "@webex/common": "npm:2.60.2" - "@webex/internal-plugin-calendar": "npm:2.60.2" - "@webex/internal-plugin-device": "npm:2.60.2" - "@webex/internal-plugin-presence": "npm:2.60.2" - "@webex/internal-plugin-support": "npm:2.60.2" - "@webex/plugin-attachment-actions": "npm:2.60.2" - "@webex/plugin-authorization": "npm:2.60.2" - "@webex/plugin-device-manager": "npm:2.60.2" - "@webex/plugin-logger": "npm:2.60.2" - "@webex/plugin-meetings": "npm:2.60.2" - "@webex/plugin-memberships": "npm:2.60.2" - "@webex/plugin-messages": "npm:2.60.2" - "@webex/plugin-people": "npm:2.60.2" - "@webex/plugin-rooms": "npm:2.60.2" - "@webex/plugin-team-memberships": "npm:2.60.2" - "@webex/plugin-teams": "npm:2.60.2" - "@webex/plugin-webhooks": "npm:2.60.2" - "@webex/storage-adapter-local-storage": "npm:2.60.2" - "@webex/webex-core": "npm:2.60.2" - lodash: "npm:^4.17.21" - checksum: 10c0/9d22c67ada7c35736fde13da1c0bbeb36e6b7e41a92f93365afeb849bf6841eda75a2cc69628ec3ddc40708851c0dfe86583222d3a432691640c49873ac8e504 - languageName: node - linkType: hard - "webex@npm:2.60.4": version: 2.60.4 resolution: "webex@npm:2.60.4" From 571199d9be0b80f198c020e3b84bb0035d1ae7c0 Mon Sep 17 00:00:00 2001 From: Vintha Vivekananda <151998489+vivekv1504@users.noreply.github.com> Date: Wed, 8 Apr 2026 17:38:47 +0530 Subject: [PATCH 03/21] feat(changelog): Enable viewing commit history between two widgets stable releases (#661) Co-authored-by: Bharath Balan <62698609+bhabalan@users.noreply.github.com> Co-authored-by: Kesava Krishnan Madavan --- docs/changelog/assets/css/app.css | 6 +- docs/changelog/assets/js/app.js | 469 +++++++++----------- docs/changelog/assets/js/comparison-view.js | 312 ++++++------- docs/changelog/index.html | 76 +++- 4 files changed, 391 insertions(+), 472 deletions(-) diff --git a/docs/changelog/assets/css/app.css b/docs/changelog/assets/css/app.css index 31de425f3..a6607e7d0 100644 --- a/docs/changelog/assets/css/app.css +++ b/docs/changelog/assets/css/app.css @@ -1,9 +1,9 @@ -/* +/* * CSS Variables for consistent theming - * + * * Usage in CSS: background-color: var(--color-webex-blue); * Usage in JS: element.style.backgroundColor = 'var(--color-success)'; - * + * * Available variables: * --color-success: Success state (green) * --color-success-hover: Success hover state diff --git a/docs/changelog/assets/js/app.js b/docs/changelog/assets/js/app.js index 6368c66d1..c70c71a03 100644 --- a/docs/changelog/assets/js/app.js +++ b/docs/changelog/assets/js/app.js @@ -5,17 +5,9 @@ let comparisonListenersInitialized = false; const githubBaseUrl = 'https://github.com/webex/widgets/'; import { comparisonState, - extractPackagesFromVersion, - findLatestPackageVersion, - getEffectiveVersion, - getPackageVersion, - determinePackageStatus, - createPackageComparisonRow, - calculateComparisonStats, - buildPackagesList, - comparePackages, - fetchAndCompareVersions, generatePackageComparisonData, + sortStableVersions, + collectCommitsFromStable, } from './comparison-view.js'; // DOM elements @@ -133,17 +125,18 @@ Handlebars.registerHelper('convertDate', function (timestamp) { return `${new Date(timestamp).toDateString()} ${new Date(timestamp).toTimeString()}`; }); +Handlebars.registerHelper('math', function (index, offset) { + return index + offset; +}); + // Util Methods const populateFormFieldsFromURL = async () => { const queryParams = new URLSearchParams(window.location.search); - // Skip single-view URL handling if comparison parameters are present if ( - queryParams.has('compare') || - queryParams.has('compareStableA') || - (queryParams.has('versionA') && queryParams.has('versionB')) - ) { - return; // Comparison mode will handle these parameters + queryParams.has('compareStableA')) + { + return; } const searchParams = { @@ -154,7 +147,7 @@ const populateFormFieldsFromURL = async () => { commitHash: queryParams.get('commitHash'), }; - let hasAtleastOneParam = false; + let hasAtLeastOneParam = false; if (searchParams.stable_version) { versionSelectDropdown.value = searchParams.stable_version; @@ -166,28 +159,28 @@ const populateFormFieldsFromURL = async () => { if (searchParams.package && !packageNameInputDropdown.disabled) { packageNameInputDropdown.value = searchParams.package; packageNameInputDropdown.dispatchEvent(new Event('change')); - hasAtleastOneParam = true; + hasAtLeastOneParam = true; } if (searchParams.version) { versionInput.value = searchParams.version; - hasAtleastOneParam = true; + hasAtLeastOneParam = true; validateVersionInput({version: searchParams.version}); } if (searchParams.commitMessage) { commitMessageInput.value = searchParams.commitMessage; - hasAtleastOneParam = true; + hasAtLeastOneParam = true; } if (searchParams.commitHash) { commitHashInput.value = searchParams.commitHash; - hasAtleastOneParam = true; + hasAtLeastOneParam = true; } updateFormState(searchParams); - if (hasAtleastOneParam) { + if (hasAtLeastOneParam) { doSearch(searchParams); } }; @@ -221,22 +214,22 @@ const fetchChangelog = async (versionPath) => { }; const populatePackageNames = (changelog) => { - let specialPackages = ['@webex/widgets', '@webex/cc-widgets']; + const specialPackages = ['@webex/widgets', '@webex/cc-widgets']; // Get all packages that actually exist in this version's changelog - let allPackages = Object.keys(changelog); + const allPackages = Object.keys(changelog); // Filter special packages that ACTUALLY EXIST in this version - let existingSpecialPackages = specialPackages.filter((pkg) => allPackages.includes(pkg)); + const existingSpecialPackages = specialPackages.filter((pkg) => allPackages.includes(pkg)); // Get remaining packages (excluding special ones) - let otherPackages = allPackages.filter((pkg) => !specialPackages.includes(pkg)); + const otherPackages = allPackages.filter((pkg) => !specialPackages.includes(pkg)); // Sort the remaining packages alphabetically otherPackages.sort(); // Build the sorted list - only add separator if special packages exist - let sortedPackages; + const sortedPackages = []; if (existingSpecialPackages.length > 0) { sortedPackages = ['separator', ...existingSpecialPackages, 'separator', ...otherPackages]; } else { @@ -244,7 +237,7 @@ const populatePackageNames = (changelog) => { sortedPackages = otherPackages; } - let optionsHtml = ''; + const optionsHtml = ''; sortedPackages.forEach((packageName) => { if (packageName === 'separator') { @@ -680,20 +673,6 @@ window.onhashchange = () => { populateVersions(); -let comparisonMode = false; -/* ============================================ - UI HELPER FUNCTIONS - ============================================ */ - -/** - * Show loading state for comparison - */ -const showComparisonLoading = () => { - if (!comparisonResults) return; - comparisonResults.innerHTML = '

Loading comparison...

'; - comparisonResults.classList.remove('hide'); -}; - /** * Show error state for comparison * @param {Error} error - The error object @@ -714,145 +693,6 @@ const showComparisonError = (error) => { DATA LAYER FUNCTIONS ============================================ */ -/** - * UI LAYER: Handle version comparison UI updates - * @param {string} versionA - Base version - * @param {string} versionB - Target version - */ -const performVersionComparison = async (versionA, versionB) => { - // Show loading state - showComparisonLoading(); - - try { - // Fetch and compare data (pure data logic) - const result = await fetchAndCompareVersions(versionA, versionB, versionPaths); - - // Display results (UI logic) - displayComparison(result.versionA, result.versionB, result.comparisonData); - } catch (error) { - // Handle error display (UI logic) - showComparisonError(error); - } -}; - -/** - * Display comparison results - * @param {string} versionA - Base version - * @param {string} versionB - Target version - * @param {Object} comparisonData - Comparison results - */ -const displayComparison = (versionA, versionB, comparisonData) => { - if (!comparisonResults) { - console.error('comparison-results element not found!'); - return; - } - - if (!comparisonTemplateElement) { - console.error('comparison-template element not found!'); - return; - } - - const comparisonTemplate = Handlebars.compile(comparisonTemplateElement.innerHTML); - - const templateData = { - versionA, - versionB, - ...comparisonData, - }; - - console.log('Template data:', templateData); - - try { - const html = comparisonTemplate(templateData); - console.log('Generated HTML length:', html.length); - - comparisonResults.innerHTML = html; - comparisonResults.classList.remove('hide'); - - // Update URL with comparison parameters for permalinks - updateComparisonURL(versionA, versionB); - - // Show the copy link button and helper text - if (copyComparisonLinkBtn) { - copyComparisonLinkBtn.classList.remove('hide'); - console.log('Copy link button shown'); - } else { - console.warn('Copy link button not found in DOM'); - } - if (comparisonHelper) { - comparisonHelper.classList.remove('hide'); - } - - // Scroll to results smoothly - setTimeout(() => { - comparisonResults.scrollIntoView({behavior: 'smooth', block: 'start'}); - }, 100); - - console.log('Comparison displayed successfully'); - } catch (error) { - console.error('Error rendering template:', error); - comparisonResults.innerHTML = `
Error rendering comparison: ${error.message}
`; - } -}; - -/** - * Update URL with comparison parameters for sharing/bookmarking - * @param {string} versionA - Base version - * @param {string} versionB - Target version - */ -const updateComparisonURL = (versionA, versionB) => { - const url = new URL(window.location); - - // Clear any single-view parameters - url.searchParams.delete('stable_version'); - url.searchParams.delete('package'); - url.searchParams.delete('version'); - url.searchParams.delete('commitMessage'); - url.searchParams.delete('commitHash'); - // Clear enhanced (package-level) comparison params so full comparison link is not stale - url.searchParams.delete('compareStableA'); - url.searchParams.delete('compareStableB'); - url.searchParams.delete('comparePackage'); - url.searchParams.delete('compareVersionA'); - url.searchParams.delete('compareVersionB'); - - - // Set comparison parameters - url.searchParams.set('compare', `${versionA}vs${versionB}`); - - // Update URL without reloading the page - window.history.pushState({}, '', url); -}; - -/** - * Parse and handle comparison URL parameters - * Supports formats: ?compare=3.9.0vs3.10.0 or ?versionA=3.9.0&versionB=3.10.0 - */ -const handleComparisonURLParams = async () => { - const urlParams = new URLSearchParams(window.location.search); - - let versionA = null; - let versionB = null; - - // Check for ?compare=AvB format - const compareParam = urlParams.get('compare'); - if (compareParam && compareParam.includes('vs')) { - const versions = compareParam.split('vs'); - versionA = versions[0]?.trim(); - versionB = versions[1]?.trim(); - } - - // Also support ?versionA=X&versionB=Y format - if (!versionA) versionA = urlParams.get('versionA'); - if (!versionB) versionB = urlParams.get('versionB'); - - // If comparison parameters are found, switch to comparison mode - if (versionA && versionB && versionA !== versionB) { - return {versionA, versionB, shouldCompare: true}; - } - - return {shouldCompare: false}; -}; /** * Switch to comparison mode programmatically @@ -860,9 +700,6 @@ const handleComparisonURLParams = async () => { * @param {string} versionB - Target version (optional) */ const switchToComparisonMode = (versionA = null, versionB = null) => { - // Update mode - comparisonMode = true; - // Update button states if (comparisonViewBtn && singleViewBtn) { comparisonViewBtn.classList.add('active', 'btn-primary'); @@ -920,9 +757,8 @@ const getUnionPackages = (changelogA, changelogB) => { * @param {Object} changelogA - Changelog A * @param {Object} changelogB - Changelog B */ -const compareAndRenderPackageVersions = (packageName, versionASpecific, versionBSpecific, changelogA, changelogB) => { +const compareAndRenderPackageVersions = (packageName, versionASpecific, versionBSpecific, changelogA, changelogB, totalCommits = 0) => { try { - // Generate comparison data (pure data logic from comparison-view.js) const comparisonData = generatePackageComparisonData( packageName, versionASpecific, @@ -931,9 +767,8 @@ const compareAndRenderPackageVersions = (packageName, versionASpecific, versionB changelogB ); - console.log('comparisonData', comparisonData); + comparisonData.totalCommits = totalCommits; - // Validate DOM elements if (!comparisonResults) { console.error('comparison-results element not found'); return; @@ -944,12 +779,12 @@ const compareAndRenderPackageVersions = (packageName, versionASpecific, versionB return; } - // Render template const template = Handlebars.compile(comparisonTemplateElement.innerHTML); const html = template(comparisonData); - // Update DOM - comparisonResults.innerHTML = html; + // NOTE: renderCommitHistory must be called first — it resets this container via `=`. + // This `+=` appends the package comparison table below the commit history. + comparisonResults.innerHTML += html; comparisonResults.classList.remove('hide'); // Update URL for sharing @@ -996,7 +831,7 @@ const populateUnionPackages = (changelogA, changelogB) => { return; } - let optionsHtml = ''; + let optionsHtml = ''; allPackages.forEach((pkg) => { optionsHtml += ``; }); @@ -1117,18 +952,6 @@ const handleEnhancedComparisonURL = async () => { return {shouldCompare: false}; }; - -/** - * Populate version dropdowns for comparison mode - */ -const populateComparisonVersions = () => { - if (versionSelectDropdown && versionSelectDropdown.innerHTML) { - const options = versionSelectDropdown.innerHTML; - if (versionASelect) versionASelect.innerHTML = options; - if (versionBSelect) versionBSelect.innerHTML = options; - } -}; - /** * Reset comparison form selections */ @@ -1162,9 +985,6 @@ const clearComparisonForm = () => { const clearComparisonURLParams = () => { const url = new URL(window.location); [ - 'compare', - 'versionA', - 'versionB', 'compareStableA', 'compareStableB', 'comparePackage', @@ -1195,8 +1015,7 @@ const updateCompareButtonState = () => { compareButton.disabled = false; } } else { - // No package selected - enable for full version comparison - compareButton.disabled = false; + compareButton.disabled = true; } }; @@ -1216,7 +1035,6 @@ const updatePrereleaseLabels = () => { * Handle stable version changes - fetch changelogs and populate packages */ const handleStableVersionChange = async () => { - console.log('🟢 handleStableVersionChange FIRED'); const stableA = versionASelect.value; const stableB = versionBSelect.value; @@ -1244,7 +1062,6 @@ const handleStableVersionChange = async () => { * Handle package selection - populate pre-release versions */ const handlePackageChange = () => { - console.log('🟢 handlePackageChange FIRED'); const selectedPackage = comparisonPackageSelect.value; if (versionAPrereleaseSelect) versionAPrereleaseSelect.value = ''; @@ -1279,8 +1096,6 @@ const handlePackageChange = () => { * Switch to single view mode */ const switchToSingleViewMode = () => { - console.log('🔵 Switching to SINGLE VIEW mode'); - comparisonMode = false; // Update button styles singleViewBtn.classList.add('active', 'btn-primary'); @@ -1294,25 +1109,6 @@ const switchToSingleViewMode = () => { clearComparisonURLParams(); }; -/** - * Switch to comparison view mode - */ -const switchToComparisonViewMode = () => { - console.log('🔵 Switching to COMPARISON VIEW mode'); - comparisonMode = true; - - // Update button styles - comparisonViewBtn.classList.add('active', 'btn-primary'); - comparisonViewBtn.classList.remove('btn-default'); - singleViewBtn.classList.remove('active', 'btn-primary'); - singleViewBtn.classList.add('btn-default'); - - // Toggle visibility (centralized view state) - updateUIVisibility('comparison'); - - populateComparisonVersions(); -}; - /** * Validate comparison form inputs */ @@ -1327,13 +1123,130 @@ const validateComparisonInputs = (stableA, stableB, selectedPackage, versionASpe return false; } + // Base stable version must be older than or equal to target + const allStables = sortStableVersions(Object.keys(versionPaths)); + const idxA = allStables.indexOf(stableA); + const idxB = allStables.indexOf(stableB); + + if (idxA > idxB) { + alert('Base version must be older than or equal to target version'); + return false; + } + + // If same stable and both pre-releases selected, base must be older + if (stableA === stableB && versionASpecific && versionBSpecific) { + const numA = parseInt(versionASpecific.match(/\.(\d+)$/)?.[1] || '0', 10); + const numB = parseInt(versionBSpecific.match(/\.(\d+)$/)?.[1] || '0', 10); + if (numA > numB) { + alert('Base pre-release version must be older than target pre-release version'); + return false; + } + } + return true; }; +/* ============================================ + CROSS-STABLE COMMIT AGGREGATION (app-level) + These functions interact with versionPaths and + the DOM, so they live here instead of comparison-view.js + ============================================ */ + +/** + * Return the ordered list of stable versions between stableA and stableB (inclusive). + */ +const getStableVersionsBetween = (stableA, stableB) => { + const allStables = sortStableVersions(Object.keys(versionPaths)); + const idxA = allStables.indexOf(stableA); + const idxB = allStables.indexOf(stableB); + if (idxA === -1 || idxB === -1) return []; + const lo = Math.min(idxA, idxB); + const hi = Math.max(idxA, idxB); + return allStables.slice(lo, hi + 1); +}; + +/** + * Walk every stable between stableA..stableB, + * fetch each log file, and collect deduplicated commits. + */ +const collectCommitsAcrossStables = async (packageName, stableA, stableB, versionA, versionB) => { + const stables = getStableVersionsBetween(stableA, stableB); + if (stables.length === 0) return []; + + const allCommits = []; + for (let i = 0; i < stables.length; i++) { + const stable = stables[i]; + const path = versionPaths[stable]; + if (!path) continue; + + let changelog; + if (stable === stableA && comparisonState.cachedChangelogA) { + changelog = comparisonState.cachedChangelogA; + } else if (stable === stableB && comparisonState.cachedChangelogB) { + changelog = comparisonState.cachedChangelogB; + } else { + try { + const res = await fetch(path); + if (!res.ok) throw new Error(`Failed to fetch changelog for stable ${stable}`); + changelog = await res.json(); + } catch (error) { + console.error('Error fetching changelog:', error); + continue; + } + } + + const packageData = changelog[packageName]; + if (!packageData) continue; + + let position; + if (stables.length === 1) position = 'only'; + else if (i === 0) position = 'start'; + else if (i === stables.length - 1) position = 'end'; + else position = 'middle'; + + const commits = collectCommitsFromStable(packageData, stable, versionA, versionB, position); + allCommits.push(...commits); + } + + // Final dedup by hash + const seen = new Map(); + allCommits.forEach((c) => { + if (!seen.has(c.hash)) seen.set(c.hash, c); + }); + return Array.from(seen.values()); +}; + +/** + * Render the commit history using the Handlebars template in index.html + */ +const renderCommitHistory = (packageName, versionA, versionB, commits) => { + const templateEl = document.getElementById('commit-history-template'); + if (!templateEl) { + console.error('commit-history-template not found in DOM'); + return; + } + + const template = Handlebars.compile(templateEl.innerHTML); + const html = template({ + packageName, + versionA, + versionB, + commits, + totalCommits: commits.length, + githubBaseUrl, + }); + + comparisonResults.innerHTML = html; + comparisonResults.classList.remove('hide'); + + if (copyComparisonLinkBtn) copyComparisonLinkBtn.classList.remove('hide'); + if (comparisonHelper) comparisonHelper.classList.remove('hide'); +}; + /** * Handle comparison form submission */ -const handleComparisonSubmit = (event) => { +const handleComparisonSubmit = async (event) => { event.preventDefault(); const stableA = versionASelect.value; @@ -1347,21 +1260,38 @@ const handleComparisonSubmit = (event) => { } if (selectedPackage && (versionASpecific || versionBSpecific)) { - // Package-level comparison const finalVersionA = versionASpecific || stableA; const finalVersionB = versionBSpecific || stableB; - console.log('Comparing:', finalVersionA, 'vs', finalVersionB); - compareAndRenderPackageVersions( - selectedPackage, - finalVersionA, - finalVersionB, - comparisonState.cachedChangelogA, - comparisonState.cachedChangelogB - ); + try { + const commits = await collectCommitsAcrossStables( + selectedPackage, + stableA, + stableB, + finalVersionA, + finalVersionB + ); + + // Order matters: renderCommitHistory resets the container; compareAndRender appends to it. + renderCommitHistory(selectedPackage, finalVersionA, finalVersionB, commits); + + // Package-level comparison table (appended below) + compareAndRenderPackageVersions( + selectedPackage, + finalVersionA, + finalVersionB, + comparisonState.cachedChangelogA, + comparisonState.cachedChangelogB, + commits.length + ); + + updateEnhancedComparisonURL(stableA, stableB, selectedPackage, finalVersionA, finalVersionB); + } catch (error) { + showComparisonError(error); + } } else { - // Full version comparison - performVersionComparison(stableA, stableB); + alert('Please select a package and at least one pre-release version to compare.'); + return; } if (compareButton) compareButton.disabled = false; @@ -1380,15 +1310,13 @@ const handleClearClick = () => { */ const setupComparisonEventListeners = () => { if (comparisonListenersInitialized) { - console.log('🔴 Comparison listeners already initialized,skipping......'); return; } - console.log('🟢 Setting up comparison event listeners first time......'); comparisonListenersInitialized = true; // Mode toggle buttons if (singleViewBtn) singleViewBtn.addEventListener('click', switchToSingleViewMode); - if (comparisonViewBtn) comparisonViewBtn.addEventListener('click', switchToComparisonViewMode); + if (comparisonViewBtn) comparisonViewBtn.addEventListener('click', () => switchToComparisonMode()); // Version and package selectors if (versionASelect) versionASelect.addEventListener('change', handleStableVersionChange); @@ -1432,24 +1360,33 @@ const loadEnhancedComparisonFromURL = async (enhancedParams) => { versionAPrereleaseSelect.value = enhancedParams.versionA; versionBPrereleaseSelect.value = enhancedParams.versionB; - compareAndRenderPackageVersions( - enhancedParams.packageName, - enhancedParams.versionA, - enhancedParams.versionB, - comparisonState.cachedChangelogA, - comparisonState.cachedChangelogB - ); -}; - -/** - * Handle standard comparison URL parameters on page load - */ -const loadStandardComparisonFromURL = async (urlParams) => { - switchToComparisonMode(urlParams.versionA, urlParams.versionB); + try { + const commits = await collectCommitsAcrossStables( + enhancedParams.packageName, + enhancedParams.stableA, + enhancedParams.stableB, + enhancedParams.versionA, + enhancedParams.versionB + ); - await new Promise((resolve) => setTimeout(resolve, 300)); + renderCommitHistory( + enhancedParams.packageName, + enhancedParams.versionA, + enhancedParams.versionB, + commits + ); - performVersionComparison(urlParams.versionA, urlParams.versionB); + compareAndRenderPackageVersions( + enhancedParams.packageName, + enhancedParams.versionA, + enhancedParams.versionB, + comparisonState.cachedChangelogA, + comparisonState.cachedChangelogB, + commits.length + ); + } catch (error) { + console.error('Error loading commit history from URL:', error); + } }; /** @@ -1465,12 +1402,6 @@ const initializeComparisonMode = async () => { await loadEnhancedComparisonFromURL(enhancedParams); return; } - - // Check for standard comparison URL - const urlParams = await handleComparisonURLParams(); - if (urlParams.shouldCompare) { - await loadStandardComparisonFromURL(urlParams); - } }; /** diff --git a/docs/changelog/assets/js/comparison-view.js b/docs/changelog/assets/js/comparison-view.js index 7352606aa..80e5363f4 100644 --- a/docs/changelog/assets/js/comparison-view.js +++ b/docs/changelog/assets/js/comparison-view.js @@ -19,53 +19,6 @@ const comparisonState = { this.currentStableB = stableB; }, }; -const extractPackagesFromVersion = (changelog, specificVersions = null) => { - const packageMap = {}; - - for (const packageName of Object.keys(changelog)) { - const packageVersions = changelog[packageName]; - console.log('packageVersions', packageVersions); - - // Safety check: ensure packageVersions is an object - if (!packageVersions || typeof packageVersions !== 'object') continue; - - const versionKeys = Object.keys(packageVersions); - console.log('versionKeys', versionKeys); - - if (versionKeys.length === 0) continue; - - let selectedVersion = null; - - // Check if user specified a specific version for this package - if (specificVersions && specificVersions[packageName]) { - const requestedVersion = specificVersions[packageName]; - - if (packageVersions[requestedVersion]) { - selectedVersion = requestedVersion; - } - } - - // If no specific version requested or not found, use earliest (first) version - if (!selectedVersion) { - let earliestVersion = versionKeys[0]; - let earliestDate = packageVersions[earliestVersion]?.published_date || Infinity; - - for (const version of versionKeys) { - const publishedDate = packageVersions[version]?.published_date || Infinity; - if (publishedDate < earliestDate) { - earliestDate = publishedDate; - earliestVersion = version; - } - } - - selectedVersion = earliestVersion; - } - - packageMap[packageName] = selectedVersion; - } - - return packageMap; -}; const findLatestPackageVersion = (changelog, packageName) => { if (!changelog[packageName]) return null; @@ -202,145 +155,148 @@ const buildPackagesList = ( return packagesArray; }; -const comparePackages = (packagesA, packagesB, changelogA, changelogB, stableVersionA, stableVersionB) => { - // Get ALL package names from both changelogs (entire changelog, not just specific versions) - const allPackageNames = new Set([ - ...Object.keys(changelogA), //ALL packages in changelog A - ...Object.keys(changelogB), //ALL packages in changelog B - ]); - - const packages = []; - let changedCount = 0; - let unchangedCount = 0; - let onlyInACount = 0; - let onlyInBCount = 0; - - // Helper function to find stable version first, then highest pre-release version - const findStableVersion = (changelog, packageName, stableVersion) => { - if (!changelog[packageName]) return null; - - const versions = Object.keys(changelog[packageName]); - if (versions.length === 0) return null; - - // Escape dots in version string for regex (3.4.0 -> 3\.4\.0) - const escapedVersion = stableVersion.replace(/\./g, '\\.'); - - // Priority 1: Find exact stable version (e.g., "3.4.0" only, no suffixes) - const exactStablePattern = new RegExp(`^${escapedVersion}$`); - const exactStableVersion = versions.find((ver) => exactStablePattern.test(ver)); - - if (exactStableVersion) { - return exactStableVersion; - } - - // Priority 2: Find oldest pre-release version (any tag: next, alpha, beta, rc, etc.) - // Pattern: 3.4.0-{tag}.{number} -> captures tag and number - const prereleasePattern = new RegExp(`^${escapedVersion}-([a-z]+)\\.(\\d+)$`, 'i'); - - const prereleaseVersions = versions - .filter((ver) => prereleasePattern.test(ver)) - .sort((a, b) => { - const matchA = a.match(prereleasePattern); - const matchB = b.match(prereleasePattern); - if (!matchA || !matchB) return 0; - const numA = parseInt(matchA[2], 10); - const numB = parseInt(matchB[2], 10); - return numA - numB; // Sort ascending (lowest first) - }); - //console.log('prereleaseVersions', prereleaseVersions); - //console.log('versions', versions); - // Return highest pre-release version, or fallback to first available - return prereleaseVersions[0] || versions[0]; - }; +/* ============================================ + COMMIT HISTORY — CROSS-STABLE COLLECTION + Walk every stable version between stableA and stableB, + open each log file, and collect commits per the rules below. + ============================================ */ + +const sortStableVersions = (versions) => + [...versions].sort((a, b) => { + const p = (v) => v.split('.').map(Number); + const [aMaj, aMin, aPatch] = p(a); + const [bMaj, bMin, bPatch] = p(b); + return aMaj !== bMaj ? aMaj - bMaj : aMin !== bMin ? aMin - bMin : aPatch - bPatch; + }); - allPackageNames.forEach((packageName) => { - // Use release version per stable train (exact stable or highest prerelease), not chronologically earliest - const versionA = findStableVersion(changelogA, packageName, stableVersionA); - const versionB = findStableVersion(changelogB, packageName, stableVersionB); - - let status, changeClass; //Declare variables for status label and CSS class - - if (versionA && versionB) { - //checks if package is in both changelogs - if (versionA === versionB) { - //if versionA is the same as versionB, then it is unchanged - status = 'Unchanged'; - changeClass = 'unchanged'; - unchangedCount++; - } else { - status = 'Version Changed'; - changeClass = 'version-changed'; - changedCount++; - } - } else if (versionA && !versionB) { - status = 'Removed'; - changeClass = 'only-in-a'; - onlyInACount++; - } else if (!versionA && versionB) { - status = 'Added'; - changeClass = 'only-in-b'; - onlyInBCount++; - } +const isPreRelease = (version, stableVersion) => { + const escaped = stableVersion.replace(/\./g, '\\.'); + return new RegExp(`^${escaped}-`).test(version); +}; - packages.push({ - packageName, - versionA: versionA || 'N/A', - versionB: versionB || 'N/A', - status, - changeClass, - }); - }); +const isExactStable = (version) => /^\d+\.\d+\.\d+$/.test(version); - // Sort packages alphabetically - packages.sort((a, b) => a.packageName.localeCompare(b.packageName)); +const getPreReleaseNum = (version) => { + const match = version.match(/\.(\d+)$/); + return match ? parseInt(match[1], 10) : 0; +}; - return { - packages, - totalPackages: allPackageNames.size, - changedCount, - unchangedCount, - onlyInACount, - onlyInBCount, - }; +const getPreReleaseTag = (version, stableVersion) => { + return version.slice(stableVersion.length + 1).replace(/\.\d+$/, ''); }; -//Data Fetching -const fetchAndCompareVersions = async (versionA, versionB, versionPaths) => { - const [changelogA, changelogB] = await Promise.all([ - fetch(versionPaths[versionA]).then((res) => { - if (!res.ok) throw new Error(`Failed to fetch ${versionA}`); - return res.json(); - }), - fetch(versionPaths[versionB]).then((res) => { - if (!res.ok) throw new Error(`Failed to fetch ${versionB}`); - return res.json(); - }), - ]); - - // Extract packages from both versions - const packagesA = extractPackagesFromVersion(changelogA); - const packagesB = extractPackagesFromVersion(changelogB); - - // Compare packages - const comparisonData = comparePackages(packagesA, packagesB, changelogA, changelogB, versionA, versionB); +/** + * Collect commits from one stable version's package data. + * + * Rules: + * 'start' → from versionA (inclusive) through ALL remaining pre-releases + * 'middle' → skip exact stable entry; ALL pre-releases of this stable + * 'end' → ALL pre-releases from next.1 up to versionB (inclusive) + * 'only' → stableA === stableB; from versionA to versionB within same file + */ +const collectCommitsFromStable = (packageData, stableVersion, versionA, versionB, position) => { + if (!packageData) return []; + const all = Object.keys(packageData); + let versionsToUse = []; + + // Determine the target tag from the user-selected versions + const targetTag = !isExactStable(versionA) + ? getPreReleaseTag(versionA, stableVersion) + : !isExactStable(versionB) + ? getPreReleaseTag(versionB, stableVersion) + : null; + + if (position === 'start') { + if (versionA === stableVersion) { + versionsToUse = all.filter((v)=>isPreRelease(v,stableVersion)); + } else { + const tagA = getPreReleaseTag(versionA, stableVersion); + const numA = getPreReleaseNum(versionA); + versionsToUse = all.filter((v) => { + if (!isPreRelease(v, stableVersion)) return false; + const tag = getPreReleaseTag(v, stableVersion); + if (tag !== tagA) return false; + const num = getPreReleaseNum(v); + return num >= numA; + }); + } + } else if (position === 'middle') { + versionsToUse = + isExactStable(versionA) && isExactStable(versionB) + ? [] + : all.filter((v) => { + if (!isPreRelease(v, stableVersion)) return false; + if (!targetTag) return true; + return getPreReleaseTag(v, stableVersion) === targetTag; + }); + } else if (position === 'end') { + if (versionB === stableVersion) { + versionsToUse = [stableVersion]; + } else { + const tagB = getPreReleaseTag(versionB, stableVersion); + const numB = getPreReleaseNum(versionB); + versionsToUse = all.filter((v) => { + if (!isPreRelease(v, stableVersion)) return false; + const tag = getPreReleaseTag(v, stableVersion); + if (tag !== tagB) return false; + const num = getPreReleaseNum(v); + return num <= numB; + }); + } + } else { + // 'only' — stableA === stableB + if (versionA === stableVersion && versionB === stableVersion) { + versionsToUse = [stableVersion]; + } else if (versionA === stableVersion) { + const tagB = getPreReleaseTag(versionB, stableVersion); + const numB = getPreReleaseNum(versionB); + versionsToUse = all.filter((v) => { + if (v === stableVersion) return true; + if (!isPreRelease(v, stableVersion)) return false; + const tag = getPreReleaseTag(v, stableVersion); + if (tag !== tagB) return false; + const num = getPreReleaseNum(v); + return num <= numB; + }); + } else { + const tagA = getPreReleaseTag(versionA, stableVersion); + const numA = getPreReleaseNum(versionA); + const tagB = getPreReleaseTag(versionB, stableVersion); + const numB = getPreReleaseNum(versionB); + versionsToUse = all.filter((v) => { + if (!isPreRelease(v, stableVersion)) return false; + const tag = getPreReleaseTag(v, stableVersion); + const num = getPreReleaseNum(v); + const afterStart = tag === tagA ? num >= numA : true; + const beforeEnd = tag === tagB ? num <= numB : true; + return afterStart && beforeEnd; + }); + } + } - return { - versionA, - versionB, - comparisonData, - }; + const seen = new Map(); + versionsToUse.forEach((ver) => { + Object.entries(packageData[ver]?.commits || {}).forEach(([hash, message]) => { + if (!seen.has(hash)) { + seen.set(hash, { + hash, + shortHash: hash.substring(0, 7), + message, + version: ver, + stableGroup: stableVersion, + }); + } + }); + }); + return Array.from(seen.values()); }; + const generatePackageComparisonData = (packageName, versionASpecific, versionBSpecific, changelogA, changelogB) => { const effectiveVersionA = getEffectiveVersion(changelogA, packageName, versionASpecific); const effectiveVersionB = getEffectiveVersion(changelogB, packageName, versionBSpecific); - console.log('effectiveVersionA', effectiveVersionA); - console.log('effectiveVersionB', effectiveVersionB); // Get package data from changelogs const pkgDataA = changelogA[packageName]?.[effectiveVersionA]; const pkgDataB = changelogB[packageName]?.[effectiveVersionB]; - console.log('pkgDataA', pkgDataA); - console.log('pkgDataB', pkgDataB); - // Validate versions exist if (!pkgDataA && !pkgDataB) { throw new Error(`Could not find version data for ${packageName}`); @@ -373,15 +329,7 @@ const generatePackageComparisonData = (packageName, versionASpecific, versionBSp //Export All the functions export { comparisonState, - extractPackagesFromVersion, - findLatestPackageVersion, - getEffectiveVersion, - getPackageVersion, - determinePackageStatus, - createPackageComparisonRow, - calculateComparisonStats, - buildPackagesList, - comparePackages, - fetchAndCompareVersions, generatePackageComparisonData, + sortStableVersions, + collectCommitsFromStable, }; diff --git a/docs/changelog/index.html b/docs/changelog/index.html index 18a7eb548..e6e9e5f9f 100644 --- a/docs/changelog/index.html +++ b/docs/changelog/index.html @@ -154,9 +154,9 @@ @@ -180,7 +180,7 @@
-
@@ -193,7 +193,7 @@ Search Examples: