Skip to content

Commit c8d9fdd

Browse files
committed
fix(store): error handle cc.register method
1 parent ab03c0c commit c8d9fdd

9 files changed

Lines changed: 603 additions & 946 deletions

File tree

packages/contact-center/store/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"deploy:npm": "yarn npm publish"
2424
},
2525
"dependencies": {
26-
"@webex/contact-center": "3.10.0-next.20",
26+
"@webex/contact-center": "3.10.0-next.33",
2727
"mobx": "6.13.5",
2828
"typescript": "5.6.3"
2929
},

packages/contact-center/store/src/store.ts

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -133,33 +133,38 @@ class Store implements IStore {
133133
reject(new Error('Webex SDK failed to initialize'));
134134
}, 6000);
135135

136-
//@ts-expect-error To be fixed in SDK - https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6762
137-
const webex = Webex.init({
138-
config: options.webexConfig,
139-
credentials: {
140-
access_token: options.access_token,
141-
},
142-
});
136+
try {
137+
//@ts-expect-error To be fixed in SDK - https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6762
138+
const webex = Webex.init({
139+
config: options.webexConfig,
140+
credentials: {
141+
access_token: options.access_token,
142+
},
143+
});
143144

144-
webex.once('ready', () => {
145-
setupEventListeners(webex.cc);
146-
clearTimeout(timer);
147-
this.registerCC(webex)
148-
.then(() => {
149-
this.logger.log('CC-Widgets: Store init(): store initialization complete', {
150-
module: 'cc-store#store.ts',
151-
method: 'init',
145+
webex.once('ready', () => {
146+
setupEventListeners(webex.cc);
147+
clearTimeout(timer);
148+
this.registerCC(webex)
149+
.then(() => {
150+
this.logger.log('CC-Widgets: Store init(): store initialization complete', {
151+
module: 'cc-store#store.ts',
152+
method: 'init',
153+
});
154+
resolve();
155+
})
156+
.catch((error) => {
157+
this.logger.error(`CC-Widgets: Store init(): registration failed - ${error}`, {
158+
module: 'cc-store#store.ts',
159+
method: 'init',
160+
});
161+
reject(error);
152162
});
153-
resolve();
154-
})
155-
.catch((error) => {
156-
this.logger.error(`CC-Widgets: Store init(): registration failed - ${error}`, {
157-
module: 'cc-store#store.ts',
158-
method: 'init',
159-
});
160-
reject(error);
161-
});
162-
});
163+
});
164+
} catch (error) {
165+
clearTimeout(timer);
166+
reject(error);
167+
}
163168
});
164169
}
165170
}

packages/contact-center/store/src/storeEventsWrapper.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,15 @@ class StoreWrapper implements IStoreWrapper {
384384
};
385385

386386
init(options: InitParams): Promise<void> {
387-
return this.store.init(options, this.setupIncomingTaskHandler);
387+
return this.store.init(options, this.setupIncomingTaskHandler).catch((error) => {
388+
const err = error instanceof Error ? error : new Error(String(error));
389+
390+
if (this.onErrorCallback) {
391+
this.onErrorCallback('Store', err);
392+
}
393+
394+
throw err;
395+
});
388396
}
389397

