diff --git a/docs/api/README.md b/docs/api/README.md index b349d5c..18e810c 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -8,12 +8,14 @@ Genesys Web Messaging Tester - [BotDisconnectedWaitingForResponseError](classes/BotDisconnectedWaitingForResponseError.md) - [Conversation](classes/Conversation.md) +- [ReorderedMessageDelayer](classes/ReorderedMessageDelayer.md) - [SessionTranscriber](classes/SessionTranscriber.md) - [TimeoutWaitingForResponseError](classes/TimeoutWaitingForResponseError.md) - [WebMessengerGuestSession](classes/WebMessengerGuestSession.md) ### Interfaces +- [MessageDelayer](interfaces/MessageDelayer.md) - [SessionConfig](interfaces/SessionConfig.md) - [SessionResponse](interfaces/SessionResponse.md) - [StructuredMessage](interfaces/StructuredMessage.md) diff --git a/docs/api/classes/Conversation.md b/docs/api/classes/Conversation.md index 4b8bcc4..82b4143 100644 --- a/docs/api/classes/Conversation.md +++ b/docs/api/classes/Conversation.md @@ -55,7 +55,7 @@ console.log(reply); #### Defined in -[packages/genesys-web-messaging-tester/src/Conversation.ts:126](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/Conversation.ts#L126) +[packages/genesys-web-messaging-tester/src/Conversation.ts:133](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/Conversation.ts#L133) ## Methods @@ -78,7 +78,7 @@ Sends text to the conversation #### Defined in -[packages/genesys-web-messaging-tester/src/Conversation.ts:166](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/Conversation.ts#L166) +[packages/genesys-web-messaging-tester/src/Conversation.ts:175](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/Conversation.ts#L175) ___ @@ -97,7 +97,7 @@ background. This method allows you to wait for this process to finish. #### Defined in -[packages/genesys-web-messaging-tester/src/Conversation.ts:145](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/Conversation.ts#L145) +[packages/genesys-web-messaging-tester/src/Conversation.ts:154](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/Conversation.ts#L154) ___ @@ -115,7 +115,7 @@ If you want to wait for a specific message use [waitForResponseWithTextContainin #### Defined in -[packages/genesys-web-messaging-tester/src/Conversation.ts:188](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/Conversation.ts#L188) +[packages/genesys-web-messaging-tester/src/Conversation.ts:197](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/Conversation.ts#L197) ___ @@ -147,7 +147,7 @@ use [waitForResponseText](Conversation.md#waitforresponsetext). #### Defined in -[packages/genesys-web-messaging-tester/src/Conversation.ts:246](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/Conversation.ts#L246) +[packages/genesys-web-messaging-tester/src/Conversation.ts:255](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/Conversation.ts#L255) ___ @@ -176,7 +176,7 @@ use [waitForResponseText](Conversation.md#waitforresponsetext). #### Defined in -[packages/genesys-web-messaging-tester/src/Conversation.ts:277](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/Conversation.ts#L277) +[packages/genesys-web-messaging-tester/src/Conversation.ts:286](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/Conversation.ts#L286) ___ @@ -198,4 +198,4 @@ Wait for all responses until there is a predefined amount of 'silence'. #### Defined in -[packages/genesys-web-messaging-tester/src/Conversation.ts:204](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/Conversation.ts#L204) +[packages/genesys-web-messaging-tester/src/Conversation.ts:213](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/Conversation.ts#L213) diff --git a/docs/api/classes/ReorderedMessageDelayer.md b/docs/api/classes/ReorderedMessageDelayer.md new file mode 100644 index 0000000..5c70a39 --- /dev/null +++ b/docs/api/classes/ReorderedMessageDelayer.md @@ -0,0 +1,710 @@ +[Genesys Web Messaging Tester](../README.md) / ReorderedMessageDelayer + +# Class: ReorderedMessageDelayer + +Reorders messages with a timestamp, being sure to maintain the overall order of messages with/without +timestamps. + +> All messaging follows a request/response pattern. However, web messaging is an asynchronous +> channel and therefore no guarantee to ordering is provided. +> Source: https://developer.genesys.cloud/commdigital/digital/webmessaging/websocketapi#messaging + +## Hierarchy + +- `EventEmitter` + + ↳ **`ReorderedMessageDelayer`** + +## Implements + +- [`MessageDelayer`](../interfaces/MessageDelayer.md) + +## Table of contents + +### Constructors + +- [constructor](ReorderedMessageDelayer.md#constructor) + +### Properties + +- [captureRejectionSymbol](ReorderedMessageDelayer.md#capturerejectionsymbol) +- [captureRejections](ReorderedMessageDelayer.md#capturerejections) +- [defaultMaxListeners](ReorderedMessageDelayer.md#defaultmaxlisteners) +- [errorMonitor](ReorderedMessageDelayer.md#errormonitor) + +### Accessors + +- [delay](ReorderedMessageDelayer.md#delay) + +### Methods + +- [add](ReorderedMessageDelayer.md#add) +- [addListener](ReorderedMessageDelayer.md#addlistener) +- [emit](ReorderedMessageDelayer.md#emit) +- [eventNames](ReorderedMessageDelayer.md#eventnames) +- [getMaxListeners](ReorderedMessageDelayer.md#getmaxlisteners) +- [listenerCount](ReorderedMessageDelayer.md#listenercount) +- [listeners](ReorderedMessageDelayer.md#listeners) +- [off](ReorderedMessageDelayer.md#off) +- [on](ReorderedMessageDelayer.md#on) +- [once](ReorderedMessageDelayer.md#once) +- [prependListener](ReorderedMessageDelayer.md#prependlistener) +- [prependOnceListener](ReorderedMessageDelayer.md#prependoncelistener) +- [rawListeners](ReorderedMessageDelayer.md#rawlisteners) +- [removeAllListeners](ReorderedMessageDelayer.md#removealllisteners) +- [removeListener](ReorderedMessageDelayer.md#removelistener) +- [setMaxListeners](ReorderedMessageDelayer.md#setmaxlisteners) +- [listenerCount](ReorderedMessageDelayer.md#listenercount-1) +- [on](ReorderedMessageDelayer.md#on-1) +- [once](ReorderedMessageDelayer.md#once-1) + +## Constructors + +### constructor + +• **new ReorderedMessageDelayer**(`delayBeforeEmittingInMs?`, `intervalInMs?`, `intervalSet?`, `intervalClear?`) + +#### Parameters + +| Name | Type | Default value | +| :------ | :------ | :------ | +| `delayBeforeEmittingInMs` | `number` | `5000` | +| `intervalInMs` | `number` | `1000` | +| `intervalSet` | (`callback`: (...`args`: `any`[]) => `void`, `ms?`: `number`, ...`args`: `any`[]) => `NodeJS.Timeout` | `setInterval` | +| `intervalClear` | (`intervalId`: `undefined` \| `string` \| `number` \| `Timeout`) => `void` | `clearInterval` | + +#### Overrides + +EventEmitter.constructor + +#### Defined in + +[packages/genesys-web-messaging-tester/src/genesys/message-delayer/ReorderedMessageDelayer.ts:32](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/genesys/message-delayer/ReorderedMessageDelayer.ts#L32) + +## Properties + +### captureRejectionSymbol + +▪ `Static` `Readonly` **captureRejectionSymbol**: typeof [`captureRejectionSymbol`](WebMessengerGuestSession.md#capturerejectionsymbol) + +#### Inherited from + +EventEmitter.captureRejectionSymbol + +#### Defined in + +node_modules/@types/node/events.d.ts:38 + +___ + +### captureRejections + +▪ `Static` **captureRejections**: `boolean` + +Sets or gets the default captureRejection value for all emitters. + +#### Inherited from + +EventEmitter.captureRejections + +#### Defined in + +node_modules/@types/node/events.d.ts:44 + +___ + +### defaultMaxListeners + +▪ `Static` **defaultMaxListeners**: `number` + +#### Inherited from + +EventEmitter.defaultMaxListeners + +#### Defined in + +node_modules/@types/node/events.d.ts:45 + +___ + +### errorMonitor + +▪ `Static` `Readonly` **errorMonitor**: typeof [`errorMonitor`](WebMessengerGuestSession.md#errormonitor) + +This symbol shall be used to install a listener for only monitoring `'error'` +events. Listeners installed using this symbol are called before the regular +`'error'` listeners are called. + +Installing a listener using this symbol does not change the behavior once an +`'error'` event is emitted, therefore the process will still crash if no +regular `'error'` listener is installed. + +#### Inherited from + +EventEmitter.errorMonitor + +#### Defined in + +node_modules/@types/node/events.d.ts:37 + +## Accessors + +### delay + +• `get` **delay**(): `number` + +#### Returns + +`number` + +#### Implementation of + +MessageDelayer.delay + +#### Defined in + +[packages/genesys-web-messaging-tester/src/genesys/message-delayer/ReorderedMessageDelayer.ts:137](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/genesys/message-delayer/ReorderedMessageDelayer.ts#L137) + +## Methods + +### add + +▸ **add**(`message`, `received`): `void` + +Add a message to the pool. Each message added reset a timer to wait for any other messages +before releasing the oldest message. + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `message` | `Response`<`unknown`\> | +| `received` | `Date` | + +#### Returns + +`void` + +#### Implementation of + +[MessageDelayer](../interfaces/MessageDelayer.md).[add](../interfaces/MessageDelayer.md#add) + +#### Defined in + +[packages/genesys-web-messaging-tester/src/genesys/message-delayer/ReorderedMessageDelayer.ts:67](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/genesys/message-delayer/ReorderedMessageDelayer.ts#L67) + +___ + +### addListener + +▸ **addListener**(`event`, `listener`): [`ReorderedMessageDelayer`](ReorderedMessageDelayer.md) + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `event` | `string` \| `symbol` | +| `listener` | (...`args`: `any`[]) => `void` | + +#### Returns + +[`ReorderedMessageDelayer`](ReorderedMessageDelayer.md) + +#### Implementation of + +[MessageDelayer](../interfaces/MessageDelayer.md).[addListener](../interfaces/MessageDelayer.md#addlistener) + +#### Inherited from + +EventEmitter.addListener + +#### Defined in + +node_modules/@types/node/events.d.ts:57 + +___ + +### emit + +▸ **emit**(`event`, `...args`): `boolean` + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `event` | `string` \| `symbol` | +| `...args` | `any`[] | + +#### Returns + +`boolean` + +#### Implementation of + +[MessageDelayer](../interfaces/MessageDelayer.md).[emit](../interfaces/MessageDelayer.md#emit) + +#### Inherited from + +EventEmitter.emit + +#### Defined in + +node_modules/@types/node/events.d.ts:67 + +___ + +### eventNames + +▸ **eventNames**(): (`string` \| `symbol`)[] + +#### Returns + +(`string` \| `symbol`)[] + +#### Implementation of + +[MessageDelayer](../interfaces/MessageDelayer.md).[eventNames](../interfaces/MessageDelayer.md#eventnames) + +#### Inherited from + +EventEmitter.eventNames + +#### Defined in + +node_modules/@types/node/events.d.ts:72 + +___ + +### getMaxListeners + +▸ **getMaxListeners**(): `number` + +#### Returns + +`number` + +#### Implementation of + +[MessageDelayer](../interfaces/MessageDelayer.md).[getMaxListeners](../interfaces/MessageDelayer.md#getmaxlisteners) + +#### Inherited from + +EventEmitter.getMaxListeners + +#### Defined in + +node_modules/@types/node/events.d.ts:64 + +___ + +### listenerCount + +▸ **listenerCount**(`event`): `number` + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `event` | `string` \| `symbol` | + +#### Returns + +`number` + +#### Implementation of + +[MessageDelayer](../interfaces/MessageDelayer.md).[listenerCount](../interfaces/MessageDelayer.md#listenercount) + +#### Inherited from + +EventEmitter.listenerCount + +#### Defined in + +node_modules/@types/node/events.d.ts:68 + +___ + +### listeners + +▸ **listeners**(`event`): `Function`[] + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `event` | `string` \| `symbol` | + +#### Returns + +`Function`[] + +#### Implementation of + +[MessageDelayer](../interfaces/MessageDelayer.md).[listeners](../interfaces/MessageDelayer.md#listeners) + +#### Inherited from + +EventEmitter.listeners + +#### Defined in + +node_modules/@types/node/events.d.ts:65 + +___ + +### off + +▸ **off**(`event`, `listener`): [`ReorderedMessageDelayer`](ReorderedMessageDelayer.md) + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `event` | `string` \| `symbol` | +| `listener` | (...`args`: `any`[]) => `void` | + +#### Returns + +[`ReorderedMessageDelayer`](ReorderedMessageDelayer.md) + +#### Implementation of + +[MessageDelayer](../interfaces/MessageDelayer.md).[off](../interfaces/MessageDelayer.md#off) + +#### Inherited from + +EventEmitter.off + +#### Defined in + +node_modules/@types/node/events.d.ts:61 + +___ + +### on + +▸ **on**(`event`, `listener`): [`ReorderedMessageDelayer`](ReorderedMessageDelayer.md) + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `event` | `string` \| `symbol` | +| `listener` | (...`args`: `any`[]) => `void` | + +#### Returns + +[`ReorderedMessageDelayer`](ReorderedMessageDelayer.md) + +#### Implementation of + +[MessageDelayer](../interfaces/MessageDelayer.md).[on](../interfaces/MessageDelayer.md#on) + +#### Inherited from + +EventEmitter.on + +#### Defined in + +node_modules/@types/node/events.d.ts:58 + +___ + +### once + +▸ **once**(`event`, `listener`): [`ReorderedMessageDelayer`](ReorderedMessageDelayer.md) + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `event` | `string` \| `symbol` | +| `listener` | (...`args`: `any`[]) => `void` | + +#### Returns + +[`ReorderedMessageDelayer`](ReorderedMessageDelayer.md) + +#### Implementation of + +[MessageDelayer](../interfaces/MessageDelayer.md).[once](../interfaces/MessageDelayer.md#once) + +#### Inherited from + +EventEmitter.once + +#### Defined in + +node_modules/@types/node/events.d.ts:59 + +___ + +### prependListener + +▸ **prependListener**(`event`, `listener`): [`ReorderedMessageDelayer`](ReorderedMessageDelayer.md) + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `event` | `string` \| `symbol` | +| `listener` | (...`args`: `any`[]) => `void` | + +#### Returns + +[`ReorderedMessageDelayer`](ReorderedMessageDelayer.md) + +#### Implementation of + +[MessageDelayer](../interfaces/MessageDelayer.md).[prependListener](../interfaces/MessageDelayer.md#prependlistener) + +#### Inherited from + +EventEmitter.prependListener + +#### Defined in + +node_modules/@types/node/events.d.ts:70 + +___ + +### prependOnceListener + +▸ **prependOnceListener**(`event`, `listener`): [`ReorderedMessageDelayer`](ReorderedMessageDelayer.md) + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `event` | `string` \| `symbol` | +| `listener` | (...`args`: `any`[]) => `void` | + +#### Returns + +[`ReorderedMessageDelayer`](ReorderedMessageDelayer.md) + +#### Implementation of + +[MessageDelayer](../interfaces/MessageDelayer.md).[prependOnceListener](../interfaces/MessageDelayer.md#prependoncelistener) + +#### Inherited from + +EventEmitter.prependOnceListener + +#### Defined in + +node_modules/@types/node/events.d.ts:71 + +___ + +### rawListeners + +▸ **rawListeners**(`event`): `Function`[] + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `event` | `string` \| `symbol` | + +#### Returns + +`Function`[] + +#### Implementation of + +[MessageDelayer](../interfaces/MessageDelayer.md).[rawListeners](../interfaces/MessageDelayer.md#rawlisteners) + +#### Inherited from + +EventEmitter.rawListeners + +#### Defined in + +node_modules/@types/node/events.d.ts:66 + +___ + +### removeAllListeners + +▸ **removeAllListeners**(`event?`): [`ReorderedMessageDelayer`](ReorderedMessageDelayer.md) + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `event?` | `string` \| `symbol` | + +#### Returns + +[`ReorderedMessageDelayer`](ReorderedMessageDelayer.md) + +#### Implementation of + +[MessageDelayer](../interfaces/MessageDelayer.md).[removeAllListeners](../interfaces/MessageDelayer.md#removealllisteners) + +#### Inherited from + +EventEmitter.removeAllListeners + +#### Defined in + +node_modules/@types/node/events.d.ts:62 + +___ + +### removeListener + +▸ **removeListener**(`event`, `listener`): [`ReorderedMessageDelayer`](ReorderedMessageDelayer.md) + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `event` | `string` \| `symbol` | +| `listener` | (...`args`: `any`[]) => `void` | + +#### Returns + +[`ReorderedMessageDelayer`](ReorderedMessageDelayer.md) + +#### Implementation of + +[MessageDelayer](../interfaces/MessageDelayer.md).[removeListener](../interfaces/MessageDelayer.md#removelistener) + +#### Inherited from + +EventEmitter.removeListener + +#### Defined in + +node_modules/@types/node/events.d.ts:60 + +___ + +### setMaxListeners + +▸ **setMaxListeners**(`n`): [`ReorderedMessageDelayer`](ReorderedMessageDelayer.md) + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `n` | `number` | + +#### Returns + +[`ReorderedMessageDelayer`](ReorderedMessageDelayer.md) + +#### Implementation of + +[MessageDelayer](../interfaces/MessageDelayer.md).[setMaxListeners](../interfaces/MessageDelayer.md#setmaxlisteners) + +#### Inherited from + +EventEmitter.setMaxListeners + +#### Defined in + +node_modules/@types/node/events.d.ts:63 + +___ + +### listenerCount + +▸ `Static` **listenerCount**(`emitter`, `event`): `number` + +**`Deprecated`** + +since v4.0.0 + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `emitter` | `EventEmitter` | +| `event` | `string` \| `symbol` | + +#### Returns + +`number` + +#### Inherited from + +EventEmitter.listenerCount + +#### Defined in + +node_modules/@types/node/events.d.ts:26 + +___ + +### on + +▸ `Static` **on**(`emitter`, `event`): `AsyncIterableIterator`<`any`\> + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `emitter` | `EventEmitter` | +| `event` | `string` | + +#### Returns + +`AsyncIterableIterator`<`any`\> + +#### Inherited from + +EventEmitter.on + +#### Defined in + +node_modules/@types/node/events.d.ts:23 + +___ + +### once + +▸ `Static` **once**(`emitter`, `event`): `Promise`<`any`[]\> + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `emitter` | `NodeEventTarget` | +| `event` | `string` \| `symbol` | + +#### Returns + +`Promise`<`any`[]\> + +#### Inherited from + +EventEmitter.once + +#### Defined in + +node_modules/@types/node/events.d.ts:21 + +▸ `Static` **once**(`emitter`, `event`): `Promise`<`any`[]\> + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `emitter` | `DOMEventTarget` | +| `event` | `string` | + +#### Returns + +`Promise`<`any`[]\> + +#### Inherited from + +EventEmitter.once + +#### Defined in + +node_modules/@types/node/events.d.ts:22 diff --git a/docs/api/classes/WebMessengerGuestSession.md b/docs/api/classes/WebMessengerGuestSession.md index 7719572..e429218 100644 --- a/docs/api/classes/WebMessengerGuestSession.md +++ b/docs/api/classes/WebMessengerGuestSession.md @@ -26,6 +26,10 @@ https://developer.genesys.cloud/api/digital/webmessaging/websocketapi#configure- - [defaultMaxListeners](WebMessengerGuestSession.md#defaultmaxlisteners) - [errorMonitor](WebMessengerGuestSession.md#errormonitor) +### Accessors + +- [messageDelayInMs](WebMessengerGuestSession.md#messagedelayinms) + ### Methods - [addListener](WebMessengerGuestSession.md#addlistener) @@ -53,7 +57,7 @@ https://developer.genesys.cloud/api/digital/webmessaging/websocketapi#configure- ### constructor -• **new WebMessengerGuestSession**(`config`, `participantData?`, `wsFactory?`) +• **new WebMessengerGuestSession**(`config`, `participantData?`, `wsFactory?`, `messageDelayer?`) #### Parameters @@ -62,6 +66,7 @@ https://developer.genesys.cloud/api/digital/webmessaging/websocketapi#configure- | `config` | [`SessionConfig`](../interfaces/SessionConfig.md) | | `participantData` | `Record`<`string`, `string`\> | | `wsFactory` | (`url`: `string`, `options?`: `ClientRequestArgs` \| `ClientOptions`) => `WebSocket` | +| `messageDelayer` | [`MessageDelayer`](../interfaces/MessageDelayer.md) | #### Overrides @@ -69,7 +74,7 @@ EventEmitter.constructor #### Defined in -[packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts:31](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts#L31) +[packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts:51](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts#L51) ## Properties @@ -94,7 +99,7 @@ EventEmitter.constructor #### Defined in -[packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts:34](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts#L34) +[packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts:54](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts#L54) ___ @@ -162,6 +167,20 @@ EventEmitter.errorMonitor node_modules/@types/node/events.d.ts:37 +## Accessors + +### messageDelayInMs + +• `get` **messageDelayInMs**(): `number` + +#### Returns + +`number` + +#### Defined in + +[packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts:71](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts#L71) + ## Methods ### addListener @@ -199,7 +218,7 @@ ___ #### Defined in -[packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts:121](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts#L121) +[packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts:147](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts#L147) ___ @@ -526,7 +545,7 @@ ___ #### Defined in -[packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts:98](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts#L98) +[packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts:124](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts#L124) ___ diff --git a/docs/api/interfaces/MessageDelayer.md b/docs/api/interfaces/MessageDelayer.md new file mode 100644 index 0000000..87d44af --- /dev/null +++ b/docs/api/interfaces/MessageDelayer.md @@ -0,0 +1,433 @@ +[Genesys Web Messaging Tester](../README.md) / MessageDelayer + +# Interface: MessageDelayer + +Provides the ability to delay messages for the purpose of re-ordering them. +This is useful for reordering messages that are received out of order, presumably +due to it being async and not guaranteeing order. + +## Hierarchy + +- `EventEmitter` + + ↳ **`MessageDelayer`** + +## Implemented by + +- [`ReorderedMessageDelayer`](../classes/ReorderedMessageDelayer.md) + +## Table of contents + +### Accessors + +- [delay](MessageDelayer.md#delay) + +### Methods + +- [add](MessageDelayer.md#add) +- [addListener](MessageDelayer.md#addlistener) +- [emit](MessageDelayer.md#emit) +- [eventNames](MessageDelayer.md#eventnames) +- [getMaxListeners](MessageDelayer.md#getmaxlisteners) +- [listenerCount](MessageDelayer.md#listenercount) +- [listeners](MessageDelayer.md#listeners) +- [off](MessageDelayer.md#off) +- [on](MessageDelayer.md#on) +- [once](MessageDelayer.md#once) +- [prependListener](MessageDelayer.md#prependlistener) +- [prependOnceListener](MessageDelayer.md#prependoncelistener) +- [rawListeners](MessageDelayer.md#rawlisteners) +- [removeAllListeners](MessageDelayer.md#removealllisteners) +- [removeListener](MessageDelayer.md#removelistener) +- [setMaxListeners](MessageDelayer.md#setmaxlisteners) + +## Accessors + +### delay + +• `get` **delay**(): `number` + +#### Returns + +`number` + +#### Defined in + +[packages/genesys-web-messaging-tester/src/genesys/message-delayer/MessageDelayer.ts:10](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/genesys/message-delayer/MessageDelayer.ts#L10) + +## Methods + +### add + +▸ **add**(`message`, `whenReceived`): `void` + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `message` | `Response`<`unknown`\> | +| `whenReceived` | `Date` | + +#### Returns + +`void` + +#### Defined in + +[packages/genesys-web-messaging-tester/src/genesys/message-delayer/MessageDelayer.ts:11](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/genesys/message-delayer/MessageDelayer.ts#L11) + +___ + +### addListener + +▸ **addListener**(`event`, `listener`): [`MessageDelayer`](MessageDelayer.md) + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `event` | `string` \| `symbol` | +| `listener` | (...`args`: `any`[]) => `void` | + +#### Returns + +[`MessageDelayer`](MessageDelayer.md) + +#### Inherited from + +EventEmitter.addListener + +#### Defined in + +node_modules/@types/node/events.d.ts:57 + +___ + +### emit + +▸ **emit**(`event`, `...args`): `boolean` + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `event` | `string` \| `symbol` | +| `...args` | `any`[] | + +#### Returns + +`boolean` + +#### Inherited from + +EventEmitter.emit + +#### Defined in + +node_modules/@types/node/events.d.ts:67 + +___ + +### eventNames + +▸ **eventNames**(): (`string` \| `symbol`)[] + +#### Returns + +(`string` \| `symbol`)[] + +#### Inherited from + +EventEmitter.eventNames + +#### Defined in + +node_modules/@types/node/events.d.ts:72 + +___ + +### getMaxListeners + +▸ **getMaxListeners**(): `number` + +#### Returns + +`number` + +#### Inherited from + +EventEmitter.getMaxListeners + +#### Defined in + +node_modules/@types/node/events.d.ts:64 + +___ + +### listenerCount + +▸ **listenerCount**(`event`): `number` + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `event` | `string` \| `symbol` | + +#### Returns + +`number` + +#### Inherited from + +EventEmitter.listenerCount + +#### Defined in + +node_modules/@types/node/events.d.ts:68 + +___ + +### listeners + +▸ **listeners**(`event`): `Function`[] + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `event` | `string` \| `symbol` | + +#### Returns + +`Function`[] + +#### Inherited from + +EventEmitter.listeners + +#### Defined in + +node_modules/@types/node/events.d.ts:65 + +___ + +### off + +▸ **off**(`event`, `listener`): [`MessageDelayer`](MessageDelayer.md) + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `event` | `string` \| `symbol` | +| `listener` | (...`args`: `any`[]) => `void` | + +#### Returns + +[`MessageDelayer`](MessageDelayer.md) + +#### Inherited from + +EventEmitter.off + +#### Defined in + +node_modules/@types/node/events.d.ts:61 + +___ + +### on + +▸ **on**(`event`, `listener`): [`MessageDelayer`](MessageDelayer.md) + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `event` | `string` \| `symbol` | +| `listener` | (...`args`: `any`[]) => `void` | + +#### Returns + +[`MessageDelayer`](MessageDelayer.md) + +#### Inherited from + +EventEmitter.on + +#### Defined in + +node_modules/@types/node/events.d.ts:58 + +___ + +### once + +▸ **once**(`event`, `listener`): [`MessageDelayer`](MessageDelayer.md) + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `event` | `string` \| `symbol` | +| `listener` | (...`args`: `any`[]) => `void` | + +#### Returns + +[`MessageDelayer`](MessageDelayer.md) + +#### Inherited from + +EventEmitter.once + +#### Defined in + +node_modules/@types/node/events.d.ts:59 + +___ + +### prependListener + +▸ **prependListener**(`event`, `listener`): [`MessageDelayer`](MessageDelayer.md) + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `event` | `string` \| `symbol` | +| `listener` | (...`args`: `any`[]) => `void` | + +#### Returns + +[`MessageDelayer`](MessageDelayer.md) + +#### Inherited from + +EventEmitter.prependListener + +#### Defined in + +node_modules/@types/node/events.d.ts:70 + +___ + +### prependOnceListener + +▸ **prependOnceListener**(`event`, `listener`): [`MessageDelayer`](MessageDelayer.md) + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `event` | `string` \| `symbol` | +| `listener` | (...`args`: `any`[]) => `void` | + +#### Returns + +[`MessageDelayer`](MessageDelayer.md) + +#### Inherited from + +EventEmitter.prependOnceListener + +#### Defined in + +node_modules/@types/node/events.d.ts:71 + +___ + +### rawListeners + +▸ **rawListeners**(`event`): `Function`[] + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `event` | `string` \| `symbol` | + +#### Returns + +`Function`[] + +#### Inherited from + +EventEmitter.rawListeners + +#### Defined in + +node_modules/@types/node/events.d.ts:66 + +___ + +### removeAllListeners + +▸ **removeAllListeners**(`event?`): [`MessageDelayer`](MessageDelayer.md) + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `event?` | `string` \| `symbol` | + +#### Returns + +[`MessageDelayer`](MessageDelayer.md) + +#### Inherited from + +EventEmitter.removeAllListeners + +#### Defined in + +node_modules/@types/node/events.d.ts:62 + +___ + +### removeListener + +▸ **removeListener**(`event`, `listener`): [`MessageDelayer`](MessageDelayer.md) + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `event` | `string` \| `symbol` | +| `listener` | (...`args`: `any`[]) => `void` | + +#### Returns + +[`MessageDelayer`](MessageDelayer.md) + +#### Inherited from + +EventEmitter.removeListener + +#### Defined in + +node_modules/@types/node/events.d.ts:60 + +___ + +### setMaxListeners + +▸ **setMaxListeners**(`n`): [`MessageDelayer`](MessageDelayer.md) + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `n` | `number` | + +#### Returns + +[`MessageDelayer`](MessageDelayer.md) + +#### Inherited from + +EventEmitter.setMaxListeners + +#### Defined in + +node_modules/@types/node/events.d.ts:63 diff --git a/docs/api/interfaces/SessionConfig.md b/docs/api/interfaces/SessionConfig.md index b3f934f..6aac9f1 100644 --- a/docs/api/interfaces/SessionConfig.md +++ b/docs/api/interfaces/SessionConfig.md @@ -18,7 +18,7 @@ #### Defined in -[packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts:17](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts#L17) +[packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts:29](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts#L29) ___ @@ -28,7 +28,7 @@ ___ #### Defined in -[packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts:19](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts#L19) +[packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts:31](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts#L31) ___ @@ -38,4 +38,4 @@ ___ #### Defined in -[packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts:18](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts#L18) +[packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts:30](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts#L30) diff --git a/docs/api/interfaces/SessionResponse.md b/docs/api/interfaces/SessionResponse.md index 8a8ec4a..26afad8 100644 --- a/docs/api/interfaces/SessionResponse.md +++ b/docs/api/interfaces/SessionResponse.md @@ -43,7 +43,7 @@ SuccessResponse.class #### Defined in -[packages/genesys-web-messaging-tester/src/genesys/SessionResponse.ts:10](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/genesys/SessionResponse.ts#L10) +[packages/genesys-web-messaging-tester/src/genesys/SessionResponse.ts:11](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/genesys/SessionResponse.ts#L11) ___ @@ -71,4 +71,4 @@ SuccessResponse.type #### Defined in -[packages/genesys-web-messaging-tester/src/genesys/SessionResponse.ts:9](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/genesys/SessionResponse.ts#L9) +[packages/genesys-web-messaging-tester/src/genesys/SessionResponse.ts:10](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/genesys/SessionResponse.ts#L10) diff --git a/docs/api/interfaces/WebMessengerSession.md b/docs/api/interfaces/WebMessengerSession.md index 31f0c4b..e0516df 100644 --- a/docs/api/interfaces/WebMessengerSession.md +++ b/docs/api/interfaces/WebMessengerSession.md @@ -10,6 +10,10 @@ ## Table of contents +### Accessors + +- [messageDelayInMs](WebMessengerSession.md#messagedelayinms) + ### Methods - [addListener](WebMessengerSession.md#addlistener) @@ -30,6 +34,27 @@ - [sendText](WebMessengerSession.md#sendtext) - [setMaxListeners](WebMessengerSession.md#setmaxlisteners) +## Accessors + +### messageDelayInMs + +• `get` **messageDelayInMs**(): `number` + +The Web Messenger server can sometimes return responses out of order. To cater for this +we have to have a delay after every message is received before passing it to any listeners +of the implementation. This delay hopefully provides enough time for any messages that should +have preceded the other to be received and ordered. + +This delay should be taken into account for any timeout values of downstream functionality. + +#### Returns + +`number` + +#### Defined in + +[packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts:21](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts#L21) + ## Methods ### addListener @@ -67,7 +92,7 @@ ___ #### Defined in -[packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts:13](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts#L13) +[packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts:25](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts#L25) ___ @@ -394,7 +419,7 @@ ___ #### Defined in -[packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts:11](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts#L11) +[packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts:23](https://github.com/ovotech/genesys-web-messaging-tester/blob/main/packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts#L23) ___ diff --git a/docs/release-strategy.md b/docs/release-strategy.md index fa49818..c0883cf 100644 --- a/docs/release-strategy.md +++ b/docs/release-strategy.md @@ -6,8 +6,20 @@ This is the process for publishing a change to the NPM registry: 2. Once merged into `main` create tags for whichever package(s) changed: * `git tag genesys-web-messaging-tester-vX.X.X` * `git tag genesys-web-messaging-tester-cli-vX.X.X` -3. Using GitHub's UI create a Release based on the most recent tag +3. Push the tags + * `git push origin genesys-web-messaging-tester-vX.X.X` + * `git push origin genesys-web-messaging-tester-cli-vX.X.X` +4. Using GitHub's UI create a Release based on the most recent tag _If a merged PR increased the version of both packages then create the two tags (following the format in the step above) then create a release off of the tag created last._ + + +## Beta releases + +1. Suffix the versions with `-beta.0`, increase the number for subsequent publishes +2. Publish the beta version + ``` + npm publish --tag beta + ``` diff --git a/docs/unordered-messages.md b/docs/unordered-messages.md new file mode 100644 index 0000000..f418ac8 --- /dev/null +++ b/docs/unordered-messages.md @@ -0,0 +1,32 @@ +# Handling Unordered Messages + +The Web Messenger server can sometimes return responses out of order, as mentioned in the documentation: +> All messaging follows a request/response pattern. However, web messaging is an asynchronous +> channel and therefore no guarantee to ordering is provided. +> Source: https://developer.genesys.cloud/commdigital/digital/webmessaging/websocketapi#messaging + +I suspect the official embeddable client deals with this by re-ordering the messages on the fly in the UI. However, +this approach is problematic for this tool, as it asserts on the order of messages being received: + +```yaml +#... +scenarios: + "Accept Survey": + - waitForReplyContaining: Welcome to our company's chatbot # Expected to come first + - waitForReplyContaining: How can I help you? +``` + +This tool handles out-of-order messages by delaying their propagation into the rest of the tool. During this delay messages +can then be re-reordered. + +The delay, detection and re-ordering of messages all happens in the [`ReorderedMessageDelayer`](./api/classes/ReorderedMessageDelayer.md) class + +## How the CLI handles out-of-order messages + +The approach guaranteed to address the issue of out-of-order messages is to configure a long delay when instantiating [`ReorderedMessageDelayer`](./api/classes/ReorderedMessageDelayer.md). +However, a longer delay between messages results in longer tests, at OVO this meant a suite of tests that usually took 14 mins taking 54 mins. + +The CLI addresses this problem by starting all tests with a very short delay. Should a test fail then it will check to see if +the conversation contained any unordered messages and if so will retry the test with a long delay. This means the majority of tests +will continue to run at normal speed, and only those that have deemed to have failed due to unordered messages are forced to endure a +long delay between messages. diff --git a/examples/api/package.json b/examples/api/package.json index 623c1fd..18cd55d 100644 --- a/examples/api/package.json +++ b/examples/api/package.json @@ -10,11 +10,11 @@ "test:ts": "node -r ts-node/register ../api/src/ts-script.ts" }, "devDependencies": { - "@ovotech/genesys-web-messaging-tester": "*", + "@ovotech/genesys-web-messaging-tester": "2.0.5", "@types/jest": "^29.0.3", "@types/node": "^14.14.2", - "@typescript-eslint/eslint-plugin": "^4.31.1", - "@typescript-eslint/parser": "^4.31.1", + "@typescript-eslint/eslint-plugin": "^5.0.0", + "@typescript-eslint/parser": "^5.0.0", "dotenv": "^16.3.1", "eslint": "^7.32.0", "eslint-config-prettier": "^8.3.0", diff --git a/packages/genesys-web-messaging-tester-cli/__tests__/RetryOnUnorderedMessageFailure.spec.ts b/packages/genesys-web-messaging-tester-cli/__tests__/RetryOnUnorderedMessageFailure.spec.ts new file mode 100644 index 0000000..dcc8ce5 --- /dev/null +++ b/packages/genesys-web-messaging-tester-cli/__tests__/RetryOnUnorderedMessageFailure.spec.ts @@ -0,0 +1,195 @@ +import { Cli, createCli } from '../src/cli'; +import { accessSync, readFileSync } from 'fs'; +import { Command } from 'commander'; +import { + Conversation, + WebMessageServerFixture, + WebMessengerGuestSession, +} from '@ovotech/genesys-web-messaging-tester'; +import stripAnsi from 'strip-ansi'; +import getPort from 'get-port'; +import WebSocket from 'ws'; +import { waitForMs } from './fixtures/wait'; + +jest.setTimeout(50000); +describe('Test script YAML loaded', () => { + let genesysServerFixture: WebMessageServerFixture; + let latestConversation: Conversation; + + let capturedOutput: { + errOut: string[]; + stdOut: string[]; + }; + + let program: Command; + let fsReadFileSync: jest.MockedFunction; + let fsAccessSync: jest.MockedFunction; + + let cli: Cli; + + beforeEach(async () => { + genesysServerFixture = new WebMessageServerFixture(await getPort()); + + fsAccessSync = jest.fn(); + fsReadFileSync = jest.fn(); + + capturedOutput = { + errOut: [], + stdOut: [], + }; + + program = new Command(); + + program.configureOutput({ + writeErr: (str) => { + console.log(str); + capturedOutput.errOut.push(str); + }, + writeOut: (str: string) => { + console.log(str); + capturedOutput.stdOut.push(str); + }, + }); + + cli = createCli({ + program, + fsReadFileSync, + fsAccessSync, + conversationFactory: (session) => { + latestConversation = new Conversation(session); + return latestConversation; + }, + webMessengerSessionFactory: (config, reorderedMessageDelayer) => + new WebMessengerGuestSession( + config, + { IsTest: 'true' }, + reorderedMessageDelayer, + () => new WebSocket(`ws://localhost:${genesysServerFixture.port}`), + ), + processExitOverride: () => { + throw new Error('force app to exit'); + }, + }); + }); + + afterEach(() => { + genesysServerFixture.close(); + }); + + test('passes when messages are in order', async () => { + const yaml = ` +config: + deploymentId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + region: xxxx.pure.cloud +scenarios: + scenario1: + - say: hi + - waitForReplyContaining: hello1 + - waitForReplyContaining: hello2 +`; + + fsReadFileSync.mockReturnValue(yaml); + const cliPromise = cli([...['node', '/path/to/cli'], ...['/test/path/config.json']]); + + const serverConnection = await genesysServerFixture.waitForConnection(); + + serverConnection.simulateSessionResponseMessage(); + await latestConversation.waitForConversationToStart(); + + await serverConnection.waitForMessage(); + serverConnection.simulateOutboundTextStructuredMessage('hello1'); + await waitForMs(2000); + serverConnection.simulateOutboundTextStructuredMessage('hello2'); + + try { + await cliPromise; + } catch (e) { + console.error(e); + } + expect(capturedOutput.errOut.map(stripAnsi)).toStrictEqual([]); + expect(capturedOutput.stdOut.map(stripAnsi).join('')).toStrictEqual(` +scenario1 (PASS) +--------------------- +Them: hello1 +Them: hello2 + +Scenario Test Results +--------------------- +PASS - scenario1 +`); + }); + test('retries when messages are in out of order', async () => { + const yaml = ` +config: + deploymentId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + region: xxxx.pure.cloud +scenarios: + scenario1: + - say: hi + - waitForReplyContaining: hello1 + - waitForReplyContaining: hello2 +`; + + fsReadFileSync.mockReturnValue(yaml); + const cliPromise = cli([ + ...['node', '/path/to/cli'], + ...['/test/path/config.json', '--timeout', '6'], + ]); //.catch((e) => console.error(e)); + + const serverConnection = await genesysServerFixture.waitForConnection(); + + serverConnection.simulateSessionResponseMessage(); + await latestConversation.waitForConversationToStart(); + + await serverConnection.waitForMessage(); + serverConnection.simulateOutboundTextStructuredMessage( + 'hello2', + new Date('2001-01-01T01:01:16.001Z'), + ); + await waitForMs(3000); + serverConnection.simulateOutboundTextStructuredMessage( + 'hello1', + new Date('2001-01-01T01:01:13.001Z'), + ); + + const [, serverConnection2] = await Promise.all([ + serverConnection.waitForConnectionToClose(), + genesysServerFixture.waitForConnection(), + ]); + + // Retry + + serverConnection2.simulateSessionResponseMessage(); + await latestConversation.waitForConversationToStart(); + + await serverConnection2.waitForMessage(); + serverConnection2.simulateOutboundTextStructuredMessage( + 'hello2', + new Date('2001-01-01T01:01:16.001Z'), + ); + await waitForMs(3000); + serverConnection2.simulateOutboundTextStructuredMessage( + 'hello1', + new Date('2001-01-01T01:01:13.001Z'), + ); + + try { + await cliPromise; + } catch (e) { + console.error(e); + } + expect(capturedOutput.errOut.map(stripAnsi)).toStrictEqual([]); + expect(capturedOutput.stdOut.map(stripAnsi).join('')).toStrictEqual(` +scenario1 (PASS) +--------------------- +Them: hello1 +Them: hello2 + +Scenario Test Results +--------------------- +PASS - scenario1 + ^ This test was retried following a failure that coincided with unordered messages being being received from Genesys + Read more here: https://github.com/ovotech/genesys-web-messaging-tester/blob/main/docs/cli/unordered-messages.md +`); + }); +}); diff --git a/packages/genesys-web-messaging-tester-cli/__tests__/fixtures/wait.ts b/packages/genesys-web-messaging-tester-cli/__tests__/fixtures/wait.ts new file mode 100644 index 0000000..ced7879 --- /dev/null +++ b/packages/genesys-web-messaging-tester-cli/__tests__/fixtures/wait.ts @@ -0,0 +1,3 @@ +export async function waitForMs(interval: number): Promise { + await new Promise((resolve) => setTimeout(resolve, interval)); +} diff --git a/packages/genesys-web-messaging-tester-cli/__tests__/yamlTestScript/configSectionLoaded.spec.ts b/packages/genesys-web-messaging-tester-cli/__tests__/yamlTestScript/configSectionLoaded.spec.ts index 1533f1f..d988da3 100644 --- a/packages/genesys-web-messaging-tester-cli/__tests__/yamlTestScript/configSectionLoaded.spec.ts +++ b/packages/genesys-web-messaging-tester-cli/__tests__/yamlTestScript/configSectionLoaded.spec.ts @@ -1,11 +1,14 @@ import { Cli, createCli, Dependencies } from '../../src/cli'; import { readFileSync } from 'fs'; import { Command } from 'commander'; +import { ReorderedMessageDelayer } from '@ovotech/genesys-web-messaging-tester'; describe('Session Config', () => { let program: Command; let fsReadFileSync: jest.MockedFunction; + let reorderedMessageDelayer: jest.Mocked>; + let reorderedMessageDelayerFactory: jest.Mocked; let webMessengerSessionFactory: jest.Mocked; let conversationFactory: jest.Mocked; @@ -14,6 +17,9 @@ describe('Session Config', () => { beforeEach(() => { fsReadFileSync = jest.fn(); + reorderedMessageDelayer = { delay: 0, add: jest.fn(), on: jest.fn() }; + reorderedMessageDelayerFactory = jest.fn().mockReturnValue(reorderedMessageDelayer); + const webMessengerSession = { on: jest.fn(), close: jest.fn() }; webMessengerSessionFactory = jest.fn().mockReturnValue(webMessengerSession); @@ -26,6 +32,7 @@ describe('Session Config', () => { program, fsReadFileSync, fsAccessSync: jest.fn(), + reorderedMessageDelayerFactory, webMessengerSessionFactory, conversationFactory, processExitOverride: () => { @@ -52,11 +59,14 @@ scenarios: ...['/test/path'], ]); - expect(webMessengerSessionFactory).toHaveBeenCalledWith({ - deploymentId: 'test-deployment-id', - region: 'test-region', - origin: 'test-origin', - }); + expect(webMessengerSessionFactory).toHaveBeenCalledWith( + { + deploymentId: 'test-deployment-id', + region: 'test-region', + origin: 'test-origin', + }, + reorderedMessageDelayer, + ); }); test('Test-Script session config not necessary if session config args provided', async () => { @@ -74,11 +84,14 @@ scenarios: ...['/test/path'], ]); - expect(webMessengerSessionFactory).toHaveBeenCalledWith({ - deploymentId: 'test-deployment-id-2', - region: 'test-region-2', - origin: 'test-origin-2', - }); + expect(webMessengerSessionFactory).toHaveBeenCalledWith( + { + deploymentId: 'test-deployment-id-2', + region: 'test-region-2', + origin: 'test-origin-2', + }, + reorderedMessageDelayer, + ); }); test('Test-Script session config used if session config args not provided', async () => { @@ -94,10 +107,13 @@ scenarios: await cli([...['node', '/path/to/cli'], ...['/test/path']]); - expect(webMessengerSessionFactory).toHaveBeenCalledWith({ - deploymentId: 'test-deployment-id-3', - region: 'test-region-3', - origin: 'test-origin-3', - }); + expect(webMessengerSessionFactory).toHaveBeenCalledWith( + { + deploymentId: 'test-deployment-id-3', + region: 'test-region-3', + origin: 'test-origin-3', + }, + reorderedMessageDelayer, + ); }); }); diff --git a/packages/genesys-web-messaging-tester-cli/package.json b/packages/genesys-web-messaging-tester-cli/package.json index 4eed77d..41ece2e 100644 --- a/packages/genesys-web-messaging-tester-cli/package.json +++ b/packages/genesys-web-messaging-tester-cli/package.json @@ -1,6 +1,6 @@ { "name": "@ovotech/genesys-web-messaging-tester-cli", - "version": "1.0.13", + "version": "1.0.14", "main": "lib/index.js", "types": "lib/index.d.ts", "license": "Apache-2.0", @@ -27,7 +27,7 @@ "web-messaging-tester": "lib/index.js" }, "dependencies": { - "@ovotech/genesys-web-messaging-tester": "^2.0.4", + "@ovotech/genesys-web-messaging-tester": "2.0.5", "chalk": "^4.1.2", "ci-info": "^3.5.0", "commander": "^11.0.0", @@ -43,8 +43,8 @@ "@types/jest-when": "^3.5.2", "@types/js-yaml": "^4.0.5", "@types/node": "^14.14.2", - "@typescript-eslint/eslint-plugin": "^4.31.1", - "@typescript-eslint/parser": "^4.31.1", + "@typescript-eslint/eslint-plugin": "^5.0.0", + "@typescript-eslint/parser": "^5.0.0", "eslint": "^7.32.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-jest": "^27.2.3", diff --git a/packages/genesys-web-messaging-tester-cli/src/ScenarioResult.ts b/packages/genesys-web-messaging-tester-cli/src/ScenarioResult.ts index 1953420..dd32101 100644 --- a/packages/genesys-web-messaging-tester-cli/src/ScenarioResult.ts +++ b/packages/genesys-web-messaging-tester-cli/src/ScenarioResult.ts @@ -6,6 +6,7 @@ import { import { createConversationIdGetter } from './genesysPlatform/messageIdToConversationIdFactory'; export interface ScenarioResult { + wasRetriedDueToUnorderedMessageFailure: boolean; scenario: TestScriptScenario; transcription: TranscribedMessage[]; conversationId: diff --git a/packages/genesys-web-messaging-tester-cli/src/cli.ts b/packages/genesys-web-messaging-tester-cli/src/cli.ts index a74d514..1f0cd6a 100644 --- a/packages/genesys-web-messaging-tester-cli/src/cli.ts +++ b/packages/genesys-web-messaging-tester-cli/src/cli.ts @@ -8,9 +8,10 @@ import { createYamlFileReader } from './fileSystem/yamlFileReader'; import { extractScenarios } from './testScript/parseTestScript'; import { Conversation, + ReorderedMessageDelayer, SessionConfig, - TranscribedMessage, SessionTranscriber, + TranscribedMessage, WebMessengerGuestSession, WebMessengerSession, } from '@ovotech/genesys-web-messaging-tester'; @@ -24,6 +25,7 @@ import { messageIdToConversationIdFactory, MessageIdToConvoIdClient, } from './genesysPlatform/messageIdToConversationIdFactory'; +import { RetryTask, tryableTask } from './taskRetry'; function parsePositiveInt(value: string) { const parsedValue = parseInt(value, 10); @@ -45,7 +47,11 @@ export interface Dependencies { outputConsole?: Console; program?: Command; ui?: Ui; - webMessengerSessionFactory?: (sessionConfig: SessionConfig) => WebMessengerSession; + reorderedMessageDelayerFactory?: (delayBeforeEmittingInMs: number) => ReorderedMessageDelayer; + webMessengerSessionFactory?: ( + sessionConfig: SessionConfig, + reorderedMessageDelayer: ReorderedMessageDelayer, + ) => WebMessengerSession; conversationFactory?: (session: WebMessengerSession) => Conversation; fsReadFileSync?: typeof readFileSync; fsAccessSync?: typeof accessSync; @@ -69,7 +75,10 @@ export type Cli = (args: string[]) => Promise; export function createCli({ program = new Command(), ui = new Ui(), - webMessengerSessionFactory = (config) => new WebMessengerGuestSession(config, { IsTest: 'true' }), + reorderedMessageDelayerFactory = (delayBeforeEmittingInMs) => + new ReorderedMessageDelayer(delayBeforeEmittingInMs), + webMessengerSessionFactory = (config, reorderedMessageDelayer) => + new WebMessengerGuestSession(config, { IsTest: 'true' }, reorderedMessageDelayer), conversationFactory = (session) => new Conversation(session), fsReadFileSync = readFileSync, fsAccessSync = accessSync, @@ -205,17 +214,22 @@ GENESYSCLOUD_OAUTHCLIENT_SECRET`, const tasks = new Listr( testScriptScenarios.map((scenario) => ({ title: ui?.titleOfTask(scenario), - task: async (context, task) => { - const transcription: TranscribedMessage[] = []; - const session = webMessengerSessionFactory(scenario.sessionConfig); + task: async (context, task) => + tryableTask(async (isRetry) => { + const transcription: TranscribedMessage[] = []; + + const reorderedMessageDelayer = reorderedMessageDelayerFactory(isRetry ? 6000 : 1000); + const session = webMessengerSessionFactory( + scenario.sessionConfig, + reorderedMessageDelayer, + ); - let conversationIdGetter: ReturnType | undefined = - undefined; - if (associateId.enabled) { - conversationIdGetter = createConversationIdGetter(session, associateId.client); - } + let conversationIdGetter: ReturnType | undefined = + undefined; + if (associateId.enabled) { + conversationIdGetter = createConversationIdGetter(session, associateId.client); + } - try { new SessionTranscriber(session).on( 'messageTranscribed', (event: TranscribedMessage) => { @@ -239,35 +253,49 @@ GENESYSCLOUD_OAUTHCLIENT_SECRET`, const convo = conversationFactory(session); await convo.waitForConversationToStart(); - for (const step of scenario.steps) { - await step(convo, { timeoutInSeconds: options.timeout }); + let stepError: unknown; + try { + for (const step of scenario.steps) { + await step(convo, { timeoutInSeconds: options.timeout }); + } + } catch (e) { + if (!isRetry && reorderedMessageDelayer.unorderdMessageDetected) { + task.output = ui?.retryingTestDueToFailureLikelyByUnorderedMessage(); + task.title = ui?.titleOfTask(scenario, true); + throw new RetryTask(); + } else { + stepError = e; + } + } finally { + session.close(); } - } catch (error) { + + if (stepError) { + context.scenarioResults.push({ + scenario, + transcription, + wasRetriedDueToUnorderedMessageFailure: isRetry, + hasPassed: false, + reasonForError: + stepError instanceof Error ? stepError : new Error('Unexpected error occurred'), + conversationId: conversationIdGetter + ? { associateId: true, conversationIdGetter } + : { associateId: false }, + }); + throw new Error(ui?.titleOfFinishedTask(scenario, false)); + } + + task.title = ui?.titleOfFinishedTask(scenario, true); context.scenarioResults.push({ scenario, transcription, - hasPassed: false, - reasonForError: - error instanceof Error ? error : new Error('Unexpected error occurred'), + wasRetriedDueToUnorderedMessageFailure: isRetry, + hasPassed: true, conversationId: conversationIdGetter ? { associateId: true, conversationIdGetter } : { associateId: false }, }); - throw new Error(ui?.titleOfFinishedTask(scenario, false)); - } finally { - session.close(); - } - - task.title = ui?.titleOfFinishedTask(scenario, true); - context.scenarioResults.push({ - scenario, - transcription, - hasPassed: true, - conversationId: conversationIdGetter - ? { associateId: true, conversationIdGetter } - : { associateId: false }, - }); - }, + }), })), { concurrent: options.parallel, diff --git a/packages/genesys-web-messaging-tester-cli/src/taskRetry.ts b/packages/genesys-web-messaging-tester-cli/src/taskRetry.ts new file mode 100644 index 0000000..0e8f6b3 --- /dev/null +++ b/packages/genesys-web-messaging-tester-cli/src/taskRetry.ts @@ -0,0 +1,16 @@ +export class RetryTask extends Error { + constructor() { + super('Retrying'); + Object.setPrototypeOf(this, RetryTask.prototype); + } +} + +export async function tryableTask(func: (isRetry: boolean) => Promise): Promise { + try { + await func(false); + } catch (e) { + if (e instanceof RetryTask) { + await func(true); + } + } +} diff --git a/packages/genesys-web-messaging-tester-cli/src/ui.ts b/packages/genesys-web-messaging-tester-cli/src/ui.ts index 54005d4..7f5812a 100644 --- a/packages/genesys-web-messaging-tester-cli/src/ui.ts +++ b/packages/genesys-web-messaging-tester-cli/src/ui.ts @@ -28,8 +28,15 @@ export class Ui { return Ui.trailingNewline(chalk.red(error.message)); } - public titleOfTask(scenario: TestScriptScenario): string { - return scenario.name; + public titleOfTask( + scenario: TestScriptScenario, + isRetryDueToUnorderedMsgFailure = false, + ): string { + if (isRetryDueToUnorderedMsgFailure) { + return `${scenario.name} (RETRYING)`; + } else { + return scenario.name; + } } public titleOfFinishedTask(scenario: TestScriptScenario, hasPassed: boolean): string { @@ -44,6 +51,10 @@ export class Ui { return Ui.trailingNewline(`${chalk.bold.grey(`${event.who}:`)} ${chalk.grey(event.message)}`); } + public retryingTestDueToFailureLikelyByUnorderedMessage(): string { + return Ui.trailingNewline('Test failed. Retrying as unordered messages detected'); + } + public firstLineOfMessageTranscribed(event: TranscribedMessage): string { const lines = event.message.trim().split('\n'); @@ -205,6 +216,15 @@ export class Ui { } else { lines.push(chalk.red(`FAIL - ${r.scenario.name}`)); } + + if (r.wasRetriedDueToUnorderedMessageFailure) { + lines.push( + chalk.yellow( + ' ^ This test was retried following a failure that coincided with unordered messages being being received from Genesys\n' + + ' Read more here: https://github.com/ovotech/genesys-web-messaging-tester/blob/main/docs/cli/unordered-messages.md', + ), + ); + } } return Ui.trailingNewline( diff --git a/packages/genesys-web-messaging-tester/__tests__/Conversation.spec.ts b/packages/genesys-web-messaging-tester/__tests__/Conversation.spec.ts index a220a96..b99ac71 100644 --- a/packages/genesys-web-messaging-tester/__tests__/Conversation.spec.ts +++ b/packages/genesys-web-messaging-tester/__tests__/Conversation.spec.ts @@ -1,10 +1,13 @@ -import { Conversation, WebMessengerGuestSession } from '../src'; +import { + Conversation, + WebMessageServerConnectionFixture, + WebMessageServerFixture, + WebMessengerGuestSession, +} from '../src'; import WebSocket from 'ws'; import getPort from 'get-port'; -import { WebMessageServerFixture } from './fixtures/WebMessageServerFixture'; -import { WebMessageServerConnectionFixture } from './fixtures/WebMessageServerConnectionFixture'; -// eslint-disable-next-line @typescript-eslint/no-var-requires -const FakeTimers = require('@sinonjs/fake-timers'); +import { NoDelay } from './fixtures/NoDelay'; +import FakeTimers from '@sinonjs/fake-timers'; describe('Conversation', () => { let genesysServerFixture: WebMessageServerFixture; @@ -21,6 +24,7 @@ describe('Conversation', () => { region: 'xxxx.pure.cloud', }, {}, + new NoDelay(), () => new WebSocket(`ws://localhost:${genesysServerFixture.port}`), ); @@ -108,6 +112,8 @@ Received before disconnection: await new Promise((resolve) => session.on('structuredMessage', resolve)); const clock = FakeTimers.createClock(); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore const conversation = new Conversation(session, clock.setTimeout, clock.clearTimeout); serverConnection.simulateSessionResponseMessage(); diff --git a/packages/genesys-web-messaging-tester/__tests__/Transcriber.spec.ts b/packages/genesys-web-messaging-tester/__tests__/Transcriber.spec.ts index ae75f50..ba91b2b 100644 --- a/packages/genesys-web-messaging-tester/__tests__/Transcriber.spec.ts +++ b/packages/genesys-web-messaging-tester/__tests__/Transcriber.spec.ts @@ -1,17 +1,13 @@ -import { WebMessengerGuestSession, TranscribedMessage, SessionTranscriber } from '../src'; -import WebSocket from 'ws'; -import getPort from 'get-port'; - -import { - WebMessageServerFixture, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - //@ts-ignore -} from './fixtures/WebMessageServerFixture'; import { + SessionTranscriber, + TranscribedMessage, WebMessageServerConnectionFixture, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - //@ts-ignore -} from './fixtures/WebMessageServerConnectionFixture'; + WebMessageServerFixture, + WebMessengerGuestSession, +} from '../src'; +import WebSocket from 'ws'; +import getPort from 'get-port'; +import { NoDelay } from './fixtures/NoDelay'; describe('Transcriber', () => { let genesysServerFixture: WebMessageServerFixture; @@ -28,6 +24,7 @@ describe('Transcriber', () => { region: 'xxxx.pure.cloud', }, {}, + new NoDelay(), () => new WebSocket(`ws://localhost:${genesysServerFixture.port}`), ); diff --git a/packages/genesys-web-messaging-tester/__tests__/WebMessengerGuestSession.spec.ts b/packages/genesys-web-messaging-tester/__tests__/WebMessengerGuestSession.spec.ts index 2c337d2..b23210b 100644 --- a/packages/genesys-web-messaging-tester/__tests__/WebMessengerGuestSession.spec.ts +++ b/packages/genesys-web-messaging-tester/__tests__/WebMessengerGuestSession.spec.ts @@ -1,12 +1,7 @@ -import { WebMessengerGuestSession, StructuredMessage } from '../src'; +import { StructuredMessage, WebMessageServerFixture, WebMessengerGuestSession } from '../src'; import WebSocket from 'ws'; import getPort from 'get-port'; - -import { - WebMessageServerFixture, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore -} from './fixtures/WebMessageServerFixture'; +import { NoDelay } from './fixtures/NoDelay'; describe('WebMessengerGuestSession', () => { let genesysServerFixture: WebMessageServerFixture; @@ -33,6 +28,7 @@ describe('WebMessengerGuestSession', () => { origin: 'x.test', }, {}, + new NoDelay(), wsFactory, ); @@ -49,6 +45,7 @@ describe('WebMessengerGuestSession', () => { region: 'xxxx.pure.cloud', }, {}, + new NoDelay(), () => new WebSocket(`ws://localhost:${genesysServerFixture.port}`), ); @@ -68,6 +65,7 @@ describe('WebMessengerGuestSession', () => { region: 'xxxx.pure.cloud', }, {}, + new NoDelay(), () => new WebSocket(`ws://localhost:${genesysServerFixture.port}`), ); @@ -93,6 +91,7 @@ describe('WebMessengerGuestSession', () => { region: 'xxxx.pure.cloud', }, {}, + new NoDelay(), () => new WebSocket(`ws://localhost:${genesysServerFixture.port}`), ); @@ -130,6 +129,7 @@ describe('WebMessengerGuestSession', () => { region: 'xxxx.pure.cloud', }, { test: 'test-value' }, + new NoDelay(), () => new WebSocket(`ws://localhost:${genesysServerFixture.port}`), ); diff --git a/packages/genesys-web-messaging-tester/__tests__/fixtures/NoDelay.ts b/packages/genesys-web-messaging-tester/__tests__/fixtures/NoDelay.ts new file mode 100644 index 0000000..d04b1ae --- /dev/null +++ b/packages/genesys-web-messaging-tester/__tests__/fixtures/NoDelay.ts @@ -0,0 +1,16 @@ +import { EventEmitter } from 'events'; +import { MessageDelayer, Response } from '../../src'; + +export class NoDelay extends EventEmitter implements MessageDelayer { + constructor() { + super(); + } + + public add(message: Response): void { + this.emit('message', message); + } + + get delay(): number { + return 0; + } +} diff --git a/packages/genesys-web-messaging-tester/package.json b/packages/genesys-web-messaging-tester/package.json index e845a49..6a4f839 100644 --- a/packages/genesys-web-messaging-tester/package.json +++ b/packages/genesys-web-messaging-tester/package.json @@ -1,6 +1,6 @@ { "name": "@ovotech/genesys-web-messaging-tester", - "version": "2.0.4", + "version": "2.0.5", "main": "lib/index.js", "types": "lib/index.d.ts", "license": "Apache-2.0", @@ -29,14 +29,15 @@ }, "devDependencies": { "@ikerin/build-readme": "^1.1.1", - "@sinonjs/fake-timers": "^10.3.0", + "@sinonjs/fake-timers": "^11.0.0", "@types/debug": "^4.1.8", "@types/jest": "^29.0.3", "@types/node": "^14.14.2", + "@types/sinonjs__fake-timers": "^8.1.2", "@types/uuid": "^9.0.2", "@types/ws": "^8.5.5", - "@typescript-eslint/eslint-plugin": "^4.31.1", - "@typescript-eslint/parser": "^4.31.1", + "@typescript-eslint/eslint-plugin": "^5.0.0", + "@typescript-eslint/parser": "^5.0.0", "eslint": "^7.32.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-jest": "^27.2.3", diff --git a/packages/genesys-web-messaging-tester/src/Conversation.ts b/packages/genesys-web-messaging-tester/src/Conversation.ts index 0234880..b80dcc2 100644 --- a/packages/genesys-web-messaging-tester/src/Conversation.ts +++ b/packages/genesys-web-messaging-tester/src/Conversation.ts @@ -123,6 +123,13 @@ Received before disconnection: export class Conversation { private sessionStarted: boolean; + /** + * When defining timeout durations we need to take into consideration the delay that occurs in the + * WebMessengerSession to correct for out of order messages; + * @private + */ + private readonly timeoutCompensation: number; + constructor( private readonly messengerSession: WebMessengerSession, private readonly timeoutSet: typeof setTimeout = setTimeout, @@ -130,6 +137,8 @@ export class Conversation { ) { this.sessionStarted = false; this.messengerSession.once('sessionStarted', () => (this.sessionStarted = true)); + + this.timeoutCompensation = messengerSession.messageDelayInMs; } private static containsDisconnectEvent(event: StructuredMessageEventBody): boolean { @@ -173,7 +182,7 @@ export class Conversation { setTimeout(() => { this.messengerSession.sendText(text); resolve(); - }, delayInMs); + }, delayInMs + this.timeoutCompensation); }); } else { this.messengerSession.sendText(text); @@ -219,7 +228,7 @@ export class Conversation { waitingTimeout = this.timeoutSet(() => { this.messengerSession.off('structuredMessage', func); resolve(messages); - }, timeToWaitAfterLastMessageInMs); + }, timeToWaitAfterLastMessageInMs + this.timeoutCompensation); } }; @@ -227,7 +236,7 @@ export class Conversation { waitingTimeout = this.timeoutSet(() => { this.messengerSession.off('structuredMessage', func); resolve(messages); - }, timeToWaitAfterLastMessageInMs); + }, timeToWaitAfterLastMessageInMs + this.timeoutCompensation); this.messengerSession.on('structuredMessage', func); }); @@ -354,7 +363,7 @@ export class Conversation { messagesWithTextReceived, ), ); - }, timeoutInMs); + }, timeoutInMs + this.timeoutCompensation); }); } } diff --git a/packages/genesys-web-messaging-tester/src/genesys/SessionResponse.ts b/packages/genesys-web-messaging-tester/src/genesys/SessionResponse.ts index 5183dea..aa92aa4 100644 --- a/packages/genesys-web-messaging-tester/src/genesys/SessionResponse.ts +++ b/packages/genesys-web-messaging-tester/src/genesys/SessionResponse.ts @@ -3,6 +3,7 @@ import { SuccessResponse } from './Response'; export interface SessionResponseSuccessBody { connected: boolean; newSession: boolean; + readOnly?: boolean; } export interface SessionResponse extends SuccessResponse { diff --git a/packages/genesys-web-messaging-tester/src/genesys/StructuredMessage.ts b/packages/genesys-web-messaging-tester/src/genesys/StructuredMessage.ts index ca7fa34..acc8ade 100644 --- a/packages/genesys-web-messaging-tester/src/genesys/StructuredMessage.ts +++ b/packages/genesys-web-messaging-tester/src/genesys/StructuredMessage.ts @@ -5,7 +5,7 @@ export interface StructuredMessageTextBody { direction: 'Inbound' | 'Outbound'; text: string; id: string; - originatingEntity: string; + originatingEntity?: string; // Bot channel: { time: string; messageId?: string; diff --git a/packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts b/packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts index f1604a2..f6165f9 100644 --- a/packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts +++ b/packages/genesys-web-messaging-tester/src/genesys/WebMessengerGuestSession.ts @@ -1,4 +1,4 @@ -import { RawData, WebSocket, ClientOptions } from 'ws'; +import { ClientOptions, RawData, WebSocket } from 'ws'; import { v4 as uuidv4 } from 'uuid'; import { Response } from './Response'; import { SessionResponse } from './SessionResponse'; @@ -6,8 +6,20 @@ import { StructuredMessage } from './StructuredMessage'; import { EventEmitter } from 'events'; import debug from 'debug'; import { ClientRequestArgs } from 'http'; +import { MessageDelayer } from './message-delayer/MessageDelayer'; +import { ReorderedMessageDelayer } from './message-delayer/ReorderedMessageDelayer'; export interface WebMessengerSession extends EventEmitter { + /** + * The Web Messenger server can sometimes return responses out of order. To cater for this + * we have to have a delay after every message is received before passing it to any listeners + * of the implementation. This delay hopefully provides enough time for any messages that should + * have preceded the other to be received and ordered. + * + * This delay should be taken into account for any timeout values of downstream functionality. + */ + get messageDelayInMs(): number; + sendText(message: string): void; close(): void; @@ -19,6 +31,14 @@ export interface SessionConfig { readonly origin?: string | undefined; } +function isSessionResponse(message: Response): message is SessionResponse { + return message.type === 'response' && message.class === 'SessionResponse'; +} + +export function isStructuredMessage(message: Response): message is StructuredMessage { + return message.type === 'message' && message.class === 'StructuredMessage'; +} + /** * @see https://developer.genesys.cloud/api/digital/webmessaging/websocketapi#configure-a-guest-session */ @@ -31,6 +51,7 @@ export class WebMessengerGuestSession extends EventEmitter { constructor( private readonly config: SessionConfig, private readonly participantData: Record = {}, + private readonly messageDelayer: MessageDelayer = new ReorderedMessageDelayer(), readonly wsFactory = (url: string, options?: ClientOptions | ClientRequestArgs) => new WebSocket(url, options), ) { @@ -43,6 +64,12 @@ export class WebMessengerGuestSession extends EventEmitter { this.ws = wsFactory(url, { origin: this.config.origin }); this.ws.on('open', () => this.connected()); this.ws.on('message', (data) => this.messageReceived(data)); + + messageDelayer.on('message', (message: Response) => this.processMessage(message)); + } + + public get messageDelayInMs(): number { + return this.messageDelayer.delay; } private connected(): void { @@ -57,6 +84,20 @@ export class WebMessengerGuestSession extends EventEmitter { this.ws.send(JSON.stringify(payload)); } + private processMessage(message: Response): void { + if (isSessionResponse(message)) { + this.emit('sessionStarted', message); + return; + } + + if (isStructuredMessage(message)) { + this.emit('structuredMessage', message); + return; + } + + console.log('Unknown message', message); + } + private messageReceived(data: RawData): void { if (!Buffer.isBuffer(data)) { throw new Error('Expected data as Buffer type'); @@ -77,22 +118,7 @@ export class WebMessengerGuestSession extends EventEmitter { throw Error(`Session Response was ${message.code} instead of 200 due to '${message.body}'`); } - switch (message.type) { - case 'response': - if (message.class === 'SessionResponse') { - const sessionResponse = message as SessionResponse; - this.emit('sessionStarted', sessionResponse); - } - break; - case 'message': - if (message.class === 'StructuredMessage') { - const structuredMessage = message as StructuredMessage; - this.emit('structuredMessage', structuredMessage); - } - break; - default: - console.log('Unknown message', message); - } + this.messageDelayer.add(message, new Date()); } public sendText(message: string): void { diff --git a/packages/genesys-web-messaging-tester/src/genesys/message-delayer/MessageDelayer.ts b/packages/genesys-web-messaging-tester/src/genesys/message-delayer/MessageDelayer.ts new file mode 100644 index 0000000..c6deb1e --- /dev/null +++ b/packages/genesys-web-messaging-tester/src/genesys/message-delayer/MessageDelayer.ts @@ -0,0 +1,12 @@ +import { EventEmitter } from 'events'; +import { Response } from '../Response'; + +/** + * Provides the ability to delay messages for the purpose of re-ordering them. + * This is useful for reordering messages that are received out of order, presumably + * due to it being async and not guaranteeing order. + */ +export interface MessageDelayer extends EventEmitter { + get delay(): number; + add(message: Response, whenReceived: Date): void; +} diff --git a/packages/genesys-web-messaging-tester/src/genesys/message-delayer/ReorderedMessageDelayer.ts b/packages/genesys-web-messaging-tester/src/genesys/message-delayer/ReorderedMessageDelayer.ts new file mode 100644 index 0000000..e3f8552 --- /dev/null +++ b/packages/genesys-web-messaging-tester/src/genesys/message-delayer/ReorderedMessageDelayer.ts @@ -0,0 +1,145 @@ +import { EventEmitter } from 'events'; +import debug from 'debug'; +import { Response } from '../Response'; +import { setInterval } from 'timers'; +import { orderByTimestamp } from './orderByTimestamp'; +import { MessageDelayer } from './MessageDelayer'; +import { isStructuredMessage } from '../WebMessengerGuestSession'; +import { StructuredMessage } from '../StructuredMessage'; + +export interface ReceivedMsg> { + received: Date; + response: T; +} + +/** + * Reorders messages with a timestamp, being sure to maintain the overall order of messages with/without + * timestamps. + * + * > All messaging follows a request/response pattern. However, web messaging is an asynchronous + * > channel and therefore no guarantee to ordering is provided. + * > Source: https://developer.genesys.cloud/commdigital/digital/webmessaging/websocketapi#messaging + */ +export class ReorderedMessageDelayer extends EventEmitter implements MessageDelayer { + private static readonly debugger = debug('ReorderedMessageDelayer'); + + private messages: ReceivedMsg>[] = []; + + private lastMessageWithTimestamp: StructuredMessage | undefined = undefined; + + private intervalReference: NodeJS.Timeout | undefined; + + private unorderedMessageOccurred = false; + + constructor( + private readonly delayBeforeEmittingInMs: number = 1000, + private readonly intervalInMs: number = 1000, + private readonly intervalSet: typeof setInterval = setInterval, + private readonly intervalClear: typeof clearInterval = clearInterval, + ) { + super(); + if (intervalInMs <= 0) { + throw new Error('Interval must be greater than 0'); + } + } + + private logUnorderedMessageTimeDiff(message: Response): void { + if (isStructuredMessage(message)) { + if (this.lastMessageWithTimestamp) { + const timeDifference = + new Date(message.body.channel.time).getTime() - + new Date(this.lastMessageWithTimestamp.body.channel.time).getTime(); + + if (timeDifference < 0) { + this.unorderedMessageOccurred = true; + ReorderedMessageDelayer.debugger( + "Message received was out of order. Last msg's timestamp came before this one by %d ms", + -timeDifference, + ); + } else { + // If timeDifference check above is true then last message is more recent so keep, else update below + this.lastMessageWithTimestamp = message; + } + } else { + this.lastMessageWithTimestamp = message; + } + } + } + + public get unorderdMessageDetected(): boolean { + return this.unorderedMessageOccurred; + } + + /** + * Add a message to the pool. Each message added reset a timer to wait for any other messages + * before releasing the oldest message. + */ + public add(message: Response, received: Date): void { + this.logUnorderedMessageTimeDiff(message); + + this.messages.push({ received, response: message }); + + if (!this.intervalReference) { + this.startInterval(); + } + } + + private startInterval(): void { + if (!this.intervalReference) { + this.intervalReference = this.intervalSet( + () => this.emitMessagesAfterSilence(), + this.intervalInMs, + ); + ReorderedMessageDelayer.debugger('Interval started'); + } + } + + private stopInterval(): void { + if (this.intervalReference) { + this.intervalClear(this.intervalReference); + this.intervalReference = undefined; + ReorderedMessageDelayer.debugger('Interval stopped'); + } + } + + private emitMessagesAfterSilence(): void { + const result = orderByTimestamp(this.messages); + if (result.wasRearranged) { + ReorderedMessageDelayer.debugger( + 'Flushing messages. Out of order message detected: %O', + this.messages, + ); + } else { + ReorderedMessageDelayer.debugger('Flushing messages. No out of order messages'); + } + + this.messages = result.responses; + + if (isStructuredMessage(this.messages[0].response)) { + const now = new Date().getTime(); + const ageOfMessageInMs = now - this.messages[0].received.getTime(); + if (ageOfMessageInMs >= this.delayBeforeEmittingInMs) { + const message = this.messages.shift()?.response; + this.emit('message', message); + ReorderedMessageDelayer.debugger('Emitted message with timestamp: %O', { + msDelayed: ageOfMessageInMs, + message, + }); + } + } else { + const message = this.messages.shift()?.response; + // No timestamp so just emit + this.emit('message', message); + ReorderedMessageDelayer.debugger('Emitted message without timestamp %O', message); + } + + if (this.messages.length === 0) { + this.stopInterval(); + return; + } + } + + public get delay(): number { + return this.delayBeforeEmittingInMs; + } +} diff --git a/packages/genesys-web-messaging-tester/src/genesys/message-delayer/orderByTimestamp.spec.ts b/packages/genesys-web-messaging-tester/src/genesys/message-delayer/orderByTimestamp.spec.ts new file mode 100644 index 0000000..0a2fded --- /dev/null +++ b/packages/genesys-web-messaging-tester/src/genesys/message-delayer/orderByTimestamp.spec.ts @@ -0,0 +1,155 @@ +import { orderByTimestamp, orderByTimestampResult } from './orderByTimestamp'; +import { Response } from '../Response'; +import { StructuredMessage } from '../StructuredMessage'; +import { SessionResponse, SessionResponseSuccessBody } from '../SessionResponse'; +import { ReceivedMsg } from './ReorderedMessageDelayer'; + +function createStructuredMessage(text: string, time: string): StructuredMessage { + return { + type: 'message', + class: 'StructuredMessage', + code: 200, + body: { + text, + direction: 'Inbound', + id: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', + channel: { + time, + messageId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', + }, + type: 'Text', + metadata: { + correlationId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', + }, + }, + }; +} + +function createSessionResponse(body: SessionResponseSuccessBody): SessionResponse { + return { + body, + class: 'SessionResponse', + code: 200, + type: 'response', + }; +} + +describe('orderWithTimestamps', () => { + const received = new Date(); + + test('Correctly sorts an array with timestamped and non-timestamped elements', () => { + const unordered: ReceivedMsg>[] = [ + { received, response: createStructuredMessage('1', '2023-07-22T21:19:41.256Z') }, + { + received, + response: createSessionResponse({ connected: true, newSession: false, readOnly: false }), + }, + { received, response: createStructuredMessage('3', '2023-07-22T21:19:55.256Z') }, + { received, response: createStructuredMessage('7', '2023-07-22T20:13:55.256Z') }, + { + received, + response: createSessionResponse({ connected: false, newSession: true, readOnly: false }), + }, + { + received, + response: createSessionResponse({ connected: false, newSession: false, readOnly: true }), + }, + { received, response: createStructuredMessage('6', '2023-07-22T21:19:25.256Z') }, + ]; + + expect(orderByTimestamp(unordered)).toStrictEqual({ + wasRearranged: true, + responses: [ + { + received, + response: createStructuredMessage('7', '2023-07-22T20:13:55.256Z'), + }, + { + received, + response: createSessionResponse({ connected: true, newSession: false, readOnly: false }), + }, + { + received, + response: createStructuredMessage('6', '2023-07-22T21:19:25.256Z'), + }, + { + received, + response: createStructuredMessage('1', '2023-07-22T21:19:41.256Z'), + }, + { + received, + response: createSessionResponse({ connected: false, newSession: true, readOnly: false }), + }, + { + received, + response: createSessionResponse({ connected: false, newSession: false, readOnly: true }), + }, + { + received, + response: createStructuredMessage('3', '2023-07-22T21:19:55.256Z'), + }, + ], + }); + }); + + test('Does not change the order of non-timestamped elements', () => { + const input: ReceivedMsg>[] = [ + { + received, + response: createSessionResponse({ connected: true, newSession: false, readOnly: false }), + }, + { + received, + response: createSessionResponse({ connected: true, newSession: true, readOnly: false }), + }, + { + received, + response: createSessionResponse({ connected: true, newSession: true, readOnly: true }), + }, + ]; + + expect(orderByTimestamp(input)).toStrictEqual({ + wasRearranged: false, + responses: [ + { + received, + response: createSessionResponse({ connected: true, newSession: false, readOnly: false }), + }, + { + received, + response: createSessionResponse({ connected: true, newSession: true, readOnly: false }), + }, + { + received, + response: createSessionResponse({ connected: true, newSession: true, readOnly: true }), + }, + ], + }); + }); + + test('Correctly sorts an array with only timestamped elements', () => { + const unordered: ReceivedMsg>[] = [ + { received, response: createStructuredMessage('1', '2023-07-22T21:19:41.256Z') }, + { received, response: createStructuredMessage('2', '2023-07-22T21:19:55.256Z') }, + { received, response: createStructuredMessage('3', '2023-07-22T21:19:25.256Z') }, + ]; + + expect(orderByTimestamp(unordered)).toStrictEqual({ + wasRearranged: true, + responses: [ + { + received, + response: createStructuredMessage('3', '2023-07-22T21:19:25.256Z'), + }, + { + received, + response: createStructuredMessage('1', '2023-07-22T21:19:41.256Z'), + }, + { + received, + response: createStructuredMessage('2', '2023-07-22T21:19:55.256Z'), + }, + ], + }); + }); +}); diff --git a/packages/genesys-web-messaging-tester/src/genesys/message-delayer/orderByTimestamp.ts b/packages/genesys-web-messaging-tester/src/genesys/message-delayer/orderByTimestamp.ts new file mode 100644 index 0000000..d207093 --- /dev/null +++ b/packages/genesys-web-messaging-tester/src/genesys/message-delayer/orderByTimestamp.ts @@ -0,0 +1,61 @@ +import { StructuredMessage } from '../StructuredMessage'; +import { isStructuredMessage } from '../WebMessengerGuestSession'; +import { Response } from '../Response'; +import { ReceivedMsg } from './ReorderedMessageDelayer'; + +export interface orderByTimestampResult { + wasRearranged: boolean; + responses: ReceivedMsg>[]; +} + +export function isReceivedResponseStructuredMsg( + message: ReceivedMsg | StructuredMessage>, +): message is ReceivedMsg { + return isStructuredMessage(message.response); +} + +export function orderByTimestamp( + array: ReceivedMsg | StructuredMessage>[], +): orderByTimestampResult { + const withTimestamps: ReceivedMsg[] = []; + const withoutTimestamps: ReceivedMsg>[] = []; + + array.forEach((item) => { + if (isReceivedResponseStructuredMsg(item)) { + withTimestamps.push(item); + } else { + withoutTimestamps.push(item); + } + }); + + withTimestamps.sort((a, b) => { + return ( + new Date(a.response.body.channel.time).getTime() - + new Date(b.response.body.channel.time).getTime() + ); + }); + + const result: ReceivedMsg>[] = []; + let j = 0, + k = 0; + + for (let i = 0; i < array.length; i++) { + if (isReceivedResponseStructuredMsg(array[i])) { + result.push(withTimestamps[j]); + j++; + } else { + result.push(withoutTimestamps[k]); + k++; + } + } + + let wasRearranged = false; + for (let i = 0; i < array.length; i++) { + if (!Object.is(result[i], array[i])) { + wasRearranged = true; + break; + } + } + + return { wasRearranged, responses: result }; +} diff --git a/packages/genesys-web-messaging-tester/src/index.ts b/packages/genesys-web-messaging-tester/src/index.ts index 5e72e6f..a5b6891 100644 --- a/packages/genesys-web-messaging-tester/src/index.ts +++ b/packages/genesys-web-messaging-tester/src/index.ts @@ -3,11 +3,20 @@ export { SessionConfig, WebMessengerSession, } from './genesys/WebMessengerGuestSession'; +export { MessageDelayer } from './genesys/message-delayer/MessageDelayer'; +export { ReorderedMessageDelayer } from './genesys/message-delayer/ReorderedMessageDelayer'; export { StructuredMessage } from './genesys/StructuredMessage'; export { SessionResponse } from './genesys/SessionResponse'; +export { Response } from './genesys/Response'; export { Conversation, TimeoutWaitingForResponseError, BotDisconnectedWaitingForResponseError, } from './Conversation'; export { SessionTranscriber, TranscribedMessage } from './transcribe/Transcriber'; + +// Test fixtures. Included to testing dependents of this library easier + +export { webMessagePayloads } from './testFixtures/webMessagePayloads'; +export { WebMessageServerConnectionFixture } from './testFixtures/WebMessageServerConnectionFixture'; +export { WebMessageServerFixture } from './testFixtures/WebMessageServerFixture'; diff --git a/packages/genesys-web-messaging-tester/__tests__/fixtures/WebMessageServerConnectionFixture.ts b/packages/genesys-web-messaging-tester/src/testFixtures/WebMessageServerConnectionFixture.ts similarity index 87% rename from packages/genesys-web-messaging-tester/__tests__/fixtures/WebMessageServerConnectionFixture.ts rename to packages/genesys-web-messaging-tester/src/testFixtures/WebMessageServerConnectionFixture.ts index 5bce9c9..13efbd3 100644 --- a/packages/genesys-web-messaging-tester/__tests__/fixtures/WebMessageServerConnectionFixture.ts +++ b/packages/genesys-web-messaging-tester/src/testFixtures/WebMessageServerConnectionFixture.ts @@ -1,9 +1,5 @@ import WebSocket from 'ws'; -import { - webMessagePayloads, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - //@ts-ignore -} from './webMessagePayloads'; +import { webMessagePayloads } from './webMessagePayloads'; export class WebMessageServerConnectionFixture { constructor(private readonly ws: WebSocket) {} @@ -24,6 +20,10 @@ export class WebMessageServerConnectionFixture { }); } + public async waitForConnectionToClose(): Promise { + return new Promise((resolve) => this.ws.on('close', resolve)); + } + public simulateSessionResponseMessage(): void { const payload = webMessagePayloads.sessionResponse(); this.ws.send(JSON.stringify(payload)); diff --git a/packages/genesys-web-messaging-tester/__tests__/fixtures/WebMessageServerFixture.ts b/packages/genesys-web-messaging-tester/src/testFixtures/WebMessageServerFixture.ts similarity index 80% rename from packages/genesys-web-messaging-tester/__tests__/fixtures/WebMessageServerFixture.ts rename to packages/genesys-web-messaging-tester/src/testFixtures/WebMessageServerFixture.ts index 72e2200..3ed3e1a 100644 --- a/packages/genesys-web-messaging-tester/__tests__/fixtures/WebMessageServerFixture.ts +++ b/packages/genesys-web-messaging-tester/src/testFixtures/WebMessageServerFixture.ts @@ -1,9 +1,5 @@ import WebSocket, { WebSocketServer } from 'ws'; -import { - WebMessageServerConnectionFixture, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - //@ts-ignore -} from './WebMessageServerConnectionFixture'; +import { WebMessageServerConnectionFixture } from './WebMessageServerConnectionFixture'; export class WebMessageServerFixture { private wss: WebSocketServer; diff --git a/packages/genesys-web-messaging-tester/__tests__/fixtures/webMessagePayloads.ts b/packages/genesys-web-messaging-tester/src/testFixtures/webMessagePayloads.ts similarity index 96% rename from packages/genesys-web-messaging-tester/__tests__/fixtures/webMessagePayloads.ts rename to packages/genesys-web-messaging-tester/src/testFixtures/webMessagePayloads.ts index 0b20e54..d75b5e0 100644 --- a/packages/genesys-web-messaging-tester/__tests__/fixtures/webMessagePayloads.ts +++ b/packages/genesys-web-messaging-tester/src/testFixtures/webMessagePayloads.ts @@ -1,4 +1,4 @@ -import { StructuredMessage, SessionResponse } from '../../src'; +import { StructuredMessage, SessionResponse } from '../index'; /** * Payloads taken from real interactions diff --git a/yarn.lock b/yarn.lock index 837ffdd..d976c29 100644 --- a/yarn.lock +++ b/yarn.lock @@ -457,6 +457,13 @@ __metadata: languageName: node linkType: hard +"@eslint-community/regexpp@npm:^4.4.0": + version: 4.6.2 + resolution: "@eslint-community/regexpp@npm:4.6.2" + checksum: a3c341377b46b54fa228f455771b901d1a2717f95d47dcdf40199df30abc000ba020f747f114f08560d119e979d882a94cf46cfc51744544d54b00319c0f2724 + languageName: node + linkType: hard + "@eslint/eslintrc@npm:^0.4.3": version: 0.4.3 resolution: "@eslint/eslintrc@npm:0.4.3" @@ -478,11 +485,11 @@ __metadata: version: 0.0.0-use.local resolution: "@examples/api@workspace:examples/api" dependencies: - "@ovotech/genesys-web-messaging-tester": "*" + "@ovotech/genesys-web-messaging-tester": 2.0.5 "@types/jest": ^29.0.3 "@types/node": ^14.14.2 - "@typescript-eslint/eslint-plugin": ^4.31.1 - "@typescript-eslint/parser": ^4.31.1 + "@typescript-eslint/eslint-plugin": ^5.0.0 + "@typescript-eslint/parser": ^5.0.0 dotenv: ^16.3.1 eslint: ^7.32.0 eslint-config-prettier: ^8.3.0 @@ -909,14 +916,14 @@ __metadata: version: 0.0.0-use.local resolution: "@ovotech/genesys-web-messaging-tester-cli@workspace:packages/genesys-web-messaging-tester-cli" dependencies: - "@ovotech/genesys-web-messaging-tester": ^2.0.4 + "@ovotech/genesys-web-messaging-tester": 2.0.5 "@types/humanize-duration": ^3.27.1 "@types/jest": ^29.0.3 "@types/jest-when": ^3.5.2 "@types/js-yaml": ^4.0.5 "@types/node": ^14.14.2 - "@typescript-eslint/eslint-plugin": ^4.31.1 - "@typescript-eslint/parser": ^4.31.1 + "@typescript-eslint/eslint-plugin": ^5.0.0 + "@typescript-eslint/parser": ^5.0.0 chalk: ^4.1.2 ci-info: ^3.5.0 commander: ^11.0.0 @@ -952,19 +959,20 @@ __metadata: languageName: unknown linkType: soft -"@ovotech/genesys-web-messaging-tester@*, @ovotech/genesys-web-messaging-tester@^2.0.4, @ovotech/genesys-web-messaging-tester@workspace:packages/genesys-web-messaging-tester": +"@ovotech/genesys-web-messaging-tester@2.0.5, @ovotech/genesys-web-messaging-tester@workspace:packages/genesys-web-messaging-tester": version: 0.0.0-use.local resolution: "@ovotech/genesys-web-messaging-tester@workspace:packages/genesys-web-messaging-tester" dependencies: "@ikerin/build-readme": ^1.1.1 - "@sinonjs/fake-timers": ^10.3.0 + "@sinonjs/fake-timers": ^11.0.0 "@types/debug": ^4.1.8 "@types/jest": ^29.0.3 "@types/node": ^14.14.2 + "@types/sinonjs__fake-timers": ^8.1.2 "@types/uuid": ^9.0.2 "@types/ws": ^8.5.5 - "@typescript-eslint/eslint-plugin": ^4.31.1 - "@typescript-eslint/parser": ^4.31.1 + "@typescript-eslint/eslint-plugin": ^5.0.0 + "@typescript-eslint/parser": ^5.0.0 debug: ^4.3.3 eslint: ^7.32.0 eslint-config-prettier: ^8.3.0 @@ -1037,12 +1045,12 @@ __metadata: languageName: node linkType: hard -"@sinonjs/fake-timers@npm:^10.3.0": - version: 10.3.0 - resolution: "@sinonjs/fake-timers@npm:10.3.0" +"@sinonjs/fake-timers@npm:^11.0.0": + version: 11.0.0 + resolution: "@sinonjs/fake-timers@npm:11.0.0" dependencies: "@sinonjs/commons": ^3.0.0 - checksum: 614d30cb4d5201550c940945d44c9e0b6d64a888ff2cd5b357f95ad6721070d6b8839cd10e15b76bf5e14af0bcc1d8f9ec00d49a46318f1f669a4bec1d7f3148 + checksum: 3a090dbc12bcb3b9c441d5f80e8bbc41381ac4fd86ef6dee5b260b856edff82a0f1522e9414ff177f1bba85ec5436e6ebf9e9d1824a65f44f6b4fb2113f88f87 languageName: node linkType: hard @@ -1198,13 +1206,6 @@ __metadata: languageName: node linkType: hard -"@types/json-schema@npm:^7.0.7": - version: 7.0.11 - resolution: "@types/json-schema@npm:7.0.11" - checksum: 527bddfe62db9012fccd7627794bd4c71beb77601861055d87e3ee464f2217c85fca7a4b56ae677478367bbd248dbde13553312b7d4dbc702a2f2bbf60c4018d - languageName: node - linkType: hard - "@types/json-schema@npm:^7.0.9": version: 7.0.12 resolution: "@types/json-schema@npm:7.0.12" @@ -1247,6 +1248,13 @@ __metadata: languageName: node linkType: hard +"@types/sinonjs__fake-timers@npm:^8.1.2": + version: 8.1.2 + resolution: "@types/sinonjs__fake-timers@npm:8.1.2" + checksum: bbc73a5ab6c0ec974929392f3d6e1e8db4ebad97ec506d785301e1c3d8a4f98a35b1aa95b97035daef02886fd8efd7788a2fa3ced2ec7105988bfd8dce61eedd + languageName: node + linkType: hard + "@types/stack-utils@npm:^2.0.0": version: 2.0.1 resolution: "@types/stack-utils@npm:2.0.1" @@ -1293,68 +1301,44 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:^4.31.1": - version: 4.33.0 - resolution: "@typescript-eslint/eslint-plugin@npm:4.33.0" +"@typescript-eslint/eslint-plugin@npm:^5.0.0": + version: 5.62.0 + resolution: "@typescript-eslint/eslint-plugin@npm:5.62.0" dependencies: - "@typescript-eslint/experimental-utils": 4.33.0 - "@typescript-eslint/scope-manager": 4.33.0 - debug: ^4.3.1 - functional-red-black-tree: ^1.0.1 - ignore: ^5.1.8 - regexpp: ^3.1.0 - semver: ^7.3.5 + "@eslint-community/regexpp": ^4.4.0 + "@typescript-eslint/scope-manager": 5.62.0 + "@typescript-eslint/type-utils": 5.62.0 + "@typescript-eslint/utils": 5.62.0 + debug: ^4.3.4 + graphemer: ^1.4.0 + ignore: ^5.2.0 + natural-compare-lite: ^1.4.0 + semver: ^7.3.7 tsutils: ^3.21.0 peerDependencies: - "@typescript-eslint/parser": ^4.0.0 - eslint: ^5.0.0 || ^6.0.0 || ^7.0.0 + "@typescript-eslint/parser": ^5.0.0 + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: typescript: optional: true - checksum: d74855d0a5ffe0b2f362ec02fcd9301d39a53fb4155b9bd0cb15a0a31d065143129ebf98df9d86af4b6f74de1d423a4c0d8c0095520844068117453afda5bc4f + checksum: fc104b389c768f9fa7d45a48c86d5c1ad522c1d0512943e782a56b1e3096b2cbcc1eea3fcc590647bf0658eef61aac35120a9c6daf979bf629ad2956deb516a1 languageName: node linkType: hard -"@typescript-eslint/experimental-utils@npm:4.33.0": - version: 4.33.0 - resolution: "@typescript-eslint/experimental-utils@npm:4.33.0" - dependencies: - "@types/json-schema": ^7.0.7 - "@typescript-eslint/scope-manager": 4.33.0 - "@typescript-eslint/types": 4.33.0 - "@typescript-eslint/typescript-estree": 4.33.0 - eslint-scope: ^5.1.1 - eslint-utils: ^3.0.0 - peerDependencies: - eslint: "*" - checksum: f859800ada0884f92db6856f24efcb1d073ac9883ddc2b1aa9339f392215487895bed8447ebce3741e8141bb32e545244abef62b73193ba9a8a0527c523aabae - languageName: node - linkType: hard - -"@typescript-eslint/parser@npm:^4.31.1": - version: 4.33.0 - resolution: "@typescript-eslint/parser@npm:4.33.0" +"@typescript-eslint/parser@npm:^5.0.0": + version: 5.62.0 + resolution: "@typescript-eslint/parser@npm:5.62.0" dependencies: - "@typescript-eslint/scope-manager": 4.33.0 - "@typescript-eslint/types": 4.33.0 - "@typescript-eslint/typescript-estree": 4.33.0 - debug: ^4.3.1 + "@typescript-eslint/scope-manager": 5.62.0 + "@typescript-eslint/types": 5.62.0 + "@typescript-eslint/typescript-estree": 5.62.0 + debug: ^4.3.4 peerDependencies: - eslint: ^5.0.0 || ^6.0.0 || ^7.0.0 + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: typescript: optional: true - checksum: 102457eae1acd516211098fea081c8a2ed728522bbda7f5a557b6ef23d88970514f9a0f6285d53fca134d3d4d7d17822b5d5e12438d5918df4d1f89cc9e67d57 - languageName: node - linkType: hard - -"@typescript-eslint/scope-manager@npm:4.33.0": - version: 4.33.0 - resolution: "@typescript-eslint/scope-manager@npm:4.33.0" - dependencies: - "@typescript-eslint/types": 4.33.0 - "@typescript-eslint/visitor-keys": 4.33.0 - checksum: 9a25fb7ba7c725ea7227a24d315b0f6aacbad002e2549a049edf723c1d3615c22f5c301f0d7d615b377f2cdf2f3519d97e79af0c459de6ef8d2aaf0906dff13e + checksum: d168f4c7f21a7a63f47002e2d319bcbb6173597af5c60c1cf2de046b46c76b4930a093619e69faf2d30214c29ab27b54dcf1efc7046a6a6bd6f37f59a990e752 languageName: node linkType: hard @@ -1368,10 +1352,20 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/types@npm:4.33.0": - version: 4.33.0 - resolution: "@typescript-eslint/types@npm:4.33.0" - checksum: 3baae1ca35872421b4eb60f5d3f3f32dc1d513f2ae0a67dee28c7d159fd7a43ed0d11a8a5a0f0c2d38507ffa036fc7c511cb0f18a5e8ac524b3ebde77390ec53 +"@typescript-eslint/type-utils@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/type-utils@npm:5.62.0" + dependencies: + "@typescript-eslint/typescript-estree": 5.62.0 + "@typescript-eslint/utils": 5.62.0 + debug: ^4.3.4 + tsutils: ^3.21.0 + peerDependencies: + eslint: "*" + peerDependenciesMeta: + typescript: + optional: true + checksum: fc41eece5f315dfda14320be0da78d3a971d650ea41300be7196934b9715f3fe1120a80207551eb71d39568275dbbcf359bde540d1ca1439d8be15e9885d2739 languageName: node linkType: hard @@ -1382,24 +1376,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:4.33.0": - version: 4.33.0 - resolution: "@typescript-eslint/typescript-estree@npm:4.33.0" - dependencies: - "@typescript-eslint/types": 4.33.0 - "@typescript-eslint/visitor-keys": 4.33.0 - debug: ^4.3.1 - globby: ^11.0.3 - is-glob: ^4.0.1 - semver: ^7.3.5 - tsutils: ^3.21.0 - peerDependenciesMeta: - typescript: - optional: true - checksum: 2566984390c76bd95f43240057215c068c69769e406e27aba41e9f21fd300074d6772e4983fa58fe61e80eb5550af1548d2e31e80550d92ba1d051bb00fe6f5c - languageName: node - linkType: hard - "@typescript-eslint/typescript-estree@npm:5.62.0": version: 5.62.0 resolution: "@typescript-eslint/typescript-estree@npm:5.62.0" @@ -1418,7 +1394,7 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/utils@npm:^5.10.0": +"@typescript-eslint/utils@npm:5.62.0, @typescript-eslint/utils@npm:^5.10.0": version: 5.62.0 resolution: "@typescript-eslint/utils@npm:5.62.0" dependencies: @@ -1436,16 +1412,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:4.33.0": - version: 4.33.0 - resolution: "@typescript-eslint/visitor-keys@npm:4.33.0" - dependencies: - "@typescript-eslint/types": 4.33.0 - eslint-visitor-keys: ^2.0.0 - checksum: 59953e474ad4610c1aa23b2b1a964445e2c6201521da6367752f37939d854352bbfced5c04ea539274065e012b1337ba3ffa49c2647a240a4e87155378ba9873 - languageName: node - linkType: hard - "@typescript-eslint/visitor-keys@npm:5.62.0": version: 5.62.0 resolution: "@typescript-eslint/visitor-keys@npm:5.62.0" @@ -2157,7 +2123,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.0.1, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.3, debug@npm:^4.3.4": +"debug@npm:4, debug@npm:^4.0.1, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.3, debug@npm:^4.3.4": version: 4.3.4 resolution: "debug@npm:4.3.4" dependencies: @@ -2417,17 +2383,6 @@ __metadata: languageName: node linkType: hard -"eslint-utils@npm:^3.0.0": - version: 3.0.0 - resolution: "eslint-utils@npm:3.0.0" - dependencies: - eslint-visitor-keys: ^2.0.0 - peerDependencies: - eslint: ">=5" - checksum: 0668fe02f5adab2e5a367eee5089f4c39033af20499df88fe4e6aba2015c20720404d8c3d6349b6f716b08fdf91b9da4e5d5481f265049278099c4c836ccb619 - languageName: node - linkType: hard - "eslint-visitor-keys@npm:^1.1.0, eslint-visitor-keys@npm:^1.3.0": version: 1.3.0 resolution: "eslint-visitor-keys@npm:1.3.0" @@ -2898,7 +2853,7 @@ __metadata: languageName: node linkType: hard -"globby@npm:^11.0.3, globby@npm:^11.1.0": +"globby@npm:^11.1.0": version: 11.1.0 resolution: "globby@npm:11.1.0" dependencies: @@ -2919,6 +2874,13 @@ __metadata: languageName: node linkType: hard +"graphemer@npm:^1.4.0": + version: 1.4.0 + resolution: "graphemer@npm:1.4.0" + checksum: bab8f0be9b568857c7bec9fda95a89f87b783546d02951c40c33f84d05bb7da3fd10f863a9beb901463669b6583173a8c8cc6d6b306ea2b9b9d5d3d943c3a673 + languageName: node + linkType: hard + "handlebars@npm:^4.7.7": version: 4.7.7 resolution: "handlebars@npm:4.7.7" @@ -3048,7 +3010,7 @@ __metadata: languageName: node linkType: hard -"ignore@npm:^5.1.8, ignore@npm:^5.2.0": +"ignore@npm:^5.2.0": version: 5.2.4 resolution: "ignore@npm:5.2.4" checksum: 3d4c309c6006e2621659311783eaea7ebcd41fe4ca1d78c91c473157ad6666a57a2df790fe0d07a12300d9aac2888204d7be8d59f9aaf665b1c7fcdb432517ef @@ -4199,6 +4161,13 @@ __metadata: languageName: node linkType: hard +"natural-compare-lite@npm:^1.4.0": + version: 1.4.0 + resolution: "natural-compare-lite@npm:1.4.0" + checksum: 5222ac3986a2b78dd6069ac62cbb52a7bf8ffc90d972ab76dfe7b01892485d229530ed20d0c62e79a6b363a663b273db3bde195a1358ce9e5f779d4453887225 + languageName: node + linkType: hard + "natural-compare@npm:^1.4.0": version: 1.4.0 resolution: "natural-compare@npm:1.4.0"