From 5113b9320be1b6fba5f8d7a46fa0fabe72b1b5d4 Mon Sep 17 00:00:00 2001 From: Sreekanth Narayanan Date: Fri, 24 May 2024 02:06:37 +0530 Subject: [PATCH 1/4] feat(plugin-presence): preliminary changes to upgrade to v2 --- packages/@webex/plugin-presence/src/index.ts | 5 +- .../plugin-presence/src/presence-batcher.ts | 2 +- .../plugin-presence/src/presence-worker.ts | 36 +++--- .../@webex/plugin-presence/src/presence.ts | 107 +++++++++--------- .../src/{interface.ts => presence.type.ts} | 67 ++++++++--- 5 files changed, 129 insertions(+), 88 deletions(-) rename packages/@webex/plugin-presence/src/{interface.ts => presence.type.ts} (60%) diff --git a/packages/@webex/plugin-presence/src/index.ts b/packages/@webex/plugin-presence/src/index.ts index 95b82cd29ca..418f76c6b80 100644 --- a/packages/@webex/plugin-presence/src/index.ts +++ b/packages/@webex/plugin-presence/src/index.ts @@ -9,7 +9,7 @@ import {has} from 'lodash'; import Presence from './presence'; import config from './config'; -import {IPredicate, IResponse, ITransform} from './interface'; +import {IPredicate, IResponse, ITransform} from './presence.type'; registerPlugin('presence', Presence, { payloadTransformer: { @@ -18,6 +18,9 @@ registerPlugin('presence', Presence, { name: 'normalizeSingleStatusResponse', direction: 'inbound', test(ctx, response: IResponse) { + /** + * TODO: Might have to change this. + */ // POST to /apheleia/api/v1/events return Promise.resolve(has(response, 'body.eventType') && has(response, 'body.subject')); }, diff --git a/packages/@webex/plugin-presence/src/presence-batcher.ts b/packages/@webex/plugin-presence/src/presence-batcher.ts index 00181ad1e98..a3cd779bed2 100644 --- a/packages/@webex/plugin-presence/src/presence-batcher.ts +++ b/packages/@webex/plugin-presence/src/presence-batcher.ts @@ -3,7 +3,7 @@ */ import {Batcher} from '@webex/webex-core'; -import {IPresenceBatcher} from './interface'; +import {IPresenceBatcher} from './presence.type'; /** * @class diff --git a/packages/@webex/plugin-presence/src/presence-worker.ts b/packages/@webex/plugin-presence/src/presence-worker.ts index e66f80418c9..06d30ab538e 100644 --- a/packages/@webex/plugin-presence/src/presence-worker.ts +++ b/packages/@webex/plugin-presence/src/presence-worker.ts @@ -1,6 +1,6 @@ import {debounce} from 'lodash'; -import {IGenericKeyValue, IWebex} from './interface'; +import {IGenericKeyValue, IWebex} from './presence.type'; import { FETCH_DELAY, GROUNDSKEEPER_INTERVAL, @@ -144,21 +144,25 @@ export default class PresenceWorker { Object.assign(this.flights, boarding); this.fetchers = {}; - this.webex.presence.list(Object.keys(boarding)).then((response) => { - const now = new Date().getTime(); - - response.statusList.forEach((presence) => { - const id = presence.subject; - - delete this.flights[id]; - this.presences[id] = now; - }); - - this.webex.presence.emitEvent(PRESENCE_UPDATE, { - type: ENVELOPE_TYPE.PRESENCE, - payload: response, - }); - }); + /** + * TODO: webex.presence.list is no longer present. How do we handle this? + * Should we just subscribe? + */ + // this.webex.presence.list(Object.keys(boarding)).then((response) => { + // const now = new Date().getTime(); + + // response.statusList.forEach((presence) => { + // const id = presence.subject; + + // delete this.flights[id]; + // this.presences[id] = now; + // }); + + // this.webex.presence.emitEvent(PRESENCE_UPDATE, { + // type: ENVELOPE_TYPE.PRESENCE, + // payload: response, + // }); + // }); } debouncedFetch = debounce(this.checkFetchers, FETCH_DELAY); diff --git a/packages/@webex/plugin-presence/src/presence.ts b/packages/@webex/plugin-presence/src/presence.ts index 55df070a032..d2655ee248f 100644 --- a/packages/@webex/plugin-presence/src/presence.ts +++ b/packages/@webex/plugin-presence/src/presence.ts @@ -8,7 +8,7 @@ import {WebexPlugin} from '@webex/webex-core'; import PresenceBatcher from './presence-batcher'; import PresenceWorker from './presence-worker'; -import {IEventPayload, IPresence, IPresenceStatusObject} from './interface'; +import {IEventPayload, IPresence} from './presence.type'; const defaultSubscriptionTtl = 600; const USER = 'user'; @@ -87,62 +87,65 @@ const Presence: IPresence = WebexPlugin.extend({ }, /** - * The status object - * @typedef {Object} PresenceStatusObject - * @property {string} url: Public resource identifier for presence - * @property {string} subject: User ID for the user the returned composed presence represents - * @property {string} status: Current composed presence state - * @property {string} statusTime: DateTime in RFC3339 format that the current status began - * @property {string} lastActive: DateTime in RFC3339 format that the service last saw activity from the user. - * @property {string} expires: DEPRECATED - DateTime in RFC3339 format that represents when the current - * status will expire. Will not exist if expiresTTL is -1. - * @property {Number} expiresTTL: TTL in seconds until the status will expire. If TTL is -1 the current - * status has no known expiration. - * @property {string} expiresTime: DateTime in RFC3339 format that the current status will expire. Missing - * field means no known expiration. - * @property {Object} vectorCounters: Used for packet ordering and tracking. - * @property {Boolean} suppressNotifications: Indicates if notification suppression is recommended for this status. - * @property {string} lastSeenDeviceUrl: Resource Identifier of the last device to post presence activity for - * this user. + * TODO: We decided to remove this. */ + // /** + // * The status object + // * @typedef {Object} PresenceStatusObject + // * @property {string} url: Public resource identifier for presence + // * @property {string} subject: User ID for the user the returned composed presence represents + // * @property {string} status: Current composed presence state + // * @property {string} statusTime: DateTime in RFC3339 format that the current status began + // * @property {string} lastActive: DateTime in RFC3339 format that the service last saw activity from the user. + // * @property {string} expires: DEPRECATED - DateTime in RFC3339 format that represents when the current + // * status will expire. Will not exist if expiresTTL is -1. + // * @property {Number} expiresTTL: TTL in seconds until the status will expire. If TTL is -1 the current + // * status has no known expiration. + // * @property {string} expiresTime: DateTime in RFC3339 format that the current status will expire. Missing + // * field means no known expiration. + // * @property {Object} vectorCounters: Used for packet ordering and tracking. + // * @property {Boolean} suppressNotifications: Indicates if notification suppression is recommended for this status. + // * @property {string} lastSeenDeviceUrl: Resource Identifier of the last device to post presence activity for + // * this user. + // */ - /** - * Gets the current presence status of a given person id - * @param {string} personId - * @returns {Promise} resolves with status object of person - */ - get(personId: string): Promise { - if (!personId) { - return Promise.reject(new Error('A person id is required')); - } + // /** + // * Gets the current presence status of a given person id + // * @param {string} personId + // * @returns {Promise} resolves with status object of person + // */ + // get(personId: string): Promise { + // if (!personId) { + // return Promise.reject(new Error('A person id is required')); + // } - return this.webex - .request({ - method: 'GET', - service: 'apheleia', - resource: `compositions?userId=${personId}`, - }) - .then((response) => response.body); - }, + // return this.webex + // .request({ + // method: 'GET', + // service: 'apheleia', + // resource: `compositions?userId=${personId}`, + // }) + // .then((response) => response.body); + // }, - /** - * @typedef {Object} PresenceStatusesObject - * @property {Array.} statusList - */ - /** - * Gets the current presence statuses of an array of people ids - * @param {Array} personIds - * @returns {Promise} resolves with an object with key of `statusList` array - */ - list(personIds: string[]): Promise<{statusList: IPresenceStatusObject[]}> { - if (!personIds || !Array.isArray(personIds)) { - return Promise.reject(new Error('An array of person ids is required')); - } + // /** + // * @typedef {Object} PresenceStatusesObject + // * @property {Array.} statusList + // */ + // /** + // * Gets the current presence statuses of an array of people ids + // * @param {Array} personIds + // * @returns {Promise} resolves with an object with key of `statusList` array + // */ + // list(personIds: string[]): Promise<{statusList: IPresenceStatusObject[]}> { + // if (!personIds || !Array.isArray(personIds)) { + // return Promise.reject(new Error('An array of person ids is required')); + // } - return Promise.all(personIds.map((id) => this.batcher.request(id))).then((presences) => ({ - statusList: presences, - })); - }, + // return Promise.all(personIds.map((id) => this.batcher.request(id))).then((presences) => ({ + // statusList: presences, + // })); + // }, /** * Subscribes to a person's presence status updates diff --git a/packages/@webex/plugin-presence/src/interface.ts b/packages/@webex/plugin-presence/src/presence.type.ts similarity index 60% rename from packages/@webex/plugin-presence/src/interface.ts rename to packages/@webex/plugin-presence/src/presence.type.ts index 4f8bc74c8ee..926808cb218 100644 --- a/packages/@webex/plugin-presence/src/interface.ts +++ b/packages/@webex/plugin-presence/src/presence.type.ts @@ -1,25 +1,53 @@ -export enum PresenceStatus { - ACTIVE = 'active', - CALENDAR_ITEM = 'calendarItem', - CALL = 'call', +export enum Operation { CLEAR = 'clear', + SET = 'set', +} + +export enum Availability { + AVAILABLE = 'available', + AWAY = 'away', + BUSY = 'busy', DND = 'dnd', - INACTIVE = 'inactive', +} + +export enum WorkStatus { + CALENDAR_ITEM = 'calendarItem', + CALL = 'call', MEETING = 'meeting', - OOO = 'ooo', PRESENTING = 'presenting', + OOO = 'ooo', } -export interface IPresenceStatusObject { - url: string; - subject: string; - status: PresenceStatus; - statusTime: string; - lastActive: string; - expiresTTL: number; - vectorCounters: object; - suppressNotifications: boolean; -} +export type PresenceResponse = { + operation: Operation; + type: WorkStatus; + label: string; + ttlSecs: number; + expires: string; +}; + +// export enum PresenceStatus { +// ACTIVE = 'active', +// CALENDAR_ITEM = 'calendarItem', +// CALL = 'call', +// CLEAR = 'clear', +// DND = 'dnd', +// INACTIVE = 'inactive', +// MEETING = 'meeting', +// OOO = 'ooo', +// PRESENTING = 'presenting', +// } + +// export interface IPresenceStatusObject { +// url: string; +// subject: string; +// status: PresenceStatus; +// statusTime: string; +// lastActive: string; +// expiresTTL: number; +// vectorCounters: object; +// suppressNotifications: boolean; +// } export interface IEventPayload { type: string; @@ -32,11 +60,14 @@ export interface IPresence { enable(): Promise; disable(): Promise; isEnabled(): Promise; - get(personId: string): Promise; - list(personIds: string[]): Promise<{statusList: IPresenceStatusObject[]}>; + // get(personId: string): Promise; + // list(personIds: string[]): Promise<{statusList: IPresenceStatusObject[]}>; subscribe(personIds: string | string[], subscriptionTtl?: number): Promise<{responses: any[]}>; unsubscribe(personIds: string | string[]): Promise<{responses: any}>; setStatus(status: string, ttl: number): Promise; + setAvailability(availability: Availability): Promise; + setWorkStatus(workStatus: WorkStatus): Promise; + setActive(): Promise; enqueue(id: string): void; dequeue(id: string): void; } From 9c9c1d6e18fb0401c75bb409f4abac600d98ea1f Mon Sep 17 00:00:00 2001 From: Shreyas Sharma Date: Fri, 24 May 2024 20:41:12 +0530 Subject: [PATCH 2/4] feat(presence): setactive-setworkstatus-setavailability --- .../@webex/plugin-presence/src/presence.ts | 93 ++++++++++++++++++- .../plugin-presence/src/presence.type.ts | 7 ++ 2 files changed, 99 insertions(+), 1 deletion(-) diff --git a/packages/@webex/plugin-presence/src/presence.ts b/packages/@webex/plugin-presence/src/presence.ts index d2655ee248f..c11f68fef83 100644 --- a/packages/@webex/plugin-presence/src/presence.ts +++ b/packages/@webex/plugin-presence/src/presence.ts @@ -8,7 +8,14 @@ import {WebexPlugin} from '@webex/webex-core'; import PresenceBatcher from './presence-batcher'; import PresenceWorker from './presence-worker'; -import {IEventPayload, IPresence} from './presence.type'; +import { + Availability, + IEventPayload, + IPresence, + Operation, + PresenceRequestBody, + WorkStatus, +} from './presence.type'; const defaultSubscriptionTtl = 600; const USER = 'user'; @@ -147,6 +154,90 @@ const Presence: IPresence = WebexPlugin.extend({ // })); // }, + /** + * + * @returns {Promise} resolves with an object with key of `statusList` array + */ + setActive(): Promise { + return this.webex.request({ + method: 'POST', + service: 'apheleiaV2', + resource: `activity`, + }); + }, + + /** + * + * @param {Operation} operation + * @param {WorkStatus} status + * @returns {Promise} resolves with an object with key of `statusList` array + */ + setWorkstatus(operation: Operation, status?: WorkStatus): Promise { + let body: PresenceRequestBody; + + if (operation === Operation.SET) { + if (!status) { + return null; // TODO: Need to have better handling for this. + } + body = { + operation, + type: status, + ttlSecs: 10, + }; + } else { + body = { + operation, + }; + } + + return this.webex.request({ + method: 'POST', + api: 'apheleiaV2', + resource: 'workStatus', + body, + }); + }, + + /** + * + * @param {Operation} operation + * @param {String} label + * @param {Availability} availabilityStatus + * @returns {Promise} + */ + setAvailability( + operation: Operation, + label: string, + availabilityStatus?: Availability + ): Promise { + let body: PresenceRequestBody; + + if (operation === Operation.SET) { + if (!availabilityStatus) { + return null; // TODO: Need to have better handling for this. + } + body = { + operation, + type: availabilityStatus, + ttlSecs: 10, + label, + }; + } else { + body = { + operation, + label, + }; + } + + // This endpoint is still not available for all users. + return this.webex.request({ + method: 'POST', + api: 'apheleiaV2', + resource: 'availability', + body, + }); + }, + /** * Subscribes to a person's presence status updates * Updates are sent via mercury events `apheleia.subscription_update` diff --git a/packages/@webex/plugin-presence/src/presence.type.ts b/packages/@webex/plugin-presence/src/presence.type.ts index 926808cb218..86897d49b4f 100644 --- a/packages/@webex/plugin-presence/src/presence.type.ts +++ b/packages/@webex/plugin-presence/src/presence.type.ts @@ -18,6 +18,13 @@ export enum WorkStatus { OOO = 'ooo', } +export type PresenceRequestBody = { + operation: Operation; + type?: WorkStatus | Availability; + ttlSecs?: number; + label?: string; +}; + export type PresenceResponse = { operation: Operation; type: WorkStatus; From 909667968816f13fb978c63ea214525acd1931f7 Mon Sep 17 00:00:00 2001 From: Shreyas Sharma Date: Fri, 24 May 2024 20:44:40 +0530 Subject: [PATCH 3/4] feat(presence): add-comment --- packages/@webex/plugin-presence/src/presence.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@webex/plugin-presence/src/presence.ts b/packages/@webex/plugin-presence/src/presence.ts index c11f68fef83..a1b94fa448a 100644 --- a/packages/@webex/plugin-presence/src/presence.ts +++ b/packages/@webex/plugin-presence/src/presence.ts @@ -207,7 +207,7 @@ const Presence: IPresence = WebexPlugin.extend({ */ setAvailability( operation: Operation, - label: string, + label: string, // This label can be deviceUrl, need to test and then we can use deviceUrl as the label here. availabilityStatus?: Availability ): Promise { let body: PresenceRequestBody; From 6bec3c67f6923449de43d24bf6c87825f5106231 Mon Sep 17 00:00:00 2001 From: Sreekanth Narayanan Date: Wed, 29 May 2024 02:38:32 +0530 Subject: [PATCH 4/4] docs(presence): added documentation for enums, interfaces and methods --- packages/@webex/plugin-presence/README.md | 4 +- packages/@webex/plugin-presence/src/api.ts | 7 ++ .../@webex/plugin-presence/src/presence.ts | 8 +- .../plugin-presence/src/presence.type.ts | 97 +++++++++++++++++++ packages/@webex/plugin-presence/tsconfig.json | 2 +- 5 files changed, 112 insertions(+), 6 deletions(-) create mode 100644 packages/@webex/plugin-presence/src/api.ts diff --git a/packages/@webex/plugin-presence/README.md b/packages/@webex/plugin-presence/README.md index 2f9151ac5e7..d031d7a4cb4 100644 --- a/packages/@webex/plugin-presence/README.md +++ b/packages/@webex/plugin-presence/README.md @@ -2,7 +2,7 @@ [![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 Presence service +Plugin for the Presence service in Webex. The presence plugin provides a set of methods to work with compositions as well as subscriptions of the presence status of users. - [Install](#install) - [Usage](#usage) @@ -33,7 +33,7 @@ This package is maintained by [Cisco Webex for Developers](https://developer.web ## Contribute -Pull requests are welcome. Please see [CONTRIBUTING.md](https://github.com/webex/webex-js-sdk/blob/master/CONTRIBUTING.md) for more details. +Pull requests are welcome. Please see [CONTRIBUTING.md](https://github.com/webex/webex-js-sdk/blob/next/CONTRIBUTING.md) for more details. ## License diff --git a/packages/@webex/plugin-presence/src/api.ts b/packages/@webex/plugin-presence/src/api.ts new file mode 100644 index 00000000000..c3ad13f4be8 --- /dev/null +++ b/packages/@webex/plugin-presence/src/api.ts @@ -0,0 +1,7 @@ +import Presence from './presence'; +import PresenceBatcher from './presence-batcher'; +import PresenceWorker from './presence-worker'; + +export * from './presence.type'; + +export {Presence, PresenceBatcher, PresenceWorker}; diff --git a/packages/@webex/plugin-presence/src/presence.ts b/packages/@webex/plugin-presence/src/presence.ts index a1b94fa448a..1f84eb15230 100644 --- a/packages/@webex/plugin-presence/src/presence.ts +++ b/packages/@webex/plugin-presence/src/presence.ts @@ -22,6 +22,8 @@ const USER = 'user'; const USER_PRESENCE_ENABLED = 'user-presence-enabled'; /** + * Class for the presence plugin. The presence plugin provides a set of methods to work with + * compositions as well as subscriptions of the presence status of users. * @class * @extends WebexPlugin */ @@ -54,9 +56,9 @@ const Presence: IPresence = WebexPlugin.extend({ }, /** - * Trigger an event. - * @param {string} event - * @param {string} payload + * Emits an event to the listener. + * @param {string} event - name of the event emitted. + * @param {string} payload - payload of the event emitted. * @returns {undefined} */ emitEvent(event: string, payload: IEventPayload): void { diff --git a/packages/@webex/plugin-presence/src/presence.type.ts b/packages/@webex/plugin-presence/src/presence.type.ts index 86897d49b4f..f2d0d03bc8f 100644 --- a/packages/@webex/plugin-presence/src/presence.type.ts +++ b/packages/@webex/plugin-presence/src/presence.type.ts @@ -1,20 +1,53 @@ export enum Operation { + /** + * Clear the presence status of the user. + */ CLEAR = 'clear', + /** + * Set the presence status of the user. + */ SET = 'set', } export enum Availability { + /** + * The user is available. + */ AVAILABLE = 'available', + /** + * The user is away. + */ AWAY = 'away', + /** + * The user is busy. + */ BUSY = 'busy', + /** + * The user is in do-not-disturb mode. + */ DND = 'dnd', } export enum WorkStatus { + /** + * The user is currently in a calendar event. + */ CALENDAR_ITEM = 'calendarItem', + /** + * The user is currently in a call. + */ CALL = 'call', + /** + * The user is currently in a meeting. + */ MEETING = 'meeting', + /** + * The user is currently presenting. + */ PRESENTING = 'presenting', + /** + * The user is currently out of office. + */ OOO = 'ooo', } @@ -61,21 +94,85 @@ export interface IEventPayload { payload: any; } +/** + * Interface for the presence plugin. The presence plugin provides a set of methods to work with + * compositions as well as subscriptions of the presence status of users. + */ export interface IPresence { + /** + * Initialize the presence worker for client + * @returns {undefined} + */ initialize(): void; + /** + * Emits an event to the listener. + * @param event - name of the event emitted. + * @param payload - payload of the event emitted. + */ emitEvent(event: string, payload: IEventPayload): void; + /** + * Enables presence feature + * @returns {Promise} resolves with true, if successful + */ enable(): Promise; + /** + * Disables presence feature + * @returns {Promise} resolves with false, if successful + */ disable(): Promise; + /** + * Returns true if presence is enabled, false otherwise + * @returns {Promise} resolves with true if presence is enabled + */ isEnabled(): Promise; // get(personId: string): Promise; // list(personIds: string[]): Promise<{statusList: IPresenceStatusObject[]}>; + /** + * Subscribes to a person's presence status updates + * Updates are sent via mercury events `apheleia.subscription_update` + * @param {string | Array} personIds + * @param {number} subscriptionTtl - Requested length of subscriptions in seconds. + * @returns {Promise} + */ subscribe(personIds: string | string[], subscriptionTtl?: number): Promise<{responses: any[]}>; + /** + * Unsubscribes from a person or group of people's presence subscription + * @param {string | Array} personIds + * @returns {Promise} + */ unsubscribe(personIds: string | string[]): Promise<{responses: any}>; + /** + * Set the status for this user. Use this method if you're not interested in granular control over + * the activity, availability and workstatus. + * @param status - string identifying the status of the user. TODO: Maybe this is the wrong type? + * @param ttl - duration of the status that needs to be set. + */ setStatus(status: string, ttl: number): Promise; + /** + * Sets the availability of the user. + * @param availability - is the user available, away, busy or dnd. + */ setAvailability(availability: Availability): Promise; + /** + * Sets the work status of the user. + * @param workStatus - is the user in a calendar event, call, meeting, presenting or out-of-office. + */ setWorkStatus(workStatus: WorkStatus): Promise; + /** + * Signals that the user is currently active. + */ setActive(): Promise; + /** + * Retrieves and subscribes to a user's presence. + * @param {string} id + * @returns {undefined} + */ enqueue(id: string): void; + /** + * Retract from subscribing to a user's presence. + * @param {string} id + * @returns {undefined} + */ dequeue(id: string): void; } diff --git a/packages/@webex/plugin-presence/tsconfig.json b/packages/@webex/plugin-presence/tsconfig.json index aa8ca880ded..b047e5c6684 100644 --- a/packages/@webex/plugin-presence/tsconfig.json +++ b/packages/@webex/plugin-presence/tsconfig.json @@ -3,7 +3,7 @@ "include": ["src"], "typedocOptions": { "entryPoints": [ - "./src/interface.ts" + "./src/api.ts" ], "sort": [ "source-order"