From ad05a33e218f4d029027f1fafb4c952964f1a189 Mon Sep 17 00:00:00 2001 From: devcsomnicg <129495456+devcsomnicg@users.noreply.github.com> Date: Tue, 10 Oct 2023 19:51:48 +0530 Subject: [PATCH] Embedded msg (#232) * Embedded Manager Module - WIP * Embedded Manager Module - added uuid library * Code modified for embedded msg * Documentation added * Events example added * Embedded msg configurations changed * Import issue of embeddedManager fixed * Circuler dependency solved * Linting error fixing * events code break down into separate folders * Import issues fixed --------- Co-authored-by: Vishal Joshi --- EmbeddedManagerDoc.md | 256 +++++++++++++++++++++++ package.json | 2 + react-example/src/index.tsx | 2 + react-example/src/views/Embedded.tsx | 165 +++++++++++++++ react-example/src/views/Home.tsx | 3 + react-example/yarn.lock | 8 +- src/embedded/consts.ts | 7 + src/embedded/embeddedManager.ts | 89 ++++++++ src/embedded/embeddedMessage.ts | 65 ++++++ src/embedded/embeddedMessageProcessor.ts | 41 ++++ src/embedded/embeddedSessionManager.ts | 156 ++++++++++++++ src/embedded/index.ts | 4 + src/embedded/types.ts | 15 ++ src/events/consts.ts | 11 + src/events/embedded/events.schema.ts | 61 ++++++ src/events/embedded/events.ts | 58 +++++ src/events/embedded/types.ts | 57 +++++ src/events/events.schema.ts | 22 +- src/events/events.test.ts | 4 +- src/events/events.ts | 149 +------------ src/events/in-app/events.schema.ts | 21 ++ src/events/in-app/events.ts | 147 +++++++++++++ src/events/{ => in-app}/types.ts | 0 src/events/index.ts | 5 +- src/index.ts | 1 + yarn.lock | 44 ++-- 26 files changed, 1206 insertions(+), 187 deletions(-) create mode 100644 EmbeddedManagerDoc.md create mode 100644 react-example/src/views/Embedded.tsx create mode 100644 src/embedded/consts.ts create mode 100644 src/embedded/embeddedManager.ts create mode 100644 src/embedded/embeddedMessage.ts create mode 100644 src/embedded/embeddedMessageProcessor.ts create mode 100644 src/embedded/embeddedSessionManager.ts create mode 100644 src/embedded/index.ts create mode 100644 src/embedded/types.ts create mode 100644 src/events/consts.ts create mode 100644 src/events/embedded/events.schema.ts create mode 100644 src/events/embedded/events.ts create mode 100644 src/events/embedded/types.ts create mode 100644 src/events/in-app/events.schema.ts create mode 100644 src/events/in-app/events.ts rename src/events/{ => in-app}/types.ts (100%) diff --git a/EmbeddedManagerDoc.md b/EmbeddedManagerDoc.md new file mode 100644 index 00000000..4b1a9567 --- /dev/null +++ b/EmbeddedManagerDoc.md @@ -0,0 +1,256 @@ +# EmbeddedMessageManager Class +The `EmbeddedManager` class is a fundamental component of the Web SDK project that facilitates the seamless integration and management of embedded messages +and interactions within web applications. It offers developers a set of methods and functionalities to efficiently handle messages sent from external sources +and provides mechanisms to respond to updates and actions associated with these messages. + +Key Features and Functionalities: + +- Message Synchronization: Developers can utilize the syncMessages method to synchronize messages for a specific user. Upon synchronization, a provided callback function is invoked, allowing developers to take actions based on the synchronized messages. +- Listeners: The class allows developers to register listeners using the addUpdateListener and addActionHandler methods. These listeners enable the application to respond to updates and actions related to embedded messages. +- Handlers Retrieval: The class offers methods to retrieve arrays of registered update and action handlers using getUpdateHandlers and getActionHandlers, respectively. This enables developers to manage and interact with these handlers as needed. + +Properties +- messages: IEmbeddedMessage[]: An array that holds the embedded messages received. + +### Table of Contents +- [Syncing Messages](#syncing-messages) +- [Adding Listeners](#adding-listeners) +- [Retrieving Action Handlers](#retrieving-action-handlers) +- [Retrieving Update Handlers](#retrieving-update-handlers) + +# Usage + +1. `Syncing Messages` +The EmbeddedManager class provides a method to sync messages for a specific user and invoke a callback upon synchronization. +public async syncMessages(userId: string, callback: () => void): Promise + +Params +- userId: The identifier of the user for whom to sync messages. +- callback: A function to be called after message synchronization is complete. + +```ts +import { EmbeddedManager } from '@iterable/web-sdk'; + +await new EmbeddedManager().syncMessages('harrymash2006', () => console.log('Synced message')); +``` + +2. `Adding Listeners` +Developers can register listeners to handle updates and actions related to embedded messages. +public addUpdateListener(updateListener: EmbeddedMessageUpdateHandler): void +public addActionHandler(actionHandler: EmbeddedMessageActionHandler): void + +- updateListener: A listener that implements the EmbeddedMessageUpdateHandler interface to handle message update events. +- actionHandler: A listener that implements the EmbeddedMessageActionHandler interface to handle message action events. + +3. `Retrieving Action Handlers` +This method retrieves an array of registered action handlers. +public getActionHandlers(): Array + +4. `Retrieving Update Handlers` +This method retrieves an array of registered update handlers. +public getUpdateHandlers(): Array + +Private Methods +- The following are private methods used internally by the class: + +- retrieveEmbeddedMessages(userId: string): Retrieves embedded messages for the specified user. +- setMessages(_processor: EmbeddedMessagingProcessor): Sets the internal messages array based on the provided processor. +- trackNewlyRetrieved(_processor: EmbeddedMessagingProcessor): Tracks newly retrieved messages and invokes tracking methods. +- notifyUpdateDelegates(): Notifies registered update listeners of message updates. +- notifyDelegatesOfInvalidApiKeyOrSyncStop(): Notifies listeners when the API key is invalid or synchronization stops. + +# EmbeddedSessionManager Class + +The `EmbeddedSessionManager` class is a core component of the Web SDK project designed to manage user interactions with embedded messages in web applications. This class offers developers a range of functions to track and analyze user sessions, impressions, and engagement with embedded messages. It provides methods to start and end sessions, track impressions for individual messages, and retrieve impression data. By utilizing these functionalities, developers can gain insights into how users engage with embedded messages, enabling them to optimize user experiences and measure the effectiveness of message content. + +Properties +- impressions: Map: A map that holds impression data for embedded messages. +- session: IEmbeddedSession: An object representing the current embedded session. + +### Table of Contents +- [Checking Tracking Status](#checking-tracking-status) +- [Starting a Session](#starting-a-session) +- [Ending a Session](#ending-a-session) +- [Managing Impressions](#managing-impressions) + +# Usage + +1. `Checking Tracking Status` +The EmbeddedSessionManager class provides a method to check if session tracking is active. + +```ts +public isTracking(): boolean +``` +Returns true if tracking is active, otherwise false. + +2. `Starting a Session` + Developers can start a new embedded session using the startSession method. + +```ts +public startSession(): void +``` +This method initializes a new session, provided that tracking is not already active. + +3. `Ending a Session` +The class offers the endSession method to end the current session and track impression data. + +```ts +public async endSession(): Promise +``` +This method finalizes the session, collects impression data, and triggers the tracking of the session if applicable. + +4. `Managing Impressions` +- startImpression(messageId: string): Starts tracking an impression for a specific message. +The startImpression function initiates the tracking of an impression associated with a specific message. Impressions provide insights into how users engage with embedded messages, allowing developers to measure their effectiveness. + + When a message's impression tracking starts, the following steps are taken: + + - The function accepts a messageId parameter, which uniquely identifies the message for which the impression is being tracked. + - If an impression for the provided messageId already exists in the impressions map, the function updates the existing impression's start time to the current date and time. + - If an impression for the provided messageId does not exist, a new EmbeddedImpressionData object is created, representing the impression. This object's start time is set to the current date and time, and it is added to the impressions map with the messageId as the key. + +- pauseImpression(messageId: string): Pauses tracking for an impression associated with a message. +The pauseImpression function temporarily halts the tracking of an ongoing impression associated with a particular message. Pausing impressions allows developers to accurately measure how long users engage with a message before taking actions. + + When an impression's tracking is paused, the following actions occur: + + - The function receives a messageId parameter, identifying the message whose impression tracking needs to be paused. + - The function checks if an impression with the given messageId exists in the impressions map. + - If an impression is found, its start time is used to calculate the duration of engagement for the impression. + - The impression's displayCount is incremented to keep track of the number of times the impression has been displayed. + - The calculated duration is added to the impression's duration value, indicating the total time users have spent engaging with the message. + - The impression's start time is reset to undefined, effectively pausing the tracking of the current engagement. + +- endAllImpressions(): Ends tracking for all impressions. +The endAllImpressions function concludes the tracking of all ongoing impressions. It ensures that all impressions are accurately measured and finalized before ending an embedded session. + + When all impressions are ended: + + - The function iterates through all impressions stored in the impressions map. + - For each impression, the same calculation and updates performed in the pauseImpression function are applied, effectively finalizing the impression's engagement duration and display count. + - After iterating through all impressions, the impressions map is cleared, removing all impression data. + +- getImpressionList(): Retrieves a list of tracked impressions. +The getImpressionList function retrieves a list of tracked impressions, including their associated metadata. This function is essential for reporting and analyzing user engagement with embedded messages. + + When calling getImpressionList: + + - The function initializes an empty array to hold the list of impressions. + - It iterates through all impressions stored in the impressions map. + - For each impression, an EmbeddedImpressionData object is created, containing the messageId, displayCount, and duration of the impression. This object is added to the array. + - Once all impressions have been processed, the array of impression data is returned. + +# Tracking Function + +1. `trackEmbeddedMessageReceived(payload: IEmbeddedMessage)` +The tracking function `trackEmbeddedMessageReceived` enables developers to track received embedded messages and send relevant data to the server for analysis. + +- Parameters + - payload: IEmbeddedMessage: The payload representing the received embedded message. +- Return Value + - The function returns a promise containing the result of the tracking request, which is a part of the baseIterableRequest function. +- Behavior + - The function constructs a POST request to the server endpoint /embedded-messaging/events/received. + - It packages the received embedded message data in the request payload. + - The function performs validation of the request payload using the trackEmbeddedMessageSchema schema. + - The request is sent to the server for analysis. +- Example +```ts +const receivedMessage = { + metadata: { + messageId: 'abc123', + campaignId: 1, + }, + elements: { + title: 'Welcome Message', + body: 'Thank you for using our app!', + }, +}; + +trackEmbeddedMessageReceived(receivedMessage) + .then(response => { + console.log('Message reception tracked:', response); + }) + .catch(error => { + console.error('Error tracking message reception:', error); + }); +``` + +2. `trackEmbeddedMessageClick(payload: IEmbeddedMessageMetadata, buttonIdentifier: string, clickedUrl: string, appPackageName: string)` +The tracking function `trackEmbeddedMessageClick` enables developers to track user clicks on buttons within embedded messages. And it's also enables developers to collect and analyze user interaction data to improve engagement and measure the effectiveness of message content. + +- Parameters + - payload: IEmbeddedMessageMetadata: Metadata associated with the embedded message. + - buttonIdentifier: string: Unique identifier for the clicked button. + - clickedUrl: string: The URL associated with the clicked button. + - appPackageName: string: The package name of the application. +- Return Value + - The function returns a promise containing the result of the tracking request, which is a part of the baseIterableRequest function. +- Behavior + - The function constructs a POST request to the server endpoint /embedded-messaging/events/click. + - It packages the following data in the request payload: + - messageId: The unique identifier of the embedded message. + - buttonIdentifier: The identifier of the clicked button. + - targetUrl: The URL associated with the clicked button. + - deviceInfo: An object containing the package name of the application. + - The function performs validation of the request payload using the trackEmbeddedMessageClickSchema schema. + - The request is sent to the server for analysis. +- Example +```ts +const payload = { + messageId: 'abc123', + campaignId: 1, +}; + +const buttonIdentifier = 'button-123'; +const clickedUrl = 'https://example.com'; +const appPackageName = 'com.example.app'; + +trackEmbeddedMessageClick(payload, buttonIdentifier, clickedUrl, appPackageName) + .then(response => { + console.log('Click tracking successful:', response); + }) + .catch(error => { + console.error('Error tracking click:', error); + }); +``` + +3. `trackEmbeddedSession(payload: IEmbeddedSession)` +The tracking function `trackEmbeddedSession` enables developers to track impressions and user sessions associated with embedded messages. The function facilitates data collection for analysis and insights into user engagement patterns. + +- Parameters + - payload: IEmbeddedSession: The payload representing the session to be tracked. +- Return Value + - The function returns a promise containing the result of the tracking request, which is a part of the baseIterableRequest function. +- Behavior + - The function constructs a POST request to the server endpoint /embedded-messaging/events/impression. + - It packages the session data in the request payload. + - The function performs validation of the request payload using the trackEmbeddedSessionSchema schema. + - The request is sent to the server for analysis. +- Example +```ts +const sessionData = { + start: new Date(), + end: new Date(), + impressions: [ + { + messageId: 'abc123', + displayCount: 3, + duration: 10, + }, + { + messageId: 'def456', + displayCount: 2, + duration: 8, + }, + ], +}; + +trackEmbeddedSession(sessionData) + .then(response => { + console.log('Session tracking successful:', response); + }) + .catch(error => { + console.error('Error tracking session:', error); + }); +``` \ No newline at end of file diff --git a/package.json b/package.json index 489864ea..25116ccf 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "throttle-debounce": "^3.0.1", + "uuid": "^9.0.0", "yup": "^0.32.9" }, "scripts": { @@ -71,6 +72,7 @@ "@types/react": "^18.2.21", "@types/react-dom": "^18.2.7", "@types/throttle-debounce": "^2.1.0", + "@types/uuid": "^9.0.2", "@typescript-eslint/eslint-plugin": "^5.38.1", "@typescript-eslint/parser": "^5.38.1", "@webpack-cli/serve": "^1.6.0", diff --git a/react-example/src/index.tsx b/react-example/src/index.tsx index 17b9eca3..fe4870a8 100644 --- a/react-example/src/index.tsx +++ b/react-example/src/index.tsx @@ -8,6 +8,7 @@ import Commerce from 'src/views/Commerce'; import Events from 'src/views/Events'; import Users from 'src/views/Users'; import InApp from 'src/views/InApp'; +import EmbeddedMessage from 'src/views/Embedded'; import { BrowserRouter, Routes, Route } from 'react-router-dom'; import Link from 'src/components/Link'; import styled from 'styled-components'; @@ -82,6 +83,7 @@ const HomeLink = styled(Link)` } /> } /> } /> + } /> diff --git a/react-example/src/views/Embedded.tsx b/react-example/src/views/Embedded.tsx new file mode 100644 index 00000000..cc27e813 --- /dev/null +++ b/react-example/src/views/Embedded.tsx @@ -0,0 +1,165 @@ +import { FC, FormEvent, useEffect, useState } from 'react'; +import { Button, EndpointWrapper, Form, Heading } from './Components.styled'; + +import { + initialize, + EmbeddedManager, + trackEmbeddedMessageReceived, + trackEmbeddedMessageClick, + trackEmbeddedSession +} from '@iterable/web-sdk'; +import TextField from 'src/components/TextField'; + +interface Props {} + +export const EmbeddedMessage: FC = () => { + const [userId, setUserId] = useState(); + + useEffect(() => { + initialize(process.env.API_KEY); + }, []); + + const handleFetchEmbeddedMessages = async (e: FormEvent) => { + e.preventDefault(); + try { + await new EmbeddedManager().syncMessages(userId, () => + console.log('Synced message') + ); + } catch (error: any) { + console.log('error', error); + } + }; + + const submitEmbeddedMessagesReceivedEvent = async ( + e: FormEvent + ) => { + e.preventDefault(); + const receivedMessage = { + metadata: { + messageId: 'abc123', + campaignId: 1 + }, + elements: { + title: 'Welcome Message', + body: 'Thank you for using our app!' + } + }; + + trackEmbeddedMessageReceived(receivedMessage) + .then((response) => { + console.log('Message reception tracked:', response); + }) + .catch((error) => { + console.error('Error tracking message reception:', error); + }); + }; + + const submitEmbeddedMessagesClickEvent = async ( + e: FormEvent + ) => { + e.preventDefault(); + const payload = { + messageId: 'abc123', + campaignId: 1 + }; + + const buttonIdentifier = 'button-123'; + const clickedUrl = 'https://example.com'; + const appPackageName = 'com.example.app'; + + trackEmbeddedMessageClick( + payload, + buttonIdentifier, + clickedUrl, + appPackageName + ) + .then((response) => { + console.log('Click tracking successful:', response); + }) + .catch((error) => { + console.error('Error tracking click:', error); + }); + }; + + const submitEmbeddedMessagesImpressionEvent = async ( + e: FormEvent + ) => { + e.preventDefault(); + const sessionData = { + id: '123', + start: new Date(), + end: new Date(), + impressions: [ + { + messageId: 'abc123', + displayCount: 3, + duration: 10 + }, + { + messageId: 'def456', + displayCount: 2, + duration: 8 + } + ] + }; + + trackEmbeddedSession(sessionData) + .then((response) => { + console.log('Session tracking successful:', response); + }) + .catch((error) => { + console.error('Error tracking session:', error); + }); + }; + + const eventsList = [ + { + heading: 'GET /embedded-messaging/events/received', + onSubmit: handleFetchEmbeddedMessages, + btnText: 'Fetch Embedded Messages' + }, + { + heading: 'POST /embedded-messaging/events/received', + onSubmit: submitEmbeddedMessagesReceivedEvent, + btnText: 'Submit' + }, + { + heading: 'POST /embedded-messaging/events/click', + onSubmit: submitEmbeddedMessagesClickEvent, + btnText: 'Submit' + }, + { + heading: 'POST /embedded-messaging/events/impression', + onSubmit: submitEmbeddedMessagesImpressionEvent, + btnText: 'Submit' + } + ]; + + return ( + <> +

Embedded Message

+ + setUserId(e.target.value)} + id="item-1" + placeholder="e.g. phone_number" + data-qa-update-user-input + required + /> +
+ {eventsList.map((element: any) => ( + <> + {element.heading} + +
+ +
+
+ + ))} + + ); +}; + +export default EmbeddedMessage; diff --git a/react-example/src/views/Home.tsx b/react-example/src/views/Home.tsx index 01a2452d..3156f7df 100644 --- a/react-example/src/views/Home.tsx +++ b/react-example/src/views/Home.tsx @@ -31,6 +31,9 @@ export const Home: FC = () => { inApp + + embedded + ); diff --git a/react-example/yarn.lock b/react-example/yarn.lock index cc244ab1..e8ee9503 100644 --- a/react-example/yarn.lock +++ b/react-example/yarn.lock @@ -688,7 +688,7 @@ integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== "@iterable/web-sdk@../": - version "1.0.4" + version "1.0.5" dependencies: "@pabra/sortby" "^1.0.1" axios "^0.21.4" @@ -697,6 +697,7 @@ react "^18.2.0" react-dom "^18.2.0" throttle-debounce "^3.0.1" + uuid "^9.0.0" yup "^0.32.9" "@jest/console@^27.3.1": @@ -6451,6 +6452,11 @@ uuid@^8.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== +uuid@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" + integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== + v8-compile-cache@^2.0.3: version "2.3.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" diff --git a/src/embedded/consts.ts b/src/embedded/consts.ts new file mode 100644 index 00000000..eea4ec2f --- /dev/null +++ b/src/embedded/consts.ts @@ -0,0 +1,7 @@ +export const embedded_msg_endpoint = '/embedded-messaging/messages'; + +// Error Msgs +export const ErrorMessage = { + invalid_api_key: 'Invalid API Key', + subscription_inactive: 'SUBSCRIPTION_INACTIVE' +}; diff --git a/src/embedded/embeddedManager.ts b/src/embedded/embeddedManager.ts new file mode 100644 index 00000000..9a416ffd --- /dev/null +++ b/src/embedded/embeddedManager.ts @@ -0,0 +1,89 @@ +import { baseIterableRequest } from '../request'; +import { + EmbeddedMessageUpdateHandler, + EmbeddedMessageActionHandler +} from './types'; +import { IterableResponse } from '../types'; +import { IEmbeddedMessage } from '../events/embedded/types'; +import { EmbeddedMessagingProcessor } from './embeddedMessageProcessor'; +import { trackEmbeddedMessageReceived } from '../events'; +import { embedded_msg_endpoint, ErrorMessage } from './consts'; + +export class EmbeddedManager { + private messages: IEmbeddedMessage[] = []; + private updateListeners: EmbeddedMessageUpdateHandler[] = []; + private actionListeners: EmbeddedMessageActionHandler[] = []; + + public async syncMessages(userId: string, callback: () => void) { + await this.retrieveEmbeddedMessages(userId); + callback(); + } + + private async retrieveEmbeddedMessages(userId: string) { + try { + const iterableResult: any = await baseIterableRequest({ + method: 'GET', + url: `${embedded_msg_endpoint}?userId=${userId}` + }); + + if (iterableResult?.data?.embeddedMessages?.length) { + const processor = new EmbeddedMessagingProcessor( + [...this.messages], + iterableResult?.data?.embeddedMessages + ); + + this.setMessageProcesser(processor); + await this.trackNewlyRetrieved(processor); + this.messages = [...iterableResult?.data?.embeddedMessages]; + } + } catch (error: any) { + if (error.response.data) { + const { msg } = error.response.data; + + if ( + msg.toLowerCase() === ErrorMessage.invalid_api_key.toLowerCase() || + msg.toLowerCase() === ErrorMessage.subscription_inactive.toLowerCase() + ) { + this.notifyDelegatesOfInvalidApiKeyOrSyncStop(); + } + } + } + } + + private setMessageProcesser(_processor: EmbeddedMessagingProcessor) { + this.messages = _processor.processedMessagesList(); + } + + private async trackNewlyRetrieved(_processor: EmbeddedMessagingProcessor) { + const msgsList = _processor.newlyRetrievedMessages(); + + for (let i = 0; i < msgsList.length; i++) { + await trackEmbeddedMessageReceived(msgsList[i]); + } + } + + public addUpdateListener(updateListener: EmbeddedMessageUpdateHandler) { + this.updateListeners.push(updateListener); + } + + public addActionHandler(actionHandler: EmbeddedMessageActionHandler) { + this.actionListeners.push(actionHandler); + } + + private notifyDelegatesOfInvalidApiKeyOrSyncStop() { + this.updateListeners.forEach( + (updateListener: EmbeddedMessageUpdateHandler) => { + updateListener.onEmbeddedMessagingDisabled(); + } + ); + } + + public getActionHandlers(): Array { + return this.actionListeners; + } + + //Get the list of updateHandlers + public getUpdateHandlers(): Array { + return this.updateListeners; + } +} diff --git a/src/embedded/embeddedMessage.ts b/src/embedded/embeddedMessage.ts new file mode 100644 index 00000000..3fe0cc18 --- /dev/null +++ b/src/embedded/embeddedMessage.ts @@ -0,0 +1,65 @@ + +export class IterableEmbeddedMessage { + public metadata: EmbeddedMessageMetadata; + public elements?: EmbeddedMessageElements; + public payload?: Array; + + constructor(metadata: EmbeddedMessageMetadata, + elements?: EmbeddedMessageElements, + payload?: Array) { + this.metadata = metadata + this.elements = elements + this.payload = payload + } + + public initMetaData(messageId: string, campaignId?: number, isProof?: boolean, placementId?: number) { + let metadata = new EmbeddedMessageMetadata(messageId, campaignId, isProof, placementId) + + return new IterableEmbeddedMessage(metadata); + } +} + +export class EmbeddedMessageMetadata { + public messageId: string; + public campaignId?: number; + public isProof?: boolean; + public placementId?: number; + + constructor(messageId: string, campaignId?: number, isProof?: boolean, placementId?: number) { + this.messageId = messageId; + this.campaignId = campaignId; + this.isProof = isProof; + this.placementId = placementId; + } +} + +export class EmbeddedMessageElements { + public title?: string = ''; + public body?: string = ''; + public mediaUrl: string = ''; + + public buttons?: Array + public text?: Array + public defaultAction?: EmbeddedMessageElementsDefaultAction +} + +export class EmbeddedMessageElementsButton { + public id?: string; + public title?: string; + public action?: EmbeddedMessageElementsButtonAction; +} + +export class EmbeddedMessageElementsText { + public id: string = ''; + public text?: string; +} + +export class EmbeddedMessageElementsButtonAction { + public type: string = ''; + public data?: string; +} + +export class EmbeddedMessageElementsDefaultAction { + public type: string = ''; + public data?: string; +} \ No newline at end of file diff --git a/src/embedded/embeddedMessageProcessor.ts b/src/embedded/embeddedMessageProcessor.ts new file mode 100644 index 00000000..1a4948d5 --- /dev/null +++ b/src/embedded/embeddedMessageProcessor.ts @@ -0,0 +1,41 @@ +import { IEmbeddedMessage } from '../events/embedded/types'; + +export class EmbeddedMessagingProcessor { + private currentMessages: Array; + private fetchedMessages: Array; + + constructor( + currentMessages: Array, + fetchedMessages: Array + ) { + this.currentMessages = currentMessages; // old messages + this.fetchedMessages = fetchedMessages; // all messages + } + + public processedMessagesList() { + return this.fetchedMessages; + } + + public newlyRetrievedMessages() { + return this.getNewMessages(); + } + + public getNewMessages() { + return this.fetchedMessages.filter( + (messageInfo: IEmbeddedMessage) => + !this.getCurrentMessageIds().includes(messageInfo.metadata.messageId) + ); + } + + public getCurrentMessageIds(): string[] { + return this.currentMessages.map( + (messageInfo: IEmbeddedMessage) => messageInfo.metadata.messageId + ); + } + + public getFetchedMessageIds(): string[] { + return this.fetchedMessages.map( + (messageInfo: IEmbeddedMessage) => messageInfo.metadata.messageId + ); + } +} diff --git a/src/embedded/embeddedSessionManager.ts b/src/embedded/embeddedSessionManager.ts new file mode 100644 index 00000000..d9bf0a83 --- /dev/null +++ b/src/embedded/embeddedSessionManager.ts @@ -0,0 +1,156 @@ +import { v4 as uuidv4 } from 'uuid'; +import { + IEmbeddedSession, + IEmbeddedImpression +} from '../events/embedded/types'; +import { IEmbeddedImpressionData } from './types'; +import { trackEmbeddedSession } from '../events/embedded/events'; + +class EmbeddedSession { + public start?: Date; + public end?: Date; + public placementId?: string; + public impressions?: Array; + public id: string; + + constructor( + start?: Date, + end?: Date, + placementId?: string, + impressions?: Array + ) { + this.start = start; + this.end = end; + this.placementId = placementId; + this.impressions = impressions; + this.id = uuidv4(); + } +} + +class EmbeddedImpressionData { + public messageId: string; + public displayCount: number; + public duration: number; + public start?: Date = undefined; + + constructor(messageId: string, displayCount?: number, duration?: number) { + this.messageId = messageId; + this.displayCount = displayCount ? displayCount : 0; + this.duration = duration ? duration : 0.0; + } +} + +export class EmbeddedSessionManager { + private impressions: Map = new Map(); + public session: IEmbeddedSession = new EmbeddedSession( + undefined, + undefined, + '0', + undefined + ); + + public isTracking(): boolean { + return !!this.session.start; + } + + public startSession() { + if (this.isTracking()) { + console.log('Embedded session started twice'); + return; + } + + this.session = new EmbeddedSession(new Date(), undefined, '0', undefined); + } + + public async endSession() { + if (!this.isTracking()) { + console.log('Embedded session ended without start'); + return; + } + + this.impressions.forEach((impressionData, messageId) => + this.pauseImpression(messageId) + ); + this.session.end = new Date(); + + if (!this.session.impressions?.length) { + console.log('No impressions in the session. Skipping tracking.'); + return; + } + + if (this.impressions.size) { + //re-initialising session object + this.session = new EmbeddedSession(undefined, undefined, '0', undefined); + + const sessionToTrack = new EmbeddedSession( + this.session.start, + new Date(), + '0', + this.getImpressionList() + ); + + await trackEmbeddedSession(sessionToTrack); + + this.impressions = new Map(); + } + } + + public startImpression(messageId: string) { + let impressionData: IEmbeddedImpressionData | undefined = + this.impressions.get(messageId); + + if (!impressionData) { + impressionData = new EmbeddedImpressionData(messageId); + this.impressions.set(messageId, impressionData); + } + + impressionData.start = new Date(); + } + + public pauseImpression(messageId: string) { + const impressionData: IEmbeddedImpressionData | undefined = + this.impressions.get(messageId); + + if (!impressionData) { + console.log('onMessageImpressionEnded: impressionData not found'); + return; + } + + if (!impressionData?.start) { + console.log('onMessageImpressionEnded: impressionStarted is null'); + return; + } + + this.updateDisplayCountAndDuration(impressionData); + } + + private getImpressionList() { + const impressionList: Array = []; + + this.impressions.forEach((impressionData) => { + impressionList.push( + new EmbeddedImpressionData( + impressionData.messageId, + impressionData.displayCount, + impressionData.duration + ) + ); + }); + + return impressionList; + } + + private updateDisplayCountAndDuration( + impressionData: IEmbeddedImpressionData + ) { + if (impressionData.start) { + impressionData.displayCount = impressionData.displayCount + 1; + impressionData.duration = + impressionData.duration + + (new Date().getTime() - impressionData.start.getTime()) / 1000.0; + impressionData.start = undefined; + } + + return impressionData; + } +} diff --git a/src/embedded/index.ts b/src/embedded/index.ts new file mode 100644 index 00000000..f1653cbe --- /dev/null +++ b/src/embedded/index.ts @@ -0,0 +1,4 @@ +export * from './embeddedManager'; +export * from './types'; +export * from './embeddedSessionManager'; +export * from './embeddedMessage'; diff --git a/src/embedded/types.ts b/src/embedded/types.ts new file mode 100644 index 00000000..8f64f438 --- /dev/null +++ b/src/embedded/types.ts @@ -0,0 +1,15 @@ +export interface EmbeddedMessageUpdateHandler { + onMessagesUpdated: () => void; + onEmbeddedMessagingDisabled: () => void; +} + +export interface EmbeddedMessageActionHandler { + onTapAction: (url: string) => void; +} + +export interface IEmbeddedImpressionData { + messageId: string; + displayCount: number; + duration: number; + start?: Date; +} diff --git a/src/events/consts.ts b/src/events/consts.ts new file mode 100644 index 00000000..61442c90 --- /dev/null +++ b/src/events/consts.ts @@ -0,0 +1,11 @@ +export const EndPoints = { + event_track: '/events/track', + msg_received_event_track: '/embedded-messaging/events/received', + msg_click_event_track: '/embedded-messaging/events/click', + msg_impression_event_track: '/embedded-messaging/events/impression', + track_app_close: '/events/trackInAppClose', + track_app_open: '/events/trackInAppOpen', + track_app_click: '/events/trackInAppClick', + track_app_delivery: '/events/trackInAppDelivery', + track_app_consume: '/events/inAppConsume' +}; diff --git a/src/events/embedded/events.schema.ts b/src/events/embedded/events.schema.ts new file mode 100644 index 00000000..4ab06031 --- /dev/null +++ b/src/events/embedded/events.schema.ts @@ -0,0 +1,61 @@ +import { boolean, number, object, string, array, mixed, date } from 'yup'; + +export const trackEmbeddedMessageSchema = object().shape({ + metadata: object().shape({ + messageId: string(), + campaignId: number(), + isProof: boolean(), + placementId: number() + }), + elements: object().shape({ + title: string(), + body: string(), + mediaUrl: string(), + buttons: array().of( + object().shape({ + id: string(), + title: string(), + action: object().shape({ + type: string(), + data: string() + }) + }) + ), + text: array().of( + object().shape({ + id: string(), + text: string() + }) + ), + defaultAction: array().of( + object().shape({ + type: string(), + data: string() + }) + ) + }), + payload: array().of(mixed()) +}); + +export const trackEmbeddedMessageClickSchema = object().shape({ + messageId: string(), + buttonIdentifier: string(), + targetUrl: string(), + deviceInfo: object().shape({ + appPackageName: string() + }) +}); + +export const trackEmbeddedSessionSchema = object().shape({ + start: date(), + end: date(), + placementId: string(), + impressions: array().of( + object().shape({ + messageId: string(), + displayCount: number(), + duration: number() + }) + ), + id: string() +}); diff --git a/src/events/embedded/events.ts b/src/events/embedded/events.ts new file mode 100644 index 00000000..4b501671 --- /dev/null +++ b/src/events/embedded/events.ts @@ -0,0 +1,58 @@ +import { baseIterableRequest } from '../../request'; +import { + IEmbeddedMessage, + IEmbeddedMessageMetadata, + IEmbeddedSession +} from './types'; +import { IterableResponse } from '../../types'; +import { + trackEmbeddedMessageSchema, + trackEmbeddedMessageClickSchema, + trackEmbeddedSessionSchema +} from './events.schema'; +import { EndPoints } from '../consts'; + +export const trackEmbeddedMessageReceived = (payload: IEmbeddedMessage) => { + return baseIterableRequest({ + method: 'POST', + url: EndPoints.msg_received_event_track, + data: payload, + validation: { + data: trackEmbeddedMessageSchema + } + }); +}; + +export const trackEmbeddedMessageClick = ( + payload: IEmbeddedMessageMetadata, + buttonIdentifier: string, + clickedUrl: string, + appPackageName: string +) => { + return baseIterableRequest({ + method: 'POST', + url: EndPoints.msg_click_event_track, + data: { + messageId: payload.messageId, + buttonIdentifier: buttonIdentifier, + targetUrl: clickedUrl, + deviceInfo: { + appPackageName: appPackageName + } + }, + validation: { + data: trackEmbeddedMessageClickSchema + } + }); +}; + +export const trackEmbeddedSession = (payload: IEmbeddedSession) => { + return baseIterableRequest({ + method: 'POST', + url: EndPoints.msg_impression_event_track, + data: payload, + validation: { + data: trackEmbeddedSessionSchema + } + }); +}; diff --git a/src/events/embedded/types.ts b/src/events/embedded/types.ts new file mode 100644 index 00000000..150f64bb --- /dev/null +++ b/src/events/embedded/types.ts @@ -0,0 +1,57 @@ +export interface IEmbeddedMessageMetadata { + messageId: string; + campaignId?: number; + isProof?: boolean; + placementId?: number; +} + +export interface IEmbeddedMessageElementsButton { + id: string; + title: string; + action?: IEmbeddedMessageElementsButtonAction; +} + +export interface IEmbeddedMessageElementsText { + id: string; + text?: string; +} + +export interface IEmbeddedMessageElementsButtonAction { + type: string; + data?: string; +} + +export interface IEmbeddedMessageElementsDefaultAction { + type: string; + data?: string; +} + +export interface IEmbeddedMessageElements { + title?: string; + body?: string; + mediaUrl?: string; + + buttons?: [IEmbeddedMessageElementsButton]; + text?: [IEmbeddedMessageElementsText]; + defaultAction?: IEmbeddedMessageElementsDefaultAction; +} + +export interface IEmbeddedMessage { + metadata: IEmbeddedMessageMetadata; + elements?: IEmbeddedMessageElements; + payload?: Array; +} + +export interface IEmbeddedImpression { + messageId: string; + displayCount: number; + duration: number; +} + +export interface IEmbeddedSession { + start?: Date; + end?: Date; + placementId?: string; + impressions?: Array; + id: string; +} diff --git a/src/events/events.schema.ts b/src/events/events.schema.ts index b515525d..e993fa44 100644 --- a/src/events/events.schema.ts +++ b/src/events/events.schema.ts @@ -1,4 +1,4 @@ -import { boolean, number, object, string } from 'yup'; +import { number, object, string } from 'yup'; export const trackSchema = object().shape({ eventName: string().required(), @@ -8,23 +8,3 @@ export const trackSchema = object().shape({ campaignId: number(), templateId: number() }); - -export const eventRequestSchema = object().shape({ - messageId: string().required(), - clickedUrl: string(), - messageContext: object().shape({ - saveToInbox: boolean(), - silentInbox: boolean(), - location: string() - }), - closeAction: string(), - deviceInfo: object() - .shape({ - deviceId: string().required(), - platform: string().required(), - appPackageName: string().required() - }) - .required(), - inboxSessionId: string(), - createdAt: number() -}); diff --git a/src/events/events.test.ts b/src/events/events.test.ts index 057d0579..ad5ee831 100644 --- a/src/events/events.test.ts +++ b/src/events/events.test.ts @@ -1,13 +1,13 @@ import MockAdapter from 'axios-mock-adapter'; import { baseAxiosRequest } from '../request'; +import { track } from './events'; import { - track, trackInAppClick, trackInAppClose, trackInAppConsume, trackInAppDelivery, trackInAppOpen -} from './events'; +} from './in-app/events'; import { WEB_PLATFORM } from '../constants'; import { createClientError } from '../utils/testUtils'; diff --git a/src/events/events.ts b/src/events/events.ts index 2b863083..8e261092 100644 --- a/src/events/events.ts +++ b/src/events/events.ts @@ -1,8 +1,8 @@ import { baseIterableRequest } from '../request'; -import { InAppEventRequestParams, InAppTrackRequestParams } from './types'; +import { InAppTrackRequestParams } from './in-app/types'; import { IterableResponse } from '../types'; -import { WEB_PLATFORM } from '../constants'; -import { eventRequestSchema, trackSchema } from './events.schema'; +import { trackSchema } from './events.schema'; +import { EndPoints } from './consts'; export const track = (payload: InAppTrackRequestParams) => { /* a customer could potentially send these up if they're not using TypeScript */ @@ -11,151 +11,10 @@ export const track = (payload: InAppTrackRequestParams) => { return baseIterableRequest({ method: 'POST', - url: '/events/track', + url: EndPoints.event_track, data: payload, validation: { data: trackSchema } }); }; - -export const trackInAppClose = (payload: InAppEventRequestParams) => { - /* a customer could potentially send these up if they're not using TypeScript */ - delete (payload as any).userId; - delete (payload as any).email; - - return baseIterableRequest({ - method: 'POST', - url: '/events/trackInAppClose', - data: { - ...payload, - deviceInfo: { - ...payload.deviceInfo, - platform: WEB_PLATFORM, - deviceId: global.navigator.userAgent || '' - } - }, - validation: { - data: eventRequestSchema - } - }); -}; - -export const trackInAppOpen = ( - payload: Omit< - InAppEventRequestParams, - 'clickedUrl' | 'inboxSessionId' | 'closeAction' - > -) => { - /* a customer could potentially send these up if they're not using TypeScript */ - delete (payload as any).userId; - delete (payload as any).email; - - return baseIterableRequest({ - method: 'POST', - url: '/events/trackInAppOpen', - data: { - ...payload, - deviceInfo: { - ...payload.deviceInfo, - platform: WEB_PLATFORM, - deviceId: global.navigator.userAgent || '' - } - }, - validation: { - data: eventRequestSchema.omit([ - 'clickedUrl', - 'inboxSessionId', - 'closeAction' - ]) - } - }); -}; - -export const trackInAppClick = ( - payload: Omit, - sendBeacon = false -) => { - /* a customer could potentially send these up if they're not using TypeScript */ - delete (payload as any).userId; - delete (payload as any).email; - - return baseIterableRequest({ - method: 'POST', - url: '/events/trackInAppClick', - sendBeacon, - data: { - ...payload, - deviceInfo: { - ...payload.deviceInfo, - platform: WEB_PLATFORM, - deviceId: global.navigator.userAgent || '' - } - }, - validation: { - data: eventRequestSchema.omit(['inboxSessionId', 'closeAction']) - } - }); -}; - -export const trackInAppDelivery = ( - payload: Omit< - InAppEventRequestParams, - 'clickedUrl' | 'closeAction' | 'inboxSessionId' - > -) => { - /* a customer could potentially send these up if they're not using TypeScript */ - delete (payload as any).userId; - delete (payload as any).email; - - return baseIterableRequest({ - method: 'POST', - url: '/events/trackInAppDelivery', - data: { - ...payload, - deviceInfo: { - ...payload.deviceInfo, - platform: WEB_PLATFORM, - deviceId: global.navigator.userAgent || '' - } - }, - validation: { - data: eventRequestSchema.omit([ - 'clickedUrl', - 'inboxSessionId', - 'closeAction' - ]) - } - }); -}; - -export const trackInAppConsume = ( - payload: Omit< - InAppEventRequestParams, - 'clickedUrl' | 'closeAction' | 'inboxSessionId' - > -) => { - /* a customer could potentially send these up if they're not using TypeScript */ - delete (payload as any).userId; - delete (payload as any).email; - - return baseIterableRequest({ - method: 'POST', - url: '/events/inAppConsume', - data: { - ...payload, - deviceInfo: { - ...payload.deviceInfo, - platform: WEB_PLATFORM, - deviceId: global.navigator.userAgent || '' - } - }, - validation: { - data: eventRequestSchema.omit([ - 'clickedUrl', - 'inboxSessionId', - 'closeAction' - ]) - } - }); -}; diff --git a/src/events/in-app/events.schema.ts b/src/events/in-app/events.schema.ts new file mode 100644 index 00000000..24fd49b1 --- /dev/null +++ b/src/events/in-app/events.schema.ts @@ -0,0 +1,21 @@ +import { boolean, number, object, string } from 'yup'; + +export const eventRequestSchema = object().shape({ + messageId: string().required(), + clickedUrl: string(), + messageContext: object().shape({ + saveToInbox: boolean(), + silentInbox: boolean(), + location: string() + }), + closeAction: string(), + deviceInfo: object() + .shape({ + deviceId: string().required(), + platform: string().required(), + appPackageName: string().required() + }) + .required(), + inboxSessionId: string(), + createdAt: number() +}); diff --git a/src/events/in-app/events.ts b/src/events/in-app/events.ts new file mode 100644 index 00000000..0336ee3a --- /dev/null +++ b/src/events/in-app/events.ts @@ -0,0 +1,147 @@ +import { baseIterableRequest } from '../../request'; +import { InAppEventRequestParams } from './types'; +import { IterableResponse } from '../../types'; +import { WEB_PLATFORM } from '../../constants'; +import { eventRequestSchema } from './events.schema'; +import { EndPoints } from '../consts'; + +export const trackInAppClose = (payload: InAppEventRequestParams) => { + /* a customer could potentially send these up if they're not using TypeScript */ + delete (payload as any).userId; + delete (payload as any).email; + + return baseIterableRequest({ + method: 'POST', + url: EndPoints.track_app_close, + data: { + ...payload, + deviceInfo: { + ...payload.deviceInfo, + platform: WEB_PLATFORM, + deviceId: global.navigator.userAgent || '' + } + }, + validation: { + data: eventRequestSchema + } + }); +}; + +export const trackInAppOpen = ( + payload: Omit< + InAppEventRequestParams, + 'clickedUrl' | 'inboxSessionId' | 'closeAction' + > +) => { + /* a customer could potentially send these up if they're not using TypeScript */ + delete (payload as any).userId; + delete (payload as any).email; + + return baseIterableRequest({ + method: 'POST', + url: EndPoints.track_app_open, + data: { + ...payload, + deviceInfo: { + ...payload.deviceInfo, + platform: WEB_PLATFORM, + deviceId: global.navigator.userAgent || '' + } + }, + validation: { + data: eventRequestSchema.omit([ + 'clickedUrl', + 'inboxSessionId', + 'closeAction' + ]) + } + }); +}; + +export const trackInAppClick = ( + payload: Omit, + sendBeacon = false +) => { + /* a customer could potentially send these up if they're not using TypeScript */ + delete (payload as any).userId; + delete (payload as any).email; + + return baseIterableRequest({ + method: 'POST', + url: EndPoints.track_app_click, + sendBeacon, + data: { + ...payload, + deviceInfo: { + ...payload.deviceInfo, + platform: WEB_PLATFORM, + deviceId: global.navigator.userAgent || '' + } + }, + validation: { + data: eventRequestSchema.omit(['inboxSessionId', 'closeAction']) + } + }); +}; + +export const trackInAppDelivery = ( + payload: Omit< + InAppEventRequestParams, + 'clickedUrl' | 'closeAction' | 'inboxSessionId' + > +) => { + /* a customer could potentially send these up if they're not using TypeScript */ + delete (payload as any).userId; + delete (payload as any).email; + + return baseIterableRequest({ + method: 'POST', + url: EndPoints.track_app_delivery, + data: { + ...payload, + deviceInfo: { + ...payload.deviceInfo, + platform: WEB_PLATFORM, + deviceId: global.navigator.userAgent || '' + } + }, + validation: { + data: eventRequestSchema.omit([ + 'clickedUrl', + 'inboxSessionId', + 'closeAction' + ]) + } + }); +}; + +export const trackInAppConsume = ( + payload: Omit< + InAppEventRequestParams, + 'clickedUrl' | 'closeAction' | 'inboxSessionId' + > +) => { + /* a customer could potentially send these up if they're not using TypeScript */ + delete (payload as any).userId; + delete (payload as any).email; + + return baseIterableRequest({ + method: 'POST', + url: EndPoints.track_app_consume, + data: { + ...payload, + deviceInfo: { + ...payload.deviceInfo, + platform: WEB_PLATFORM, + deviceId: global.navigator.userAgent || '' + } + }, + validation: { + data: eventRequestSchema.omit([ + 'clickedUrl', + 'inboxSessionId', + 'closeAction' + ]) + } + }); +}; diff --git a/src/events/types.ts b/src/events/in-app/types.ts similarity index 100% rename from src/events/types.ts rename to src/events/in-app/types.ts diff --git a/src/events/index.ts b/src/events/index.ts index 6833ef2b..4a1936c4 100644 --- a/src/events/index.ts +++ b/src/events/index.ts @@ -1,2 +1,5 @@ -export * from './types'; export * from './events'; +export * from './in-app/events'; +export * from './in-app/types'; +export * from './embedded/events'; +export * from './embedded/types'; diff --git a/src/index.ts b/src/index.ts index 42d4311e..c9822c0f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,5 +4,6 @@ export * from './inapp'; export * from './events'; export * from './commerce'; export * from './types'; +export * from './embedded'; export * from './components/notification'; export { config } from './utils/config'; diff --git a/yarn.lock b/yarn.lock index 9d9cc9cc..eaade084 100644 --- a/yarn.lock +++ b/yarn.lock @@ -38,9 +38,9 @@ integrity sha512-DGjt2QZse5SGd9nfOSqO4WLJ8NN/oHkijbXbPrxuoJO3oIPJL3TciZs9FX+cOHNiY9E9l0opL8g7BmLe3T+9ew== "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.22.9": - version "7.22.9" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.9.tgz#71cdb00a1ce3a329ce4cbec3a44f9fef35669730" - integrity sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ== + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.20.tgz#8df6e96661209623f1975d66c35ffca66f3306d0" + integrity sha512-BQYjKbpXjoXwFW5jGqiizJQQT/aC7pFm9Ok1OWssonuguICi264lbgMzRp2ZMmRSlfkX6DsWDDcsrctK8Rwfiw== "@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.5.5", "@babel/core@^7.7.2", "@babel/core@^7.7.5": version "7.16.0" @@ -292,10 +292,10 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz#220df993bfe904a4a6b02ab4f3385a5ebf6e2389" integrity sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w== -"@babel/helper-validator-identifier@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.15.tgz#601fa28e4cc06786c18912dca138cec73b882044" - integrity sha512-4E/F9IIEi8WR94324mbDUMo074YTheJmd7eZF5vITTeYchqAi6sYXRLHUVsmkdmY4QjfKTcB2jB7dVP3NaBElQ== +"@babel/helper-validator-identifier@^7.22.19": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== "@babel/helper-validator-option@^7.14.5": version "7.14.5" @@ -1064,12 +1064,12 @@ to-fast-properties "^2.0.0" "@babel/types@^7.22.15", "@babel/types@^7.22.5": - version "7.22.17" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.17.tgz#f753352c4610ffddf9c8bc6823f9ff03e2303eee" - integrity sha512-YSQPHLFtQNE5xN9tHuZnzu8vPr61wVTBZdfv1meex1NBosa4iT05k/Jw06ddJugi4bk7The/oSwQGFcksmEJQg== + version "7.22.19" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.19.tgz#7425343253556916e440e662bb221a93ddb75684" + integrity sha512-P7LAw/LbojPzkgp5oznjE6tQEIWbp4PkkfrZDINTro9zgBRtI324/EYsiSI7lhPbpIQ+DCeR2NNmMWANGGfZsg== dependencies: "@babel/helper-string-parser" "^7.22.5" - "@babel/helper-validator-identifier" "^7.22.15" + "@babel/helper-validator-identifier" "^7.22.19" to-fast-properties "^2.0.0" "@bcoe/v8-coverage@^0.2.3": @@ -1653,6 +1653,11 @@ resolved "https://registry.yarnpkg.com/@types/throttle-debounce/-/throttle-debounce-2.1.0.tgz#1c3df624bfc4b62f992d3012b84c56d41eab3776" integrity sha512-5eQEtSCoESnh2FsiLTxE121IiE60hnMqcb435fShf4bpLRjEu1Eoekht23y6zXS9Ts3l+Szu3TARnTsA0GkOkQ== +"@types/uuid@^9.0.2": + version "9.0.2" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.2.tgz#ede1d1b1e451548d44919dc226253e32a6952c4b" + integrity sha512-kNnC1GFBLuhImSnV7w4njQkUiJi0ZXUycu1rUaouPqiKlXkh77JKgdRnTAp1x5eBwcIwbtI+3otwzuIDEuDoxQ== + "@types/ws@^8.2.2": version "8.2.2" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.2.2.tgz#7c5be4decb19500ae6b3d563043cd407bf366c21" @@ -2506,9 +2511,9 @@ caniuse-lite@^1.0.30001274, caniuse-lite@^1.0.30001370: integrity sha512-JVQnfoO7FK7WvU4ZkBRbPjaot4+YqxogSDosHv0Hv5mWpUESmN+UubMU6L/hGz8QlQ2aY5U0vR6MOs6j/CXpNA== caniuse-lite@^1.0.30001517: - version "1.0.30001534" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001534.tgz#f24a9b2a6d39630bac5c132b5dff89b39a12e7dd" - integrity sha512-vlPVrhsCS7XaSh2VvWluIQEzVhefrUQcEsQWSS5A5V+dM07uv1qHeQzAOTGIMy9i3e9bH15+muvI/UHojVgS/Q== + version "1.0.30001535" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001535.tgz#908a5b7ef11172f51f0b88f3d850aef1c6a3cf7b" + integrity sha512-48jLyUkiWFfhm/afF7cQPqPjaUmSraEhK4j+FCTJpgnGGEZHqyLe3hmWH7lIooZdSzXL0ReMvHz0vKDoTBsrwg== chalk@^2.0.0, chalk@^2.4.2: version "2.4.2" @@ -3194,9 +3199,9 @@ electron-to-chromium@^1.3.886, electron-to-chromium@^1.4.202: integrity sha512-ICHvGaCIQR3P88uK8aRtx8gmejbVJyC6bB4LEC3anzBrIzdzC7aiZHY4iFfXhN4st6I7lMO0x4sgBHf/7kBvRw== electron-to-chromium@^1.4.477: - version "1.4.519" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.519.tgz#01b9bc3f1bb50c4971bdd1eeca6d9a73575bd581" - integrity sha512-kqs9oGYL4UFVkLKhqCTgBCYZv+wZ374yABDMqlDda9HvlkQxvSr7kgf4hfWVjMieDbX+1MwPHFBsOGCMIBaFKg== + version "1.4.523" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.523.tgz#f82f99243c827df05c26776d49712cb284972df6" + integrity sha512-9AreocSUWnzNtvLcbpng6N+GkXnCcBR80IQkxRC9Dfdyg4gaWNUPBujAHUpKkiUkoSoR9UlhA4zD/IgBklmhzg== emittery@^0.8.1: version "0.8.1" @@ -7063,6 +7068,11 @@ uuid@^3.4.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== +uuid@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5" + integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg== + v8-compile-cache@^2.0.3: version "2.3.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"