Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(plugin-cc): Spark 562169 task accept api implementation #4005

Open
wants to merge 8 commits into
base: wxcc
Choose a base branch
from

Conversation

Kesari3008
Copy link
Contributor

@Kesari3008 Kesari3008 commented Nov 26, 2024

COMPLETES #SPARK-562169

This pull request addresses

Call Accept Flow Implementation in CC SDK

by making the following changes

Created a new file contact.ts and moved the contact API code from the agent desktop to CC SDK. This file contains the API invocation a handling of success and failure events as per the agent desktop. This layer is present behind the services layer.
Create a new class called TaksCControl, a public module to access task-specific public methods. Implemented the call accept flow for the browser login option there.
Updated sample app to test the call accept flow.
Added UT for the changes done

Change Type

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Tooling change
  • Internal code refactor

The following scenarios were tested

Scenario 1: Agent login with Browser option. Initiated an incoming call and answered it from the sample app.
Scenario 2: Agent login with Browser option. Initiated an incoming call and declined it from the sample app.
Scenario 3: Agent login with an Extension option. Initiated an incoming call, answer and decline buttons are disabled as the call needs to be answered from an actual device.

https://app.vidcast.io/share/358a656c-ca26-4d69-8aee-526b33345fe3

I certified that

  • I have read and followed contributing guidelines

  • I discussed changes with code owners prior to submitting this pull request

  • I have not skipped any automated checks

  • All existing and new tests passed

  • I have updated the documentation accordingly


Make sure to have followed the contributing guidelines before submitting.

Summary by CodeRabbit

  • New Features

    • Introduced a new Contact Center module in the Webex SDK, enhancing functionality for managing agent interactions.
    • Added a new link to the "Contact Center" sample in the documentation for improved navigation.
    • Implemented a WebSocket manager to handle connections and events more robustly.
    • Added comprehensive agent management features, including login, logout, and status updates.
  • Bug Fixes

    • Improved error handling and logging for various operations within the Contact Center functionalities.
  • Documentation

    • Updated README and various documentation files to reflect new features and usage instructions.
  • Tests

    • Expanded unit tests for the Contact Center and related services to ensure functionality and error handling.

@Kesari3008 Kesari3008 requested review from a team as code owners November 26, 2024 14:37
@Kesari3008 Kesari3008 added the validated If the pull request is validated for automation. label Nov 26, 2024
Copy link

coderabbitai bot commented Nov 26, 2024

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

The pull request introduces several changes across multiple files, primarily focusing on enhancing the deployment workflow, adding new features for a contact center application, and improving error handling in the Mercury class. Key modifications include the addition of a new branch for deployment, updates to the documentation and sample applications, and the introduction of a comprehensive error handling framework. Additionally, new configuration files and service classes are created to support the contact center functionalities, along with corresponding unit tests to ensure robustness.

Changes

File Change Summary
.github/workflows/deploy.yml Added wxcc branch to deployable branches; added condition to skip publish-documentation job for wxcc.
docs/index.html Added link for "Contact Center" in the "Samples" section.
docs/samples/contact-center/app.js Implemented contact center application using Webex SDK; includes user authentication, agent management, and UI interactions.
docs/samples/contact-center/index.html Created new HTML file for Webex JavaScript SDK sample focused on Meetings Plugin.
docs/samples/contact-center/style.css Added comprehensive CSS stylesheet for the contact center interface.
packages/@webex/internal-plugin-mercury/src/mercury.js Enhanced error handling and added new properties for connection management; deprecated listen and stopListening methods.
packages/@webex/internal-plugin-mercury/src/socket/socket-base.js Updated open method to include new optional parameters for socket behavior.
packages/@webex/internal-plugin-mercury/test/unit/spec/mercury.js Added tests for connection handling and error management in Mercury class.
packages/@webex/plugin-cc/README.md Created README file for @webex/plugin-cc, detailing usage and development instructions.
packages/@webex/plugin-cc/__mocks__/workerMock.js Introduced mock implementation of the Worker class for testing.
packages/@webex/plugin-cc/babel.config.json Added Babel configuration for TypeScript support.
packages/@webex/plugin-cc/jest.config.js Introduced Jest configuration for testing with coverage reporting.
packages/@webex/plugin-cc/package.json Created new package.json for @webex/plugin-cc with metadata and scripts.
packages/@webex/plugin-cc/src/cc.ts Added ContactCenter class for managing contact center interactions.
packages/@webex/plugin-cc/src/config.ts Created configuration file for the contact center plugin.
packages/@webex/plugin-cc/src/constants.ts Added constants for agent states and API endpoint mappings.
packages/@webex/plugin-cc/src/index.ts Registered Contact Center plugin within Webex framework.
packages/@webex/plugin-cc/src/logger-proxy.ts Introduced LoggerProxy class for centralized logging management.
packages/@webex/plugin-cc/src/services/Task/constants.ts Defined constants related to task messaging and API endpoints.
packages/@webex/plugin-cc/src/services/Task/contact.ts Implemented routingContact function for managing contact tasks.
packages/@webex/plugin-cc/src/services/Task/index.ts Created Task class for managing audio streams in calling service.
packages/@webex/plugin-cc/src/services/Task/types.ts Defined various types related to task management.
packages/@webex/plugin-cc/src/services/WebCallingService.ts Added WebCallingService class for managing web calling features.
packages/@webex/plugin-cc/src/services/WebSocket/config.ts Introduced WebSocket configuration object.
packages/@webex/plugin-cc/src/services/WebSocket/index.ts Created WebSocket class for managing WebSocket connections.
packages/@webex/plugin-cc/src/services/WebSocket/types.ts Added interface for WebSocket management.
packages/@webex/plugin-cc/src/services/agent/index.ts Implemented routing functions for agent-related operations.
packages/@webex/plugin-cc/src/services/agent/types.ts Defined types for agent state management.
packages/@webex/plugin-cc/src/services/config/Util.ts Introduced utility functions for processing agent configurations.
packages/@webex/plugin-cc/src/services/config/constants.ts Defined constants and endpoint mappings for team and auxiliary code management.
packages/@webex/plugin-cc/src/services/config/index.ts Created AgentConfigService for managing agent configuration data retrieval.
packages/@webex/plugin-cc/src/services/config/types.ts Defined various types and constants for contact center service.
packages/@webex/plugin-cc/src/services/constants.ts Added constants for API URLs and agent management.
packages/@webex/plugin-cc/src/services/core/Err.ts Established error handling structure for agents and tasks.
packages/@webex/plugin-cc/src/services/core/GlobalTypes.ts Introduced message and failure type definitions.
packages/@webex/plugin-cc/src/services/core/HttpRequest.ts Implemented HttpRequest class for managing HTTP requests.
packages/@webex/plugin-cc/src/services/core/Utils.ts Added utility functions for error handling.
packages/@webex/plugin-cc/src/services/core/WebSocket/WebSocketManager.ts Created WebSocketManager class for managing WebSocket connections.
packages/@webex/plugin-cc/src/services/core/WebSocket/connection-service.ts Introduced ConnectionService class for managing WebSocket connections.
packages/@webex/plugin-cc/src/services/core/WebSocket/keepalive.worker.js Added Web Worker script for managing WebSocket keepalive functionality.
packages/@webex/plugin-cc/src/services/core/aqm-reqs.ts Implemented AqmReqs class for managing API requests and WebSocket notifications.
packages/@webex/plugin-cc/src/services/core/constants.ts Defined various timeout and interval constants.
packages/@webex/plugin-cc/src/services/core/types.ts Introduced new type definitions for request handling.
packages/@webex/plugin-cc/src/services/index.ts Created Services class for encapsulating service components.
packages/@webex/plugin-cc/src/types.ts Defined various types and interfaces for the Webex Contact Center plugin.
packages/@webex/plugin-cc/test/unit/spec/cc.ts Established unit tests for the ContactCenter class.
packages/@webex/plugin-cc/test/unit/spec/services/WebCallingService.ts Added unit tests for the WebCallingService class.
packages/@webex/plugin-cc/test/unit/spec/services/agent/index.ts Introduced unit tests for the AQM routing agent.
packages/@webex/plugin-cc/test/unit/spec/services/config/index.ts Created unit tests for the AgentConfigService class.
packages/@webex/plugin-cc/test/unit/spec/services/core/HttpRequest.ts Added unit tests for the HttpRequest service.
packages/@webex/plugin-cc/test/unit/spec/services/core/WebSocket/WebSocketManager.ts Established unit tests for the WebSocketManager class.
packages/@webex/plugin-cc/test/unit/spec/services/core/WebSocket/connection-service.ts Introduced unit tests for the ConnectionService class.
packages/@webex/plugin-cc/test/unit/spec/services/core/aqm-reqs.ts Added unit tests for the AqmReqs class.
packages/@webex/plugin-cc/tsconfig.json Created TypeScript configuration file for the plugin.
packages/@webex/test-helper-mock-webex/src/index.js Added entry for cc to nonInternalPlugins.
packages/@webex/webex-core/src/lib/services/service-catalog.js Modified findServiceUrlFromUrl method for enhanced URL matching.
packages/@webex/webex-core/test/unit/spec/services/service-catalog.js Enhanced test suite for findServiceUrlFromUrl method.
packages/calling/src/index.ts Added export for CallingClientConfig.
packages/webex/package.json Updated package.json to include new exports and dependencies.
packages/webex/src/contact-center.js Introduced a new Webex module for contact center functionality.
packages/webex/src/webex.js Required @webex/plugin-cc module.
packages/webex/test/unit/spec/webex.js Enhanced testing environment with necessary mocks.
tooling/lib/build.js Modified buildSamples function to include cleanup for additional sample files.
webpack.config.js Added new entry point for the contact center module.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Webex
    participant ContactCenter
    participant WebSocketManager
    participant AgentConfigService

    User->>Webex: Initialize Webex instance
    Webex->>ContactCenter: Register Contact Center plugin
    ContactCenter->>AgentConfigService: Fetch agent configurations
    AgentConfigService-->>ContactCenter: Return configurations
    ContactCenter->>WebSocketManager: Initialize WebSocket connection
    WebSocketManager-->>ContactCenter: Connection established
    User->>ContactCenter: Perform agent actions
    ContactCenter->>WebSocketManager: Send agent actions
    WebSocketManager-->>ContactCenter: Acknowledge actions
Loading

Possibly related PRs

Suggested labels

validated

Suggested reviewers

  • marcin-bazyl
  • chburket
  • mkesavan13
  • sreenara
  • adhmenon

Poem

🐇 In the meadow where changes bloom,
New branches sprout, dispelling gloom.
A contact center springs to life,
With agents ready, free from strife.
Webex dances, connections flow,
In this garden, progress will grow! 🌼


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@Kesari3008 Kesari3008 changed the base branch from next to feat/wxcc November 26, 2024 14:37
@Kesari3008 Kesari3008 changed the base branch from feat/wxcc to wxcc November 26, 2024 14:46
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments.

🛑 Comments failed to post (111)
packages/@webex/plugin-cc/src/logger-proxy.ts (1)

3-9: 💡 Codebase verification

🛠️ Refactor suggestion

Refactoring needed: LoggerProxy is widely used across multiple packages

The suggested refactoring would require changes across multiple packages:

  • @webex/plugin-cc: Used extensively in services and tests
  • @webex/plugin-meetings: Heavy usage across meeting management, media handling, and various subsystems

The proposed changes would impact:

  • 200+ direct logger access points
  • Multiple test files that mock logger behavior
  • Critical functionality in both CC and Meetings plugins
🔗 Analysis chain

Refactor to improve type safety and error handling

The current implementation has several areas for improvement:

  1. Using a class with only static members is an anti-pattern
  2. No validation in initialize method
  3. No protection against using uninitialized logger
  4. Missing type safety for logger methods

Consider this alternative implementation:

-export default class LoggerProxy {
-  public static logger: Logger;
-
-  public static initialize(logger: Logger): void {
-    LoggerProxy.logger = logger;
-  }
-}
+let logger: Logger | undefined;
+
+export function initializeLogger(newLogger: Logger): void {
+  if (!newLogger) {
+    throw new Error('Logger instance is required');
+  }
+  logger = newLogger;
+}
+
+export function getLogger(): Logger {
+  if (!logger) {
+    throw new Error('Logger not initialized. Call initializeLogger first.');
+  }
+  return logger;
+}

This implementation:

  • Uses a module-level singleton pattern instead of static class members
  • Adds validation in the initialize function
  • Provides type-safe access to the logger
  • Throws clear errors when used incorrectly

Consider adding a default logger implementation to avoid runtime errors:

class NoOpLogger implements Logger {
  // Implement with no-op methods
}

let logger: Logger = new NoOpLogger();

Let's check if there are any direct static accesses to LoggerProxy.logger that would need to be updated:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find direct static logger access
# Expected: Instances where LoggerProxy.logger is accessed directly

rg "LoggerProxy\.logger\." -t ts

Length of output: 57012

🧰 Tools
🪛 Biome (1.9.4)

[error] 3-9: Avoid classes that contain only static members.

Prefer using simple functions instead of classes with only static members.

(lint/complexity/noStaticOnlyClass)

packages/@webex/plugin-cc/src/index.ts (1)

10-10: 💡 Codebase verification

Export additional types and interfaces from the plugin

Based on the search results, there are several important types and interfaces in types.ts that should be exported for plugin consumers:

  • IContactCenter: Main interface defining the plugin's functionality
  • CCPluginConfig: Configuration interface for the plugin
  • LoginOption: Type for login options
  • Team, AgentLogin, BuddyAgents: Types for core agent functionality
  • Various response types like StationLoginResponse, StationLogoutResponse, etc.

Add these exports to index.ts:

export type {
  IContactCenter,
  CCPluginConfig,
  LoginOption,
  Team,
  AgentLogin,
  BuddyAgents,
  StationLoginResponse,
  StationLogoutResponse,
  StationReLoginResponse,
  SetStateResponse,
  BuddyAgentsResponse
} from './types';
🔗 Analysis chain

Consider exporting plugin types and interfaces.

While the default export is appropriate, consider also exporting any relevant types, interfaces, or enums that consumers of this plugin might need. This is particularly important for TypeScript users of the SDK.

Let's check for any types that might need to be exported:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Look for type definitions in related files that might need to be exported
# Expected: Find type definitions in cc.ts and related files

# Search for type definitions in the CC plugin directory
rg -t ts "^(export )?type |^(export )?interface " "packages/@webex/plugin-cc/src/"

Length of output: 10398

packages/@webex/plugin-cc/src/services/core/GlobalTypes.ts (1)

8-14: 🛠️ Refactor suggestion

Improve Failure type definition and remove redundancy.

The current implementation has the following concerns:

  1. orgId is redundant as it's already present in the base Msg type
  2. reasonCode could benefit from a more specific type definition
+/** Error codes for failure messages */
+export enum FailureReasonCode {
+  INVALID_REQUEST = 400,
+  UNAUTHORIZED = 401,
+  NOT_FOUND = 404,
+  INTERNAL_ERROR = 500,
+}
+
+/**
+ * Failure message type for error scenarios
+ */
 export type Failure = Msg<{
   agentId: string;
   trackingId: string;
-  reasonCode: number;
-  orgId: string;  // Remove redundant field
+  reasonCode: FailureReasonCode;
   reason: string;
 }>;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

/** Error codes for failure messages */
export enum FailureReasonCode {
  INVALID_REQUEST = 400,
  UNAUTHORIZED = 401,
  NOT_FOUND = 404,
  INTERNAL_ERROR = 500,
}

/**
 * Failure message type for error scenarios
 */
export type Failure = Msg<{
  agentId: string;
  trackingId: string;
  reasonCode: FailureReasonCode;
  reason: string;
}>;
packages/@webex/plugin-cc/src/services/Task/constants.ts (2)

9-9: ⚠️ Potential issue

Fix inconsistent path format in RESUME constant.

The RESUME constant is missing a leading slash, unlike other endpoint constants.

-export const RESUME = 'record/resume';
+export const RESUME = '/record/resume';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

export const RESUME = '/record/resume';

1-11: 💡 Codebase verification

Based on the search results, I can now provide a definitive response about the completeness of task operation endpoints:

Task operation endpoints should include /accept and /decline endpoints

The codebase shows that:

  • The Task service has accept() and decline() methods in Task/index.ts
  • The contact service implements the /accept endpoint at ${TASK_API}${interactionId}/accept
  • The code handles both accepting and declining tasks

However, while /accept is defined as an API endpoint, the ACCEPT and DECLINE constants are missing from Task/constants.ts. These should be added for consistency with other endpoint constants:

