Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
43946ca
feat(cc-digital-channel): add-new-package
akulakum Dec 16, 2025
eb2e774
feat(cc-digital-channel): update-yarn-lock-file
akulakum Dec 17, 2025
1bca291
feat(cc-digital-channel): remove-static-values
akulakum Dec 18, 2025
7a694f1
feat(cc-digital-channel): fix-digital-channel-responsive
akulakum Dec 18, 2025
efb887c
Merge branch 'next' into CC_DIGITAL_CHANNEL
akulakum Dec 18, 2025
5ab4742
feat(cc-digital-channel): initalize-digital-app-once
akulakum Dec 19, 2025
f8f12a7
feat(cc-digital-channel): change-from-vite-to-weback-config
akulakum Dec 23, 2025
f0432e0
feat(cc-digital-channel): update-yarn-yml-file
akulakum Dec 23, 2025
598aa3f
feat(cc-digital-channel): add-presentational-layer
akulakum Dec 24, 2025
6273e60
Merge branch 'next' into CC_DIGITAL_CHANNEL
akulakum Jan 5, 2026
d3e037d
feat(cc-digital-channel): fix-style-issue
akulakum Jan 5, 2026
cd2bbd7
feat(cc-digital-channel): update-beta-version
akulakum Jan 9, 2026
199a7e0
feat(cc-digital-channel): add-visual-rebrand
akulakum Jan 13, 2026
10fca07
Merge branch 'next' into CC_DIGITAL_CHANNEL
akulakum Jan 13, 2026
97e4ac7
feat(cc-digital-channel): get-access-token-from-store
akulakum Jan 14, 2026
085cb53
Merge branch 'next' of github.com:webex/widgets into CC_DIGITAL_CHANNEL
akulakum Jan 19, 2026
eeb2ad1
feat(cc-digital-channel): get-datacenter-from-store
akulakum Jan 20, 2026
402aea9
Merge branch 'next' into CC_DIGITAL_CHANNEL
akulakum Jan 20, 2026
08cdf82
fix: updated with public package
rsarika Jan 27, 2026
c1ea8d0
Merge branch 'next' of https://github.com/webex/widgets into CC_DIGIT…
rsarika Jan 27, 2026
9b47030
Merge branch 'next' into CC_DIGITAL_CHANNEL
akulakum Jan 28, 2026
94ca387
Merge branch 'next' into CC_DIGITAL_CHANNEL
akulakum Jan 29, 2026
01e6c63
feat(cc-digital-channel): remove-console-log
akulakum Jan 29, 2026
f052ab8
feat(cc-digital-channel): implement-datacenter-logic
akulakum Jan 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions jest.setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
// This is a workaround for the fact that JSDOM does not support canvas methods like getContext.
import 'jest-canvas-mock';

// Set up the global that cc-digital-interactions expects
global.AGENTX_SERVICE = {};

// Web components used in @momentum-design imports rely on browser-only APIs like attachInternals.
// Jest (via JSDOM) doesn't support these, causing runtime errors in tests.
// We mock these methods on HTMLElement to prevent test failures.
Expand Down
20 changes: 20 additions & 0 deletions packages/contact-center/cc-digital-channels/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import globals from 'globals';
import pluginJs from '@eslint/js';
import tseslint from 'typescript-eslint';
import pluginReact from 'eslint-plugin-react';
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
import eslintConfigPrettier from 'eslint-config-prettier';

export default [
{files: ['**/src/**/*.{js,mjs,cjs,ts,jsx,tsx}']},
{ignores: ['.babelrc.js', '*config.{js,ts}', 'dist', 'node_modules', 'coverage']},
{languageOptions: {globals: globals.browser}},
pluginJs.configs.recommended,
...tseslint.configs.recommended,
{
...pluginReact.configs.flat.recommended,
settings: {react: {version: 'detect'}},
},
eslintPluginPrettierRecommended,
eslintConfigPrettier,
];
9 changes: 9 additions & 0 deletions packages/contact-center/cc-digital-channels/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const jestConfig = require('../../../jest.config.js');

