From d50a3ae89eaae887004e38ef0f3edcf911433b77 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Fri, 7 Jul 2023 17:34:01 -0300 Subject: [PATCH] chore: fix multiple login attempt (#40) Co-authored-by: Diego Sampaio --- src/client/Client.ts | 10 ++- src/client/Omnichannel.ts | 16 ++-- src/client/WebClient.ts | 145 +++++++++++++++--------------- src/errors/AlreadyLoggingError.ts | 5 ++ src/lib/prom.ts | 18 ++++ src/profile.ts | 134 ++++++++++++++++----------- 6 files changed, 192 insertions(+), 136 deletions(-) create mode 100644 src/errors/AlreadyLoggingError.ts diff --git a/src/client/Client.ts b/src/client/Client.ts index b735d54..905d2f2 100644 --- a/src/client/Client.ts +++ b/src/client/Client.ts @@ -31,7 +31,9 @@ const useSsl = typeof SSL_ENABLED !== 'undefined' ? ['yes', 'true'].includes(SSL export type ClientType = 'web' | 'android' | 'ios'; // eslint-disable-next-line @typescript-eslint/naming-convention -interface ClientLoadTest { +export interface ClientLoadTest { + status: 'logged' | 'not-logged' | 'logging'; + joinRoom(rid: string): Promise; setStatus(): Promise; @@ -84,8 +86,6 @@ export class Client implements ClientLoadTest { subscribedToLivechat = false; - loggedIn = false; - constructor( host: string, type: 'web' | 'android' | 'ios', @@ -116,6 +116,8 @@ export class Client implements ClientLoadTest { this.subscribe = prom.promWrapperSubscribe((...args) => this.client.subscribe(...args)); } + status: 'logged' | 'not-logged' | 'logging' = 'not-logged'; + get: IAPIRequest; post: IAPIRequest; @@ -220,7 +222,7 @@ export class Client implements ClientLoadTest { await Promise.all(this.getLoginMethods().map((params) => this.client.methodCall(...params))); - this.loggedIn = true; + this.status = 'logged'; } async listenPresence(_userIds: string[]): Promise { diff --git a/src/client/Omnichannel.ts b/src/client/Omnichannel.ts index 53a3529..c6840b9 100644 --- a/src/client/Omnichannel.ts +++ b/src/client/Omnichannel.ts @@ -13,7 +13,7 @@ export class OmnichannelClient extends Client { } async getRoutingConfig(): Promise<{ [k: string]: string } | undefined> { - if (!this.loggedIn) { + if (this.status === 'logging') { await this.login(); } @@ -29,7 +29,7 @@ export class OmnichannelClient extends Client { } async getAgentDepartments(): Promise<{ departments: Department[] } | undefined> { - if (!this.loggedIn) { + if (this.status === 'logging') { await this.login(); } @@ -45,7 +45,7 @@ export class OmnichannelClient extends Client { } async getQueuedInquiries(): Promise<{ inquiries: Inquiry[] } | undefined> { - if (!this.loggedIn) { + if (this.status === 'logging') { await this.login(); } @@ -65,7 +65,7 @@ export class OmnichannelClient extends Client { return; } - if (!this.loggedIn) { + if (this.status === 'logging') { await this.login(); } @@ -86,7 +86,7 @@ export class OmnichannelClient extends Client { } async takeInquiry(id: string): Promise { - if (!this.loggedIn) { + if (this.status === 'logging') { await this.login(); } @@ -105,7 +105,7 @@ export class OmnichannelClient extends Client { } async getInquiry(id: string): Promise { - if (!this.loggedIn) { + if (this.status === 'logging') { await this.login(); } @@ -123,7 +123,7 @@ export class OmnichannelClient extends Client { } async getVisitorInfo(vid: string): Promise { - if (!this.loggedIn) { + if (this.status === 'logging') { await this.login(); } @@ -140,7 +140,7 @@ export class OmnichannelClient extends Client { } async openLivechatRoom(rid: string, vid: string): Promise { - if (!this.loggedIn) { + if (this.status === 'logging') { await this.login(); } diff --git a/src/client/WebClient.ts b/src/client/WebClient.ts index 4b7f63c..2acd131 100644 --- a/src/client/WebClient.ts +++ b/src/client/WebClient.ts @@ -26,82 +26,79 @@ export class WebClient extends Client { @suppressError @action async login(): Promise { - if (this.loginPromise) { - return this.loginPromise; + if (this.status === 'logged') { + throw new Error('Already logged in'); } - this.loginPromise = new Promise(async (resolve, reject) => { - const { credentials } = this; - - try { - await this.beforeLogin(); - - const user = await this.client.login(credentials); - - // await this.subscribeLoggedNotify(); - await Promise.all( - [ - 'deleteCustomSound', - 'updateCustomSound', - 'updateEmojiCustom', - 'deleteEmojiCustom', - 'deleteCustomUserStatus', - 'updateCustomUserStatus', - 'banner-changed', - 'updateAvatar', - 'Users:NameChanged', - 'Users:Deleted', - 'roles-change', - 'voip.statuschanged', - 'permissions-changed', - ].map((event) => this.subscribe('stream-notify-logged', event, false)), - ); - - // await subscribeNotifyUser(); - await Promise.all( - [ - 'uiInteraction', - 'video-conference', - 'force_logout', - 'message', - 'subscriptions-changed', - 'notification', - 'otr', - 'rooms-changed', - 'webrtc', - 'userData', - ].map((event) => this.subscribe('stream-notify-user', `${user.id}/${event}`, false)), - ); - - await Promise.all( - [ - 'app/added', - 'app/removed', - 'app/updated', - 'app/settingUpdated', - 'command/added', - 'command/disabled', - 'command/updated', - 'command/removed', - 'actions/changed', - ].map((event) => this.subscribe('stream-apps', event, false)), - ); - - await Promise.all(this.getLoginMethods().map((params) => this.methodViaRest(...params))); - - const subscriptions = await this.methodViaRest('subscriptions/get', {}); - - this.subscriptions = subscriptions as unknown as Subscription[]; - - this.loggedIn = true; - - resolve(); - } catch (error) { - reject({ error, credentials }); - } - }); - - return this.loginPromise; + if (this.status === 'logging') { + throw new Error('Already logging in'); + } + + // TODO if an error happens, we should rollback the status to not-logged + this.status = 'logging'; + + const { credentials } = this; + + await this.beforeLogin(); + + const user = await this.client.login(credentials); + + // await this.subscribeLoggedNotify(); + await Promise.all( + [ + 'deleteCustomSound', + 'updateCustomSound', + 'updateEmojiCustom', + 'deleteEmojiCustom', + 'deleteCustomUserStatus', + 'updateCustomUserStatus', + 'banner-changed', + 'updateAvatar', + 'Users:NameChanged', + 'Users:Deleted', + 'roles-change', + 'voip.statuschanged', + 'permissions-changed', + ].map((event) => this.subscribe('stream-notify-logged', event, false)), + ); + + // await subscribeNotifyUser(); + await Promise.all( + [ + 'uiInteraction', + 'video-conference', + 'force_logout', + 'message', + 'subscriptions-changed', + 'notification', + 'otr', + 'rooms-changed', + 'webrtc', + 'userData', + ].map((event) => this.subscribe('stream-notify-user', `${user.id}/${event}`, false)), + ); + + await Promise.all( + [ + 'app/added', + 'app/removed', + 'app/updated', + 'app/settingUpdated', + 'command/added', + 'command/disabled', + 'command/updated', + 'command/removed', + 'actions/changed', + ].map((event) => this.subscribe('stream-apps', event, false)), + ); + + await Promise.all(this.getLoginMethods().map((params) => this.methodViaRest(...params))); + + const subscriptions = await this.methodViaRest('subscriptions/get', {}); + + this.subscriptions = subscriptions as unknown as Subscription[]; + + this.status = 'logged'; } @suppressError diff --git a/src/errors/AlreadyLoggingError.ts b/src/errors/AlreadyLoggingError.ts new file mode 100644 index 0000000..cc4601d --- /dev/null +++ b/src/errors/AlreadyLoggingError.ts @@ -0,0 +1,5 @@ +export class AlreadyLoggingError extends Error { + constructor() { + super('Already logging'); + } +} diff --git a/src/lib/prom.ts b/src/lib/prom.ts index 430973b..730323e 100644 --- a/src/lib/prom.ts +++ b/src/lib/prom.ts @@ -75,6 +75,24 @@ export default client; export { client }; +const promWrapperBase = +

(prom: P) => + Promise>(action: string, fn: F): F => { + return (async (...args: any[]) => { + const endTimer = prom.startTimer({ action }); + try { + const result = await fn(...args); + endTimer({ status: 'success' }); + return result; + } catch (e) { + endTimer({ status: 'error' }); + throw e; + } + }) as unknown as F; + }; + +export const promWrapperAction = promWrapperBase(actions); + export const promWrapperRest = Promise>(method: string, fn: F): F => { return (async (url: string, ...args: any[]) => { const [endpoint] = url.split('?'); diff --git a/src/profile.ts b/src/profile.ts index 658aa30..aa3d486 100644 --- a/src/profile.ts +++ b/src/profile.ts @@ -8,15 +8,38 @@ import { getRandomInt, rand } from './lib/rand'; import { getClients } from './macros/getClients'; import { populateDatabase, isFullPopulation } from './populate'; import { WebClient } from './client/WebClient'; +import { AlreadyLoggingError } from './errors/AlreadyLoggingError'; + +const suppressError = Promise>(fn: F): F => { + return (async (...args: any) => { + try { + return await fn(...args); + } catch (error) { + // ignore AlreadyLoggingError + if (error instanceof AlreadyLoggingError) { + return; + } + console.error(error); + } + }) as F; +}; export default (): void => { let clients: Client[]; async function getLoggedInClient() { const client = rand(clients); - if (!client.loggedIn) { - await client.login(); + + if (client.status === 'logging') { + throw new AlreadyLoggingError(); } + + if (client.status === 'logged') { + return client; + } + + await client.login(); + return client; } @@ -71,10 +94,6 @@ export default (): void => { async setup() { clients = await getClients(WebClient, config.HOW_MANY_USERS); - - // if (config.JOIN_ROOM) { - // await joinRooms(clients); - // } } })({ message: config.MESSAGES_PER_SECOND, @@ -88,66 +107,81 @@ export default (): void => { console.log('Starting sending messages'); }); - b.on('message', async () => { - const client = await getLoggedInClient(); + b.on( + 'message', + suppressError(async () => { + const client = await getLoggedInClient(); - const subscription = client.getRandomSubscription(); + const subscription = client.getRandomSubscription(); - if (!subscription) { - return; - } - try { - await client.sendMessage(config.MESSAGE, subscription.rid); - } catch (error) { - console.error('Error sending message', error); - } - }); + if (!subscription) { + return; + } + try { + await client.sendMessage(config.MESSAGE, subscription.rid); + } catch (error) { + console.error('Error sending message', error); + } + }), + ); - b.on('setUserStatus', async () => { - const client = await getLoggedInClient(); + b.on( + 'setUserStatus', + suppressError(async () => { + const client = await getLoggedInClient(); - await client.setStatus(); - }); + await client.setStatus(); + }), + ); - b.on('readMessages', async () => { - const client = await getLoggedInClient(); + b.on( + 'readMessages', + suppressError(async () => { + const client = await getLoggedInClient(); - const subscription = client.getRandomSubscription(); - if (!subscription) { - return; - } + const subscription = client.getRandomSubscription(); + if (!subscription) { + return; + } - await client.read(subscription.rid); - }); + await client.read(subscription.rid); + }), + ); - b.on('openRoom', async () => { - const client = await getLoggedInClient(); + b.on( + 'openRoom', + suppressError(async () => { + const client = await getLoggedInClient(); - const subscription = client.getRandomSubscription(); - if (!subscription) { - return; - } - await client.openRoom(subscription.rid); - }); + const subscription = client.getRandomSubscription(); + if (!subscription) { + return; + } + await client.openRoom(subscription.rid); + }), + ); - b.on('subscribePresence', async () => { - const client = await getLoggedInClient(); + b.on( + 'subscribePresence', + suppressError(async () => { + const client = await getLoggedInClient(); - // change half the subscriptions to presence - const newSubs = Math.min(Math.round(client.getManyPresences() / 2), 1); + // change half the subscriptions to presence + const newSubs = Math.min(Math.round(client.getManyPresences() / 2), 1); - const newIds = []; + const newIds = []; - for (let i = 0; i < newSubs; i++) { - newIds.push(userId(getRandomInt(config.HOW_MANY_USERS))); - } + for (let i = 0; i < newSubs; i++) { + newIds.push(userId(getRandomInt(config.HOW_MANY_USERS))); + } - const userIds = client.usersPresence.slice(newSubs); + const userIds = client.usersPresence.slice(newSubs); - userIds.push(...newIds); + userIds.push(...newIds); - await client.listenPresence(userIds); - }); + await client.listenPresence(userIds); + }), + ); b.run().catch((e) => { console.error('Error during run', e);