export const ACCEPT = '/accept';
export const DECLINE = '/decline';
🔗 Analysis chain

Verify completeness of task operation endpoints.

Given that this PR implements the "call accept flow", consider if endpoints for accepting/declining tasks should be included in these constants.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for accept/decline related API calls in the codebase
rg -g '*.{ts,js}' -i "('|\")/(accept|decline)"

# Search for any task-related API endpoint definitions
rg -g '*.{ts,js}' "const.*task.*=.*'/v1/tasks.*'"

Length of output: 108


Script:

#!/bin/bash
# Search for any task-related API calls or endpoints with broader patterns
rg -g '*.{ts,js}' "'/v1/tasks" -A 2 -B 2

# Search for task-related HTTP methods or operations
rg -g '*.{ts,js}' -i "(post|put|get|delete).*task" -A 2 -B 2

# Look for task service implementation files
fd -e ts -e js "task(service|handler|controller)"

Length of output: 131531


Script:

#!/bin/bash
# Search for task-related API endpoints with a broader pattern
rg -g '*.{ts,js}' -i "(accept|decline|task.*api)" -A 2 -B 2

# Look for task-related service files to understand the API structure
fd -e ts -e js "task" -d 3

# Search for task-related HTTP methods or operations
rg -g '*.{ts,js}' -i "(post|put|get|delete).*task.*accept" -A 2 -B 2

Length of output: 132360

packages/webex/src/contact-center.js (1)

14-18: 🛠️ Refactor suggestion

Improve initialization method robustness

The current implementation has a few concerns:

  1. It mutates the input parameter (hence the eslint disable)
  2. Lacks input validation
  3. No error handling for invalid configurations

Consider this more robust implementation:

-Webex.init = function init(attrs = {}) {
-  attrs.config = merge({}, config, attrs.config); // eslint-disable-line no-param-reassign
+Webex.init = function init(attrs = {}) {
+  if (attrs.config && typeof attrs.config !== 'object') {
+    throw new TypeError('Config must be an object');
+  }
+  
+  const mergedAttrs = {
+    ...attrs,
+    config: merge({}, config, attrs.config)
+  };
 
-  return new Webex(attrs);
+  return new Webex(mergedAttrs);
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Webex.init = function init(attrs = {}) {
  if (attrs.config && typeof attrs.config !== 'object') {
    throw new TypeError('Config must be an object');
  }
  
  const mergedAttrs = {
    ...attrs,
    config: merge({}, config, attrs.config)
  };

  return new Webex(mergedAttrs);
};
packages/@webex/plugin-cc/src/config.ts (2)

3-24: 🛠️ Refactor suggestion

Add TypeScript interfaces for type safety

The configuration object would benefit from explicit TypeScript interfaces to ensure type safety and provide better documentation of the expected structure.

Consider adding interfaces like this:

interface CCMetrics {
  clientName: string;
  clientType: string;
}

interface ServiceData {
  indicator: string;
  domain: string;
}

interface CallingClientConfig {
  logger: {
    level: typeof LOGGER.INFO;
  };
  serviceData: ServiceData;
}

interface CCConfig {
  allowMultiLogin: boolean;
  clientType: string;
  isKeepAliveEnabled: boolean;
  force: boolean;
  metrics: CCMetrics;
  callingClientConfig: CallingClientConfig;
}

const config: {cc: CCConfig} = {
  // ... existing configuration
};

export default config;

19-20: ⚠️ Potential issue

Critical: Hardcoded production domain needs to be environment-aware

The TODO comment indicates that the domain should be dynamic, but currently it's hardcoded to a production URL. This could cause issues:

  1. Risk of accidentally hitting production in non-prod environments
  2. Lack of flexibility for different deployments
  3. Potential security risk if credentials are sent to wrong environment

Consider implementing environment-based configuration:

const domains = {
  production: 'rtw.prod-us1.rtmsprod.net',
  staging: 'rtw.stage-us1.rtmsprod.net',
  development: 'rtw.dev-us1.rtmsprod.net',
};

const domain = domains[process.env.NODE_ENV || 'development'];
packages/@webex/plugin-cc/src/services/core/Utils.ts (2)

6-11: 🛠️ Refactor suggestion

Enhance type safety and error handling

While the function works, there are several improvements that could make it more robust:

-const getCommonErrorDetails = (errObj: WebexRequestPayload) => {
+interface ErrorDetails {
+  trackingId: string | undefined;
+  msg: unknown;
+}
+
+const getCommonErrorDetails = (errObj: WebexRequestPayload | undefined): ErrorDetails => {
+  if (!errObj) {
+    return {
+      trackingId: undefined,
+      msg: 'No error object provided'
+    };
+  }
   return {
-    trackingId: errObj?.headers?.trackingid || errObj?.headers?.TrackingID,
+    trackingId: errObj?.headers?.['trackingid']?.toLowerCase() || errObj?.headers?.['TrackingID']?.toLowerCase(),
     msg: errObj?.body,
   };
 };

Changes suggested:

  1. Added explicit return type interface
  2. Added null/undefined check for errObj
  3. Normalized trackingId case handling
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

interface ErrorDetails {
  trackingId: string | undefined;
  msg: unknown;
}

const getCommonErrorDetails = (errObj: WebexRequestPayload | undefined): ErrorDetails => {
  if (!errObj) {
    return {
      trackingId: undefined,
      msg: 'No error object provided'
    };
  }
  return {
    trackingId: errObj?.headers?.['trackingid']?.toLowerCase() || errObj?.headers?.['TrackingID']?.toLowerCase(),
    msg: errObj?.body,
  };
};

13-18: ⚠️ Potential issue

Fix type safety issues in error handling

The current implementation has several type safety concerns that could lead to runtime errors:

-export const getErrorDetails = (error: any, methodName: string) => {
+export const getErrorDetails = (error: { details?: Failure }, methodName: string): Error => {
-  const failure = error.details as Failure;
+  const failure = error?.details;
   LoggerProxy.logger.error(`${methodName} failed with trackingId: ${failure?.trackingId}`);

-  return new Error(failure?.data?.reason ?? `Error while performing ${methodName}`);
+  return new Error(
+    failure?.data?.reason || `Error while performing ${methodName}. ${
+      failure?.trackingId ? `TrackingId: ${failure.trackingId}` : ''
+    }`
+  );
 };

Changes needed:

  1. Replace any with a proper type definition
  2. Remove unsafe type assertion
  3. Include tracking ID in the default error message for better traceability
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

export const getErrorDetails = (error: { details?: Failure }, methodName: string): Error => {
  const failure = error?.details;
  LoggerProxy.logger.error(`${methodName} failed with trackingId: ${failure?.trackingId}`);

  return new Error(
    failure?.data?.reason || `Error while performing ${methodName}. ${
      failure?.trackingId ? `TrackingId: ${failure.trackingId}` : ''
    }`
  );
};
packages/@webex/plugin-cc/src/services/core/HttpRequest.ts (3)

20-34: 🛠️ Refactor suggestion

Enhance request method with validation, error handling, and resilience

The request method should include input validation, proper error handling, and resilience mechanisms.

   public async request(options: {
     service: string;
     resource: string;
     method: HTTP_METHODS;
     body?: RequestBody;
   }): Promise<IHttpResponse> {
     const {service, resource, method, body} = options;
 
+    // Input validation
+    if (!service?.trim()) throw new Error('Service is required');
+    if (!resource?.trim()) throw new Error('Resource is required');
+    if (!Object.values(HTTP_METHODS).includes(method)) {
+      throw new Error('Invalid HTTP method');
+    }
+
+    try {
       return this.webex.request({
         service,
         resource,
         method,
         body,
+        timeout: 30000, // Add reasonable timeout
       });
+    } catch (error) {
+      // Wrap error with context
+      throw new Error(`Request failed for ${service}/${resource}: ${error.message}`);
+    }
   }

Consider implementing a retry mechanism for transient failures:

private async withRetry<T>(
  operation: () => Promise<T>,
  maxRetries: number = 3
): Promise<T> {
  let lastError: Error;
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await operation();
    } catch (error) {
      lastError = error;
      if (!this.isTransientError(error) || attempt === maxRetries) {
        throw error;
      }
      await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));
    }
  }
  throw lastError;
}

Consider implementing request caching for frequently accessed resources to improve performance.


15-18: 🛠️ Refactor suggestion

Add webex instance validation in constructor

