From a4fcba4f43428d05f666216b5e75220ec15a300b Mon Sep 17 00:00:00 2001 From: Aleksey Konstantinov Date: Fri, 2 Aug 2024 11:32:33 +0300 Subject: [PATCH] ULMS-3167 Updated api-clients, refactored api routes, removed deprecated --- __tests__/basic-client.test.js | 135 ----------------------- __tests__/error.test.js | 10 +- package-lock.json | 4 +- package.json | 2 +- src/basic-client.js | 52 ++++----- src/conference.js | 55 --------- src/dispatcher.js | 196 --------------------------------- src/error.js | 183 ++++++++++++++++++++++++++++-- src/event.js | 44 -------- src/fvs.js | 30 +++++ src/http-client.js | 36 ++---- src/index.js | 5 - src/portal.js | 13 --- src/presence-ws.js | 14 ++- src/presence.js | 48 ++++++++ src/profile-service.js | 6 +- src/profile.js | 65 ----------- src/redux/middleware.js | 8 +- src/retry.js | 11 +- src/service.js | 82 -------------- src/tenant.js | 83 +++++++++++--- src/token-provider.js | 26 ++++- src/ulms.js | 116 +++++++++++-------- 23 files changed, 476 insertions(+), 748 deletions(-) delete mode 100644 __tests__/basic-client.test.js delete mode 100644 src/conference.js delete mode 100644 src/dispatcher.js delete mode 100644 src/event.js delete mode 100644 src/portal.js delete mode 100644 src/profile.js delete mode 100644 src/service.js diff --git a/__tests__/basic-client.test.js b/__tests__/basic-client.test.js deleted file mode 100644 index 3eef475..0000000 --- a/__tests__/basic-client.test.js +++ /dev/null @@ -1,135 +0,0 @@ -import BasicClient from '../src/basic-client' - -const methodFunctionMock = jest.fn(() => - Promise.resolve({ data: {}, status: 200 }), -) -const baseUrl = 'https://test.url' -const httpClient = { - get: methodFunctionMock, - put: methodFunctionMock, - post: methodFunctionMock, - patch: methodFunctionMock, - delete: methodFunctionMock, -} -const tokenProvider = { - getToken: jest.fn().mockImplementation(() => 'token'), -} - -const headersWithoutLabels = { - authorization: 'Bearer token', - 'content-type': 'application/json', -} - -const Client = new BasicClient(baseUrl, httpClient, tokenProvider) - -const labels = { - app_audience: 'audience', - app_label: 'label', - app_version: 'version', - scope: 'scope', -} - -const ulmsLabels = { - 'ulms-app-audience': 'audience', - 'ulms-app-label': 'label', - 'ulms-app-version': 'version', - 'ulms-scope': 'scope', -} - -const urlGeneratorCases = [ - ['objectParams', { a: 0, b: 1, c: 2 }, '?a=0&b=1&c=2'], - [ - 'advancedObjectParams', - { a: 'a', b: [1, 2, 3], c: { cc: 3, dd: 4 } }, - '?a=a&b[]=1&b[]=2&b[]=3&c[cc]=3&c[dd]=4', - ], - ['objectParamsUndefined', { a: undefined }, ''], - ['arrayParams', ['a', 'b', 'c'], '?0=a&1=b&2=c'], - [ - 'advancedArrayParams', - { alphabet: ['a', 'b', 'c'] }, - '?alphabet[]=a&alphabet[]=b&alphabet[]=c', - ], - ['withoutParams', undefined, ''], -] - -const methodsWithoutDataCases = [['get'], ['delete']] - -const methodsWithDataCases = [ - ['post', 'data'], - ['put', 'data'], - ['patch', 'data'], -] -describe('Basic client suite', () => { - it.each(urlGeneratorCases)( - 'URL generator should work with: endpoint — %s and %j as params', - (endpoint, parameters, expectedParameters) => { - const resultUrl = Client.url(`/${endpoint}`, parameters) - expect(resultUrl).toBe(`${baseUrl}/${endpoint}${expectedParameters}`) - }, - ) - - it('Headers should compare with labels', () => { - const resultHeaders = BasicClient.headers('token', { label: 'a' }) - expect(resultHeaders.authorization).toBe('Bearer token') - expect(resultHeaders['content-type']).toBe('application/json') - expect(resultHeaders.label).toBe('a') - }) - - it('Headers should compare without labels', () => { - const resultHeaders = BasicClient.headers('token') - expect(resultHeaders).toEqual(headersWithoutLabels) - }) - - it('Labels should set and destroy', () => { - Client.setLabels(labels) - expect(Client.labels).toEqual(ulmsLabels) - - Client.clearLabels() - expect(Client.labels).toEqual({}) - }) - - it.each(methodsWithoutDataCases)( - '%s method should work (without labels)', - async (method) => { - await Client[method](baseUrl) - expect(httpClient[method]).toBeCalledWith(baseUrl, { - headers: headersWithoutLabels, - }) - }, - ) - - it.each(methodsWithoutDataCases)( - '%s method should work (with labels)', - async (method) => { - Client.setLabels(labels) - await Client[method](baseUrl) - Client.clearLabels() - expect(httpClient[method]).toBeCalledWith(baseUrl, { - headers: { ...headersWithoutLabels, ...ulmsLabels }, - }) - }, - ) - - it.each(methodsWithDataCases)( - '%s method should work (without labels) with payload: %s', - async (method, data) => { - await Client[method](baseUrl, data) - expect(httpClient[method]).toBeCalledWith(baseUrl, data, { - headers: headersWithoutLabels, - }) - }, - ) - - it.each(methodsWithDataCases)( - '%s method should work (with labels) with payload: %s', - async (method, data) => { - Client.setLabels(labels) - await Client[method](baseUrl, data) - Client.clearLabels() - expect(httpClient[method]).toBeCalledWith(baseUrl, data, { - headers: { ...headersWithoutLabels, ...ulmsLabels }, - }) - }, - ) -}) diff --git a/__tests__/error.test.js b/__tests__/error.test.js index d54f12b..58dd5bd 100644 --- a/__tests__/error.test.js +++ b/__tests__/error.test.js @@ -1,12 +1,12 @@ import { MQTTClientError, MQTTRPCServiceError, - PresenceError, + PresenceWsError, TimeoutError, } from '../src/error' const clientError = new MQTTClientError('Message') -const presenceError = new PresenceError('Message') +const presenceWsError = new PresenceWsError('Message') const serviceError = new MQTTRPCServiceError('Message') const timeoutError = new TimeoutError('Message') @@ -26,13 +26,13 @@ describe('Custom errors have right titles', () => { // PresenceError it('PresenceError message is valid (`fromType` method)', () => { - expect(PresenceError.fromType('test').message).toEqual('UNKNOWN_ERROR') + expect(PresenceWsError.fromType('test').message).toEqual('UNKNOWN_ERROR') }) it('PresenceError name is valid', () => { - expect(presenceError.name).toEqual('PresenceError') + expect(presenceWsError.name).toEqual('PresenceWsError') }) it('PresenceError message is valid (default)', () => { - expect(presenceError.message).toEqual('Message') + expect(presenceWsError.message).toEqual('Message') }) // MQTTRPCServiceError diff --git a/package-lock.json b/package-lock.json index ec08bc4..4e20f11 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@ulms/api-clients", - "version": "7.9.10", + "version": "7.9.10-dev.2-ULMS-3167", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ulms/api-clients", - "version": "7.9.10", + "version": "7.9.10-dev.2-ULMS-3167", "license": "MIT", "dependencies": { "axios": "1.6.2", diff --git a/package.json b/package.json index 1587440..97b5241 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ulms/api-clients", - "version": "7.9.10", + "version": "7.9.10-dev.2-ULMS-3167", "description": "JavaScript API clients for ULMS platform", "keywords": [], "homepage": "https://github.com/foxford/ulms-api-clients-js#readme", diff --git a/src/basic-client.js b/src/basic-client.js index f17e92c..e0837b9 100644 --- a/src/basic-client.js +++ b/src/basic-client.js @@ -26,7 +26,9 @@ const parseParameter = (key, value) => { return `${key}=${value}` } -const responseTransformer = (_) => _.data +async function handleResponse() { + throw new Error('Method `handleResponse` not implemented.') +} class BasicClient { constructor(baseUrl, httpClient, tokenProvider) { @@ -34,8 +36,8 @@ class BasicClient { this.customHeaders = {} this.httpClient = httpClient this.tokenProvider = tokenProvider - this.labels = {} this.trackEvent = undefined + this.handleResponse = handleResponse } url(endpoint, parameters) { @@ -44,7 +46,7 @@ class BasicClient { // eslint-disable-next-line no-restricted-syntax for (const [key, value] of Object.entries(parameters)) { - if (value !== undefined) { + if (value !== undefined && value !== null) { urlParameters += urlParameters ? `&${parseParameter(key, value)}` : parseParameter(key, value) @@ -59,12 +61,11 @@ class BasicClient { return `${this.baseUrl}${endpoint}` } - static headers(token, labels = {}, headers = {}) { + static headers(token, headers = {}) { return { ...headers, authorization: `Bearer ${token}`, 'content-type': 'application/json', - ...labels, } } @@ -72,25 +73,10 @@ class BasicClient { this.customHeaders = headers } - setLabels(labels) { - const { app_audience, app_label, app_version, scope } = labels // eslint-disable-line camelcase - - this.labels = { - ...(app_audience !== undefined && { 'ulms-app-audience': app_audience }), // eslint-disable-line camelcase - ...(app_label !== undefined && { 'ulms-app-label': app_label }), // eslint-disable-line camelcase - ...(app_version !== undefined && { 'ulms-app-version': app_version }), // eslint-disable-line camelcase - ...(scope !== undefined && { 'ulms-scope': scope }), - } - } - setTrackEventFunction(trackEvent) { this.trackEvent = trackEvent } - clearLabels() { - this.labels = {} - } - async get(url, options = {}) { const { retry: retryEnabled } = options @@ -99,13 +85,13 @@ class BasicClient { const token = await this.tokenProvider.getToken() const headers = { ...options.headers, - ...BasicClient.headers(token, this.labels, this.customHeaders), + ...BasicClient.headers(token, this.customHeaders), } const requestOptions = { ...options, headers } return this.httpClient .get(url, requestOptions) - .then(responseTransformer) + .then(this.handleResponse) } return retry(task, onRetry) @@ -114,31 +100,31 @@ class BasicClient { const token = await this.tokenProvider.getToken() const headers = { ...options.headers, - ...BasicClient.headers(token, this.labels, this.customHeaders), + ...BasicClient.headers(token, this.customHeaders), } const requestOptions = { ...options, headers } - return this.httpClient.get(url, requestOptions).then(responseTransformer) + return this.httpClient.get(url, requestOptions).then(this.handleResponse) } async put(url, data, options = {}) { const token = await this.tokenProvider.getToken() const headers = { ...options.headers, - ...BasicClient.headers(token, this.labels, this.customHeaders), + ...BasicClient.headers(token, this.customHeaders), } const requestOptions = { ...options, headers } return this.httpClient .put(url, data, requestOptions) - .then(responseTransformer) + .then(this.handleResponse) } async post(url, data, options = {}) { const token = await this.tokenProvider.getToken() const headers = { ...options.headers, - ...BasicClient.headers(token, this.labels, this.customHeaders), + ...BasicClient.headers(token, this.customHeaders), } const requestOptions = { ...options, headers } @@ -151,7 +137,7 @@ class BasicClient { : undefined const result = this.httpClient .post(url, data, requestOptions) - .then(responseTransformer) + .then(this.handleResponse) result.catch((error) => { const responseEnd = Date.now() @@ -174,31 +160,31 @@ class BasicClient { return this.httpClient .post(url, data, requestOptions) - .then(responseTransformer) + .then(this.handleResponse) } async patch(url, data, options = {}) { const token = await this.tokenProvider.getToken() const headers = { ...options.headers, - ...BasicClient.headers(token, this.labels, this.customHeaders), + ...BasicClient.headers(token, this.customHeaders), } const requestOptions = { ...options, headers } return this.httpClient .patch(url, data, requestOptions) - .then(responseTransformer) + .then(this.handleResponse) } async delete(url, options = {}) { const token = await this.tokenProvider.getToken() const headers = { ...options.headers, - ...BasicClient.headers(token, this.labels, this.customHeaders), + ...BasicClient.headers(token, this.customHeaders), } const requestOptions = { ...options, headers } - return this.httpClient.delete(url, requestOptions).then(responseTransformer) + return this.httpClient.delete(url, requestOptions).then(this.handleResponse) } } diff --git a/src/conference.js b/src/conference.js deleted file mode 100644 index c14dc6e..0000000 --- a/src/conference.js +++ /dev/null @@ -1,55 +0,0 @@ -import Service from './service' - -/** - * Agent reader configuration - * @name AgentReaderConfig - * @type {object} - * @property {string} agent_id - * @property {boolean} receive_audio - * @property {boolean} receive_video - */ - -/** - * Agent writer configuration - * @name AgentWriterConfig - * @type {object} - * @property {string} agent_id - * @property {boolean} send_audio - * @property {boolean} send_video - * @property {number} video_remb - */ - -/** - * @deprecated Use Broker class instead of Conference class - */ -class Conference extends Service { - /** - * Conference events enum - * @returns {{ - * AGENT_WRITER_CONFIG_UPDATE: string, - * GROUP_UPDATE: string, - * ROOM_CLOSE: string, - * ROOM_ENTER: string, - * ROOM_LEAVE: string, - * RTC_STREAM_AGENT_SPEAKING: string - * RTC_STREAM_UPDATE: string - * }} - */ - static get events() { - return { - AGENT_WRITER_CONFIG_UPDATE: 'agent_writer_config.update', - GROUP_UPDATE: 'video_group.update', - ROOM_CLOSE: 'room.close', - ROOM_ENTER: 'room.enter', - ROOM_LEAVE: 'room.leave', - RTC_STREAM_AGENT_SPEAKING: 'rtc_stream.agent_speaking', - RTC_STREAM_UPDATE: 'rtc_stream.update', - } - } - - constructor(mqttClient, agentId) { - super(mqttClient, agentId, 'conference.svc.netology-group.services') - } -} - -export default Conference diff --git a/src/dispatcher.js b/src/dispatcher.js deleted file mode 100644 index ec664ac..0000000 --- a/src/dispatcher.js +++ /dev/null @@ -1,196 +0,0 @@ -import BasicClient from './basic-client' - -/** - * @deprecated Use ULMS client instead of Dispatcher client - */ -class Dispatcher extends BasicClient { - /** - * Scope kind enum - * @returns {{CHAT: string, MINIGROUP: string, P2P: string, WEBINAR: string}} - */ - static get kind() { - return { - CHAT: 'chats', - MINIGROUP: 'minigroups', - P2P: 'p2p', - WEBINAR: 'webinars', - } - } - - /** - * Scope status enum - * @returns {{REAL_TIME: string, CLOSED: string, FINISHED: string, ADJUSTED: string, TRANSCODED: string}} - */ - static get scopeStatus() { - return { - REAL_TIME: 'real-time', - CLOSED: 'closed', - FINISHED: 'finished', - ADJUSTED: 'adjusted', - TRANSCODED: 'transcoded', - } - } - - /** - * Class properties enum - * @returns {{IS_ADULT: string, HAS_USER_ACCESS_TO_BOARD: string}} - */ - static get classKeys() { - return { - IS_ADULT: 'is_adult', - HAS_USER_ACCESS_TO_BOARD: 'has_user_access_to_board', - EMOTIONS: 'emotions', - } - } - - /** - * Account properties enum - * @returns {{ONBOARDING: string}} - */ - static get accountKeys() { - return { - LAST_SEEN_MESSAGE_ID_BY_ROOMS: 'last_seen_message_id_by_rooms', - ONBOARDING: 'onboarding', - } - } - - /** - * Bans media stream and collaboration for user - * @param {{ accountId: string, ban: boolean, classId: string }} - * @returns {Promise} - */ - banUser({ accountId, ban, classId }) { - return this.get( - `${this.baseUrl}/account/${accountId}/ban/${classId}`, // get last ban operation id for user - // eslint-disable-next-line camelcase - ).then(({ last_seen_op_id }) => - this.post(`${this.baseUrl}/account/${accountId}/ban`, { - ban, - class_id: classId, - // eslint-disable-next-line camelcase - last_seen_op_id, - }), - ) - } - - /** - * Commit edition by scope - * @param {string} audience - * @param {string} scope - * @param {string} editionId - * @returns {Promise} - */ - commitEdition(audience, scope, editionId) { - return this.post( - `${this.baseUrl}/audiences/${audience}/classes/${scope}/editions/${editionId}`, - ) - } - - /** - * Fetch token data for NATS - * @param {string} classroomId - * @returns {Promise} - */ - fetchTokenData(classroomId) { - return this.post(`${this.baseUrl}/classrooms/${classroomId}/tokens`) - } - - /** - * Read dispatcher scope - * @param {string} kind - * @param {string} audience - * @param {string} scope - * @param {object} options - * @returns {Promise} - */ - readScope(kind, audience, scope, options) { - return this.get( - this.url(`/audiences/${audience}/${kind}/${scope}`, options), - ) - } - - /** - * Read class property - * @param {string} kind - * @param {string} classId - * @param {string} propertyId - * @returns {Promise} - */ - readClassProperty(kind, classId, propertyId) { - return this.get( - `${this.baseUrl}/${kind}/${classId}/properties/${propertyId}`, - ) - } - - /** - * Update class property - * @param {string} kind - * @param {string} classId - * @param {string} propertyId - * @param {object} data - * @returns {Promise} - */ - updateClassProperty(kind, classId, propertyId, data) { - return this.put( - `${this.baseUrl}/${kind}/${classId}/properties/${propertyId}`, - data, - ) - } - - /** - * Read account property - * @param {string} propertyId - * @returns {Promise} - */ - readAccountProperty(propertyId) { - return this.get(`${this.baseUrl}/account/properties/${propertyId}`) - } - - /** - * Update account property - * @param {string} propertyId - * @param {object} data - * @returns {Promise} - */ - updateAccountProperty(propertyId, data) { - return this.put(`${this.baseUrl}/account/properties/${propertyId}`, data) - } - - /** - * Update dispatcher scope - * @param {string} kind - * @param {string} audience - * @param {string} scope - * @param {object} data - * @returns {Promise} - */ - updateScope(kind, audience, scope, data) { - return this.put( - `${this.baseUrl}/audiences/${audience}/${kind}/${scope}`, - data, - ) - } - - /** - * Update position timestamp - * @param {string} kind - * @param {string} classId - * @param {number} position - * @returns {Promise} - */ - updatePosition(kind, classId, position) { - const controller = new AbortController() - const { signal } = controller - const timeoutId = setTimeout(() => controller.abort(), 10 * 1000) - - return this.post( - `${this.baseUrl}/${kind}/${classId}/timestamps`, - { position }, - { signal }, - ).finally(() => { - clearTimeout(timeoutId) - }) - } -} - -export default Dispatcher diff --git a/src/error.js b/src/error.js index 6b35956..a9c8a01 100644 --- a/src/error.js +++ b/src/error.js @@ -1,4 +1,167 @@ /* eslint-disable max-classes-per-file, unicorn/prevent-abbreviations */ + +// old api error kinds +// todo: remove after migration to new error kinds (on backend) +const apiErrorKindOldMap = { + ACCESS_DENIED: 'access_denied', + CAPACITY_EXCEEDED: 'capacity_exceeded', + TOXIC_COMMENT: 'toxic_comment', + TOXIC_COMMENT_CLASSIFIER_REQUEST_FAILED: + 'toxic_comment_classifier_request_failed', +} +const apiErrorKindMap = { + INTERNAL_FAILURE: 'internal_failure', + SYSTEM_ACCESS_DENIED: 'system_access_denied', + ULMS_INVALID_COMMENT: 'ulms_invalid_comment', + ULMS_SERVER_CAPACITY_EXCEEDED: 'ulms_server_capacity_exceeded', + ULMS_TOXIC_COMMENT: 'ulms_toxic_comment', +} +// todo: remove after migration to new error kinds (on backend) +const transformApiErrorKindMap = { + [apiErrorKindOldMap.ACCESS_DENIED]: apiErrorKindMap.SYSTEM_ACCESS_DENIED, + [apiErrorKindOldMap.CAPACITY_EXCEEDED]: + apiErrorKindMap.ULMS_SERVER_CAPACITY_EXCEEDED, + [apiErrorKindOldMap.TOXIC_COMMENT]: apiErrorKindMap.ULMS_TOXIC_COMMENT, + [apiErrorKindOldMap.TOXIC_COMMENT_CLASSIFIER_REQUEST_FAILED]: + apiErrorKindMap.ULMS_INVALID_COMMENT, +} + +const decodeErrorKindMap = { + JSON_PARSE_ERROR: 'json_parse_error', +} + +export class UlmsError extends Error { + constructor(message, options) { + super(message, options) + + const { isTransient = false } = options || {} + + this.isTransient = isTransient + this.name = 'UlmsError' + } + + static get apiErrorKinds() { + return apiErrorKindMap + } + + static get decodeErrorKinds() { + return decodeErrorKindMap + } + + /** + * Factory method for creating error instance + * @param payload {{ kind?: string, isTransient?: boolean }} + * @returns {UlmsError} + */ + static fromPayload(payload) { + const { kind, isTransient } = payload + // todo: remove after migration to new error kinds (on backend), use kind instead + const transformedKind = transformApiErrorKindMap[kind] || kind + + return new UlmsError(transformedKind, { isTransient }) + } + + get kind() { + return this.message + } +} + +// // Error kind matching example +// try { +// const result = await ulmsClient.createEvent(...) +// } catch (error) { +// switch (error.kind) { +// case UlmsError.apiErrorKinds.ACCESS_DENIED: { +// // show ACCESS_DENIED screen +// break +// } +// case UlmsError.apiErrorKinds.TOXIC_COMMENT: { +// // show TOXIC_COMMENT screen +// break +// } +// case UlmsError.decodeErrorKinds.JSON_PARSE_ERROR: { +// // show JSON_PARSE_ERROR screen +// break +// } +// default: { +// // default +// sentry.captureException(error) +// // show default screen +// } +// } +// } + +export class PresenceError extends Error { + constructor(message, options) { + super(message, options) + + const { isTransient = false } = options || {} + + this.isTransient = isTransient + this.name = 'PresenceError' + } + + static get apiErrorKinds() { + return apiErrorKindMap + } + + static get decodeErrorKinds() { + return decodeErrorKindMap + } + + /** + * Factory method for creating error instance + * @param payload {{ kind?: string, isTransient?: boolean }} + * @returns {PresenceError} + */ + static fromPayload(payload) { + const { kind, isTransient } = payload + // todo: remove after migration to new error kinds (on backend), use kind instead + const transformedKind = transformApiErrorKindMap[kind] || kind + + return new PresenceError(transformedKind, { isTransient }) + } + + get kind() { + return this.message + } +} + +export class FVSError extends Error { + constructor(...arguments_) { + super(...arguments_) + + this.isTransient = false + this.name = 'FVSError' + } + + static get decodeErrorKinds() { + return decodeErrorKindMap + } +} + +export class TenantError extends Error { + constructor(...arguments_) { + super(...arguments_) + + this.isTransient = false + this.name = 'TenantError' + } + + static get decodeErrorKinds() { + return decodeErrorKindMap + } +} + +export class NetworkError extends Error { + constructor(...arguments_) { + super(...arguments_) + + this.isTransient = true + this.name = 'NetworkError' + } +} + export class MQTTClientError extends Error { constructor(...args) { super(...args) @@ -27,11 +190,11 @@ export class TimeoutError extends Error { } } -export class PresenceError extends Error { +export class PresenceWsError extends Error { constructor(...args) { super(...args) - this.name = 'PresenceError' + this.name = 'PresenceWsError' } static get recoverableTypes() { @@ -64,26 +227,26 @@ export class PresenceError extends Error { static fromType(type) { const errorType = - PresenceError.recoverableTypes[type] || - PresenceError.unrecoverableTypes[type] || - PresenceError.unrecoverableTypes.UNKNOWN_ERROR + PresenceWsError.recoverableTypes[type] || + PresenceWsError.unrecoverableTypes[type] || + PresenceWsError.unrecoverableTypes.UNKNOWN_ERROR - return new PresenceError(errorType) + return new PresenceWsError(errorType) } static isReplacedError(error) { - if (!(error instanceof PresenceError)) return false + if (!(error instanceof PresenceWsError)) return false const { message } = error return ( - message === PresenceError.unrecoverableTypes.REPLACED || - message === PresenceError.unrecoverableTypes.SESSION_REPLACED + message === PresenceWsError.unrecoverableTypes.REPLACED || + message === PresenceWsError.unrecoverableTypes.SESSION_REPLACED ) } isRecoverable() { - return !!PresenceError.recoverableTypes[this.message] + return !!PresenceWsError.recoverableTypes[this.message] } } diff --git a/src/event.js b/src/event.js deleted file mode 100644 index f42d92b..0000000 --- a/src/event.js +++ /dev/null @@ -1,44 +0,0 @@ -import Service from './service' - -/** - * @deprecated Use Broker class instead of Event class - */ -class Event extends Service { - /** - * Change type enum - * @returns {{ADDITION: string, MODIFICATION: string, REMOVAL: string}} - */ - static get changeTypes() { - return { - ADDITION: 'addition', - MODIFICATION: 'modification', - REMOVAL: 'removal', - } - } - - /** - * Events enum - * @returns {{ - * AGENT_UPDATE: string, - * EVENT_CREATE: string, - * ROOM_ENTER: string, - * ROOM_LEAVE: string, - * ROOM_UPDATE: string - * }} - */ - static get events() { - return { - AGENT_UPDATE: 'agent.update', - EVENT_CREATE: 'event.create', - ROOM_ENTER: 'room.enter', - ROOM_LEAVE: 'room.leave', - ROOM_UPDATE: 'room.update', - } - } - - constructor(mqttClient, agentId) { - super(mqttClient, agentId, 'event.svc.netology-group.services') - } -} - -export default Event diff --git a/src/fvs.js b/src/fvs.js index 14380c3..f5616e9 100644 --- a/src/fvs.js +++ b/src/fvs.js @@ -1,6 +1,36 @@ import BasicClient from './basic-client' +import { FVSError } from './error' +async function handleResponse(response) { + let data + + try { + data = await response.json() + } catch (error) { + throw new FVSError(FVSError.decodeErrorKinds.JSON_PARSE_ERROR, { + cause: error, + }) + } + + if (!response.ok) { + const { code, detail, message } = data + // eslint-disable-next-line sonarjs/no-nested-template-literals + const errorMessage = `[${response.status}]${code ? `[${code}]` : ''} ${detail}: ${message}` + + throw new FVSError(errorMessage) + } + + return data +} + +// fixme: need authorization header class FVS extends BasicClient { + constructor(...arguments_) { + super(...arguments_) + + this.handleResponse = handleResponse + } + /** * Get issue types in Minigroup * @returns {Promise} diff --git a/src/http-client.js b/src/http-client.js index ae1048b..4612c41 100644 --- a/src/http-client.js +++ b/src/http-client.js @@ -1,4 +1,5 @@ /* eslint-disable class-methods-use-this */ +import { NetworkError } from './error' function createTimeoutSignal(timeout) { const controller = new AbortController() @@ -10,28 +11,6 @@ function createTimeoutSignal(timeout) { } class FetchHttpClient { - static async handleResponse(response) { - const bodyAsText = await response.text() - let data - - try { - data = JSON.parse(bodyAsText) - } catch { - data = { message: bodyAsText } - } - - const result = { - data, - status: response.status, - } - - if (!response.ok) { - throw result - } - - return result - } - request(url, config) { const { timeout, ...requestConfig } = config const requestOptions = { @@ -46,9 +25,16 @@ class FetchHttpClient { onFinally = cleanup } - return fetch(url, requestOptions) - .then(FetchHttpClient.handleResponse) - .finally(() => (onFinally ? onFinally() : undefined)) + const fetchPromise = fetch(url, requestOptions).catch((error) => { + throw new NetworkError('', { cause: error }) + }) + + if (onFinally) { + // eslint-disable-next-line promise/catch-or-return + fetchPromise.finally(() => onFinally()) + } + + return fetchPromise } get(url, config) { diff --git a/src/index.js b/src/index.js index 3090d47..eba4e76 100644 --- a/src/index.js +++ b/src/index.js @@ -1,18 +1,13 @@ export { default as Backoff } from './backoff' export { default as Broker } from './broker' -export { default as Conference } from './conference' -export { default as Dispatcher } from './dispatcher' -export { default as Event } from './event' export { default as FVS } from './fvs' export { default as FetchHttpClient } from './http-client' export { MQTTClient, ReconnectingMQTTClient, defaultOptions } from './mqtt' export { default as NATSClient } from './nats-client' export { default as NatsManager } from './nats-manager' export { default as NetworkStatusMonitor } from './network-status-monitor' -export { default as Portal } from './portal' export { default as Presence } from './presence' export { default as PresenceWS } from './presence-ws' -export { default as HttpProfileResource } from './profile' export { default as ProfileService } from './profile-service' export { default as Storage } from './storage' export { default as Tenant } from './tenant' diff --git a/src/portal.js b/src/portal.js deleted file mode 100644 index 27502b4..0000000 --- a/src/portal.js +++ /dev/null @@ -1,13 +0,0 @@ -import BasicClient from './basic-client' - -class Portal extends BasicClient { - /** - * Get portal options - * @returns {Promise} - */ - getOptions() { - return this.get(this.baseUrl, { mode: 'cors', credentials: 'include' }) - } -} - -export default Portal diff --git a/src/presence-ws.js b/src/presence-ws.js index e80239d..d5f2115 100644 --- a/src/presence-ws.js +++ b/src/presence-ws.js @@ -3,7 +3,7 @@ import Debug from 'debug' import EventEmitter from 'events' // eslint-disable-line unicorn/prefer-node-protocol import { makeDeferred } from './common' -import { PresenceError } from './error' +import { PresenceWsError } from './error' import WsTransport from './ws-transport' const debug = Debug('presence-ws') @@ -112,7 +112,7 @@ class PresenceWS extends EventEmitter { // if previousTransport closed with replaced error, ignore and return if ( previousTransport && - PresenceError.isReplacedError(this.lastProtocolError) + PresenceWsError.isReplacedError(this.lastProtocolError) ) { return p } @@ -155,7 +155,7 @@ class PresenceWS extends EventEmitter { (maybeDisconnectReason && maybeDisconnectReason.payload && maybeDisconnectReason.payload.type !== - PresenceError.recoverableTypes.SERVER_SHUTDOWN) + PresenceWsError.recoverableTypes.SERVER_SHUTDOWN) ) { debug('[flow] transport disconnected, throw') @@ -174,7 +174,7 @@ class PresenceWS extends EventEmitter { const reason = error ? (error.payload && error.payload.type) || error.type === ERROR_MESSAGE_TYPE - ? PresenceError.fromType(error.payload.type.toUpperCase()) + ? PresenceWsError.fromType(error.payload.type.toUpperCase()) : error : undefined @@ -213,7 +213,7 @@ class PresenceWS extends EventEmitter { } else if (type === ERROR_MESSAGE_TYPE) { const { is_transient: isTransient } = payload // process service system messages (error) - this.lastProtocolError = PresenceError.fromType( + this.lastProtocolError = PresenceWsError.fromType( payload.type.toUpperCase(), ) @@ -245,7 +245,9 @@ class PresenceWS extends EventEmitter { disconnected() { if (!this.connected) { return Promise.reject( - PresenceError.fromType(PresenceError.unrecoverableTypes.NOT_CONNECTED), + PresenceWsError.fromType( + PresenceWsError.unrecoverableTypes.NOT_CONNECTED, + ), ) } diff --git a/src/presence.js b/src/presence.js index 86d8d8a..9c1a35d 100644 --- a/src/presence.js +++ b/src/presence.js @@ -1,6 +1,54 @@ import BasicClient from './basic-client' +import { PresenceError } from './error' + +async function handleResponse(response) { + let data + + try { + data = await response.json() + } catch (error) { + throw new PresenceError(PresenceError.decodeErrorKinds.JSON_PARSE_ERROR, { + cause: error, + }) + } + + if (!response.ok) { + const { + // new error format + kind = '', + is_transient: isTransient = false, + + // old error format (ProblemDetail { type: string, title: string, detail: string }) + // todo: remove after migration to new error format + type, + } = data + let errorPayload = { + kind, + isTransient, + } + + // override error payload in case of old error format (or another format from portal or fvs, etc...) + // todo: remove after migration to new error format + if (type !== undefined) { + errorPayload = { + kind: type, + isTransient, + } + } + + throw PresenceError.fromPayload(errorPayload) + } + + return data +} class Presence extends BasicClient { + constructor(...arguments_) { + super(...arguments_) + + this.handleResponse = handleResponse + } + /** * List agent * @param {string} classroomId diff --git a/src/profile-service.js b/src/profile-service.js index 80efdcc..0980c08 100644 --- a/src/profile-service.js +++ b/src/profile-service.js @@ -39,7 +39,7 @@ class ProfileService { this.queue.add(() => this.client - .listProfiles(ids, scope) + .listProfile(ids, scope) .then((response) => { for (const profile of response) { const { id } = profile @@ -76,11 +76,11 @@ class ProfileService { } forceReadProfile(id, scope) { - return this.client.getProfile(id, scope, true) + return this.client.readProfile(id, scope, true) } readMeProfile(scope) { - return this.client.getProfile('me', scope) + return this.client.readProfile('me', scope) } readProfile(id, scope) { diff --git a/src/profile.js b/src/profile.js deleted file mode 100644 index 5ed3709..0000000 --- a/src/profile.js +++ /dev/null @@ -1,65 +0,0 @@ -/* eslint-disable promise/no-nesting */ -const responseTransformer = (_) => _.data - -// todo: extend from BasicClient -class HttpProfileResource { - constructor(host, endpoint, httpClient, tokenProvider) { - this.baseUrl = `${host}/${endpoint}` - this.httpClient = httpClient - this.tokenProvider = tokenProvider - } - - static headers(parameters) { - return { - authorization: `Bearer ${parameters.token}`, - 'content-type': 'application/json', - } - } - - getProfile(id, scope, force = false) { - let qs = '' - - if (scope) { - qs = `?scope=${scope}` - } - - // to avoid Nginx cache - if (force) { - qs += `${qs.length > 0 ? '&' : '?'}timestamp=${Date.now()}` - } - - return this.tokenProvider.getToken().then((token) => - this.httpClient - .get(`${this.baseUrl}/users/${id}${qs}`, { - headers: HttpProfileResource.headers({ token }), - }) - .then(responseTransformer), - ) - } - - listProfiles(ids, scope) { - const qs = `?ids=${ids.join(',')}&scope=${scope}` - - return this.tokenProvider.getToken().then((token) => - this.httpClient - .get(`${this.baseUrl}/users${qs}`, { - headers: HttpProfileResource.headers({ token }), - }) - .then(responseTransformer), - ) - } - - updateAttributes(id, scope, data) { - const qs = `?scope=${scope}` - - return this.tokenProvider.getToken().then((token) => - this.httpClient - .patch(`${this.baseUrl}/users/${id}${qs}`, data, { - headers: HttpProfileResource.headers({ token }), - }) - .then(responseTransformer), - ) - } -} - -export default HttpProfileResource diff --git a/src/redux/middleware.js b/src/redux/middleware.js index 443f70e..406dbc7 100644 --- a/src/redux/middleware.js +++ b/src/redux/middleware.js @@ -3,7 +3,7 @@ import Debug from 'debug' import Backoff from '../backoff' import { sleep } from '../common' -import { PresenceError } from '../error' +import { PresenceWsError } from '../error' import { AGENT_ENTERED, @@ -160,7 +160,7 @@ async function startPresenceFlow( reason = error } - if (reason instanceof PresenceError && reason.isRecoverable()) { + if (reason instanceof PresenceWsError && reason.isRecoverable()) { retryCount += 1 } else { break @@ -232,8 +232,8 @@ async function getPresenceAgentList( trackError(error) } - const { status } = error - const isErrorUnrecoverable = !!status + const { isTransient } = error + const isErrorUnrecoverable = !isTransient const retryLimitExceeded = retryCount === RETRY_LIMIT // error is unrecoverable OR retry limit reached diff --git a/src/retry.js b/src/retry.js index 0a18cc7..bd306a2 100644 --- a/src/retry.js +++ b/src/retry.js @@ -46,10 +46,12 @@ async function retry(task, onRetry, retryLimit = RETRY_LIMIT) { function isErrorRetryable(error) { /* Повторная попытка разрешена для следующих ошибок: + - [+] ошибка из апи, которая позволяет выполнить повторную попытку отправки запроса - [+] клиентская сетевая ошибка ("Failed to fetch *") - - [+] таймаут запроса + - [+] клиентский таймаут запроса - [+] HTTP ответ со статус-кодом: 422, 424, 429 или 5xx */ + const isTransientApiError = error.isTransient const isNetworkError = error instanceof TypeError && error.message.startsWith('Failed to fetch') const isTimeoutError = @@ -61,7 +63,12 @@ function isErrorRetryable(error) { error.status >= 500 : false - return isNetworkError || isTimeoutError || isPassedByStatusCode + return ( + isTransientApiError || + isNetworkError || + isTimeoutError || + isPassedByStatusCode + ) } export default retry diff --git a/src/service.js b/src/service.js deleted file mode 100644 index e8f38b1..0000000 --- a/src/service.js +++ /dev/null @@ -1,82 +0,0 @@ -/* eslint-disable unicorn/prevent-abbreviations */ -// eslint-disable-next-line unicorn/prefer-node-protocol -import EventEmitter from 'events' - -import Codec from './codec' - -/** - * @deprecated Use Broker class instead of Service base class - */ -class Service { - constructor(mqttClient, agentId, appName) { - this.agentId = agentId - this.appName = appName - this.topicPatternNotifications = `apps/${this.appName}/api/v1/rooms/+roomId/events` - this.mqtt = mqttClient - - this.codec = new Codec( - (data) => JSON.stringify(data), - (data) => { - let payload - - try { - payload = JSON.parse(data.toString()) - } catch { - payload = {} - } - - return payload - }, - ) - // eslint-disable-next-line unicorn/prefer-event-target - this.ee = new EventEmitter() - - this.attachRoutes() - } - - attachRoutes() { - this.mqtt.attachRoute( - this.topicPatternNotifications, - this.subMessageHandler.bind(this), - ) - } - - detachRoutes() { - this.mqtt.detachRoute(this.topicPatternNotifications) - } - - on(eventName, eventHandler) { - this.ee.addListener(eventName, eventHandler) - } - - off(eventName, eventHandler) { - this.ee.removeListener(eventName, eventHandler) - } - - subMessageHandler(topicParams, topic, message, packet) { - const payload = this.codec.decode(message) - const { properties } = packet - const { - userProperties: { label, type }, - } = properties - let event - - if (type === 'event' && payload !== undefined) { - event = { - type: label, - data: payload, - } - - this.ee.emit(event.type, event) - } else { - // do nothing - } - } - - destroy() { - this.detachRoutes() - this.ee.removeAllListeners() - } -} - -export default Service diff --git a/src/tenant.js b/src/tenant.js index 06c7392..e4a0a46 100644 --- a/src/tenant.js +++ b/src/tenant.js @@ -1,6 +1,47 @@ import BasicClient from './basic-client' +import { TenantError } from './error' + +async function handleResponse(response) { + let data + + try { + data = await response.json() + } catch (error) { + throw new TenantError(TenantError.decodeErrorKinds.JSON_PARSE_ERROR, { + cause: error, + }) + } + + if (!response.ok) { + const { error, errors, messages } = data + // eslint-disable-next-line sonarjs/no-nested-template-literals + let errorMessage = `[${response.status}]` + + if (error) { + errorMessage += ` ${error}` + } + + if (errors) { + errorMessage += ` ${errors}` + } + + if (messages) { + errorMessage += ` ${messages.join(',')}` + } + + throw new TenantError(errorMessage) + } + + return data +} class Tenant extends BasicClient { + constructor(...arguments_) { + super(...arguments_) + + this.handleResponse = handleResponse + } + /** * Profile role enum * @returns {{MODERATOR: string, USER: string}} @@ -16,16 +57,17 @@ class Tenant extends BasicClient { * Read profile * @param {string} id * @param {string} scope + * @param {boolean} force * @returns {Promise} */ - readProfile(id, scope) { - let qs = '' - - if (scope) { - qs = `?scope=${scope}` - } - - return this.get(`${this.baseUrl}/users/${id}${qs}`) + readProfile(id, scope, force = false) { + return this.get( + this.url(`/users/${id}`, { + scope, + // to avoid Nginx cache + timestamp: force ? Date.now() : undefined, + }), + ) } /** @@ -35,18 +77,17 @@ class Tenant extends BasicClient { * @returns {Promise} */ listProfile(ids, scope) { - const qs = `?ids=${ids.join(',')}&scope=${scope}` - - return this.get(`${this.baseUrl}/users${qs}`) + return this.get(this.url(`/users`, { ids: ids.join(','), scope })) } /** * Read tenant scope * @param {string} scope + * @param {string} lessonId * @returns {Promise} */ - readScope(scope) { - return this.get(`${this.baseUrl}/webinars/${scope}`) + readScope(scope, lessonId) { + return this.get(this.url(`/webinars/${scope}`, { lesson_id: lessonId })) } /** @@ -66,6 +107,22 @@ class Tenant extends BasicClient { createMaterialUrl(id) { return `${this.baseUrl}/materials/${id}` } + + /** + * Get portal options + * @param {string} optionsEndpoint + * @returns {Promise} + */ + getOptions(optionsEndpoint) { + const { origin } = new URL(this.baseUrl) + + return this.httpClient + .get(`${origin}/${optionsEndpoint}`, { + mode: 'cors', + credentials: 'include', + }) + .then(this.handleResponse) + } } export default Tenant diff --git a/src/token-provider.js b/src/token-provider.js index 967238d..133b6fa 100644 --- a/src/token-provider.js +++ b/src/token-provider.js @@ -5,11 +5,34 @@ import retry, { isErrorRetryable } from './retry' const onRetry = (error) => !isErrorRetryable(error) +async function handleResponse(response) { + let data + + try { + data = await response.json() + } catch (error) { + console.log('[TP:handleResponse] body parsing catch', error) + // todo: change error type (UnexpectedError) + throw error + } + + if (!response.ok) { + // eslint-disable-next-line no-throw-literal + throw { + data, + status: response.status, + } + } + + return data +} + class TokenProvider { constructor(baseUrl, httpClient) { this.baseUrl = baseUrl this.context = undefined this.errorHandler = undefined + this.handleResponse = handleResponse this.httpClient = httpClient this.tokenData = undefined this.tokenP = undefined @@ -39,8 +62,9 @@ class TokenProvider { this.tokenRequestStart = Date.now() this.fetchTokenData() + .then(this.handleResponse) .then((response) => { - this.updateTokenData(response.data) + this.updateTokenData(response) this.resolveAndReset() }) .catch((error) => { diff --git a/src/ulms.js b/src/ulms.js index 8acd4ee..62333ab 100644 --- a/src/ulms.js +++ b/src/ulms.js @@ -1,22 +1,5 @@ import BasicClient from './basic-client' - -const eventEndpoints = { - agentsList: (id) => `/event_rooms/${id}/agents`, - agentsUpdate: (id) => `/event_rooms/${id}/agents`, - banList: (id) => `/event_rooms/${id}/bans`, - changesCreate: (id) => `/editions/${id}/changes`, - changesDelete: (id) => `/changes/${id}`, - changesList: (id) => `/editions/${id}/changes`, - editionsCreate: (id) => `/event_rooms/${id}/editions`, - editionsDelete: (id) => `/editions/${id}`, - editionsList: (id) => `/event_rooms/${id}/editions`, - eventsCreate: (id) => `/event_rooms/${id}/events`, - eventsList: (id) => `/event_rooms/${id}/events`, - roomRead: (id) => `/event_rooms/${id}`, - roomState: (id) => `/event_rooms/${id}/state`, - roomUpdateLockedTypes: (id) => `/event_rooms/${id}/locked_types`, - roomUpdateWhiteboardAccess: (id) => `/event_rooms/${id}/whiteboard_access`, -} +import { UlmsError } from './error' /** * Agent reader configuration @@ -69,9 +52,56 @@ const eventEndpoints = { * @property {[number | null, number | null]} time */ +async function handleResponse(response) { + let data + + try { + data = await response.json() + } catch (error) { + throw new UlmsError(UlmsError.decodeErrorKinds.JSON_PARSE_ERROR, { + cause: error, + }) + } + + if (!response.ok) { + const { + // new error format + kind = '', + is_transient: isTransient = false, + + // old error format (ProblemDetail { type: string, title: string, detail: string }) + // todo: remove after migration to new error format + type, + } = data + let errorPayload = { + kind, + isTransient, + } + + // override error payload in case of old error format (or another format from portal or fvs, etc...) + // todo: remove after migration to new error format + if (type !== undefined) { + errorPayload = { + kind: type, + isTransient, + } + } + + throw UlmsError.fromPayload(errorPayload) + } + + return data +} + class ULMS extends BasicClient { agentLabel + constructor(...arguments_) { + super(...arguments_) + + this.handleResponse = handleResponse + } + setAgentLabel(label) { this.agentLabel = label } @@ -120,10 +150,10 @@ class ULMS extends BasicClient { */ static get classKeys() { return { - IS_ADULT: 'is_adult', + EMOTIONS: 'emotions', HAS_USER_ACCESS_TO_BOARD: 'has_user_access_to_board', + IS_ADULT: 'is_adult', TOXIC_COMMENT_CLASSIFIER_ENABLED: 'toxic_comment_classifier_enabled', - EMOTIONS: 'emotions', } } @@ -144,7 +174,7 @@ class ULMS extends BasicClient { * @returns {Promise} */ banUser({ accountId, ban, classId }) { - this.post(`${this.baseUrl}/account/${accountId}/ban`, { + return this.post(`${this.baseUrl}/account/${accountId}/ban`, { ban, class_id: classId, }) @@ -178,7 +208,7 @@ class ULMS extends BasicClient { * @returns {Promise} */ createEdition(roomId) { - return this.post(this.url(eventEndpoints.editionsCreate(roomId))) + return this.post(this.url(`/event_rooms/${roomId}/editions`)) } /** @@ -199,7 +229,7 @@ class ULMS extends BasicClient { type, } - return this.post(this.url(eventEndpoints.eventsCreate(roomId)), parameters) + return this.post(this.url(`/event_rooms/${roomId}/events`), parameters) } /** @@ -215,10 +245,7 @@ class ULMS extends BasicClient { type, } - return this.post( - this.url(eventEndpoints.changesCreate(editionId)), - parameters, - ) + return this.post(this.url(`/editions/${editionId}/changes`), parameters) } /** @@ -295,7 +322,7 @@ class ULMS extends BasicClient { * @returns {Promise} */ deleteEdition(id) { - return this.delete(this.url(eventEndpoints.editionsDelete(id))) + return this.delete(this.url(`/editions/${id}`)) } /** @@ -304,7 +331,7 @@ class ULMS extends BasicClient { * @returns {Promise} */ deleteChange(id) { - return this.delete(this.url(eventEndpoints.changesDelete(id))) + return this.delete(this.url(`/changes/${id}`)) } /** @@ -335,9 +362,7 @@ class ULMS extends BasicClient { * @returns {Promise} */ listAgent(roomId, filterParameters = {}) { - return this.get( - this.url(eventEndpoints.agentsList(roomId), filterParameters), - ) + return this.get(this.url(`/event_rooms/${roomId}/agents`, filterParameters)) } /** @@ -359,7 +384,7 @@ class ULMS extends BasicClient { * @returns {Promise} */ listBans(roomId, filterParameters = {}) { - return this.get(this.url(eventEndpoints.banList(roomId), filterParameters)) + return this.get(this.url(`/event_rooms/${roomId}/bans`, filterParameters)) } /** @@ -369,7 +394,7 @@ class ULMS extends BasicClient { * @returns {Promise} */ listChange(id, filterParameters = {}) { - return this.get(this.url(eventEndpoints.changesList(id), filterParameters)) + return this.get(this.url(`/editions/${id}/changes`, filterParameters)) } /** @@ -380,7 +405,7 @@ class ULMS extends BasicClient { */ listEdition(roomId, filterParameters = {}) { return this.get( - this.url(eventEndpoints.editionsList(roomId), filterParameters), + this.url(`/event_rooms/${roomId}/editions`, filterParameters), ) } @@ -391,9 +416,7 @@ class ULMS extends BasicClient { * @returns {Promise} */ listEvent(roomId, filterParameters = {}) { - return this.get( - this.url(eventEndpoints.eventsList(roomId), filterParameters), - ) + return this.get(this.url(`/event_rooms/${roomId}/events`, filterParameters)) } /** @@ -461,7 +484,7 @@ class ULMS extends BasicClient { * @returns {Promise} */ readEventRoom(roomId) { - return this.get(this.url(eventEndpoints.roomRead(roomId))) + return this.get(this.url(`/event_rooms/${roomId}`)) } /** @@ -498,7 +521,7 @@ class ULMS extends BasicClient { sets, } - return this.get(this.url(eventEndpoints.roomState(roomId), parameters)) + return this.get(this.url(`/event_rooms/${roomId}/state`, parameters)) } /** @@ -529,7 +552,7 @@ class ULMS extends BasicClient { value, } - return this.patch(this.url(eventEndpoints.agentsUpdate(roomId)), parameters) + return this.patch(this.url(`/event_rooms/${roomId}/agents`), parameters) } /** @@ -639,7 +662,7 @@ class ULMS extends BasicClient { * @returns {Promise} */ updateLockedTypes(roomId, lockedTypes) { - return this.post(this.url(eventEndpoints.roomUpdateLockedTypes(roomId)), { + return this.post(this.url(`/event_rooms/${roomId}/locked_types`), { locked_types: lockedTypes, }) } @@ -687,12 +710,9 @@ class ULMS extends BasicClient { * @returns {Promise} */ updateWhiteboardAccess(roomId, payload) { - return this.post( - this.url(eventEndpoints.roomUpdateWhiteboardAccess(roomId)), - { - whiteboard_access: payload, - }, - ) + return this.post(this.url(`/event_rooms/${roomId}/whiteboard_access`), { + whiteboard_access: payload, + }) } }