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(internal-plugin-usersub): add usersub plugin to allow the publis… #3936

Open
wants to merge 1 commit into
base: next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const packages = [
'@webex/internal-plugin-support',
'@webex/internal-plugin-team',
'@webex/internal-plugin-user',
'@webex/internal-plugin-usersub',
'@webex/internal-plugin-voicea',
'@webex/internal-plugin-wdm',
'@webex/jsdoctrinetest',
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
},
"scripts": {
"@all": "yarn @workspaces run",
"@legacy": "yarn @workspaces --from \"{@webex/common,@webex/common-evented,@webex/common-timers,@webex/helper-html,@webex/helper-image,@webex/http-core,@webex/internal-plugin-avatar,@webex/internal-plugin-board,@webex/internal-plugin-calendar,@webex/internal-plugin-conversation,@webex/internal-plugin-device,@webex/internal-plugin-dss,@webex/internal-plugin-ediscovery,@webex/internal-plugin-encryption,@webex/internal-plugin-feature,@webex/internal-plugin-flag,@webex/internal-plugin-llm,@webex/internal-plugin-locus,@webex/internal-plugin-lyra,@webex/internal-plugin-mercury,@webex/internal-plugin-metrics,@webex/internal-plugin-presence,@webex/internal-plugin-scheduler,@webex/internal-plugin-search,@webex/internal-plugin-support,@webex/internal-plugin-team,@webex/internal-plugin-user,@webex/internal-plugin-voicea,@webex/internal-plugin-wdm,@webex/jsdoctrinetest,@webex/media-helpers,@webex/plugin-attachment-actions,@webex/plugin-authorization,@webex/plugin-authorization-browser,@webex/plugin-authorization-browser-first-party,@webex/plugin-authorization-node,@webex/plugin-device-manager,@webex/plugin-logger,@webex/plugin-meetings,@webex/plugin-memberships,@webex/plugin-messages,@webex/plugin-people,@webex/plugin-presence,@webex/plugin-rooms,@webex/plugin-team-memberships,@webex/plugin-teams,@webex/plugin-webhooks,@webex/recipe-private-web-client,@webex/storage-adapter-local-forage,@webex/storage-adapter-local-storage,@webex/storage-adapter-session-storage,@webex/storage-adapter-spec,@webex/test-helper-appid,@webex/test-helper-automation,@webex/test-helper-chai,@webex/test-helper-file,@webex/test-helper-make-local-url,@webex/test-helper-mocha,@webex/test-helper-mock-web-socket,@webex/test-helper-mock-webex,@webex/test-helper-refresh-callback,@webex/test-helper-retry,@webex/test-helper-server,@webex/test-helper-test-users,@webex/test-users,@webex/webex-core,@webex/webex-server,@webex/webrtc,@webex/xunit-with-logs,webex}\" run",
"@legacy": "yarn @workspaces --from \"{@webex/common,@webex/common-evented,@webex/common-timers,@webex/helper-html,@webex/helper-image,@webex/http-core,@webex/internal-plugin-avatar,@webex/internal-plugin-board,@webex/internal-plugin-calendar,@webex/internal-plugin-conversation,@webex/internal-plugin-device,@webex/internal-plugin-dss,@webex/internal-plugin-ediscovery,@webex/internal-plugin-encryption,@webex/internal-plugin-feature,@webex/internal-plugin-flag,@webex/internal-plugin-llm,@webex/internal-plugin-locus,@webex/internal-plugin-lyra,@webex/internal-plugin-mercury,@webex/internal-plugin-metrics,@webex/internal-plugin-presence,@webex/internal-plugin-scheduler,@webex/internal-plugin-search,@webex/internal-plugin-support,@webex/internal-plugin-team,@webex/internal-plugin-user,@webex/internal-plugin-usersub,@webex/internal-plugin-voicea,@webex/internal-plugin-wdm,@webex/jsdoctrinetest,@webex/media-helpers,@webex/plugin-attachment-actions,@webex/plugin-authorization,@webex/plugin-authorization-browser,@webex/plugin-authorization-browser-first-party,@webex/plugin-authorization-node,@webex/plugin-device-manager,@webex/plugin-logger,@webex/plugin-meetings,@webex/plugin-memberships,@webex/plugin-messages,@webex/plugin-people,@webex/plugin-presence,@webex/plugin-rooms,@webex/plugin-team-memberships,@webex/plugin-teams,@webex/plugin-webhooks,@webex/recipe-private-web-client,@webex/storage-adapter-local-forage,@webex/storage-adapter-local-storage,@webex/storage-adapter-session-storage,@webex/storage-adapter-spec,@webex/test-helper-appid,@webex/test-helper-automation,@webex/test-helper-chai,@webex/test-helper-file,@webex/test-helper-make-local-url,@webex/test-helper-mocha,@webex/test-helper-mock-web-socket,@webex/test-helper-mock-webex,@webex/test-helper-refresh-callback,@webex/test-helper-retry,@webex/test-helper-server,@webex/test-helper-test-users,@webex/test-users,@webex/webex-core,@webex/webex-server,@webex/webrtc,@webex/xunit-with-logs,webex}\" run",
"@legacy-tools": "yarn @workspaces --topological-dev --from \"{@webex/babel-config-legacy,@webex/env-config-legacy,@webex/eslint-config-legacy,@webex/jest-config-legacy,@webex/legacy-tools}\" run",
"@tools": "yarn @workspaces --topological-dev --from \"{@webex/cli-tools,@webex/package-tools}\" run",
"@workspaces": "yarn workspaces foreach --parallel --verbose",
Expand Down
6 changes: 6 additions & 0 deletions packages/@webex/internal-plugin-usersub/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const config = {
root: true,
extends: ['@webex/eslint-config-legacy'],
};

module.exports = config;
42 changes: 42 additions & 0 deletions packages/@webex/internal-plugin-usersub/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# @webex/internal-plugin-usersub

[![standard-readme compliant](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme)

> Plugin for the UserSub services

This is an internal Cisco Webex plugin. As such, it does not strictly adhere to semantic versioning. Use at your own risk. If you're not working on one of our first party clients, please look at our [developer api](https://developer.webex.com/) and stick to our public plugins.

- [Install](#install)
- [Usage](#usage)
- [Contribute](#contribute)
- [Maintainers](#maintainers)
- [License](#license)

## Install

```bash
npm install --save @webex/internal-plugin-usersub
```

## Usage

```js
import '@webex/internal-plugin-usersub';

import WebexCore from '@webex/webex-core';

const webex = new WebexCore();
webex.internal.usersub.WHATEVER;
```

## Maintainers

This package is maintained by [Cisco Webex for Developers](https://developer.webex.com/).

## Contribute

Pull requests welcome. Please see [CONTRIBUTING.md](https://github.com/webex/webex-js-sdk/blob/master/CONTRIBUTING.md) for more details.

## License

© 2016-2020 Cisco and/or its affiliates. All Rights Reserved.
3 changes: 3 additions & 0 deletions packages/@webex/internal-plugin-usersub/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const babelConfigLegacy = require('@webex/babel-config-legacy');

module.exports = babelConfigLegacy;
3 changes: 3 additions & 0 deletions packages/@webex/internal-plugin-usersub/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const config = require('@webex/jest-config-legacy');

module.exports = config;
52 changes: 52 additions & 0 deletions packages/@webex/internal-plugin-usersub/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"name": "@webex/internal-plugin-usersub",
"description": "Internal plugin for managing user subscriptions in Webex SDK",
"license": "MIT",
"main": "dist/index.js",
"devMain": "src/index.js",
"repository": {
"type": "git",
"url": "https://github.com/webex/webex-js-sdk.git",
"directory": "packages/@webex/internal-plugin-usersub"
},
"engines": {
"node": ">=16"
},
"browserify": {
"transform": [
"babelify",
"envify"
]
},
"devDependencies": {
"@babel/core": "^7.17.10",
"@webex/babel-config-legacy": "workspace:*",
"@webex/eslint-config-legacy": "workspace:*",
"@webex/jest-config-legacy": "workspace:*",
"@webex/legacy-tools": "workspace:*",
"@webex/test-helper-chai": "workspace:*",
"@webex/test-helper-mocha": "workspace:*",
"@webex/test-helper-mock-webex": "workspace:*",
"@webex/test-helper-test-users": "workspace:*",
"eslint": "^8.24.0",
"prettier": "^2.7.1",
"sinon": "^9.2.4"
},
"dependencies": {
"@webex/common": "workspace:*",
"@webex/common-timers": "workspace:*",
"@webex/internal-plugin-device": "workspace:*",
"@webex/webex-core": "workspace:*",
"lodash": "^4.17.21",
"uuid": "^3.3.2"
},
"scripts": {
"build": "yarn build:src",
"build:src": "webex-legacy-tools build -dest \"./dist\" -src \"./src\" -js -ts -maps",
"deploy:npm": "yarn npm publish",
"test": "yarn test:style && yarn test:unit && yarn test:integration && yarn test:browser",
"test:browser:broken": "webex-legacy-tools test --integration --runner karma",
"test:style": "eslint ./src/**/*.*",
"test:unit": "webex-legacy-tools test --unit --runner jest"
}
mmulcair1981 marked this conversation as resolved.
Show resolved Hide resolved
}
1 change: 1 addition & 0 deletions packages/@webex/internal-plugin-usersub/process
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = {browser: true};
7 changes: 7 additions & 0 deletions packages/@webex/internal-plugin-usersub/src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*!
* Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
*/

export default {
userSub: {},
};
2 changes: 2 additions & 0 deletions packages/@webex/internal-plugin-usersub/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const EXPIRATION_OFFSET = 60 * 1000; // 1 minute in ms
export const USERSUB_SERVICE_NAME = 'usersub';
12 changes: 12 additions & 0 deletions packages/@webex/internal-plugin-usersub/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*!
* Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
*/

import {registerInternalPlugin} from '@webex/webex-core';

import Usersub from './usersub';
import config from './config';

registerInternalPlugin('usersub', Usersub, {config});

export {default} from './usersub';
151 changes: 151 additions & 0 deletions packages/@webex/internal-plugin-usersub/src/usersub.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/*!
* Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
*/

import '@webex/internal-plugin-device';

import {WebexPlugin} from '@webex/webex-core';
import {safeSetTimeout} from '@webex/common-timers';
import {EXPIRATION_OFFSET, USERSUB_SERVICE_NAME} from './constants';

/**
* @class
* @extends WebexPlugin
*/
const Usersub = WebexPlugin.extend({
namespace: 'Usersub',

session: {
crossClientState: {
type: 'object',
/**
* Returns a new Map instance as the default value for crossClientState.
* @returns {Map}
*/
default() {
return new Map();
},
},

refreshTimer: {
default: undefined,
type: 'any',
},
},

/**
* Sets the value for answer-calls-on-wxcc for the calling application.
* @param {boolean} enable - The state will be enabled/disabled.
* @param {string} appName - The app setting the state.
* @param {number} ttl - Time To Live for the event in seconds.
* @returns {Promise}
*/
updateAnswerCallsCrossClient(enable: boolean, appName: string, ttl: number) {
if (typeof enable !== 'boolean') {
return Promise.reject(new Error('Enable parameter must be a boolean'));
}

if (!appName) {
return Promise.reject(new Error('An appName is required'));
}

if (ttl <= 0) {
return Promise.reject(new Error('A positive ttl is required'));
}

const jsonData = {
users: [this.webex.internal.device.userId],
compositions: [
{
type: 'cross-client-state',
ttl,
composition: {
devices: [
{
deviceId: this._extractIdFromUrl(this.webex.internal.device.url),
appName,
Comment on lines +65 to +66
Copy link

@coderabbitai coderabbitai bot Oct 24, 2024

Choose a reason for hiding this comment

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

⚠️ Potential issue

Handle empty deviceId before sending the request

The deviceId extracted by _extractIdFromUrl could be an empty string if the device URL does not match the expected format. This may cause issues in the JSON payload sent to the server. Consider adding a check to handle the case when deviceId is empty or invalid.

Apply this diff to validate deviceId before sending the request:

+ const deviceId = this._extractIdFromUrl(this.webex.internal.device.url);
+ if (!deviceId) {
+   return Promise.reject(new Error('Device ID could not be extracted from device URL.'));
+ }

Then, update jsonData to use the deviceId variable:

- deviceId: this._extractIdFromUrl(this.webex.internal.device.url),
+ deviceId: deviceId,
📝 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.

Suggested change
deviceId: this._extractIdFromUrl(this.webex.internal.device.url),
appName,
const deviceId = this._extractIdFromUrl(this.webex.internal.device.url);
if (!deviceId) {
return Promise.reject(new Error('Device ID could not be extracted from device URL.'));
}
deviceId: deviceId,
appName,

Copy link
Author

Choose a reason for hiding this comment

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

deviceId can be empty - so not rejecting it in this case

Copy link

Choose a reason for hiding this comment

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

@mmulcair1981, understood. Thank you for the clarification.

(_/)
(•_•)
(>🌸<)


✏️ Learnings added
Learnt from: mmulcair1981
PR: webex/webex-js-sdk#3936
File: packages/@webex/internal-plugin-usersub/src/usersub.ts:65-66
Timestamp: 2024-10-24T08:50:23.497Z
Learning: In the `updateAnswerCallsCrossClient` method in the `Usersub` class (`packages/@webex/internal-plugin-usersub/src/usersub.ts`), the `deviceId` can be empty, and the request should not be rejected in this case.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

state: {
mmulcair1981 marked this conversation as resolved.
Show resolved Hide resolved
'answer-calls-on-wxcc': enable,
},
},
],
},
},
],
};

return this.webex
.request({
method: 'POST',
service: USERSUB_SERVICE_NAME,
resource: 'publish',
body: jsonData,
})
mmulcair1981 marked this conversation as resolved.
Show resolved Hide resolved
.then((response) => {
this.crossClientState.set(appName, enable);
this._startRefreshTimer(appName, ttl);

return response.body;
})
.catch((error) => {
this.logger.error(
`userSub: updateAnswerCallsCrossClient failed with error: ${error.message}`
);

return Promise.reject(error);
});
},

/**
* Starts the refresh timer for the cross-client state.
* @private
* @param {string} appName - The app setting the state.
* @param {number} ttl - Time To Live for the event in seconds.
* @returns {void}
*/
_startRefreshTimer(appName: string, ttl: number): void {
this._stopRefreshTimer();
const answerCallsState = this.crossClientState.get(appName);
if (answerCallsState) {
const refreshTime = ttl * 1000 - EXPIRATION_OFFSET;
if (refreshTime <= 0) {
this.logger.warn('Refresh time is non-positive, skipping timer setup.');

return;
}
this.refreshTimer = safeSetTimeout(
() => this.updateAnswerCallsCrossClient(answerCallsState, appName, ttl),
refreshTime
);
}
},

/**
* Stops the refresh timer for the cross-client state.
* @private
* @returns {void}
*/
_stopRefreshTimer(): void {
if (this.refreshTimer) {
clearTimeout(this.refreshTimer);
}
},

/**
* Extracts the device ID from a given URL.
* @param {string} url - The URL to extract the device ID from.
* @returns {string | null} The extracted device ID or null if not found.
*/
_extractIdFromUrl(url: string): string {
if (url) {
const regex = /\/devices\/([^/?]+)/;
const match = url.match(regex);

return match ? match[1] : '';
}

return '';
},
});

export default Usersub;
Loading
Loading