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: WxCC SDK Subscribe Notifications and establish Mercury Connection #3909

Merged
merged 28 commits into from
Oct 23, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
cdd51d1
feat: added code for cc mercury connection
rsarika Oct 11, 2024
3a55c07
fix: refactored code
rsarika Oct 14, 2024
454f760
fix: updated testcases for register and ccmercury
rsarika Oct 14, 2024
815ac31
fix: refactored code
rsarika Oct 14, 2024
843cc57
fix: refactored code to use paramaters as objects
rsarika Oct 16, 2024
e02f66f
fix: removed deviceRegistrationRequired as its not required
rsarika Oct 16, 2024
161db04
fix: added definition of once to get ready event
rsarika Oct 16, 2024
c3ac347
fix: refactored code to rename Mercury to WebScoket for better readab…
rsarika Oct 17, 2024
e885207
fix: refactor code
rsarika Oct 17, 2024
536a413
fix: enhanced findServiceUrlFromUrl to check hostname instead of star…
rsarika Oct 21, 2024
e130a51
fix: refactored code
rsarika Oct 21, 2024
dd61006
fix: refactored code to handle registerConnect errors
rsarika Oct 21, 2024
c523d49
fix: refactored code to handle off events
rsarika Oct 21, 2024
41ff18d
fix: added comments to explain changes in socket-base
rsarika Oct 21, 2024
03c6043
fix: updated cc.ts test cases
rsarika Oct 21, 2024
04a6646
fix: refactored code to remove .d file
rsarika Oct 21, 2024
99d56af
fix: updated testcases of mercury to test _onmessage and _getEventHa…
rsarika Oct 21, 2024
3b86995
fix: updated testcases of mercury to test _onmessage and _getEventHa…
rsarika Oct 21, 2024
90173ca
fix: refactored code
rsarika Oct 22, 2024
1f65a69
fix: refactored code
rsarika Oct 22, 2024
83c4836
fix: refactored code and added UT's
rsarika Oct 22, 2024
8c258d6
fix: added testcases to service-catalog and fixed findServiceUrlFromU…
rsarika Oct 22, 2024
fabe038
fix: updated test cases
rsarika Oct 22, 2024
c6a0a57
fix: updated tests
rsarika Oct 22, 2024
aa52e00
fix: added ts doc for websocket interface and its members
rsarika Oct 22, 2024
a9f6011
fix: removed undefined check as without datachannelUrl it wont reach…
rsarika Oct 22, 2024
ace98bc
fix: added unregister to IContactCenter
rsarika Oct 22, 2024
448f7d4
fix: fixed mercury connection and converted tests to jest
rsarika Oct 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/samples/contact-center/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ function initWebex(e) {
credentialsFormElm.addEventListener('submit', initWebex);

function register() {
webex.cc.register(true).then((data) => {
webex.cc.register().then((data) => {
console.log('Event subscription successful: ', data);
}).catch(() => {
console.log('Event subscription failed');
Expand Down
21 changes: 14 additions & 7 deletions packages/@webex/internal-plugin-mercury/src/mercury.js
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,8 @@ const Mercury = WebexPlugin.extend({
token: token.toString(),
trackingId: `${this.webex.sessionId}_${Date.now()}`,
logger: this.logger,
authorizationRequired: this.config.authorizationRequired ?? true,
acknowledgementRequired: this.config.acknowledgementRequired ?? true,
};

// if the consumer has supplied request options use them
Expand Down Expand Up @@ -399,8 +401,11 @@ const Mercury = WebexPlugin.extend({
},

_getEventHandlers(eventType) {
const [namespace, name] = eventType.split('.');
const handlers = [];
if (!eventType) {
Kesari3008 marked this conversation as resolved.
Show resolved Hide resolved
return handlers;
Copy link
Collaborator

Choose a reason for hiding this comment

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

can you add comments on what is a handler and how it looks like

Copy link
Contributor Author

Choose a reason for hiding this comment

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

example of handlers w.r.t existing mercury
[
{
"name": "processKmsMessageEvent",
"namespace": "encryption"
}
]

Copy link
Contributor Author

@rsarika rsarika Oct 17, 2024

Choose a reason for hiding this comment

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

_getEventHandlers(eventType) {
    const handlers = [];
    if (!eventType) {
      return handlers;
    }
    const [namespace, name] = eventType.split('.');

    if (!this.webex[namespace] && !this.webex.internal[namespace]) {
      return handlers;
    }

    const handlerName = camelCase(`process_${name}_event`);

    if ((this.webex[namespace] || this.webex.internal[namespace])[handlerName]) {
      handlers.push({
        name: handlerName,
        namespace,
      });
    }

    return handlers;
  },

Copy link
Contributor

Choose a reason for hiding this comment

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

Please fix the above comment.. looks like half of it is in code while the other half is text.

}
const [namespace, name] = eventType.split('.');

if (!this.webex[namespace] && !this.webex.internal[namespace]) {
return handlers;
Expand Down Expand Up @@ -511,13 +516,15 @@ const Mercury = WebexPlugin.extend({
)
.then(() => {
this._emit('event', event.data);
const [namespace] = data.eventType.split('.');
if (data.eventType) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

just a check for data.eventType to avoid undefined/null error

const [namespace] = data.eventType.split('.');
sreenara marked this conversation as resolved.
Show resolved Hide resolved

if (namespace === data.eventType) {
this._emit(`event:${namespace}`, envelope);
} else {
this._emit(`event:${namespace}`, envelope);
this._emit(`event:${data.eventType}`, envelope);
if (namespace === data.eventType) {
this._emit(`event:${namespace}`, envelope);
} else {
this._emit(`event:${namespace}`, envelope);
this._emit(`event:${data.eventType}`, envelope);
}
}
})
.catch((reason) => {
Expand Down
44 changes: 33 additions & 11 deletions packages/@webex/internal-plugin-mercury/src/socket/socket-base.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ export default class Socket extends EventEmitter {
* @param {string} options.token (required)
* @param {string} options.trackingId (required)
* @param {Logger} options.logger (required)
* @param {boolean} options.authorizationRequired
* @param {boolean} options.acknowledgementRequired
rsarika marked this conversation as resolved.
Show resolved Hide resolved
* @param {string} options.logLevelToken
* @returns {Promise}
*/
Expand Down Expand Up @@ -210,7 +212,18 @@ export default class Socket extends EventEmitter {
options
);

Object.keys(options).forEach((key) => {
// Destructure and set default values for authorizationRequired and acknowledgementRequired
const {
authorizationRequired = true,
acknowledgementRequired = true,
...remainingOptions
} = options;

this.authorizationRequired = authorizationRequired;
this.acknowledgementRequired = acknowledgementRequired;

// Assign the remaining options to the instance
Object.keys(remainingOptions).forEach((key) => {
Reflect.defineProperty(this, key, {
enumerable: false,
value: options[key],
Expand Down Expand Up @@ -250,13 +263,18 @@ export default class Socket extends EventEmitter {

socket.onopen = () => {
this.logger.info(`socket,${this._domain}: connected`);
this._authorize()
.then(() => {
this.logger.info(`socket,${this._domain}: authorized`);
socket.onclose = this.onclose;
resolve();
})
.catch(reject);
// Added the "authorizationRequired" condition to bypass the "_authorize" in case of the contact center context, in which case it is configured as false.
if (this.authorizationRequired) {
sreenara marked this conversation as resolved.
Show resolved Hide resolved
this._authorize()
.then(() => {
this.logger.info(`socket,${this._domain}: authorized`);
socket.onclose = this.onclose;
resolve();
})
.catch(reject);
} else {
this._ping();
}
};

socket.onerror = (event) => {
Expand Down Expand Up @@ -310,9 +328,13 @@ export default class Socket extends EventEmitter {
// modified and we don't actually care about anything but the data property
const processedEvent = {data};

this._acknowledge(processedEvent);
if (data.type === 'pong') {
this.emit('pong', processedEvent);
// Added the "acknowledgementRequired" condition to bypass the "_acknowledge" in case of the contact center context, in which case it is configured as false.
if (this.acknowledgementRequired) {
sreenara marked this conversation as resolved.
Show resolved Hide resolved
this._acknowledge(processedEvent);
}
if (data.type === 'pong' || data.type === 'ping') {
sreenara marked this conversation as resolved.
Show resolved Hide resolved
// added above ping condition to handle pong messages of contact center where type is 'ping' instead of 'pong'
this.emit('pong', {...processedEvent, type: 'pong'});
} else {
this.emit('message', processedEvent);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,21 @@ describe('plugin-mercury', () => {
return res;
});
});

it('_onmessage without eventType', () => {
sinon.spy(mercury, '_getEventHandlers');
sinon.spy(mercury, '_emit');
const event = {data: {data: {eventType: undefined, mydata: 'some data'}}};
mercury.logger.error.restore();
sinon.stub(mercury.logger, 'error');
return Promise.resolve(mercury._onmessage(event)).then(() => {
assert.calledWith(mercury._getEventHandlers, undefined);
assert.calledWith(mercury._emit, 'event', event.data);
assert.notCalled(mercury.logger.error);
mercury._emit.restore();
mercury._getEventHandlers.restore();
});
});
});

describe('#_applyOverrides()', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ describe('plugin-mercury', () => {
));

it('accepts a logLevelToken option', () => {
const acknowledgeSpy = sinon.spy(socket, '_acknowledge');
const promise = socket.open('ws://example.com', {
forceCloseDelay: mockoptions.forceCloseDelay,
pingInterval: mockoptions.pingInterval,
Expand All @@ -147,6 +148,7 @@ describe('plugin-mercury', () => {
token: 'mocktoken',
trackingId: 'mocktrackingid',
logLevelToken: 'mocklogleveltoken',
acknowledgementRequired: true,
});

mockWebSocket.readyState = 1;
Expand All @@ -162,9 +164,83 @@ describe('plugin-mercury', () => {
});

return promise.then(() => {
assert.called(acknowledgeSpy);
assert.equal(socket.logLevelToken, 'mocklogleveltoken');
});
});

it('accepts acknowledgementRequired option as false and skip acknowledge', () => {
const acknowledgeSpy = sinon.spy(socket, '_acknowledge');
const promise = socket.open('ws://example.com', {
forceCloseDelay: mockoptions.forceCloseDelay,
pingInterval: mockoptions.pingInterval,
pongTimeout: mockoptions.pongTimeout,
logger: console,
token: 'mocktoken',
trackingId: 'mocktrackingid',
logLevelToken: 'mocklogleveltoken',
acknowledgementRequired: false,
});

mockWebSocket.readyState = 1;
mockWebSocket.emit('open');

mockWebSocket.emit('message', {
data: JSON.stringify({
id: uuid.v4(),
data: {
eventType: 'mercury.buffer_state',
},
}),
});

return promise.then(() => {
assert.notCalled(acknowledgeSpy);
assert.equal(socket.logLevelToken, 'mocklogleveltoken');
});
});

it('accepts authorizationRequired option as false and skip authorize', () => {
const s = new Socket();
const authorizeSpy = sinon.spy(socket, '_authorize');
socket.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);
Kesari3008 marked this conversation as resolved.
Show resolved Hide resolved
assert.called(socket._ping);
});

it('accepts authorizationRequired option as true and calles authorize', () => {
const s = new Socket();
const authorizeSpy = sinon.spy(socket, '_authorize');
socket.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);
});
});

describe('#binaryType', () => {
Expand Down
13 changes: 13 additions & 0 deletions packages/@webex/plugin-cc/src/WebSocket/IWebSocket.ts
sreenara marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {WebSocketEvent} from '../types';

interface IWebSocket {
sreenara marked this conversation as resolved.
Show resolved Hide resolved
on(event: string, callback: (event: WebSocketEvent) => void): void;
off(event: string, callback: (event: WebSocketEvent) => void): void;
subscribeAndConnect(params: {datachannelUrl: string; body: object}): Promise<void>;
isConnected(): boolean;
disconnectWebSocket(): Promise<void>;
getSubscriptionId(): string | undefined;
getDatachannelUrl(): string | undefined;
}

export default IWebSocket;
Loading
Loading