jestConfig.rootDir = '../../../';
jestConfig.testMatch = ['**/cc-digital-channels/tests/**/*.ts', '**/cc-digital-channels/tests/**/*.tsx'];
jestConfig.transformIgnorePatterns = [
'/node_modules/(?!(@momentum-design/components|@momentum-ui/web-components|@momentum-ui/react-collaboration|@lit|lit|cheerio|@popperjs|@webex-engage|@interactjs|react-error-boundary))',
];

module.exports = jestConfig;
72 changes: 72 additions & 0 deletions packages/contact-center/cc-digital-channels/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
{
"name": "@webex/cc-digital-channels",
"description": "Webex Contact Center Widgets: Digital Channels",
"license": "Cisco's General Terms (https://www.cisco.com/site/us/en/about/legal/contract-experience/index.html)",
"version": "1.28.0-ccwidgets.126",
"main": "dist/index.js",
"types": "dist/types/index.d.ts",
"publishConfig": {
"access": "public"
},
"files": [
"dist/",
"package.json"
],
"scripts": {
"clean": "rm -rf dist && rm -rf node_modules",
"clean:dist": "rm -rf dist",
"build": "yarn run -T tsc",
"build:src": "yarn run clean:dist && yarn run build && webpack",
"build:watch": "webpack --watch",
"test:unit": "jest --coverage",
"test:styles": "eslint",
"deploy:npm": "yarn npm publish"
},
"dependencies": {
"@webex/cc-store": "workspace:*",
"cc-digital-interactions": "3.0.6-beta.1",
"mobx-react-lite": "^4.1.0"
},
"devDependencies": {
"@babel/core": "7.25.2",
"@babel/preset-env": "7.25.4",
"@babel/preset-react": "7.24.7",
"@babel/preset-typescript": "7.25.9",
"@eslint/js": "^9.20.0",
"@testing-library/dom": "10.4.0",
"@testing-library/jest-dom": "6.6.2",
"@testing-library/react": "16.0.1",
"@types/jest": "29.5.14",
"@webex/test-fixtures": "workspace:*",
"babel-jest": "29.7.0",
"babel-loader": "9.2.1",
"css-loader": "7.1.2",
"eslint": "^9.20.1",
"eslint-config-prettier": "^10.0.1",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "^2.25.2",
"eslint-plugin-n": "^15.0.0 || ^16.0.0 ",
"eslint-plugin-prettier": "^5.2.3",
"eslint-plugin-promise": "^6.0.0",
"eslint-plugin-react": "^7.37.4",
"globals": "^16.0.0",
"jest": "29.7.0",
"jest-environment-jsdom": "29.7.0",
"prettier": "^3.5.1",
"sass": "1.79.5",
"sass-loader": "16.0.2",
"style-loader": "4.0.0",
"ts-jest": "^29.1.1",
"ts-loader": "9.5.1",
"typescript": "5.6.3",
"typescript-eslint": "^8.24.1",
"webpack": "5.94.0",
"webpack-cli": "5.1.4",
"webpack-merge": "6.0.1"
},
"peerDependencies": {
"@momentum-ui/web-components": "^2.23.35",
"react": ">=18.3.1",
"react-dom": ">=18.3.1"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React, {useMemo} from 'react';
import Engage from 'cc-digital-interactions';

import '@momentum-ui/web-components';
import {DigitalChannelsComponentProps} from './digital-channels.types';

/**
* Presentation component for Digital Channels.
* Renders the Engage widget with proper theming.
*/
const DigitalChannelsComponent: React.FunctionComponent<DigitalChannelsComponentProps> = ({
conversationId,
jwtToken,
dataCenter,
currentTheme = 'LIGHT',
}) => {
// Create a stable key based on critical props to force remount when they change
// This prevents issues with the Froala editor trying to cleanup/reinitialize improperly
const componentKey = useMemo(() => {
return `${conversationId}-${jwtToken.slice(-8)}-${dataCenter}`;
}, [conversationId, jwtToken, dataCenter]);

const isDarkTheme = currentTheme === 'DARK';

return (
<div>
<md-theme id="app-theme" theme="momentumV2" {...(isDarkTheme ? {darktheme: true} : {lighttheme: true})}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did we confirm if this is required? To use widgets we have asked dev to wrap the app in Theme and Icon Provider, so if a developer does that and here we are again wrapping the component that mean the Engage widget is wrapped twice. Just wanna ensure this doesnt break anything. Maybe its worthwhile to check with momentum team about this, We are wrapping a web-component in a react component.

<Engage
key={componentKey}
conversationId={conversationId}
jwtToken={jwtToken}
dataCenter={dataCenter}
interactionId=""
readonly={false}
theme={isDarkTheme ? 'dark' : 'light'}
isVisualRebrand={true}
/>
</md-theme>
</div>
);
};

export {DigitalChannelsComponent};
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {ITask} from '@webex/cc-store';

export interface UseDigitalChannelsInitProps {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Maybe we can rename this and remove the Use part from the names.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, Im assuming this is for hook. We were using this naming convention in web-client, in this repo we just use the work hook

Suggested change
export interface UseDigitalChannelsInitProps {
export interface DigitalChanelsHooksProps {

currentTask: ITask;
jwtToken: string;
dataCenter: string;
logger: {
log: (message: string, meta?: Record<string, unknown>) => void;
error: (message: string, error?: unknown, meta?: Record<string, unknown>) => void;
};
isDigitalChannelsInitialized: boolean;
setDigitalChannelsInitialized: (value: boolean) => void;
skipInit?: boolean;
}

export interface UseDigitalChannelsDataProps {
getAccessToken: () => Promise<string>;
currentTask: ITask | null;
logger?: {
log: (message: string, meta?: Record<string, unknown>) => void;
error: (message: string, meta?: Record<string, unknown>) => void;
};
}

export interface DigitalChannelsProps {
currentTheme?: string;
}

export interface DigitalChannelsComponentProps {
conversationId: string;
jwtToken: string;
dataCenter: string;
currentTheme?: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React from 'react';
import {observer} from 'mobx-react-lite';
import {ErrorBoundary} from 'react-error-boundary';

import store from '@webex/cc-store';
import {useDigitalChannelsInit, useDigitalChannelsData} from '../helper';
import {DigitalChannelsComponent} from './DigitalChannelsComponent';
import {DigitalChannelsProps} from './digital-channels.types';

const DigitalChannelsInternal: React.FunctionComponent<DigitalChannelsProps> = observer(({currentTheme}) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to move this to another file just for organisation perspective perhaps.

const {logger, currentTask, isDigitalChannelsInitialized, setDigitalChannelsInitialized, getAccessToken, dataCenter} =
store;

// Fetch JWT token and conversation ID
const {jwtToken, conversationId, hasError} = useDigitalChannelsData({
getAccessToken,
currentTask,
logger,
});

// Initialize Digital Channels app once we have all required data
const {initialized} = useDigitalChannelsInit({
currentTask: currentTask || ({} as typeof currentTask),
jwtToken: jwtToken || '',
dataCenter: dataCenter || '',
logger,
isDigitalChannelsInitialized,
setDigitalChannelsInitialized,
// Skip initialization if we don't have required data
skipInit: !currentTask || !jwtToken || !dataCenter,
});

// Early return after all hooks are called
if (!currentTask || !jwtToken || !dataCenter || hasError || !initialized || !conversationId) {
return null;
}

return (
<DigitalChannelsComponent
conversationId={conversationId}
jwtToken={jwtToken}
dataCenter={dataCenter}
currentTheme={currentTheme}
/>
);
});

const DigitalChannels: React.FunctionComponent<DigitalChannelsProps> = (props) => {
return (
<ErrorBoundary
fallbackRender={() => <></>}
onError={(error: Error) => {
if (store.onErrorCallback) store.onErrorCallback('DigitalChannels', error);
}}
>
<DigitalChannelsInternal {...props} />
</ErrorBoundary>
);
};

export {DigitalChannels};
116 changes: 116 additions & 0 deletions packages/contact-center/cc-digital-channels/src/helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import {useEffect, useState, useMemo} from 'react';
import {initializeApp} from 'cc-digital-interactions';

import {UseDigitalChannelsInitProps, UseDigitalChannelsDataProps} from './digital-channels/digital-channels.types';

/**
* Hook to handle Digital Channels initialization.
* Ensures initialization happens only once per session using store flag.
*/
export const useDigitalChannelsInit = (props: UseDigitalChannelsInitProps) => {
const {
currentTask,
jwtToken,
dataCenter,
logger,
isDigitalChannelsInitialized,
setDigitalChannelsInitialized,
skipInit = false,
} = props;

const [initialized, setInitialized] = useState(isDigitalChannelsInitialized);

useEffect(() => {
// Skip initialization if required data is not available
if (skipInit) {
return;
}

const initialize = async () => {
// Initialize the digital channels app only once per session
if (!isDigitalChannelsInitialized) {
logger.log(
`[DIGITAL_CHANNELS_INIT] 🚀 Starting Digital Channels initialization for the FIRST TIME (dataCenter: ${dataCenter})...`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we not have emogis in logs.

{
module: 'cc-digital-channels',
method: 'useDigitalChannelsInit',
}
);

try {
await initializeApp(dataCenter, jwtToken);
setDigitalChannelsInitialized(true);
setInitialized(true);
logger.log('[DIGITAL_CHANNELS_INIT] ✅ Digital Channels app initialized SUCCESSFULLY', {
module: 'cc-digital-channels',
method: 'useDigitalChannelsInit',
});
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
logger.error(`[DIGITAL_CHANNELS_INIT] ❌ Failed to initialize Digital Channels app: ${errorMessage}`, {
module: 'cc-digital-channels',
method: 'useDigitalChannelsInit',
error,
});
}
} else {
logger.log('[DIGITAL_CHANNELS_INIT] ✅ App already initialized. Skipping re-initialization.', {
module: 'cc-digital-channels',
method: 'useDigitalChannelsInit',
});
setInitialized(true);
}
};

initialize();
}, [currentTask, skipInit, jwtToken]);

return {initialized};
};

/**
* Hook to handle fetching Digital Channels data (token and conversationId).
* Centralizes token fetching logic to keep the component clean.
*/
export const useDigitalChannelsData = (props: UseDigitalChannelsDataProps) => {
const {getAccessToken, currentTask, logger} = props;

const [jwtToken, setJwtToken] = useState<string>('');
const [tokenError, setTokenError] = useState<boolean>(false);

// Fetch access token from the store
useEffect(() => {
const fetchToken = async () => {
try {
const token = await getAccessToken();
setJwtToken(token);
} catch (error) {
logger?.error('[DIGITAL_CHANNELS] ❌ Failed to get access token', {
module: 'cc-digital-channels',
method: 'useDigitalChannelsData.fetchToken',
error,
});
setTokenError(true);
}
};
fetchToken();
}, [getAccessToken, logger]);

// Extract conversationId from currentTask (always call this, return empty string if no task)
const conversationId = useMemo(() => {
if (!currentTask) return '';
return (
(currentTask.data.interaction as {callAssociatedDetails?: {mediaResourceId?: string}}).callAssociatedDetails
?.mediaResourceId || ''
);
}, [currentTask]);

const hasError = tokenError;

return {
jwtToken,
conversationId,
tokenError,
hasError,
};
};
4 changes: 4 additions & 0 deletions packages/contact-center/cc-digital-channels/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import {DigitalChannels} from './digital-channels';

export {DigitalChannels};
export default DigitalChannels;
15 changes: 15 additions & 0 deletions packages/contact-center/cc-digital-channels/src/types/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Declare custom HTML elements used by the Webex Engage components
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we wont require md-theme, we can get rid of this file as well

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this comment is still applicable... but if so, we can remove this file.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets check this please.

declare global {
namespace JSX {
interface IntrinsicElements {
'md-theme': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement> & {
theme?: string;
class?: string;
darktheme?: boolean;
lighttheme?: boolean;
};
}
}
}

export {};
Loading
Loading