390398
registerCC = (webex?: WithWebex['webex']) => {

packages/contact-center/store/tests/store.ts

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,15 +179,34 @@ describe('Store', () => {
179179
expect(storeInstance.registerCC).toHaveBeenCalledWith(mockWebex);
180180
});
181181

182-
it('should reject the promise if registerCC fails in init method', async () => {
182+
it('should log an error and reject the promise if registerCC fails in init method', async () => {
183183
const initParams = {
184184
webexConfig: {anyConfig: true},
185185
access_token: 'fake_token',
186186
};
187187

188-
jest.spyOn(storeInstance, 'registerCC').mockRejectedValue(new Error('registerCC failed'));
188+
const error = new Error('registerCC failed');
189+
jest.spyOn(storeInstance, 'registerCC').mockRejectedValue(error);
190+
191+
// Provide a logger so the init() error handler can log without failing
192+
// @ts-expect-error partial logger mock for test
193+
storeInstance.logger = {
194+
error: jest.fn(),
195+
log: jest.fn(),
196+
warn: jest.fn(),
197+
info: jest.fn(),
198+
trace: jest.fn(),
199+
};
189200

190201
await expect(storeInstance.init(initParams, jest.fn())).rejects.toThrow('registerCC failed');
202+
203+
expect(storeInstance.logger.error).toHaveBeenCalledWith(
204+
'CC-Widgets: Store init(): registration failed - Error: registerCC failed',
205+
{
206+
module: 'cc-store#store.ts',
207+
method: 'init',
208+
}
209+
);
191210
});
192211

193212
it('should reject the promise if Webex SDK fails to initialize', async () => {
@@ -206,5 +225,27 @@ describe('Store', () => {
206225

207226
await expect(initPromise).rejects.toThrow('Webex SDK failed to initialize');
208227
});
228+
229+
it('should clear timeout and reject if Webex.init throws synchronously', async () => {
230+
const initParams = {
231+
webexConfig: {anyConfig: true},
232+
access_token: 'fake_token',
233+
};
234+
235+
const syncError = new Error('sync init error');
236+
// @ts-expect-error overriding mock implementation for this test
237+
const initSpy = jest.spyOn(Webex, 'init').mockImplementation(() => {
238+
throw syncError;
239+
});
240+
241+
await expect(storeInstance.init(initParams, jest.fn())).rejects.toThrow('sync init error');
242+
243+
expect(initSpy).toHaveBeenCalledWith({
244+
config: initParams.webexConfig,
245+
credentials: {
246+
access_token: initParams.access_token,
247+
},
248+
});
249+
});
209250
});
210251
});

packages/contact-center/store/tests/storeEventsWrapper.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,52 @@ describe('storeEventsWrapper', () => {
645645
expect(mockTask.on).toHaveBeenCalledWith(TASK_EVENTS.AGENT_CONSULT_CREATED, expect.any(Function));
646646
});
647647

648+
it('should call onErrorCallback and rethrow when store.init rejects with an Error', async () => {
649+
const cc = storeWrapper['store'].cc;
650+
const logger = storeWrapper['store'].logger;
651+
const error = new Error('init failed');
652+
const onErrorCallback = jest.fn();
653+
654+
storeWrapper['store'].init = jest.fn().mockRejectedValue(error);
655+
// Directly set onErrorCallback to focus on init error handling behavior
656+
storeWrapper.onErrorCallback = onErrorCallback;
657+
658+
const options = {
659+
webex: {
660+
cc,
661+
logger,
662+
},
663+
};
664+
665+
await expect(storeWrapper.init(options)).rejects.toThrow('init failed');
666+
667+
expect(onErrorCallback).toHaveBeenCalledWith('Store', error);
668+
});
669+
670+
it('should wrap non-Error rejections and pass wrapped Error to onErrorCallback', async () => {
671+
const cc = storeWrapper['store'].cc;
672+
const logger = storeWrapper['store'].logger;
673+
const rawError = 'init failed as string';
674+
const onErrorCallback = jest.fn();
675+
676+
storeWrapper['store'].init = jest.fn().mockRejectedValue(rawError);
677+
storeWrapper.onErrorCallback = onErrorCallback;
678+
679+
const options = {
680+
webex: {
681+
cc,
682+
logger,
683+
},
684+
};
685+
686+
await expect(storeWrapper.init(options)).rejects.toThrow('init failed as string');
687+
688+
expect(onErrorCallback).toHaveBeenCalledWith('Store', expect.any(Error));
689+
const [, wrappedError] = onErrorCallback.mock.calls[0];
690+
expect(wrappedError).toBeInstanceOf(Error);
691+
expect(wrappedError.message).toBe('init failed as string');
692+
});
693+
648694
it('should handle task assignment and call onTaskAssigned callback', () => {
649695
const mockTaskAssignedCallback = jest.fn();
650696
storeWrapper.setTaskAssigned(mockTaskAssignedCallback);

widgets-samples/cc/samples-cc-react-app/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"react-dom": "18.3.1",
2626
"ts-loader": "^9.5.1",
2727
"typescript": "^5.6.3",
28-
"webex": "3.9.0-next.30",
28+
"webex": "3.10.0-next.49",
2929
"webpack": "^5.94.0",
3030
"webpack-cli": "^5.1.4",
3131
"webpack-dev-server": "^5.1.0",

widgets-samples/cc/samples-cc-react-app/src/App.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -643,10 +643,16 @@ function App() {
643643
disabled={accessToken.trim() === ''}
644644
onClick={() => {
645645
setShowLoader(true);
646-
store.init({webexConfig, access_token: accessToken}).then(() => {
647-
setIsSdkReady(true);
648-
setShowLoader(false);
649-
});
646+
store
647+
.init({webexConfig, access_token: accessToken})
648+
.then(() => {
649+
setIsSdkReady(true);
650+
setShowLoader(false);
651+
})
652+
.catch((error) => {
653+
console.error('Failed to initialize widgets:', error);
654+
setShowLoader(false);
655+
});
650656
}}
651657
data-testid="samples:init-widgets-button"
652658
>

widgets-samples/cc/samples-cc-react-app/webpack.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ module.exports = {
5555
// TS/TSX → transpile only (skip type-checking)
5656
{
5757
test: /\.[jt]sx?$/,
58-
include: [path.resolve(__dirname, 'src'), ...PKG_SRC],
58+
include: [path.resolve(__dirname, 'src'), ...PKG_SRC, resolveMonorepoRoot('node_modules/xxh3-ts')],
5959
loader: 'ts-loader',
6060
options: {
6161
transpileOnly: true, // ✅ disables type-checking

0 commit comments

Comments
 (0)