Consider validating the webex instance to ensure it has the required request method.

   private constructor(options: {webex: WebexSDK}) {
     const {webex} = options;
+    if (typeof webex.request !== 'function') {
+      throw new Error('Invalid WebexSDK instance: missing request method');
+    }
     this.webex = webex;
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  private constructor(options: {webex: WebexSDK}) {
    const {webex} = options;
    if (typeof webex.request !== 'function') {
      throw new Error('Invalid WebexSDK instance: missing request method');
    }
    this.webex = webex;
  }

7-13: 🛠️ Refactor suggestion

Improve error handling in getInstance

The method should handle invalid options more explicitly and provide meaningful error messages.

   public static getInstance(options?: {webex: WebexSDK}): HttpRequest {
+    if (!options?.webex) {
+      throw new Error('WebexSDK instance is required to initialize HttpRequest');
+    }
-    if (!HttpRequest.instance && options && options.webex) {
+    if (!HttpRequest.instance) {
       HttpRequest.instance = new HttpRequest(options);
     }

     return HttpRequest.instance;
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  public static getInstance(options?: {webex: WebexSDK}): HttpRequest {
    if (!options?.webex) {
      throw new Error('WebexSDK instance is required to initialize HttpRequest');
    }
    if (!HttpRequest.instance) {
      HttpRequest.instance = new HttpRequest(options);
    }

    return HttpRequest.instance;
  }
packages/@webex/plugin-cc/src/services/index.ts (1)

13-19: 🛠️ Refactor suggestion

Add input validation and error handling.

The constructor should validate the webSocketManager input and handle potential initialization failures.

Consider applying these improvements:

 constructor(options: {webSocketManager: WebSocketManager}) {
   const {webSocketManager} = options;
+  if (!webSocketManager) {
+    throw new Error('WebSocketManager is required');
+  }
+  try {
     const aqmReq = new AqmReqs(webSocketManager);
     this.config = new AgentConfigService();
     this.agent = routingAgent(aqmReq);
     this.contact = routingContact(aqmReq);
+  } catch (error) {
+    throw new Error(`Failed to initialize Services: ${error.message}`);
+  }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  constructor(options: {webSocketManager: WebSocketManager}) {
    const {webSocketManager} = options;
    if (!webSocketManager) {
      throw new Error('WebSocketManager is required');
    }
    try {
      const aqmReq = new AqmReqs(webSocketManager);
      this.config = new AgentConfigService();
      this.agent = routingAgent(aqmReq);
      this.contact = routingContact(aqmReq);
    } catch (error) {
      throw new Error(`Failed to initialize Services: ${error.message}`);
    }
  }
packages/@webex/plugin-cc/jest.config.js (1)

21-40: 💡 Codebase verification

Reporter configuration needs alignment across Jest configs

The review needs revision as there are inconsistencies in reporter configurations across the codebase:

  • The root jest.config.js and packages/calling/jest.config.js have similar but not identical configurations
  • The HTML reporter is only present in the plugin-cc config but not in others
  • Output paths vary slightly (e.g., './coverage/junit' vs 'coverage/junit')

For better maintainability and consistency:

  • Consider standardizing reporter configurations across all Jest configs
  • Align output directory paths (./coverage/junit vs coverage/junit)
  • Evaluate if HTML reporter should be added to other configs as well
🔗 Analysis chain

Comprehensive reporter setup with proper paths

The reporter configuration is well-structured with appropriate output paths and settings. The disabled auto-open for HTML reports is suitable for CI environments.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Verify no conflicting reporter configurations exist in other Jest configs
rg -l "jest-junit|jest-html-reporters" $(fd -t f "jest.config.js")

Length of output: 3900


Script:

#!/bin/bash
# Let's check the content of these files to see if there are any conflicting configurations
rg -A 10 "reporters.*:" jest.config.js packages/calling/jest.config.js

Length of output: 1076

packages/@webex/plugin-cc/src/services/WebSocket/types.ts (1)

8-21: 🛠️ Refactor suggestion

Improve type safety for event handling

The current string-based event system could benefit from stronger typing to prevent runtime errors and improve developer experience.

Consider these improvements:

+/**
+ * Supported WebSocket event types
+ */
+export enum WebSocketEventType {
+  CONNECTED = 'connected',
+  DISCONNECTED = 'disconnected',
+  MESSAGE = 'message',
+  ERROR = 'error'
+}

 interface IWebSocket {
   /**
    * Subscribe to the WebSocket events
-   * @param {string} event
+   * @param {WebSocketEventType} event - The event type to subscribe to
    * @param {function} callback
+   * @throws {Error} If the event type is not supported
    * @returns {void}
    */
-  on(event: string, callback: (event: WebSocketEvent) => void): void;
+  on(event: WebSocketEventType, callback: (event: WebSocketEvent) => void): void;

   /**
    * Unsubscribe from the WebSocket events
-   * @param {string} event
+   * @param {WebSocketEventType} event - The event type to unsubscribe from
    * @param {function} callback
    * @returns {void}
    */
-  off(event: string, callback: (event: WebSocketEvent) => void): void;
+  off(event: WebSocketEventType, callback: (event: WebSocketEvent) => void): void;

Committable suggestion skipped: line range outside the PR's diff.

packages/@webex/plugin-cc/src/services/core/types.ts (1)

48-48: 🛠️ Refactor suggestion

Replace void with undefined in union type

The static analysis tool correctly flags that void in a union type can be confusing. In TypeScript, void represents the absence of a return value, while undefined is a value that can be returned.

Apply this change:

-export type CbRes<TRes> = (res: any) => void | TRes;
+export type CbRes<TRes> = (res: any) => undefined | TRes;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

export type CbRes<TRes> = (res: any) => undefined | TRes;
🧰 Tools
🪛 Biome (1.9.4)

[error] 48-48: void is confusing inside a union type.

Unsafe fix: Use undefined instead.

(lint/suspicious/noConfusingVoidType)

packages/@webex/plugin-cc/src/services/WebSocket/config.ts (1)

25-26: ⚠️ Potential issue

Fix incorrect JSDoc type annotation

The type annotation @type {[type]} is incorrect and should be @type {number} to match the actual type of the configuration value.

-   * @type {[type]}
+   * @type {number}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

   * @type {number}
   */
packages/@webex/plugin-cc/README.md (1)

22-26: 🛠️ Refactor suggestion

Update the repository URL in clone instructions

The git clone URL contains a placeholder <your-fork> which might confuse users.

-git clone https://github.com/\<your-fork\>/webex-js-sdk.git
+git clone https://github.com/webex/webex-js-sdk.git
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```bash
git clone https://github.com/webex/webex-js-sdk.git
cd webex-js-sdk/
yarn install
```
packages/@webex/plugin-cc/src/services/Task/index.ts (2)

45-45: ⚠️ Potential issue

Fix method name typo

The method name 'declinecall' violates camelCase convention and contains a typo.

-      this.webCallingService.declinecall(taskId);
+      this.webCallingService.declineCall(taskId);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

      this.webCallingService.declineCall(taskId);

23-35: ⚠️ Potential issue

Add cleanup for microphone stream on error

The microphone stream should be cleaned up if the call fails to prevent resource leaks.

   public async accept(loginOption: LoginOption, taskId: string): Promise<void> {
     try {
       if (loginOption === LoginOption.BROWSER) {
         this.localAudioStream = await createMicrophoneStream({audio: true});
-        this.webCallingService.answerCall(this.localAudioStream, taskId);
+        try {
+          await this.webCallingService.answerCall(this.localAudioStream, taskId);
+        } catch (error) {
+          await this.localAudioStream.stop();
+          throw error;
+        }
       } else {
         // TODO: Invoke the accept API from services layer. This is going to be used in Outbound Dialer scenario
         this.services.contact.accept({interactionId: taskId});
       }
     } catch (error) {
       throw getErrorDetails(error, 'accept');
     }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  public async accept(loginOption: LoginOption, taskId: string): Promise<void> {
    try {
      if (loginOption === LoginOption.BROWSER) {
        this.localAudioStream = await createMicrophoneStream({audio: true});
        try {
          await this.webCallingService.answerCall(this.localAudioStream, taskId);
        } catch (error) {
          await this.localAudioStream.stop();
          throw error;
        }
      } else {
        // TODO: Invoke the accept API from services layer. This is going to be used in Outbound Dialer scenario
        this.services.contact.accept({interactionId: taskId});
      }
    } catch (error) {
      throw getErrorDetails(error, 'accept');
    }
  }
packages/@webex/plugin-cc/src/services/config/constants.ts (1)

16-46: 💡 Codebase verification

⚠️ Potential issue

Based on the inspection of the code and search results, I can now provide the final response:

URL parameters need proper encoding to prevent injection vulnerabilities

The endpoint mapping in constants.ts has several query parameters that are directly interpolated into URLs without proper encoding:

  • filter array is joined without encoding
  • attributes array is joined without encoding
  • page and pageSize numeric parameters lack type validation
  1. Create a dedicated URL builder utility:
export class URLBuilder {
  private params = new URLSearchParams();
  
  constructor(private baseUrl: string) {}

  addParam(key: string, value: string | number | string[]) {
    if (Array.isArray(value)) {
      this.params.append(key, value.map(v => encodeURIComponent(v)).join(','));
    } else {
      this.params.append(key, encodeURIComponent(String(value)));
    }
    return this;
  }

  build() {
    const queryString = this.params.toString();
    return `${this.baseUrl}${queryString ? '?' + queryString : ''}`;
  }
}

// Usage example:
export const endPointMap = {
  listTeams: (orgId: string, page: number, pageSize: number, filter: string[], attributes: string[]) => {
    const builder = new URLBuilder(`organization/${encodeURIComponent(orgId)}/v2/team`);
    builder.addParam('page', page)
           .addParam('pageSize', pageSize)
           .addParam('attributes', attributes);
    
    if (filter?.length > 0) {
      builder.addParam('filter', `id=in=${filter}`);
    }
    
    return builder.build();
  },
  // ... other endpoints
};

This change:

  1. Properly encodes all URL parameters
  2. Provides a consistent way to build URLs
  3. Makes the code more maintainable and secure
  4. Prevents URL injection vulnerabilities
🔗 Analysis chain

Address potential security issues and inconsistencies in endpoint mapping.

Several concerns need to be addressed:

  1. URL parameters should be properly encoded to prevent URL injection
  2. API versioning is inconsistent (some endpoints use v2, others don't)
  3. No parameter validation before URL construction

Consider implementing these improvements:

import {encodeURIComponent} from 'querystring';

export const endPointMap = {
  // ... other endpoints ...
  listTeams: (
    orgId: string,
    page: number,
    pageSize: number,
    filter: string[],
    attributes: string[]
  ) => {
    if (!orgId) throw new Error('orgId is required');
    
    const params = new URLSearchParams({
      page: String(page),
      pageSize: String(pageSize),
      attributes: attributes.join(',')
    });
    
    if (filter?.length > 0) {
      params.append('filter', `id=in=${filter.map(f => encodeURIComponent(f)).join(',')}`);
    }
    
    return `organization/${encodeURIComponent(orgId)}/v2/team?${params.toString()}`;
  },
  // Apply similar changes to other endpoints
};

Consider:

  1. Creating a dedicated URL builder utility
  2. Standardizing API versions across endpoints
  3. Adding TypeScript interfaces for endpoint parameters
  4. Adding input validation for all parameters
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for consistent API versioning
rg -t typescript "organization/\$\{.*?\}/v\d" --no-filename

# Look for potential URL encoding usage
rg -t typescript "encodeURIComponent|URLSearchParams" --no-filename

Length of output: 200


Script:

#!/bin/bash
# Check for consistent API versioning without file type filter
rg "organization/\${.*?}/v\d"

# Look for potential URL encoding usage without file type filter
rg "encodeURIComponent|URLSearchParams"

# Find all TypeScript/JavaScript files that might contain endpoint definitions
fd "\.(ts|js)$" | grep -i "endpoint\|api\|constant"

Length of output: 132201


Script:

#!/bin/bash
# Search for URL encoding usage in TypeScript/JavaScript files
rg "encodeURIComponent|URLSearchParams" --type-add 'ts:*.ts' --type-add 'js:*.js' -t ts -t js

# Search for API version patterns in endpoint definitions
rg "organization/\${.*?}/v\d" --type-add 'ts:*.ts' --type-add 'js:*.js' -t ts -t js

# Find all files that might contain endpoint definitions
fd -e ts -e js | grep -i "endpoint\|api\|constant"

Length of output: 132308


Script:

#!/bin/bash
# Search for API version patterns with fixed regex
rg "organization/\$\{.*?\}/v[0-9]" --type-add 'ts:*.ts' --type-add 'js:*.js' -t ts -t js

# Search for URL encoding usage in the specific file
rg "encodeURIComponent|URLSearchParams" packages/@webex/plugin-cc/src/services/config/constants.ts

# Find all files that might contain endpoint definitions
fd -e ts -e js | grep -i "endpoint\|api\|constant"

Length of output: 3104


Script:

#!/bin/bash
# Check the content of the specific constants file
cat packages/@webex/plugin-cc/src/services/config/constants.ts

# Look for similar endpoint mapping patterns in other files
rg "export const.*?Map.*?=.*?\{.*?organization/\$\{.*?\}" --type-add 'ts:*.ts' --type-add 'js:*.js' -t ts -t js -A 10

# Check for URL parameter encoding in other similar endpoint files
rg "encodeURIComponent|URLSearchParams" --type-add 'ts:*.ts' --type-add 'js:*.js' -t ts -t js packages/@webex/plugin-cc/src/

Length of output: 2146

packages/@webex/plugin-cc/test/unit/spec/services/core/HttpRequest.ts (2)

20-25: 🛠️ Refactor suggestion

Add test cases for singleton pattern and initialization

The test suite should verify the singleton behavior and handle invalid initialization scenarios.

Consider adding these test cases:

describe('getInstance', () => {
  it('should return the same instance when called multiple times', () => {
    const instance1 = HttpRequest.getInstance({webex: mockWebex});
    const instance2 = HttpRequest.getInstance({webex: mockWebex});
    expect(instance1).toBe(instance2);
  });

  it('should throw error when initialized without webex', () => {
    expect(() => HttpRequest.getInstance({})).toThrow();
  });
});

53-65: 🛠️ Refactor suggestion

Enhance error handling test coverage

The current error test could be more comprehensive. Consider testing:

  • Different error types (network errors, timeout, etc.)
  • Verify error logging behavior
  • HTTP error status codes (4xx, 5xx)
it('should properly log and handle HTTP errors', async () => {
  const mockHttpError = {
    statusCode: 404,
    body: { error: 'Not Found' },
    message: 'HTTP Error'
  };
  mockRequest.mockRejectedValueOnce(mockHttpError);

  await expect(
    httpRequest.request({
      service: 'service',
      resource: 'resource',
      method: HTTP_METHODS.GET
    })
  ).rejects.toThrow('HTTP Error');

  expect(mockWebex.logger.error).toHaveBeenCalledWith(
    expect.stringContaining('404'),
    mockHttpError
  );
});
packages/@webex/plugin-cc/test/unit/spec/services/agent/index.ts (4)

41-46: 🛠️ Refactor suggestion

Improve test data types and payload validation

The tests use 'as any' type assertions and lack validation of the actual request payloads. Consider:

  1. Using proper types instead of 'as any'
  2. Validating the actual request payloads
  3. Testing with different input scenarios

Example improvement for stationLogin (apply similar pattern to other tests):

interface StationLoginData {
  stationId: string;
  extension?: string;
}

it('stationLogin', async () => {
  const reqSpy = jest.spyOn(fakeAqm, 'req');
  const testData: StationLoginData = {
    stationId: 'STATION123',
    extension: '1234'
  };
  
  const req = await agent.stationLogin({data: testData});
  
  expect(req).toBeDefined();
  expect(reqSpy).toHaveBeenCalledWith(
    expect.any(Function)
  );
  // Validate the actual request payload
  const requestFn = reqSpy.mock.calls[0][0];
  const requestPayload = requestFn();
  expect(requestPayload).toEqual(
    expect.objectContaining({
      stationId: testData.stationId,
      extension: testData.extension
    })
  );
});

Also applies to: 48-53, 54-59


19-23: 🛠️ Refactor suggestion

Enhance type safety in mock implementations

The current mock setup could be improved with proper typing to catch potential type-related issues early:

-    fakeAqm = new AqmReqs() as jest.Mocked<AqmReqs>;
-    fakeAqm.reqEmpty = jest.fn().mockImplementation((fn) => fn);
-    fakeAqm.req = jest.fn().mockImplementation((fn) => fn);
+    fakeAqm = new AqmReqs() as jest.Mocked<AqmReqs>;
+    fakeAqm.reqEmpty = jest.fn().mockImplementation(<T>(fn: () => T) => fn());
+    fakeAqm.req = jest.fn().mockImplementation(<T>(fn: () => T) => fn());
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    fakeAqm = new AqmReqs() as jest.Mocked<AqmReqs>;
    fakeAqm.reqEmpty = jest.fn().mockImplementation(<T>(fn: () => T) => fn());
    fakeAqm.req = jest.fn().mockImplementation(<T>(fn: () => T) => fn());

    agent = routingAgent(fakeAqm);

26-32: 🛠️ Refactor suggestion

Enhance error handling test coverage

While the logout test includes an error scenario, it doesn't properly assert the error handling behavior. Also, other methods should have similar error scenario coverage.

   it('logout', async () => {
     const reqSpy = jest.spyOn(fakeAqm, 'reqEmpty');
-    reqSpy.mockRejectedValue(new Error('dasd'));
+    const errorMessage = 'Logout failed';
+    reqSpy.mockRejectedValue(new Error(errorMessage));
-    const req = await agent.logout({data: {logoutReason: 'User requested logout'}});
-    expect(req).toBeDefined();
-    expect(reqSpy).toHaveBeenCalled();
+    await expect(
+      agent.logout({data: {logoutReason: 'User requested logout'}})
+    ).rejects.toThrow(errorMessage);
+    expect(reqSpy).toHaveBeenCalledWith(
+      expect.any(Function)
+    );
   });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  it('logout', async () => {
    const reqSpy = jest.spyOn(fakeAqm, 'reqEmpty');
    const errorMessage = 'Logout failed';
    reqSpy.mockRejectedValue(new Error(errorMessage));
    await expect(
      agent.logout({data: {logoutReason: 'User requested logout'}})
    ).rejects.toThrow(errorMessage);
    expect(reqSpy).toHaveBeenCalledWith(
      expect.any(Function)
    );
  });

12-60: 💡 Codebase verification

⚠️ Potential issue

Review comment is valid - test coverage needed for accept functionality

The codebase shows clear implementation of accept functionality in services/Task/index.ts and services/Task/contact.ts, including:

  • Task accept method in the Task service
  • Accept API endpoint handling in contact service
  • Error handling for accept operations

However, the current test file lacks coverage for these implemented features. Please add test cases for:

  1. Task accept success via services.contact.accept
  2. Error handling scenario using getErrorDetails
  3. Accept API payload validation with interactionId

Locations needing test coverage:

  • packages/@webex/plugin-cc/src/services/Task/index.ts:accept()
  • packages/@webex/plugin-cc/src/services/Task/contact.ts:accept
🔗 Analysis chain

Add tests for call accept flow

According to the PR objectives, this change implements the call accept flow. However, there are no tests covering this functionality. Please add test cases for:

  1. Call accept success scenario
  2. Call accept failure scenarios
  3. Validation of accept-related payloads
  4. Edge cases in the accept flow
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for call accept implementation to ensure test coverage matches
rg -t typescript "accept" packages/@webex/plugin-cc/src/

Length of output: 91


Script:

#!/bin/bash
# Search for call accept implementation with correct file extension
rg "accept" packages/@webex/plugin-cc/src/

# Also search for any routing agent implementation files
fd "routing.*agent" packages/@webex/plugin-cc/src/

# Look for any contact or call related files
fd "contact|call" packages/@webex/plugin-cc/src/

Length of output: 1401

packages/@webex/plugin-cc/src/services/core/WebSocket/keepalive.worker.js (2)

25-49: ⚠️ Potential issue

Critical: Fix potential race conditions and timeout inconsistency

The checkNetworkStatus function has several issues:

  1. Race condition: initialised and isSocketClosed flags are modified from multiple places without proper synchronization
  2. Inconsistency: Comment mentions 16s timeout but code uses closeSocketTimeout which defaults to 5s
  3. State management: Multiple state flags make the logic hard to follow

Consider refactoring to:

 const checkNetworkStatus = function () {
   const onlineStatus = checkOnlineStatus();
   postMessage({ type: "keepalive", onlineStatus });
-  if (!onlineStatus && !initialised) {
+  if (!onlineStatus && !timeOutId) {  // Use timeoutId as state indicator
     initialised = true;
-    // Sets a timeout of 16s, checks if socket didn't close then it closes forcefully
+    // Sets a timeout to force close the socket if it remains open while offline
     timeOutId = setTimeout(() => {
       if (!isSocketClosed) {
-        initiateWebSocketClosure = true;
         postMessage({ type: "closeSocket" });
       }
+      timeOutId = null;
+      initialised = false;
     }, closeSocketTimeout);
   }

-  if (onlineStatus && initialised) {
+  if (onlineStatus && timeOutId) {
     initialised = false;
+    clearTimeout(timeOutId);
+    timeOutId = null;
   }
-
-  if (initiateWebSocketClosure) {
-    initiateWebSocketClosure = false;
-    clearTimeout(timeOutId);
-    timeOutId = null;
-  }
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

// Checks network status and if it's offline then force closes WebSocket
const checkNetworkStatus = function () {
  const onlineStatus = checkOnlineStatus();
  postMessage({ type: "keepalive", onlineStatus });
  if (!onlineStatus && !timeOutId) {  // Use timeoutId as state indicator
    initialised = true;
    // Sets a timeout to force close the socket if it remains open while offline
    timeOutId = setTimeout(() => {
      if (!isSocketClosed) {
        postMessage({ type: "closeSocket" });
      }
      timeOutId = null;
      initialised = false;
    }, closeSocketTimeout);
  }

  if (onlineStatus && timeOutId) {
    initialised = false;
    clearTimeout(timeOutId);
    timeOutId = null;
  }
};

51-74: 🛠️ Refactor suggestion

Improve message handler robustness and configuration

The message event handler could be improved in several areas:

  1. Error handling for invalid messages
  2. Documentation for configuration values
  3. Type checking for event data

Consider this improvement:

+const DEFAULT_INTERVAL = 4000;  // 4 seconds
+const DEFAULT_TIMEOUT = 5000;   // 5 seconds
+
 addEventListener("message", (event) => {
+  if (!event.data) {
+    console.error('Invalid message received');
+    return;
+  }
+
   if (event.data?.type === "start") {
-    intervalDuration = event.data?.intervalDuration || 4000;
-    closeSocketTimeout = event.data?.closeSocketTimeout || 5000;
+    intervalDuration = event.data.intervalDuration || DEFAULT_INTERVAL;
+    closeSocketTimeout = event.data.closeSocketTimeout || DEFAULT_TIMEOUT;
+
+    if (intervalId) {
+      console.warn('Worker already running, clearing previous interval');
+      clearInterval(intervalId);
+    }
+
     console.log("event=Websocket startWorker | keepalive Worker started");
     intervalId = setInterval(
       (checkIfSocketClosed) => {
         checkNetworkStatus();
         isSocketClosed = checkIfSocketClosed;
       },
       intervalDuration,
-      event.data?.isSocketClosed
+      !!event.data.isSocketClosed
     );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

const DEFAULT_INTERVAL = 4000;  // 4 seconds
const DEFAULT_TIMEOUT = 5000;   // 5 seconds

addEventListener("message", (event) => {
  if (!event.data) {
    console.error('Invalid message received');
    return;
  }

  if (event.data?.type === "start") {
    intervalDuration = event.data.intervalDuration || DEFAULT_INTERVAL;
    closeSocketTimeout = event.data.closeSocketTimeout || DEFAULT_TIMEOUT;

    if (intervalId) {
      console.warn('Worker already running, clearing previous interval');
      clearInterval(intervalId);
    }

    console.log("event=Websocket startWorker | keepalive Worker started");
    intervalId = setInterval(
      (checkIfSocketClosed) => {
        checkNetworkStatus();
        isSocketClosed = checkIfSocketClosed;
      },
      intervalDuration,
      !!event.data.isSocketClosed
    );

    resetOfflineHandler();
  }

  if (event.data?.type === "terminate" && intervalId) {
    console.log("event=Websocket terminateWorker | keepalive Worker stopped");
    clearInterval(intervalId);
    intervalId = null;
    resetOfflineHandler();
  }
});
packages/@webex/plugin-cc/src/services/agent/types.ts (3)

16-41: 🛠️ Refactor suggestion

Enhance type safety and consider PII implications.

A few suggestions for improvement:

  1. channelsMap uses a generic Record<string, string[]>. Consider defining specific channel types.
  2. Fields like dialNumber and deviceId contain PII. Ensure these are properly handled in logging and storage.
-  channelsMap: Record<string, string[]>;
+  /** Map of channel types to their capabilities */
+  channelsMap: Record<ChannelType, ChannelCapability[]>;

+  /** @sensitive Phone number for dialing */
   dialNumber?: string;
+  /** @sensitive Unique identifier of the device */
   deviceId?: string | null;

Also, consider adding these types:

export type ChannelType = 'voice' | 'chat' | 'email' | 'social';
export type ChannelCapability = 'inbound' | 'outbound' | 'transfer' | 'conference';

61-81: 🛠️ Refactor suggestion

Reduce duplication through type composition.

The StationLoginSuccess type shares many fields with ReloginSuccess. Consider extracting common fields into a base type.

type BaseLoginSuccess = Msg<{
  eventType: 'AgentDesktopMessage';
  agentId: string;
  trackingId: string;
  auxCodeId: string;
  teamId: string;
  agentSessionId: string;
  // ... other common fields
}>;

export type StationLoginSuccess = BaseLoginSuccess & {
  supervisorSessionId?: string;
  type: 'AgentStationLoginSuccess';
};

export type ReloginSuccess = BaseLoginSuccess & {
  isExtension: boolean;
  type: 'AgentReloginSuccess';
};

94-108: 🛠️ Refactor suggestion

Add validation constraints and improve null handling.

The type could benefit from:

  1. Adding validation constraints through branded types
  2. Better handling of nullable fields
  3. Documentation for security-sensitive fields
// Add these type definitions
type PhoneNumber = string & { readonly brand: unique symbol };
type TeamId = string & { readonly brand: unique symbol };

export type UserStationLogin = {
  /** @sensitive Phone number for dialing */
  dialNumber?: PhoneNumber | null;
  /** @sensitive Extension number */
  dn?: PhoneNumber | null;
  /** Team identifier */
  teamId: TeamId | null;
  // ... rest of the fields
};

// Add validation functions
function isValidPhoneNumber(value: string): value is PhoneNumber {
  return /^\+?[\d-]{10,}$/.test(value);
}
packages/@webex/plugin-cc/src/services/agent/index.ts (2)

61-66: 🛠️ Refactor suggestion

Standardize error handling across endpoints

The stationLogin endpoint implements a more detailed error handler that captures status, error type, and tracking ID. Consider applying this enhanced error handling pattern to other endpoints for consistency and better debugging capabilities.

Example implementation for other endpoints:

err: (e: any) =>
  new Err.Details('Service.aqm.agent.[endpoint]', {
    status: e.response?.status ?? 0,
    type: e.response?.data?.errorType,
    trackingId: e.response?.headers?.trackingid?.split('_')[1],
  })

27-27: 🛠️ Refactor suggestion

Improve type safety for success messages

The current implementation uses empty objects with type assertions, which could hide potential runtime issues. Consider creating proper message structures:

// Instead of:
msg: {} as Agent.ReloginSuccess,

// Consider:
msg: {
  success: true,
  // ... other required properties based on Agent.ReloginSuccess type
} satisfies Agent.ReloginSuccess,

This change would:

  1. Make the code more type-safe
  2. Ensure required properties are properly initialized
  3. Prevent potential runtime issues

Also applies to: 47-47, 72-72, 93-93, 114-114

packages/@webex/plugin-cc/test/unit/spec/services/WebCallingService.ts (3)

90-132: 🛠️ Refactor suggestion

Add error handling test cases and improve incoming call test.

The test suite is missing coverage for error scenarios during registration. Additionally, the incoming call test could be improved:

  1. No verification that the call object is properly handled
  2. Potential race condition between registration and incoming call handling

Consider adding these test cases:

it('should handle registration errors', async () => {
  line = callingClient.getLines().line1 as ILine;
  const error = new Error('Registration failed');
  
  jest.spyOn(line, 'register').mockRejectedValue(error);
  
  await expect(webRTCCalling.registerWebCallingLine())
    .rejects.toThrow('Registration failed');
});

// Improve incoming call test
it('should handle incoming calls after registration', async () => {
  // First ensure registration is complete
  const registrationPromise = webRTCCalling.registerWebCallingLine();
  
  // Trigger registration success
  line.on.mock.calls
    .find((call) => call[0] === LINE_EVENTS.REGISTERED)[1]({
      mobiusDeviceId: 'device123',
      status: 'registered'
    });
    
  await registrationPromise;
  
  // Now test incoming call handling
  const callObj = {
    id: 'call123',
    on: jest.fn()
  } as unknown as ICall;
  
  // Trigger incoming call
  line.on.mock.calls
    .find((call) => call[0] === LINE_EVENTS.INCOMING_CALL)[1](callObj);
    
  // Verify call handling
  expect(callObj.on).toHaveBeenCalled();
  // Add more specific assertions about call handling
});

135-147: 🛠️ Refactor suggestion

Enhance deregistration test coverage.

The current deregistration test is basic and could be improved in several ways:

  1. Missing error handling test cases
  2. Using direct property access ['line'] is not ideal
  3. No verification of cleanup after deregistration

Consider adding these test cases:

describe('deregisterWebCallingLine', () => {
  beforeEach(() => {
    line = callingClient.getLines().line1 as ILine;
    // Use a proper method to set the line if available
    webRTCCalling['line'] = line;
  });

  it('should deregister the web calling line', async () => {
    const deregisterSpy = jest.spyOn(line, 'deregister');
    await webRTCCalling.deregisterWebCallingLine();
    expect(deregisterSpy).toHaveBeenCalled();
    // Verify line is cleaned up
    expect(webRTCCalling['line']).toBeNull();
  });

  it('should handle deregistration errors', async () => {
    const error = new Error('Deregistration failed');
    jest.spyOn(line, 'deregister').mockRejectedValue(error);
    
    await expect(webRTCCalling.deregisterWebCallingLine())
      .rejects.toThrow('Deregistration failed');
  });

  it('should handle case when line is not registered', async () => {
    webRTCCalling['line'] = null;
    await expect(webRTCCalling.deregisterWebCallingLine())
      .resolves.toBeUndefined();
  });
});

1-147: 💡 Codebase verification

Missing test coverage for call accept/answer functionality

The implementation shows two distinct call acceptance paths that need test coverage:

  1. WebCallingService's answerCall method for WebRTC calls
  2. Task service's call acceptance through contact.accept for outbound scenarios

Neither of these functionalities are covered in the current test suite. Consider adding:

  • Tests for WebCallingService's answerCall method, verifying both successful call answer and error scenarios
  • Tests for Task service's call acceptance flow, including the outbound dialer scenario
🔗 Analysis chain

Verify coverage for call accept flow.

While the tests cover registration and incoming calls, there seems to be missing coverage for the call accept flow mentioned in the PR objectives.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for call accept related tests and implementations
echo "Searching for call accept related code..."
rg -l "acceptCall|answerCall" --type ts

echo "Searching for call accept related test files..."
rg -l "test.*accept.*call|test.*answer.*call" --type ts

echo "Checking for any TODO comments related to call acceptance..."
rg -i "todo.*accept|todo.*answer" --type ts

Length of output: 753


Script:

#!/bin/bash
# Let's examine the implementation of call accept in WebCallingService and Task
echo "WebCallingService implementation:"
rg -A 10 "acceptCall|answerCall" packages/@webex/plugin-cc/src/services/WebCallingService.ts

echo -e "\nTask service implementation:"
rg -A 10 "acceptCall|answerCall" packages/@webex/plugin-cc/src/services/Task/index.ts

echo -e "\nChecking test coverage in Task service tests:"
fd "Task.*spec.*ts" packages/@webex/plugin-cc/test/

Length of output: 1252


Script:

#!/bin/bash
# Let's check Task service test implementation
echo "Task service test content:"
rg -A 10 "test.*accept.*call|test.*answer.*call|describe.*accept" packages/@webex/plugin-cc/test/unit/spec/services/Task/

# Let's also check WebCallingService test implementation for answer call
echo -e "\nWebCallingService test content for answer call:"
rg -A 10 "test.*answer.*call|describe.*answer" packages/@webex/plugin-cc/test/unit/spec/services/WebCallingService.ts

Length of output: 513

docs/samples/contact-center/index.html (2)

6-6: ⚠️ Potential issue

Fix incorrect page title

The page title indicates "Meetings Plugin" but this is actually a Contact Center sample application.

-  <title>Webex JavaScript SDK Sample: Meetings Plugin</title>
+  <title>Webex JavaScript SDK Sample: Contact Center</title>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  <title>Webex JavaScript SDK Sample: Contact Center</title>

88-135: 🛠️ Refactor suggestion

Improve accessibility and user experience

The Agent Station section needs accessibility and UX enhancements:

  1. Add ARIA labels for screen readers
  2. Include form validation feedback
  3. Add loading states for async operations
-                  <select id="teamsDropdown" class="form-control w-auto my-3">Teams</select>
+                  <select id="teamsDropdown" class="form-control w-auto my-3" 
+                          aria-label="Select team" 
+                          aria-describedby="teamsHelp">Teams</select>
+                  <div id="teamsHelp" class="form-text">Select your team to proceed with login</div>

-                  <input id="dialNumber" name="dialNumber" placeholder="Dial Number" value="" type="text">
+                  <input id="dialNumber" name="dialNumber" 
+                         placeholder="Dial Number" 
+                         pattern="[0-9]+" 
+                         aria-label="Enter dial number"
+                         required>
+                  <div class="invalid-feedback">Please enter a valid dial number</div>

-                  <button id="loginAgent" disabled class="btn btn-primary my-3" onclick="doAgentLogin()">
+                  <button id="loginAgent" disabled class="btn btn-primary my-3" onclick="doAgentLogin()">
+                    <span class="spinner-border spinner-border-sm hidden" role="status" aria-hidden="true"></span>
                     Login With Selected Team
                   </button>

Committable suggestion skipped: line range outside the PR's diff.

packages/@webex/plugin-cc/test/unit/spec/services/core/WebSocket/connection-service.ts (3)

72-81: 🛠️ Refactor suggestion

Add error scenario test for ping message parsing.

The ping message handling test should also verify behavior with invalid JSON messages.

it('should handle invalid ping message gracefully', () => {
  const invalidMessage = new CustomEvent<string>('message', {
    detail: 'invalid json',
  });
  connectionService['onPing'](invalidMessage);
  expect(LoggerProxy.logger.error).toHaveBeenCalled();
  expect(connectionService['isKeepAlive']).toBe(false);
});

127-164: 🛠️ Refactor suggestion

Add offline and timeout scenario tests.

The reconnection test suite should include tests for:

  1. Offline browser scenario
  2. Reconnection timeout handling
it('should not attempt reconnection when offline', async () => {
  Object.defineProperty(global, 'navigator', {
    value: { onLine: false },
    configurable: true,
  });
  
  await connectionService['handleSocketClose']();
  expect(mockWebSocketManager.initWebSocket).not.toHaveBeenCalled();
});

it('should handle reconnection timeout', async () => {
  connectionService['connectionProp'].lostConnectionRecoveryTimeout = 1000;
  connectionService['onSocketClose']();
  
  jest.advanceTimersByTime(1000);
  
  expect(connectionService['isRestoreFailed']).toBe(true);
});

32-61: 🛠️ Refactor suggestion

Add cleanup for navigator mock in afterEach.

The test setup is good, but we should clean up any global mocks in the afterEach block to prevent test pollution.

 afterEach(() => {
   jest.useRealTimers();
   jest.clearAllTimers();
+  // Reset navigator mock if it was modified
+  Object.defineProperty(global, 'navigator', {
+    value: undefined,
+    configurable: true,
+  });
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

describe('ConnectionService', () => {
  let connectionService: ConnectionService;
  let mockWebSocketManager: jest.Mocked<WebSocketManager>;
  const mockSubscribeRequest: SubscribeRequest = {
    force: true,
    isKeepAliveEnabled: false,
    clientType: 'WebexCCSDK',
    allowMultiLogin: true,
  };

  beforeEach(() => {
    jest.clearAllMocks();
    jest.useFakeTimers();

    mockWebSocketManager = new WebSocketManager({
      webex: {} as any,
    }) as jest.Mocked<WebSocketManager>;
    mockWebSocketManager.initWebSocket = jest.fn().mockResolvedValue({});

    // Mock the addEventListener method
    mockWebSocketManager.addEventListener = jest.fn();

    connectionService = new ConnectionService(mockWebSocketManager, mockSubscribeRequest);
  });

  afterEach(() => {
    jest.useRealTimers();
    jest.clearAllTimers();
    // Reset navigator mock if it was modified
    Object.defineProperty(global, 'navigator', {
      value: undefined,
      configurable: true,
    });
  });
docs/samples/contact-center/style.css (3)

92-96: ⚠️ Potential issue

Remove commented code and consolidate duplicate container styles

There's commented out code in the join-meeting-btn-grid class and a duplicate container class definition.

 .join-meeting-btn-grid {
   display: grid;
   grid-template-columns: 1fr 1fr 1fr;
-  /* border: 10px solid black */
 } 

-/* Container - Docs / Streams Fixed */
-.container {
-  display: flex;
-  width: 100%;
-  gap: 1rem;
-  flex-direction: row;
-}

Update the original container class to include the gap property:

 .container {
   display: flex;
   width: 100%;
   flex-direction: row;
   justify-content: space-between;
   margin-bottom: 1rem;
+  gap: 1rem;
 }

Also applies to: 333-339


12-39: 🛠️ Refactor suggestion

Consolidate duplicate button styles and use CSS variables consistently

There are duplicate opacity rules for disabled buttons, and the color values could benefit from using CSS variables.

 button.btn--green {
   color: #fff;
   color: var(--md-button-join-text-color, #fff);
-  background-color: #00ab50;
+  background-color: var(--color-success);
-  background-color: var(--md-button-join-bg-color, #00ab50);
   border-color: transparent;
 }

-button.btn--green:disabled {
-  opacity: 0.5;
-}

 button.btn--red {
   color: #fff;
   color: var(--md-button-cancel-text-color, #fff);
-  background-color: #f7644a;
+  background-color: var(--color-danger);
-  background-color: var(--md-button-cancel-bg-color, #f7644a);
   border-color: transparent;
 }

 .btn--red:disabled,
 .btn--green:disabled {
   opacity: 0.5;
 }

Committable suggestion skipped: line range outside the PR's diff.


1-448: 💡 Codebase verification

Add focus styles and improve color contrast ratios

The verification confirms accessibility concerns in the codebase:

  1. Multiple interactive elements with cursor: pointer lack focus styles:

    • .collapsible (line 81)
    • .clickable (line 193)
    • .global-toggle-btn needs focus styles beyond just box-shadow
  2. Several color combinations need contrast verification:

    • Text color #555 on light backgrounds
    • Warning text #7d4705
    • Error text #de3434
    • Link colors #0052bf and #1a73e8
    • Button text colors on various backgrounds

Recommended fixes:

/* Add to style.css */
.collapsible:focus,
.clickable:focus,
.global-toggle-btn:focus {
  outline: 2px solid #0052bf;
  outline-offset: 2px;
}

/* Update colors for better contrast */
.note {
  color: #444; /* Darker than #555 */
}

.webex-warning {
  color: #5c3504; /* Darker than #7d4705 */
}

.webex-error {
  color: #c41e1e; /* Darker than #de3434 */
}
🔗 Analysis chain

Enhance accessibility compliance

Consider the following accessibility improvements:

  1. Add visible focus indicators for interactive elements
  2. Verify color contrast ratios meet WCAG guidelines, especially for:
    • Text using #555 on light backgrounds
    • Warning text using #7d4705
    • Error text using #de3434
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for potential accessibility issues in the codebase

# Search for interactive elements that might need focus styles
echo "Interactive elements without focus styles:"
rg -n "cursor:\s*pointer" --type css

# Search for potentially problematic color values
echo "\nColor values to verify for contrast:"
rg -n "color:\s*#[0-9a-f]{3,6}" --type css

Length of output: 6084

packages/@webex/plugin-cc/test/unit/spec/services/core/WebSocket/WebSocketManager.ts (2)

103-105: 🛠️ Refactor suggestion

Restore console mocks after tests

Console methods are mocked but not restored. Add an afterEach block to restore them.

+  afterEach(() => {
+    (console.log as jest.Mock).mockRestore();
+    (console.error as jest.Mock).mockRestore();
+  });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    console.log = jest.fn();
    console.error = jest.fn();
  });

  afterEach(() => {
    (console.log as jest.Mock).mockRestore();
    (console.error as jest.Mock).mockRestore();
  });

98-102: 🛠️ Refactor suggestion

Replace setTimeout with Jest timer mocks

Using real timeouts in tests can lead to flaky tests. Consider using Jest's timer mocks for more reliable test execution.

-    setTimeout(() => {
+    jest.useFakeTimers();
+    process.nextTick(() => {
       MockWebSocket.inst.onopen();
       MockWebSocket.inst.onmessage({ data: JSON.stringify({ type: "Welcome" }) });
-    }, 1);
+    });
+    jest.runAllTimers();

Committable suggestion skipped: line range outside the PR's diff.

.github/workflows/deploy.yml (1)

8-8: 💡 Codebase verification

Branch naming convention is compliant, but documentation skip needs attention

The wxcc branch follows the required naming convention as it doesn't contain any / characters. However, skipping documentation generation for this branch (line 212: if: github.ref != 'refs/heads/wxcc') is concerning as it may lead to documentation gaps. Documentation should be maintained for all deployable branches to ensure proper API documentation and changelog updates.

  • Line 212: Remove the condition if: github.ref != 'refs/heads/wxcc' to ensure documentation is generated for all deployable branches
  • Line 8: The wxcc branch name is compliant with the stated requirement
🔗 Analysis chain

Verify branch naming convention and deployment implications.

The addition of wxcc branch to deployable branches needs verification:

  1. Does it follow the comment's requirement that "all white-listed branches cannot include any / characters"? ✓
  2. Will this branch be used for long-term development or is it temporary?
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if there are any existing branches with similar naming pattern
git branch -r | grep -i "wxcc"

# Check recent commits to understand branch usage
git log --oneline --grep="wxcc" --all

Length of output: 30


Script:

#!/bin/bash
# Let's check the workflow file content to understand the branch configuration
cat .github/workflows/deploy.yml

# Also check if there are any other workflow files that might have branch configurations
fd -e yml -e yaml . .github/workflows/

Length of output: 12752

packages/@webex/internal-plugin-mercury/test/unit/spec/socket.js (1)

203-243: ⚠️ Potential issue

Fix syntax and logical errors in authorization tests.

The test cases for authorizationRequired have several issues:

  1. Incorrect test structure: The promise chain is missing
  2. Mixing socket instances: Using both s and socket variables
  3. Missing assertions for the authorization behavior

Apply this diff to fix the issues:

-      it('accepts authorizationRequired option as false and skip authorize', () => {
-        const s = new Socket();
-        const authorizeSpy = sinon.spy(socket, '_authorize');
-        socket.open('ws://example.com', {
+      it('accepts authorizationRequired option as false and skip authorize', () => {
+        const s = new Socket();
+        const authorizeSpy = sinon.spy(s, '_authorize');
+        const promise = s.open('ws://example.com', {
           forceCloseDelay: mockoptions.forceCloseDelay,
           pingInterval: mockoptions.pingInterval,
           pongTimeout: mockoptions.pongTimeout,
           logger: console,
           token: 'mocktoken',
           trackingId: 'mocktrackingid',
           logLevelToken: 'mocklogleveltoken',
           authorizationRequired: false,
         });
-
-          mockWebSocket.readyState = 1;
-          mockWebSocket.emit('open');
-
-          assert.notCalled(authorizeSpy);
-          assert.called(socket._ping);
+        
+        mockWebSocket.readyState = 1;
+        mockWebSocket.emit('open');
+        
+        return promise.then(() => {
+          assert.notCalled(authorizeSpy);
+          assert.called(s._ping);
+          return s.close();
         });
+      });

-      it('accepts authorizationRequired option as true and calles authorize', () => {
-        const s = new Socket();
-        const authorizeSpy = sinon.spy(socket, '_authorize');
-        socket.open('ws://example.com', {
+      it('accepts authorizationRequired option as true and calls authorize', () => {
+        const s = new Socket();
+        const authorizeSpy = sinon.spy(s, '_authorize');
+        const promise = s.open('ws://example.com', {
           forceCloseDelay: mockoptions.forceCloseDelay,
           pingInterval: mockoptions.pingInterval,
           pongTimeout: mockoptions.pongTimeout,
           logger: console,
           token: 'mocktoken',
           trackingId: 'mocktrackingid',
           logLevelToken: 'mocklogleveltoken',
           authorizationRequired: true,
         });
-
-          mockWebSocket.readyState = 1;
-          mockWebSocket.emit('open');
-
-          assert.called(authorizeSpy);
-          assert.called(socket._ping);
+        
+        mockWebSocket.readyState = 1;
+        mockWebSocket.emit('open');
+        
+        return promise.then(() => {
+          assert.called(authorizeSpy);
+          assert.called(s._ping);
+          return s.close();
+        });
       });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

      it('accepts authorizationRequired option as false and skip authorize', () => {
        const s = new Socket();
        const authorizeSpy = sinon.spy(s, '_authorize');
        const promise = s.open('ws://example.com', {
          forceCloseDelay: mockoptions.forceCloseDelay,
          pingInterval: mockoptions.pingInterval,
          pongTimeout: mockoptions.pongTimeout,
          logger: console,
          token: 'mocktoken',
          trackingId: 'mocktrackingid',
          logLevelToken: 'mocklogleveltoken',
          authorizationRequired: false,
        });
        
        mockWebSocket.readyState = 1;
        mockWebSocket.emit('open');
        
        return promise.then(() => {
          assert.notCalled(authorizeSpy);
          assert.called(s._ping);
          return s.close();
        });
      });

      it('accepts authorizationRequired option as true and calls authorize', () => {
        const s = new Socket();
        const authorizeSpy = sinon.spy(s, '_authorize');
        const promise = s.open('ws://example.com', {
          forceCloseDelay: mockoptions.forceCloseDelay,
          pingInterval: mockoptions.pingInterval,
          pongTimeout: mockoptions.pongTimeout,
          logger: console,
          token: 'mocktoken',
          trackingId: 'mocktrackingid',
          logLevelToken: 'mocklogleveltoken',
          authorizationRequired: true,
        });
        
        mockWebSocket.readyState = 1;
        mockWebSocket.emit('open');
        
        return promise.then(() => {
          assert.called(authorizeSpy);
          assert.called(s._ping);
          return s.close();
        });
      });
packages/@webex/plugin-cc/src/services/WebSocket/index.ts (4)

6-6: ⚠️ Potential issue

Avoid naming conflicts: Rename the class 'WebSocket'

The name WebSocket is a global interface in browsers for WebSocket communication. Naming your class WebSocket could cause confusion or potential conflicts. Consider renaming the class to something more specific, such as CCWebSocket or CustomWebSocket, to avoid ambiguity.


6-6: 🛠️ Refactor suggestion

Avoid using 'any' when extending 'Mercury'

Casting Mercury as any to extend it bypasses TypeScript's type checking, which can lead to runtime errors and defeats the purpose of using TypeScript. Instead of casting to any, update the Mercury class or interface definitions to properly allow extension, or use generics to achieve the desired inheritance without losing type safety.


13-13: 🛠️ Refactor suggestion

Adjust 'webSocketUrl' type to include 'undefined'

The webSocketUrl property is initially undefined and is reset to undefined in disconnectWebSocket, but it's currently typed as string. Update its type to string | undefined to accurately reflect its possible values.

Apply this change:

- private webSocketUrl: string;
+ private webSocketUrl: string | undefined;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  private webSocketUrl: string | undefined;

31-36: ⚠️ Potential issue

Update JSDoc comments to match the method signature

The JSDoc comments for connectWebSocket do not align with the method's parameters and return type. The comments reference params.datachannelUrl and params.body, and specify a return type of Promise<void>, whereas the method accepts options.webSocketUrl and returns void. Update the JSDoc to accurately reflect the method signature.

Suggested change:

/**
-  * Subscribe and connect to the websocket
-  * @param {object} params
-  * @param {string} params.datachannelUrl
-  * @param {SubscribeRequest} params.body
-  * @returns {Promise<void>}
+  * Connects to the WebSocket using the provided URL
+  * @param {object} options
+  * @param {string} options.webSocketUrl - The WebSocket URL to connect to
+  * @returns {void}
   */
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

/**
 * Connects to the WebSocket using the provided URL
 * @param {object} options
 * @param {string} options.webSocketUrl - The WebSocket URL to connect to
 * @returns {void}
 */
packages/@webex/plugin-cc/src/services/WebCallingService.ts (3)

88-89: ⚠️ Potential issue

Correct the log message in declineCall method

In the else block of the declineCall method, the log message incorrectly states "Cannot mute a non WebRtc Call" instead of "Cannot decline a non WebRtc Call".

Apply this diff to fix the log message:

-      this.webex.logger.log(`[WebRtc]: Cannot mute a non WebRtc Call: ${taskId}`);
+      this.webex.logger.log(`[WebRtc]: Cannot decline a non WebRtc Call: ${taskId}`);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

      this.webex.logger.log(`[WebRtc]: Cannot decline a non WebRtc Call: ${taskId}`);
    }

83-90: 🛠️ Refactor suggestion

Rename declinecall to declineCall to follow naming conventions

The method declinecall should be renamed to declineCall to maintain consistency with camelCase naming conventions for method names in TypeScript.

Apply this diff to rename the method:

-  public declinecall(taskId: string) {
+  public declineCall(taskId: string) {

Also, make sure to update any references to this method elsewhere in the code.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  public declineCall(taskId: string) {
    if (this.call) {
      this.webex.logger.info(`[WebRtc]: Call end requested: ${taskId}`);
      this.call.end();
    } else {
      this.webex.logger.log(`[WebRtc]: Cannot mute a non WebRtc Call: ${taskId}`);
    }
  }

24-51: 🛠️ Refactor suggestion

Add error handling for potential exceptions in registerWebCallingLine

Currently, registerWebCallingLine does not handle exceptions that may occur during registration, such as errors from createClient or this.line.register().

Wrap the registration logic in a try-catch block to handle potential exceptions:

  public async registerWebCallingLine(): Promise<void> {
+   try {
      this.callingClient = await createClient(this.webex as any, this.callingClientConfig);
      this.line = Object.values(this.callingClient.getLines())[0];

      // Existing event listeners and promise handling...

      this.line.register();
+   } catch (error) {
+     this.webex.logger.error(`Error registering web calling line: ${error.message}`);
+     throw error;
+   }
  }

Committable suggestion skipped: line range outside the PR's diff.

packages/@webex/plugin-cc/src/services/core/Err.ts (3)

87-100: 🛠️ Refactor suggestion

Properly initialize the Error class with a message.

In the Message class constructor, the super() call should include the error message to ensure that the Error base class is properly initialized with the message. This allows for accurate error messages and stack traces.

Update the super() call to pass the error message:

-    super();
+    super(this.message);

Ensure that this.message is assigned before the super() call, or adjust the code accordingly to pass the correct message.

Alternatively, you can restructure the constructor:

constructor(id: IdsMessage, value?: string | Error) {
  const message = typeof value === 'string' ? value : value?.message || '';
  super(message);
  this.id = id;
  if (value instanceof Error) {
    this.name = value.name;
  }
}

85-85: ⚠️ Potential issue

Fix typo in parameter name 'errror' to 'error' in constructor overload.

The parameter name in the constructor overload on line 85 has a typo. It should be corrected from errror to error to maintain consistency and prevent potential confusion.

Apply this diff to fix the typo:

-  constructor(id: IdsMessage, errror: Error);
+  constructor(id: IdsMessage, error: Error);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  constructor(id: IdsMessage, error: Error);

90-91: 🛠️ Refactor suggestion

Avoid manually assigning the stack trace.

Manually reassigning the stack trace using this.stack = new Error().stack!; is unnecessary since the Error base class already captures the stack trace when you call super(message);. Removing this assignment prevents potential issues with stack trace accuracy.

Remove the manual stack assignment:

-    this.stack = new Error().stack!;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.


packages/@webex/plugin-cc/src/services/core/WebSocket/connection-service.ts (1)

140-143: 🛠️ Refactor suggestion

Limit reconnection attempts to prevent infinite retries

Currently, the reconnection attempts may continue indefinitely in case of persistent connectivity issues. Consider implementing a maximum retry limit or an exponential backoff strategy to prevent resource exhaustion.

Here's an example of limiting reconnection attempts:

+ let retryCount = 0;
+ const MAX_RETRIES = 5;
  this.reconnectInterval = setInterval(async () => {
+   if (retryCount >= MAX_RETRIES) {
+     clearInterval(this.reconnectInterval);
+     LoggerProxy.logger.error('Maximum reconnection attempts reached.');
+     return;
+   }
    await this.handleSocketClose();
+   retryCount++;
  }, CONNECTIVITY_CHECK_INTERVAL);

Committable suggestion skipped: line range outside the PR's diff.

packages/@webex/plugin-cc/src/types.ts (1)

199-211: 🛠️ Refactor suggestion

Ensure consistent naming conventions in type aliases

There are inconsistencies in the casing of type names in the RequestBody union:

  • Contact.HoldResumePayload vs Contact.WrapupPayLoad (difference in 'Payload' vs 'PayLoad')
  • Contact.cancelCtq uses camelCase, whereas other types use PascalCase.

Recommend standardizing the naming conventions to improve code consistency.

packages/@webex/plugin-cc/src/services/core/WebSocket/WebSocketManager.ts (5)

115-120: 🛠️ Refactor suggestion

Provide error details when rejecting the Promise

In the onerror handler of the WebSocket, the Promise is rejected without providing the error details, which can make debugging difficult.

Modify the onerror handler to pass the error event when rejecting:

this.websocket.onerror = (event: any) => {
  LoggerProxy.logger.error(
    `[WebSocketStatus] | event=socketConnectionFailed | WebSocket connection failed ${event}`
  );
-   reject();
+   reject(event);
};

This provides more context to the caller about why the connection failed.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

      this.websocket.onerror = (event: any) => {
        LoggerProxy.logger.error(
          `[WebSocketStatus] | event=socketConnectionFailed | WebSocket connection failed ${event}`
        );
        reject(event);
      };

28-28: 🛠️ Refactor suggestion

Initialize websocket to null instead of an empty object

In the constructor, this.websocket is initialized as an empty object cast to WebSocket. This can lead to runtime errors if this.websocket methods are called before assignment. It's safer to initialize it to null and perform null checks where necessary.

Apply this diff to initialize websocket to null:

- this.websocket = {} as WebSocket;
+ this.websocket = null;

Ensure that all usages of this.websocket include null checks to prevent potential errors.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    this.websocket = null;

62-76: ⚠️ Potential issue

Handle failure in the register method appropriately

If the API request in register fails, this.url remains null, but the code proceeds without signaling this failure to the caller. This can lead to issues later when connect is called with a null URL, potentially causing the application to hang without a clear error.

Modify the register method to throw an error when the API call fails:

...
} catch (e) {
  LoggerProxy.logger.error(
    `Register API Failed, Request to RoutingNotifs websocket registration API failed ${e}`
  );
+ throw e;
}

This ensures that failures are propagated and can be handled appropriately by the caller.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  private async register(connectionConfig: SubscribeRequest) {
    try {
      const subscribeResponse: SubscribeResponse = await this.webex.request({
        service: WCC_API_GATEWAY,
        resource: SUBSCRIBE_API,
        method: HTTP_METHODS.POST,
        body: connectionConfig,
      });
      this.url = subscribeResponse.body.webSocketUrl;
    } catch (e) {
      LoggerProxy.logger.error(
        `Register API Failed, Request to RoutingNotifs websocket registration API failed ${e}`
      );
      throw e;
    }
  }

38-49: ⚠️ Potential issue

Ensure initWebSocket handles registration failures

In initWebSocket, if register fails and throws an error, the rejection is not caught, which can lead to unhandled promise rejections. Additionally, if this.url remains null, the subsequent calls may not behave as expected.

Update initWebSocket to handle errors from register:

async initWebSocket(options: {body: SubscribeRequest}): Promise<WelcomeResponse> {
  const connectionConfig = options.body;
- await this.register(connectionConfig);
+ try {
+   await this.register(connectionConfig);
+ } catch (e) {
+   return Promise.reject(e);
+ }
  
  return new Promise((resolve, reject) => {
    this.welcomePromiseResolve = resolve;
    this.connect().catch((error) => {
      LoggerProxy.logger.error(`[WebSocketStatus] | Error in connecting Websocket ${error}`);
      reject(error);
    });
  });
}

This ensures that any errors during registration are properly propagated.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  async initWebSocket(options: {body: SubscribeRequest}): Promise<WelcomeResponse> {
    const connectionConfig = options.body;
    try {
      await this.register(connectionConfig);
    } catch (e) {
      return Promise.reject(e);
    }

    return new Promise((resolve, reject) => {
      this.welcomePromiseResolve = resolve;
      this.connect().catch((error) => {
        LoggerProxy.logger.error(`[WebSocketStatus] | Error in connecting Websocket ${error}`);
        reject(error);
      });
    });
  }

87-146: ⚠️ Potential issue

Resolve or reject the Promise in connect

The connect method creates a Promise but does not resolve or reject it within the WebSocket event handlers, potentially causing the Promise to remain pending indefinitely.

Modify the connect method to resolve or reject the Promise appropriately:

return new Promise((resolve, reject) => {
  this.websocket.onopen = () => {
    // Existing code...
+   resolve();
  };

  this.websocket.onerror = (event: any) => {
    LoggerProxy.logger.error(
      `[WebSocketStatus] | event=socketConnectionFailed | WebSocket connection failed ${event}`
    );
-   reject();
+   reject(event);
  };

  this.websocket.onclose = async (event: any) => {
    // Existing code...
+   reject(new Error('WebSocket connection closed unexpectedly'));
  };
});

This ensures that the caller can handle the outcome of the connection attempt.

Committable suggestion skipped: line range outside the PR's diff.

packages/@webex/plugin-cc/src/services/Task/types.ts (4)

28-69: 🛠️ Refactor suggestion

Define specific types instead of using 'any' and optional properties

In the AgentContact type:

  • The participants property in the Interaction type (line 136) is defined as any; // todo. Using any can lead to less type safety. Consider defining a specific interface or type for participants.
  • Several properties like eventTime, participantId, fromOwner, etc., are marked as optional (?), but may be required for proper functioning. Ensure that optional properties are correctly identified.
  • There's inconsistency in the type of isWebCallMute (line 62) and isConferencing (line 49). In AgentContact, isConferencing is a required boolean, whereas in Interaction, isConferencing is optional and possibly a string. Ensure consistent types across the application.

136-136: 🛠️ Refactor suggestion

Replace 'any' with a specific type for 'participants'

Using any for the participants property reduces type safety and can lead to runtime errors. Define a specific interface or type for participants to ensure better type checking.

Example:

type Participant = {
  id: string;
  name: string;
  role: string;
  // other relevant fields
};

// Update the Interaction type
participants: Participant[];

182-182: ⚠️ Potential issue

Inconsistent type for 'isConferencing' property

In the callProcessingDetails of the Interaction type:

  • The isConferencing property is defined as an optional string (isConferencing?: string;). However, in the AgentContact type, isConferencing is defined as a required boolean (isConferencing: boolean;).

To ensure type consistency, consider aligning the types:

- isConferencing?: string;
+ isConferencing?: boolean;

Or if the property can be both boolean and string, consider unifying the type definition.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    isConferencing?: boolean;

81-101: 🛠️ Refactor suggestion

⚠️ Potential issue

Review property names and consistency in 'Contact' type

  • Property channelTypeexport (line 82) seems to have an incorrect name. It might be a result of a misnamed variable or typo.
  • There is a mix of optional (?) and required properties. Verify if properties like subject, terminationType, and outdialType should be optional or required.
  • Ensure that the naming conventions are consistent (e.g., using camelCase for property names).

Apply these corrections:

-/** Channel type pof the contact e.g. email|chat|telephony */
-channelTypeexport: string;
+/** Channel type of the contact e.g. email|chat|telephony */
+channelType: string;

Committable suggestion skipped: line range outside the PR's diff.

packages/@webex/plugin-cc/src/cc.ts (4)

121-122: 🛠️ Refactor suggestion

Handle potential undefined orgId when fetching agent configuration

In connectWebsocket, orgId is retrieved using this.$webex.credentials.getOrgId(). If orgId is undefined, subsequent calls may fail. Implement a check to handle this scenario gracefully.

Include the following check:

+ if (!orgId) {
+   throw new Error('Organization ID is undefined. Ensure the user is authenticated.');
+ }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

          const orgId = this.$webex.credentials.getOrgId();
          if (!orgId) {
            throw new Error('Organization ID is undefined. Ensure the user is authenticated.');
          }
          this.agentConfig = await this.services.config.getAgentConfig(orgId, agentId);

211-217: ⚠️ Potential issue

Check for uninitialized agentConfig in getDeviceId

In the getDeviceId method, this.agentConfig.agentId is accessed (line 216) without confirming if this.agentConfig is initialized. This could lead to a runtime error if getDeviceId is called before register(). Ensure that this.agentConfig is available before using it.

Add a validation step:

+ if (!this.agentConfig) {
+   throw new Error('agentConfig is not initialized. Please call register() before getDeviceId().');
+ }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  private getDeviceId(loginOption: string, dialNumber: string): string {
    if (!this.agentConfig) {
      throw new Error('agentConfig is not initialized. Please call register() before getDeviceId().');
    }

    if (loginOption === LoginOption.EXTENSION || loginOption === LoginOption.AGENT_DN) {
      return dialNumber;
    }

    return WEB_RTC_PREFIX + this.agentConfig.agentId;
  }

97-105: ⚠️ Potential issue

Ensure agentConfig is initialized before using in getBuddyAgents

In the getBuddyAgents method, this.agentConfig.agentProfileID is used without checking if this.agentConfig is initialized. If register() has not been called prior to this method, this.agentConfig could be undefined, leading to a runtime error. Consider adding a check to ensure this.agentConfig is initialized or enforce that register() must be called before using this method.

Apply the following check:

+ if (!this.agentConfig) {
+   throw new Error('agentConfig is not initialized. Please call register() before getBuddyAgents().');
+ }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  public async getBuddyAgents(data: BuddyAgents): Promise<BuddyAgentsResponse> {
    try {
      if (!this.agentConfig) {
        throw new Error('agentConfig is not initialized. Please call register() before getBuddyAgents().');
      }
      return await this.services.agent.buddyAgents({
        data: {agentProfileId: this.agentConfig.agentProfileID, ...data},
      });
    } catch (error) {
      throw getErrorDetails(error, 'getBuddyAgents');
    }
  }

143-172: ⚠️ Potential issue

Prevent potential runtime errors by verifying agentConfig in stationLogin

The stationLogin method utilizes this.agentConfig.agentId (lines 148 and 152) without verifying if this.agentConfig is initialized. If register() hasn't been called before stationLogin(), this.agentConfig may be undefined, causing a runtime error. It's essential to ensure this.agentConfig is initialized before proceeding.

Consider adding the following check:

+ if (!this.agentConfig) {
+   throw new Error('agentConfig is not initialized. Please call register() before stationLogin().');
+ }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  public async stationLogin(data: AgentLogin): Promise<StationLoginResponse> {
    try {
      if (!this.agentConfig) {
        throw new Error('agentConfig is not initialized. Please call register() before stationLogin().');
      }
      const loginResponse = this.services.agent.stationLogin({
        data: {
          dialNumber:
            data.loginOption === LoginOption.BROWSER ? this.agentConfig.agentId : data.dialNumber,
          teamId: data.teamId,
          deviceType: data.loginOption,
          isExtension: data.loginOption === LoginOption.EXTENSION,
          deviceId: this.getDeviceId(data.loginOption, data.dialNumber),
          roles: [AGENT],
          // TODO: The public API should not have the following properties so filling them with empty values for now. If needed, we can add them in the future.
          teamName: EMPTY_STRING,
          siteId: EMPTY_STRING,
          usesOtherDN: false,
          auxCodeId: EMPTY_STRING,
        },
      });

      if (data.loginOption === LoginOption.BROWSER) {
        await this.webCallingService.registerWebCallingLine();
      }

      await loginResponse;

      return loginResponse;
    } catch (error) {
      throw getErrorDetails(error, 'stationLogin');
    }
  }
packages/@webex/plugin-cc/src/services/core/aqm-reqs.ts (2)

12-12: 🛠️ Refactor suggestion

Inconsistent variable naming: Rename 'pendingNotifCancelrequest' to 'pendingNotifCancelRequests'

The variable pendingNotifCancelrequest should be renamed to pendingNotifCancelRequests to follow camelCase conventions and to reflect that it stores multiple requests. This improves code readability and consistency.

Apply this diff to rename the variable:

- private pendingNotifCancelrequest: Record<string, Pending> = {};
+ private pendingNotifCancelRequests: Record<string, Pending> = {};

...

- delete this.pendingNotifCancelrequest[keyCancel];
+ delete this.pendingNotifCancelRequests[keyCancel];

...

- this.pendingNotifCancelrequest[keyCancel] = {
+ this.pendingNotifCancelRequests[keyCancel] = {

...

- const kReqAlt = Object.keys(this.pendingNotifCancelrequest);
+ const kReqAlt = Object.keys(this.pendingNotifCancelRequests);

- const req = this.pendingNotifCancelrequest[thisReq];
+ const req = this.pendingNotifCancelRequests[thisReq];

Also applies to: 64-64, 78-78, 231-233


141-141: 🛠️ Refactor suggestion

Use 'setTimeout' instead of 'window.setTimeout' for environment compatibility

Using window.setTimeout assumes a browser environment. To ensure compatibility with Node.js or other environments, use setTimeout directly.

Apply this diff to update the function call:

-           window.setTimeout(
+           setTimeout(
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        setTimeout(
packages/@webex/plugin-cc/src/services/config/Util.ts (1)

72-82: ⚠️ Potential issue

Correct variable name from 'dailPlan' to 'dialPlan'

The variable dailPlan appears to be misspelled throughout this block. To maintain consistency and avoid confusion, it should be corrected to dialPlan.

Apply this diff to fix the typo:

   dialPlanData.forEach((dailPlan: DialPlanEntity) => {
+    dialPlanData.forEach((dialPlan: DialPlanEntity) => {
       if (profileDialPlans.includes(dialPlan.id)) {
         const filteredPlan = {
-          regex: dailPlan.regularExpression,
-          prefix: dailPlan.prefix,
-          strippedChars: dailPlan.strippedChars,
-          name: dailPlan.name,
+          regex: dialPlan.regularExpression,
+          prefix: dialPlan.prefix,
+          strippedChars: dialPlan.strippedChars,
+          name: dialPlan.name,
         };
         dialPlanEntries.push(filteredPlan);
       }
     });

Committable suggestion skipped: line range outside the PR's diff.

docs/samples/contact-center/app.js (3)

233-234: ⚠️ Potential issue

Fix Incorrect HTML option Tags in Error Handling

The innerHTML strings for error messages in fetchBuddyAgents have improperly formatted <option> tags. Missing closing </option> tags can lead to rendering issues.

Apply this diff to correct the HTML strings:

- buddyAgentsDropdownElm.innerHTML = `<option disabled="true">Failed to fetch buddy agents<option>`;
+ buddyAgentsDropdownElm.innerHTML = `<option disabled="true">Failed to fetch buddy agents</option>`;

- buddyAgentsDropdownElm.innerHTML = `<option disabled="true">Failed to fetch buddy agents, ${error}<option>`;
+ buddyAgentsDropdownElm.innerHTML = `<option disabled="true">Failed to fetch buddy agents, ${error}</option>`;

Also applies to: 253-254


177-186: 🛠️ Refactor suggestion

Remove Hardcoded Re-Login Logic Used for Testing

The setTimeout function re-logs the agent after 15 seconds for testing purposes. Hardcoded testing code should be removed or properly managed to prevent unintended behavior in production.

Apply this diff to remove the testing code:

- // Re-Login Agent after 15 seconds for testing purpose
- setTimeout(async () => {
-   try {
-     const response = await webex.cc.stationReLogin();
-     console.log('Agent Re-Login successful', response);
-   } catch (error) {
-     console.log('Agent Re-Login failed', error);
-   }
- }, 15000);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.



198-200: ⚠️ Potential issue

Declare auxCodeId Before Usage

The variable auxCodeId is used without being declared, which can cause a ReferenceError in strict mode. Ensure all variables are properly declared before use.

Apply this diff to declare auxCodeId:

+ let auxCodeId;
...
function handleAgentStatus(event) {
  const select = document.getElementById('idleCodesDropdown');
  auxCodeId = event.target.value;
  agentStatus = select.options[select.selectedIndex].text;
}

Alternatively, declare auxCodeId at the top along with other global variables.

Committable suggestion skipped: line range outside the PR's diff.

packages/@webex/plugin-cc/src/services/Task/contact.ts (4)

4-4: ⚠️ Potential issue

Undefined 'err' variable used in request configurations

The 'err' variable is used in multiple request configurations but is not defined or imported. Ensure that 'err' is properly defined or imported before use to prevent reference errors.


144-177: ⚠️ Potential issue

Inconsistent usage of 'this.aqm.req' instead of 'aqm.req'

In the methods consult, end, and cancelTask, this.aqm.req is used, whereas in other methods, aqm.req is used. This inconsistency may lead to errors, as this.aqm may not be undefined in the current context. Ensure that aqm.req is used consistently across all methods.

Apply this diff to correct the inconsistency:

-        consult: this.aqm.req((p: {interactionId: string; data: Contact.ConsultData; url: string}) => ({
+        consult: aqm.req((p: {interactionId: string; data: Contact.ConsultData; url: string}) => ({

...

-        end: this.aqm.req((p: {interactionId: string}) => ({
+        end: aqm.req((p: {interactionId: string}) => ({

...

-        cancelTask: this.aqm.req((p: {interactionId: string}) => ({
+        cancelTask: aqm.req((p: {interactionId: string}) => ({

Also applies to: 283-302, 329-348


221-221: 🛠️ Refactor suggestion

Use constants from 'CC_EVENTS' instead of hardcoded event type strings

In the blindTransfer and vteamTransfer methods, event types are hardcoded as strings. For consistency and to prevent typos, use the constants defined in CC_EVENTS.

Apply this diff to replace hardcoded strings with constants:

// In 'blindTransfer' method
-              data: {type: 'AgentBlindTransferFailedEvent'},
+              data: {type: CC_EVENTS.AGENT_BLIND_TRANSFER_FAILED},

// In 'vteamTransfer' method
-              data: {type: 'AgentVteamTransferFailed'},
+              data: {type: CC_EVENTS.AGENT_VTEAM_TRANSFER_FAILED},

Also applies to: 246-246


320-320: ⚠️ Potential issue

Typographical error in 'CC_EVENTS.AGENt_WRAPUP_FAILED'

There is a typographical error in the event type AGENt_WRAPUP_FAILED. The correct constant is CC_EVENTS.AGENT_WRAPUP_FAILED, with 'AGENT' fully in uppercase.

Apply this diff to fix the typo:

-              data: {type: CC_EVENTS.AGENt_WRAPUP_FAILED},
+              data: {type: CC_EVENTS.AGENT_WRAPUP_FAILED},
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

          data: {type: CC_EVENTS.AGENT_WRAPUP_FAILED},
packages/@webex/webex-core/src/lib/services/service-catalog.js (1)

248-254: ⚠️ Potential issue

Import 'URL' from the 'url' module to ensure 'URL' is defined

The code uses the URL constructor to parse URLs, but URL is not imported. This will lead to a ReferenceError at runtime. To fix this, import URL from the 'url' module at the top of the file.

Apply this diff to fix the issue:

- import Url from 'url';
+ import {URL} from 'url';

Also applies to: 262-267, 285-300

packages/@webex/plugin-cc/src/services/config/types.ts (4)

19-20: ⚠️ Potential issue

Duplicate event values in 'CC_EVENTS'

Both AGENT_BUDDY_AGENTS and AGENT_BUDDY_AGENTS_SUCCESS are assigned the same value 'BuddyAgents'. This could cause confusion or bugs when handling these events, as they would be indistinguishable by their value. Please assign a unique string value to each event to ensure correct event handling.


139-139: 🛠️ Refactor suggestion

Restrict 'accessWrapUpCode' and 'accessIdleCode' to specific allowed values

The properties accessWrapUpCode and accessIdleCode are currently typed as string, but according to the comments, they can only take the values 'ALL' or 'SPECIFIC'. To enhance type safety and prevent invalid values, consider defining them as string literal union types:

- accessWrapUpCode: string;
+ accessWrapUpCode: 'ALL' | 'SPECIFIC';

- accessIdleCode: string;
+ accessIdleCode: 'ALL' | 'SPECIFIC';

Also applies to: 144-144


70-125: 🛠️ Refactor suggestion

Ensure consistent naming conventions for type names

Some type names, such as agentWrapUpCodes and agentDefaultWrapupCode, start with a lowercase letter, while others like AgentResponse and DesktopProfileResponse start with an uppercase letter. According to TypeScript conventions, type and interface names should use PascalCase. Please update the type names to ensure consistency and improve code readability.

Also applies to: 130-269, 482-555


498-499: 🛠️ Refactor suggestion

Use consistent types for 'regexUS' and 'regexOther' properties

The properties regexUS and regexOther are typed as RegExp | string, allowing them to be either a RegExp object or a string. Mixing types can lead to runtime errors if not handled carefully. It's advisable to choose a single type, either RegExp or string, to ensure consistent usage and simplify type checking.

packages/@webex/plugin-cc/src/services/config/index.ts (6)

66-74: ⚠️ Potential issue

Handle potential null or undefined teamIds in userConfigData

In the getAgentConfig method, when accessing userConfigData.teamIds, there's a possibility that teamIds might be null or undefined, which could lead to unexpected behavior or errors when calling getAllTeams.

Apply this diff to safely handle the teamIds property:

- const userTeamPromise = userConfigData.teamIds
+ const userTeamPromise = userConfigData.teamIds && userConfigData.teamIds.length > 0

Updated code:

const userTeamPromise = userConfigData.teamIds && userConfigData.teamIds.length > 0
  ? this.getAllTeams(
      orgId,
      DEFAULT_PAGE_SIZE,
      userConfigData.teamIds,
      DEFAULT_TEAM_ATTRIBUTES
    )
  : Promise.resolve([]);

This ensures that getAllTeams is only called when teamIds is a non-empty array.


376-377: ⚠️ Potential issue

Handle potential undefined response.body.data

In the getOrganizationSetting method, there is an assumption that response.body.data[0] exists. If data is undefined or an empty array, accessing [0] could lead to errors.

Add a check to ensure response.body.data exists and has elements before accessing the first item:

+ if (!response.body.data || response.body.data.length === 0) {
+   throw new Error('No organization settings data found in response.');
+ }
return Promise.resolve(response.body.data[0]);

This ensures that the method gracefully handles unexpected API responses and provides a clear error message.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

      if (!response.body.data || response.body.data.length === 0) {
        throw new Error('No organization settings data found in response.');
      }
      return Promise.resolve(response.body.data[0]);
    } catch (error) {

417-434: ⚠️ Potential issue

Ensure proper error handling in getURLMapping

In the getURLMapping method, when returning response.body.data, there is no check for the existence of data. If data is undefined, it could cause an error.

Add a null check before returning the data:

+ if (!response.body || !response.body.data) {
+   throw new Error('No URL mapping data found in response.');
+ }
return Promise.resolve(response.body.data);

This ensures that the method handles unexpected API responses appropriately.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

      const resource = endPointMap.urlMapping(orgId);
      const response = await this.httpReq.request({
        service: WCC_API_GATEWAY,
        resource,
        method: HTTP_METHODS.GET,
      });

      if (response.statusCode !== 200) {
        throw new Error(`API call failed with ${response.statusCode}`);
      }

      LoggerProxy.logger.log('getURLMapping api success.');

      if (!response.body || !response.body.data) {
        throw new Error('No URL mapping data found in response.');
      }
      return Promise.resolve(response.body.data);
    } catch (error) {
      LoggerProxy.logger.error(`getURLMapping API call failed with ${error}`);
      throw error;
    }

312-315: ⚠️ Potential issue

Consistent pagination logic in getAllAuxCodes

Similar to getAllTeams, the pagination loop in getAllAuxCodes may have an off-by-one error. To ensure all pages are fetched, adjust the loop condition.

Apply this diff:

- for (page = DEFAULT_PAGE + 1; page < totalPages; page += 1) {
+ for (page = DEFAULT_PAGE + 1; page <= totalPages; page += 1) {

This modification ensures that the last page of auxiliary codes is included in the results.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

      for (page = DEFAULT_PAGE + 1; page <= totalPages; page += 1) {
        promises.push(this.getListOfAuxCodes(orgId, page, pageSize, filter, attributes));
      }


84-93: 🛠️ Refactor suggestion

Handle rejected promises in Promise.all

In the getAgentConfig method, using Promise.all without individual try-catch blocks can cause the entire method to fail if any single promise rejects. This may result in a loss of partial data that could be useful.

Consider using Promise.allSettled to handle each promise individually:

- const [
-   agentProfileConfigData,
-   userDialPlanData,
-   userTeamData,
-   orgInfo,
-   orgSettingsData,
-   tenantData,
-   urlMappingData,
-   auxCodesData,
- ] = await Promise.all([
+ const results = await Promise.allSettled([
    agentProfilePromise,
    userDialPlanPromise,
    userTeamPromise,
    orgInfoPromise,
    orgSettingsPromise,
    tenantDataPromise,
    urlMappingPromise,
    auxCodesPromise,
  ]);

+ // Process the results, checking for fulfillment or rejection
+ // and handling accordingly

This allows for more granular error handling and the ability to proceed with available data where appropriate.

Committable suggestion skipped: line range outside the PR's diff.


236-239: ⚠️ Potential issue

Potential off-by-one error in pagination loop in getAllTeams

In the getAllTeams method, the for-loop condition may skip the last page due to the use of a zero-based index for page and possibly a one-based index for totalPages. If totalPages represents the total number of pages and is inclusive, the condition page < totalPages might not retrieve the last page.

Consider adjusting the loop condition to ensure all pages are fetched:

- for (page = DEFAULT_PAGE + 1; page < totalPages; page += 1) {
+ for (page = DEFAULT_PAGE + 1; page <= totalPages; page += 1) {

Ensure that DEFAULT_PAGE and totalPages are consistently defined (either zero-based or one-based) and adjust the loop accordingly.

Committable suggestion skipped: line range outside the PR's diff.

packages/@webex/plugin-cc/test/unit/spec/services/core/aqm-reqs.ts (3)

69-74: ⚠️ Potential issue

Improper mocking of addEventListener and dispatchEvent.

Assigning dispatchEvent within the addEventListener mock is not standard practice and may lead to unexpected behavior.

Apply the following changes to correctly mock event handling:

-webSocketManagerInstance.addEventListener = jest.fn((event, callback) => {
-  if (event === 'message') {
-    webSocketManagerInstance.dispatchEvent = callback;
-  }
-});
+webSocketManagerInstance.addEventListener = jest.fn();
+webSocketManagerInstance.dispatchEvent = jest.fn();

When dispatching events, call dispatchEvent directly with the appropriate event object.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    // Mock the addEventListener method
    webSocketManagerInstance.addEventListener = jest.fn();
    webSocketManagerInstance.dispatchEvent = jest.fn();

80-107: ⚠️ Potential issue

Test may pass without verifying expected behavior.

The test will pass even if req({}) does not throw an error, as there is no assertion in the try block.

Ensure the test fails when the expected error is not thrown:

try {
  await req({});
+  fail('Expected error was not thrown');
} catch (e) {
  expect(e).toBeDefined();
}

Alternatively, use:

-await req({});
+await expect(req({})).rejects.toBeDefined();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  it('AqmReqs should be defined', async () => {
    httpRequestInstance.request.mockResolvedValueOnce(mockHttpRequestResolvedValue);

    const req = aqm.req(() => ({
      url: '/url',
      timeout: 2000,
      notifSuccess: {
        bind: {
          type: 'RoutingMessage',
          data: {type: 'AgentConsultConferenced', interactionId: 'intrid'},
        },
        msg: {},
      },
      notifFail: {
        bind: {
          type: 'RoutingMessage',
          data: {type: 'AgentConsultConferenceFailed'},
        },
        errId: 'Service.aqm.contact.consult',
      },
    }));

    await expect(req({})).rejects.toBeDefined();
  });

229-268: ⚠️ Potential issue

Test lacks proper error assertion.

The test catches an error but doesn't verify its content, which may lead to false positives.

Enhance the test to assert the error message:

try {
  await req({});
} catch (e) {
  expect(e).toBeDefined();
+  expect(e.message).toBe('Async error');
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

      httpRequestInstance.request.mockRejectedValueOnce(new Error('Async error'));

      const req = aqm.req(() => ({
        url: '/url',
        timeout: 4000,
        notifSuccess: {
          bind: {
            type: 'RoutingMessage',
            data: {
              type: 'AgentConsultCreated',
              interactionId: '6920dda3-337a-48b1-b82d-2333392f9906',
            },
          },
          msg: {},
        },
        notifFail: {
          bind: {
            type: 'RoutingMessage',
            data: {type: 'AgentConsultFailed'},
          },
          errId: 'Service.aqm.contact.consult',
        },
        notifCancel: {
          bind: {
            type: 'RoutingMessage',
            data: {
              type: 'AgentCtqCancelled',
              interactionId: '6920dda3-337a-48b1-b82d-2333392f9906',
            },
          },
          msg: {},
        },
      }));

      try {
        await req({});
      } catch (e) {
        expect(e).toBeDefined();
        expect(e.message).toBe('Async error');
      }
    });
packages/@webex/plugin-cc/test/unit/spec/cc.ts (1)

57-57: ⚠️ Potential issue

Incorrect mocking of 'once' method may lead to unreliable tests

The once method is mocked to immediately invoke the callback:

once: jest.fn((event, callback) => callback()),

This causes the callback to be executed immediately upon registration, rather than when the 'READY' event is emitted. As a result, in the test case starting at line 89, the callback within webex.once('READY', callback) is called before webex.emit('READY') is invoked, which does not accurately simulate the event-driven behavior.

To fix this issue, consider updating the mock to properly register the event and callback, and invoke the callback only when emit is called with the corresponding event. Here's a possible fix:

- once: jest.fn((event, callback) => callback()),
+ const events = {};
+ webex.on = jest.fn((event, callback) => {
+   events[event] = callback;
+ });
+ webex.once = jest.fn((event, callback) => {
+   events[event] = (...args) => {
+     callback(...args);
+     delete events[event]; // For 'once' behavior
+   };
+ });
+ webex.emit = jest.fn((event, ...args) => {
+   if (events[event]) {
+     events[event](...args);
+   }
+ });

Adjust your tests to use the updated mock and ensure that event listeners are correctly handled when emit is called.

Also applies to: 89-96

packages/@webex/plugin-cc/test/unit/spec/services/config/index.ts (11)

413-423: ⚠️ Potential issue

Correct error handling when API returns non-200 status code

In the getURLMapping test, ensure you're simulating errors by rejecting the promise and adjust your assertions for consistency.

Apply this diff to correct the test:

-it('should throw an error if API call returns non-200 status code', async () => {
-  const mockError = {statusCode: 500};
-  mockHttpRequest.request.mockResolvedValue(mockError);

-  await expect(agentConfigService.getURLMapping(mockOrgId)).rejects.toThrow(
-    'API call failed with 500'
-  );
-  expect(LoggerProxy.logger.error).toHaveBeenCalledWith(
-    expect.stringContaining('getURLMapping API call failed with')
-  );
-});
+it('should throw an error if API call returns non-200 status code', async () => {
+  const mockError = new Error('API call failed with 500');
+  mockHttpRequest.request.mockRejectedValue(mockError);

+  await expect(agentConfigService.getURLMapping(mockOrgId)).rejects.toThrow(
+    'API call failed with 500'
+  );
+  expect(LoggerProxy.logger.error).toHaveBeenCalledWith(
+    expect.stringContaining('getURLMapping API call failed with')
+  );
+});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    it('should throw an error if API call returns non-200 status code', async () => {
      const mockError = new Error('API call failed with 500');
      mockHttpRequest.request.mockRejectedValue(mockError);

      await expect(agentConfigService.getURLMapping(mockOrgId)).rejects.toThrow(
        'API call failed with 500'
      );
      expect(LoggerProxy.logger.error).toHaveBeenCalledWith(
        expect.stringContaining('getURLMapping API call failed with')
      );
    });

71-82: ⚠️ Potential issue

Correct error handling when API returns non-200 status code

In the getUserUsingCI test, when the API call returns a non-200 status code, the test currently mocks the request to resolve with a response object containing statusCode: 400. However, in such cases, the service method should reject the promise with an error. To accurately test this behavior, you should mock the request to reject with an error and use await expect(...).rejects.toThrow(...) in your test.

Apply this diff to correct the test:

-it('should throw an error if the getUserUsingCI call fails with other than 200', async () => {
-  const mockResponse = {
-    statusCode: 400,
-  };
-  (mockHttpRequest.request as jest.Mock).mockResolvedValue(mockResponse);

-  try {
-    await agentConfigService.getUserUsingCI(mockOrgId, mockAgentId);
-  } catch (error) {
-    expect(error).toEqual(new Error(`API call failed with ${mockResponse.statusCode}`));
-  }
-});
+it('should throw an error if the getUserUsingCI call fails with other than 200', async () => {
+  const mockError = new Error('API call failed with 400');
+  (mockHttpRequest.request as jest.Mock).mockRejectedValue(mockError);

+  await expect(agentConfigService.getUserUsingCI(mockOrgId, mockAgentId)).rejects.toThrow(
+    'API call failed with 400'
+  );
+});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    it('should throw an error if the getUserUsingCI call fails with other than 200', async () => {
      const mockError = new Error('API call failed with 400');
      (mockHttpRequest.request as jest.Mock).mockRejectedValue(mockError);

      await expect(agentConfigService.getUserUsingCI(mockOrgId, mockAgentId)).rejects.toThrow(
        'API call failed with 400'
      );
    });

380-390: ⚠️ Potential issue

Correct error handling when API returns non-200 status code

Modify the getTenantData test to reject the promise when simulating a non-200 status code response.

Apply this diff to correct the test:

-it('should throw an error if API call returns non-200 status code', async () => {
-  const mockError = {statusCode: 500};
-  mockHttpRequest.request.mockResolvedValue(mockError);

-  await expect(agentConfigService.getTenantData(mockOrgId)).rejects.toThrow(
-    'API call failed with 500'
-  );
-  expect(LoggerProxy.logger.error).toHaveBeenCalledWith(
-    expect.stringContaining('getTenantData API call failed with')
-  );
-});
+it('should throw an error if API call returns non-200 status code', async () => {
+  const mockError = new Error('API call failed with 500');
+  mockHttpRequest.request.mockRejectedValue(mockError);

+  await expect(agentConfigService.getTenantData(mockOrgId)).rejects.toThrow(
+    'API call failed with 500'
+  );
+  expect(LoggerProxy.logger.error).toHaveBeenCalledWith(
+    expect.stringContaining('getTenantData API call failed with')
+  );
+});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    it('should throw an error if API call returns non-200 status code', async () => {
      const mockError = new Error('API call failed with 500');
      mockHttpRequest.request.mockRejectedValue(mockError);

      await expect(agentConfigService.getTenantData(mockOrgId)).rejects.toThrow(
        'API call failed with 500'
      );
      expect(LoggerProxy.logger.error).toHaveBeenCalledWith(
        expect.stringContaining('getTenantData API call failed with')
      );
    });

290-300: ⚠️ Potential issue

Correct error handling when API returns non-200 status code

In the getOrgInfo test, ensure that the promise is rejected with an error when simulating a non-200 status code. Update the test to reflect this behavior.

Apply this diff to correct the test:

-it('should throw an error if API call returns non-200 status code', async () => {
-  const mockError = {statusCode: 500};
-  mockHttpRequest.request.mockResolvedValue(mockError);

-  await expect(agentConfigService.getOrgInfo(mockOrgId)).rejects.toThrow(
-    'API call failed with 500'
-  );
-  expect(LoggerProxy.logger.error).toHaveBeenCalledWith(
-    expect.stringContaining('getOrgInfo API call failed with')
-  );
-});
+it('should throw an error if API call returns non-200 status code', async () => {
+  const mockError = new Error('API call failed with 500');
+  mockHttpRequest.request.mockRejectedValue(mockError);

+  await expect(agentConfigService.getOrgInfo(mockOrgId)).rejects.toThrow('API call failed with 500');
+  expect(LoggerProxy.logger.error).toHaveBeenCalledWith(
+    expect.stringContaining('getOrgInfo API call failed with')
+  );
+});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    it('should throw an error if API call returns non-200 status code', async () => {
      const mockError = new Error('API call failed with 500');
      mockHttpRequest.request.mockRejectedValue(mockError);

      await expect(agentConfigService.getOrgInfo(mockOrgId)).rejects.toThrow('API call failed with 500');
      expect(LoggerProxy.logger.error).toHaveBeenCalledWith(
        expect.stringContaining('getOrgInfo API call failed with')
      );
    });

567-581: ⚠️ Potential issue

Correct error handling when API returns non-200 status code

Similarly, for the getAllAuxCodes test, ensure error handling is consistent by mocking a rejected promise and using await expect(...).rejects.toThrow(...).

Apply this diff to correct the test:

-it('should throw an error if API call returns non-200 status code', async () => {
-  const pageSize = 10;
-  const filter = ['filter1'];
-  const attributes = ['attribute1'];

-  const mockError = {statusCode: 500};
-  (mockHttpRequest.request as jest.Mock).mockResolvedValue(mockError);

-  await expect(
-    agentConfigService.getAllAuxCodes(mockOrgId, pageSize, filter, attributes)
-  ).rejects.toThrow('API call failed with 500');
-  expect(LoggerProxy.logger.error).toHaveBeenCalledWith(
-    expect.stringContaining('getListOfAuxCodes API call failed with')
-  );
-});
+it('should throw an error if API call returns non-200 status code', async () => {
+  const pageSize = 10;
+  const filter = ['filter1'];
+  const attributes = ['attribute1'];

+  const mockError = new Error('API call failed with 500');
+  (mockHttpRequest.request as jest.Mock).mockRejectedValue(mockError);

+  await expect(
+    agentConfigService.getAllAuxCodes(mockOrgId, pageSize, filter, attributes)
+  ).rejects.toThrow('API call failed with 500');
+  expect(LoggerProxy.logger.error).toHaveBeenCalledWith(
+    expect.stringContaining('getListOfAuxCodes API call failed with')
+  );
+});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    it('should throw an error if API call returns non-200 status code', async () => {
      const pageSize = 10;
      const filter = ['filter1'];
      const attributes = ['attribute1'];

      const mockError = new Error('API call failed with 500');
      (mockHttpRequest.request as jest.Mock).mockRejectedValue(mockError);

      await expect(
        agentConfigService.getAllAuxCodes(mockOrgId, pageSize, filter, attributes)
      ).rejects.toThrow('API call failed with 500');
      expect(LoggerProxy.logger.error).toHaveBeenCalledWith(
        expect.stringContaining('getListOfAuxCodes API call failed with')
      );
    });

333-343: ⚠️ Potential issue

Correct error handling when API returns non-200 status code

Adjust the getOrganizationSetting test to mock a rejected promise for non-200 status codes and use consistent error assertion methods.

Apply this diff to correct the test:

-it('should throw an error if API call returns non-200 status code', async () => {
-  const mockError = {statusCode: 500};
-  mockHttpRequest.request.mockResolvedValue(mockError);

-  await expect(agentConfigService.getOrganizationSetting(mockOrgId)).rejects.toThrow(
-    'API call failed with 500'
-  );
-  expect(LoggerProxy.logger.error).toHaveBeenCalledWith(
-    expect.stringContaining('getOrganizationSetting API call failed with')
-  );
-});
+it('should throw an error if API call returns non-200 status code', async () => {
+  const mockError = new Error('API call failed with 500');
+  mockHttpRequest.request.mockRejectedValue(mockError);

+  await expect(agentConfigService.getOrganizationSetting(mockOrgId)).rejects.toThrow(
+    'API call failed with 500'
+  );
+  expect(LoggerProxy.logger.error).toHaveBeenCalledWith(
+    expect.stringContaining('getOrganizationSetting API call failed with')
+  );
+});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    it('should throw an error if API call returns non-200 status code', async () => {
      const mockError = new Error('API call failed with 500');
      mockHttpRequest.request.mockRejectedValue(mockError);

      await expect(agentConfigService.getOrganizationSetting(mockOrgId)).rejects.toThrow(
        'API call failed with 500'
      );
      expect(LoggerProxy.logger.error).toHaveBeenCalledWith(
        expect.stringContaining('getOrganizationSetting API call failed with')
      );
    });

181-193: ⚠️ Potential issue

Correct error handling when API returns non-200 status code

In the getListOfTeams test, the error scenario should mock a rejected promise instead of resolving with a response containing statusCode: 400. This ensures the test accurately reflects the service's error handling.

Apply this diff to correct the test:

-it('should throw an error if the getListOfTeams call fails with other than 200', async () => {
-  const mockResponse = {
-    statusCode: 400,
-  };
-  (mockHttpRequest.request as jest.Mock).mockResolvedValue(mockResponse);

-  try {
-    await agentConfigService.getListOfTeams(mockOrgId, page, pageSize, filter, attributes);
-  } catch (error) {
-    expect(error).toEqual(new Error(`API call failed with ${mockResponse.statusCode}`));
-  }
-});
+it('should throw an error if the getListOfTeams call fails with other than 200', async () => {
+  const mockError = new Error('API call failed with 400');
+  (mockHttpRequest.request as jest.Mock).mockRejectedValue(mockError);

+  await expect(
+    agentConfigService.getListOfTeams(mockOrgId, page, pageSize, filter, attributes)
+  ).rejects.toThrow('API call failed with 400');
+});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    it('should throw an error if the getListOfTeams call fails with other than 200', async () => {
      const mockError = new Error('API call failed with 400');
      (mockHttpRequest.request as jest.Mock).mockRejectedValue(mockError);

      await expect(
        agentConfigService.getListOfTeams(mockOrgId, page, pageSize, filter, attributes)
      ).rejects.toThrow('API call failed with 400');
    });
  });

446-456: ⚠️ Potential issue

Correct error handling when API returns non-200 status code

Update the getDialPlanData test to correctly simulate a rejected promise for non-200 status codes.

Apply this diff to correct the test:

-it('should throw an error if API call returns non-200 status code', async () => {
-  const mockError = {statusCode: 500};
-  mockHttpRequest.request.mockResolvedValue(mockError);

-  await expect(agentConfigService.getDialPlanData(mockOrgId)).rejects.toThrow(
-    'API call failed with 500'
-  );
-  expect(LoggerProxy.logger.error).toHaveBeenCalledWith(
-    expect.stringContaining('getDialPlanData API call failed with')
-  );
-});
+it('should throw an error if API call returns non-200 status code', async () => {
+  const mockError = new Error('API call failed with 500');
+  mockHttpRequest.request.mockRejectedValue(mockError);

+  await expect(agentConfigService.getDialPlanData(mockOrgId)).rejects.toThrow(
+    'API call failed with 500'
+  );
+  expect(LoggerProxy.logger.error).toHaveBeenCalledWith(
+    expect.stringContaining('getDialPlanData API call failed with')
+  );
+});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    it('should throw an error if API call returns non-200 status code', async () => {
      const mockError = new Error('API call failed with 500');
      mockHttpRequest.request.mockRejectedValue(mockError);

      await expect(agentConfigService.getDialPlanData(mockOrgId)).rejects.toThrow(
        'API call failed with 500'
      );
      expect(LoggerProxy.logger.error).toHaveBeenCalledWith(
        expect.stringContaining('getDialPlanData API call failed with')
      );
    });

507-521: ⚠️ Potential issue

Correct error handling when API returns non-200 status code

In the getAllTeams test, adjust the error simulation to reject the promise and unify the error assertion pattern.

Apply this diff to correct the test:

-it('should throw an error if API call returns non-200 status code', async () => {
-  const pageSize = 10;
-  const filter = ['filter1'];
-  const attributes = ['attribute1'];

-  const mockError = {statusCode: 500};
-  (mockHttpRequest.request as jest.Mock).mockResolvedValue(mockError);

-  await expect(
-    agentConfigService.getAllTeams(mockOrgId, pageSize, filter, attributes)
-  ).rejects.toThrow('API call failed with 500');
-  expect(LoggerProxy.logger.error).toHaveBeenCalledWith(
-    expect.stringContaining('getListOfTeams API call failed with')
-  );
-});
+it('should throw an error if API call returns non-200 status code', async () => {
+  const pageSize = 10;
+  const filter = ['filter1'];
+  const attributes = ['attribute1'];

+  const mockError = new Error('API call failed with 500');
+  (mockHttpRequest.request as jest.Mock).mockRejectedValue(mockError);

+  await expect(
+    agentConfigService.getAllTeams(mockOrgId, pageSize, filter, attributes)
+  ).rejects.toThrow('API call failed with 500');
+  expect(LoggerProxy.logger.error).toHaveBeenCalledWith(
+    expect.stringContaining('getListOfTeams API call failed with')
+  );
+});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    it('should throw an error if API call returns non-200 status code', async () => {
      const pageSize = 10;
      const filter = ['filter1'];
      const attributes = ['attribute1'];

      const mockError = new Error('API call failed with 500');
      (mockHttpRequest.request as jest.Mock).mockRejectedValue(mockError);

      await expect(
        agentConfigService.getAllTeams(mockOrgId, pageSize, filter, attributes)
      ).rejects.toThrow('API call failed with 500');
      expect(LoggerProxy.logger.error).toHaveBeenCalledWith(
        expect.stringContaining('getListOfTeams API call failed with')
      );
    });

123-135: ⚠️ Potential issue

Correct error handling when API returns non-200 status code

Similar to the previous comment, in the getDesktopProfileById test, the API call failure with a non-200 status code should be simulated by rejecting the promise with an error. Modify the test to mock a rejected promise and use await expect(...).rejects.toThrow(...).

Apply this diff to correct the test:

-it('should throw an error if the getDesktopProfileById call fails with other than 200', async () => {
-  const mockResponse = {
-    statusCode: 400,
-  };
-  (mockHttpRequest.request as jest.Mock).mockResolvedValue(mockResponse);

-  try {
-    await agentConfigService.getDesktopProfileById(mockOrgId, desktopProfileId);
-  } catch (error) {
-    expect(error).toEqual(new Error(`API call failed with ${mockResponse.statusCode}`));
-  }
-});
+it('should throw an error if the getDesktopProfileById call fails with other than 200', async () => {
+  const mockError = new Error('API call failed with 400');
+  (mockHttpRequest.request as jest.Mock).mockRejectedValue(mockError);

+  await expect(
+    agentConfigService.getDesktopProfileById(mockOrgId, desktopProfileId)
+  ).rejects.toThrow('API call failed with 400');
+});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    it('should throw an error if the getDesktopProfileById call fails with other than 200', async () => {
      const mockError = new Error('API call failed with 400');
      (mockHttpRequest.request as jest.Mock).mockRejectedValue(mockError);

      await expect(
        agentConfigService.getDesktopProfileById(mockOrgId, desktopProfileId)
      ).rejects.toThrow('API call failed with 400');
    });
  });

256-268: ⚠️ Potential issue

Correct error handling when API returns non-200 status code

For the getListOfAuxCodes test, modify the error simulation to reject the promise with an error and adjust the assertions accordingly.

Apply this diff to correct the test:

-it('should throw an error if the getListOfAuxCodes call fails with other than 200', async () => {
-  const mockResponse = {
-    statusCode: 400,
-  };
-  (mockHttpRequest.request as jest.Mock).mockResolvedValue(mockResponse);

-  try {
-    await agentConfigService.getListOfAuxCodes(mockOrgId, page, pageSize, filter, attributes);
-  } catch (error) {
-    expect(error).toEqual(new Error(`API call failed with ${mockResponse.statusCode}`));
-  }
-});
+it('should throw an error if the getListOfAuxCodes call fails with other than 200', async () => {
+  const mockError = new Error('API call failed with 400');
+  (mockHttpRequest.request as jest.Mock).mockRejectedValue(mockError);

+  await expect(
+    agentConfigService.getListOfAuxCodes(mockOrgId, page, pageSize, filter, attributes)
+  ).rejects.toThrow('API call failed with 400');
+});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    it('should throw an error if the getListOfAuxCodes call fails with other than 200', async () => {
      const mockError = new Error('API call failed with 400');
      (mockHttpRequest.request as jest.Mock).mockRejectedValue(mockError);

      await expect(
        agentConfigService.getListOfAuxCodes(mockOrgId, page, pageSize, filter, attributes)
      ).rejects.toThrow('API call failed with 400');
    });
  });

Copy link
Contributor

@sreenara sreenara left a comment

Choose a reason for hiding this comment

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

Please update the PR description with details of the new code added such as a new class called Task. Additionally, it would be good if you could talk about what the contact.ts is and what changes if any were made to the agentx-services code while moving the file here.

Copy link
Contributor

@sreenara sreenara left a comment

Choose a reason for hiding this comment

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

I have a few comments regarding the design. Haven't reviewed the UTs yet. Please see the comments.

@@ -1,5 +1,6 @@
import {CallingClientConfig} from '@webex/calling';
import * as Agent from './services/agent/types';
import * as Contact from './services/Task/types';
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we stick to the convention of naming the folder in all small letters?

Suggested change
import * as Contact from './services/Task/types';
import * as Contact from './services/task/types';

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Addressed

@@ -217,7 +218,14 @@ export type RequestBody =
| Agent.Logout
| Agent.UserStationLogin
| Agent.StateChange
| Agent.BuddyAgents;
| Agent.BuddyAgents
| Contact.HoldResumePayload
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a reason why we are importing as Contact? What's the significance of that term?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In agent Desktop, these call controls are referred as contact controls and we planned to call them task so replaced agent with contact

Copy link
Contributor

Choose a reason for hiding this comment

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

@Kesari3008 would the developer see these names?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No


export default class Task extends EventEmitter {
private localAudioStream: LocalMicrophoneStream;
private services: Services; // This will be used later for invoking Contact APIs
Copy link
Contributor

Choose a reason for hiding this comment

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

Does the Task need the entire Services object? Doesn't services have unnecessary members which don't pertain to the Task?

Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps we could only pass the members of services that are needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We do not need the entire services object but we need to keep the contact layer behind the services layer and aqmReqs needs to be passed to the contact layer which is present within the services layer

Copy link
Contributor

Choose a reason for hiding this comment

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

So we only need to pass the services objects which are needed here in task right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Passing contact and websocketmanager instead of complete services object

this.registerCallingEvent();
}

private registerCallingEvent() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
private registerCallingEvent() {
private registerIncomingCallEvent() {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Addressed

Copy link
Contributor

Choose a reason for hiding this comment

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

The way this class has been structured makes me feel like this is a bit of a task manager but also a task.
Also shouldn't we have types of Tasks? We won't only be doing calls in the future right?

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, how do we plan to handle multiple tasks at the same time?

@@ -57,6 +59,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
});

this.webCallingService = new WebCallingService(this.$webex, this.$config.callingClientConfig);
this.task = new Task(this.services, this.webCallingService);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are we initializing a task here.. at the start, there won't be any tasks right?

@@ -132,7 +159,7 @@ function register() {
agentLogin.innerHTML = '<option value="" selected>Choose Agent Login ...</option>'; // Clear previously selected option on agentLogin.
dialNumber.value = '';
dialNumber.disabled = true;
if(loginVoiceOptions.length > 0) agentLoginButton.disabled = false;
if(loginVoiceOptions.length > 0) loginAgentElm.disabled = false;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
if(loginVoiceOptions.length > 0) loginAgentElm.disabled = false;
if (loginVoiceOptions.length > 0) loginAgentElm.disabled = false;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

},
});

function registerListeners(task) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This part is confusing because of the way the Task class has been implemented. I'm listening on webex.cc.task, but the payload is also a task? What would the difference between tasks of different types be.. how would the payload vary..

taskId = event.detail.task.interactionId;
const callerDisplay = event.detail.task.interaction.callAssociatedDetails.ani;

incomingDetailsElm.innerText = `Call from ${callerDisplay}`;
Copy link
Contributor

Choose a reason for hiding this comment

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

In the case of non-desktop mode, prehaps it might be better to say "Call from ..., please answer on the endpoint where the agent's extension is registered".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Addressed

@adhmenon
Copy link
Contributor

@Kesari3008, are we using the latest wxcc branch - only reason I ask is because I think I see some old code (which has now been changed).

this.taskPayload = payload.data;
if (this.webCallingService.loginOption !== LoginOption.BROWSER) {
this.emit('task:incoming', payload.data);
} else if (this.call) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Just curious, is it possible to encounter a situation where we do not have the calling service created and we are receicing web socket events (for BROWSER) - I would assume if either was present we would be having both (as per the initialisation we do in cc.ts).
Only reason I'm asking is because we can avoid this double if condition. Just a query, however.

@@ -142,7 +142,7 @@ export class WebSocketManager extends EventEmitter {
this.emit('message', e.data);
const eventData = JSON.parse(e.data);

if (eventData.type === 'Welcome') {
if (eventData.type === CC_EVENTS.WELCOME) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for adding this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
validated If the pull request is validated for automation.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants