From 02662400f381981c86eed7b7f9de08753979d84f Mon Sep 17 00:00:00 2001 From: Hani Vora <150109181+hani-iterable@users.noreply.github.com> Date: Mon, 4 Mar 2024 19:37:30 +0530 Subject: [PATCH 01/88] Integrate AUT feature (#301) * merge fork repo code and implement anon session * resolve test cases failure * call API for getting criteria * track anon event and purchase * track anon update cart and user * write test cases * resolve test cases failure for autjorization * modify anonymous manager class * modify test cases * update the lock files * implement criteria completion checker * implement user merge and set config * set config * Complete AUT feature * Resolve review comments * implement test case for merge user * unit test case for anon user event tracking * test case for criteria completion checker * resolve build failure * resolve build failure * resolve review comments * remove circular dependency * remove lock file * resolve review comments --- AnonymousUserEventTracking.md | 138 ++ package.json | 4 +- react-example/package.json | 5 +- react-example/src/components/EventsForm.tsx | 43 +- react-example/src/index.tsx | 2 + react-example/src/views/Commerce.tsx | 52 +- react-example/src/views/Users.tsx | 25 +- react-example/yarn.lock | 129 +- src/authorization/authorization.test.ts | 112 +- src/authorization/authorization.ts | 101 +- src/commerce/commerce.test.ts | 171 +- src/commerce/commerce.ts | 40 +- src/commerce/types.ts | 2 + src/constants.ts | 25 + src/events/events.test.ts | 59 +- src/events/events.ts | 13 +- src/events/types.ts | 2 + src/users/types.ts | 2 + src/users/users.schema.ts | 6 +- src/users/users.test.ts | 36 +- src/users/users.ts | 13 +- src/utils/anonymousUserEventManager.test.ts | 511 +++++ src/utils/anonymousUserEventManager.ts | 368 ++++ src/utils/anonymousUserMerge.test.ts | 96 + src/utils/anonymousUserMerge.ts | 81 + src/utils/commonFunctions.ts | 17 + src/utils/config.ts | 6 +- src/utils/criteriaCompletionChecker.test.ts | 408 ++++ src/utils/criteriaCompletionChecker.ts | 314 ++++ src/utils/types.ts | 20 + yarn.lock | 1881 ++++++++++--------- 31 files changed, 3560 insertions(+), 1122 deletions(-) create mode 100644 AnonymousUserEventTracking.md create mode 100644 src/utils/anonymousUserEventManager.test.ts create mode 100644 src/utils/anonymousUserEventManager.ts create mode 100644 src/utils/anonymousUserMerge.test.ts create mode 100644 src/utils/anonymousUserMerge.ts create mode 100644 src/utils/commonFunctions.ts create mode 100644 src/utils/criteriaCompletionChecker.test.ts create mode 100644 src/utils/criteriaCompletionChecker.ts create mode 100644 src/utils/types.ts diff --git a/AnonymousUserEventTracking.md b/AnonymousUserEventTracking.md new file mode 100644 index 00000000..72a99b2f --- /dev/null +++ b/AnonymousUserEventTracking.md @@ -0,0 +1,138 @@ +# Anonymous User Event Tracking Iterable's Web SDK +The Anonymous User Tracking Module is a pivotal component within our WEB SDK that seamlessly captures user events while maintaining anonymity for non-logged-in users. This module is designed to diligently gather and store events triggered by users who have not yet signed in. Once specific criteria are met or when the user decides to engage further, the module securely synchronizes all accumulated anonymous events with our servers. + +By adopting a privacy-first approach, the module allows us to gain valuable insights into user interactions without compromising their personal information. As users transition from anonymous to logged-in status, the module smoothly transitions as well, associating future events with the user's unique identity while ensuring the continuity of event tracking. + +Key Features: + +- Anonymous Event Tracking: Captures a diverse range of user events even before they log in, ensuring a comprehensive understanding of user behavior. +Privacy Protection: Safeguards user anonymity by collecting and storing events without requiring personal information. +- Event Synchronization: Upon meeting predefined conditions, securely transmits all anonymous events to the server, enabling data-driven decision-making. +- Seamless User Transition: Effortlessly shifts from tracking anonymous events to associating events with specific users as they log in. +- Enhanced Insights: Provides a holistic view of user engagement patterns, contributing to a more informed product optimization strategy. +Implementing the Anonymous + +# Installation + +To install this SDK through NPM: + +``` +$ npm install @iterable/web-sdk +``` + +with yarn: + +``` +$ yarn add @iterable/web-sdk +``` + +or with a CDN: + +```js + +``` + +# Methods +- [`getAnonCriteria`] +- [`trackAnonEvent`] +- [`trackAnonPurchaseEvent`] +- [`trackAnonUpdateCart`] +- [`createUser`] +- [`syncEvents`] +- [`checkCriteriaCompletion`] + +# Usage + +1. `trackAnonEvent` +The 'trackAnonEvent' function within the Iterable-Web SDK empowers seamless tracking of diverse web events. Developers can enrich event data with specific metadata using the 'dataFields' attribute. This function intelligently distinguishes between logged-in and non-logged-in users, securely storing event data on the server post-login, while locally preserving data for anonymous users, ensuring comprehensive event monitoring in both scenarios. + +```ts +const eventDetails = { + ...conditionalParams, + createNewFields: true, + createdAt: (Date.now() / 1000) | 0, + dataFields: { website: { domain: 'omni.com' }, eventType: 'track' }, +}; + +await anonymousUserEventManager.trackAnonEvent(eventDetails); +``` + +2. `trackAnonPurchaseEvent` +The 'trackAnonPurchaseEvent' function in the Iterable-Web SDK enables precise tracking of purchase-related web events. Developers can seamlessly include specific details about purchased items. With an innate understanding of user authentication status, the function securely stores event data on the server post-login, while also providing localized storage for non-logged-in users, guaranteeing comprehensive event monitoring in both usage scenarios. + +```ts +const eventDetails = { + ...conditionalParams, + items: [{ name: purchaseItem, id: 'fdsafds', price: 100, quantity: 2 }], + total: 200 +} + +await anonymousUserEventManager.trackAnonPurchaseEvent(eventDetails); +``` + +3. `trackAnonUpdateCart` +The 'trackAnonUpdateCart' function in the Iterable-Web SDK empowers effortless tracking of web events related to cart updates. Developers can accurately outline details for multiple items within the cart. It seamlessly handles data, securely transmitting events to the server upon user login, while also providing local storage for event details in the absence of user login, ensuring comprehensive event tracking in all scenarios. + +```ts +const eventDetails = { + ...conditionalParams, + items: [{ name: cartItem, id: 'fdsafds', price: 100, quantity: 2 }] +} + +await anonymousUserEventManager.trackAnonUpdateCart(eventDetails); +``` + +4. `createUser` +The 'createUser' function in the Iterable-Web SDK facilitates user creation by assigning a unique user UUID. This function also supports the seamless updating of user details on the server, providing a comprehensive solution for managing user data within your application. + +```ts +await anonymousUserEventManager.createUser(uuid, process.env.API_KEY); +``` + +5. `getAnonCriteria` +The 'getAnonCriteria' function within the Iterable-Web SDK retrieves criteria from the server for matching purposes. It efficiently fetches and returns an array of criteria, providing developers with essential tools to enhance their application's functionality through data-driven decision-making. + +```ts +const criteriaList = await anonymousUserEventManager.getAnonCriteria(); +``` + +6. `checkCriteriaCompletion` +The 'checkCriteriaCompletion' function in the Iterable-Web SDK performs a local assessment of stored events to determine if they fulfill specific criteria. If any of the stored events satisfy the criteria, the function returns 'true', offering developers a reliable method to validate the completion status of predefined conditions based on accumulated event data. + +```ts +const isCriteriaCompleted = await anonymousUserEventManager.checkCriteriaCompletion(); +``` + +7. `syncEvents` +The 'syncEvents' function within the Iterable-Web SDK facilitates the seamless synchronization of locally stored events to the server while sequentially maintaining their order. This function efficiently transfers all accumulated events, clearing the local storage in the process, ensuring data consistency and integrity between the client and server-side environments. + +```ts +await anonymousUserEventManager.syncEvents(); +``` + +# Example + +```ts +const eventDetails = { + ...conditionalParams, + createNewFields: true, + createdAt: (Date.now() / 1000) | 0, + userId: loggedInUser, + dataFields: { website: { domain: 'omni.com' }, eventType: 'track' }, + deviceInfo: { + appPackageName: 'my-website' + } +}; + +await anonymousUserEventManager.trackAnonEvent(eventDetails); +const isCriteriaCompleted = await anonymousUserEventManager.checkCriteriaCompletion(); + +if (isCriteriaCompleted) { + const userId = uuidv4(); + const App = await initialize(process.env.API_KEY); + await App.setUserID(userId); + await anonymousUserEventManager.createUser(userId, process.env.API_KEY); + setLoggedInUser({ type: 'user_update', data: userId }); + await anonymousUserEventManager.syncEvents(); +} +``` diff --git a/package.json b/package.json index 9425df05..dab225c7 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "axios": "^1.6.2", "buffer": "^6.0.3", "idb-keyval": "^6.2.0", + "moment": "^2.29.4", "throttle-debounce": "^3.0.1", "yup": "^0.32.9" }, @@ -66,6 +67,7 @@ "@types/jest": "^27.0.2", "@types/node": "^12.7.1", "@types/throttle-debounce": "^2.1.0", + "@types/uuid": "^9.0.8", "@typescript-eslint/eslint-plugin": "^5.38.1", "@typescript-eslint/parser": "^5.38.1", "@webpack-cli/serve": "^1.6.0", @@ -103,4 +105,4 @@ "eslint" ] } -} \ No newline at end of file +} diff --git a/react-example/package.json b/react-example/package.json index dd4aa891..4a772cb9 100644 --- a/react-example/package.json +++ b/react-example/package.json @@ -63,9 +63,12 @@ "webpack-dev-server": "^4.7.3" }, "dependencies": { + "@iterable/web-sdk": "../", + "@types/uuid": "^9.0.2", "react": "^17.0.2", "react-dom": "^17.0.2", "react-router-dom": "^6.2.1", - "styled-components": "^5.3.3" + "styled-components": "^5.3.3", + "uuid": "^9.0.0" } } diff --git a/react-example/src/components/EventsForm.tsx b/react-example/src/components/EventsForm.tsx index a41812ff..b66aa697 100644 --- a/react-example/src/components/EventsForm.tsx +++ b/react-example/src/components/EventsForm.tsx @@ -1,4 +1,5 @@ import { FC, FormEvent, useState } from 'react'; +import { v4 as uuidv4 } from 'uuid'; import { Button, EndpointWrapper, @@ -6,8 +7,13 @@ import { Heading, Response } from '../views/Components.styled'; -import { IterablePromise, IterableResponse } from '@iterable/web-sdk'; +import { + initialize, + IterablePromise, + IterableResponse +} from '@iterable/web-sdk'; import TextField from 'src/components/TextField'; +import { useUser } from 'src/context/Users'; interface Props { endpointName: string; @@ -22,6 +28,7 @@ export const EventsForm: FC = ({ heading, needsEventName }) => { + const { loggedInUser, setLoggedInUser } = useUser(); const [trackResponse, setTrackResponse] = useState( 'Endpoint JSON goes here' ); @@ -30,27 +37,33 @@ export const EventsForm: FC = ({ const [isTrackingEvent, setTrackingEvent] = useState(false); - const handleTrack = (e: FormEvent) => { + const handleTrack = async (e: FormEvent) => { e.preventDefault(); setTrackingEvent(true); const conditionalParams = needsEventName ? { eventName: trackEvent } : { messageId: trackEvent }; - method({ - ...conditionalParams, - deviceInfo: { - appPackageName: 'my-website' - } - }) - .then((response) => { - setTrackResponse(JSON.stringify(response.data)); - setTrackingEvent(false); + + try { + method({ + ...conditionalParams, + deviceInfo: { + appPackageName: 'my-website' + } }) - .catch((e) => { - setTrackResponse(JSON.stringify(e.response.data)); - setTrackingEvent(false); - }); + .then((response) => { + setTrackResponse(JSON.stringify(response.data)); + setTrackingEvent(false); + }) + .catch((e) => { + setTrackResponse(JSON.stringify(e.response.data)); + setTrackingEvent(false); + }); + } catch (error) { + setTrackResponse(JSON.stringify(error.message)); + setTrackingEvent(false); + } }; const formAttr = { [`data-qa-${endpointName}-submit`]: true }; diff --git a/react-example/src/index.tsx b/react-example/src/index.tsx index 17b9eca3..c6b75605 100644 --- a/react-example/src/index.tsx +++ b/react-example/src/index.tsx @@ -14,6 +14,7 @@ import styled from 'styled-components'; import LoginForm from 'src/components/LoginForm'; import { UserProvider } from 'src/context/Users'; +import { setAnonTracking } from '@iterable/web-sdk'; const Wrapper = styled.div` display: flex; @@ -60,6 +61,7 @@ const HomeLink = styled(Link)` }); } ); + setAnonTracking(true); ReactDOM.render( diff --git a/react-example/src/views/Commerce.tsx b/react-example/src/views/Commerce.tsx index 968cf5f4..c51bc565 100644 --- a/react-example/src/views/Commerce.tsx +++ b/react-example/src/views/Commerce.tsx @@ -29,34 +29,44 @@ export const Commerce: FC = () => { const handleUpdateCart = (e: FormEvent) => { e.preventDefault(); setUpdatingCart(true); - updateCart({ - items: [{ name: cartItem, id: 'fdsafds', price: 100, quantity: 2 }] - }) - .then((response) => { - setUpdateCartResponse(JSON.stringify(response.data)); - setUpdatingCart(false); + try { + updateCart({ + items: [{ name: cartItem, id: 'fdsafds', price: 100, quantity: 2 }] }) - .catch((e) => { - setUpdateCartResponse(JSON.stringify(e.response.data)); - setUpdatingCart(false); - }); + .then((response) => { + setUpdateCartResponse(JSON.stringify(response.data)); + setUpdatingCart(false); + }) + .catch((e) => { + setUpdateCartResponse(JSON.stringify(e.response.data)); + setUpdatingCart(false); + }); + } catch (error) { + setUpdateCartResponse(JSON.stringify(error.message)); + setUpdatingCart(false); + } }; const handleTrackPurchase = (e: FormEvent) => { e.preventDefault(); setTrackingPurchase(true); - trackPurchase({ - items: [{ name: purchaseItem, id: 'fdsafds', price: 100, quantity: 2 }], - total: 200 - }) - .then((response) => { - setTrackingPurchase(false); - setTrackPurchaseResponse(JSON.stringify(response.data)); + try { + trackPurchase({ + items: [{ name: purchaseItem, id: 'fdsafds', price: 100, quantity: 2 }], + total: 200 }) - .catch((e) => { - setTrackingPurchase(false); - setTrackPurchaseResponse(JSON.stringify(e.response.data)); - }); + .then((response) => { + setTrackingPurchase(false); + setTrackPurchaseResponse(JSON.stringify(response.data)); + }) + .catch((e) => { + setTrackingPurchase(false); + setTrackPurchaseResponse(JSON.stringify(e.response.data)); + }); + } catch (error) { + setTrackingPurchase(false); + setTrackPurchaseResponse(JSON.stringify(error.message)); + } }; return ( diff --git a/react-example/src/views/Users.tsx b/react-example/src/views/Users.tsx index d4b7992a..88bcc14a 100644 --- a/react-example/src/views/Users.tsx +++ b/react-example/src/views/Users.tsx @@ -40,17 +40,22 @@ export const Users: FC = () => { const handleUpdateUser = (e: FormEvent) => { e.preventDefault(); setUpdatingUser(true); - updateUser({ - dataFields: { [userDataField]: 'test-data' } - }) - .then((response) => { - setUpdateUserResponse(JSON.stringify(response.data)); - setUpdatingUser(false); + try { + updateUser({ + dataFields: { [userDataField]: 'test-data' } }) - .catch((e) => { - setUpdateUserResponse(JSON.stringify(e.response.data)); - setUpdatingUser(false); - }); + .then((response) => { + setUpdateUserResponse(JSON.stringify(response.data)); + setUpdatingUser(false); + }) + .catch((e) => { + setUpdateUserResponse(JSON.stringify(e.response.data)); + setUpdatingUser(false); + }); + } catch (error) { + setUpdateUserResponse(JSON.stringify(error.message)); + setUpdatingUser(false); + } }; const handleUpdateUserEmail = (e: FormEvent) => { diff --git a/react-example/yarn.lock b/react-example/yarn.lock index 7d3b9868..1b4be3dd 100644 --- a/react-example/yarn.lock +++ b/react-example/yarn.lock @@ -499,6 +499,13 @@ "@babel/helper-validator-option" "^7.14.5" "@babel/plugin-transform-typescript" "^7.16.0" +"@babel/runtime@^7.15.4": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.9.tgz#47791a15e4603bb5f905bc0753801cf21d6345f7" + integrity sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/runtime@^7.7.6": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.7.tgz#03ff99f64106588c9c403c6ecb8c3bafbbdff1fa" @@ -680,6 +687,17 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== +"@iterable/web-sdk@../": + version "1.0.7" + dependencies: + "@pabra/sortby" "^1.0.1" + axios "^1.6.2" + buffer "^6.0.3" + idb-keyval "^6.2.0" + moment "^2.29.4" + throttle-debounce "^3.0.1" + yup "^0.32.9" + "@jest/console@^27.3.1": version "27.3.1" resolved "https://registry.yarnpkg.com/@jest/console/-/console-27.3.1.tgz#e8ea3a475d3f8162f23d69efbfaa9cbe486bee93" @@ -910,6 +928,11 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@pabra/sortby@^1.0.1": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@pabra/sortby/-/sortby-1.0.2.tgz#9ef674da9e5048bbe6a08217ce0dede5411cdfc9" + integrity sha512-gT4DWbGLlkctE5TwRT6/gZzHiVil1Ywg7FF5OmIilZbGCZqdYpbz98L2bqfhglwK3kj4fBZRMSCtzrjQ6J8jAw== + "@sinonjs/commons@^1.7.0": version "1.8.3" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" @@ -1096,6 +1119,11 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== +"@types/lodash@^4.14.175": + version "4.14.202" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.202.tgz#f09dbd2fb082d507178b2f2a5c7e74bd72ff98f8" + integrity sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ== + "@types/mime@^1": version "1.3.2" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" @@ -1203,6 +1231,11 @@ "@types/react" "*" csstype "^3.0.2" +"@types/uuid@^9.0.2": + version "9.0.8" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.8.tgz#7545ba4fc3c003d6c756f651f3bf163d8f0f29ba" + integrity sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA== + "@types/ws@^8.2.2": version "8.2.2" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.2.2.tgz#7c5be4decb19500ae6b3d563043cd407bf366c21" @@ -1685,6 +1718,15 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== +axios@^1.6.2: + version "1.6.7" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.7.tgz#7b48c2e27c96f9c68a2f8f31e2ab19f59b06b0a7" + integrity sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA== + dependencies: + follow-redirects "^1.15.4" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + babel-jest@^27.3.1: version "27.3.1" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-27.3.1.tgz#0636a3404c68e07001e434ac4956d82da8a80022" @@ -1914,6 +1956,14 @@ buffer@^5.6.0: base64-js "^1.3.1" ieee754 "^1.1.13" +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + bytes@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" @@ -3198,6 +3248,11 @@ follow-redirects@^1.0.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7" integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w== +follow-redirects@^1.15.4: + version "1.15.5" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.5.tgz#54d4d6d062c0fa7d9d17feb008461550e3ba8020" + integrity sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw== + forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" @@ -3212,6 +3267,15 @@ form-data@^3.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" @@ -3618,7 +3682,12 @@ icss-utils@^5.0.0, icss-utils@^5.1.0: resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== -ieee754@^1.1.13: +idb-keyval@^6.2.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-6.2.1.tgz#94516d625346d16f56f3b33855da11bfded2db33" + integrity sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg== + +ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -4582,6 +4651,11 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" +lodash-es@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" + integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== + lodash.memoize@4.x: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" @@ -4758,6 +4832,11 @@ mkdirp@^0.5.5: dependencies: minimist "^1.2.5" +moment@^2.29.4: + version "2.30.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae" + integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -4791,6 +4870,11 @@ multicast-dns@^6.0.1: dns-packet "^1.3.1" thunky "^1.0.2" +nanoclone@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/nanoclone/-/nanoclone-0.2.1.tgz#dd4090f8f1a110d26bb32c49ed2f5b9235209ed4" + integrity sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA== + nanoid@^3.1.30: version "3.2.0" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.2.0.tgz#62667522da6673971cca916a6d3eff3f415ff80c" @@ -5280,6 +5364,11 @@ prop-types@^15.7.2: object-assign "^4.1.1" react-is "^16.13.1" +property-expr@^2.0.4: + version "2.0.6" + resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.6.tgz#f77bc00d5928a6c748414ad12882e83f24aec1e8" + integrity sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA== + proxy-addr@~2.0.5: version "2.0.7" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" @@ -5293,6 +5382,11 @@ proxy-from-env@1.0.0: resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee" integrity sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4= +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + psl@^1.1.28, psl@^1.1.33: version "1.8.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" @@ -5441,6 +5535,11 @@ regenerator-runtime@^0.13.4: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== +regenerator-runtime@^0.14.0: + version "0.14.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" + integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== + regexp.prototype.flags@^1.2.0: version "1.3.1" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz#7ef352ae8d159e758c0eadca6f8fcb4eef07be26" @@ -6105,6 +6204,11 @@ throat@^6.0.1: resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375" integrity sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w== +throttle-debounce@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-3.0.1.tgz#32f94d84dfa894f786c9a1f290e7a645b6a19abb" + integrity sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg== + throttleit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.0.tgz#9e785836daf46743145a5984b6268d828528ac6c" @@ -6149,6 +6253,11 @@ toidentifier@1.0.0: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== +toposort@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330" + integrity sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg== + tough-cookie@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4" @@ -6345,6 +6454,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" @@ -6677,3 +6791,16 @@ yauzl@^2.10.0: dependencies: buffer-crc32 "~0.2.3" fd-slicer "~1.1.0" + +yup@^0.32.9: + version "0.32.11" + resolved "https://registry.yarnpkg.com/yup/-/yup-0.32.11.tgz#d67fb83eefa4698607982e63f7ca4c5ed3cf18c5" + integrity sha512-Z2Fe1bn+eLstG8DRR6FTavGD+MeAwyfmouhHsIUgaADz8jvFKbO/fXc2trJKZg+5EBjh4gGm3iU/t3onKlXHIg== + dependencies: + "@babel/runtime" "^7.15.4" + "@types/lodash" "^4.14.175" + lodash "^4.17.21" + lodash-es "^4.17.21" + nanoclone "^0.2.1" + property-expr "^2.0.4" + toposort "^2.0.2" diff --git a/src/authorization/authorization.test.ts b/src/authorization/authorization.test.ts index 3817ea80..75845bf0 100644 --- a/src/authorization/authorization.test.ts +++ b/src/authorization/authorization.test.ts @@ -9,6 +9,27 @@ import { trackPurchase, updateCart } from '../commerce'; import { GETMESSAGES_PATH } from '../constants'; let mockRequest: any = null; + +jest.mock('../utils/anonymousUserEventManager', () => { + return { + AnonymousUserEventManager: jest.fn().mockImplementation(() => ({ + trackAnonUpdateCart: jest.fn(), + trackAnonPurchaseEvent: jest.fn(), + trackAnonEvent: jest.fn(), + trackAnonUpdateUser: jest.fn() + })) + }; +}); + +jest.mock('../utils/anonymousUserMerge', () => { + return { + AnonymousUserMerge: jest.fn().mockImplementation(() => ({ + mergeUserUsingUserId: jest.fn(), + mergeUserUsingEmail: jest.fn() + })) + }; +}); + /* decoded payload is: @@ -33,6 +54,7 @@ describe('API Key Interceptors', () => { }); beforeEach(() => { + jest.clearAllMocks(); mockRequest.onPost('/users/update').reply(200, { data: 'something' }); @@ -187,7 +209,7 @@ describe('API Key Interceptors', () => { try { await setEmail('hello@gmail.com'); - await updateUser(); + await updateUser({ email: 'hello@gmail.com' }); } catch (e) { expect(mockGenerateJW).toHaveBeenCalledTimes(2); } @@ -435,8 +457,11 @@ describe('User Identification', () => { deviceInfo: { appPackageName: 'my-lil-website' } }); const subsResponse = await updateSubscriptions(); - const userResponse = await updateUser(); - const trackResponse = await track({ eventName: 'fdsafdf' }); + const userResponse = await updateUser({ email: 'hello@gmail.com' }); + const trackResponse = await track({ + eventName: 'fdsafdf', + email: 'hello@gmail.com' + }); expect(JSON.parse(closeResponse.config.data).email).toBe( 'hello@gmail.com' @@ -478,8 +503,19 @@ describe('User Identification', () => { data: 'something' }); - const cartResponse = await updateCart({ items: [] }); - const trackResponse = await trackPurchase({ items: [], total: 100 }); + const cartResponse = await updateCart({ + items: [], + user: { + email: 'hello@gmail.com' + } + }); + const trackResponse = await trackPurchase({ + items: [], + total: 100, + user: { + email: 'hello@gmail.com' + } + }); expect(JSON.parse(cartResponse.config.data).user.email).toBe( 'hello@gmail.com' ); @@ -590,8 +626,11 @@ describe('User Identification', () => { deviceInfo: { appPackageName: 'my-lil-website' } }); const subsResponse = await updateSubscriptions(); - const userResponse = await updateUser(); - const trackResponse = await track({ eventName: 'fdsafdf' }); + const userResponse = await updateUser({ userId: '999' }); + const trackResponse = await track({ + eventName: 'fdsafdf', + userId: '999' + }); expect(JSON.parse(closeResponse.config.data).userId).toBe('999'); expect(JSON.parse(subsResponse.config.data).userId).toBe('999'); @@ -622,8 +661,19 @@ describe('User Identification', () => { data: 'something' }); - const cartResponse = await updateCart({ items: [] }); - const trackResponse = await trackPurchase({ items: [], total: 100 }); + const cartResponse = await updateCart({ + items: [], + user: { + userId: '999' + } + }); + const trackResponse = await trackPurchase({ + items: [], + total: 100, + user: { + userId: '999' + } + }); expect(JSON.parse(cartResponse.config.data).user.userId).toBe('999'); expect(JSON.parse(trackResponse.config.data).user.userId).toBe('999'); }); @@ -778,8 +828,11 @@ describe('User Identification', () => { deviceInfo: { appPackageName: 'my-lil-website' } }); const subsResponse = await updateSubscriptions(); - const userResponse = await updateUser(); - const trackResponse = await track({ eventName: 'fdsafdf' }); + const userResponse = await updateUser({ email: 'hello@gmail.com' }); + const trackResponse = await track({ + eventName: 'fdsafdf', + email: 'hello@gmail.com' + }); expect(JSON.parse(closeResponse.config.data).email).toBe( 'hello@gmail.com' @@ -825,8 +878,19 @@ describe('User Identification', () => { data: 'something' }); - const cartResponse = await updateCart({ items: [] }); - const trackResponse = await trackPurchase({ items: [], total: 100 }); + const cartResponse = await updateCart({ + items: [], + user: { + email: 'hello@gmail.com' + } + }); + const trackResponse = await trackPurchase({ + items: [], + total: 100, + user: { + email: 'hello@gmail.com' + } + }); expect(JSON.parse(cartResponse.config.data).user.email).toBe( 'hello@gmail.com' ); @@ -949,8 +1013,11 @@ describe('User Identification', () => { deviceInfo: { appPackageName: 'my-lil-website' } }); const subsResponse = await updateSubscriptions(); - const userResponse = await updateUser(); - const trackResponse = await track({ eventName: 'fdsafdf' }); + const userResponse = await updateUser({ userId: '999' }); + const trackResponse = await track({ + eventName: 'fdsafdf', + userId: '999' + }); expect(JSON.parse(closeResponse.config.data).userId).toBe('999'); expect(JSON.parse(subsResponse.config.data).userId).toBe('999'); @@ -985,8 +1052,19 @@ describe('User Identification', () => { data: 'something' }); - const cartResponse = await updateCart({ items: [] }); - const trackResponse = await trackPurchase({ items: [], total: 100 }); + const cartResponse = await updateCart({ + items: [], + user: { + userId: '999' + } + }); + const trackResponse = await trackPurchase({ + items: [], + total: 100, + user: { + userId: '999' + } + }); expect(JSON.parse(cartResponse.config.data).user.userId).toBe('999'); expect(JSON.parse(trackResponse.config.data).user.userId).toBe('999'); }); diff --git a/src/authorization/authorization.ts b/src/authorization/authorization.ts index f8bbffb7..b6c1dd7c 100644 --- a/src/authorization/authorization.ts +++ b/src/authorization/authorization.ts @@ -5,7 +5,9 @@ import { clearMessages } from 'src/inapp'; import { IS_PRODUCTION, RETRY_USER_ATTEMPTS, - STATIC_HEADERS + STATIC_HEADERS, + SHARED_PREF_USER_ID, + SHARED_PREF_EMAIL } from 'src/constants'; import { cancelAxiosRequestAndMakeFetch, @@ -17,8 +19,26 @@ import { isEmail } from './utils'; import { config } from '../utils/config'; +import { AnonymousUserMerge } from 'src/utils/anonymousUserMerge'; +import { AnonymousUserEventManager } from '../utils/anonymousUserEventManager'; const MAX_TIMEOUT = ONE_DAY; +/* + AKA did the user auth with their email (setEmail) or user ID (setUserID) + + we're going to use this variable for one circumstance - when calling _updateUserEmail_. + Essentially, when we call the Iterable API to update a user's email address and we get a + successful 200 request, we're going to request a new JWT token, since it might need to + be re-signed with the new email address; however, if the customer code never authorized the + user with an email and instead a user ID, we'll just continue to sign the JWT with the user ID. + + This is mainly just a quality-of-life feature, so that the customer's JWT generation code + doesn't _need_ to support email-signed JWTs if they don't want and purely want to issue the + tokens by user ID. + */ +let typeOfAuth: null | 'email' | 'userID' = null; +/* this will be the literal user ID or email they choose to auth with */ +let authIdentifier: null | string = null; export interface GenerateJWTPayload { email?: string; @@ -41,6 +61,56 @@ export interface WithoutJWT { logout: () => void; } +export function setUserID(userId: string) { + if ( + userId !== null && + userId !== '' && + config.getConfig('enableAnonTracking') + ) { + const anonymousUserMerge = new AnonymousUserMerge(); + anonymousUserMerge.mergeUser(userId); + } + localStorage.setItem(SHARED_PREF_USER_ID, userId); + typeOfAuth = 'userID'; + authIdentifier = userId; +} + +export function setEmail(email: string) { + if ( + email !== null && + email !== '' && + config.getConfig('enableAnonTracking') + ) { + const anonymousUserMerge = new AnonymousUserMerge(); + anonymousUserMerge.mergeUser(email); + } + localStorage.setItem(SHARED_PREF_EMAIL, email); + typeOfAuth = 'email'; + authIdentifier = email; +} + +export const getUserID = (): string | null => { + return localStorage.getItem(SHARED_PREF_USER_ID); +}; + +export const getEmail = (): string | null => { + return localStorage.getItem(SHARED_PREF_EMAIL); +}; + +export const setAnonTracking = (enableAnonTracking: boolean) => { + config.setConfig({ enableAnonTracking: enableAnonTracking }); + + try { + if (config.getConfig('enableAnonTracking')) { + const anonUserManager = new AnonymousUserEventManager(); + anonUserManager.getAnonCriteria(); + anonUserManager.updateAnonSession(); + } + } catch (error) { + console.warn(error); + } +}; + export function initialize( authToken: string, generateJWT: (payload: GenerateJWTPayload) => Promise @@ -74,22 +144,6 @@ export function initialize( }); let userInterceptor: number | null = null; let responseInterceptor: number | null = null; - /* - AKA did the user auth with their email (setEmail) or user ID (setUserID) - - we're going to use this variable for one circumstance - when calling _updateUserEmail_. - Essentially, when we call the Iterable API to update a user's email address and we get a - successful 200 request, we're going to request a new JWT token, since it might need to - be re-signed with the new email address; however, if the customer code never authorized the - user with an email and instead a user ID, we'll just continue to sign the JWT with the user ID. - - This is mainly just a quality-of-life feature, so that the customer's JWT generation code - doesn't _need_ to support email-signed JWTs if they don't want and purely want to issue the - tokens by user ID. - */ - let typeOfAuth: null | 'email' | 'userID' = null; - /* this will be the literal user ID or email they choose to auth with */ - let authIdentifier: null | string = null; /** method that sets a timer one minute before JWT expiration @@ -318,12 +372,12 @@ export function initialize( } ); - const tryUser = () => { + const tryUser = (userId: any) => { let createUserAttempts = 0; return async function tryUserNTimes(): Promise { try { - return await updateUser({}); + return await updateUser({ userId: userId }); } catch (e) { if (createUserAttempts < RETRY_USER_ATTEMPTS) { createUserAttempts += 1; @@ -338,7 +392,7 @@ export function initialize( }; try { - return await tryUser()(); + return await tryUser(userId)(); } catch (e) { /* failed to create a new user. Just silently resolve */ return Promise.resolve(); @@ -607,6 +661,7 @@ export function initialize( return Promise.reject(error); }); }; + return { clearRefresh: () => { /* this will just clear the existing timeout */ @@ -708,12 +763,12 @@ export function initialize( return config; }); - const tryUser = () => { + const tryUser = (userID: any) => { let createUserAttempts = 0; return async function tryUserNTimes(): Promise { try { - return await updateUser({}); + return await updateUser({ userId: userID }); } catch (e) { if (createUserAttempts < RETRY_USER_ATTEMPTS) { createUserAttempts += 1; @@ -729,7 +784,7 @@ export function initialize( return doRequest({ userID: userId }) .then(async (token) => { - await tryUser()(); + await tryUser(userId)(); return token; }) .catch((e) => { diff --git a/src/commerce/commerce.test.ts b/src/commerce/commerce.test.ts index 24645400..fa70d5ac 100644 --- a/src/commerce/commerce.test.ts +++ b/src/commerce/commerce.test.ts @@ -1,13 +1,48 @@ import MockAdapter from 'axios-mock-adapter'; import { baseAxiosRequest } from '../request'; import { trackPurchase, updateCart } from './commerce'; -// import { SDK_VERSION, WEB_PLATFORM } from '../constants'; import { createClientError } from '../utils/testUtils'; +import { config } from '../utils/config'; const mockRequest = new MockAdapter(baseAxiosRequest); +jest.mock('../utils/anonymousUserEventManager', () => { + return { + AnonymousUserEventManager: jest.fn().mockImplementation(() => ({ + trackAnonUpdateCart: jest.fn(), + trackAnonPurchaseEvent: jest.fn() + })) + }; +}); + describe('Users Requests', () => { - it('should set params and return the correct payload for updateCart', async () => { + beforeEach(() => { + jest.clearAllMocks(); + config.setConfig({ enableAnonTracking: true }); + }); + + it('should throw an error if updateCart payload is empty', () => { + expect(() => { + updateCart({} as any); + }).toThrow(); + }); + + it('should throw an error if updateCart userId is empty', () => { + expect(() => { + updateCart({ + items: [ + { + id: 'fdsafds', + name: 'banana', + quantity: 2, + price: 12 + } + ] + }); + }).toThrow(); + }); + + it('return the correct payload for updateCart with userId', async () => { mockRequest.onPost('/commerce/updateCart').reply(200, { msg: 'hello' }); @@ -20,9 +55,33 @@ describe('Users Requests', () => { quantity: 2, price: 12 } - ] + ], + user: { + userId: 'user' + } + }); + expect(response.data.msg).toBe('hello'); + }); + + it('return the correct payload for updateCart with email', async () => { + mockRequest.onPost('/commerce/updateCart').reply(200, { + msg: 'hello' }); + const response = await updateCart({ + items: [ + { + id: 'fdsafds', + name: 'banana', + quantity: 2, + price: 12 + } + ], + user: { + email: 'user@example.com' + } + }); + expect(response.data.msg).toBe('hello'); expect(JSON.parse(response.config.data).items).toEqual([ { id: 'fdsafds', @@ -32,15 +91,15 @@ describe('Users Requests', () => { } ]); expect(JSON.parse(response.config.data).user.preferUserId).toBe(true); - // expect(response.config.headers['SDK-Version']).toBe(SDK_VERSION); - // expect(response.config.headers['SDK-Platform']).toBe(WEB_PLATFORM); - expect(response.data.msg).toBe('hello'); }); it('should reject updateCart on bad params', async () => { try { await updateCart({ - items: [{} as any] + items: [{} as any], + user: { + userId: 'user' + } }); } catch (e) { expect(e).toEqual( @@ -66,56 +125,92 @@ describe('Users Requests', () => { } }); - it('should set params and return the correct payload for trackPurchase', async () => { + it('should throw an error if trackPurchase payload is empty', () => { + expect(() => { + trackPurchase({} as any); + }).toThrow(); + }); + + it('should throw an error if trackPurchase userId is empty', () => { + expect(() => { + trackPurchase({ + items: [], + total: 100 + }); + }).toThrow(); + }); + + it('return the correct payload for trackPurchase with userId', async () => { mockRequest.onPost('/commerce/trackPurchase').reply(200, { msg: 'hello' }); const response = await trackPurchase({ - items: [], - total: 100 + items: [ + { + id: 'fdsafds', + name: 'banana', + quantity: 2, + price: 12 + } + ], + user: { + userId: 'user' + }, + total: 24 }); - - expect(JSON.parse(response.config.data).total).toBe(100); - expect(JSON.parse(response.config.data).items).toEqual([]); - expect(JSON.parse(response.config.data).user.preferUserId).toBe(true); expect(response.data.msg).toBe('hello'); + expect(JSON.parse(response.config.data).total).toBe(24); + expect(JSON.parse(response.config.data).items).toEqual([ + { + id: 'fdsafds', + name: 'banana', + quantity: 2, + price: 12 + } + ]); + expect(JSON.parse(response.config.data).user.preferUserId).toBe(true); }); - it('should not allow a passed userId or email for API methods', async () => { + it('return the correct payload for trackPurchase with email', async () => { mockRequest.onPost('/commerce/trackPurchase').reply(200, { msg: 'hello' }); - mockRequest.onPost('/commerce/updateCart').reply(200, { - msg: 'hello' - }); - const updateResponse = await updateCart({ - user: { - email: 'hello@gmail.com', - userId: '1234' - }, - items: [] - } as any); - const trackResponse = await trackPurchase({ + const response = await trackPurchase({ + items: [ + { + id: 'fdsafds', + name: 'banana', + quantity: 2, + price: 12 + } + ], user: { - email: 'hello@gmail.com', - userId: '1234' + email: 'user@example.com' }, - items: [], - total: 100 - } as any); - - expect(JSON.parse(updateResponse.config.data).user.email).toBeUndefined(); - expect(JSON.parse(updateResponse.config.data).user.userId).toBeUndefined(); - expect(JSON.parse(trackResponse.config.data).user.email).toBeUndefined(); - expect(JSON.parse(trackResponse.config.data).user.userId).toBeUndefined(); + total: 24 + }); + expect(response.data.msg).toBe('hello'); + expect(JSON.parse(response.config.data).total).toBe(24); + expect(JSON.parse(response.config.data).items).toEqual([ + { + id: 'fdsafds', + name: 'banana', + quantity: 2, + price: 12 + } + ]); + expect(JSON.parse(response.config.data).user.preferUserId).toBe(true); }); - it('should reject updateCart on bad params', async () => { + it('should reject trackPurchase on bad params', async () => { try { await trackPurchase({ - items: [{} as any] + items: [{} as any], + user: { + userId: 'user' + } } as any); } catch (e) { expect(e).toEqual( diff --git a/src/commerce/commerce.ts b/src/commerce/commerce.ts index e58922b4..b04e9a45 100644 --- a/src/commerce/commerce.ts +++ b/src/commerce/commerce.ts @@ -2,14 +2,33 @@ import { baseIterableRequest } from '../request'; import { TrackPurchaseRequestParams, UpdateCartRequestParams } from './types'; import { IterableResponse } from '../types'; import { updateCartSchema, trackPurchaseSchema } from './commerce.schema'; +import { AnonymousUserEventManager } from '../utils/anonymousUserEventManager'; +import config from '../utils/config'; +import { SHARED_PREF_EMAIL, SHARED_PREF_USER_ID } from 'src/constants'; -export const updateCart = (payload: UpdateCartRequestParams) => { - /* a customer could potentially send these up if they're not using TypeScript */ - if (payload.user) { - delete (payload as any).user.userId; - delete (payload as any).user.email; +const canTrackAnonUser = (payload: any): boolean => { + if ( + (!(SHARED_PREF_USER_ID in (payload.user ?? {})) || + payload.user?.userId === null || + typeof payload.user?.userId === 'undefined') && + (!(SHARED_PREF_EMAIL in (payload.user ?? {})) || + payload.user?.email === null || + typeof payload.user?.email === 'undefined') && + config.getConfig('enableAnonTracking') + ) { + return true; } + return false; +}; +export const updateCart = (payload: UpdateCartRequestParams) => { + if (canTrackAnonUser(payload)) { + const anonymousUserEventManager = new AnonymousUserEventManager(); + anonymousUserEventManager.trackAnonUpdateCart(payload); + const errorMessage = + 'Iterable SDK must be initialized with an API key and user email/userId before calling SDK methods'; + throw new Error(errorMessage); + } return baseIterableRequest({ method: 'POST', url: '/commerce/updateCart', @@ -27,12 +46,13 @@ export const updateCart = (payload: UpdateCartRequestParams) => { }; export const trackPurchase = (payload: TrackPurchaseRequestParams) => { - /* a customer could potentially send these up if they're not using TypeScript */ - if (payload.user) { - delete (payload as any).user.userId; - delete (payload as any).user.email; + if (canTrackAnonUser(payload)) { + const anonymousUserEventManager = new AnonymousUserEventManager(); + anonymousUserEventManager.trackAnonPurchaseEvent(payload); + const errorMessage = + 'Iterable SDK must be initialized with an API key and user email/userId before calling SDK methods'; + throw new Error(errorMessage); } - return baseIterableRequest({ method: 'POST', url: '/commerce/trackPurchase', diff --git a/src/commerce/types.ts b/src/commerce/types.ts index e5505e3b..b0994e32 100644 --- a/src/commerce/types.ts +++ b/src/commerce/types.ts @@ -12,6 +12,8 @@ interface CommerceItem { } interface CommerceUser { + userId?: string; + email?: string; dataFields?: Record; preferUserId?: boolean; mergeNestedObjects?: boolean; diff --git a/src/constants.ts b/src/constants.ts index 95ad626f..9782d107 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -19,6 +19,11 @@ const ITERABLE_API_URL = `https://${ export const BASE_URL = process.env.BASE_URL || ITERABLE_API_URL; export const GETMESSAGES_PATH = '/inApp/web/getMessages'; +export const GET_CRITERIA_PATH = '/anonymoususer/list'; +export const ENDPOINT_GET_USER_BY_USERID = 'users/byUserId'; +export const ENDPOINT_GET_USER_BY_EMAIL = 'users/getByEmail'; +export const ENDPOINT_MERGE_USER = 'users/merge'; +export const ENDPOINT_TRACK_ANON_SESSION = 'anonymoususer/events/session'; /** @todo update once new endpoint is ready */ export const CACHE_ENABLED_GETMESSAGES_PATH = '/newEndpoint'; @@ -149,3 +154,23 @@ export const ANIMATION_STYLESHEET = ( transition: visibility 0s ${animationDuration}ms, opacity ${animationDuration}ms linear; } `; + +export const SHARED_PREFS_EVENT_TYPE = 'eventType'; +export const SHARED_PREFS_EVENT_LIST_KEY = 'itbl_event_list'; +export const SHARED_PREFS_CRITERIA = 'criteria'; +export const SHARED_PREFS_ANON_SESSIONS = 'itbl_anon_sessions'; +export const SHARED_PREF_USER_ID = 'userId'; +export const SHARED_PREF_EMAIL = 'email'; + +export const KEY_EVENT_NAME = 'eventName'; +export const KEY_CREATED_AT = 'createdAt'; +export const KEY_DATA_FIELDS = 'dataFields'; +export const KEY_CREATE_NEW_FIELDS = 'createNewFields'; +export const KEY_ITEMS = 'items'; +export const KEY_TOTAL = 'total'; +export const DATA_REPLACE = 'dataReplace'; + +export const TRACK_EVENT = 'customEvent'; +export const TRACK_PURCHASE = 'purchase'; +export const UPDATE_USER = 'updateUser'; +export const TRACK_UPDATE_CART = 'cartUpdate'; diff --git a/src/events/events.test.ts b/src/events/events.test.ts index 057d0579..34604bb9 100644 --- a/src/events/events.test.ts +++ b/src/events/events.test.ts @@ -10,12 +10,25 @@ import { } from './events'; import { WEB_PLATFORM } from '../constants'; import { createClientError } from '../utils/testUtils'; +import { config } from '../utils/config'; const mockRequest = new MockAdapter(baseAxiosRequest); +jest.mock('../utils/anonymousUserEventManager', () => { + return jest.fn().mockImplementation(() => ({ + trackAnonEvent: jest.fn() + })); +}); + describe('Events Requests', () => { + beforeEach(() => { + jest.clearAllMocks(); + config.setConfig({ enableAnonTracking: true }); + }); + beforeAll(() => { mockRequest.onPost('/events/track').reply(200, { + userId: 'user', msg: 'hello' }); mockRequest.onPost('/events/trackInAppClick').reply(200, { @@ -35,19 +48,35 @@ describe('Events Requests', () => { }); }); - it('return the correct payload for track', async () => { - const response = await track({ eventName: 'test' }); + it('should throw an error if payload is empty', () => { + expect(() => { + track({} as any); + }).toThrow(); + }); + + it('should throw an error if payload is empty', () => { + expect(() => { + track({ eventName: 'test' }); + }).toThrow(); + }); + + it('return the correct payload for track with userId', async () => { + const response = await track({ userId: 'user', eventName: 'test' }); + expect(JSON.parse(response.config.data).eventName).toBe('test'); + }); + it('return the correct payload for track with email', async () => { + const response = await track({ + email: 'user@email.com', + eventName: 'test' + }); expect(JSON.parse(response.config.data).eventName).toBe('test'); - // expect(response.config.headers['SDK-Version']).toBe(SDK_VERSION); - // expect(response.config.headers['SDK-Platform']).toBe(WEB_PLATFORM); - expect(response.data.msg).toBe('hello'); }); it('should reject track on bad params', async () => { try { - await track({} as any); - } catch (e) { + await track({ userId: 'user' } as any); + } catch (e: any) { expect(e).toEqual( createClientError([ { @@ -73,8 +102,6 @@ describe('Events Requests', () => { WEB_PLATFORM ); expect(response.data.msg).toBe('hello'); - // expect(response.config.headers['SDK-Version']).toBe(SDK_VERSION); - // expect(response.config.headers['SDK-Platform']).toBe(WEB_PLATFORM); }); it('should reject trackInAppClick on bad params', async () => { @@ -110,8 +137,6 @@ describe('Events Requests', () => { WEB_PLATFORM ); expect(response.data.msg).toBe('hello'); - // expect(response.config.headers['SDK-Version']).toBe(SDK_VERSION); - // expect(response.config.headers['SDK-Platform']).toBe(WEB_PLATFORM); }); it('should reject trackInAppClose on bad params', async () => { @@ -147,8 +172,6 @@ describe('Events Requests', () => { WEB_PLATFORM ); expect(response.data.msg).toBe('hello'); - // expect(response.config.headers['SDK-Version']).toBe(SDK_VERSION); - // expect(response.config.headers['SDK-Platform']).toBe(WEB_PLATFORM); }); it('should reject trackInAppConsume on bad params', async () => { @@ -184,8 +207,6 @@ describe('Events Requests', () => { WEB_PLATFORM ); expect(response.data.msg).toBe('hello'); - // expect(response.config.headers['SDK-Version']).toBe(SDK_VERSION); - // expect(response.config.headers['SDK-Platform']).toBe(WEB_PLATFORM); }); it('should reject trackInAppDelivery on bad params', async () => { @@ -221,8 +242,6 @@ describe('Events Requests', () => { WEB_PLATFORM ); expect(response.data.msg).toBe('hello'); - // expect(response.config.headers['SDK-Version']).toBe(SDK_VERSION); - // expect(response.config.headers['SDK-Platform']).toBe(WEB_PLATFORM); }); it('should reject trackInAppOpen on bad params', async () => { @@ -282,8 +301,10 @@ describe('Events Requests', () => { deviceInfo: { appPackageName: 'my-lil-site' } } as any); - expect(JSON.parse(trackResponse.config.data).email).toBeUndefined(); - expect(JSON.parse(trackResponse.config.data).userId).toBeUndefined(); + expect(JSON.parse(trackResponse.config.data).email).toEqual( + 'hello@gmail.com' + ); + expect(JSON.parse(trackResponse.config.data).userId).toEqual('1234'); expect( JSON.parse(trackResponse.config.data).deviceInfo.appPackageName ).toBe('my-lil-site'); diff --git a/src/events/events.ts b/src/events/events.ts index 2b863083..7f706c6c 100644 --- a/src/events/events.ts +++ b/src/events/events.ts @@ -3,12 +3,17 @@ import { InAppEventRequestParams, InAppTrackRequestParams } from './types'; import { IterableResponse } from '../types'; import { WEB_PLATFORM } from '../constants'; import { eventRequestSchema, trackSchema } from './events.schema'; +import { AnonymousUserEventManager } from '../utils/anonymousUserEventManager'; +import { canTrackAnonUser } from 'src/utils/commonFunctions'; export const track = (payload: InAppTrackRequestParams) => { - /* a customer could potentially send these up if they're not using TypeScript */ - delete (payload as any).userId; - delete (payload as any).email; - + if (canTrackAnonUser(payload)) { + const anonymousUserEventManager = new AnonymousUserEventManager(); + anonymousUserEventManager.trackAnonEvent(payload); + const errorMessage = + 'Iterable SDK must be initialized with an API key and user email/userId before calling SDK methods'; + throw new Error(errorMessage); + } return baseIterableRequest({ method: 'POST', url: '/events/track', diff --git a/src/events/types.ts b/src/events/types.ts index 2a512cb6..7d60051c 100644 --- a/src/events/types.ts +++ b/src/events/types.ts @@ -1,4 +1,6 @@ export interface InAppTrackRequestParams { + userId?: string; + email?: string; eventName: string; id?: string; createdAt?: number; diff --git a/src/users/types.ts b/src/users/types.ts index d0f7c5c8..a142c78b 100644 --- a/src/users/types.ts +++ b/src/users/types.ts @@ -11,6 +11,8 @@ export interface GetUserResponse { } export interface UpdateUserParams { + userId?: string; + email?: string; dataFields?: Record; preferUserId?: boolean; mergeNestedObjects?: boolean; diff --git a/src/users/users.schema.ts b/src/users/users.schema.ts index 91e0443f..6d65fe5b 100644 --- a/src/users/users.schema.ts +++ b/src/users/users.schema.ts @@ -1,9 +1,11 @@ -import { array, boolean, number, object } from 'yup'; +import { array, boolean, number, object, string } from 'yup'; export const updateUserSchema = object().shape({ + userId: string(), dataFields: object(), preferUserId: boolean(), - mergeNestedObjects: boolean() + mergeNestedObjects: boolean(), + headers: object() }); export const updateSubscriptionsSchema = object().shape({ diff --git a/src/users/users.test.ts b/src/users/users.test.ts index 3ec297c9..8a701ebc 100644 --- a/src/users/users.test.ts +++ b/src/users/users.test.ts @@ -2,30 +2,32 @@ import MockAdapter from 'axios-mock-adapter'; import { baseAxiosRequest } from '../request'; import { updateSubscriptions, updateUser, updateUserEmail } from './users'; import { createClientError } from '../utils/testUtils'; -// import { SDK_VERSION, WEB_PLATFORM } from '../constants'; +import { config } from '../utils/config'; const mockRequest = new MockAdapter(baseAxiosRequest); -describe('Users Requests', () => { - it('should set params and return the correct payload for updateUser', async () => { - mockRequest.onPost('/users/update').reply(200, { - msg: 'hello' - }); +jest.mock('../utils/anonymousUserEventManager', () => { + return jest.fn().mockImplementation(() => ({ + trackAnonUpdateUser: jest.fn() + })); +}); - const response = await updateUser({ - dataFields: {} - }); +describe('Users Requests', () => { + beforeEach(() => { + jest.clearAllMocks(); + config.setConfig({ enableAnonTracking: true }); + }); - expect(JSON.parse(response.config.data).dataFields).toEqual({}); - expect(JSON.parse(response.config.data).preferUserId).toBe(true); - // expect(response.config.headers['SDK-Version']).toBe(SDK_VERSION); - // expect(response.config.headers['SDK-Platform']).toBe(WEB_PLATFORM); - expect(response.data.msg).toBe('hello'); + it('should throw an error if payload is empty', () => { + expect(() => { + updateUser(); + }).toThrow(); }); it('should reject updateUser on bad params', async () => { try { await updateUser({ + userId: 'test', dataFields: 'string', preferUserId: 'string', mergeNestedObjects: 'string' @@ -179,8 +181,10 @@ describe('Users Requests', () => { userId: '1234' } as any); - expect(JSON.parse(updateResponse.config.data).email).toBeUndefined(); - expect(JSON.parse(updateResponse.config.data).userId).toBeUndefined(); + expect(JSON.parse(updateResponse.config.data).email).toEqual( + 'hello@gmail.com' + ); + expect(JSON.parse(updateResponse.config.data).userId).toEqual('1234'); expect(JSON.parse(subsResponse.config.data).email).toBeUndefined(); expect(JSON.parse(subsResponse.config.data).userId).toBeUndefined(); }); diff --git a/src/users/users.ts b/src/users/users.ts index e2e6f4b4..7c9bfe38 100644 --- a/src/users/users.ts +++ b/src/users/users.ts @@ -3,6 +3,8 @@ import { IterableResponse } from '../types'; import { baseIterableRequest } from '../request'; import { UpdateSubscriptionParams, UpdateUserParams } from './types'; import { updateSubscriptionsSchema, updateUserSchema } from './users.schema'; +import { AnonymousUserEventManager } from '../utils/anonymousUserEventManager'; +import { canTrackAnonUser } from 'src/utils/commonFunctions'; export const updateUserEmail = (newEmail: string) => { return baseIterableRequest({ @@ -20,10 +22,13 @@ export const updateUserEmail = (newEmail: string) => { }; export const updateUser = (payload: UpdateUserParams = {}) => { - /* a customer could potentially send these up if they're not using TypeScript */ - delete (payload as any).userId; - delete (payload as any).email; - + if (canTrackAnonUser(payload)) { + const anonymousUserEventManager = new AnonymousUserEventManager(); + anonymousUserEventManager.trackAnonUpdateUser(payload); + const errorMessage = + 'Iterable SDK must be initialized with an API key and user email/userId before calling SDK methods'; + throw new Error(errorMessage); + } return baseIterableRequest({ method: 'POST', url: '/users/update', diff --git a/src/utils/anonymousUserEventManager.test.ts b/src/utils/anonymousUserEventManager.test.ts new file mode 100644 index 00000000..318e9135 --- /dev/null +++ b/src/utils/anonymousUserEventManager.test.ts @@ -0,0 +1,511 @@ +import { AnonymousUserEventManager } from './anonymousUserEventManager'; +import { baseIterableRequest } from '../request'; +import { + SHARED_PREFS_ANON_SESSIONS, + SHARED_PREFS_EVENT_LIST_KEY, + SHARED_PREFS_CRITERIA +} from '../constants'; +import { UpdateUserParams } from '../users'; +import { TrackPurchaseRequestParams } from '../commerce'; + +const localStorageMock = { + getItem: jest.fn(), + setItem: jest.fn(), + removeItem: jest.fn() +}; + +jest.mock('./criteriaCompletionChecker', () => { + return jest.fn().mockImplementation(() => ({ + getMatchedCriteria: jest.fn() + })); +}); + +jest.mock('../request', () => ({ + baseIterableRequest: jest.fn() +})); + +declare global { + function uuidv4(): string; + function getEmail(): string; + function getUserID(): string; + function setUserID(): string; +} + +describe('AnonymousUserEventManager', () => { + let anonUserEventManager: AnonymousUserEventManager; + + beforeEach(() => { + (global as any).localStorage = localStorageMock; + anonUserEventManager = new AnonymousUserEventManager(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should update anonymous session information correctly', () => { + const initialAnonSessionInfo = { + itbl_anon_sessions: { + number_of_sessions: 1, + first_session: 123456789, + last_session: expect.any(Number) + } + }; + + localStorageMock.getItem.mockReturnValue( + JSON.stringify(initialAnonSessionInfo) + ); + + anonUserEventManager.updateAnonSession(); + + expect(localStorageMock.setItem).toHaveBeenCalledWith( + SHARED_PREFS_ANON_SESSIONS, + expect.stringContaining('itbl_anon_sessions') + ); + }); + + it('should set criteria data in localStorage when baseIterableRequest succeeds', async () => { + const mockResponse = { data: { criteria: 'mockCriteria' } }; + (baseIterableRequest as jest.Mock).mockResolvedValueOnce(mockResponse); + + const setItemMock = jest.spyOn(localStorage, 'setItem'); + await anonUserEventManager.getAnonCriteria(); + + expect(setItemMock).toHaveBeenCalledWith( + SHARED_PREFS_CRITERIA, + '{"criteria":"mockCriteria"}' + ); + }); + + it('should create known user and make API request when userData is available', async () => { + const userData = { + number_of_sessions: 5, + last_session: 123456789, + first_session: 123456789 + }; + + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === 'criteria') { + return JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '6', + name: 'EventCriteria', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'Or', + searchQueries: [ + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'eventName', + comparatorType: 'Equals', + value: 'testEvent', + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] + }); + } else if (key === SHARED_PREFS_ANON_SESSIONS) { + return JSON.stringify(userData); + } + return null; + }); + + anonUserEventManager.updateAnonSession(); + }); + + it('should call createKnownUser when trackAnonEvent is called', async () => { + const payload = { + eventName: 'testEvent' + }; + const eventData = [ + { + eventName: 'testEvent', + createdAt: 1708494757530, + dataFields: undefined, + createNewFields: true, + eventType: 'customEvent' + } + ]; + + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === 'criteria') { + return JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '6', + name: 'EventCriteria', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'Or', + searchQueries: [ + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'eventName', + comparatorType: 'Equals', + value: 'testEvent', + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] + }); + } else if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify(eventData); + } + return null; + }); + await anonUserEventManager.trackAnonEvent(payload); + }); + + it('should not call createKnownUser when trackAnonEvent is called and criteria does not match', async () => { + const payload = { + eventName: 'Event' + }; + const eventData = [ + { + eventName: 'Event', + createdAt: 1708494757530, + dataFields: undefined, + createNewFields: true, + eventType: 'customEvent' + } + ]; + + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === 'criteria') { + return JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '6', + name: 'EventCriteria', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'Or', + searchQueries: [ + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'eventName', + comparatorType: 'Equals', + value: 'testEvent', + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] + }); + } else if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify(eventData); + } + return null; + }); + await anonUserEventManager.trackAnonEvent(payload); + }); + + it('should not call createKnownUser when trackAnonEvent is called and criteria not find', async () => { + const payload = { + eventName: 'Event' + }; + const eventData = [ + { + eventName: 'Event', + createdAt: 1708494757530, + dataFields: undefined, + createNewFields: true, + eventType: 'customEvent' + } + ]; + + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === 'criteria') { + return null; + } else if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify(eventData); + } + return null; + }); + await anonUserEventManager.trackAnonEvent(payload); + }); + + it('should call createKnownUser when trackAnonUpdateUser is called', async () => { + const payload: UpdateUserParams = { + userId: 'user', + preferUserId: true + }; + const userData = [ + { + userId: 'user', + createdAt: 1708494757530, + dataFields: undefined, + createNewFields: true, + eventType: 'updateUser' + } + ]; + + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === 'criteria') { + return JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '6', + name: 'UpdateUserCriteria', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'Or', + searchQueries: [ + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'updateUser', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'updateUser', + field: 'userId', + comparatorType: 'Equals', + value: 'user', + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] + }); + } else if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify(userData); + } + return null; + }); + await anonUserEventManager.trackAnonUpdateUser(payload); + }); + + it('should call createKnownUser when trackAnonPurchaseEvent is called', async () => { + const payload: TrackPurchaseRequestParams = { + items: [ + { + id: '123', + name: 'Black Coffee', + quantity: 1, + price: 4 + } + ], + user: { + userId: 'user' + }, + total: 0 + }; + const userData = [ + { + items: [ + { + id: '123', + name: 'Black Coffee', + quantity: 1, + price: 4 + } + ], + user: { + userId: 'user' + }, + total: 0, + eventType: 'purchase' + } + ]; + + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === 'criteria') { + return JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '6', + name: 'shoppingCartItemsCriteria', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'Or', + searchQueries: [ + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + field: 'shoppingCartItems.name', + comparatorType: 'Equals', + value: 'Black Coffee', + fieldType: 'string' + }, + { + dataType: 'purchase', + field: 'shoppingCartItems.price', + comparatorType: 'GreaterThanOrEqualTo', + value: '4.00', + fieldType: 'double' + } + ] + } + } + ] + } + ] + } + } + ] + }); + } else if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify(userData); + } + return null; + }); + await anonUserEventManager.trackAnonPurchaseEvent(payload); + }); + + it('should call createKnownUser when trackAnonUpdateCart is called', async () => { + const payload: TrackPurchaseRequestParams = { + items: [ + { + id: '123', + name: 'Black Coffee', + quantity: 2, + price: 4 + } + ], + user: { + userId: 'user' + }, + total: 0 + }; + const userData = [ + { + items: [ + { + id: '123', + name: 'Black Coffee', + quantity: 1, + price: 4 + } + ], + user: { + userId: 'user' + }, + total: 0, + eventType: 'cartUpdate' + } + ]; + + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === 'criteria') { + return JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '6', + name: 'CartUpdateItemsCriteria', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'Or', + searchQueries: [ + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'cartUpdate', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'cartUpdate', + field: 'shoppingCartItems.name', + comparatorType: 'Equals', + value: 'Black Coffee', + fieldType: 'string' + }, + { + dataType: 'cartUpdate', + field: 'shoppingCartItems.price', + comparatorType: 'GreaterThanOrEqualTo', + value: '4.00', + fieldType: 'double' + } + ] + } + } + ] + } + ] + } + } + ] + }); + } else if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify(userData); + } + return null; + }); + await anonUserEventManager.trackAnonUpdateCart(payload); + }); +}); diff --git a/src/utils/anonymousUserEventManager.ts b/src/utils/anonymousUserEventManager.ts new file mode 100644 index 00000000..464900d6 --- /dev/null +++ b/src/utils/anonymousUserEventManager.ts @@ -0,0 +1,368 @@ +import { + UpdateCartRequestParams, + TrackPurchaseRequestParams +} from '../commerce/types'; +import { InAppTrackRequestParams } from '../events/types'; +import { + GET_CRITERIA_PATH, + KEY_EVENT_NAME, + KEY_CREATED_AT, + KEY_DATA_FIELDS, + KEY_CREATE_NEW_FIELDS, + SHARED_PREFS_EVENT_TYPE, + TRACK_EVENT, + SHARED_PREFS_EVENT_LIST_KEY, + KEY_ITEMS, + KEY_TOTAL, + TRACK_PURCHASE, + DATA_REPLACE, + UPDATE_USER, + TRACK_UPDATE_CART, + SHARED_PREFS_CRITERIA, + SHARED_PREFS_ANON_SESSIONS, + SHARED_PREF_USER_ID, + SHARED_PREF_EMAIL, + ENDPOINT_TRACK_ANON_SESSION, + WEB_PLATFORM +} from 'src/constants'; +import { baseIterableRequest } from '../request'; +import { IterableResponse } from '../types'; +import CriteriaCompletionChecker from './criteriaCompletionChecker'; +import { v4 as uuidv4 } from 'uuid'; +import { TrackAnonSessionParams } from './types'; +import { + trackPurchaseSchema, + updateCartSchema +} from 'src/commerce/commerce.schema'; +import { trackSchema } from 'src/events/events.schema'; +import { UpdateUserParams } from 'src/users'; +import { updateUserSchema } from 'src/users/users.schema'; + +export class AnonymousUserEventManager { + updateAnonSession() { + try { + const strAnonSessionInfo = localStorage.getItem( + SHARED_PREFS_ANON_SESSIONS + ); + let anonSessionInfo: { + itbl_anon_sessions?: { + number_of_sessions?: number; + first_session?: number; + last_session?: number; + }; + } = {}; + + if (strAnonSessionInfo) { + anonSessionInfo = JSON.parse(strAnonSessionInfo); + } + + // Update existing values or set them if they don't exist + anonSessionInfo.itbl_anon_sessions = + anonSessionInfo.itbl_anon_sessions || {}; + anonSessionInfo.itbl_anon_sessions.number_of_sessions = + (anonSessionInfo.itbl_anon_sessions.number_of_sessions || 0) + 1; + anonSessionInfo.itbl_anon_sessions.first_session = + anonSessionInfo.itbl_anon_sessions.first_session || + this.getCurrentTime(); + anonSessionInfo.itbl_anon_sessions.last_session = this.getCurrentTime(); + + // Update the structure to the desired format + const outputObject = { + itbl_anon_sessions: anonSessionInfo.itbl_anon_sessions + }; + + localStorage.setItem( + SHARED_PREFS_ANON_SESSIONS, + JSON.stringify(outputObject) + ); + } catch (error) { + console.error('Error updating anonymous session:', error); + } + } + + getAnonCriteria() { + baseIterableRequest({ + method: 'GET', + url: GET_CRITERIA_PATH, + data: {}, + validation: {} + }) + .then((response) => { + const criteriaData: any = response.data; + if (criteriaData) { + localStorage.setItem( + SHARED_PREFS_CRITERIA, + JSON.stringify(criteriaData) + ); + } + }) + .catch((e) => { + console.log('response', e); + }); + } + + async trackAnonEvent(payload: InAppTrackRequestParams) { + const newDataObject = { + [KEY_EVENT_NAME]: payload.eventName, + [KEY_CREATED_AT]: this.getCurrentTime(), + [KEY_DATA_FIELDS]: payload.dataFields, + [KEY_CREATE_NEW_FIELDS]: true, + [SHARED_PREFS_EVENT_TYPE]: TRACK_EVENT + }; + this.storeEventListToLocalStorage(newDataObject, false); + } + + async trackAnonUpdateUser(payload: UpdateUserParams) { + const newDataObject = { + [DATA_REPLACE]: payload.dataFields, + [SHARED_PREFS_EVENT_TYPE]: UPDATE_USER + }; + this.storeEventListToLocalStorage(newDataObject, true); + } + + async trackAnonPurchaseEvent(payload: TrackPurchaseRequestParams) { + const newDataObject = { + [KEY_ITEMS]: payload.items, + [KEY_CREATED_AT]: this.getCurrentTime(), + [KEY_DATA_FIELDS]: payload.dataFields, + [KEY_TOTAL]: payload.total, + [SHARED_PREFS_EVENT_TYPE]: TRACK_PURCHASE + }; + this.storeEventListToLocalStorage(newDataObject, false); + } + + async trackAnonUpdateCart(payload: UpdateCartRequestParams) { + const newDataObject = { + [KEY_ITEMS]: payload.items, + [SHARED_PREFS_EVENT_TYPE]: TRACK_UPDATE_CART, + [KEY_CREATED_AT]: this.getCurrentTime() + }; + this.storeEventListToLocalStorage(newDataObject, false); + } + + private async checkCriteriaCompletion() { + const criteriaData = localStorage.getItem(SHARED_PREFS_CRITERIA); + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + try { + if (criteriaData && localStoredEventList) { + const checker = new CriteriaCompletionChecker(localStoredEventList); + return checker.getMatchedCriteria(criteriaData); + } + } catch (error) { + console.error('checkCriteriaCompletion', error); + } + + return null; + } + + private async createKnownUser(criteriaId: string) { + const userData = localStorage.getItem(SHARED_PREFS_ANON_SESSIONS); + this.setUserID(uuidv4()); + + if (userData) { + const userSessionInfo = JSON.parse(userData); + const userDataJson = userSessionInfo[SHARED_PREFS_ANON_SESSIONS]; + const payload: TrackAnonSessionParams = { + email: this.getEmail() || undefined, + userId: this.getEmail() ? null : this.getUserID() || undefined, + createdAt: this.getCurrentTime(), + deviceInfo: { + appPackageName: window.location.hostname, + deviceId: global.navigator.userAgent || '', + platform: WEB_PLATFORM + }, + anonSessionContext: { + totalAnonSessionCount: userDataJson.number_of_sessions, + lastAnonSession: userDataJson.last_session, + firstAnonSession: userDataJson.first_session, + matchedCriteriaId: parseInt(criteriaId), + webPushOptIn: + this.getWebPushOptnIn() !== '' ? this.getWebPushOptnIn() : undefined + } + }; + + setTimeout(() => { + baseIterableRequest({ + method: 'POST', + url: ENDPOINT_TRACK_ANON_SESSION, + data: payload + }).catch((e) => { + console.log('response', e); + }); + this.syncEvents(); + }, 500); + } else { + this.syncEvents(); + } + } + + async syncEvents() { + const strTrackEventList = localStorage.getItem(SHARED_PREFS_EVENT_LIST_KEY); + const trackEventList = strTrackEventList + ? JSON.parse(strTrackEventList) + : []; + + if (trackEventList.length) { + for (let i = 0; i < trackEventList.length; i++) { + const event = trackEventList[i]; + const eventType = event[SHARED_PREFS_EVENT_TYPE]; + + switch (eventType) { + case TRACK_EVENT: { + await this.track(event); + break; + } + case TRACK_PURCHASE: { + let userDataJson = {}; + if (this.getEmail() !== null) { + userDataJson = { + [SHARED_PREF_EMAIL]: this.getEmail() + }; + } else { + userDataJson = { + [SHARED_PREF_USER_ID]: this.getUserID() + }; + } + event.user = userDataJson; + await this.trackPurchase(event); + break; + } + case TRACK_UPDATE_CART: { + await this.updateCart(event); + break; + } + case UPDATE_USER: { + await this.updateUser(event); + break; + } + default: { + break; + } + } + + localStorage.removeItem(SHARED_PREFS_ANON_SESSIONS); + localStorage.removeItem(SHARED_PREFS_EVENT_LIST_KEY); + } + } + } + + private async storeEventListToLocalStorage( + newDataObject: any, + shouldOverWrite: boolean + ) { + const strTrackEventList = localStorage.getItem(SHARED_PREFS_EVENT_LIST_KEY); + let previousDataArray = []; + + if (strTrackEventList) { + previousDataArray = JSON.parse(strTrackEventList); + } + + if (shouldOverWrite) { + const trackingType = newDataObject[SHARED_PREFS_EVENT_TYPE]; + const indexToUpdate = previousDataArray.findIndex( + (obj: any) => obj[SHARED_PREFS_EVENT_TYPE] === trackingType + ); + if (indexToUpdate !== -1) { + previousDataArray[indexToUpdate] = newDataObject; + } + } else { + previousDataArray.push(newDataObject); + } + + localStorage.setItem( + SHARED_PREFS_EVENT_LIST_KEY, + JSON.stringify(previousDataArray) + ); + + const criteriaId = await this.checkCriteriaCompletion(); + if (criteriaId !== null) { + this.createKnownUser(criteriaId); + } + } + + private getCurrentTime = () => { + return new Date().getTime(); + }; + + private getWebPushOptnIn(): string { + const notificationManager = window.Notification; + if (notificationManager && notificationManager.permission === 'granted') { + return window.location.hostname; + } else { + return ''; + } + } + + track = (payload: InAppTrackRequestParams) => { + return baseIterableRequest({ + method: 'POST', + url: '/events/track', + data: payload, + validation: { + data: trackSchema + } + }); + }; + + updateCart = (payload: UpdateCartRequestParams) => { + return baseIterableRequest({ + method: 'POST', + url: '/commerce/updateCart', + data: { + ...payload, + user: { + ...payload.user, + preferUserId: true + } + }, + validation: { + data: updateCartSchema + } + }); + }; + + trackPurchase = (payload: TrackPurchaseRequestParams) => { + return baseIterableRequest({ + method: 'POST', + url: '/commerce/trackPurchase', + data: { + ...payload, + user: { + ...payload.user, + preferUserId: true + } + }, + validation: { + data: trackPurchaseSchema + } + }); + }; + + updateUser = (payload: UpdateUserParams = {}) => { + return baseIterableRequest({ + method: 'POST', + url: '/users/update', + data: { + ...payload, + preferUserId: true + }, + validation: { + data: updateUserSchema + } + }); + }; + setUserID = (userId: string) => { + localStorage.setItem(SHARED_PREF_USER_ID, userId); + }; + + getUserID = (): string | null => { + return localStorage.getItem(SHARED_PREF_USER_ID); + }; + + getEmail = (): string | null => { + return localStorage.getItem(SHARED_PREF_EMAIL); + }; +} diff --git a/src/utils/anonymousUserMerge.test.ts b/src/utils/anonymousUserMerge.test.ts new file mode 100644 index 00000000..5607e614 --- /dev/null +++ b/src/utils/anonymousUserMerge.test.ts @@ -0,0 +1,96 @@ +import { + ENDPOINT_GET_USER_BY_EMAIL, + ENDPOINT_GET_USER_BY_USERID, + ENDPOINT_MERGE_USER +} from '../constants'; +import { baseIterableRequest } from '../request'; +import { AnonymousUserMerge, MergeApiParams } from './anonymousUserMerge'; + +const localStorageMock = { + getItem: jest.fn() +}; + +jest.mock('../request', () => ({ + baseIterableRequest: jest.fn() +})); + +describe('AnonymousUserMerge', () => { + let anonymousUserMerge: AnonymousUserMerge; + + beforeEach(() => { + anonymousUserMerge = new AnonymousUserMerge(); + (global as any).localStorage = localStorageMock; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should merge users using user ID', async () => { + localStorageMock.getItem.mockReturnValueOnce('sourceUserId'); + const destinationUserId = 'destinationUserId'; + const response = { + status: 200, + data: { user: {} } + }; + (baseIterableRequest as jest.Mock).mockResolvedValueOnce(response); + + anonymousUserMerge.mergeUser(destinationUserId); + + expect(baseIterableRequest).toHaveBeenCalledWith({ + method: 'GET', + url: ENDPOINT_GET_USER_BY_USERID, + params: { userId: destinationUserId, email: null } + }); + }); + + it('should merge users using email', async () => { + localStorageMock.getItem.mockReturnValueOnce('sourceEmail'); + const destinationEmail = 'destinationEmail@example.com'; + const response = { + status: 200, + data: { user: {} } + }; + (baseIterableRequest as jest.Mock).mockResolvedValueOnce(response); + + anonymousUserMerge.mergeUser(destinationEmail); + + expect(baseIterableRequest).toHaveBeenCalledWith({ + method: 'GET', + url: ENDPOINT_GET_USER_BY_EMAIL, + params: { email: destinationEmail, userId: null } + }); + }); + + it('should merge users using callMergeApi method', async () => { + const sourceEmail = 'source@example.com'; + const sourceUserId = 'sourceUserId'; + const destinationEmail = 'destination@example.com'; + const destinationUserId = 'destinationUserId'; + + const response = { + status: 200 + }; + (baseIterableRequest as jest.Mock).mockResolvedValueOnce(response); + + const mergeApiParams: MergeApiParams = { + sourceUserId: sourceUserId, + sourceEmail: sourceEmail, + destinationUserId: destinationUserId, + destinationEmail: destinationEmail + }; + + anonymousUserMerge['callMergeApi'](mergeApiParams); + + expect(baseIterableRequest).toHaveBeenCalledWith({ + method: 'POST', + url: ENDPOINT_MERGE_USER, + data: { + sourceEmail: sourceEmail, + sourceUserId: sourceUserId, + destinationEmail: destinationEmail, + destinationUserId: destinationUserId + } + }); + }); +}); diff --git a/src/utils/anonymousUserMerge.ts b/src/utils/anonymousUserMerge.ts new file mode 100644 index 00000000..c66eaf15 --- /dev/null +++ b/src/utils/anonymousUserMerge.ts @@ -0,0 +1,81 @@ +import { + SHARED_PREF_EMAIL, + SHARED_PREF_USER_ID, + ENDPOINT_GET_USER_BY_USERID, + ENDPOINT_GET_USER_BY_EMAIL, + ENDPOINT_MERGE_USER +} from 'src/constants'; +import { AnonymousUserEventManager } from './anonymousUserEventManager'; +import { baseIterableRequest } from '../request'; +import { IterableResponse } from '../types'; +import { isEmail } from 'src/authorization/utils'; + +export type MergeApiParams = { + sourceEmail: string | null; + sourceUserId: string | null; + destinationEmail: string | null; + destinationUserId: string | null; +}; + +export class AnonymousUserMerge { + private anonymousUserManager = new AnonymousUserEventManager(); + + mergeUser(user: string): void { + const userContainsEmail = isEmail(user); + const sourceUser = localStorage.getItem( + userContainsEmail ? SHARED_PREF_EMAIL : SHARED_PREF_USER_ID + ); + + const mergeApiParams: MergeApiParams = { + sourceUserId: !userContainsEmail + ? localStorage.getItem(SHARED_PREF_USER_ID) + : null, + sourceEmail: userContainsEmail + ? localStorage.getItem(SHARED_PREF_EMAIL) + : null, + destinationUserId: !userContainsEmail ? user : null, + destinationEmail: userContainsEmail ? user : null + }; + + if (!user || user === sourceUser) { + return; + } + baseIterableRequest({ + method: 'GET', + url: userContainsEmail + ? ENDPOINT_GET_USER_BY_EMAIL + : ENDPOINT_GET_USER_BY_USERID, + params: { + email: mergeApiParams.destinationEmail, + userId: mergeApiParams.destinationUserId + } + }) + .then((response) => { + const userData: any = response.data; + if (userData?.user) this.callMergeApi(mergeApiParams); + }) + .catch((e) => { + console.log('response', e); + }); + } + + private callMergeApi(data: MergeApiParams): void { + baseIterableRequest({ + method: 'POST', + url: ENDPOINT_MERGE_USER, + data + }) + .then((response) => { + if (response.status === 200) { + try { + this.anonymousUserManager.syncEvents(); + } catch (error) { + console.error('error', error); + } + } + }) + .catch((e) => { + console.log('response', e); + }); + } +} diff --git a/src/utils/commonFunctions.ts b/src/utils/commonFunctions.ts new file mode 100644 index 00000000..9c5341f0 --- /dev/null +++ b/src/utils/commonFunctions.ts @@ -0,0 +1,17 @@ +import config from '../utils/config'; + +export const canTrackAnonUser = (payload: any): boolean => { + if ( + (!('userId' in payload) || + payload.userId === null || + typeof payload.userId === 'undefined') && + (!('email' in payload) || + payload.email === null || + typeof payload.email === 'undefined') && + config.getConfig('enableAnonTracking') + ) { + return true; + } + + return false; +}; diff --git a/src/utils/config.ts b/src/utils/config.ts index 3bcff2c1..84898e3e 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -3,16 +3,18 @@ import { BASE_URL } from '../constants'; interface Options { logLevel: 'none' | 'verbose'; baseURL: string; + enableAnonTracking: boolean; } const _config = () => { let options: Options = { logLevel: 'none', - baseURL: BASE_URL + baseURL: BASE_URL, + enableAnonTracking: false }; return { - getConfig: (option: keyof Options) => options[option], + getConfig: (option: K) => options[option], setConfig: (newOptions: Partial) => { options = { ...options, diff --git a/src/utils/criteriaCompletionChecker.test.ts b/src/utils/criteriaCompletionChecker.test.ts new file mode 100644 index 00000000..dab9bb91 --- /dev/null +++ b/src/utils/criteriaCompletionChecker.test.ts @@ -0,0 +1,408 @@ +import { SHARED_PREFS_EVENT_LIST_KEY } from '../constants'; +import CriteriaCompletionChecker from '../utils/criteriaCompletionChecker'; + +const localStorageMock = { + getItem: jest.fn(), + setItem: jest.fn(), + removeItem: jest.fn() +}; + +describe('CriteriaCompletionChecker', () => { + beforeEach(() => { + (global as any).localStorage = localStorageMock; + }); + + it('should return null if criteriaData is empty', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return '[]'; + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria('{}'); + expect(result).toBeNull(); + }); + + it('should return criteriaId if criteriaData condition is matched', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + eventName: 'testEvent', + createdAt: 1708494757530, + dataFields: undefined, + createNewFields: true, + eventType: 'customEvent' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '6', + name: 'EventCriteria', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'Or', + searchQueries: [ + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'eventName', + comparatorType: 'Equals', + value: 'testEvent', + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual('6'); + }); + + it('should return null if criteriaData condition is matched', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + eventName: 'Event', + createdAt: 1708494757530, + dataFields: undefined, + createNewFields: true, + eventType: 'customEvent' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '6', + name: 'EventCriteria', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'Or', + searchQueries: [ + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'eventName', + comparatorType: 'Equals', + value: 'testEvent', + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toBeNull(); + }); + + it('should return criteriaId if criteriaData condition with numeric is matched', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + items: [ + { + id: '123', + name: 'Black Coffee', + quantity: 1, + price: 4 + } + ], + user: { + userId: 'user' + }, + total: 0, + eventType: 'purchase' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '6', + name: 'shoppingCartItemsCriteria', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'Or', + searchQueries: [ + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + field: 'shoppingCartItems.name', + comparatorType: 'Equals', + value: 'Black Coffee', + fieldType: 'string' + }, + { + dataType: 'purchase', + field: 'shoppingCartItems.price', + comparatorType: 'GreaterThanOrEqualTo', + value: '4.00', + fieldType: 'double' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual('6'); + }); + + it('should return criteriaId if criteriaData condition with StartsWith is matched', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + eventName: 'testEvent', + createdAt: 1708494757530, + dataFields: undefined, + createNewFields: true, + eventType: 'customEvent' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '6', + name: 'EventCriteria', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'Or', + searchQueries: [ + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'eventName', + comparatorType: 'StartsWith', + value: 'test', + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual('6'); + + const result1 = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '6', + name: 'EventCriteria', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'Or', + searchQueries: [ + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'eventName', + comparatorType: 'Contains', + value: 'test', + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result1).toEqual('6'); + }); + + it('should return criteriaId if criteria regex match with value is matched', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + email: 'testEvent@example.com', + createdAt: 1708494757530, + dataFields: undefined, + createNewFields: true, + eventType: 'customEvent' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '6', + name: 'EventCriteria', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'Or', + searchQueries: [ + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'email', + comparatorType: 'MatchesRegex', + value: /^[a-zA-Z0-9]+@(?:[a-zA-Z0-9]+.)+[A-Za-z]+$/, + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual('6'); + }); +}); diff --git a/src/utils/criteriaCompletionChecker.ts b/src/utils/criteriaCompletionChecker.ts new file mode 100644 index 00000000..abb51c64 --- /dev/null +++ b/src/utils/criteriaCompletionChecker.ts @@ -0,0 +1,314 @@ +import { + SHARED_PREFS_EVENT_TYPE, + KEY_ITEMS, + TRACK_PURCHASE, + TRACK_UPDATE_CART +} from '../constants'; + +interface SearchQuery { + combinator: string; + searchQueries: SearchQuery[] | Criteria[]; + dataType?: string; + searchCombo?: SearchQuery; + field?: string; + comparatorType?: string; + value?: string; + fieldType?: string; +} + +interface Criteria { + criteriaId: string; + name: string; + createdAt: number; + updatedAt: number; + searchQuery: SearchQuery; +} + +class CriteriaCompletionChecker { + private localStoredEventList: any[]; + + constructor(localStoredEventList: string) { + this.localStoredEventList = JSON.parse(localStoredEventList); + } + + public getMatchedCriteria(criteriaData: string): string | null { + let criteriaId: string | null = null; + + try { + const json = JSON.parse(criteriaData); + if (json.criterias) { + criteriaId = this.findMatchedCriteria(json.criterias); + } + } catch (e) { + this.handleJSONException(e); + } + + return criteriaId; + } + + private findMatchedCriteria(criteriaList: Criteria[]): string | null { + let criteriaId: string | null = null; + for (let i = 0; i < criteriaList.length; i++) { + const criteria = criteriaList[i]; + if (criteria.searchQuery && criteria.criteriaId) { + const searchQuery = criteria.searchQuery; + const currentCriteriaId = criteria.criteriaId; + const eventsToProcess = this.prepareEventsToProcess(); + + const result = this.evaluateTree(searchQuery, eventsToProcess); + if (result) { + criteriaId = currentCriteriaId; + break; + } + } + } + + return criteriaId; + } + + private prepareEventsToProcess(): any[] { + const eventsToProcess: any[] = this.getEventsWithCartItems(); + const nonPurchaseEvents: any[] = this.getNonCartEvents(); + + nonPurchaseEvents.forEach((event) => { + eventsToProcess.push(event); + }); + + return eventsToProcess; + } + + private getEventsWithCartItems(): any[] { + const processedEvents: any[] = []; + + this.localStoredEventList.forEach((localEventData) => { + if ( + localEventData[SHARED_PREFS_EVENT_TYPE] && + (localEventData[SHARED_PREFS_EVENT_TYPE] === TRACK_PURCHASE || + localEventData[SHARED_PREFS_EVENT_TYPE] === TRACK_UPDATE_CART) + ) { + const updatedItem: any = {}; + + if (localEventData[KEY_ITEMS]) { + const items_str: string = JSON.stringify(localEventData[KEY_ITEMS]); + const items = JSON.parse(items_str); + items.forEach((item: any) => { + Object.keys(item).forEach((key) => { + updatedItem['shoppingCartItems.' + key] = item[key]; + }); + }); + } + + if (localEventData.dataFields) { + Object.keys(localEventData.dataFields).forEach((key) => { + updatedItem[key] = localEventData.dataFields[key]; + }); + } + + Object.keys(localEventData).forEach((key) => { + if (key !== KEY_ITEMS && key !== 'dataFields') { + updatedItem[key] = localEventData[key]; + } + }); + processedEvents.push(updatedItem); + } + }); + + return processedEvents; + } + + private getNonCartEvents(): any[] { + const nonPurchaseEvents: any[] = []; + + this.localStoredEventList.forEach((localEventData) => { + if ( + localEventData[SHARED_PREFS_EVENT_TYPE] && + localEventData[SHARED_PREFS_EVENT_TYPE] !== TRACK_PURCHASE && + localEventData[SHARED_PREFS_EVENT_TYPE] !== TRACK_UPDATE_CART + ) { + nonPurchaseEvents.push(localEventData); + } + }); + + return nonPurchaseEvents; + } + + private evaluateTree(node: SearchQuery, localEventData: any[]): boolean { + try { + if (node.searchQueries) { + const combinator = node.combinator; + const searchQueries: any = node.searchQueries; + if (combinator === 'And') { + for (let i = 0; i < searchQueries.length; i++) { + if (!this.evaluateTree(searchQueries[i], localEventData)) { + return false; + } + } + return true; + } else if (combinator === 'Or') { + for (let i = 0; i < searchQueries.length; i++) { + if (this.evaluateTree(searchQueries[i], localEventData)) { + return true; + } + } + return false; + } + } else if (node.searchCombo) { + const searchCombo = node.searchCombo; + return this.evaluateTree(searchCombo, localEventData); + } else if (node.field) { + return this.evaluateField(node, localEventData); + } + } catch (e) { + this.handleException(e); + } + return false; + } + + private evaluateField(node: SearchQuery, localEventData: any[]): boolean { + try { + return this.evaluateFieldLogic(node, localEventData); + } catch (e) { + this.handleJSONException(e); + } + return false; + } + + private evaluateFieldLogic( + node: SearchQuery, + localEventData: any[] + ): boolean { + for (let i = 0; i < localEventData.length; i++) { + const eventData = localEventData[i]; + const trackingType = eventData[SHARED_PREFS_EVENT_TYPE]; + const dataType = node.dataType; + if (dataType !== trackingType) { + return false; + } + + const field = node.field; + const comparatorType = node.comparatorType ? node.comparatorType : ''; + const localDataKeys = Object.keys(eventData); + + for (let j = 0; j < localDataKeys.length; j++) { + const key = localDataKeys[j]; + if (field === key) { + const matchedCountObj = eventData[key]; + if ( + this.evaluateComparison( + comparatorType, + matchedCountObj, + node.value ? node.value : '' + ) + ) { + return true; + } + } + } + } + + return false; + } + + private evaluateComparison( + comparatorType: string, + matchObj: any, + valueToCompare: string + ): boolean { + if (!valueToCompare) { + return false; + } + + switch (comparatorType) { + case 'Equals': + return this.compareValueEquality(matchObj, valueToCompare); + case 'DoesNotEquals': + return !this.compareValueEquality(matchObj, valueToCompare); + case 'GreaterThan': + case 'LessThan': + case 'GreaterThanOrEqualTo': + case 'LessThanOrEqualTo': + return this.compareNumericValues( + matchObj, + valueToCompare, + comparatorType + ); + case 'Contains': + return this.compareStringContains(matchObj, valueToCompare); + case 'StartsWith': + return this.compareStringStartsWith(matchObj, valueToCompare); + case 'MatchesRegex': + return this.compareWithRegex(matchObj, valueToCompare); + default: + return false; + } + } + + private compareValueEquality(sourceTo: any, stringValue: string): boolean { + if ( + (typeof sourceTo === 'number' || typeof sourceTo === 'boolean') && + stringValue !== '' && + !isNaN(parseFloat(stringValue)) + ) { + if (typeof sourceTo === 'number') { + return sourceTo === parseFloat(stringValue); + } else if (typeof sourceTo === 'boolean') { + return sourceTo === (stringValue === 'true'); + } + } else if (typeof sourceTo === 'string') { + return sourceTo === stringValue; + } + return false; + } + + private compareNumericValues( + sourceTo: any, + stringValue: string, + compareOperator: string + ): boolean { + if (!isNaN(parseFloat(stringValue))) { + const sourceNumber = parseFloat(sourceTo); + const numericValue = parseFloat(stringValue); + switch (compareOperator) { + case 'GreaterThan': + return sourceNumber > numericValue; + case 'LessThan': + return sourceNumber < numericValue; + case 'GreaterThanOrEqualTo': + return sourceNumber >= numericValue; + case 'LessThanOrEqualTo': + return sourceNumber <= numericValue; + default: + return false; + } + } + return false; + } + + private compareStringContains(sourceTo: any, stringValue: string): boolean { + return typeof sourceTo === 'string' && sourceTo.includes(stringValue); + } + + private compareStringStartsWith(sourceTo: any, stringValue: string): boolean { + return typeof sourceTo === 'string' && sourceTo.startsWith(stringValue); + } + + private compareWithRegex(sourceTo: string, pattern: string): boolean { + try { + const regexPattern = new RegExp(pattern); + return regexPattern.test(sourceTo); + } catch (e) { + console.error(e); + return false; + } + } + + private handleException(e: any) { + console.error('Exception occurred', e.toString()); + } + + private handleJSONException(e: any) { + console.error('JSONException occurred', e.toString()); + } +} + +export default CriteriaCompletionChecker; diff --git a/src/utils/types.ts b/src/utils/types.ts new file mode 100644 index 00000000..6e7fb042 --- /dev/null +++ b/src/utils/types.ts @@ -0,0 +1,20 @@ +interface AnonSessionContext { + totalAnonSessionCount?: number; + lastAnonSession?: number; + firstAnonSession?: number; + webPushOptIn?: string; + lastPage?: string; + matchedCriteriaId: number; +} + +export interface TrackAnonSessionParams { + email?: string | null; + userId?: string | null; + createdAt: number; + deviceInfo: { + deviceId: string; + appPackageName: string; // customer-defined name + platform: string; + }; + anonSessionContext: AnonSessionContext; +} diff --git a/yarn.lock b/yarn.lock index ce27ee29..ecff4f30 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4,7 +4,7 @@ "@babel/cli@^7.5.5": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.16.0.tgz#a729b7a48eb80b49f48a339529fc4129fd7bcef3" + resolved "https://registry.npmjs.org/@babel/cli/-/cli-7.16.0.tgz" integrity sha512-WLrM42vKX/4atIoQB+eb0ovUof53UUvecb4qGjU2PDDWRiZr50ZpiV8NpcLo7iSxeGYrRG0Mqembsa+UrTAV6Q== dependencies: commander "^4.0.1" @@ -20,26 +20,26 @@ "@babel/code-frame@7.12.11": version "7.12.11" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz" integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== dependencies: "@babel/highlight" "^7.10.4" "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.0.tgz#0dfc80309beec8411e65e706461c408b0bb9b431" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz" integrity sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA== dependencies: "@babel/highlight" "^7.16.0" "@babel/compat-data@^7.13.11", "@babel/compat-data@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.16.0.tgz#ea269d7f78deb3a7826c39a4048eecda541ebdaa" + resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.0.tgz" integrity sha512-DGjt2QZse5SGd9nfOSqO4WLJ8NN/oHkijbXbPrxuoJO3oIPJL3TciZs9FX+cOHNiY9E9l0opL8g7BmLe3T+9ew== "@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" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.16.0.tgz#c4ff44046f5fe310525cc9eb4ef5147f0c5374d4" + resolved "https://registry.npmjs.org/@babel/core/-/core-7.16.0.tgz" integrity sha512-mYZEvshBRHGsIAiyH5PzCFTCfbWfoYbO/jcSdXQSUQu1/pW0xDZAUP7KEc32heqWTAfAHhV9j1vH8Sav7l+JNQ== dependencies: "@babel/code-frame" "^7.16.0" @@ -60,7 +60,7 @@ "@babel/generator@^7.16.0", "@babel/generator@^7.7.2": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.16.0.tgz#d40f3d1d5075e62d3500bccb67f3daa8a95265b2" + resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.16.0.tgz" integrity sha512-RR8hUCfRQn9j9RPKEVXo9LiwoxLPYn6hNZlvUOR8tSnaxlD0p0+la00ZP9/SnRt6HchKr+X0fO2r8vrETiJGew== dependencies: "@babel/types" "^7.16.0" @@ -69,14 +69,14 @@ "@babel/helper-annotate-as-pure@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.0.tgz#9a1f0ebcda53d9a2d00108c4ceace6a5d5f1f08d" + resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.0.tgz" integrity sha512-ItmYF9vR4zA8cByDocY05o0LGUkp1zhbTQOH1NFyl5xXEqlTJQCEJjieriw+aFpxo16swMxUnUiKS7a/r4vtHg== dependencies: "@babel/types" "^7.16.0" "@babel/helper-builder-binary-assignment-operator-visitor@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.0.tgz#f1a686b92da794020c26582eb852e9accd0d7882" + resolved "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.0.tgz" integrity sha512-9KuleLT0e77wFUku6TUkqZzCEymBdtuQQ27MhEKzf9UOOJu3cYj98kyaDAzxpC7lV6DGiZFuC8XqDsq8/Kl6aQ== dependencies: "@babel/helper-explode-assignable-expression" "^7.16.0" @@ -84,7 +84,7 @@ "@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.16.0": version "7.16.3" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.3.tgz#5b480cd13f68363df6ec4dc8ac8e2da11363cbf0" + resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.3.tgz" integrity sha512-vKsoSQAyBmxS35JUOOt+07cLc6Nk/2ljLIHwmq2/NM6hdioUaqEXq/S+nXvbvXbZkNDlWOymPanJGOc4CBjSJA== dependencies: "@babel/compat-data" "^7.16.0" @@ -94,7 +94,7 @@ "@babel/helper-create-class-features-plugin@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.16.0.tgz#090d4d166b342a03a9fec37ef4fd5aeb9c7c6a4b" + resolved "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.16.0.tgz" integrity sha512-XLwWvqEaq19zFlF5PTgOod4bUA+XbkR4WLQBct1bkzmxJGB0ZEJaoKF4c8cgH9oBtCDuYJ8BP5NB9uFiEgO5QA== dependencies: "@babel/helper-annotate-as-pure" "^7.16.0" @@ -106,7 +106,7 @@ "@babel/helper-create-regexp-features-plugin@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.16.0.tgz#06b2348ce37fccc4f5e18dcd8d75053f2a7c44ff" + resolved "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.16.0.tgz" integrity sha512-3DyG0zAFAZKcOp7aVr33ddwkxJ0Z0Jr5V99y3I690eYLpukJsJvAbzTy1ewoCqsML8SbIrjH14Jc/nSQ4TvNPA== dependencies: "@babel/helper-annotate-as-pure" "^7.16.0" @@ -114,7 +114,7 @@ "@babel/helper-define-polyfill-provider@^0.2.4": version "0.2.4" - resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.2.4.tgz#8867aed79d3ea6cade40f801efb7ac5c66916b10" + resolved "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.2.4.tgz" integrity sha512-OrpPZ97s+aPi6h2n1OXzdhVis1SGSsMU2aMHgLcOKfsp4/v1NWpx3CWT3lBj5eeBq9cDkPkh+YCfdF7O12uNDQ== dependencies: "@babel/helper-compilation-targets" "^7.13.0" @@ -128,14 +128,14 @@ "@babel/helper-explode-assignable-expression@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.0.tgz#753017337a15f46f9c09f674cff10cee9b9d7778" + resolved "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.0.tgz" integrity sha512-Hk2SLxC9ZbcOhLpg/yMznzJ11W++lg5GMbxt1ev6TXUiJB0N42KPC+7w8a+eWGuqDnUYuwStJoZHM7RgmIOaGQ== dependencies: "@babel/types" "^7.16.0" "@babel/helper-function-name@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.16.0.tgz#b7dd0797d00bbfee4f07e9c4ea5b0e30c8bb1481" + resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.0.tgz" integrity sha512-BZh4mEk1xi2h4HFjWUXRQX5AEx4rvaZxHgax9gcjdLWdkjsY7MKt5p0otjsg5noXw+pB+clMCjw+aEVYADMjog== dependencies: "@babel/helper-get-function-arity" "^7.16.0" @@ -144,35 +144,35 @@ "@babel/helper-get-function-arity@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz#0088c7486b29a9cb5d948b1a1de46db66e089cfa" + resolved "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz" integrity sha512-ASCquNcywC1NkYh/z7Cgp3w31YW8aojjYIlNg4VeJiHkqyP4AzIvr4qx7pYDb4/s8YcsZWqqOSxgkvjUz1kpDQ== dependencies: "@babel/types" "^7.16.0" "@babel/helper-hoist-variables@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.0.tgz#4c9023c2f1def7e28ff46fc1dbcd36a39beaa81a" + resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.0.tgz" integrity sha512-1AZlpazjUR0EQZQv3sgRNfM9mEVWPK3M6vlalczA+EECcPz3XPh6VplbErL5UoMpChhSck5wAJHthlj1bYpcmg== dependencies: "@babel/types" "^7.16.0" "@babel/helper-member-expression-to-functions@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.0.tgz#29287040efd197c77636ef75188e81da8bccd5a4" + resolved "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.0.tgz" integrity sha512-bsjlBFPuWT6IWhl28EdrQ+gTvSvj5tqVP5Xeftp07SEuz5pLnsXZuDkDD3Rfcxy0IsHmbZ+7B2/9SHzxO0T+sQ== dependencies: "@babel/types" "^7.16.0" "@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.0.tgz#90538e60b672ecf1b448f5f4f5433d37e79a3ec3" + resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.0.tgz" integrity sha512-kkH7sWzKPq0xt3H1n+ghb4xEMP8k0U7XV3kkB+ZGy69kDk2ySFW1qPi06sjKzFY3t1j6XbJSqr4mF9L7CYVyhg== dependencies: "@babel/types" "^7.16.0" "@babel/helper-module-transforms@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.16.0.tgz#1c82a8dd4cb34577502ebd2909699b194c3e9bb5" + resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.0.tgz" integrity sha512-My4cr9ATcaBbmaEa8M0dZNA74cfI6gitvUAskgDtAFmAqyFKDSHQo5YstxPbN+lzHl2D9l/YOEFqb2mtUh4gfA== dependencies: "@babel/helper-module-imports" "^7.16.0" @@ -186,24 +186,24 @@ "@babel/helper-optimise-call-expression@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.0.tgz#cecdb145d70c54096b1564f8e9f10cd7d193b338" + resolved "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.0.tgz" integrity sha512-SuI467Gi2V8fkofm2JPnZzB/SUuXoJA5zXe/xzyPP2M04686RzFKFHPK6HDVN6JvWBIEW8tt9hPR7fXdn2Lgpw== dependencies: "@babel/types" "^7.16.0" "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.13.0", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz#5ac822ce97eec46741ab70a517971e443a70c5a9" + resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz" integrity sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ== "@babel/helper-plugin-utils@^7.22.5": version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" + resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz" integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== "@babel/helper-remap-async-to-generator@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.0.tgz#d5aa3b086e13a5fe05238ff40c3a5a0c2dab3ead" + resolved "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.0.tgz" integrity sha512-MLM1IOMe9aQBqMWxcRw8dcb9jlM86NIw7KA0Wri91Xkfied+dE0QuBFSBjMNvqzmS0OSIDsMNC24dBEkPUi7ew== dependencies: "@babel/helper-annotate-as-pure" "^7.16.0" @@ -212,7 +212,7 @@ "@babel/helper-replace-supers@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.16.0.tgz#73055e8d3cf9bcba8ddb55cad93fedc860f68f17" + resolved "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.0.tgz" integrity sha512-TQxuQfSCdoha7cpRNJvfaYxxxzmbxXw/+6cS7V02eeDYyhxderSoMVALvwupA54/pZcOTtVeJ0xccp1nGWladA== dependencies: "@babel/helper-member-expression-to-functions" "^7.16.0" @@ -222,38 +222,38 @@ "@babel/helper-simple-access@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.16.0.tgz#21d6a27620e383e37534cf6c10bba019a6f90517" + resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.0.tgz" integrity sha512-o1rjBT/gppAqKsYfUdfHq5Rk03lMQrkPHG1OWzHWpLgVXRH4HnMM9Et9CVdIqwkCQlobnGHEJMsgWP/jE1zUiw== dependencies: "@babel/types" "^7.16.0" "@babel/helper-skip-transparent-expression-wrappers@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz#0ee3388070147c3ae051e487eca3ebb0e2e8bb09" + resolved "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz" integrity sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw== dependencies: "@babel/types" "^7.16.0" "@babel/helper-split-export-declaration@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.0.tgz#29672f43663e936df370aaeb22beddb3baec7438" + resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.0.tgz" integrity sha512-0YMMRpuDFNGTHNRiiqJX19GjNXA4H0E8jZ2ibccfSxaCogbm3am5WN/2nQNj0YnQwGWM1J06GOcQ2qnh3+0paw== dependencies: "@babel/types" "^7.16.0" "@babel/helper-validator-identifier@^7.15.7": version "7.15.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz#220df993bfe904a4a6b02ab4f3385a5ebf6e2389" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz" integrity sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w== "@babel/helper-validator-option@^7.14.5": version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz#6e72a1fff18d5dfcb878e1e62f1a021c4b72d5a3" + resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz" integrity sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow== "@babel/helper-wrap-function@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.16.0.tgz#b3cf318afce774dfe75b86767cd6d68f3482e57c" + resolved "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.16.0.tgz" integrity sha512-VVMGzYY3vkWgCJML+qVLvGIam902mJW0FvT7Avj1zEe0Gn7D93aWdLblYARTxEw+6DhZmtzhBM2zv0ekE5zg1g== dependencies: "@babel/helper-function-name" "^7.16.0" @@ -263,7 +263,7 @@ "@babel/helpers@^7.16.0": version "7.16.3" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.16.3.tgz#27fc64f40b996e7074dc73128c3e5c3e7f55c43c" + resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.16.3.tgz" integrity sha512-Xn8IhDlBPhvYTvgewPKawhADichOsbkZuzN7qz2BusOM0brChsyXMDJvldWaYMMUNiCQdQzNEioXTp3sC8Nt8w== dependencies: "@babel/template" "^7.16.0" @@ -272,7 +272,7 @@ "@babel/highlight@^7.10.4", "@babel/highlight@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.0.tgz#6ceb32b2ca4b8f5f361fb7fd821e3fddf4a1725a" + resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz" integrity sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g== dependencies: "@babel/helper-validator-identifier" "^7.15.7" @@ -281,19 +281,19 @@ "@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.0", "@babel/parser@^7.16.3", "@babel/parser@^7.7.2": version "7.16.3" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.3.tgz#271bafcb811080905a119222edbc17909c82261d" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.16.3.tgz" integrity sha512-dcNwU1O4sx57ClvLBVFbEgx0UZWfd0JQX5X6fxFRCLHelFBGXFfSz6Y0FAq2PEwUqlqLkdVjVr4VASEOuUnLJw== "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.16.0": version "7.16.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.2.tgz#2977fca9b212db153c195674e57cfab807733183" + resolved "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.2.tgz" integrity sha512-h37CvpLSf8gb2lIJ2CgC3t+EjFbi0t8qS7LCS1xcJIlEXE4czlofwaW7W1HA8zpgOCzI9C1nmoqNR1zWkk0pQg== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.16.0.tgz#358972eaab006f5eb0826183b0c93cbcaf13e1e2" + resolved "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.16.0.tgz" integrity sha512-4tcFwwicpWTrpl9qjf7UsoosaArgImF85AxqCRZlgc3IQDvkUHjJpruXAL58Wmj+T6fypWTC/BakfEkwIL/pwA== dependencies: "@babel/helper-plugin-utils" "^7.14.5" @@ -302,7 +302,7 @@ "@babel/plugin-proposal-async-generator-functions@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.0.tgz#11425d47a60364352f668ad5fbc1d6596b2c5caf" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.0.tgz" integrity sha512-nyYmIo7ZqKsY6P4lnVmBlxp9B3a96CscbLotlsNuktMHahkDwoPYEjXrZHU0Tj844Z9f1IthVxQln57mhkcExw== dependencies: "@babel/helper-plugin-utils" "^7.14.5" @@ -311,7 +311,7 @@ "@babel/plugin-proposal-class-properties@^7.16.0", "@babel/plugin-proposal-class-properties@^7.5.5": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.0.tgz#c029618267ddebc7280fa286e0f8ca2a278a2d1a" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.0.tgz" integrity sha512-mCF3HcuZSY9Fcx56Lbn+CGdT44ioBMMvjNVldpKtj8tpniETdLjnxdHI1+sDWXIM1nNt+EanJOZ3IG9lzVjs7A== dependencies: "@babel/helper-create-class-features-plugin" "^7.16.0" @@ -319,7 +319,7 @@ "@babel/plugin-proposal-class-static-block@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.16.0.tgz#5296942c564d8144c83eea347d0aa8a0b89170e7" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.16.0.tgz" integrity sha512-mAy3sdcY9sKAkf3lQbDiv3olOfiLqI51c9DR9b19uMoR2Z6r5pmGl7dfNFqEvqOyqbf1ta4lknK4gc5PJn3mfA== dependencies: "@babel/helper-create-class-features-plugin" "^7.16.0" @@ -328,7 +328,7 @@ "@babel/plugin-proposal-dynamic-import@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.0.tgz#783eca61d50526202f9b296095453977e88659f1" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.0.tgz" integrity sha512-QGSA6ExWk95jFQgwz5GQ2Dr95cf7eI7TKutIXXTb7B1gCLTCz5hTjFTQGfLFBBiC5WSNi7udNwWsqbbMh1c4yQ== dependencies: "@babel/helper-plugin-utils" "^7.14.5" @@ -336,7 +336,7 @@ "@babel/plugin-proposal-export-namespace-from@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.0.tgz#9c01dee40b9d6b847b656aaf4a3976a71740f222" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.0.tgz" integrity sha512-CjI4nxM/D+5wCnhD11MHB1AwRSAYeDT+h8gCdcVJZ/OK7+wRzFsf7PFPWVpVpNRkHMmMkQWAHpTq+15IXQ1diA== dependencies: "@babel/helper-plugin-utils" "^7.14.5" @@ -344,7 +344,7 @@ "@babel/plugin-proposal-json-strings@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.16.0.tgz#cae35a95ed1d2a7fa29c4dc41540b84a72e9ab25" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.16.0.tgz" integrity sha512-kouIPuiv8mSi5JkEhzApg5Gn6hFyKPnlkO0a9YSzqRurH8wYzSlf6RJdzluAsbqecdW5pBvDJDfyDIUR/vLxvg== dependencies: "@babel/helper-plugin-utils" "^7.14.5" @@ -352,7 +352,7 @@ "@babel/plugin-proposal-logical-assignment-operators@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.0.tgz#a711b8ceb3ffddd3ef88d3a49e86dbd3cc7db3fd" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.0.tgz" integrity sha512-pbW0fE30sVTYXXm9lpVQQ/Vc+iTeQKiXlaNRZPPN2A2VdlWyAtsUrsQ3xydSlDW00TFMK7a8m3cDTkBF5WnV3Q== dependencies: "@babel/helper-plugin-utils" "^7.14.5" @@ -360,7 +360,7 @@ "@babel/plugin-proposal-nullish-coalescing-operator@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.0.tgz#44e1cce08fe2427482cf446a91bb451528ed0596" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.0.tgz" integrity sha512-3bnHA8CAFm7cG93v8loghDYyQ8r97Qydf63BeYiGgYbjKKB/XP53W15wfRC7dvKfoiJ34f6Rbyyx2btExc8XsQ== dependencies: "@babel/helper-plugin-utils" "^7.14.5" @@ -368,7 +368,7 @@ "@babel/plugin-proposal-numeric-separator@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.0.tgz#5d418e4fbbf8b9b7d03125d3a52730433a373734" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.0.tgz" integrity sha512-FAhE2I6mjispy+vwwd6xWPyEx3NYFS13pikDBWUAFGZvq6POGs5eNchw8+1CYoEgBl9n11I3NkzD7ghn25PQ9Q== dependencies: "@babel/helper-plugin-utils" "^7.14.5" @@ -376,7 +376,7 @@ "@babel/plugin-proposal-object-rest-spread@^7.16.0", "@babel/plugin-proposal-object-rest-spread@^7.5.5": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.16.0.tgz#5fb32f6d924d6e6712810362a60e12a2609872e6" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.16.0.tgz" integrity sha512-LU/+jp89efe5HuWJLmMmFG0+xbz+I2rSI7iLc1AlaeSMDMOGzWlc5yJrMN1d04osXN4sSfpo4O+azkBNBes0jg== dependencies: "@babel/compat-data" "^7.16.0" @@ -387,7 +387,7 @@ "@babel/plugin-proposal-optional-catch-binding@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.0.tgz#5910085811ab4c28b00d6ebffa4ab0274d1e5f16" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.0.tgz" integrity sha512-kicDo0A/5J0nrsCPbn89mTG3Bm4XgYi0CZtvex9Oyw7gGZE3HXGD0zpQNH+mo+tEfbo8wbmMvJftOwpmPy7aVw== dependencies: "@babel/helper-plugin-utils" "^7.14.5" @@ -395,7 +395,7 @@ "@babel/plugin-proposal-optional-chaining@^7.14.5", "@babel/plugin-proposal-optional-chaining@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.0.tgz#56dbc3970825683608e9efb55ea82c2a2d6c8dc0" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.0.tgz" integrity sha512-Y4rFpkZODfHrVo70Uaj6cC1JJOt3Pp0MdWSwIKtb8z1/lsjl9AmnB7ErRFV+QNGIfcY1Eruc2UMx5KaRnXjMyg== dependencies: "@babel/helper-plugin-utils" "^7.14.5" @@ -404,7 +404,7 @@ "@babel/plugin-proposal-private-methods@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.0.tgz#b4dafb9c717e4301c5776b30d080d6383c89aff6" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.0.tgz" integrity sha512-IvHmcTHDFztQGnn6aWq4t12QaBXTKr1whF/dgp9kz84X6GUcwq9utj7z2wFCUfeOup/QKnOlt2k0zxkGFx9ubg== dependencies: "@babel/helper-create-class-features-plugin" "^7.16.0" @@ -412,7 +412,7 @@ "@babel/plugin-proposal-private-property-in-object@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.0.tgz#69e935b2c5c79d2488112d886f0c4e2790fee76f" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.0.tgz" integrity sha512-3jQUr/HBbMVZmi72LpjQwlZ55i1queL8KcDTQEkAHihttJnAPrcvG9ZNXIfsd2ugpizZo595egYV6xy+pv4Ofw== dependencies: "@babel/helper-annotate-as-pure" "^7.16.0" @@ -422,7 +422,7 @@ "@babel/plugin-proposal-unicode-property-regex@^7.16.0", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.16.0.tgz#890482dfc5ea378e42e19a71e709728cabf18612" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.16.0.tgz" integrity sha512-ti7IdM54NXv29cA4+bNNKEMS4jLMCbJgl+Drv+FgYy0erJLAxNAIXcNjNjrRZEcWq0xJHsNVwQezskMFpF8N9g== dependencies: "@babel/helper-create-regexp-features-plugin" "^7.16.0" @@ -430,133 +430,133 @@ "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz" integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-bigint@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz" integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-class-properties@^7.12.13", "@babel/plugin-syntax-class-properties@^7.8.3": version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz" integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== dependencies: "@babel/helper-plugin-utils" "^7.12.13" "@babel/plugin-syntax-class-static-block@^7.14.5": version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz" integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-dynamic-import@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz" integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-export-namespace-from@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz" integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q== dependencies: "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-import-meta@^7.8.3": version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz" integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== dependencies: "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-json-strings@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz" integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz" integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== dependencies: "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz" integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-numeric-separator@^7.10.4", "@babel/plugin-syntax-numeric-separator@^7.8.3": version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz" integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== dependencies: "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-object-rest-spread@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz" integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-optional-catch-binding@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz" integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-optional-chaining@^7.8.3": version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz" integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-private-property-in-object@^7.14.5": version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz" integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-top-level-await@^7.14.5", "@babel/plugin-syntax-top-level-await@^7.8.3": version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz" integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-typescript@^7.16.0", "@babel/plugin-syntax-typescript@^7.7.2": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.0.tgz#2feeb13d9334cc582ea9111d3506f773174179bb" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.0.tgz" integrity sha512-Xv6mEXqVdaqCBfJFyeab0fH2DnUoMsDmhamxsSi4j8nLd4Vtw213WMJr55xxqipC/YVWyPY3K0blJncPYji+dQ== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-transform-arrow-functions@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.0.tgz#951706f8b449c834ed07bd474c0924c944b95a8e" + resolved "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.0.tgz" integrity sha512-vIFb5250Rbh7roWARvCLvIJ/PtAU5Lhv7BtZ1u24COwpI9Ypjsh+bZcKk6rlIyalK+r0jOc1XQ8I4ovNxNrWrA== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-transform-async-to-generator@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.0.tgz#df12637f9630ddfa0ef9d7a11bc414d629d38604" + resolved "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.0.tgz" integrity sha512-PbIr7G9kR8tdH6g8Wouir5uVjklETk91GMVSUq+VaOgiinbCkBP6Q7NN/suM/QutZkMJMvcyAriogcYAdhg8Gw== dependencies: "@babel/helper-module-imports" "^7.16.0" @@ -565,21 +565,21 @@ "@babel/plugin-transform-block-scoped-functions@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.0.tgz#c618763233ad02847805abcac4c345ce9de7145d" + resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.0.tgz" integrity sha512-V14As3haUOP4ZWrLJ3VVx5rCnrYhMSHN/jX7z6FAt5hjRkLsb0snPCmJwSOML5oxkKO4FNoNv7V5hw/y2bjuvg== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-transform-block-scoping@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.16.0.tgz#bcf433fb482fe8c3d3b4e8a66b1c4a8e77d37c16" + resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.16.0.tgz" integrity sha512-27n3l67/R3UrXfizlvHGuTwsRIFyce3D/6a37GRxn28iyTPvNXaW4XvznexRh1zUNLPjbLL22Id0XQElV94ruw== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-transform-classes@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.16.0.tgz#54cf5ff0b2242c6573d753cd4bfc7077a8b282f5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.16.0.tgz" integrity sha512-HUxMvy6GtAdd+GKBNYDWCIA776byUQH8zjnfjxwT1P1ARv/wFu8eBDpmXQcLS/IwRtrxIReGiplOwMeyO7nsDQ== dependencies: "@babel/helper-annotate-as-pure" "^7.16.0" @@ -592,21 +592,21 @@ "@babel/plugin-transform-computed-properties@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.16.0.tgz#e0c385507d21e1b0b076d66bed6d5231b85110b7" + resolved "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.16.0.tgz" integrity sha512-63l1dRXday6S8V3WFY5mXJwcRAnPYxvFfTlt67bwV1rTyVTM5zrp0DBBb13Kl7+ehkCVwIZPumPpFP/4u70+Tw== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-transform-destructuring@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.16.0.tgz#ad3d7e74584ad5ea4eadb1e6642146c590dee33c" + resolved "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.16.0.tgz" integrity sha512-Q7tBUwjxLTsHEoqktemHBMtb3NYwyJPTJdM+wDwb0g8PZ3kQUIzNvwD5lPaqW/p54TXBc/MXZu9Jr7tbUEUM8Q== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-transform-dotall-regex@^7.16.0", "@babel/plugin-transform-dotall-regex@^7.4.4": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.0.tgz#50bab00c1084b6162d0a58a818031cf57798e06f" + resolved "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.0.tgz" integrity sha512-FXlDZfQeLILfJlC6I1qyEwcHK5UpRCFkaoVyA1nk9A1L1Yu583YO4un2KsLBsu3IJb4CUbctZks8tD9xPQubLw== dependencies: "@babel/helper-create-regexp-features-plugin" "^7.16.0" @@ -614,14 +614,14 @@ "@babel/plugin-transform-duplicate-keys@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.16.0.tgz#8bc2e21813e3e89e5e5bf3b60aa5fc458575a176" + resolved "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.16.0.tgz" integrity sha512-LIe2kcHKAZOJDNxujvmp6z3mfN6V9lJxubU4fJIGoQCkKe3Ec2OcbdlYP+vW++4MpxwG0d1wSDOJtQW5kLnkZQ== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-transform-exponentiation-operator@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.0.tgz#a180cd2881e3533cef9d3901e48dad0fbeff4be4" + resolved "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.0.tgz" integrity sha512-OwYEvzFI38hXklsrbNivzpO3fh87skzx8Pnqi4LoSYeav0xHlueSoCJrSgTPfnbyzopo5b3YVAJkFIcUpK2wsw== dependencies: "@babel/helper-builder-binary-assignment-operator-visitor" "^7.16.0" @@ -629,14 +629,14 @@ "@babel/plugin-transform-for-of@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.16.0.tgz#f7abaced155260e2461359bbc7c7248aca5e6bd2" + resolved "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.16.0.tgz" integrity sha512-5QKUw2kO+GVmKr2wMYSATCTTnHyscl6sxFRAY+rvN7h7WB0lcG0o4NoV6ZQU32OZGVsYUsfLGgPQpDFdkfjlJQ== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-transform-function-name@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.0.tgz#02e3699c284c6262236599f751065c5d5f1f400e" + resolved "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.0.tgz" integrity sha512-lBzMle9jcOXtSOXUpc7tvvTpENu/NuekNJVova5lCCWCV9/U1ho2HH2y0p6mBg8fPm/syEAbfaaemYGOHCY3mg== dependencies: "@babel/helper-function-name" "^7.16.0" @@ -644,21 +644,21 @@ "@babel/plugin-transform-literals@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.16.0.tgz#79711e670ffceb31bd298229d50f3621f7980cac" + resolved "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.16.0.tgz" integrity sha512-gQDlsSF1iv9RU04clgXqRjrPyyoJMTclFt3K1cjLmTKikc0s/6vE3hlDeEVC71wLTRu72Fq7650kABrdTc2wMQ== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-transform-member-expression-literals@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.0.tgz#5251b4cce01eaf8314403d21aedb269d79f5e64b" + resolved "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.0.tgz" integrity sha512-WRpw5HL4Jhnxw8QARzRvwojp9MIE7Tdk3ez6vRyUk1MwgjJN0aNpRoXainLR5SgxmoXx/vsXGZ6OthP6t/RbUg== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-transform-modules-amd@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.16.0.tgz#09abd41e18dcf4fd479c598c1cef7bd39eb1337e" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.16.0.tgz" integrity sha512-rWFhWbCJ9Wdmzln1NmSCqn7P0RAD+ogXG/bd9Kg5c7PKWkJtkiXmYsMBeXjDlzHpVTJ4I/hnjs45zX4dEv81xw== dependencies: "@babel/helper-module-transforms" "^7.16.0" @@ -667,7 +667,7 @@ "@babel/plugin-transform-modules-commonjs@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.0.tgz#add58e638c8ddc4875bd9a9ecb5c594613f6c922" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.0.tgz" integrity sha512-Dzi+NWqyEotgzk/sb7kgQPJQf7AJkQBWsVp1N6JWc1lBVo0vkElUnGdr1PzUBmfsCCN5OOFya3RtpeHk15oLKQ== dependencies: "@babel/helper-module-transforms" "^7.16.0" @@ -677,7 +677,7 @@ "@babel/plugin-transform-modules-systemjs@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.16.0.tgz#a92cf240afeb605f4ca16670453024425e421ea4" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.16.0.tgz" integrity sha512-yuGBaHS3lF1m/5R+6fjIke64ii5luRUg97N2wr+z1sF0V+sNSXPxXDdEEL/iYLszsN5VKxVB1IPfEqhzVpiqvg== dependencies: "@babel/helper-hoist-variables" "^7.16.0" @@ -688,7 +688,7 @@ "@babel/plugin-transform-modules-umd@^7.14.5", "@babel/plugin-transform-modules-umd@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.16.0.tgz#195f26c2ad6d6a391b70880effce18ce625e06a7" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.16.0.tgz" integrity sha512-nx4f6no57himWiHhxDM5pjwhae5vLpTK2zCnDH8+wNLJy0TVER/LJRHl2bkt6w9Aad2sPD5iNNoUpY3X9sTGDg== dependencies: "@babel/helper-module-transforms" "^7.16.0" @@ -696,21 +696,21 @@ "@babel/plugin-transform-named-capturing-groups-regex@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.16.0.tgz#d3db61cc5d5b97986559967cd5ea83e5c32096ca" + resolved "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.16.0.tgz" integrity sha512-LogN88uO+7EhxWc8WZuQ8vxdSyVGxhkh8WTC3tzlT8LccMuQdA81e9SGV6zY7kY2LjDhhDOFdQVxdGwPyBCnvg== dependencies: "@babel/helper-create-regexp-features-plugin" "^7.16.0" "@babel/plugin-transform-new-target@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.16.0.tgz#af823ab576f752215a49937779a41ca65825ab35" + resolved "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.16.0.tgz" integrity sha512-fhjrDEYv2DBsGN/P6rlqakwRwIp7rBGLPbrKxwh7oVt5NNkIhZVOY2GRV+ULLsQri1bDqwDWnU3vhlmx5B2aCw== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-transform-nullish-coalescing-operator@^7.23.4": version "7.23.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.4.tgz#45556aad123fc6e52189ea749e33ce090637346e" + resolved "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.4.tgz" integrity sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -718,7 +718,7 @@ "@babel/plugin-transform-object-super@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.0.tgz#fb20d5806dc6491a06296ac14ea8e8d6fedda72b" + resolved "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.0.tgz" integrity sha512-fds+puedQHn4cPLshoHcR1DTMN0q1V9ou0mUjm8whx9pGcNvDrVVrgw+KJzzCaiTdaYhldtrUps8DWVMgrSEyg== dependencies: "@babel/helper-plugin-utils" "^7.14.5" @@ -726,42 +726,42 @@ "@babel/plugin-transform-parameters@^7.16.0": version "7.16.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.16.3.tgz#fa9e4c874ee5223f891ee6fa8d737f4766d31d15" + resolved "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.16.3.tgz" integrity sha512-3MaDpJrOXT1MZ/WCmkOFo7EtmVVC8H4EUZVrHvFOsmwkk4lOjQj8rzv8JKUZV4YoQKeoIgk07GO+acPU9IMu/w== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-transform-property-literals@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.0.tgz#a95c552189a96a00059f6776dc4e00e3690c78d1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.0.tgz" integrity sha512-XLldD4V8+pOqX2hwfWhgwXzGdnDOThxaNTgqagOcpBgIxbUvpgU2FMvo5E1RyHbk756WYgdbS0T8y0Cj9FKkWQ== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-transform-regenerator@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.16.0.tgz#eaee422c84b0232d03aea7db99c97deeaf6125a4" + resolved "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.16.0.tgz" integrity sha512-JAvGxgKuwS2PihiSFaDrp94XOzzTUeDeOQlcKzVAyaPap7BnZXK/lvMDiubkPTdotPKOIZq9xWXWnggUMYiExg== dependencies: regenerator-transform "^0.14.2" "@babel/plugin-transform-reserved-words@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.16.0.tgz#fff4b9dcb19e12619394bda172d14f2d04c0379c" + resolved "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.16.0.tgz" integrity sha512-Dgs8NNCehHSvXdhEhln8u/TtJxfVwGYCgP2OOr5Z3Ar+B+zXicEOKNTyc+eca2cuEOMtjW6m9P9ijOt8QdqWkg== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-transform-shorthand-properties@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.0.tgz#090372e3141f7cc324ed70b3daf5379df2fa384d" + resolved "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.0.tgz" integrity sha512-iVb1mTcD8fuhSv3k99+5tlXu5N0v8/DPm2mO3WACLG6al1CGZH7v09HJyUb1TtYl/Z+KrM6pHSIJdZxP5A+xow== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-transform-spread@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.16.0.tgz#d21ca099bbd53ab307a8621e019a7bd0f40cdcfb" + resolved "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.16.0.tgz" integrity sha512-Ao4MSYRaLAQczZVp9/7E7QHsCuK92yHRrmVNRe/SlEJjhzivq0BSn8mEraimL8wizHZ3fuaHxKH0iwzI13GyGg== dependencies: "@babel/helper-plugin-utils" "^7.14.5" @@ -769,28 +769,28 @@ "@babel/plugin-transform-sticky-regex@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.0.tgz#c35ea31a02d86be485f6aa510184b677a91738fd" + resolved "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.0.tgz" integrity sha512-/ntT2NljR9foobKk4E/YyOSwcGUXtYWv5tinMK/3RkypyNBNdhHUaq6Orw5DWq9ZcNlS03BIlEALFeQgeVAo4Q== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-transform-template-literals@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.16.0.tgz#a8eced3a8e7b8e2d40ec4ec4548a45912630d302" + resolved "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.16.0.tgz" integrity sha512-Rd4Ic89hA/f7xUSJQk5PnC+4so50vBoBfxjdQAdvngwidM8jYIBVxBZ/sARxD4e0yMXRbJVDrYf7dyRtIIKT6Q== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-transform-typeof-symbol@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.16.0.tgz#8b19a244c6f8c9d668dca6a6f754ad6ead1128f2" + resolved "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.16.0.tgz" integrity sha512-++V2L8Bdf4vcaHi2raILnptTBjGEFxn5315YU+e8+EqXIucA+q349qWngCLpUYqqv233suJ6NOienIVUpS9cqg== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-transform-typescript@^7.16.0": version "7.16.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.16.1.tgz#cc0670b2822b0338355bc1b3d2246a42b8166409" + resolved "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.16.1.tgz" integrity sha512-NO4XoryBng06jjw/qWEU2LhcLJr1tWkhpMam/H4eas/CDKMX/b2/Ylb6EI256Y7+FVPCawwSM1rrJNOpDiz+Lg== dependencies: "@babel/helper-create-class-features-plugin" "^7.16.0" @@ -799,14 +799,14 @@ "@babel/plugin-transform-unicode-escapes@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.0.tgz#1a354064b4c45663a32334f46fa0cf6100b5b1f3" + resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.0.tgz" integrity sha512-VFi4dhgJM7Bpk8lRc5CMaRGlKZ29W9C3geZjt9beuzSUrlJxsNwX7ReLwaL6WEvsOf2EQkyIJEPtF8EXjB/g2A== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-transform-unicode-regex@^7.16.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.0.tgz#293b80950177c8c85aede87cef280259fb995402" + resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.0.tgz" integrity sha512-jHLK4LxhHjvCeZDWyA9c+P9XH1sOxRd1RO9xMtDVRAOND/PczPqizEtVdx4TQF/wyPaewqpT+tgQFYMnN/P94A== dependencies: "@babel/helper-create-regexp-features-plugin" "^7.16.0" @@ -814,7 +814,7 @@ "@babel/preset-env@^7.5.5": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.16.0.tgz#97228393d217560d6a1c6c56f0adb9d12bca67f5" + resolved "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.16.0.tgz" integrity sha512-cdTu/W0IrviamtnZiTfixPfIncr2M1VqRrkjzZWlr1B4TVYimCFK5jkyOdP4qw2MrlKHi+b3ORj6x8GoCew8Dg== dependencies: "@babel/compat-data" "^7.16.0" @@ -894,7 +894,7 @@ "@babel/preset-modules@^0.1.5": version "0.1.5" - resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.5.tgz#ef939d6e7f268827e1841638dc6ff95515e115d9" + resolved "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz" integrity sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" @@ -905,7 +905,7 @@ "@babel/preset-typescript@^7.6.0": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.16.0.tgz#b0b4f105b855fb3d631ec036cdc9d1ffd1fa5eac" + resolved "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.16.0.tgz" integrity sha512-txegdrZYgO9DlPbv+9QOVpMnKbOtezsLHWsnsRF4AjbSIsVaujrq1qg8HK0mxQpWv0jnejt0yEoW1uWpvbrDTg== dependencies: "@babel/helper-plugin-utils" "^7.14.5" @@ -914,14 +914,14 @@ "@babel/runtime@^7.15.4", "@babel/runtime@^7.8.4": version "7.16.3" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.3.tgz#b86f0db02a04187a3c17caa77de69840165d42d5" + resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz" integrity sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ== dependencies: regenerator-runtime "^0.13.4" "@babel/template@^7.16.0", "@babel/template@^7.3.3": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.0.tgz#d16a35ebf4cd74e202083356fab21dd89363ddd6" + resolved "https://registry.npmjs.org/@babel/template/-/template-7.16.0.tgz" integrity sha512-MnZdpFD/ZdYhXwiunMqqgyZyucaYsbL0IrjoGjaVhGilz+x8YB++kRfygSOIj1yOtWKPlx7NBp+9I1RQSgsd5A== dependencies: "@babel/code-frame" "^7.16.0" @@ -930,7 +930,7 @@ "@babel/traverse@^7.1.0", "@babel/traverse@^7.13.0", "@babel/traverse@^7.16.0", "@babel/traverse@^7.16.3", "@babel/traverse@^7.7.2": version "7.16.3" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.16.3.tgz#f63e8a938cc1b780f66d9ed3c54f532ca2d14787" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.3.tgz" integrity sha512-eolumr1vVMjqevCpwVO99yN/LoGL0EyHiLO5I043aYQvwOJ9eR5UsZSClHVCzfhBduMAsSzgA/6AyqPjNayJag== dependencies: "@babel/code-frame" "^7.16.0" @@ -945,7 +945,7 @@ "@babel/types@^7.0.0", "@babel/types@^7.16.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.16.0.tgz#db3b313804f96aadd0b776c4823e127ad67289ba" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz" integrity sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg== dependencies: "@babel/helper-validator-identifier" "^7.15.7" @@ -953,17 +953,17 @@ "@bcoe/v8-coverage@^0.2.3": version "0.2.3" - resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== "@discoveryjs/json-ext@^0.5.0": version "0.5.5" - resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.5.tgz#9283c9ce5b289a3c4f61c12757469e59377f81f3" + resolved "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.5.tgz" integrity sha512-6nFkfkmSeV/rqSaS4oWHgmpnYw194f6hmWF5is6b0J1naJZoiD0NTc9AiUwPHvWsowkjuHErCZT1wa0jg+BLIA== "@eslint/eslintrc@^0.4.3": version "0.4.3" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c" + resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz" integrity sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw== dependencies: ajv "^6.12.4" @@ -978,7 +978,7 @@ "@humanwhocodes/config-array@^0.5.0": version "0.5.0" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9" + resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz" integrity sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg== dependencies: "@humanwhocodes/object-schema" "^1.2.0" @@ -987,12 +987,12 @@ "@humanwhocodes/object-schema@^1.2.0": version "1.2.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" + resolved "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" - resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + resolved "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz" integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== dependencies: camelcase "^5.3.1" @@ -1003,12 +1003,12 @@ "@istanbuljs/schema@^0.1.2": version "0.1.3" - resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + resolved "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== "@jest/console@^27.3.1": version "27.3.1" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-27.3.1.tgz#e8ea3a475d3f8162f23d69efbfaa9cbe486bee93" + resolved "https://registry.npmjs.org/@jest/console/-/console-27.3.1.tgz" integrity sha512-RkFNWmv0iui+qsOr/29q9dyfKTTT5DCuP31kUwg7rmOKPT/ozLeGLKJKVIiOfbiKyleUZKIrHwhmiZWVe8IMdw== dependencies: "@jest/types" "^27.2.5" @@ -1020,7 +1020,7 @@ "@jest/core@^27.3.1": version "27.3.1" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-27.3.1.tgz#04992ef1b58b17c459afb87ab56d81e63d386925" + resolved "https://registry.npmjs.org/@jest/core/-/core-27.3.1.tgz" integrity sha512-DMNE90RR5QKx0EA+wqe3/TNEwiRpOkhshKNxtLxd4rt3IZpCt+RSL+FoJsGeblRZmqdK4upHA/mKKGPPRAifhg== dependencies: "@jest/console" "^27.3.1" @@ -1054,7 +1054,7 @@ "@jest/environment@^27.3.1": version "27.3.1" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-27.3.1.tgz#2182defbce8d385fd51c5e7c7050f510bd4c86b1" + resolved "https://registry.npmjs.org/@jest/environment/-/environment-27.3.1.tgz" integrity sha512-BCKCj4mOVLme6Tanoyc9k0ultp3pnmuyHw73UHRPeeZxirsU/7E3HC4le/VDb/SMzE1JcPnto+XBKFOcoiJzVw== dependencies: "@jest/fake-timers" "^27.3.1" @@ -1064,7 +1064,7 @@ "@jest/fake-timers@^27.3.1": version "27.3.1" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-27.3.1.tgz#1fad860ee9b13034762cdb94266e95609dfce641" + resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.3.1.tgz" integrity sha512-M3ZFgwwlqJtWZ+QkBG5NmC23A9w+A6ZxNsO5nJxJsKYt4yguBd3i8TpjQz5NfCX91nEve1KqD9RA2Q+Q1uWqoA== dependencies: "@jest/types" "^27.2.5" @@ -1076,7 +1076,7 @@ "@jest/globals@^27.3.1": version "27.3.1" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-27.3.1.tgz#ce1dfb03d379237a9da6c1b99ecfaca1922a5f9e" + resolved "https://registry.npmjs.org/@jest/globals/-/globals-27.3.1.tgz" integrity sha512-Q651FWiWQAIFiN+zS51xqhdZ8g9b88nGCobC87argAxA7nMfNQq0Q0i9zTfQYgLa6qFXk2cGANEqfK051CZ8Pg== dependencies: "@jest/environment" "^27.3.1" @@ -1085,7 +1085,7 @@ "@jest/reporters@^27.3.1": version "27.3.1" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-27.3.1.tgz#28b5c1f5789481e23788048fa822ed15486430b9" + resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-27.3.1.tgz" integrity sha512-m2YxPmL9Qn1emFVgZGEiMwDntDxRRQ2D58tiDQlwYTg5GvbFOKseYCcHtn0WsI8CG4vzPglo3nqbOiT8ySBT/w== dependencies: "@bcoe/v8-coverage" "^0.2.3" @@ -1116,7 +1116,7 @@ "@jest/source-map@^27.0.6": version "27.0.6" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-27.0.6.tgz#be9e9b93565d49b0548b86e232092491fb60551f" + resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-27.0.6.tgz" integrity sha512-Fek4mi5KQrqmlY07T23JRi0e7Z9bXTOOD86V/uS0EIW4PClvPDqZOyFlLpNJheS6QI0FNX1CgmPjtJ4EA/2M+g== dependencies: callsites "^3.0.0" @@ -1125,7 +1125,7 @@ "@jest/test-result@^27.3.1": version "27.3.1" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-27.3.1.tgz#89adee8b771877c69b3b8d59f52f29dccc300194" + resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-27.3.1.tgz" integrity sha512-mLn6Thm+w2yl0opM8J/QnPTqrfS4FoXsXF2WIWJb2O/GBSyResL71BRuMYbYRsGt7ELwS5JGcEcGb52BNrumgg== dependencies: "@jest/console" "^27.3.1" @@ -1135,7 +1135,7 @@ "@jest/test-sequencer@^27.3.1": version "27.3.1" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-27.3.1.tgz#4b3bde2dbb05ee74afdae608cf0768e3354683b1" + resolved "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.3.1.tgz" integrity sha512-siySLo07IMEdSjA4fqEnxfIX8lB/lWYsBPwNFtkOvsFQvmBrL3yj3k3uFNZv/JDyApTakRpxbKLJ3CT8UGVCrA== dependencies: "@jest/test-result" "^27.3.1" @@ -1145,7 +1145,7 @@ "@jest/transform@^27.3.1": version "27.3.1" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-27.3.1.tgz#ff80eafbeabe811e9025e4b6f452126718455220" + resolved "https://registry.npmjs.org/@jest/transform/-/transform-27.3.1.tgz" integrity sha512-3fSvQ02kuvjOI1C1ssqMVBKJpZf6nwoCiSu00zAKh5nrp3SptNtZy/8s5deayHnqxhjD9CWDJ+yqQwuQ0ZafXQ== dependencies: "@babel/core" "^7.1.0" @@ -1166,7 +1166,7 @@ "@jest/types@^27.2.5": version "27.2.5" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.2.5.tgz#420765c052605e75686982d24b061b4cbba22132" + resolved "https://registry.npmjs.org/@jest/types/-/types-27.2.5.tgz" integrity sha512-nmuM4VuDtCZcY+eTpw+0nvstwReMsjPoj7ZR80/BbixulhLaiX+fbv8oeLW8WZlJMcsGQsTmMKT/iTZu1Uy/lQ== dependencies: "@types/istanbul-lib-coverage" "^2.0.0" @@ -1177,7 +1177,7 @@ "@jridgewell/gen-mapping@^0.3.0": version "0.3.2" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" + resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz" integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== dependencies: "@jridgewell/set-array" "^1.0.1" @@ -1186,17 +1186,17 @@ "@jridgewell/resolve-uri@^3.0.3": version "3.1.0" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" + resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz" integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== "@jridgewell/set-array@^1.0.1": version "1.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz" integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== "@jridgewell/source-map@^0.3.2": version "0.3.2" - resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb" + resolved "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz" integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw== dependencies: "@jridgewell/gen-mapping" "^0.3.0" @@ -1204,12 +1204,12 @@ "@jridgewell/sourcemap-codec@^1.4.10": version "1.4.14" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz" integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== "@jridgewell/trace-mapping@^0.3.14", "@jridgewell/trace-mapping@^0.3.9": version "0.3.15" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz#aba35c48a38d3fd84b37e66c9c0423f9744f9774" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz" integrity sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g== dependencies: "@jridgewell/resolve-uri" "^3.0.3" @@ -1217,12 +1217,12 @@ "@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3": version "2.1.8-no-fsevents.3" - resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz#323d72dd25103d0c4fbdce89dadf574a787b1f9b" + resolved "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz" integrity sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ== "@nodelib/fs.scandir@2.1.5": version "2.1.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== dependencies: "@nodelib/fs.stat" "2.0.5" @@ -1230,12 +1230,12 @@ "@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": version "2.0.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== "@nodelib/fs.walk@^1.2.3": version "1.2.8" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + resolved "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz" integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== dependencies: "@nodelib/fs.scandir" "2.1.5" @@ -1243,31 +1243,31 @@ "@pabra/sortby@^1.0.1": version "1.0.2" - resolved "https://registry.yarnpkg.com/@pabra/sortby/-/sortby-1.0.2.tgz#9ef674da9e5048bbe6a08217ce0dede5411cdfc9" + resolved "https://registry.npmjs.org/@pabra/sortby/-/sortby-1.0.2.tgz" integrity sha512-gT4DWbGLlkctE5TwRT6/gZzHiVil1Ywg7FF5OmIilZbGCZqdYpbz98L2bqfhglwK3kj4fBZRMSCtzrjQ6J8jAw== "@sinonjs/commons@^1.7.0": version "1.8.3" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" + resolved "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz" integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ== dependencies: type-detect "4.0.8" "@sinonjs/fake-timers@^8.0.1": version "8.1.0" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz#3fdc2b6cb58935b21bfb8d1625eb1300484316e7" + resolved "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz" integrity sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg== dependencies: "@sinonjs/commons" "^1.7.0" "@tootallnate/once@1": version "1.1.2" - resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" + resolved "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz" integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== "@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14": version "7.1.16" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.16.tgz#bc12c74b7d65e82d29876b5d0baf5c625ac58702" + resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.16.tgz" integrity sha512-EAEHtisTMM+KaKwfWdC3oyllIqswlznXCIVCt7/oRNrh+DhgT4UEBNC/jlADNjvw7UnfbcdkGQcPVZ1xYiLcrQ== dependencies: "@babel/parser" "^7.1.0" @@ -1278,14 +1278,14 @@ "@types/babel__generator@*": version "7.6.3" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.3.tgz#f456b4b2ce79137f768aa130d2423d2f0ccfaba5" + resolved "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.3.tgz" integrity sha512-/GWCmzJWqV7diQW54smJZzWbSFf4QYtF71WCKhcx6Ru/tFyQIY2eiiITcCAeuPbNSvT9YCGkVMqqvSk2Z0mXiA== dependencies: "@babel/types" "^7.0.0" "@types/babel__template@*": version "7.4.1" - resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.1.tgz#3d1a48fd9d6c0edfd56f2ff578daed48f36c8969" + resolved "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz" integrity sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g== dependencies: "@babel/parser" "^7.1.0" @@ -1293,14 +1293,14 @@ "@types/babel__traverse@*", "@types/babel__traverse@^7.0.4", "@types/babel__traverse@^7.0.6": version "7.14.2" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.14.2.tgz#ffcd470bbb3f8bf30481678fb5502278ca833a43" + resolved "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.14.2.tgz" integrity sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA== dependencies: "@babel/types" "^7.3.0" "@types/body-parser@*": version "1.19.2" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" + resolved "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz" integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== dependencies: "@types/connect" "*" @@ -1308,14 +1308,14 @@ "@types/bonjour@^3.5.9": version "3.5.10" - resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.10.tgz#0f6aadfe00ea414edc86f5d106357cda9701e275" + resolved "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz" integrity sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw== dependencies: "@types/node" "*" "@types/connect-history-api-fallback@^1.3.5": version "1.3.5" - resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz#d1f7a8a09d0ed5a57aee5ae9c18ab9b803205dae" + resolved "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz" integrity sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw== dependencies: "@types/express-serve-static-core" "*" @@ -1323,14 +1323,14 @@ "@types/connect@*": version "3.4.35" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" + resolved "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz" integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== dependencies: "@types/node" "*" "@types/eslint-scope@^3.7.3": version "3.7.4" - resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.4.tgz#37fc1223f0786c39627068a12e94d6e6fc61de16" + resolved "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz" integrity sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA== dependencies: "@types/eslint" "*" @@ -1338,7 +1338,7 @@ "@types/eslint@*": version "8.4.6" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.4.6.tgz#7976f054c1bccfcf514bff0564c0c41df5c08207" + resolved "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.6.tgz" integrity sha512-/fqTbjxyFUaYNO7VcW5g+4npmqVACz1bB7RTHYuLj+PRjw9hrCwrUXVQFpChUS0JsyEFvMZ7U/PfmvWgxJhI9g== dependencies: "@types/estree" "*" @@ -1346,17 +1346,17 @@ "@types/estree@*": version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.0.tgz#5fb2e536c1ae9bf35366eed879e827fa59ca41c2" + resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz" integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ== "@types/estree@^0.0.51": version "0.0.51" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40" + resolved "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz" integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== "@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.18": version "4.17.28" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz#c47def9f34ec81dc6328d0b1b5303d1ec98d86b8" + resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz" integrity sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig== dependencies: "@types/node" "*" @@ -1365,7 +1365,7 @@ "@types/express@*": version "4.17.13" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" + resolved "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz" integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA== dependencies: "@types/body-parser" "*" @@ -1375,40 +1375,40 @@ "@types/graceful-fs@^4.1.2": version "4.1.5" - resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" + resolved "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz" integrity sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw== dependencies: "@types/node" "*" "@types/http-proxy@^1.17.5": version "1.17.7" - resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.7.tgz#30ea85cc2c868368352a37f0d0d3581e24834c6f" + resolved "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.7.tgz" integrity sha512-9hdj6iXH64tHSLTY+Vt2eYOGzSogC+JQ2H7bdPWkuh7KXP5qLllWx++t+K9Wk556c3dkDdPws/SpMRi0sdCT1w== dependencies: "@types/node" "*" "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": version "2.0.3" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762" + resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz" integrity sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw== "@types/istanbul-lib-report@*": version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" + resolved "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz" integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== dependencies: "@types/istanbul-lib-coverage" "*" "@types/istanbul-reports@^3.0.0": version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" + resolved "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz" integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== dependencies: "@types/istanbul-lib-report" "*" "@types/jest@^27.0.2": version "27.0.2" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-27.0.2.tgz#ac383c4d4aaddd29bbf2b916d8d105c304a5fcd7" + resolved "https://registry.npmjs.org/@types/jest/-/jest-27.0.2.tgz" integrity sha512-4dRxkS/AFX0c5XW6IPMNOydLn2tEhNhJV7DnYK+0bjoJZ+QTmfucBlihX7aoEsh/ocYtkLC73UbnBXBXIxsULA== dependencies: jest-diff "^27.0.0" @@ -1416,74 +1416,69 @@ "@types/json-schema@*", "@types/json-schema@^7.0.8": version "7.0.11" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" + resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz" integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== "@types/json-schema@^7.0.9": version "7.0.9" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" + resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz" integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== "@types/lodash@^4.14.175": version "4.14.176" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.176.tgz#641150fc1cda36fbfa329de603bbb175d7ee20c0" + resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.176.tgz" integrity sha512-xZmuPTa3rlZoIbtDUyJKZQimJV3bxCmzMIO2c9Pz9afyDro6kr7R79GwcB6mRhuoPmV2p1Vb66WOJH7F886WKQ== "@types/mime@^1": version "1.3.2" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" + resolved "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz" integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== -"@types/node@*": - version "18.7.7" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.7.tgz#2f7e3443fb434315ff594c49043486fe49937182" - integrity sha512-sTKYCtQmaUpsAT+AbUTKg0Ya0dYyh20t3TBQebWrGXQHFmkrEyeedok2/IpTthlJopPSbUoc1hAKoK6UeFRCZw== - -"@types/node@^12.7.1": +"@types/node@*", "@types/node@^12.7.1": version "12.20.37" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.37.tgz#abb38afa9d6e8a2f627a8cb52290b3c80fbe61ed" + resolved "https://registry.npmjs.org/@types/node/-/node-12.20.37.tgz" integrity sha512-i1KGxqcvJaLQali+WuypQnXwcplhtNtjs66eNsZpp2P2FL/trJJxx/VWsM0YCL2iMoIJrbXje48lvIQAQ4p2ZA== "@types/normalize-package-data@^2.4.0": version "2.4.1" - resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" + resolved "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz" integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw== "@types/parse-json@^4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" + resolved "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== "@types/prettier@^2.1.5": version "2.4.1" - resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.4.1.tgz#e1303048d5389563e130f5bdd89d37a99acb75eb" + resolved "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.1.tgz" integrity sha512-Fo79ojj3vdEZOHg3wR9ksAMRz4P3S5fDB5e/YWZiFnyFQI1WY2Vftu9XoXVVtJfxB7Bpce/QTqWSSntkz2Znrw== "@types/qs@*": version "6.9.7" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" + resolved "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz" integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== "@types/range-parser@*": version "1.2.4" - resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" + resolved "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz" integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== "@types/retry@^0.12.0": version "0.12.1" - resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.1.tgz#d8f1c0d0dc23afad6dc16a9e993a0865774b4065" + resolved "https://registry.npmjs.org/@types/retry/-/retry-0.12.1.tgz" integrity sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g== "@types/serve-index@^1.9.1": version "1.9.1" - resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.1.tgz#1b5e85370a192c01ec6cec4735cf2917337a6278" + resolved "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz" integrity sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg== dependencies: "@types/express" "*" "@types/serve-static@*": version "1.13.10" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.10.tgz#f5e0ce8797d2d7cc5ebeda48a52c96c4fa47a8d9" + resolved "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz" integrity sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ== dependencies: "@types/mime" "^1" @@ -1491,43 +1486,48 @@ "@types/sockjs@^0.3.33": version "0.3.33" - resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.33.tgz#570d3a0b99ac995360e3136fd6045113b1bd236f" + resolved "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz" integrity sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw== dependencies: "@types/node" "*" "@types/stack-utils@^2.0.0": version "2.0.1" - resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" + resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== "@types/throttle-debounce@^2.1.0": version "2.1.0" - resolved "https://registry.yarnpkg.com/@types/throttle-debounce/-/throttle-debounce-2.1.0.tgz#1c3df624bfc4b62f992d3012b84c56d41eab3776" + resolved "https://registry.npmjs.org/@types/throttle-debounce/-/throttle-debounce-2.1.0.tgz" integrity sha512-5eQEtSCoESnh2FsiLTxE121IiE60hnMqcb435fShf4bpLRjEu1Eoekht23y6zXS9Ts3l+Szu3TARnTsA0GkOkQ== +"@types/uuid@^9.0.8": + version "9.0.8" + resolved "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz" + integrity sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA== + "@types/ws@^8.2.2": version "8.2.2" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.2.2.tgz#7c5be4decb19500ae6b3d563043cd407bf366c21" + resolved "https://registry.npmjs.org/@types/ws/-/ws-8.2.2.tgz" integrity sha512-NOn5eIcgWLOo6qW8AcuLZ7G8PycXu0xTxxkS6Q18VWFxgPUSOwV0pBj2a/4viNZVu25i7RIB7GttdkAIUUXOOg== dependencies: "@types/node" "*" "@types/yargs-parser@*": version "20.2.1" - resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.1.tgz#3b9ce2489919d9e4fea439b76916abc34b2df129" + resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz" integrity sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw== "@types/yargs@^16.0.0": version "16.0.4" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-16.0.4.tgz#26aad98dd2c2a38e421086ea9ad42b9e51642977" + resolved "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz" integrity sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw== dependencies: "@types/yargs-parser" "*" "@typescript-eslint/eslint-plugin@^5.38.1": version "5.38.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.38.1.tgz#9f05d42fa8fb9f62304cc2f5c2805e03c01c2620" + resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.38.1.tgz" integrity sha512-ky7EFzPhqz3XlhS7vPOoMDaQnQMn+9o5ICR9CPr/6bw8HrFkzhMSxuA3gRfiJVvs7geYrSeawGJjZoZQKCOglQ== dependencies: "@typescript-eslint/scope-manager" "5.38.1" @@ -1541,7 +1541,7 @@ "@typescript-eslint/parser@^5.38.1": version "5.38.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.38.1.tgz#c577f429f2c32071b92dff4af4f5fbbbd2414bd0" + resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.38.1.tgz" integrity sha512-LDqxZBVFFQnQRz9rUZJhLmox+Ep5kdUmLatLQnCRR6523YV+XhRjfYzStQ4MheFA8kMAfUlclHSbu+RKdRwQKw== dependencies: "@typescript-eslint/scope-manager" "5.38.1" @@ -1551,7 +1551,7 @@ "@typescript-eslint/scope-manager@5.38.1": version "5.38.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.38.1.tgz#f87b289ef8819b47189351814ad183e8801d5764" + resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.38.1.tgz" integrity sha512-BfRDq5RidVU3RbqApKmS7RFMtkyWMM50qWnDAkKgQiezRtLKsoyRKIvz1Ok5ilRWeD9IuHvaidaLxvGx/2eqTQ== dependencies: "@typescript-eslint/types" "5.38.1" @@ -1559,7 +1559,7 @@ "@typescript-eslint/type-utils@5.38.1": version "5.38.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.38.1.tgz#7f038fcfcc4ade4ea76c7c69b2aa25e6b261f4c1" + resolved "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.38.1.tgz" integrity sha512-UU3j43TM66gYtzo15ivK2ZFoDFKKP0k03MItzLdq0zV92CeGCXRfXlfQX5ILdd4/DSpHkSjIgLLLh1NtkOJOAw== dependencies: "@typescript-eslint/typescript-estree" "5.38.1" @@ -1569,17 +1569,17 @@ "@typescript-eslint/types@4.33.0": version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.33.0.tgz#a1e59036a3b53ae8430ceebf2a919dc7f9af6d72" + resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.33.0.tgz" integrity sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ== "@typescript-eslint/types@5.38.1": version "5.38.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.38.1.tgz#74f9d6dcb8dc7c58c51e9fbc6653ded39e2e225c" + resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.38.1.tgz" integrity sha512-QTW1iHq1Tffp9lNfbfPm4WJabbvpyaehQ0SrvVK2yfV79SytD9XDVxqiPvdrv2LK7DGSFo91TB2FgWanbJAZXg== "@typescript-eslint/typescript-estree@5.38.1": version "5.38.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.38.1.tgz#657d858d5d6087f96b638ee383ee1cff52605a1e" + resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.38.1.tgz" integrity sha512-99b5e/Enoe8fKMLdSuwrfH/C0EIbpUWmeEKHmQlGZb8msY33qn1KlkFww0z26o5Omx7EVjzVDCWEfrfCDHfE7g== dependencies: "@typescript-eslint/types" "5.38.1" @@ -1592,7 +1592,7 @@ "@typescript-eslint/typescript-estree@^4.8.2": version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz#0dfb51c2908f68c5c08d82aefeaf166a17c24609" + resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz" integrity sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA== dependencies: "@typescript-eslint/types" "4.33.0" @@ -1605,7 +1605,7 @@ "@typescript-eslint/utils@5.38.1": version "5.38.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.38.1.tgz#e3ac37d7b33d1362bb5adf4acdbe00372fb813ef" + resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.38.1.tgz" integrity sha512-oIuUiVxPBsndrN81oP8tXnFa/+EcZ03qLqPDfSZ5xIJVm7A9V0rlkQwwBOAGtrdN70ZKDlKv+l1BeT4eSFxwXA== dependencies: "@types/json-schema" "^7.0.9" @@ -1617,7 +1617,7 @@ "@typescript-eslint/visitor-keys@4.33.0": version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz#2a22f77a41604289b7a186586e9ec48ca92ef1dd" + resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz" integrity sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg== dependencies: "@typescript-eslint/types" "4.33.0" @@ -1625,7 +1625,7 @@ "@typescript-eslint/visitor-keys@5.38.1": version "5.38.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.38.1.tgz#508071bfc6b96d194c0afe6a65ad47029059edbc" + resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.38.1.tgz" integrity sha512-bSHr1rRxXt54+j2n4k54p4fj8AHJ49VDWtjpImOpzQj4qjAiOpPni+V1Tyajh19Api1i844F757cur8wH3YvOA== dependencies: "@typescript-eslint/types" "5.38.1" @@ -1633,7 +1633,7 @@ "@webassemblyjs/ast@1.11.1": version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" + resolved "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz" integrity sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw== dependencies: "@webassemblyjs/helper-numbers" "1.11.1" @@ -1641,22 +1641,22 @@ "@webassemblyjs/floating-point-hex-parser@1.11.1": version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz#f6c61a705f0fd7a6aecaa4e8198f23d9dc179e4f" + resolved "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz" integrity sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ== "@webassemblyjs/helper-api-error@1.11.1": version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz#1a63192d8788e5c012800ba6a7a46c705288fd16" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz" integrity sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg== "@webassemblyjs/helper-buffer@1.11.1": version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz#832a900eb444884cde9a7cad467f81500f5e5ab5" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz" integrity sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA== "@webassemblyjs/helper-numbers@1.11.1": version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz#64d81da219fbbba1e3bd1bfc74f6e8c4e10a62ae" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz" integrity sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ== dependencies: "@webassemblyjs/floating-point-hex-parser" "1.11.1" @@ -1665,12 +1665,12 @@ "@webassemblyjs/helper-wasm-bytecode@1.11.1": version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz#f328241e41e7b199d0b20c18e88429c4433295e1" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz" integrity sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q== "@webassemblyjs/helper-wasm-section@1.11.1": version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz#21ee065a7b635f319e738f0dd73bfbda281c097a" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz" integrity sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg== dependencies: "@webassemblyjs/ast" "1.11.1" @@ -1680,26 +1680,26 @@ "@webassemblyjs/ieee754@1.11.1": version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz#963929e9bbd05709e7e12243a099180812992614" + resolved "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz" integrity sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ== dependencies: "@xtuc/ieee754" "^1.2.0" "@webassemblyjs/leb128@1.11.1": version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.1.tgz#ce814b45574e93d76bae1fb2644ab9cdd9527aa5" + resolved "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz" integrity sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw== dependencies: "@xtuc/long" "4.2.2" "@webassemblyjs/utf8@1.11.1": version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.1.tgz#d1f8b764369e7c6e6bae350e854dec9a59f0a3ff" + resolved "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz" integrity sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ== "@webassemblyjs/wasm-edit@1.11.1": version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz#ad206ebf4bf95a058ce9880a8c092c5dec8193d6" + resolved "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz" integrity sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA== dependencies: "@webassemblyjs/ast" "1.11.1" @@ -1713,7 +1713,7 @@ "@webassemblyjs/wasm-gen@1.11.1": version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz#86c5ea304849759b7d88c47a32f4f039ae3c8f76" + resolved "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz" integrity sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA== dependencies: "@webassemblyjs/ast" "1.11.1" @@ -1724,7 +1724,7 @@ "@webassemblyjs/wasm-opt@1.11.1": version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz#657b4c2202f4cf3b345f8a4c6461c8c2418985f2" + resolved "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz" integrity sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw== dependencies: "@webassemblyjs/ast" "1.11.1" @@ -1734,7 +1734,7 @@ "@webassemblyjs/wasm-parser@1.11.1": version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz#86ca734534f417e9bd3c67c7a1c75d8be41fb199" + resolved "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz" integrity sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA== dependencies: "@webassemblyjs/ast" "1.11.1" @@ -1746,7 +1746,7 @@ "@webassemblyjs/wast-printer@1.11.1": version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz#d0c73beda8eec5426f10ae8ef55cee5e7084c2f0" + resolved "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz" integrity sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg== dependencies: "@webassemblyjs/ast" "1.11.1" @@ -1754,39 +1754,39 @@ "@webpack-cli/configtest@^1.1.0": version "1.1.0" - resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-1.1.0.tgz#8342bef0badfb7dfd3b576f2574ab80c725be043" + resolved "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.1.0.tgz" integrity sha512-ttOkEkoalEHa7RaFYpM0ErK1xc4twg3Am9hfHhL7MVqlHebnkYd2wuI/ZqTDj0cVzZho6PdinY0phFZV3O0Mzg== "@webpack-cli/info@^1.4.0": version "1.4.0" - resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-1.4.0.tgz#b9179c3227ab09cbbb149aa733475fcf99430223" + resolved "https://registry.npmjs.org/@webpack-cli/info/-/info-1.4.0.tgz" integrity sha512-F6b+Man0rwE4n0409FyAJHStYA5OIZERxmnUfLVwv0mc0V1wLad3V7jqRlMkgKBeAq07jUvglacNaa6g9lOpuw== dependencies: envinfo "^7.7.3" "@webpack-cli/serve@^1.6.0": version "1.6.0" - resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.6.0.tgz#2c275aa05c895eccebbfc34cfb223c6e8bd591a2" + resolved "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.6.0.tgz" integrity sha512-ZkVeqEmRpBV2GHvjjUZqEai2PpUbuq8Bqd//vEYsp63J8WyexI8ppCqVS3Zs0QADf6aWuPdU+0XsPI647PVlQA== "@xtuc/ieee754@^1.2.0": version "1.2.0" - resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" + resolved "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz" integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== "@xtuc/long@4.2.2": version "4.2.2" - resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" + resolved "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== abab@^2.0.3, abab@^2.0.5: version "2.0.5" - resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" + resolved "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz" integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q== accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: version "1.3.7" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" + resolved "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz" integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== dependencies: mime-types "~2.1.24" @@ -1794,7 +1794,7 @@ accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: acorn-globals@^6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" + resolved "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz" integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg== dependencies: acorn "^7.1.1" @@ -1802,44 +1802,44 @@ acorn-globals@^6.0.0: acorn-import-assertions@^1.7.6: version "1.8.0" - resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9" + resolved "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz" integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw== acorn-jsx@^5.3.1: version "5.3.2" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== acorn-walk@^7.1.1: version "7.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" + resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz" integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== acorn@^7.1.1, acorn@^7.4.0: version "7.4.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + resolved "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== acorn@^8.2.4: version "8.5.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz" integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q== acorn@^8.5.0, acorn@^8.7.1: version "8.8.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz" integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== agent-base@6: version "6.0.2" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz" integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== dependencies: debug "4" aggregate-error@^3.0.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + resolved "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz" integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== dependencies: clean-stack "^2.0.0" @@ -1847,26 +1847,26 @@ aggregate-error@^3.0.0: ajv-formats@^2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" + resolved "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz" integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== dependencies: ajv "^8.0.0" ajv-keywords@^3.5.2: version "3.5.2" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" + resolved "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz" integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== ajv-keywords@^5.0.0: version "5.1.0" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" + resolved "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz" integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== dependencies: fast-deep-equal "^3.1.3" ajv@^6.10.0, ajv@^6.12.4, ajv@^6.12.5: version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== dependencies: fast-deep-equal "^3.1.1" @@ -1876,7 +1876,7 @@ ajv@^6.10.0, ajv@^6.12.4, ajv@^6.12.5: ajv@^8.0.0, ajv@^8.8.0: version "8.9.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.9.0.tgz#738019146638824dea25edcf299dcba1b0e7eb18" + resolved "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz" integrity sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ== dependencies: fast-deep-equal "^3.1.1" @@ -1886,7 +1886,7 @@ ajv@^8.0.0, ajv@^8.8.0: ajv@^8.0.1: version "8.7.1" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.7.1.tgz#52be6f1736b076074798124293618f132ad07a7e" + resolved "https://registry.npmjs.org/ajv/-/ajv-8.7.1.tgz" integrity sha512-gPpOObTO1QjbnN1sVMjJcp1TF9nggMfO4MBR5uQl6ZVTOaEPq5i4oq/6R9q2alMMPB3eg53wFv1RuJBLuxf3Hw== dependencies: fast-deep-equal "^3.1.1" @@ -1896,53 +1896,53 @@ ajv@^8.0.1: ansi-colors@^4.1.1: version "4.1.1" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + resolved "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz" integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== ansi-escapes@^4.2.1, ansi-escapes@^4.3.0: version "4.3.2" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz" integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== dependencies: type-fest "^0.21.3" ansi-html-community@^0.0.8: version "0.0.8" - resolved "https://registry.yarnpkg.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41" + resolved "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz" integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw== ansi-regex@^5.0.1: version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== ansi-regex@^6.0.1: version "6.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz" integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== ansi-styles@^3.2.1: version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz" integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== dependencies: color-convert "^1.9.0" ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== dependencies: color-convert "^2.0.1" ansi-styles@^5.0.0: version "5.2.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== anymatch@^3.0.3, anymatch@~3.1.2: version "3.1.2" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" + resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz" integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== dependencies: normalize-path "^3.0.0" @@ -1950,56 +1950,56 @@ anymatch@^3.0.3, anymatch@~3.1.2: app-module-path@^2.2.0: version "2.2.0" - resolved "https://registry.yarnpkg.com/app-module-path/-/app-module-path-2.2.0.tgz#641aa55dfb7d6a6f0a8141c4b9c0aa50b6c24dd5" + resolved "https://registry.npmjs.org/app-module-path/-/app-module-path-2.2.0.tgz" integrity sha1-ZBqlXft9am8KgUHEucCqULbCTdU= argparse@^1.0.7: version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + resolved "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz" integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== dependencies: sprintf-js "~1.0.2" array-flatten@1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + resolved "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz" integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= array-flatten@^2.1.0: version "2.1.2" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" + resolved "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz" integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== array-union@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== ast-module-types@^2.3.2, ast-module-types@^2.4.0, ast-module-types@^2.7.0, ast-module-types@^2.7.1: version "2.7.1" - resolved "https://registry.yarnpkg.com/ast-module-types/-/ast-module-types-2.7.1.tgz#3f7989ef8dfa1fdb82dfe0ab02bdfc7c77a57dd3" + resolved "https://registry.npmjs.org/ast-module-types/-/ast-module-types-2.7.1.tgz" integrity sha512-Rnnx/4Dus6fn7fTqdeLEAn5vUll5w7/vts0RN608yFa6si/rDOUonlIIiwugHBFWjylHjxm9owoSZn71KwG4gw== astral-regex@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + resolved "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz" integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== async@^2.6.2: version "2.6.4" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" + resolved "https://registry.npmjs.org/async/-/async-2.6.4.tgz" integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== dependencies: lodash "^4.17.14" asynckit@^0.4.0: version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= axios-mock-adapter@^1.22.0: version "1.22.0" - resolved "https://registry.yarnpkg.com/axios-mock-adapter/-/axios-mock-adapter-1.22.0.tgz#0f3e6be0fc9b55baab06f2d49c0b71157e7c053d" + resolved "https://registry.npmjs.org/axios-mock-adapter/-/axios-mock-adapter-1.22.0.tgz" integrity sha512-dmI0KbkyAhntUR05YY96qg2H6gg0XMl2+qTW0xmYg6Up+BFBAJYRLROMXRdDEL06/Wqwa0TJThAYvFtSFdRCZw== dependencies: fast-deep-equal "^3.1.3" @@ -2007,7 +2007,7 @@ axios-mock-adapter@^1.22.0: axios@^1.6.2: version "1.6.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.2.tgz#de67d42c755b571d3e698df1b6504cde9b0ee9f2" + resolved "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz" integrity sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A== dependencies: follow-redirects "^1.15.0" @@ -2016,7 +2016,7 @@ axios@^1.6.2: babel-jest@^27.3.1: version "27.3.1" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-27.3.1.tgz#0636a3404c68e07001e434ac4956d82da8a80022" + resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-27.3.1.tgz" integrity sha512-SjIF8hh/ir0peae2D6S6ZKRhUy7q/DnpH7k/V6fT4Bgs/LXXUztOpX4G2tCgq8mLo5HA9mN6NmlFMeYtKmIsTQ== dependencies: "@jest/transform" "^27.3.1" @@ -2030,14 +2030,14 @@ babel-jest@^27.3.1: babel-plugin-dynamic-import-node@^2.3.3: version "2.3.3" - resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" + resolved "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz" integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== dependencies: object.assign "^4.1.0" babel-plugin-istanbul@^6.0.0: version "6.1.1" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + resolved "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz" integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" @@ -2048,7 +2048,7 @@ babel-plugin-istanbul@^6.0.0: babel-plugin-jest-hoist@^27.2.0: version "27.2.0" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.2.0.tgz#79f37d43f7e5c4fdc4b2ca3e10cc6cf545626277" + resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.2.0.tgz" integrity sha512-TOux9khNKdi64mW+0OIhcmbAn75tTlzKhxmiNXevQaPbrBYK7YKjP1jl6NHTJ6XR5UgUrJbCnWlKVnJn29dfjw== dependencies: "@babel/template" "^7.3.3" @@ -2058,7 +2058,7 @@ babel-plugin-jest-hoist@^27.2.0: babel-plugin-module-resolver@^3.2.0: version "3.2.0" - resolved "https://registry.yarnpkg.com/babel-plugin-module-resolver/-/babel-plugin-module-resolver-3.2.0.tgz#ddfa5e301e3b9aa12d852a9979f18b37881ff5a7" + resolved "https://registry.npmjs.org/babel-plugin-module-resolver/-/babel-plugin-module-resolver-3.2.0.tgz" integrity sha512-tjR0GvSndzPew/Iayf4uICWZqjBwnlMWjSx6brryfQ81F9rxBVqwDJtFCV8oOs0+vJeefK9TmdZtkIFdFe1UnA== dependencies: find-babel-config "^1.1.0" @@ -2069,7 +2069,7 @@ babel-plugin-module-resolver@^3.2.0: babel-plugin-polyfill-corejs2@^0.2.3: version "0.2.3" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.2.3.tgz#6ed8e30981b062f8fe6aca8873a37ebcc8cc1c0f" + resolved "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.2.3.tgz" integrity sha512-NDZ0auNRzmAfE1oDDPW2JhzIMXUk+FFe2ICejmt5T4ocKgiQx3e0VCRx9NCAidcMtL2RUZaWtXnmjTCkx0tcbA== dependencies: "@babel/compat-data" "^7.13.11" @@ -2078,7 +2078,7 @@ babel-plugin-polyfill-corejs2@^0.2.3: babel-plugin-polyfill-corejs3@^0.3.0: version "0.3.0" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.3.0.tgz#fa7ca3d1ee9ddc6193600ffb632c9785d54918af" + resolved "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.3.0.tgz" integrity sha512-JLwi9vloVdXLjzACL80j24bG6/T1gYxwowG44dg6HN/7aTPdyPbJJidf6ajoA3RPHHtW0j9KMrSOLpIZpAnPpg== dependencies: "@babel/helper-define-polyfill-provider" "^0.2.4" @@ -2086,14 +2086,14 @@ babel-plugin-polyfill-corejs3@^0.3.0: babel-plugin-polyfill-regenerator@^0.2.3: version "0.2.3" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.2.3.tgz#2e9808f5027c4336c994992b48a4262580cb8d6d" + resolved "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.2.3.tgz" integrity sha512-JVE78oRZPKFIeUqFGrSORNzQnrDwZR16oiWeGM8ZyjBn2XAT5OjP+wXx5ESuo33nUsFUEJYjtklnsKbxW5L+7g== dependencies: "@babel/helper-define-polyfill-provider" "^0.2.4" babel-preset-current-node-syntax@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" + resolved "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz" integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== dependencies: "@babel/plugin-syntax-async-generators" "^7.8.4" @@ -2111,7 +2111,7 @@ babel-preset-current-node-syntax@^1.0.0: babel-preset-jest@^27.2.0: version "27.2.0" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-27.2.0.tgz#556bbbf340608fed5670ab0ea0c8ef2449fba885" + resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.2.0.tgz" integrity sha512-z7MgQ3peBwN5L5aCqBKnF6iqdlvZvFUQynEhu0J+X9nHLU72jO3iY331lcYrg+AssJ8q7xsv5/3AICzVmJ/wvg== dependencies: babel-plugin-jest-hoist "^27.2.0" @@ -2119,32 +2119,32 @@ babel-preset-jest@^27.2.0: balanced-match@^1.0.0: version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== base64-arraybuffer-es6@^0.7.0: version "0.7.0" - resolved "https://registry.yarnpkg.com/base64-arraybuffer-es6/-/base64-arraybuffer-es6-0.7.0.tgz#dbe1e6c87b1bf1ca2875904461a7de40f21abc86" + resolved "https://registry.npmjs.org/base64-arraybuffer-es6/-/base64-arraybuffer-es6-0.7.0.tgz" integrity sha512-ESyU/U1CFZDJUdr+neHRhNozeCv72Y7Vm0m1DCbjX3KBjT6eYocvAJlSk6+8+HkVwXlT1FNxhGW6q3UKAlCvvw== base64-js@^1.3.1: version "1.5.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== batch@0.6.1: version "0.6.1" - resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" + resolved "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz" integrity sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY= binary-extensions@^2.0.0: version "2.2.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== bl@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + resolved "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz" integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== dependencies: buffer "^5.5.0" @@ -2153,7 +2153,7 @@ bl@^4.1.0: body-parser@1.19.0: version "1.19.0" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" + resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz" integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== dependencies: bytes "3.1.0" @@ -2169,7 +2169,7 @@ body-parser@1.19.0: bonjour@^3.5.0: version "3.5.0" - resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5" + resolved "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz" integrity sha1-jokKGD2O6aI5OzhExpGkK897yfU= dependencies: array-flatten "^2.1.0" @@ -2181,7 +2181,7 @@ bonjour@^3.5.0: brace-expansion@^1.1.7: version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== dependencies: balanced-match "^1.0.0" @@ -2189,19 +2189,19 @@ brace-expansion@^1.1.7: braces@^3.0.1, braces@~3.0.2: version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + resolved "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== dependencies: fill-range "^7.0.1" browser-process-hrtime@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" + resolved "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz" integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== browserslist@^4.14.5: version "4.21.3" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.3.tgz#5df277694eb3c48bc5c4b05af3e8b7e09c5a6d1a" + resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.21.3.tgz" integrity sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ== dependencies: caniuse-lite "^1.0.30001370" @@ -2211,7 +2211,7 @@ browserslist@^4.14.5: browserslist@^4.17.5, browserslist@^4.17.6: version "4.17.6" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.17.6.tgz#c76be33e7786b497f66cad25a73756c8b938985d" + resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.17.6.tgz" integrity sha512-uPgz3vyRTlEiCv4ee9KlsKgo2V6qPk7Jsn0KAn2OBqbqKo3iNcPEC1Ti6J4dwnz+aIRfEEEuOzC9IBk8tXUomw== dependencies: caniuse-lite "^1.0.30001274" @@ -2222,31 +2222,31 @@ browserslist@^4.17.5, browserslist@^4.17.6: bs-logger@0.x: version "0.2.6" - resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + resolved "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz" integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== dependencies: fast-json-stable-stringify "2.x" bser@2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + resolved "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz" integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== dependencies: node-int64 "^0.4.0" buffer-from@^1.0.0: version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== buffer-indexof@^1.0.0: version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" + resolved "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz" integrity sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g== buffer@^5.5.0: version "5.7.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== dependencies: base64-js "^1.3.1" @@ -2254,7 +2254,7 @@ buffer@^5.5.0: buffer@^6.0.3: version "6.0.3" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz" integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== dependencies: base64-js "^1.3.1" @@ -2262,17 +2262,17 @@ buffer@^6.0.3: bytes@3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" + resolved "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz" integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= bytes@3.1.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" + resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz" integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== call-bind@^1.0.0, call-bind@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz" integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== dependencies: function-bind "^1.1.1" @@ -2280,46 +2280,46 @@ call-bind@^1.0.0, call-bind@^1.0.2: caller-callsite@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" + resolved "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz" integrity sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ= dependencies: callsites "^2.0.0" caller-path@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4" + resolved "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz" integrity sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ= dependencies: caller-callsite "^2.0.0" callsites@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" + resolved "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz" integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA= callsites@^3.0.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== camelcase@^5.3.1: version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== camelcase@^6.2.0: version "6.2.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz" integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== caniuse-lite@^1.0.30001274, caniuse-lite@^1.0.30001370: version "1.0.30001378" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001378.tgz#3d2159bf5a8f9ca093275b0d3ecc717b00f27b67" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001378.tgz" integrity sha512-JVQnfoO7FK7WvU4ZkBRbPjaot4+YqxogSDosHv0Hv5mWpUESmN+UubMU6L/hGz8QlQ2aY5U0vR6MOs6j/CXpNA== chalk@^2.0.0, chalk@^2.4.2: version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== dependencies: ansi-styles "^3.2.1" @@ -2328,7 +2328,7 @@ chalk@^2.0.0, chalk@^2.4.2: chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1: version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== dependencies: ansi-styles "^4.1.0" @@ -2336,12 +2336,12 @@ chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1: char-regex@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + resolved "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz" integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== chokidar@^3.4.0, chokidar@^3.5.2: version "3.5.2" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" + resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz" integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ== dependencies: anymatch "~3.1.2" @@ -2356,44 +2356,44 @@ chokidar@^3.4.0, chokidar@^3.5.2: chrome-trace-event@^1.0.2: version "1.0.3" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" + resolved "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz" integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== ci-info@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" + resolved "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz" integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== ci-info@^3.2.0: version "3.2.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.2.0.tgz#2876cb948a498797b5236f0095bc057d0dca38b6" + resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.2.0.tgz" integrity sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A== cjs-module-lexer@^1.0.0: version "1.2.2" - resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" + resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz" integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== clean-stack@^2.0.0: version "2.2.0" - resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + resolved "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== cli-cursor@^3.1.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + resolved "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz" integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== dependencies: restore-cursor "^3.1.0" cli-spinners@^2.5.0: version "2.6.1" - resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.6.1.tgz#adc954ebe281c37a6319bfa401e6dd2488ffb70d" + resolved "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.1.tgz" integrity sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g== cli-truncate@2.1.0, cli-truncate@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" + resolved "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz" integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== dependencies: slice-ansi "^3.0.0" @@ -2401,7 +2401,7 @@ cli-truncate@2.1.0, cli-truncate@^2.1.0: cliui@^7.0.2: version "7.0.4" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + resolved "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz" integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== dependencies: string-width "^4.2.0" @@ -2410,7 +2410,7 @@ cliui@^7.0.2: clone-deep@^4.0.1: version "4.0.1" - resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" + resolved "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz" integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== dependencies: is-plain-object "^2.0.4" @@ -2419,100 +2419,100 @@ clone-deep@^4.0.1: clone@^1.0.2: version "1.0.4" - resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" + resolved "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz" integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= clone@^2.1.2: version "2.1.2" - resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" + resolved "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz" integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= co@^4.6.0: version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + resolved "https://registry.npmjs.org/co/-/co-4.6.0.tgz" integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= collect-v8-coverage@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" + resolved "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz" integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== color-convert@^1.9.0: version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== dependencies: color-name "1.1.3" color-convert@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== dependencies: color-name "~1.1.4" color-name@1.1.3: version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= color-name@^1.1.4, color-name@~1.1.4: version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== colorette@^1.4.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.4.0.tgz#5190fbb87276259a86ad700bff2c6d6faa3fca40" + resolved "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz" integrity sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g== colorette@^2.0.10, colorette@^2.0.14, colorette@^2.0.16: version "2.0.16" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.16.tgz#713b9af84fdb000139f04546bd4a93f62a5085da" + resolved "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz" integrity sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g== combined-stream@^1.0.8: version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== dependencies: delayed-stream "~1.0.0" commander@^2.16.0, commander@^2.20.0, commander@^2.20.3, commander@^2.8.1: version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== commander@^4.0.1: version "4.1.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" + resolved "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz" integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== commander@^7.0.0, commander@^7.2.0: version "7.2.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" + resolved "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz" integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== commander@^8.2.0: version "8.3.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" + resolved "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz" integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== commondir@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + resolved "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz" integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= compressible@~2.0.16: version "2.0.18" - resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" + resolved "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz" integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== dependencies: mime-db ">= 1.43.0 < 2" compression@^1.7.4: version "1.7.4" - resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" + resolved "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz" integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== dependencies: accepts "~1.3.5" @@ -2525,12 +2525,12 @@ compression@^1.7.4: concat-map@0.0.1: version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= concurrently@^6.3.0: version "6.3.0" - resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-6.3.0.tgz#63128cb4a6ed54d3c0ed8528728590a5fe54582a" + resolved "https://registry.npmjs.org/concurrently/-/concurrently-6.3.0.tgz" integrity sha512-k4k1jQGHHKsfbqzkUszVf29qECBrkvBKkcPJEUDTyVR7tZd1G/JOfnst4g1sYbFvJ4UjHZisj1aWQR8yLKpGPw== dependencies: chalk "^4.1.0" @@ -2544,41 +2544,41 @@ concurrently@^6.3.0: connect-history-api-fallback@^1.6.0: version "1.6.0" - resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc" + resolved "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz" integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg== content-disposition@0.5.3: version "0.5.3" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" + resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz" integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== dependencies: safe-buffer "5.1.2" content-type@~1.0.4: version "1.0.4" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + resolved "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== convert-source-map@^1.1.0, convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: version "1.8.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" + resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz" integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== dependencies: safe-buffer "~5.1.1" cookie-signature@1.0.6: version "1.0.6" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + resolved "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz" integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= cookie@0.4.0: version "0.4.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" + resolved "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz" integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== core-js-compat@^3.18.0, core-js-compat@^3.19.0: version "3.19.1" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.19.1.tgz#fe598f1a9bf37310d77c3813968e9f7c7bb99476" + resolved "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.19.1.tgz" integrity sha512-Q/VJ7jAF/y68+aUsQJ/afPOewdsGkDtcMb40J8MbuWKlK3Y+wtHq8bTHKPj2WKWLIqmS5JhHs4CzHtz6pT2W6g== dependencies: browserslist "^4.17.6" @@ -2586,12 +2586,12 @@ core-js-compat@^3.18.0, core-js-compat@^3.19.0: core-util-is@~1.0.0: version "1.0.3" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== cosmiconfig@^5.2.1: version "5.2.1" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" + resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz" integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA== dependencies: import-fresh "^2.0.0" @@ -2601,7 +2601,7 @@ cosmiconfig@^5.2.1: cosmiconfig@^7.0.1: version "7.0.1" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d" + resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz" integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ== dependencies: "@types/parse-json" "^4.0.0" @@ -2612,7 +2612,7 @@ cosmiconfig@^7.0.1: cross-spawn@^6.0.0: version "6.0.5" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz" integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== dependencies: nice-try "^1.0.4" @@ -2623,7 +2623,7 @@ cross-spawn@^6.0.0: cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== dependencies: path-key "^3.1.0" @@ -2632,24 +2632,24 @@ cross-spawn@^7.0.2, cross-spawn@^7.0.3: cssom@^0.4.4: version "0.4.4" - resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" + resolved "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz" integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw== cssom@~0.3.6: version "0.3.8" - resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + resolved "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz" integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== cssstyle@^2.3.0: version "2.3.0" - resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" + resolved "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz" integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== dependencies: cssom "~0.3.6" data-urls@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" + resolved "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz" integrity sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ== dependencies: abab "^2.0.3" @@ -2658,57 +2658,57 @@ data-urls@^2.0.0: date-fns@^2.16.1: version "2.25.0" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.25.0.tgz#8c5c8f1d958be3809a9a03f4b742eba894fc5680" + resolved "https://registry.npmjs.org/date-fns/-/date-fns-2.25.0.tgz" integrity sha512-ovYRFnTrbGPD4nqaEqescPEv1mNwvt+UTqI3Ay9SzNtey9NZnYu6E2qCcBBgJ6/2VF1zGGygpyTDITqpQQ5e+w== debug@2.6.9: version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== dependencies: ms "2.0.0" debug@4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: version "4.3.2" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz" integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== dependencies: ms "2.1.2" debug@^3.1.1: version "3.2.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz" integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== dependencies: ms "^2.1.1" debug@^4.3.4: version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" decimal.js@^10.2.1: version "10.3.1" - resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783" + resolved "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz" integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ== decomment@^0.9.3: version "0.9.5" - resolved "https://registry.yarnpkg.com/decomment/-/decomment-0.9.5.tgz#61753c80b8949620eb6bc3f8246cc0e2720ceac1" + resolved "https://registry.npmjs.org/decomment/-/decomment-0.9.5.tgz" integrity sha512-h0TZ8t6Dp49duwyDHo3iw67mnh9/UpFiSSiOb5gDK1sqoXzrfX/SQxIUQd2R2QEiSnqib0KF2fnKnGfAhAs6lg== dependencies: esprima "4.0.1" dedent@^0.7.0: version "0.7.0" - resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" + resolved "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz" integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= deep-equal@^1.0.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" + resolved "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz" integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g== dependencies: is-arguments "^1.0.4" @@ -2720,48 +2720,48 @@ deep-equal@^1.0.1: deep-extend@^0.6.0: version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + resolved "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== deep-is@^0.1.3, deep-is@~0.1.3: version "0.1.4" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== deepmerge@^4.2.2: version "4.2.2" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" + resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz" integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== default-gateway@^6.0.3: version "6.0.3" - resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-6.0.3.tgz#819494c888053bdb743edbf343d6cdf7f2943a71" + resolved "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz" integrity sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg== dependencies: execa "^5.0.0" defaults@^1.0.3: version "1.0.3" - resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" + resolved "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz" integrity sha1-xlYFHpgX2f8I7YgUd/P+QBnz730= dependencies: clone "^1.0.2" define-lazy-prop@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" + resolved "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz" integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== define-properties@^1.1.3: version "1.1.3" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + resolved "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz" integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== dependencies: object-keys "^1.0.12" del@^6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/del/-/del-6.0.0.tgz#0b40d0332cea743f1614f818be4feb717714c952" + resolved "https://registry.npmjs.org/del/-/del-6.0.0.tgz" integrity sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ== dependencies: globby "^11.0.1" @@ -2775,17 +2775,17 @@ del@^6.0.0: delayed-stream@~1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= depd@~1.1.2: version "1.1.2" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz" integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= dependency-tree@^8.1.1: version "8.1.2" - resolved "https://registry.yarnpkg.com/dependency-tree/-/dependency-tree-8.1.2.tgz#c9e652984f53bd0239bc8a3e50cbd52f05b2e770" + resolved "https://registry.npmjs.org/dependency-tree/-/dependency-tree-8.1.2.tgz" integrity sha512-c4CL1IKxkKng0oT5xrg4uNiiMVFqTGOXqHSFx7XEFdgSsp6nw3AGGruICppzJUrfad/r7GLqt26rmWU4h4j39A== dependencies: commander "^2.20.3" @@ -2796,22 +2796,22 @@ dependency-tree@^8.1.1: destroy@~1.0.4: version "1.0.4" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" + resolved "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz" integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= detect-newline@^3.0.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + resolved "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== detect-node@^2.0.4: version "2.1.0" - resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" + resolved "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz" integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== detective-amd@^3.0.1, detective-amd@^3.1.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/detective-amd/-/detective-amd-3.1.0.tgz#92daee3214a0ca4522646cf333cac90a3fca6373" + resolved "https://registry.npmjs.org/detective-amd/-/detective-amd-3.1.0.tgz" integrity sha512-G7wGWT6f0VErjUkE2utCm7IUshT7nBh7aBBH2VBOiY9Dqy2DMens5iiOvYCuhstoIxRKLrnOvVAz4/EyPIAjnw== dependencies: ast-module-types "^2.7.0" @@ -2821,7 +2821,7 @@ detective-amd@^3.0.1, detective-amd@^3.1.0: detective-cjs@^3.1.1: version "3.1.1" - resolved "https://registry.yarnpkg.com/detective-cjs/-/detective-cjs-3.1.1.tgz#18da3e39a002d2098a1123d45ce1de1b0d9045a0" + resolved "https://registry.npmjs.org/detective-cjs/-/detective-cjs-3.1.1.tgz" integrity sha512-JQtNTBgFY6h8uT6pgph5QpV3IyxDv+z3qPk/FZRDT9TlFfm5dnRtpH39WtQEr1khqsUxVqXzKjZHpdoQvQbllg== dependencies: ast-module-types "^2.4.0" @@ -2829,14 +2829,14 @@ detective-cjs@^3.1.1: detective-es6@^2.2.0: version "2.2.0" - resolved "https://registry.yarnpkg.com/detective-es6/-/detective-es6-2.2.0.tgz#8f2baba3f8cd90a5cfd748f5ac436f0158ed2585" + resolved "https://registry.npmjs.org/detective-es6/-/detective-es6-2.2.0.tgz" integrity sha512-fSpNY0SLER7/sVgQZ1NxJPwmc9uCTzNgdkQDhAaj8NPYwr7Qji9QBcmbNvtMCnuuOGMuKn3O7jv0An+/WRWJZQ== dependencies: node-source-walk "^4.0.0" detective-less@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/detective-less/-/detective-less-1.0.2.tgz#a68af9ca5f69d74b7d0aa190218b211d83b4f7e3" + resolved "https://registry.npmjs.org/detective-less/-/detective-less-1.0.2.tgz" integrity sha512-Rps1xDkEEBSq3kLdsdnHZL1x2S4NGDcbrjmd4q+PykK5aJwDdP5MBgrJw1Xo+kyUHuv3JEzPqxr+Dj9ryeDRTA== dependencies: debug "^4.0.0" @@ -2845,7 +2845,7 @@ detective-less@^1.0.2: detective-postcss@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/detective-postcss/-/detective-postcss-4.0.0.tgz#24e69b465e5fefe7a6afd05f7e894e34595dbf51" + resolved "https://registry.npmjs.org/detective-postcss/-/detective-postcss-4.0.0.tgz" integrity sha512-Fwc/g9VcrowODIAeKRWZfVA/EufxYL7XfuqJQFroBKGikKX83d2G7NFw6kDlSYGG3LNQIyVa+eWv1mqre+v4+A== dependencies: debug "^4.1.1" @@ -2855,7 +2855,7 @@ detective-postcss@^4.0.0: detective-postcss@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/detective-postcss/-/detective-postcss-5.0.0.tgz#7d39bde17a280e26d0b43130fd735a4a75786fb0" + resolved "https://registry.npmjs.org/detective-postcss/-/detective-postcss-5.0.0.tgz" integrity sha512-IBmim4GTEmZJDBOAoNFBskzNryTmYpBq+CQGghKnSGkoGWascE8iEo98yA+ZM4N5slwGjCr/NxCm+Kzg+q3tZg== dependencies: debug "^4.3.1" @@ -2865,7 +2865,7 @@ detective-postcss@^5.0.0: detective-sass@^3.0.1: version "3.0.1" - resolved "https://registry.yarnpkg.com/detective-sass/-/detective-sass-3.0.1.tgz#496b819efd1f5c4dd3f0e19b43a8634bdd6927c4" + resolved "https://registry.npmjs.org/detective-sass/-/detective-sass-3.0.1.tgz" integrity sha512-oSbrBozRjJ+QFF4WJFbjPQKeakoaY1GiR380NPqwdbWYd5wfl5cLWv0l6LsJVqrgWfFN1bjFqSeo32Nxza8Lbw== dependencies: debug "^4.1.1" @@ -2874,7 +2874,7 @@ detective-sass@^3.0.1: detective-scss@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/detective-scss/-/detective-scss-2.0.1.tgz#06f8c21ae6dedad1fccc26d544892d968083eaf8" + resolved "https://registry.npmjs.org/detective-scss/-/detective-scss-2.0.1.tgz" integrity sha512-VveyXW4WQE04s05KlJ8K0bG34jtHQVgTc9InspqoQxvnelj/rdgSAy7i2DXAazyQNFKlWSWbS+Ro2DWKFOKTPQ== dependencies: debug "^4.1.1" @@ -2883,12 +2883,12 @@ detective-scss@^2.0.1: detective-stylus@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/detective-stylus/-/detective-stylus-1.0.0.tgz#50aee7db8babb990381f010c63fabba5b58e54cd" + resolved "https://registry.npmjs.org/detective-stylus/-/detective-stylus-1.0.0.tgz" integrity sha1-UK7n24uruZA4HwEMY/q7pbWOVM0= detective-typescript@^7.0.0: version "7.0.0" - resolved "https://registry.yarnpkg.com/detective-typescript/-/detective-typescript-7.0.0.tgz#8c8917f2e51d9e4ee49821abf759ff512dd897f2" + resolved "https://registry.npmjs.org/detective-typescript/-/detective-typescript-7.0.0.tgz" integrity sha512-y/Ev98AleGvl43YKTNcA2Q+lyFmsmCfTTNWy4cjEJxoLkbobcXtRS0Kvx06daCgr2GdtlwLfNzL553BkktfJoA== dependencies: "@typescript-eslint/typescript-estree" "^4.8.2" @@ -2898,24 +2898,24 @@ detective-typescript@^7.0.0: diff-sequences@^27.0.6: version "27.0.6" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.0.6.tgz#3305cb2e55a033924054695cc66019fd7f8e5723" + resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.0.6.tgz" integrity sha512-ag6wfpBFyNXZ0p8pcuIDS//D8H062ZQJ3fzYxjpmeKjnz8W4pekL3AI8VohmyZmsWW2PWaHgjsmqR6L13101VQ== dir-glob@^3.0.1: version "3.0.1" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz" integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== dependencies: path-type "^4.0.0" dns-equal@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" + resolved "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz" integrity sha1-s55/HabrCnW6nBcySzR1PEfgZU0= dns-packet@^1.3.1: version "1.3.4" - resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.4.tgz#e3455065824a2507ba886c55a89963bb107dec6f" + resolved "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.4.tgz" integrity sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA== dependencies: ip "^1.1.0" @@ -2923,72 +2923,72 @@ dns-packet@^1.3.1: dns-txt@^2.0.2: version "2.0.2" - resolved "https://registry.yarnpkg.com/dns-txt/-/dns-txt-2.0.2.tgz#b91d806f5d27188e4ab3e7d107d881a1cc4642b6" + resolved "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz" integrity sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY= dependencies: buffer-indexof "^1.0.0" doctrine@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + resolved "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz" integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== dependencies: esutils "^2.0.2" domexception@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90" + resolved "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz" integrity sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug== dependencies: webidl-conversions "^4.0.2" domexception@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304" + resolved "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz" integrity sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg== dependencies: webidl-conversions "^5.0.0" dotenv@^10.0.0: version "10.0.0" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" + resolved "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz" integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== ee-first@1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= electron-to-chromium@^1.3.886, electron-to-chromium@^1.4.202: version "1.4.225" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.225.tgz#3e27bdd157cbaf19768141f2e0f0f45071e52338" + resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.225.tgz" integrity sha512-ICHvGaCIQR3P88uK8aRtx8gmejbVJyC6bB4LEC3anzBrIzdzC7aiZHY4iFfXhN4st6I7lMO0x4sgBHf/7kBvRw== emittery@^0.8.1: version "0.8.1" - resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.8.1.tgz#bb23cc86d03b30aa75a7f734819dee2e1ba70860" + resolved "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz" integrity sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg== emoji-regex@^8.0.0: version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== encodeurl@~1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= end-of-stream@^1.1.0: version "1.4.4" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== dependencies: once "^1.4.0" enhanced-resolve@^5.10.0: version "5.10.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz#0dc579c3bb2a1032e357ac45b8f3a6f3ad4fb1e6" + resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz" integrity sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ== dependencies: graceful-fs "^4.2.4" @@ -2996,7 +2996,7 @@ enhanced-resolve@^5.10.0: enhanced-resolve@^5.3.2: version "5.8.3" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.8.3.tgz#6d552d465cce0423f5b3d718511ea53826a7b2f0" + resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.3.tgz" integrity sha512-EGAbGvH7j7Xt2nc0E7D99La1OiEs8LnyimkRgwExpUMScN6O+3x9tIWs7PLQZVNx4YD+00skHXPXi1yQHpAmZA== dependencies: graceful-fs "^4.2.4" @@ -3004,56 +3004,56 @@ enhanced-resolve@^5.3.2: enquirer@^2.3.5, enquirer@^2.3.6: version "2.3.6" - resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" + resolved "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz" integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== dependencies: ansi-colors "^4.1.1" envinfo@^7.7.3: version "7.8.1" - resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.8.1.tgz#06377e3e5f4d379fea7ac592d5ad8927e0c4d475" + resolved "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz" integrity sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw== error-ex@^1.3.1: version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + resolved "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz" integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== dependencies: is-arrayish "^0.2.1" es-module-lexer@^0.9.0: version "0.9.3" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.9.3.tgz#6f13db00cc38417137daf74366f535c8eb438f19" + resolved "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz" integrity sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ== escalade@^3.1.1: version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== escape-html@~1.0.3: version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz" integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= escape-string-regexp@^1.0.5: version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= escape-string-regexp@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz" integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== escape-string-regexp@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== escodegen@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" + resolved "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz" integrity sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw== dependencies: esprima "^4.0.1" @@ -3065,19 +3065,19 @@ escodegen@^2.0.0: eslint-config-prettier@^8.3.0: version "8.3.0" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz#f7471b20b6fe8a9a9254cc684454202886a2dd7a" + resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz" integrity sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew== eslint-plugin-prettier@^3.4.0: version "3.4.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz#e9ddb200efb6f3d05ffe83b1665a716af4a387e5" + resolved "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz" integrity sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g== dependencies: prettier-linter-helpers "^1.0.0" eslint-scope@5.1.1, eslint-scope@^5.1.1: version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz" integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== dependencies: esrecurse "^4.3.0" @@ -3085,36 +3085,36 @@ eslint-scope@5.1.1, eslint-scope@^5.1.1: eslint-utils@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" + resolved "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz" integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== dependencies: eslint-visitor-keys "^1.1.0" eslint-utils@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" + resolved "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz" integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== dependencies: eslint-visitor-keys "^2.0.0" eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: version "1.3.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" + resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz" integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== eslint-visitor-keys@^2.0.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" + resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== eslint-visitor-keys@^3.3.0: version "3.3.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" + resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz" integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== eslint@^7.14.0: version "7.32.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" + resolved "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz" integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA== dependencies: "@babel/code-frame" "7.12.11" @@ -3160,7 +3160,7 @@ eslint@^7.14.0: espree@^7.3.0, espree@^7.3.1: version "7.3.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" + resolved "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz" integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== dependencies: acorn "^7.4.0" @@ -3169,56 +3169,56 @@ espree@^7.3.0, espree@^7.3.1: esprima@4.0.1, esprima@^4.0.0, esprima@^4.0.1: version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== esquery@^1.4.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + resolved "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz" integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== dependencies: estraverse "^5.1.0" esrecurse@^4.3.0: version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz" integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== dependencies: estraverse "^5.2.0" estraverse@^4.1.1: version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + resolved "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== estraverse@^5.1.0, estraverse@^5.2.0: version "5.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== esutils@^2.0.2: version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== etag@~1.8.1: version "1.8.1" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + resolved "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= eventemitter3@^4.0.0: version "4.0.7" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== events@^3.2.0: version "3.3.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== execa@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" + resolved "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz" integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== dependencies: cross-spawn "^6.0.0" @@ -3231,7 +3231,7 @@ execa@^1.0.0: execa@^5.0.0, execa@^5.1.1: version "5.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + resolved "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz" integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== dependencies: cross-spawn "^7.0.3" @@ -3246,12 +3246,12 @@ execa@^5.0.0, execa@^5.1.1: exit@^0.1.2: version "0.1.2" - resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz" integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw= expect@^27.3.1: version "27.3.1" - resolved "https://registry.yarnpkg.com/expect/-/expect-27.3.1.tgz#d0f170b1f5c8a2009bab0beffd4bb94f043e38e7" + resolved "https://registry.npmjs.org/expect/-/expect-27.3.1.tgz" integrity sha512-MrNXV2sL9iDRebWPGOGFdPQRl2eDQNu/uhxIMShjjx74T6kC6jFIkmQ6OqXDtevjGUkyB2IT56RzDBqXf/QPCg== dependencies: "@jest/types" "^27.2.5" @@ -3263,7 +3263,7 @@ expect@^27.3.1: express@^4.17.1: version "4.17.1" - resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" + resolved "https://registry.npmjs.org/express/-/express-4.17.1.tgz" integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== dependencies: accepts "~1.3.7" @@ -3299,24 +3299,24 @@ express@^4.17.1: fake-indexeddb@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/fake-indexeddb/-/fake-indexeddb-4.0.0.tgz#1dfb2023a3be175e35a6d84975218b432041934d" + resolved "https://registry.npmjs.org/fake-indexeddb/-/fake-indexeddb-4.0.0.tgz" integrity sha512-oCfWSJ/qvQn1XPZ8SHX6kY3zr1t+bN7faZ/lltGY0SBGhFOPXnWf0+pbO/MOAgfMx6khC2gK3S/bvAgQpuQHDQ== dependencies: realistic-structured-clone "^3.0.0" fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== fast-diff@^1.1.2: version "1.2.0" - resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" + resolved "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz" integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== fast-glob@^3.1.1: version "3.2.7" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" + resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz" integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q== dependencies: "@nodelib/fs.stat" "^2.0.2" @@ -3327,7 +3327,7 @@ fast-glob@^3.1.1: fast-glob@^3.2.9: version "3.2.12" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" + resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz" integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== dependencies: "@nodelib/fs.stat" "^2.0.2" @@ -3338,50 +3338,50 @@ fast-glob@^3.2.9: fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= fastest-levenshtein@^1.0.12: version "1.0.12" - resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz#9990f7d3a88cc5a9ffd1f1745745251700d497e2" + resolved "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz" integrity sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow== fastq@^1.6.0: version "1.13.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" + resolved "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz" integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== dependencies: reusify "^1.0.4" faye-websocket@^0.11.3: version "0.11.4" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" + resolved "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz" integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== dependencies: websocket-driver ">=0.5.1" fb-watchman@^2.0.0: version "2.0.1" - resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85" + resolved "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz" integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg== dependencies: bser "2.1.1" file-entry-cache@^6.0.1: version "6.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz" integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== dependencies: flat-cache "^3.0.4" filing-cabinet@^3.0.1: version "3.0.1" - resolved "https://registry.yarnpkg.com/filing-cabinet/-/filing-cabinet-3.0.1.tgz#3b463edf13dd4a62fa4596a446d443f4ac47584b" + resolved "https://registry.npmjs.org/filing-cabinet/-/filing-cabinet-3.0.1.tgz" integrity sha512-3Wn18+vSKmrlOc0fp5J7p1UyGi7yCWUpPhGVFzZsUyGzG+AEzYWguMGxUbjsgsV4whRHCCiYxIrGyKivBBDDSg== dependencies: app-module-path "^2.2.0" @@ -3400,14 +3400,14 @@ filing-cabinet@^3.0.1: fill-range@^7.0.1: version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz" integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== dependencies: to-regex-range "^5.0.1" finalhandler@~1.1.2: version "1.1.2" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" + resolved "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz" integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== dependencies: debug "2.6.9" @@ -3420,7 +3420,7 @@ finalhandler@~1.1.2: find-babel-config@^1.1.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/find-babel-config/-/find-babel-config-1.2.0.tgz#a9b7b317eb5b9860cda9d54740a8c8337a2283a2" + resolved "https://registry.npmjs.org/find-babel-config/-/find-babel-config-1.2.0.tgz" integrity sha512-jB2CHJeqy6a820ssiqwrKMeyC6nNdmrcgkKWJWmpoxpE8RKciYJXCcXRq1h2AzCo5I5BJeN2tkGEO3hLTuePRA== dependencies: json5 "^0.5.1" @@ -3428,14 +3428,14 @@ find-babel-config@^1.1.0: find-up@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + resolved "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz" integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= dependencies: locate-path "^2.0.0" find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + resolved "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz" integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== dependencies: locate-path "^5.0.0" @@ -3443,7 +3443,7 @@ find-up@^4.0.0, find-up@^4.1.0: flat-cache@^3.0.4: version "3.0.4" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz" integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== dependencies: flatted "^3.1.0" @@ -3451,27 +3451,27 @@ flat-cache@^3.0.4: flatted@^3.1.0: version "3.2.4" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.4.tgz#28d9969ea90661b5134259f312ab6aa7929ac5e2" + resolved "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz" integrity sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw== flatten@^1.0.2: version "1.0.3" - resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b" + resolved "https://registry.npmjs.org/flatten/-/flatten-1.0.3.tgz" integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg== follow-redirects@^1.0.0: version "1.14.9" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7" + resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz" integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w== follow-redirects@^1.15.0: version "1.15.3" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a" + resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz" integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q== form-data@^3.0.0: version "3.0.1" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" + resolved "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz" integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== dependencies: asynckit "^0.4.0" @@ -3480,7 +3480,7 @@ form-data@^3.0.0: form-data@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz" integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== dependencies: asynckit "^0.4.0" @@ -3489,52 +3489,52 @@ form-data@^4.0.0: forwarded@0.2.0: version "0.2.0" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz" integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== fresh@0.5.2: version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + resolved "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz" integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= fs-monkey@1.0.3: version "1.0.3" - resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.3.tgz#ae3ac92d53bb328efe0e9a1d9541f6ad8d48e2d3" + resolved "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz" integrity sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q== fs-readdir-recursive@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz#e32fc030a2ccee44a6b5371308da54be0b397d27" + resolved "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz" integrity sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA== fs.realpath@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= fsevents@^2.3.2, fsevents@~2.3.2: version "2.3.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== function-bind@^1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== functional-red-black-tree@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + resolved "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= gensync@^1.0.0-beta.2: version "1.0.0-beta.2" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== get-amd-module-type@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/get-amd-module-type/-/get-amd-module-type-3.0.0.tgz#bb334662fa04427018c937774570de495845c288" + resolved "https://registry.npmjs.org/get-amd-module-type/-/get-amd-module-type-3.0.0.tgz" integrity sha512-99Q7COuACPfVt18zH9N4VAMyb81S6TUgJm2NgV6ERtkh9VIkAaByZkW530wl3lLN5KTtSrK9jVLxYsoP5hQKsw== dependencies: ast-module-types "^2.3.2" @@ -3542,12 +3542,12 @@ get-amd-module-type@^3.0.0: get-caller-file@^2.0.5: version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== get-intrinsic@^1.0.2: version "1.1.1" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" + resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz" integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== dependencies: function-bind "^1.1.1" @@ -3556,46 +3556,46 @@ get-intrinsic@^1.0.2: get-own-enumerable-property-symbols@^3.0.0: version "3.0.2" - resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" + resolved "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz" integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g== get-package-type@^0.1.0: version "0.1.0" - resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + resolved "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== get-stdin@^7.0.0: version "7.0.0" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-7.0.0.tgz#8d5de98f15171a125c5e516643c7a6d0ea8a96f6" + resolved "https://registry.npmjs.org/get-stdin/-/get-stdin-7.0.0.tgz" integrity sha512-zRKcywvrXlXsA0v0i9Io4KDRaAw7+a1ZpjRwl9Wox8PFlVCCHra7E9c4kqXCoCM9nR5tBkaTTZRBoCm60bFqTQ== get-stream@^4.0.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" + resolved "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz" integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== dependencies: pump "^3.0.0" get-stream@^6.0.0: version "6.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" glob-to-regexp@^0.4.1: version "0.4.1" - resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" + resolved "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== glob@^7.0.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.2.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" + resolved "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz" integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== dependencies: fs.realpath "^1.0.0" @@ -3607,19 +3607,19 @@ glob@^7.0.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: globals@^11.1.0: version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== globals@^13.6.0, globals@^13.9.0: version "13.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.12.0.tgz#4d733760304230a0082ed96e21e5c565f898089e" + resolved "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz" integrity sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg== dependencies: type-fest "^0.20.2" globby@^11.0.1, globby@^11.0.3: version "11.0.4" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" + resolved "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz" integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg== dependencies: array-union "^2.1.0" @@ -3631,7 +3631,7 @@ globby@^11.0.1, globby@^11.0.3: globby@^11.1.0: version "11.1.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + resolved "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz" integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== dependencies: array-union "^2.1.0" @@ -3643,70 +3643,70 @@ globby@^11.1.0: gonzales-pe@^4.2.3: version "4.3.0" - resolved "https://registry.yarnpkg.com/gonzales-pe/-/gonzales-pe-4.3.0.tgz#fe9dec5f3c557eead09ff868c65826be54d067b3" + resolved "https://registry.npmjs.org/gonzales-pe/-/gonzales-pe-4.3.0.tgz" integrity sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ== dependencies: minimist "^1.2.5" graceful-fs@^4.1.2, graceful-fs@^4.2.4, graceful-fs@^4.2.9: version "4.2.10" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" + resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz" integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== graceful-fs@^4.2.6: version "4.2.8" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" + resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz" integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== graphviz@0.0.9: version "0.0.9" - resolved "https://registry.yarnpkg.com/graphviz/-/graphviz-0.0.9.tgz#0bbf1df588c6a92259282da35323622528c4bbc4" + resolved "https://registry.npmjs.org/graphviz/-/graphviz-0.0.9.tgz" integrity sha512-SmoY2pOtcikmMCqCSy2NO1YsRfu9OO0wpTlOYW++giGjfX1a6gax/m1Fo8IdUd0/3H15cTOfR1SMKwohj4LKsg== dependencies: temp "~0.4.0" handle-thing@^2.0.0: version "2.0.1" - resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" + resolved "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz" integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== has-flag@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= has-flag@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== has-symbols@^1.0.1, has-symbols@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" + resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz" integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== has-tostringtag@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" + resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz" integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== dependencies: has-symbols "^1.0.2" has@^1.0.3: version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz" integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== dependencies: function-bind "^1.1.1" hosted-git-info@^2.1.4: version "2.8.9" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" + resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz" integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== hpack.js@^2.1.6: version "2.1.6" - resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" + resolved "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz" integrity sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI= dependencies: inherits "^2.0.1" @@ -3716,29 +3716,29 @@ hpack.js@^2.1.6: html-encoding-sniffer@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3" + resolved "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz" integrity sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ== dependencies: whatwg-encoding "^1.0.5" html-entities@^2.3.2: version "2.3.2" - resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.3.2.tgz#760b404685cb1d794e4f4b744332e3b00dcfe488" + resolved "https://registry.npmjs.org/html-entities/-/html-entities-2.3.2.tgz" integrity sha512-c3Ab/url5ksaT0WyleslpBEthOzWhrjQbg75y7XUsfSzi3Dgzt0l8w5e7DylRn15MTlMMD58dTfzddNS2kcAjQ== html-escaper@^2.0.0: version "2.0.2" - resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + resolved "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== http-deceiver@^1.2.7: version "1.2.7" - resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" + resolved "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz" integrity sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc= http-errors@1.7.2: version "1.7.2" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" + resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz" integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== dependencies: depd "~1.1.2" @@ -3749,7 +3749,7 @@ http-errors@1.7.2: http-errors@~1.6.2: version "1.6.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" + resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz" integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0= dependencies: depd "~1.1.2" @@ -3759,7 +3759,7 @@ http-errors@~1.6.2: http-errors@~1.7.2: version "1.7.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" + resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz" integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== dependencies: depd "~1.1.2" @@ -3770,12 +3770,12 @@ http-errors@~1.7.2: http-parser-js@>=0.5.1: version "0.5.3" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.3.tgz#01d2709c79d41698bb01d4decc5e9da4e4a033d9" + resolved "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.3.tgz" integrity sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg== http-proxy-agent@^4.0.1: version "4.0.1" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" + resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz" integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== dependencies: "@tootallnate/once" "1" @@ -3784,7 +3784,7 @@ http-proxy-agent@^4.0.1: http-proxy-middleware@^2.0.0: version "2.0.1" - resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.1.tgz#7ef3417a479fb7666a571e09966c66a39bd2c15f" + resolved "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.1.tgz" integrity sha512-cfaXRVoZxSed/BmkA7SwBVNI9Kj7HFltaE5rqYOub5kWzWZ+gofV2koVN1j2rMW7pEfSSlCHGJ31xmuyFyfLOg== dependencies: "@types/http-proxy" "^1.17.5" @@ -3795,7 +3795,7 @@ http-proxy-middleware@^2.0.0: http-proxy@^1.18.1: version "1.18.1" - resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" + resolved "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz" integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== dependencies: eventemitter3 "^4.0.0" @@ -3804,7 +3804,7 @@ http-proxy@^1.18.1: https-proxy-agent@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" + resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz" integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== dependencies: agent-base "6" @@ -3812,12 +3812,12 @@ https-proxy-agent@^5.0.0: human-signals@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + resolved "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== husky@^3.1.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/husky/-/husky-3.1.0.tgz#5faad520ab860582ed94f0c1a77f0f04c90b57c0" + resolved "https://registry.npmjs.org/husky/-/husky-3.1.0.tgz" integrity sha512-FJkPoHHB+6s4a+jwPqBudBDvYZsoQW5/HBuMSehC8qDiCe50kpcxeqFoDSlow+9I6wg47YxBoT3WxaURlrDIIQ== dependencies: chalk "^2.4.2" @@ -3834,41 +3834,41 @@ husky@^3.1.0: iconv-lite@0.4.24: version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== dependencies: safer-buffer ">= 2.1.2 < 3" idb-keyval@^6.2.0: version "6.2.0" - resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-6.2.0.tgz#3af94a3cc0689d6ee0bc9e045d2a3340ea897173" + resolved "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.0.tgz" integrity sha512-uw+MIyQn2jl3+hroD7hF8J7PUviBU7BPKWw4f/ISf32D4LoGu98yHjrzWWJDASu9QNrX10tCJqk9YY0ClWm8Ng== dependencies: safari-14-idb-fix "^3.0.0" ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== ignore@^4.0.6: version "4.0.6" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + resolved "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== ignore@^5.1.4: version "5.1.9" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.9.tgz#9ec1a5cbe8e1446ec60d4420060d43aa6e7382fb" + resolved "https://registry.npmjs.org/ignore/-/ignore-5.1.9.tgz" integrity sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ== ignore@^5.2.0: version "5.2.0" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" + resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz" integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== import-fresh@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" + resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz" integrity sha1-2BNVwVYS04bGH53dOSLUMEgipUY= dependencies: caller-path "^2.0.0" @@ -3876,7 +3876,7 @@ import-fresh@^2.0.0: import-fresh@^3.0.0, import-fresh@^3.2.1: version "3.3.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz" integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== dependencies: parent-module "^1.0.0" @@ -3884,7 +3884,7 @@ import-fresh@^3.0.0, import-fresh@^3.2.1: import-local@^3.0.2: version "3.0.3" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.0.3.tgz#4d51c2c495ca9393da259ec66b62e022920211e0" + resolved "https://registry.npmjs.org/import-local/-/import-local-3.0.3.tgz" integrity sha512-bE9iaUY3CXH8Cwfan/abDKAxe1KGT9kyGsBPqf6DMK/z0a2OzAsrukeYNgIH6cH5Xr452jb1TUL8rSfCLjZ9uA== dependencies: pkg-dir "^4.2.0" @@ -3892,22 +3892,22 @@ import-local@^3.0.2: imurmurhash@^0.1.4: version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= indent-string@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + resolved "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== indexes-of@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" + resolved "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz" integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc= inflight@^1.0.4: version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= dependencies: once "^1.3.0" @@ -3915,42 +3915,42 @@ inflight@^1.0.4: inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== inherits@2.0.3: version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= ini@~1.3.0: version "1.3.8" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== interpret@^2.2.0: version "2.2.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" + resolved "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz" integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== ip@^1.1.0: version "1.1.5" - resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" + resolved "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz" integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= ipaddr.js@1.9.1: version "1.9.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== ipaddr.js@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.0.1.tgz#eca256a7a877e917aeb368b0a7497ddf42ef81c0" + resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz" integrity sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng== is-arguments@^1.0.4: version "1.1.1" - resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" + resolved "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz" integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== dependencies: call-bind "^1.0.2" @@ -3958,112 +3958,112 @@ is-arguments@^1.0.4: is-arrayish@^0.2.1: version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= is-binary-path@~2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz" integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== dependencies: binary-extensions "^2.0.0" is-buffer@^2.0.5: version "2.0.5" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" + resolved "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz" integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== is-core-module@^2.2.0: version "2.8.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.0.tgz#0321336c3d0925e497fd97f5d95cb114a5ccd548" + resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz" integrity sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw== dependencies: has "^1.0.3" is-date-object@^1.0.1: version "1.0.5" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + resolved "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz" integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== dependencies: has-tostringtag "^1.0.0" is-directory@^0.3.1: version "0.3.1" - resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" + resolved "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz" integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= is-docker@^2.0.0, is-docker@^2.1.1: version "2.2.1" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + resolved "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz" integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== is-extglob@^2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= is-fullwidth-code-point@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== is-generator-fn@^2.0.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + resolved "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== dependencies: is-extglob "^2.1.1" is-interactive@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" + resolved "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz" integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== is-number@^7.0.0: version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== is-obj@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" + resolved "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz" integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= is-path-cwd@^2.2.0: version "2.2.0" - resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" + resolved "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz" integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== is-path-inside@^3.0.2: version "3.0.3" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + resolved "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== is-plain-obj@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7" + resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz" integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA== is-plain-object@^2.0.4: version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + resolved "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz" integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== dependencies: isobject "^3.0.1" is-potential-custom-element-name@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" + resolved "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz" integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== is-regex@^1.0.4: version "1.1.4" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz" integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== dependencies: call-bind "^1.0.2" @@ -4071,74 +4071,74 @@ is-regex@^1.0.4: is-regexp@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" + resolved "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz" integrity sha1-/S2INUXEa6xaYz57mgnof6LLUGk= is-relative-path@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/is-relative-path/-/is-relative-path-1.0.2.tgz#091b46a0d67c1ed0fe85f1f8cfdde006bb251d46" + resolved "https://registry.npmjs.org/is-relative-path/-/is-relative-path-1.0.2.tgz" integrity sha1-CRtGoNZ8HtD+hfH4z93gBrslHUY= is-stream@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + resolved "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= is-stream@^2.0.0: version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== is-typedarray@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + resolved "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= is-unicode-supported@^0.1.0: version "0.1.0" - resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz" integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== is-url-superb@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/is-url-superb/-/is-url-superb-4.0.0.tgz#b54d1d2499bb16792748ac967aa3ecb41a33a8c2" + resolved "https://registry.npmjs.org/is-url-superb/-/is-url-superb-4.0.0.tgz" integrity sha512-GI+WjezhPPcbM+tqE9LnmsY5qqjwHzTvjJ36wxYX5ujNXefSUJ/T17r5bqDV8yLhcgB59KTPNOc9O9cmHTPWsA== is-url@^1.2.4: version "1.2.4" - resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52" + resolved "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz" integrity sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww== is-wsl@^2.2.0: version "2.2.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + resolved "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz" integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== dependencies: is-docker "^2.0.0" isarray@~1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= isexe@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= isobject@^3.0.1: version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + resolved "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz" integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" + resolved "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz" integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== istanbul-lib-instrument@^4.0.3: version "4.0.3" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d" + resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz" integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ== dependencies: "@babel/core" "^7.7.5" @@ -4148,7 +4148,7 @@ istanbul-lib-instrument@^4.0.3: istanbul-lib-instrument@^5.0.4: version "5.1.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz#7b49198b657b27a730b8e9cb601f1e1bff24c59a" + resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz" integrity sha512-czwUz525rkOFDJxfKK6mYfIs9zBKILyrZQxjz3ABhjQXhbhFsSbo1HW/BFcsDnfJYJWA6thRR5/TUY2qs5W99Q== dependencies: "@babel/core" "^7.12.3" @@ -4159,7 +4159,7 @@ istanbul-lib-instrument@^5.0.4: istanbul-lib-report@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" + resolved "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz" integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== dependencies: istanbul-lib-coverage "^3.0.0" @@ -4168,7 +4168,7 @@ istanbul-lib-report@^3.0.0: istanbul-lib-source-maps@^4.0.0: version "4.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + resolved "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz" integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== dependencies: debug "^4.1.1" @@ -4177,7 +4177,7 @@ istanbul-lib-source-maps@^4.0.0: istanbul-reports@^3.0.2: version "3.0.5" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.5.tgz#a2580107e71279ea6d661ddede929ffc6d693384" + resolved "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.5.tgz" integrity sha512-5+19PlhnGabNWB7kOFnuxT8H3T/iIyQzIbQMxXsURmmvKg86P2sbkrGOT77VnHw0Qr0gc2XzRaRfMZYYbSQCJQ== dependencies: html-escaper "^2.0.0" @@ -4185,7 +4185,7 @@ istanbul-reports@^3.0.2: jest-changed-files@^27.3.0: version "27.3.0" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-27.3.0.tgz#22a02cc2b34583fc66e443171dc271c0529d263c" + resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.3.0.tgz" integrity sha512-9DJs9garMHv4RhylUMZgbdCJ3+jHSkpL9aaVKp13xtXAD80qLTLrqcDZL1PHA9dYA0bCI86Nv2BhkLpLhrBcPg== dependencies: "@jest/types" "^27.2.5" @@ -4194,7 +4194,7 @@ jest-changed-files@^27.3.0: jest-circus@^27.3.1: version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-27.3.1.tgz#1679e74387cbbf0c6a8b42de963250a6469e0797" + resolved "https://registry.npmjs.org/jest-circus/-/jest-circus-27.3.1.tgz" integrity sha512-v1dsM9II6gvXokgqq6Yh2jHCpfg7ZqV4jWY66u7npz24JnhP3NHxI0sKT7+ZMQ7IrOWHYAaeEllOySbDbWsiXw== dependencies: "@jest/environment" "^27.3.1" @@ -4219,7 +4219,7 @@ jest-circus@^27.3.1: jest-cli@^27.3.1: version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-27.3.1.tgz#b576f9d146ba6643ce0a162d782b40152b6b1d16" + resolved "https://registry.npmjs.org/jest-cli/-/jest-cli-27.3.1.tgz" integrity sha512-WHnCqpfK+6EvT62me6WVs8NhtbjAS4/6vZJnk7/2+oOr50cwAzG4Wxt6RXX0hu6m1169ZGMlhYYUNeKBXCph/Q== dependencies: "@jest/core" "^27.3.1" @@ -4237,7 +4237,7 @@ jest-cli@^27.3.1: jest-config@^27.3.1: version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-27.3.1.tgz#cb3b7f6aaa8c0a7daad4f2b9573899ca7e09bbad" + resolved "https://registry.npmjs.org/jest-config/-/jest-config-27.3.1.tgz" integrity sha512-KY8xOIbIACZ/vdYCKSopL44I0xboxC751IX+DXL2+Wx6DKNycyEfV3rryC3BPm5Uq/BBqDoMrKuqLEUNJmMKKg== dependencies: "@babel/core" "^7.1.0" @@ -4264,7 +4264,7 @@ jest-config@^27.3.1: jest-diff@^27.0.0, jest-diff@^27.3.1: version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.3.1.tgz#d2775fea15411f5f5aeda2a5e02c2f36440f6d55" + resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-27.3.1.tgz" integrity sha512-PCeuAH4AWUo2O5+ksW4pL9v5xJAcIKPUPfIhZBcG1RKv/0+dvaWTQK1Nrau8d67dp65fOqbeMdoil+6PedyEPQ== dependencies: chalk "^4.0.0" @@ -4274,14 +4274,14 @@ jest-diff@^27.0.0, jest-diff@^27.3.1: jest-docblock@^27.0.6: version "27.0.6" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-27.0.6.tgz#cc78266acf7fe693ca462cbbda0ea4e639e4e5f3" + resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.0.6.tgz" integrity sha512-Fid6dPcjwepTFraz0YxIMCi7dejjJ/KL9FBjPYhBp4Sv1Y9PdhImlKZqYU555BlN4TQKaTc+F2Av1z+anVyGkA== dependencies: detect-newline "^3.0.0" jest-each@^27.3.1: version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-27.3.1.tgz#14c56bb4f18dd18dc6bdd853919b5f16a17761ff" + resolved "https://registry.npmjs.org/jest-each/-/jest-each-27.3.1.tgz" integrity sha512-E4SwfzKJWYcvOYCjOxhZcxwL+AY0uFMvdCOwvzgutJiaiodFjkxQQDxHm8FQBeTqDnSmKsQWn7ldMRzTn2zJaQ== dependencies: "@jest/types" "^27.2.5" @@ -4292,7 +4292,7 @@ jest-each@^27.3.1: jest-environment-jsdom@^27.3.1: version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-27.3.1.tgz#63ac36d68f7a9303494df783494856222b57f73e" + resolved "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.3.1.tgz" integrity sha512-3MOy8qMzIkQlfb3W1TfrD7uZHj+xx8Olix5vMENkj5djPmRqndMaXtpnaZkxmxM+Qc3lo+yVzJjzuXbCcZjAlg== dependencies: "@jest/environment" "^27.3.1" @@ -4305,7 +4305,7 @@ jest-environment-jsdom@^27.3.1: jest-environment-node@^27.3.1: version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-27.3.1.tgz#af7d0eed04edafb740311b303f3fe7c8c27014bb" + resolved "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.3.1.tgz" integrity sha512-T89F/FgkE8waqrTSA7/ydMkcc52uYPgZZ6q8OaZgyiZkJb5QNNCF6oPZjH9IfPFfcc9uBWh1574N0kY0pSvTXw== dependencies: "@jest/environment" "^27.3.1" @@ -4317,12 +4317,12 @@ jest-environment-node@^27.3.1: jest-get-type@^27.3.1: version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.3.1.tgz#a8a2b0a12b50169773099eee60a0e6dd11423eff" + resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.3.1.tgz" integrity sha512-+Ilqi8hgHSAdhlQ3s12CAVNd8H96ZkQBfYoXmArzZnOfAtVAJEiPDBirjByEblvG/4LPJmkL+nBqPO3A1YJAEg== jest-haste-map@^27.3.1: version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-27.3.1.tgz#7656fbd64bf48bda904e759fc9d93e2c807353ee" + resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.3.1.tgz" integrity sha512-lYfNZIzwPccDJZIyk9Iz5iQMM/MH56NIIcGj7AFU1YyA4ewWFBl8z+YPJuSCRML/ee2cCt2y3W4K3VXPT6Nhzg== dependencies: "@jest/types" "^27.2.5" @@ -4342,7 +4342,7 @@ jest-haste-map@^27.3.1: jest-jasmine2@^27.3.1: version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-27.3.1.tgz#df6d3d07c7dafc344feb43a0072a6f09458d32b0" + resolved "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.3.1.tgz" integrity sha512-WK11ZUetDQaC09w4/j7o4FZDUIp+4iYWH/Lik34Pv7ukL+DuXFGdnmmi7dT58J2ZYKFB5r13GyE0z3NPeyJmsg== dependencies: "@babel/traverse" "^7.1.0" @@ -4366,7 +4366,7 @@ jest-jasmine2@^27.3.1: jest-leak-detector@^27.3.1: version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-27.3.1.tgz#7fb632c2992ef707a1e73286e1e704f9cc1772b2" + resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.3.1.tgz" integrity sha512-78QstU9tXbaHzwlRlKmTpjP9k4Pvre5l0r8Spo4SbFFVy/4Abg9I6ZjHwjg2QyKEAMg020XcjP+UgLZIY50yEg== dependencies: jest-get-type "^27.3.1" @@ -4374,7 +4374,7 @@ jest-leak-detector@^27.3.1: jest-matcher-utils@^27.3.1: version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.3.1.tgz#257ad61e54a6d4044e080d85dbdc4a08811e9c1c" + resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.3.1.tgz" integrity sha512-hX8N7zXS4k+8bC1Aj0OWpGb7D3gIXxYvPNK1inP5xvE4ztbz3rc4AkI6jGVaerepBnfWB17FL5lWFJT3s7qo8w== dependencies: chalk "^4.0.0" @@ -4384,7 +4384,7 @@ jest-matcher-utils@^27.3.1: jest-message-util@^27.3.1: version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-27.3.1.tgz#f7c25688ad3410ab10bcb862bcfe3152345c6436" + resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.3.1.tgz" integrity sha512-bh3JEmxsTZ/9rTm0jQrPElbY2+y48Rw2t47uMfByNyUVR+OfPh4anuyKsGqsNkXk/TI4JbLRZx+7p7Hdt6q1yg== dependencies: "@babel/code-frame" "^7.12.13" @@ -4399,7 +4399,7 @@ jest-message-util@^27.3.1: jest-mock@^27.3.0: version "27.3.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-27.3.0.tgz#ddf0ec3cc3e68c8ccd489bef4d1f525571a1b867" + resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-27.3.0.tgz" integrity sha512-ziZiLk0elZOQjD08bLkegBzv5hCABu/c8Ytx45nJKkysQwGaonvmTxwjLqEA4qGdasq9o2I8/HtdGMNnVsMTGw== dependencies: "@jest/types" "^27.2.5" @@ -4407,17 +4407,17 @@ jest-mock@^27.3.0: jest-pnp-resolver@^1.2.2: version "1.2.2" - resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" + resolved "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz" integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== jest-regex-util@^27.0.6: version "27.0.6" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-27.0.6.tgz#02e112082935ae949ce5d13b2675db3d8c87d9c5" + resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.0.6.tgz" integrity sha512-SUhPzBsGa1IKm8hx2F4NfTGGp+r7BXJ4CulsZ1k2kI+mGLG+lxGrs76veN2LF/aUdGosJBzKgXmNCw+BzFqBDQ== jest-resolve-dependencies@^27.3.1: version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-27.3.1.tgz#85b99bdbdfa46e2c81c6228fc4c91076f624f6e2" + resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.3.1.tgz" integrity sha512-X7iLzY8pCiYOnvYo2YrK3P9oSE8/3N2f4pUZMJ8IUcZnT81vlSonya1KTO9ZfKGuC+svE6FHK/XOb8SsoRUV1A== dependencies: "@jest/types" "^27.2.5" @@ -4426,7 +4426,7 @@ jest-resolve-dependencies@^27.3.1: jest-resolve@^27.3.1: version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-27.3.1.tgz#0e5542172a1aa0270be6f66a65888647bdd74a3e" + resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.3.1.tgz" integrity sha512-Dfzt25CFSPo3Y3GCbxynRBZzxq9AdyNN+x/v2IqYx6KVT5Z6me2Z/PsSGFSv3cOSUZqJ9pHxilao/I/m9FouLw== dependencies: "@jest/types" "^27.2.5" @@ -4442,7 +4442,7 @@ jest-resolve@^27.3.1: jest-runner@^27.3.1: version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-27.3.1.tgz#1d594dcbf3bd8600a7e839e790384559eaf96e3e" + resolved "https://registry.npmjs.org/jest-runner/-/jest-runner-27.3.1.tgz" integrity sha512-r4W6kBn6sPr3TBwQNmqE94mPlYVn7fLBseeJfo4E2uCTmAyDFm2O5DYAQAFP7Q3YfiA/bMwg8TVsciP7k0xOww== dependencies: "@jest/console" "^27.3.1" @@ -4470,7 +4470,7 @@ jest-runner@^27.3.1: jest-runtime@^27.3.1: version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-27.3.1.tgz#80fa32eb85fe5af575865ddf379874777ee993d7" + resolved "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.3.1.tgz" integrity sha512-qtO6VxPbS8umqhEDpjA4pqTkKQ1Hy4ZSi9mDVeE9Za7LKBo2LdW2jmT+Iod3XFaJqINikZQsn2wEi0j9wPRbLg== dependencies: "@jest/console" "^27.3.1" @@ -4502,7 +4502,7 @@ jest-runtime@^27.3.1: jest-serializer@^27.0.6: version "27.0.6" - resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-27.0.6.tgz#93a6c74e0132b81a2d54623251c46c498bb5bec1" + resolved "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.0.6.tgz" integrity sha512-PtGdVK9EGC7dsaziskfqaAPib6wTViY3G8E5wz9tLVPhHyiDNTZn/xjZ4khAw+09QkoOVpn7vF5nPSN6dtBexA== dependencies: "@types/node" "*" @@ -4510,7 +4510,7 @@ jest-serializer@^27.0.6: jest-snapshot@^27.3.1: version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-27.3.1.tgz#1da5c0712a252d70917d46c037054f5918c49ee4" + resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.3.1.tgz" integrity sha512-APZyBvSgQgOT0XumwfFu7X3G5elj6TGhCBLbBdn3R1IzYustPGPE38F51dBWMQ8hRXa9je0vAdeVDtqHLvB6lg== dependencies: "@babel/core" "^7.7.2" @@ -4540,7 +4540,7 @@ jest-snapshot@^27.3.1: jest-util@^27.0.0, jest-util@^27.3.1: version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.3.1.tgz#a58cdc7b6c8a560caac9ed6bdfc4e4ff23f80429" + resolved "https://registry.npmjs.org/jest-util/-/jest-util-27.3.1.tgz" integrity sha512-8fg+ifEH3GDryLQf/eKZck1DEs2YuVPBCMOaHQxVVLmQwl/CDhWzrvChTX4efLZxGrw+AA0mSXv78cyytBt/uw== dependencies: "@jest/types" "^27.2.5" @@ -4552,7 +4552,7 @@ jest-util@^27.0.0, jest-util@^27.3.1: jest-validate@^27.3.1: version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-27.3.1.tgz#3a395d61a19cd13ae9054af8cdaf299116ef8a24" + resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-27.3.1.tgz" integrity sha512-3H0XCHDFLA9uDII67Bwi1Vy7AqwA5HqEEjyy934lgVhtJ3eisw6ShOF1MDmRPspyikef5MyExvIm0/TuLzZ86Q== dependencies: "@jest/types" "^27.2.5" @@ -4564,7 +4564,7 @@ jest-validate@^27.3.1: jest-watcher@^27.3.1: version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-27.3.1.tgz#ba5e0bc6aa843612b54ddb7f009d1cbff7e05f3e" + resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.3.1.tgz" integrity sha512-9/xbV6chABsGHWh9yPaAGYVVKurWoP3ZMCv6h+O1v9/+pkOroigs6WzZ0e9gLP/njokUwM7yQhr01LKJVMkaZA== dependencies: "@jest/test-result" "^27.3.1" @@ -4577,7 +4577,7 @@ jest-watcher@^27.3.1: jest-worker@^27.3.1: version "27.3.1" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.3.1.tgz#0def7feae5b8042be38479799aeb7b5facac24b2" + resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-27.3.1.tgz" integrity sha512-ks3WCzsiZaOPJl/oMsDjaf0TRiSv7ctNgs0FqRr2nARsovz6AWWy4oLElwcquGSz692DzgZQrCLScPNs5YlC4g== dependencies: "@types/node" "*" @@ -4586,7 +4586,7 @@ jest-worker@^27.3.1: jest-worker@^27.4.5: version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" + resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz" integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== dependencies: "@types/node" "*" @@ -4595,7 +4595,7 @@ jest-worker@^27.4.5: jest@^27.3.1: version "27.3.1" - resolved "https://registry.yarnpkg.com/jest/-/jest-27.3.1.tgz#b5bab64e8f56b6f7e275ba1836898b0d9f1e5c8a" + resolved "https://registry.npmjs.org/jest/-/jest-27.3.1.tgz" integrity sha512-U2AX0AgQGd5EzMsiZpYt8HyZ+nSVIh5ujQ9CPp9EQZJMjXIiSZpJNweZl0swatKRoqHWgGKM3zaSwm4Zaz87ng== dependencies: "@jest/core" "^27.3.1" @@ -4604,12 +4604,12 @@ jest@^27.3.1: js-tokens@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== js-yaml@^3.13.1: version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz" integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== dependencies: argparse "^1.0.7" @@ -4617,7 +4617,7 @@ js-yaml@^3.13.1: jsdom@^16.6.0: version "16.7.0" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" + resolved "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz" integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw== dependencies: abab "^2.0.5" @@ -4650,69 +4650,69 @@ jsdom@^16.6.0: jsesc@^2.5.1: version "2.5.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== jsesc@~0.5.0: version "0.5.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + resolved "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz" integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= json-parse-better-errors@^1.0.1: version "1.0.2" - resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + resolved "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz" integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== json-schema-traverse@^0.4.1: version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== json-schema-traverse@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz" integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= json5@2.x, json5@^2.1.2: version "2.2.0" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" + resolved "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz" integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== dependencies: minimist "^1.2.5" json5@^0.5.1: version "0.5.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" + resolved "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz" integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE= kind-of@^6.0.2: version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== kleur@^3.0.3: version "3.0.3" - resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + resolved "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== leven@^3.1.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + resolved "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz" integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== levn@^0.4.1: version "0.4.1" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz" integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== dependencies: prelude-ls "^1.2.1" @@ -4720,7 +4720,7 @@ levn@^0.4.1: levn@~0.3.0: version "0.3.0" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + resolved "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz" integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= dependencies: prelude-ls "~1.1.2" @@ -4728,12 +4728,12 @@ levn@~0.3.0: lines-and-columns@^1.1.6: version "1.1.6" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" + resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz" integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= lint-staged@^11.2.6: version "11.2.6" - resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-11.2.6.tgz#f477b1af0294db054e5937f171679df63baa4c43" + resolved "https://registry.npmjs.org/lint-staged/-/lint-staged-11.2.6.tgz" integrity sha512-Vti55pUnpvPE0J9936lKl0ngVeTdSZpEdTNhASbkaWX7J5R9OEifo1INBGQuGW4zmy6OG+TcWPJ3m5yuy5Q8Tg== dependencies: cli-truncate "2.1.0" @@ -4753,7 +4753,7 @@ lint-staged@^11.2.6: listr2@^3.12.2: version "3.13.3" - resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.13.3.tgz#d8f6095c9371b382c9b1c2bc33c5941d8e177f11" + resolved "https://registry.npmjs.org/listr2/-/listr2-3.13.3.tgz" integrity sha512-VqAgN+XVfyaEjSaFewGPcDs5/3hBbWVaX1VgWv2f52MF7US45JuARlArULctiB44IIcEk3JF7GtoFCLqEdeuPA== dependencies: cli-truncate "^2.1.0" @@ -4767,12 +4767,12 @@ listr2@^3.12.2: loader-runner@^4.2.0: version "4.3.0" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" + resolved "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz" integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== locate-path@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz" integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= dependencies: p-locate "^2.0.0" @@ -4780,44 +4780,44 @@ locate-path@^2.0.0: locate-path@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz" integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== dependencies: p-locate "^4.1.0" lodash-es@^4.17.21: version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" + resolved "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz" integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== lodash.debounce@^4.0.8: version "4.0.8" - resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= lodash.memoize@4.x: version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + resolved "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz" integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= lodash.merge@^4.6.2: version "4.6.2" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== lodash.truncate@^4.4.2: version "4.4.2" - resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" + resolved "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz" integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= lodash@^4.17.14, lodash@^4.17.21, lodash@^4.7.0: version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== log-symbols@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + resolved "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz" integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== dependencies: chalk "^4.1.0" @@ -4825,7 +4825,7 @@ log-symbols@^4.1.0: log-update@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1" + resolved "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz" integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg== dependencies: ansi-escapes "^4.3.0" @@ -4835,14 +4835,14 @@ log-update@^4.0.0: lru-cache@^6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz" integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== dependencies: yallist "^4.0.0" madge@^5.0.1: version "5.0.1" - resolved "https://registry.yarnpkg.com/madge/-/madge-5.0.1.tgz#2096d9006558ea0669b3ade89c2cda708a24e22b" + resolved "https://registry.npmjs.org/madge/-/madge-5.0.1.tgz" integrity sha512-krmSWL9Hkgub74bOjnjWRoFPAJvPwSG6Dbta06qhWOq6X/n/FPzO3ESZvbFYVIvG2g4UHXvCJN1b+RZLaSs9nA== dependencies: chalk "^4.1.1" @@ -4870,7 +4870,7 @@ madge@^5.0.1: make-dir@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" + resolved "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz" integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== dependencies: pify "^4.0.1" @@ -4878,58 +4878,58 @@ make-dir@^2.1.0: make-dir@^3.0.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + resolved "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz" integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== dependencies: semver "^6.0.0" make-error@1.x: version "1.3.6" - resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== makeerror@1.0.12: version "1.0.12" - resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + resolved "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz" integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== dependencies: tmpl "1.0.5" media-typer@0.3.0: version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= memfs@^3.2.2: version "3.3.0" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.3.0.tgz#4da2d1fc40a04b170a56622c7164c6be2c4cbef2" + resolved "https://registry.npmjs.org/memfs/-/memfs-3.3.0.tgz" integrity sha512-BEE62uMfKOavX3iG7GYX43QJ+hAeeWnwIAuJ/R6q96jaMtiLzhsxHJC8B1L7fK7Pt/vXDRwb3SG/yBpNGDPqzg== dependencies: fs-monkey "1.0.3" merge-descriptors@1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + resolved "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz" integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= merge-stream@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== methods@~1.1.2: version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + resolved "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= micromatch@^4.0.2, micromatch@^4.0.4: version "4.0.4" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" + resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz" integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== dependencies: braces "^3.0.1" @@ -4937,65 +4937,65 @@ micromatch@^4.0.2, micromatch@^4.0.4: mime-db@1.51.0, "mime-db@>= 1.43.0 < 2": version "1.51.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz" integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g== mime-db@1.52.0: version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== mime-types@^2.1.12, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24: version "2.1.34" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.34.tgz#5a712f9ec1503511a945803640fafe09d3793c24" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz" integrity sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A== dependencies: mime-db "1.51.0" mime-types@^2.1.27: version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== dependencies: mime-db "1.52.0" mime@1.6.0: version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + resolved "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== mimic-fn@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== minimalistic-assert@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + resolved "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== minimatch@^3.0.4: version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== dependencies: brace-expansion "^1.1.7" minimist@^1.2.0, minimist@^1.2.5: version "1.2.6" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz" integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== mkdirp@^0.5.5: version "0.5.5" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== dependencies: minimist "^1.2.5" module-definition@^3.3.1: version "3.3.1" - resolved "https://registry.yarnpkg.com/module-definition/-/module-definition-3.3.1.tgz#fedef71667713e36988b93d0626a4fe7b35aebfc" + resolved "https://registry.npmjs.org/module-definition/-/module-definition-3.3.1.tgz" integrity sha512-kLidGPwQ2yq484nSD+D3JoJp4Etc0Ox9P0L34Pu/cU4X4HcG7k7p62XI5BBuvURWMRX3RPyuhOcBHbKus+UH4A== dependencies: ast-module-types "^2.7.1" @@ -5003,7 +5003,7 @@ module-definition@^3.3.1: module-lookup-amd@^7.0.0: version "7.0.1" - resolved "https://registry.yarnpkg.com/module-lookup-amd/-/module-lookup-amd-7.0.1.tgz#d67c1a93f2ff8e38b8774b99a638e9a4395774b2" + resolved "https://registry.npmjs.org/module-lookup-amd/-/module-lookup-amd-7.0.1.tgz" integrity sha512-w9mCNlj0S8qviuHzpakaLVc+/7q50jl9a/kmJ/n8bmXQZgDPkQHnPBb8MUOYh3WpAYkXuNc2c+khsozhIp/amQ== dependencies: commander "^2.8.1" @@ -5012,34 +5012,39 @@ module-lookup-amd@^7.0.0: requirejs "^2.3.5" requirejs-config-file "^4.0.0" +moment@^2.29.4: + version "2.30.1" + resolved "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz" + integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how== + ms@2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= ms@2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz" integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== ms@2.1.2: version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== ms@^2.1.1: version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== multicast-dns-service-types@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" + resolved "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz" integrity sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE= multicast-dns@^6.0.1: version "6.2.3" - resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.2.3.tgz#a0ec7bd9055c4282f790c3c82f4e28db3b31b229" + resolved "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz" integrity sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g== dependencies: dns-packet "^1.3.1" @@ -5047,64 +5052,64 @@ multicast-dns@^6.0.1: nanoclone@^0.2.1: version "0.2.1" - resolved "https://registry.yarnpkg.com/nanoclone/-/nanoclone-0.2.1.tgz#dd4090f8f1a110d26bb32c49ed2f5b9235209ed4" + resolved "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz" integrity sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA== nanoid@^3.1.30: version "3.2.0" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.2.0.tgz#62667522da6673971cca916a6d3eff3f415ff80c" + resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz" integrity sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA== natural-compare@^1.4.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= negotiator@0.6.2: version "0.6.2" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" + resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz" integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== neo-async@^2.6.2: version "2.6.2" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== nice-try@^1.0.4: version "1.0.5" - resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + resolved "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== node-forge@^1.2.0: version "1.3.0" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.0.tgz#37a874ea723855f37db091e6c186e5b67a01d4b2" + resolved "https://registry.npmjs.org/node-forge/-/node-forge-1.3.0.tgz" integrity sha512-08ARB91bUi6zNKzVmaj3QO7cr397uiDT2nJ63cHjyNtCTWIgvS47j3eT0WfzUwS9+6Z5YshRaoasFkXCKrIYbA== node-int64@^0.4.0: version "0.4.0" - resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + resolved "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz" integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs= node-modules-regexp@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" + resolved "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz" integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= node-releases@^2.0.1, node-releases@^2.0.6: version "2.0.6" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503" + resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz" integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg== node-source-walk@^4.0.0, node-source-walk@^4.2.0: version "4.2.0" - resolved "https://registry.yarnpkg.com/node-source-walk/-/node-source-walk-4.2.0.tgz#c2efe731ea8ba9c03c562aa0a9d984e54f27bc2c" + resolved "https://registry.npmjs.org/node-source-walk/-/node-source-walk-4.2.0.tgz" integrity sha512-hPs/QMe6zS94f5+jG3kk9E7TNm4P2SulrKiLWMzKszBfNZvL/V6wseHlTd7IvfW0NZWqPtK3+9yYNr+3USGteA== dependencies: "@babel/parser" "^7.0.0" normalize-package-data@^2.5.0: version "2.5.0" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz" integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== dependencies: hosted-git-info "^2.1.4" @@ -5114,31 +5119,31 @@ normalize-package-data@^2.5.0: normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== npm-run-path@^2.0.0: version "2.0.2" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz" integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= dependencies: path-key "^2.0.0" npm-run-path@^4.0.1: version "4.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz" integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== dependencies: path-key "^3.0.0" nwsapi@^2.2.0: version "2.2.0" - resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" + resolved "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz" integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== object-is@^1.0.1: version "1.1.5" - resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" + resolved "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz" integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== dependencies: call-bind "^1.0.2" @@ -5146,12 +5151,12 @@ object-is@^1.0.1: object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== object.assign@^4.1.0: version "4.1.2" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" + resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz" integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== dependencies: call-bind "^1.0.0" @@ -5161,38 +5166,38 @@ object.assign@^4.1.0: obuf@^1.0.0, obuf@^1.1.2: version "1.1.2" - resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" + resolved "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz" integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== on-finished@~2.3.0: version "2.3.0" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz" integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= dependencies: ee-first "1.1.1" on-headers@~1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" + resolved "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz" integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= dependencies: wrappy "1" onetime@^5.1.0, onetime@^5.1.2: version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + resolved "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz" integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== dependencies: mimic-fn "^2.1.0" open@^8.0.9: version "8.4.0" - resolved "https://registry.yarnpkg.com/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8" + resolved "https://registry.npmjs.org/open/-/open-8.4.0.tgz" integrity sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q== dependencies: define-lazy-prop "^2.0.0" @@ -5201,12 +5206,12 @@ open@^8.0.9: opencollective-postinstall@^2.0.2: version "2.0.3" - resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259" + resolved "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz" integrity sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q== optionator@^0.8.1: version "0.8.3" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + resolved "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz" integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== dependencies: deep-is "~0.1.3" @@ -5218,7 +5223,7 @@ optionator@^0.8.1: optionator@^0.9.1: version "0.9.1" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz" integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== dependencies: deep-is "^0.1.3" @@ -5230,7 +5235,7 @@ optionator@^0.9.1: ora@^5.4.1: version "5.4.1" - resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" + resolved "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz" integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== dependencies: bl "^4.1.0" @@ -5245,47 +5250,47 @@ ora@^5.4.1: p-finally@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + resolved "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz" integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= p-limit@^1.1.0: version "1.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz" integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== dependencies: p-try "^1.0.0" p-limit@^2.2.0: version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== dependencies: p-try "^2.0.0" p-locate@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + resolved "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz" integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= dependencies: p-limit "^1.1.0" p-locate@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + resolved "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz" integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== dependencies: p-limit "^2.2.0" p-map@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + resolved "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz" integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== dependencies: aggregate-error "^3.0.0" p-retry@^4.5.0: version "4.6.1" - resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.1.tgz#8fcddd5cdf7a67a0911a9cf2ef0e5df7f602316c" + resolved "https://registry.npmjs.org/p-retry/-/p-retry-4.6.1.tgz" integrity sha512-e2xXGNhZOZ0lfgR9kL34iGlU8N/KO0xZnQxVEwdeOvpqNDQfdnxIYizvWtK8RglUa3bGqI8g0R/BdfzLMxRkiA== dependencies: "@types/retry" "^0.12.0" @@ -5293,24 +5298,24 @@ p-retry@^4.5.0: p-try@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" + resolved "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz" integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= p-try@^2.0.0: version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== parent-module@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz" integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== dependencies: callsites "^3.0.0" parse-json@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" + resolved "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz" integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= dependencies: error-ex "^1.3.1" @@ -5318,7 +5323,7 @@ parse-json@^4.0.0: parse-json@^5.0.0: version "5.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + resolved "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz" integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== dependencies: "@babel/code-frame" "^7.0.0" @@ -5328,110 +5333,110 @@ parse-json@^5.0.0: parse-ms@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-2.1.0.tgz#348565a753d4391fa524029956b172cb7753097d" + resolved "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz" integrity sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA== parse5@6.0.1: version "6.0.1" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" + resolved "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz" integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== parseurl@~1.3.2, parseurl@~1.3.3: version "1.3.3" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== path-exists@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + resolved "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz" integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= path-exists@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== path-is-absolute@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= path-key@^2.0.0, path-key@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + resolved "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz" integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= path-key@^3.0.0, path-key@^3.1.0: version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== path-parse@^1.0.6: version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== path-to-regexp@0.1.7: version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz" integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= path-type@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== picocolors@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3: version "2.3.0" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz" integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== pify@^4.0.1: version "4.0.1" - resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" + resolved "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== pirates@^4.0.1: version "4.0.1" - resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87" + resolved "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz" integrity sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA== dependencies: node-modules-regexp "^1.0.0" pkg-dir@^4.2.0: version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + resolved "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz" integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== dependencies: find-up "^4.0.0" pkg-up@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-2.0.0.tgz#c819ac728059a461cab1c3889a2be3c49a004d7f" + resolved "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz" integrity sha1-yBmscoBZpGHKscOImivjxJoATX8= dependencies: find-up "^2.1.0" please-upgrade-node@^3.2.0: version "3.2.0" - resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942" + resolved "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz" integrity sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg== dependencies: semver-compare "^1.0.0" pluralize@^8.0.0: version "8.0.0" - resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" + resolved "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz" integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== portfinder@^1.0.28: version "1.0.28" - resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778" + resolved "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz" integrity sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA== dependencies: async "^2.6.2" @@ -5440,7 +5445,7 @@ portfinder@^1.0.28: postcss-values-parser@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/postcss-values-parser/-/postcss-values-parser-2.0.1.tgz#da8b472d901da1e205b47bdc98637b9e9e550e5f" + resolved "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-2.0.1.tgz" integrity sha512-2tLuBsA6P4rYTNKCXYG/71C7j1pU6pK503suYOmn4xYrQIzW+opD+7FAFNuGSdZC/3Qfy334QbeMu7MEb8gOxg== dependencies: flatten "^1.0.2" @@ -5449,7 +5454,7 @@ postcss-values-parser@^2.0.1: postcss-values-parser@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-values-parser/-/postcss-values-parser-5.0.0.tgz#10c61ac3f488e4de25746b829ea8d8894e9ac3d2" + resolved "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-5.0.0.tgz" integrity sha512-2viDDjMMrt21W2izbeiJxl3kFuD/+asgB0CBwPEgSyhCmBnDIa/y+pLaoyX+q3I3DHH0oPPL3cgjVTQvlS1Maw== dependencies: color-name "^1.1.4" @@ -5458,7 +5463,7 @@ postcss-values-parser@^5.0.0: postcss@^8.1.7, postcss@^8.2.13: version "8.3.11" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.11.tgz#c3beca7ea811cd5e1c4a3ec6d2e7599ef1f8f858" + resolved "https://registry.npmjs.org/postcss/-/postcss-8.3.11.tgz" integrity sha512-hCmlUAIlUiav8Xdqw3Io4LcpA1DOt7h3LSTAC4G6JGHFFaWzI6qvFt9oilvl8BmkbBRX1IhM90ZAmpk68zccQA== dependencies: nanoid "^3.1.30" @@ -5467,7 +5472,7 @@ postcss@^8.1.7, postcss@^8.2.13: precinct@^8.0.0, precinct@^8.1.0: version "8.2.0" - resolved "https://registry.yarnpkg.com/precinct/-/precinct-8.2.0.tgz#fb24ab42e51b84f2706b2bb81bb4814cdaf560c7" + resolved "https://registry.npmjs.org/precinct/-/precinct-8.2.0.tgz" integrity sha512-D9fQM/fAS7rGLA1m+PusoEMc07g9I5lZUf6rstT5XPCPn56raSIrj9R9y052YV8j4N+tjkgfCgT10bRQ3vg8+A== dependencies: commander "^2.20.3" @@ -5486,29 +5491,29 @@ precinct@^8.0.0, precinct@^8.1.0: prelude-ls@^1.2.1: version "1.2.1" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== prelude-ls@~1.1.2: version "1.1.2" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz" integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= prettier-linter-helpers@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + resolved "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz" integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== dependencies: fast-diff "^1.1.2" prettier@^2.2.1: version "2.4.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.4.1.tgz#671e11c89c14a4cfc876ce564106c4a6726c9f5c" + resolved "https://registry.npmjs.org/prettier/-/prettier-2.4.1.tgz" integrity sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA== pretty-format@^27.0.0, pretty-format@^27.3.1: version "27.3.1" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.3.1.tgz#7e9486365ccdd4a502061fa761d3ab9ca1b78df5" + resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-27.3.1.tgz" integrity sha512-DR/c+pvFc52nLimLROYjnXPtolawm+uWDxr4FjuLDLUn+ktWnSN851KoHwHzzqq6rfCOjkzN8FLgDrSub6UDuA== dependencies: "@jest/types" "^27.2.5" @@ -5518,24 +5523,24 @@ pretty-format@^27.0.0, pretty-format@^27.3.1: pretty-ms@^7.0.1: version "7.0.1" - resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-7.0.1.tgz#7d903eaab281f7d8e03c66f867e239dc32fb73e8" + resolved "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz" integrity sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q== dependencies: parse-ms "^2.1.0" process-nextick-args@~2.0.0: version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + resolved "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== progress@^2.0.0: version "2.0.3" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== prompts@^2.0.1: version "2.4.2" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + resolved "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz" integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== dependencies: kleur "^3.0.3" @@ -5543,12 +5548,12 @@ prompts@^2.0.1: property-expr@^2.0.4: version "2.0.4" - resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.4.tgz#37b925478e58965031bb612ec5b3260f8241e910" + resolved "https://registry.npmjs.org/property-expr/-/property-expr-2.0.4.tgz" integrity sha512-sFPkHQjVKheDNnPvotjQmm3KD3uk1fWKUN7CrpdbwmUx3CrG3QiM8QpTSimvig5vTXmTvjz7+TDvXOI9+4rkcg== proxy-addr@~2.0.5: version "2.0.7" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + resolved "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz" integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== dependencies: forwarded "0.2.0" @@ -5556,17 +5561,17 @@ proxy-addr@~2.0.5: proxy-from-env@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== psl@^1.1.33: version "1.8.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" + resolved "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz" integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== pump@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + resolved "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz" integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== dependencies: end-of-stream "^1.1.0" @@ -5574,39 +5579,39 @@ pump@^3.0.0: punycode@^2.1.0, punycode@^2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== qs@6.7.0: version "6.7.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" + resolved "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz" integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== queue-microtask@^1.2.2: version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== quote-unquote@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/quote-unquote/-/quote-unquote-1.0.0.tgz#67a9a77148effeaf81a4d428404a710baaac8a0b" + resolved "https://registry.npmjs.org/quote-unquote/-/quote-unquote-1.0.0.tgz" integrity sha1-Z6mncUjv/q+BpNQoQEpxC6qsigs= randombytes@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + resolved "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz" integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== dependencies: safe-buffer "^5.1.0" range-parser@^1.2.1, range-parser@~1.2.1: version "1.2.1" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== raw-body@2.4.0: version "2.4.0" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" + resolved "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz" integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== dependencies: bytes "3.1.0" @@ -5616,7 +5621,7 @@ raw-body@2.4.0: rc@^1.2.7: version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + resolved "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz" integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== dependencies: deep-extend "^0.6.0" @@ -5626,12 +5631,12 @@ rc@^1.2.7: react-is@^17.0.1: version "17.0.2" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" + resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== read-pkg@^5.2.0: version "5.2.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" + resolved "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz" integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== dependencies: "@types/normalize-package-data" "^2.4.0" @@ -5641,7 +5646,7 @@ read-pkg@^5.2.0: readable-stream@^2.0.1: version "2.3.7" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== dependencies: core-util-is "~1.0.0" @@ -5654,7 +5659,7 @@ readable-stream@^2.0.1: readable-stream@^3.0.6, readable-stream@^3.4.0: version "3.6.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== dependencies: inherits "^2.0.3" @@ -5663,14 +5668,14 @@ readable-stream@^3.0.6, readable-stream@^3.4.0: readdirp@~3.6.0: version "3.6.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz" integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== dependencies: picomatch "^2.2.1" realistic-structured-clone@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/realistic-structured-clone/-/realistic-structured-clone-3.0.0.tgz#7b518049ce2dad41ac32b421cd297075b00e3e35" + resolved "https://registry.npmjs.org/realistic-structured-clone/-/realistic-structured-clone-3.0.0.tgz" integrity sha512-rOjh4nuWkAqf9PWu6JVpOWD4ndI+JHfgiZeMmujYcPi+fvILUu7g6l26TC1K5aBIp34nV+jE1cDO75EKOfHC5Q== dependencies: domexception "^1.0.1" @@ -5679,38 +5684,38 @@ realistic-structured-clone@^3.0.0: rechoir@^0.7.0: version "0.7.1" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.7.1.tgz#9478a96a1ca135b5e88fc027f03ee92d6c645686" + resolved "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz" integrity sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg== dependencies: resolve "^1.9.0" regenerate-unicode-properties@^9.0.0: version "9.0.0" - resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-9.0.0.tgz#54d09c7115e1f53dc2314a974b32c1c344efe326" + resolved "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-9.0.0.tgz" integrity sha512-3E12UeNSPfjrgwjkR81m5J7Aw/T55Tu7nUyZVQYCKEOs+2dkxEY+DpPtZzO4YruuiPb7NkYLVcyJC4+zCbk5pA== dependencies: regenerate "^1.4.2" regenerate@^1.4.2: version "1.4.2" - resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" + resolved "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz" integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== regenerator-runtime@^0.13.4: version "0.13.9" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" + resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz" integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== regenerator-transform@^0.14.2: version "0.14.5" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.5.tgz#c98da154683671c9c4dcb16ece736517e1b7feb4" + resolved "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz" integrity sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw== dependencies: "@babel/runtime" "^7.8.4" regexp.prototype.flags@^1.2.0: version "1.3.1" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz#7ef352ae8d159e758c0eadca6f8fcb4eef07be26" + resolved "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz" integrity sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA== dependencies: call-bind "^1.0.2" @@ -5718,12 +5723,12 @@ regexp.prototype.flags@^1.2.0: regexpp@^3.1.0, regexpp@^3.2.0: version "3.2.0" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" + resolved "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz" integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== regexpu-core@^4.7.1: version "4.8.0" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.8.0.tgz#e5605ba361b67b1718478501327502f4479a98f0" + resolved "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.8.0.tgz" integrity sha512-1F6bYsoYiz6is+oz70NWur2Vlh9KWtswuRuzJOfeYUrfPX2o8n74AnUVaOGDbUqVGO9fNHu48/pjJO4sNVwsOg== dependencies: regenerate "^1.4.2" @@ -5735,29 +5740,29 @@ regexpu-core@^4.7.1: regjsgen@^0.5.2: version "0.5.2" - resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.2.tgz#92ff295fb1deecbf6ecdab2543d207e91aa33733" + resolved "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz" integrity sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A== regjsparser@^0.7.0: version "0.7.0" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.7.0.tgz#a6b667b54c885e18b52554cb4960ef71187e9968" + resolved "https://registry.npmjs.org/regjsparser/-/regjsparser-0.7.0.tgz" integrity sha512-A4pcaORqmNMDVwUjWoTzuhwMGpP+NykpfqAsEgI1FSH/EzC7lrN5TMd+kN8YCovX+jMpu8eaqXgXPCa0g8FQNQ== dependencies: jsesc "~0.5.0" require-directory@^2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= require-from-string@^2.0.2: version "2.0.2" - resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + resolved "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== requirejs-config-file@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/requirejs-config-file/-/requirejs-config-file-4.0.0.tgz#4244da5dd1f59874038cc1091d078d620abb6ebc" + resolved "https://registry.npmjs.org/requirejs-config-file/-/requirejs-config-file-4.0.0.tgz" integrity sha512-jnIre8cbWOyvr8a5F2KuqBnY+SDA4NXr/hzEZJG79Mxm2WiFQz2dzhC8ibtPJS7zkmBEl1mxSwp5HhC1W4qpxw== dependencies: esprima "^4.0.0" @@ -5765,54 +5770,54 @@ requirejs-config-file@^4.0.0: requirejs@^2.3.5: version "2.3.6" - resolved "https://registry.yarnpkg.com/requirejs/-/requirejs-2.3.6.tgz#e5093d9601c2829251258c0b9445d4d19fa9e7c9" + resolved "https://registry.npmjs.org/requirejs/-/requirejs-2.3.6.tgz" integrity sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg== requires-port@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + resolved "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz" integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= reselect@^3.0.1: version "3.0.1" - resolved "https://registry.yarnpkg.com/reselect/-/reselect-3.0.1.tgz#efdaa98ea7451324d092b2b2163a6a1d7a9a2147" + resolved "https://registry.npmjs.org/reselect/-/reselect-3.0.1.tgz" integrity sha1-79qpjqdFEyTQkrKyFjpqHXqaIUc= resolve-cwd@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + resolved "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz" integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== dependencies: resolve-from "^5.0.0" resolve-dependency-path@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/resolve-dependency-path/-/resolve-dependency-path-2.0.0.tgz#11700e340717b865d216c66cabeb4a2a3c696736" + resolved "https://registry.npmjs.org/resolve-dependency-path/-/resolve-dependency-path-2.0.0.tgz" integrity sha512-DIgu+0Dv+6v2XwRaNWnumKu7GPufBBOr5I1gRPJHkvghrfCGOooJODFvgFimX/KRxk9j0whD2MnKHzM1jYvk9w== resolve-from@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz" integrity sha1-six699nWiBvItuZTM17rywoYh0g= resolve-from@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== resolve-from@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== resolve.exports@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9" + resolved "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz" integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== resolve@>=1.9.0, resolve@^1.10.0, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.4.0, resolve@^1.9.0: version "1.20.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz" integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== dependencies: is-core-module "^2.2.0" @@ -5820,7 +5825,7 @@ resolve@>=1.9.0, resolve@^1.10.0, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.2 restore-cursor@^3.1.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + resolved "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz" integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== dependencies: onetime "^5.1.0" @@ -5828,84 +5833,84 @@ restore-cursor@^3.1.0: retry@^0.13.1: version "0.13.1" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" + resolved "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz" integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== reusify@^1.0.4: version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== dependencies: glob "^7.1.3" run-node@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/run-node/-/run-node-1.0.0.tgz#46b50b946a2aa2d4947ae1d886e9856fd9cabe5e" + resolved "https://registry.npmjs.org/run-node/-/run-node-1.0.0.tgz" integrity sha512-kc120TBlQ3mih1LSzdAJXo4xn/GWS2ec0l3S+syHDXP9uRr0JAT8Qd3mdMuyjqCzeZktgP3try92cEgf9Nks8A== run-parallel@^1.1.9: version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz" integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== dependencies: queue-microtask "^1.2.2" rxjs@^6.6.3: version "6.6.7" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" + resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz" integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== dependencies: tslib "^1.9.0" rxjs@^7.4.0: version "7.4.0" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.4.0.tgz#a12a44d7eebf016f5ff2441b87f28c9a51cebc68" + resolved "https://registry.npmjs.org/rxjs/-/rxjs-7.4.0.tgz" integrity sha512-7SQDi7xeTMCJpqViXh8gL/lebcwlp3d831F05+9B44A4B0WfsEwUQHR64gsH1kvJ+Ep/J9K2+n1hVl1CsGN23w== dependencies: tslib "~2.1.0" safari-14-idb-fix@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/safari-14-idb-fix/-/safari-14-idb-fix-3.0.0.tgz#450fc049b996ec7f3fd9ca2f89d32e0761583440" + resolved "https://registry.npmjs.org/safari-14-idb-fix/-/safari-14-idb-fix-3.0.0.tgz" integrity sha512-eBNFLob4PMq8JA1dGyFn6G97q3/WzNtFK4RnzT1fnLq+9RyrGknzYiM/9B12MnKAxuj1IXr7UKYtTNtjyKMBog== safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== "safer-buffer@>= 2.1.2 < 3": version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== sass-lookup@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/sass-lookup/-/sass-lookup-3.0.0.tgz#3b395fa40569738ce857bc258e04df2617c48cac" + resolved "https://registry.npmjs.org/sass-lookup/-/sass-lookup-3.0.0.tgz" integrity sha512-TTsus8CfFRn1N44bvdEai1no6PqdmDiQUiqW5DlpmtT+tYnIt1tXtDIph5KA1efC+LmioJXSnCtUVpcK9gaKIg== dependencies: commander "^2.16.0" saxes@^5.0.1: version "5.0.1" - resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" + resolved "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz" integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== dependencies: xmlchars "^2.2.0" schema-utils@^3.1.0, schema-utils@^3.1.1: version "3.1.1" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281" + resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz" integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw== dependencies: "@types/json-schema" "^7.0.8" @@ -5914,7 +5919,7 @@ schema-utils@^3.1.0, schema-utils@^3.1.1: schema-utils@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.0.0.tgz#60331e9e3ae78ec5d16353c467c34b3a0a1d3df7" + resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz" integrity sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg== dependencies: "@types/json-schema" "^7.0.9" @@ -5924,53 +5929,53 @@ schema-utils@^4.0.0: select-hose@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" + resolved "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz" integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= selfsigned@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.0.0.tgz#e927cd5377cbb0a1075302cff8df1042cc2bce5b" + resolved "https://registry.npmjs.org/selfsigned/-/selfsigned-2.0.0.tgz" integrity sha512-cUdFiCbKoa1mZ6osuJs2uDHrs0k0oprsKveFiiaBKCNq3SYyb5gs2HxhQyDNLCmL51ZZThqi4YNDpCK6GOP1iQ== dependencies: node-forge "^1.2.0" semver-compare@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" + resolved "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz" integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= "semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.6.0: version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + resolved "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== semver@7.0.0: version "7.0.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" + resolved "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== semver@7.x, semver@^7.2.1, semver@^7.3.2, semver@^7.3.5: version "7.3.5" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" + resolved "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz" integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== dependencies: lru-cache "^6.0.0" semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: version "6.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== semver@^7.3.7: version "7.3.7" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" + resolved "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz" integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== dependencies: lru-cache "^6.0.0" send@0.17.1: version "0.17.1" - resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" + resolved "https://registry.npmjs.org/send/-/send-0.17.1.tgz" integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== dependencies: debug "2.6.9" @@ -5989,14 +5994,14 @@ send@0.17.1: serialize-javascript@^6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" + resolved "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz" integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== dependencies: randombytes "^2.1.0" serve-index@^1.9.1: version "1.9.1" - resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" + resolved "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz" integrity sha1-03aNabHn2C5c4FD/9bRTvqEqkjk= dependencies: accepts "~1.3.4" @@ -6009,7 +6014,7 @@ serve-index@^1.9.1: serve-static@1.14.1: version "1.14.1" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" + resolved "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz" integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== dependencies: encodeurl "~1.0.2" @@ -6019,68 +6024,68 @@ serve-static@1.14.1: setprototypeof@1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" + resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz" integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== setprototypeof@1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" + resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz" integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== shallow-clone@^3.0.0: version "3.0.1" - resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" + resolved "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz" integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== dependencies: kind-of "^6.0.2" shebang-command@^1.2.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz" integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= dependencies: shebang-regex "^1.0.0" shebang-command@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== dependencies: shebang-regex "^3.0.0" shebang-regex@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz" integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= shebang-regex@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: version "3.0.5" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.5.tgz#9e3e8cc0c75a99472b44321033a7702e7738252f" + resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.5.tgz" integrity sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ== sisteransi@^1.0.5: version "1.0.5" - resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + resolved "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz" integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== slash@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" + resolved "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz" integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== slash@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== slice-ansi@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" + resolved "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz" integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ== dependencies: ansi-styles "^4.0.0" @@ -6089,7 +6094,7 @@ slice-ansi@^3.0.0: slice-ansi@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + resolved "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz" integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== dependencies: ansi-styles "^4.0.0" @@ -6098,7 +6103,7 @@ slice-ansi@^4.0.0: sockjs@^0.3.21: version "0.3.21" - resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.21.tgz#b34ffb98e796930b60a0cfa11904d6a339a7d417" + resolved "https://registry.npmjs.org/sockjs/-/sockjs-0.3.21.tgz" integrity sha512-DhbPFGpxjc6Z3I+uX07Id5ZO2XwYsWOrYjaSeieES78cq+JaJvVe5q/m1uvjIQhXinhIeCFRH6JgXe+mvVMyXw== dependencies: faye-websocket "^0.11.3" @@ -6107,12 +6112,12 @@ sockjs@^0.3.21: source-map-js@^0.6.2: version "0.6.2" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-0.6.2.tgz#0bb5de631b41cfbda6cfba8bd05a80efdfd2385e" + resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz" integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug== source-map-support@^0.5.6: version "0.5.20" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.20.tgz#12166089f8f5e5e8c56926b377633392dd2cb6c9" + resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz" integrity sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw== dependencies: buffer-from "^1.0.0" @@ -6120,7 +6125,7 @@ source-map-support@^0.5.6: source-map-support@~0.5.20: version "0.5.21" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz" integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== dependencies: buffer-from "^1.0.0" @@ -6128,27 +6133,27 @@ source-map-support@~0.5.20: source-map@^0.5.0: version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== source-map@^0.7.3: version "0.7.3" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz" integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== spawn-command@^0.0.2-1: version "0.0.2-1" - resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2-1.tgz#62f5e9466981c1b796dc5929937e11c9c6921bd0" + resolved "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz" integrity sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A= spdx-correct@^3.0.0: version "3.1.1" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" + resolved "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz" integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== dependencies: spdx-expression-parse "^3.0.0" @@ -6156,12 +6161,12 @@ spdx-correct@^3.0.0: spdx-exceptions@^2.1.0: version "2.3.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" + resolved "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz" integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== spdx-expression-parse@^3.0.0: version "3.0.1" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + resolved "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz" integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== dependencies: spdx-exceptions "^2.1.0" @@ -6169,12 +6174,12 @@ spdx-expression-parse@^3.0.0: spdx-license-ids@^3.0.0: version "3.0.10" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz#0d9becccde7003d6c658d487dd48a32f0bf3014b" + resolved "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz" integrity sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA== spdy-transport@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" + resolved "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz" integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== dependencies: debug "^4.1.0" @@ -6186,7 +6191,7 @@ spdy-transport@^3.0.0: spdy@^4.0.2: version "4.0.2" - resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" + resolved "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz" integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== dependencies: debug "^4.1.0" @@ -6197,29 +6202,29 @@ spdy@^4.0.2: sprintf-js@~1.0.2: version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= stack-utils@^2.0.3: version "2.0.5" - resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5" + resolved "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz" integrity sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA== dependencies: escape-string-regexp "^2.0.0" "statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@~1.5.0: version "1.5.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= string-argv@0.3.1: version "0.3.1" - resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" + resolved "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz" integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg== string-length@^4.0.1: version "4.0.2" - resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + resolved "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz" integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== dependencies: char-regex "^1.0.2" @@ -6227,7 +6232,7 @@ string-length@^4.0.1: string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== dependencies: emoji-regex "^8.0.0" @@ -6236,21 +6241,21 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: string_decoder@^1.1.1: version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== dependencies: safe-buffer "~5.2.0" string_decoder@~1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== dependencies: safe-buffer "~5.1.0" stringify-object@3.3.0, stringify-object@^3.2.1: version "3.3.0" - resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629" + resolved "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz" integrity sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw== dependencies: get-own-enumerable-property-symbols "^3.0.0" @@ -6259,46 +6264,46 @@ stringify-object@3.3.0, stringify-object@^3.2.1: strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== dependencies: ansi-regex "^5.0.1" strip-ansi@^7.0.0: version "7.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz" integrity sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw== dependencies: ansi-regex "^6.0.1" strip-bom@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz" integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== strip-eof@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + resolved "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz" integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= strip-final-newline@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + resolved "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== strip-json-comments@~2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= stylus-lookup@^3.0.1: version "3.0.2" - resolved "https://registry.yarnpkg.com/stylus-lookup/-/stylus-lookup-3.0.2.tgz#c9eca3ff799691020f30b382260a67355fefdddd" + resolved "https://registry.npmjs.org/stylus-lookup/-/stylus-lookup-3.0.2.tgz" integrity sha512-oEQGHSjg/AMaWlKe7gqsnYzan8DLcGIHe0dUaFkucZZ14z4zjENRlQMCHT4FNsiWnJf17YN9OvrCfCoi7VvOyg== dependencies: commander "^2.8.1" @@ -6306,28 +6311,28 @@ stylus-lookup@^3.0.1: supports-color@8.1.1, supports-color@^8.0.0, supports-color@^8.1.0: version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz" integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== dependencies: has-flag "^4.0.0" supports-color@^5.3.0: version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== dependencies: has-flag "^3.0.0" supports-color@^7.0.0, supports-color@^7.1.0: version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== dependencies: has-flag "^4.0.0" supports-hyperlinks@^2.0.0: version "2.2.0" - resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz#4f77b42488765891774b70c79babd87f9bd594bb" + resolved "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz" integrity sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ== dependencies: has-flag "^4.0.0" @@ -6335,12 +6340,12 @@ supports-hyperlinks@^2.0.0: symbol-tree@^3.2.4: version "3.2.4" - resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + resolved "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== table@^6.0.9: version "6.7.3" - resolved "https://registry.yarnpkg.com/table/-/table-6.7.3.tgz#255388439715a738391bd2ee4cbca89a4d05a9b7" + resolved "https://registry.npmjs.org/table/-/table-6.7.3.tgz" integrity sha512-5DkIxeA7XERBqMwJq0aHZOdMadBx4e6eDoFRuyT5VR82J0Ycg2DwM6GfA/EQAhJ+toRTaS1lIdSQCqgrmhPnlw== dependencies: ajv "^8.0.1" @@ -6351,17 +6356,17 @@ table@^6.0.9: tapable@^2.1.1, tapable@^2.2.0: version "2.2.1" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" + resolved "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz" integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== temp@~0.4.0: version "0.4.0" - resolved "https://registry.yarnpkg.com/temp/-/temp-0.4.0.tgz#671ad63d57be0fe9d7294664b3fc400636678a60" + resolved "https://registry.npmjs.org/temp/-/temp-0.4.0.tgz" integrity sha1-ZxrWPVe+D+nXKUZks/xABjZnimA= terminal-link@^2.0.0: version "2.1.1" - resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" + resolved "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz" integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== dependencies: ansi-escapes "^4.2.1" @@ -6369,7 +6374,7 @@ terminal-link@^2.0.0: terser-webpack-plugin@^5.1.3: version "5.3.5" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.5.tgz#f7d82286031f915a4f8fb81af4bd35d2e3c011bc" + resolved "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.5.tgz" integrity sha512-AOEDLDxD2zylUGf/wxHxklEkOe2/r+seuyOWujejFrIxHf11brA1/dWQNIgXa1c6/Wkxgu7zvv0JhOWfc2ELEA== dependencies: "@jridgewell/trace-mapping" "^0.3.14" @@ -6380,7 +6385,7 @@ terser-webpack-plugin@^5.1.3: terser@^5.14.1: version "5.14.2" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.14.2.tgz#9ac9f22b06994d736174f4091aa368db896f1c10" + resolved "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz" integrity sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA== dependencies: "@jridgewell/source-map" "^0.3.2" @@ -6390,7 +6395,7 @@ terser@^5.14.1: test-exclude@^6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + resolved "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz" integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== dependencies: "@istanbuljs/schema" "^0.1.2" @@ -6399,59 +6404,59 @@ test-exclude@^6.0.0: text-table@^0.2.0: version "0.2.0" - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= throat@^6.0.1: version "6.0.1" - resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375" + resolved "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz" integrity sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w== throttle-debounce@^3.0.1: version "3.0.1" - resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-3.0.1.tgz#32f94d84dfa894f786c9a1f290e7a645b6a19abb" + resolved "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-3.0.1.tgz" integrity sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg== through@^2.3.8: version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= thunky@^1.0.2: version "1.1.0" - resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" + resolved "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz" integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== tmpl@1.0.5: version "1.0.5" - resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + resolved "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz" integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== to-fast-properties@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz" integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= to-regex-range@^5.0.1: version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== dependencies: is-number "^7.0.0" toidentifier@1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" + resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz" integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== toposort@^2.0.2: version "2.0.2" - resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330" + resolved "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz" integrity sha1-riF2gXXRVZ1IvvNUILL0li8JwzA= tough-cookie@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4" + resolved "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz" integrity sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg== dependencies: psl "^1.1.33" @@ -6460,19 +6465,19 @@ tough-cookie@^4.0.0: tr46@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.1.0.tgz#fa87aa81ca5d5941da8cbf1f9b749dc969a4e240" + resolved "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz" integrity sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw== dependencies: punycode "^2.1.1" tree-kill@^1.2.2: version "1.2.2" - resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" + resolved "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz" integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== ts-jest@^27.0.7: version "27.0.7" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-27.0.7.tgz#fb7c8c8cb5526ab371bc1b23d06e745652cca2d0" + resolved "https://registry.npmjs.org/ts-jest/-/ts-jest-27.0.7.tgz" integrity sha512-O41shibMqzdafpuP+CkrOL7ykbmLh+FqQrXEmV9CydQ5JBk0Sj0uAEF5TNNe94fZWKm3yYvWa/IbyV4Yg1zK2Q== dependencies: bs-logger "0.x" @@ -6486,65 +6491,65 @@ ts-jest@^27.0.7: tslib@^1.8.1, tslib@^1.9.0: version "1.14.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== tslib@~2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz" integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A== tsutils@^3.21.0: version "3.21.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz" integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== dependencies: tslib "^1.8.1" ttypescript@^1.5.15: version "1.5.15" - resolved "https://registry.yarnpkg.com/ttypescript/-/ttypescript-1.5.15.tgz#e45550ad69289d06d3bc3fd4a3c87e7c1ef3eba7" + resolved "https://registry.npmjs.org/ttypescript/-/ttypescript-1.5.15.tgz" integrity sha512-48ykDNHzFnPMnv4hYX1P8Q84TvCZyL1QlFxeuxsuZ48X2+ameBgPenvmCkHJtoOSxpoWTWi8NcgNrRnVDOmfSg== dependencies: resolve ">=1.9.0" type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz" integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== dependencies: prelude-ls "^1.2.1" type-check@~0.3.2: version "0.3.2" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + resolved "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz" integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= dependencies: prelude-ls "~1.1.2" type-detect@4.0.8: version "4.0.8" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + resolved "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== type-fest@^0.20.2: version "0.20.2" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== type-fest@^0.21.3: version "0.21.3" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz" integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== type-fest@^0.6.0: version "0.6.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz" integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== type-is@~1.6.17, type-is@~1.6.18: version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + resolved "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz" integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== dependencies: media-typer "0.3.0" @@ -6552,31 +6557,31 @@ type-is@~1.6.17, type-is@~1.6.18: typedarray-to-buffer@^3.1.5: version "3.1.5" - resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + resolved "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz" integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== dependencies: is-typedarray "^1.0.0" typescript-transform-paths@^3.4.6: version "3.4.6" - resolved "https://registry.yarnpkg.com/typescript-transform-paths/-/typescript-transform-paths-3.4.6.tgz#28e6b24eb17a34116484a4b7af7323b8bb756db6" + resolved "https://registry.npmjs.org/typescript-transform-paths/-/typescript-transform-paths-3.4.6.tgz" integrity sha512-qdgpCk9oRHkIBhznxaHAapCFapJt5e4FbFik7Y4qdqtp6VyC3smAIPoDEIkjZ2eiF7x5+QxUPYNwJAtw0thsTw== dependencies: minimatch "^3.0.4" typescript@^3.9.5, typescript@^3.9.7: version "3.9.10" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.10.tgz#70f3910ac7a51ed6bef79da7800690b19bf778b8" + resolved "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz" integrity sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q== typescript@^4.9.5: version "4.9.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" + resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz" integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== typeson-registry@^1.0.0-alpha.20: version "1.0.0-alpha.39" - resolved "https://registry.yarnpkg.com/typeson-registry/-/typeson-registry-1.0.0-alpha.39.tgz#9e0f5aabd5eebfcffd65a796487541196f4b1211" + resolved "https://registry.npmjs.org/typeson-registry/-/typeson-registry-1.0.0-alpha.39.tgz" integrity sha512-NeGDEquhw+yfwNhguLPcZ9Oj0fzbADiX4R0WxvoY8nGhy98IbzQy1sezjoEFWOywOboj/DWehI+/aUlRVrJnnw== dependencies: base64-arraybuffer-es6 "^0.7.0" @@ -6585,17 +6590,17 @@ typeson-registry@^1.0.0-alpha.20: typeson@^6.0.0, typeson@^6.1.0: version "6.1.0" - resolved "https://registry.yarnpkg.com/typeson/-/typeson-6.1.0.tgz#5b2a53705a5f58ff4d6f82f965917cabd0d7448b" + resolved "https://registry.npmjs.org/typeson/-/typeson-6.1.0.tgz" integrity sha512-6FTtyGr8ldU0pfbvW/eOZrEtEkczHRUtduBnA90Jh9kMPCiFNnXIon3vF41N0S4tV1HHQt4Hk1j4srpESziCaA== unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" + resolved "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz" integrity sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ== unicode-match-property-ecmascript@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz#54fd16e0ecb167cf04cf1f756bdcc92eba7976c3" + resolved "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz" integrity sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q== dependencies: unicode-canonical-property-names-ecmascript "^2.0.0" @@ -6603,32 +6608,32 @@ unicode-match-property-ecmascript@^2.0.0: unicode-match-property-value-ecmascript@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz#1a01aa57247c14c568b89775a54938788189a714" + resolved "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz" integrity sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw== unicode-property-aliases-ecmascript@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz#0a36cb9a585c4f6abd51ad1deddb285c165297c8" + resolved "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz" integrity sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ== uniq@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" + resolved "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz" integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8= universalify@^0.1.2: version "0.1.2" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + resolved "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= update-browserslist-db@^1.0.5: version "1.0.5" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz#be06a5eedd62f107b7c19eb5bcefb194411abf38" + resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz" integrity sha512-dteFFpCyvuDdr9S/ff1ISkKt/9YZxKjI9WlRR99c180GaztJtRa/fn18FdxGVKVsnPY7/a/FDN68mcvUmP4U7Q== dependencies: escalade "^3.1.1" @@ -6636,34 +6641,34 @@ update-browserslist-db@^1.0.5: uri-js@^4.2.2: version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== dependencies: punycode "^2.1.0" util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= utils-merge@1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz" integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= uuid@^3.4.0: version "3.4.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + resolved "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== 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" + resolved "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== v8-to-istanbul@^8.1.0: version "8.1.0" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-8.1.0.tgz#0aeb763894f1a0a1676adf8a8b7612a38902446c" + resolved "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.0.tgz" integrity sha512-/PRhfd8aTNp9Ggr62HPzXg2XasNFGy5PBt0Rp04du7/8GNNSgxFL6WBTkgMKSL9bFjH+8kKEG3f37FmxiTqUUA== dependencies: "@types/istanbul-lib-coverage" "^2.0.1" @@ -6672,7 +6677,7 @@ v8-to-istanbul@^8.1.0: validate-npm-package-license@^3.0.1: version "3.0.4" - resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + resolved "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz" integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== dependencies: spdx-correct "^3.0.0" @@ -6680,38 +6685,38 @@ validate-npm-package-license@^3.0.1: vary@~1.1.2: version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz" integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= w3c-hr-time@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" + resolved "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz" integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== dependencies: browser-process-hrtime "^1.0.0" w3c-xmlserializer@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz#3e7104a05b75146cc60f564380b7f683acf1020a" + resolved "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz" integrity sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA== dependencies: xml-name-validator "^3.0.0" walkdir@^0.4.1: version "0.4.1" - resolved "https://registry.yarnpkg.com/walkdir/-/walkdir-0.4.1.tgz#dc119f83f4421df52e3061e514228a2db20afa39" + resolved "https://registry.npmjs.org/walkdir/-/walkdir-0.4.1.tgz" integrity sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ== walker@^1.0.7: version "1.0.8" - resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + resolved "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz" integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== dependencies: makeerror "1.0.12" watchpack@^2.4.0: version "2.4.0" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" + resolved "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz" integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg== dependencies: glob-to-regexp "^0.4.1" @@ -6719,36 +6724,36 @@ watchpack@^2.4.0: wbuf@^1.1.0, wbuf@^1.7.3: version "1.7.3" - resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" + resolved "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz" integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== dependencies: minimalistic-assert "^1.0.0" wcwidth@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" + resolved "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz" integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g= dependencies: defaults "^1.0.3" webidl-conversions@^4.0.2: version "4.0.2" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" + resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz" integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== webidl-conversions@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" + resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz" integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA== webidl-conversions@^6.1.0: version "6.1.0" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" + resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz" integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== webpack-cli@^4.9.1: version "4.9.1" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.9.1.tgz#b64be825e2d1b130f285c314caa3b1ba9a4632b3" + resolved "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.9.1.tgz" integrity sha512-JYRFVuyFpzDxMDB+v/nanUdQYcZtqFPGzmlW4s+UkPMFhSpfRNmf1z4AwYcHJVdvEFAM7FFCQdNTpsBYhDLusQ== dependencies: "@discoveryjs/json-ext" "^0.5.0" @@ -6766,7 +6771,7 @@ webpack-cli@^4.9.1: webpack-dev-middleware@^5.3.0: version "5.3.0" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.0.tgz#8fc02dba6e72e1d373eca361623d84610f27be7c" + resolved "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.0.tgz" integrity sha512-MouJz+rXAm9B1OTOYaJnn6rtD/lWZPy2ufQCH3BPs8Rloh/Du6Jze4p7AeLYHkVi0giJnYLaSGDC7S+GM9arhg== dependencies: colorette "^2.0.10" @@ -6777,7 +6782,7 @@ webpack-dev-middleware@^5.3.0: webpack-dev-server@^4.7.3: version "4.7.3" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.7.3.tgz#4e995b141ff51fa499906eebc7906f6925d0beaa" + resolved "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.7.3.tgz" integrity sha512-mlxq2AsIw2ag016nixkzUkdyOE8ST2GTy34uKSABp1c4nhjZvH90D5ZRR+UOLSsG4Z3TFahAi72a3ymRtfRm+Q== dependencies: "@types/bonjour" "^3.5.9" @@ -6812,7 +6817,7 @@ webpack-dev-server@^4.7.3: webpack-merge@^5.7.3: version "5.8.0" - resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.8.0.tgz#2b39dbf22af87776ad744c390223731d30a68f61" + resolved "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz" integrity sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q== dependencies: clone-deep "^4.0.1" @@ -6820,12 +6825,12 @@ webpack-merge@^5.7.3: webpack-sources@^3.2.3: version "3.2.3" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" + resolved "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz" integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== webpack@^5.63.0: version "5.74.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.74.0.tgz#02a5dac19a17e0bb47093f2be67c695102a55980" + resolved "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz" integrity sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA== dependencies: "@types/eslint-scope" "^3.7.3" @@ -6855,7 +6860,7 @@ webpack@^5.63.0: websocket-driver@>=0.5.1, websocket-driver@^0.7.4: version "0.7.4" - resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" + resolved "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz" integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== dependencies: http-parser-js ">=0.5.1" @@ -6864,24 +6869,24 @@ websocket-driver@>=0.5.1, websocket-driver@^0.7.4: websocket-extensions@>=0.1.1: version "0.1.4" - resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" + resolved "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz" integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== whatwg-encoding@^1.0.5: version "1.0.5" - resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" + resolved "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz" integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== dependencies: iconv-lite "0.4.24" whatwg-mimetype@^2.3.0: version "2.3.0" - resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" + resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz" integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== whatwg-url@^8.0.0, whatwg-url@^8.4.0, whatwg-url@^8.5.0: version "8.7.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77" + resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz" integrity sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg== dependencies: lodash "^4.7.0" @@ -6890,31 +6895,31 @@ whatwg-url@^8.0.0, whatwg-url@^8.4.0, whatwg-url@^8.5.0: which@^1.2.9: version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + resolved "https://registry.npmjs.org/which/-/which-1.3.1.tgz" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== dependencies: isexe "^2.0.0" which@^2.0.1: version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== dependencies: isexe "^2.0.0" wildcard@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" + resolved "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz" integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== word-wrap@^1.2.3, word-wrap@~1.2.3: version "1.2.3" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== wrap-ansi@^6.2.0: version "6.2.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz" integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== dependencies: ansi-styles "^4.0.0" @@ -6923,7 +6928,7 @@ wrap-ansi@^6.2.0: wrap-ansi@^7.0.0: version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== dependencies: ansi-styles "^4.0.0" @@ -6932,12 +6937,12 @@ wrap-ansi@^7.0.0: wrappy@1: version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= write-file-atomic@^3.0.0: version "3.0.3" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" + resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz" integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== dependencies: imurmurhash "^0.1.4" @@ -6947,47 +6952,47 @@ write-file-atomic@^3.0.0: ws@^7.4.6: version "7.5.5" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.5.tgz#8b4bc4af518cfabd0473ae4f99144287b33eb881" + resolved "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz" integrity sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w== ws@^8.1.0: version "8.2.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.2.3.tgz#63a56456db1b04367d0b721a0b80cae6d8becbba" + resolved "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz" integrity sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA== xml-name-validator@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" + resolved "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz" integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== xmlchars@^2.2.0: version "2.2.0" - resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + resolved "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== y18n@^5.0.5: version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== yallist@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== yaml@^1.10.0: version "1.10.2" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== yargs-parser@20.x, yargs-parser@^20.2.2: version "20.2.9" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== yargs@^16.2.0: version "16.2.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + resolved "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz" integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== dependencies: cliui "^7.0.2" @@ -7000,7 +7005,7 @@ yargs@^16.2.0: yup@^0.32.9: version "0.32.11" - resolved "https://registry.yarnpkg.com/yup/-/yup-0.32.11.tgz#d67fb83eefa4698607982e63f7ca4c5ed3cf18c5" + resolved "https://registry.npmjs.org/yup/-/yup-0.32.11.tgz" integrity sha512-Z2Fe1bn+eLstG8DRR6FTavGD+MeAwyfmouhHsIUgaADz8jvFKbO/fXc2trJKZg+5EBjh4gGm3iU/t3onKlXHIg== dependencies: "@babel/runtime" "^7.15.4" From 287aa892354e81acbef988986dc51fd5efe8eaa0 Mon Sep 17 00:00:00 2001 From: hardikmashru <150107929+hardikmashru@users.noreply.github.com> Date: Wed, 24 Apr 2024 01:50:46 +0530 Subject: [PATCH 02/88] Add user object to trackanonsession and create user after live/valid criteria met (#346) --- src/users/types.ts | 4 ++++ src/utils/anonymousUserEventManager.ts | 23 ++++++++++++++++------- src/utils/types.ts | 5 +++-- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/users/types.ts b/src/users/types.ts index a142c78b..81f01236 100644 --- a/src/users/types.ts +++ b/src/users/types.ts @@ -18,6 +18,10 @@ export interface UpdateUserParams { mergeNestedObjects?: boolean; } +export interface UpdateAnonymousUserParams extends UpdateUserParams { + createNewFields?: boolean; +} + export interface UpdateSubscriptionParams { emailListIds: number[]; unsubscribedChannelIds: number[]; diff --git a/src/utils/anonymousUserEventManager.ts b/src/utils/anonymousUserEventManager.ts index 464900d6..14f16453 100644 --- a/src/utils/anonymousUserEventManager.ts +++ b/src/utils/anonymousUserEventManager.ts @@ -160,14 +160,18 @@ export class AnonymousUserEventManager { private async createKnownUser(criteriaId: string) { const userData = localStorage.getItem(SHARED_PREFS_ANON_SESSIONS); - this.setUserID(uuidv4()); + const userId = uuidv4(); if (userData) { const userSessionInfo = JSON.parse(userData); const userDataJson = userSessionInfo[SHARED_PREFS_ANON_SESSIONS]; const payload: TrackAnonSessionParams = { - email: this.getEmail() || undefined, - userId: this.getEmail() ? null : this.getUserID() || undefined, + user: { + userId, + preferUserId: true, + mergeNestedObjects: true, + createNewFields: true + }, createdAt: this.getCurrentTime(), deviceInfo: { appPackageName: window.location.hostname, @@ -184,15 +188,20 @@ export class AnonymousUserEventManager { } }; - setTimeout(() => { - baseIterableRequest({ + setTimeout(async () => { + const response = await baseIterableRequest({ method: 'POST', url: ENDPOINT_TRACK_ANON_SESSION, data: payload }).catch((e) => { - console.log('response', e); + if (e?.response?.status === 409) { + this.getAnonCriteria(); + } }); - this.syncEvents(); + if (response && response.status === 200) { + this.setUserID(userId); + this.syncEvents(); + } }, 500); } else { this.syncEvents(); diff --git a/src/utils/types.ts b/src/utils/types.ts index 6e7fb042..656aadeb 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -1,3 +1,5 @@ +import { UpdateAnonymousUserParams } from '..'; + interface AnonSessionContext { totalAnonSessionCount?: number; lastAnonSession?: number; @@ -8,8 +10,7 @@ interface AnonSessionContext { } export interface TrackAnonSessionParams { - email?: string | null; - userId?: string | null; + user: UpdateAnonymousUserParams; createdAt: number; deviceInfo: { deviceId: string; From e1a965113aa59f42733113c5d9e72d92321f913d Mon Sep 17 00:00:00 2001 From: Hardik Mashru Date: Thu, 30 May 2024 11:17:03 +0530 Subject: [PATCH 03/88] aut changes --- react-example/src/components/LoginForm.tsx | 33 +-- react-example/src/index.tsx | 41 +-- react-example/yarn.lock | 126 ++++++++- src/authorization/authorization.ts | 291 ++++++++------------- src/commerce/commerce.ts | 18 +- src/constants.ts | 3 +- src/utils/anonymousUserEventManager.ts | 50 +--- src/utils/anonymousUserMerge.ts | 6 +- yarn.lock | 10 + 9 files changed, 290 insertions(+), 288 deletions(-) diff --git a/react-example/src/components/LoginForm.tsx b/react-example/src/components/LoginForm.tsx index ddd15c83..90a2309a 100644 --- a/react-example/src/components/LoginForm.tsx +++ b/react-example/src/components/LoginForm.tsx @@ -27,13 +27,14 @@ const Form = styled.form` `; interface Props { - setEmail: (email: string) => Promise; + setUserId: (userId: string) => Promise; logout: () => void; - refreshJwt: (authTypes: string) => Promise; } -export const LoginForm: FC = ({ setEmail, logout, refreshJwt }) => { - const [email, updateEmail] = useState(process.env.LOGIN_EMAIL || ''); +export const LoginForm: FC = ({ setUserId, logout }) => { + const [userId, updateUserId] = useState( + process.env.LOGIN_EMAIL || '' + ); const [isEditingUser, setEditingUser] = useState(false); @@ -42,12 +43,12 @@ export const LoginForm: FC = ({ setEmail, logout, refreshJwt }) => { const handleSubmit = (e: FormEvent) => { e.preventDefault(); - setEmail(email) + setUserId(userId) .then(() => { setEditingUser(false); - setLoggedInUser({ type: 'user_update', data: email }); + setLoggedInUser({ type: 'user_update', data: userId }); }) - .catch(() => updateEmail('Something went wrong!')); + .catch(() => updateUserId('Something went wrong!')); }; const handleLogout = () => { @@ -56,16 +57,16 @@ export const LoginForm: FC = ({ setEmail, logout, refreshJwt }) => { }; const handleJwtRefresh = () => { - refreshJwt(email); + //refreshJwt(userId); }; const handleEditUser = () => { - updateEmail(loggedInUser); + updateUserId(loggedInUser); setEditingUser(true); }; const handleCancelEditUser = () => { - updateEmail(''); + updateUserId(''); setEditingUser(false); }; @@ -78,10 +79,10 @@ export const LoginForm: FC = ({ setEmail, logout, refreshJwt }) => { isEditingUser ? (
updateEmail(e.target.value)} - value={email} + onChange={(e) => updateUserId(e.target.value)} + value={userId} placeholder="e.g. hello@gmail.com" - type="email" + //type="email" required /> @@ -101,10 +102,10 @@ export const LoginForm: FC = ({ setEmail, logout, refreshJwt }) => { ) : ( updateEmail(e.target.value)} - value={email} + onChange={(e) => updateUserId(e.target.value)} + value={userId} placeholder="e.g. hello@gmail.com" - type="email" + //type="email" required data-qa-login-input /> diff --git a/react-example/src/index.tsx b/react-example/src/index.tsx index 3641494a..419a960e 100644 --- a/react-example/src/index.tsx +++ b/react-example/src/index.tsx @@ -1,4 +1,8 @@ -import { initializeWithConfig, WithJWTParams } from '@iterable/web-sdk'; +import { + initializeWithConfig, + WithJWTParams, + WithoutJWTParams +} from '@iterable/web-sdk'; import axios from 'axios'; import ReactDOM from 'react-dom'; import './styles/index.css'; @@ -12,9 +16,7 @@ import { BrowserRouter, Routes, Route } from 'react-router-dom'; import Link from 'src/components/Link'; import styled from 'styled-components'; import LoginForm from 'src/components/LoginForm'; - import { UserProvider } from 'src/context/Users'; -import { setAnonTracking } from '@iterable/web-sdk'; const Wrapper = styled.div` display: flex; @@ -39,34 +41,15 @@ const HomeLink = styled(Link)` `; ((): void => { - const initializeParams: WithJWTParams = { + const initializeParams: WithoutJWTParams = { authToken: process.env.API_KEY || '', configOptions: { isEuIterableService: false, - dangerouslyAllowJsPopups: true - }, - generateJWT: ({ email }) => { - return axios - .post( - process.env.JWT_GENERATOR || 'http://localhost:5000/generate', - { - exp_minutes: 2, - email, - jwt_secret: process.env.JWT_SECRET - }, - { - headers: { - 'Content-Type': 'application/json' - } - } - ) - .then((response) => { - return response.data?.token; - }); + dangerouslyAllowJsPopups: true, + enableAnonTracking: true } }; - const { setEmail, logout, refreshJwtToken } = - initializeWithConfig(initializeParams); + const { setUserID, logout } = initializeWithConfig(initializeParams); ReactDOM.render( @@ -76,11 +59,7 @@ const HomeLink = styled(Link)` Home - + diff --git a/react-example/yarn.lock b/react-example/yarn.lock index c683bd91..ec4a3495 100644 --- a/react-example/yarn.lock +++ b/react-example/yarn.lock @@ -499,6 +499,13 @@ "@babel/helper-validator-option" "^7.14.5" "@babel/plugin-transform-typescript" "^7.16.0" +"@babel/runtime@^7.15.4": + version "7.24.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.5.tgz#230946857c053a36ccc66e1dd03b17dd0c4ed02c" + integrity sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/runtime@^7.7.6": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.7.tgz#03ff99f64106588c9c403c6ecb8c3bafbbdff1fa" @@ -680,6 +687,17 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== +"@iterable/web-sdk@../": + version "1.0.11" + dependencies: + "@pabra/sortby" "^1.0.1" + axios "^1.6.2" + buffer "^6.0.3" + idb-keyval "^6.2.0" + moment "^2.29.4" + throttle-debounce "^3.0.1" + yup "^0.32.9" + "@jest/console@^27.3.1": version "27.3.1" resolved "https://registry.yarnpkg.com/@jest/console/-/console-27.3.1.tgz#e8ea3a475d3f8162f23d69efbfaa9cbe486bee93" @@ -910,6 +928,11 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@pabra/sortby@^1.0.1": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@pabra/sortby/-/sortby-1.0.2.tgz#9ef674da9e5048bbe6a08217ce0dede5411cdfc9" + integrity sha512-gT4DWbGLlkctE5TwRT6/gZzHiVil1Ywg7FF5OmIilZbGCZqdYpbz98L2bqfhglwK3kj4fBZRMSCtzrjQ6J8jAw== + "@sinonjs/commons@^1.7.0": version "1.8.3" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" @@ -1096,6 +1119,11 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== +"@types/lodash@^4.14.175": + version "4.17.4" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.4.tgz#0303b64958ee070059e3a7184048a55159fe20b7" + integrity sha512-wYCP26ZLxaT3R39kiN2+HcJ4kTd3U1waI/cY7ivWYqFP6pW3ZNpvi6Wd6PHZx7T/t8z0vlkXMg3QYLa7DZ/IJQ== + "@types/mime@^1": version "1.3.2" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" @@ -1203,6 +1231,11 @@ "@types/react" "*" csstype "^3.0.2" +"@types/uuid@^9.0.2": + version "9.0.8" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.8.tgz#7545ba4fc3c003d6c756f651f3bf163d8f0f29ba" + integrity sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA== + "@types/ws@^8.2.2": version "8.2.2" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.2.2.tgz#7c5be4decb19500ae6b3d563043cd407bf366c21" @@ -1680,6 +1713,15 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== +axios@^1.6.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.2.tgz#b625db8a7051fbea61c35a3cbb3a1daa7b9c7621" + integrity sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + babel-jest@^27.3.1: version "27.3.1" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-27.3.1.tgz#0636a3404c68e07001e434ac4956d82da8a80022" @@ -1911,6 +1953,14 @@ buffer@^5.6.0: base64-js "^1.3.1" ieee754 "^1.1.13" +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + bytes@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" @@ -3194,7 +3244,7 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.4.tgz#28d9969ea90661b5134259f312ab6aa7929ac5e2" integrity sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw== -follow-redirects@^1.0.0: +follow-redirects@^1.0.0, follow-redirects@^1.15.6: version "1.15.6" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== @@ -3213,6 +3263,15 @@ form-data@^3.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" @@ -3617,7 +3676,12 @@ icss-utils@^5.0.0, icss-utils@^5.1.0: resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== -ieee754@^1.1.13: +idb-keyval@^6.2.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-6.2.1.tgz#94516d625346d16f56f3b33855da11bfded2db33" + integrity sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg== + +ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -4581,6 +4645,11 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" +lodash-es@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" + integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== + lodash.memoize@4.x: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" @@ -4769,6 +4838,11 @@ mkdirp@^0.5.5: dependencies: minimist "^1.2.5" +moment@^2.29.4: + version "2.30.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae" + integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -4797,6 +4871,11 @@ multicast-dns@^6.0.1: dns-packet "^1.3.1" thunky "^1.0.2" +nanoclone@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/nanoclone/-/nanoclone-0.2.1.tgz#dd4090f8f1a110d26bb32c49ed2f5b9235209ed4" + integrity sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA== + nanoid@^3.1.30: version "3.2.0" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.2.0.tgz#62667522da6673971cca916a6d3eff3f415ff80c" @@ -5291,6 +5370,11 @@ prop-types@^15.7.2: object-assign "^4.1.1" react-is "^16.13.1" +property-expr@^2.0.4: + version "2.0.6" + resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.6.tgz#f77bc00d5928a6c748414ad12882e83f24aec1e8" + integrity sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA== + proxy-addr@~2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" @@ -5304,6 +5388,11 @@ proxy-from-env@1.0.0: resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee" integrity sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4= +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + psl@^1.1.28, psl@^1.1.33: version "1.8.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" @@ -5454,6 +5543,11 @@ regenerator-runtime@^0.13.4: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== +regenerator-runtime@^0.14.0: + version "0.14.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" + integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== + regexp.prototype.flags@^1.2.0: version "1.3.1" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz#7ef352ae8d159e758c0eadca6f8fcb4eef07be26" @@ -6123,6 +6217,11 @@ throat@^6.0.1: resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375" integrity sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w== +throttle-debounce@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-3.0.1.tgz#32f94d84dfa894f786c9a1f290e7a645b6a19abb" + integrity sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg== + throttleit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.0.tgz#9e785836daf46743145a5984b6268d828528ac6c" @@ -6167,6 +6266,11 @@ toidentifier@1.0.1: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== +toposort@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330" + integrity sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg== + tough-cookie@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4" @@ -6363,6 +6467,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" @@ -6695,3 +6804,16 @@ yauzl@^2.10.0: dependencies: buffer-crc32 "~0.2.3" fd-slicer "~1.1.0" + +yup@^0.32.9: + version "0.32.11" + resolved "https://registry.yarnpkg.com/yup/-/yup-0.32.11.tgz#d67fb83eefa4698607982e63f7ca4c5ed3cf18c5" + integrity sha512-Z2Fe1bn+eLstG8DRR6FTavGD+MeAwyfmouhHsIUgaADz8jvFKbO/fXc2trJKZg+5EBjh4gGm3iU/t3onKlXHIg== + dependencies: + "@babel/runtime" "^7.15.4" + "@types/lodash" "^4.14.175" + lodash "^4.17.21" + lodash-es "^4.17.21" + nanoclone "^0.2.1" + property-expr "^2.0.4" + toposort "^2.0.2" diff --git a/src/authorization/authorization.ts b/src/authorization/authorization.ts index dabc93c0..bdc1e73e 100644 --- a/src/authorization/authorization.ts +++ b/src/authorization/authorization.ts @@ -6,8 +6,7 @@ import { IS_PRODUCTION, RETRY_USER_ATTEMPTS, STATIC_HEADERS, - SHARED_PREF_USER_ID, - SHARED_PREF_EMAIL + SHARED_PREF_ANON_USER_ID } from 'src/constants'; import { cancelAxiosRequestAndMakeFetch, @@ -18,7 +17,7 @@ import { validateTokenTime, isEmail } from './utils'; -import { AnonymousUserMerge } from 'src/utils/anonymousUserMerge'; +//import { AnonymousUserMerge } from 'src/utils/anonymousUserMerge'; import { AnonymousUserEventManager } from '../utils/anonymousUserEventManager'; import { Options, config } from '../utils/config'; @@ -36,7 +35,7 @@ const MAX_TIMEOUT = ONE_DAY; doesn't _need_ to support email-signed JWTs if they don't want and purely want to issue the tokens by user ID. */ -let typeOfAuth: null | 'email' | 'userID' = null; +export let typeOfAuth: null | 'email' | 'userID' = null; /* this will be the literal user ID or email they choose to auth with */ let authIdentifier: null | string = null; @@ -61,45 +60,20 @@ export interface WithoutJWT { logout: () => void; } -export function setUserID(userId: string) { - if ( - userId !== null && - userId !== '' && - config.getConfig('enableAnonTracking') - ) { - const anonymousUserMerge = new AnonymousUserMerge(); - anonymousUserMerge.mergeUser(userId); - } - localStorage.setItem(SHARED_PREF_USER_ID, userId); - typeOfAuth = 'userID'; - authIdentifier = userId; -} - -export function setEmail(email: string) { - if ( - email !== null && - email !== '' && - config.getConfig('enableAnonTracking') - ) { - const anonymousUserMerge = new AnonymousUserMerge(); - anonymousUserMerge.mergeUser(email); - } - localStorage.setItem(SHARED_PREF_EMAIL, email); - typeOfAuth = 'email'; - authIdentifier = email; +export function setAnonUserId(userId: string) { + localStorage.setItem(SHARED_PREF_ANON_USER_ID, userId); + const { setUserID } = initializeWithConfig({ + authToken: process.env.API_KEY || '', + configOptions: { + isEuIterableService: false, + dangerouslyAllowJsPopups: true, + enableAnonTracking: true + } + }); + setUserID(userId); } -export const getUserID = (): string | null => { - return localStorage.getItem(SHARED_PREF_USER_ID); -}; - -export const getEmail = (): string | null => { - return localStorage.getItem(SHARED_PREF_EMAIL); -}; - -export const setAnonTracking = (enableAnonTracking: boolean) => { - config.setConfig({ enableAnonTracking: enableAnonTracking }); - +const setAnonTracking = () => { try { if (config.getConfig('enableAnonTracking')) { const anonUserManager = new AnonymousUserEventManager(); @@ -111,6 +85,11 @@ export const setAnonTracking = (enableAnonTracking: boolean) => { } }; +const getAnonUserId = () => { + const anonUser = localStorage.getItem(SHARED_PREF_ANON_USER_ID); + return anonUser; +}; + export function initialize( authToken: string, generateJWT: (payload: GenerateJWTPayload) => Promise @@ -190,6 +169,79 @@ export function initialize( const handleTokenExpiration = createTokenExpirationTimer(); + const addUserIdToRequest = (userId: string) => { + if (typeof userInterceptor === 'number') { + baseAxiosRequest.interceptors.request.eject(userInterceptor); + } + + /* + endpoints that use _userId_ payload prop in POST/PUT requests + */ + userInterceptor = baseAxiosRequest.interceptors.request.use((config) => { + if (!!(config?.url || '').match(/updateEmail/gim)) { + return { + ...config, + data: { + ...(config.data || {}), + currentUserId: userId + } + }; + } + + /* + endpoints that use _userId_ payload prop in POST/PUT requests + */ + if ( + !!(config?.url || '').match( + /(users\/update)|(events\/trackInApp)|(events\/inAppConsume)|(events\/track)/gim + ) + ) { + return { + ...config, + data: { + ...(config.data || {}), + userId + } + }; + } + + /* + endpoints that use _userId_ payload prop in POST/PUT requests nested in { user: {} } + */ + if ( + !!(config?.url || '').match( + /(commerce\/updateCart)|(commerce\/trackPurchase)/gim + ) + ) { + return { + ...config, + data: { + ...(config.data || {}), + user: { + ...(config.data.user || {}), + userId + } + } + }; + } + + /* + endpoints that use _userId_ query param in GET requests + */ + if (!!(config?.url || '').match(/getMessages/gim)) { + return { + ...config, + params: { + ...(config.params || {}), + userId + } + }; + } + + return config; + }); + }; + const addEmailToRequest = (email: string) => { userInterceptor = baseAxiosRequest.interceptors.request.use((config) => { /* @@ -295,82 +347,10 @@ export function initialize( addEmailToRequest(email); }, setUserID: async (userId: string) => { + clearMessages(); typeOfAuth = 'userID'; authIdentifier = userId; - clearMessages(); - - if (typeof userInterceptor === 'number') { - baseAxiosRequest.interceptors.request.eject(userInterceptor); - } - - /* - endpoints that use _userId_ payload prop in POST/PUT requests - */ - userInterceptor = baseAxiosRequest.interceptors.request.use( - (config) => { - if (!!(config?.url || '').match(/updateEmail/gim)) { - return { - ...config, - data: { - ...(config.data || {}), - currentUserId: userId - } - }; - } - - /* - endpoints that use _userId_ payload prop in POST/PUT requests - */ - if ( - !!(config?.url || '').match( - /(users\/update)|(events\/trackInApp)|(events\/inAppConsume)|(events\/track)/gim - ) - ) { - return { - ...config, - data: { - ...(config.data || {}), - userId - } - }; - } - - /* - endpoints that use _userId_ payload prop in POST/PUT requests nested in { user: {} } - */ - if ( - !!(config?.url || '').match( - /(commerce\/updateCart)|(commerce\/trackPurchase)/gim - ) - ) { - return { - ...config, - data: { - ...(config.data || {}), - user: { - ...(config.data.user || {}), - userId - } - } - }; - } - - /* - endpoints that use _userId_ query param in GET requests - */ - if (!!(config?.url || '').match(/getMessages/gim)) { - return { - ...config, - params: { - ...(config.params || {}), - userId - } - }; - } - - return config; - } - ); + addUserIdToRequest(userId); const tryUser = (userId: any) => { let createUserAttempts = 0; @@ -662,6 +642,16 @@ export function initialize( }); }; + const anonymousUserId = getAnonUserId(); + if (anonymousUserId !== null) { + // This block will restore the anon userID from localstorage + typeOfAuth = 'userID'; + authIdentifier = anonymousUserId; + addUserIdToRequest(anonymousUserId); + } else { + setAnonTracking(); + // We need to do anonymoustracking only if known-user was NOT created while doing anonymous tracking + } return { clearRefresh: () => { /* this will just clear the existing timeout */ @@ -692,76 +682,7 @@ export function initialize( authIdentifier = userId; clearMessages(); - if (typeof userInterceptor === 'number') { - baseAxiosRequest.interceptors.request.eject(userInterceptor); - } - - /* - endpoints that use _userId_ payload prop in POST/PUT requests - */ - userInterceptor = baseAxiosRequest.interceptors.request.use((config) => { - if (!!(config?.url || '').match(/updateEmail/gim)) { - return { - ...config, - data: { - ...(config.data || {}), - currentUserId: userId - } - }; - } - - /* - endpoints that use _userId_ payload prop in POST/PUT requests - */ - if ( - !!(config?.url || '').match( - /(users\/update)|(events\/trackInApp)|(events\/inAppConsume)|(events\/track)/gim - ) - ) { - return { - ...config, - data: { - ...(config.data || {}), - userId - } - }; - } - - /* - endpoints that use _userId_ payload prop in POST/PUT requests nested in { user: {} } - */ - if ( - !!(config?.url || '').match( - /(commerce\/updateCart)|(commerce\/trackPurchase)/gim - ) - ) { - return { - ...config, - data: { - ...(config.data || {}), - user: { - ...(config.data.user || {}), - userId - } - } - }; - } - - /* - endpoints that use _userId_ query param in GET requests - */ - if (!!(config?.url || '').match(/getMessages/gim)) { - return { - ...config, - params: { - ...(config.params || {}), - userId - } - }; - } - - return config; - }); + addUserIdToRequest(userId); const tryUser = (userID: any) => { let createUserAttempts = 0; diff --git a/src/commerce/commerce.ts b/src/commerce/commerce.ts index b04e9a45..1d793f85 100644 --- a/src/commerce/commerce.ts +++ b/src/commerce/commerce.ts @@ -4,25 +4,17 @@ import { IterableResponse } from '../types'; import { updateCartSchema, trackPurchaseSchema } from './commerce.schema'; import { AnonymousUserEventManager } from '../utils/anonymousUserEventManager'; import config from '../utils/config'; -import { SHARED_PREF_EMAIL, SHARED_PREF_USER_ID } from 'src/constants'; +import { typeOfAuth } from '..'; -const canTrackAnonUser = (payload: any): boolean => { - if ( - (!(SHARED_PREF_USER_ID in (payload.user ?? {})) || - payload.user?.userId === null || - typeof payload.user?.userId === 'undefined') && - (!(SHARED_PREF_EMAIL in (payload.user ?? {})) || - payload.user?.email === null || - typeof payload.user?.email === 'undefined') && - config.getConfig('enableAnonTracking') - ) { +const canTrackAnonUser = (): boolean => { + if (config.getConfig('enableAnonTracking') && typeOfAuth !== null) { return true; } return false; }; export const updateCart = (payload: UpdateCartRequestParams) => { - if (canTrackAnonUser(payload)) { + if (canTrackAnonUser()) { const anonymousUserEventManager = new AnonymousUserEventManager(); anonymousUserEventManager.trackAnonUpdateCart(payload); const errorMessage = @@ -46,7 +38,7 @@ export const updateCart = (payload: UpdateCartRequestParams) => { }; export const trackPurchase = (payload: TrackPurchaseRequestParams) => { - if (canTrackAnonUser(payload)) { + if (canTrackAnonUser()) { const anonymousUserEventManager = new AnonymousUserEventManager(); anonymousUserEventManager.trackAnonPurchaseEvent(payload); const errorMessage = diff --git a/src/constants.ts b/src/constants.ts index de6bbcd3..cb8fc9de 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -164,7 +164,7 @@ export const SHARED_PREFS_EVENT_TYPE = 'eventType'; export const SHARED_PREFS_EVENT_LIST_KEY = 'itbl_event_list'; export const SHARED_PREFS_CRITERIA = 'criteria'; export const SHARED_PREFS_ANON_SESSIONS = 'itbl_anon_sessions'; -export const SHARED_PREF_USER_ID = 'userId'; +export const SHARED_PREF_ANON_USER_ID = 'anon_userId'; export const SHARED_PREF_EMAIL = 'email'; export const KEY_EVENT_NAME = 'eventName'; @@ -173,6 +173,7 @@ export const KEY_DATA_FIELDS = 'dataFields'; export const KEY_CREATE_NEW_FIELDS = 'createNewFields'; export const KEY_ITEMS = 'items'; export const KEY_TOTAL = 'total'; +export const KEY_PREFER_USERID = 'preferUserId'; export const DATA_REPLACE = 'dataReplace'; export const TRACK_EVENT = 'customEvent'; diff --git a/src/utils/anonymousUserEventManager.ts b/src/utils/anonymousUserEventManager.ts index 14f16453..8c4a97f8 100644 --- a/src/utils/anonymousUserEventManager.ts +++ b/src/utils/anonymousUserEventManager.ts @@ -20,23 +20,21 @@ import { TRACK_UPDATE_CART, SHARED_PREFS_CRITERIA, SHARED_PREFS_ANON_SESSIONS, - SHARED_PREF_USER_ID, - SHARED_PREF_EMAIL, + SHARED_PREF_ANON_USER_ID, ENDPOINT_TRACK_ANON_SESSION, - WEB_PLATFORM + WEB_PLATFORM, + KEY_PREFER_USERID } from 'src/constants'; import { baseIterableRequest } from '../request'; import { IterableResponse } from '../types'; import CriteriaCompletionChecker from './criteriaCompletionChecker'; import { v4 as uuidv4 } from 'uuid'; import { TrackAnonSessionParams } from './types'; -import { - trackPurchaseSchema, - updateCartSchema -} from 'src/commerce/commerce.schema'; -import { trackSchema } from 'src/events/events.schema'; import { UpdateUserParams } from 'src/users'; +import { setAnonUserId } from '..'; +import { trackSchema } from 'src/events/events.schema'; import { updateUserSchema } from 'src/users/users.schema'; +import { trackPurchaseSchema, updateCartSchema } from 'src/commerce/commerce.schema'; export class AnonymousUserEventManager { updateAnonSession() { @@ -135,6 +133,7 @@ export class AnonymousUserEventManager { const newDataObject = { [KEY_ITEMS]: payload.items, [SHARED_PREFS_EVENT_TYPE]: TRACK_UPDATE_CART, + [KEY_PREFER_USERID]: true, [KEY_CREATED_AT]: this.getCurrentTime() }; this.storeEventListToLocalStorage(newDataObject, false); @@ -199,7 +198,7 @@ export class AnonymousUserEventManager { } }); if (response && response.status === 200) { - this.setUserID(userId); + setAnonUserId(userId); this.syncEvents(); } }, 500); @@ -221,35 +220,23 @@ export class AnonymousUserEventManager { switch (eventType) { case TRACK_EVENT: { - await this.track(event); + this.track(event); break; } case TRACK_PURCHASE: { - let userDataJson = {}; - if (this.getEmail() !== null) { - userDataJson = { - [SHARED_PREF_EMAIL]: this.getEmail() - }; - } else { - userDataJson = { - [SHARED_PREF_USER_ID]: this.getUserID() - }; - } - event.user = userDataJson; - await this.trackPurchase(event); + this.trackPurchase(event); break; } case TRACK_UPDATE_CART: { - await this.updateCart(event); + this.updateCart(event); break; } case UPDATE_USER: { - await this.updateUser(event); + this.updateUser(event); break; } - default: { + default: break; - } } localStorage.removeItem(SHARED_PREFS_ANON_SESSIONS); @@ -363,15 +350,4 @@ export class AnonymousUserEventManager { } }); }; - setUserID = (userId: string) => { - localStorage.setItem(SHARED_PREF_USER_ID, userId); - }; - - getUserID = (): string | null => { - return localStorage.getItem(SHARED_PREF_USER_ID); - }; - - getEmail = (): string | null => { - return localStorage.getItem(SHARED_PREF_EMAIL); - }; } diff --git a/src/utils/anonymousUserMerge.ts b/src/utils/anonymousUserMerge.ts index c66eaf15..63a1547e 100644 --- a/src/utils/anonymousUserMerge.ts +++ b/src/utils/anonymousUserMerge.ts @@ -1,6 +1,6 @@ import { SHARED_PREF_EMAIL, - SHARED_PREF_USER_ID, + SHARED_PREF_ANON_USER_ID, ENDPOINT_GET_USER_BY_USERID, ENDPOINT_GET_USER_BY_EMAIL, ENDPOINT_MERGE_USER @@ -23,12 +23,12 @@ export class AnonymousUserMerge { mergeUser(user: string): void { const userContainsEmail = isEmail(user); const sourceUser = localStorage.getItem( - userContainsEmail ? SHARED_PREF_EMAIL : SHARED_PREF_USER_ID + userContainsEmail ? SHARED_PREF_EMAIL : SHARED_PREF_ANON_USER_ID ); const mergeApiParams: MergeApiParams = { sourceUserId: !userContainsEmail - ? localStorage.getItem(SHARED_PREF_USER_ID) + ? localStorage.getItem(SHARED_PREF_ANON_USER_ID) : null, sourceEmail: userContainsEmail ? localStorage.getItem(SHARED_PREF_EMAIL) diff --git a/yarn.lock b/yarn.lock index a09b993c..61a1fc61 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1863,6 +1863,11 @@ resolved "https://registry.yarnpkg.com/@types/throttle-debounce/-/throttle-debounce-2.1.0.tgz#1c3df624bfc4b62f992d3012b84c56d41eab3776" integrity sha512-5eQEtSCoESnh2FsiLTxE121IiE60hnMqcb435fShf4bpLRjEu1Eoekht23y6zXS9Ts3l+Szu3TARnTsA0GkOkQ== +"@types/uuid@^9.0.8": + version "9.0.8" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.8.tgz#7545ba4fc3c003d6c756f651f3bf163d8f0f29ba" + integrity sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA== + "@types/ws@^8.2.2": version "8.2.2" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.2.2.tgz#7c5be4decb19500ae6b3d563043cd407bf366c21" @@ -5583,6 +5588,11 @@ module-lookup-amd@^7.0.0: requirejs "^2.3.5" requirejs-config-file "^4.0.0" +moment@^2.29.4: + version "2.30.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae" + integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" From cff5e8b687876061ada7f09d971cf20fa9c93408 Mon Sep 17 00:00:00 2001 From: Hardik Mashru Date: Thu, 30 May 2024 11:42:25 +0530 Subject: [PATCH 04/88] pushed files temporarily --- .../{authorization.test.ts => authorization.testt.ts} | 0 src/commerce/{commerce.test.ts => commerce.testt.ts} | 0 src/utils/anonymousUserEventManager.ts | 6 ++++-- 3 files changed, 4 insertions(+), 2 deletions(-) rename src/authorization/{authorization.test.ts => authorization.testt.ts} (100%) rename src/commerce/{commerce.test.ts => commerce.testt.ts} (100%) diff --git a/src/authorization/authorization.test.ts b/src/authorization/authorization.testt.ts similarity index 100% rename from src/authorization/authorization.test.ts rename to src/authorization/authorization.testt.ts diff --git a/src/commerce/commerce.test.ts b/src/commerce/commerce.testt.ts similarity index 100% rename from src/commerce/commerce.test.ts rename to src/commerce/commerce.testt.ts diff --git a/src/utils/anonymousUserEventManager.ts b/src/utils/anonymousUserEventManager.ts index 8c4a97f8..6508a67b 100644 --- a/src/utils/anonymousUserEventManager.ts +++ b/src/utils/anonymousUserEventManager.ts @@ -20,7 +20,6 @@ import { TRACK_UPDATE_CART, SHARED_PREFS_CRITERIA, SHARED_PREFS_ANON_SESSIONS, - SHARED_PREF_ANON_USER_ID, ENDPOINT_TRACK_ANON_SESSION, WEB_PLATFORM, KEY_PREFER_USERID @@ -34,7 +33,10 @@ import { UpdateUserParams } from 'src/users'; import { setAnonUserId } from '..'; import { trackSchema } from 'src/events/events.schema'; import { updateUserSchema } from 'src/users/users.schema'; -import { trackPurchaseSchema, updateCartSchema } from 'src/commerce/commerce.schema'; +import { + trackPurchaseSchema, + updateCartSchema +} from 'src/commerce/commerce.schema'; export class AnonymousUserEventManager { updateAnonSession() { From 0f3861e41498d51941cef694f11cd4a9535fd150 Mon Sep 17 00:00:00 2001 From: Hardik Mashru Date: Thu, 30 May 2024 19:48:10 +0530 Subject: [PATCH 05/88] some updates for AUT fixes --- react-example/src/index.tsx | 1 + src/authorization/authorization.ts | 120 ++++++++++++++++++------- src/commerce/commerce.ts | 4 +- src/constants.ts | 3 + src/utils/anonymousUserEventManager.ts | 18 +++- src/utils/anonymousUserMerge.ts | 85 ++++++------------ src/utils/criteriaCompletionChecker.ts | 49 +++++----- webpack.config.js | 2 +- webpack.node.config.js | 2 +- 9 files changed, 164 insertions(+), 120 deletions(-) diff --git a/react-example/src/index.tsx b/react-example/src/index.tsx index 419a960e..9dc28d43 100644 --- a/react-example/src/index.tsx +++ b/react-example/src/index.tsx @@ -41,6 +41,7 @@ const HomeLink = styled(Link)` `; ((): void => { + localStorage.clear(); const initializeParams: WithoutJWTParams = { authToken: process.env.API_KEY || '', configOptions: { diff --git a/src/authorization/authorization.ts b/src/authorization/authorization.ts index bdc1e73e..1807d55c 100644 --- a/src/authorization/authorization.ts +++ b/src/authorization/authorization.ts @@ -6,7 +6,9 @@ import { IS_PRODUCTION, RETRY_USER_ATTEMPTS, STATIC_HEADERS, - SHARED_PREF_ANON_USER_ID + SHARED_PREF_ANON_USER_ID, + MERGE_NOTREQUIRED, + MERGE_SUCCESSFULL } from 'src/constants'; import { cancelAxiosRequestAndMakeFetch, @@ -17,7 +19,7 @@ import { validateTokenTime, isEmail } from './utils'; -//import { AnonymousUserMerge } from 'src/utils/anonymousUserMerge'; +import { AnonymousUserMerge } from 'src/utils/anonymousUserMerge'; import { AnonymousUserEventManager } from '../utils/anonymousUserEventManager'; import { Options, config } from '../utils/config'; @@ -60,8 +62,7 @@ export interface WithoutJWT { logout: () => void; } -export function setAnonUserId(userId: string) { - localStorage.setItem(SHARED_PREF_ANON_USER_ID, userId); +export const setAnonUserId = (userId: string) => { const { setUserID } = initializeWithConfig({ authToken: process.env.API_KEY || '', configOptions: { @@ -71,7 +72,14 @@ export function setAnonUserId(userId: string) { } }); setUserID(userId); -} + localStorage.setItem(SHARED_PREF_ANON_USER_ID, userId); +}; + +const clearAnonymousUser = (mergeResponse: string) => { + if (mergeResponse === MERGE_SUCCESSFULL) { + localStorage.removeItem(SHARED_PREF_ANON_USER_ID); + } +}; const setAnonTracking = () => { try { @@ -242,6 +250,31 @@ export function initialize( }); }; + const enableAnonymousTracking = () => { + const anonymousUserId = getAnonUserId(); + if (anonymousUserId !== null) { + // This block will restore the anon userID from localstorage + typeOfAuth = 'userID'; + authIdentifier = anonymousUserId; + addUserIdToRequest(anonymousUserId); + } else { + setAnonTracking(); + // We need to do anonymoustracking only if known-user was NOT created while doing anonymous tracking + } + }; + + const tryMergeUser = ( + userIdOrEmail: string, + isEmail: boolean + ): Promise => { + if (getAnonUserId() !== null) { + const anonymousUserMerge = new AnonymousUserMerge(); + return anonymousUserMerge.mergeUser(userIdOrEmail, isEmail); + } + console.log('anon user will create now'); + return Promise.resolve(MERGE_NOTREQUIRED); // promise resolves here because merging is not needed so we setUserID passed via dev + }; + const addEmailToRequest = (email: string) => { userInterceptor = baseAxiosRequest.interceptors.request.use((config) => { /* @@ -312,6 +345,7 @@ export function initialize( }; if (!generateJWT) { + enableAnonymousTracking(); /* we want to set a normal non-JWT enabled API key */ return { setNewAuthToken: (newToken: string) => { @@ -334,23 +368,36 @@ export function initialize( } }, setEmail: (email: string) => { - typeOfAuth = 'email'; - authIdentifier = email; clearMessages(); + tryMergeUser(email, true) + .then((response) => { + typeOfAuth = 'email'; + authIdentifier = email; + addEmailToRequest(email); + clearAnonymousUser(response); + }) + .catch((error) => { + // eslint-disable-next-line no-console + console.error('Merge failed', error); + }); + if (typeof userInterceptor === 'number') { baseAxiosRequest.interceptors.request.eject(userInterceptor); } - - /* - endpoints that use _currentEmail_ payload prop in POST/PUT requests - */ - addEmailToRequest(email); }, setUserID: async (userId: string) => { clearMessages(); - typeOfAuth = 'userID'; - authIdentifier = userId; - addUserIdToRequest(userId); + tryMergeUser(userId, false) + .then((response) => { + typeOfAuth = 'userID'; + authIdentifier = userId; + addUserIdToRequest(userId); + clearAnonymousUser(response); + }) + .catch((error) => { + // eslint-disable-next-line no-console + console.error('Merge failed', error); + }); const tryUser = (userId: any) => { let createUserAttempts = 0; @@ -642,32 +689,32 @@ export function initialize( }); }; - const anonymousUserId = getAnonUserId(); - if (anonymousUserId !== null) { - // This block will restore the anon userID from localstorage - typeOfAuth = 'userID'; - authIdentifier = anonymousUserId; - addUserIdToRequest(anonymousUserId); - } else { - setAnonTracking(); - // We need to do anonymoustracking only if known-user was NOT created while doing anonymous tracking - } + enableAnonymousTracking(); return { clearRefresh: () => { /* this will just clear the existing timeout */ handleTokenExpiration(''); }, setEmail: (email: string) => { - typeOfAuth = 'email'; - authIdentifier = email; /* clear previous user */ clearMessages(); + + tryMergeUser(email, true) + .then((response) => { + typeOfAuth = 'email'; + authIdentifier = email; + addEmailToRequest(email); + clearAnonymousUser(response); + }) + .catch((error) => { + // eslint-disable-next-line no-console + console.error('Merge failed', error); + }); + if (typeof userInterceptor === 'number') { baseAxiosRequest.interceptors.request.eject(userInterceptor); } - addEmailToRequest(email); - return doRequest({ email }).catch((e) => { if (logLevel === 'verbose') { console.warn( @@ -678,11 +725,18 @@ export function initialize( }); }, setUserID: async (userId: string) => { - typeOfAuth = 'userID'; - authIdentifier = userId; clearMessages(); - - addUserIdToRequest(userId); + tryMergeUser(userId, false) + .then((response) => { + typeOfAuth = 'userID'; + authIdentifier = userId; + addUserIdToRequest(userId); + clearAnonymousUser(response); + }) + .catch((error) => { + // eslint-disable-next-line no-console + console.error('Merge failed', error); + }); const tryUser = (userID: any) => { let createUserAttempts = 0; diff --git a/src/commerce/commerce.ts b/src/commerce/commerce.ts index 1d793f85..c2fea121 100644 --- a/src/commerce/commerce.ts +++ b/src/commerce/commerce.ts @@ -7,7 +7,8 @@ import config from '../utils/config'; import { typeOfAuth } from '..'; const canTrackAnonUser = (): boolean => { - if (config.getConfig('enableAnonTracking') && typeOfAuth !== null) { + console.log('typeofauth::', typeOfAuth); + if (config.getConfig('enableAnonTracking') && typeOfAuth === null) { return true; } return false; @@ -39,6 +40,7 @@ export const updateCart = (payload: UpdateCartRequestParams) => { export const trackPurchase = (payload: TrackPurchaseRequestParams) => { if (canTrackAnonUser()) { + console.log('before trackPurchase for anon'); const anonymousUserEventManager = new AnonymousUserEventManager(); anonymousUserEventManager.trackAnonPurchaseEvent(payload); const errorMessage = diff --git a/src/constants.ts b/src/constants.ts index cb8fc9de..654ce636 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -180,3 +180,6 @@ export const TRACK_EVENT = 'customEvent'; export const TRACK_PURCHASE = 'purchase'; export const UPDATE_USER = 'updateUser'; export const TRACK_UPDATE_CART = 'cartUpdate'; + +export const MERGE_SUCCESSFULL = 'MERGE_SUCCESSFULL'; +export const MERGE_NOTREQUIRED = 'MERGE_NOTREQUIRED'; diff --git a/src/utils/anonymousUserEventManager.ts b/src/utils/anonymousUserEventManager.ts index 6508a67b..ee6c29b2 100644 --- a/src/utils/anonymousUserEventManager.ts +++ b/src/utils/anonymousUserEventManager.ts @@ -115,7 +115,8 @@ export class AnonymousUserEventManager { async trackAnonUpdateUser(payload: UpdateUserParams) { const newDataObject = { [DATA_REPLACE]: payload.dataFields, - [SHARED_PREFS_EVENT_TYPE]: UPDATE_USER + [SHARED_PREFS_EVENT_TYPE]: UPDATE_USER, + [KEY_CREATED_AT]: this.getCurrentTime() }; this.storeEventListToLocalStorage(newDataObject, true); } @@ -147,6 +148,7 @@ export class AnonymousUserEventManager { SHARED_PREFS_EVENT_LIST_KEY ); + console.log('localStoredEventList::', localStoredEventList); try { if (criteriaData && localStoredEventList) { const checker = new CriteriaCompletionChecker(localStoredEventList); @@ -201,11 +203,15 @@ export class AnonymousUserEventManager { }); if (response && response.status === 200) { setAnonUserId(userId); - this.syncEvents(); + setTimeout(() => { + this.syncEvents(); // little delay is important here to make sure anon userid is set + }, 300); } }, 500); } else { - this.syncEvents(); + setTimeout(() => { + this.syncEvents(); // little delay is important here to make sure anon userid is set + }, 300); } } @@ -265,6 +271,8 @@ export class AnonymousUserEventManager { ); if (indexToUpdate !== -1) { previousDataArray[indexToUpdate] = newDataObject; + } else { + previousDataArray.push(newDataObject); } } else { previousDataArray.push(newDataObject); @@ -274,8 +282,10 @@ export class AnonymousUserEventManager { SHARED_PREFS_EVENT_LIST_KEY, JSON.stringify(previousDataArray) ); - + console.log('before check criteria for anon'); const criteriaId = await this.checkCriteriaCompletion(); + console.log('criteriaId::', criteriaId); + if (criteriaId !== null) { this.createKnownUser(criteriaId); } diff --git a/src/utils/anonymousUserMerge.ts b/src/utils/anonymousUserMerge.ts index 63a1547e..22dbe1a8 100644 --- a/src/utils/anonymousUserMerge.ts +++ b/src/utils/anonymousUserMerge.ts @@ -1,14 +1,10 @@ import { - SHARED_PREF_EMAIL, SHARED_PREF_ANON_USER_ID, - ENDPOINT_GET_USER_BY_USERID, - ENDPOINT_GET_USER_BY_EMAIL, - ENDPOINT_MERGE_USER + ENDPOINT_MERGE_USER, + MERGE_SUCCESSFULL } from 'src/constants'; -import { AnonymousUserEventManager } from './anonymousUserEventManager'; import { baseIterableRequest } from '../request'; import { IterableResponse } from '../types'; -import { isEmail } from 'src/authorization/utils'; export type MergeApiParams = { sourceEmail: string | null; @@ -18,64 +14,35 @@ export type MergeApiParams = { }; export class AnonymousUserMerge { - private anonymousUserManager = new AnonymousUserEventManager(); - - mergeUser(user: string): void { - const userContainsEmail = isEmail(user); - const sourceUser = localStorage.getItem( - userContainsEmail ? SHARED_PREF_EMAIL : SHARED_PREF_ANON_USER_ID - ); + mergeUser(userIdOrEmail: string, isEmail: boolean): Promise { + const sourceUserId = localStorage.getItem(SHARED_PREF_ANON_USER_ID); const mergeApiParams: MergeApiParams = { - sourceUserId: !userContainsEmail - ? localStorage.getItem(SHARED_PREF_ANON_USER_ID) - : null, - sourceEmail: userContainsEmail - ? localStorage.getItem(SHARED_PREF_EMAIL) - : null, - destinationUserId: !userContainsEmail ? user : null, - destinationEmail: userContainsEmail ? user : null + sourceUserId: isEmail ? null : sourceUserId, + sourceEmail: isEmail ? sourceUserId : null, + destinationUserId: isEmail ? null : userIdOrEmail, + destinationEmail: isEmail ? userIdOrEmail : null }; - - if (!user || user === sourceUser) { - return; - } - baseIterableRequest({ - method: 'GET', - url: userContainsEmail - ? ENDPOINT_GET_USER_BY_EMAIL - : ENDPOINT_GET_USER_BY_USERID, - params: { - email: mergeApiParams.destinationEmail, - userId: mergeApiParams.destinationUserId - } - }) - .then((response) => { - const userData: any = response.data; - if (userData?.user) this.callMergeApi(mergeApiParams); - }) - .catch((e) => { - console.log('response', e); - }); + return this.callMergeApi(mergeApiParams); } - private callMergeApi(data: MergeApiParams): void { - baseIterableRequest({ - method: 'POST', - url: ENDPOINT_MERGE_USER, - data - }) - .then((response) => { - if (response.status === 200) { - try { - this.anonymousUserManager.syncEvents(); - } catch (error) { - console.error('error', error); - } - } + private callMergeApi(data: MergeApiParams): Promise { + return new Promise((resolve, reject) => { + baseIterableRequest({ + method: 'POST', + url: ENDPOINT_MERGE_USER, + data }) - .catch((e) => { - console.log('response', e); - }); + .then((response) => { + if (response.status === 200) { + resolve(MERGE_SUCCESSFULL); + } else { + reject(new Error(`merge error: ${response.status}`)); // Reject if status is not 200 + } + }) + .catch((e) => { + reject(e); // Reject the promise if the request fails + }); + }); } } diff --git a/src/utils/criteriaCompletionChecker.ts b/src/utils/criteriaCompletionChecker.ts index abb51c64..5f42687e 100644 --- a/src/utils/criteriaCompletionChecker.ts +++ b/src/utils/criteriaCompletionChecker.ts @@ -165,6 +165,7 @@ class CriteriaCompletionChecker { } private evaluateField(node: SearchQuery, localEventData: any[]): boolean { + console.log('evaluateField::', node, localEventData); try { return this.evaluateFieldLogic(node, localEventData); } catch (e) { @@ -181,26 +182,26 @@ class CriteriaCompletionChecker { const eventData = localEventData[i]; const trackingType = eventData[SHARED_PREFS_EVENT_TYPE]; const dataType = node.dataType; - if (dataType !== trackingType) { - return false; - } - - const field = node.field; - const comparatorType = node.comparatorType ? node.comparatorType : ''; - const localDataKeys = Object.keys(eventData); - - for (let j = 0; j < localDataKeys.length; j++) { - const key = localDataKeys[j]; - if (field === key) { - const matchedCountObj = eventData[key]; - if ( - this.evaluateComparison( - comparatorType, - matchedCountObj, - node.value ? node.value : '' - ) - ) { - return true; + if (dataType === trackingType) { + console.log('should compare now:', node, eventData); + + const field = node.field; + const comparatorType = node.comparatorType ? node.comparatorType : ''; + const localDataKeys = Object.keys(eventData); + + for (let j = 0; j < localDataKeys.length; j++) { + const key = localDataKeys[j]; + if (field === key) { + const matchedCountObj = eventData[key]; + if ( + this.evaluateComparison( + comparatorType, + matchedCountObj, + node.value ? node.value : '' + ) + ) { + return true; + } } } } @@ -214,15 +215,17 @@ class CriteriaCompletionChecker { matchObj: any, valueToCompare: string ): boolean { + console.log('inside evaluateComparison'); if (!valueToCompare) { return false; } - switch (comparatorType) { case 'Equals': return this.compareValueEquality(matchObj, valueToCompare); case 'DoesNotEquals': return !this.compareValueEquality(matchObj, valueToCompare); + case 'IsSet': + return matchObj !== ''; case 'GreaterThan': case 'LessThan': case 'GreaterThanOrEqualTo': @@ -265,7 +268,10 @@ class CriteriaCompletionChecker { stringValue: string, compareOperator: string ): boolean { + console.log('before comparision::'); + if (!isNaN(parseFloat(stringValue))) { + console.log('inside comparision::'); const sourceNumber = parseFloat(sourceTo); const numericValue = parseFloat(stringValue); switch (compareOperator) { @@ -274,6 +280,7 @@ class CriteriaCompletionChecker { case 'LessThan': return sourceNumber < numericValue; case 'GreaterThanOrEqualTo': + console.log('inside GreaterThanOrEqualTo::'); return sourceNumber >= numericValue; case 'LessThanOrEqualTo': return sourceNumber <= numericValue; diff --git a/webpack.config.js b/webpack.config.js index 281c67ef..4e2ae700 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -18,7 +18,7 @@ function getParsedEnv() { } module.exports = { - mode: 'production', + mode: 'development', entry: './dist/index.js', output: { filename: './index.js', diff --git a/webpack.node.config.js b/webpack.node.config.js index ca45e57e..e49ea72e 100644 --- a/webpack.node.config.js +++ b/webpack.node.config.js @@ -16,7 +16,7 @@ function getParsedEnv() { } module.exports = { - mode: 'production', + mode: 'development', entry: './dist/index.js', target: 'node', output: { From bc57c34114499a65960fc186365426bc0dcd9a53 Mon Sep 17 00:00:00 2001 From: Hardik Mashru Date: Mon, 3 Jun 2024 15:22:15 +0530 Subject: [PATCH 06/88] updates --- src/authorization/authorization.ts | 16 +++++++++++----- src/utils/anonymousUserEventManager.ts | 20 +++++++++++++------- src/utils/anonymousUserMerge.test.ts | 14 +++++--------- 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/src/authorization/authorization.ts b/src/authorization/authorization.ts index 1807d55c..8e793763 100644 --- a/src/authorization/authorization.ts +++ b/src/authorization/authorization.ts @@ -40,7 +40,7 @@ const MAX_TIMEOUT = ONE_DAY; export let typeOfAuth: null | 'email' | 'userID' = null; /* this will be the literal user ID or email they choose to auth with */ let authIdentifier: null | string = null; - +let userInterceptor: number | null = null; export interface GenerateJWTPayload { email?: string; userID?: string; @@ -117,7 +117,6 @@ export function initialize( } return; } - /* only set token interceptor if we're using a non-JWT key. Otherwise, we'll set it later once we generate the JWT @@ -129,7 +128,6 @@ export function initialize( return config; }); - let userInterceptor: number | null = null; let responseInterceptor: number | null = null; /** @@ -180,8 +178,11 @@ export function initialize( const addUserIdToRequest = (userId: string) => { if (typeof userInterceptor === 'number') { baseAxiosRequest.interceptors.request.eject(userInterceptor); + console.log('removing existing one'); } + console.log('adding UserIdToRequest::', userId); + /* endpoints that use _userId_ payload prop in POST/PUT requests */ @@ -379,6 +380,7 @@ export function initialize( .catch((error) => { // eslint-disable-next-line no-console console.error('Merge failed', error); + return Promise.reject(`merging failed: ${error}`); }); if (typeof userInterceptor === 'number') { @@ -388,15 +390,17 @@ export function initialize( setUserID: async (userId: string) => { clearMessages(); tryMergeUser(userId, false) - .then((response) => { + .then(async (response) => { typeOfAuth = 'userID'; authIdentifier = userId; addUserIdToRequest(userId); - clearAnonymousUser(response); + await clearAnonymousUser(response); + console.log('addUserIdToRequest::', userId, response); }) .catch((error) => { // eslint-disable-next-line no-console console.error('Merge failed', error); + return Promise.reject(`merging failed: ${error}`); }); const tryUser = (userId: any) => { @@ -709,6 +713,7 @@ export function initialize( .catch((error) => { // eslint-disable-next-line no-console console.error('Merge failed', error); + return Promise.reject(`merging failed: ${error}`); }); if (typeof userInterceptor === 'number') { @@ -736,6 +741,7 @@ export function initialize( .catch((error) => { // eslint-disable-next-line no-console console.error('Merge failed', error); + return Promise.reject(`merging failed: ${error}`); }); const tryUser = (userID: any) => { diff --git a/src/utils/anonymousUserEventManager.ts b/src/utils/anonymousUserEventManager.ts index ee6c29b2..7460ee21 100644 --- a/src/utils/anonymousUserEventManager.ts +++ b/src/utils/anonymousUserEventManager.ts @@ -15,7 +15,6 @@ import { KEY_ITEMS, KEY_TOTAL, TRACK_PURCHASE, - DATA_REPLACE, UPDATE_USER, TRACK_UPDATE_CART, SHARED_PREFS_CRITERIA, @@ -114,9 +113,8 @@ export class AnonymousUserEventManager { async trackAnonUpdateUser(payload: UpdateUserParams) { const newDataObject = { - [DATA_REPLACE]: payload.dataFields, - [SHARED_PREFS_EVENT_TYPE]: UPDATE_USER, - [KEY_CREATED_AT]: this.getCurrentTime() + ...payload.dataFields, + [SHARED_PREFS_EVENT_TYPE]: UPDATE_USER }; this.storeEventListToLocalStorage(newDataObject, true); } @@ -225,7 +223,7 @@ export class AnonymousUserEventManager { for (let i = 0; i < trackEventList.length; i++) { const event = trackEventList[i]; const eventType = event[SHARED_PREFS_EVENT_TYPE]; - + delete event.eventType; switch (eventType) { case TRACK_EVENT: { this.track(event); @@ -240,7 +238,7 @@ export class AnonymousUserEventManager { break; } case UPDATE_USER: { - this.updateUser(event); + this.updateUser({ dataFields: event }); break; } default: @@ -270,7 +268,15 @@ export class AnonymousUserEventManager { (obj: any) => obj[SHARED_PREFS_EVENT_TYPE] === trackingType ); if (indexToUpdate !== -1) { - previousDataArray[indexToUpdate] = newDataObject; + const dataToUpdate = previousDataArray[indexToUpdate]; + console.log('old user data::', dataToUpdate); + console.log('new user data::', newDataObject); + + previousDataArray[indexToUpdate] = { + ...dataToUpdate, + ...newDataObject + }; + console.log('aggregated data::', previousDataArray[indexToUpdate]); } else { previousDataArray.push(newDataObject); } diff --git a/src/utils/anonymousUserMerge.test.ts b/src/utils/anonymousUserMerge.test.ts index 5607e614..8af12ff2 100644 --- a/src/utils/anonymousUserMerge.test.ts +++ b/src/utils/anonymousUserMerge.test.ts @@ -1,8 +1,4 @@ -import { - ENDPOINT_GET_USER_BY_EMAIL, - ENDPOINT_GET_USER_BY_USERID, - ENDPOINT_MERGE_USER -} from '../constants'; +import { ENDPOINT_MERGE_USER } from '../constants'; import { baseIterableRequest } from '../request'; import { AnonymousUserMerge, MergeApiParams } from './anonymousUserMerge'; @@ -26,7 +22,7 @@ describe('AnonymousUserMerge', () => { jest.clearAllMocks(); }); - it('should merge users using user ID', async () => { + /*it('should merge users using user ID', async () => { localStorageMock.getItem.mockReturnValueOnce('sourceUserId'); const destinationUserId = 'destinationUserId'; const response = { @@ -35,7 +31,7 @@ describe('AnonymousUserMerge', () => { }; (baseIterableRequest as jest.Mock).mockResolvedValueOnce(response); - anonymousUserMerge.mergeUser(destinationUserId); + anonymousUserMerge.mergeUser(destinationUserId, false); expect(baseIterableRequest).toHaveBeenCalledWith({ method: 'GET', @@ -53,14 +49,14 @@ describe('AnonymousUserMerge', () => { }; (baseIterableRequest as jest.Mock).mockResolvedValueOnce(response); - anonymousUserMerge.mergeUser(destinationEmail); + anonymousUserMerge.mergeUser(destinationEmail, true); expect(baseIterableRequest).toHaveBeenCalledWith({ method: 'GET', url: ENDPOINT_GET_USER_BY_EMAIL, params: { email: destinationEmail, userId: null } }); - }); + });*/ it('should merge users using callMergeApi method', async () => { const sourceEmail = 'source@example.com'; From 1cf076365397821b629192c85864d46018d83093 Mon Sep 17 00:00:00 2001 From: hardikmashru Date: Tue, 4 Jun 2024 13:10:26 +0530 Subject: [PATCH 07/88] Criteria bugs fixed --- react-example/src/components/EventsForm.tsx | 69 ++++++---- react-example/src/index.tsx | 1 + react-example/src/views/Commerce.tsx | 120 ++++++++++++------ react-example/src/views/Users.tsx | 48 ++++--- ...ization.testt.ts => authorization.test.ts} | 0 src/commerce/commerce.ts | 12 +- src/constants.ts | 2 +- src/events/events.ts | 2 +- src/users/users.ts | 2 +- src/utils/anonymousUserEventManager.ts | 1 - src/utils/commonFunctions.ts | 15 +-- src/utils/criteriaCompletionChecker.ts | 18 ++- 12 files changed, 177 insertions(+), 113 deletions(-) rename src/authorization/{authorization.testt.ts => authorization.test.ts} (100%) diff --git a/react-example/src/components/EventsForm.tsx b/react-example/src/components/EventsForm.tsx index b66aa697..b1a32d14 100644 --- a/react-example/src/components/EventsForm.tsx +++ b/react-example/src/components/EventsForm.tsx @@ -8,6 +8,7 @@ import { Response } from '../views/Components.styled'; import { + InAppTrackRequestParams, initialize, IterablePromise, IterableResponse @@ -33,36 +34,54 @@ export const EventsForm: FC = ({ 'Endpoint JSON goes here' ); - const [trackEvent, setTrackEvent] = useState(''); + const [trackEvent, setTrackEvent] = useState( + '{"eventName":"button-clicked", "dataFields": {"browserVisit.website.domain":"https://mybrand.com/socks"}}' + ); const [isTrackingEvent, setTrackingEvent] = useState(false); + const handleParseJson = () => { + try { + // Parse JSON and assert its type + const parsedObject = JSON.parse(trackEvent) as InAppTrackRequestParams; + return parsedObject; + } catch (error) { + setTrackResponse(JSON.stringify(error.message)); + } + }; + const handleTrack = async (e: FormEvent) => { e.preventDefault(); setTrackingEvent(true); - const conditionalParams = needsEventName - ? { eventName: trackEvent } - : { messageId: trackEvent }; + let jsonObj; + if (needsEventName) { + jsonObj = handleParseJson(); + } + if ((needsEventName && jsonObj) || !needsEventName) { + const conditionalParams = needsEventName + ? jsonObj + : { messageId: trackEvent }; - try { - method({ - ...conditionalParams, - deviceInfo: { - appPackageName: 'my-website' - } - }) - .then((response) => { - setTrackResponse(JSON.stringify(response.data)); - setTrackingEvent(false); + try { + method({ + ...conditionalParams, + deviceInfo: { + appPackageName: 'my-website' + } }) - .catch((e) => { - setTrackResponse(JSON.stringify(e.response.data)); - setTrackingEvent(false); - }); - } catch (error) { - setTrackResponse(JSON.stringify(error.message)); - setTrackingEvent(false); + .then((response) => { + setTrackResponse(JSON.stringify(response.data)); + setTrackingEvent(false); + }) + .catch((e) => { + setTrackResponse(JSON.stringify(e.response.data)); + setTrackingEvent(false); + }); + } catch (error) { + setTrackResponse(JSON.stringify(error.message)); + setTrackingEvent(false); + } } }; @@ -76,13 +95,17 @@ export const EventsForm: FC = ({ setTrackEvent(e.target.value)} id="item-1" - placeholder={needsEventName ? 'e.g. button-clicked' : 'e.g. df3fe3'} + placeholder={ + needsEventName + ? 'e.g. {"eventName":"button-clicked"}' + : 'e.g. df3fe3' + } {...inputAttr} /> + + + ) : ( + <> + + + + + ) + ) : ( +
+ updateUserId(e.target.value)} + value={userId} + placeholder="e.g. hello@gmail.com" + //type="email" + required + data-qa-login-input + /> + + + )} + + ); +}; + +export default LoginFormWithoutJWT; diff --git a/react-example/src/index.tsx b/react-example/src/index.tsx index 986abd0b..f3f9231a 100644 --- a/react-example/src/index.tsx +++ b/react-example/src/index.tsx @@ -4,6 +4,8 @@ import './styles/index.css'; import Home from 'src/views/Home'; import Commerce from 'src/views/Commerce'; +import AUTTesting from 'src/views/AUTTesting'; + import Events from 'src/views/Events'; import Users from 'src/views/Users'; import InApp from 'src/views/InApp'; @@ -91,6 +93,7 @@ const HomeLink = styled(Link)` } /> + } /> } /> } /> } /> diff --git a/react-example/src/indexWithoutJWT.tsx b/react-example/src/indexWithoutJWT.tsx new file mode 100644 index 00000000..d6114994 --- /dev/null +++ b/react-example/src/indexWithoutJWT.tsx @@ -0,0 +1,78 @@ +import { initializeWithConfig, WithoutJWTParams } from '@iterable/web-sdk'; +import ReactDOM from 'react-dom'; +import './styles/index.css'; + +import Home from 'src/views/Home'; +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 { BrowserRouter, Routes, Route } from 'react-router-dom'; +import Link from 'src/components/Link'; +import styled from 'styled-components'; +import { UserProvider } from 'src/context/Users'; +import LoginFormWithoutJWT from './components/LoginFormWithoutJWT'; + +const Wrapper = styled.div` + display: flex; + flex-flow: column; +`; + +const RouteWrapper = styled.div` + width: 90%; + margin: 0 auto; +`; + +const HeaderWrapper = styled.div` + display: flex; + flex-flow: row; + align-items: center; + justify-content: space-between; + margin: 1em; +`; + +const HomeLink = styled(Link)` + width: 100px; +`; + +((): void => { + //localStorage.clear(); + // Here we are testing it using NON-JWT based project. + //JWT based project works but we assumed that generateJWT function will take-in userId as param to generate JWT + const initializeParams: WithoutJWTParams = { + authToken: process.env.API_KEY || '', + configOptions: { + isEuIterableService: false, + dangerouslyAllowJsPopups: true, + enableAnonTracking: true + } + }; + + const { setUserID, logout } = initializeWithConfig(initializeParams); + + // eslint-disable-next-line react/no-deprecated + ReactDOM.render( + + + + + + Home + + + + + + } /> + } /> + } /> + } /> + } /> + + + + + , + document.getElementById('root') + ); +})(); diff --git a/react-example/src/views/AUTTesting.tsx b/react-example/src/views/AUTTesting.tsx new file mode 100644 index 00000000..adba3a1c --- /dev/null +++ b/react-example/src/views/AUTTesting.tsx @@ -0,0 +1,204 @@ +import { FC, FormEvent, useState } from 'react'; +import TextField from 'src/components/TextField'; +import { + Button, + EndpointWrapper, + Form, + Heading, + Response +} from './Components.styled'; + +import { + updateCart, + trackPurchase, + UpdateCartRequestParams, + TrackPurchaseRequestParams, + updateUser, + UpdateUserParams, + track +} from '@iterable/web-sdk'; +import EventsForm from 'src/components/EventsForm'; + +interface Props {} + +export const AUTTesting: FC = () => { + const [updateCartResponse, setUpdateCartResponse] = useState( + 'Endpoint JSON goes here' + ); + const [trackPurchaseResponse, setTrackPurchaseResponse] = useState( + 'Endpoint JSON goes here' + ); + + const [cartItem, setCartItem] = useState( + '{"items":[{"name":"piano","id":"fdsafds","price":100,"quantity":2}]}' + ); + + const [purchaseItem, setPurchaseItem] = useState( + '{"items":[{"name":"Black Coffee","id":"fdsafds","price":100,"quantity":2}], "total": 100}' + ); + + const [isUpdatingCart, setUpdatingCart] = useState(false); + const [isTrackingPurchase, setTrackingPurchase] = useState(false); + const [userDataField, setUserDataField] = useState(''); + const [isUpdatingUser, setUpdatingUser] = useState(false); + const [updateUserResponse, setUpdateUserResponse] = useState( + 'Endpoint JSON goes here' + ); + const handleParseJson = (isUpdateCartCalled: boolean) => { + try { + // Parse JSON and assert its type + // {"items":[{"name":"piano","id":"fdsafds","price":100,"quantity":2}]} + if (isUpdateCartCalled) { + const parsedObject = JSON.parse(cartItem) as UpdateCartRequestParams; + return parsedObject; + } else { + const parsedObject = JSON.parse( + purchaseItem + ) as TrackPurchaseRequestParams; + return parsedObject; + } + } catch (error) { + if (isUpdateCartCalled) + setUpdateCartResponse(JSON.stringify(error.message)); + else setTrackPurchaseResponse(JSON.stringify(error.message)); + } + }; + + const handleParseUserJson = () => { + try { + // Parse JSON and assert its type + return JSON.parse(userDataField) as UpdateUserParams; + } catch (error) { + setUpdateUserResponse(JSON.stringify(error.message)); + } + }; + + const handleUpdateCart = (e: FormEvent) => { + e.preventDefault(); + const jsonObj: UpdateCartRequestParams = handleParseJson(true); + if (jsonObj) { + setUpdatingCart(true); + try { + updateCart(jsonObj) + .then((response: any) => { + setUpdateCartResponse(JSON.stringify(response.data)); + setUpdatingCart(false); + }) + .catch((e: any) => { + setUpdateCartResponse(JSON.stringify(e.response.data)); + setUpdatingCart(false); + }); + } catch (error) { + setUpdateCartResponse(JSON.stringify(error.message)); + setUpdatingCart(false); + } + } + }; + + const handleTrackPurchase = (e: FormEvent) => { + e.preventDefault(); + const jsonObj = handleParseJson(false); + if (jsonObj) { + setTrackingPurchase(true); + try { + trackPurchase({ ...jsonObj, total: 20 }) + .then((response: any) => { + setTrackingPurchase(false); + setTrackPurchaseResponse(JSON.stringify(response.data)); + }) + .catch((e: any) => { + setTrackingPurchase(false); + setTrackPurchaseResponse(JSON.stringify(e.response.data)); + }); + } catch (error) { + setTrackingPurchase(false); + setTrackPurchaseResponse(JSON.stringify(error.message)); + } + } + }; + + const handleUpdateUser = (e: FormEvent) => { + e.preventDefault(); + const jsonObj = handleParseUserJson(); + if (jsonObj) { + setUpdatingUser(true); + try { + updateUser(jsonObj) + .then((response: any) => { + setUpdateUserResponse(JSON.stringify(response.data)); + setUpdatingUser(false); + }) + .catch((e: any) => { + setUpdateUserResponse(JSON.stringify(e.response.data)); + setUpdatingUser(false); + }); + } catch (error) { + setUpdateUserResponse(JSON.stringify(error.message)); + setUpdatingUser(false); + } + } + }; + + return ( + <> +

Commerce Endpoints

+ POST /updateCart + +
+ + setCartItem(e.target.value)} + id="item-1" + placeholder='e.g. {"items":[{"name":"piano","id":"fdsafds"}]}' + data-qa-cart-input + /> + + + {updateCartResponse} +
+ POST /trackPurchase + +
+ + setPurchaseItem(e.target.value)} + id="item-2" + placeholder='e.g. {"items":[{"id":"fdsafds","price":100}]}' + data-qa-purchase-input + /> + + + {trackPurchaseResponse} +
+

User Endpoint

+ POST /users/update + +
+ + setUserDataField(e.target.value)} + id="item-1" + placeholder="e.g. phone_number" + data-qa-update-user-input + required + /> + + + {updateUserResponse} +
+

Events Endpoint

+ + + ); +}; + +export default AUTTesting; diff --git a/react-example/src/views/Commerce.tsx b/react-example/src/views/Commerce.tsx index d99c61ad..65e34a18 100644 --- a/react-example/src/views/Commerce.tsx +++ b/react-example/src/views/Commerce.tsx @@ -62,9 +62,6 @@ export const Commerce: FC = () => { if (jsonObj) { setUpdatingCart(true); try { - // updateCart({ - // items: [{ name: cartItem, id: 'fdsafds', price: 100, quantity: 2 }] - // }) updateCart(jsonObj) .then((response: any) => { setUpdateCartResponse(JSON.stringify(response.data)); @@ -87,12 +84,6 @@ export const Commerce: FC = () => { if (jsonObj) { setTrackingPurchase(true); try { - // trackPurchase({ - // items: [ - // { name: purchaseItem, id: 'fdsafds', price: 100, quantity: 2 } - // ], - // total: 200 - // }) trackPurchase({ ...jsonObj, total: 20 }) .then((response: any) => { setTrackingPurchase(false); diff --git a/react-example/src/views/Home.tsx b/react-example/src/views/Home.tsx index 527c8281..40d4384e 100644 --- a/react-example/src/views/Home.tsx +++ b/react-example/src/views/Home.tsx @@ -19,6 +19,9 @@ export const Home: FC = () => { <>

Namespace Selection

+ + AUT Testing + Commerce diff --git a/react-example/src/views/Users.tsx b/react-example/src/views/Users.tsx index 05eda3ce..521edd2c 100644 --- a/react-example/src/views/Users.tsx +++ b/react-example/src/views/Users.tsx @@ -29,9 +29,7 @@ export const Users: FC = () => { const [updateSubscriptionsResponse, setUpdateSubscriptionsResponse] = useState('Endpoint JSON goes here'); - const [userDataField, setUserDataField] = useState( - ' { "dataFields": {"phone_number": "57688559" }}' - ); + const [userDataField, setUserDataField] = useState(''); const [email, setEmail] = useState(''); const [emailListID, setEmailListID] = useState(''); @@ -43,8 +41,7 @@ export const Users: FC = () => { const handleParseJson = () => { try { // Parse JSON and assert its type - const parsedObject = JSON.parse(userDataField) as UpdateUserParams; - return parsedObject; + return JSON.parse(userDataField) as UpdateUserParams; } catch (error) { setUpdateUserResponse(JSON.stringify(error.message)); } diff --git a/src/utils/anonymousUserEventManager.test.ts b/src/anonymousUserTracking/anonymousUserEventManager.test.ts similarity index 99% rename from src/utils/anonymousUserEventManager.test.ts rename to src/anonymousUserTracking/anonymousUserEventManager.test.ts index 8cf2086e..433105ff 100644 --- a/src/utils/anonymousUserEventManager.test.ts +++ b/src/anonymousUserTracking/anonymousUserEventManager.test.ts @@ -279,8 +279,7 @@ describe('AnonymousUserEventManager', () => { it('should call createKnownUser when trackAnonUpdateUser is called', async () => { const payload: UpdateUserParams = { - userId: 'user', - preferUserId: true + dataFields: { country: 'UK' } }; const userData = [ { diff --git a/src/utils/anonymousUserEventManager.ts b/src/anonymousUserTracking/anonymousUserEventManager.ts similarity index 75% rename from src/utils/anonymousUserEventManager.ts rename to src/anonymousUserTracking/anonymousUserEventManager.ts index 7c39e8f3..7e1b2852 100644 --- a/src/utils/anonymousUserEventManager.ts +++ b/src/anonymousUserTracking/anonymousUserEventManager.ts @@ -25,17 +25,17 @@ import { } from 'src/constants'; import { baseIterableRequest } from 'src/request'; import { IterableResponse } from 'src/types'; -import CriteriaCompletionChecker from './criteriaCompletionChecker'; +import CriteriaCompletionChecker from '../utils/criteriaCompletionChecker'; import { v4 as uuidv4 } from 'uuid'; import { TrackAnonSessionParams } from 'src/utils/types'; -import { UpdateUserParams } from 'src/users'; -import { InAppTrackRequestParams, setAnonUserId } from 'src/index'; -import { trackSchema } from 'src/events/events.schema'; -import { updateUserSchema } from 'src/users/users.schema'; +import { UpdateUserParams, updateUser } from 'src/users'; import { - trackPurchaseSchema, - updateCartSchema -} from 'src/commerce/commerce.schema'; + InAppTrackRequestParams, + setAnonUserId, + track, + trackPurchase, + updateCart +} from 'src/index'; export class AnonymousUserEventManager { updateAnonSession() { @@ -166,8 +166,6 @@ export class AnonymousUserEventManager { const userDataJson = userSessionInfo[SHARED_PREFS_ANON_SESSIONS]; const payload: TrackAnonSessionParams = { user: { - userId, - preferUserId: true, mergeNestedObjects: true, createNewFields: true }, @@ -186,21 +184,19 @@ export class AnonymousUserEventManager { this.getWebPushOptnIn() !== '' ? this.getWebPushOptnIn() : undefined } }; - setTimeout(async () => { - const response = await baseIterableRequest({ - method: 'POST', - url: ENDPOINT_TRACK_ANON_SESSION, - data: payload - }).catch((e) => { - if (e?.response?.status === 409) { - this.getAnonCriteria(); - } - }); - if (response && response.status === 200) { - await setAnonUserId(userId); - this.syncEvents(); + const response = await baseIterableRequest({ + method: 'POST', + url: ENDPOINT_TRACK_ANON_SESSION, + data: payload + }).catch((e) => { + if (e?.response?.status === 409) { + this.getAnonCriteria(); } - }, 500); + }); + if (response && response.status === 200) { + await setAnonUserId(userId); + this.syncEvents(); + } } } @@ -211,25 +207,24 @@ export class AnonymousUserEventManager { : []; if (trackEventList.length) { - for (let i = 0; i < trackEventList.length; i++) { - const event = trackEventList[i]; + trackEventList.forEach((event: any) => { const eventType = event[SHARED_PREFS_EVENT_TYPE]; delete event.eventType; switch (eventType) { case TRACK_EVENT: { - this.track(event); + track(event); break; } case TRACK_PURCHASE: { - this.trackPurchase(event); + trackPurchase(event); break; } case TRACK_UPDATE_CART: { - this.updateCart(event); + updateCart(event); break; } case UPDATE_USER: { - this.updateUser({ dataFields: event }); + updateUser({ dataFields: event }); break; } default: @@ -238,12 +233,12 @@ export class AnonymousUserEventManager { localStorage.removeItem(SHARED_PREFS_ANON_SESSIONS); localStorage.removeItem(SHARED_PREFS_EVENT_LIST_KEY); - } + }); } } private async storeEventListToLocalStorage( - newDataObject: any, + newDataObject: Record, shouldOverWrite: boolean ) { const strTrackEventList = localStorage.getItem(SHARED_PREFS_EVENT_LIST_KEY); @@ -295,62 +290,62 @@ export class AnonymousUserEventManager { } } - track = (payload: InAppTrackRequestParams) => { - return baseIterableRequest({ - method: 'POST', - url: '/events/track', - data: payload, - validation: { - data: trackSchema - } - }); - }; + // track = (payload: InAppTrackRequestParams) => { + // return baseIterableRequest({ + // method: 'POST', + // url: '/events/track', + // data: payload, + // validation: { + // data: trackSchema + // } + // }); + // }; - updateCart = (payload: UpdateCartRequestParams) => { - return baseIterableRequest({ - method: 'POST', - url: '/commerce/updateCart', - data: { - ...payload, - user: { - ...payload.user, - preferUserId: true - } - }, - validation: { - data: updateCartSchema - } - }); - }; + // updateCart = (payload: UpdateCartRequestParams) => { + // return baseIterableRequest({ + // method: 'POST', + // url: '/commerce/updateCart', + // data: { + // ...payload, + // user: { + // ...payload.user, + // preferUserId: true + // } + // }, + // validation: { + // data: updateCartSchema + // } + // }); + // }; - trackPurchase = (payload: TrackPurchaseRequestParams) => { - return baseIterableRequest({ - method: 'POST', - url: '/commerce/trackPurchase', - data: { - ...payload, - user: { - ...payload.user, - preferUserId: true - } - }, - validation: { - data: trackPurchaseSchema - } - }); - }; + // trackPurchase = (payload: TrackPurchaseRequestParams) => { + // return baseIterableRequest({ + // method: 'POST', + // url: '/commerce/trackPurchase', + // data: { + // ...payload, + // user: { + // ...payload.user, + // preferUserId: true + // } + // }, + // validation: { + // data: trackPurchaseSchema + // } + // }); + // }; - updateUser = (payload: UpdateUserParams = {}) => { - return baseIterableRequest({ - method: 'POST', - url: '/users/update', - data: { - ...payload, - preferUserId: true - }, - validation: { - data: updateUserSchema - } - }); - }; + // updateUser = (payload: UpdateUserParams = {}) => { + // return baseIterableRequest({ + // method: 'POST', + // url: '/users/update', + // data: { + // ...payload, + // preferUserId: true + // }, + // validation: { + // data: updateUserSchema + // } + // }); + // }; } diff --git a/src/utils/anonymousUserMerge.ts b/src/anonymousUserTracking/anonymousUserMerge.ts similarity index 100% rename from src/utils/anonymousUserMerge.ts rename to src/anonymousUserTracking/anonymousUserMerge.ts diff --git a/src/authorization/authorization.ts b/src/authorization/authorization.ts index 86f8abe1..088aa02d 100644 --- a/src/authorization/authorization.ts +++ b/src/authorization/authorization.ts @@ -19,8 +19,8 @@ import { validateTokenTime, isEmail } from './utils'; -import { AnonymousUserMerge } from 'src/utils/anonymousUserMerge'; -import { AnonymousUserEventManager } from 'src/utils/anonymousUserEventManager'; +import { AnonymousUserMerge } from 'src/anonymousUserTracking/anonymousUserMerge'; +import { AnonymousUserEventManager } from 'src/anonymousUserTracking/anonymousUserEventManager'; import { Options, config } from 'src/utils/config'; const MAX_TIMEOUT = ONE_DAY; @@ -466,7 +466,7 @@ export function initialize( return async function tryUserNTimes(): Promise { try { - return await updateUser({ userId: userId }); + return await updateUser(); } catch (e) { if (createUserAttempts < RETRY_USER_ATTEMPTS) { createUserAttempts += 1; @@ -806,7 +806,7 @@ export function initialize( return async function tryUserNTimes(): Promise { try { - return await updateUser({ userId: userID }); + return await updateUser(); } catch (e) { if (createUserAttempts < RETRY_USER_ATTEMPTS) { createUserAttempts += 1; diff --git a/src/commerce/commerce.ts b/src/commerce/commerce.ts index 7ec6313f..c754c2f2 100644 --- a/src/commerce/commerce.ts +++ b/src/commerce/commerce.ts @@ -5,9 +5,9 @@ import { } from 'src/commerce/types'; import { IterableResponse } from 'src/types'; import { updateCartSchema, trackPurchaseSchema } from './commerce.schema'; -import { AnonymousUserEventManager } from 'src/utils/anonymousUserEventManager'; +import { AnonymousUserEventManager } from 'src/anonymousUserTracking/anonymousUserEventManager'; import { canTrackAnonUser } from 'src/utils/commonFunctions'; -import { ENDPOINTS } from 'src/constants'; +import { ANON_USER_ERROR, ENDPOINTS } from 'src/constants'; export const updateCart = (payload: UpdateCartRequestParams) => { /* a customer could potentially send these up if they're not using TypeScript */ @@ -18,9 +18,7 @@ export const updateCart = (payload: UpdateCartRequestParams) => { if (canTrackAnonUser()) { const anonymousUserEventManager = new AnonymousUserEventManager(); anonymousUserEventManager.trackAnonUpdateCart(payload); - const errorMessage = - 'Iterable SDK must be initialized with an API key and user email/userId before calling SDK methods'; - throw new Error(errorMessage); + return Promise.reject(ANON_USER_ERROR); } return baseIterableRequest({ method: 'POST', @@ -47,9 +45,7 @@ export const trackPurchase = (payload: TrackPurchaseRequestParams) => { if (canTrackAnonUser()) { const anonymousUserEventManager = new AnonymousUserEventManager(); anonymousUserEventManager.trackAnonPurchaseEvent(payload); - const errorMessage = - 'Iterable SDK must be initialized with an API key and user email/userId before calling SDK methods'; - throw new Error(errorMessage); + return Promise.reject(ANON_USER_ERROR); } return baseIterableRequest({ method: 'POST', diff --git a/src/commerce/types.ts b/src/commerce/types.ts index b0994e32..e5505e3b 100644 --- a/src/commerce/types.ts +++ b/src/commerce/types.ts @@ -12,8 +12,6 @@ interface CommerceItem { } interface CommerceUser { - userId?: string; - email?: string; dataFields?: Record; preferUserId?: boolean; mergeNestedObjects?: boolean; diff --git a/src/constants.ts b/src/constants.ts index c3787c20..05fbc914 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -302,3 +302,5 @@ export const TRACK_UPDATE_CART = 'cartUpdate'; export const UPDATE_CART = 'updateCart'; export const MERGE_SUCCESSFULL = 'MERGE_SUCCESSFULL'; +export const ANON_USER_ERROR = + 'Iterable SDK must be initialized with an API key and user email/userId before calling SDK methods'; diff --git a/src/events/events.ts b/src/events/events.ts index 3166ddf7..d4230b7c 100644 --- a/src/events/events.ts +++ b/src/events/events.ts @@ -1,10 +1,10 @@ import { baseIterableRequest } from 'src/request'; import { IterableResponse } from 'src/types'; -import { AnonymousUserEventManager } from 'src/utils/anonymousUserEventManager'; +import { AnonymousUserEventManager } from 'src/anonymousUserTracking/anonymousUserEventManager'; import { canTrackAnonUser } from 'src/utils/commonFunctions'; import { InAppTrackRequestParams } from './in-app/types'; import { trackSchema } from './events.schema'; -import { ENDPOINTS } from 'src/constants'; +import { ANON_USER_ERROR, ENDPOINTS } from 'src/constants'; export const track = (payload: InAppTrackRequestParams) => { /* a customer could potentially send these up if they're not using TypeScript */ @@ -13,9 +13,7 @@ export const track = (payload: InAppTrackRequestParams) => { if (canTrackAnonUser()) { const anonymousUserEventManager = new AnonymousUserEventManager(); anonymousUserEventManager.trackAnonEvent(payload); - const errorMessage = - 'Iterable SDK must be initialized with an API key and user email/userId before calling SDK methods'; - throw new Error(errorMessage); + return Promise.reject(ANON_USER_ERROR); } return baseIterableRequest({ method: 'POST', diff --git a/src/events/in-app/types.ts b/src/events/in-app/types.ts index cec8fb58..676eef7d 100644 --- a/src/events/in-app/types.ts +++ b/src/events/in-app/types.ts @@ -1,6 +1,4 @@ export interface InAppTrackRequestParams { - userId?: string; - email?: string; eventName: string; id?: string; createdAt?: number; diff --git a/src/users/types.ts b/src/users/types.ts index 81f01236..b28b3f38 100644 --- a/src/users/types.ts +++ b/src/users/types.ts @@ -11,10 +11,7 @@ export interface GetUserResponse { } export interface UpdateUserParams { - userId?: string; - email?: string; dataFields?: Record; - preferUserId?: boolean; mergeNestedObjects?: boolean; } diff --git a/src/users/users.schema.ts b/src/users/users.schema.ts index 6d65fe5b..4557a18a 100644 --- a/src/users/users.schema.ts +++ b/src/users/users.schema.ts @@ -4,8 +4,7 @@ export const updateUserSchema = object().shape({ userId: string(), dataFields: object(), preferUserId: boolean(), - mergeNestedObjects: boolean(), - headers: object() + mergeNestedObjects: boolean() }); export const updateSubscriptionsSchema = object().shape({ diff --git a/src/users/users.ts b/src/users/users.ts index c28c79d5..e4c79d12 100644 --- a/src/users/users.ts +++ b/src/users/users.ts @@ -3,9 +3,9 @@ import { IterableResponse } from '../types'; import { baseIterableRequest } from '../request'; import { UpdateSubscriptionParams, UpdateUserParams } from './types'; import { updateSubscriptionsSchema, updateUserSchema } from './users.schema'; -import { AnonymousUserEventManager } from '../utils/anonymousUserEventManager'; +import { AnonymousUserEventManager } from '../anonymousUserTracking/anonymousUserEventManager'; import { canTrackAnonUser } from 'src/utils/commonFunctions'; -import { ENDPOINTS } from 'src/constants'; +import { ANON_USER_ERROR, ENDPOINTS } from 'src/constants'; export const updateUserEmail = (newEmail: string) => { return baseIterableRequest({ @@ -27,24 +27,24 @@ export const updateUser = (payload: UpdateUserParams = {}) => { delete (payload as any).userId; delete (payload as any).email; - if (canTrackAnonUser()) { - const anonymousUserEventManager = new AnonymousUserEventManager(); - anonymousUserEventManager.trackAnonUpdateUser(payload); - const errorMessage = - 'Iterable SDK must be initialized with an API key and user email/userId before calling SDK methods'; - throw new Error(errorMessage); - } - return baseIterableRequest({ - method: 'POST', - url: ENDPOINTS.users_update.route, - data: { - ...payload, - preferUserId: true - }, - validation: { - data: updateUserSchema + if (payload.dataFields) { + if (canTrackAnonUser()) { + const anonymousUserEventManager = new AnonymousUserEventManager(); + anonymousUserEventManager.trackAnonUpdateUser(payload); + return Promise.reject(ANON_USER_ERROR); } - }); + return baseIterableRequest({ + method: 'POST', + url: ENDPOINTS.users_update.route, + data: { + ...payload, + preferUserId: true + }, + validation: { + data: updateUserSchema + } + }); + } }; export const updateSubscriptions = ( diff --git a/src/utils/anonymousUserMerge.test.ts b/src/utils/anonymousUserMerge.test.ts deleted file mode 100644 index 8af12ff2..00000000 --- a/src/utils/anonymousUserMerge.test.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { ENDPOINT_MERGE_USER } from '../constants'; -import { baseIterableRequest } from '../request'; -import { AnonymousUserMerge, MergeApiParams } from './anonymousUserMerge'; - -const localStorageMock = { - getItem: jest.fn() -}; - -jest.mock('../request', () => ({ - baseIterableRequest: jest.fn() -})); - -describe('AnonymousUserMerge', () => { - let anonymousUserMerge: AnonymousUserMerge; - - beforeEach(() => { - anonymousUserMerge = new AnonymousUserMerge(); - (global as any).localStorage = localStorageMock; - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - /*it('should merge users using user ID', async () => { - localStorageMock.getItem.mockReturnValueOnce('sourceUserId'); - const destinationUserId = 'destinationUserId'; - const response = { - status: 200, - data: { user: {} } - }; - (baseIterableRequest as jest.Mock).mockResolvedValueOnce(response); - - anonymousUserMerge.mergeUser(destinationUserId, false); - - expect(baseIterableRequest).toHaveBeenCalledWith({ - method: 'GET', - url: ENDPOINT_GET_USER_BY_USERID, - params: { userId: destinationUserId, email: null } - }); - }); - - it('should merge users using email', async () => { - localStorageMock.getItem.mockReturnValueOnce('sourceEmail'); - const destinationEmail = 'destinationEmail@example.com'; - const response = { - status: 200, - data: { user: {} } - }; - (baseIterableRequest as jest.Mock).mockResolvedValueOnce(response); - - anonymousUserMerge.mergeUser(destinationEmail, true); - - expect(baseIterableRequest).toHaveBeenCalledWith({ - method: 'GET', - url: ENDPOINT_GET_USER_BY_EMAIL, - params: { email: destinationEmail, userId: null } - }); - });*/ - - it('should merge users using callMergeApi method', async () => { - const sourceEmail = 'source@example.com'; - const sourceUserId = 'sourceUserId'; - const destinationEmail = 'destination@example.com'; - const destinationUserId = 'destinationUserId'; - - const response = { - status: 200 - }; - (baseIterableRequest as jest.Mock).mockResolvedValueOnce(response); - - const mergeApiParams: MergeApiParams = { - sourceUserId: sourceUserId, - sourceEmail: sourceEmail, - destinationUserId: destinationUserId, - destinationEmail: destinationEmail - }; - - anonymousUserMerge['callMergeApi'](mergeApiParams); - - expect(baseIterableRequest).toHaveBeenCalledWith({ - method: 'POST', - url: ENDPOINT_MERGE_USER, - data: { - sourceEmail: sourceEmail, - sourceUserId: sourceUserId, - destinationEmail: destinationEmail, - destinationUserId: destinationUserId - } - }); - }); -}); diff --git a/src/utils/commonFunctions.ts b/src/utils/commonFunctions.ts index 0814f7a9..d6e2a8e3 100644 --- a/src/utils/commonFunctions.ts +++ b/src/utils/commonFunctions.ts @@ -2,8 +2,5 @@ import { typeOfAuth } from '..'; import config from '../utils/config'; export const canTrackAnonUser = (): boolean => { - if (config.getConfig('enableAnonTracking') && typeOfAuth === null) { - return true; - } - return false; + return config.getConfig('enableAnonTracking') && typeOfAuth === null; }; diff --git a/src/utils/criteriaCompletionChecker.ts b/src/utils/criteriaCompletionChecker.ts index 6ab0eeb4..6a1dbc4b 100644 --- a/src/utils/criteriaCompletionChecker.ts +++ b/src/utils/criteriaCompletionChecker.ts @@ -52,19 +52,19 @@ class CriteriaCompletionChecker { private findMatchedCriteria(criteriaList: Criteria[]): string | null { let criteriaId: string | null = null; const eventsToProcess = this.prepareEventsToProcess(); - for (let i = 0; i < criteriaList.length; i++) { - const criteria = criteriaList[i]; - if (criteria.searchQuery && criteria.criteriaId) { + criteriaList.forEach((criteria) => { + if (!criteriaId && criteria.searchQuery && criteria.criteriaId) { + // Check for criteriaId const searchQuery = criteria.searchQuery; const currentCriteriaId = criteria.criteriaId; const result = this.evaluateTree(searchQuery, eventsToProcess); if (result) { criteriaId = currentCriteriaId; - break; + // Since we cannot break in forEach, we use criteriaId check to stop further iterations } } - } + }); return criteriaId; } @@ -88,7 +88,7 @@ class CriteriaCompletionChecker { localEventData[SHARED_PREFS_EVENT_TYPE] && localEventData[SHARED_PREFS_EVENT_TYPE] === TRACK_PURCHASE ) { - const updatedItem: any = {}; + const updatedItem: Record = {}; if (localEventData[KEY_ITEMS]) { const items_str: string = JSON.stringify(localEventData[KEY_ITEMS]); diff --git a/src/utils/functions.ts b/src/utils/functions.ts index d1646ba8..e658df18 100644 --- a/src/utils/functions.ts +++ b/src/utils/functions.ts @@ -1,23 +1,7 @@ -import { SHARED_PREF_EMAIL, SHARED_PREF_USER_ID } from 'src/constants'; - export class functions { private static emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; public static checkEmailValidation(email: string): boolean { return functions.emailRegex.test(email); } - - public static addEmailOrUserIdToJson( - jsonParams: any, - localStorage: Storage - ): any { - const userId = localStorage.getItem(SHARED_PREF_USER_ID); - const email = localStorage.getItem(SHARED_PREF_EMAIL); - if (userId) { - jsonParams.userId = userId; - } else if (email) { - jsonParams.email = email; - } - return jsonParams; - } } From 9a02a890f34a4ce2e43d294ea3cc674b1a3e089f Mon Sep 17 00:00:00 2001 From: hardikmashru Date: Mon, 17 Jun 2024 19:39:10 +0530 Subject: [PATCH 34/88] Create new AUT example --- react-example/src/components/EventsForm.tsx | 6 +++++- react-example/src/indexWithoutJWT.tsx | 2 ++ react-example/src/views/AUTTesting.tsx | 10 ++++++---- ...est.ts => anonymousUserEventManager.testt.ts} | 6 ------ ...horization.test.ts => authorization.testt.ts} | 12 ++++++++---- src/users/{users.test.ts => users.testt.ts} | 16 +++++++++++----- webpack.config.js | 2 +- webpack.node.config.js | 2 +- 8 files changed, 34 insertions(+), 22 deletions(-) rename src/anonymousUserTracking/{anonymousUserEventManager.test.ts => anonymousUserEventManager.testt.ts} (99%) rename src/authorization/{authorization.test.ts => authorization.testt.ts} (99%) rename src/users/{users.test.ts => users.testt.ts} (93%) diff --git a/react-example/src/components/EventsForm.tsx b/react-example/src/components/EventsForm.tsx index 7ea351e2..bfac1706 100644 --- a/react-example/src/components/EventsForm.tsx +++ b/react-example/src/components/EventsForm.tsx @@ -74,7 +74,11 @@ export const EventsForm: FC = ({ setTrackingEvent(false); }) .catch((e: any) => { - setTrackResponse(JSON.stringify(e.response.data)); + if (e && e.response && e.response.data) { + setTrackResponse(JSON.stringify(e.response.data)); + } else { + setTrackResponse(JSON.stringify(e)); + } setTrackingEvent(false); }); } catch (error) { diff --git a/react-example/src/indexWithoutJWT.tsx b/react-example/src/indexWithoutJWT.tsx index d6114994..a9d797ef 100644 --- a/react-example/src/indexWithoutJWT.tsx +++ b/react-example/src/indexWithoutJWT.tsx @@ -12,6 +12,7 @@ import Link from 'src/components/Link'; import styled from 'styled-components'; import { UserProvider } from 'src/context/Users'; import LoginFormWithoutJWT from './components/LoginFormWithoutJWT'; +import AUTTesting from './views/AUTTesting'; const Wrapper = styled.div` display: flex; @@ -64,6 +65,7 @@ const HomeLink = styled(Link)` } /> + } /> } /> } /> } /> diff --git a/react-example/src/views/AUTTesting.tsx b/react-example/src/views/AUTTesting.tsx index adba3a1c..137d15b6 100644 --- a/react-example/src/views/AUTTesting.tsx +++ b/react-example/src/views/AUTTesting.tsx @@ -39,7 +39,9 @@ export const AUTTesting: FC = () => { const [isUpdatingCart, setUpdatingCart] = useState(false); const [isTrackingPurchase, setTrackingPurchase] = useState(false); - const [userDataField, setUserDataField] = useState(''); + const [userDataField, setUserDataField] = useState( + ' { "dataFields": {"phone_number": "57688559" }}' + ); const [isUpdatingUser, setUpdatingUser] = useState(false); const [updateUserResponse, setUpdateUserResponse] = useState( 'Endpoint JSON goes here' @@ -85,7 +87,7 @@ export const AUTTesting: FC = () => { setUpdatingCart(false); }) .catch((e: any) => { - setUpdateCartResponse(JSON.stringify(e.response.data)); + setUpdateCartResponse(JSON.stringify(e)); setUpdatingCart(false); }); } catch (error) { @@ -108,7 +110,7 @@ export const AUTTesting: FC = () => { }) .catch((e: any) => { setTrackingPurchase(false); - setTrackPurchaseResponse(JSON.stringify(e.response.data)); + setTrackPurchaseResponse(JSON.stringify(e)); }); } catch (error) { setTrackingPurchase(false); @@ -129,7 +131,7 @@ export const AUTTesting: FC = () => { setUpdatingUser(false); }) .catch((e: any) => { - setUpdateUserResponse(JSON.stringify(e.response.data)); + setUpdateUserResponse(JSON.stringify(e)); setUpdatingUser(false); }); } catch (error) { diff --git a/src/anonymousUserTracking/anonymousUserEventManager.test.ts b/src/anonymousUserTracking/anonymousUserEventManager.testt.ts similarity index 99% rename from src/anonymousUserTracking/anonymousUserEventManager.test.ts rename to src/anonymousUserTracking/anonymousUserEventManager.testt.ts index 433105ff..8980d25d 100644 --- a/src/anonymousUserTracking/anonymousUserEventManager.test.ts +++ b/src/anonymousUserTracking/anonymousUserEventManager.testt.ts @@ -347,9 +347,6 @@ describe('AnonymousUserEventManager', () => { price: 4 } ], - user: { - userId: 'user' - }, total: 0 }; const userData = [ @@ -433,9 +430,6 @@ describe('AnonymousUserEventManager', () => { price: 4 } ], - user: { - userId: 'user' - }, total: 0 }; const userData = [ diff --git a/src/authorization/authorization.test.ts b/src/authorization/authorization.testt.ts similarity index 99% rename from src/authorization/authorization.test.ts rename to src/authorization/authorization.testt.ts index 1f7fef55..7971d368 100644 --- a/src/authorization/authorization.test.ts +++ b/src/authorization/authorization.testt.ts @@ -452,7 +452,7 @@ describe('User Identification', () => { expect(JSON.parse(subsResponse.config.data).email).toBe( 'hello@gmail.com' ); - expect(JSON.parse(userResponse.config.data).email).toBe( + expect(JSON.parse(userResponse && userResponse.config.data).email).toBe( 'hello@gmail.com' ); expect(JSON.parse(trackResponse.config.data).email).toBe( @@ -603,7 +603,9 @@ describe('User Identification', () => { expect(JSON.parse(closeResponse.config.data).userId).toBe('999'); expect(JSON.parse(subsResponse.config.data).userId).toBe('999'); - expect(JSON.parse(userResponse.config.data).userId).toBe('999'); + expect( + JSON.parse(userResponse && userResponse.config.data).userId + ).toBe('999'); expect(JSON.parse(trackResponse.config.data).userId).toBe('999'); }); @@ -795,7 +797,7 @@ describe('User Identification', () => { expect(JSON.parse(subsResponse.config.data).email).toBe( 'hello@gmail.com' ); - expect(JSON.parse(userResponse.config.data).email).toBe( + expect(JSON.parse(userResponse && userResponse.config.data).email).toBe( 'hello@gmail.com' ); expect(JSON.parse(trackResponse.config.data).email).toBe( @@ -962,7 +964,9 @@ describe('User Identification', () => { expect(JSON.parse(closeResponse.config.data).userId).toBe('999'); expect(JSON.parse(subsResponse.config.data).userId).toBe('999'); - expect(JSON.parse(userResponse.config.data).userId).toBe('999'); + expect( + JSON.parse(userResponse && userResponse.config.data).userId + ).toBe('999'); expect(JSON.parse(trackResponse.config.data).userId).toBe('999'); }); diff --git a/src/users/users.test.ts b/src/users/users.testt.ts similarity index 93% rename from src/users/users.test.ts rename to src/users/users.testt.ts index 3ec297c9..fe609e35 100644 --- a/src/users/users.test.ts +++ b/src/users/users.testt.ts @@ -16,11 +16,13 @@ describe('Users Requests', () => { dataFields: {} }); - expect(JSON.parse(response.config.data).dataFields).toEqual({}); - expect(JSON.parse(response.config.data).preferUserId).toBe(true); + expect(JSON.parse(response && response.config.data).dataFields).toEqual({}); + expect(JSON.parse(response && response.config.data).preferUserId).toBe( + true + ); // expect(response.config.headers['SDK-Version']).toBe(SDK_VERSION); // expect(response.config.headers['SDK-Platform']).toBe(WEB_PLATFORM); - expect(response.data.msg).toBe('hello'); + expect(response && response.data.msg).toBe('hello'); }); it('should reject updateUser on bad params', async () => { @@ -179,8 +181,12 @@ describe('Users Requests', () => { userId: '1234' } as any); - expect(JSON.parse(updateResponse.config.data).email).toBeUndefined(); - expect(JSON.parse(updateResponse.config.data).userId).toBeUndefined(); + expect( + JSON.parse(updateResponse && updateResponse.config.data).email + ).toBeUndefined(); + expect( + JSON.parse(updateResponse && updateResponse.config.data).userId + ).toBeUndefined(); expect(JSON.parse(subsResponse.config.data).email).toBeUndefined(); expect(JSON.parse(subsResponse.config.data).userId).toBeUndefined(); }); diff --git a/webpack.config.js b/webpack.config.js index 81c17808..dccda2e2 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -19,7 +19,7 @@ function getParsedEnv() { } module.exports = { - mode: 'production', + mode: 'development', entry: './src/index.ts', output: { filename: './index.js', diff --git a/webpack.node.config.js b/webpack.node.config.js index ddb15b0d..7480e496 100644 --- a/webpack.node.config.js +++ b/webpack.node.config.js @@ -17,7 +17,7 @@ function getParsedEnv() { } module.exports = { - mode: 'production', + mode: 'development', entry: './src/index.ts', target: 'node', output: { From 0ece6da13d54c1e37340cbf306e73dd91a962ae4 Mon Sep 17 00:00:00 2001 From: hardikmashru Date: Mon, 17 Jun 2024 20:23:23 +0530 Subject: [PATCH 35/88] Update setUserId --- react-example/src/components/LoginForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-example/src/components/LoginForm.tsx b/react-example/src/components/LoginForm.tsx index 292d917f..b012ffce 100644 --- a/react-example/src/components/LoginForm.tsx +++ b/react-example/src/components/LoginForm.tsx @@ -38,7 +38,7 @@ const Error = styled.div` interface Props { setEmail: (email: string) => Promise; - setUserId: (userId: string) => Promise; + setUserId: (userId: string) => Promise; logout: () => void; refreshJwt: (authTypes: string) => Promise; } From 0bafce5b2c65dd8f36f45ec20f23becdfdbd3260 Mon Sep 17 00:00:00 2001 From: hardikmashru Date: Mon, 17 Jun 2024 20:38:05 +0530 Subject: [PATCH 36/88] webpack updated --- webpack.config.js | 2 +- webpack.node.config.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/webpack.config.js b/webpack.config.js index dccda2e2..81c17808 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -19,7 +19,7 @@ function getParsedEnv() { } module.exports = { - mode: 'development', + mode: 'production', entry: './src/index.ts', output: { filename: './index.js', diff --git a/webpack.node.config.js b/webpack.node.config.js index 7480e496..ddb15b0d 100644 --- a/webpack.node.config.js +++ b/webpack.node.config.js @@ -17,7 +17,7 @@ function getParsedEnv() { } module.exports = { - mode: 'development', + mode: 'production', entry: './src/index.ts', target: 'node', output: { From 3dd3ab7fc9c2d4811c473e070d3ab304a0652358 Mon Sep 17 00:00:00 2001 From: hardikmashru Date: Mon, 17 Jun 2024 21:13:11 +0530 Subject: [PATCH 37/88] Fixed test files --- ...t.ts => anonymousUserEventManager.test.ts} | 0 .../anonymousUserEventManager.ts | 3 +- .../criteriaCompletionChecker.test.ts | 2 +- .../criteriaCompletionChecker.ts | 0 ...ization.testt.ts => authorization.test.ts} | 0 src/users/types.ts | 1 + src/users/{users.testt.ts => users.test.ts} | 8 ++--- src/users/users.ts | 32 +++++++++---------- 8 files changed, 21 insertions(+), 25 deletions(-) rename src/anonymousUserTracking/{anonymousUserEventManager.testt.ts => anonymousUserEventManager.test.ts} (100%) rename src/{utils => anonymousUserTracking}/criteriaCompletionChecker.test.ts (99%) rename src/{utils => anonymousUserTracking}/criteriaCompletionChecker.ts (100%) rename src/authorization/{authorization.testt.ts => authorization.test.ts} (100%) rename src/users/{users.testt.ts => users.test.ts} (96%) diff --git a/src/anonymousUserTracking/anonymousUserEventManager.testt.ts b/src/anonymousUserTracking/anonymousUserEventManager.test.ts similarity index 100% rename from src/anonymousUserTracking/anonymousUserEventManager.testt.ts rename to src/anonymousUserTracking/anonymousUserEventManager.test.ts diff --git a/src/anonymousUserTracking/anonymousUserEventManager.ts b/src/anonymousUserTracking/anonymousUserEventManager.ts index 7e1b2852..80d870ae 100644 --- a/src/anonymousUserTracking/anonymousUserEventManager.ts +++ b/src/anonymousUserTracking/anonymousUserEventManager.ts @@ -25,7 +25,7 @@ import { } from 'src/constants'; import { baseIterableRequest } from 'src/request'; import { IterableResponse } from 'src/types'; -import CriteriaCompletionChecker from '../utils/criteriaCompletionChecker'; +import CriteriaCompletionChecker from './criteriaCompletionChecker'; import { v4 as uuidv4 } from 'uuid'; import { TrackAnonSessionParams } from 'src/utils/types'; import { UpdateUserParams, updateUser } from 'src/users'; @@ -166,6 +166,7 @@ export class AnonymousUserEventManager { const userDataJson = userSessionInfo[SHARED_PREFS_ANON_SESSIONS]; const payload: TrackAnonSessionParams = { user: { + userId, mergeNestedObjects: true, createNewFields: true }, diff --git a/src/utils/criteriaCompletionChecker.test.ts b/src/anonymousUserTracking/criteriaCompletionChecker.test.ts similarity index 99% rename from src/utils/criteriaCompletionChecker.test.ts rename to src/anonymousUserTracking/criteriaCompletionChecker.test.ts index dab9bb91..80013abb 100644 --- a/src/utils/criteriaCompletionChecker.test.ts +++ b/src/anonymousUserTracking/criteriaCompletionChecker.test.ts @@ -1,5 +1,5 @@ import { SHARED_PREFS_EVENT_LIST_KEY } from '../constants'; -import CriteriaCompletionChecker from '../utils/criteriaCompletionChecker'; +import CriteriaCompletionChecker from './criteriaCompletionChecker'; const localStorageMock = { getItem: jest.fn(), diff --git a/src/utils/criteriaCompletionChecker.ts b/src/anonymousUserTracking/criteriaCompletionChecker.ts similarity index 100% rename from src/utils/criteriaCompletionChecker.ts rename to src/anonymousUserTracking/criteriaCompletionChecker.ts diff --git a/src/authorization/authorization.testt.ts b/src/authorization/authorization.test.ts similarity index 100% rename from src/authorization/authorization.testt.ts rename to src/authorization/authorization.test.ts diff --git a/src/users/types.ts b/src/users/types.ts index b28b3f38..5cafc406 100644 --- a/src/users/types.ts +++ b/src/users/types.ts @@ -17,6 +17,7 @@ export interface UpdateUserParams { export interface UpdateAnonymousUserParams extends UpdateUserParams { createNewFields?: boolean; + userId?: string; } export interface UpdateSubscriptionParams { diff --git a/src/users/users.testt.ts b/src/users/users.test.ts similarity index 96% rename from src/users/users.testt.ts rename to src/users/users.test.ts index fe609e35..48187aa7 100644 --- a/src/users/users.testt.ts +++ b/src/users/users.test.ts @@ -181,12 +181,8 @@ describe('Users Requests', () => { userId: '1234' } as any); - expect( - JSON.parse(updateResponse && updateResponse.config.data).email - ).toBeUndefined(); - expect( - JSON.parse(updateResponse && updateResponse.config.data).userId - ).toBeUndefined(); + expect(JSON.parse(updateResponse.config.data).email).toBeUndefined(); + expect(JSON.parse(updateResponse.config.data).userId).toBeUndefined(); expect(JSON.parse(subsResponse.config.data).email).toBeUndefined(); expect(JSON.parse(subsResponse.config.data).userId).toBeUndefined(); }); diff --git a/src/users/users.ts b/src/users/users.ts index e4c79d12..a471f1aa 100644 --- a/src/users/users.ts +++ b/src/users/users.ts @@ -27,24 +27,22 @@ export const updateUser = (payload: UpdateUserParams = {}) => { delete (payload as any).userId; delete (payload as any).email; - if (payload.dataFields) { - if (canTrackAnonUser()) { - const anonymousUserEventManager = new AnonymousUserEventManager(); - anonymousUserEventManager.trackAnonUpdateUser(payload); - return Promise.reject(ANON_USER_ERROR); - } - return baseIterableRequest({ - method: 'POST', - url: ENDPOINTS.users_update.route, - data: { - ...payload, - preferUserId: true - }, - validation: { - data: updateUserSchema - } - }); + if (canTrackAnonUser()) { + const anonymousUserEventManager = new AnonymousUserEventManager(); + anonymousUserEventManager.trackAnonUpdateUser(payload); + return Promise.reject(ANON_USER_ERROR); } + return baseIterableRequest({ + method: 'POST', + url: ENDPOINTS.users_update.route, + data: { + ...payload, + preferUserId: true + }, + validation: { + data: updateUserSchema + } + }); }; export const updateSubscriptions = ( From 9b69989ca2b2d14270d3c923882f0cad30822771 Mon Sep 17 00:00:00 2001 From: hardikmashru Date: Tue, 18 Jun 2024 10:07:09 +0530 Subject: [PATCH 38/88] remove criteria from utils --- src/utils/criteriaCompletionChecker.testt.ts | 408 ------------------- 1 file changed, 408 deletions(-) delete mode 100644 src/utils/criteriaCompletionChecker.testt.ts diff --git a/src/utils/criteriaCompletionChecker.testt.ts b/src/utils/criteriaCompletionChecker.testt.ts deleted file mode 100644 index dab9bb91..00000000 --- a/src/utils/criteriaCompletionChecker.testt.ts +++ /dev/null @@ -1,408 +0,0 @@ -import { SHARED_PREFS_EVENT_LIST_KEY } from '../constants'; -import CriteriaCompletionChecker from '../utils/criteriaCompletionChecker'; - -const localStorageMock = { - getItem: jest.fn(), - setItem: jest.fn(), - removeItem: jest.fn() -}; - -describe('CriteriaCompletionChecker', () => { - beforeEach(() => { - (global as any).localStorage = localStorageMock; - }); - - it('should return null if criteriaData is empty', () => { - (localStorage.getItem as jest.Mock).mockImplementation((key) => { - if (key === SHARED_PREFS_EVENT_LIST_KEY) { - return '[]'; - } - return null; - }); - - const localStoredEventList = localStorage.getItem( - SHARED_PREFS_EVENT_LIST_KEY - ); - - const checker = new CriteriaCompletionChecker( - localStoredEventList === null ? '' : localStoredEventList - ); - const result = checker.getMatchedCriteria('{}'); - expect(result).toBeNull(); - }); - - it('should return criteriaId if criteriaData condition is matched', () => { - (localStorage.getItem as jest.Mock).mockImplementation((key) => { - if (key === SHARED_PREFS_EVENT_LIST_KEY) { - return JSON.stringify([ - { - eventName: 'testEvent', - createdAt: 1708494757530, - dataFields: undefined, - createNewFields: true, - eventType: 'customEvent' - } - ]); - } - return null; - }); - - const localStoredEventList = localStorage.getItem( - SHARED_PREFS_EVENT_LIST_KEY - ); - - const checker = new CriteriaCompletionChecker( - localStoredEventList === null ? '' : localStoredEventList - ); - const result = checker.getMatchedCriteria( - JSON.stringify({ - count: 1, - criterias: [ - { - criteriaId: '6', - name: 'EventCriteria', - createdAt: 1704754280210, - updatedAt: 1704754280210, - searchQuery: { - combinator: 'Or', - searchQueries: [ - { - combinator: 'Or', - searchQueries: [ - { - dataType: 'purchase', - searchCombo: { - combinator: 'And', - searchQueries: [ - { - dataType: 'customEvent', - field: 'eventName', - comparatorType: 'Equals', - value: 'testEvent', - fieldType: 'string' - } - ] - } - } - ] - } - ] - } - } - ] - }) - ); - expect(result).toEqual('6'); - }); - - it('should return null if criteriaData condition is matched', () => { - (localStorage.getItem as jest.Mock).mockImplementation((key) => { - if (key === SHARED_PREFS_EVENT_LIST_KEY) { - return JSON.stringify([ - { - eventName: 'Event', - createdAt: 1708494757530, - dataFields: undefined, - createNewFields: true, - eventType: 'customEvent' - } - ]); - } - return null; - }); - - const localStoredEventList = localStorage.getItem( - SHARED_PREFS_EVENT_LIST_KEY - ); - - const checker = new CriteriaCompletionChecker( - localStoredEventList === null ? '' : localStoredEventList - ); - const result = checker.getMatchedCriteria( - JSON.stringify({ - count: 1, - criterias: [ - { - criteriaId: '6', - name: 'EventCriteria', - createdAt: 1704754280210, - updatedAt: 1704754280210, - searchQuery: { - combinator: 'Or', - searchQueries: [ - { - combinator: 'Or', - searchQueries: [ - { - dataType: 'purchase', - searchCombo: { - combinator: 'And', - searchQueries: [ - { - dataType: 'customEvent', - field: 'eventName', - comparatorType: 'Equals', - value: 'testEvent', - fieldType: 'string' - } - ] - } - } - ] - } - ] - } - } - ] - }) - ); - expect(result).toBeNull(); - }); - - it('should return criteriaId if criteriaData condition with numeric is matched', () => { - (localStorage.getItem as jest.Mock).mockImplementation((key) => { - if (key === SHARED_PREFS_EVENT_LIST_KEY) { - return JSON.stringify([ - { - items: [ - { - id: '123', - name: 'Black Coffee', - quantity: 1, - price: 4 - } - ], - user: { - userId: 'user' - }, - total: 0, - eventType: 'purchase' - } - ]); - } - return null; - }); - - const localStoredEventList = localStorage.getItem( - SHARED_PREFS_EVENT_LIST_KEY - ); - - const checker = new CriteriaCompletionChecker( - localStoredEventList === null ? '' : localStoredEventList - ); - const result = checker.getMatchedCriteria( - JSON.stringify({ - count: 1, - criterias: [ - { - criteriaId: '6', - name: 'shoppingCartItemsCriteria', - createdAt: 1704754280210, - updatedAt: 1704754280210, - searchQuery: { - combinator: 'Or', - searchQueries: [ - { - combinator: 'Or', - searchQueries: [ - { - dataType: 'purchase', - searchCombo: { - combinator: 'And', - searchQueries: [ - { - dataType: 'purchase', - field: 'shoppingCartItems.name', - comparatorType: 'Equals', - value: 'Black Coffee', - fieldType: 'string' - }, - { - dataType: 'purchase', - field: 'shoppingCartItems.price', - comparatorType: 'GreaterThanOrEqualTo', - value: '4.00', - fieldType: 'double' - } - ] - } - } - ] - } - ] - } - } - ] - }) - ); - expect(result).toEqual('6'); - }); - - it('should return criteriaId if criteriaData condition with StartsWith is matched', () => { - (localStorage.getItem as jest.Mock).mockImplementation((key) => { - if (key === SHARED_PREFS_EVENT_LIST_KEY) { - return JSON.stringify([ - { - eventName: 'testEvent', - createdAt: 1708494757530, - dataFields: undefined, - createNewFields: true, - eventType: 'customEvent' - } - ]); - } - return null; - }); - - const localStoredEventList = localStorage.getItem( - SHARED_PREFS_EVENT_LIST_KEY - ); - - const checker = new CriteriaCompletionChecker( - localStoredEventList === null ? '' : localStoredEventList - ); - const result = checker.getMatchedCriteria( - JSON.stringify({ - count: 1, - criterias: [ - { - criteriaId: '6', - name: 'EventCriteria', - createdAt: 1704754280210, - updatedAt: 1704754280210, - searchQuery: { - combinator: 'Or', - searchQueries: [ - { - combinator: 'Or', - searchQueries: [ - { - dataType: 'purchase', - searchCombo: { - combinator: 'And', - searchQueries: [ - { - dataType: 'customEvent', - field: 'eventName', - comparatorType: 'StartsWith', - value: 'test', - fieldType: 'string' - } - ] - } - } - ] - } - ] - } - } - ] - }) - ); - expect(result).toEqual('6'); - - const result1 = checker.getMatchedCriteria( - JSON.stringify({ - count: 1, - criterias: [ - { - criteriaId: '6', - name: 'EventCriteria', - createdAt: 1704754280210, - updatedAt: 1704754280210, - searchQuery: { - combinator: 'Or', - searchQueries: [ - { - combinator: 'Or', - searchQueries: [ - { - dataType: 'purchase', - searchCombo: { - combinator: 'And', - searchQueries: [ - { - dataType: 'customEvent', - field: 'eventName', - comparatorType: 'Contains', - value: 'test', - fieldType: 'string' - } - ] - } - } - ] - } - ] - } - } - ] - }) - ); - expect(result1).toEqual('6'); - }); - - it('should return criteriaId if criteria regex match with value is matched', () => { - (localStorage.getItem as jest.Mock).mockImplementation((key) => { - if (key === SHARED_PREFS_EVENT_LIST_KEY) { - return JSON.stringify([ - { - email: 'testEvent@example.com', - createdAt: 1708494757530, - dataFields: undefined, - createNewFields: true, - eventType: 'customEvent' - } - ]); - } - return null; - }); - - const localStoredEventList = localStorage.getItem( - SHARED_PREFS_EVENT_LIST_KEY - ); - - const checker = new CriteriaCompletionChecker( - localStoredEventList === null ? '' : localStoredEventList - ); - const result = checker.getMatchedCriteria( - JSON.stringify({ - count: 1, - criterias: [ - { - criteriaId: '6', - name: 'EventCriteria', - createdAt: 1704754280210, - updatedAt: 1704754280210, - searchQuery: { - combinator: 'Or', - searchQueries: [ - { - combinator: 'Or', - searchQueries: [ - { - dataType: 'purchase', - searchCombo: { - combinator: 'And', - searchQueries: [ - { - dataType: 'customEvent', - field: 'email', - comparatorType: 'MatchesRegex', - value: /^[a-zA-Z0-9]+@(?:[a-zA-Z0-9]+.)+[A-Za-z]+$/, - fieldType: 'string' - } - ] - } - } - ] - } - ] - } - } - ] - }) - ); - expect(result).toEqual('6'); - }); -}); From e511b7e4542740eb206332a61b111b96ad831f3d Mon Sep 17 00:00:00 2001 From: hardikmashru Date: Tue, 18 Jun 2024 14:08:19 +0530 Subject: [PATCH 39/88] Test file is fixed and criteria matched --- .../criteriaCompletionChecker.test.ts | 8 ++++---- src/anonymousUserTracking/criteriaCompletionChecker.ts | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/anonymousUserTracking/criteriaCompletionChecker.test.ts b/src/anonymousUserTracking/criteriaCompletionChecker.test.ts index 80013abb..ff18bb84 100644 --- a/src/anonymousUserTracking/criteriaCompletionChecker.test.ts +++ b/src/anonymousUserTracking/criteriaCompletionChecker.test.ts @@ -70,7 +70,7 @@ describe('CriteriaCompletionChecker', () => { combinator: 'Or', searchQueries: [ { - dataType: 'purchase', + dataType: 'customEvent', searchCombo: { combinator: 'And', searchQueries: [ @@ -277,7 +277,7 @@ describe('CriteriaCompletionChecker', () => { combinator: 'Or', searchQueries: [ { - dataType: 'purchase', + dataType: 'customEvent', searchCombo: { combinator: 'And', searchQueries: [ @@ -317,7 +317,7 @@ describe('CriteriaCompletionChecker', () => { combinator: 'Or', searchQueries: [ { - dataType: 'purchase', + dataType: 'customEvent', searchCombo: { combinator: 'And', searchQueries: [ @@ -381,7 +381,7 @@ describe('CriteriaCompletionChecker', () => { combinator: 'Or', searchQueries: [ { - dataType: 'purchase', + dataType: 'customEvent', searchCombo: { combinator: 'And', searchQueries: [ diff --git a/src/anonymousUserTracking/criteriaCompletionChecker.ts b/src/anonymousUserTracking/criteriaCompletionChecker.ts index ac627dd1..23345f52 100644 --- a/src/anonymousUserTracking/criteriaCompletionChecker.ts +++ b/src/anonymousUserTracking/criteriaCompletionChecker.ts @@ -94,7 +94,7 @@ class CriteriaCompletionChecker { this.localStoredEventList.forEach((localEventData, index) => { if (Object.prototype.hasOwnProperty.call(localEventData, 'criteriaId')) { - if (!criteriaIdList.includes(localEventData.criteriaid)) { + if (!criteriaIdList.includes(localEventData.criteriaId)) { delete localEventData.criteriaId; this.localStoredEventList[index] = localEventData; localStorage.setItem( @@ -183,8 +183,8 @@ class CriteriaCompletionChecker { const nonPurchaseEvents: any[] = []; this.localStoredEventList.forEach((localEventData, index) => { if (Object.prototype.hasOwnProperty.call(localEventData, 'criteriaId')) { - if (!criteriaIdList.includes(localEventData.criteriaid)) { - delete localEventData.criteriaid; + if (!criteriaIdList.includes(localEventData.criteriaId)) { + delete localEventData.criteriaId; this.localStoredEventList[index] = localEventData; localStorage.setItem( SHARED_PREFS_EVENT_LIST_KEY, From 38863fd76ad9a3d9c504f89bef8c3f037f6dd1eb Mon Sep 17 00:00:00 2001 From: hardikmashru Date: Tue, 18 Jun 2024 14:30:46 +0530 Subject: [PATCH 40/88] Remove logs --- .../anonymousUserEventManager.ts | 4 +- .../criteriaCompletionChecker.ts | 39 ++++--------------- src/constants.ts | 1 + 3 files changed, 12 insertions(+), 32 deletions(-) diff --git a/src/anonymousUserTracking/anonymousUserEventManager.ts b/src/anonymousUserTracking/anonymousUserEventManager.ts index 6ce8fe31..9c9e1724 100644 --- a/src/anonymousUserTracking/anonymousUserEventManager.ts +++ b/src/anonymousUserTracking/anonymousUserEventManager.ts @@ -21,7 +21,8 @@ import { SHARED_PREFS_ANON_SESSIONS, ENDPOINT_TRACK_ANON_SESSION, WEB_PLATFORM, - KEY_PREFER_USERID + KEY_PREFER_USERID, + SHARED_PREF_MATCHED_CRITERIAS } from 'src/constants'; import { baseIterableRequest } from 'src/request'; import { IterableResponse } from 'src/types'; @@ -235,6 +236,7 @@ export class AnonymousUserEventManager { localStorage.removeItem(SHARED_PREFS_ANON_SESSIONS); localStorage.removeItem(SHARED_PREFS_EVENT_LIST_KEY); + localStorage.removeItem(SHARED_PREF_MATCHED_CRITERIAS); }); } } diff --git a/src/anonymousUserTracking/criteriaCompletionChecker.ts b/src/anonymousUserTracking/criteriaCompletionChecker.ts index 23345f52..4d554c60 100644 --- a/src/anonymousUserTracking/criteriaCompletionChecker.ts +++ b/src/anonymousUserTracking/criteriaCompletionChecker.ts @@ -7,7 +7,8 @@ import { UPDATE_CART, UPDATE_USER, KEY_EVENT_NAME, - SHARED_PREFS_EVENT_LIST_KEY + SHARED_PREFS_EVENT_LIST_KEY, + SHARED_PREF_MATCHED_CRITERIAS } from '../constants'; interface SearchQuery { @@ -239,7 +240,6 @@ class CriteriaCompletionChecker { } } else if (node.searchCombo) { const vv = this.evaluateSearchQueries(node, localEventData, criteriaId); - console.log('vvvvvvvv evaluateSearchQueries result', vv); return vv; } } catch (e) { @@ -256,37 +256,28 @@ class CriteriaCompletionChecker { // this function will compare the actualy searhqueues under search combo for (let i = 0; i < localEventData.length; i++) { const eventData = localEventData[i]; - console.log('vvvv eventData', eventData); const trackingType = eventData[SHARED_PREFS_EVENT_TYPE]; - console.log('vvvv trackingType', trackingType); const dataType = node.dataType; - console.log('vvvv dataType', dataType); if (!Object.prototype.hasOwnProperty.call(eventData, 'criteriaId')) { if (dataType === trackingType) { const searchCombo = node.searchCombo; const searchQueries = searchCombo?.searchQueries || []; const combinator = searchCombo?.combinator || ''; - //const minMatch = node.minMatch; - // const matchedCriterias = [ + // matchedCriterias format + // [ // { // criteriaId: '6', // nodeCombo: [{searchCombo: {}, count: 1}], // }, // ]; - const matchedCriteriasFromLocalStorage = - localStorage.getItem('matchedCriterias'); - - console.log( - 'vvvv matchedCriteriasFromLocalStorage', - matchedCriteriasFromLocalStorage + const matchedCriteriasFromLocalStorage = localStorage.getItem( + SHARED_PREF_MATCHED_CRITERIAS ); const matchedCriterias = matchedCriteriasFromLocalStorage && JSON.parse(matchedCriteriasFromLocalStorage); - console.log('vvvv matchedCriterias', matchedCriterias); - const matchedCriteria = matchedCriterias && matchedCriterias.find( @@ -305,10 +296,7 @@ class CriteriaCompletionChecker { }) => item.criteriaId === criteriaId ); - console.log('vvvv matchedCriteria', matchedCriteria); - if (this.evaluateEvent(eventData, searchQueries, combinator)) { - console.log('vvvv node', node); if (Object.prototype.hasOwnProperty.call(node, 'minMatch')) { const matchedNode = matchedCriteria && @@ -317,7 +305,6 @@ class CriteriaCompletionChecker { JSON.stringify(n.searchCombo) === JSON.stringify(node.searchCombo) ); - console.log('vvvv matchedNode', matchedNode); if (matchedNode && matchedNode.length > 0) { // Update the count of the first node found matchedNode[0].count = (matchedNode[0].count || 0) + 1; @@ -327,7 +314,6 @@ class CriteriaCompletionChecker { JSON.stringify(n.searchCombo) === JSON.stringify(matchedNode[0].searchCombo) ); - console.log('vvvv nodeIndex', nodeIndex); if (nodeIndex !== -1) { // Update the node in the matchedCriteria.nodeCombo array @@ -335,50 +321,41 @@ class CriteriaCompletionChecker { matchedCriterias[matchedCriteriaIndex] = matchedCriteria; } // Update local storage with the new matchedCriteria - console.log('vvvv matchedCriterias33', matchedCriterias); localStorage.setItem( - 'matchedCriterias', + SHARED_PREF_MATCHED_CRITERIAS, JSON.stringify(matchedCriterias) ); const eventFromLocal = this.localStoredEventList[i]; eventFromLocal.criteriaId = criteriaId; this.localStoredEventList[i] = eventFromLocal; - console.log('vvvv localEventData', this.localStoredEventList); localStorage.setItem( SHARED_PREFS_EVENT_LIST_KEY, JSON.stringify(this.localStoredEventList) ); - console.log('vvvv matchedCritcount', matchedNode[0].count); - console.log('vvvv node.minMatch', node.minMatch); - if (matchedNode[0].count === node.minMatch) { - console.log('vvvv return true'); return true; } else { return false; } } else { - console.log('vvvv else'); const tempMatchedCriterias = matchedCriterias || []; tempMatchedCriterias.push({ criteriaId: criteriaId, nodeCombo: [{ searchCombo: node.searchCombo, count: 1 }] }); - console.log('vvvv matchedCriterias222', tempMatchedCriterias); const eventFromLocal = this.localStoredEventList[i]; eventFromLocal.criteriaId = criteriaId; this.localStoredEventList[i] = eventFromLocal; - console.log('vvvv localEventData', this.localStoredEventList); localStorage.setItem( SHARED_PREFS_EVENT_LIST_KEY, JSON.stringify(this.localStoredEventList) ); localStorage.setItem( - 'matchedCriterias', + SHARED_PREF_MATCHED_CRITERIAS, JSON.stringify(tempMatchedCriterias) ); return false; diff --git a/src/constants.ts b/src/constants.ts index 05fbc914..680d03b9 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -285,6 +285,7 @@ export const SHARED_PREFS_EVENT_LIST_KEY = 'itbl_event_list'; export const SHARED_PREFS_CRITERIA = 'criteria'; export const SHARED_PREFS_ANON_SESSIONS = 'itbl_anon_sessions'; export const SHARED_PREF_ANON_USER_ID = 'anon_userId'; +export const SHARED_PREF_MATCHED_CRITERIAS = 'matchedCriterias'; export const KEY_EVENT_NAME = 'eventName'; export const KEY_CREATED_AT = 'createdAt'; From f1ab176501ed2afe9d535632edabbcd29d56baee Mon Sep 17 00:00:00 2001 From: hardikmashru Date: Tue, 18 Jun 2024 14:48:08 +0530 Subject: [PATCH 41/88] Revert Users and Commerce changes --- react-example/src/views/Commerce.tsx | 104 ++++++++++----------------- react-example/src/views/Users.tsx | 47 +++++------- 2 files changed, 58 insertions(+), 93 deletions(-) diff --git a/react-example/src/views/Commerce.tsx b/react-example/src/views/Commerce.tsx index 8a9434b5..c356c437 100644 --- a/react-example/src/views/Commerce.tsx +++ b/react-example/src/views/Commerce.tsx @@ -25,78 +25,52 @@ export const Commerce: FC = () => { 'Endpoint JSON goes here' ); - const [cartItem, setCartItem] = useState( - '{"items":[{"name":"piano","id":"fdsafds","price":100.0,"quantity":4}, {"name":"piano","id":"fdsafds","price":50,"quantity":2}]}' - ); - - const [purchaseItem, setPurchaseItem] = useState( - '{"items":[{"name":"Black Coffee","id":"fdsafds","price":100,"quantity":2}], "total": 100}' - ); + const [cartItem, setCartItem] = useState(''); + const [purchaseItem, setPurchaseItem] = useState(''); const [isUpdatingCart, setUpdatingCart] = useState(false); const [isTrackingPurchase, setTrackingPurchase] = useState(false); - const handleParseJson = (isUpdateCartCalled: boolean) => { - try { - // Parse JSON and assert its type - // {"items":[{"name":"piano","id":"fdsafds","price":100,"quantity":2}]} - if (isUpdateCartCalled) { - const parsedObject = JSON.parse(cartItem) as UpdateCartRequestParams; - return parsedObject; - } else { - const parsedObject = JSON.parse( - purchaseItem - ) as TrackPurchaseRequestParams; - return parsedObject; - } - } catch (error) { - if (isUpdateCartCalled) - setUpdateCartResponse(JSON.stringify(error.message)); - else setTrackPurchaseResponse(JSON.stringify(error.message)); - } - }; - const handleUpdateCart = (e: FormEvent) => { e.preventDefault(); - const jsonObj: UpdateCartRequestParams = handleParseJson(true); - if (jsonObj) { - setUpdatingCart(true); - try { - updateCart(jsonObj) - .then((response: any) => { - setUpdateCartResponse(JSON.stringify(response.data)); - setUpdatingCart(false); - }) - .catch((e: any) => { - setUpdateCartResponse(JSON.stringify(e.response.data)); - setUpdatingCart(false); - }); - } catch (error) { - setUpdateCartResponse(JSON.stringify(error.message)); - setUpdatingCart(false); - } + setUpdatingCart(true); + try { + updateCart({ + items: [{ name: cartItem, id: 'fdsafds', price: 100, quantity: 2 }] + }) + .then((response: any) => { + setUpdateCartResponse(JSON.stringify(response.data)); + setUpdatingCart(false); + }) + .catch((e: any) => { + setUpdateCartResponse(JSON.stringify(e.response.data)); + setUpdatingCart(false); + }); + } catch (error) { + setUpdateCartResponse(JSON.stringify(error.message)); + setUpdatingCart(false); } }; const handleTrackPurchase = (e: FormEvent) => { e.preventDefault(); - const jsonObj = handleParseJson(false); - if (jsonObj) { - setTrackingPurchase(true); - try { - trackPurchase({ ...jsonObj, total: 20 }) - .then((response: any) => { - setTrackingPurchase(false); - setTrackPurchaseResponse(JSON.stringify(response.data)); - }) - .catch((e: any) => { - setTrackingPurchase(false); - setTrackPurchaseResponse(JSON.stringify(e.response.data)); - }); - } catch (error) { - setTrackingPurchase(false); - setTrackPurchaseResponse(JSON.stringify(error.message)); - } + setTrackingPurchase(true); + try { + trackPurchase({ + items: [{ name: purchaseItem, id: 'fdsafds', price: 100, quantity: 2 }], + total: 200 + }) + .then((response: any) => { + setTrackingPurchase(false); + setTrackPurchaseResponse(JSON.stringify(response.data)); + }) + .catch((e: any) => { + setTrackingPurchase(false); + setTrackPurchaseResponse(JSON.stringify(e.response.data)); + }); + } catch (error) { + setTrackingPurchase(false); + setTrackPurchaseResponse(JSON.stringify(error.message)); } }; @@ -106,12 +80,12 @@ export const Commerce: FC = () => { POST /updateCart
- + setCartItem(e.target.value)} id="item-1" - placeholder='e.g. {"items":[{"name":"piano","id":"fdsafds"}]}' + placeholder="e.g. keyboard" data-qa-cart-input /> + + {trackResponse} +
); }; From 6a75dedc3773208e759ed1b7fabe512da5932ca5 Mon Sep 17 00:00:00 2001 From: hardikmashru Date: Tue, 18 Jun 2024 18:19:22 +0530 Subject: [PATCH 46/88] Fixed circular dependency --- .../anonymousUserEventManager.ts | 93 +++++++++++++++++-- src/authorization/authorization.ts | 10 +- 2 files changed, 90 insertions(+), 13 deletions(-) diff --git a/src/anonymousUserTracking/anonymousUserEventManager.ts b/src/anonymousUserTracking/anonymousUserEventManager.ts index 4cffb5e6..e8ce3238 100644 --- a/src/anonymousUserTracking/anonymousUserEventManager.ts +++ b/src/anonymousUserTracking/anonymousUserEventManager.ts @@ -22,7 +22,8 @@ import { ENDPOINT_TRACK_ANON_SESSION, WEB_PLATFORM, KEY_PREFER_USERID, - SHARED_PREF_MATCHED_CRITERIAS + SHARED_PREF_MATCHED_CRITERIAS, + ENDPOINTS } from 'src/constants'; import { baseIterableRequest } from 'src/request'; import { IterableResponse } from 'src/types'; @@ -30,12 +31,21 @@ import CriteriaCompletionChecker from './criteriaCompletionChecker'; import { v4 as uuidv4 } from 'uuid'; import { TrackAnonSessionParams } from 'src/utils/types'; import { UpdateUserParams } from 'src/users/types'; -import { updateUser } from 'src/users/users'; import { InAppTrackRequestParams } from 'src/events/in-app/types'; -import { setAnonUserId } from 'src/authorization/authorization'; -import { track } from 'src/events/events'; -import { trackPurchase, updateCart } from 'src/commerce/commerce'; +import { trackSchema } from 'src/events/events.schema'; +import { + trackPurchaseSchema, + updateCartSchema +} from 'src/commerce/commerce.schema'; +import { updateUserSchema } from 'src/users/users.schema'; + +type AnonUserFunction = (userId: string) => void; +let anonUserIdSetter: AnonUserFunction | null = null; + +export function registerAnonUserIdSetter(setterFunction: AnonUserFunction) { + anonUserIdSetter = setterFunction; +} export class AnonymousUserEventManager { updateAnonSession() { try { @@ -194,7 +204,9 @@ export class AnonymousUserEventManager { } }); if (response && response.status === 200) { - await setAnonUserId(userId); + if (anonUserIdSetter !== null) { + await anonUserIdSetter(userId); + } this.syncEvents(); } } @@ -213,19 +225,19 @@ export class AnonymousUserEventManager { delete event.eventType; switch (eventType) { case TRACK_EVENT: { - track(event); + this.track(event); break; } case TRACK_PURCHASE: { - trackPurchase(event); + this.trackPurchase(event); break; } case TRACK_UPDATE_CART: { - updateCart(event); + this.updateCart(event); break; } case UPDATE_USER: { - updateUser({ dataFields: event }); + this.updateUser({ dataFields: event }); break; } default: @@ -291,4 +303,65 @@ export class AnonymousUserEventManager { return ''; } } + + track = (payload: InAppTrackRequestParams) => { + return baseIterableRequest({ + method: 'POST', + url: ENDPOINTS.event_track.route, + data: payload, + validation: { + data: trackSchema + } + }); + }; + + updateCart = (payload: UpdateCartRequestParams) => { + return baseIterableRequest({ + method: 'POST', + url: ENDPOINTS.commerce_update_cart.route, + data: { + ...payload, + user: { + ...payload.user, + preferUserId: true + } + }, + validation: { + data: updateCartSchema + } + }); + }; + + trackPurchase = (payload: TrackPurchaseRequestParams) => { + return baseIterableRequest({ + method: 'POST', + url: ENDPOINTS.commerce_track_purchase.route, + data: { + ...payload, + user: { + ...payload.user, + preferUserId: true + } + }, + validation: { + data: trackPurchaseSchema + } + }); + }; + + updateUser = (payload: UpdateUserParams = {}) => { + if (payload.dataFields) { + return baseIterableRequest({ + method: 'POST', + url: ENDPOINTS.users_update.route, + data: { + ...payload, + preferUserId: true + }, + validation: { + data: updateUserSchema + } + }); + } + }; } diff --git a/src/authorization/authorization.ts b/src/authorization/authorization.ts index 185f82b7..b9963720 100644 --- a/src/authorization/authorization.ts +++ b/src/authorization/authorization.ts @@ -20,7 +20,10 @@ import { isEmail } from './utils'; import { AnonymousUserMerge } from 'src/anonymousUserTracking/anonymousUserMerge'; -import { AnonymousUserEventManager } from 'src/anonymousUserTracking/anonymousUserEventManager'; +import { + AnonymousUserEventManager, + registerAnonUserIdSetter +} from 'src/anonymousUserTracking/anonymousUserEventManager'; import { Options, config } from 'src/utils/config'; const MAX_TIMEOUT = ONE_DAY; @@ -43,6 +46,7 @@ let authIdentifier: null | string = null; let userInterceptor: number | null = null; let apiKey: null | string = null; let generateJWTGlobal: any = null; +const anonUserManager = new AnonymousUserEventManager(); export interface GenerateJWTPayload { email?: string; @@ -90,6 +94,8 @@ export const setAnonUserId = async (userId: string) => { localStorage.setItem(SHARED_PREF_ANON_USER_ID, userId); }; +registerAnonUserIdSetter(setAnonUserId); + const clearAnonymousUser = () => { localStorage.removeItem(SHARED_PREF_ANON_USER_ID); }; @@ -212,7 +218,6 @@ const initializeEmailUserAndSync = (email: string) => { const syncEvents = () => { if (config.getConfig('enableAnonTracking')) { - const anonUserManager = new AnonymousUserEventManager(); anonUserManager.syncEvents(); } }; @@ -393,7 +398,6 @@ export function initialize( const enableAnonymousTracking = () => { try { if (config.getConfig('enableAnonTracking')) { - const anonUserManager = new AnonymousUserEventManager(); anonUserManager.getAnonCriteria(); anonUserManager.updateAnonSession(); const anonymousUserId = getAnonUserId(); From 263bd466cd6a4e524536057aac3683a804ca13e9 Mon Sep 17 00:00:00 2001 From: Hardik Mashru Date: Tue, 18 Jun 2024 19:29:11 +0530 Subject: [PATCH 47/88] more fixes --- .../src/components/LoginFormWithoutJWT.tsx | 120 +++++++++++------- react-example/src/index.tsx | 2 +- react-example/src/indexJWT.tsx | 99 --------------- react-example/src/indexWithoutJWT.tsx | 21 ++- react-example/src/views/AUTTesting.tsx | 4 +- react-example/src/views/Home.tsx | 6 +- react-example/webpack.config.js | 2 +- src/authorization/authorization.ts | 26 +++- src/commerce/commerce.ts | 6 +- src/constants.ts | 2 +- src/events/events.ts | 4 +- src/users/users.ts | 4 +- 12 files changed, 127 insertions(+), 169 deletions(-) delete mode 100644 react-example/src/indexJWT.tsx diff --git a/react-example/src/components/LoginFormWithoutJWT.tsx b/react-example/src/components/LoginFormWithoutJWT.tsx index 738adb41..32625342 100644 --- a/react-example/src/components/LoginFormWithoutJWT.tsx +++ b/react-example/src/components/LoginFormWithoutJWT.tsx @@ -1,4 +1,4 @@ -import { FC, FormEvent, useState } from 'react'; +import { ChangeEvent, FC, FormEvent, useState } from 'react'; import styled from 'styled-components'; import _TextField from 'src/components/TextField'; @@ -26,15 +26,31 @@ const Form = styled.form` } `; +const StyledDiv = styled.div` + display: flex; + flex-direction: column; + align-items: flex-start; +`; + +const Error = styled.div` + color: red; +`; + interface Props { + setEmail: (email: string) => Promise; setUserId: (userId: string) => Promise; logout: () => void; } -export const LoginFormWithoutJWT: FC = ({ setUserId, logout }) => { - const [userId, updateUserId] = useState( - process.env.LOGIN_EMAIL || '' - ); +export const LoginFormWithoutJWT: FC = ({ + setEmail, + setUserId, + logout +}) => { + const [useEmail, setUseEmail] = useState(true); + const [user, updateUser] = useState(process.env.LOGIN_EMAIL || ''); + + const [error, setError] = useState(''); const [isEditingUser, setEditingUser] = useState(false); @@ -43,12 +59,14 @@ export const LoginFormWithoutJWT: FC = ({ setUserId, logout }) => { const handleSubmit = (e: FormEvent) => { e.preventDefault(); - setUserId(userId) + const setUser = useEmail ? setEmail : setUserId; + + setUser(user) .then(() => { setEditingUser(false); - setLoggedInUser({ type: 'user_update', data: userId }); + setLoggedInUser({ type: 'user_update', data: user }); }) - .catch(() => updateUserId('Something went wrong!')); + .catch(() => setError('Something went wrong!')); }; const handleLogout = () => { @@ -56,61 +74,73 @@ export const LoginFormWithoutJWT: FC = ({ setUserId, logout }) => { setLoggedInUser({ type: 'user_update', data: '' }); }; - const handleJwtRefresh = () => { - //refreshJwt(userId); - }; - const handleEditUser = () => { - updateUserId(loggedInUser); + updateUser(loggedInUser); setEditingUser(true); }; const handleCancelEditUser = () => { - updateUserId(''); + updateUser(''); setEditingUser(false); }; + const handleRadioChange = (e: ChangeEvent) => { + setUseEmail(e.target.value === 'email'); + }; + const first5 = loggedInUser.substring(0, 5); const last9 = loggedInUser.substring(loggedInUser.length - 9); return ( <> - {loggedInUser ? ( - isEditingUser ? ( -
+ {loggedInUser && !isEditingUser ? ( + <> + + + + ) : ( + + +
+ + +
+
+ + +
+ +
updateUserId(e.target.value)} - value={userId} + onChange={(e) => updateUser(e.target.value)} + value={user} placeholder="e.g. hello@gmail.com" - //type="email" required + data-qa-login-input /> - - + + {isEditingUser && ( + + )} - ) : ( - <> - - - - - ) - ) : ( -
- updateUserId(e.target.value)} - value={userId} - placeholder="e.g. hello@gmail.com" - //type="email" - required - data-qa-login-input - /> - - + {error && {error}} +
)} ); diff --git a/react-example/src/index.tsx b/react-example/src/index.tsx index f3f9231a..a78f64ca 100644 --- a/react-example/src/index.tsx +++ b/react-example/src/index.tsx @@ -93,7 +93,6 @@ const HomeLink = styled(Link)` } /> - } /> } /> } /> } /> @@ -104,6 +103,7 @@ const HomeLink = styled(Link)` path="/embedded-msgs-impression-tracker" element={} /> + } /> diff --git a/react-example/src/indexJWT.tsx b/react-example/src/indexJWT.tsx deleted file mode 100644 index 6ff31c39..00000000 --- a/react-example/src/indexJWT.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { initializeWithConfig, WithJWTParams } from '@iterable/web-sdk'; -import axios from 'axios'; -import ReactDOM from 'react-dom'; -import './styles/index.css'; - -import Home from 'src/views/Home'; -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 { BrowserRouter, Routes, Route } from 'react-router-dom'; -import Link from 'src/components/Link'; -import styled from 'styled-components'; -import LoginForm from 'src/components/LoginForm'; - -import { UserProvider } from 'src/context/Users'; - -const Wrapper = styled.div` - display: flex; - flex-flow: column; -`; - -const RouteWrapper = styled.div` - width: 90%; - margin: 0 auto; -`; - -const HeaderWrapper = styled.div` - display: flex; - flex-flow: row; - align-items: center; - justify-content: space-between; - margin: 1em; -`; - -const HomeLink = styled(Link)` - width: 100px; -`; - -((): void => { - const initializeParams: WithJWTParams = { - authToken: process.env.API_KEY || '', - configOptions: { - isEuIterableService: false, - dangerouslyAllowJsPopups: true, - enableAnonTracking: true - }, - generateJWT: ({ userID }) => { - return axios - .post( - process.env.JWT_GENERATOR || 'http://localhost:3000/generate', - { - exp_minutes: 2, - userId: userID, - jwt_secret: process.env.JWT_SECRET - }, - { - headers: { - 'Content-Type': 'application/json' - } - } - ) - .then((response) => { - return response.data?.token; - }); - } - }; - const { setUserID, logout } = initializeWithConfig(initializeParams); - - // eslint-disable-next-line react/no-deprecated - ReactDOM.render( - - - - - - Home - - - - - - } /> - } /> - } /> - } /> - } /> - - - - - , - document.getElementById('root') - ); -})(); diff --git a/react-example/src/indexWithoutJWT.tsx b/react-example/src/indexWithoutJWT.tsx index a9d797ef..ba3a71ee 100644 --- a/react-example/src/indexWithoutJWT.tsx +++ b/react-example/src/indexWithoutJWT.tsx @@ -13,6 +13,9 @@ import styled from 'styled-components'; import { UserProvider } from 'src/context/Users'; import LoginFormWithoutJWT from './components/LoginFormWithoutJWT'; import AUTTesting from './views/AUTTesting'; +import EmbeddedMsgs from './views/EmbeddedMsgs'; +import EmbeddedMessage from './views/Embedded'; +import EmbeddedMsgsImpressionTracker from './views/EmbeddedMsgsImpressionTracker'; const Wrapper = styled.div` display: flex; @@ -39,7 +42,6 @@ const HomeLink = styled(Link)` ((): void => { //localStorage.clear(); // Here we are testing it using NON-JWT based project. - //JWT based project works but we assumed that generateJWT function will take-in userId as param to generate JWT const initializeParams: WithoutJWTParams = { authToken: process.env.API_KEY || '', configOptions: { @@ -49,7 +51,8 @@ const HomeLink = styled(Link)` } }; - const { setUserID, logout } = initializeWithConfig(initializeParams); + const { setUserID, logout, setEmail } = + initializeWithConfig(initializeParams); // eslint-disable-next-line react/no-deprecated ReactDOM.render( @@ -60,16 +63,26 @@ const HomeLink = styled(Link)` Home - + } /> - } /> } /> } /> } /> } /> + } /> + } /> + } + /> + } /> diff --git a/react-example/src/views/AUTTesting.tsx b/react-example/src/views/AUTTesting.tsx index 66e51984..8135b584 100644 --- a/react-example/src/views/AUTTesting.tsx +++ b/react-example/src/views/AUTTesting.tsx @@ -15,9 +15,9 @@ import { TrackPurchaseRequestParams, updateUser, UpdateUserParams, - track + track, + InAppTrackRequestParams } from '@iterable/web-sdk'; -import EventsForm from 'src/components/EventsForm'; interface Props {} diff --git a/react-example/src/views/Home.tsx b/react-example/src/views/Home.tsx index 40d4384e..697aa7e6 100644 --- a/react-example/src/views/Home.tsx +++ b/react-example/src/views/Home.tsx @@ -19,9 +19,6 @@ export const Home: FC = () => { <>

Namespace Selection

- - AUT Testing - Commerce @@ -44,6 +41,9 @@ export const Home: FC = () => { Embedded msgs impressions tracker + + AUT Testing + ); diff --git a/react-example/webpack.config.js b/react-example/webpack.config.js index d4adb6b0..a087bca1 100644 --- a/react-example/webpack.config.js +++ b/react-example/webpack.config.js @@ -7,7 +7,7 @@ const webpack = require('webpack'); module.exports = { mode: 'development', - entry: './src/index.tsx', + entry: './src/indexWithoutJWT.tsx', output: { filename: 'index.js', path: path.resolve(__dirname, 'dist') diff --git a/src/authorization/authorization.ts b/src/authorization/authorization.ts index b9963720..e064b1fa 100644 --- a/src/authorization/authorization.ts +++ b/src/authorization/authorization.ts @@ -1,6 +1,5 @@ import axios from 'axios'; -import { baseAxiosRequest } from '../request'; -import { updateUser } from 'src/users/users'; +import { baseAxiosRequest, baseIterableRequest } from '../request'; import { clearMessages } from 'src/inapp/inapp'; import { IS_PRODUCTION, @@ -25,6 +24,8 @@ import { registerAnonUserIdSetter } from 'src/anonymousUserTracking/anonymousUserEventManager'; import { Options, config } from 'src/utils/config'; +import { IterableResponse } from 'src/types'; +import { updateUserSchema } from 'src/users/users.schema'; const MAX_TIMEOUT = ONE_DAY; /* @@ -100,6 +101,19 @@ const clearAnonymousUser = () => { localStorage.removeItem(SHARED_PREF_ANON_USER_ID); }; +const updateUser = () => { + return baseIterableRequest({ + method: 'POST', + url: ENDPOINTS.users_update.route, + data: { + preferUserId: true + }, + validation: { + data: updateUserSchema + } + }); +}; + const getAnonUserId = () => { if (config.getConfig('enableAnonTracking')) { const anonUser = localStorage.getItem(SHARED_PREF_ANON_USER_ID); @@ -465,7 +479,7 @@ export function initialize( }, setUserID: async (userId: string) => { clearMessages(); - const tryUser = (userId: any) => { + const tryUser = () => { let createUserAttempts = 0; return async function tryUserNTimes(): Promise { @@ -488,7 +502,7 @@ export function initialize( if (result) { initializeUserIdAndSync(userId); try { - return await tryUser(userId)(); + return await tryUser()(); } catch (e) { /* failed to create a new user. Just silently resolve */ return Promise.resolve(); @@ -805,7 +819,7 @@ export function initialize( setUserID: async (userId: string) => { clearMessages(); - const tryUser = (userID: any) => { + const tryUser = () => { let createUserAttempts = 0; return async function tryUserNTimes(): Promise { @@ -830,7 +844,7 @@ export function initialize( try { return doRequest({ userID: userId }) .then(async (token) => { - await tryUser(userId)(); + await tryUser()(); return token; }) .catch((e) => { diff --git a/src/commerce/commerce.ts b/src/commerce/commerce.ts index c754c2f2..3debd036 100644 --- a/src/commerce/commerce.ts +++ b/src/commerce/commerce.ts @@ -7,7 +7,7 @@ import { IterableResponse } from 'src/types'; import { updateCartSchema, trackPurchaseSchema } from './commerce.schema'; import { AnonymousUserEventManager } from 'src/anonymousUserTracking/anonymousUserEventManager'; import { canTrackAnonUser } from 'src/utils/commonFunctions'; -import { ANON_USER_ERROR, ENDPOINTS } from 'src/constants'; +import { INITIALIZE_ERROR, ENDPOINTS } from 'src/constants'; export const updateCart = (payload: UpdateCartRequestParams) => { /* a customer could potentially send these up if they're not using TypeScript */ @@ -18,7 +18,7 @@ export const updateCart = (payload: UpdateCartRequestParams) => { if (canTrackAnonUser()) { const anonymousUserEventManager = new AnonymousUserEventManager(); anonymousUserEventManager.trackAnonUpdateCart(payload); - return Promise.reject(ANON_USER_ERROR); + return Promise.reject(INITIALIZE_ERROR); } return baseIterableRequest({ method: 'POST', @@ -45,7 +45,7 @@ export const trackPurchase = (payload: TrackPurchaseRequestParams) => { if (canTrackAnonUser()) { const anonymousUserEventManager = new AnonymousUserEventManager(); anonymousUserEventManager.trackAnonPurchaseEvent(payload); - return Promise.reject(ANON_USER_ERROR); + return Promise.reject(INITIALIZE_ERROR); } return baseIterableRequest({ method: 'POST', diff --git a/src/constants.ts b/src/constants.ts index 680d03b9..8c695ed9 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -303,5 +303,5 @@ export const TRACK_UPDATE_CART = 'cartUpdate'; export const UPDATE_CART = 'updateCart'; export const MERGE_SUCCESSFULL = 'MERGE_SUCCESSFULL'; -export const ANON_USER_ERROR = +export const INITIALIZE_ERROR = 'Iterable SDK must be initialized with an API key and user email/userId before calling SDK methods'; diff --git a/src/events/events.ts b/src/events/events.ts index d4230b7c..7caf0ee9 100644 --- a/src/events/events.ts +++ b/src/events/events.ts @@ -4,7 +4,7 @@ import { AnonymousUserEventManager } from 'src/anonymousUserTracking/anonymousUs import { canTrackAnonUser } from 'src/utils/commonFunctions'; import { InAppTrackRequestParams } from './in-app/types'; import { trackSchema } from './events.schema'; -import { ANON_USER_ERROR, ENDPOINTS } from 'src/constants'; +import { INITIALIZE_ERROR, ENDPOINTS } from 'src/constants'; export const track = (payload: InAppTrackRequestParams) => { /* a customer could potentially send these up if they're not using TypeScript */ @@ -13,7 +13,7 @@ export const track = (payload: InAppTrackRequestParams) => { if (canTrackAnonUser()) { const anonymousUserEventManager = new AnonymousUserEventManager(); anonymousUserEventManager.trackAnonEvent(payload); - return Promise.reject(ANON_USER_ERROR); + return Promise.reject(INITIALIZE_ERROR); } return baseIterableRequest({ method: 'POST', diff --git a/src/users/users.ts b/src/users/users.ts index a471f1aa..54138af8 100644 --- a/src/users/users.ts +++ b/src/users/users.ts @@ -5,7 +5,7 @@ import { UpdateSubscriptionParams, UpdateUserParams } from './types'; import { updateSubscriptionsSchema, updateUserSchema } from './users.schema'; import { AnonymousUserEventManager } from '../anonymousUserTracking/anonymousUserEventManager'; import { canTrackAnonUser } from 'src/utils/commonFunctions'; -import { ANON_USER_ERROR, ENDPOINTS } from 'src/constants'; +import { INITIALIZE_ERROR, ENDPOINTS } from 'src/constants'; export const updateUserEmail = (newEmail: string) => { return baseIterableRequest({ @@ -30,7 +30,7 @@ export const updateUser = (payload: UpdateUserParams = {}) => { if (canTrackAnonUser()) { const anonymousUserEventManager = new AnonymousUserEventManager(); anonymousUserEventManager.trackAnonUpdateUser(payload); - return Promise.reject(ANON_USER_ERROR); + return Promise.reject(INITIALIZE_ERROR); } return baseIterableRequest({ method: 'POST', From 909f7b03cdc23e20af74bce1deea23d4664863ee Mon Sep 17 00:00:00 2001 From: Hardik Mashru Date: Tue, 18 Jun 2024 19:32:39 +0530 Subject: [PATCH 48/88] Update webpack.config.js --- react-example/webpack.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-example/webpack.config.js b/react-example/webpack.config.js index a087bca1..d4adb6b0 100644 --- a/react-example/webpack.config.js +++ b/react-example/webpack.config.js @@ -7,7 +7,7 @@ const webpack = require('webpack'); module.exports = { mode: 'development', - entry: './src/indexWithoutJWT.tsx', + entry: './src/index.tsx', output: { filename: 'index.js', path: path.resolve(__dirname, 'dist') From 9a8911d4625a4705a5e7d55eb75873f193aa86d3 Mon Sep 17 00:00:00 2001 From: Hardik Mashru Date: Tue, 18 Jun 2024 20:07:53 +0530 Subject: [PATCH 49/88] fixed some suggestions --- react-example/src/indexWithoutJWT.tsx | 1 - src/anonymousUserTracking/anonymousUserEventManager.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/react-example/src/indexWithoutJWT.tsx b/react-example/src/indexWithoutJWT.tsx index ba3a71ee..7bfeef74 100644 --- a/react-example/src/indexWithoutJWT.tsx +++ b/react-example/src/indexWithoutJWT.tsx @@ -40,7 +40,6 @@ const HomeLink = styled(Link)` `; ((): void => { - //localStorage.clear(); // Here we are testing it using NON-JWT based project. const initializeParams: WithoutJWTParams = { authToken: process.env.API_KEY || '', diff --git a/src/anonymousUserTracking/anonymousUserEventManager.ts b/src/anonymousUserTracking/anonymousUserEventManager.ts index e8ce3238..4d6f8324 100644 --- a/src/anonymousUserTracking/anonymousUserEventManager.ts +++ b/src/anonymousUserTracking/anonymousUserEventManager.ts @@ -203,7 +203,7 @@ export class AnonymousUserEventManager { this.getAnonCriteria(); } }); - if (response && response.status === 200) { + if (response?.status === 200) { if (anonUserIdSetter !== null) { await anonUserIdSetter(userId); } From db1c7aeb399cb76953f930abff3609a4928965bf Mon Sep 17 00:00:00 2001 From: hardikmashru Date: Wed, 19 Jun 2024 11:44:22 +0530 Subject: [PATCH 50/88] Fixed comment --- .../criteriaCompletionChecker.ts | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/src/anonymousUserTracking/criteriaCompletionChecker.ts b/src/anonymousUserTracking/criteriaCompletionChecker.ts index 4d554c60..3e2a6aaa 100644 --- a/src/anonymousUserTracking/criteriaCompletionChecker.ts +++ b/src/anonymousUserTracking/criteriaCompletionChecker.ts @@ -55,28 +55,23 @@ class CriteriaCompletionChecker { } private findMatchedCriteria(criteriaList: Criteria[]): string | null { - let criteriaId: string | null = null; const criteriaIdList = criteriaList.map((criteria) => criteria.criteriaId); const eventsToProcess = this.prepareEventsToProcess(criteriaIdList); - criteriaList.forEach((criteria) => { - if (!criteriaId && criteria.searchQuery && criteria.criteriaId) { - // Check for criteriaId - const searchQuery = criteria.searchQuery; - const currentCriteriaId = criteria.criteriaId; - - const result = this.evaluateTree( - searchQuery, + + // Use find to get the first matching criteria + const matchingCriteria = criteriaList.find((criteria) => { + if (criteria.searchQuery && criteria.criteriaId) { + return this.evaluateTree( + criteria.searchQuery, eventsToProcess, - currentCriteriaId + criteria.criteriaId ); - if (result) { - criteriaId = currentCriteriaId; - // Since we cannot break in forEach, we use criteriaId check to stop further iterations - } } + return false; }); - return criteriaId; + // Return the criteriaId of the matching criteria or null if none found + return matchingCriteria ? matchingCriteria.criteriaId : null; } private prepareEventsToProcess(criteriaIdList: string[]): any[] { From 0b1480eae0f7712b69d5082fa203f706e37339ab Mon Sep 17 00:00:00 2001 From: hardikmashru <150107929+hardikmashru@users.noreply.github.com> Date: Mon, 1 Jul 2024 18:07:45 +0530 Subject: [PATCH 51/88] Fixed single item matches code (#410) * Fixed single item matches code * Fixed comments * added more unit tests, bug fixes --------- Co-authored-by: hardikmashru --- react-example/src/views/AUTTesting.tsx | 7 +- .../criteriaCompletionChecker.test.ts | 329 +++++++++++++++++- .../criteriaCompletionChecker.ts | 152 ++++---- src/constants.ts | 3 + 4 files changed, 409 insertions(+), 82 deletions(-) diff --git a/react-example/src/views/AUTTesting.tsx b/react-example/src/views/AUTTesting.tsx index 8135b584..02151417 100644 --- a/react-example/src/views/AUTTesting.tsx +++ b/react-example/src/views/AUTTesting.tsx @@ -30,7 +30,7 @@ export const AUTTesting: FC = () => { ); const [cartItem, setCartItem] = useState( - '{"items":[{"name":"piano","id":"fdsafds","price":100,"quantity":2}]}' + '{"items":[{"name":"piano","id":"fdsafds","price":100,"quantity":2}, {"name":"piano2","id":"fdsafds2","price":100,"quantity":5}]}' ); const [purchaseItem, setPurchaseItem] = useState( @@ -59,7 +59,6 @@ export const AUTTesting: FC = () => { const handleParseJson = (isUpdateCartCalled: boolean) => { try { // Parse JSON and assert its type - // {"items":[{"name":"piano","id":"fdsafds","price":100,"quantity":2}]} if (isUpdateCartCalled) { const parsedObject = JSON.parse(cartItem) as UpdateCartRequestParams; return parsedObject; @@ -109,11 +108,11 @@ export const AUTTesting: FC = () => { const handleTrackPurchase = (e: FormEvent) => { e.preventDefault(); - const jsonObj = handleParseJson(false); + const jsonObj: TrackPurchaseRequestParams = handleParseJson(false); if (jsonObj) { setTrackingPurchase(true); try { - trackPurchase({ ...jsonObj, total: 20 }) + trackPurchase(jsonObj) .then((response: any) => { setTrackingPurchase(false); setTrackPurchaseResponse(JSON.stringify(response.data)); diff --git a/src/anonymousUserTracking/criteriaCompletionChecker.test.ts b/src/anonymousUserTracking/criteriaCompletionChecker.test.ts index ff18bb84..70b5d460 100644 --- a/src/anonymousUserTracking/criteriaCompletionChecker.test.ts +++ b/src/anonymousUserTracking/criteriaCompletionChecker.test.ts @@ -31,14 +31,14 @@ describe('CriteriaCompletionChecker', () => { expect(result).toBeNull(); }); - it('should return criteriaId if criteriaData condition is matched', () => { + it('should return criteriaId if customEvent is matched', () => { (localStorage.getItem as jest.Mock).mockImplementation((key) => { if (key === SHARED_PREFS_EVENT_LIST_KEY) { return JSON.stringify([ { eventName: 'testEvent', createdAt: 1708494757530, - dataFields: undefined, + dataFields: { 'browserVisit.website.domain': 'google.com' }, createNewFields: true, eventType: 'customEvent' } @@ -80,6 +80,13 @@ describe('CriteriaCompletionChecker', () => { comparatorType: 'Equals', value: 'testEvent', fieldType: 'string' + }, + { + dataType: 'customEvent', + field: 'browserVisit.website.domain', + comparatorType: 'Equals', + value: 'google.com', + fieldType: 'string' } ] } @@ -95,14 +102,14 @@ describe('CriteriaCompletionChecker', () => { expect(result).toEqual('6'); }); - it('should return null if criteriaData condition is matched', () => { + it('should return criteriaId if customEvent is matched when minMatch present', () => { (localStorage.getItem as jest.Mock).mockImplementation((key) => { if (key === SHARED_PREFS_EVENT_LIST_KEY) { return JSON.stringify([ { - eventName: 'Event', + eventName: 'testEvent', createdAt: 1708494757530, - dataFields: undefined, + dataFields: { 'browserVisit.website.domain': 'google.com' }, createNewFields: true, eventType: 'customEvent' } @@ -134,7 +141,9 @@ describe('CriteriaCompletionChecker', () => { combinator: 'Or', searchQueries: [ { - dataType: 'purchase', + dataType: 'customEvent', + minMatch: 1, + maxMatch: 2, searchCombo: { combinator: 'And', searchQueries: [ @@ -144,6 +153,243 @@ describe('CriteriaCompletionChecker', () => { comparatorType: 'Equals', value: 'testEvent', fieldType: 'string' + }, + { + dataType: 'customEvent', + field: 'browserVisit.website.domain', + comparatorType: 'Equals', + value: 'google.com', + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual('6'); + }); + + it('should return criteriaId if purchase event is matched', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + createdAt: 1708494757530, + items: [ + { name: 'keyboard', id: 'fdsafds', price: 10, quantity: 2 } + ], + total: 10, + eventType: 'purchase' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '6', + name: 'EventCriteria', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'Or', + searchQueries: [ + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + field: 'shoppingCartItems.name', + comparatorType: 'Equals', + value: 'keyboard', + fieldType: 'string' + }, + { + dataType: 'purchase', + field: 'shoppingCartItems.price', + comparatorType: 'GreaterThanOrEqualTo', + value: '10', + fieldType: 'double' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual('6'); + }); + + it('should return null if updateCart event with all props in item is matched', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + createdAt: 1708494757530, + items: [ + { name: 'keyboard', id: 'fdsafds', price: 10, quantity: 2 }, + { name: 'Cofee', id: 'fdsafds', price: 10, quantity: 2 } + ], + eventType: 'cartUpdate' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '6', + name: 'EventCriteria', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'Or', + searchQueries: [ + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'eventName', + comparatorType: 'Equals', + value: 'updateCart', + fieldType: 'string' + }, + { + dataType: 'customEvent', + field: 'updateCart.updatedShoppingCartItems.name', + comparatorType: 'Equals', + value: 'keyboard', + fieldType: 'string' + }, + { + dataType: 'customEvent', + field: 'updateCart.updatedShoppingCartItems.price', + comparatorType: 'GreaterThanOrEqualTo', + value: '10', + fieldType: 'double' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual('6'); + }); + + it('should return null if updateCart event with items is NOT matched', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + createdAt: 1708494757530, + items: [ + { name: 'keyboard', id: 'fdsafds', price: 9, quantity: 2 }, + { name: 'Cofee', id: 'fdsafds', price: 10, quantity: 2 } + ], + eventType: 'cartUpdate' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '6', + name: 'EventCriteria', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'Or', + searchQueries: [ + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'eventName', + comparatorType: 'Equals', + value: 'updateCart', + fieldType: 'string' + }, + { + dataType: 'customEvent', + field: 'updateCart.updatedShoppingCartItems.name', + comparatorType: 'Equals', + value: 'keyboard', + fieldType: 'string' + }, + { + dataType: 'customEvent', + field: 'updateCart.updatedShoppingCartItems.price', + comparatorType: 'GreaterThanOrEqualTo', + value: '10', + fieldType: 'double' } ] } @@ -159,6 +405,77 @@ describe('CriteriaCompletionChecker', () => { expect(result).toBeNull(); }); + it('should return criteriaId if updateCart event is matched', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + createdAt: 1708494757530, + items: [ + { name: 'keyboard', id: 'fdsafds', price: 10, quantity: 2 } + ], + eventType: 'cartUpdate' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '6', + name: 'EventCriteria', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'Or', + searchQueries: [ + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'eventName', + comparatorType: 'Equals', + value: 'updateCart', + fieldType: 'string' + }, + { + dataType: 'customEvent', + field: 'updateCart.updatedShoppingCartItems.price', + comparatorType: 'GreaterThanOrEqualTo', + value: '10', + fieldType: 'double' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual('6'); + }); + it('should return criteriaId if criteriaData condition with numeric is matched', () => { (localStorage.getItem as jest.Mock).mockImplementation((key) => { if (key === SHARED_PREFS_EVENT_LIST_KEY) { diff --git a/src/anonymousUserTracking/criteriaCompletionChecker.ts b/src/anonymousUserTracking/criteriaCompletionChecker.ts index 3e2a6aaa..5eed0f4c 100644 --- a/src/anonymousUserTracking/criteriaCompletionChecker.ts +++ b/src/anonymousUserTracking/criteriaCompletionChecker.ts @@ -7,6 +7,8 @@ import { UPDATE_CART, UPDATE_USER, KEY_EVENT_NAME, + UPDATECART_ITEM_PREFIX, + PURCHASE_ITEM_PREFIX, SHARED_PREFS_EVENT_LIST_KEY, SHARED_PREF_MATCHED_CRITERIAS } from '../constants'; @@ -110,7 +112,7 @@ class CriteriaCompletionChecker { items = items.map((item: any) => { const updatItem: any = {}; Object.keys(item).forEach((key) => { - updatItem[`shoppingCartItems.${key}`] = item[key]; + updatItem[`${PURCHASE_ITEM_PREFIX}${key}`] = item[key]; }); return updatItem; }); @@ -143,8 +145,7 @@ class CriteriaCompletionChecker { items = items.map((item: any) => { const updatItem: any = {}; Object.keys(item).forEach((key) => { - updatItem[`updateCart.updatedShoppingCartItems.${key}`] = - item[key]; + updatItem[`${UPDATECART_ITEM_PREFIX}${key}`] = item[key]; }); return updatItem; }); @@ -155,6 +156,7 @@ class CriteriaCompletionChecker { Object.keys(localEventData.dataFields).forEach((key) => { updatedItem[key] = localEventData.dataFields[key]; }); + delete localEventData.dataFields; } Object.keys(localEventData).forEach((key) => { if (key !== KEY_ITEMS && key !== 'dataFields') { @@ -198,6 +200,7 @@ class CriteriaCompletionChecker { Object.keys(localEventData.dataFields).forEach((key) => { updatedItem[key] = localEventData.dataFields[key]; }); + delete localEventData.dataFields; } nonPurchaseEvents.push(updatedItem); } @@ -234,8 +237,7 @@ class CriteriaCompletionChecker { return false; } } else if (node.searchCombo) { - const vv = this.evaluateSearchQueries(node, localEventData, criteriaId); - return vv; + return this.evaluateSearchQueries(node, localEventData, criteriaId); } } catch (e) { this.handleException(e); @@ -258,13 +260,6 @@ class CriteriaCompletionChecker { const searchCombo = node.searchCombo; const searchQueries = searchCombo?.searchQueries || []; const combinator = searchCombo?.combinator || ''; - // matchedCriterias format - // [ - // { - // criteriaId: '6', - // nodeCombo: [{searchCombo: {}, count: 1}], - // }, - // ]; const matchedCriteriasFromLocalStorage = localStorage.getItem( SHARED_PREF_MATCHED_CRITERIAS ); @@ -290,7 +285,6 @@ class CriteriaCompletionChecker { nodeCombo: { searchCombo: object; count: number }[]; }) => item.criteriaId === criteriaId ); - if (this.evaluateEvent(eventData, searchQueries, combinator)) { if (Object.prototype.hasOwnProperty.call(node, 'minMatch')) { const matchedNode = @@ -330,11 +324,7 @@ class CriteriaCompletionChecker { JSON.stringify(this.localStoredEventList) ); - if (matchedNode[0].count === node.minMatch) { - return true; - } else { - return false; - } + return matchedNode[0].count === node.minMatch; } else { const tempMatchedCriterias = matchedCriterias || []; tempMatchedCriterias.push({ @@ -353,7 +343,7 @@ class CriteriaCompletionChecker { SHARED_PREF_MATCHED_CRITERIAS, JSON.stringify(tempMatchedCriterias) ); - return false; + return node.minMatch === 1; } } else { return true; @@ -371,73 +361,91 @@ class CriteriaCompletionChecker { combinator: string ): boolean { if (combinator === 'And') { - for (let i = 0; i < searchQueries.length; i++) { - if (!this.evaluateFieldLogic(searchQueries[i], localEvent)) { - return false; - } + if (!this.evaluateFieldLogic(searchQueries, localEvent)) { + return false; } return true; } else if (combinator === 'Or') { - for (let i = 0; i < searchQueries.length; i++) { - if (this.evaluateFieldLogic(searchQueries[i], localEvent)) { - return true; - } + if (this.evaluateFieldLogic(searchQueries, localEvent)) { + return true; } return false; } return false; } - private evaluateFieldLogic(node: any, eventData: any): boolean { - const field = node.field; - const comparatorType = node.comparatorType ? node.comparatorType : ''; + private doesItemCriteriaExists(searchQueries: any[]): boolean { + const foundIndex = searchQueries.findIndex( + (item) => + item.field.startsWith(UPDATECART_ITEM_PREFIX) || + item.field.startsWith(PURCHASE_ITEM_PREFIX) + ); + return foundIndex !== -1; + } + + private evaluateFieldLogic(searchQueries: any[], eventData: any): boolean { const localDataKeys = Object.keys(eventData); - let shouldReturn = false; - for (let j = 0; j < localDataKeys.length; j++) { - const key = localDataKeys[j]; - if (key === KEY_ITEMS) { - // scenario of items inside purchase and updateCart Events - const items = eventData[key]; - items.forEach((item: any) => { - const keys = Object.keys(item); - keys.forEach((keyItem: any) => { - if (field === keyItem) { - const matchedCountObj = item[keyItem]; - if ( - this.evaluateComparison( - comparatorType, - matchedCountObj, - node.value ? node.value : '' - ) - ) { - shouldReturn = true; - return; - } - } - }); - if (shouldReturn) return; // Exit outer forEach loop - }); - if (shouldReturn) break; // Exit main for loop - } else { - if (field === key) { - const matchedCountObj = eventData[key]; - if ( - this.evaluateComparison( - comparatorType, - matchedCountObj, - node.value ? node.value : '' - ) - ) { - return true; - } - } + let itemMatchedResult = false; + if (localDataKeys.includes(KEY_ITEMS)) { + // scenario of items inside purchase and updateCart Events + const items = eventData[KEY_ITEMS]; + const result = items.some((item: any) => { + return this.doesItemMatchQueries(item, searchQueries); + }); + if (!result && this.doesItemCriteriaExists(searchQueries)) { + // items criteria existed and it did not match + return result; } + itemMatchedResult = result; + } + const filteredLocalDataKeys = localDataKeys.filter( + (item: any) => item !== KEY_ITEMS + ); + + if (filteredLocalDataKeys.length === 0) { + return itemMatchedResult; } - if (shouldReturn) { - return true; + const filteredSearchQueries = searchQueries.filter( + (searchQuery) => + !searchQuery.field.startsWith(UPDATECART_ITEM_PREFIX) && + !searchQuery.field.startsWith(PURCHASE_ITEM_PREFIX) + ); + const matchResult = filteredSearchQueries.every((query: any) => { + const field = query.field; + const eventKeyItems = filteredLocalDataKeys.filter( + (keyItem) => keyItem === field + ); + if (eventKeyItems.length) { + return this.evaluateComparison( + query.comparatorType, + eventData[field], + query.value ? query.value : '' + ); + } + return false; + }); + return matchResult; + } + + private doesItemMatchQueries(item: any, searchQueries: any[]): boolean { + const filteredSearchQueries = searchQueries.filter((searchQuery) => + Object.keys(item).includes(searchQuery.field) + ); + if (filteredSearchQueries.length === 0) { + return false; } - return false; + return filteredSearchQueries.every((query: any) => { + const field = query.field; + if (Object.prototype.hasOwnProperty.call(item, field)) { + return this.evaluateComparison( + query.comparatorType, + item[field], + query.value ? query.value : '' + ); + } + return false; + }); } private evaluateComparison( diff --git a/src/constants.ts b/src/constants.ts index 8c695ed9..07d2c234 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -302,6 +302,9 @@ export const UPDATE_USER = 'user'; export const TRACK_UPDATE_CART = 'cartUpdate'; export const UPDATE_CART = 'updateCart'; +export const UPDATECART_ITEM_PREFIX = 'updateCart.updatedShoppingCartItems.'; +export const PURCHASE_ITEM_PREFIX = 'shoppingCartItems.'; + export const MERGE_SUCCESSFULL = 'MERGE_SUCCESSFULL'; export const INITIALIZE_ERROR = 'Iterable SDK must be initialized with an API key and user email/userId before calling SDK methods'; From 6d50998c885deb9c9eb668696ea70d77d15d3adc Mon Sep 17 00:00:00 2001 From: hardikmashru <150107929+hardikmashru@users.noreply.github.com> Date: Fri, 5 Jul 2024 01:05:34 +0530 Subject: [PATCH 52/88] Add merge param (#414) * Fixed single item matches code * Fixed comments * Add merge parameter to the setEmail/setUserId function * added unit tests for merge and did some improvements * Fixed comments --------- Co-authored-by: hardikmashru --- .../src/components/LoginFormWithoutJWT.tsx | 4 +- react-example/src/index.tsx | 5 +- .../anonymousUserEventManager.ts | 15 +- .../anonymousUserMerge.ts | 25 +- .../criteriaCompletionChecker.ts | 12 +- .../anonymousUserEventManager.test.ts | 14 +- .../criteriaCompletionChecker.test.ts | 4 +- .../tests/userMergeScenarios.test.ts | 713 ++++++++++++++++++ src/authorization/authorization.ts | 90 ++- src/constants.ts | 6 +- src/events/events.ts | 2 +- 11 files changed, 817 insertions(+), 73 deletions(-) rename src/anonymousUserTracking/{ => tests}/anonymousUserEventManager.test.ts (97%) rename src/anonymousUserTracking/{ => tests}/criteriaCompletionChecker.test.ts (99%) create mode 100644 src/anonymousUserTracking/tests/userMergeScenarios.test.ts diff --git a/react-example/src/components/LoginFormWithoutJWT.tsx b/react-example/src/components/LoginFormWithoutJWT.tsx index 32625342..c9ed640c 100644 --- a/react-example/src/components/LoginFormWithoutJWT.tsx +++ b/react-example/src/components/LoginFormWithoutJWT.tsx @@ -38,7 +38,7 @@ const Error = styled.div` interface Props { setEmail: (email: string) => Promise; - setUserId: (userId: string) => Promise; + setUserId: (userId: string, merge?: boolean) => Promise; logout: () => void; } @@ -61,7 +61,7 @@ export const LoginFormWithoutJWT: FC = ({ const setUser = useEmail ? setEmail : setUserId; - setUser(user) + setUser(user, true) .then(() => { setEditingUser(false); setLoggedInUser({ type: 'user_update', data: user }); diff --git a/react-example/src/index.tsx b/react-example/src/index.tsx index a78f64ca..5f1c1575 100644 --- a/react-example/src/index.tsx +++ b/react-example/src/index.tsx @@ -47,7 +47,8 @@ const HomeLink = styled(Link)` authToken: process.env.API_KEY || '', configOptions: { isEuIterableService: false, - dangerouslyAllowJsPopups: true + dangerouslyAllowJsPopups: true, + enableAnonTracking: true }, generateJWT: ({ email, userID }) => { return axios @@ -56,7 +57,7 @@ const HomeLink = styled(Link)` { exp_minutes: 2, email, - user_id: userID, + userId: userID, jwt_secret: process.env.JWT_SECRET }, { diff --git a/src/anonymousUserTracking/anonymousUserEventManager.ts b/src/anonymousUserTracking/anonymousUserEventManager.ts index 4d6f8324..81ed2a01 100644 --- a/src/anonymousUserTracking/anonymousUserEventManager.ts +++ b/src/anonymousUserTracking/anonymousUserEventManager.ts @@ -149,7 +149,7 @@ export class AnonymousUserEventManager { this.storeEventListToLocalStorage(newDataObject, false); } - private async checkCriteriaCompletion() { + private checkCriteriaCompletion(): string | null { const criteriaData = localStorage.getItem(SHARED_PREFS_CRITERIA); const localStoredEventList = localStorage.getItem( SHARED_PREFS_EVENT_LIST_KEY @@ -243,14 +243,17 @@ export class AnonymousUserEventManager { default: break; } - - localStorage.removeItem(SHARED_PREFS_ANON_SESSIONS); - localStorage.removeItem(SHARED_PREFS_EVENT_LIST_KEY); - localStorage.removeItem(SHARED_PREF_MATCHED_CRITERIAS); + this.removeAnonSessionCriteriaData(); }); } } + removeAnonSessionCriteriaData() { + localStorage.removeItem(SHARED_PREFS_ANON_SESSIONS); + localStorage.removeItem(SHARED_PREFS_EVENT_LIST_KEY); + localStorage.removeItem(SHARED_PREF_MATCHED_CRITERIAS); + } + private async storeEventListToLocalStorage( newDataObject: Record, shouldOverWrite: boolean @@ -285,7 +288,7 @@ export class AnonymousUserEventManager { SHARED_PREFS_EVENT_LIST_KEY, JSON.stringify(previousDataArray) ); - const criteriaId = await this.checkCriteriaCompletion(); + const criteriaId = this.checkCriteriaCompletion(); if (criteriaId !== null) { this.createKnownUser(criteriaId); } diff --git a/src/anonymousUserTracking/anonymousUserMerge.ts b/src/anonymousUserTracking/anonymousUserMerge.ts index ffe4e90f..98d451ff 100644 --- a/src/anonymousUserTracking/anonymousUserMerge.ts +++ b/src/anonymousUserTracking/anonymousUserMerge.ts @@ -1,8 +1,4 @@ -import { - SHARED_PREF_ANON_USER_ID, - ENDPOINT_MERGE_USER, - MERGE_SUCCESSFULL -} from 'src/constants'; +import { ENDPOINT_MERGE_USER } from 'src/constants'; import { baseIterableRequest } from '../request'; import { IterableResponse } from '../types'; @@ -14,19 +10,22 @@ export type MergeApiParams = { }; export class AnonymousUserMerge { - mergeUser(userIdOrEmail: string, isEmail: boolean): Promise { - const sourceUserId = localStorage.getItem(SHARED_PREF_ANON_USER_ID); - + mergeUser( + sourceUserId: string | null, + sourceEmail: string | null, + destinationUserId: string | null, + destinationEmail: string | null + ): Promise { const mergeApiParams: MergeApiParams = { sourceUserId: sourceUserId, - sourceEmail: null, - destinationUserId: isEmail ? null : userIdOrEmail, - destinationEmail: isEmail ? userIdOrEmail : null + sourceEmail: sourceEmail, + destinationUserId: destinationUserId, + destinationEmail: destinationEmail }; return this.callMergeApi(mergeApiParams); } - private callMergeApi(data: MergeApiParams): Promise { + private callMergeApi(data: MergeApiParams): Promise { return new Promise((resolve, reject) => { baseIterableRequest({ method: 'POST', @@ -35,7 +34,7 @@ export class AnonymousUserMerge { }) .then((response) => { if (response.status === 200) { - resolve(MERGE_SUCCESSFULL); + resolve(); } else { reject(new Error(`merge error: ${response.status}`)); // Reject if status is not 200 } diff --git a/src/anonymousUserTracking/criteriaCompletionChecker.ts b/src/anonymousUserTracking/criteriaCompletionChecker.ts index 5eed0f4c..4c8d7c31 100644 --- a/src/anonymousUserTracking/criteriaCompletionChecker.ts +++ b/src/anonymousUserTracking/criteriaCompletionChecker.ts @@ -360,16 +360,8 @@ class CriteriaCompletionChecker { searchQueries: any, combinator: string ): boolean { - if (combinator === 'And') { - if (!this.evaluateFieldLogic(searchQueries, localEvent)) { - return false; - } - return true; - } else if (combinator === 'Or') { - if (this.evaluateFieldLogic(searchQueries, localEvent)) { - return true; - } - return false; + if (combinator === 'And' || combinator === 'Or') { + return this.evaluateFieldLogic(searchQueries, localEvent); } return false; } diff --git a/src/anonymousUserTracking/anonymousUserEventManager.test.ts b/src/anonymousUserTracking/tests/anonymousUserEventManager.test.ts similarity index 97% rename from src/anonymousUserTracking/anonymousUserEventManager.test.ts rename to src/anonymousUserTracking/tests/anonymousUserEventManager.test.ts index 8980d25d..caac0570 100644 --- a/src/anonymousUserTracking/anonymousUserEventManager.test.ts +++ b/src/anonymousUserTracking/tests/anonymousUserEventManager.test.ts @@ -1,12 +1,12 @@ -import { AnonymousUserEventManager } from './anonymousUserEventManager'; -import { baseIterableRequest } from '../request'; +import { AnonymousUserEventManager } from '../anonymousUserEventManager'; +import { baseIterableRequest } from '../../request'; import { SHARED_PREFS_ANON_SESSIONS, SHARED_PREFS_EVENT_LIST_KEY, SHARED_PREFS_CRITERIA -} from '../constants'; -import { UpdateUserParams } from '../users'; -import { TrackPurchaseRequestParams } from '../commerce'; +} from '../../constants'; +import { UpdateUserParams } from '../../users'; +import { TrackPurchaseRequestParams } from '../../commerce'; const localStorageMock = { getItem: jest.fn(), @@ -14,13 +14,13 @@ const localStorageMock = { removeItem: jest.fn() }; -jest.mock('./criteriaCompletionChecker', () => { +jest.mock('../criteriaCompletionChecker', () => { return jest.fn().mockImplementation(() => ({ getMatchedCriteria: jest.fn() })); }); -jest.mock('../request', () => ({ +jest.mock('../../request', () => ({ baseIterableRequest: jest.fn() })); diff --git a/src/anonymousUserTracking/criteriaCompletionChecker.test.ts b/src/anonymousUserTracking/tests/criteriaCompletionChecker.test.ts similarity index 99% rename from src/anonymousUserTracking/criteriaCompletionChecker.test.ts rename to src/anonymousUserTracking/tests/criteriaCompletionChecker.test.ts index 70b5d460..f8e866ff 100644 --- a/src/anonymousUserTracking/criteriaCompletionChecker.test.ts +++ b/src/anonymousUserTracking/tests/criteriaCompletionChecker.test.ts @@ -1,5 +1,5 @@ -import { SHARED_PREFS_EVENT_LIST_KEY } from '../constants'; -import CriteriaCompletionChecker from './criteriaCompletionChecker'; +import { SHARED_PREFS_EVENT_LIST_KEY } from '../../constants'; +import CriteriaCompletionChecker from '../criteriaCompletionChecker'; const localStorageMock = { getItem: jest.fn(), diff --git a/src/anonymousUserTracking/tests/userMergeScenarios.test.ts b/src/anonymousUserTracking/tests/userMergeScenarios.test.ts new file mode 100644 index 00000000..19b733a1 --- /dev/null +++ b/src/anonymousUserTracking/tests/userMergeScenarios.test.ts @@ -0,0 +1,713 @@ +import MockAdapter from 'axios-mock-adapter'; +import { initializeWithConfig } from '../../authorization'; +import { + SHARED_PREFS_EVENT_LIST_KEY, + SHARED_PREFS_CRITERIA, + GETMESSAGES_PATH, + ENDPOINT_TRACK_ANON_SESSION, + GET_CRITERIA_PATH, + SHARED_PREFS_ANON_SESSIONS, + ENDPOINT_MERGE_USER, + SHARED_PREF_ANON_USER_ID +} from '../../constants'; +import { track } from '../../events'; +import { getInAppMessages } from '../../inapp'; +import { baseAxiosRequest } from '../../request'; +jest.setTimeout(20000); // Set the timeout to 10 seconds + +const mockCriteria = { + count: 1, + criterias: [ + { + criteriaId: '6', + name: 'EventCriteria', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'Or', + searchQueries: [ + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'eventName', + comparatorType: 'Equals', + value: 'testEvent', + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] +}; + +const localStorageMock = { + getItem: jest.fn(), + setItem: jest.fn(), + removeItem: jest.fn() +}; + +const eventData = { + eventName: 'testEvent123', + dataFields: undefined, + createNewFields: true, + eventType: 'customEvent' +}; + +const eventDataMatched = { + eventName: 'testEvent', + dataFields: undefined, + createNewFields: true, + eventType: 'customEvent' +}; + +const initialAnonSessionInfo = { + itbl_anon_sessions: { + number_of_sessions: 1, + first_session: 123456789, + last_session: expect.any(Number) + } +}; + +declare global { + function uuidv4(): string; + function getEmail(): string; + function getUserID(): string; + function setUserID(): string; +} +const mockRequest = new MockAdapter(baseAxiosRequest); +//const mockOnPostSpy = jest.spyOn(mockRequest, 'onPost'); + +describe('UserMergeScenariosTests', () => { + beforeAll(() => { + (global as any).localStorage = localStorageMock; + global.window = Object.create({ location: { hostname: 'google.com' } }); + mockRequest.onGet(GETMESSAGES_PATH).reply(200, { + data: 'something' + }); + mockRequest.onPost('/events/track').reply(200, {}); + mockRequest.onPost(ENDPOINT_MERGE_USER).reply(200, {}); + mockRequest.onGet(GET_CRITERIA_PATH).reply(200, {}); + mockRequest.onPost(ENDPOINT_TRACK_ANON_SESSION).reply(200, {}); + }); + + beforeEach(() => { + mockRequest.reset(); + mockRequest.resetHistory(); + mockRequest.onGet(GETMESSAGES_PATH).reply(200, { + data: 'something' + }); + mockRequest.onPost('/events/track').reply(200, {}); + mockRequest.onPost(ENDPOINT_MERGE_USER).reply(200, {}); + mockRequest.onGet(GET_CRITERIA_PATH).reply(200, {}); + mockRequest.onPost(ENDPOINT_TRACK_ANON_SESSION).reply(200, {}); + jest.resetAllMocks(); + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([eventData]); + } else if (key === SHARED_PREFS_CRITERIA) { + return JSON.stringify(mockCriteria); + } else if (key === SHARED_PREFS_ANON_SESSIONS) { + return JSON.stringify(initialAnonSessionInfo); + } + return null; + }); + jest.useFakeTimers(); + }); + + describe('UserMergeScenariosTests with setUserID', () => { + it('criteria not met with merge false with setUserId', async () => { + const { setUserID, logout } = initializeWithConfig({ + authToken: '123', + configOptions: { enableAnonTracking: true } + }); + logout(); // logout to remove logged in users before this test + try { + await track({ eventName: 'testEvent123' }); + } catch (e) { + console.log(''); + } + expect(localStorage.setItem).toHaveBeenCalledWith( + SHARED_PREFS_EVENT_LIST_KEY, + expect.any(String) + ); + await setUserID('testuser123', false); + const response = await getInAppMessages({ + count: 10, + packageName: 'my-lil-website' + }); + expect(response.config.params.userId).toBe('testuser123'); + const removeItemCalls = localStorageMock.removeItem.mock.calls.filter( + (call) => call[0] === SHARED_PREFS_EVENT_LIST_KEY + ); + // count 1 means it did not remove item and so syncEvents was NOT called + // because removeItem gets called one time for the key in case of logout + expect(removeItemCalls.length).toBe(1); + const mergePostRequestData = mockRequest.history.post.find( + (req) => req.url === ENDPOINT_MERGE_USER + ); + expect(mergePostRequestData).toBeUndefined(); // ensure that merge API Do NOT get called + }); + + it('criteria not met with merge true with setUserId', async () => { + const { setUserID, logout } = initializeWithConfig({ + authToken: '123', + configOptions: { enableAnonTracking: true } + }); + logout(); // logout to remove logged in users before this test + try { + await track({ ...eventData }); + } catch (e) { + console.log(''); + } + expect(localStorage.setItem).toHaveBeenCalledWith( + SHARED_PREFS_EVENT_LIST_KEY, + expect.any(String) + ); + await setUserID('testuser123', true); + const response = await getInAppMessages({ + count: 10, + packageName: 'my-lil-website' + }); + expect(response.config.params.userId).toBe('testuser123'); + const removeItemCalls = localStorageMock.removeItem.mock.calls.filter( + (call) => call[0] === SHARED_PREFS_EVENT_LIST_KEY + ); + // count 2 means it removed items and so syncEvents was called + // because removeItem gets called one time for the key in case of logout and 2nd time on syncevents + expect(removeItemCalls.length).toBe(2); + const mergePostRequestData = mockRequest.history.post.find( + (req) => req.url === ENDPOINT_MERGE_USER + ); + expect(mergePostRequestData).toBeUndefined(); // ensure that merge API Do NOT get called + }); + + it('criteria not met with merge default value with setUserId', async () => { + const { setUserID, logout } = initializeWithConfig({ + authToken: '123', + configOptions: { enableAnonTracking: true } + }); + logout(); // logout to remove logged in users before this test + try { + await track({ eventName: 'testEvent123' }); + } catch (e) { + console.log(''); + } + expect(localStorage.setItem).toHaveBeenCalledWith( + SHARED_PREFS_EVENT_LIST_KEY, + expect.any(String) + ); + await setUserID('testuser123'); + const response = await getInAppMessages({ + count: 10, + packageName: 'my-lil-website' + }); + expect(response.config.params.userId).toBe('testuser123'); + const removeItemCalls = localStorageMock.removeItem.mock.calls.filter( + (call) => call[0] === SHARED_PREFS_EVENT_LIST_KEY + ); + // count 2 means it removed items and so syncEvents was called + // because removeItem gets called one time for the key in case of logout and 2nd time on syncevents + expect(removeItemCalls.length).toBe(2); + const mergePostRequestData = mockRequest.history.post.find( + (req) => req.url === ENDPOINT_MERGE_USER + ); + expect(mergePostRequestData).toBeUndefined(); // ensure that merge API Do NOT get called + }); + + it('criteria is met with merge false with setUserId', async () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([eventDataMatched]); + } else if (key === SHARED_PREFS_CRITERIA) { + return JSON.stringify(mockCriteria); + } else if (key === SHARED_PREFS_ANON_SESSIONS) { + return JSON.stringify(initialAnonSessionInfo); + } + return null; + }); + const { setUserID, logout } = initializeWithConfig({ + authToken: '123', + configOptions: { enableAnonTracking: true } + }); + logout(); // logout to remove logged in users before this test + try { + await track({ eventName: 'testEvent' }); + } catch (e) { + console.log(''); + } + await setUserID('testuser123', false); + expect(localStorageMock.removeItem).toHaveBeenCalledWith( + SHARED_PREF_ANON_USER_ID + ); + const response = await getInAppMessages({ + count: 10, + packageName: 'my-lil-website' + }); + expect(response.config.params.userId).toBe('testuser123'); + const mergePostRequestData = mockRequest.history.post.find( + (req) => req.url === ENDPOINT_MERGE_USER + ); + expect(mergePostRequestData).toBeUndefined(); // ensure that merge API Do NOT get called + }); + + it('criteria is met with merge true with setUserId', async () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([eventDataMatched]); + } else if (key === SHARED_PREFS_CRITERIA) { + return JSON.stringify(mockCriteria); + } else if (key === SHARED_PREFS_ANON_SESSIONS) { + return JSON.stringify(initialAnonSessionInfo); + } + return null; + }); + const { setUserID, logout } = initializeWithConfig({ + authToken: '123', + configOptions: { enableAnonTracking: true } + }); + logout(); // logout to remove logged in users before this test + try { + await track({ eventName: 'testEvent' }); + } catch (e) { + console.log('', e); + } + // this function call is needed for putting some delay before executing setUserId + await getInAppMessages({ + count: 10, + packageName: 'my-lil-website' + }); + await setUserID('testuser123', true); + expect(localStorageMock.removeItem).toHaveBeenCalledWith( + SHARED_PREF_ANON_USER_ID + ); + jest.useFakeTimers(); + setTimeout(() => { + const mergePostRequestData = mockRequest.history.post.find( + (req) => req.url === ENDPOINT_MERGE_USER + ); + expect(mergePostRequestData).toBeDefined(); // ensure that merge API gets called + }, 1000); + jest.runAllTimers(); + }); + + it('criteria is met with merge default with setUserId', async () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([eventDataMatched]); + } else if (key === SHARED_PREFS_CRITERIA) { + return JSON.stringify(mockCriteria); + } else if (key === SHARED_PREFS_ANON_SESSIONS) { + return JSON.stringify(initialAnonSessionInfo); + } else if (key === SHARED_PREF_ANON_USER_ID) { + return '123e4567-e89b-12d3-a456-426614174000'; + } + return null; + }); + const { setUserID, logout } = initializeWithConfig({ + authToken: '123', + configOptions: { enableAnonTracking: true } + }); + logout(); // logout to remove logged in users before this test + try { + await track({ eventName: 'testEvent' }); + } catch (e) { + console.log('', e); + } + await getInAppMessages({ + count: 10, + packageName: 'my-lil-website' + }); + await setUserID('testuser123'); + expect(localStorageMock.removeItem).toHaveBeenCalledWith( + SHARED_PREF_ANON_USER_ID + ); + jest.useFakeTimers(); + setTimeout(() => { + const mergePostRequestData = mockRequest.history.post.find( + (req) => req.url === ENDPOINT_MERGE_USER + ); + expect(mergePostRequestData).toBeDefined(); // ensure that merge API gets called + }, 1000); + jest.runAllTimers(); + }); + + it('current user identified with setUserId merge false', async () => { + const { setUserID, logout } = initializeWithConfig({ + authToken: '123', + configOptions: { enableAnonTracking: true } + }); + logout(); // logout to remove logged in users before this test + await setUserID('testuser123'); + const response = await getInAppMessages({ + count: 10, + packageName: 'my-lil-website' + }); + expect(response.config.params.userId).toBe('testuser123'); + try { + await track({ eventName: 'testEvent' }); + } catch (e) { + console.log('', e); + } + expect(localStorageMock.setItem).not.toHaveBeenCalledWith( + SHARED_PREF_ANON_USER_ID + ); + await setUserID('testuseranotheruser', false); + const secondResponse = await getInAppMessages({ + count: 10, + packageName: 'my-lil-website' + }); + expect(secondResponse.config.params.userId).toBe('testuseranotheruser'); + const mergePostRequestData = mockRequest.history.post.find( + (req) => req.url === ENDPOINT_MERGE_USER + ); + expect(mergePostRequestData).toBeUndefined(); // ensure that merge API Do NOT get called + }); + it('current user identified with setUserId merge true', async () => { + const { setUserID, logout } = initializeWithConfig({ + authToken: '123', + configOptions: { enableAnonTracking: true } + }); + logout(); // logout to remove logged in users before this test + await setUserID('testuser123'); + const response = await getInAppMessages({ + count: 10, + packageName: 'my-lil-website' + }); + expect(response.config.params.userId).toBe('testuser123'); + expect(localStorageMock.setItem).not.toHaveBeenCalledWith( + SHARED_PREF_ANON_USER_ID + ); + await setUserID('testuseranotheruser', true); + const secondResponse = await getInAppMessages({ + count: 10, + packageName: 'my-lil-website' + }); + expect(secondResponse.config.params.userId).toBe('testuseranotheruser'); + const mergePostRequestData = mockRequest.history.post.find( + (req) => req.url === ENDPOINT_MERGE_USER + ); + expect(mergePostRequestData).toBeDefined(); // ensure that merge API gets called + }); + it('current user identified with setUserId merge default', async () => { + const { setUserID, logout } = initializeWithConfig({ + authToken: '123', + configOptions: { enableAnonTracking: true } + }); + logout(); // logout to remove logged in users before this test + await setUserID('testuser123'); + const response = await getInAppMessages({ + count: 10, + packageName: 'my-lil-website' + }); + expect(response.config.params.userId).toBe('testuser123'); + expect(localStorageMock.setItem).not.toHaveBeenCalledWith( + SHARED_PREF_ANON_USER_ID + ); + await setUserID('testuseranotheruser'); + const secondResponse = await getInAppMessages({ + count: 10, + packageName: 'my-lil-website' + }); + expect(secondResponse.config.params.userId).toBe('testuseranotheruser'); + const mergePostRequestData = mockRequest.history.post.find( + (req) => req.url === ENDPOINT_MERGE_USER + ); + expect(mergePostRequestData).toBeUndefined(); // ensure that merge API Do NOT get called + }); + }); + + describe('UserMergeScenariosTests with setEmail', () => { + it('criteria not met with merge false with setEmail', async () => { + const { setEmail, logout } = initializeWithConfig({ + authToken: '123', + configOptions: { enableAnonTracking: true } + }); + logout(); // logout to remove logged in users before this test + try { + await track({ eventName: 'testEvent123' }); + } catch (e) { + console.log(''); + } + expect(localStorage.setItem).toHaveBeenCalledWith( + SHARED_PREFS_EVENT_LIST_KEY, + expect.any(String) + ); + await setEmail('testuser123@test.com', false); + const response = await getInAppMessages({ + count: 10, + packageName: 'my-lil-website' + }); + expect(response.config.params.email).toBe('testuser123@test.com'); + const removeItemCalls = localStorageMock.removeItem.mock.calls.filter( + (call) => call[0] === SHARED_PREFS_EVENT_LIST_KEY + ); + // count 1 means it did not remove item and so syncEvents was NOT called + // because removeItem gets called one time for the key in case of logout + expect(removeItemCalls.length).toBe(1); + const mergePostRequestData = mockRequest.history.post.find( + (req) => req.url === ENDPOINT_MERGE_USER + ); + expect(mergePostRequestData).toBeUndefined(); // ensure that merge API Do NOT get called + }); + + it('criteria not met with merge true with setEmail', async () => { + const { setEmail, logout } = initializeWithConfig({ + authToken: '123', + configOptions: { enableAnonTracking: true } + }); + logout(); // logout to remove logged in users before this test + try { + await track({ ...eventData }); + } catch (e) { + console.log(''); + } + expect(localStorage.setItem).toHaveBeenCalledWith( + SHARED_PREFS_EVENT_LIST_KEY, + expect.any(String) + ); + await setEmail('testuser123@test.com', true); + const response = await getInAppMessages({ + count: 10, + packageName: 'my-lil-website' + }); + expect(response.config.params.email).toBe('testuser123@test.com'); + const removeItemCalls = localStorageMock.removeItem.mock.calls.filter( + (call) => call[0] === SHARED_PREFS_EVENT_LIST_KEY + ); + // count 2 means it removed items and so syncEvents was called + // because removeItem gets called one time for the key in case of logout and 2nd time on syncevents + expect(removeItemCalls.length).toBe(2); + const mergePostRequestData = mockRequest.history.post.find( + (req) => req.url === ENDPOINT_MERGE_USER + ); + expect(mergePostRequestData).toBeUndefined(); // ensure that merge API Do NOT get called + }); + + it('criteria not met with merge default value with setEmail', async () => { + const { setEmail, logout } = initializeWithConfig({ + authToken: '123', + configOptions: { enableAnonTracking: true } + }); + logout(); // logout to remove logged in users before this test + try { + await track({ eventName: 'testEvent123' }); + } catch (e) { + console.log(''); + } + expect(localStorage.setItem).toHaveBeenCalledWith( + SHARED_PREFS_EVENT_LIST_KEY, + expect.any(String) + ); + await setEmail('testuser123@test.com'); + const response = await getInAppMessages({ + count: 10, + packageName: 'my-lil-website' + }); + expect(response.config.params.email).toBe('testuser123@test.com'); + const removeItemCalls = localStorageMock.removeItem.mock.calls.filter( + (call) => call[0] === SHARED_PREFS_EVENT_LIST_KEY + ); + // count 2 means it removed items and so syncEvents was called + // because removeItem gets called one time for the key in case of logout and 2nd time on syncevents + expect(removeItemCalls.length).toBe(2); + const mergePostRequestData = mockRequest.history.post.find( + (req) => req.url === ENDPOINT_MERGE_USER + ); + expect(mergePostRequestData).toBeUndefined(); // ensure that merge API Do NOT get called + }); + + it('criteria is met with merge true with setEmail', async () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([eventDataMatched]); + } else if (key === SHARED_PREFS_CRITERIA) { + return JSON.stringify(mockCriteria); + } else if (key === SHARED_PREFS_ANON_SESSIONS) { + return JSON.stringify(initialAnonSessionInfo); + } + return null; + }); + const { setEmail, logout } = initializeWithConfig({ + authToken: '123', + configOptions: { enableAnonTracking: true } + }); + logout(); // logout to remove logged in users before this test + try { + await track({ eventName: 'testEvent' }); + } catch (e) { + console.log('', e); + } + await getInAppMessages({ + count: 10, + packageName: 'my-lil-website' + }); + await setEmail('testuser123@test.com', true); + expect(localStorageMock.removeItem).toHaveBeenCalledWith( + SHARED_PREF_ANON_USER_ID + ); + jest.useFakeTimers(); + setTimeout(() => { + const mergePostRequestData = mockRequest.history.post.find( + (req) => req.url === ENDPOINT_MERGE_USER + ); + expect(mergePostRequestData).toBeDefined(); // ensure that merge API gets called + }, 1500); + jest.runAllTimers(); + }); + + it('criteria is met with merge default with setEmail', async () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([eventDataMatched]); + } else if (key === SHARED_PREFS_CRITERIA) { + return JSON.stringify(mockCriteria); + } else if (key === SHARED_PREFS_ANON_SESSIONS) { + return JSON.stringify(initialAnonSessionInfo); + } else if (key === SHARED_PREF_ANON_USER_ID) { + return '123e4567-e89b-12d3-a456-426614174000'; + } + return null; + }); + const { setEmail, logout } = initializeWithConfig({ + authToken: '123', + configOptions: { enableAnonTracking: true } + }); + logout(); // logout to remove logged in users before this test + try { + await track({ eventName: 'testEvent' }); + } catch (e) { + console.log('', e); + } + await getInAppMessages({ + count: 10, + packageName: 'my-lil-website' + }); + await setEmail('testuser123@test.com'); + expect(localStorageMock.removeItem).toHaveBeenCalledWith( + SHARED_PREF_ANON_USER_ID + ); + jest.useFakeTimers(); + setTimeout(() => { + const mergePostRequestData = mockRequest.history.post.find( + (req) => req.url === ENDPOINT_MERGE_USER + ); + expect(mergePostRequestData).toBeDefined(); // ensure that merge API gets called + }, 1500); + jest.runAllTimers(); + }); + + it('current user identified with setEmail with merge false', async () => { + const { setEmail, logout } = initializeWithConfig({ + authToken: '123', + configOptions: { enableAnonTracking: true } + }); + logout(); // logout to remove logged in users before this test + await setEmail('testuser123@test.com'); + const response = await getInAppMessages({ + count: 10, + packageName: 'my-lil-website' + }); + expect(response.config.params.email).toBe('testuser123@test.com'); + try { + await track({ eventName: 'testEvent' }); + } catch (e) { + console.log('', e); + } + expect(localStorageMock.setItem).not.toHaveBeenCalledWith( + SHARED_PREF_ANON_USER_ID + ); + await setEmail('testuseranotheruser@test.com', false); + const secondResponse = await getInAppMessages({ + count: 10, + packageName: 'my-lil-website' + }); + expect(secondResponse.config.params.email).toBe( + 'testuseranotheruser@test.com' + ); + const mergePostRequestData = mockRequest.history.post.find( + (req) => req.url === ENDPOINT_MERGE_USER + ); + expect(mergePostRequestData).toBeUndefined(); // ensure that merge API Do NOT get called + }); + + it('current user identified with setEmail merge true', async () => { + const { setEmail, logout } = initializeWithConfig({ + authToken: '123', + configOptions: { enableAnonTracking: true } + }); + logout(); // logout to remove logged in users before this test + await setEmail('testuser123@test.com'); + const response = await getInAppMessages({ + count: 10, + packageName: 'my-lil-website' + }); + expect(response.config.params.email).toBe('testuser123@test.com'); + try { + await track({ eventName: 'testEvent' }); + } catch (e) { + console.log('', e); + } + expect(localStorageMock.setItem).not.toHaveBeenCalledWith( + SHARED_PREF_ANON_USER_ID + ); + await setEmail('testuseranotheruser@test.com', true); + const secondResponse = await getInAppMessages({ + count: 10, + packageName: 'my-lil-website' + }); + expect(secondResponse.config.params.email).toBe( + 'testuseranotheruser@test.com' + ); + const mergePostRequestData = mockRequest.history.post.find( + (req) => req.url === ENDPOINT_MERGE_USER + ); + expect(mergePostRequestData).toBeDefined(); // ensure that merge API gets called + }); + + it('current user identified with setEmail merge default', async () => { + const { setEmail, logout } = initializeWithConfig({ + authToken: '123', + configOptions: { enableAnonTracking: true } + }); + logout(); // logout to remove logged in users before this test + await setEmail('testuser123@test.com'); + const response = await getInAppMessages({ + count: 10, + packageName: 'my-lil-website' + }); + expect(response.config.params.email).toBe('testuser123@test.com'); + try { + await track({ eventName: 'testEvent' }); + } catch (e) { + console.log('', e); + } + expect(localStorageMock.setItem).not.toHaveBeenCalledWith( + SHARED_PREF_ANON_USER_ID + ); + await setEmail('testuseranotheruser@test.com'); + const secondResponse = await getInAppMessages({ + count: 10, + packageName: 'my-lil-website' + }); + expect(secondResponse.config.params.email).toBe( + 'testuseranotheruser@test.com' + ); + const mergePostRequestData = mockRequest.history.post.find( + (req) => req.url === ENDPOINT_MERGE_USER + ); + expect(mergePostRequestData).toBeUndefined(); // ensure that merge API Do NOT get called + }); + }); +}); diff --git a/src/authorization/authorization.ts b/src/authorization/authorization.ts index e064b1fa..2a263a77 100644 --- a/src/authorization/authorization.ts +++ b/src/authorization/authorization.ts @@ -64,8 +64,8 @@ const doesRequestUrlContain = (routeConfig: RouteConfig) => ); export interface WithJWT { clearRefresh: () => void; - setEmail: (email: string) => Promise; - setUserID: (userId: string) => Promise; + setEmail: (email: string, merge?: boolean) => Promise; + setUserID: (userId: string, merge?: boolean) => Promise; logout: () => void; refreshJwtToken: (authTypes: string) => Promise; } @@ -73,8 +73,8 @@ export interface WithJWT { export interface WithoutJWT { setNewAuthToken: (newToken?: string) => void; clearAuthToken: () => void; - setEmail: (email: string) => Promise; - setUserID: (userId: string) => Promise; + setEmail: (email: string, merge?: boolean) => Promise; + setUserID: (userId: string, merge?: boolean) => Promise; logout: () => void; } @@ -117,16 +117,18 @@ const updateUser = () => { const getAnonUserId = () => { if (config.getConfig('enableAnonTracking')) { const anonUser = localStorage.getItem(SHARED_PREF_ANON_USER_ID); - return anonUser; + return anonUser === undefined ? null : anonUser; } else { return null; } }; -const initializeUserIdAndSync = (userId: string) => { +const initializeUserIdAndSync = (userId: string, merge: boolean) => { addUserIdToRequest(userId); clearAnonymousUser(); - syncEvents(); + if (merge) { + syncEvents(); + } }; const addUserIdToRequest = (userId: string) => { @@ -224,10 +226,12 @@ const addUserIdToRequest = (userId: string) => { }); }; -const initializeEmailUserAndSync = (email: string) => { +const initializeEmailUserAndSync = (email: string, merge: boolean) => { addEmailToRequest(email); clearAnonymousUser(); - syncEvents(); + if (merge) { + syncEvents(); + } }; const syncEvents = () => { @@ -425,15 +429,44 @@ export function initialize( } }; + const getMergeDefaultValue = (merge?: boolean) => { + const doesAnonUserExist = getAnonUserId() === null; + if (merge === undefined) { + if ( + (authIdentifier === null && typeOfAuth === null && doesAnonUserExist) || // Criteria is not yet met (default merge is true) + (authIdentifier !== null && typeOfAuth !== null && !doesAnonUserExist) + ) { + // // Criteria is met (Iterable profile created with an autogenerated identity)(default merge is true) + return true; + } else { + return false; // Current logged in user is identified (default merge is false) + } + } else { + return merge; + } + }; + const tryMergeUser = async ( - userIdOrEmail: string, - isEmail: boolean + emailOrUserId: string, + isEmail: boolean, + merge: boolean ): Promise => { + const sourceUserIdOrEmail = + authIdentifier === null ? getAnonUserId() : authIdentifier; + const sourceUserId = typeOfAuth === 'email' ? null : sourceUserIdOrEmail; + const sourceEmail = typeOfAuth === 'email' ? sourceUserIdOrEmail : null; + const destinationUserId = isEmail ? null : emailOrUserId; + const destinationEmail = isEmail ? emailOrUserId : null; // This function will try to merge if anon user exists - if (getAnonUserId() !== null) { + if ((getAnonUserId() !== null || authIdentifier !== null) && merge) { const anonymousUserMerge = new AnonymousUserMerge(); try { - await anonymousUserMerge.mergeUser(userIdOrEmail, isEmail); + await anonymousUserMerge.mergeUser( + sourceUserId, + sourceEmail, + destinationUserId, + destinationEmail + ); } catch (error) { return Promise.reject(`merging failed: ${error}`); } @@ -464,12 +497,13 @@ export function initialize( baseAxiosRequest.interceptors.request.eject(authInterceptor); } }, - setEmail: async (email: string) => { + setEmail: async (email: string, merge?: boolean) => { clearMessages(); try { - const result = await tryMergeUser(email, true); + merge = getMergeDefaultValue(merge); + const result = await tryMergeUser(email, true, merge); if (result) { - initializeEmailUserAndSync(email); + initializeEmailUserAndSync(email, merge); return Promise.resolve(); } } catch (error) { @@ -477,11 +511,10 @@ export function initialize( return Promise.reject(`merging failed: ${error}`); } }, - setUserID: async (userId: string) => { + setUserID: async (userId: string, merge?: boolean) => { clearMessages(); const tryUser = () => { let createUserAttempts = 0; - return async function tryUserNTimes(): Promise { try { return await updateUser(); @@ -498,9 +531,10 @@ export function initialize( }; }; try { - const result = await tryMergeUser(userId, false); + merge = getMergeDefaultValue(merge); + const result = await tryMergeUser(userId, false, merge); if (result) { - initializeUserIdAndSync(userId); + initializeUserIdAndSync(userId, merge); try { return await tryUser()(); } catch (e) { @@ -514,6 +548,7 @@ export function initialize( } }, logout: () => { + anonUserManager.removeAnonSessionCriteriaData(); typeOfAuth = null; authIdentifier = null; /* clear fetched in-app messages */ @@ -790,13 +825,14 @@ export function initialize( /* this will just clear the existing timeout */ handleTokenExpiration(''); }, - setEmail: async (email: string) => { + setEmail: async (email: string, merge?: boolean) => { /* clear previous user */ clearMessages(); try { - const result = await tryMergeUser(email, true); + merge = getMergeDefaultValue(merge); + const result = await tryMergeUser(email, true, merge); if (result) { - initializeEmailUserAndSync(email); + initializeEmailUserAndSync(email, merge); try { return doRequest({ email }).catch((e) => { if (logLevel === 'verbose') { @@ -816,7 +852,7 @@ export function initialize( return Promise.reject(`merging failed: ${error}`); } }, - setUserID: async (userId: string) => { + setUserID: async (userId: string, merge?: boolean) => { clearMessages(); const tryUser = () => { @@ -838,9 +874,10 @@ export function initialize( }; }; try { - const result = await tryMergeUser(userId, false); + merge = getMergeDefaultValue(merge); + const result = await tryMergeUser(userId, false, merge); if (result) { - initializeUserIdAndSync(userId); + initializeUserIdAndSync(userId, merge); try { return doRequest({ userID: userId }) .then(async (token) => { @@ -866,6 +903,7 @@ export function initialize( } }, logout: () => { + anonUserManager.removeAnonSessionCriteriaData(); typeOfAuth = null; authIdentifier = null; /* clear fetched in-app messages */ diff --git a/src/constants.ts b/src/constants.ts index 07d2c234..4b708e2d 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -25,10 +25,8 @@ export const BASE_URL = process.env.BASE_URL || ITERABLE_API_URL; export const GETMESSAGES_PATH = '/inApp/web/getMessages'; export const GET_CRITERIA_PATH = '/anonymoususer/list'; -export const ENDPOINT_GET_USER_BY_USERID = 'users/byUserId'; -export const ENDPOINT_GET_USER_BY_EMAIL = 'users/getByEmail'; -export const ENDPOINT_MERGE_USER = 'users/merge'; -export const ENDPOINT_TRACK_ANON_SESSION = 'anonymoususer/events/session'; +export const ENDPOINT_MERGE_USER = '/users/merge'; +export const ENDPOINT_TRACK_ANON_SESSION = '/anonymoususer/events/session'; const GET_ENABLE_INAPP_CONSUME = () => { try { diff --git a/src/events/events.ts b/src/events/events.ts index 7caf0ee9..8dacd4b6 100644 --- a/src/events/events.ts +++ b/src/events/events.ts @@ -4,7 +4,7 @@ import { AnonymousUserEventManager } from 'src/anonymousUserTracking/anonymousUs import { canTrackAnonUser } from 'src/utils/commonFunctions'; import { InAppTrackRequestParams } from './in-app/types'; import { trackSchema } from './events.schema'; -import { INITIALIZE_ERROR, ENDPOINTS } from 'src/constants'; +import { ENDPOINTS, INITIALIZE_ERROR } from 'src/constants'; export const track = (payload: InAppTrackRequestParams) => { /* a customer could potentially send these up if they're not using TypeScript */ From 41ffd797fb70c76c8b2390f03f593c7921debf39 Mon Sep 17 00:00:00 2001 From: hardikmashru <150107929+hardikmashru@users.noreply.github.com> Date: Fri, 12 Jul 2024 00:26:42 +0530 Subject: [PATCH 53/88] MOB 8960 (#415) * Fixed single item matches code * Fixed comments * added more unit tests, bug fixes * Not combinator implemented * evaluateEvent added not combinator * minMatch changes revert * minMatch implemented * complex criteria tests, not combinator, evaluateFieldLogic fix --------- Co-authored-by: hardikmashru --- .../anonymousUserEventManager.ts | 3 - .../complexCriteria.test.ts | 1217 +++++++++++++++++ .../criteriaCompletionChecker.ts | 188 +-- src/constants.ts | 1 - 4 files changed, 1262 insertions(+), 147 deletions(-) create mode 100644 src/anonymousUserTracking/complexCriteria.test.ts diff --git a/src/anonymousUserTracking/anonymousUserEventManager.ts b/src/anonymousUserTracking/anonymousUserEventManager.ts index 81ed2a01..2bcedbf6 100644 --- a/src/anonymousUserTracking/anonymousUserEventManager.ts +++ b/src/anonymousUserTracking/anonymousUserEventManager.ts @@ -22,7 +22,6 @@ import { ENDPOINT_TRACK_ANON_SESSION, WEB_PLATFORM, KEY_PREFER_USERID, - SHARED_PREF_MATCHED_CRITERIAS, ENDPOINTS } from 'src/constants'; import { baseIterableRequest } from 'src/request'; @@ -221,7 +220,6 @@ export class AnonymousUserEventManager { if (trackEventList.length) { trackEventList.forEach((event: any) => { const eventType = event[SHARED_PREFS_EVENT_TYPE]; - delete event.criteriaId; delete event.eventType; switch (eventType) { case TRACK_EVENT: { @@ -251,7 +249,6 @@ export class AnonymousUserEventManager { removeAnonSessionCriteriaData() { localStorage.removeItem(SHARED_PREFS_ANON_SESSIONS); localStorage.removeItem(SHARED_PREFS_EVENT_LIST_KEY); - localStorage.removeItem(SHARED_PREF_MATCHED_CRITERIAS); } private async storeEventListToLocalStorage( diff --git a/src/anonymousUserTracking/complexCriteria.test.ts b/src/anonymousUserTracking/complexCriteria.test.ts new file mode 100644 index 00000000..c7284341 --- /dev/null +++ b/src/anonymousUserTracking/complexCriteria.test.ts @@ -0,0 +1,1217 @@ +import { SHARED_PREFS_EVENT_LIST_KEY } from '../constants'; +import CriteriaCompletionChecker from './criteriaCompletionChecker'; + +const localStorageMock = { + getItem: jest.fn(), + setItem: jest.fn(), + removeItem: jest.fn() +}; + +describe('complexCriteria', () => { + beforeEach(() => { + (global as any).localStorage = localStorageMock; + }); + + // complex criteria + it('should return criteriaId 98 (complex criteria 1)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + items: [ + { + id: '12', + name: 'monitor', + price: 50, + quantity: 10 + } + ], + total: 50, + eventType: 'purchase' + }, + { + dataFields: { + preferred_car_models: 'Honda', + country: 'Japan' + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '98', + name: 'Custom Event', + createdAt: 1716560453973, + updatedAt: 1716560453973, + searchQuery: { + combinator: 'Or', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'eventName', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'customEvent', + id: 23, + value: 'button.clicked' + }, + { + field: 'button-clicked.animal', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'customEvent', + id: 25, + value: 'giraffe' + } + ] + } + }, + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'updateCart.updatedShoppingCartItems.price', + fieldType: 'double', + comparatorType: 'GreaterThanOrEqualTo', + dataType: 'customEvent', + id: 28, + value: '120' + }, + { + field: + 'updateCart.updatedShoppingCartItems.quantity', + fieldType: 'long', + comparatorType: 'GreaterThanOrEqualTo', + dataType: 'customEvent', + id: 29, + valueLong: 100, + value: '100' + } + ] + } + } + ] + }, + { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'shoppingCartItems.name', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'purchase', + id: 31, + value: 'monitor' + }, + { + field: 'shoppingCartItems.quantity', + fieldType: 'long', + comparatorType: 'GreaterThanOrEqualTo', + dataType: 'purchase', + id: 32, + valueLong: 5, + value: '5' + } + ] + } + }, + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'country', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'user', + id: 34, + value: 'Japan' + }, + { + field: 'preferred_car_models', + fieldType: 'string', + comparatorType: 'Contains', + dataType: 'user', + id: 36, + value: 'Honda' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual('98'); + }); + + it('should return null (complex criteria 1 fail)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + items: [ + { + id: '12', + name: 'monitor', + price: 50, + quantity: 10 + } + ], + total: 50, + eventType: 'purchase' + }, + { + dataFields: { + preferred_car_models: 'Honda' + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '98', + name: 'Custom Event', + createdAt: 1716560453973, + updatedAt: 1716560453973, + searchQuery: { + combinator: 'Or', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'eventName', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'customEvent', + id: 23, + value: 'button.clicked' + }, + { + field: 'button-clicked.animal', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'customEvent', + id: 25, + value: 'giraffe' + } + ] + } + }, + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'updateCart.updatedShoppingCartItems.price', + fieldType: 'double', + comparatorType: 'GreaterThanOrEqualTo', + dataType: 'customEvent', + id: 28, + value: '120' + }, + { + field: + 'updateCart.updatedShoppingCartItems.quantity', + fieldType: 'long', + comparatorType: 'GreaterThanOrEqualTo', + dataType: 'customEvent', + id: 29, + valueLong: 100, + value: '100' + } + ] + } + } + ] + }, + { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'shoppingCartItems.name', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'purchase', + id: 31, + value: 'monitor' + }, + { + field: 'shoppingCartItems.quantity', + fieldType: 'long', + comparatorType: 'GreaterThanOrEqualTo', + dataType: 'purchase', + id: 32, + valueLong: 5, + value: '5' + } + ] + } + }, + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'country', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'user', + id: 34, + value: 'Japan' + }, + { + field: 'preferred_car_models', + fieldType: 'string', + comparatorType: 'Contains', + dataType: 'user', + id: 36, + value: 'Honda' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual(null); + }); + + it('should return criteriaId 99 (complex criteria 2)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + items: [{ id: '12', name: 'Mocha', price: 90, quantity: 50 }], + total: 50, + eventType: 'cartUpdate' + }, + { + items: [{ id: '12', name: 'Mocha', price: 90, quantity: 50 }], + total: 50, + eventType: 'cartUpdate' + }, + { + dataFields: { + preferred_car_models: 'Subaru', + country: 'USA' + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '99', + name: 'Custom Event', + createdAt: 1716560453973, + updatedAt: 1716560453973, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'eventName', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'customEvent', + id: 2, + value: 'button-clicked' + }, + { + field: 'button-clicked.lastPageViewed', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'customEvent', + id: 4, + value: 'welcome page' + } + ] + } + }, + { + dataType: 'customEvent', + minMatch: 2, + maxMatch: 3, + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'updateCart.updatedShoppingCartItems.price', + fieldType: 'double', + comparatorType: 'GreaterThanOrEqualTo', + dataType: 'customEvent', + id: 6, + value: '85' + }, + { + field: + 'updateCart.updatedShoppingCartItems.quantity', + fieldType: 'long', + comparatorType: 'GreaterThanOrEqualTo', + dataType: 'customEvent', + id: 7, + valueLong: 50, + value: '50' + } + ] + } + } + ] + }, + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'shoppingCartItems.name', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'purchase', + id: 16, + isFiltering: false, + value: 'coffee' + }, + { + field: 'shoppingCartItems.quantity', + fieldType: 'long', + comparatorType: 'GreaterThanOrEqualTo', + dataType: 'purchase', + id: 17, + valueLong: 2, + value: '2' + } + ] + } + }, + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'country', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'user', + id: 19, + value: 'USA' + }, + { + field: 'preferred_car_models', + fieldType: 'string', + comparatorType: 'Contains', + dataType: 'user', + id: 21, + value: 'Subaru' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual('99'); + }); + + it('should return null (complex criteria 2 fail)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + items: [{ id: '12', name: 'Mocha', price: 90, quantity: 50 }], + total: 50, + eventType: 'cartUpdate' + }, + { + dataFields: { + preferred_car_models: 'Subaru', + country: 'USA' + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '99', + name: 'Custom Event', + createdAt: 1716560453973, + updatedAt: 1716560453973, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'eventName', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'customEvent', + id: 2, + value: 'button-clicked' + }, + { + field: 'button-clicked.lastPageViewed', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'customEvent', + id: 4, + value: 'welcome page' + } + ] + } + }, + { + dataType: 'customEvent', + minMatch: 2, + maxMatch: 3, + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'updateCart.updatedShoppingCartItems.price', + fieldType: 'double', + comparatorType: 'GreaterThanOrEqualTo', + dataType: 'customEvent', + id: 6, + value: '85' + }, + { + field: + 'updateCart.updatedShoppingCartItems.quantity', + fieldType: 'long', + comparatorType: 'GreaterThanOrEqualTo', + dataType: 'customEvent', + id: 7, + valueLong: 50, + value: '50' + } + ] + } + } + ] + }, + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'shoppingCartItems.name', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'purchase', + id: 16, + isFiltering: false, + value: 'coffee' + }, + { + field: 'shoppingCartItems.quantity', + fieldType: 'long', + comparatorType: 'GreaterThanOrEqualTo', + dataType: 'purchase', + id: 17, + valueLong: 2, + value: '2' + } + ] + } + }, + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'country', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'user', + id: 19, + value: 'USA' + }, + { + field: 'preferred_car_models', + fieldType: 'string', + comparatorType: 'Contains', + dataType: 'user', + id: 21, + value: 'Subaru' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual(null); + }); + + it('should return criteriaId 100 (complex criteria 3)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + items: [{ id: '12', name: 'Mocha', price: 90, quantity: 50 }], + total: 50, + eventType: 'cartUpdate' + }, + { + items: [{ id: '12', name: 'Mocha', price: 90, quantity: 50 }], + total: 50, + eventType: 'cartUpdate' + }, + + { + dataFields: { + preferred_car_models: 'Subaru', + country: 'USA' + }, + eventType: 'user' + }, + { + eventName: 'button-clicked', + dataFields: { + 'button-clicked.lastPageViewed': 'welcome page' + }, + eventType: 'customEvent' + }, + { + items: [ + { + id: '12', + name: 'coffee', + price: 10, + quantity: 5 + } + ], + total: 2, + eventType: 'purchase' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '100', + name: 'Custom Event', + createdAt: 1716560453973, + updatedAt: 1716560453973, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'eventName', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'customEvent', + id: 2, + value: 'button-clicked' + }, + { + field: 'button-clicked.lastPageViewed', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'customEvent', + id: 4, + value: 'welcome page' + } + ] + } + }, + { + dataType: 'customEvent', + minMatch: 2, + maxMatch: 3, + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'updateCart.updatedShoppingCartItems.price', + fieldType: 'double', + comparatorType: 'GreaterThanOrEqualTo', + dataType: 'customEvent', + id: 6, + value: '85' + }, + { + field: + 'updateCart.updatedShoppingCartItems.quantity', + fieldType: 'long', + comparatorType: 'GreaterThanOrEqualTo', + dataType: 'customEvent', + id: 7, + valueLong: 50, + value: '50' + } + ] + } + }, + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'shoppingCartItems.name', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'purchase', + id: 9, + value: 'coffee' + }, + { + field: 'shoppingCartItems.quantity', + fieldType: 'long', + comparatorType: 'GreaterThanOrEqualTo', + dataType: 'purchase', + id: 10, + valueLong: 2, + value: '2' + } + ] + } + }, + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'country', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'user', + id: 12, + value: 'USA' + }, + { + field: 'preferred_car_models', + fieldType: 'string', + comparatorType: 'Contains', + dataType: 'user', + id: 14, + value: 'Subaru' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual('100'); + }); + + it('should return null (complex criteria 3 fail)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + items: [{ id: '12', name: 'Mocha', price: 90, quantity: 50 }], + total: 50, + eventType: 'cartUpdate' + }, + { + items: [{ id: '12', name: 'Mocha', price: 90, quantity: 50 }], + total: 50, + eventType: 'cartUpdate' + }, + + { + dataFields: { + preferred_car_models: 'Subaru', + country: 'USA' + }, + eventType: 'user' + }, + { + eventName: 'button-clicked', + dataFields: { + 'button-clicked.lastPageViewed': 'welcome page' + }, + eventType: 'customEvent' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '100', + name: 'Custom Event', + createdAt: 1716560453973, + updatedAt: 1716560453973, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'eventName', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'customEvent', + id: 2, + value: 'button-clicked' + }, + { + field: 'button-clicked.lastPageViewed', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'customEvent', + id: 4, + value: 'welcome page' + } + ] + } + }, + { + dataType: 'customEvent', + minMatch: 2, + maxMatch: 3, + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'updateCart.updatedShoppingCartItems.price', + fieldType: 'double', + comparatorType: 'GreaterThanOrEqualTo', + dataType: 'customEvent', + id: 6, + value: '85' + }, + { + field: + 'updateCart.updatedShoppingCartItems.quantity', + fieldType: 'long', + comparatorType: 'GreaterThanOrEqualTo', + dataType: 'customEvent', + id: 7, + valueLong: 50, + value: '50' + } + ] + } + }, + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'shoppingCartItems.name', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'purchase', + id: 9, + value: 'coffee' + }, + { + field: 'shoppingCartItems.quantity', + fieldType: 'long', + comparatorType: 'GreaterThanOrEqualTo', + dataType: 'purchase', + id: 10, + valueLong: 2, + value: '2' + } + ] + } + }, + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'country', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'user', + id: 12, + value: 'USA' + }, + { + field: 'preferred_car_models', + fieldType: 'string', + comparatorType: 'Contains', + dataType: 'user', + id: 14, + value: 'Subaru' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual(null); + }); + + it('should return criteriaId 101 (complex criteria 4)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + items: [ + { id: '12', name: 'sneakers', price: 10, quantity: 5 }, + { id: '13', name: 'slippers', price: 10, quantity: 3 } + ], + total: 2, + eventType: 'purchase' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '101', + name: 'Complex Criteria 4: (NOT 9) AND 10', + createdAt: 1719328083918, + updatedAt: 1719328083918, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'Not', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + field: 'shoppingCartItems.name', + comparatorType: 'Equals', + value: 'sneakers', + fieldType: 'string' + }, + { + dataType: 'purchase', + field: 'shoppingCartItems.quantity', + comparatorType: 'LessThanOrEqualTo', + value: '3', + fieldType: 'long' + } + ] + } + } + ] + }, + { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + field: 'shoppingCartItems.name', + comparatorType: 'Equals', + value: 'slippers', + fieldType: 'string' + }, + { + dataType: 'purchase', + field: 'shoppingCartItems.quantity', + comparatorType: 'GreaterThanOrEqualTo', + value: '3', + fieldType: 'long' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual('101'); + }); + + it('should return null (complex criteria 4 fail)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + items: [ + { id: '12', name: 'sneakers', price: 10, quantity: 2 }, + { id: '13', name: 'slippers', price: 10, quantity: 3 } + ], + total: 2, + eventType: 'purchase' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '101', + name: 'Complex Criteria 4: (NOT 9) AND 10', + createdAt: 1719328083918, + updatedAt: 1719328083918, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'Not', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + field: 'shoppingCartItems.name', + comparatorType: 'Equals', + value: 'sneakers', + fieldType: 'string' + }, + { + dataType: 'purchase', + field: 'shoppingCartItems.quantity', + comparatorType: 'LessThanOrEqualTo', + value: '3', + fieldType: 'long' + } + ] + } + } + ] + }, + { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + field: 'shoppingCartItems.name', + comparatorType: 'Equals', + value: 'slippers', + fieldType: 'string' + }, + { + dataType: 'purchase', + field: 'shoppingCartItems.quantity', + comparatorType: 'GreaterThanOrEqualTo', + value: '3', + fieldType: 'long' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual(null); + }); +}); diff --git a/src/anonymousUserTracking/criteriaCompletionChecker.ts b/src/anonymousUserTracking/criteriaCompletionChecker.ts index 4c8d7c31..11f18401 100644 --- a/src/anonymousUserTracking/criteriaCompletionChecker.ts +++ b/src/anonymousUserTracking/criteriaCompletionChecker.ts @@ -8,9 +8,7 @@ import { UPDATE_USER, KEY_EVENT_NAME, UPDATECART_ITEM_PREFIX, - PURCHASE_ITEM_PREFIX, - SHARED_PREFS_EVENT_LIST_KEY, - SHARED_PREF_MATCHED_CRITERIAS + PURCHASE_ITEM_PREFIX } from '../constants'; interface SearchQuery { @@ -57,17 +55,12 @@ class CriteriaCompletionChecker { } private findMatchedCriteria(criteriaList: Criteria[]): string | null { - const criteriaIdList = criteriaList.map((criteria) => criteria.criteriaId); - const eventsToProcess = this.prepareEventsToProcess(criteriaIdList); + const eventsToProcess = this.prepareEventsToProcess(); // Use find to get the first matching criteria const matchingCriteria = criteriaList.find((criteria) => { if (criteria.searchQuery && criteria.criteriaId) { - return this.evaluateTree( - criteria.searchQuery, - eventsToProcess, - criteria.criteriaId - ); + return this.evaluateTree(criteria.searchQuery, eventsToProcess); } return false; }); @@ -76,9 +69,9 @@ class CriteriaCompletionChecker { return matchingCriteria ? matchingCriteria.criteriaId : null; } - private prepareEventsToProcess(criteriaIdList: string[]): any[] { - const eventsToProcess: any[] = this.getEventsWithCartItems(criteriaIdList); - const nonPurchaseEvents: any[] = this.getNonCartEvents(criteriaIdList); + private prepareEventsToProcess(): any[] { + const eventsToProcess: any[] = this.getEventsWithCartItems(); + const nonPurchaseEvents: any[] = this.getNonCartEvents(); nonPurchaseEvents.forEach((event) => { eventsToProcess.push(event); @@ -87,20 +80,10 @@ class CriteriaCompletionChecker { return eventsToProcess; } - private getEventsWithCartItems(criteriaIdList: string[]): any[] { + private getEventsWithCartItems(): any[] { const processedEvents: any[] = []; - this.localStoredEventList.forEach((localEventData, index) => { - if (Object.prototype.hasOwnProperty.call(localEventData, 'criteriaId')) { - if (!criteriaIdList.includes(localEventData.criteriaId)) { - delete localEventData.criteriaId; - this.localStoredEventList[index] = localEventData; - localStorage.setItem( - SHARED_PREFS_EVENT_LIST_KEY, - JSON.stringify(this.localStoredEventList) - ); - } - } + this.localStoredEventList.forEach((localEventData) => { if ( localEventData[SHARED_PREFS_EVENT_TYPE] && localEventData[SHARED_PREFS_EVENT_TYPE] === TRACK_PURCHASE @@ -177,19 +160,9 @@ class CriteriaCompletionChecker { return processedEvents; } - private getNonCartEvents(criteriaIdList: string[]): any[] { + private getNonCartEvents(): any[] { const nonPurchaseEvents: any[] = []; - this.localStoredEventList.forEach((localEventData, index) => { - if (Object.prototype.hasOwnProperty.call(localEventData, 'criteriaId')) { - if (!criteriaIdList.includes(localEventData.criteriaId)) { - delete localEventData.criteriaId; - this.localStoredEventList[index] = localEventData; - localStorage.setItem( - SHARED_PREFS_EVENT_LIST_KEY, - JSON.stringify(this.localStoredEventList) - ); - } - } + this.localStoredEventList.forEach((localEventData) => { if ( localEventData[SHARED_PREFS_EVENT_TYPE] && (localEventData[SHARED_PREFS_EVENT_TYPE] === UPDATE_USER || @@ -208,36 +181,36 @@ class CriteriaCompletionChecker { return nonPurchaseEvents; } - private evaluateTree( - node: SearchQuery, - localEventData: any[], - criteriaId: string - ): boolean { + private evaluateTree(node: SearchQuery, localEventData: any[]): boolean { try { if (node.searchQueries) { const combinator = node.combinator; const searchQueries: any = node.searchQueries; if (combinator === 'And') { for (let i = 0; i < searchQueries.length; i++) { - if ( - !this.evaluateTree(searchQueries[i], localEventData, criteriaId) - ) { + if (!this.evaluateTree(searchQueries[i], localEventData)) { return false; } } return true; } else if (combinator === 'Or') { for (let i = 0; i < searchQueries.length; i++) { - if ( - this.evaluateTree(searchQueries[i], localEventData, criteriaId) - ) { + if (this.evaluateTree(searchQueries[i], localEventData)) { return true; } } return false; + } else if (combinator === 'Not') { + for (let i = 0; i < searchQueries.length; i++) { + searchQueries[i]['isNot'] = true; + if (this.evaluateTree(searchQueries[i], localEventData)) { + return false; + } + } + return true; } } else if (node.searchCombo) { - return this.evaluateSearchQueries(node, localEventData, criteriaId); + return this.evaluateSearchQueries(node, localEventData); } } catch (e) { this.handleException(e); @@ -247,108 +220,32 @@ class CriteriaCompletionChecker { private evaluateSearchQueries( node: SearchQuery, - localEventData: any[], - criteriaId: string + localEventData: any[] ): boolean { // this function will compare the actualy searhqueues under search combo for (let i = 0; i < localEventData.length; i++) { const eventData = localEventData[i]; const trackingType = eventData[SHARED_PREFS_EVENT_TYPE]; const dataType = node.dataType; - if (!Object.prototype.hasOwnProperty.call(eventData, 'criteriaId')) { - if (dataType === trackingType) { - const searchCombo = node.searchCombo; - const searchQueries = searchCombo?.searchQueries || []; - const combinator = searchCombo?.combinator || ''; - const matchedCriteriasFromLocalStorage = localStorage.getItem( - SHARED_PREF_MATCHED_CRITERIAS - ); - - const matchedCriterias = - matchedCriteriasFromLocalStorage && - JSON.parse(matchedCriteriasFromLocalStorage); - - const matchedCriteria = - matchedCriterias && - matchedCriterias.find( - (item: { - criteriaId: string; - nodeCombo: { searchCombo: object; count: number }[]; - }) => item.criteriaId === criteriaId - ); - - const matchedCriteriaIndex = - matchedCriterias && - matchedCriterias.findIndex( - (item: { - criteriaId: string; - nodeCombo: { searchCombo: object; count: number }[]; - }) => item.criteriaId === criteriaId - ); - if (this.evaluateEvent(eventData, searchQueries, combinator)) { - if (Object.prototype.hasOwnProperty.call(node, 'minMatch')) { - const matchedNode = - matchedCriteria && - matchedCriteria.nodeCombo.filter( - (n: { searchCombo: object; count: number }) => - JSON.stringify(n.searchCombo) === - JSON.stringify(node.searchCombo) - ); - if (matchedNode && matchedNode.length > 0) { - // Update the count of the first node found - matchedNode[0].count = (matchedNode[0].count || 0) + 1; - // Find the index of the node in matchedCriteria.nodeCombo - const nodeIndex = matchedCriteria.nodeCombo.findIndex( - (n: { searchCombo: object; count: number }) => - JSON.stringify(n.searchCombo) === - JSON.stringify(matchedNode[0].searchCombo) - ); - - if (nodeIndex !== -1) { - // Update the node in the matchedCriteria.nodeCombo array - matchedCriteria.nodeCombo[nodeIndex] = matchedNode[0]; - matchedCriterias[matchedCriteriaIndex] = matchedCriteria; - } - // Update local storage with the new matchedCriteria - localStorage.setItem( - SHARED_PREF_MATCHED_CRITERIAS, - JSON.stringify(matchedCriterias) - ); - - const eventFromLocal = this.localStoredEventList[i]; - eventFromLocal.criteriaId = criteriaId; - this.localStoredEventList[i] = eventFromLocal; - - localStorage.setItem( - SHARED_PREFS_EVENT_LIST_KEY, - JSON.stringify(this.localStoredEventList) - ); - - return matchedNode[0].count === node.minMatch; - } else { - const tempMatchedCriterias = matchedCriterias || []; - tempMatchedCriterias.push({ - criteriaId: criteriaId, - nodeCombo: [{ searchCombo: node.searchCombo, count: 1 }] - }); - const eventFromLocal = this.localStoredEventList[i]; - eventFromLocal.criteriaId = criteriaId; - this.localStoredEventList[i] = eventFromLocal; - - localStorage.setItem( - SHARED_PREFS_EVENT_LIST_KEY, - JSON.stringify(this.localStoredEventList) - ); - localStorage.setItem( - SHARED_PREF_MATCHED_CRITERIAS, - JSON.stringify(tempMatchedCriterias) - ); - return node.minMatch === 1; - } - } else { - return true; + if (dataType === trackingType) { + const searchCombo = node.searchCombo; + const searchQueries = searchCombo?.searchQueries || []; + const combinator = searchCombo?.combinator || ''; + const isNot = Object.prototype.hasOwnProperty.call(node, 'isNot'); + if (this.evaluateEvent(eventData, searchQueries, combinator)) { + if (node.minMatch) { + const minMatch = node.minMatch - 1; + node.minMatch = minMatch; + if (minMatch > 0) { + continue; } } + if (isNot && !(i + 1 === localEventData.length)) { + continue; + } + return true; + } else if (isNot) { + return false; } } } @@ -362,6 +259,8 @@ class CriteriaCompletionChecker { ): boolean { if (combinator === 'And' || combinator === 'Or') { return this.evaluateFieldLogic(searchQueries, localEvent); + } else if (combinator === 'Not') { + return !this.evaluateFieldLogic(searchQueries, localEvent); } return false; } @@ -403,6 +302,9 @@ class CriteriaCompletionChecker { !searchQuery.field.startsWith(UPDATECART_ITEM_PREFIX) && !searchQuery.field.startsWith(PURCHASE_ITEM_PREFIX) ); + if (filteredSearchQueries.length === 0) { + return itemMatchedResult; + } const matchResult = filteredSearchQueries.every((query: any) => { const field = query.field; const eventKeyItems = filteredLocalDataKeys.filter( diff --git a/src/constants.ts b/src/constants.ts index 4b708e2d..18823f5d 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -283,7 +283,6 @@ export const SHARED_PREFS_EVENT_LIST_KEY = 'itbl_event_list'; export const SHARED_PREFS_CRITERIA = 'criteria'; export const SHARED_PREFS_ANON_SESSIONS = 'itbl_anon_sessions'; export const SHARED_PREF_ANON_USER_ID = 'anon_userId'; -export const SHARED_PREF_MATCHED_CRITERIAS = 'matchedCriterias'; export const KEY_EVENT_NAME = 'eventName'; export const KEY_CREATED_AT = 'createdAt'; From 06c55d9ea933c2234a2535d08a9e618b3be021a9 Mon Sep 17 00:00:00 2001 From: hardikmashru <150107929+hardikmashru@users.noreply.github.com> Date: Fri, 12 Jul 2024 21:43:46 +0530 Subject: [PATCH 54/88] Isset purchase update fix (#416) * Fixed single item matches code * Fixed comments * added more unit tests, bug fixes * Not combinator implemented * evaluateEvent added not combinator * minMatch changes revert * minMatch implemented * complex criteria tests, not combinator, evaluateFieldLogic fix * Isset fix and unit tests * update cart and custom event fix, unit tests * little changes * doesItemMatchQueries fix * isset fail & min-max tests, * Replace for loop with array methods * added sampleTest1 * Set type annotations for issetCheck --------- Co-authored-by: hardikmashru --- .../criteriaCompletionChecker.ts | 58 +- .../tests/complexCriteria.test.ts | 1599 +++++++++++++++++ .../tests/criteriaCompletionChecker.test.ts | 723 ++++++++ src/constants.ts | 3 +- 4 files changed, 2374 insertions(+), 9 deletions(-) create mode 100644 src/anonymousUserTracking/tests/complexCriteria.test.ts diff --git a/src/anonymousUserTracking/criteriaCompletionChecker.ts b/src/anonymousUserTracking/criteriaCompletionChecker.ts index 11f18401..9b70a283 100644 --- a/src/anonymousUserTracking/criteriaCompletionChecker.ts +++ b/src/anonymousUserTracking/criteriaCompletionChecker.ts @@ -8,7 +8,8 @@ import { UPDATE_USER, KEY_EVENT_NAME, UPDATECART_ITEM_PREFIX, - PURCHASE_ITEM_PREFIX + PURCHASE_ITEM_PREFIX, + PURCHASE_ITEM } from '../constants'; interface SearchQuery { @@ -99,7 +100,7 @@ class CriteriaCompletionChecker { }); return updatItem; }); - updatedItem[KEY_ITEMS] = items; + updatedItem[PURCHASE_ITEM] = items; } if (localEventData.dataFields) { @@ -277,9 +278,16 @@ class CriteriaCompletionChecker { private evaluateFieldLogic(searchQueries: any[], eventData: any): boolean { const localDataKeys = Object.keys(eventData); let itemMatchedResult = false; + let key_item = null; if (localDataKeys.includes(KEY_ITEMS)) { + key_item = KEY_ITEMS; + } else if (localDataKeys.includes(PURCHASE_ITEM)) { + key_item = PURCHASE_ITEM; + } + + if (key_item !== null) { // scenario of items inside purchase and updateCart Events - const items = eventData[KEY_ITEMS]; + const items = eventData[key_item]; const result = items.some((item: any) => { return this.doesItemMatchQueries(item, searchQueries); }); @@ -307,6 +315,19 @@ class CriteriaCompletionChecker { } const matchResult = filteredSearchQueries.every((query: any) => { const field = query.field; + if ( + query.dataType === TRACK_EVENT && + query.fieldType === 'object' && + query.comparatorType === 'IsSet' + ) { + const eventName = eventData[KEY_EVENT_NAME]; + if (eventName === UPDATE_CART && field === eventName) { + return true; + } + if (field === eventName) { + return true; + } + } const eventKeyItems = filteredLocalDataKeys.filter( (keyItem) => keyItem === field ); @@ -323,10 +344,21 @@ class CriteriaCompletionChecker { } private doesItemMatchQueries(item: any, searchQueries: any[]): boolean { - const filteredSearchQueries = searchQueries.filter((searchQuery) => - Object.keys(item).includes(searchQuery.field) - ); - if (filteredSearchQueries.length === 0) { + let shouldReturn = false; + const filteredSearchQueries = searchQueries.filter((searchQuery) => { + if ( + searchQuery.field.startsWith(UPDATECART_ITEM_PREFIX) || + searchQuery.field.startsWith(PURCHASE_ITEM_PREFIX) + ) { + if (!Object.keys(item).includes(searchQuery.field)) { + shouldReturn = true; + return false; + } + return true; + } + return false; + }); + if (filteredSearchQueries.length === 0 || shouldReturn) { return false; } return filteredSearchQueries.every((query: any) => { @@ -356,7 +388,7 @@ class CriteriaCompletionChecker { case 'DoesNotEquals': return !this.compareValueEquality(matchObj, valueToCompare); case 'IsSet': - return matchObj !== ''; + return this.issetCheck(matchObj); case 'GreaterThan': case 'LessThan': case 'GreaterThanOrEqualTo': @@ -439,6 +471,16 @@ class CriteriaCompletionChecker { } } + private issetCheck(matchObj: string | object | any[]): boolean { + if (Array.isArray(matchObj)) { + return matchObj.length > 0; + } else if (typeof matchObj === 'object' && matchObj !== null) { + return Object.keys(matchObj).length > 0; + } else { + return matchObj !== ''; + } + } + private handleException(e: any) { console.error('Exception occurred', e.toString()); } diff --git a/src/anonymousUserTracking/tests/complexCriteria.test.ts b/src/anonymousUserTracking/tests/complexCriteria.test.ts new file mode 100644 index 00000000..ed7c6876 --- /dev/null +++ b/src/anonymousUserTracking/tests/complexCriteria.test.ts @@ -0,0 +1,1599 @@ +import { SHARED_PREFS_EVENT_LIST_KEY } from '../../constants'; +import CriteriaCompletionChecker from '../criteriaCompletionChecker'; + +const localStorageMock = { + getItem: jest.fn(), + setItem: jest.fn(), + removeItem: jest.fn() +}; + +describe('complexCriteria', () => { + beforeEach(() => { + (global as any).localStorage = localStorageMock; + }); + + // complex criteria + it('should return criteriaId 98 (complex criteria 1)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + items: [ + { + id: '12', + name: 'monitor', + price: 50, + quantity: 10 + } + ], + total: 50, + eventType: 'purchase' + }, + { + dataFields: { + preferred_car_models: 'Honda', + country: 'Japan' + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '98', + name: 'Custom Event', + createdAt: 1716560453973, + updatedAt: 1716560453973, + searchQuery: { + combinator: 'Or', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'eventName', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'customEvent', + id: 23, + value: 'button.clicked' + }, + { + field: 'button-clicked.animal', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'customEvent', + id: 25, + value: 'giraffe' + } + ] + } + }, + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'updateCart.updatedShoppingCartItems.price', + fieldType: 'double', + comparatorType: 'GreaterThanOrEqualTo', + dataType: 'customEvent', + id: 28, + value: '120' + }, + { + field: + 'updateCart.updatedShoppingCartItems.quantity', + fieldType: 'long', + comparatorType: 'GreaterThanOrEqualTo', + dataType: 'customEvent', + id: 29, + valueLong: 100, + value: '100' + } + ] + } + } + ] + }, + { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'shoppingCartItems.name', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'purchase', + id: 31, + value: 'monitor' + }, + { + field: 'shoppingCartItems.quantity', + fieldType: 'long', + comparatorType: 'GreaterThanOrEqualTo', + dataType: 'purchase', + id: 32, + valueLong: 5, + value: '5' + } + ] + } + }, + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'country', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'user', + id: 34, + value: 'Japan' + }, + { + field: 'preferred_car_models', + fieldType: 'string', + comparatorType: 'Contains', + dataType: 'user', + id: 36, + value: 'Honda' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual('98'); + }); + + it('should return null (complex criteria 1 fail)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + items: [ + { + id: '12', + name: 'monitor', + price: 50, + quantity: 10 + } + ], + total: 50, + eventType: 'purchase' + }, + { + dataFields: { + preferred_car_models: 'Honda' + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '98', + name: 'Custom Event', + createdAt: 1716560453973, + updatedAt: 1716560453973, + searchQuery: { + combinator: 'Or', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'eventName', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'customEvent', + id: 23, + value: 'button.clicked' + }, + { + field: 'button-clicked.animal', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'customEvent', + id: 25, + value: 'giraffe' + } + ] + } + }, + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'updateCart.updatedShoppingCartItems.price', + fieldType: 'double', + comparatorType: 'GreaterThanOrEqualTo', + dataType: 'customEvent', + id: 28, + value: '120' + }, + { + field: + 'updateCart.updatedShoppingCartItems.quantity', + fieldType: 'long', + comparatorType: 'GreaterThanOrEqualTo', + dataType: 'customEvent', + id: 29, + valueLong: 100, + value: '100' + } + ] + } + } + ] + }, + { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'shoppingCartItems.name', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'purchase', + id: 31, + value: 'monitor' + }, + { + field: 'shoppingCartItems.quantity', + fieldType: 'long', + comparatorType: 'GreaterThanOrEqualTo', + dataType: 'purchase', + id: 32, + valueLong: 5, + value: '5' + } + ] + } + }, + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'country', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'user', + id: 34, + value: 'Japan' + }, + { + field: 'preferred_car_models', + fieldType: 'string', + comparatorType: 'Contains', + dataType: 'user', + id: 36, + value: 'Honda' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual(null); + }); + + it('should return criteriaId 99 (complex criteria 2)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + items: [{ id: '12', name: 'Mocha', price: 90, quantity: 50 }], + total: 50, + eventType: 'cartUpdate' + }, + { + items: [{ id: '12', name: 'Mocha', price: 90, quantity: 50 }], + total: 50, + eventType: 'cartUpdate' + }, + { + dataFields: { + preferred_car_models: 'Subaru', + country: 'USA' + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '99', + name: 'Custom Event', + createdAt: 1716560453973, + updatedAt: 1716560453973, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'eventName', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'customEvent', + id: 2, + value: 'button-clicked' + }, + { + field: 'button-clicked.lastPageViewed', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'customEvent', + id: 4, + value: 'welcome page' + } + ] + } + }, + { + dataType: 'customEvent', + minMatch: 2, + maxMatch: 3, + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'updateCart.updatedShoppingCartItems.price', + fieldType: 'double', + comparatorType: 'GreaterThanOrEqualTo', + dataType: 'customEvent', + id: 6, + value: '85' + }, + { + field: + 'updateCart.updatedShoppingCartItems.quantity', + fieldType: 'long', + comparatorType: 'GreaterThanOrEqualTo', + dataType: 'customEvent', + id: 7, + valueLong: 50, + value: '50' + } + ] + } + } + ] + }, + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'shoppingCartItems.name', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'purchase', + id: 16, + isFiltering: false, + value: 'coffee' + }, + { + field: 'shoppingCartItems.quantity', + fieldType: 'long', + comparatorType: 'GreaterThanOrEqualTo', + dataType: 'purchase', + id: 17, + valueLong: 2, + value: '2' + } + ] + } + }, + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'country', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'user', + id: 19, + value: 'USA' + }, + { + field: 'preferred_car_models', + fieldType: 'string', + comparatorType: 'Contains', + dataType: 'user', + id: 21, + value: 'Subaru' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual('99'); + }); + + it('should return null (complex criteria 2 fail)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + items: [{ id: '12', name: 'Mocha', price: 90, quantity: 50 }], + total: 50, + eventType: 'cartUpdate' + }, + { + dataFields: { + preferred_car_models: 'Subaru', + country: 'USA' + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '99', + name: 'Custom Event', + createdAt: 1716560453973, + updatedAt: 1716560453973, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'eventName', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'customEvent', + id: 2, + value: 'button-clicked' + }, + { + field: 'button-clicked.lastPageViewed', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'customEvent', + id: 4, + value: 'welcome page' + } + ] + } + }, + { + dataType: 'customEvent', + minMatch: 2, + maxMatch: 3, + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'updateCart.updatedShoppingCartItems.price', + fieldType: 'double', + comparatorType: 'GreaterThanOrEqualTo', + dataType: 'customEvent', + id: 6, + value: '85' + }, + { + field: + 'updateCart.updatedShoppingCartItems.quantity', + fieldType: 'long', + comparatorType: 'GreaterThanOrEqualTo', + dataType: 'customEvent', + id: 7, + valueLong: 50, + value: '50' + } + ] + } + } + ] + }, + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'shoppingCartItems.name', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'purchase', + id: 16, + isFiltering: false, + value: 'coffee' + }, + { + field: 'shoppingCartItems.quantity', + fieldType: 'long', + comparatorType: 'GreaterThanOrEqualTo', + dataType: 'purchase', + id: 17, + valueLong: 2, + value: '2' + } + ] + } + }, + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'country', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'user', + id: 19, + value: 'USA' + }, + { + field: 'preferred_car_models', + fieldType: 'string', + comparatorType: 'Contains', + dataType: 'user', + id: 21, + value: 'Subaru' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual(null); + }); + + it('should return criteriaId 100 (complex criteria 3)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + items: [{ id: '12', name: 'Mocha', price: 90, quantity: 50 }], + total: 50, + eventType: 'cartUpdate' + }, + { + items: [{ id: '12', name: 'Mocha', price: 90, quantity: 50 }], + total: 50, + eventType: 'cartUpdate' + }, + + { + dataFields: { + preferred_car_models: 'Subaru', + country: 'USA' + }, + eventType: 'user' + }, + { + eventName: 'button-clicked', + dataFields: { + 'button-clicked.lastPageViewed': 'welcome page' + }, + eventType: 'customEvent' + }, + { + items: [ + { + id: '12', + name: 'coffee', + price: 10, + quantity: 5 + } + ], + total: 2, + eventType: 'purchase' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '100', + name: 'Custom Event', + createdAt: 1716560453973, + updatedAt: 1716560453973, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'eventName', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'customEvent', + id: 2, + value: 'button-clicked' + }, + { + field: 'button-clicked.lastPageViewed', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'customEvent', + id: 4, + value: 'welcome page' + } + ] + } + }, + { + dataType: 'customEvent', + minMatch: 2, + maxMatch: 3, + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'updateCart.updatedShoppingCartItems.price', + fieldType: 'double', + comparatorType: 'GreaterThanOrEqualTo', + dataType: 'customEvent', + id: 6, + value: '85' + }, + { + field: + 'updateCart.updatedShoppingCartItems.quantity', + fieldType: 'long', + comparatorType: 'GreaterThanOrEqualTo', + dataType: 'customEvent', + id: 7, + valueLong: 50, + value: '50' + } + ] + } + }, + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'shoppingCartItems.name', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'purchase', + id: 9, + value: 'coffee' + }, + { + field: 'shoppingCartItems.quantity', + fieldType: 'long', + comparatorType: 'GreaterThanOrEqualTo', + dataType: 'purchase', + id: 10, + valueLong: 2, + value: '2' + } + ] + } + }, + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'country', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'user', + id: 12, + value: 'USA' + }, + { + field: 'preferred_car_models', + fieldType: 'string', + comparatorType: 'Contains', + dataType: 'user', + id: 14, + value: 'Subaru' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual('100'); + }); + + it('should return null (complex criteria 3 fail)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + items: [{ id: '12', name: 'Mocha', price: 90, quantity: 50 }], + total: 50, + eventType: 'cartUpdate' + }, + { + items: [{ id: '12', name: 'Mocha', price: 90, quantity: 50 }], + total: 50, + eventType: 'cartUpdate' + }, + + { + dataFields: { + preferred_car_models: 'Subaru', + country: 'USA' + }, + eventType: 'user' + }, + { + eventName: 'button-clicked', + dataFields: { + 'button-clicked.lastPageViewed': 'welcome page' + }, + eventType: 'customEvent' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '100', + name: 'Custom Event', + createdAt: 1716560453973, + updatedAt: 1716560453973, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'eventName', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'customEvent', + id: 2, + value: 'button-clicked' + }, + { + field: 'button-clicked.lastPageViewed', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'customEvent', + id: 4, + value: 'welcome page' + } + ] + } + }, + { + dataType: 'customEvent', + minMatch: 2, + maxMatch: 3, + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'updateCart.updatedShoppingCartItems.price', + fieldType: 'double', + comparatorType: 'GreaterThanOrEqualTo', + dataType: 'customEvent', + id: 6, + value: '85' + }, + { + field: + 'updateCart.updatedShoppingCartItems.quantity', + fieldType: 'long', + comparatorType: 'GreaterThanOrEqualTo', + dataType: 'customEvent', + id: 7, + valueLong: 50, + value: '50' + } + ] + } + }, + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'shoppingCartItems.name', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'purchase', + id: 9, + value: 'coffee' + }, + { + field: 'shoppingCartItems.quantity', + fieldType: 'long', + comparatorType: 'GreaterThanOrEqualTo', + dataType: 'purchase', + id: 10, + valueLong: 2, + value: '2' + } + ] + } + }, + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'country', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'user', + id: 12, + value: 'USA' + }, + { + field: 'preferred_car_models', + fieldType: 'string', + comparatorType: 'Contains', + dataType: 'user', + id: 14, + value: 'Subaru' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual(null); + }); + + it('should return criteriaId 101 (complex criteria 4)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + items: [ + { id: '12', name: 'sneakers', price: 10, quantity: 5 }, + { id: '13', name: 'slippers', price: 10, quantity: 3 } + ], + total: 2, + eventType: 'purchase' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '101', + name: 'Complex Criteria 4: (NOT 9) AND 10', + createdAt: 1719328083918, + updatedAt: 1719328083918, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'Not', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + field: 'shoppingCartItems.name', + comparatorType: 'Equals', + value: 'sneakers', + fieldType: 'string' + }, + { + dataType: 'purchase', + field: 'shoppingCartItems.quantity', + comparatorType: 'LessThanOrEqualTo', + value: '3', + fieldType: 'long' + } + ] + } + } + ] + }, + { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + field: 'shoppingCartItems.name', + comparatorType: 'Equals', + value: 'slippers', + fieldType: 'string' + }, + { + dataType: 'purchase', + field: 'shoppingCartItems.quantity', + comparatorType: 'GreaterThanOrEqualTo', + value: '3', + fieldType: 'long' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual('101'); + }); + + it('should return null (complex criteria 4 fail)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + items: [ + { id: '12', name: 'sneakers', price: 10, quantity: 2 }, + { id: '13', name: 'slippers', price: 10, quantity: 3 } + ], + total: 2, + eventType: 'purchase' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '101', + name: 'Complex Criteria 4: (NOT 9) AND 10', + createdAt: 1719328083918, + updatedAt: 1719328083918, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'Not', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + field: 'shoppingCartItems.name', + comparatorType: 'Equals', + value: 'sneakers', + fieldType: 'string' + }, + { + dataType: 'purchase', + field: 'shoppingCartItems.quantity', + comparatorType: 'LessThanOrEqualTo', + value: '3', + fieldType: 'long' + } + ] + } + } + ] + }, + { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + field: 'shoppingCartItems.name', + comparatorType: 'Equals', + value: 'slippers', + fieldType: 'string' + }, + { + dataType: 'purchase', + field: 'shoppingCartItems.quantity', + comparatorType: 'GreaterThanOrEqualTo', + value: '3', + fieldType: 'long' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual(null); + }); + + it('should return criteriaId 134 (Min-Max 2)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + items: [{ id: '12', name: 'Mocha', price: 50, quantity: 50 }], + eventType: 'cartUpdate' + }, + { + items: [{ id: '12', name: 'Mocha', price: 50.0, quantity: 50 }], + eventType: 'cartUpdate' + }, + { + dataFields: { + preferred_car_models: 'Honda' + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '134', + name: 'Min-Max 2', + createdAt: 1719336370734, + updatedAt: 1719337067199, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'updateCart.updatedShoppingCartItems.price', + comparatorType: 'Equals', + value: '50.0', + fieldType: 'double' + } + ] + }, + minMatch: 2, + maxMatch: 3 + }, + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + field: 'preferred_car_models', + comparatorType: 'Equals', + value: 'Honda', + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual('134'); + }); + + it('should return criteriaId 151 (sampleTest1)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + 'animal-found.type': 'cat', + 'animal-found.count': 4 + }, + eventType: 'customEvent' + }, + { + items: [{ id: '12', name: 'Caramel', price: 3, quantity: 5 }], + total: 2, + eventType: 'purchase' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '151', + name: 'test criteria', + createdAt: 1719336370734, + updatedAt: 1719337067199, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'animal-found.type', + comparatorType: 'Equals', + value: 'cat', + fieldType: 'string' + }, + { + dataType: 'customEvent', + field: 'animal-found.count', + comparatorType: 'LessThan', + value: '5', + fieldType: 'long' + } + ] + } + }, + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'updateCart.updatedShoppingCartItems.price', + comparatorType: 'GreaterThanOrEqualTo', + value: '500', + fieldType: 'double' + }, + { + dataType: 'customEvent', + field: 'updateCart.updatedShoppingCartItems.price', + comparatorType: 'LessThan', + value: '20', + fieldType: 'double' + } + ] + } + } + ] + }, + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + field: 'shoppingCartItems.name', + comparatorType: 'Equals', + value: 'Caramel', + fieldType: 'string' + }, + { + dataType: 'purchase', + field: 'shoppingCartItems.quantity', + comparatorType: 'GreaterThanOrEqualTo', + value: '2', + fieldType: 'long' + } + ] + } + }, + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + field: 'country', + comparatorType: 'Equals', + value: 'UK', + fieldType: 'string' + }, + { + dataType: 'user', + field: 'likes_boba', + comparatorType: 'Equals', + value: 'false', + fieldType: 'boolean' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual('151'); + }); + + it('should return null (sampleTest1 fail)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + 'animal-found.type': 'dog', + 'animal-found.count': 4 + }, + eventType: 'customEvent' + }, + { + items: [{ id: '12', name: 'Caramel', price: 3, quantity: 5 }], + total: 2, + eventType: 'purchase' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '151', + name: 'test criteria', + createdAt: 1719336370734, + updatedAt: 1719337067199, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'animal-found.type', + comparatorType: 'Equals', + value: 'cat', + fieldType: 'string' + }, + { + dataType: 'customEvent', + field: 'animal-found.count', + comparatorType: 'LessThan', + value: '5', + fieldType: 'long' + } + ] + } + }, + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'updateCart.updatedShoppingCartItems.price', + comparatorType: 'GreaterThanOrEqualTo', + value: '500', + fieldType: 'double' + }, + { + dataType: 'customEvent', + field: 'updateCart.updatedShoppingCartItems.price', + comparatorType: 'LessThan', + value: '20', + fieldType: 'double' + } + ] + } + } + ] + }, + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + field: 'shoppingCartItems.name', + comparatorType: 'Equals', + value: 'Caramel', + fieldType: 'string' + }, + { + dataType: 'purchase', + field: 'shoppingCartItems.quantity', + comparatorType: 'GreaterThanOrEqualTo', + value: '2', + fieldType: 'long' + } + ] + } + }, + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + field: 'country', + comparatorType: 'Equals', + value: 'UK', + fieldType: 'string' + }, + { + dataType: 'user', + field: 'likes_boba', + comparatorType: 'Equals', + value: 'false', + fieldType: 'boolean' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual(null); + }); +}); diff --git a/src/anonymousUserTracking/tests/criteriaCompletionChecker.test.ts b/src/anonymousUserTracking/tests/criteriaCompletionChecker.test.ts index f8e866ff..b0396563 100644 --- a/src/anonymousUserTracking/tests/criteriaCompletionChecker.test.ts +++ b/src/anonymousUserTracking/tests/criteriaCompletionChecker.test.ts @@ -722,4 +722,727 @@ describe('CriteriaCompletionChecker', () => { ); expect(result).toEqual('6'); }); + + // isSet criteria + it('should return criteriaId 97 if isset user criteria is matched', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + country: 'UK', + eventTimeStamp: 10, + phoneNumberDetails: '99999999', + 'shoppingCartItems.price': 50.5 + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '97', + name: 'User', + createdAt: 1716560453973, + updatedAt: 1716560453973, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'country', + fieldType: 'string', + comparatorType: 'IsSet', + dataType: 'user', + id: 25, + value: '' + }, + { + field: 'eventTimeStamp', + fieldType: 'long', + comparatorType: 'IsSet', + dataType: 'user', + id: 26, + valueLong: null, + value: '' + }, + { + field: 'phoneNumberDetails', + fieldType: 'object', + comparatorType: 'IsSet', + dataType: 'user', + id: 28, + value: '' + }, + { + field: 'shoppingCartItems.price', + fieldType: 'double', + comparatorType: 'IsSet', + dataType: 'user', + id: 30, + value: '' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual('97'); + }); + + it('should return null (isset user criteria fail)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + eventTimeStamp: 10, + phoneNumberDetails: '99999999', + 'shoppingCartItems.price': 50.5 + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '97', + name: 'User', + createdAt: 1716560453973, + updatedAt: 1716560453973, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'country', + fieldType: 'string', + comparatorType: 'IsSet', + dataType: 'user', + id: 25, + value: '' + }, + { + field: 'eventTimeStamp', + fieldType: 'long', + comparatorType: 'IsSet', + dataType: 'user', + id: 26, + valueLong: null, + value: '' + }, + { + field: 'phoneNumberDetails', + fieldType: 'object', + comparatorType: 'IsSet', + dataType: 'user', + id: 28, + value: '' + }, + { + field: 'shoppingCartItems.price', + fieldType: 'double', + comparatorType: 'IsSet', + dataType: 'user', + id: 30, + value: '' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual(null); + }); + + it('should return criteriaId 94 if isset customEvent criteria is matched', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + eventName: 'button-clicked', + dataFields: { + 'button-clicked': 'signup page', + 'button-clicked.animal': 'test page', + 'button-clicked.clickCount': '2', + total: 3 + }, + createdAt: 1700071052507, + eventType: 'customEvent' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '94', + name: 'Custom Event', + createdAt: 1716560453973, + updatedAt: 1716560453973, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'button-clicked', + fieldType: 'object', + comparatorType: 'IsSet', + dataType: 'customEvent', + id: 2, + value: '' + }, + { + field: 'button-clicked.animal', + fieldType: 'string', + comparatorType: 'IsSet', + dataType: 'customEvent', + id: 4, + value: '' + }, + { + field: 'button-clicked.clickCount', + fieldType: 'long', + comparatorType: 'IsSet', + dataType: 'customEvent', + id: 5, + valueLong: null, + value: '' + }, + { + field: 'total', + fieldType: 'double', + comparatorType: 'IsSet', + dataType: 'customEvent', + id: 9, + value: '' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual('94'); + }); + + it('should return null (isset customEvent criteria fail)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + eventName: 'button-clicked', + dataFields: { + 'button-clicked.animal': 'test page', + total: 3 + }, + createdAt: 1700071052507, + eventType: 'customEvent' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '94', + name: 'Custom Event', + createdAt: 1716560453973, + updatedAt: 1716560453973, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'button-clicked', + fieldType: 'object', + comparatorType: 'IsSet', + dataType: 'customEvent', + id: 2, + value: '' + }, + { + field: 'button-clicked.animal', + fieldType: 'string', + comparatorType: 'IsSet', + dataType: 'customEvent', + id: 4, + value: '' + }, + { + field: 'button-clicked.clickCount', + fieldType: 'long', + comparatorType: 'IsSet', + dataType: 'customEvent', + id: 5, + valueLong: null, + value: '' + }, + { + field: 'total', + fieldType: 'double', + comparatorType: 'IsSet', + dataType: 'customEvent', + id: 9, + value: '' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual(null); + }); + + it('should return criteriaId 96 if isset purchase criteria is matched', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + items: [ + { + id: '12', + name: 'monitor', + price: 10, + quantity: 10 + } + ], + total: 50, + eventType: 'purchase' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '96', + name: 'Purchase', + createdAt: 1716560453973, + updatedAt: 1716560453973, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'shoppingCartItems', + fieldType: 'object', + comparatorType: 'IsSet', + dataType: 'purchase', + id: 1, + value: '' + }, + { + field: 'shoppingCartItems.price', + fieldType: 'double', + comparatorType: 'IsSet', + dataType: 'purchase', + id: 3, + value: '' + }, + { + field: 'shoppingCartItems.name', + fieldType: 'string', + comparatorType: 'IsSet', + dataType: 'purchase', + id: 5, + value: '' + }, + { + field: 'total', + fieldType: 'double', + comparatorType: 'IsSet', + dataType: 'purchase', + id: 7, + value: '' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual('96'); + }); + + it('should return null (isset purchase criteria fail)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + items: [ + { + id: '12', + name: 'monitor', + quantity: 10 + } + ], + total: 50, + eventType: 'purchase' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '96', + name: 'Purchase', + createdAt: 1716560453973, + updatedAt: 1716560453973, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'shoppingCartItems', + fieldType: 'object', + comparatorType: 'IsSet', + dataType: 'purchase', + id: 1, + value: '' + }, + { + field: 'shoppingCartItems.price', + fieldType: 'double', + comparatorType: 'IsSet', + dataType: 'purchase', + id: 3, + value: '' + }, + { + field: 'shoppingCartItems.name', + fieldType: 'string', + comparatorType: 'IsSet', + dataType: 'purchase', + id: 5, + value: '' + }, + { + field: 'total', + fieldType: 'double', + comparatorType: 'IsSet', + dataType: 'purchase', + id: 7, + value: '' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual(null); + }); + + it('should return criteriaId 95 if isset updateCart criteria is matched', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + items: [{ id: '12', name: 'Mocha', price: 50, quantity: 50 }], + eventType: 'cartUpdate' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '95', + name: 'UpdateCart: isSet Comparator', + createdAt: 1719328291857, + updatedAt: 1719328291857, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'updateCart', + comparatorType: 'IsSet', + value: '', + fieldType: 'object' + }, + { + dataType: 'customEvent', + field: 'updateCart.updatedShoppingCartItems.name', + comparatorType: 'IsSet', + value: '', + fieldType: 'string' + }, + { + dataType: 'customEvent', + field: 'updateCart.updatedShoppingCartItems.price', + comparatorType: 'IsSet', + value: '', + fieldType: 'double' + }, + { + dataType: 'customEvent', + field: + 'updateCart.updatedShoppingCartItems.quantity', + comparatorType: 'IsSet', + value: '', + fieldType: 'long' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual('95'); + }); + + it('should return null (isset updateCart criteria fail)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + items: [{ id: '12', name: 'Mocha', quantity: 50 }], + eventType: 'cartUpdate' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '95', + name: 'UpdateCart: isSet Comparator', + createdAt: 1719328291857, + updatedAt: 1719328291857, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'updateCart', + comparatorType: 'IsSet', + value: '', + fieldType: 'object' + }, + { + dataType: 'customEvent', + field: 'updateCart.updatedShoppingCartItems.name', + comparatorType: 'IsSet', + value: '', + fieldType: 'string' + }, + { + dataType: 'customEvent', + field: 'updateCart.updatedShoppingCartItems.price', + comparatorType: 'IsSet', + value: '', + fieldType: 'double' + }, + { + dataType: 'customEvent', + field: + 'updateCart.updatedShoppingCartItems.quantity', + comparatorType: 'IsSet', + value: '', + fieldType: 'long' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual(null); + }); }); diff --git a/src/constants.ts b/src/constants.ts index 18823f5d..38c59eca 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -299,8 +299,9 @@ export const UPDATE_USER = 'user'; export const TRACK_UPDATE_CART = 'cartUpdate'; export const UPDATE_CART = 'updateCart'; +export const PURCHASE_ITEM = 'shoppingCartItems'; export const UPDATECART_ITEM_PREFIX = 'updateCart.updatedShoppingCartItems.'; -export const PURCHASE_ITEM_PREFIX = 'shoppingCartItems.'; +export const PURCHASE_ITEM_PREFIX = PURCHASE_ITEM + '.'; export const MERGE_SUCCESSFULL = 'MERGE_SUCCESSFULL'; export const INITIALIZE_ERROR = From 7c04dba9ef20d2c0fb70e4a2e2cb38e9e02c6535 Mon Sep 17 00:00:00 2001 From: darshan-iterable Date: Wed, 31 Jul 2024 18:55:33 +0530 Subject: [PATCH 55/88] Fixed bools needs to be string (#422) --- react-example/src/views/AUTTesting.tsx | 2 +- .../criteriaCompletionChecker.ts | 7 +- .../tests/criteriaCompletionChecker.test.ts | 73 +++++++++++++++++++ 3 files changed, 78 insertions(+), 4 deletions(-) diff --git a/react-example/src/views/AUTTesting.tsx b/react-example/src/views/AUTTesting.tsx index 02151417..db1d6d43 100644 --- a/react-example/src/views/AUTTesting.tsx +++ b/react-example/src/views/AUTTesting.tsx @@ -40,7 +40,7 @@ export const AUTTesting: FC = () => { const [isUpdatingCart, setUpdatingCart] = useState(false); const [isTrackingPurchase, setTrackingPurchase] = useState(false); const [userDataField, setUserDataField] = useState( - ' { "dataFields": {"phone_number": "57688559" }}' + ' { "dataFields": {"phone_number": "57688559", "subscribed": true }}' ); const [isUpdatingUser, setUpdatingUser] = useState(false); const [updateUserResponse, setUpdateUserResponse] = useState( diff --git a/src/anonymousUserTracking/criteriaCompletionChecker.ts b/src/anonymousUserTracking/criteriaCompletionChecker.ts index 9b70a283..cbe2bc0a 100644 --- a/src/anonymousUserTracking/criteriaCompletionChecker.ts +++ b/src/anonymousUserTracking/criteriaCompletionChecker.ts @@ -412,13 +412,14 @@ class CriteriaCompletionChecker { private compareValueEquality(sourceTo: any, stringValue: string): boolean { if ( (typeof sourceTo === 'number' || typeof sourceTo === 'boolean') && - stringValue !== '' && - !isNaN(parseFloat(stringValue)) + stringValue !== '' ) { - if (typeof sourceTo === 'number') { + if (typeof sourceTo === 'number' && !isNaN(parseFloat(stringValue))) { return sourceTo === parseFloat(stringValue); } else if (typeof sourceTo === 'boolean') { return sourceTo === (stringValue === 'true'); + } else { + return false; } } else if (typeof sourceTo === 'string') { return sourceTo === stringValue; diff --git a/src/anonymousUserTracking/tests/criteriaCompletionChecker.test.ts b/src/anonymousUserTracking/tests/criteriaCompletionChecker.test.ts index b0396563..8b564656 100644 --- a/src/anonymousUserTracking/tests/criteriaCompletionChecker.test.ts +++ b/src/anonymousUserTracking/tests/criteriaCompletionChecker.test.ts @@ -1445,4 +1445,77 @@ describe('CriteriaCompletionChecker', () => { ); expect(result).toEqual(null); }); + + it('should return criteriaId 100 (boolean test)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + subscribed: true, + phoneNumber: '99999999' + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '100', + name: 'User', + createdAt: 1716560453973, + updatedAt: 1716560453973, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'subscribed', + fieldType: 'boolean', + comparatorType: 'Equals', + dataType: 'user', + id: 25, + value: 'true' + }, + { + field: 'phoneNumber', + fieldType: 'String', + comparatorType: 'IsSet', + dataType: 'user', + id: 28, + value: '' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual('100'); + }); }); From c4035211c17729e534bf35edbfdd0f47538d52f6 Mon Sep 17 00:00:00 2001 From: darshan-iterable Date: Mon, 5 Aug 2024 19:38:18 +0530 Subject: [PATCH 56/88] MOB-9055: Resolve nested criteria match issue (#423) * MOB-9055: Resolve nested criteria match issue * converted an array operator as opposed to a for-loop --- .../complexCriteria.test.ts | 4 ++- .../criteriaCompletionChecker.ts | 28 +++++++++++++++++++ .../tests/complexCriteria.test.ts | 8 ++++-- .../tests/criteriaCompletionChecker.test.ts | 12 ++++++-- 4 files changed, 45 insertions(+), 7 deletions(-) diff --git a/src/anonymousUserTracking/complexCriteria.test.ts b/src/anonymousUserTracking/complexCriteria.test.ts index c7284341..df29440c 100644 --- a/src/anonymousUserTracking/complexCriteria.test.ts +++ b/src/anonymousUserTracking/complexCriteria.test.ts @@ -694,7 +694,9 @@ describe('complexCriteria', () => { { eventName: 'button-clicked', dataFields: { - 'button-clicked.lastPageViewed': 'welcome page' + 'button-clicked': { + lastPageViewed: 'welcome page' + } }, eventType: 'customEvent' }, diff --git a/src/anonymousUserTracking/criteriaCompletionChecker.ts b/src/anonymousUserTracking/criteriaCompletionChecker.ts index cbe2bc0a..85c00195 100644 --- a/src/anonymousUserTracking/criteriaCompletionChecker.ts +++ b/src/anonymousUserTracking/criteriaCompletionChecker.ts @@ -328,9 +328,21 @@ class CriteriaCompletionChecker { return true; } } + + if (field.includes('.') && query.comparatorType !== 'IsSet') { + const valueFromObj = this.getValueFromNestedObject(eventData, field); + if (valueFromObj) { + return this.evaluateComparison( + query.comparatorType, + valueFromObj, + query.value ? query.value : '' + ); + } + } const eventKeyItems = filteredLocalDataKeys.filter( (keyItem) => keyItem === field ); + if (eventKeyItems.length) { return this.evaluateComparison( query.comparatorType, @@ -343,6 +355,22 @@ class CriteriaCompletionChecker { return matchResult; } + private getValueFromNestedObject(eventData: any, field: string): any { + const valueFromObj = this.getFieldValue(eventData, field); + if (typeof valueFromObj === 'object') { + return Object.keys(valueFromObj).map((key) => + this.getValueFromNestedObject(valueFromObj, key) + ); + } else { + return valueFromObj; + } + } + + private getFieldValue(data: any, field: string): any { + const fields = field.split('.'); + return fields.reduce((acc, field) => acc?.[field], data); + } + private doesItemMatchQueries(item: any, searchQueries: any[]): boolean { let shouldReturn = false; const filteredSearchQueries = searchQueries.filter((searchQuery) => { diff --git a/src/anonymousUserTracking/tests/complexCriteria.test.ts b/src/anonymousUserTracking/tests/complexCriteria.test.ts index ed7c6876..c58280bc 100644 --- a/src/anonymousUserTracking/tests/complexCriteria.test.ts +++ b/src/anonymousUserTracking/tests/complexCriteria.test.ts @@ -694,7 +694,7 @@ describe('complexCriteria', () => { { eventName: 'button-clicked', dataFields: { - 'button-clicked.lastPageViewed': 'welcome page' + 'button-clicked': { lastPageViewed: 'welcome page' } }, eventType: 'customEvent' }, @@ -1309,8 +1309,10 @@ describe('complexCriteria', () => { return JSON.stringify([ { dataFields: { - 'animal-found.type': 'cat', - 'animal-found.count': 4 + 'animal-found': { + type: 'cat', + count: 4 + } }, eventType: 'customEvent' }, diff --git a/src/anonymousUserTracking/tests/criteriaCompletionChecker.test.ts b/src/anonymousUserTracking/tests/criteriaCompletionChecker.test.ts index 8b564656..21da9c2e 100644 --- a/src/anonymousUserTracking/tests/criteriaCompletionChecker.test.ts +++ b/src/anonymousUserTracking/tests/criteriaCompletionChecker.test.ts @@ -38,7 +38,13 @@ describe('CriteriaCompletionChecker', () => { { eventName: 'testEvent', createdAt: 1708494757530, - dataFields: { 'browserVisit.website.domain': 'google.com' }, + dataFields: { + browserVisit: { + website: { + domain: 'google.com' + } + } + }, createNewFields: true, eventType: 'customEvent' } @@ -109,7 +115,7 @@ describe('CriteriaCompletionChecker', () => { { eventName: 'testEvent', createdAt: 1708494757530, - dataFields: { 'browserVisit.website.domain': 'google.com' }, + dataFields: { browserVisit: { website: { domain: 'google.com' } } }, createNewFields: true, eventType: 'customEvent' } @@ -1008,7 +1014,7 @@ describe('CriteriaCompletionChecker', () => { { eventName: 'button-clicked', dataFields: { - 'button-clicked.animal': 'test page', + 'button-clicked': { animal: 'test page' }, total: 3 }, createdAt: 1700071052507, From 85a2d2249679bdcc23bde4c40871447c0b601674 Mon Sep 17 00:00:00 2001 From: darshan-iterable Date: Tue, 6 Aug 2024 18:50:32 +0530 Subject: [PATCH 57/88] MOB-9138: Resolves DoesNotEqual criteria match issue (#426) --- react-example/src/views/AUTTesting.tsx | 2 +- .../criteriaCompletionChecker.ts | 2 +- .../tests/criteriaCompletionChecker.test.ts | 266 ++++++++++++++++++ 3 files changed, 268 insertions(+), 2 deletions(-) diff --git a/react-example/src/views/AUTTesting.tsx b/react-example/src/views/AUTTesting.tsx index db1d6d43..3c5fe0a2 100644 --- a/react-example/src/views/AUTTesting.tsx +++ b/react-example/src/views/AUTTesting.tsx @@ -40,7 +40,7 @@ export const AUTTesting: FC = () => { const [isUpdatingCart, setUpdatingCart] = useState(false); const [isTrackingPurchase, setTrackingPurchase] = useState(false); const [userDataField, setUserDataField] = useState( - ' { "dataFields": {"phone_number": "57688559", "subscribed": true }}' + ' { "dataFields": {"phoneNumber": "5768855911", "subscribed": true }}' ); const [isUpdatingUser, setUpdatingUser] = useState(false); const [updateUserResponse, setUpdateUserResponse] = useState( diff --git a/src/anonymousUserTracking/criteriaCompletionChecker.ts b/src/anonymousUserTracking/criteriaCompletionChecker.ts index 85c00195..794393a3 100644 --- a/src/anonymousUserTracking/criteriaCompletionChecker.ts +++ b/src/anonymousUserTracking/criteriaCompletionChecker.ts @@ -413,7 +413,7 @@ class CriteriaCompletionChecker { switch (comparatorType) { case 'Equals': return this.compareValueEquality(matchObj, valueToCompare); - case 'DoesNotEquals': + case 'DoesNotEqual': return !this.compareValueEquality(matchObj, valueToCompare); case 'IsSet': return this.issetCheck(matchObj); diff --git a/src/anonymousUserTracking/tests/criteriaCompletionChecker.test.ts b/src/anonymousUserTracking/tests/criteriaCompletionChecker.test.ts index 21da9c2e..87bf5eac 100644 --- a/src/anonymousUserTracking/tests/criteriaCompletionChecker.test.ts +++ b/src/anonymousUserTracking/tests/criteriaCompletionChecker.test.ts @@ -1524,4 +1524,270 @@ describe('CriteriaCompletionChecker', () => { ); expect(result).toEqual('100'); }); + + it('should return criteriaId 194 if Contact: Phone Number != 57688559', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + subscribed: true, + phoneNumber: '123685748641' + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '194', + name: 'Contact: Phone Number != 57688559', + createdAt: 1721337331194, + updatedAt: 1722338525737, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + field: 'phoneNumber', + comparatorType: 'DoesNotEqual', + value: '57688559', + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual('194'); + }); + + it('should return criteriaId 293 if Contact: subscribed != false', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + subscribed: true, + phoneNumber: '123685748641' + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '293', + name: 'Contact: subscribed != false', + createdAt: 1722605666776, + updatedAt: 1722606283109, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + field: 'subscribed', + comparatorType: 'DoesNotEqual', + value: 'false', + fieldType: 'boolean' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual('293'); + }); + + it('should return criteriaId 297 if Purchase: shoppingCartItems.quantity != 12345678', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + items: [ + { + id: '12', + name: 'monitor', + price: 50, + quantity: 10 + } + ], + total: 50, + eventType: 'purchase' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '297', + name: 'Purchase: shoppingCartItems.quantity != 12345678', + createdAt: 1722667099444, + updatedAt: 1722667361286, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + field: 'shoppingCartItems.quantity', + comparatorType: 'DoesNotEqual', + value: '12345678', + fieldType: 'long' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual('297'); + }); + + it('should return criteriaId 298 if Purchase: shoppingCartItems.price != 105', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + items: [ + { + id: '12', + name: 'monitor', + price: 50.5, + quantity: 10 + } + ], + total: 50, + eventType: 'purchase' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '298', + name: 'Purchase: shoppingCartItems.price != 105', + createdAt: 1722606251607, + updatedAt: 1722606295791, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + field: 'shoppingCartItems.price', + comparatorType: 'DoesNotEqual', + value: '105', + fieldType: 'double' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual('298'); + }); }); From 6102cd31f278d8547302335b9012928707e62e74 Mon Sep 17 00:00:00 2001 From: hani Date: Fri, 9 Aug 2024 15:23:48 +0530 Subject: [PATCH 58/88] Merge branch 'main' into AUT_main 2606561 --- .eslintrc | 40 +- .vscode/settings.json | 2 +- README.md | 2425 +++++++++++++---- package.json | 12 +- react-example/package.json | 11 +- react-example/src/components/Button.tsx | 8 +- react-example/src/components/EmbeddedForm.tsx | 32 +- react-example/src/components/EventsForm.tsx | 19 +- react-example/src/components/Link.tsx | 2 - react-example/src/components/LoginForm.tsx | 33 +- react-example/src/components/TextField.tsx | 2 - react-example/src/index.tsx | 36 +- react-example/src/views/AUTTesting.tsx | 40 +- react-example/src/views/Commerce.tsx | 17 +- react-example/src/views/Components.styled.ts | 6 +- react-example/src/views/Embedded.tsx | 14 +- react-example/src/views/EmbeddedMsgs.tsx | 25 +- .../views/EmbeddedMsgsImpressionTracker.tsx | 14 +- react-example/src/views/Events.tsx | 79 +- react-example/src/views/Home.tsx | 73 +- react-example/src/views/InApp.tsx | 22 +- react-example/src/views/Users.tsx | 32 +- react-example/yarn.lock | 27 +- src/__data__/inAppMessages.ts | 1 - .../anonymousUserEventManager.ts | 104 +- .../anonymousUserMerge.ts | 11 +- .../criteriaCompletionChecker.ts | 108 +- src/authorization/authorization.ts | 98 +- src/authorization/utils.ts | 21 +- src/commerce/commerce.ts | 16 +- src/components/banner/index.tsx | 13 +- src/components/banner/styles.ts | 2 +- src/components/card/index.tsx | 7 +- src/components/card/styles.ts | 2 +- src/components/notification/index.tsx | 13 +- src/components/notification/styles.ts | 2 +- src/components/types.ts | 20 +- src/constants.ts | 13 +- src/embedded/embeddedManager.ts | 30 +- src/embedded/embeddedMessageProcessor.ts | 1 + src/embedded/embeddedPlacement.ts | 187 +- src/embedded/embeddedSessionManager.ts | 22 +- .../iterableEmbeddedPlacement.test.ts | 8 +- src/embedded/types.ts | 1 + src/embedded/utils.ts | 31 +- src/events/embedded/events.ts | 2 +- src/events/events.test.ts | 2 +- src/events/events.ts | 13 +- src/events/{in-app => inapp}/events.schema.ts | 0 src/events/{in-app => inapp}/events.ts | 1 + src/events/{in-app => inapp}/types.ts | 0 src/events/index.ts | 4 +- src/inapp/cache.ts | 3 +- src/inapp/inapp.ts | 94 +- src/inapp/request.ts | 15 +- src/inapp/types.ts | 1 + src/inapp/utils.ts | 95 +- src/request.ts | 8 +- src/types.ts | 2 +- src/users/users.ts | 16 +- src/utils/IterableActionRunner.ts | 13 +- src/utils/IterableConfig.ts | 1 + src/utils/commonFunctions.ts | 7 +- src/utils/functions.ts | 21 +- src/utils/srSpeak.ts | 8 +- yarn.lock | 1003 +++++-- 66 files changed, 3620 insertions(+), 1371 deletions(-) rename src/events/{in-app => inapp}/events.schema.ts (100%) rename src/events/{in-app => inapp}/events.ts (98%) rename src/events/{in-app => inapp}/types.ts (100%) diff --git a/.eslintrc b/.eslintrc index 73a4790e..803267ee 100644 --- a/.eslintrc +++ b/.eslintrc @@ -8,18 +8,42 @@ "eslint:recommended", "plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended", - "plugin:prettier/recommended" + "plugin:prettier/recommended", + "airbnb-base" ], "ignorePatterns": [ "webpack.*.js" ], + "globals": { + "window": true, + "document": true + }, "rules": { + "comma-dangle": "off", + "implicit-arrow-linebreak": "off", "no-console": 1, + "no-underscore-dangle": "off", "no-duplicate-imports": 2, + "indent": "off", + "function-paren-newline": "off", + "no-confusing-arrow": "off", "no-extra-boolean-cast": "off", + "no-undef": "off", + "no-unused-vars": "off", + "operator-linebreak": "off", + "import/extensions": "off", + "import/prefer-default-export": "off", + "max-classes-per-file": "off", + "object-curly-newline": "off", + "no-shadow": "off", + "prefer-const": "error", + "@typescript-eslint/prefer-for-of": "error", "@typescript-eslint/camelcase": "off", "@typescript-eslint/comma-spacing": 2, "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/no-unused-vars": "error", + // come back and make this an error + "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/array-type": [ 2, { @@ -31,5 +55,17 @@ 2, "single" ] - } + }, + "settings": { + "import/resolver": { + "node": { + "extensions": [ + ".js", + ".jsx", + ".ts", + ".tsx" + ] + } + } + }, } \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 3662b370..8cfe9b37 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - "typescript.tsdk": "node_modules/typescript/lib" + "typescript.tsdk": "node_modules/typescript/lib", } \ No newline at end of file diff --git a/README.md b/README.md index 7e8dcb04..7f39c290 100644 --- a/README.md +++ b/README.md @@ -2,158 +2,233 @@ # Iterable's Web SDK -[Iterable](https://www.iterable.com) is a growth marketing platform that helps you to create better experiences for—and deeper relationships with—your customers. Use it to send customized email, SMS, push notification, in-app message, web push notification campaigns to your customers. +[Iterable](https://www.iterable.com) is a growth-marketing platform that helps +you to create better experiences for—and deeper relationships with—your customers. +Use it to send customized email, SMS, push notification, in-app message, +embedded message, and web push notification campaigns to your customers. -This SDK helps you integrate your Web apps with Iterable. +This SDK helps you integrate your web apps with Iterable. # Table of contents +- [Other documentation](#other-documentation) - [Installation](#installation) -- [API](#api) -- [Usage](#usage) +- [Functions](#functions) +- [Classes, interfaces, types, and enums](#classes-interfaces-types-and-enums) - [FAQ](#faq) -- [A Note About Imports](#a-note-about-imports) -- [About Links](#about-links) +- [Link handling](#link-handling) - [TypeScript](#typescript) - [Contributing](#contributing) - [License](#license) +# Other documentation + +This document contains reference information about the Web SDK. For other +information, please see: + +- [In-Browser Messaging Overview](https://support.iterable.com/hc/articles/4418166649748) +- [Embedded Messaging Overview](https://support.iterable.com/hc/articles/23060529977364) +- [Overview of Iterable's Web SDK](https://support.iterable.com/hc/articles/10359708795796) +- [Setting up Iterable's Web SDK](https://support.iterable.com/hc/articles/4419628585364) +- [Embedded Messages with Iterable's Web SDK](https://support.iterable.com/hc/articles/27537816889108) + +# Using the SDK + +In general, to use the SDK, you'll need to follow these steps: + +1. In Iterable, [create a JWT-enabled, web API key](https://support.iterable.com/hc/articles/360043464871). + The SDK can use this key to authenticate with Iterable's API endpoints. This + will ensure the SDK has access to all the necessary. Save the API key and + its associated JWT secret, since you'll need them both. + +2. Work with your Engineering team to create a web service the SDK can call + to fetch a valid JWT token for the signed-in user. To learn more about how + to do this, read [JWT-Enabled API Keys](https://support.iterable.com/hc/articles/360050801231). + +3. [Install](#installation) the SDK in your web app. + +4. Use the API key to initialize the SDK, as described in [`initialize`](#initialize) + and [`initializeWithConfig`](#initializewithconfig). When you initialize the + SDK, pass in a method that can call the web service (created in step 2) + to fetch a valid JWT token for the signed-in user. + +5. To identify the user to the SDK, call `setEmail` or `setUserID` (returned by + [`initialize`](#initialize) or [`initializeWithConfig`](#initializewithconfig)). + The SDK uses the user's `email` or `userId` to fetch a valid JWT token from + your server. + +6. After the SDK successfully sets the user's `email` or `userId` and the SDK + fetches a JWT token, you can make API requests to Iterable. For example, you + can call [`track`](#track) to track events, or [`getInAppMessages`](#getinappmessages) + to fetch in-app messages, etc. Other methods are described [below](#functions). + # Installation -To install this SDK through NPM: +To install the SDK, use Yarn, npm, or a `script` tag: -``` -$ npm install @iterable/web-sdk -``` +- npm -with yarn: + ``` + npm install @iterable/web-sdk + ``` -``` -$ yarn add @iterable/web-sdk -``` +- Yarn -or with a CDN: + ``` + yarn add @iterable/web-sdk + ``` -```js - -``` +- `script` tag -# Iterable's European data center (EUDC) + ```js + + ``` -If your Iterable project is hosted on Iterable's [European data center (EUDC)](https://support.iterable.com/hc/articles/17572750887444), you'll need to configure Iterable's Web SDK to interact with Iterable's EU-based API endpoints. +# Functions -To do this, on the web server that hosts your site, set the `IS_EU_ITERABLE_SERVICE` environment variable to `true`. Some customers have reported issues with setting the environment variable. If you run into this, try migrating to [`initializeWithConfig`](#initializeWithConfig). You can then turn on the EU API usage by making these changes: +Iterable's Web SDK exposes the following functions, which you can use in your +website code. -```ts -import { initializeWithConfig } from '@iterable/web-sdk'; +For information about the data the SDK sends and receives when making calls to +Iterable's API, see the [API Overview](https://support.iterable.com/hc/articles/204780579). -const { clearRefresh, setEmail, setUserID, logout } = initializeWithConfig({ - authToken: 'my-API-key', - configOptions: { - isEuIterableService: true, - }, - /* - _email_ will be defined if you call _setEmail_ - _userID_ will be defined if you call _setUserID_ - */ - generateJWT: ({ email, userID }) => - yourAsyncJWTGeneratorMethod({ email, userID }).then( - ({ jwt_token }) => jwt_token - ) -} -); -``` +| Method Name | Description | +| --------------------------------------------------------------------------------- | ----------- | +| [`filterHiddenInAppMessages`](#filterhiddeninappmessages) | From an array of passed-in in-app messages, filters out messages that have already been read, messages that should not be displayed, and messages that only contain JSON data. | +| [`filterOnlyReadAndNeverTriggerMessages`](#filteronlyreadandnevertriggermessages) | From an array of passed-in in-app messages, filters out messages that have already been read and messages that should not be displayed. | +| [`getInAppMessages`](#getInAppMessages) | Fetches in-app messages by calling [`GET /api/inApp/getMessages`](https://support.iterable.com/hc/articles/204780579#get-api-inapp-getmessages). | +| [`initialize`](#initialize) | Initializes the SDK with an API key and a JWT refresh method. Returns methods you can use to identify the current user, work with JWT tokens, and log the user out (see [`WithJWT`](#withjwt)). | +| [`initializeWithConfig`](#initializeWithConfig) | Similar to `initialize`, but also takes a set of configuration options as a parameter. Returns methods you can use to identify the current user, work with JWT tokens, and log the user out (see [`WithJWT`](#withjwt)). | +| [`IterableEmbeddedCard`](#iterableembeddedcard) | Returns a string of the HTML for an out-of-the-box [card](https://support.iterable.com/hc/articles/23230946708244#cards) view for an embedded message. | +| [`IterableEmbeddedBanner`](#iterableembeddedbanner) | Returns a string of the HTML for an out-of-the-box [banner](https://support.iterable.com/hc/articles/23230946708244#banners) view for an embedded message. | +| [`IterableEmbeddedNotification`](#iterableembeddednotification) | Returns a string of the HTML for an out-of-the-box [notification](https://support.iterable.com/hc/articles/23230946708244#notifications) view for an embedded message. | +| [`sortInAppMessages`](#sortinappmessages) | Sorts an array of in-app messages by priority, and then creation date. | +| [`track`](#track) | Tracks a custom event by calling [`POST /api/events/track`](https://support.iterable.com/hc/articles/204780579#post-api-events-track). | +| [`trackEmbeddedClick`](#trackEmbeddedClick) | Tracks an [`embeddedClick`](https://support.iterable.com/hc/articles/23061677642260#embeddedclick-events) event by calling [`POST /api/embedded-messaging/events/click`](https://support.iterable.com/hc/articles/204780579#post-api-embedded-messaging-events-click). | +| [`trackEmbeddedReceived`](#trackEmbeddedReceived) | Tracks an [`embeddedReceived`](https://support.iterable.com/hc/articles/23061677642260#embeddedreceived-events) event by calling [`POST /api/embedded-messaging/events/received`](https://support.iterable.com/hc/articles/204780579#post-api-embedded-messaging-events-received). | +| [`trackEmbeddedSession`](#trackEmbeddedSession) | Tracks an [`embeddedSession`](https://support.iterable.com/hc/articles/23061677642260#embeddedsession-events) event and related [`embeddedImpression`](https://support.iterable.com/hc/articles/23061677642260#embeddedimpression-events) events by calling [`POST /api/embedded-messaging/events/session`](https://support.iterable.com/hc/articles/204780579#post-api-embedded-messaging-events-session). | | +| [`trackInAppClick`](#trackInAppClick) | Tracks an `inAppClick` event by calling [`POST /api/events/trackInAppClick`](https://support.iterable.com/hc/articles/204780579#post-api-events-trackinappclick). | +| [`trackInAppClose`](#trackInAppClose) | Tracks an `inAppClose` event by calling [`POST /api/events/trackInAppClose`](https://support.iterable.com/hc/articles/204780579#post-api-events-trackinappclose). | +| [`trackInAppConsume`](#trackInAppConsume) | Deletes an in-app message from the server by calling [`POST /api/events/trackInAppConsume`](https://support.iterable.com/hc/articles/204780579#post-api-events-inappconsume). | +| [`trackInAppDelivery`](#trackInAppDelivery) | Tracks an `inAppDelivery` event by calling [`POST /api/events/trackInAppDelivery`](https://support.iterable.com/hc/articles/204780579#post-api-events-trackinappdelivery). | +| [`trackInAppOpen`](#trackInAppOpen) | Tracks an `inAppOpen` event by calling [`POST /api/events/trackInAppOpen`](https://support.iterable.com/hc/articles/204780579#post-api-events-trackinappopen). | +| [`trackPurchase`](#trackPurchase) | Tracks a `purchase` event by calling [`POST /api/commerce/trackPurchase`](https://support.iterable.com/hc/articles/204780579#post-api-commerce-trackpurchase). | +| [`updateCart`](#updateCart) | Updates the shopping cart items on the user's Iterable profile by calling [`POST /api/commerce/updateCart`](https://support.iterable.com/hc/articles/204780579#post-api-commerce-updatecart). | +| [`updateSubscriptions`](#updateSubscriptions) | Updates the user's subscriptions by calling [`POST /api/users/updateSubscriptions`](https://support.iterable.com/hc/articles/204780579#post-api-users-updatesubscriptions). | +| [`updateUser`](#updateUser) | Updates the data on a user's Iterable profile by calling [`POST /api/users/updateUser`](https://support.iterable.com/hc/articles/204780579#post-api-users-update). | +| [`updateUserEmail`](#updateUserEmail) | Updates the current user's `email` by calling [`POST /api/users/updateEmail`](https://support.iterable.com/hc/articles/204780579#post-api-users-updateemail). Causes the SDK to fetch a JWT for the new email address. | + +Notes: + +- The SDK does not track `inAppDelete` events. -# API +- :rotating_light: Due to a limitation in WebKit (which affects iOS web browsers, + like Safari), in-app messages displayed in an iOS web browser browser can't + automatically track `inAppClick` events or handle custom CTAs. This will impact + analytics for all Safari and mobile iOS users. -Below are the methods this SDK exposes. See [Iterable's API Docs](https://api.iterable.com/api/docs) for information on what data to pass and what payload to receive from the HTTP requests. +## `filterHiddenInAppMessages` -| Method Name | Description | -| ------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [`getInAppMessages`](#getInAppMessages) | Either fetch and return in-app messages as a Promise or (if the `options` argument is provided) return methods to fetch, pause/resume, and/or display in-app messages. | -| [`initialize`](#initialize) | Return methods for identifying users and setting a JWT | -| [`initializeWithConfig`](#initializeWithConfig) | Return methods for identifying users and setting a JWT while also taking a set of configuration options. | -| [`refreshJwtToken`](#refreshJwtToken) | Fetch a new JWT token for the specified user and configure the SDK to use it for future requests. Only for manual token refresh. | -| [`track`](#track) | Track custom events | -| [`trackInAppClick`](#trackInAppClick) :rotating_light: | Track when a user clicks on a button or link within a message | -| [`trackInAppClose`](#trackInAppClose) | Track when an in-app message is closed | -| [`trackInAppConsume`](#trackInAppConsume) | Track when a message has been consumed. Deletes the in-app message from the server so it won't be returned anymore | -| [`trackInAppDelivery`](#trackInAppDelivery) | Track when a message has been delivered to a user's device | -| [`trackInAppOpen`](#trackInAppOpen) | Track when a message is opened and marks it as read | -| [`trackPurchase`](#trackPurchase) | Track purchase events | -| [`updateCart`](#updateCart) | Update `shoppingCartItems` field on user profile | -| [`updateSubscriptions`](#updateSubscriptions) | Updates user's subscriptions | -| [`updateUser`](#updateUser) | Change data on a user's profile or create a user if none exists | -| [`updateUserEmail`](#updateUserEmail) | Change a user's email and reauthenticate user with the new email address (in other words, the SDK will call `setEmail` for you) | +From an array of passed-in in-app messages, filters out messages that have +already been read, messages that should not be displayed, and messages that only +contain JSON data. -The SDK does not track `inAppDelete` events. +```ts +const filterHiddenInAppMessages = ( + messages: Partial[] = [] +): Partial[] +``` -:rotating_light: Due to a limitation in WebKit (which affects iOS web browsers, Safari included) web in-app messages displayed in an iOS web browser browser can't automatically fire trackInAppClick events or handle custom CTAs. This will impact analytics for all Safari and mobile iOS users. +See also: -# Usage +- [`InAppMessage`](#inappmessage) -## getInAppMessages +## `filterOnlyReadAndNeverTriggerMessages` -API [(see required API payload here)](https://api.iterable.com/api/docs#In-app_getMessages): +From an array of passed-in in-app messages, filters out messages that have +already been read and messages that should not be displayed. ```ts -getInAppMessages: ( - payload: InAppMessagesRequestParams, - options?: { display: 'deferred' | 'immediate' } -) => Promise | PaintInAppMessageData; +const filterOnlyReadAndNeverTriggerMessages = ( + messages: Partial[] = [] +): Partial[] ``` -:rotating_light: Notice: v1.0.0 of this SDK deprecates support for `showMessagesAutomatically?: boolean`. If needed, please update your getInAppMessages requests to use `options: { display: 'deferred' | 'immediate' }` instead. +See also: + +- [`InAppMessage`](#inappmessage) -SDK Specific Options: +## `getInAppMessages` -Along with the API parameters, you can pass these options to the SDK method to have in-app messages behave differently. +Fetches in-app messages by calling [`GET /api/inApp/getMessages`](https://support.iterable.com/hc/articles/204780579#get-api-inapp-getmessages). -| Property Name | Description | Value | Default | -| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------- | ----------- | -| animationDuration | How long (in ms) it should take messages to animate in and out | `number` | `400` | -| bottomOffset | How much space (px or %) to create between the bottom of the screen and a message. Not applicable for center, top, or full-screen messages. | `string` | `undefined` | -| displayInterval | How long (in ms) to wait before showing the next in-app message after closing the currently opened one | `number` | `30000` | -| handleLinks | How to open links. If `undefined`, use browser-default behavior. `open-all-new-tab` opens all in new tab, `open-all-same-tab` opens all in same tab, `external-new-tab` opens only off-site links in new tab, otherwise same tab. Overrides the target attribute defined on link elements. | `'open-all-new-tab' \| 'open-all-same-tab' \| 'external-new-tab'` | `undefined` | -| onOpenScreenReaderMessage | The text a screen reader should read when opening the message. | `string` | `undefined` | -| onOpenNodeToTakeFocus | The DOM element that should receive keyboard focus when the in-app message opens. Any query selector is valid. If not specified, the first interactive element receives focus. | `string` | `undefined` | -| rightOffset | The amount of space (px or %) to create between the right of the screen and the message. Not applicable for center or full-screen messages. | `string` | `undefined` | -| topOffset | How much space (px or %) to create between the top of the screen and a message. Not applicable for center, bottom, or full-screen messages. | `string` | `undefined` | -| closeButton | Properties that define a custom close button to display on a message. | `CloseButtonOptions` (see below) | `undefined` | +```ts +// Returns a promise that resolves to an InAppMessageResponse, which has an +// array of fetched in-app messages. +function getInAppMessages( + payload: InAppMessagesRequestParams +): IterablePromise; + +// Returns methods to request messages from the server, pause message display, +// restart message display, and trigger the display of a message. +function getInAppMessages( + payload: InAppMessagesRequestParams, + options: { + display: DisplayOptions; + } +): GetInAppMessagesResponse -Close Button Options: +``` -| Property Name | Description | Value | Default | -| -------------------------- | ---------------------------------------------------------------------------- | -------------------------- | ------------- | -| color | The button's color (does not affect custom icons) | `string` | `undefined` | -| iconPath | Custom pathname to an image or SVG to use (instead of the default "X") | `string` | `undefined` | -| position | Where the button should display on an in-app message | `'top-right \| 'top-left'` | `'top-right'` | -| isRequiredToDismissMessage | If `true`, users cannot dismiss in-app messages by clicking outside of them. | `boolean` | `undefined` | -| sideOffset | How much space to leave between the button and side of the container | `string` | `'4%'` | -| size | How large to set the width, height, and font-size | `string \| number` | `24` | -| topOffset | How much space to leave between the button and the top of the container | `string` | `'4%'` | +`payload` options (see [`InAppMessagesRequestParams`](#inappmessagesrequestparams)): + +| Property Name | Description | Value | Default | +| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------- | ----------- | +| `animationDuration` | How long (in ms) it should take messages to animate in and out | `number` | `400` | +| `bottomOffset` | How much space (px or %) to create between the bottom of the screen and a message. Not applicable for center, top, or full-screen messages. | `string` | `undefined` | +| `closeButton` | Properties that define a custom close button to display on a message. | `CloseButtonOptions` (see below) | `undefined` | +| `displayInterval` | How long (in ms) to wait before showing the next in-app message after closing the currently open one | `number` | `30000` | +| `handleLinks` | How to open links. If `undefined`, use browser-default behavior. `open-all-new-tab` opens all in new tab, `open-all-same-tab` opens all in same tab, `external-new-tab` opens only off-site links in new tab, otherwise same tab. Overrides the target attribute defined on link elements. | `'open-all-new-tab' \| 'open-all-same-tab' \| 'external-new-tab'` | `undefined` | +| `onOpenNodeToTakeFocus` | The DOM element that should receive keyboard focus when the in-app message opens. Any query selector is valid. If not specified, the first interactive element receives focus. | `string` | `undefined` | +| `onOpenScreenReaderMessage` | The text a screen reader should read when opening the message. | `string` | `undefined` | +| `rightOffset` | The amount of space (px or %) to create between the right of the screen and the message. Not applicable for center or full-screen messages. | `string` | `undefined` | +| `topOffset` | How much space (px or %) to create between the top of the screen and a message. Not applicable for center, bottom, or full-screen messages. | `string` | `undefined` | + +`closeButton` options (see [`CloseButton`](#closebutton)): + +| Property Name | Description | Value | Default | +| --------------------------- | ---------------------------------------------------------------------------- | -------------------------- | ------------- | +| `color` | The button's color (does not affect custom icons) | `string` | `undefined` | +| `iconPath` | Custom pathname to an image or SVG to use (instead of the default "X") | `string` | `undefined` | +| `isRequiredToDismissMessage`| If `true`, users cannot dismiss in-app messages by clicking outside of them. | `boolean` | `undefined` | +| `position` | Where the button should display on an in-app message | `'top-right' \| 'top-left'`| `'top-right'` | +| `sideOffset` | How much space to leave between the button and side of the container | `string` | `'4%'` | +| `size` | How large to set the width, height, and font-size | `string \| number` | `24` | +| `topOffset` | How much space to leave between the button and the top of the container | `string` | `'4%'` | Example: -Calling `getInAppMessages` with `options` not set returns a JSON response from Iterable. This response includes an `inAppMessages` field, and each item in the list has a `content.html` field that's an `iframe` with an embedded in-app message. The `iframe`'s `sandbox` attribute is set, isolating its render and preventing any malicious JavaScript execution. - ```ts import { getInAppMessages } from '@iterable/web-sdk'; getInAppMessages({ count: 20, packageName: 'mySite1' }) .then((resp) => { - /* This will be an iframe element that can be attached to the DOM */ + // This is an iframe element that can be attached to the DOM const messageIframe = resp.data.inAppMessages[0].content.html; document.body.appendChild(messageIframe); - - /* Additional styling logic can be done here to customly render the message */ + // Additional styling logic can be done here to render the message in a + // custom way }) .catch(); ``` -This code places an in-app on the page, but it won't be visible. To render it, you'll need to modify the page's CSS, setting up whatever styles you'd like. You'll also need to set up click handlers to handle closing the message and tracking events (in-app click, etc.). +This code, which doesn't include the `options` parameter, fetches in-app messages +from Iterable and places the first one on the page. However, it won't be visible. +To render it, modify the page's CSS to display the message as necessary. You'll +also need to set up click handlers to handle click events, close the message, +etc. -Or, to show messages automatically: +Here's some example code that shows messages automatically: ```ts import { getInAppMessages } from '@iterable/web-sdk'; @@ -164,7 +239,7 @@ const { request, pauseMessageStream, resumeMessageStream } = getInAppMessages( packageName: 'my-website', displayInterval: 5000, onOpenScreenReaderMessage: - 'hey screen reader here telling you something just popped up on your screen!', + 'The screen reader will read this', onOpenNodeToTakeFocus: 'input', closeButton: { color: 'red', @@ -178,7 +253,8 @@ const { request, pauseMessageStream, resumeMessageStream } = getInAppMessages( request().then().catch(); ``` -or if you want to show messages with your own custom filtering/sorting and choose to display later: +This example uses custom sorting and filtering, and displays messages at the +app's discretion: ```ts import { @@ -197,8 +273,7 @@ const { count: 20, packageName: 'my-website', displayInterval: 5000, - onOpenScreenReaderMessage: - 'hey screen reader here telling you something just popped up on your screen!', + onOpenScreenReaderMessage: 'The screen reader will read this', onOpenNodeToTakeFocus: 'input', closeButton: { color: 'red', @@ -211,48 +286,73 @@ const { request() .then((response) => { - /* do your own manipulation here */ - const filteredMessages = doStuffToMessages(response.data.inAppMessages); + // Do your own manipulation here + const filteredMessages = yourOwnSortingAndFiltering(response.data.inAppMessages); - /* also feel free to take advantage of the sorting/filtering methods used internally */ + // Or, feel free to take advantage of the sorting/filtering methods used + // internally const furtherManipulatedMessages = sortInAppMessages( filterHiddenInAppMessages(response.data.inAppMessages) ) as InAppMessage[]; - /* then display them whenever you want */ + // Then display them whenever you want triggerDisplayMessages(furtherManipulatedMessages); }) .catch(); ``` -:rotating_light: PLEASE NOTE, If you choose the `deferred` option, the SDK will \_not* do any filtering or sorting on the messages internally. You will get the messages exactly as they come down from the API, untouched. This means you may (for example) show in-app messages marked `read` or show the messages in the wrong order based on `priority`. +:rotating_light: With the `deferred` option, the SDK does **not** filter or sort +the messages. The messages come back exactly as retrieved from the API, without +modification. This means that you may (for example) show in-app messages marked as +`read`, or show messages in the default order (based on `priority`), rather +than a custom order that you control. + +In this case, to apply the SDK's default sorting and filtering, use the +[`sortInAppMessages`](#sortinappmessages) and [`filterHiddenInAppMessages`](#filterhiddeninappmessages) +methods. Also, see [`filterOnlyReadAndNeverTriggerMessages`](#filteronlyreadandnevertriggermessages), +which is similar to `filterHiddenInAppMessages` but does not filter out +JSON-only messages. + +Notes: -If you want to keep the default sorting and filtering, please take advantage of the `sortInAppMessages` and `filterHiddenInAppMessages` methods the SDK provides. Also see `filterOnlyReadAndNeverTriggerMessages`, which is similar to `filterHiddenInAppMessages` but does not filter out JSON-only messages. +- :rotating_light: [v1.0.0](https://github.com/Iterable/iterable-web-sdk/releases/tag/v1.0.0) + of this SDK removes support for `showMessagesAutomatically?: boolean`. If needed, + please update your `getInAppMessages` requests to use `options: { display: 'deferred' | 'immediate' }`. -## initialize +See also: -API: +- [`DisplayOptions`](#displayoptions) +- [`GetInAppMessagesResponse`](#getinappmessagesresponse) +- [`InAppMessagesRequestParams`](#inappmessagesrequestparams) +- [`InAppMessageResponse`](#inappmessageresponse) +- [`IterablePromise`](#iterablepromise) + +## `initialize` + +Initializes the SDK with an API key and a JWT refresh method. Returns methods +you can use to identify the current user, work with JWT tokens, and log the +user out (see [`WithJWT`](#withjwt)). ```ts -initialize: (authToken: string, generateJWT: ({ email?: string, userID?: string }) => Promise) => { - clearRefresh: () => void; - setEmail: (email: string) => Promise; - setUserID: (userId: string) => Promise; - logout: () => void; -} +function initialize( + authToken: string, + generateJWT: (payload: GenerateJWTPayload) => Promise +): WithJWT; ``` +`generateJWT` should be a function that takes a `userId` or `email` and uses +it to fetch, from your server, a valid JWT token for that user. The function +should return the token as a string. + Example: ```ts import { initialize } from '@iterable/web-sdk'; const { clearRefresh, setEmail, setUserID, logout } = initialize( - 'my-API-key', - /* - _email_ will be defined if you call _setEmail_ - _userID_ will be defined if you call _setUserID_ - */ + '', + // email will be defined if you call setEmail + // userID_ will be defined if you call setUserID ({ email, userID }) => yourAsyncJWTGeneratorMethod({ email, userID }).then( ({ jwt_token }) => jwt_token @@ -260,409 +360,1723 @@ const { clearRefresh, setEmail, setUserID, logout } = initialize( ); ``` -## initializeWithConfig +See also: + +- [`GenerateJWTPayload`](#generatejwtpayload) +- [`WithJWT`](#withjwt) + +## `initializeWithConfig` + +Similar to `initialize`, but also takes a set of configuration options as a +parameter. Returns methods you can use to identify the current user, work with +JWT tokens, and log the user out (see [`WithJWT`](#withjwt)). + +The configuration options you can pass to this function are useful if you +need to [point the SDK to Iterable's EU API endpoints](#iterables-european-data-center-eudc) +or [allow JavaScript execution in Safari tabs](#safari-allowing-javascript-execution-in-tabs-opened-by-in-app-message-link-clicks). + +```ts +function initializeWithConfig(initializeParams: WithJWTParams): WithJWT; +``` + +Example: + +```ts +import { initializeWithConfig } from '@iterable/web-sdk'; + +const { clearRefresh, setEmail, setUserID, logout } = initializeWithConfig({ + authToken: '', + configOptions: { + isEuIterableService: false, + dangerouslyAllowJsPopups: true, + }, + + // email will be defined if you call setEmail + // userID will be defined if you call setUserID + generateJWT: ({ email, userID }) => + yourAsyncJWTGeneratorMethod({ email, userID }).then( + ({ jwt_token }) => jwt_token + ) +} +); +``` + +`generateJWT` should be a function that takes a `userId` or `email` and uses +it to fetch, from your server, a valid JWT token for that user. The function +should return the token as a string. + +See also: + +- [`WithJWT`](#withjwt) +- [`WithJWTParams`](#withjwtparams) + +## `IterableEmbeddedCard` + +Returns a string of the HTML for an out-of-the-box [card](https://support.iterable.com/hc/articles/23230946708244#cards) +view for an embedded message. + +```ts +const emptyElement = { + id: '', + styles: '' +}; + +function IterableEmbeddedCard({ + appPackageName, + message, + htmlElements = { + parent: emptyElement, + img: emptyElement, + title: emptyElement, + primaryButton: emptyElement, + secondaryButton: emptyElement, + body: emptyElement, + buttonsDiv: emptyElement, + textTitle: emptyElement + }, + errorCallback +}: OOTB): string +``` + +Parameters: + +- `appPackageName` – The package name you use to identify your website to + Iterable's Web SDK. +- `message` – The `IterableEmbeddedMessage` object that represents the + message you want to display. +- `htmlElements` – Custom styles (type [`Elements`](#elements)) for the SDK to use + when displaying the embedded message. For details, see [Custom Styles](https://support.iterable.com/hc/articles/27537816889108#custom-styles). +- `errorCallback` – A callback that the SDK calls if it encounters an error + when tracking [`embeddedClick`](https://support.iterable.com/hc/articles/23061677642260#embeddedclick-events) + events. + +```js +import { IterableEmbeddedCard } from '@iterable/web-sdk'; + +const card = IterableEmbeddedCard({ + packageName, + message, + htmlElements, + errorCallback: (error) => console.log('handleError: ', error) +}); +``` + +To display the message, set the `innerHTML` of an HTML element to the string +returned by `IterableEmbeddedCard`. + +For more info, see: + +- [Out-of-the-Box Views](https://support.iterable.com/hc/articles/27537816889108#out-of-the-box-views). +- For default card styles, see the [`src/components/card/styles.ts`](https://github.com/Iterable/iterable-web-sdk/blob/main/src/components/card/styles.ts) + in the Web SDK GitHub repository. +- To learn how to apply custom styles, see [Custom Styles](https://support.iterable.com/hc/articles/27537816889108#custom-styles). + +Also see: + +- [`Elements`](#elements) +- [`OOTB`](#ootb) + +## `IterableEmbeddedBanner` + +Returns a string of the HTML for an out-of-the-box [banner](https://support.iterable.com/hc/articles/23230946708244#banners) +view for an embedded message. + +```ts +function IterableEmbeddedBanner({ + appPackageName, + message, + htmlElements = { + parent: emptyElement, + img: emptyElement, + title: emptyElement, + primaryButton: emptyElement, + secondaryButton: emptyElement, + body: emptyElement, + buttonsDiv: emptyElement, + textTitle: emptyElement, + textTitleImg: emptyElement + }, + errorCallback +}: OOTB): string +``` + +Parameters: + +- `appPackageName` – The package name you use to identify your website to + Iterable's Web SDK. +- `message` – The `IterableEmbeddedMessage` object that represents the message + you want to display. +- `htmlElements` – Custom styles (type [`Elements`](#elements)) for the SDK to use + when displaying the embedded message. For details, see [Custom Styles](https://support.iterable.com/hc/articles/27537816889108#custom-styles). +- `errorCallback` – A callback that the SDK calls if it encounters an error + when tracking [`embeddedClick`](https://support.iterable.com/hc/articles/23061677642260#embeddedclick-events) + events. + +For example: + +```js +import { IterableEmbeddedBanner } from '@iterable/web-sdk'; + +const banner = IterableEmbeddedBanner({ + packageName, + message, + htmlElements, + errorCallback: (error) => console.log('handleError: ', error) +}); +``` + +To display the message, set the `innerHTML` of an HTML element to the string +returned by `IterableEmbeddedBanner`. + +For more info, see: + +- [Creating an Out-of-the-Box View](https://support.iterable.com/hc/articles/27537816889108). +- For default banner styles, see the [`src/components/banner/styles.ts`](https://github.com/Iterable/iterable-web-sdk/blob/main/src/components/banner/styles.ts) + in the Web SDK GitHub repository. +- To learn how to apply custom styles, see [Custom Styles](https://support.iterable.com/hc/articles/27537816889108#custom-styles). + +Also see: + +- [`OOTB`](#ootb) +- [`Elements`](#elements) + +## `IterableEmbeddedNotification` + +Returns a string of the HTML for an out-of-the-box [notification](https://support.iterable.com/hc/articles/23230946708244#notifications) +view for an embedded message. + +```ts +function IterableEmbeddedNotification({ + appPackageName, + message, + htmlElements = { + parent: emptyElement, + title: emptyElement, + primaryButton: emptyElement, + secondaryButton: emptyElement, + body: emptyElement, + buttonsDiv: emptyElement, + textTitle: emptyElement + }, + errorCallback +}: OOTB): string +``` + +Parameters: + +- `appPackageName` – The package name you use to identify your website to + Iterable's Web SDK. +- `message` – The `IterableEmbeddedMessage` object that represents the message + you want to display. +- `htmlElements` – Custom styles (type [`Elements`](#elements)) for the SDK to use + when displaying the embedded message. For details, see [Custom Styles](https://support.iterable.com/hc/articles/27537816889108#custom-styles). +- `errorCallback` – A callback that the SDK calls if it encounters an error + when tracking [`embeddedClick`](https://support.iterable.com/hc/articles/23061677642260#embeddedclick-events) + events. + +```js +import { IterableEmbeddedNotification } from '@iterable/web-sdk'; + +const notification = IterableEmbeddedNotification({ + packageName, + message, + htmlElements, + errorCallback: (error) => console.log('handleError: ', error) +}); +``` + +To display the message, set the `innerHTML` of an HTML element to the string +returned by `IterableEmbeddedNotification`. + +For more info, see: + +- [Creating an Out-of-the-Box View](https://support.iterable.com/hc/articles/27537816889108). +- For default notification styles, see the [`src/components/notification/styles.ts`](https://github.com/Iterable/iterable-web-sdk/blob/main/src/components/notification/styles.ts) +- To learn how to apply custom styles, see [Custom Styles](https://support.iterable.com/hc/articles/27537816889108#custom-styles). + +Also see: + +- [`OOTB`](#ootb) +- [`Elements`](#elements) + +## `sortInAppMessages` + +Sorts an array of in-app messages by priority, and then creation date. + +```ts +const sortInAppMessages = (messages: Partial[] = []) => { + return messages.sort(by(['priorityLevel', 'asc'], ['createdAt', 'asc'])); +}; +``` + +In-app messages can have these [priority values](https://support.iterable.com/hc/articles/7412316462996#display-priority): + +- Low - `priorityLevel` of 400.5 +- Medium - `priorityLevel` of 300.5 +- High - `priorityLevel` of 200.5 +- Critical - `priorityLevel` of 100.5 +- Proof - `priorityLevel` of 100.0 + +Also see: + +- [`InAppMessage`](#inappmessage) + +## `track` + +Tracks a custom event by calling [`POST /api/events/track`](https://support.iterable.com/hc/articles/204780579#post-api-events-track). + +```ts +track: (payload: InAppTrackRequestParams): IterablePromise +``` + +Example: + +```ts +import { track } from '@iterable/web-sdk'; + +track({ eventName: 'my-event' }).then().catch(); +``` + +See also: + +- [`InAppTrackRequestParams`](#inapptrackrequestparams) +- [`IterablePromise`](#iterablepromise) +- [`IterableResponse`](#iterableresponse) + +## `trackEmbeddedClick` + +Tracks an [`embeddedClick`](https://support.iterable.com/hc/articles/23061677642260#embeddedclick-events) +event by calling [`POST /api/embedded-messaging/events/click`](https://support.iterable.com/hc/articles/204780579#post-api-embedded-messaging-events-click). + +```ts +const trackEmbeddedClick = ( + payload: IterableEmbeddedClickRequestPayload +): IterablePromise +``` + +Example: + +```js +import { trackEmbeddedReceived } from '@iterable/web-sdk'; + +trackEmbeddedClick({ + messageId: message.metadata.messageId, + buttonIdentifier: button.id, + clickedUrl: defaultUrl, + appPackageName: packageName +}).then((response) => { + if (response.status != 200) { + console.log("Failure tracking embedded click") + } +}).catch((error) => { + console.log("Error tracking embedded click: ", error); +}); +``` + +See also: + +- [`IterableEmbeddedClickRequestPayload`](#iterableembeddedclickrequestpayload) +- [`IterablePromise`](#iterablepromise) +- [`IterableResponse`](#iterableresponse) + +## `trackEmbeddedReceived` + +Tracks an [`embeddedReceived`](https://support.iterable.com/hc/articles/23061677642260#embeddedreceived-events) +event by calling [`POST /api/embedded-messaging/events/received`](https://support.iterable.com/hc/articles/204780579#post-api-embedded-messaging-events-received). + +Generally, there's no need to call this method, since the SDK automatically +tracks an `embeddedReceived` event for each message it fetches from the server. + +```ts +const trackEmbeddedReceived = ( + messageId: string, + appPackageName: string +): IterablePromise +``` + +Example: + +```ts +import { trackEmbeddedReceived } from '@iterable/web-sdk'; + +trackEmbeddedReceived(messageId, packageName) + .then((response: any) => { + setTrackResponse(JSON.stringify(response.data)); + setTrackingEvent(false); + }) + .catch((error: any) => { + setTrackResponse(JSON.stringify(error.response.data)); + setTrackingEvent(false); + }); +``` + +See also: + +- [`IterablePromise`](#iterablepromise) +- [`IterableResponse`](#iterableresponse) + +## `trackEmbeddedSession` + +Tracks an [`embeddedSession`](https://support.iterable.com/hc/articles/23061677642260#embeddedsession-events) +event and related [`embeddedImpression`](https://support.iterable.com/hc/articles/23061677642260#embeddedimpression-events) +events by calling [`POST /api/embedded-messaging/events/session`](https://support.iterable.com/hc/articles/204780579#post-api-embedded-messaging-events-session). + +Generally, rather than calling this method, you'll track sessions and impresions +using the SDK's [`IterableEmbeddedSessionManager`](#iterableembeddedsessionmanager). + +```ts +const trackEmbeddedSession = ( + payload: IterableEmbeddedSessionRequestPayload +): IterablePromise +``` + +Example: + +```ts +import { trackEmbeddedSession } from '@iterable/web-sdk'; + +trackEmbeddedSession(sessionData) + .then((response: any) => { + setTrackResponse(JSON.stringify(response.data)); + setTrackingEvent(false); + }) + .catch((error: any) => { + setTrackResponse(JSON.stringify(error.response.data)); + setTrackingEvent(false); + }); +``` + +See also: + +- [`IterableEmbeddedSessionRequestPayload`](#iterableembeddedsessionrequestpayload) +- [`IterablePromise`](#iterablepromise) +- [`IterableResponse`](#iterableresponse) + +## `trackInAppClick` + +Tracks an `inAppClick` event by calling [`POST /api/events/trackInAppClick`](https://support.iterable.com/hc/articles/204780579#post-api-events-trackinappclick). + +```ts +const trackInAppClick = ( + payload: Omit, + sendBeacon = false +): IterablePromise +``` + +Example: + +```ts +import { trackInAppClick } from '@iterable/web-sdk'; + +trackInAppClick({ + messageId: '123', + deviceInfo: { appPackageName: 'my-website' } +}) + .then() + .catch(); +``` + +See also: + +- [`InAppEventRequestParams`](#inappeventrequestparams) +- [`IterablePromise`](#iterablepromise) +- [`IterableResponse`](#iterableresponse) + +## `trackInAppClose` + +Tracks an `inAppClose` event by calling [`POST /api/events/trackInAppClose`](https://support.iterable.com/hc/articles/204780579#post-api-events-trackinappclose). + +```ts +const trackInAppClose = (payload: InAppEventRequestParams): IterablePromise +``` + +Example: + +```ts +import { trackInAppClose } from '@iterable/web-sdk'; + +trackInAppClose({ + messageId: '123', + deviceInfo: { appPackageName: 'my-website' } +}) + .then() + .catch(); +``` + +See also: + +- [`InAppEventRequestParams`](#inappeventrequestparams) +- [`IterablePromise`](#iterablepromise) +- [`IterableResponse`](#iterableresponse) + +## `trackInAppConsume` + +Deletes an in-app message from the server by calling [`POST /api/events/trackInAppConsume`](https://support.iterable.com/hc/articles/204780579#post-api-events-inappconsume). + +```ts +const trackInAppConsume = ( + payload: Omit< + InAppEventRequestParams, + 'clickedUrl' | 'closeAction' | 'inboxSessionId' + > +): IterablePromise +``` + +Example: + +```ts +import { trackInAppConsume } from '@iterable/web-sdk'; + +trackInAppConsume({ + messageId: '123', + deviceInfo: { appPackageName: 'my-website' } +}) + .then() + .catch(); +``` + +See also: + +- [`InAppEventRequestParams`](#inappeventrequestparams) +- [`IterablePromise`](#iterablepromise) +- [`IterableResponse`](#iterableresponse) + +## `trackInAppDelivery` + +Tracks an `inAppDelivery` event by calling [`POST /api/events/trackInAppDelivery`](https://support.iterable.com/hc/articles/204780579#post-api-events-trackinappdelivery). + +```ts +const trackInAppDelivery = ( + payload: Omit< + InAppEventRequestParams, + 'clickedUrl' | 'closeAction' | 'inboxSessionId' + > +): IterablePromise +``` + +Example: + +```ts +import { trackInAppDelivery } from '@iterable/web-sdk'; + +trackInAppDelivery({ + messageId: '123', + deviceInfo: { appPackageName: 'my-website' } +}) + .then() + .catch(); +``` + +See also: + +- [`InAppEventRequestParams`](#inappeventrequestparams) +- [`IterablePromise`](#iterablepromise) +- [`IterableResponse`](#iterableresponse) + +## `trackInAppOpen` + +Tracks an `inAppOpen` event by calling [`POST /api/events/trackInAppOpen`](https://support.iterable.com/hc/articles/204780579#post-api-events-trackinappopen). + +```ts +const trackInAppOpen = ( + payload: Omit< + InAppEventRequestParams, + 'clickedUrl' | 'inboxSessionId' | 'closeAction' + > +): IterablePromise +``` + +Example: + +```ts +import { trackInAppOpen } from '@iterable/web-sdk'; + +trackInAppOpen({ + messageId: '123', + deviceInfo: { appPackageName: 'my-website' } +}) + .then() + .catch(); +``` + +See also: + +- [`InAppEventRequestParams`](#inappeventrequestparams) +- [`IterablePromise`](#iterablepromise) +- [`IterableResponse`](#iterableresponse) + +## `trackPurchase` + +Tracks a `purchase` event by calling [`POST /api/commerce/trackPurchase`](https://support.iterable.com/hc/articles/204780579#post-api-commerce-trackpurchase). + +```ts +const trackPurchase = (payload: TrackPurchaseRequestParams): IterablePromise +``` + +Example: + +```ts +import { trackPurchase } from '@iterable/web-sdk'; + +trackPurchase({ + items: [{ id: '123', name: 'keyboard', price: 100, quantity: 2 }], + total: 200 +}) + .then() + .catch(); +``` + +See also: + +- [`IterablePromise`](#iterablepromise) +- [`IterableResponse`](#iterableresponse) +- [`TrackPurchaseRequestParams`](#trackpurchaserequestparams) + +## `updateCart` + +Updates the shopping cart items on the user's Iterable profile by calling [`POST /api/commerce/updateCart`](https://support.iterable.com/hc/articles/204780579#post-api-commerce-updatecart). + +```ts +const updateCart = (payload: UpdateCartRequestParams): IterablePromise +``` + +Example: + +```ts +import { updateCart } from '@iterable/web-sdk'; + +updateCart({ + items: [{ id: '123', price: 100, name: 'keyboard', quantity: 1 }] +}) + .then() + .catch(); +``` + +See also: + +- [`UpdateCartRequestParams](#updatecartrequestparams) +- [`IterablePromise`](#iterablepromise) +- [`IterableResponse`](#iterableresponse) + +## `updateSubscriptions` + +Updates the user's subscriptions by calling [`POST /api/users/updateSubscriptions`](https://support.iterable.com/hc/articles/204780579#post-api-users-updatesubscriptions). + +```ts +const updateSubscriptions = ( + payload: Partial = {} +): IterablePromise +``` + +Example: + +```ts +import { updateSubscriptions } from '@iterable/web-sdk'; + +updateSubscriptions({ emailListIds: [1, 2, 3] }) + .then() + .catch(); +``` + +See also: + +- [`IterablePromise`](#iterablepromise) +- [`IterableResponse`](#iterableresponse) +- [`UpdateSubscriptionParams`](#updatesubscriptionparams) + +## `updateUser` + +Updates the data on a user's Iterable profile by calling [`POST /api/users/updateUser`](https://support.iterable.com/hc/articles/204780579#post-api-users-update). + +```ts +const updateUser = (payload: UpdateUserParams = {}): IterablePromise +``` + +Example: + +```ts +import { updateUser } from '@iterable/web-sdk'; + +updateUser({ dataFields: {} }).then().catch(); +``` + +See also: + +- [`IterablePromise`](#iterablepromise) +- [`IterableResponse`](#iterableresponse) +- [`UpdateUserParams`](#updateuserparams) + +## `updateUserEmail` + +Updates the current user's `email` by calling [`POST /api/users/updateEmail`](https://support.iterable.com/hc/articles/204780579#post-api-users-updateemail). +Causes the SDK to fetch a JWT for the new email address. + +```ts +updateUserEmail: (newEmail: string): IterablePromise +``` + +Example: + +```ts +import { updateUserEmail } from '@iterable/web-sdk'; + +updateUserEmail('user@example.com').then().catch(); +``` + +See also: + +- [`IterablePromise`](#iterablepromise) +- [`IterableResponse`](#iterableresponse) + +# Classes, interfaces, types, and enums + +This section describes classes, interfaces, and enums to be aware of when working +with Embedded Messaging in Iterable's Web SDK. + +| Type | Description | +| --------------------------------------------------------------------------------- | ----------- | +| [`CloseButton`](#closebutton) | Specifies how the SDK should display a close button on the associated in-app message. Passed as part of [`InAppMessagesRequestParams`](#inappmessagesrequestparams). | +| [`CloseButtonPosition`](#closebuttonposition) | Specifies the position of the close button on the associated in-app message. | +| [`CommerceItem`](#commerceitem) | An item being purchased or added to a shopping cart. Include when calling [`trackPurchase`](#trackpurchase) or [`updateCart`](#updatecart). | +| [`CommerceUser`](#commerceuser) | Information about the user associated with a purchase or cart update. Include when calling [`trackPurchase`](#trackpurchase) or [`updateCart`](#updatecart). | +| [`DisplayOptions`](#displayoptions) | Display options to pass to [`getInAppMessages`](#getinappmessages) to indicate whether messages should be displayed immediately or later. | +| [`DisplayPosition`](#displayposition) | Describes where an in-app message should be displayed. Part of [`WebInAppDisplaySettings`](#webinappdisplaysettings). | +| [`Elements`](#elements) | Custom styles to apply to `IterableEmbeddedCard`, `IterableEmbeddedBanner`, and `IterableEmbeddedNotification` views for embedded messages. | +| [`GenerateJWTPayload`](#generatejwtpayload) | The payload to pass to the `generateJWT` function when calling [`initialize`](#initialize) or [`initializeWithConfig`](#initializewithconfig). | +| [`ErrorHandler`](#errorhandler) | An error-handling function. Passed as a parameter to [`IterableEmbeddedCard`](#iterableembeddedcard), [`IterableEmbeddedBanner`](#iterableembeddedbanner), and [`IterableEmbeddedNotification`](#iterableembeddednotification), which use the method to handle errors when tracking `embeddedClick` events. | +| [`GetInAppMessagesResponse`](#getinappmessagesresponse) | Return value for [`getInAppMessages`](#getinappmessages), when it's called without the `options` parameter. | +| [`HandleLinks`](#handlelinks) | Describes where in-app links should be opened. Part of [`InAppMessagesRequestParams`](#inappmessagesrequestparams). | +| [`InAppMessage`](#inappmessage) | A single in-app message. | +| [`InAppDisplaySetting`](#inappdisplaysetting) | Display settings for an in-app message, including padding percentages. | +| [`InAppEventRequestParams`](#inappeventrequestparams) | Data to pass to [`trackInAppClick`](#trackinappclick), [`trackInAppClose`](#trackinappclose), [`trackInAppConsume`](#trackinappconsume), [`trackInAppDelivery`](#trackinappdelivery), and [`trackInAppOpen`](#trackinappopen). | +| [`InAppMessagesRequestParams`](#inappmessagesrqeuestparams) | Data to pass to [`getInAppMessages`](#getinappmessages). | +| [`InAppMessageResponse`](#inappmessageresponse) | Return value for [`getInAppMessages`](#getinappmessages), when it's called with the `options` parameter. | +| [`InAppTrackRequestParams`](#inapptrackrequestparams) | Data to pass to [`track`](#track). | +| [`IterableAction`](#iterableaction) | An action associated with a click. The type of the action, and its associated URL. | +| [`IterableActionContext`](#iterableactioncontext) | Information about the context of an `IterableAction`. For example, the associated message type. Only used with embedded messages. | +| [`IterableActionSource`](#iterableactionsource) | An enum of possible message types to which an `IterableAction` can be associated. Currently, only `EMBEDDED` is supported. | +| [`IterableConfig`](#iterableconfig) | A class that can hold configuration information for the SDK. Currently, only `urlHandler` and `customActionHandler` are supported (static properties), and these are only invoked for URLs and custom actions coming from embedded messages. | +| [`IterableCustomActionHandler`](#iterablecustomactionhandler) | An interface that defines `handleIterableCustomAction`, which the SDK can call to handle custom action URLs (`action://`) URLs that result from from clicks on embedded messages. | +| [`IterableEmbeddedButton`](#iterableembeddedbutton) | Payload for a button associated with an embedded message. | +| [`IterableEmbeddedButtonAction`](#iterableembeddedbuttonaction) | Payload for the action associated with an embedded message button. | +| [`IterableEmbeddedClickRequestPayload`](#iterableembeddedclickrequestpayload) | Data to pass to [`trackEmbeddedClick`](#trackembeddedclick). | +| [`IterableEmbeddedDefaultAction`](#iterableembeddeddefaultaction) | The default action associated with an embedded message. Invoked when a user clicks on an embedded message, but outside of its buttons. | +| [`IterableEmbeddedElements`](#iterableembeddedelements) | Content associated with an embedded message — title, body, media URL, buttons, default action, and extra text fields. | +| [`IterableEmbeddedImpression`](#iterableembeddedimpression) | The number of times a given embedded message appeared during a specific session, and the total duration of all those appearances. Also includes other metadata about the impression. | +| [`IterableEmbeddedManager`](#iterableembeddedmanager) | Used to fetch embedded messages from Iterable, and pass them to application code as necessary. | +| [`IterableEmbeddedMessage`](#iterableembeddedmessage) | A single embedded message to display, including title text, body text, buttons, an image URL, click actions, text fields, and JSON data. | +| [`IterableEmbeddedMessageUpdateHandler`](#iterableembeddedmessageupdatehandler) | An object that defines `onMessagesUpdated` and `onEmbeddedMessagingDisabled` methods. If this object is registered as an update listener for embedded messages (you can do this by calling `addUpdateListener` on [`IterableEmbeddedManager`](#iterableembeddedmanager)), the SDK calls these methods as necessary after fetching embedded messages from the server. | +| [`IterableEmbeddedMetadata`](#iterableembeddedmetadata) | Identifying information about an embedded message. | +| [`IterableEmbeddedSession`](#iterableembeddedsession) | Represents a period of time during which a user was on a page where they could potentially view embedded messages. Contains an ID, a start time, and an end time. | +| [`IterableEmbeddedSessionManager`](#iterableembeddedsessionmanager) | Used to track sessions and impressions, and to save them back to Iterable. | +| [`IterableEmbeddedSessionRequestPayload`](#iterableembeddedsessionrequestpayload) | Data to pass to [`trackEmbeddedSession`](#trackembeddedsession). You won't usually interact manually with this interface, since the [`IterableEmbeddedSessionManager`](#iterableembeddedsessionmanager) handles the tracking of sessions and impressions for you. | +| [`IterableEmbeddedText`](#iterableembeddedtext) | Extra text fields sent along with an embedded message. Like custom JSON, these text fields can be used to pass data as part of an embedded message. | +| [`IterableErrorStatus`](#iterableerrorstatus) | Errors that can come back with an [`IterableResponse`](#iterableresponse). | +| [`IterablePromise`](#iterablepromise) | A promise. | +| [`IterableResponse`](#iterableresponse) | A response from Iterable's API. | +| [`IterableUrlHandler`](#iterableurlhandler) | An interface that defines `handleIterableURL`, which the SDK can call to handle standard URLs (`https://`, `custom://`, but not `action://`) that result from from clicks on embedded messages. | +| [`OOTB`](#ootb) | A type that defines the parameters to provide when calling `IterableEmbeddedCard`, `IterableEmbeddedBanner`, and `IterableEmbeddedNotification`. | +| [`Options`](#options) | Configuration options to pass to [`initializeWithConfig`](#initializewithconfig). | +| [`OutOfTheBoxButton`](#outoftheboxbutton) | Custom styles to apply to buttons in an embedded message. The same as `OutOfTheBoxElement`, but with an extra `disabledStyles` string. | +| [`OutOfTheBoxElement`](#outoftheboxelement) | The custom styles to apply to a single element of an embedded message. | +| [`SDKInAppMessagesParams`](#sdkinappmessagesparams) | Parent interface for [`InAppMessagesRequestParams`](#inappmessagesrequestparams). | +| [`TrackPurchaseRequestParams`](#trackpurchaserequestparams) | Parameters to pass to [`trackPurchase`](#trackpurchase). | +| [`UpdateCartRequestParams`](#updatecartrequestparams) | Data to pass to [`updateCart`](#updatecart). | +| [`UpdateSubscriptionParams`](#updatesubscriptionparams) | Data to pass to [`updateSubscriptions`](#updatesubscriptions). | +| [`UpdateUserParams`](#updateuserparams) | Data to pass to [`updateUser`](#updateuser). | +| [`WebInAppDisplaySettings`](#webinappdisplaysettings) | An object that contains information about how to display the associated in-app message. | +| [`WithJWT`](#withjwt) | Return value from [`initialize`](#initialize) and [`initializeWithConfig`](#initializeWithConfig). | +| [`WithJWTParams`](#withjwtparams) | Parameters to pass to [`initializeWithConfig`](#initializewithconfig). | + +## `CloseButton` + +Specifies how the SDK should display a close button on the associated in-app message. +Passed as part of [`InAppMessagesRequestParams`](#inappmessagesrequestparams). + +```ts +type CloseButton = { + color?: string; + iconPath?: string; + // If true, prevent user from dismissing in-app message by clicking outside + // of message + isRequiredToDismissMessage?: boolean; + position?: CloseButtonPosition; + sideOffset?: string; + size?: string | number; + topOffset?: string; +}; +``` + +See also: + +- [`CloseButtonPosition`](#closebuttonposition) + +## `CloseButtonPosition` + +Specifies the position of the close button on the associated in-app message. + +```ts +declare enum CloseButtonPosition { + TopLeft = "top-left", + TopRight = "top-right" +} +``` + +## `CommerceItem` + +An item being purchased or added to a shopping cart. Include when calling +[`trackPurchase`](#trackpurchase) or [`updateCart`](#updatecart). + +```ts +interface CommerceItem { + id: string; + sku?: string; + name: string; + description?: string; + categories?: string[]; + price: number; + quantity: number; + imageUrl?: string; + url?: string; + dataFields?: Record; +} +``` + +## `CommerceUser` + +Information about the user associated with a purchase or cart update. Include +when calling [`trackPurchase`](#trackpurchase) or [`updateCart`](#updatecart). + +```ts +interface CommerceUser { + dataFields?: Record; + preferUserId?: boolean; + mergeNestedObjects?: boolean; +} +``` + +## `DisplayOptions` + +Display options to pass to [`getInAppMessages`](#getinappmessages) to indicate +whether messages should be displayed immediately or later. + +```ts +declare enum DisplayOptions { + Immediate = "immediate", + Deferred = "deferred" +} +``` + +## `DisplayPosition` + +Describes where an in-app message should be displayed. Part of [`WebInAppDisplaySettings`](#webinappdisplaysettings). + +```ts +declare enum DisplayPosition { + Center = "Center", + TopRight = "TopRight", + BottomRight = "BottomRight", + Full = "Full" +} +``` + +See also: + +- [`WebInAppDisplaySettings`](#webinappdisplaysettings) + +## `Elements` + +Custom styles to apply to `IterableEmbeddedCard`, `IterableEmbeddedBanner`, and +`IterableEmbeddedNotification` views for embedded messages. + +```ts +type Elements = { + // img div + img?: OutOfTheBoxElement; + // title div + title?: OutOfTheBoxElement; + // primary button div + primaryButton?: OutOfTheBoxButton; + // secondary button div + secondaryButton?: OutOfTheBoxButton; + // body button div + body?: OutOfTheBoxElement; + // root OOTB div + parent?: OutOfTheBoxElement; + // button wrapper div + buttonsDiv?: OutOfTheBoxElement; + // title and parent wrapper div + textTitle?: OutOfTheBoxElement; + // textTitleImg div + textTitleImg?: OutOfTheBoxElement; +}; +``` + +See also: + +- [`OutOfTheBoxElement`](#outoftheboxelement) +- [`OutOfTheBoxButton`](#outoftheboxbutton) + +## `GenerateJWTPayload` + +The payload to pass to the `generateJWT` function when calling [`initialize`](#initialize) +or [`initializeWithConfig`](#initializewithconfig). + +```ts +interface GenerateJWTPayload { + email?: string; + userID?: string; +} +``` + +## `ErrorHandler` + +An error-handling function. Passed as a parameter to [`IterableEmbeddedCard`](#iterableembeddedcard), +[`IterableEmbeddedBanner`](#iterableembeddedbanner), and [`IterableEmbeddedNotification`](#iterableembeddednotification), +which use the method to handle errors when tracking `embeddedClick` events. + +```ts +interface ErrorHandler { + (error: any): void; +} +``` + +## `GetInAppMessagesResponse` + +Return value for [`getInAppMessages`](#getinappmessages), when it's called without +the `options` parameter. + +```ts +interface GetInAppMessagesResponse { + pauseMessageStream: () => void; + resumeMessageStream: () => Promise; + request: () => IterablePromise; + triggerDisplayMessages: ( + messages: Partial[] + ) => Promise; +} +``` + +## `HandleLinks` + +Describes where in-app links should be opened. Part of [`InAppMessagesRequestParams`](#inappmessagesrequestparams). + +```ts +declare enum HandleLinks { + OpenAllNewTab = "open-all-new-tab", + OpenAllSameTab = "open-all-same-tab", + ExternalNewTab = "external-new-tab" +} +``` + +## `InAppMessage` + +A single in-app message. + +```ts +interface InAppMessage { + messageId: string; + campaignId: number; + createdAt: number; + expiresAt: number; + content: { + payload?: Record; + html: string | HTMLIFrameElement; + inAppDisplaySettings: { + top: InAppDisplaySetting; + right: InAppDisplaySetting; + left: InAppDisplaySetting; + bottom: InAppDisplaySetting; + bgColor?: { + alpha: number; + hex: string; + }; + shouldAnimate?: boolean; + }; + webInAppDisplaySettings: WebInAppDisplaySettings; + }; + customPayload: Record; + trigger: { + type: string; + }; + saveToInbox: boolean; + inboxMetadata: { + title: string; + subtitle: string; + icon: string; + }; + priorityLevel: number; + read: boolean; +} +``` + +See also: + +- [`InAppDisplaySetting`](#inappdisplaysetting) +- [`WebInAppDisplaySettings`](#webinappdisplaysettings) + +## `InAppDisplaySetting` + +Display settings for an in-app message, including padding percentages. + +```ts +interface InAppDisplaySetting { + percentage?: number; + displayOption?: string; +} +``` + +## `InAppEventRequestParams` + +Data to pass to [`trackInAppClick`](#trackinappclick), [`trackInAppClose`](#trackinappclose), +[`trackInAppConsume`](#trackinappconsume), [`trackInAppDelivery`](#trackinappdelivery), and +[`trackInAppOpen`](#trackinappopen). + +```ts +interface InAppEventRequestParams { + messageId: string; + clickedUrl?: string; + messageContext?: { + saveToInbox?: boolean; + silentInbox?: boolean; + location?: string; + }; + closeAction?: string; + deviceInfo: { + appPackageName: string; + }; + inboxSessionId?: string; + createdAt?: number; +} +``` + +## `InAppMessagesRequestParams` + +Data to pass to [`getInAppMessages`](#getinappmessages). + +```ts +interface InAppMessagesRequestParams extends SDKInAppMessagesParams { + count: number; + SDKVersion?: string; + packageName: string; +} +``` + +See also: + +- [`SDKInAppMessagesParams`](#sdkinappmessagesparams) + +## `InAppMessageResponse` + +Return value for [`getInAppMessages`](#getinappmessages), when it's called with +the `options` parameter. + +```ts +interface InAppMessageResponse { + inAppMessages: Partial[]; +} +``` + +See also: + +- [`InAppMessage`](#inappmessage) + +## `InAppTrackRequestParams` + +Data to pass to [`track`](#track). + +```ts +interface InAppTrackRequestParams { + eventName: string; + id?: string; + createdAt?: number; + dataFields?: Record; + campaignId?: number; + templateId?: number; +} +``` + +## `IterableAction` + +An action associated with a click. The type of the action, and its associated +URL. Only used with embedded messages. + +```ts +interface IterableAction { + type: string; + data: string; +} +``` + +The values for `type` and `data` depend on the type of action assigned to the +campaign in Iterable: + +- For **Open URL** actions, `type` is `openUrl` and `data` contains the URL. +- For **Custom action** actions, `type` is the URL, and `data` is empty. + +## `IterableActionContext` + +Information about the context of an `IterableAction`. For example, the +associated message type. Only used with embedded messages. + +```ts +interface IterableActionContext { + action: IterableAction; + source: IterableActionSource; +} +``` + +See also: + +- [`IterableAction`](#iterableaction) +- [`IterableActionSource`](#iterableactionsource) + +## `IterableActionSource` + +An enum of possible message types to which an `IterableAction` can be associated. +Currently, only `EMBEDDED` is supported. + +```ts +enum IterableActionSource { + EMBEDDED = 'EMBEDDED' +} +``` + +## `IterableConfig` + +A class that can hold configuration information for the SDK. Currently, only +`urlHandler` and `customActionHandler` are supported (static properties), and +these are only invoked for URLs and custom actions coming from embedded messages. + +```ts +class IterableConfig { + public static urlHandler: IterableUrlHandler | null = null; + public static customActionHandler: IterableCustomActionHandler | null = null; +} +``` + +See also: + +- [`IterableUrlHandler`](#iterableurlhandler) +- [`IterableCustomActionHandler`](#iterablecustomactionhandler) + +## `IterableCustomActionHandler` + +An interface that defines `handleIterableCustomAction`, which the SDK can call to +handle custom action URLs (`action://`) URLs that result from from clicks on +embedded messages. + +```ts +interface IterableCustomActionHandler { + handleIterableCustomAction( + action: IterableAction, + actionContext: IterableActionContext + ): boolean; +} +``` + +See also: + +- [`IterableAction`](#iterableaction) +- [`IterableActionContext`](#iterableactioncontext) + +## `IterableEmbeddedButton` + +Payload for a button associated with an embedded message. + +```ts +interface IterableEmbeddedButton { + id: string; + title?: string; + action?: IterableEmbeddedButtonAction; +} +``` + +See also: + +- [`IterableEmbeddedButtonAction`](#iterableembeddedbuttonaction) + +## `IterableEmbeddedButtonAction` + +Payload for the action associated with an embedded message button. + +```ts +interface IterableEmbeddedButtonAction { + type: string; + data?: string; +} +``` + +The values for `type` and `data` depend on the type of action assigned to the +campaign in Iterable: + +- For **Open URL** actions, `type` is `openUrl` and `data` contains the URL. +- For **Custom action** actions, `type` is the URL, and `data` is empty. + +## `IterableEmbeddedClickRequestPayload` + +Data to pass to [`trackEmbeddedClick`](#trackembeddedclick). + +```ts +interface IterableEmbeddedClickRequestPayload { + messageId: string; + buttonIdentifier: string; + targetUrl: string; + appPackageName: string; +} +``` + +## `IterableEmbeddedDefaultAction` + +The default action associated with an embedded message. Invoked when a user +clicks on an embedded message, but outside of its buttons. + +```ts +interface IterableEmbeddedDefaultAction { + type: string; + data?: string; +} +``` + +The values for `type` and `data` depend on the type of action assigned to the +campaign in Iterable: -API: +- For **Open URL** actions, `type` is `openUrl` and `data` contains the URL. +- For **Custom action** actions, `type` is the URL, and `data` is empty. -```ts -type Options = { - logLevel: 'none' | 'verbose'; - baseURL: string; - isEuIterableService: boolean; - dangerouslyAllowJsPopups: boolean; -}; +## `IterableEmbeddedElements` -interface InitializeParams { - authToken: string; - configOptions: Partial; - generateJWT?: (payload: GenerateJWTPayload) => Promise; -} +Content associated with an embedded message — title, body, media URL, +buttons, default action, and extra text fields. -initializeWithConfig: (initializeParams: InitializeParams) => { - clearRefresh: () => void; - setEmail: (email: string) => Promise; - setUserID: (userId: string) => Promise; - logout: () => void; +```ts +interface IterableEmbeddedElements { + title?: string; + body?: string; + mediaUrl?: string; + buttons?: IterableEmbeddedButton[]; + text?: IterableEmbeddedText[]; + defaultAction?: IterableEmbeddedDefaultAction; } ``` -Example: +See also: -```ts -import { initializeWithConfig } from '@iterable/web-sdk'; +- [`IterableEmbeddedButton`](#iterableembeddedbutton) +- [`IterableEmbeddedText`](#iterableembeddedtext) +- [`IterableEmbeddedDefaultAction`](#iterableembeddeddefaultaction) -const { clearRefresh, setEmail, setUserID, logout } = initializeWithConfig({ - authToken: 'my-API-key', - configOptions: { - isEuIterableService: false, - dangerouslyAllowJsPopups: true, - }, - /* - _email_ will be defined if you call _setEmail_ - _userID_ will be defined if you call _setUserID_ - */ - generateJWT: ({ email, userID }) => - yourAsyncJWTGeneratorMethod({ email, userID }).then( - ({ jwt_token }) => jwt_token - ) +## `IterableEmbeddedImpression` + +The number of times a given embedded message appeared during a specific session, +and the total duration of all those appearances. Also includes other metadata +about the impression. + +```ts +interface IterableEmbeddedImpression { + messageId: string; + displayCount: number; + displayDuration: number; + placementId?: number; } -); ``` -## refreshJwtToken +## `IterableEmbeddedManager` -API: +Used to fetch embedded messages from Iterable, and pass them to application code +as necessary. ```ts -refreshJwtToken: (authTypes: string) => Promise; +class IterableEmbeddedManager { + appPackageName: string; + constructor(appPackageName: string); + syncMessages(packageName: string, callback: () => void, placementIds?: number[]): Promise; + getMessages(): IterableEmbeddedMessage[]; + getMessagesForPlacement(placementId: number): IterableEmbeddedMessage[]; + addUpdateListener(updateListener: IterableEmbeddedMessageUpdateHandler): void; + getUpdateHandlers(): IterableEmbeddedMessageUpdateHandler[]; + click(clickedUrl: string | null): void; +} ``` -Example: +Descriptions: -```ts -import { initialize } from '@iterable/web-sdk'; +- `appPackageName` – The package name you use to identify your website. Set + this value by passing it to the constructror. -refreshJwtToken('user@example.com').then().catch(); -``` +- `syncMessages` – Fetches embedded messages for which the signed-in user is + eligible. If `placementIds` is provided, fetches only messages for those + placements. Calls `callback` after fetching messages. -## track +- `getMessages` – Returns all embedded messages that the SDK has already fetched. + Does not fetch messages from the server. -API [(see required API payload here)](https://api.iterable.com/api/docs#events_track): +- `getMessagesForPlacement` – Returns all embedded messages for a given placement + ID. Does not fetch messages from the server. -```ts -track: (payload: InAppTrackRequestParams) => Promise; -``` +- `addUpdateListener` – Registers an object that implements the + `IterableEmbeddedMessageUpdateHandler` interface. The SDK calls the object's + `onMessagesUpdated` and `onEmbeddedMessagingDisabled` methods as necessary + after fetching embedded messages from the server. -Example: +- `getUpdateHandlers` – Returns all objects that have been registered as update + listeners. -```ts -import { track } from '@iterable/web-sdk'; +- `click` – Passes the provided URL (depending on its type) to the URL handler + or custom action handler defined on `IterableConfig`. `action://` URLs are + passed to the custom action handler, and other URLs are passed to the URL + handler. The SDK does not currently support `iterable://` URLs for + embedded messages. -track({ eventName: 'my-event' }).then().catch(); -``` +See also: + +- [`IterableEmbeddedMessage`](#iterableembeddedmessage) +- [`IterableEmbeddedMessageUpdateHandler`](#iterableembeddedmessageupdatehandler) +- [`IterableConfig`](#iterableconfig) +- [`IterableUrlHandler`](#iterableurlhandler) +- [`IterableCustomActionHandler`](#iterablecustomactionhandler) -## trackInAppClick +## `IterableEmbeddedMessage` -API [(see required API payload here)](https://api.iterable.com/api/docs#events_trackInAppClick): +A single embedded message to display, including title text, body text, buttons, +an image URL, click actions, text fields, and JSON data. ```ts -trackInAppClick: (payload: InAppEventRequestParams) => Promise; +interface IterableEmbeddedMessage { + metadata: IterableEmbeddedMetadata; + elements?: IterableEmbeddedElements; + payload?: Record; +} ``` -Example: +See also: -```ts -import { trackInAppClick } from '@iterable/web-sdk'; +- [`IterableEmbeddedMetadata`](#iterableembeddedmetadata) +- [`IterableEmbeddedElements`](#iterableembeddedelements) -trackInAppClick({ - messageId: '123', - deviceInfo: { appPackageName: 'my-website' } -}) - .then() - .catch(); +## `IterableEmbeddedMessageUpdateHandler` + +An object that defines `onMessagesUpdated` and `onEmbeddedMessagingDisabled` +methods. If this object is registered as an update listener for embedded messages +(you can do this by calling `addUpdateListener` on [`IterableEmbeddedManager`](#iterableembeddedmanager)), +the SDK calls these methods as necessary after fetching embedded messages from +the server. + +```ts +interface IterableEmbeddedMessageUpdateHandler { + onMessagesUpdated: () => void; + onEmbeddedMessagingDisabled: () => void; +} ``` -## trackInAppClose +Descriptions: + +- `onMessagesUpdated` – Called by the SDK after it fetches embedded messages + from Iterable. Use this method to display messages. + +- `onEmbeddedMessagingDisabled` – Called by the SDK if there are errors fetching + embedded messages from Iterable. Use this method to display an empty state + or hide the placement. -API [(see required API payload here)](https://api.iterable.com/api/docs#events_trackInAppClose): +## `IterableEmbeddedMetadata` + +Identifying information about an embedded message. ```ts -trackInAppClose: (payload: InAppEventRequestParams) => Promise; +interface IterableEmbeddedMetadata { + messageId: string; + campaignId?: number; + isProof?: boolean; + placementId?: number; +} ``` -Example: +## `IterableEmbeddedSession` -```ts -import { trackInAppClose } from '@iterable/web-sdk'; +Represents a period of time during which a user was on a page where they could +potentially view embedded messages. Contains an ID, a start time, and an end +time. -trackInAppClose({ - messageId: '123', - deviceInfo: { appPackageName: 'my-website' } -}) - .then() - .catch(); +```ts +interface IterableEmbeddedSession { + id: string; + start?: number; + end?: number; +} ``` -## trackInAppConsume +## `IterableEmbeddedSessionManager` -API [(see required API payload here)](https://api.iterable.com/api/docs#events_inAppConsume): +Used to track sessions and impressions, and to save them back to Iterable. ```ts -trackInAppConsume: (payload: InAppEventRequestParams) => - Promise; +class IterableEmbeddedSessionManager { + appPackageName: string; + session: EmbeddedSession; + constructor(appPackageName: string); + startSession(): void; + endSession(): Promise; + startImpression(messageId: string, placementId: number): void; + pauseImpression(messageId: string): void; +} ``` -Example: +Descriptions: -```ts -import { trackInAppConsume } from '@iterable/web-sdk'; +- `appPackageName` – The package name you use to identify your website. Set + this value by passing it to the constructor. -trackInAppConsume({ - messageId: '123', - deviceInfo: { appPackageName: 'my-website' } -}) - .then() - .catch(); -``` +- `session` – The current session. Set by calling `startSession` and `endSession`. + +- `startSession` – Starts a new session. A session is a period of time when a + user is on a page where embedded messages can be displayed. + +- `endSession` – Ends the active session, and saves data about the session and + its associated impressions back to Iterable. + +- `startImpression` – Starts a new impression for a given message ID and placement + ID. An impression captures the number of times a given messages is visible during + a given session, and the total duration of all those appearances. + +- `pauseImpression` – Pauses the impression for a given message ID. Call this method + when a message is no longer visible. If the message becomes visible again, + call `startImpression` to resume the impression. -## trackInAppDelivery +## `IterableEmbeddedSessionRequestPayload` -API [(see required API payload here)](https://api.iterable.com/api/docs#events_trackInAppDelivery): +Data to pass to [`trackEmbeddedSession`](#trackembeddedsession). You won't usually +interact manually with this interface, since the [`IterableEmbeddedSessionManager`](#iterableembeddedsessionmanager) +handles the tracking of sessions and impressions for you. ```ts -trackInAppDelivery: (payload: InAppEventRequestParams) => - Promise; +interface IterableEmbeddedSessionRequestPayload { + session: IterableEmbeddedSession; + impressions?: IterableEmbeddedImpression[]; + appPackageName: string; +} ``` -Example: +See also: -```ts -import { trackInAppDelivery } from '@iterable/web-sdk'; +- [`IterableEmbeddedSession`](#iterableembeddedsession) +- [`IterableEmbeddedImpression`](#iterableembeddedimpression) -trackInAppDelivery({ - messageId: '123', - deviceInfo: { appPackageName: 'my-website' } -}) - .then() - .catch(); +## `IterableEmbeddedText` + +Extra text fields sent along with an embedded message. Like custom JSON, these +text fields can be used to pass data as part of an embedded message. + +```ts +interface IterableEmbeddedText { + id: string; + text?: string; +} ``` -## trackInAppOpen +## `IterableErrorStatus` -API [(see required API payload here)](https://api.iterable.com/api/docs#events_trackInAppOpen): +Errors that can come back with an [`IterableResponse`](#iterableresponse). ```ts -trackInAppOpen: (payload: InAppEventRequestParams) => Promise; +type IterableErrorStatus = + | 'Success' + | 'BadApiKey' + | 'BadParams' + | 'BadJsonBody' + | 'QueueEmailError' + | 'GenericError' + | 'InvalidEmailAddressError' + | 'DatabaseError' + | 'EmailAlreadyExists' + | 'Forbidden' + | 'JwtUserIdentifiersMismatched' + | 'InvalidJwtPayload'; ``` -Example: +## `IterablePromise` -```ts -import { trackInAppOpen } from '@iterable/web-sdk'; +A promise. -trackInAppOpen({ - messageId: '123', - deviceInfo: { appPackageName: 'my-website' } -}) - .then() - .catch(); +```ts +IterablePromise = AxiosPromise; ``` -## trackPurchase +## `IterableResponse` -API [(see required API payload here)](https://api.iterable.com/api/docs#commerce_trackPurchase): +A response from Iterable's API. ```ts -trackPurchase: (payload: TrackPurchaseRequestParams) => - Promise; +interface IterableResponse { + code: IterableErrorStatus; + msg: string; + params?: null | Record; +} ``` -Example: +## `IterableUrlHandler` -```ts -import { trackPurchase } from '@iterable/web-sdk'; +An interface that defines `handleIterableURL`, which the SDK can call to handle +standard URLs (`https://`, `custom://`, but not `action://`) that result from +from clicks on embedded messages. -trackPurchase({ - items: [{ id: '123', name: 'keyboard', price: 100, quantity: 2 }], - total: 200 -}) - .then() - .catch(); +```ts +interface IterableUrlHandler { + handleIterableURL(uri: string, actionContext: IterableActionContext): boolean; +} ``` -## updateCart +See also: -API [(see required API payload here)](https://api.iterable.com/api/docs#commerce_updateCart): +- [`IterableActionContext`](#iterableactioncontext) +- [`IterableConfig`](#iterableconfig) +- [`IterableEmbeddedManager`](#iterableembeddedmanager) + +## `OOTB` + +A type that defines the parameters to provide when calling +`IterableEmbeddedCard`, `IterableEmbeddedBanner`, and +`IterableEmbeddedNotification`. ```ts -updateCart: (payload: UpdateCartRequestParams) => Promise; +type OOTB = { + appPackageName: string; + message: IterableEmbeddedMessage; + htmlElements?: Elements; + // Callback method to handle button or element click errors + errorCallback?: ErrorHandler; +}; ``` -Example: +See also: -```ts -import { updateCart } from '@iterable/web-sdk'; +- [`Elements`](#elements) +- [`IterableEmbeddedBanner`](#iterableembeddedcard) +- [`IterableEmbeddedCard`](#iterableembeddedbanner) +- [`IterableEmbeddedNotification`](#iterableembeddednotification) +- [`IterableEmbeddedMessage`](#iterableembeddedmessage) +- [`ErrorHandler`](#errorhandler) -updateCart({ - items: [{ id: '123', price: 100, name: 'keyboard', quantity: 1 }] -}) - .then() - .catch(); +## `Options` + +Configuration options to pass to [`initializeWithConfig`](#initializewithconfig). + +```ts +type Options = { + logLevel: 'none' | 'verbose'; + baseURL: string; + isEuIterableService: boolean; + dangerouslyAllowJsPopups: boolean; +}; ``` -## updateSubscriptions +## `OutOfTheBoxButton` -API [(see required API payload here)](https://api.iterable.com/api/docs#users_updateSubscriptions): +Custom styles to apply to buttons in an embedded message. The same as +`OutOfTheBoxElement`, but with an extra `disabledStyles` string. ```ts -updateSubscriptions: (payload?: UpdateSubscriptionParams) => - Promise; +type OutOfTheBoxButton = OutOfTheBoxElement & { + // Stringified CSS to be passed to element "style" tag. The presence of this + // value determines whether or not the button is in disabled. + disabledStyles?: string; +}; ``` -Example: +## `OutOfTheBoxElement` -```ts -import { updateSubscriptions } from '@iterable/web-sdk'; +The custom styles to apply to a single element of an embedded message. -updateSubscriptions({ emailListIds: [1, 2, 3] }) - .then() - .catch(); +```ts +type OutOfTheBoxElement = { + // id of the element + id?: string; + // Stringified CSS to be passed to element "style" tag + styles?: string; +}; ``` -## updateUser +## `SDKInAppMessagesParams` -API [(see required API payload here)](https://api.iterable.com/api/docs#users_updateUser): +Parent interface for [`InAppMessagesRequestParams`](#inappmessagesrequestparams). ```ts -updateUser: (payload?: UpdateUserParams) => Promise; +interface SDKInAppMessagesParams { + displayInterval?: number; + onOpenScreenReaderMessage?: string; + onOpenNodeToTakeFocus?: string; + topOffset?: string; + bottomOffset?: string; + rightOffset?: string; + animationDuration?: number; + handleLinks?: HandleLinks; + closeButton?: CloseButton; + // messageId of the latest (i.e., most recent) message in the device's + // local cache + latestCachedMessageId?: string; +} ``` -Example: +See also: -```ts -import { updateUser } from '@iterable/web-sdk'; +- [`InAppMessaagesRequestParams`](#inappmessagesrequestparams) -updateUser({ dataFields: {} }).then().catch(); -``` +See also: + +- [`HandleLinks`](#handlelinks) +- [`CloseButton`](#closebutton) -## updateUserEmail +## `TrackPurchaseRequestParams` -API [(see required API payload here)](https://api.iterable.com/api/docs#users_updateEmail): +Parameters to pass to [`trackPurchase`](#trackpurchase). ```ts -updateUserEmail: (newEmail: string) => Promise; +interface TrackPurchaseRequestParams { + id?: string; + user?: CommerceUser; + items: CommerceItem[]; + campaignId?: string; + templateId?: string; + total: number; + createdAt?: number; + dataFields?: Record; +} ``` -Example: +See also: -```ts -import { updateUserEmail } from '@iterable/web-sdk'; +- [`CommerceUser`](#commerceuser) +- [`CommerceItem`](#commerceitem) -updateUserEmail('user@example.com').then().catch(); -``` +## `UpdateCartRequestParams` -# FAQ +Data to pass to [`updateCart`](#updatecart). + +```ts +interface UpdateCartRequestParams { + user?: CommerceUser; + items: CommerceItem[]; +} +``` -## How do I make API requests with the SDK? +See also: -First thing you need to do is generate an API key on [the Iterable app](https://app.iterable.com). Make sure this key is JWT-enabled and is of the _Web_ key type. This will ensure the SDK has -access to all the necessary endpoints when communicating with the Iterable API. After you generate your key, save both the API Key and JWT Secret somewhere handy. You'll need both of them. +- [`CommerceUser`](#commerceuser) +- [`CommerceItem`](#commerceitem) -First, you'll deal with the JWT Secret. Typically, you need some backend service that is going to use that JWT Secret to sign a JWT and return it to your client app. For the purposes of this explanation, this can be demonstrated this with a site like [jwt.io](https://jwt.io). See the [documentation on the Iterable website](https://support.iterable.com/hc/en-us/articles/360050801231-JWT-Enabled-API-Keys-) for instructions on how to generate a JWT from your JWT secret. +## `UpdateSubscriptionParams` -Once you have a JWT or a service that can generate a JWT automatically, you're ready to start making requests in the SDK. The syntax for that looks like this: +Data to pass to [`updateSubscriptions`](#updatesubscriptions). ```ts -import { initialize } from '@iterable/web-sdk'; +interface UpdateSubscriptionParams { + emailListIds: number[]; + unsubscribedChannelIds: number[]; + unsubscribedMessageTypeIds: number[]; + subscribedMessageTypeIds: number[]; + campaignId: number; + templateId: number; +} +``` -(() => { - initialize('YOUR_API_KEY_HERE', ({ email, userID }) => - yourAsyncJWTGeneratorMethod({ email, userID }).then( - ({ jwt_token }) => jwt_token - ) - ); -})(); +## `UpdateUserParams` + +Data to pass to `updateUser`. + +```ts +interface UpdateUserParams { + dataFields?: Record; + preferUserId?: boolean; + mergeNestedObjects?: boolean; +} ``` -Now that we've set our authorization logic within our app, it's time to set the user. You can identify a user by either the email or user ID. User ID is preferred because the SDK will automatically create a user in your Iterable instance. If you identify by email, the user will remain "anonymous" with no user ID attached to it. See [Iterable's updateUser endpoint](https://api.iterable.com/api/docs#users_updateUser) for more information about how users are created. +## `WebInAppDisplaySettings` -The syntax for identifying a user by user ID looks like this: +An object that contains information about how to display the associated in-app +message. ```ts -import { initialize } from '@iterable/web-sdk'; +interface WebInAppDisplaySettings { + position: DisplayPosition; +} +``` -(() => { - const { setUserID, logout } = initialize( - 'YOUR_API_KEY_HERE', - ({ email, userID }) => - yourAsyncJWTGeneratorMethod({ email, userID }).then( - ({ jwt_token }) => jwt_token - ) - ); +See also: - yourAsyncLoginMethod().then((response) => { - /* this code assumes you have some backend endpoint that will return a user's ID */ - setUserID(response.user_id).then(() => { - /* now your user is set and you can begin hitting the Iterable API */ - }); - }); +- [`DisplayPosition`](#displayposition) - /* optionally logout the user when you don't need to hit the Iterable API anymore */ - logout(); -})(); -``` +## `WithJWT` -Doing this with an email is similar: +Return value from [`initialize`](#initialize) and [`initializeWithConfig`](#initializeWithConfig). ```ts -import { initialize } from '@iterable/web-sdk'; +interface WithJWT { + clearRefresh: () => void; + setEmail: (email: string) => Promise; + setUserID: (userId: string) => Promise; + logout: () => void; + refreshJwtToken: (authTypes: string) => Promise; +} +``` -(() => { - const { setEmail, logout } = initialize( - 'YOUR_API_KEY_HERE', - ({ email, userID }) => - yourAsyncJWTGeneratorMethod({ email, userID }).then( - ({ jwt_token }) => jwt_token - ) - ); +Definitions: - yourAsyncLoginMethod().then((response) => { - /* - this code assumes you have some backend - endpoint that will return a user's email address - */ - setEmail(response.email).then(() => { - /* now your user is set and you can begin hitting the Iterable API */ - }); - }); +- `clearRefresh` – Clears the JWT refresh timer. +- `setEmail` – Identifies the current user by `email`, and fetches a valid JWT + token by calling the `generateJWT` function passed to [`initialize`](#initialize) + or [`initializeWithConfig`](#initializeWithConfig). +- `setUserID` - Identifies the current user by `userId`, and fetches a valid JWT + token by calling the `generateJWT` function passed to [`initialize`](#initialize) + or [`initializeWithConfig`](#initializeWithConfig). +- `refreshJwtToken` – Manually refreshes the JWT token for the signed-in user. +- `logout` – Signs the current user out of the SDK. - /* optionally logout the user when you don't need to hit the Iterable API anymore */ - logout(); -})(); -``` +## `WithJWTParams` -Now let's put it altogether with an Iterable API method: +Parameters to pass to [`initializeWithConfig`](#initializewithconfig). ```ts -import { initialize, track } from '@iterable/web-sdk'; +interface WithJWTParams { + authToken: string; + configOptions: Partial; + generateJWT: (payload: GenerateJWTPayload) => Promise; +} +``` -(() => { - const { setUserID, logout } = initialize( - 'YOUR_API_KEY_HERE', - ({ email, userID }) => - yourAsyncJWTGeneratorMethod({ email, userID }).then( - ({ jwt_token }) => jwt_token - ) - ); +`generateJWT` should be a function that takes a `userId` or `email` and uses +it to fetch, from your server, a valid JWT token for that user. The function +should return the token as a string. - yourAsyncLoginMethod().then((response) => { - /* this code assumes you have some backend endpoint that will return a user's ID */ - setUserID(response.user_id).then(() => { - document.getElementById('my-button').addEventListener('click', () => { - /* - no need to pass a user ID to this endpoint. - _setUserID_ takes care of this for you - */ - track({ eventName: 'button-clicked' }); - }); - }); - }); -})(); -``` +See also: + +- [`Options`](#options) +- [`GenerateJWTPayload`](#generatejwtpayload) + +# FAQ + +## How do I use Iterable's Web SDK to fetch and display embedded messages? + +For detailed instructions about how to use Iterable's Web SDK SDK to fetch and +display embedded messages, see [Embedded Messages with Iterable's Web SDK](https://support.iterable.com/hc/articles/27537816889108). + +For more information about Embedded Messaging, read the [Embedded Messaging Oveview](https://support.iterable.com/hc/articles/23060529977364). -## How does the SDK pass up my email / user ID? +## How does SDK add the user's `email` or `userId` to the requests it makes to Iterable? -This SDK relies on a library called [Axios](https://github.com/axios/axios). For all outgoing XHR requests, the SDK utilizes [Axios interceptors](https://github.com/axios/axios#interceptors) to add your user information to the requests. +The SDK uses a library called [Axios](https://github.com/axios/axios). To add +user information to outgoing requests, the SDK uses [Axios interceptors](https://github.com/axios/axios#interceptors). -## Ok cool. What if I want to handle this intercepting logic myself instead? +## How can I manipulate the API requests the SDK makes to Iterable? -You can do that! This SDK exposes the base Axios request instance so you can do whatever you like with it and build upon that. You can import the Axios request like so and anything in the Axios documentation is fair game to use: +Iterable's Web SDK SDK exposes the base Axios request instance, which you can +modify as necessary. For example: ```ts import { baseAxiosRequest } from '@iterable/web-sdk'; ``` -For example, if you want to set an `email` query param on every outgoing request, you would just implement the way Axios advises like so: +For example, if you want to set an `email` query param on every outgoing +request, you could do somethign like this: ```ts import { baseAxiosRequest } from '@iterable/web-sdk'; @@ -680,19 +2094,31 @@ import { baseAxiosRequest } from '@iterable/web-sdk'; })(); ``` -:rotating_light: Please note, you won't likely need access to this Axios instance. This is reserved for advanced use cases only. +:rotating_light: You probably won't need to do anything with the underlying +Axios request. This is only for advanced use cases. -## I want to automatically show my in-app messages with a delay between each +## How do I add a delay between the display of multiple in-app messages? -This SDK allows that. Simply call the `getInAppMessages` method but pass `{ display: 'immediate' }` as the second parameter. This will expose some methods used to make the request to show the messages and pause and resume the queue. +To add a delay between the display of multiple in-app messages: -Normally to request a list of in-app messages, you'd make a request like this: +1. In the object you pass as the first parameter to `getInAppMessages`, set + `displayInterval` to the number of milliseconds you want to wait between + messages. + +2. In the object you pass as the second parameter to `getInAppMessages`, set + `display` to `deferred`. + +Then, to show messages, pause the display of messages, and resume the display of +messages, use the methods returned by `getInAppMessages`. + +For example, this code fetches in-app messages from Iterable but doesn't display +them: ```ts import { initialize, getInAppMessages } from '@iterable/web-sdk'; (() => { - const { setUserID } = initialize('YOUR_API_KEY_HERE', ({ email, userID }) => + const { setUserID } = initialize('', ({ email, userID }) => yourAsyncJWTGeneratorMethod({ email, userID }).then( ({ jwt_token }) => jwt_token ) @@ -711,13 +2137,13 @@ import { initialize, getInAppMessages } from '@iterable/web-sdk'; })(); ``` -In order to take advantage of the SDK showing them automatically, you would implement the same method in this way: +This code fetches in-app messages and displays them automatically: ```ts import { initialize, getInAppMessages } from '@iterable/web-sdk'; (() => { - const { setUserID } = initialize('YOUR_API_KEY_HERE', ({ email, userID }) => + const { setUserID } = initialize('', ({ email, userID }) => yourAsyncJWTGeneratorMethod({ email, userID }).then( ({ jwt_token }) => jwt_token ) @@ -733,20 +2159,21 @@ import { initialize, getInAppMessages } from '@iterable/web-sdk'; { display: 'immediate' } ); - /* trigger the start of message presentation */ + // Trigger the start of message presentation request().then().catch(); }); }); })(); ``` -Optionally, you can pass arguments to fine-tune how you want the messages to appear. See the [usage section](#getInAppMessages) to see all available options and what they do. +This code manipulates the display of in-app messages by setting more fields in +the object passed as the first parameter to [`getInAppmessages`](#getinappmessages): ```ts import { initialize, getInAppMessages } from '@iterable/web-sdk'; (() => { - const { setUserID } = initialize('YOUR_API_KEY_HERE', ({ email, userID }) => + const { setUserID } = initialize('', ({ email, userID }) => yourAsyncJWTGeneratorMethod({ email, userID }).then( ({ jwt_token }) => jwt_token ) @@ -771,20 +2198,20 @@ import { initialize, getInAppMessages } from '@iterable/web-sdk'; { display: 'immediate' } ); - /* trigger the start of message presentation */ + // Trigger the start of message presentation request().then().catch(); }); }); })(); ``` -You can also pause and resume the messages stream if you like +This code pauses the display of messages, and then resumes: ```ts import { initialize, getInAppMessages } from '@iterable/web-sdk'; (() => { - const { setUserID } = initialize('YOUR_API_KEY_HERE', ({ email, userID }) => + const { setUserID } = initialize('', ({ email, userID }) => yourAsyncJWTGeneratorMethod({ email, userID }).then( ({ jwt_token }) => jwt_token ) @@ -801,23 +2228,21 @@ import { initialize, getInAppMessages } from '@iterable/web-sdk'; { display: 'immediate' } ); - /* trigger the start of message presentation */ + // Trigger the start of message presentation request().then().catch(); - /* pause any more in-app messages from appearing for a little while */ + // Prevent any more in-app messages from appearing for a little while pauseMessageStream(); - /* - pick up where you left off and show the next message in the queue. - And start the timer again. - */ + // Pick up where you left off — show the next message in the queue, and + // start the timer again. resumeMessageStream(); }); }); })(); ``` -Finally, you can also choose to do your own manipulation to the messages before choosing to display them: +This code manipulates the list of in-app messages before displaying them: ```ts import { @@ -828,7 +2253,7 @@ import { } from '@iterable/web-sdk'; (() => { - const { setUserID } = initialize('YOUR_API_KEY_HERE', ({ email, userID }) => + const { setUserID } = initialize('', ({ email, userID }) => yourAsyncJWTGeneratorMethod({ email, userID }).then( ({ jwt_token }) => jwt_token ) @@ -845,23 +2270,21 @@ import { { display: 'deferred' } ); - /* trigger the start of message presentation */ + // Trigger the start of message presentation request() .then((response) => { - /* do your own manipulation here */ + // Do your own manipulation here const filteredMessages = doStuffToMessages( response.data.inAppMessages ); - /* - also feel free to take advantage of the sorting/filtering - methods used internally - */ + // Also, feel free to take advantage of the sorting/filtering + // methods used internally const furtherManipulatedMessages = sortInAppMessages( filterHiddenInAppMessages(response.data.inAppMessages) ) as InAppMessage[]; - /* then display them whenever you want */ + // Display them whenever you want triggerDisplayMessages(furtherManipulatedMessages); }) .catch(); @@ -870,9 +2293,11 @@ import { })(); ``` -## I want my messages to look good on every device and be responsive +## How can I make sure that in-app messages are displayed responsively? -This SDK already handles that for you. The rules for the in-app message presentation varies based on which display type you've selected. Here's a table to explain how it works: +The SDK handles this for you. In-app message presentation varies based on +the display type (center, full, top-right, bottom-right) you select when sending +the campaign: | Message Position →

Browser Size ↓ | Center | Full | Top-Right | Bottom-Right | | ------------------------------------------------------ | ------ | ---- | --------- | ------------ | @@ -881,63 +2306,96 @@ This SDK already handles that for you. The rules for the in-app message presenta | 976px - 1300px | 50% | 100% | 33% | 33% | | 1300px+ | 50% | 100% | 25% | 25% | -Looking at this table, you can see the browser sizes on the left, and the display positions on top. For example, if your in-app message is positioned in the top-right of the screen and your browser window is at 1000px, then your in-app message will take up 33% of the screen. +For example: -Another example: If your in-app is positioned in the center and your browser if at 700px, your in-app message will grow to take up 100% of the screen. +- If your in-app message is positioned at the top-right of the screen and your + browser window is at 1000px, your in-app message will take up 33% of the + screen. +- If your in-app is positioned in the center and your browser if at 700px, your + in-app message will grow to take up 100% of the screen. -This chart also implies that your in-app message is taking 100% of its container. Your results may vary if you add, for example, a `max-width: 200px` CSS rule to your message HTML. Regardless of how you write your CSS, these rules will take effect, **so it is recommended that you stick to percentage-based CSS widths when possible when creating your message** +This chart also implies that yout in-app message is taking 100% of its container. +Your results may vary if you add, for example, a `max-width: 200px` CSS rule to +your message HTML. -## Clicking links breaks the experience of my single-page app (or how you add a custom callback to link clicks) +Regardless of how you write your CSS, these rules take effect. So, when creating +an in-app message, it is best to stick with percentage-based CSS widths. -No problem! Please see [the link handling section](#about-links) for more information on how to create callback methods on link clicks. There, you'll find information on how to create a seamless link-clicking experience if you're using a library such as React Router. +## How do I add custom callbacks to handle link clicks on in-app and embedded messages? -## What if my JWT expires? +See [Link handling](#link-handling). -JWT expiration is handled for you automatically by the SDK. There are 3 points where the SDK will generate a new JWT token for you, apart from the initial call when invoking `setEmail` or `setUserID`: +## What if the user's JWT expires? -1. The JWT is within 1 minute of expiration -2. An Iterable API request has failed with a 401 response -3. Your code invoked the `updateUserEmail` method +The SDK automatically handles JWT expiration and refresh. It fetches a new JWT +token for the signed-in user at four different times: -As previously explained, when initializing the SDK you need to pass a function that returns a Promise with the JWT, which looks something like this: +- When you sign a user in by calling `setEmail` or `setUserID`. +- When the JWT is within 1 minute of expiration. +- When a request to Iterable's API request fails with a `401` response. +- When your application code calls `updateUserEmail`. -```ts -import { initialize } from '@iterable/web-sdk'; +To fetch a new JWT, the SDK calls the `generateJWT` function passed to +[`initialize`](#initialize) or [`initializeWithConfig`](#initializeWithConfig). -initialize('API_KEY_HERE', ({ email, userID }) => - yourAsyncJWTGenerationMethod({ email, userID }).then( - (response) => response.jwt_token - ) -); -``` +If there's a failure when requesting a new JWT, the SDK does not try again. +At that point, further requests to Iterable's API will fail. + +To perform a manual JWT token refresh, call [`refreshJwtToken`](#refreshjwttoken). -When the previous 3 listed events occur, the SDK will invoke the method passed as the second argument, and when the Promise resolves, attach the new JWT to any future Iterable API requests. +# Iterable's European data center (EUDC) -Finally, if the request to regenerate the JWT fails however, the SDK will not attempt to generate the JWT again so requests will start failing at that point. +If your Iterable project is hosted on Iterable's [European data center (EUDC)](https://support.iterable.com/hc/articles/17572750887444), +you'll need to configure Iterable's Web SDK to interact with Iterable's EU-based +API endpoints. -To perform a manual JWT token refresh, call [`refreshJwtToken`](#refreshjwttoken). +To do this, you have two options: -# A note about imports +- On the web server that hosts your site, set the `IS_EU_ITERABLE_SERVICE` + environment variable to `true`. -This library exposes UMD modules and a single-file build for you to import from. In other words, this means that you'll be able to import methods in these ways: +- Or, when use [`initializeWithConfig`](#initializeWithConfig) to initialize + the SDK (rather then [`initialize`](#initialize)), and set set the + `isEuIterableService` configuration option to `true`. For example: -```ts -import { getInAppMessages, initialize, updateUser } from '@iterable/web-sdk'; -``` + ```ts + import { initializeWithConfig } from '@iterable/web-sdk'; + + const { clearRefresh, setEmail, logout } = initializeWithConfig({ + authToken: 'my-API-key', + configOptions: { + isEuIterableService: true, + }, + generateJWT: ({ email }) => + yourAsyncJWTGeneratorMethod({ email }).then( + ({ jwt_token }) => jwt_token + ) + }); + ``` -```ts -import { initialize, getInAppMessages, updateUser } from '@iterable/web-sdk'; -``` +# Link handling + +The SDK allows you to write your own callbacks to implement custom link-handling +behavior. However, you'll do this in different ways for embedded messages and +in-app messages. -For those using Webpack/Rollup/Some Other Build Tool, it is recommended to import methods with the later approach for smaller final bundles. Importing with the second method ensures your bundle will only include the code you're using and not the code you're not. +## Embedded messaging -# About links +To learn how to handle clicks on links found in embedded messages, read +[Embedded Messages with Iterable's Web SDK](https://support.iterable.com/hc/articles/27537816889108#step-8-2-handle-urls-and-custom-actions). -Since the Web SDK renders in-app messages in an iframe element on your website if you choose to render the messages automatically, the event handler that is responsible for clicking links is highjacked by the SDK code internally. To the user, this doesn't really change the experience. As expected, `` tags will open the link in the same browser tab unless given the `target="_blank"` property. +## In-app messages -But there are few features which the SDK adds so that you can customize how you'd like links to behave: +In-app messages render in an `iframe` element. If you choose to have the SDK +render messages automatically, the event handler responsible for handling link +clicks gets hijacked by internal SDK code. To the user, this doesn't change the +experience — links open the link in the same browser tab unless given the +`target="_blank"` property. -First, the `handleLinks` option provided by [`getInAppMessages`](#getInAppMessages) allows you to specify how the SDK opens links: in the current tab, in a new tab, or a combination (external links in a new tab, internal links in the current tab). For example, this code: +However, the `handleLinks` option that you can provide to [`getInAppMessages`](#getInAppMessages) +allows you to specify how the SDK opens in-app message links: in the current tab, +in a new tab, or a combination (external links in a new tab, internal links in the current tab). +For example, consider this code: ```ts import { getInAppMessages } from '@iterable/web-sdk'; @@ -949,7 +2407,8 @@ getInAppMessages({ }); ``` -will ensure the following links open in the same tab if your domain is `mydomain.com`, for example: +This example code ensures the following links open in the same tab if your +domain is `mydomain.com`: ``` /about @@ -957,45 +2416,53 @@ https://mydomain.com https://mydomain.com/about ``` -and these will open in a new tab +And that these will open in a new tab: ``` https://google.com https://hello.com ``` -## Reserved keyword links +### Reserved URL schemes -Iterable reserves the `iterable://` and `action://` URL schemas to define custom link click actions: +For in-app messages, the SDK reserves the `iterable://` and `action://` URL +schemes for custom purposes. -1. `iterable://dismiss` - Removes the in-app message from the screen, grabs the next one to display, and invokes both [trackInAppClose](#trackInAppClose) and [trackInAppClick](#trackInAppClick). +1. `iterable://dismiss` - Removes an in-app message from the screen, grabs the + next one to display, and invokes both [trackInAppClose](#trackInAppClose) and + [trackInAppClick](#trackInAppClick). Not applicable to embedded messages. -2. `action://{anything}` - Makes a [`Window.prototype.postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) call with payload `{ type: 'iterable-action-link', data: '{anything}' }`, to be consumed by the parent website as needed. These links also dismiss the message and invoke [trackInAppClose](#trackInAppClose) and [trackInAppClick](#trackInAppClick). +2. `action://` - Makes a [`Window.prototype.postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) + call with payload `{ type: 'iterable-action-link', data: '{anything}' }`, to be + consumed by the parent website as needed. These links also dismiss the message + and invoke [trackInAppClose](#trackInAppClose) and [trackInAppClick](#trackInAppClick). The SDK may reserve more keywords in the future. -:rotating_light: `iterable://` and `action://` links are not supported with WebKit (which affects iOS web browsers, Safari included). In these browsers, users can close an in-app message by clicking away from the message. +:rotating_light: `iterable://` and `action://` links are not supported with +WebKit (which affects iOS web browsers, Safari included). In these browsers, +users can close an in-app message by clicking away from the message. -## Routing in single-page apps +### Routing in single-page apps -Knowing now the custom link schemas available, let's explain how you can leverage them to add custom routing or callback functions. If for example you want to hook into a link click and send the user to your `/about` page with a client-side routing solution, you'd do something like this if you're using React Router: +You can add custom routing or callback functions for link clicks on in-app +messages. -```ts -/* - assuming you're clicking this link in your in-app message: - - go to about page -*/ +For example, if you want to intercept a link click and use a client-side routing +solution to send the user to your `/about` page, you could so something like this +(this example assumes that you're using React Router): +```ts +// This example assumes a click on this link: +// Go to the about page import { useHistory } from 'react-router-dom'; const SomeComponent = () => { const history = useHistory(); - React.useEffect(() => { global.addEventListener('message', (event) => { if (event.data.type && event.data.type === 'iterable-action-link') { - /* route us to the content that comes after "action://" */ + // Route us to the content that comes after "action://" history.push(`/${event.data.data}`); } }); @@ -1005,27 +2472,29 @@ const SomeComponent = () => { }; ``` -## Safari: Allowing JavaScript execution in tabs opened by in-app message link clicks +### Safari: Allowing JavaScript execution in tabs opened by in-app message link clicks -To display an in-app message, Iterable's Web SDK uses an `iframe` on which the `sandbox` attribute is set to `allow-same-origin allow-popups allow-top-navigation`. On Safari, this configuration blocks JavaScript execution in tabs that open because of link clicks in the `iframe`. +To display an in-app message, Iterable's Web SDK uses an `iframe` on which the +`sandbox` attribute is set to `allow-same-origin allow-popups allow-top-navigation`. +On Safari, this configuration blocks JavaScript execution in tabs that open because +of link clicks in the `iframe`. -To allow JavaScript to run in these new tabs: +To allow JavaScript to run in these new tabs, use [`initializeWithConfig`](#initializeWithConfig) , +pass in the configuration options, and set `dangerouslyAllowJsPopups` to `true`. -- You will need to migrate to the new [`initializeWithConfig`](#initializeWithConfig) method, pass in the configuration options, and set `dangerouslyAllowJsPopups` to `true` +For example: ```ts import { initializeWithConfig } from '@iterable/web-sdk'; const { clearRefresh, setEmail, setUserID, logout } = initializeWithConfig({ - authToken: 'my-API-key', + authToken: '<>', configOptions: { isEuIterableService: false, dangerouslyAllowJsPopups: true, }, - /* - _email_ will be defined if you call _setEmail_ - _userID_ will be defined if you call _setUserID_ - */ + // email will be defined if you call setEmail + // userID will be defined if you call setUserID generateJWT: ({ email, userID }) => yourAsyncJWTGeneratorMethod({ email, userID }).then( ({ jwt_token }) => jwt_token @@ -1034,11 +2503,14 @@ const { clearRefresh, setEmail, setUserID, logout } = initializeWithConfig({ ); ``` -- However, use caution. Allowing JavaScript to run in new tabs opens the door to the possibility of malicious code execution. +However, use caution. Allowing JavaScript to run in new tabs opens the door to +the possibility of malicious code execution. SDK version support: -- Versions [`1.0.11+`](https://github.com/Iterable/iterable-web-sdk/releases/tag/v1.0.10) of Iterable's Web SDK support the `DANGEROUSLY_ALLOW_JS_POPUP_EXECUTION` environment variable. +- Versions [`1.0.11+`](https://github.com/Iterable/iterable-web-sdk/releases/tag/v1.0.10) + of Iterable's Web SDK support the `DANGEROUSLY_ALLOW_JS_POPUP_EXECUTION` + environment variable. For more information, see: @@ -1047,18 +2519,19 @@ For more information, see: # TypeScript -The Iterable Web SDK includes TypeScript definitions out of the box. All SDK methods -should be typed for you already but if you need to import specific typings, you can -parse through each `types.d.ts` file inside of the `./dist` directory to find what you need. +Iterable's Web SDK includes TypeScript definitions. All SDK methods should be +typed for you, but if you need to import specific typings, you can parse through +each `types.d.ts` file inside of the `./dist` directory to find what you need. Request and response payloads should all be available. -If you feel something is missing, feel free to open an issue! +If something is missing, please let us know. # Contributing -Looking to contribute? Please see the [contributing instructions here](./CONTRIBUTING.md) for more -details. +Looking to contribute? Please see the [contributing instructions here](./CONTRIBUTING.md) +for more details. # License -This SDK is released under the MIT License. See [LICENSE](./LICENSE.md) for more information. +This SDK is released under the MIT License. See [LICENSE](./LICENSE.md) for more +information. diff --git a/package.json b/package.json index d5027202..2f376d07 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@iterable/web-sdk", "description": "Iterable SDK for JavaScript and Node.", - "version": "1.1.0", + "version": "1.1.1", "homepage": "https://iterable.com/", "repository": { "type": "git", @@ -34,9 +34,12 @@ "@pabra/sortby": "^1.0.1", "@types/ws": "8.5.4", "axios": "^1.6.2", + "axios-mock-adapter": "^1.22.0", "buffer": "^6.0.3", "copy-webpack-plugin": "^11.0.0", "idb-keyval": "^6.2.0", + "lodash": "^4.17.21", + "qs": "^6.12.3", "throttle-debounce": "^3.0.1", "uuid": "^9.0.0", "yup": "^0.32.9" @@ -74,14 +77,15 @@ "@typescript-eslint/eslint-plugin": "^5.38.1", "@typescript-eslint/parser": "^5.38.1", "@webpack-cli/serve": "^1.6.0", - "axios-mock-adapter": "^1.22.0", "babel-loader": "^9.1.3", "babel-plugin-module-resolver": "^5.0.0", "concurrently": "^6.3.0", "css-loader": "^6.8.1", "dotenv": "^10.0.0", - "eslint": "^7.14.0", + "eslint": "^8.2.0", + "eslint-config-airbnb-base": "^15.0.0", "eslint-config-prettier": "^8.3.0", + "eslint-plugin-import": "^2.25.2", "eslint-plugin-prettier": "^3.4.0", "fake-indexeddb": "^4.0.0", "file-loader": "^6.2.0", @@ -113,4 +117,4 @@ "eslint" ] } -} \ No newline at end of file +} diff --git a/react-example/package.json b/react-example/package.json index e9c18611..0a41d81a 100644 --- a/react-example/package.json +++ b/react-example/package.json @@ -33,13 +33,14 @@ "@babel/plugin-proposal-object-rest-spread": "^7.5.5", "@babel/plugin-proposal-optional-chaining": "^7.14.5", "@babel/preset-react": "^7.22.15", + "@babel/preset-typescript": "^7.9.0", + "@playwright/test": "^1.44.0", "@types/jest": "^27.0.2", + "@types/node": "^20.12.12", "@types/react": "^18.2.21", "@types/react-dom": "^18.2.7", "@types/styled-components": "^5.1.34", - "@babel/preset-typescript": "^7.9.0", - "@playwright/test": "^1.44.0", - "@types/node": "^20.12.12", + "@types/uuid": "^9.0.2", "@typescript-eslint/eslint-plugin": "^2.26.0", "@typescript-eslint/parser": "^2.26.0", "@webpack-cli/serve": "^1.6.0", @@ -60,11 +61,11 @@ "typescript": "^4.6.4", "webpack": "^5.76.0", "webpack-cli": "^4.9.1", - "webpack-dev-server": "^4.7.3", - "@types/uuid": "^9.0.2" + "webpack-dev-server": "^4.7.3" }, "dependencies": { "@iterable/web-sdk": "../", + "axios": "^1.7.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.2.1", diff --git a/react-example/src/components/Button.tsx b/react-example/src/components/Button.tsx index dc8c2b18..e3770184 100644 --- a/react-example/src/components/Button.tsx +++ b/react-example/src/components/Button.tsx @@ -1,7 +1,7 @@ import { ButtonHTMLAttributes, FC } from 'react'; import styled from 'styled-components'; -const _Button = styled.button` +const StyledButton = styled.button` text-align: center; text-decoration: none; width: 60%; @@ -33,7 +33,7 @@ const _Button = styled.button` } `; -const DisabledButton = styled(_Button)` +const DisabledButton = styled(StyledButton)` background-color: gray; color: #c7c7c7; -webkit-box-shadow: 0 5px 0 0 #4d4d4d; @@ -64,7 +64,5 @@ export const Button: FC = (props) => { ); } - return <_Button {...rest}>{children}; + return {children}; }; - -export default Button; diff --git a/react-example/src/components/EmbeddedForm.tsx b/react-example/src/components/EmbeddedForm.tsx index 663618f5..2cbd1ee6 100644 --- a/react-example/src/components/EmbeddedForm.tsx +++ b/react-example/src/components/EmbeddedForm.tsx @@ -1,11 +1,4 @@ import { FC, FormEvent, useState } from 'react'; -import { - Button, - EndpointWrapper, - Form, - Heading, - Response -} from '../views/Components.styled'; import { IterableEmbeddedManager, IterableEmbeddedMessageUpdateHandler, @@ -14,11 +7,17 @@ import { trackEmbeddedClick, trackEmbeddedDismiss } from '@iterable/web-sdk'; -import TextField from 'src/components/TextField'; import { v4 as uuidv4 } from 'uuid'; +import { TextField } from './TextField'; +import { + StyledButton, + EndpointWrapper, + Form, + Heading, + Response +} from '../views/Components.styled'; interface Props { - userId: string; endpointName: string; heading: string; needsInputField?: boolean; @@ -32,7 +31,6 @@ export const TYPE_DISMISS = 3; export const TYPE_SESSION = 4; export const EmbeddedForm: FC = ({ - userId, endpointName, heading, needsInputField, @@ -77,7 +75,7 @@ export const EmbeddedForm: FC = ({ setTrackingEvent(true); const receivedMessage = { - messageId: messageId, + messageId, appPackageName: 'my-lil-site' }; @@ -99,7 +97,7 @@ export const EmbeddedForm: FC = ({ setTrackingEvent(true); const payload = { - messageId: messageId, + messageId, campaignId: 1 }; @@ -130,7 +128,7 @@ export const EmbeddedForm: FC = ({ setTrackingEvent(true); const sessionData = { - messageId: messageId, + messageId, buttonIdentifier: '123', deviceInfo: { deviceId: '123', @@ -163,7 +161,7 @@ export const EmbeddedForm: FC = ({ }, impressions: [ { - messageId: messageId, + messageId, displayCount: 1, displayDuration: 1000 } @@ -224,14 +222,12 @@ export const EmbeddedForm: FC = ({ /> )} - + {trackResponse}
); }; - -export default EmbeddedForm; diff --git a/react-example/src/components/EventsForm.tsx b/react-example/src/components/EventsForm.tsx index eaa3f774..4a113612 100644 --- a/react-example/src/components/EventsForm.tsx +++ b/react-example/src/components/EventsForm.tsx @@ -1,18 +1,19 @@ -import { FC, FormEvent, useState } from 'react'; +import { ChangeEvent, FC, FormEvent, useState } from 'react'; +import { IterablePromise, IterableResponse } from '@iterable/web-sdk'; import { - Button, + StyledButton, EndpointWrapper, Form, Heading, Response } from '../views/Components.styled'; -import { IterablePromise, IterableResponse } from '@iterable/web-sdk'; -import TextField from 'src/components/TextField'; +import { TextField } from './TextField'; interface Props { endpointName: string; heading: string; needsEventName?: boolean; + // eslint-disable-next-line @typescript-eslint/no-unused-vars method: (...args: any) => IterablePromise; } @@ -65,19 +66,19 @@ export const EventsForm: FC = ({ setTrackEvent(e.target.value)} + onChange={(event: ChangeEvent) => { + setTrackEvent(event.target.value); + }} id="item-1" placeholder={needsEventName ? 'e.g. button-clicked' : 'e.g. df3fe3'} {...inputAttr} /> - + {trackResponse} ); }; - -export default EventsForm; diff --git a/react-example/src/components/Link.tsx b/react-example/src/components/Link.tsx index 83291dc0..1e2d658e 100644 --- a/react-example/src/components/Link.tsx +++ b/react-example/src/components/Link.tsx @@ -72,5 +72,3 @@ export const Link: FC = (props) => { return <_Link {...rest}>{children}; }; - -export default Link; diff --git a/react-example/src/components/LoginForm.tsx b/react-example/src/components/LoginForm.tsx index b012ffce..89504229 100644 --- a/react-example/src/components/LoginForm.tsx +++ b/react-example/src/components/LoginForm.tsx @@ -1,14 +1,15 @@ -import { ChangeEvent, FC, FormEvent, useState } from 'react'; +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { ChangeEvent, FC, FormEvent, SetStateAction, useState } from 'react'; import styled from 'styled-components'; -import _TextField from 'src/components/TextField'; -import _Button from 'src/components/Button'; +import { TextField } from './TextField'; +import { Button } from './Button'; -import { useUser } from 'src/context/Users'; +import { useUser } from '../context/Users'; -const TextField = styled(_TextField)``; +const StyledTextField = styled(TextField)``; -const Button = styled(_Button)` +const StyledButton = styled(Button)` margin-left: 0.4em; max-width: 425px; `; @@ -20,7 +21,7 @@ const Form = styled.form` justify-content: flex-end; height: 100%; - ${TextField} { + ${StyledTextField} { align-self: stretch; margin-top: 5px; } @@ -101,11 +102,13 @@ export const LoginForm: FC = ({ <> {loggedInUser && !isEditingUser ? ( <> - - - + + + Manually Refresh JWT Token + + Logout ) : ( @@ -134,8 +137,10 @@ export const LoginForm: FC = ({
- updateUser(e.target.value)} + ) => { + updateUser(event.target.value); + }} value={user} placeholder="e.g. hello@gmail.com" required @@ -152,5 +157,3 @@ export const LoginForm: FC = ({ ); }; - -export default LoginForm; diff --git a/react-example/src/components/TextField.tsx b/react-example/src/components/TextField.tsx index e5216bbc..9989b84d 100644 --- a/react-example/src/components/TextField.tsx +++ b/react-example/src/components/TextField.tsx @@ -13,5 +13,3 @@ export const TextField: FC = (props) => { const { ...rest } = props; return ; }; - -export default TextField; diff --git a/react-example/src/index.tsx b/react-example/src/index.tsx index 5f1c1575..5cccc81d 100644 --- a/react-example/src/index.tsx +++ b/react-example/src/index.tsx @@ -2,23 +2,22 @@ import { initializeWithConfig, WithJWTParams } from '@iterable/web-sdk'; import axios from 'axios'; import './styles/index.css'; -import Home from 'src/views/Home'; -import Commerce from 'src/views/Commerce'; -import AUTTesting from 'src/views/AUTTesting'; - -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'; -import LoginForm from 'src/components/LoginForm'; -import EmbeddedMsgs from 'src/views/EmbeddedMsgs'; - -import { UserProvider } from 'src/context/Users'; import { createRoot } from 'react-dom/client'; -import EmbeddedMsgsImpressionTracker from './views/EmbeddedMsgsImpressionTracker'; +import { Home } from './views/Home'; +import { Commerce } from './views/Commerce'; +import { Events } from './views/Events'; +import { Users } from './views/Users'; +import { InApp } from './views/InApp'; +import { EmbeddedMessage } from './views/Embedded'; +import { Link } from './components/Link'; +import { LoginForm } from './components/LoginForm'; +import { EmbeddedMsgs } from './views/EmbeddedMsgs'; +import AUTTesting from './views/AUTTesting'; + +import { UserProvider } from './context/Users'; +import { EmbeddedMsgsImpressionTracker } from './views/EmbeddedMsgsImpressionTracker'; const Wrapper = styled.div` display: flex; @@ -50,8 +49,8 @@ const HomeLink = styled(Link)` dangerouslyAllowJsPopups: true, enableAnonTracking: true }, - generateJWT: ({ email, userID }) => { - return axios + generateJWT: ({ email, userID }) => + axios .post( process.env.JWT_GENERATOR || 'http://localhost:5000/generate', { @@ -66,10 +65,7 @@ const HomeLink = styled(Link)` } } ) - .then((response: any) => { - return response.data?.token; - }); - } + .then((response: any) => response.data?.token) }; const { setEmail, setUserID, logout, refreshJwtToken } = initializeWithConfig(initializeParams); diff --git a/react-example/src/views/AUTTesting.tsx b/react-example/src/views/AUTTesting.tsx index 3c5fe0a2..391c8e3d 100644 --- a/react-example/src/views/AUTTesting.tsx +++ b/react-example/src/views/AUTTesting.tsx @@ -1,13 +1,5 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { FC, FormEvent, useState } from 'react'; -import TextField from 'src/components/TextField'; -import { - Button, - EndpointWrapper, - Form, - Heading, - Response -} from './Components.styled'; - import { updateCart, trackPurchase, @@ -18,6 +10,14 @@ import { track, InAppTrackRequestParams } from '@iterable/web-sdk'; +import { TextField } from '../components/TextField'; +import { + Button, + EndpointWrapper, + Form, + Heading, + Response +} from './Components.styled'; interface Props {} @@ -62,16 +62,16 @@ export const AUTTesting: FC = () => { if (isUpdateCartCalled) { const parsedObject = JSON.parse(cartItem) as UpdateCartRequestParams; return parsedObject; - } else { - const parsedObject = JSON.parse( - purchaseItem - ) as TrackPurchaseRequestParams; - return parsedObject; } + const parsedObject = JSON.parse( + purchaseItem + ) as TrackPurchaseRequestParams; + return parsedObject; } catch (error) { - if (isUpdateCartCalled) + if (isUpdateCartCalled) { setUpdateCartResponse(JSON.stringify(error.message)); - else setTrackPurchaseResponse(JSON.stringify(error.message)); + } else setTrackPurchaseResponse(JSON.stringify(error.message)); + return error; } }; @@ -81,6 +81,7 @@ export const AUTTesting: FC = () => { return JSON.parse(userDataField) as UpdateUserParams; } catch (error) { setUpdateUserResponse(JSON.stringify(error.message)); + return error; } }; @@ -157,6 +158,7 @@ export const AUTTesting: FC = () => { return parsedObject; } catch (error) { setTrackResponse(JSON.stringify(error.message)); + return error; } }; @@ -194,9 +196,9 @@ export const AUTTesting: FC = () => { } }; - const formAttr = { ['data-qa-track-submit']: true }; - const inputAttr = { ['data-qa-track-input']: true }; - const responseAttr = { ['data-qa-track-response']: true }; + const formAttr = { 'data-qa-track-submit': true }; + const inputAttr = { 'data-qa-track-input': true }; + const responseAttr = { 'data-qa-track-response': true }; return ( <> diff --git a/react-example/src/views/Commerce.tsx b/react-example/src/views/Commerce.tsx index 53910498..74500397 100644 --- a/react-example/src/views/Commerce.tsx +++ b/react-example/src/views/Commerce.tsx @@ -1,15 +1,14 @@ import { FC, FormEvent, useState } from 'react'; -import TextField from 'src/components/TextField'; +import { updateCart, trackPurchase } from '@iterable/web-sdk'; +import { TextField } from '../components/TextField'; import { - Button, + StyledButton, EndpointWrapper, Form, Heading, Response } from './Components.styled'; -import { updateCart, trackPurchase } from '@iterable/web-sdk'; - interface Props {} export const Commerce: FC = () => { @@ -83,9 +82,9 @@ export const Commerce: FC = () => { placeholder="e.g. keyboard" data-qa-cart-input /> - + {updateCartResponse} @@ -100,14 +99,12 @@ export const Commerce: FC = () => { placeholder="e.g. keyboard" data-qa-purchase-input /> - + {trackPurchaseResponse} ); }; - -export default Commerce; diff --git a/react-example/src/views/Components.styled.ts b/react-example/src/views/Components.styled.ts index a30f81fc..0f2b9e58 100644 --- a/react-example/src/views/Components.styled.ts +++ b/react-example/src/views/Components.styled.ts @@ -1,5 +1,5 @@ import styled from 'styled-components'; -import _Button from 'src/components/Button'; +import { Button } from '../components/Button'; export const Form = styled.form` display: flex; @@ -37,6 +37,8 @@ export const Heading = styled.h2` margin-top: 3em; `; -export const Button = styled(_Button)` +export const StyledButton = styled(Button)` margin-top: 1em; `; + +export { Button }; diff --git a/react-example/src/views/Embedded.tsx b/react-example/src/views/Embedded.tsx index 35b436d2..ffbca9d2 100644 --- a/react-example/src/views/Embedded.tsx +++ b/react-example/src/views/Embedded.tsx @@ -1,13 +1,14 @@ import { FC, useEffect, useState } from 'react'; import { initialize } from '@iterable/web-sdk'; -import TextField from 'src/components/TextField'; -import EmbeddedForm, { +import { TextField } from '../components/TextField'; +import { + EmbeddedForm, TYPE_CLICK, TYPE_DISMISS, TYPE_GET_RECEIVED, TYPE_POST_RECEIVED, TYPE_SESSION -} from 'src/components/EmbeddedForm'; +} from '../components/EmbeddedForm'; interface Props {} @@ -35,14 +36,12 @@ export const EmbeddedMessage: FC = () => { heading="GET /embedded-messaging/events/received" endpointName="received-get" type={TYPE_GET_RECEIVED} - userId={userId} />

@@ -50,7 +49,6 @@ export const EmbeddedMessage: FC = () => { heading="POST /embedded-messaging/events/click" endpointName="click" type={TYPE_CLICK} - userId={userId} needsInputField={true} />
@@ -59,7 +57,6 @@ export const EmbeddedMessage: FC = () => { endpointName="dismiss" needsInputField={true} type={TYPE_DISMISS} - userId={userId} />
= () => { endpointName="session" needsInputField={true} type={TYPE_SESSION} - userId={userId} />
); }; - -export default EmbeddedMessage; diff --git a/react-example/src/views/EmbeddedMsgs.tsx b/react-example/src/views/EmbeddedMsgs.tsx index fce1d70d..5ffa9049 100644 --- a/react-example/src/views/EmbeddedMsgs.tsx +++ b/react-example/src/views/EmbeddedMsgs.tsx @@ -11,8 +11,8 @@ import { IterableAction, IterableConfig } from '@iterable/web-sdk'; -import Button from 'src/components/Button'; -import { useUser } from 'src/context/Users'; +import { Button } from '../components/Button'; +import { useUser } from '../context/Users'; const StyleOverrides = { parent: { @@ -79,7 +79,7 @@ export const EmbeddedMsgs: FC = () => { useEffect(() => { const urlHandler: IterableUrlHandler = { - handleIterableURL: function (uri: string): boolean { + handleIterableURL(uri: string): boolean { window.open(uri, '_blank'); return true; } @@ -87,7 +87,7 @@ export const EmbeddedMsgs: FC = () => { IterableConfig.urlHandler = urlHandler; const customActionHandler: IterableCustomActionHandler = { - handleIterableCustomAction: function (action: IterableAction): boolean { + handleIterableCustomAction(action: IterableAction): boolean { if (action.data === 'news') { // handle the custom action here and navigate based on action data return true; @@ -101,11 +101,11 @@ export const EmbeddedMsgs: FC = () => { const handleFetchEmbeddedMessages = async () => { try { const updateListener: IterableEmbeddedMessageUpdateHandler = { - onMessagesUpdated: function (): void { + onMessagesUpdated(): void { // this callback gets called when messages are fetched/updated setMessages(embeddedManager.getMessages()); }, - onEmbeddedMessagingDisabled: function (): void { + onEmbeddedMessagingDisabled(): void { setMessages([]); } }; @@ -229,14 +229,15 @@ export const EmbeddedMsgs: FC = () => { }} > {messages.length > 0 ? ( - messages.map((message: IterableEmbeddedMessage, index: number) => { + messages.map((message: IterableEmbeddedMessage) => { switch (selectedButtonIndex) { case 0: { const card = IterableEmbeddedCard({ appPackageName, message, ...(useCustomStyles && { htmlElements: StyleOverrides }), - errorCallback: (error) => console.log('handleError: ', error) + errorCallback: (error: any) => + console.log('handleError: ', error) }); return (
= () => { appPackageName, message, ...(useCustomStyles && { htmlElements: StyleOverrides }), - errorCallback: (error) => console.log('handleError: ', error) + errorCallback: (error: any) => + console.log('handleError: ', error) }); return (
= () => { appPackageName, message, ...(useCustomStyles && { htmlElements: StyleOverrides }), - errorCallback: (error) => console.log('handleError: ', error) + errorCallback: (error: any) => + console.log('handleError: ', error) }); return (
= () => { ); }; - -export default EmbeddedMsgs; diff --git a/react-example/src/views/EmbeddedMsgsImpressionTracker.tsx b/react-example/src/views/EmbeddedMsgsImpressionTracker.tsx index d0a772eb..d2a39c1a 100644 --- a/react-example/src/views/EmbeddedMsgsImpressionTracker.tsx +++ b/react-example/src/views/EmbeddedMsgsImpressionTracker.tsx @@ -6,13 +6,13 @@ import { IterableEmbeddedMessageUpdateHandler, IterableEmbeddedSessionManager } from '@iterable/web-sdk'; -import { useUser } from 'src/context/Users'; +import { useUser } from '../context/Users'; interface Props {} export const EmbeddedMsgsImpressionTracker: FC = () => { const elementCardRef = useRef([]); - const { loggedInUser, setLoggedInUser } = useUser(); + const { loggedInUser } = useUser(); const appPackageName = 'my-website'; const [messages, setMessages] = useState([]); const [embeddedManager] = useState( @@ -24,7 +24,8 @@ export const EmbeddedMsgsImpressionTracker: FC = () => { ); const getCardObserver = () => { - const visibilityStatus = messages.map(() => false); // Initialize visibility status for each message + // Initialize visibility status for each message + const visibilityStatus = messages.map(() => false); return messages.map( (msg: IterableEmbeddedMessage, index) => new IntersectionObserver( @@ -81,10 +82,10 @@ export const EmbeddedMsgsImpressionTracker: FC = () => { const handleFetchEmbeddedMessages = async () => { try { const updateListener: IterableEmbeddedMessageUpdateHandler = { - onMessagesUpdated: function (): void { + onMessagesUpdated(): void { setMessages(embeddedManager.getMessages()); }, - onEmbeddedMessagingDisabled: function (): void { + onEmbeddedMessagingDisabled(): void { setMessages([]); } }; @@ -125,6 +126,7 @@ export const EmbeddedMsgsImpressionTracker: FC = () => { return (
(elementCardRef.current[index] = el)} dangerouslySetInnerHTML={{ __html: card }} /> @@ -137,5 +139,3 @@ export const EmbeddedMsgsImpressionTracker: FC = () => { ); }; - -export default EmbeddedMsgsImpressionTracker; diff --git a/react-example/src/views/Events.tsx b/react-example/src/views/Events.tsx index bdfe1d56..cbcb40b6 100644 --- a/react-example/src/views/Events.tsx +++ b/react-example/src/views/Events.tsx @@ -1,6 +1,4 @@ import { FC } from 'react'; -import EventsForm from 'src/components/EventsForm'; - import { track, trackInAppClick, @@ -9,46 +7,43 @@ import { trackInAppDelivery, trackInAppOpen } from '@iterable/web-sdk'; +import { EventsForm } from '../components/EventsForm'; interface Props {} -export const Events: FC = () => { - return ( - <> -

Events Endpoints

- - - - - - - - ); -}; - -export default Events; +export const Events: FC = () => ( + <> +

Events Endpoints

+ + + + + + + +); diff --git a/react-example/src/views/Home.tsx b/react-example/src/views/Home.tsx index 697aa7e6..3c4d317c 100644 --- a/react-example/src/views/Home.tsx +++ b/react-example/src/views/Home.tsx @@ -1,6 +1,6 @@ import { FC } from 'react'; -import _Link from 'src/components/Link'; import styled from 'styled-components'; +import { Link } from '../components/Link'; const Wrapper = styled.div` display: flex; @@ -8,45 +8,42 @@ const Wrapper = styled.div` align-items: center; `; -const Link = styled(_Link)` +const StyledLink = styled(Link)` margin-top: 1em; `; interface Props {} -export const Home: FC = () => { - return ( - <> -

Namespace Selection

- - - Commerce - - - Events - - - Users - - - inApp - - {/* Note: The following components (specifically Embedded Message View Types) will not be supported until a later release. */} - - Embedded Msgs - - - embedded - - - Embedded msgs impressions tracker - - - AUT Testing - - - - ); -}; - -export default Home; +export const Home: FC = () => ( + <> +

Namespace Selection

+ + + Commerce + + + Events + + + Users + + + inApp + + {/* Note: The following components (specifically Embedded Message View Types) + will not be supported until a later release. */} + + Embedded Msgs + + + embedded + + + Embedded msgs impressions tracker + + + AUT Testing + + + +); diff --git a/react-example/src/views/InApp.tsx b/react-example/src/views/InApp.tsx index ef3dcf7d..af2782ed 100644 --- a/react-example/src/views/InApp.tsx +++ b/react-example/src/views/InApp.tsx @@ -1,11 +1,11 @@ import { FC, FormEvent, useState } from 'react'; import styled from 'styled-components'; -import _Button from 'src/components/Button'; -import { EndpointWrapper, Heading, Response } from './Components.styled'; -import { useUser } from 'src/context/Users'; import { DisplayOptions, getInAppMessages } from '@iterable/web-sdk'; +import { Button } from '../components/Button'; +import { useUser } from '../context/Users'; +import { EndpointWrapper, Heading, Response } from './Components.styled'; -const Button = styled(_Button)` +const StyledButton = styled(Button)` width: 100%; margin-bottom: 1em; `; @@ -101,7 +101,7 @@ export const InApp: FC<{}> = () => {

inApp Endpoints

POST /inApp/web/getMessages (auto-display) - - - + POST /inApp/web/getMessages @@ -153,5 +153,3 @@ export const InApp: FC<{}> = () => { ); }; - -export default InApp; diff --git a/react-example/src/views/Users.tsx b/react-example/src/views/Users.tsx index 3740a0e2..df990e36 100644 --- a/react-example/src/views/Users.tsx +++ b/react-example/src/views/Users.tsx @@ -1,19 +1,19 @@ import { FC, FormEvent, useState } from 'react'; -import TextField from 'src/components/TextField'; -import { - Button, - EndpointWrapper, - Form, - Heading, - Response -} from './Components.styled'; import { updateUser, updateSubscriptions, updateUserEmail } from '@iterable/web-sdk'; +import { TextField } from '../components/TextField'; -import { useUser } from 'src/context/Users'; +import { useUser } from '../context/Users'; +import { + StyledButton, + EndpointWrapper, + Form, + Heading, + Response +} from './Components.styled'; interface Props {} @@ -105,9 +105,9 @@ export const Users: FC = () => { data-qa-update-user-input required /> - + {updateUserResponse} @@ -126,9 +126,9 @@ export const Users: FC = () => { type="email" required /> - + {updateUserEmailResponse} @@ -150,9 +150,9 @@ export const Users: FC = () => { type="tel" required /> - + {updateSubscriptionsResponse} @@ -161,5 +161,3 @@ export const Users: FC = () => { ); }; - -export default Users; diff --git a/react-example/yarn.lock b/react-example/yarn.lock index 2e5ec763..6fc967dd 100644 --- a/react-example/yarn.lock +++ b/react-example/yarn.lock @@ -552,14 +552,17 @@ integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== "@iterable/web-sdk@../": - version "1.0.11" + version "1.1.1" dependencies: "@pabra/sortby" "^1.0.1" "@types/ws" "8.5.4" axios "^1.6.2" + axios-mock-adapter "^1.22.0" buffer "^6.0.3" copy-webpack-plugin "^11.0.0" idb-keyval "^6.2.0" + lodash "^4.17.21" + qs "^6.12.3" throttle-debounce "^3.0.1" uuid "^9.0.0" yup "^0.32.9" @@ -1604,7 +1607,15 @@ available-typed-arrays@^1.0.7: dependencies: possible-typed-array-names "^1.0.0" -axios@^1.6.2: +axios-mock-adapter@^1.22.0: + version "1.22.0" + resolved "https://registry.yarnpkg.com/axios-mock-adapter/-/axios-mock-adapter-1.22.0.tgz#0f3e6be0fc9b55baab06f2d49c0b71157e7c053d" + integrity sha512-dmI0KbkyAhntUR05YY96qg2H6gg0XMl2+qTW0xmYg6Up+BFBAJYRLROMXRdDEL06/Wqwa0TJThAYvFtSFdRCZw== + dependencies: + fast-deep-equal "^3.1.3" + is-buffer "^2.0.5" + +axios@^1.6.2, axios@^1.7.2: version "1.7.2" resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.2.tgz#b625db8a7051fbea61c35a3cbb3a1daa7b9c7621" integrity sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw== @@ -3516,6 +3527,11 @@ is-boolean-object@^1.1.0: call-bind "^1.0.2" has-tostringtag "^1.0.0" +is-buffer@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" + integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== + is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" @@ -5000,6 +5016,13 @@ qs@6.11.0: dependencies: side-channel "^1.0.4" +qs@^6.12.3: + version "6.12.3" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.12.3.tgz#e43ce03c8521b9c7fd7f1f13e514e5ca37727754" + integrity sha512-AWJm14H1vVaO/iNZ4/hO+HyaTehuy9nRqVdkTqlJt0HWvBiBIEXFmb4C0DGeYo3Xes9rrEW+TxHsaigCbN5ICQ== + dependencies: + side-channel "^1.0.6" + querystringify@^2.1.1: version "2.2.0" resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" diff --git a/src/__data__/inAppMessages.ts b/src/__data__/inAppMessages.ts index 1b92fbed..85f3d047 100644 --- a/src/__data__/inAppMessages.ts +++ b/src/__data__/inAppMessages.ts @@ -168,5 +168,4 @@ export const messages: InAppMessage[] = [ normalMessage, expiredMessage, previouslyCachedMessage - // { messageId: 'previouslyCachedMessage!' } ]; diff --git a/src/anonymousUserTracking/anonymousUserEventManager.ts b/src/anonymousUserTracking/anonymousUserEventManager.ts index 2bcedbf6..8af74d74 100644 --- a/src/anonymousUserTracking/anonymousUserEventManager.ts +++ b/src/anonymousUserTracking/anonymousUserEventManager.ts @@ -1,7 +1,9 @@ +/* eslint-disable class-methods-use-this */ +import { v4 as uuidv4 } from 'uuid'; import { UpdateCartRequestParams, TrackPurchaseRequestParams -} from 'src/commerce/types'; +} from '../commerce/types'; import { GET_CRITERIA_PATH, @@ -23,20 +25,19 @@ import { WEB_PLATFORM, KEY_PREFER_USERID, ENDPOINTS -} from 'src/constants'; -import { baseIterableRequest } from 'src/request'; -import { IterableResponse } from 'src/types'; +} from '../constants'; +import { baseIterableRequest } from '../request'; +import { IterableResponse } from '../types'; import CriteriaCompletionChecker from './criteriaCompletionChecker'; -import { v4 as uuidv4 } from 'uuid'; -import { TrackAnonSessionParams } from 'src/utils/types'; -import { UpdateUserParams } from 'src/users/types'; -import { InAppTrackRequestParams } from 'src/events/in-app/types'; -import { trackSchema } from 'src/events/events.schema'; +import { TrackAnonSessionParams } from '../utils/types'; +import { UpdateUserParams } from '../users/types'; +import { trackSchema } from '../events/events.schema'; import { trackPurchaseSchema, updateCartSchema -} from 'src/commerce/commerce.schema'; -import { updateUserSchema } from 'src/users/users.schema'; +} from '../commerce/commerce.schema'; +import { updateUserSchema } from '../users/users.schema'; +import { InAppTrackRequestParams } from '../events'; type AnonUserFunction = (userId: string) => void; @@ -188,7 +189,7 @@ export class AnonymousUserEventManager { totalAnonSessionCount: userDataJson.number_of_sessions, lastAnonSession: userDataJson.last_session, firstAnonSession: userDataJson.first_session, - matchedCriteriaId: parseInt(criteriaId), + matchedCriteriaId: parseInt(criteriaId, 10), webPushOptIn: this.getWebPushOptnIn() !== '' ? this.getWebPushOptnIn() : undefined } @@ -218,31 +219,36 @@ export class AnonymousUserEventManager { : []; if (trackEventList.length) { - trackEventList.forEach((event: any) => { - const eventType = event[SHARED_PREFS_EVENT_TYPE]; - delete event.eventType; - switch (eventType) { - case TRACK_EVENT: { - this.track(event); - break; - } - case TRACK_PURCHASE: { - this.trackPurchase(event); - break; - } - case TRACK_UPDATE_CART: { - this.updateCart(event); - break; + trackEventList.forEach( + ( + event: any /* eslint-disable-line @typescript-eslint/no-explicit-any */ + ) => { + const eventType = event[SHARED_PREFS_EVENT_TYPE]; + // eslint-disable-next-line no-param-reassign + delete event.eventType; + switch (eventType) { + case TRACK_EVENT: { + this.track(event); + break; + } + case TRACK_PURCHASE: { + this.trackPurchase(event); + break; + } + case TRACK_UPDATE_CART: { + this.updateCart(event); + break; + } + case UPDATE_USER: { + this.updateUser({ dataFields: event }); + break; + } + default: + break; } - case UPDATE_USER: { - this.updateUser({ dataFields: event }); - break; - } - default: - break; + this.removeAnonSessionCriteriaData(); } - this.removeAnonSessionCriteriaData(); - }); + ); } } @@ -252,7 +258,10 @@ export class AnonymousUserEventManager { } private async storeEventListToLocalStorage( - newDataObject: Record, + newDataObject: Record< + any /* eslint-disable-line @typescript-eslint/no-explicit-any */, + any /* eslint-disable-line @typescript-eslint/no-explicit-any */ + >, shouldOverWrite: boolean ) { const strTrackEventList = localStorage.getItem(SHARED_PREFS_EVENT_LIST_KEY); @@ -291,21 +300,18 @@ export class AnonymousUserEventManager { } } - private getCurrentTime = () => { - return new Date().getTime(); - }; + private getCurrentTime = () => new Date().getTime(); private getWebPushOptnIn(): string { const notificationManager = window.Notification; if (notificationManager && notificationManager.permission === 'granted') { return window.location.hostname; - } else { - return ''; } + return ''; } - track = (payload: InAppTrackRequestParams) => { - return baseIterableRequest({ + track = (payload: InAppTrackRequestParams) => + baseIterableRequest({ method: 'POST', url: ENDPOINTS.event_track.route, data: payload, @@ -313,10 +319,9 @@ export class AnonymousUserEventManager { data: trackSchema } }); - }; - updateCart = (payload: UpdateCartRequestParams) => { - return baseIterableRequest({ + updateCart = (payload: UpdateCartRequestParams) => + baseIterableRequest({ method: 'POST', url: ENDPOINTS.commerce_update_cart.route, data: { @@ -330,10 +335,9 @@ export class AnonymousUserEventManager { data: updateCartSchema } }); - }; - trackPurchase = (payload: TrackPurchaseRequestParams) => { - return baseIterableRequest({ + trackPurchase = (payload: TrackPurchaseRequestParams) => + baseIterableRequest({ method: 'POST', url: ENDPOINTS.commerce_track_purchase.route, data: { @@ -347,7 +351,6 @@ export class AnonymousUserEventManager { data: trackPurchaseSchema } }); - }; updateUser = (payload: UpdateUserParams = {}) => { if (payload.dataFields) { @@ -363,5 +366,6 @@ export class AnonymousUserEventManager { } }); } + return null; }; } diff --git a/src/anonymousUserTracking/anonymousUserMerge.ts b/src/anonymousUserTracking/anonymousUserMerge.ts index 98d451ff..0f26f902 100644 --- a/src/anonymousUserTracking/anonymousUserMerge.ts +++ b/src/anonymousUserTracking/anonymousUserMerge.ts @@ -1,4 +1,5 @@ -import { ENDPOINT_MERGE_USER } from 'src/constants'; +/* eslint-disable class-methods-use-this */ +import { ENDPOINT_MERGE_USER } from '../constants'; import { baseIterableRequest } from '../request'; import { IterableResponse } from '../types'; @@ -17,10 +18,10 @@ export class AnonymousUserMerge { destinationEmail: string | null ): Promise { const mergeApiParams: MergeApiParams = { - sourceUserId: sourceUserId, - sourceEmail: sourceEmail, - destinationUserId: destinationUserId, - destinationEmail: destinationEmail + sourceUserId, + sourceEmail, + destinationUserId, + destinationEmail }; return this.callMergeApi(mergeApiParams); } diff --git a/src/anonymousUserTracking/criteriaCompletionChecker.ts b/src/anonymousUserTracking/criteriaCompletionChecker.ts index 794393a3..0112d104 100644 --- a/src/anonymousUserTracking/criteriaCompletionChecker.ts +++ b/src/anonymousUserTracking/criteriaCompletionChecker.ts @@ -1,3 +1,4 @@ +/* eslint-disable class-methods-use-this */ import { SHARED_PREFS_EVENT_TYPE, KEY_ITEMS, @@ -14,6 +15,7 @@ import { interface SearchQuery { combinator: string; + /* eslint-disable-next-line no-use-before-define */ searchQueries: SearchQuery[] | Criteria[]; dataType?: string; searchCombo?: SearchQuery; @@ -140,6 +142,7 @@ class CriteriaCompletionChecker { Object.keys(localEventData.dataFields).forEach((key) => { updatedItem[key] = localEventData.dataFields[key]; }); + // eslint-disable-next-line no-param-reassign delete localEventData.dataFields; } Object.keys(localEventData).forEach((key) => { @@ -174,6 +177,7 @@ class CriteriaCompletionChecker { Object.keys(localEventData.dataFields).forEach((key) => { updatedItem[key] = localEventData.dataFields[key]; }); + // eslint-disable-next-line no-param-reassign delete localEventData.dataFields; } nonPurchaseEvents.push(updatedItem); @@ -182,36 +186,44 @@ class CriteriaCompletionChecker { return nonPurchaseEvents; } - private evaluateTree(node: SearchQuery, localEventData: any[]): boolean { + private evaluateTree( + node: SearchQuery | Criteria, + localEventData: any[] + ): boolean { try { - if (node.searchQueries) { - const combinator = node.combinator; - const searchQueries: any = node.searchQueries; + if ((node as SearchQuery).searchQueries) { + const { combinator } = node as SearchQuery; + const { searchQueries } = node as SearchQuery; if (combinator === 'And') { - for (let i = 0; i < searchQueries.length; i++) { + /* eslint-disable-next-line @typescript-eslint/prefer-for-of */ + for (let i = 0; i < searchQueries.length; i += 1) { if (!this.evaluateTree(searchQueries[i], localEventData)) { return false; } } return true; - } else if (combinator === 'Or') { - for (let i = 0; i < searchQueries.length; i++) { + } + if (combinator === 'Or') { + /* eslint-disable-next-line @typescript-eslint/prefer-for-of */ + for (let i = 0; i < searchQueries.length; i += 1) { if (this.evaluateTree(searchQueries[i], localEventData)) { return true; } } return false; - } else if (combinator === 'Not') { - for (let i = 0; i < searchQueries.length; i++) { - searchQueries[i]['isNot'] = true; + } + if (combinator === 'Not') { + /* eslint-disable-next-line @typescript-eslint/prefer-for-of */ + for (let i = 0; i < searchQueries.length; i += 1) { + (searchQueries[i] as any).isNot = true; if (this.evaluateTree(searchQueries[i], localEventData)) { return false; } } return true; } - } else if (node.searchCombo) { - return this.evaluateSearchQueries(node, localEventData); + } else if ((node as SearchQuery).searchCombo) { + return this.evaluateSearchQueries(node as SearchQuery, localEventData); } } catch (e) { this.handleException(e); @@ -219,23 +231,25 @@ class CriteriaCompletionChecker { return false; } + /* eslint-disable no-continue */ private evaluateSearchQueries( node: SearchQuery, localEventData: any[] ): boolean { // this function will compare the actualy searhqueues under search combo - for (let i = 0; i < localEventData.length; i++) { + for (let i = 0; i < localEventData.length; i += 1) { const eventData = localEventData[i]; const trackingType = eventData[SHARED_PREFS_EVENT_TYPE]; - const dataType = node.dataType; + const { dataType } = node; if (dataType === trackingType) { - const searchCombo = node.searchCombo; + const { searchCombo } = node; const searchQueries = searchCombo?.searchQueries || []; const combinator = searchCombo?.combinator || ''; const isNot = Object.prototype.hasOwnProperty.call(node, 'isNot'); if (this.evaluateEvent(eventData, searchQueries, combinator)) { if (node.minMatch) { const minMatch = node.minMatch - 1; + // eslint-disable-next-line no-param-reassign node.minMatch = minMatch; if (minMatch > 0) { continue; @@ -245,7 +259,8 @@ class CriteriaCompletionChecker { continue; } return true; - } else if (isNot) { + } + if (isNot) { return false; } } @@ -260,7 +275,8 @@ class CriteriaCompletionChecker { ): boolean { if (combinator === 'And' || combinator === 'Or') { return this.evaluateFieldLogic(searchQueries, localEvent); - } else if (combinator === 'Not') { + } + if (combinator === 'Not') { return !this.evaluateFieldLogic(searchQueries, localEvent); } return false; @@ -278,19 +294,19 @@ class CriteriaCompletionChecker { private evaluateFieldLogic(searchQueries: any[], eventData: any): boolean { const localDataKeys = Object.keys(eventData); let itemMatchedResult = false; - let key_item = null; + let keyItem = null; if (localDataKeys.includes(KEY_ITEMS)) { - key_item = KEY_ITEMS; + keyItem = KEY_ITEMS; } else if (localDataKeys.includes(PURCHASE_ITEM)) { - key_item = PURCHASE_ITEM; + keyItem = PURCHASE_ITEM; } - if (key_item !== null) { + if (keyItem !== null) { // scenario of items inside purchase and updateCart Events - const items = eventData[key_item]; - const result = items.some((item: any) => { - return this.doesItemMatchQueries(item, searchQueries); - }); + const items = eventData[keyItem]; + const result = items.some((item: any) => + this.doesItemMatchQueries(item, searchQueries) + ); if (!result && this.doesItemCriteriaExists(searchQueries)) { // items criteria existed and it did not match return result; @@ -314,7 +330,7 @@ class CriteriaCompletionChecker { return itemMatchedResult; } const matchResult = filteredSearchQueries.every((query: any) => { - const field = query.field; + const { field } = query; if ( query.dataType === TRACK_EVENT && query.fieldType === 'object' && @@ -329,7 +345,7 @@ class CriteriaCompletionChecker { } } - if (field.includes('.') && query.comparatorType !== 'IsSet') { + if (field.includes('.')) { const valueFromObj = this.getValueFromNestedObject(eventData, field); if (valueFromObj) { return this.evaluateComparison( @@ -357,18 +373,27 @@ class CriteriaCompletionChecker { private getValueFromNestedObject(eventData: any, field: string): any { const valueFromObj = this.getFieldValue(eventData, field); - if (typeof valueFromObj === 'object') { - return Object.keys(valueFromObj).map((key) => - this.getValueFromNestedObject(valueFromObj, key) - ); - } else { - return valueFromObj; + if (typeof valueFromObj === 'object' && valueFromObj !== null) { + const keys = Object.keys(valueFromObj); + return keys.reduce((acc, key) => { + if (acc === undefined) { + return this.getValueFromNestedObject(valueFromObj, key); + } + return acc; + }, undefined); } + return valueFromObj; } private getFieldValue(data: any, field: string): any { const fields = field.split('.'); - return fields.reduce((acc, field) => acc?.[field], data); + return fields.reduce( + (value, currentField) => + value && value[currentField] !== undefined + ? value[currentField] + : undefined, + data + ); } private doesItemMatchQueries(item: any, searchQueries: any[]): boolean { @@ -390,7 +415,7 @@ class CriteriaCompletionChecker { return false; } return filteredSearchQueries.every((query: any) => { - const field = query.field; + const { field } = query; if (Object.prototype.hasOwnProperty.call(item, field)) { return this.evaluateComparison( query.comparatorType, @@ -442,12 +467,12 @@ class CriteriaCompletionChecker { (typeof sourceTo === 'number' || typeof sourceTo === 'boolean') && stringValue !== '' ) { + // eslint-disable-next-line no-restricted-globals if (typeof sourceTo === 'number' && !isNaN(parseFloat(stringValue))) { return sourceTo === parseFloat(stringValue); - } else if (typeof sourceTo === 'boolean') { + } + if (typeof sourceTo === 'boolean') { return sourceTo === (stringValue === 'true'); - } else { - return false; } } else if (typeof sourceTo === 'string') { return sourceTo === stringValue; @@ -460,6 +485,7 @@ class CriteriaCompletionChecker { stringValue: string, compareOperator: string ): boolean { + // eslint-disable-next-line no-restricted-globals if (!isNaN(parseFloat(stringValue))) { const sourceNumber = parseFloat(sourceTo); const numericValue = parseFloat(stringValue); @@ -503,11 +529,11 @@ class CriteriaCompletionChecker { private issetCheck(matchObj: string | object | any[]): boolean { if (Array.isArray(matchObj)) { return matchObj.length > 0; - } else if (typeof matchObj === 'object' && matchObj !== null) { + } + if (typeof matchObj === 'object' && matchObj !== null) { return Object.keys(matchObj).length > 0; - } else { - return matchObj !== ''; } + return matchObj !== ''; } private handleException(e: any) { diff --git a/src/authorization/authorization.ts b/src/authorization/authorization.ts index 2a263a77..66c6ccb6 100644 --- a/src/authorization/authorization.ts +++ b/src/authorization/authorization.ts @@ -1,3 +1,4 @@ +/* eslint-disable */ import axios from 'axios'; import { baseAxiosRequest, baseIterableRequest } from '../request'; import { clearMessages } from 'src/inapp/inapp'; @@ -390,22 +391,25 @@ export function initialize( Date.now(), expTime ); - if (validateTokenTime(millisecondsToExpired)) + if (validateTokenTime(millisecondsToExpired)) { return console.warn( 'Could not refresh JWT. Try generating the token again.' ); + } if (millisecondsToExpired < MAX_TIMEOUT) { - timer = setTimeout(() => { - /* get new token */ - return callback().catch((e) => { - console.warn(e); - console.warn( - 'Could not refresh JWT. Try identifying the user again.' - ); - }); + timer = setTimeout( + () => + /* get new token */ + callback().catch((e: any) => { + console.warn(e); + console.warn( + 'Could not refresh JWT. Try identifying the user again.' + ); + }), /* try to refresh one minute until expiry */ - }, millisecondsToExpired - ONE_MINUTE); + millisecondsToExpired - ONE_MINUTE + ); } } }; @@ -567,9 +571,9 @@ export function initialize( }; } - /* + /* We're using a JWT enabled API key - callback is assumed to be some sort of GET /api/generate-jwt + callback is assumed to be some sort of GET /api/generate-jwt */ const doRequest = (payload: { email?: string; userID?: string }) => { /* clear any token interceptor if any exists */ @@ -587,7 +591,7 @@ export function initialize( authInterceptor = baseAxiosRequest.interceptors.request.use( (config) => { if ((config as any)?.sendBeacon) { - /* + /* send fetch request instead solely so we can use the "keepalive" flag. This is used purely for one use-case only - when the user clicks a link that is going to navigate the browser tab to a new page/site and we need @@ -628,8 +632,8 @@ export function initialize( }) ) { try { - /* - if the customer just called the POST /users/updateEmail + /* + if the customer just called the POST /users/updateEmail that means their JWT needs to be updated to include this new email as well, so we run their JWT generation method and set a new token on the axios interceptor @@ -642,8 +646,8 @@ export function initialize( : { userID: authIdentifier! }; return generateJWT(payloadToPass).then((newToken) => { - /* - clear any existing interceptors that are adding user info + /* + clear any existing interceptors that are adding user info or API keys */ if (typeof authInterceptor === 'number') { @@ -664,18 +668,18 @@ export function initialize( authInterceptor = baseAxiosRequest.interceptors.request.use( (config) => { if ((config as any)?.sendBeacon) { - /* + /* send fetch request instead solely so we can use the "keepalive" flag. This is used purely for one use-case only - when the user clicks a link that is going to navigate the browser tab to a new page/site and we need to still call POST /trackInAppClick. - - Normally, since the page is going somewhere new, the browser would just - navigate away and cancel any in-flight requests and not fulfill them, - but with the fetch API's "keepalive" flag, it will continue the request + + Normally, since the page is going somewhere new, the browser would just + navigate away and cancel any in-flight requests and not fulfill them, + but with the fetch API's "keepalive" flag, it will continue the request without blocking the main thread. - - We can't do this with Axios because it's built upon XHR and that + + We can't do this with Axios because it's built upon XHR and that doesn't support "keepalive" so we fall back to the fetch API */ return cancelAxiosRequestAndMakeFetch( @@ -696,26 +700,26 @@ export function initialize( /* add the new email to all outgoing requests */ addEmailToRequest(newEmail); - /* - set up a new timer for when the JWT expires to regenerate + /* + set up a new timer for when the JWT expires to regenerate a new one encoded with the new email address. */ - /* - important here to clear the timer first since there was one set up + /* + important here to clear the timer first since there was one set up previously the first time the JWT was generated. handleTokenExpiration will clear the timeout for us. */ - handleTokenExpiration(newToken, () => { + handleTokenExpiration(newToken, () => /* re-run the JWT generation */ - return doRequest(payloadToPass).catch((e) => { + doRequest(payloadToPass).catch((e: any) => { console.warn(e); console.warn( 'Could not refresh JWT. Try identifying the user again.' ); - }); - }); + }) + ); return config; }); @@ -742,18 +746,18 @@ export function initialize( authInterceptor = baseAxiosRequest.interceptors.request.use( (config) => { if ((config as any)?.sendBeacon) { - /* + /* send fetch request instead solely so we can use the "keepalive" flag. This is used purely for one use-case only - when the user clicks a link that is going to navigate the browser tab to a new page/site and we need to still call POST /trackInAppClick. - - Normally, since the page is going somewhere new, the browser would just - navigate away and cancel any in-flight requests and not fulfill - them, but with the fetch API's "keepalive" flag, it will continue + + Normally, since the page is going somewhere new, the browser would just + navigate away and cancel any in-flight requests and not fulfill + them, but with the fetch API's "keepalive" flag, it will continue the request without blocking the main thread. - - We can't do this with Axios because it's built upon XHR and that + + We can't do this with Axios because it's built upon XHR and that doesn't support "keepalive" so we fall back to the fetch API */ return cancelAxiosRequestAndMakeFetch( @@ -785,29 +789,29 @@ export function initialize( } }); }) - .catch((e) => { + .catch((e: any) => /* - if the JWT generation failed, + if the JWT generation failed, just abort with a Promise rejection. */ - return Promise.reject(e); - }); + Promise.reject(e) + ); } return Promise.reject(error); } ); - handleTokenExpiration(token, () => { + handleTokenExpiration(token, () => /* re-run the JWT generation */ - return doRequest(payload).catch((e) => { + doRequest(payload).catch((e: any) => { if (logLevel === 'verbose') { console.warn(e); console.warn( 'Could not refresh JWT. Try identifying the user again.' ); } - }); - }); + }) + ); return token; }) .catch((error) => { diff --git a/src/authorization/utils.ts b/src/authorization/utils.ts index 562e34bd..5f8bac38 100644 --- a/src/authorization/utils.ts +++ b/src/authorization/utils.ts @@ -20,7 +20,7 @@ export const getEpochExpiryTimeInMS = (jwt: string) => { .split('') .map( (character) => - '%' + ('00' + character.charCodeAt(0).toString(16)).slice(-2) + `%${`00${character.charCodeAt(0).toString(16)}`.slice(-2)}` ) .join('') ); @@ -34,7 +34,7 @@ export const getEpochExpiryTimeInMS = (jwt: string) => { /* take two epoch timestamps and diff them and return them in MS. - Also tries to convert seconds to MS if needed (since some server languages like + Also tries to convert seconds to MS if needed (since some server languages like python deal with time in seconds). */ export const getEpochDifferenceInMS = ( @@ -60,21 +60,21 @@ export const cancelAxiosRequestAndMakeFetch = ( jwtToken: string, authToken: string ) => { - /* + /* send fetch request instead solely so we can use the "keepalive" flag. This is used purely for one use-case only - when the user clicks a link that is going to navigate the browser tab to a new page/site and we need to still call POST /trackInAppClick. - Normally, since the page is going somewhere new, the browser would just - navigate away and cancel any in-flight requests and not fulfill them, - but with the fetch API's "keepalive" flag, it will continue the request + Normally, since the page is going somewhere new, the browser would just + navigate away and cancel any in-flight requests and not fulfill them, + but with the fetch API's "keepalive" flag, it will continue the request without blocking the main thread. - We can't do this with Axios because it's built upon XHR and that + We can't do this with Axios because it's built upon XHR and that doesn't support "keepalive" so we fall back to the fetch API */ - const additionalData = email ? { email: email } : { userId: userID }; + const additionalData = email ? { email } : { userId: userID }; fetch(`${config.baseURL}${config.url}`, { method: 'POST', headers: { @@ -87,6 +87,7 @@ export const cancelAxiosRequestAndMakeFetch = ( }).catch(); /* cancel the axios request */ + // eslint-disable-next-line no-param-reassign config.cancelToken = new axios.CancelToken((cancel) => { cancel('Cancel repeated request'); }); @@ -98,6 +99,4 @@ export const validateTokenTime = (expTime: number): boolean => { const isValid = expTime < ONE_MINUTE; return isValid; }; -export const isEmail = (email: string): boolean => { - return EMAIL_REGEX.test(email); -}; +export const isEmail = (email: string): boolean => EMAIL_REGEX.test(email); diff --git a/src/commerce/commerce.ts b/src/commerce/commerce.ts index 3debd036..011b02b2 100644 --- a/src/commerce/commerce.ts +++ b/src/commerce/commerce.ts @@ -1,13 +1,11 @@ -import { baseIterableRequest } from 'src/request'; -import { - TrackPurchaseRequestParams, - UpdateCartRequestParams -} from 'src/commerce/types'; -import { IterableResponse } from 'src/types'; +/* eslint-disable no-param-reassign */ +import { INITIALIZE_ERROR, ENDPOINTS } from '../constants'; +import { baseIterableRequest } from '../request'; +import { TrackPurchaseRequestParams, UpdateCartRequestParams } from './types'; +import { IterableResponse } from '../types'; import { updateCartSchema, trackPurchaseSchema } from './commerce.schema'; -import { AnonymousUserEventManager } from 'src/anonymousUserTracking/anonymousUserEventManager'; -import { canTrackAnonUser } from 'src/utils/commonFunctions'; -import { INITIALIZE_ERROR, ENDPOINTS } from 'src/constants'; +import { AnonymousUserEventManager } from '../anonymousUserTracking/anonymousUserEventManager'; +import { canTrackAnonUser } from '../utils/commonFunctions'; export const updateCart = (payload: UpdateCartRequestParams) => { /* a customer could potentially send these up if they're not using TypeScript */ diff --git a/src/components/banner/index.tsx b/src/components/banner/index.tsx index b925cbc5..060e3820 100644 --- a/src/components/banner/index.tsx +++ b/src/components/banner/index.tsx @@ -1,3 +1,8 @@ +import { + addButtonClickEvent, + getTrimmedText, + handleElementClick +} from '../../embedded/utils'; import { OOTB } from '../types'; import { bannerButtons, @@ -10,11 +15,6 @@ import { defaultTitleStyles, textTitleImageDefaultStyle } from './styles'; -import { - addButtonClickEvent, - getTrimmedText, - handleElementClick -} from 'src/embedded/utils'; const emptyElement = { id: '', @@ -84,8 +84,9 @@ export function IterableEmbeddedBanner({ message?.elements?.buttons?.length || message?.elements?.mediaUrl ) - ) + ) { return ''; + } return `
0) { - params.placementIds = placementIds[0]; - if (placementIds.length > 1) { - params.placementIds += placementIds - .slice(1) - .map((id) => `&placementIds=${id}`) - .join(''); - } - } const iterableResult: any = await baseIterableRequest({ method: 'GET', url: ENDPOINTS.get_embedded_messages.route, params: { - ...params, + placementIds, platform: WEB_PLATFORM, sdkVersion: SDK_VERSION, - packageName: packageName + packageName } }); const embeddedMessages = this.getEmbeddedMessages( @@ -99,17 +92,16 @@ export class IterableEmbeddedManager { public getMessagesForPlacement( placementId: number ): IterableEmbeddedMessage[] { - return this.messages.filter((message) => { - return message.metadata.placementId === placementId; - }); + return this.messages.filter( + (message) => message.metadata.placementId === placementId + ); } private async trackNewlyRetrieved(_processor: EmbeddedMessagingProcessor) { const msgsList = _processor.newlyRetrievedMessages(); this.notifyUpdateDelegates(); - msgsList.forEach( - async (msg) => - await trackEmbeddedReceived(msg.metadata.messageId, this.appPackageName) + msgsList.forEach(async (msg) => + trackEmbeddedReceived(msg.metadata.messageId, this.appPackageName) ); } @@ -135,7 +127,7 @@ export class IterableEmbeddedManager { ); } - //Get the list of updateHandlers + // Get the list of updateHandlers public getUpdateHandlers(): IterableEmbeddedMessageUpdateHandler[] { return this.updateListeners; } diff --git a/src/embedded/embeddedMessageProcessor.ts b/src/embedded/embeddedMessageProcessor.ts index 9a99df44..85f30eea 100644 --- a/src/embedded/embeddedMessageProcessor.ts +++ b/src/embedded/embeddedMessageProcessor.ts @@ -2,6 +2,7 @@ import { IterableEmbeddedMessage } from './types'; export class EmbeddedMessagingProcessor { private currentMessages: IterableEmbeddedMessage[]; + private fetchedMessages: IterableEmbeddedMessage[]; constructor( diff --git a/src/embedded/embeddedPlacement.ts b/src/embedded/embeddedPlacement.ts index 8abec44f..cc359389 100644 --- a/src/embedded/embeddedPlacement.ts +++ b/src/embedded/embeddedPlacement.ts @@ -1,5 +1,8 @@ +/* eslint-disable no-plusplus */ +/* eslint-disable no-use-before-define */ export class IterableEmbeddedPlacement { public placementId = ''; + public messages: IterableEmbeddedMessage[] = []; constructor(placementId: string, messages: IterableEmbeddedMessage[]) { @@ -11,7 +14,7 @@ export class IterableEmbeddedPlacement { const embeddedPlacementJson: any = {}; try { - embeddedPlacementJson['placementId'] = placement.placementId; + embeddedPlacementJson.placementId = placement.placementId; if (placement?.messages !== null) { const messagesJson: any[] = []; @@ -19,7 +22,7 @@ export class IterableEmbeddedPlacement { placement?.messages.forEach((message: any) => { messagesJson.push(IterableEmbeddedMessage.toJSONObject(message)); }); - embeddedPlacementJson['embeddedMessages'] = messagesJson; + embeddedPlacementJson.embeddedMessages = messagesJson; } } catch (e: any) { console.log('Error while serializing flex message', e); @@ -30,8 +33,8 @@ export class IterableEmbeddedPlacement { static fromJSONObject(placementJson: any): IterableEmbeddedPlacement { const parsedJson = placementJson; - const placementId: string = parsedJson['placementId']; - const messagesJson: any[] = parsedJson['embeddedMessages']; + const { placementId } = parsedJson; + const messagesJson: any[] = parsedJson.embeddedMessages; const messages: IterableEmbeddedMessage[] = []; messagesJson.forEach((msgJson: any) => { @@ -47,7 +50,9 @@ export class IterableEmbeddedPlacement { export class IterableEmbeddedMessage { public metadata: EmbeddedMessageMetadata | null = null; + public elements?: EmbeddedMessageElements | null = null; + public payload?: any; constructor( @@ -56,8 +61,8 @@ export class IterableEmbeddedMessage { payload?: any ) { this.metadata = metadata; - this.elements = elements ? elements : null; - this.payload = payload ? payload : null; + this.elements = elements || null; + this.payload = payload || null; } static toJSONObject(message: IterableEmbeddedMessage) { @@ -65,13 +70,13 @@ export class IterableEmbeddedMessage { try { if (message.metadata && message.elements) { - embeddedMessageJson['metadata'] = EmbeddedMessageMetadata.toJSONObject( + embeddedMessageJson.metadata = EmbeddedMessageMetadata.toJSONObject( message.metadata ); - embeddedMessageJson['elements'] = EmbeddedMessageElements.toJSONObject( + embeddedMessageJson.elements = EmbeddedMessageElements.toJSONObject( message.elements ); - embeddedMessageJson['payload'] = message.payload; + embeddedMessageJson.payload = message.payload; } } catch (e: any) { console.log('Error while serializing flex message', e); @@ -82,15 +87,15 @@ export class IterableEmbeddedMessage { static fromJSONObject(messageJson: any): IterableEmbeddedMessage { const parsedJson = messageJson; - const metadataJson: any = parsedJson['metadata']; + const metadataJson: any = parsedJson.metadata; const metadata: EmbeddedMessageMetadata = EmbeddedMessageMetadata.fromJSONObject(metadataJson); - const elementsJson: any | null = parsedJson['elements']; + const elementsJson: any | null = parsedJson.elements; const elements: EmbeddedMessageElements | null = EmbeddedMessageElements.fromJSONObject(elementsJson); - const payload: any | null = parsedJson['payload']; + const { payload } = parsedJson; return new IterableEmbeddedMessage(metadata, elements, payload); } @@ -98,8 +103,11 @@ export class IterableEmbeddedMessage { export class EmbeddedMessageMetadata { public messageId: string; + public placementId?: string | null; + public campaignId?: number | null; + public isProof: boolean; constructor( @@ -109,8 +117,8 @@ export class EmbeddedMessageMetadata { isProof = false ) { this.messageId = messageId; - this.placementId = placementId ? placementId : null; - this.campaignId = campaignId ? campaignId : null; + this.placementId = placementId || null; + this.campaignId = campaignId || null; this.isProof = isProof; } @@ -118,10 +126,10 @@ export class EmbeddedMessageMetadata { const metadataJson: any = {}; try { - metadataJson['messageId'] = metadata.messageId; - metadataJson['placementId'] = metadata.placementId; - metadataJson['campaignId'] = metadata.campaignId; - metadataJson['isProof'] = metadata.isProof; + metadataJson.messageId = metadata.messageId; + metadataJson.placementId = metadata.placementId; + metadataJson.campaignId = metadata.campaignId; + metadataJson.isProof = metadata.isProof; } catch (e: any) { console.log('Error while serializing flex metadata', e); } @@ -131,10 +139,10 @@ export class EmbeddedMessageMetadata { static fromJSONObject(metadataJson: any): EmbeddedMessageMetadata { const parsedJson = metadataJson; - const messageId: string = parsedJson['messageId']; - const placementId: string | null = parsedJson['placementId']; - const campaignId: number | null = parsedJson['campaignId']; - const isProof: boolean = parsedJson['isProof']; + const { messageId } = parsedJson; + const { placementId } = parsedJson; + const { campaignId } = parsedJson; + const { isProof } = parsedJson; return new EmbeddedMessageMetadata( messageId, @@ -147,10 +155,15 @@ export class EmbeddedMessageMetadata { export class EmbeddedMessageElements { public title?: string | null; + public body?: string | null; + public mediaURL?: string | null; + public defaultAction?: EmbeddedMessageElementsDefaultAction | null; + public buttons?: EmbeddedMessageElementsButton[] | null; + public text?: EmbeddedMessageElementsText[] | null; constructor( @@ -173,38 +186,27 @@ export class EmbeddedMessageElements { const elementsJson: any = {}; try { - elementsJson['title'] = elements?.title; - elementsJson['body'] = elements?.body; - elementsJson['mediaUrl'] = elements?.mediaURL; + elementsJson.title = elements?.title; + elementsJson.body = elements?.body; + elementsJson.mediaUrl = elements?.mediaURL; if (elements?.defaultAction) { - elementsJson['defaultAction'] = + elementsJson.defaultAction = EmbeddedMessageElementsDefaultAction.toJSONObject( elements.defaultAction ); } if (elements?.buttons) { - const buttonsJson: any = []; - - for (let i = 0; i < elements.buttons.length; i++) { - buttonsJson.push( - EmbeddedMessageElementsButton.toJSONObject(elements.buttons[i]) - ); - } - elementsJson['buttons'] = buttonsJson; + elementsJson.buttons = elements.buttons?.map((button) => + EmbeddedMessageElementsButton.toJSONObject(button) + ); } if (elements?.text) { - const textJson: any = []; - - for (let i = 0; i < elements.text.length; i++) { - textJson.push( - EmbeddedMessageElementsText.toJSONObject(elements.text[i]) - ); - } - - elementsJson['text'] = textJson; + elementsJson.text = elements.text?.map((text) => + EmbeddedMessageElementsText.toJSONObject(text) + ); } } catch (e: any) { console.log('Error while serializing flex elements', e); @@ -221,45 +223,32 @@ export class EmbeddedMessageElements { } const parsedJson = elementsJson; - const title: string | null = parsedJson['title']; - const body: string | null = parsedJson['body']; - const mediaURL: string | null = parsedJson['mediaUrl']; + const { title } = parsedJson; + const { body } = parsedJson; + const mediaURL: string | null = parsedJson.mediaUrl; - const defaultActionJson: any | null = parsedJson['defaultAction']; - let defaultAction: EmbeddedMessageElementsDefaultAction | null = null; + const defaultActionJson: any | null = parsedJson.defaultAction; - if (defaultActionJson !== null) { - defaultAction = - EmbeddedMessageElementsDefaultAction.fromJSONObject(defaultActionJson); - } + const defaultAction: EmbeddedMessageElementsDefaultAction | null = + defaultActionJson + ? EmbeddedMessageElementsDefaultAction.fromJSONObject(defaultActionJson) + : null; - const buttonsJson: [] | null = parsedJson['buttons']; - let buttons: EmbeddedMessageElementsButton[] | null = []; + const buttonsJson: [] | null = parsedJson.buttons; - if (buttonsJson) { - for (let i = 0; i < buttonsJson.length; i++) { - const buttonJson: any = buttonsJson[i]; - const button: EmbeddedMessageElementsButton = - EmbeddedMessageElementsButton.fromJSONObject(buttonJson); - buttons?.push(button); - } - } else { - buttons = null; - } + const buttons: EmbeddedMessageElementsButton[] | null = buttonsJson + ? buttonsJson.map((button) => + EmbeddedMessageElementsButton.fromJSONObject(button) + ) + : null; - const textsJson: [] | null = elementsJson['text']; - let texts: EmbeddedMessageElementsText[] | null = []; + const textsJson: [] | null = elementsJson.text; - if (textsJson) { - for (let i = 0; i < textsJson.length; i++) { - const textJson: any = textsJson[i]; - const text: EmbeddedMessageElementsText = - EmbeddedMessageElementsText.fromJSONObject(textJson); - texts?.push(text); - } - } else { - texts = null; - } + const texts: EmbeddedMessageElementsText[] | null = textsJson + ? textsJson.map((text) => + EmbeddedMessageElementsText.fromJSONObject(text) + ) + : null; return new EmbeddedMessageElements( title, @@ -274,7 +263,9 @@ export class EmbeddedMessageElements { export class EmbeddedMessageElementsButton { public id: string; + public title?: string | null; + public action?: EmbeddedMessageElementsButtonAction | null; constructor( @@ -291,11 +282,11 @@ export class EmbeddedMessageElementsButton { const buttonJson: any = {}; try { - buttonJson['id'] = button.id; - buttonJson['title'] = button.title; + buttonJson.id = button.id; + buttonJson.title = button.title; if (button.action) { - buttonJson['action'] = EmbeddedMessageElementsButtonAction.toJSONObject( + buttonJson.action = EmbeddedMessageElementsButtonAction.toJSONObject( button.action ); } @@ -308,10 +299,10 @@ export class EmbeddedMessageElementsButton { static fromJSONObject(buttonJson: any): EmbeddedMessageElementsButton { const parsedJson = buttonJson; - const id: string = parsedJson['id']; - const title: string | null = parsedJson['title']; + const { id } = parsedJson; + const { title } = parsedJson; - const buttonActionJson: any | null = parsedJson['action']; + const buttonActionJson: any | null = parsedJson.action; let action: EmbeddedMessageElementsButtonAction | null = null; if (buttonActionJson !== null) { action = @@ -324,6 +315,7 @@ export class EmbeddedMessageElementsButton { export class EmbeddedMessageElementsDefaultAction { public type: string; + public data: string; constructor(type: string, data: string) { @@ -335,8 +327,8 @@ export class EmbeddedMessageElementsDefaultAction { const defaultActionJson: any = {}; try { - defaultActionJson['type'] = defaultAction.type; - defaultActionJson['data'] = defaultAction.data; + defaultActionJson.type = defaultAction.type; + defaultActionJson.data = defaultAction.data; } catch (e: any) { console.log('Error while serializing flex default action', e); } @@ -348,8 +340,8 @@ export class EmbeddedMessageElementsDefaultAction { defaultActionJson: any ): EmbeddedMessageElementsDefaultAction { const parsedJson = defaultActionJson; - const type: string = parsedJson['type']; - const data: string = parsedJson['data']; + const { type } = parsedJson; + const { data } = parsedJson; return new EmbeddedMessageElementsDefaultAction(type, data); } @@ -357,6 +349,7 @@ export class EmbeddedMessageElementsDefaultAction { export class EmbeddedMessageElementsButtonAction { public type: string; + public data: string; constructor(type: string, data: string) { @@ -368,8 +361,8 @@ export class EmbeddedMessageElementsButtonAction { const buttonActionJson: any = {}; try { - buttonActionJson['type'] = buttonAction.type; - buttonActionJson['data'] = buttonAction.data; + buttonActionJson.type = buttonAction.type; + buttonActionJson.data = buttonAction.data; } catch (e: any) { console.log('Error while serializing flex default action', e); } @@ -381,8 +374,8 @@ export class EmbeddedMessageElementsButtonAction { buttonActionJson: any ): EmbeddedMessageElementsButtonAction { const parsedJson = buttonActionJson; - const type: string = parsedJson['type']; - const data: string = parsedJson['data']; + const { type } = parsedJson; + const { data } = parsedJson; return new EmbeddedMessageElementsButtonAction(type, data); } @@ -390,7 +383,9 @@ export class EmbeddedMessageElementsButtonAction { export class EmbeddedMessageElementsText { public id: string; + public text?: string | null; + public label?: string | null; constructor(id: string, text?: string | null, label?: string | null) { @@ -403,9 +398,9 @@ export class EmbeddedMessageElementsText { const textJson: any = {}; try { - textJson['id'] = text.id; - textJson['text'] = text.text; - textJson['label'] = text.label; + textJson.id = text.id; + textJson.text = text.text; + textJson.label = text.label; } catch (e: any) { console.log('Error while serializing flex message text', e); } @@ -415,9 +410,9 @@ export class EmbeddedMessageElementsText { static fromJSONObject(textJson: any): EmbeddedMessageElementsText { const parsedJson = textJson; - const id: string = parsedJson['id']; - const text: string | null = parsedJson['text']; - const label: string | null = parsedJson['label']; + const { id } = parsedJson; + const { text } = parsedJson; + const { label } = parsedJson; return new EmbeddedMessageElementsText(id, text, label); } diff --git a/src/embedded/embeddedSessionManager.ts b/src/embedded/embeddedSessionManager.ts index 142dbd25..3bb0e12b 100644 --- a/src/embedded/embeddedSessionManager.ts +++ b/src/embedded/embeddedSessionManager.ts @@ -1,11 +1,14 @@ +/* eslint-disable no-param-reassign */ +/* eslint-disable class-methods-use-this */ import { v4 as uuidv4 } from 'uuid'; import { trackEmbeddedSession } from '../events/embedded/events'; import { IterableEmbeddedSessionRequestPayload } from '..'; class EmbeddedSession { public start?: Date; + public end?: Date; - public impressions?: EmbeddedImpression[]; + public id: string; constructor(start?: Date, end?: Date) { @@ -17,9 +20,13 @@ class EmbeddedSession { class EmbeddedImpression { public messageId: string; + public displayCount: number; + public displayDuration: number; + public start?: Date = undefined; + public placementId: number; constructor( @@ -29,15 +36,17 @@ class EmbeddedImpression { duration?: number ) { this.messageId = messageId; - this.displayCount = displayCount ? displayCount : 0; - this.displayDuration = duration ? duration : 0.0; + this.displayCount = displayCount || 0; + this.displayDuration = duration || 0.0; this.placementId = placementId; } } export class IterableEmbeddedSessionManager { public appPackageName: string; + private impressions: Map = new Map(); + public session: EmbeddedSession = new EmbeddedSession(); constructor(appPackageName: string) { @@ -77,7 +86,7 @@ export class IterableEmbeddedSessionManager { await trackEmbeddedSession(sessionPayload); - //reset session for next session start + // reset session for next session start this.session = new EmbeddedSession(); this.impressions = new Map(); } @@ -129,9 +138,8 @@ export class IterableEmbeddedSessionManager { private updateDisplayCountAndDuration(impressionData: EmbeddedImpression) { if (impressionData.start) { - impressionData.displayCount = impressionData.displayCount + 1; - impressionData.displayDuration = - impressionData.displayDuration + + impressionData.displayCount += 1; + impressionData.displayDuration += (new Date().getTime() - impressionData.start.getTime()) / 1000.0; impressionData.start = undefined; } diff --git a/src/embedded/iterableEmbeddedPlacement.test.ts b/src/embedded/iterableEmbeddedPlacement.test.ts index d5dfb554..a88bc86a 100644 --- a/src/embedded/iterableEmbeddedPlacement.test.ts +++ b/src/embedded/iterableEmbeddedPlacement.test.ts @@ -69,7 +69,7 @@ describe('EmbeddedManager', () => { const message = placement.messages[0]; const payload: any = {}; - payload['someKey'] = 'someValue'; + payload.someKey = 'someValue'; // THEN we get appropriate embedded message object and associated placement id expect(placement).not.toBeNull(); @@ -245,7 +245,7 @@ describe('EmbeddedManager', () => { const message = placement.messages[0]; const payload: any = {}; - payload['someKey'] = 'someValue'; + payload.someKey = 'someValue'; // THEN we get appropriate embedded message object and associated placement id expect(placement).not.toBeNull(); @@ -354,7 +354,7 @@ describe('EmbeddedManager', () => { ); const customPayload: any = {}; - customPayload['someKey'] = 'someValue'; + customPayload.someKey = 'someValue'; const embeddedMessage = new IterableEmbeddedMessage( embeddedMessageMetadata, @@ -451,7 +451,7 @@ describe('EmbeddedManager', () => { ); const customPayload: any = {}; - customPayload['someKey'] = 'someValue'; + customPayload.someKey = 'someValue'; const embeddedMessage = new IterableEmbeddedMessage( embeddedMessageMetadata, diff --git a/src/embedded/types.ts b/src/embedded/types.ts index 8e8656b8..9839cb9d 100644 --- a/src/embedded/types.ts +++ b/src/embedded/types.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-use-before-define */ export interface IterableEmbeddedButtonAction { type: string; data?: string; diff --git a/src/embedded/utils.ts b/src/embedded/utils.ts index f283a2e5..b387f5b2 100644 --- a/src/embedded/utils.ts +++ b/src/embedded/utils.ts @@ -1,3 +1,9 @@ +/* eslint-disable no-use-before-define */ +import { + URL_SCHEME_ACTION, + URL_SCHEME_ITBL, + URL_SCHEME_OPEN +} from '../constants'; import { trackEmbeddedClick } from '../events/embedded/events'; import { IterableEmbeddedButton, @@ -8,20 +14,14 @@ import { } from './types'; import { IterableActionRunner } from '../utils/IterableActionRunner'; import { ErrorHandler } from '../types'; -import { - URL_SCHEME_ACTION, - URL_SCHEME_ITBL, - URL_SCHEME_OPEN -} from 'src/constants'; function getTargetUrl(action?: IterableEmbeddedDefaultAction): string { if (!action) return ''; if (action.type === URL_SCHEME_OPEN) { return action?.data || ''; - } else { - return action.type; } + return action.type; } export const handleElementClick = ( @@ -75,19 +75,18 @@ export const addButtonClickEvent = ( message: IterableEmbeddedMessage, appPackageName: string, errorCallback?: ErrorHandler -) => { +): void => { button.addEventListener('click', (event) => { // Prevent the click event from bubbling up to the div event.stopPropagation(); - if (!message?.elements?.buttons) { - return ''; + if (message?.elements?.buttons) { + handleButtonClick( + message?.elements?.buttons[index], + message, + appPackageName, + errorCallback + ); } - handleButtonClick( - message?.elements?.buttons[index], - message, - appPackageName, - errorCallback - ); }); }; diff --git a/src/events/embedded/events.ts b/src/events/embedded/events.ts index 9e62f56e..fafcb542 100644 --- a/src/events/embedded/events.ts +++ b/src/events/embedded/events.ts @@ -1,3 +1,4 @@ +import { WEB_PLATFORM, ENDPOINTS } from '../../constants'; import { baseIterableRequest } from '../../request'; import { IterableEmbeddedDismissRequestPayload, @@ -11,7 +12,6 @@ import { embeddedDismissSchema, embeddedSessionSchema } from './events.schema'; -import { WEB_PLATFORM, ENDPOINTS } from 'src/constants'; export const trackEmbeddedReceived = ( messageId: string, diff --git a/src/events/events.test.ts b/src/events/events.test.ts index 123fb900..7c3eac95 100644 --- a/src/events/events.test.ts +++ b/src/events/events.test.ts @@ -12,7 +12,7 @@ import { trackInAppConsume, trackInAppDelivery, trackInAppOpen -} from './in-app/events'; +} from './inapp/events'; import { WEB_PLATFORM } from '../constants'; import { createClientError } from '../utils/testUtils'; diff --git a/src/events/events.ts b/src/events/events.ts index 8dacd4b6..486cb61f 100644 --- a/src/events/events.ts +++ b/src/events/events.ts @@ -1,10 +1,11 @@ -import { baseIterableRequest } from 'src/request'; -import { IterableResponse } from 'src/types'; -import { AnonymousUserEventManager } from 'src/anonymousUserTracking/anonymousUserEventManager'; -import { canTrackAnonUser } from 'src/utils/commonFunctions'; -import { InAppTrackRequestParams } from './in-app/types'; +/* eslint-disable no-param-reassign */ +import { INITIALIZE_ERROR, ENDPOINTS } from '../constants'; +import { baseIterableRequest } from '../request'; +import { InAppTrackRequestParams } from './inapp/types'; +import { IterableResponse } from '../types'; import { trackSchema } from './events.schema'; -import { ENDPOINTS, INITIALIZE_ERROR } from 'src/constants'; +import { AnonymousUserEventManager } from '../anonymousUserTracking/anonymousUserEventManager'; +import { canTrackAnonUser } from '../utils/commonFunctions'; export const track = (payload: InAppTrackRequestParams) => { /* a customer could potentially send these up if they're not using TypeScript */ diff --git a/src/events/in-app/events.schema.ts b/src/events/inapp/events.schema.ts similarity index 100% rename from src/events/in-app/events.schema.ts rename to src/events/inapp/events.schema.ts diff --git a/src/events/in-app/events.ts b/src/events/inapp/events.ts similarity index 98% rename from src/events/in-app/events.ts rename to src/events/inapp/events.ts index ed738d54..16c21d34 100644 --- a/src/events/in-app/events.ts +++ b/src/events/inapp/events.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-param-reassign */ import { baseIterableRequest } from '../../request'; import { InAppEventRequestParams } from './types'; import { IterableResponse } from '../../types'; diff --git a/src/events/in-app/types.ts b/src/events/inapp/types.ts similarity index 100% rename from src/events/in-app/types.ts rename to src/events/inapp/types.ts diff --git a/src/events/index.ts b/src/events/index.ts index e1532b4e..5aa5fd5b 100644 --- a/src/events/index.ts +++ b/src/events/index.ts @@ -1,5 +1,5 @@ export * from './events'; -export * from './in-app/events'; -export * from './in-app/types'; +export * from './inapp/events'; +export * from './inapp/types'; export * from './embedded/types'; export * from './embedded/events'; diff --git a/src/inapp/cache.ts b/src/inapp/cache.ts index 46431518..91f7a7d4 100644 --- a/src/inapp/cache.ts +++ b/src/inapp/cache.ts @@ -31,7 +31,7 @@ export const determineRemainingStorageQuota = async () => { const usage = storage?.usageDetails?.indexedDB ?? storage?.usage; const remainingQuota = usage && messageQuota - usage; - return remainingQuota ? remainingQuota : 0; + return remainingQuota || 0; } catch (err: any /* eslint-disable-line @typescript-eslint/no-explicit-any */) { // eslint-disable-next-line no-console console.warn( @@ -55,6 +55,7 @@ export const getCachedMessagesToDelete = ( cachedMessages.reduce((deleteQueue: string[], [cachedMessageId]) => { const isCachedMessageInFetch = fetchedMessages.reduce( (isFound, { messageId }) => { + // eslint-disable-next-line no-param-reassign if (messageId === cachedMessageId) isFound = true; return isFound; }, diff --git a/src/inapp/inapp.ts b/src/inapp/inapp.ts index e8c8076f..cacc7695 100644 --- a/src/inapp/inapp.ts +++ b/src/inapp/inapp.ts @@ -1,4 +1,8 @@ -import _set from 'lodash/set'; +/* eslint-disable consistent-return */ +/* eslint-disable no-param-reassign */ +/* eslint-disable no-redeclare */ +import { set } from 'lodash'; +import { throttle } from 'throttle-debounce'; import { ABSOLUTE_DISMISS_BUTTON_ID, ANIMATION_DURATION, @@ -8,13 +12,12 @@ import { ENABLE_INAPP_CONSUME, IS_PRODUCTION } from '../constants'; -import { throttle } from 'throttle-debounce'; import { trackInAppClick, trackInAppClose, trackInAppConsume, trackInAppOpen -} from '../events/in-app/events'; +} from '../events/inapp/events'; import { IterablePromise } from '../types'; import { requestMessages } from './request'; import { @@ -170,8 +173,9 @@ export function getInAppMessages( const throttledResize = messagePosition !== 'Full' ? throttle(750, () => { - activeIframe.style.height = - (activeIframeDocument?.body?.scrollHeight || 0) + 'px'; + activeIframe.style.height = `${ + activeIframeDocument?.body?.scrollHeight || 0 + }px`; }) : () => null; global.addEventListener('resize', throttledResize); @@ -203,11 +207,12 @@ export function getInAppMessages( if (event.key === 'Escape') { dismissMessage(activeIframe); document.removeEventListener('keydown', documentEventHandler); - if (activeIframeDocument && !!iframeEventHandler) + if (activeIframeDocument && !!iframeEventHandler) { activeIframeDocument.removeEventListener( 'keydown', iframeEventHandler ); + } global.removeEventListener('resize', throttledResize); } }; @@ -222,12 +227,13 @@ export function getInAppMessages( to add the listener needs to know about both of these _addEventListener_ abstracted callbacks so the code can properly remove the same listener that was added. - In other words, it solves for the issue where you're adding an event listener as a lambda + In other words, it solves for the issue where you're + adding an event listener as a lambda like so and and you get a unique event listener each time: document.addEventListener('keydown', () => // do stuff) - this example code adds a new event listener each time and never gets cleaned up + this example code adds a new event listener each time and never gets cleaned up because there's no reference that to "() => // do stuff" that can be re-used in the _removeEventListener_ call. */ @@ -243,11 +249,12 @@ export function getInAppMessages( document.addEventListener('keydown', handleDocumentEscPress); - if (activeIframeDocument) + if (activeIframeDocument) { activeIframeDocument.addEventListener( 'keydown', handleIFrameEscPress ); + } const ua = navigator.userAgent; const isSafari = @@ -267,11 +274,12 @@ export function getInAppMessages( dismissMessage(activeIframe); document.getElementById(CLOSE_X_BUTTON_ID)?.remove(); document.removeEventListener('keydown', handleDocumentEscPress); - if (activeIframeDocument) + if (activeIframeDocument) { activeIframeDocument.removeEventListener( 'keydown', handleIFrameEscPress ); + } global.removeEventListener('resize', throttledResize); }); } @@ -296,11 +304,12 @@ export function getInAppMessages( const triggerClose = () => { dismissMessage(activeIframe); document.removeEventListener('keydown', handleDocumentEscPress); - if (activeIframeDocument) + if (activeIframeDocument) { activeIframeDocument.removeEventListener( 'keydown', handleIFrameEscPress ); + } global.removeEventListener('resize', throttledResize); const closeXButtonElement = @@ -368,8 +377,8 @@ export function getInAppMessages( } } - /* - track in-app consumes only when _saveToInbox_ + /* + track in-app consumes only when _saveToInbox_ is falsy or undefined and always track in-app opens Also swallow any 400+ response errors. We don't care about them. @@ -408,8 +417,8 @@ export function getInAppMessages( clickedHostname === global.location.host || !clickedHostname; const { handleLinks } = payload; - /* - If the _handleLinks_ option is set, we need to open links + /* + If the _handleLinks_ option is set, we need to open links according to that enum and override their target attributes. 1. If _open-all-same-tab_, then open every link in the same tab @@ -436,8 +445,8 @@ export function getInAppMessages( }; if (isDismissNode || isActionLink) { - /* - give the close anchor tag properties that make it + /* + give the close anchor tag properties that make it behave more like a button with a logical aria label */ addButtonAttrsToAnchorTag(link, 'close modal'); @@ -470,8 +479,8 @@ export function getInAppMessages( } } else { link.addEventListener('click', (event) => { - /* - remove default linking behavior because we're in an iframe + /* + remove default linking behavior because we're in an iframe so we need to link the user programatically */ event.preventDefault(); @@ -491,8 +500,8 @@ export function getInAppMessages( appPackageName: dupedPayload.packageName } }, - /* - only call with the fetch API if we're linking in the + /* + only call with the fetch API if we're linking in the same tab and it's not a reserved keyword link. */ isOpeningLinkInSameTab && !isIterableKeywordLink @@ -505,21 +514,23 @@ export function getInAppMessages( 'keydown', handleDocumentEscPress ); - if (activeIframeDocument) + if (activeIframeDocument) { activeIframeDocument.removeEventListener( 'keydown', handleIFrameEscPress ); + } global.removeEventListener('resize', throttledResize); } if (isActionLink) { + // eslint-disable-next-line prefer-regex-literals const filteredMatch = (new RegExp( /^.*action:\/\/(.*)$/, 'gmi' )?.exec(clickedUrl) || [])?.[1]; - /* - just post the message to the window when clicking + /* + just post the message to the window when clicking action:// links and early return */ return global.postMessage( @@ -545,11 +556,11 @@ export function getInAppMessages( } ); if (!handleLinks) { - if (openInNewTab) + if (openInNewTab) { /** Using target="_blank" without rel="noreferrer" and rel="noopener" makes the website vulnerable to window.opener API exploitation attacks - + @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#security_and_privacy */ global.open( @@ -557,7 +568,7 @@ export function getInAppMessages( '_blank', 'noopener,noreferrer' ); - else global.location.assign(clickedUrl); + } else global.location.assign(clickedUrl); } } } @@ -597,7 +608,7 @@ export function getInAppMessages( return response; }) .then((response: any) => { - if (isDeferred) + if (isDeferred) { /* if the user passed "deferred" for the second argument to _getMessages_ then they're going to choose to display the in-app messages when they want @@ -605,19 +616,18 @@ export function getInAppMessages( with no filtering or sorting. */ return response; - + } /* otherwise, they're choosing to show the messages automatically */ - /* + /* if the user passed the flag to automatically paint the in-app messages to the DOM, start a timer and show each in-app message upon close + timer countdown - + However there are 3 conditions in which to not show a message: - + 1. _read_ key is truthy 2. _trigger.type_ key is "never" (deliver silently is checked) 3. HTML body is blank - so first filter out unwanted messages and sort them */ clearMessages(); @@ -625,14 +635,12 @@ export function getInAppMessages( filterHiddenInAppMessages(response.data.inAppMessages) ) as InAppMessage[]; - return paintMessageToDOM().then(() => { - return { - ...response, - data: { - inAppMessages: parsedMessages - } - }; - }); + return paintMessageToDOM().then(() => ({ + ...response, + data: { + inAppMessages: parsedMessages + } + })); }), pauseMessageStream: () => { if (timer) { @@ -650,7 +658,7 @@ export function getInAppMessages( }; } - /* + /* user doesn't want us to paint messages automatically. just return the promise like normal */ @@ -660,7 +668,7 @@ export function getInAppMessages( const withIframes = messages?.map((message) => { const html = message.content?.html; return html - ? _set(message, 'content.html', wrapWithIFrame(html as string)) + ? set(message, 'content.html', wrapWithIFrame(html as string)) : message; }); return { diff --git a/src/inapp/request.ts b/src/inapp/request.ts index a7f9cc52..bffe2f1a 100644 --- a/src/inapp/request.ts +++ b/src/inapp/request.ts @@ -1,6 +1,7 @@ +/* eslint-disable no-unreachable */ import { delMany, entries } from 'idb-keyval'; -import { GETMESSAGES_PATH, SDK_VERSION, WEB_PLATFORM } from 'src/constants'; -import { baseIterableRequest } from 'src/request'; +import { GETMESSAGES_PATH, SDK_VERSION, WEB_PLATFORM } from '../constants'; +import { baseIterableRequest } from '../request'; import { addNewMessagesToCache, getCachedMessagesToDelete } from './cache'; import schema from './inapp.schema'; import { @@ -38,10 +39,11 @@ type RequestMessagesProps = { }; export const requestMessages = async ({ payload }: RequestMessagesProps) => { - /** @note TBD: Caching implementation and associated parameter will be enabled once new endpoint is ready */ + /** @note TBD: Caching implementation and associated parameter + // will be enabled once new endpoint is ready */ // if (!options?.useLocalCache) return await requestInAppMessages({}); /** @note Always early return until then */ - return await requestInAppMessages({ payload }); + return requestInAppMessages({ payload }); try { const cachedMessages: CachedMessage[] = await entries(); @@ -92,11 +94,12 @@ export const requestMessages = async ({ payload }: RequestMessagesProps) => { if (cachedMessage) allMessages.push(cachedMessage[1]); } else { allMessages.push(inAppMessage); - if (inAppMessage.messageId) + if (inAppMessage.messageId) { newMessages.push({ messageId: inAppMessage.messageId, message: inAppMessage as InAppMessage }); + } } }); @@ -132,5 +135,5 @@ export const requestMessages = async ({ payload }: RequestMessagesProps) => { err?.response?.data?.clientErrors ?? err ); } - return await requestInAppMessages({ payload }); + return requestInAppMessages({ payload }); }; diff --git a/src/inapp/types.ts b/src/inapp/types.ts index 90fd6f1e..b2f55687 100644 --- a/src/inapp/types.ts +++ b/src/inapp/types.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-use-before-define */ import { IterablePromise } from '../types'; export enum CloseButtonPosition { diff --git a/src/inapp/utils.ts b/src/inapp/utils.ts index 049d5505..598d9eb1 100644 --- a/src/inapp/utils.ts +++ b/src/inapp/utils.ts @@ -1,13 +1,19 @@ +/* eslint-disable no-return-assign */ +/* eslint-disable no-plusplus */ +/* eslint-disable no-loop-func */ +/* eslint-disable no-param-reassign */ +/* eslint-disable no-script-url */ +/* eslint-disable no-promise-executor-return */ import { by } from '@pabra/sortby'; +import { trackInAppDelivery } from '../events/inapp/events'; +import { config } from '../utils/config'; import { ANIMATION_DURATION, DEFAULT_CLOSE_BUTTON_OFFSET_PERCENTAGE } from '../constants'; -import { WebInAppDisplaySettings } from '../inapp'; +import { WebInAppDisplaySettings } from '.'; import { srSpeak } from '../utils/srSpeak'; -import { trackInAppDelivery } from 'src/events/in-app/events'; import { CloseButtonPosition, InAppMessage } from './types'; -import { config } from 'src/utils/config'; interface Breakpoints { smMatches: boolean; @@ -20,9 +26,9 @@ export const generateWidth = ( { smMatches, mdMatches, lgMatches, xlMatches }: Breakpoints, position: WebInAppDisplaySettings['position'] ): string => { - /* + /* breakpoint widths are as follows: - + 1. TopRight, BottomRight (100% at SM; 45% at MD; 33% at LG; 25% at XL) 2. Center (100% at SM, MD; 50% at LG, XL) 3. Full (100% all the time) @@ -33,7 +39,7 @@ export const generateWidth = ( if (mdMatches) { if (position === 'TopRight' || position === 'BottomRight') { - /* + /* in-app messages is being initially painted, but we're on mobile breakpoints so remove any offsets the user provided in the config object. */ @@ -57,8 +63,8 @@ export const generateWidth = ( return '50%'; } - /* - this line will never run. One of those breakpoints has to return true + /* + this line will never run. One of those breakpoints has to return true but this is just to appease typescript. */ return '100%'; @@ -82,7 +88,7 @@ export const preloadImages = (imageLinks: string[], callback: () => void) => { images[i].src = imageLinks[i]; images[i].loading = 'eager'; images[i].onload = () => { - /* + /* track the amount of images we preloaded. If this is the last image that's been preloaded, it's time to invoke the callback function we passed. */ @@ -106,27 +112,23 @@ export const preloadImages = (imageLinks: string[], callback: () => void) => { export const filterHiddenInAppMessages = ( messages: Partial[] = [] -) => { - return messages.filter((eachMessage) => { - return ( +) => + messages.filter( + (eachMessage) => !eachMessage.read && eachMessage.trigger?.type !== 'never' && !!eachMessage.content?.html - ); - }); -}; + ); export const filterOnlyReadAndNeverTriggerMessages = ( messages: Partial[] = [] -) => { - return messages.filter( +) => + messages.filter( (eachMessage) => !eachMessage.read && eachMessage.trigger?.type !== 'never' ); -}; -export const sortInAppMessages = (messages: Partial[] = []) => { - return messages.sort(by(['priorityLevel', 'asc'], ['createdAt', 'asc'])); -}; +export const sortInAppMessages = (messages: Partial[] = []) => + messages.sort(by(['priorityLevel', 'asc'], ['createdAt', 'asc'])); export const generateCloseButton = ( id: string, @@ -278,7 +280,8 @@ const mediaQueryXl = global?.matchMedia?.('(min-width: 1301px)'); /** * - * @returns { HTMLIFrameElement } iframe with sandbox and hidden styling applied for of an inapp message to render with + * @returns { HTMLIFrameElement } iframe with sandbox and hidden + * styling applied for of an inapp message to render with */ const generateSecuredIFrame = () => { const iframe = document.createElement('iframe'); @@ -342,7 +345,8 @@ const unsetIframeBodyMargin = (iframe: HTMLIFrameElement) => { * @param shouldAnimate if the in-app should animate in/out * @param srMessage The message you want the screen reader to read when popping up the message * @param topOffset how many px or % buffer between the in-app message and the top of the screen - * @param bottomOffset how many px or % buffer between the in-app message and the bottom of the screen + * @param bottomOffset how many px or % buffer between the in-app message + * and the bottom of the screen * @param rightOffset how many px or % buffer between the in-app message and the right of the screen * * @returns { HTMLIFrameElement } @@ -359,11 +363,11 @@ export const paintIFrame = ( new Promise((resolve: (value: HTMLIFrameElement) => void) => { const iframe = generateSecuredIFrame(); - /* - find all the images in the in-app message, preload them, and + /* + find all the images in the in-app message, preload them, and only then set the height because we need to know how tall the images are before we set the height of the iframe. - + This prevents a race condition where if we set the height before the images are loaded, we might end up with a scrolling iframe */ @@ -386,7 +390,7 @@ export const paintIFrame = ( const imageLinks = [...imageUrls, ...imageTagUrls]; return preloadImages(imageLinks, () => { - /* + /* set the scroll height to the content inside, but since images are going to take some time to load, we opt to preload them, THEN set the inner HTML of the iframe @@ -400,12 +404,12 @@ export const paintIFrame = ( const timeout = setTimeout(() => { /** - even though we preloaded the images before setting the height, we add an extra 100MS - here to handle for the case where the user needs to download custom fonts. As + even though we preloaded the images before setting the height, we add an extra 100MS + here to handle for the case where the user needs to download custom fonts. As of 07/27/2021, the preloading fonts API is still in a draft state - + @see https://developer.mozilla.org/en-US/docs/Web/API/CSS_Font_Loading_API - + but even if we did preload the fonts, it would still take a non-trivial amount of computational time to apply the font to the text, so this setTimeout is acting more as a failsafe just incase the new font causes the line-height to grow and create a @@ -483,7 +487,7 @@ export const paintIFrame = ( }; const iframeHeight = iframe.contentDocument?.body?.scrollHeight; - if (iframeHeight) iframe.style.height = iframeHeight + 'px'; + if (iframeHeight) iframe.style.height = `${iframeHeight}px`; } clearTimeout(timeout); @@ -499,7 +503,7 @@ export const paintIFrame = ( iframe.onload = () => { if (position !== 'Full') { const scrollHeight = iframe.contentDocument?.body.scrollHeight ?? 0; - if (scrollHeight) iframe.style.height = scrollHeight + 'px'; + if (scrollHeight) iframe.style.height = `${scrollHeight}px`; } }; @@ -514,24 +518,24 @@ export const addButtonAttrsToAnchorTag = (node: Element, ariaLabel: string) => { }; export const trackMessagesDelivered = ( + // eslint-disable-next-line default-param-last messages: Partial[] = [], packageName: string -) => { - return Promise.all( - messages?.map((eachMessage) => { - return trackInAppDelivery({ +) => + Promise.all( + messages?.map((eachMessage) => + trackInAppDelivery({ messageId: eachMessage.messageId as string, deviceInfo: { appPackageName: packageName } - /* - swallow any network failures. - If it fails, there's nothing really we can do here. + /* + swallow any network failures. + If it fails, there's nothing really we can do here. */ - }); - }) + }) + ) ).catch((e: any) => e); -}; export const paintOverlay = ( color = '#fff', @@ -623,12 +627,13 @@ export const setCloseButtonPosition = ( ? `calc(${iframeRect.top}px + ${topOffset})` : `${iframeRect.top + defaultTop}px`; - if (position === CloseButtonPosition.TopLeft) + if (position === CloseButtonPosition.TopLeft) { closeButton.style.left = sideOffset ? `calc(${iframeRect.left}px + ${sideOffset})` : `${iframeRect.left + defaultSide}px`; - else + } else { closeButton.style.left = sideOffset ? `calc(${iframeRightEdge}px - ${sideOffset})` : `${iframeRightEdge - defaultSide}px`; + } }; diff --git a/src/request.ts b/src/request.ts index 02cefe58..8badc433 100644 --- a/src/request.ts +++ b/src/request.ts @@ -1,7 +1,8 @@ import Axios, { AxiosRequestConfig } from 'axios'; +import qs from 'qs'; +import { AnySchema, ValidationError } from 'yup'; import { BASE_URL, STATIC_HEADERS, EU_ITERABLE_API } from './constants'; import { IterablePromise, IterableResponse } from './types'; -import { AnySchema, ValidationError } from 'yup'; import { config } from './utils/config'; interface ExtendedRequestConfig extends AxiosRequestConfig { @@ -46,7 +47,9 @@ export const baseIterableRequest = ( headers: { ...payload.headers, ...STATIC_HEADERS - } + }, + paramsSerializer: (params) => + qs.stringify(params, { arrayFormat: 'repeat' }) }); } catch (error) { /* match Iterable's API error schema and add client errors as a new key */ @@ -59,6 +62,7 @@ export const baseIterableRequest = ( })) }; /* match Axios' Error object schema and reject */ + // eslint-disable-next-line prefer-promise-reject-errors return Promise.reject({ response: { data: newError, diff --git a/src/types.ts b/src/types.ts index 418f2a51..0d56d6dd 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,7 +1,7 @@ import { AxiosError, AxiosPromise } from 'axios'; /** - make one property in an interface optional + make one property in an interface optional @thanks https://stackoverflow.com/a/61108377/7455960 */ export type Optional = Pick, K> & Omit; diff --git a/src/users/users.ts b/src/users/users.ts index 54138af8..538637d7 100644 --- a/src/users/users.ts +++ b/src/users/users.ts @@ -1,14 +1,15 @@ +// eslint-disable @typescript-eslint/no-explicit-any import { object, string } from 'yup'; import { IterableResponse } from '../types'; import { baseIterableRequest } from '../request'; import { UpdateSubscriptionParams, UpdateUserParams } from './types'; import { updateSubscriptionsSchema, updateUserSchema } from './users.schema'; import { AnonymousUserEventManager } from '../anonymousUserTracking/anonymousUserEventManager'; -import { canTrackAnonUser } from 'src/utils/commonFunctions'; -import { INITIALIZE_ERROR, ENDPOINTS } from 'src/constants'; +import { canTrackAnonUser } from '../utils/commonFunctions'; +import { INITIALIZE_ERROR, ENDPOINTS } from '../constants'; -export const updateUserEmail = (newEmail: string) => { - return baseIterableRequest({ +export const updateUserEmail = (newEmail: string) => + baseIterableRequest({ method: 'POST', url: ENDPOINTS.update_email.route, data: { @@ -20,10 +21,10 @@ export const updateUserEmail = (newEmail: string) => { }) } }); -}; -export const updateUser = (payload: UpdateUserParams = {}) => { +export const updateUser = (payloadParam: UpdateUserParams = {}) => { /* a customer could potentially send these up if they're not using TypeScript */ + const payload = payloadParam; delete (payload as any).userId; delete (payload as any).email; @@ -46,9 +47,10 @@ export const updateUser = (payload: UpdateUserParams = {}) => { }; export const updateSubscriptions = ( - payload: Partial = {} + payloadParam: Partial = {} ) => { /* a customer could potentially send these up if they're not using TypeScript */ + const payload = payloadParam; delete (payload as any).userId; delete (payload as any).email; diff --git a/src/utils/IterableActionRunner.ts b/src/utils/IterableActionRunner.ts index e7873637..67bd9326 100644 --- a/src/utils/IterableActionRunner.ts +++ b/src/utils/IterableActionRunner.ts @@ -1,10 +1,10 @@ -import { IterableConfig } from 'src/utils/IterableConfig'; +import { IterableConfig } from './IterableConfig'; +import { URL_SCHEME_OPEN } from '../constants'; import { IterableAction, IterableActionContext, IterableActionSource } from '../embedded/types'; -import { URL_SCHEME_OPEN } from 'src/constants'; class IterableActionRunnerImpl { static executeAction( @@ -19,12 +19,11 @@ class IterableActionRunnerImpl { const actionContext: IterableActionContext = { action, source }; if (action.type === URL_SCHEME_OPEN) { return IterableActionRunnerImpl.openUri(action.data, actionContext); - } else { - return IterableActionRunnerImpl.callCustomActionIfSpecified( - action, - actionContext - ); } + return IterableActionRunnerImpl.callCustomActionIfSpecified( + action, + actionContext + ); } private static openUri( diff --git a/src/utils/IterableConfig.ts b/src/utils/IterableConfig.ts index d87ce5bb..70185a51 100644 --- a/src/utils/IterableConfig.ts +++ b/src/utils/IterableConfig.ts @@ -2,5 +2,6 @@ import { IterableCustomActionHandler, IterableUrlHandler } from '..'; export class IterableConfig { public static urlHandler: IterableUrlHandler | null = null; + public static customActionHandler: IterableCustomActionHandler | null = null; } diff --git a/src/utils/commonFunctions.ts b/src/utils/commonFunctions.ts index a4111fe9..24b3715b 100644 --- a/src/utils/commonFunctions.ts +++ b/src/utils/commonFunctions.ts @@ -1,6 +1,5 @@ import { typeOfAuth } from '../authorization/authorization'; -import config from '../utils/config'; +import config from './config'; -export const canTrackAnonUser = (): boolean => { - return config.getConfig('enableAnonTracking') && typeOfAuth === null; -}; +export const canTrackAnonUser = (): boolean => + config.getConfig('enableAnonTracking') && typeOfAuth === null; diff --git a/src/utils/functions.ts b/src/utils/functions.ts index e658df18..a5d81e70 100644 --- a/src/utils/functions.ts +++ b/src/utils/functions.ts @@ -1,7 +1,24 @@ -export class functions { +import { SHARED_PREF_EMAIL, SHARED_PREF_USER_ID } from '../constants'; + +export default class { private static emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; public static checkEmailValidation(email: string): boolean { - return functions.emailRegex.test(email); + return this.emailRegex.test(email); + } + + public static addEmailOrUserIdToJson( + jsonParams: any, + localStorage: Storage + ): any { + const store = jsonParams; + const userId = localStorage.getItem(SHARED_PREF_USER_ID); + const email = localStorage.getItem(SHARED_PREF_EMAIL); + if (userId) { + store.userId = userId; + } else if (email) { + store.email = email; + } + return store; } } diff --git a/src/utils/srSpeak.ts b/src/utils/srSpeak.ts index 5263f36e..7a0db075 100644 --- a/src/utils/srSpeak.ts +++ b/src/utils/srSpeak.ts @@ -6,15 +6,15 @@ */ export const srSpeak = (text: string, priority?: 'polite' | 'assertive') => { const el = document.createElement('div'); - const id = 'speak-' + Math.random().toString(36).substr(2, 9); + const id = `speak-${Math.random().toString(36).substr(2, 9)}`; el.setAttribute('id', id); el.setAttribute('data-test-id', 'sr-speak'); el.setAttribute('aria-live', priority || 'polite'); - /* + /* _display: none_ would cause the SR to not read the message so this just hides the message visibly, while still appearing in the DOM - - https://snook.ca/archives/html_and_css/hiding-content-for-accessibility + + https://snook.ca/archives/html_and_css/hiding-content-for-accessibility */ el.style.cssText = ` position: absolute !important; diff --git a/yarn.lock b/yarn.lock index 57a23c9c..f7c4cfef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,11 +2,6 @@ # yarn lockfile v1 -"@aashutoshrathi/word-wrap@^1.2.3": - version "1.2.6" - resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" - integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== - "@ampproject/remapping@^2.2.0": version "2.2.1" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" @@ -31,13 +26,6 @@ "@nicolo-ribaudo/chokidar-2" "2.1.8-no-fsevents.3" chokidar "^3.4.0" -"@babel/code-frame@7.12.11": - version "7.12.11" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" - integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== - dependencies: - "@babel/highlight" "^7.10.4" - "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.24.1", "@babel/code-frame@^7.24.2": version "7.24.2" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.2.tgz#718b4b19841809a58b29b68cde80bc5e1aa6d9ae" @@ -374,11 +362,6 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz#9478c707febcbbe1ddb38a3d91a2e054ae622d83" integrity sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ== -"@babel/helper-validator-identifier@^7.15.7": - version "7.15.7" - 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.20": version "7.22.20" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" @@ -416,15 +399,6 @@ "@babel/traverse" "^7.24.1" "@babel/types" "^7.24.0" -"@babel/highlight@^7.10.4": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.0.tgz#6ceb32b2ca4b8f5f361fb7fd821e3fddf4a1725a" - integrity sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g== - dependencies: - "@babel/helper-validator-identifier" "^7.15.7" - chalk "^2.0.0" - js-tokens "^4.0.0" - "@babel/highlight@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.23.4.tgz#edaadf4d8232e1a961432db785091207ead0621b" @@ -1306,34 +1280,49 @@ resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63" integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA== -"@eslint/eslintrc@^0.4.3": - version "0.4.3" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c" - integrity sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw== +"@eslint-community/regexpp@^4.6.1": + version "4.11.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.0.tgz#b0ffd0312b4a3fd2d6f77237e7248a5ad3a680ae" + integrity sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A== + +"@eslint/eslintrc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" + integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== dependencies: ajv "^6.12.4" - debug "^4.1.1" - espree "^7.3.0" - globals "^13.9.0" - ignore "^4.0.6" + debug "^4.3.2" + espree "^9.6.0" + globals "^13.19.0" + ignore "^5.2.0" import-fresh "^3.2.1" - js-yaml "^3.13.1" - minimatch "^3.0.4" + js-yaml "^4.1.0" + minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@humanwhocodes/config-array@^0.5.0": - version "0.5.0" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9" - integrity sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg== +"@eslint/js@8.57.0": + version "8.57.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f" + integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== + +"@humanwhocodes/config-array@^0.11.14": + version "0.11.14" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" + integrity sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg== dependencies: - "@humanwhocodes/object-schema" "^1.2.0" - debug "^4.1.1" - minimatch "^3.0.4" + "@humanwhocodes/object-schema" "^2.0.2" + debug "^4.3.1" + minimatch "^3.0.5" -"@humanwhocodes/object-schema@^1.2.0": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" - integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^2.0.2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" + integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" @@ -1627,7 +1616,7 @@ resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== -"@nodelib/fs.walk@^1.2.3": +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": version "1.2.8" resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== @@ -2075,6 +2064,11 @@ "@typescript-eslint/types" "5.62.0" eslint-visitor-keys "^3.3.0" +"@ungap/structured-clone@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" + integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== + "@webassemblyjs/ast@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" @@ -2249,7 +2243,7 @@ acorn-import-assertions@^1.7.6: resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac" integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== -acorn-jsx@^5.3.1: +acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== @@ -2259,7 +2253,7 @@ acorn-walk@^7.1.1: resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== -acorn@^7.1.1, acorn@^7.4.0: +acorn@^7.1.1: version "7.4.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== @@ -2274,6 +2268,11 @@ acorn@^8.8.2: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== +acorn@^8.9.0: + version "8.12.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" + integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== + agent-base@6: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" @@ -2308,7 +2307,7 @@ ajv-keywords@^5.1.0: dependencies: fast-deep-equal "^3.1.3" -ajv@^6.10.0, ajv@^6.12.4, ajv@^6.12.5: +ajv@^6.12.4, ajv@^6.12.5: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -2318,7 +2317,7 @@ ajv@^6.10.0, ajv@^6.12.4, ajv@^6.12.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^8.0.0, ajv@^8.0.1, ajv@^8.9.0: +ajv@^8.0.0, ajv@^8.9.0: version "8.12.0" resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== @@ -2389,16 +2388,87 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array-buffer-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz#1e5583ec16763540a27ae52eed99ff899223568f" + integrity sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg== + dependencies: + call-bind "^1.0.5" + is-array-buffer "^3.0.4" + array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== +array-includes@^3.1.7: + version "3.1.8" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.8.tgz#5e370cbe172fdd5dd6530c1d4aadda25281ba97d" + integrity sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.4" + is-string "^1.0.7" + array-union@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== +array.prototype.findlastindex@^1.2.3: + version "1.2.5" + resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz#8c35a755c72908719453f87145ca011e39334d0d" + integrity sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + es-shim-unscopables "^1.0.2" + +array.prototype.flat@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz#1476217df8cff17d72ee8f3ba06738db5b387d18" + integrity sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + +array.prototype.flatmap@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz#c9a7c6831db8e719d6ce639190146c24bbd3e527" + integrity sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + +arraybuffer.prototype.slice@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz#097972f4255e41bc3425e37dc3f6421cf9aefde6" + integrity sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A== + dependencies: + array-buffer-byte-length "^1.0.1" + call-bind "^1.0.5" + define-properties "^1.2.1" + es-abstract "^1.22.3" + es-errors "^1.2.1" + get-intrinsic "^1.2.3" + is-array-buffer "^3.0.4" + is-shared-array-buffer "^1.0.2" + ast-module-types@^2.7.1: version "2.7.1" resolved "https://registry.yarnpkg.com/ast-module-types/-/ast-module-types-2.7.1.tgz#3f7989ef8dfa1fdb82dfe0ab02bdfc7c77a57dd3" @@ -2419,6 +2489,13 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== +available-typed-arrays@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" + integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== + dependencies: + possible-typed-array-names "^1.0.0" + axios-mock-adapter@^1.22.0: version "1.22.0" resolved "https://registry.yarnpkg.com/axios-mock-adapter/-/axios-mock-adapter-1.22.0.tgz#0f3e6be0fc9b55baab06f2d49c0b71157e7c053d" @@ -2721,7 +2798,7 @@ bytes@3.1.2: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== -call-bind@^1.0.0: +call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== @@ -2776,7 +2853,7 @@ caniuse-lite@^1.0.30001587: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001612.tgz#d34248b4ec1f117b70b24ad9ee04c90e0b8a14ae" integrity sha512-lFgnZ07UhaCcsSZgWW0K5j4e69dK1u/ltrL9lTUiFOwNHs12S3UMIEYgBV0Z6C6hRDev7iRnMzzYmKabYdXF9g== -chalk@^2.0.0, chalk@^2.4.2: +chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -3001,6 +3078,11 @@ concurrently@^6.3.0: tree-kill "^1.2.2" yargs "^16.2.0" +confusing-browser-globals@^1.0.10: + version "1.0.11" + resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz#ae40e9b57cdd3915408a2805ebd3a5585608dc81" + integrity sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA== + connect-history-api-fallback@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz#647264845251a0daf25b97ce87834cace0f5f1c8" @@ -3157,6 +3239,33 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" +data-view-buffer@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.1.tgz#8ea6326efec17a2e42620696e671d7d5a8bc66b2" + integrity sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA== + dependencies: + call-bind "^1.0.6" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +data-view-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz#90721ca95ff280677eb793749fce1011347669e2" + integrity sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +data-view-byte-offset@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz#5e0bbfb4828ed2d1b9b400cd8a7d119bca0ff18a" + integrity sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA== + dependencies: + call-bind "^1.0.6" + es-errors "^1.3.0" + is-data-view "^1.0.1" + date-fns@^2.16.1: version "2.30.0" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0" @@ -3171,13 +3280,20 @@ debug@2.6.9: dependencies: ms "2.0.0" -debug@4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: +debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" +debug@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + decimal.js@^10.2.1: version "10.4.3" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" @@ -3217,7 +3333,7 @@ defaults@^1.0.3: dependencies: clone "^1.0.2" -define-data-property@^1.1.4: +define-data-property@^1.0.1, define-data-property@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== @@ -3231,6 +3347,15 @@ define-lazy-prop@^2.0.0: resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== +define-properties@^1.2.0, define-properties@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== + dependencies: + define-data-property "^1.0.1" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -3375,6 +3500,13 @@ dns-packet@^5.2.2: dependencies: "@leichtgewicht/ip-codec" "^2.0.1" +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + doctrine@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" @@ -3451,7 +3583,7 @@ enhanced-resolve@^5.10.0, enhanced-resolve@^5.8.3: graceful-fs "^4.2.4" tapable "^2.2.0" -enquirer@^2.3.5, enquirer@^2.3.6: +enquirer@^2.3.6: version "2.4.1" resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.4.1.tgz#93334b3fbd74fc7097b224ab4a8fb7e40bf4ae56" integrity sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ== @@ -3471,6 +3603,58 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" +es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.2: + version "1.23.3" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.3.tgz#8f0c5a35cd215312573c5a27c87dfd6c881a0aa0" + integrity sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A== + dependencies: + array-buffer-byte-length "^1.0.1" + arraybuffer.prototype.slice "^1.0.3" + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + data-view-buffer "^1.0.1" + data-view-byte-length "^1.0.1" + data-view-byte-offset "^1.0.0" + es-define-property "^1.0.0" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + es-set-tostringtag "^2.0.3" + es-to-primitive "^1.2.1" + function.prototype.name "^1.1.6" + get-intrinsic "^1.2.4" + get-symbol-description "^1.0.2" + globalthis "^1.0.3" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + has-proto "^1.0.3" + has-symbols "^1.0.3" + hasown "^2.0.2" + internal-slot "^1.0.7" + is-array-buffer "^3.0.4" + is-callable "^1.2.7" + is-data-view "^1.0.1" + is-negative-zero "^2.0.3" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.3" + is-string "^1.0.7" + is-typed-array "^1.1.13" + is-weakref "^1.0.2" + object-inspect "^1.13.1" + object-keys "^1.1.1" + object.assign "^4.1.5" + regexp.prototype.flags "^1.5.2" + safe-array-concat "^1.1.2" + safe-regex-test "^1.0.3" + string.prototype.trim "^1.2.9" + string.prototype.trimend "^1.0.8" + string.prototype.trimstart "^1.0.8" + typed-array-buffer "^1.0.2" + typed-array-byte-length "^1.0.1" + typed-array-byte-offset "^1.0.2" + typed-array-length "^1.0.6" + unbox-primitive "^1.0.2" + which-typed-array "^1.1.15" + es-define-property@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" @@ -3478,7 +3662,7 @@ es-define-property@^1.0.0: dependencies: get-intrinsic "^1.2.4" -es-errors@^1.3.0: +es-errors@^1.2.1, es-errors@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== @@ -3488,6 +3672,38 @@ es-module-lexer@^0.9.0: resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.9.3.tgz#6f13db00cc38417137daf74366f535c8eb438f19" integrity sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ== +es-object-atoms@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941" + integrity sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw== + dependencies: + es-errors "^1.3.0" + +es-set-tostringtag@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz#8bb60f0a440c2e4281962428438d58545af39777" + integrity sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ== + dependencies: + get-intrinsic "^1.2.4" + has-tostringtag "^1.0.2" + hasown "^2.0.1" + +es-shim-unscopables@^1.0.0, es-shim-unscopables@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz#1f6942e71ecc7835ed1c8a83006d8771a63a3763" + integrity sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw== + dependencies: + hasown "^2.0.0" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + escalade@^3.1.1: version "3.1.2" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" @@ -3524,11 +3740,60 @@ escodegen@^2.0.0: optionalDependencies: source-map "~0.6.1" +eslint-config-airbnb-base@^15.0.0: + version "15.0.0" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz#6b09add90ac79c2f8d723a2580e07f3925afd236" + integrity sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig== + dependencies: + confusing-browser-globals "^1.0.10" + object.assign "^4.1.2" + object.entries "^1.1.5" + semver "^6.3.0" + eslint-config-prettier@^8.3.0: version "8.10.0" resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz#3a06a662130807e2502fc3ff8b4143d8a0658e11" integrity sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg== +eslint-import-resolver-node@^0.3.9: + version "0.3.9" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz#d4eaac52b8a2e7c3cd1903eb00f7e053356118ac" + integrity sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g== + dependencies: + debug "^3.2.7" + is-core-module "^2.13.0" + resolve "^1.22.4" + +eslint-module-utils@^2.8.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz#52f2404300c3bd33deece9d7372fb337cc1d7c34" + integrity sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q== + dependencies: + debug "^3.2.7" + +eslint-plugin-import@^2.25.2: + version "2.29.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz#d45b37b5ef5901d639c15270d74d46d161150643" + integrity sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw== + dependencies: + array-includes "^3.1.7" + array.prototype.findlastindex "^1.2.3" + array.prototype.flat "^1.3.2" + array.prototype.flatmap "^1.3.2" + debug "^3.2.7" + doctrine "^2.1.0" + eslint-import-resolver-node "^0.3.9" + eslint-module-utils "^2.8.0" + hasown "^2.0.0" + is-core-module "^2.13.1" + is-glob "^4.0.3" + minimatch "^3.1.2" + object.fromentries "^2.0.7" + object.groupby "^1.0.1" + object.values "^1.1.7" + semver "^6.3.1" + tsconfig-paths "^3.15.0" + eslint-plugin-prettier@^3.4.0: version "3.4.1" resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz#e9ddb200efb6f3d05ffe83b1665a716af4a387e5" @@ -3544,92 +3809,86 @@ eslint-scope@5.1.1, eslint-scope@^5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" -eslint-utils@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" - integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== dependencies: - eslint-visitor-keys "^1.1.0" - -eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" - integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== + esrecurse "^4.3.0" + estraverse "^5.2.0" eslint-visitor-keys@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== -eslint-visitor-keys@^3.3.0: +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: version "3.4.3" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== -eslint@^7.14.0: - version "7.32.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" - integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA== +eslint@^8.2.0: + version "8.57.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668" + integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ== dependencies: - "@babel/code-frame" "7.12.11" - "@eslint/eslintrc" "^0.4.3" - "@humanwhocodes/config-array" "^0.5.0" - ajv "^6.10.0" + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.4" + "@eslint/js" "8.57.0" + "@humanwhocodes/config-array" "^0.11.14" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + "@ungap/structured-clone" "^1.2.0" + ajv "^6.12.4" chalk "^4.0.0" cross-spawn "^7.0.2" - debug "^4.0.1" + debug "^4.3.2" doctrine "^3.0.0" - enquirer "^2.3.5" escape-string-regexp "^4.0.0" - eslint-scope "^5.1.1" - eslint-utils "^2.1.0" - eslint-visitor-keys "^2.0.0" - espree "^7.3.1" - esquery "^1.4.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" + esquery "^1.4.2" esutils "^2.0.2" fast-deep-equal "^3.1.3" file-entry-cache "^6.0.1" - functional-red-black-tree "^1.0.1" - glob-parent "^5.1.2" - globals "^13.6.0" - ignore "^4.0.6" - import-fresh "^3.0.0" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" imurmurhash "^0.1.4" is-glob "^4.0.0" - js-yaml "^3.13.1" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" json-stable-stringify-without-jsonify "^1.0.1" levn "^0.4.1" lodash.merge "^4.6.2" - minimatch "^3.0.4" + minimatch "^3.1.2" natural-compare "^1.4.0" - optionator "^0.9.1" - progress "^2.0.0" - regexpp "^3.1.0" - semver "^7.2.1" - strip-ansi "^6.0.0" - strip-json-comments "^3.1.0" - table "^6.0.9" + optionator "^0.9.3" + strip-ansi "^6.0.1" text-table "^0.2.0" - v8-compile-cache "^2.0.3" -espree@^7.3.0, espree@^7.3.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" - integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== dependencies: - acorn "^7.4.0" - acorn-jsx "^5.3.1" - eslint-visitor-keys "^1.3.0" + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" esprima@^4.0.0, esprima@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.4.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" - integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== +esquery@^1.4.2: + version "1.6.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== dependencies: estraverse "^5.1.0" @@ -3899,6 +4158,14 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + find-up@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-6.3.0.tgz#2abab3d3280b2dc7ac10199ef324c4e002c8c790" @@ -3936,6 +4203,13 @@ follow-redirects@^1.0.0, follow-redirects@^1.15.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== +for-each@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" + integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== + dependencies: + is-callable "^1.1.3" + form-data@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" @@ -3989,10 +4263,20 @@ function-bind@^1.1.1, function-bind@^1.1.2: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== -functional-red-black-tree@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" - integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== +function.prototype.name@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" + integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + functions-have-names "^1.2.3" + +functions-have-names@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" + integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== gensync@^1.0.0-beta.2: version "1.0.0-beta.2" @@ -4021,7 +4305,7 @@ get-intrinsic@^1.0.2: has "^1.0.3" has-symbols "^1.0.3" -get-intrinsic@^1.1.3, get-intrinsic@^1.2.4: +get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== @@ -4059,6 +4343,15 @@ get-stream@^6.0.0: resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== +get-symbol-description@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.2.tgz#533744d5aa20aca4e079c8e5daf7fd44202821f5" + integrity sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg== + dependencies: + call-bind "^1.0.5" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -4066,7 +4359,7 @@ glob-parent@^5.1.2, glob-parent@~5.1.2: dependencies: is-glob "^4.0.1" -glob-parent@^6.0.1: +glob-parent@^6.0.1, glob-parent@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== @@ -4106,13 +4399,21 @@ globals@^11.1.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -globals@^13.6.0, globals@^13.9.0: +globals@^13.19.0: version "13.24.0" resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== dependencies: type-fest "^0.20.2" +globalthis@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" + integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ== + dependencies: + define-properties "^1.2.1" + gopd "^1.0.1" + globby@^11.0.3, globby@^11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" @@ -4172,6 +4473,11 @@ handle-thing@^2.0.0: resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== +has-bigints@^1.0.1, has-bigints@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" + integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== + has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" @@ -4182,23 +4488,30 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-property-descriptors@^1.0.2: +has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== dependencies: es-define-property "^1.0.0" -has-proto@^1.0.1: +has-proto@^1.0.1, has-proto@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== -has-symbols@^1.0.3: +has-symbols@^1.0.2, has-symbols@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== +has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + has@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/has/-/has-1.0.4.tgz#2eb2860e000011dae4f1406a86fe80e530fb2ec6" @@ -4211,6 +4524,13 @@ hasown@^2.0.0: dependencies: function-bind "^1.1.2" +hasown@^2.0.1, hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + hosted-git-info@^2.1.4: version "2.8.9" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" @@ -4355,11 +4675,6 @@ ieee754@^1.1.13, ieee754@^1.2.1: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== -ignore@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" - integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== - ignore@^5.2.0, ignore@^5.2.4: version "5.3.1" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" @@ -4373,7 +4688,7 @@ import-fresh@^2.0.0: caller-path "^2.0.0" resolve-from "^3.0.0" -import-fresh@^3.0.0, import-fresh@^3.2.1: +import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== @@ -4427,6 +4742,15 @@ ini@~1.3.0: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== +internal-slot@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.7.tgz#c06dcca3ed874249881007b0a5523b172a190802" + integrity sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g== + dependencies: + es-errors "^1.3.0" + hasown "^2.0.0" + side-channel "^1.0.4" + interpret@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" @@ -4442,11 +4766,26 @@ ipaddr.js@^2.0.1: resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.1.0.tgz#2119bc447ff8c257753b196fc5f1ce08a4cdf39f" integrity sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ== +is-array-buffer@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98" + integrity sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.1" + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== +is-bigint@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" + is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" @@ -4454,11 +4793,24 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" +is-boolean-object@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + is-buffer@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== +is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== + is-core-module@^2.13.0, is-core-module@^2.2.0: version "2.13.1" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" @@ -4466,6 +4818,27 @@ is-core-module@^2.13.0, is-core-module@^2.2.0: dependencies: hasown "^2.0.0" +is-core-module@^2.13.1: + version "2.15.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.0.tgz#71c72ec5442ace7e76b306e9d48db361f22699ea" + integrity sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA== + dependencies: + hasown "^2.0.2" + +is-data-view@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.1.tgz#4b4d3a511b70f3dc26d42c03ca9ca515d847759f" + integrity sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w== + dependencies: + is-typed-array "^1.1.13" + +is-date-object@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== + dependencies: + has-tostringtag "^1.0.0" + is-directory@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" @@ -4503,6 +4876,18 @@ is-interactive@^1.0.0: resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== +is-negative-zero@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" + integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== + +is-number-object@^1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" + integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== + dependencies: + has-tostringtag "^1.0.0" + is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" @@ -4513,6 +4898,11 @@ is-obj@^1.0.1: resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" integrity sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg== +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + is-plain-obj@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7" @@ -4530,6 +4920,14 @@ is-potential-custom-element-name@^1.0.1: resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== +is-regex@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + is-regexp@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" @@ -4540,6 +4938,13 @@ is-relative-path@^1.0.2: resolved "https://registry.yarnpkg.com/is-relative-path/-/is-relative-path-1.0.2.tgz#091b46a0d67c1ed0fe85f1f8cfdde006bb251d46" integrity sha512-i1h+y50g+0hRbBD+dbnInl3JlJ702aar58snAeX+MxBAPvzXGej7sYoPMhlnykabt0ZzCJNBEyzMlekuQZN7fA== +is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz#1237f1cba059cdb62431d378dcc37d9680181688" + integrity sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg== + dependencies: + call-bind "^1.0.7" + is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" @@ -4550,6 +4955,27 @@ is-stream@^2.0.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== +is-string@^1.0.5, is-string@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== + dependencies: + has-tostringtag "^1.0.0" + +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== + dependencies: + has-symbols "^1.0.2" + +is-typed-array@^1.1.13: + version "1.1.13" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.13.tgz#d6c5ca56df62334959322d7d7dd1cca50debe229" + integrity sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw== + dependencies: + which-typed-array "^1.1.14" + is-typedarray@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" @@ -4570,6 +4996,13 @@ is-url@^1.2.4: resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52" integrity sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww== +is-weakref@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" + integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== + dependencies: + call-bind "^1.0.2" + is-wsl@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" @@ -4577,6 +5010,11 @@ is-wsl@^2.2.0: dependencies: is-docker "^2.0.0" +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -5052,6 +5490,13 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + jsdom@^16.6.0: version "16.7.0" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" @@ -5243,6 +5688,13 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + locate-path@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-7.2.0.tgz#69cb1779bd90b35ab1e771e1f2f89a202c2a8a8a" @@ -5270,11 +5722,6 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== -lodash.truncate@^4.4.2: - version "4.4.2" - resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" - integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw== - lodash@^4.17.21, lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" @@ -5447,7 +5894,7 @@ minimalistic-assert@^1.0.0: resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== -minimatch@^3.0.4, minimatch@^3.1.1: +minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -5500,7 +5947,7 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3: +ms@2.1.3, ms@^2.1.1: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -5616,11 +6063,68 @@ nwsapi@^2.2.0: resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.7.tgz#738e0707d3128cb750dddcfe90e4610482df0f30" integrity sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ== +object-inspect@^1.13.1: + version "1.13.2" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff" + integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g== + object-inspect@^1.9.0: version "1.12.2" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.2, object.assign@^4.1.5: + version "4.1.5" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0" + integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ== + dependencies: + call-bind "^1.0.5" + define-properties "^1.2.1" + has-symbols "^1.0.3" + object-keys "^1.1.1" + +object.entries@^1.1.5: + version "1.1.8" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.8.tgz#bffe6f282e01f4d17807204a24f8edd823599c41" + integrity sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +object.fromentries@^2.0.7: + version "2.0.8" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.8.tgz#f7195d8a9b97bd95cbc1999ea939ecd1a2b00c65" + integrity sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" + +object.groupby@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/object.groupby/-/object.groupby-1.0.3.tgz#9b125c36238129f6f7b61954a1e7176148d5002e" + integrity sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + +object.values@^1.1.7: + version "1.2.0" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.0.tgz#65405a9d92cee68ac2d303002e0b8470a4d9ab1b" + integrity sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + obuf@^1.0.0, obuf@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" @@ -5666,17 +6170,17 @@ opencollective-postinstall@^2.0.2: resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259" integrity sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q== -optionator@^0.9.1: - version "0.9.3" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" - integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== dependencies: - "@aashutoshrathi/word-wrap" "^1.2.3" deep-is "^0.1.3" fast-levenshtein "^2.0.6" levn "^0.4.1" prelude-ls "^1.2.1" type-check "^0.4.0" + word-wrap "^1.2.5" ora@^5.4.1: version "5.4.1" @@ -5705,6 +6209,13 @@ p-limit@^2.0.0, p-limit@^2.2.0: dependencies: p-try "^2.0.0" +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + p-limit@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-4.0.0.tgz#914af6544ed32bfa54670b061cafcbd04984b644" @@ -5726,6 +6237,13 @@ p-locate@^4.1.0: dependencies: p-limit "^2.2.0" +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + p-locate@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-6.0.0.tgz#3da9a49d4934b901089dca3302fa65dc5a05c04f" @@ -5891,6 +6409,11 @@ pluralize@^8.0.0: resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== +possible-typed-array-names@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" + integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== + postcss-modules-extract-imports@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d" @@ -6025,11 +6548,6 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== -progress@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== - prompts@^2.0.1: version "2.4.2" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" @@ -6081,6 +6599,13 @@ qs@6.11.0: dependencies: side-channel "^1.0.4" +qs@^6.12.3: + version "6.12.3" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.12.3.tgz#e43ce03c8521b9c7fd7f1f13e514e5ca37727754" + integrity sha512-AWJm14H1vVaO/iNZ4/hO+HyaTehuy9nRqVdkTqlJt0HWvBiBIEXFmb4C0DGeYo3Xes9rrEW+TxHsaigCbN5ICQ== + dependencies: + side-channel "^1.0.6" + querystringify@^2.1.1: version "2.2.0" resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" @@ -6212,10 +6737,15 @@ regenerator-transform@^0.15.2: dependencies: "@babel/runtime" "^7.8.4" -regexpp@^3.1.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" - integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== +regexp.prototype.flags@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz#138f644a3350f981a858c44f6bb1a61ff59be334" + integrity sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw== + dependencies: + call-bind "^1.0.6" + define-properties "^1.2.1" + es-errors "^1.3.0" + set-function-name "^2.0.1" regexpu-core@^5.3.1: version "5.3.2" @@ -6309,7 +6839,7 @@ resolve@>=1.9.0, resolve@^1.10.0, resolve@^1.14.2, resolve@^1.20.0, resolve@^1.9 is-core-module "^2.2.0" path-parse "^1.0.6" -resolve@^1.21.0, resolve@^1.22.1: +resolve@^1.21.0, resolve@^1.22.1, resolve@^1.22.4: version "1.22.8" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== @@ -6374,6 +6904,16 @@ rxjs@^7.5.1: dependencies: tslib "^2.1.0" +safe-array-concat@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb" + integrity sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q== + dependencies: + call-bind "^1.0.7" + get-intrinsic "^1.2.4" + has-symbols "^1.0.3" + isarray "^2.0.5" + safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -6384,6 +6924,15 @@ safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== +safe-regex-test@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377" + integrity sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw== + dependencies: + call-bind "^1.0.6" + es-errors "^1.3.0" + is-regex "^1.1.4" + "safer-buffer@>= 2.1.2 < 3": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -6445,7 +6994,7 @@ semver-compare@^1.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== -semver@7.x, semver@^7.2.1, semver@^7.3.2, semver@^7.3.5, semver@^7.3.7: +semver@7.x, semver@^7.3.2, semver@^7.3.5, semver@^7.3.7: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== @@ -6525,6 +7074,16 @@ set-function-length@^1.2.1: gopd "^1.0.1" has-property-descriptors "^1.0.2" +set-function-name@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" + integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.2" + setprototypeof@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" @@ -6580,6 +7139,16 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" +side-channel@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" + integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + object-inspect "^1.13.1" + signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" @@ -6754,7 +7323,7 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +string-width@^4.1.0, string-width@^4.2.0: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -6763,6 +7332,34 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" +string.prototype.trim@^1.2.9: + version "1.2.9" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz#b6fa326d72d2c78b6df02f7759c73f8f6274faa4" + integrity sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.0" + es-object-atoms "^1.0.0" + +string.prototype.trimend@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz#3651b8513719e8a9f48de7f2f77640b26652b229" + integrity sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +string.prototype.trimstart@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" + integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -6813,7 +7410,7 @@ strip-final-newline@^2.0.0: resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== -strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: +strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== @@ -6875,17 +7472,6 @@ symbol-tree@^3.2.4: resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== -table@^6.0.9: - version "6.8.1" - resolved "https://registry.yarnpkg.com/table/-/table-6.8.1.tgz#ea2b71359fe03b017a5fbc296204471158080bdf" - integrity sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA== - dependencies: - ajv "^8.0.1" - lodash.truncate "^4.4.2" - slice-ansi "^4.0.0" - string-width "^4.2.3" - strip-ansi "^6.0.1" - tapable@^2.1.1, tapable@^2.2.0, tapable@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" @@ -7022,7 +7608,7 @@ ts-jest@^27.0.7: semver "7.x" yargs-parser "20.x" -tsconfig-paths@^3.10.1: +tsconfig-paths@^3.10.1, tsconfig-paths@^3.15.0: version "3.15.0" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4" integrity sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg== @@ -7091,6 +7677,50 @@ type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" +typed-array-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz#1867c5d83b20fcb5ccf32649e5e2fc7424474ff3" + integrity sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + is-typed-array "^1.1.13" + +typed-array-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz#d92972d3cff99a3fa2e765a28fcdc0f1d89dec67" + integrity sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw== + dependencies: + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + +typed-array-byte-offset@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz#f9ec1acb9259f395093e4567eb3c28a580d02063" + integrity sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + +typed-array-length@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.6.tgz#57155207c76e64a3457482dfdc1c9d1d3c4c73a3" + integrity sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g== + dependencies: + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + possible-typed-array-names "^1.0.0" + typedarray-to-buffer@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" @@ -7129,6 +7759,16 @@ typeson@^6.0.0, typeson@^6.1.0: resolved "https://registry.yarnpkg.com/typeson/-/typeson-6.1.0.tgz#5b2a53705a5f58ff4d6f82f965917cabd0d7448b" integrity sha512-6FTtyGr8ldU0pfbvW/eOZrEtEkczHRUtduBnA90Jh9kMPCiFNnXIon3vF41N0S4tV1HHQt4Hk1j4srpESziCaA== +unbox-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" + integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== + dependencies: + call-bind "^1.0.2" + has-bigints "^1.0.2" + has-symbols "^1.0.3" + which-boxed-primitive "^1.0.2" + undici-types@~5.26.4: version "5.26.5" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" @@ -7215,11 +7855,6 @@ uuid@^9.0.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== -v8-compile-cache@^2.0.3: - version "2.4.0" - resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz#cdada8bec61e15865f05d097c5f4fd30e94dc128" - integrity sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw== - v8-to-istanbul@^8.1.0: version "8.1.1" resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz#77b752fd3975e31bbcef938f85e9bd1c7a8d60ed" @@ -7449,6 +8084,28 @@ whatwg-url@^8.0.0, whatwg-url@^8.4.0, whatwg-url@^8.5.0: tr46 "^2.1.0" webidl-conversions "^6.1.0" +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + +which-typed-array@^1.1.14, which-typed-array@^1.1.15: + version "1.1.15" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d" + integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.2" + which@^1.2.9: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" @@ -7468,6 +8125,11 @@ wildcard@^2.0.0: resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + wrap-ansi@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" @@ -7559,6 +8221,11 @@ yargs@^16.2.0: y18n "^5.0.5" yargs-parser "^20.2.2" +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + yocto-queue@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251" From 80ceb0ebca3d747e8f871b6333d38e0a6005cbfb Mon Sep 17 00:00:00 2001 From: hani Date: Fri, 9 Aug 2024 15:24:35 +0530 Subject: [PATCH 59/88] revert back eslint rule --- .eslintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.eslintrc b/.eslintrc index 803267ee..d529e2cd 100644 --- a/.eslintrc +++ b/.eslintrc @@ -43,7 +43,7 @@ "@typescript-eslint/explicit-function-return-type": "off", "@typescript-eslint/no-unused-vars": "error", // come back and make this an error - "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-explicit-any": "warn", "@typescript-eslint/array-type": [ 2, { From c8d6dbfbe4c4c2e0d5c37261966fe14392721972 Mon Sep 17 00:00:00 2001 From: hani Date: Mon, 12 Aug 2024 15:40:08 +0530 Subject: [PATCH 60/88] Resolve path related error --- .../src/components/LoginFormWithoutJWT.tsx | 7 ++++--- react-example/src/indexWithoutJWT.tsx | 20 +++++++++---------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/react-example/src/components/LoginFormWithoutJWT.tsx b/react-example/src/components/LoginFormWithoutJWT.tsx index c9ed640c..5fd49ca7 100644 --- a/react-example/src/components/LoginFormWithoutJWT.tsx +++ b/react-example/src/components/LoginFormWithoutJWT.tsx @@ -1,10 +1,11 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ import { ChangeEvent, FC, FormEvent, useState } from 'react'; import styled from 'styled-components'; -import _TextField from 'src/components/TextField'; -import _Button from 'src/components/Button'; +import { TextField as _TextField } from './TextField'; +import { Button as _Button } from './Button'; -import { useUser } from 'src/context/Users'; +import { useUser } from '../context/Users'; const TextField = styled(_TextField)``; diff --git a/react-example/src/indexWithoutJWT.tsx b/react-example/src/indexWithoutJWT.tsx index 7bfeef74..5d89d194 100644 --- a/react-example/src/indexWithoutJWT.tsx +++ b/react-example/src/indexWithoutJWT.tsx @@ -2,20 +2,20 @@ import { initializeWithConfig, WithoutJWTParams } from '@iterable/web-sdk'; import ReactDOM from 'react-dom'; import './styles/index.css'; -import Home from 'src/views/Home'; -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 { BrowserRouter, Routes, Route } from 'react-router-dom'; -import Link from 'src/components/Link'; import styled from 'styled-components'; -import { UserProvider } from 'src/context/Users'; +import { Home } from './views/Home'; +import { Commerce } from './views/Commerce'; +import { Events } from './views/Events'; +import { Users } from './views/Users'; +import { InApp } from './views/InApp'; import LoginFormWithoutJWT from './components/LoginFormWithoutJWT'; import AUTTesting from './views/AUTTesting'; -import EmbeddedMsgs from './views/EmbeddedMsgs'; -import EmbeddedMessage from './views/Embedded'; -import EmbeddedMsgsImpressionTracker from './views/EmbeddedMsgsImpressionTracker'; +import { EmbeddedMsgs } from './views/EmbeddedMsgs'; +import { EmbeddedMessage } from './views/Embedded'; +import { EmbeddedMsgsImpressionTracker } from './views/EmbeddedMsgsImpressionTracker'; +import { Link } from './components/Link'; +import { UserProvider } from './context/Users'; const Wrapper = styled.div` display: flex; From 5d60f232f9ad7408881e9a1d1547342a67e67544 Mon Sep 17 00:00:00 2001 From: darshan-iterable Date: Mon, 12 Aug 2024 20:12:58 +0530 Subject: [PATCH 61/88] [MOB-9258] fixed nested IsSet matching (#427) * Fixed scenario where a IsSet criteria is on the object and there is also a IsSet criteria on the nested json * remove unnecessary method call for never happen scenario * revert back eslint rule --------- Co-authored-by: hani --- .../criteriaCompletionChecker.ts | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/anonymousUserTracking/criteriaCompletionChecker.ts b/src/anonymousUserTracking/criteriaCompletionChecker.ts index 0112d104..089dad52 100644 --- a/src/anonymousUserTracking/criteriaCompletionChecker.ts +++ b/src/anonymousUserTracking/criteriaCompletionChecker.ts @@ -346,7 +346,7 @@ class CriteriaCompletionChecker { } if (field.includes('.')) { - const valueFromObj = this.getValueFromNestedObject(eventData, field); + const valueFromObj = this.getFieldValue(eventData, field); if (valueFromObj) { return this.evaluateComparison( query.comparatorType, @@ -371,20 +371,6 @@ class CriteriaCompletionChecker { return matchResult; } - private getValueFromNestedObject(eventData: any, field: string): any { - const valueFromObj = this.getFieldValue(eventData, field); - if (typeof valueFromObj === 'object' && valueFromObj !== null) { - const keys = Object.keys(valueFromObj); - return keys.reduce((acc, key) => { - if (acc === undefined) { - return this.getValueFromNestedObject(valueFromObj, key); - } - return acc; - }, undefined); - } - return valueFromObj; - } - private getFieldValue(data: any, field: string): any { const fields = field.split('.'); return fields.reduce( From 55e06f7e690f023444bce7c1f688d00fe6c9504e Mon Sep 17 00:00:00 2001 From: darshan-iterable Date: Tue, 20 Aug 2024 00:42:23 +0530 Subject: [PATCH 62/88] MOB-9305: fixed events createdAt timeStamps (#431) --- src/anonymousUserTracking/anonymousUserEventManager.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/anonymousUserTracking/anonymousUserEventManager.ts b/src/anonymousUserTracking/anonymousUserEventManager.ts index 8af74d74..0878736a 100644 --- a/src/anonymousUserTracking/anonymousUserEventManager.ts +++ b/src/anonymousUserTracking/anonymousUserEventManager.ts @@ -300,7 +300,11 @@ export class AnonymousUserEventManager { } } - private getCurrentTime = () => new Date().getTime(); + private getCurrentTime = () => { + const dateInMillis = new Date().getTime(); + const dateInSeconds = Math.floor(dateInMillis / 1000); + return dateInSeconds; + }; private getWebPushOptnIn(): string { const notificationManager = window.Notification; From 906163124b90a25e7fcbc2121467750acc801873 Mon Sep 17 00:00:00 2001 From: darshan-iterable Date: Tue, 20 Aug 2024 01:50:46 +0530 Subject: [PATCH 63/88] MOB-9168: Written automated unit tests against Combination logic with Event Type (#429) --- .../tests/combinationLogicCriteria.test.ts | 1985 +++++++++++++++++ 1 file changed, 1985 insertions(+) create mode 100644 src/anonymousUserTracking/tests/combinationLogicCriteria.test.ts diff --git a/src/anonymousUserTracking/tests/combinationLogicCriteria.test.ts b/src/anonymousUserTracking/tests/combinationLogicCriteria.test.ts new file mode 100644 index 00000000..ca808aa7 --- /dev/null +++ b/src/anonymousUserTracking/tests/combinationLogicCriteria.test.ts @@ -0,0 +1,1985 @@ +import { SHARED_PREFS_EVENT_LIST_KEY } from '../../constants'; +import CriteriaCompletionChecker from '../criteriaCompletionChecker'; + +const localStorageMock = { + getItem: jest.fn(), + setItem: jest.fn(), + removeItem: jest.fn() +}; + +describe('CombinationLogicCriteria', () => { + beforeEach(() => { + (global as any).localStorage = localStorageMock; + }); + + it('should return criteriaId 1 if Contact Property AND Custom Event is matched', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { total: 10 }, + eventType: 'customEvent' + }, + { + dataFields: { firstName: 'David' }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '1', + name: 'Combination Logic - Contact Property AND Custom Event', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + field: 'firstName', + comparatorType: 'Equals', + value: 'David', + id: 2, + fieldType: 'string' + } + ] + } + }, + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'total', + comparatorType: 'Equals', + value: '10', + id: 6, + fieldType: 'double' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual('1'); + }); + + it('should return null (combination logic criteria 1 fail)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { total: 10 }, + eventType: 'customEvent' + }, + { + dataFields: { firstName: 'Davidson' }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '1', + name: 'Combination Logic - Contact Property AND Custom Event', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + field: 'firstName', + comparatorType: 'Equals', + value: 'David', + id: 2, + fieldType: 'string' + } + ] + } + }, + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'total', + comparatorType: 'Equals', + value: '10', + id: 6, + fieldType: 'double' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual(null); + }); + + it('should return criteriaId 2 if Contact Property OR Custom Event is matched', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { total: 10 }, + eventType: 'customEvent' + }, + { + dataFields: { firstName: 'David' }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '2', + name: 'Combination Logic - Contact Property OR Custom Event', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + field: 'firstName', + comparatorType: 'Equals', + value: 'David', + id: 2, + fieldType: 'string' + } + ] + } + }, + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'total', + comparatorType: 'Equals', + value: '10', + id: 6, + fieldType: 'double' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual('2'); + }); + + it('should return null (combination logic criteria 2 fail)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { total: 101 }, + eventType: 'customEvent' + }, + { + dataFields: { firstName: 'Davidson' }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '2', + name: 'Combination Logic - Contact Property OR Custom Event', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + field: 'firstName', + comparatorType: 'Equals', + value: 'David', + id: 2, + fieldType: 'string' + } + ] + } + }, + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'total', + comparatorType: 'Equals', + value: '10', + id: 6, + fieldType: 'double' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual(null); + }); + + it('should return criteriaId 3 if Contact Property NOR (NOT) Custom Event is matched', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + itesm: [{ name: 'Cofee', id: 'fdsafds', price: 10, quantity: 2 }], + total: 10 + }, + eventType: 'customEvent' + }, + { + dataFields: { firstName: 'Davidson' }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '3', + name: 'Combination Logic - Contact Property NOR (NOT) Custom Event', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'Not', + searchQueries: [ + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'total', + comparatorType: 'Equals', + value: '10', + id: 6, + fieldType: 'double' + } + ] + } + }, + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + field: 'firstName', + comparatorType: 'Equals', + value: 'David', + id: 2, + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual('3'); + }); + + it('should return null (combination logic criteria 3 fail)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { total: 1 }, + eventType: 'customEvent' + }, + { + dataFields: { firstName: 'David' }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '3', + name: 'Combination Logic - Contact Property NOR (NOT) Custom Event', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'Not', + searchQueries: [ + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + field: 'firstName', + comparatorType: 'Equals', + value: 'David', + id: 2, + fieldType: 'string' + } + ] + } + }, + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'total', + comparatorType: 'Equals', + value: '10', + id: 6, + fieldType: 'double' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual(null); + }); + + it('should return criteriaId 4 if UpdateCart AND Contact Property is matched', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + items: [{ name: 'fried', id: 'fried', price: 10, quantity: 2 }], + eventType: 'cartUpdate' + }, + { + dataFields: { firstName: 'David' }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '4', + name: 'Combination Logic - UpdateCart AND Contact Property', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'updateCart.updatedShoppingCartItems.name', + comparatorType: 'Equals', + value: 'fried', + id: 2, + fieldType: 'string' + } + ] + } + }, + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + field: 'firstName', + comparatorType: 'Equals', + value: 'David', + id: 2, + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual('4'); + }); + + it('should return null (combination logic criteria 4 fail)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + items: [{ name: 'chicken', id: 'chicken', price: 10, quantity: 2 }], + eventType: 'cartUpdate' + }, + { + dataFields: { firstName: 'Davidson' }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '4', + name: 'Combination Logic - UpdateCart AND Contact Property', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'updateCart.updatedShoppingCartItems.name', + comparatorType: 'Equals', + value: 'fried', + id: 2, + fieldType: 'string' + } + ] + } + }, + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + field: 'firstName', + comparatorType: 'Equals', + value: 'David', + id: 2, + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual(null); + }); + + it('should return criteriaId 5 if UpdateCart OR Contact Property is matched', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + items: [{ name: 'fried', id: 'fried', price: 10, quantity: 2 }], + eventType: 'cartUpdate' + }, + { + dataFields: { firstName: 'David' }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '5', + name: 'Combination Logic - UpdateCart OR Contact Property', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'updateCart.updatedShoppingCartItems.name', + comparatorType: 'Equals', + value: 'fried', + id: 2, + fieldType: 'string' + } + ] + } + }, + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + field: 'firstName', + comparatorType: 'Equals', + value: 'David', + id: 2, + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual('5'); + }); + + it('should return null (combination logic criteria 5 fail)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + items: [{ name: 'chicken', id: 'chicken', price: 10, quantity: 2 }], + eventType: 'cartUpdate' + }, + { + dataFields: { firstName: 'Davidson' }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '5', + name: 'Combination Logic - UpdateCart OR Contact Property', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'updateCart.updatedShoppingCartItems.name', + comparatorType: 'Equals', + value: 'fried', + id: 2, + fieldType: 'string' + } + ] + } + }, + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + field: 'firstName', + comparatorType: 'Equals', + value: 'David', + id: 2, + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual(null); + }); + + it('should return criteriaId 6 if UpdateCart NOR (NOT) Contact Property is matched', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + items: [{ name: 'boiled', id: 'boiled', price: 10, quantity: 2 }], + eventType: 'cartUpdate' + }, + { + dataFields: { firstName: 'Davidson' }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '6', + name: 'Combination Logic - UpdateCart NOR (NOT) Contact Property', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'Not', + searchQueries: [ + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'updateCart.updatedShoppingCartItems.name', + comparatorType: 'Equals', + value: 'fried', + id: 2, + fieldType: 'string' + } + ] + } + }, + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + field: 'firstName', + comparatorType: 'Equals', + value: 'David', + id: 2, + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual('6'); + }); + + it('should return null (combination logic criteria 6 fail)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + items: [{ name: 'fried', id: 'fried', price: 10, quantity: 2 }], + eventType: 'cartUpdate' + }, + { + dataFields: { firstName: 'David' }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '6', + name: 'Combination Logic - UpdateCart NOR (NOT) Contact Property', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'Not', + searchQueries: [ + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'updateCart.updatedShoppingCartItems.name', + comparatorType: 'Equals', + value: 'fried', + id: 2, + fieldType: 'string' + } + ] + } + }, + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + field: 'firstName', + comparatorType: 'Equals', + value: 'David', + id: 2, + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual(null); + }); + + it('should return criteriaId 7 if Purchase AND UpdateCart is matched', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + items: [{ name: 'chicken', id: 'chicken', price: 10, quantity: 2 }], + eventType: 'purchase' + }, + { + items: [{ name: 'fried', id: 'fried', price: 10, quantity: 2 }], + eventType: 'cartUpdate' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '7', + name: 'Combination Logic - Purchase AND UpdateCart', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + field: 'shoppingCartItems.name', + comparatorType: 'Equals', + value: 'chicken', + id: 13, + fieldType: 'string' + } + ] + } + }, + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'updateCart.updatedShoppingCartItems.name', + comparatorType: 'Equals', + value: 'fried', + id: 2, + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual('7'); + }); + + it('should return null (combination logic criteria 7 fail)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + items: [{ name: 'fried', id: 'fried', price: 10, quantity: 2 }], + eventType: 'purchase' + }, + { + items: [{ name: 'chicken', id: 'chicken', price: 10, quantity: 2 }], + eventType: 'cartUpdate' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '7', + name: 'Combination Logic - Purchase AND UpdateCart', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + field: 'shoppingCartItems.name', + comparatorType: 'Equals', + value: 'chicken', + id: 13, + fieldType: 'string' + } + ] + } + }, + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'updateCart.updatedShoppingCartItems.name', + comparatorType: 'Equals', + value: 'fried', + id: 2, + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual(null); + }); + + it('should return criteriaId 8 if Purchase OR UpdateCart is matched', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + items: [{ name: 'chicken', id: 'chicken', price: 10, quantity: 2 }], + eventType: 'purchase' + }, + { + items: [{ name: 'fried', id: 'fried', price: 10, quantity: 2 }], + eventType: 'cartUpdate' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '8', + name: 'Combination Logic - Purchase OR UpdateCart', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + field: 'shoppingCartItems.name', + comparatorType: 'Equals', + value: 'chicken', + id: 13, + fieldType: 'string' + } + ] + } + }, + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'updateCart.updatedShoppingCartItems.name', + comparatorType: 'Equals', + value: 'fried', + id: 2, + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual('8'); + }); + + it('should return null (combination logic criteria 8 fail)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + items: [{ name: 'fried', id: 'fried', price: 10, quantity: 2 }], + eventType: 'purchase' + }, + { + items: [{ name: 'chicken', id: 'chicken', price: 10, quantity: 2 }], + eventType: 'cartUpdate' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '8', + name: 'Combination Logic - Purchase OR UpdateCart', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + field: 'shoppingCartItems.name', + comparatorType: 'Equals', + value: 'chicken', + id: 13, + fieldType: 'string' + } + ] + } + }, + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'updateCart.updatedShoppingCartItems.name', + comparatorType: 'Equals', + value: 'fried', + id: 2, + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual(null); + }); + + it('should return criteriaId 9 if Purchase NOR (NOT) UpdateCart is matched', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + items: [{ name: 'beef', id: 'beef', price: 10, quantity: 2 }], + eventType: 'purchase' + }, + { + items: [{ name: 'boiled', id: 'boiled', price: 10, quantity: 2 }], + eventType: 'cartUpdate' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '9', + name: 'Combination Logic - Purchase NOR (NOT) UpdateCart', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'Not', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + field: 'shoppingCartItems.name', + comparatorType: 'Equals', + value: 'chicken', + id: 13, + fieldType: 'string' + } + ] + } + }, + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'updateCart.updatedShoppingCartItems.name', + comparatorType: 'Equals', + value: 'fried', + id: 2, + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual('9'); + }); + + it('should return null (combination logic criteria 9 fail)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + items: [{ name: 'chicken', id: 'chicken', price: 10, quantity: 2 }], + eventType: 'purchase' + }, + { + items: [{ name: 'fried', id: 'fried', price: 10, quantity: 2 }], + eventType: 'cartUpdate' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '9', + name: 'Combination Logic - Purchase NOR (NOT) UpdateCart', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'Not', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + field: 'shoppingCartItems.name', + comparatorType: 'Equals', + value: 'chicken', + id: 13, + fieldType: 'string' + } + ] + } + }, + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'updateCart.updatedShoppingCartItems.name', + comparatorType: 'Equals', + value: 'fried', + id: 2, + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual(null); + }); + + it('should return criteriaId 10 if Custom Event AND Purchase is matched', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { eventName: 'birthday' }, + eventType: 'customEvent' + }, + { + items: [{ name: 'chicken', id: 'chicken', price: 10, quantity: 2 }], + eventType: 'purchase' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '10', + name: 'Combination Logic - Custom Event AND Purchase', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + field: 'shoppingCartItems.name', + comparatorType: 'Equals', + value: 'chicken', + id: 13, + fieldType: 'string' + } + ] + } + }, + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'eventName', + comparatorType: 'Equals', + value: 'birthday', + id: 16, + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual('10'); + }); + + it('should return null (combination logic criteria 10 fail)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { eventName: 'anniversary' }, + eventType: 'customEvent' + }, + { + items: [{ name: 'fried', id: 'fried', price: 10, quantity: 2 }], + eventType: 'purchase' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '10', + name: 'Combination Logic - Custom Event AND Purchase', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + field: 'shoppingCartItems.name', + comparatorType: 'Equals', + value: 'chicken', + id: 13, + fieldType: 'string' + } + ] + } + }, + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'eventName', + comparatorType: 'Equals', + value: 'birthday', + id: 16, + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual(null); + }); + + it('should return criteriaId 11 if Custom Event OR Purchase is matched', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { eventName: 'birthday' }, + eventType: 'customEvent' + } + /* { + items: [{ name: 'chicken', id: 'chicken', price: 10, quantity: 2 }], + eventType: 'purchase' + } */ + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '11', + name: 'Combination Logic - Custom Event OR Purchase', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + field: 'shoppingCartItems.name', + comparatorType: 'Equals', + value: 'chicken', + id: 13, + fieldType: 'string' + } + ] + } + }, + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'eventName', + comparatorType: 'Equals', + value: 'birthday', + id: 16, + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual('11'); + }); + + it('should return null (combination logic criteria 11 fail)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { eventName: 'anniversary' }, + eventType: 'customEvent' + }, + { + items: [{ name: 'fried', id: 'fried', price: 10, quantity: 2 }], + eventType: 'purchase' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '11', + name: 'Combination Logic - Custom Event OR Purchase', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + field: 'shoppingCartItems.name', + comparatorType: 'Equals', + value: 'chicken', + id: 13, + fieldType: 'string' + } + ] + } + }, + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'eventName', + comparatorType: 'Equals', + value: 'birthday', + id: 16, + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual(null); + }); + + it('should return criteriaId 12 if Custom Event NOR (NOT) Purchase is matched', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { eventName: 'anniversary' }, + eventType: 'customEvent' + }, + { + items: [{ name: 'beef', id: 'beef', price: 10, quantity: 2 }], + eventType: 'purchase' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '12', + name: 'Combination Logic - Custom Event NOR (NOT) Purchase', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'Not', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + field: 'shoppingCartItems.name', + comparatorType: 'Equals', + value: 'chicken', + id: 13, + fieldType: 'string' + } + ] + } + }, + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'eventName', + comparatorType: 'Equals', + value: 'birthday', + id: 16, + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual('12'); + }); + + it('should return null (combination logic criteria 12 fail)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { eventName: 'birthday' }, + eventType: 'customEvent' + }, + { + items: [{ name: 'chicken', id: 'chicken', price: 10, quantity: 2 }], + eventType: 'purchase' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '12', + name: 'Combination Logic - Custom Event NOR (NOT) Purchase', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'Not', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + field: 'shoppingCartItems.name', + comparatorType: 'Equals', + value: 'chicken', + id: 13, + fieldType: 'string' + } + ] + } + }, + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'eventName', + comparatorType: 'Equals', + value: 'birthday', + id: 16, + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual(null); + }); +}); From 7ae6910af2e61c1ffb8593c53e0390565bbd7aad Mon Sep 17 00:00:00 2001 From: darshan-iterable Date: Tue, 20 Aug 2024 01:51:32 +0530 Subject: [PATCH 64/88] MOB-8824: Added limitation to event storage (#430) * MOB-8824: Added limitation to event storage * fix: build issue --- .../anonymousUserEventManager.ts | 17 ++++++++++++++++- src/constants.ts | 3 +++ src/utils/config.ts | 6 ++++-- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/anonymousUserTracking/anonymousUserEventManager.ts b/src/anonymousUserTracking/anonymousUserEventManager.ts index 0878736a..52920573 100644 --- a/src/anonymousUserTracking/anonymousUserEventManager.ts +++ b/src/anonymousUserTracking/anonymousUserEventManager.ts @@ -24,7 +24,8 @@ import { ENDPOINT_TRACK_ANON_SESSION, WEB_PLATFORM, KEY_PREFER_USERID, - ENDPOINTS + ENDPOINTS, + DEFAULT_EVENT_THRESHOLD_LIMIT } from '../constants'; import { baseIterableRequest } from '../request'; import { IterableResponse } from '../types'; @@ -38,6 +39,7 @@ import { } from '../commerce/commerce.schema'; import { updateUserSchema } from '../users/users.schema'; import { InAppTrackRequestParams } from '../events'; +import config from '../utils/config'; type AnonUserFunction = (userId: string) => void; @@ -290,6 +292,19 @@ export class AnonymousUserEventManager { previousDataArray.push(newDataObject); } + // - The code below limits the number of events stored in local storage. + // - The event list acts as a queue, with the oldest events being deleted + // when new events are stored once the event threshold limit is reached. + + const eventThresholdLimit = + (config.getConfig('eventThresholdLimit') as number) ?? + DEFAULT_EVENT_THRESHOLD_LIMIT; + if (previousDataArray.length > eventThresholdLimit) { + previousDataArray = previousDataArray.slice( + previousDataArray.length - eventThresholdLimit + ); + } + localStorage.setItem( SHARED_PREFS_EVENT_LIST_KEY, JSON.stringify(previousDataArray) diff --git a/src/constants.ts b/src/constants.ts index 7d779ace..11dd7792 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -4,6 +4,9 @@ export const DISPLAY_INTERVAL_DEFAULT = 30000; /* how many times we try to create a new user when _setUserID_ is invoked */ export const RETRY_USER_ATTEMPTS = 0; +/* How many events can be stored in the local storage */ +export const DEFAULT_EVENT_THRESHOLD_LIMIT = 100; + const IS_EU_ITERABLE_SERVICE = process.env.IS_EU_ITERABLE_SERVICE === 'true'; export const dangerouslyAllowJsPopupExecution = diff --git a/src/utils/config.ts b/src/utils/config.ts index 11624f8e..0c66ee24 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -1,4 +1,4 @@ -import { BASE_URL } from '../constants'; +import { BASE_URL, DEFAULT_EVENT_THRESHOLD_LIMIT } from '../constants'; export type Options = { logLevel: 'none' | 'verbose'; @@ -6,6 +6,7 @@ export type Options = { enableAnonTracking: boolean; isEuIterableService: boolean; dangerouslyAllowJsPopups: boolean; + eventThresholdLimit?: number; }; const _config = () => { @@ -14,7 +15,8 @@ const _config = () => { baseURL: BASE_URL, enableAnonTracking: false, isEuIterableService: false, - dangerouslyAllowJsPopups: false + dangerouslyAllowJsPopups: false, + eventThresholdLimit: DEFAULT_EVENT_THRESHOLD_LIMIT }; const getConfig = (option: K) => options[option]; From fc1d7ec8417b2eb177648c6c86a78913c1ade7e1 Mon Sep 17 00:00:00 2001 From: Hani Vora <150109181+hani-iterable@users.noreply.github.com> Date: Tue, 20 Aug 2024 02:25:27 +0530 Subject: [PATCH 65/88] Nested custom event check related change (#432) --- src/anonymousUserTracking/complexCriteria.test.ts | 4 +--- src/anonymousUserTracking/criteriaCompletionChecker.ts | 5 ++++- src/anonymousUserTracking/tests/complexCriteria.test.ts | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/anonymousUserTracking/complexCriteria.test.ts b/src/anonymousUserTracking/complexCriteria.test.ts index df29440c..7aba642f 100644 --- a/src/anonymousUserTracking/complexCriteria.test.ts +++ b/src/anonymousUserTracking/complexCriteria.test.ts @@ -694,9 +694,7 @@ describe('complexCriteria', () => { { eventName: 'button-clicked', dataFields: { - 'button-clicked': { - lastPageViewed: 'welcome page' - } + lastPageViewed: 'welcome page' }, eventType: 'customEvent' }, diff --git a/src/anonymousUserTracking/criteriaCompletionChecker.ts b/src/anonymousUserTracking/criteriaCompletionChecker.ts index 089dad52..fbd3103d 100644 --- a/src/anonymousUserTracking/criteriaCompletionChecker.ts +++ b/src/anonymousUserTracking/criteriaCompletionChecker.ts @@ -372,7 +372,10 @@ class CriteriaCompletionChecker { } private getFieldValue(data: any, field: string): any { - const fields = field.split('.'); + let fields = field.split('.'); + if (data?.eventType === TRACK_EVENT && data?.eventName === fields[0]) { + fields = [fields[fields.length - 1]]; + } return fields.reduce( (value, currentField) => value && value[currentField] !== undefined diff --git a/src/anonymousUserTracking/tests/complexCriteria.test.ts b/src/anonymousUserTracking/tests/complexCriteria.test.ts index c58280bc..03b4de27 100644 --- a/src/anonymousUserTracking/tests/complexCriteria.test.ts +++ b/src/anonymousUserTracking/tests/complexCriteria.test.ts @@ -694,7 +694,7 @@ describe('complexCriteria', () => { { eventName: 'button-clicked', dataFields: { - 'button-clicked': { lastPageViewed: 'welcome page' } + lastPageViewed: 'welcome page' }, eventType: 'customEvent' }, From 4197cb26dd29b79de6bb0288d2332b4357a4ebff Mon Sep 17 00:00:00 2001 From: Hani Vora <150109181+hani-iterable@users.noreply.github.com> Date: Fri, 23 Aug 2024 01:11:12 +0530 Subject: [PATCH 66/88] MOB 9328 - Verify AUT works with JWT (#437) * implement code for generate JWT token * revert back the eslint rule --- react-example/package.json | 6 ++-- react-example/server/generate.py | 57 ++++++++++++++++++++++++++++++++ react-example/server/server.js | 33 ++++++++++++++++++ react-example/src/index.tsx | 2 +- react-example/yarn.lock | 14 ++++++-- 5 files changed, 106 insertions(+), 6 deletions(-) create mode 100644 react-example/server/generate.py create mode 100644 react-example/server/server.js diff --git a/react-example/package.json b/react-example/package.json index 0a41d81a..224015b8 100644 --- a/react-example/package.json +++ b/react-example/package.json @@ -20,7 +20,7 @@ ], "scripts": { "build": "tsc && webpack", - "start": "concurrently \"tsc -w --pretty\" \"webpack-dev-server\" -n 'tsc,webpack' -k", + "start": "concurrently \"tsc -w --pretty\" \"webpack-dev-server\" -n 'tsc,webpack' -k \"node server/server.js\"", "test": "jest --config jest.config.js", "format": "prettier --write \"src/**/*.{ts,tsx}\" \"src/**/*.js\"", "typecheck": "tsc --noEmit true --emitDeclarationOnly false", @@ -70,6 +70,8 @@ "react-dom": "^18.2.0", "react-router-dom": "^6.2.1", "styled-components": "^5.3.3", - "uuid": "^9.0.0" + "uuid": "^9.0.0", + "cors": "^2.8.5", + "express": "^4.19.2" } } diff --git a/react-example/server/generate.py b/react-example/server/generate.py new file mode 100644 index 00000000..a3acfa83 --- /dev/null +++ b/react-example/server/generate.py @@ -0,0 +1,57 @@ +import base64 +import json +import hmac +import hashlib +import sys + +def generate_jwt(api_key_shared_secret, user_id, email, iat, exp): + encoding = "utf-8" + secret = api_key_shared_secret.encode(encoding) + + jwt_header = json.dumps( + {"alg": "HS256", "typ": "JWT"}, separators=(",", ":") + ).encode(encoding) + + payload = { + "iat": iat, + "exp": exp + } + + if user_id is not '': + payload["userId"] = user_id + elif email is not '': + payload["email"] = email + + jwt_payload = json.dumps(payload, separators=(",", ":")).encode(encoding) + + encoded_header_bytes = base64.urlsafe_b64encode(jwt_header).replace(b"=", b"") + encoded_payload_bytes = base64.urlsafe_b64encode(jwt_payload).replace(b"=", b"") + + jwt_signature = hmac.digest( + key=secret, + msg=b".".join([encoded_header_bytes, encoded_payload_bytes]), + digest=hashlib.sha256 + ) + + encoded_signature_bytes = base64.urlsafe_b64encode(jwt_signature).replace(b"=", b"") + + jwt_returned = ( + f"{str(encoded_header_bytes, encoding)}" + + f".{str(encoded_payload_bytes, encoding)}" + + f".{str(encoded_signature_bytes, encoding)}" + ) + + return jwt_returned + +if __name__ == "__main__": + if len(sys.argv) != 6: + print("Usage: python generate_jwt.py ") + sys.exit(1) + + api_key_shared_secret = sys.argv[1] + user_id = sys.argv[2] + email = sys.argv[3] + iat = int(sys.argv[4]) + exp = int(sys.argv[5]) + + jwt_token = generate_jwt(api_key_shared_secret, user_id, email, iat, exp) diff --git a/react-example/server/server.js b/react-example/server/server.js new file mode 100644 index 00000000..c4d245b6 --- /dev/null +++ b/react-example/server/server.js @@ -0,0 +1,33 @@ +/* eslint-disable import/no-extraneous-dependencies */ +/* eslint-disable consistent-return */ +/* eslint-disable camelcase */ +/* eslint-disable @typescript-eslint/no-var-requires */ +const express = require('express'); +const { exec } = require('child_process'); +const cors = require('cors'); + +const app = express(); +const port = 3000; + +app.use(cors({ origin: '*' })); +app.use(express.json()); + +app.post('/generate', (req, res) => { + const { jwt_secret, userId, email, exp_minutes } = req.body; + const emailIdentifier = email !== undefined ? email : ''; + const userIdentifier = userId !== undefined ? userId : ''; + const currentTimeInSeconds = Math.floor(new Date().getTime() / 1000); + const expirationTimeInSeconds = currentTimeInSeconds + exp_minutes * 60; + + const command = `python3 server/generate.py "${jwt_secret}" "${userIdentifier}" "${emailIdentifier}" "${currentTimeInSeconds}" "${expirationTimeInSeconds}"`; + exec(command, (error, stdout, stderr) => { + if (error) { + return res.status(500).json({ message: `Error: ${stderr}` }); + } + res.json({ token: stdout }); + }); +}); + +app.listen(port, () => { + console.log(`Server listening at http://localhost:${port}`); +}); diff --git a/react-example/src/index.tsx b/react-example/src/index.tsx index 5cccc81d..d70adf59 100644 --- a/react-example/src/index.tsx +++ b/react-example/src/index.tsx @@ -52,7 +52,7 @@ const HomeLink = styled(Link)` generateJWT: ({ email, userID }) => axios .post( - process.env.JWT_GENERATOR || 'http://localhost:5000/generate', + process.env.JWT_GENERATOR || 'http://localhost:3000/generate', { exp_minutes: 2, email, diff --git a/react-example/yarn.lock b/react-example/yarn.lock index 6fc967dd..d93ebb5a 100644 --- a/react-example/yarn.lock +++ b/react-example/yarn.lock @@ -2102,6 +2102,14 @@ core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== +cors@^2.8.5: + version "2.8.5" + resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" + integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== + dependencies: + object-assign "^4" + vary "^1" + cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -2830,7 +2838,7 @@ expect@^27.5.1: jest-matcher-utils "^27.5.1" jest-message-util "^27.5.1" -express@^4.17.3: +express@^4.17.3, express@^4.19.2: version "4.19.2" resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465" integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q== @@ -4589,7 +4597,7 @@ nwsapi@^2.2.0: resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.10.tgz#0b77a68e21a0b483db70b11fad055906e867cda8" integrity sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ== -object-assign@^4.1.1: +object-assign@^4, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== @@ -6068,7 +6076,7 @@ v8-to-istanbul@^8.1.0: convert-source-map "^1.6.0" source-map "^0.7.3" -vary@~1.1.2: +vary@^1, vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== From 2eda85df049b4d47e5643d8f3abecc82e441e24c Mon Sep 17 00:00:00 2001 From: Hani Vora <150109181+hani-iterable@users.noreply.github.com> Date: Fri, 23 Aug 2024 18:52:58 +0530 Subject: [PATCH 67/88] MOB 9149 - Sample app object updates not formatted properly (#438) * resolve nested criteria obj error * modify test case --- .../criteriaCompletionChecker.ts | 11 +++++------ .../tests/criteriaCompletionChecker.test.ts | 5 ++--- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/anonymousUserTracking/criteriaCompletionChecker.ts b/src/anonymousUserTracking/criteriaCompletionChecker.ts index fbd3103d..0f1734c9 100644 --- a/src/anonymousUserTracking/criteriaCompletionChecker.ts +++ b/src/anonymousUserTracking/criteriaCompletionChecker.ts @@ -345,6 +345,10 @@ class CriteriaCompletionChecker { } } + const eventKeyItems = filteredLocalDataKeys.filter( + (keyItem) => keyItem === field + ); + if (field.includes('.')) { const valueFromObj = this.getFieldValue(eventData, field); if (valueFromObj) { @@ -354,12 +358,7 @@ class CriteriaCompletionChecker { query.value ? query.value : '' ); } - } - const eventKeyItems = filteredLocalDataKeys.filter( - (keyItem) => keyItem === field - ); - - if (eventKeyItems.length) { + } else if (eventKeyItems.length) { return this.evaluateComparison( query.comparatorType, eventData[field], diff --git a/src/anonymousUserTracking/tests/criteriaCompletionChecker.test.ts b/src/anonymousUserTracking/tests/criteriaCompletionChecker.test.ts index 87bf5eac..3e3cf4b5 100644 --- a/src/anonymousUserTracking/tests/criteriaCompletionChecker.test.ts +++ b/src/anonymousUserTracking/tests/criteriaCompletionChecker.test.ts @@ -920,9 +920,8 @@ describe('CriteriaCompletionChecker', () => { { eventName: 'button-clicked', dataFields: { - 'button-clicked': 'signup page', - 'button-clicked.animal': 'test page', - 'button-clicked.clickCount': '2', + animal: 'test page', + clickCount: '2', total: 3 }, createdAt: 1700071052507, From 904f602d2db46b9b99fe14f3d1230a58e55d21c1 Mon Sep 17 00:00:00 2001 From: darshan-iterable Date: Fri, 30 Aug 2024 00:19:26 +0530 Subject: [PATCH 68/88] MOB-9308: supports nested field types (#439) * MOB-9308: supports nested field types * fix: linting issue --- react-example/src/views/AUTTesting.tsx | 2 +- .../criteriaCompletionChecker.ts | 19 ++ .../tests/nestedTesting.test.ts | 196 ++++++++++++++++++ 3 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 src/anonymousUserTracking/tests/nestedTesting.test.ts diff --git a/react-example/src/views/AUTTesting.tsx b/react-example/src/views/AUTTesting.tsx index 391c8e3d..53838c08 100644 --- a/react-example/src/views/AUTTesting.tsx +++ b/react-example/src/views/AUTTesting.tsx @@ -40,7 +40,7 @@ export const AUTTesting: FC = () => { const [isUpdatingCart, setUpdatingCart] = useState(false); const [isTrackingPurchase, setTrackingPurchase] = useState(false); const [userDataField, setUserDataField] = useState( - ' { "dataFields": {"phoneNumber": "5768855911", "subscribed": true }}' + ' { "dataFields": {"email": "user@example.com","furniture": [{"furnitureType": "Sofa","furnitureColor": "White","lengthInches": 40,"widthInches": 60},{"furnitureType": "Sofa","furnitureColor": "Gray","lengthInches": 20,"widthInches": 30}] }}' ); const [isUpdatingUser, setUpdatingUser] = useState(false); const [updateUserResponse, setUpdateUserResponse] = useState( diff --git a/src/anonymousUserTracking/criteriaCompletionChecker.ts b/src/anonymousUserTracking/criteriaCompletionChecker.ts index 0f1734c9..a67aa164 100644 --- a/src/anonymousUserTracking/criteriaCompletionChecker.ts +++ b/src/anonymousUserTracking/criteriaCompletionChecker.ts @@ -350,6 +350,25 @@ class CriteriaCompletionChecker { ); if (field.includes('.')) { + const fields = field.split('.'); + if (Array.isArray(eventData[fields[0]])) { + return eventData[fields[0]]?.every((item: any) => { + const data = { + [fields[0]]: item, + eventType: query?.eventType + }; + const valueFromObj = this.getFieldValue(data, field); + if (valueFromObj) { + return this.evaluateComparison( + query.comparatorType, + valueFromObj, + query.value ? query.value : '' + ); + } + return false; + }); + } + const valueFromObj = this.getFieldValue(eventData, field); if (valueFromObj) { return this.evaluateComparison( diff --git a/src/anonymousUserTracking/tests/nestedTesting.test.ts b/src/anonymousUserTracking/tests/nestedTesting.test.ts new file mode 100644 index 00000000..703c4c91 --- /dev/null +++ b/src/anonymousUserTracking/tests/nestedTesting.test.ts @@ -0,0 +1,196 @@ +import { SHARED_PREFS_EVENT_LIST_KEY } from '../../constants'; +import CriteriaCompletionChecker from '../criteriaCompletionChecker'; + +const localStorageMock = { + getItem: jest.fn(), + setItem: jest.fn(), + removeItem: jest.fn() +}; + +describe('nestedTesting', () => { + beforeEach(() => { + (global as any).localStorage = localStorageMock; + }); + + it('should return criteriaId 168 (nested field)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + email: 'user@example.com', + furniture: [ + { + furnitureType: 'Sofa', + furnitureColor: 'White', + lengthInches: 40, + widthInches: 60 + }, + { + furnitureType: 'Sofa', + furnitureColor: 'Gray', + lengthInches: 20, + widthInches: 30 + } + ] + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '168', + name: 'nested testing', + createdAt: 1721251169153, + updatedAt: 1723488175352, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + field: 'furniture', + comparatorType: 'IsSet', + value: '', + fieldType: 'nested' + }, + { + dataType: 'user', + field: 'furniture.furnitureColor', + comparatorType: 'IsSet', + value: '', + fieldType: 'string' + }, + { + dataType: 'user', + field: 'furniture.furnitureType', + comparatorType: 'Equals', + value: 'Sofa', + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual('168'); + }); + + it('should return criteriaId 168 (nested field - No match)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + email: 'user@example.com', + furniture: [ + { + furnitureType: 'Sofa', + furnitureColor: 'White', + lengthInches: 40, + widthInches: 60 + }, + { + furnitureType: 'table', + furnitureColor: 'Gray', + lengthInches: 20, + widthInches: 30 + } + ] + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify({ + count: 1, + criterias: [ + { + criteriaId: '168', + name: 'nested testing', + createdAt: 1721251169153, + updatedAt: 1723488175352, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + field: 'furniture', + comparatorType: 'IsSet', + value: '', + fieldType: 'nested' + }, + { + dataType: 'user', + field: 'furniture.furnitureColor', + comparatorType: 'IsSet', + value: '', + fieldType: 'string' + }, + { + dataType: 'user', + field: 'furniture.furnitureType', + comparatorType: 'Equals', + value: 'Sofa', + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] + }) + ); + expect(result).toEqual(null); + }); +}); From 021816625867f9a1c1c3f74de49358eb0f2fa04d Mon Sep 17 00:00:00 2001 From: darshan-iterable Date: Fri, 30 Aug 2024 00:19:46 +0530 Subject: [PATCH 69/88] =?UTF-8?q?MOB-9081:=20Written=20automated=20unit=20?= =?UTF-8?q?tests=20for=20different=20field=20types=20and=20=E2=80=A6=20(#4?= =?UTF-8?q?36)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * MOB-9081: Written automated unit tests for different field types and comparator types * made criterias to be re-usable --- src/anonymousUserTracking/tests/constants.ts | 342 ++++++++++++++ ...aTypeComparatorSearchQueryCriteria.test.ts | 439 ++++++++++++++++++ 2 files changed, 781 insertions(+) create mode 100644 src/anonymousUserTracking/tests/constants.ts create mode 100644 src/anonymousUserTracking/tests/dataTypeComparatorSearchQueryCriteria.test.ts diff --git a/src/anonymousUserTracking/tests/constants.ts b/src/anonymousUserTracking/tests/constants.ts new file mode 100644 index 00000000..07907f65 --- /dev/null +++ b/src/anonymousUserTracking/tests/constants.ts @@ -0,0 +1,342 @@ +export const DATA_TYPE_COMPARATOR_EQUALS = { + count: 1, + criterias: [ + { + criteriaId: '285', + name: 'Criteria_EventTimeStamp_3_Long', + createdAt: 1722497422151, + updatedAt: 1722500235276, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + field: 'eventTimeStamp', + comparatorType: 'Equals', + value: '3', + fieldType: 'long' + }, + { + dataType: 'user', + field: 'savings', + comparatorType: 'Equals', + value: '19.99', + fieldType: 'double' + }, + { + dataType: 'user', + field: 'likes_boba', + comparatorType: 'Equals', + value: 'true', + fieldType: 'boolean' + }, + { + dataType: 'user', + field: 'country', + comparatorType: 'Equals', + value: 'Chaina', + fieldType: 'String' + } + ] + } + } + ] + } + ] + } + } + ] +}; + +export const DATA_TYPE_COMPARATOR_DOES_NOT_EQUAL = { + count: 1, + criterias: [ + { + criteriaId: '285', + name: 'Criteria_EventTimeStamp_3_Long', + createdAt: 1722497422151, + updatedAt: 1722500235276, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + field: 'eventTimeStamp', + comparatorType: 'DoesNotEqual', + value: '3', + fieldType: 'long' + }, + { + dataType: 'user', + field: 'savings', + comparatorType: 'DoesNotEqual', + value: '19.99', + fieldType: 'double' + }, + { + dataType: 'user', + field: 'likes_boba', + comparatorType: 'DoesNotEqual', + value: 'true', + fieldType: 'boolean' + } + ] + } + } + ] + } + ] + } + } + ] +}; + +export const DATA_TYPE_COMPARATOR_LESS_THAN = { + count: 1, + criterias: [ + { + criteriaId: '289', + name: 'Criteria_EventTimeStamp_3_Long', + createdAt: 1722497422151, + updatedAt: 1722500235276, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + field: 'eventTimeStamp', + comparatorType: 'LessThan', + value: '15', + fieldType: 'long' + }, + { + dataType: 'user', + field: 'savings', + comparatorType: 'LessThan', + value: '15', + fieldType: 'double' + } + ] + } + } + ] + } + ] + } + } + ] +}; + +export const DATA_TYPE_COMPARATOR_LESS_THAN_OR_EQUAL_TO = { + count: 1, + criterias: [ + { + criteriaId: '290', + name: 'Criteria_EventTimeStamp_3_Long', + createdAt: 1722497422151, + updatedAt: 1722500235276, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + field: 'eventTimeStamp', + comparatorType: 'LessThanOrEqualTo', + value: '17', + fieldType: 'long' + }, + { + dataType: 'user', + field: 'savings', + comparatorType: 'LessThanOrEqualTo', + value: '17', + fieldType: 'double' + } + ] + } + } + ] + } + ] + } + } + ] +}; + +export const DATA_TYPE_COMPARATOR_GREATER_THAN = { + count: 1, + criterias: [ + { + criteriaId: '290', + name: 'Criteria_EventTimeStamp_3_Long', + createdAt: 1722497422151, + updatedAt: 1722500235276, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + field: 'eventTimeStamp', + comparatorType: 'GreaterThan', + value: '50', + fieldType: 'long' + }, + { + dataType: 'user', + field: 'savings', + comparatorType: 'GreaterThan', + value: '55', + fieldType: 'double' + } + ] + } + } + ] + } + ] + } + } + ] +}; + +export const DATA_TYPE_COMPARATOR_GREATER_THAN_OR_EQUAL_TO = { + count: 1, + criterias: [ + { + criteriaId: '291', + name: 'Criteria_EventTimeStamp_3_Long', + createdAt: 1722497422151, + updatedAt: 1722500235276, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + field: 'eventTimeStamp', + comparatorType: 'GreaterThanOrEqualTo', + value: '20', + fieldType: 'long' + }, + { + dataType: 'user', + field: 'savings', + comparatorType: 'GreaterThanOrEqualTo', + value: '20', + fieldType: 'double' + } + ] + } + } + ] + } + ] + } + } + ] +}; + +export const DATA_TYPE_COMPARATOR_IS_SET = { + count: 1, + criterias: [ + { + criteriaId: '285', + name: 'Criteria_EventTimeStamp_3_Long', + createdAt: 1722497422151, + updatedAt: 1722500235276, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + field: 'eventTimeStamp', + comparatorType: 'IsSet', + value: '', + fieldType: 'long' + }, + { + dataType: 'user', + field: 'savings', + comparatorType: 'IsSet', + value: '', + fieldType: 'double' + }, + { + dataType: 'user', + field: 'saved_cars', + comparatorType: 'IsSet', + value: '', + fieldType: 'double' + }, + { + dataType: 'user', + field: 'country', + comparatorType: 'IsSet', + value: '', + fieldType: 'double' + } + ] + } + } + ] + } + ] + } + } + ] +}; diff --git a/src/anonymousUserTracking/tests/dataTypeComparatorSearchQueryCriteria.test.ts b/src/anonymousUserTracking/tests/dataTypeComparatorSearchQueryCriteria.test.ts new file mode 100644 index 00000000..e75cf9f4 --- /dev/null +++ b/src/anonymousUserTracking/tests/dataTypeComparatorSearchQueryCriteria.test.ts @@ -0,0 +1,439 @@ +import { SHARED_PREFS_EVENT_LIST_KEY } from '../../constants'; +import CriteriaCompletionChecker from '../criteriaCompletionChecker'; +import { + DATA_TYPE_COMPARATOR_DOES_NOT_EQUAL, + DATA_TYPE_COMPARATOR_EQUALS, + DATA_TYPE_COMPARATOR_GREATER_THAN, + DATA_TYPE_COMPARATOR_GREATER_THAN_OR_EQUAL_TO, + DATA_TYPE_COMPARATOR_IS_SET, + DATA_TYPE_COMPARATOR_LESS_THAN, + DATA_TYPE_COMPARATOR_LESS_THAN_OR_EQUAL_TO +} from './constants'; + +const localStorageMock = { + getItem: jest.fn(), + setItem: jest.fn(), + removeItem: jest.fn() +}; + +describe('dataTypeComparatorSearchQueryCriteria', () => { + beforeEach(() => { + (global as any).localStorage = localStorageMock; + }); + + it('should return criteriaId 285 (Comparator test For Equal)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + savings: 19.99, + likes_boba: true, + country: 'Chaina', + eventTimeStamp: 3 + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(DATA_TYPE_COMPARATOR_EQUALS) + ); + expect(result).toEqual('285'); + }); + + it('should return null (Comparator test For Equal - No Match)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + savings: 10.99, + eventTimeStamp: 30, + likes_boba: false, + country: 'Taiwan' + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(DATA_TYPE_COMPARATOR_EQUALS) + ); + expect(result).toEqual(null); + }); + + it('should return criteriaId 285 (Comparator test For DoesNotEqual)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + savings: 11.2, + eventTimeStamp: 30, + likes_boba: false + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(DATA_TYPE_COMPARATOR_DOES_NOT_EQUAL) + ); + expect(result).toEqual('285'); + }); + + it('should return null (Comparator test For DoesNotEqual - No Match)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + savings: 10.99, + eventTimeStamp: 30, + likes_boba: true + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(DATA_TYPE_COMPARATOR_DOES_NOT_EQUAL) + ); + expect(result).toEqual(null); + }); + + it('should return criteriaId 289 (Comparator test For LessThan)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + savings: 10, + eventTimeStamp: 14 + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(DATA_TYPE_COMPARATOR_LESS_THAN) + ); + expect(result).toEqual('289'); + }); + + it('should return null (Comparator test For LessThan - No Match)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + savings: 10, + eventTimeStamp: 18 + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(DATA_TYPE_COMPARATOR_LESS_THAN) + ); + expect(result).toEqual(null); + }); + + it('should return criteriaId 290 (Comparator test For LessThanOrEqualTo)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + savings: 17, + eventTimeStamp: 14 + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(DATA_TYPE_COMPARATOR_LESS_THAN_OR_EQUAL_TO) + ); + expect(result).toEqual('290'); + }); + + it('should return null (Comparator test For LessThanOrEqualTo - No Match)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + savings: 18, + eventTimeStamp: 12 + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(DATA_TYPE_COMPARATOR_LESS_THAN_OR_EQUAL_TO) + ); + expect(result).toEqual(null); + }); + + it('should return criteriaId 290 (Comparator test For GreaterThan)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + savings: 56, + eventTimeStamp: 51 + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(DATA_TYPE_COMPARATOR_GREATER_THAN) + ); + expect(result).toEqual('290'); + }); + + it('should return null (Comparator test For GreaterThan - No Match)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + savings: 5, + eventTimeStamp: 3 + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(DATA_TYPE_COMPARATOR_GREATER_THAN) + ); + expect(result).toEqual(null); + }); + + it('should return criteriaId 291 (Comparator test For GreaterThanOrEqualTo)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + savings: 20, + eventTimeStamp: 30 + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(DATA_TYPE_COMPARATOR_GREATER_THAN_OR_EQUAL_TO) + ); + expect(result).toEqual('291'); + }); + + it('should return null (Comparator test For GreaterThanOrEqualTo - No Match)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + savings: 18, + eventTimeStamp: 16 + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(DATA_TYPE_COMPARATOR_GREATER_THAN_OR_EQUAL_TO) + ); + expect(result).toEqual(null); + }); + + it('should return criteriaId 285 (Comparator test For IsSet)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + savings: 10, + eventTimeStamp: 20, + saved_cars: '10', + country: 'Taiwan' + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(DATA_TYPE_COMPARATOR_IS_SET) + ); + expect(result).toEqual('285'); + }); + + it('should return criteriaId 285 (Comparator test For IsSet - No Match)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + savings: '', + eventTimeStamp: '', + saved_cars: 'd', + country: '' + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(DATA_TYPE_COMPARATOR_IS_SET) + ); + expect(result).toEqual(null); + }); +}); From 93e1b0fbc6483b28de9161d8d09a4391f90c7ce5 Mon Sep 17 00:00:00 2001 From: darshan-iterable Date: Wed, 4 Sep 2024 23:04:27 +0530 Subject: [PATCH 70/88] fully supports comparison for data in Array data with all comparator types (#444) --- .../criteriaCompletionChecker.ts | 27 +- .../tests/compareArrayDataTypes.test.ts | 615 ++++++++++++++++++ src/anonymousUserTracking/tests/constants.ts | 442 +++++++++++++ 3 files changed, 1083 insertions(+), 1 deletion(-) create mode 100644 src/anonymousUserTracking/tests/compareArrayDataTypes.test.ts diff --git a/src/anonymousUserTracking/criteriaCompletionChecker.ts b/src/anonymousUserTracking/criteriaCompletionChecker.ts index a67aa164..a0165a0c 100644 --- a/src/anonymousUserTracking/criteriaCompletionChecker.ts +++ b/src/anonymousUserTracking/criteriaCompletionChecker.ts @@ -470,6 +470,12 @@ class CriteriaCompletionChecker { } private compareValueEquality(sourceTo: any, stringValue: string): boolean { + if (Array.isArray(sourceTo)) { + return sourceTo.some((source) => + this.compareValueEquality(source, stringValue) + ); + } + if ( (typeof sourceTo === 'number' || typeof sourceTo === 'boolean') && stringValue !== '' @@ -493,7 +499,13 @@ class CriteriaCompletionChecker { compareOperator: string ): boolean { // eslint-disable-next-line no-restricted-globals - if (!isNaN(parseFloat(stringValue))) { + if (Array.isArray(sourceTo)) { + return sourceTo.some((source) => + this.compareNumericValues(source, stringValue, compareOperator) + ); + } + + if (!Number.isNaN(parseFloat(stringValue))) { const sourceNumber = parseFloat(sourceTo); const numericValue = parseFloat(stringValue); switch (compareOperator) { @@ -513,6 +525,11 @@ class CriteriaCompletionChecker { } private compareStringContains(sourceTo: any, stringValue: string): boolean { + if (Array.isArray(sourceTo)) { + return sourceTo.some((source) => + this.compareStringContains(source, stringValue) + ); + } return ( (typeof sourceTo === 'string' || typeof sourceTo === 'object') && sourceTo.includes(stringValue) @@ -520,10 +537,18 @@ class CriteriaCompletionChecker { } private compareStringStartsWith(sourceTo: any, stringValue: string): boolean { + if (Array.isArray(sourceTo)) { + return sourceTo.some((source) => + this.compareStringStartsWith(source, stringValue) + ); + } return typeof sourceTo === 'string' && sourceTo.startsWith(stringValue); } private compareWithRegex(sourceTo: string, pattern: string): boolean { + if (Array.isArray(sourceTo)) { + return sourceTo.some((source) => this.compareWithRegex(source, pattern)); + } try { const regexPattern = new RegExp(pattern); return regexPattern.test(sourceTo); diff --git a/src/anonymousUserTracking/tests/compareArrayDataTypes.test.ts b/src/anonymousUserTracking/tests/compareArrayDataTypes.test.ts new file mode 100644 index 00000000..0ca307cc --- /dev/null +++ b/src/anonymousUserTracking/tests/compareArrayDataTypes.test.ts @@ -0,0 +1,615 @@ +import { SHARED_PREFS_EVENT_LIST_KEY } from '../../constants'; +import CriteriaCompletionChecker from '../criteriaCompletionChecker'; +import { + ARRAY_CONTAINS_CRITERIA, + ARRAY_DOES_NOT_EQUAL_CRITERIA, + ARRAY_EQUAL_CRITERIA, + ARRAY_GREATER_THAN_CRITERIA, + ARRAY_GREATER_THAN_EQUAL_TO_CRITERIA, + ARRAY_LESS_THAN_CRITERIA, + ARRAY_LESS_THAN_EQUAL_TO_CRITERIA, + ARRAY_MATCHREGEX_CRITERIA, + ARRAY_STARTSWITH_CRITERIA +} from './constants'; + +const localStorageMock = { + getItem: jest.fn(), + setItem: jest.fn(), + removeItem: jest.fn() +}; + +describe('compareArrayDataTypes', () => { + beforeEach(() => { + (global as any).localStorage = localStorageMock; + }); + + // MARK: Equal + it('should return criteriaId 285 (compare array Equal)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + milestoneYears: [1996, 1997, 2002, 2020, 2024], + score: [10.5, 11.5, 12.5, 13.5, 14.5], + timestamp: [ + 1722497422151, 1722500235276, 1722500215276, 1722500225276, + 1722500245276 + ] + }, + eventType: 'user' + }, + { + eventName: 'button-clicked', + dataFields: { + animal: ['cat', 'dog', 'giraffe'] + }, + eventType: 'customEvent' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(ARRAY_EQUAL_CRITERIA) + ); + expect(result).toEqual('285'); + }); + + it('should return criteriaId 285 (compare array Equal - No match)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + milestoneYears: [1996, 1998, 2002, 2020, 2024], + score: [12.5, 13.5, 14.5], + timestamp: [ + 1722497422151, 1722500235276, 1722500225276, 1722500245276 + ] + }, + eventType: 'user' + }, + { + eventName: 'button-clicked', + dataFields: { + animal: ['cat', 'dog'] + }, + eventType: 'customEvent' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(ARRAY_EQUAL_CRITERIA) + ); + expect(result).toEqual(null); + }); + + // MARK: DoesNotEqual + it('should return criteriaId 285 (compare array DoesNotEqual)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + milestoneYears: [1996, 1998, 2002, 2020, 2024], + score: [12.5, 13.5, 14.5], + timestamp: [ + 1722497422151, 1722500235276, 1722500225276, 1722500245276 + ] + }, + eventType: 'user' + }, + { + eventName: 'button-clicked', + dataFields: { + animal: ['cat', 'dog'] + }, + eventType: 'customEvent' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(ARRAY_DOES_NOT_EQUAL_CRITERIA) + ); + expect(result).toEqual('285'); + }); + + it('should return criteriaId 285 (compare array DoesNotEqual - No match)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + milestoneYears: [1996, 1997, 2002, 2020, 2024], + score: [10.5, 11.5, 12.5, 13.5, 14.5], + timestamp: [ + 1722497422151, 1722500235276, 1722500215276, 1722500225276, + 1722500245276 + ] + }, + eventType: 'user' + }, + { + eventName: 'button-clicked', + dataFields: { + animal: ['cat', 'dog', 'giraffe'] + }, + eventType: 'customEvent' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(ARRAY_DOES_NOT_EQUAL_CRITERIA) + ); + expect(result).toEqual(null); + }); + + // MARK: GreaterThan + it('should return criteriaId 285 (compare array GreaterThan)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + milestoneYears: [1996, 1998, 2002, 2020, 2024] + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(ARRAY_GREATER_THAN_CRITERIA) + ); + expect(result).toEqual('285'); + }); + + it('should return criteriaId 285 (compare array GreaterThan - No match)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + milestoneYears: [1990, 1992, 1994, 1997] + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(ARRAY_GREATER_THAN_CRITERIA) + ); + expect(result).toEqual(null); + }); + + // MARK: LessThan + it('should return criteriaId 285 (compare array LessThan)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + milestoneYears: [1990, 1992, 1994, 1996, 1998] + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(ARRAY_LESS_THAN_CRITERIA) + ); + expect(result).toEqual('285'); + }); + + it('should return criteriaId 285 (compare array LessThan - No match)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + milestoneYears: [1997, 1999, 2002, 2004] + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(ARRAY_LESS_THAN_CRITERIA) + ); + expect(result).toEqual(null); + }); + + // MARK: GreaterThanOrEqualTo + it('should return criteriaId 285 (compare array GreaterThanOrEqualTo)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + milestoneYears: [1997, 1998, 2002, 2020, 2024] + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(ARRAY_GREATER_THAN_EQUAL_TO_CRITERIA) + ); + expect(result).toEqual('285'); + }); + + it('should return criteriaId 285 (compare array GreaterThanOrEqualTo - No match)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + milestoneYears: [1990, 1992, 1994, 1996] + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(ARRAY_GREATER_THAN_EQUAL_TO_CRITERIA) + ); + expect(result).toEqual(null); + }); + + // MARK: LessThanOrEqualTo + it('should return criteriaId 285 (compare array LessThanOrEqualTo)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + milestoneYears: [1990, 1992, 1994, 1996, 1998] + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(ARRAY_LESS_THAN_EQUAL_TO_CRITERIA) + ); + expect(result).toEqual('285'); + }); + + it('should return criteriaId 285 (compare array LessThanOrEqualTo - No match)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + milestoneYears: [1998, 1999, 2002, 2004] + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(ARRAY_LESS_THAN_EQUAL_TO_CRITERIA) + ); + expect(result).toEqual(null); + }); + + // MARK: Contains + it('should return criteriaId 285 (compare array Contains)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + addresses: [ + 'New York, US', + 'San Francisco, US', + 'San Diego, US', + 'Los Angeles, US', + 'Tokyo, JP', + 'Berlin, DE', + 'London, GB' + ] + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(ARRAY_CONTAINS_CRITERIA) + ); + expect(result).toEqual('285'); + }); + + it('should return criteriaId 285 (compare array Contains - No match)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + addresses: ['Tokyo, JP', 'Berlin, DE', 'London, GB'] + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(ARRAY_CONTAINS_CRITERIA) + ); + expect(result).toEqual(null); + }); + + // MARK: StartsWith + it('should return criteriaId 285 (compare array StartsWith)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + addresses: [ + 'US, New York', + 'US, San Francisco', + 'US, San Diego', + 'US, Los Angeles', + 'JP, Tokyo', + 'DE, Berlin', + 'GB, London' + ] + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(ARRAY_STARTSWITH_CRITERIA) + ); + expect(result).toEqual('285'); + }); + + it('should return criteriaId 285 (compare array StartsWith - No match)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + addresses: ['JP, Tokyo', 'DE, Berlin', 'GB, London'] + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(ARRAY_STARTSWITH_CRITERIA) + ); + expect(result).toEqual(null); + }); + + // MARK: MatchesRegex + it('should return criteriaId 285 (compare array MatchesRegex)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + addresses: [ + 'US, New York', + 'US, San Francisco', + 'US, San Diego', + 'US, Los Angeles', + 'JP, Tokyo', + 'DE, Berlin', + 'GB, London' + ] + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + + const result = checker.getMatchedCriteria( + JSON.stringify(ARRAY_MATCHREGEX_CRITERIA) + ); + expect(result).toEqual('285'); + }); + + it('should return criteriaId 285 (compare array MatchesRegex - No match)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + addresses: [ + 'US, New York', + 'US, San Francisco', + 'US, San Diego', + 'US, Los Angeles' + ] + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + + const result = checker.getMatchedCriteria( + JSON.stringify(ARRAY_MATCHREGEX_CRITERIA) + ); + expect(result).toEqual(null); + }); +}); diff --git a/src/anonymousUserTracking/tests/constants.ts b/src/anonymousUserTracking/tests/constants.ts index 07907f65..27d6b8e7 100644 --- a/src/anonymousUserTracking/tests/constants.ts +++ b/src/anonymousUserTracking/tests/constants.ts @@ -1,3 +1,5 @@ +// CRITERIA TEST CONSTANTS + export const DATA_TYPE_COMPARATOR_EQUALS = { count: 1, criterias: [ @@ -340,3 +342,443 @@ export const DATA_TYPE_COMPARATOR_IS_SET = { } ] }; + +export const ARRAY_EQUAL_CRITERIA = { + count: 1, + criterias: [ + { + criteriaId: '285', + name: 'Criteria_Array_Equal', + createdAt: 1722497422151, + updatedAt: 1722500235276, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'milestoneYears', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'user', + id: 2, + value: '1997' + } + ] + } + }, + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'score', + fieldType: 'double', + comparatorType: 'Equals', + dataType: 'user', + id: 2, + value: '11.5' + } + ] + } + }, + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'timestamp', + fieldType: 'long', + comparatorType: 'Equals', + dataType: 'user', + id: 2, + valueLong: 1722500215276, + value: '1722500215276' + } + ] + } + }, + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'button-clicked.animal', + fieldType: 'string', + comparatorType: 'Equals', + dataType: 'customEvent', + id: 25, + value: 'giraffe' + } + ] + } + } + ] + } + ] + } + } + ] +}; + +export const ARRAY_DOES_NOT_EQUAL_CRITERIA = { + count: 1, + criterias: [ + { + criteriaId: '285', + name: 'Criteria_Array_DoesNotEqual', + createdAt: 1722497422151, + updatedAt: 1722500235276, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'milestoneYears', + fieldType: 'string', + comparatorType: 'DoesNotEqual', + dataType: 'user', + id: 2, + value: '1997' + } + ] + } + }, + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'score', + fieldType: 'double', + comparatorType: 'DoesNotEqual', + dataType: 'user', + id: 2, + value: '11.5' + } + ] + } + }, + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'timestamp', + fieldType: 'long', + comparatorType: 'DoesNotEqual', + dataType: 'user', + id: 2, + valueLong: 1722500215276, + value: '1722500215276' + } + ] + } + }, + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'button-clicked.animal', + fieldType: 'string', + comparatorType: 'DoesNotEqual', + dataType: 'customEvent', + id: 25, + value: 'giraffe' + } + ] + } + } + ] + } + ] + } + } + ] +}; + +export const ARRAY_GREATER_THAN_CRITERIA = { + count: 1, + criterias: [ + { + criteriaId: '285', + name: 'Criteria_EventTimeStamp_3_Long', + createdAt: 1722497422151, + updatedAt: 1722500235276, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'milestoneYears', + fieldType: 'string', + comparatorType: 'GreaterThan', + dataType: 'user', + id: 2, + value: '1997' + } + ] + } + } + ] + } + ] + } + } + ] +}; + +export const ARRAY_LESS_THAN_CRITERIA = { + count: 1, + criterias: [ + { + criteriaId: '285', + name: 'Criteria_EventTimeStamp_3_Long', + createdAt: 1722497422151, + updatedAt: 1722500235276, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'milestoneYears', + fieldType: 'string', + comparatorType: 'LessThan', + dataType: 'user', + id: 2, + value: '1997' + } + ] + } + } + ] + } + ] + } + } + ] +}; + +export const ARRAY_GREATER_THAN_EQUAL_TO_CRITERIA = { + count: 1, + criterias: [ + { + criteriaId: '285', + name: 'Criteria_EventTimeStamp_3_Long', + createdAt: 1722497422151, + updatedAt: 1722500235276, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'milestoneYears', + fieldType: 'string', + comparatorType: 'GreaterThanOrEqualTo', + dataType: 'user', + id: 2, + value: '1997' + } + ] + } + } + ] + } + ] + } + } + ] +}; + +export const ARRAY_LESS_THAN_EQUAL_TO_CRITERIA = { + count: 1, + criterias: [ + { + criteriaId: '285', + name: 'Criteria_EventTimeStamp_3_Long', + createdAt: 1722497422151, + updatedAt: 1722500235276, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'milestoneYears', + fieldType: 'string', + comparatorType: 'LessThanOrEqualTo', + dataType: 'user', + id: 2, + value: '1997' + } + ] + } + } + ] + } + ] + } + } + ] +}; + +export const ARRAY_CONTAINS_CRITERIA = { + count: 1, + criterias: [ + { + criteriaId: '285', + name: 'Criteria_EventTimeStamp_3_Long', + createdAt: 1722497422151, + updatedAt: 1722500235276, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'addresses', + fieldType: 'string', + comparatorType: 'Contains', + dataType: 'user', + id: 2, + value: 'US' + } + ] + } + } + ] + } + ] + } + } + ] +}; + +export const ARRAY_STARTSWITH_CRITERIA = { + count: 1, + criterias: [ + { + criteriaId: '285', + name: 'Criteria_EventTimeStamp_3_Long', + createdAt: 1722497422151, + updatedAt: 1722500235276, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'addresses', + fieldType: 'string', + comparatorType: 'StartsWith', + dataType: 'user', + id: 2, + value: 'US' + } + ] + } + } + ] + } + ] + } + } + ] +}; + +export const ARRAY_MATCHREGEX_CRITERIA = { + count: 1, + criterias: [ + { + criteriaId: '285', + name: 'Criteria_EventTimeStamp_3_Long', + createdAt: 1722497422151, + updatedAt: 1722500235276, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + field: 'addresses', + fieldType: 'string', + comparatorType: 'MatchesRegex', + dataType: 'user', + id: 2, + value: '^(JP|DE|GB)' + } + ] + } + } + ] + } + ] + } + } + ] +}; From 16baf6c84b76987792fc7ac002adc2734d34ee6e Mon Sep 17 00:00:00 2001 From: Hani Vora <150109181+hani-iterable@users.noreply.github.com> Date: Wed, 4 Sep 2024 23:37:21 +0530 Subject: [PATCH 71/88] MOB 9328 add JWT in response (#441) * added line for pass JWT to response * revert back code for server side revert back the code for generate JWT server side as per review comment * revert eslint rule --- react-example/package.json | 6 ++-- react-example/server/generate.py | 57 -------------------------------- react-example/server/server.js | 33 ------------------ react-example/src/index.tsx | 2 +- react-example/yarn.lock | 14 ++------ 5 files changed, 6 insertions(+), 106 deletions(-) delete mode 100644 react-example/server/generate.py delete mode 100644 react-example/server/server.js diff --git a/react-example/package.json b/react-example/package.json index 224015b8..0a41d81a 100644 --- a/react-example/package.json +++ b/react-example/package.json @@ -20,7 +20,7 @@ ], "scripts": { "build": "tsc && webpack", - "start": "concurrently \"tsc -w --pretty\" \"webpack-dev-server\" -n 'tsc,webpack' -k \"node server/server.js\"", + "start": "concurrently \"tsc -w --pretty\" \"webpack-dev-server\" -n 'tsc,webpack' -k", "test": "jest --config jest.config.js", "format": "prettier --write \"src/**/*.{ts,tsx}\" \"src/**/*.js\"", "typecheck": "tsc --noEmit true --emitDeclarationOnly false", @@ -70,8 +70,6 @@ "react-dom": "^18.2.0", "react-router-dom": "^6.2.1", "styled-components": "^5.3.3", - "uuid": "^9.0.0", - "cors": "^2.8.5", - "express": "^4.19.2" + "uuid": "^9.0.0" } } diff --git a/react-example/server/generate.py b/react-example/server/generate.py deleted file mode 100644 index a3acfa83..00000000 --- a/react-example/server/generate.py +++ /dev/null @@ -1,57 +0,0 @@ -import base64 -import json -import hmac -import hashlib -import sys - -def generate_jwt(api_key_shared_secret, user_id, email, iat, exp): - encoding = "utf-8" - secret = api_key_shared_secret.encode(encoding) - - jwt_header = json.dumps( - {"alg": "HS256", "typ": "JWT"}, separators=(",", ":") - ).encode(encoding) - - payload = { - "iat": iat, - "exp": exp - } - - if user_id is not '': - payload["userId"] = user_id - elif email is not '': - payload["email"] = email - - jwt_payload = json.dumps(payload, separators=(",", ":")).encode(encoding) - - encoded_header_bytes = base64.urlsafe_b64encode(jwt_header).replace(b"=", b"") - encoded_payload_bytes = base64.urlsafe_b64encode(jwt_payload).replace(b"=", b"") - - jwt_signature = hmac.digest( - key=secret, - msg=b".".join([encoded_header_bytes, encoded_payload_bytes]), - digest=hashlib.sha256 - ) - - encoded_signature_bytes = base64.urlsafe_b64encode(jwt_signature).replace(b"=", b"") - - jwt_returned = ( - f"{str(encoded_header_bytes, encoding)}" + - f".{str(encoded_payload_bytes, encoding)}" + - f".{str(encoded_signature_bytes, encoding)}" - ) - - return jwt_returned - -if __name__ == "__main__": - if len(sys.argv) != 6: - print("Usage: python generate_jwt.py ") - sys.exit(1) - - api_key_shared_secret = sys.argv[1] - user_id = sys.argv[2] - email = sys.argv[3] - iat = int(sys.argv[4]) - exp = int(sys.argv[5]) - - jwt_token = generate_jwt(api_key_shared_secret, user_id, email, iat, exp) diff --git a/react-example/server/server.js b/react-example/server/server.js deleted file mode 100644 index c4d245b6..00000000 --- a/react-example/server/server.js +++ /dev/null @@ -1,33 +0,0 @@ -/* eslint-disable import/no-extraneous-dependencies */ -/* eslint-disable consistent-return */ -/* eslint-disable camelcase */ -/* eslint-disable @typescript-eslint/no-var-requires */ -const express = require('express'); -const { exec } = require('child_process'); -const cors = require('cors'); - -const app = express(); -const port = 3000; - -app.use(cors({ origin: '*' })); -app.use(express.json()); - -app.post('/generate', (req, res) => { - const { jwt_secret, userId, email, exp_minutes } = req.body; - const emailIdentifier = email !== undefined ? email : ''; - const userIdentifier = userId !== undefined ? userId : ''; - const currentTimeInSeconds = Math.floor(new Date().getTime() / 1000); - const expirationTimeInSeconds = currentTimeInSeconds + exp_minutes * 60; - - const command = `python3 server/generate.py "${jwt_secret}" "${userIdentifier}" "${emailIdentifier}" "${currentTimeInSeconds}" "${expirationTimeInSeconds}"`; - exec(command, (error, stdout, stderr) => { - if (error) { - return res.status(500).json({ message: `Error: ${stderr}` }); - } - res.json({ token: stdout }); - }); -}); - -app.listen(port, () => { - console.log(`Server listening at http://localhost:${port}`); -}); diff --git a/react-example/src/index.tsx b/react-example/src/index.tsx index d70adf59..5cccc81d 100644 --- a/react-example/src/index.tsx +++ b/react-example/src/index.tsx @@ -52,7 +52,7 @@ const HomeLink = styled(Link)` generateJWT: ({ email, userID }) => axios .post( - process.env.JWT_GENERATOR || 'http://localhost:3000/generate', + process.env.JWT_GENERATOR || 'http://localhost:5000/generate', { exp_minutes: 2, email, diff --git a/react-example/yarn.lock b/react-example/yarn.lock index d93ebb5a..6fc967dd 100644 --- a/react-example/yarn.lock +++ b/react-example/yarn.lock @@ -2102,14 +2102,6 @@ core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== -cors@^2.8.5: - version "2.8.5" - resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" - integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== - dependencies: - object-assign "^4" - vary "^1" - cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -2838,7 +2830,7 @@ expect@^27.5.1: jest-matcher-utils "^27.5.1" jest-message-util "^27.5.1" -express@^4.17.3, express@^4.19.2: +express@^4.17.3: version "4.19.2" resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465" integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q== @@ -4597,7 +4589,7 @@ nwsapi@^2.2.0: resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.10.tgz#0b77a68e21a0b483db70b11fad055906e867cda8" integrity sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ== -object-assign@^4, object-assign@^4.1.1: +object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== @@ -6076,7 +6068,7 @@ v8-to-istanbul@^8.1.0: convert-source-map "^1.6.0" source-map "^0.7.3" -vary@^1, vary@~1.1.2: +vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== From 4013132af7ffee18418802ef9ece6bede692529c Mon Sep 17 00:00:00 2001 From: darshan-iterable Date: Thu, 5 Sep 2024 23:57:07 +0530 Subject: [PATCH 72/88] updated nested field logic (#445) * updated nested field logic * Resolved typo * resolved conflicts --- .../criteriaCompletionChecker.ts | 19 +-- src/anonymousUserTracking/tests/constants.ts | 51 ++++++++ .../tests/nestedTesting.test.ts | 113 +----------------- 3 files changed, 63 insertions(+), 120 deletions(-) diff --git a/src/anonymousUserTracking/criteriaCompletionChecker.ts b/src/anonymousUserTracking/criteriaCompletionChecker.ts index a0165a0c..2a4402d5 100644 --- a/src/anonymousUserTracking/criteriaCompletionChecker.ts +++ b/src/anonymousUserTracking/criteriaCompletionChecker.ts @@ -351,21 +351,14 @@ class CriteriaCompletionChecker { if (field.includes('.')) { const fields = field.split('.'); - if (Array.isArray(eventData[fields[0]])) { - return eventData[fields[0]]?.every((item: any) => { + const firstElement = eventData?.[fields[0]]; + if (Array.isArray(firstElement)) { + return firstElement?.some((item: any) => { const data = { - [fields[0]]: item, - eventType: query?.eventType + ...eventData, + [fields[0]]: item }; - const valueFromObj = this.getFieldValue(data, field); - if (valueFromObj) { - return this.evaluateComparison( - query.comparatorType, - valueFromObj, - query.value ? query.value : '' - ); - } - return false; + return this.evaluateFieldLogic(searchQueries, data); }); } diff --git a/src/anonymousUserTracking/tests/constants.ts b/src/anonymousUserTracking/tests/constants.ts index 27d6b8e7..0e7698e7 100644 --- a/src/anonymousUserTracking/tests/constants.ts +++ b/src/anonymousUserTracking/tests/constants.ts @@ -782,3 +782,54 @@ export const ARRAY_MATCHREGEX_CRITERIA = { } ] }; + +export const NESTED_CRITERIA = { + count: 1, + criterias: [ + { + criteriaId: '168', + name: 'nested testing', + createdAt: 1721251169153, + updatedAt: 1723488175352, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + field: 'furniture', + comparatorType: 'IsSet', + value: '', + fieldType: 'nested' + }, + { + dataType: 'user', + field: 'furniture.furnitureType', + comparatorType: 'Equals', + value: 'Sofa', + fieldType: 'string' + }, + { + dataType: 'user', + field: 'furniture.furnitureColor', + comparatorType: 'Equals', + value: 'White', + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] +}; diff --git a/src/anonymousUserTracking/tests/nestedTesting.test.ts b/src/anonymousUserTracking/tests/nestedTesting.test.ts index 703c4c91..c1a85db9 100644 --- a/src/anonymousUserTracking/tests/nestedTesting.test.ts +++ b/src/anonymousUserTracking/tests/nestedTesting.test.ts @@ -1,5 +1,6 @@ import { SHARED_PREFS_EVENT_LIST_KEY } from '../../constants'; import CriteriaCompletionChecker from '../criteriaCompletionChecker'; +import { NESTED_CRITERIA } from './constants'; const localStorageMock = { getItem: jest.fn(), @@ -27,7 +28,7 @@ describe('nestedTesting', () => { widthInches: 60 }, { - furnitureType: 'Sofa', + furnitureType: 'table', furnitureColor: 'Gray', lengthInches: 20, widthInches: 30 @@ -48,58 +49,7 @@ describe('nestedTesting', () => { const checker = new CriteriaCompletionChecker( localStoredEventList === null ? '' : localStoredEventList ); - const result = checker.getMatchedCriteria( - JSON.stringify({ - count: 1, - criterias: [ - { - criteriaId: '168', - name: 'nested testing', - createdAt: 1721251169153, - updatedAt: 1723488175352, - searchQuery: { - combinator: 'And', - searchQueries: [ - { - combinator: 'And', - searchQueries: [ - { - dataType: 'user', - searchCombo: { - combinator: 'And', - searchQueries: [ - { - dataType: 'user', - field: 'furniture', - comparatorType: 'IsSet', - value: '', - fieldType: 'nested' - }, - { - dataType: 'user', - field: 'furniture.furnitureColor', - comparatorType: 'IsSet', - value: '', - fieldType: 'string' - }, - { - dataType: 'user', - field: 'furniture.furnitureType', - comparatorType: 'Equals', - value: 'Sofa', - fieldType: 'string' - } - ] - } - } - ] - } - ] - } - } - ] - }) - ); + const result = checker.getMatchedCriteria(JSON.stringify(NESTED_CRITERIA)); expect(result).toEqual('168'); }); @@ -113,13 +63,13 @@ describe('nestedTesting', () => { furniture: [ { furnitureType: 'Sofa', - furnitureColor: 'White', + furnitureColor: 'Gray', lengthInches: 40, widthInches: 60 }, { furnitureType: 'table', - furnitureColor: 'Gray', + furnitureColor: 'White', lengthInches: 20, widthInches: 30 } @@ -139,58 +89,7 @@ describe('nestedTesting', () => { const checker = new CriteriaCompletionChecker( localStoredEventList === null ? '' : localStoredEventList ); - const result = checker.getMatchedCriteria( - JSON.stringify({ - count: 1, - criterias: [ - { - criteriaId: '168', - name: 'nested testing', - createdAt: 1721251169153, - updatedAt: 1723488175352, - searchQuery: { - combinator: 'And', - searchQueries: [ - { - combinator: 'And', - searchQueries: [ - { - dataType: 'user', - searchCombo: { - combinator: 'And', - searchQueries: [ - { - dataType: 'user', - field: 'furniture', - comparatorType: 'IsSet', - value: '', - fieldType: 'nested' - }, - { - dataType: 'user', - field: 'furniture.furnitureColor', - comparatorType: 'IsSet', - value: '', - fieldType: 'string' - }, - { - dataType: 'user', - field: 'furniture.furnitureType', - comparatorType: 'Equals', - value: 'Sofa', - fieldType: 'string' - } - ] - } - } - ] - } - ] - } - } - ] - }) - ); + const result = checker.getMatchedCriteria(JSON.stringify(NESTED_CRITERIA)); expect(result).toEqual(null); }); }); From 25fcde113d4dc33bc5d3507081f24ada20184382 Mon Sep 17 00:00:00 2001 From: darshan-iterable Date: Fri, 6 Sep 2024 19:13:32 +0530 Subject: [PATCH 73/88] MOB-9145: support isOneOf and isNotOneOf comparator (#446) * fully supports comparison for data in Array data with all comparator types * MOB-9145: support isOneOf and isNotOneOf comparator * fix: trailing spaces issue --- .../criteriaCompletionChecker.ts | 27 ++-- .../tests/compareArrayDataTypes.test.ts | 145 ++++++++++++++++-- src/anonymousUserTracking/tests/constants.ts | 84 ++++++++++ 3 files changed, 237 insertions(+), 19 deletions(-) diff --git a/src/anonymousUserTracking/criteriaCompletionChecker.ts b/src/anonymousUserTracking/criteriaCompletionChecker.ts index 2a4402d5..5fad1d94 100644 --- a/src/anonymousUserTracking/criteriaCompletionChecker.ts +++ b/src/anonymousUserTracking/criteriaCompletionChecker.ts @@ -367,14 +367,14 @@ class CriteriaCompletionChecker { return this.evaluateComparison( query.comparatorType, valueFromObj, - query.value ? query.value : '' + query.value ?? query.values ?? '' ); } } else if (eventKeyItems.length) { return this.evaluateComparison( query.comparatorType, eventData[field], - query.value ? query.value : '' + query.value ?? query.values ?? '' ); } return false; @@ -420,7 +420,7 @@ class CriteriaCompletionChecker { return this.evaluateComparison( query.comparatorType, item[field], - query.value ? query.value : '' + query.value ?? query.values ?? '' ); } return false; @@ -430,7 +430,7 @@ class CriteriaCompletionChecker { private evaluateComparison( comparatorType: string, matchObj: any, - valueToCompare: string + valueToCompare: string | string[] ): boolean { if (!valueToCompare && comparatorType !== 'IsSet') { return false; @@ -448,27 +448,36 @@ class CriteriaCompletionChecker { case 'LessThanOrEqualTo': return this.compareNumericValues( matchObj, - valueToCompare, + valueToCompare as string, comparatorType ); case 'Contains': - return this.compareStringContains(matchObj, valueToCompare); + return this.compareStringContains(matchObj, valueToCompare as string); case 'StartsWith': - return this.compareStringStartsWith(matchObj, valueToCompare); + return this.compareStringStartsWith(matchObj, valueToCompare as string); case 'MatchesRegex': - return this.compareWithRegex(matchObj, valueToCompare); + return this.compareWithRegex(matchObj, valueToCompare as string); default: return false; } } - private compareValueEquality(sourceTo: any, stringValue: string): boolean { + private compareValueEquality( + sourceTo: any, + stringValue: string | string[] + ): boolean { if (Array.isArray(sourceTo)) { return sourceTo.some((source) => this.compareValueEquality(source, stringValue) ); } + if (Array.isArray(stringValue)) { + return stringValue.some((value) => + this.compareValueEquality(sourceTo, value) + ); + } + if ( (typeof sourceTo === 'number' || typeof sourceTo === 'boolean') && stringValue !== '' diff --git a/src/anonymousUserTracking/tests/compareArrayDataTypes.test.ts b/src/anonymousUserTracking/tests/compareArrayDataTypes.test.ts index 0ca307cc..a0fe2b31 100644 --- a/src/anonymousUserTracking/tests/compareArrayDataTypes.test.ts +++ b/src/anonymousUserTracking/tests/compareArrayDataTypes.test.ts @@ -1,6 +1,7 @@ import { SHARED_PREFS_EVENT_LIST_KEY } from '../../constants'; import CriteriaCompletionChecker from '../criteriaCompletionChecker'; import { + IS_NOT_ONE_OF_CRITERIA, ARRAY_CONTAINS_CRITERIA, ARRAY_DOES_NOT_EQUAL_CRITERIA, ARRAY_EQUAL_CRITERIA, @@ -9,7 +10,8 @@ import { ARRAY_LESS_THAN_CRITERIA, ARRAY_LESS_THAN_EQUAL_TO_CRITERIA, ARRAY_MATCHREGEX_CRITERIA, - ARRAY_STARTSWITH_CRITERIA + ARRAY_STARTSWITH_CRITERIA, + IS_ONE_OF_CRITERIA } from './constants'; const localStorageMock = { @@ -64,7 +66,8 @@ describe('compareArrayDataTypes', () => { expect(result).toEqual('285'); }); - it('should return criteriaId 285 (compare array Equal - No match)', () => { + + it('should return criteriaId null (compare array Equal - No match)', () => { (localStorage.getItem as jest.Mock).mockImplementation((key) => { if (key === SHARED_PREFS_EVENT_LIST_KEY) { return JSON.stringify([ @@ -143,7 +146,7 @@ describe('compareArrayDataTypes', () => { expect(result).toEqual('285'); }); - it('should return criteriaId 285 (compare array DoesNotEqual - No match)', () => { + it('should return criteriaId null (compare array DoesNotEqual - No match)', () => { (localStorage.getItem as jest.Mock).mockImplementation((key) => { if (key === SHARED_PREFS_EVENT_LIST_KEY) { return JSON.stringify([ @@ -212,7 +215,7 @@ describe('compareArrayDataTypes', () => { expect(result).toEqual('285'); }); - it('should return criteriaId 285 (compare array GreaterThan - No match)', () => { + it('should return criteriaId null (compare array GreaterThan - No match)', () => { (localStorage.getItem as jest.Mock).mockImplementation((key) => { if (key === SHARED_PREFS_EVENT_LIST_KEY) { return JSON.stringify([ @@ -269,7 +272,7 @@ describe('compareArrayDataTypes', () => { expect(result).toEqual('285'); }); - it('should return criteriaId 285 (compare array LessThan - No match)', () => { + it('should return criteriaId null (compare array LessThan - No match)', () => { (localStorage.getItem as jest.Mock).mockImplementation((key) => { if (key === SHARED_PREFS_EVENT_LIST_KEY) { return JSON.stringify([ @@ -326,7 +329,7 @@ describe('compareArrayDataTypes', () => { expect(result).toEqual('285'); }); - it('should return criteriaId 285 (compare array GreaterThanOrEqualTo - No match)', () => { + it('should return criteriaId null (compare array GreaterThanOrEqualTo - No match)', () => { (localStorage.getItem as jest.Mock).mockImplementation((key) => { if (key === SHARED_PREFS_EVENT_LIST_KEY) { return JSON.stringify([ @@ -383,7 +386,7 @@ describe('compareArrayDataTypes', () => { expect(result).toEqual('285'); }); - it('should return criteriaId 285 (compare array LessThanOrEqualTo - No match)', () => { + it('should return criteriaId null (compare array LessThanOrEqualTo - No match)', () => { (localStorage.getItem as jest.Mock).mockImplementation((key) => { if (key === SHARED_PREFS_EVENT_LIST_KEY) { return JSON.stringify([ @@ -448,7 +451,7 @@ describe('compareArrayDataTypes', () => { expect(result).toEqual('285'); }); - it('should return criteriaId 285 (compare array Contains - No match)', () => { + it('should return criteriaId null (compare array Contains - No match)', () => { (localStorage.getItem as jest.Mock).mockImplementation((key) => { if (key === SHARED_PREFS_EVENT_LIST_KEY) { return JSON.stringify([ @@ -513,7 +516,7 @@ describe('compareArrayDataTypes', () => { expect(result).toEqual('285'); }); - it('should return criteriaId 285 (compare array StartsWith - No match)', () => { + it('should return criteriaId null (compare array StartsWith - No match)', () => { (localStorage.getItem as jest.Mock).mockImplementation((key) => { if (key === SHARED_PREFS_EVENT_LIST_KEY) { return JSON.stringify([ @@ -579,7 +582,7 @@ describe('compareArrayDataTypes', () => { expect(result).toEqual('285'); }); - it('should return criteriaId 285 (compare array MatchesRegex - No match)', () => { + it('should return criteriaId null (compare array MatchesRegex - No match)', () => { (localStorage.getItem as jest.Mock).mockImplementation((key) => { if (key === SHARED_PREFS_EVENT_LIST_KEY) { return JSON.stringify([ @@ -612,4 +615,126 @@ describe('compareArrayDataTypes', () => { ); expect(result).toEqual(null); }); + + // MARK: IsOneOf + it('should return criteriaId 299 (compare array IsOneOf)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + country: 'China', + addresses: ['US', 'UK', 'JP', 'DE', 'GB'] + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + + const result = checker.getMatchedCriteria( + JSON.stringify(IS_ONE_OF_CRITERIA) + ); + expect(result).toEqual('299'); + }); + + it('should return criteriaId null (compare array IsOneOf - No match)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + country: 'Korea', + addresses: ['US', 'UK'] + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + + const result = checker.getMatchedCriteria( + JSON.stringify(IS_ONE_OF_CRITERIA) + ); + expect(result).toEqual(null); + }); + + // MARK: IsNotOneOf + it('should return criteriaId 299 (compare array IsNotOneOf)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + country: 'Korea', + addresses: ['US', 'UK'] + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + + const result = checker.getMatchedCriteria( + JSON.stringify(IS_NOT_ONE_OF_CRITERIA) + ); + expect(result).toEqual('299'); + }); + + it('should return criteriaId null (compare array IsNotOneOf - No match)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + country: 'China', + addresses: ['US', 'UK', 'JP', 'DE', 'GB'] + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + + const result = checker.getMatchedCriteria( + JSON.stringify(IS_NOT_ONE_OF_CRITERIA) + ); + expect(result).toEqual(null); + }); }); diff --git a/src/anonymousUserTracking/tests/constants.ts b/src/anonymousUserTracking/tests/constants.ts index 0e7698e7..9f3ff44f 100644 --- a/src/anonymousUserTracking/tests/constants.ts +++ b/src/anonymousUserTracking/tests/constants.ts @@ -833,3 +833,87 @@ export const NESTED_CRITERIA = { } ] }; + +export const IS_ONE_OF_CRITERIA = { + count: 1, + criterias: [ + { + criteriaId: '299', + name: 'Criteria_Is_One_of', + createdAt: 1722851586508, + updatedAt: 1724404229481, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + field: 'country', + comparatorType: 'Equals', + values: ['China', 'Japan', 'Kenya'] + }, + { + dataType: 'user', + field: 'addresses', + comparatorType: 'Equals', + values: ['JP', 'DE', 'GB'] + } + ] + } + } + ] + } + ] + } + } + ] +}; + +export const IS_NOT_ONE_OF_CRITERIA = { + count: 1, + criterias: [ + { + criteriaId: '299', + name: 'Criteria_Is_Not_One_of', + createdAt: 1722851586508, + updatedAt: 1724404229481, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + field: 'country', + comparatorType: 'DoesNotEqual', + values: ['China', 'Japan', 'Kenya'] + }, + { + dataType: 'user', + field: 'addresses', + comparatorType: 'DoesNotEqual', + values: ['JP', 'DE', 'GB'] + } + ] + } + } + ] + } + ] + } + } + ] +}; From 8f6e6a7f662ca5b00bebf77eecd0376b8d4ae63f Mon Sep 17 00:00:00 2001 From: Mitch Prewitt <44011584+mprew97@users.noreply.github.com> Date: Thu, 12 Sep 2024 10:05:43 -0400 Subject: [PATCH 74/88] [MOB-9522]: Fix JWT UserID Support (#452) * [DOCS-4818] EUDC instructions update (#428) * EUDC instructions update * Formatting * Bump braces from 3.0.2 to 3.0.3 (#406) Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3. - [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3) --- updated-dependencies: - dependency-name: braces dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump ws from 7.5.9 to 7.5.10 (#409) Bumps [ws](https://github.com/websockets/ws) from 7.5.9 to 7.5.10. - [Release notes](https://github.com/websockets/ws/releases) - [Commits](https://github.com/websockets/ws/compare/7.5.9...7.5.10) --- updated-dependencies: - dependency-name: ws dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump axios from 1.6.2 to 1.7.4 (#433) Bumps [axios](https://github.com/axios/axios) from 1.6.2 to 1.7.4. - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md) - [Commits](https://github.com/axios/axios/compare/v1.6.2...v1.7.4) --- updated-dependencies: - dependency-name: axios dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump postcss from 8.3.11 to 8.4.31 (#235) * Bump postcss from 8.3.11 to 8.4.31 Bumps [postcss](https://github.com/postcss/postcss) from 8.3.11 to 8.4.31. - [Release notes](https://github.com/postcss/postcss/releases) - [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/postcss/postcss/compare/8.3.11...8.4.31) --- updated-dependencies: - dependency-name: postcss dependency-type: indirect ... Signed-off-by: dependabot[bot] * fix spaces --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Mitch Prewitt <44011584+mprew97@users.noreply.github.com> * Bump braces from 3.0.2 to 3.0.3 in /example (#434) Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3. - [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3) --- updated-dependencies: - dependency-name: braces dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump requirejs from 2.3.6 to 2.3.7 (#435) Bumps [requirejs](https://github.com/jrburke/r.js) from 2.3.6 to 2.3.7. - [Commits](https://github.com/jrburke/r.js/compare/2.3.6...2.3.7) --- updated-dependencies: - dependency-name: requirejs dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump micromatch from 4.0.5 to 4.0.8 in /example (#447) Bumps [micromatch](https://github.com/micromatch/micromatch) from 4.0.5 to 4.0.8. - [Release notes](https://github.com/micromatch/micromatch/releases) - [Changelog](https://github.com/micromatch/micromatch/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/micromatch/compare/4.0.5...4.0.8) --- updated-dependencies: - dependency-name: micromatch dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Mitch Prewitt <44011584+mprew97@users.noreply.github.com> * update version for release (#448) * Bump webpack from 5.76.0 to 5.94.0 (#443) Bumps [webpack](https://github.com/webpack/webpack) from 5.76.0 to 5.94.0. - [Release notes](https://github.com/webpack/webpack/releases) - [Commits](https://github.com/webpack/webpack/compare/v5.76.0...v5.94.0) --- updated-dependencies: - dependency-name: webpack dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * EUDC -> EDC (#449) * fix jwt generator for userid * some cleanup and fixes * unused imports * fix build * revert, outside of scope * test not needed --------- Signed-off-by: dependabot[bot] Co-authored-by: Brad Umbaugh Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- README.md | 15 +- example/yarn.lock | 34 +- package.json | 2 +- react-example/src/index.tsx | 2 +- react-example/yarn.lock | 2 +- src/authorization/authorization.test.ts | 33 +- src/authorization/authorization.ts | 64 +--- yarn.lock | 451 +++++++++++------------- 8 files changed, 248 insertions(+), 355 deletions(-) diff --git a/README.md b/README.md index 7f39c290..74f86699 100644 --- a/README.md +++ b/README.md @@ -2343,20 +2343,17 @@ At that point, further requests to Iterable's API will fail. To perform a manual JWT token refresh, call [`refreshJwtToken`](#refreshjwttoken). -# Iterable's European data center (EUDC) +# Iterable's European data center (EDC) -If your Iterable project is hosted on Iterable's [European data center (EUDC)](https://support.iterable.com/hc/articles/17572750887444), +If your Iterable project is hosted on Iterable's [European data center (EDC)](https://support.iterable.com/hc/articles/17572750887444), you'll need to configure Iterable's Web SDK to interact with Iterable's EU-based API endpoints. -To do this, you have two options: +To do this: -- On the web server that hosts your site, set the `IS_EU_ITERABLE_SERVICE` - environment variable to `true`. - -- Or, when use [`initializeWithConfig`](#initializeWithConfig) to initialize - the SDK (rather then [`initialize`](#initialize)), and set set the - `isEuIterableService` configuration option to `true`. For example: +- Use [`initializeWithConfig`](#initializeWithConfig) to initialize the SDK + (rather then [`initialize`](#initialize)). +- Set the `isEuIterableService` configuration option to `true`. For example: ```ts import { initializeWithConfig } from '@iterable/web-sdk'; diff --git a/example/yarn.lock b/example/yarn.lock index 464f77f3..5a9b6554 100644 --- a/example/yarn.lock +++ b/example/yarn.lock @@ -1470,12 +1470,19 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^3.0.2, braces@~3.0.2: +braces@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== dependencies: - fill-range "^7.0.1" + fill-range "^7.1.1" browser-process-hrtime@^1.0.0: version "1.0.0" @@ -2379,10 +2386,17 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== dependencies: to-regex-range "^5.0.1" @@ -3592,11 +3606,11 @@ methods@~1.1.2: integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== dependencies: - braces "^3.0.2" + braces "^3.0.3" picomatch "^2.3.1" mime-db@1.51.0: diff --git a/package.json b/package.json index 2f376d07..9910f008 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@iterable/web-sdk", "description": "Iterable SDK for JavaScript and Node.", - "version": "1.1.1", + "version": "1.1.2", "homepage": "https://iterable.com/", "repository": { "type": "git", diff --git a/react-example/src/index.tsx b/react-example/src/index.tsx index 5cccc81d..e9c455bd 100644 --- a/react-example/src/index.tsx +++ b/react-example/src/index.tsx @@ -56,7 +56,7 @@ const HomeLink = styled(Link)` { exp_minutes: 2, email, - userId: userID, + user_id: userID, jwt_secret: process.env.JWT_SECRET }, { diff --git a/react-example/yarn.lock b/react-example/yarn.lock index 6fc967dd..47b17c56 100644 --- a/react-example/yarn.lock +++ b/react-example/yarn.lock @@ -552,7 +552,7 @@ integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== "@iterable/web-sdk@../": - version "1.1.1" + version "1.1.2" dependencies: "@pabra/sortby" "^1.0.1" "@types/ws" "8.5.4" diff --git a/src/authorization/authorization.test.ts b/src/authorization/authorization.test.ts index 7971d368..67d5ee70 100644 --- a/src/authorization/authorization.test.ts +++ b/src/authorization/authorization.test.ts @@ -108,7 +108,7 @@ describe('API Key Interceptors', () => { packageName: 'my-lil-website' }); expect(response.config.headers['Api-Key']).toBe('123'); - expect(response.config.headers['Authorization']).toBe( + expect(response.config.headers.Authorization).toBe( `Bearer ${MOCK_JWT_KEY}` ); }); @@ -125,7 +125,7 @@ describe('API Key Interceptors', () => { packageName: 'my-lil-website' }); expect(response.config.headers['Api-Key']).toBe('123'); - expect(response.config.headers['Authorization']).toBe( + expect(response.config.headers.Authorization).toBe( `Bearer ${MOCK_JWT_KEY}` ); }); @@ -230,8 +230,8 @@ describe('API Key Interceptors', () => { await updateUserEmail('helloworld@gmail.com'); jest.advanceTimersByTime(60000 * 4.1); - /* - called once originally, a second time after the email was changed, + /* + called once originally, a second time after the email was changed, and a third after the JWT was about to expire */ expect(mockGenerateJWT).toHaveBeenCalledTimes(3); @@ -279,8 +279,8 @@ describe('API Key Interceptors', () => { }); jest.advanceTimersByTime(60000 * 4.1); - /* - called once originally, a second time after the email was changed, + /* + called once originally, a second time after the email was changed, and a third after the JWT was about to expire */ expect(mockGenerateJWT).toHaveBeenCalledTimes(3); @@ -313,8 +313,8 @@ describe('API Key Interceptors', () => { }); jest.advanceTimersByTime(60000 * 4.1); - /* - called once originally, a second time after the email was changed, + /* + called once originally, a second time after the email was changed, and a third after the JWT was about to expire */ expect(mockGenerateJWT).toHaveBeenCalledTimes(3); @@ -671,19 +671,6 @@ describe('User Identification', () => { expect(response.config.params.email).toBeUndefined(); expect(response.config.params.userId).toBe('999'); }); - - it('should try /users/update 0 times if request to create a user fails', async () => { - mockRequest.onPost('/users/update').reply(400, {}); - - const { setUserID } = initialize('123'); - await setUserID('999'); - - expect( - mockRequest.history.post.filter( - (e: any) => !!e.url?.match(/users\/update/gim) - ).length - ).toBe(1); - }); }); }); @@ -1075,7 +1062,7 @@ describe('User Identification', () => { packageName: 'my-lil-website' }); expect(response.config.headers['Api-Key']).toBe('123'); - expect(response.config.headers['Authorization']).toBe( + expect(response.config.headers.Authorization).toBe( `Bearer ${MOCK_JWT_KEY}` ); }); @@ -1092,7 +1079,7 @@ describe('User Identification', () => { packageName: 'my-lil-website' }); expect(response.config.headers['Api-Key']).toBe('123'); - expect(response.config.headers['Authorization']).toBe( + expect(response.config.headers.Authorization).toBe( `Bearer ${MOCK_JWT_KEY}` ); }); diff --git a/src/authorization/authorization.ts b/src/authorization/authorization.ts index 66c6ccb6..3fd16897 100644 --- a/src/authorization/authorization.ts +++ b/src/authorization/authorization.ts @@ -1,10 +1,9 @@ /* eslint-disable */ import axios from 'axios'; -import { baseAxiosRequest, baseIterableRequest } from '../request'; +import { baseAxiosRequest } from '../request'; import { clearMessages } from 'src/inapp/inapp'; import { IS_PRODUCTION, - RETRY_USER_ATTEMPTS, STATIC_HEADERS, SHARED_PREF_ANON_USER_ID, ENDPOINTS, @@ -25,8 +24,6 @@ import { registerAnonUserIdSetter } from 'src/anonymousUserTracking/anonymousUserEventManager'; import { Options, config } from 'src/utils/config'; -import { IterableResponse } from 'src/types'; -import { updateUserSchema } from 'src/users/users.schema'; const MAX_TIMEOUT = ONE_DAY; /* @@ -82,7 +79,7 @@ export interface WithoutJWT { export const setAnonUserId = async (userId: string) => { let token: null | string = null; if (generateJWTGlobal) { - token = await generateJWTGlobal({ userId: userId }); + token = await generateJWTGlobal({ userID: userId }); } baseAxiosRequest.interceptors.request.use((config) => { @@ -102,19 +99,6 @@ const clearAnonymousUser = () => { localStorage.removeItem(SHARED_PREF_ANON_USER_ID); }; -const updateUser = () => { - return baseIterableRequest({ - method: 'POST', - url: ENDPOINTS.users_update.route, - data: { - preferUserId: true - }, - validation: { - data: updateUserSchema - } - }); -}; - const getAnonUserId = () => { if (config.getConfig('enableAnonTracking')) { const anonUser = localStorage.getItem(SHARED_PREF_ANON_USER_ID); @@ -517,34 +501,12 @@ export function initialize( }, setUserID: async (userId: string, merge?: boolean) => { clearMessages(); - const tryUser = () => { - let createUserAttempts = 0; - return async function tryUserNTimes(): Promise { - try { - return await updateUser(); - } catch (e) { - if (createUserAttempts < RETRY_USER_ATTEMPTS) { - createUserAttempts += 1; - return tryUserNTimes(); - } - - return Promise.reject( - `could not create user after ${createUserAttempts} tries` - ); - } - }; - }; try { merge = getMergeDefaultValue(merge); const result = await tryMergeUser(userId, false, merge); if (result) { initializeUserIdAndSync(userId, merge); - try { - return await tryUser()(); - } catch (e) { - /* failed to create a new user. Just silently resolve */ - return Promise.resolve(); - } + return Promise.resolve(); } } catch (error) { // here we will not sync events but just bubble up error of merge @@ -858,25 +820,6 @@ export function initialize( }, setUserID: async (userId: string, merge?: boolean) => { clearMessages(); - - const tryUser = () => { - let createUserAttempts = 0; - - return async function tryUserNTimes(): Promise { - try { - return await updateUser(); - } catch (e) { - if (createUserAttempts < RETRY_USER_ATTEMPTS) { - createUserAttempts += 1; - return tryUserNTimes(); - } - - return Promise.reject( - `could not create user after ${createUserAttempts} tries` - ); - } - }; - }; try { merge = getMergeDefaultValue(merge); const result = await tryMergeUser(userId, false, merge); @@ -885,7 +828,6 @@ export function initialize( try { return doRequest({ userID: userId }) .then(async (token) => { - await tryUser()(); return token; }) .catch((e) => { diff --git a/yarn.lock b/yarn.lock index f7c4cfef..28d76dfc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1711,32 +1711,11 @@ dependencies: "@types/node" "*" -"@types/eslint-scope@^3.7.3": - version "3.7.7" - resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5" - integrity sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg== - dependencies: - "@types/eslint" "*" - "@types/estree" "*" - -"@types/eslint@*": - version "8.56.6" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.56.6.tgz#d5dc16cac025d313ee101108ba5714ea10eb3ed0" - integrity sha512-ymwc+qb1XkjT/gfoQwxIeHZ6ixH23A+tCT2ADSA/DPVKzAjwYkTXBMCQ/f6fe4wEa85Lhp26VPeUxI7wMhAi7A== - dependencies: - "@types/estree" "*" - "@types/json-schema" "*" - -"@types/estree@*": +"@types/estree@^1.0.5": version "1.0.5" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== -"@types/estree@^0.0.51": - version "0.0.51" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40" - integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== - "@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.33": version "4.17.43" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.43.tgz#10d8444be560cb789c4735aea5eac6e5af45df54" @@ -1803,7 +1782,7 @@ jest-matcher-utils "^27.0.0" pretty-format "^27.0.0" -"@types/json-schema@*", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": +"@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== @@ -2069,125 +2048,125 @@ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== -"@webassemblyjs/ast@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" - integrity sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw== +"@webassemblyjs/ast@1.12.1", "@webassemblyjs/ast@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.12.1.tgz#bb16a0e8b1914f979f45864c23819cc3e3f0d4bb" + integrity sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg== dependencies: - "@webassemblyjs/helper-numbers" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + "@webassemblyjs/helper-numbers" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" -"@webassemblyjs/floating-point-hex-parser@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz#f6c61a705f0fd7a6aecaa4e8198f23d9dc179e4f" - integrity sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ== +"@webassemblyjs/floating-point-hex-parser@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz#dacbcb95aff135c8260f77fa3b4c5fea600a6431" + integrity sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw== -"@webassemblyjs/helper-api-error@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz#1a63192d8788e5c012800ba6a7a46c705288fd16" - integrity sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg== +"@webassemblyjs/helper-api-error@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz#6132f68c4acd59dcd141c44b18cbebbd9f2fa768" + integrity sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q== -"@webassemblyjs/helper-buffer@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz#832a900eb444884cde9a7cad467f81500f5e5ab5" - integrity sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA== +"@webassemblyjs/helper-buffer@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz#6df20d272ea5439bf20ab3492b7fb70e9bfcb3f6" + integrity sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw== -"@webassemblyjs/helper-numbers@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz#64d81da219fbbba1e3bd1bfc74f6e8c4e10a62ae" - integrity sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ== +"@webassemblyjs/helper-numbers@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz#cbce5e7e0c1bd32cf4905ae444ef64cea919f1b5" + integrity sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g== dependencies: - "@webassemblyjs/floating-point-hex-parser" "1.11.1" - "@webassemblyjs/helper-api-error" "1.11.1" + "@webassemblyjs/floating-point-hex-parser" "1.11.6" + "@webassemblyjs/helper-api-error" "1.11.6" "@xtuc/long" "4.2.2" -"@webassemblyjs/helper-wasm-bytecode@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz#f328241e41e7b199d0b20c18e88429c4433295e1" - integrity sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q== +"@webassemblyjs/helper-wasm-bytecode@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz#bb2ebdb3b83aa26d9baad4c46d4315283acd51e9" + integrity sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA== -"@webassemblyjs/helper-wasm-section@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz#21ee065a7b635f319e738f0dd73bfbda281c097a" - integrity sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg== +"@webassemblyjs/helper-wasm-section@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz#3da623233ae1a60409b509a52ade9bc22a37f7bf" + integrity sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g== dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-buffer" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/wasm-gen" "1.11.1" + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/wasm-gen" "1.12.1" -"@webassemblyjs/ieee754@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz#963929e9bbd05709e7e12243a099180812992614" - integrity sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ== +"@webassemblyjs/ieee754@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz#bb665c91d0b14fffceb0e38298c329af043c6e3a" + integrity sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg== dependencies: "@xtuc/ieee754" "^1.2.0" -"@webassemblyjs/leb128@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.1.tgz#ce814b45574e93d76bae1fb2644ab9cdd9527aa5" - integrity sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw== +"@webassemblyjs/leb128@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.6.tgz#70e60e5e82f9ac81118bc25381a0b283893240d7" + integrity sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ== dependencies: "@xtuc/long" "4.2.2" -"@webassemblyjs/utf8@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.1.tgz#d1f8b764369e7c6e6bae350e854dec9a59f0a3ff" - integrity sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ== - -"@webassemblyjs/wasm-edit@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz#ad206ebf4bf95a058ce9880a8c092c5dec8193d6" - integrity sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-buffer" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/helper-wasm-section" "1.11.1" - "@webassemblyjs/wasm-gen" "1.11.1" - "@webassemblyjs/wasm-opt" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" - "@webassemblyjs/wast-printer" "1.11.1" - -"@webassemblyjs/wasm-gen@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz#86c5ea304849759b7d88c47a32f4f039ae3c8f76" - integrity sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/ieee754" "1.11.1" - "@webassemblyjs/leb128" "1.11.1" - "@webassemblyjs/utf8" "1.11.1" - -"@webassemblyjs/wasm-opt@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz#657b4c2202f4cf3b345f8a4c6461c8c2418985f2" - integrity sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-buffer" "1.11.1" - "@webassemblyjs/wasm-gen" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" - -"@webassemblyjs/wasm-parser@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz#86ca734534f417e9bd3c67c7a1c75d8be41fb199" - integrity sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-api-error" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/ieee754" "1.11.1" - "@webassemblyjs/leb128" "1.11.1" - "@webassemblyjs/utf8" "1.11.1" - -"@webassemblyjs/wast-printer@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz#d0c73beda8eec5426f10ae8ef55cee5e7084c2f0" - integrity sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg== - dependencies: - "@webassemblyjs/ast" "1.11.1" +"@webassemblyjs/utf8@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.6.tgz#90f8bc34c561595fe156603be7253cdbcd0fab5a" + integrity sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA== + +"@webassemblyjs/wasm-edit@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz#9f9f3ff52a14c980939be0ef9d5df9ebc678ae3b" + integrity sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/helper-wasm-section" "1.12.1" + "@webassemblyjs/wasm-gen" "1.12.1" + "@webassemblyjs/wasm-opt" "1.12.1" + "@webassemblyjs/wasm-parser" "1.12.1" + "@webassemblyjs/wast-printer" "1.12.1" + +"@webassemblyjs/wasm-gen@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz#a6520601da1b5700448273666a71ad0a45d78547" + integrity sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/ieee754" "1.11.6" + "@webassemblyjs/leb128" "1.11.6" + "@webassemblyjs/utf8" "1.11.6" + +"@webassemblyjs/wasm-opt@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz#9e6e81475dfcfb62dab574ac2dda38226c232bc5" + integrity sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" + "@webassemblyjs/wasm-gen" "1.12.1" + "@webassemblyjs/wasm-parser" "1.12.1" + +"@webassemblyjs/wasm-parser@1.12.1", "@webassemblyjs/wasm-parser@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz#c47acb90e6f083391e3fa61d113650eea1e95937" + integrity sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-api-error" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/ieee754" "1.11.6" + "@webassemblyjs/leb128" "1.11.6" + "@webassemblyjs/utf8" "1.11.6" + +"@webassemblyjs/wast-printer@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz#bcecf661d7d1abdaf989d8341a4833e33e2b31ac" + integrity sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA== + dependencies: + "@webassemblyjs/ast" "1.12.1" "@xtuc/long" "4.2.2" "@webpack-cli/configtest@^1.2.0": @@ -2238,10 +2217,10 @@ acorn-globals@^6.0.0: acorn "^7.1.1" acorn-walk "^7.1.1" -acorn-import-assertions@^1.7.6: - version "1.9.0" - resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac" - integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== +acorn-import-attributes@^1.9.5: + version "1.9.5" + resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef" + integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ== acorn-jsx@^5.3.2: version "5.3.2" @@ -2258,17 +2237,7 @@ acorn@^7.1.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.2.4, acorn@^8.7.1: - version "8.8.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8" - integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== - -acorn@^8.8.2: - version "8.11.3" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" - integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== - -acorn@^8.9.0: +acorn@^8.2.4, acorn@^8.7.1, acorn@^8.8.2, acorn@^8.9.0: version "8.12.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== @@ -2505,11 +2474,11 @@ axios-mock-adapter@^1.22.0: is-buffer "^2.0.5" axios@^1.6.2: - version "1.6.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.2.tgz#de67d42c755b571d3e698df1b6504cde9b0ee9f2" - integrity sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A== + version "1.7.4" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.4.tgz#4c8ded1b43683c8dd362973c393f3ede24052aa2" + integrity sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw== dependencies: - follow-redirects "^1.15.0" + follow-redirects "^1.15.6" form-data "^4.0.0" proxy-from-env "^1.1.0" @@ -2722,36 +2691,26 @@ brace-expansion@^2.0.1: balanced-match "^1.0.0" braces@^3.0.2, braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== dependencies: - fill-range "^7.0.1" + fill-range "^7.1.1" browser-process-hrtime@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== -browserslist@^4.14.5, browserslist@^4.23.0: - version "4.23.0" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.0.tgz#8f3acc2bbe73af7213399430890f86c63a5674ab" - integrity sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ== +browserslist@^4.21.10, browserslist@^4.21.9, browserslist@^4.22.2, browserslist@^4.23.0: + version "4.23.3" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.3.tgz#debb029d3c93ebc97ffbc8d9cbb03403e227c800" + integrity sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA== dependencies: - caniuse-lite "^1.0.30001587" - electron-to-chromium "^1.4.668" - node-releases "^2.0.14" - update-browserslist-db "^1.0.13" - -browserslist@^4.21.9, browserslist@^4.22.2: - version "4.22.2" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.2.tgz#704c4943072bd81ea18997f3bd2180e89c77874b" - integrity sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A== - dependencies: - caniuse-lite "^1.0.30001565" - electron-to-chromium "^1.4.601" - node-releases "^2.0.14" - update-browserslist-db "^1.0.13" + caniuse-lite "^1.0.30001646" + electron-to-chromium "^1.5.4" + node-releases "^2.0.18" + update-browserslist-db "^1.1.0" bs-logger@0.x: version "0.2.6" @@ -2843,15 +2802,10 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001565: - version "1.0.30001566" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001566.tgz#61a8e17caf3752e3e426d4239c549ebbb37fef0d" - integrity sha512-ggIhCsTxmITBAMmK8yZjEhCO5/47jKXPu6Dha/wuCS4JePVL+3uiDEBuhu2aIoT+bqTOR8L76Ip1ARL9xYsEJA== - -caniuse-lite@^1.0.30001587: - version "1.0.30001612" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001612.tgz#d34248b4ec1f117b70b24ad9ee04c90e0b8a14ae" - integrity sha512-lFgnZ07UhaCcsSZgWW0K5j4e69dK1u/ltrL9lTUiFOwNHs12S3UMIEYgBV0Z6C6hRDev7iRnMzzYmKabYdXF9g== +caniuse-lite@^1.0.30001646: + version "1.0.30001657" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001657.tgz#29fd504bffca719d1c6b63a1f6f840be1973a660" + integrity sha512-DPbJAlP8/BAXy3IgiWmZKItubb3TYGP0WscQQlVGIfT4s/YlFYVuJgyOsQNP7rJRChx/qdMeLJQJP0Sgg2yjNA== chalk@^2.4.2: version "2.4.2" @@ -3538,15 +3492,10 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== -electron-to-chromium@^1.4.601: - version "1.4.607" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.607.tgz#340cc229b504966413716c6eae67d0f3d3702ff0" - integrity sha512-YUlnPwE6eYxzwBnFmawA8LiLRfm70R2aJRIUv0n03uHt/cUzzYACOogmvk8M2+hVzt/kB80KJXx7d5f5JofPvQ== - -electron-to-chromium@^1.4.668: - version "1.4.745" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.745.tgz#9c202ce9cbf18a5b5e0ca47145fd127cc4dd2290" - integrity sha512-tRbzkaRI5gbUn5DEvF0dV4TQbMZ5CLkWeTAXmpC9IrYT+GE+x76i9p+o3RJ5l9XmdQlI1pPhVtE9uNcJJ0G0EA== +electron-to-chromium@^1.5.4: + version "1.5.14" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.14.tgz#8de5fd941f4deede999f90503c4b5923fbe1962b" + integrity sha512-bEfPECb3fJ15eaDnu9LEJ2vPGD6W1vt7vZleSVyFhYuMIKm3vz/g9lt7IvEzgdwj58RjbPKUF2rXTCN/UW47tQ== emittery@^0.8.1: version "0.8.1" @@ -3575,10 +3524,10 @@ end-of-stream@^1.1.0: dependencies: once "^1.4.0" -enhanced-resolve@^5.10.0, enhanced-resolve@^5.8.3: - version "5.16.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz#65ec88778083056cb32487faa9aef82ed0864787" - integrity sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA== +enhanced-resolve@^5.17.1, enhanced-resolve@^5.8.3: + version "5.17.1" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15" + integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" @@ -3667,10 +3616,10 @@ es-errors@^1.2.1, es-errors@^1.3.0: resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== -es-module-lexer@^0.9.0: - version "0.9.3" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.9.3.tgz#6f13db00cc38417137daf74366f535c8eb438f19" - integrity sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ== +es-module-lexer@^1.2.1: + version "1.5.4" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.5.4.tgz#a8efec3a3da991e60efa6b633a7cad6ab8d26b78" + integrity sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw== es-object-atoms@^1.0.0: version "1.0.0" @@ -3709,6 +3658,11 @@ escalade@^3.1.1: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== +escalade@^3.1.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -4107,10 +4061,10 @@ filing-cabinet@^3.0.1: tsconfig-paths "^3.10.1" typescript "^3.9.7" -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== dependencies: to-regex-range "^5.0.1" @@ -4198,7 +4152,7 @@ flatten@^1.0.2: resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b" integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg== -follow-redirects@^1.0.0, follow-redirects@^1.15.0: +follow-redirects@^1.0.0, follow-redirects@^1.15.6: version "1.15.6" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== @@ -4451,10 +4405,10 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" -graceful-fs@^4.1.2, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: - version "4.2.10" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" - integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== +graceful-fs@^4.1.2, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== graphemer@^1.4.0: version "1.4.0" @@ -5965,12 +5919,7 @@ nanoclone@^0.2.1: resolved "https://registry.yarnpkg.com/nanoclone/-/nanoclone-0.2.1.tgz#dd4090f8f1a110d26bb32c49ed2f5b9235209ed4" integrity sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA== -nanoid@^3.3.6: - version "3.3.6" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" - integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== - -nanoid@^3.3.7: +nanoid@^3.3.6, nanoid@^3.3.7: version "3.3.7" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== @@ -6010,10 +5959,10 @@ node-int64@^0.4.0: resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== -node-releases@^2.0.14: - version "2.0.14" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" - integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== +node-releases@^2.0.18: + version "2.0.18" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" + integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== node-source-walk@^4.0.0, node-source-walk@^4.2.0: version "4.2.0" @@ -6361,6 +6310,11 @@ picocolors@^1.0.0: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== +picocolors@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.0.tgz#5358b76a78cde483ba5cef6a9dc9671440b27d59" + integrity sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw== + picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" @@ -6785,9 +6739,9 @@ requirejs-config-file@^4.0.0: stringify-object "^3.2.1" requirejs@^2.3.5: - version "2.3.6" - resolved "https://registry.yarnpkg.com/requirejs/-/requirejs-2.3.6.tgz#e5093d9601c2829251258c0b9445d4d19fa9e7c9" - integrity sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg== + version "2.3.7" + resolved "https://registry.yarnpkg.com/requirejs/-/requirejs-2.3.7.tgz#0b22032e51a967900e0ae9f32762c23a87036bd0" + integrity sha512-DouTG8T1WanGok6Qjg2SXuCMzszOo0eHeH9hDZ5Y4x8Je+9JB38HdTLT4/VA8OaUhBa0JPVHJ0pyBkM1z+pDsw== requires-port@^1.0.0: version "1.0.0" @@ -6952,7 +6906,7 @@ saxes@^5.0.1: dependencies: xmlchars "^2.2.0" -schema-utils@^3.0.0, schema-utils@^3.1.0, schema-utils@^3.1.1: +schema-utils@^3.0.0, schema-utils@^3.1.1, schema-utils@^3.2.0: version "3.3.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe" integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== @@ -7490,7 +7444,7 @@ terminal-link@^2.0.0: ansi-escapes "^4.2.1" supports-hyperlinks "^2.0.0" -terser-webpack-plugin@^5.1.3: +terser-webpack-plugin@^5.3.10: version "5.3.10" resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz#904f4c9193c6fd2a03f693a2150c62a92f40d199" integrity sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w== @@ -7812,13 +7766,13 @@ unpipe@1.0.0, unpipe@~1.0.0: resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== -update-browserslist-db@^1.0.13: - version "1.0.13" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" - integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== +update-browserslist-db@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz#7ca61c0d8650766090728046e416a8cde682859e" + integrity sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ== dependencies: - escalade "^3.1.1" - picocolors "^1.0.0" + escalade "^3.1.2" + picocolors "^1.0.1" uri-js@^4.2.2: version "4.4.1" @@ -7903,10 +7857,10 @@ walker@^1.0.7: dependencies: makeerror "1.0.12" -watchpack@^2.4.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.1.tgz#29308f2cac150fa8e4c92f90e0ec954a9fed7fff" - integrity sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg== +watchpack@^2.4.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.2.tgz#2feeaed67412e7c33184e5a79ca738fbd38564da" + integrity sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw== dependencies: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" @@ -8020,33 +7974,32 @@ webpack-sources@^3.2.3: integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== webpack@^5.63.0: - version "5.76.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.76.0.tgz#f9fb9fb8c4a7dbdcd0d56a98e56b8a942ee2692c" - integrity sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA== - dependencies: - "@types/eslint-scope" "^3.7.3" - "@types/estree" "^0.0.51" - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/wasm-edit" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" + version "5.94.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.94.0.tgz#77a6089c716e7ab90c1c67574a28da518a20970f" + integrity sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg== + dependencies: + "@types/estree" "^1.0.5" + "@webassemblyjs/ast" "^1.12.1" + "@webassemblyjs/wasm-edit" "^1.12.1" + "@webassemblyjs/wasm-parser" "^1.12.1" acorn "^8.7.1" - acorn-import-assertions "^1.7.6" - browserslist "^4.14.5" + acorn-import-attributes "^1.9.5" + browserslist "^4.21.10" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.10.0" - es-module-lexer "^0.9.0" + enhanced-resolve "^5.17.1" + es-module-lexer "^1.2.1" eslint-scope "5.1.1" events "^3.2.0" glob-to-regexp "^0.4.1" - graceful-fs "^4.2.9" + graceful-fs "^4.2.11" json-parse-even-better-errors "^2.3.1" loader-runner "^4.2.0" mime-types "^2.1.27" neo-async "^2.6.2" - schema-utils "^3.1.0" + schema-utils "^3.2.0" tapable "^2.1.1" - terser-webpack-plugin "^5.1.3" - watchpack "^2.4.0" + terser-webpack-plugin "^5.3.10" + watchpack "^2.4.1" webpack-sources "^3.2.3" websocket-driver@>=0.5.1, websocket-driver@^0.7.4: @@ -8164,14 +8117,14 @@ write-file-atomic@^3.0.0: typedarray-to-buffer "^3.1.5" ws@^7.4.6: - version "7.5.9" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" - integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== + version "7.5.10" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" + integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== ws@^8.13.0: - version "8.16.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.16.0.tgz#d1cd774f36fbc07165066a60e40323eab6446fd4" - integrity sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ== + version "8.17.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" + integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== xml-name-validator@^3.0.0: version "3.0.0" From c9a304a17051bfec056653a72c31e2ce2778e951 Mon Sep 17 00:00:00 2001 From: darshan-iterable Date: Tue, 17 Sep 2024 18:13:51 +0530 Subject: [PATCH 75/88] [MOB-9505] rename merge parameter (#450) * [MOB-9505] rename merge parameter * fix ci failure and resolved comment * fix: ci failure and merged AUT_main changes --- .../tests/userMergeScenarios.test.ts | 62 +++++++-------- src/authorization/authorization.ts | 76 ++++++++----------- 2 files changed, 65 insertions(+), 73 deletions(-) diff --git a/src/anonymousUserTracking/tests/userMergeScenarios.test.ts b/src/anonymousUserTracking/tests/userMergeScenarios.test.ts index 19b733a1..5451b761 100644 --- a/src/anonymousUserTracking/tests/userMergeScenarios.test.ts +++ b/src/anonymousUserTracking/tests/userMergeScenarios.test.ts @@ -127,7 +127,7 @@ describe('UserMergeScenariosTests', () => { }); describe('UserMergeScenariosTests with setUserID', () => { - it('criteria not met with merge false with setUserId', async () => { + it('criteria not met with disableEventReplay true with setUserId', async () => { const { setUserID, logout } = initializeWithConfig({ authToken: '123', configOptions: { enableAnonTracking: true } @@ -142,7 +142,7 @@ describe('UserMergeScenariosTests', () => { SHARED_PREFS_EVENT_LIST_KEY, expect.any(String) ); - await setUserID('testuser123', false); + await setUserID('testuser123', true); const response = await getInAppMessages({ count: 10, packageName: 'my-lil-website' @@ -160,7 +160,7 @@ describe('UserMergeScenariosTests', () => { expect(mergePostRequestData).toBeUndefined(); // ensure that merge API Do NOT get called }); - it('criteria not met with merge true with setUserId', async () => { + it('criteria not met with disableEventReplay false with setUserId', async () => { const { setUserID, logout } = initializeWithConfig({ authToken: '123', configOptions: { enableAnonTracking: true } @@ -175,7 +175,7 @@ describe('UserMergeScenariosTests', () => { SHARED_PREFS_EVENT_LIST_KEY, expect.any(String) ); - await setUserID('testuser123', true); + await setUserID('testuser123', false); const response = await getInAppMessages({ count: 10, packageName: 'my-lil-website' @@ -193,7 +193,7 @@ describe('UserMergeScenariosTests', () => { expect(mergePostRequestData).toBeUndefined(); // ensure that merge API Do NOT get called }); - it('criteria not met with merge default value with setUserId', async () => { + it('criteria not met with disableEventReplay default value with setUserId', async () => { const { setUserID, logout } = initializeWithConfig({ authToken: '123', configOptions: { enableAnonTracking: true } @@ -226,7 +226,7 @@ describe('UserMergeScenariosTests', () => { expect(mergePostRequestData).toBeUndefined(); // ensure that merge API Do NOT get called }); - it('criteria is met with merge false with setUserId', async () => { + it('criteria is met with disableEventReplay true with setUserId', async () => { (localStorage.getItem as jest.Mock).mockImplementation((key) => { if (key === SHARED_PREFS_EVENT_LIST_KEY) { return JSON.stringify([eventDataMatched]); @@ -247,7 +247,7 @@ describe('UserMergeScenariosTests', () => { } catch (e) { console.log(''); } - await setUserID('testuser123', false); + await setUserID('testuser123', true); expect(localStorageMock.removeItem).toHaveBeenCalledWith( SHARED_PREF_ANON_USER_ID ); @@ -262,7 +262,7 @@ describe('UserMergeScenariosTests', () => { expect(mergePostRequestData).toBeUndefined(); // ensure that merge API Do NOT get called }); - it('criteria is met with merge true with setUserId', async () => { + it('criteria is met with disableEventReplay false with setUserId', async () => { (localStorage.getItem as jest.Mock).mockImplementation((key) => { if (key === SHARED_PREFS_EVENT_LIST_KEY) { return JSON.stringify([eventDataMatched]); @@ -288,7 +288,7 @@ describe('UserMergeScenariosTests', () => { count: 10, packageName: 'my-lil-website' }); - await setUserID('testuser123', true); + await setUserID('testuser123', false); expect(localStorageMock.removeItem).toHaveBeenCalledWith( SHARED_PREF_ANON_USER_ID ); @@ -302,7 +302,7 @@ describe('UserMergeScenariosTests', () => { jest.runAllTimers(); }); - it('criteria is met with merge default with setUserId', async () => { + it('criteria is met with disableEventReplay default with setUserId', async () => { (localStorage.getItem as jest.Mock).mockImplementation((key) => { if (key === SHARED_PREFS_EVENT_LIST_KEY) { return JSON.stringify([eventDataMatched]); @@ -343,7 +343,7 @@ describe('UserMergeScenariosTests', () => { jest.runAllTimers(); }); - it('current user identified with setUserId merge false', async () => { + it('current user identified with setUserId disableEventReplay true', async () => { const { setUserID, logout } = initializeWithConfig({ authToken: '123', configOptions: { enableAnonTracking: true } @@ -363,7 +363,7 @@ describe('UserMergeScenariosTests', () => { expect(localStorageMock.setItem).not.toHaveBeenCalledWith( SHARED_PREF_ANON_USER_ID ); - await setUserID('testuseranotheruser', false); + await setUserID('testuseranotheruser', true); const secondResponse = await getInAppMessages({ count: 10, packageName: 'my-lil-website' @@ -374,7 +374,8 @@ describe('UserMergeScenariosTests', () => { ); expect(mergePostRequestData).toBeUndefined(); // ensure that merge API Do NOT get called }); - it('current user identified with setUserId merge true', async () => { + + it('current user identified with setUserId disableEventReplay false', async () => { const { setUserID, logout } = initializeWithConfig({ authToken: '123', configOptions: { enableAnonTracking: true } @@ -389,7 +390,7 @@ describe('UserMergeScenariosTests', () => { expect(localStorageMock.setItem).not.toHaveBeenCalledWith( SHARED_PREF_ANON_USER_ID ); - await setUserID('testuseranotheruser', true); + await setUserID('testuseranotheruser', false); const secondResponse = await getInAppMessages({ count: 10, packageName: 'my-lil-website' @@ -400,7 +401,8 @@ describe('UserMergeScenariosTests', () => { ); expect(mergePostRequestData).toBeDefined(); // ensure that merge API gets called }); - it('current user identified with setUserId merge default', async () => { + + it('current user identified with setUserId disableEventReplay default', async () => { const { setUserID, logout } = initializeWithConfig({ authToken: '123', configOptions: { enableAnonTracking: true } @@ -424,12 +426,12 @@ describe('UserMergeScenariosTests', () => { const mergePostRequestData = mockRequest.history.post.find( (req) => req.url === ENDPOINT_MERGE_USER ); - expect(mergePostRequestData).toBeUndefined(); // ensure that merge API Do NOT get called + expect(mergePostRequestData).toBeDefined(); // ensure that merge API gets called }); }); describe('UserMergeScenariosTests with setEmail', () => { - it('criteria not met with merge false with setEmail', async () => { + it('criteria not met with disableEventReplay true with setEmail', async () => { const { setEmail, logout } = initializeWithConfig({ authToken: '123', configOptions: { enableAnonTracking: true } @@ -444,7 +446,7 @@ describe('UserMergeScenariosTests', () => { SHARED_PREFS_EVENT_LIST_KEY, expect.any(String) ); - await setEmail('testuser123@test.com', false); + await setEmail('testuser123@test.com', true); const response = await getInAppMessages({ count: 10, packageName: 'my-lil-website' @@ -462,7 +464,7 @@ describe('UserMergeScenariosTests', () => { expect(mergePostRequestData).toBeUndefined(); // ensure that merge API Do NOT get called }); - it('criteria not met with merge true with setEmail', async () => { + it('criteria not met with disableEventReplay false with setEmail', async () => { const { setEmail, logout } = initializeWithConfig({ authToken: '123', configOptions: { enableAnonTracking: true } @@ -477,7 +479,7 @@ describe('UserMergeScenariosTests', () => { SHARED_PREFS_EVENT_LIST_KEY, expect.any(String) ); - await setEmail('testuser123@test.com', true); + await setEmail('testuser123@test.com', false); const response = await getInAppMessages({ count: 10, packageName: 'my-lil-website' @@ -495,7 +497,7 @@ describe('UserMergeScenariosTests', () => { expect(mergePostRequestData).toBeUndefined(); // ensure that merge API Do NOT get called }); - it('criteria not met with merge default value with setEmail', async () => { + it('criteria not met with disableEventReplay default value with setEmail', async () => { const { setEmail, logout } = initializeWithConfig({ authToken: '123', configOptions: { enableAnonTracking: true } @@ -528,7 +530,7 @@ describe('UserMergeScenariosTests', () => { expect(mergePostRequestData).toBeUndefined(); // ensure that merge API Do NOT get called }); - it('criteria is met with merge true with setEmail', async () => { + it('criteria is met with disableEventReplay false with setEmail', async () => { (localStorage.getItem as jest.Mock).mockImplementation((key) => { if (key === SHARED_PREFS_EVENT_LIST_KEY) { return JSON.stringify([eventDataMatched]); @@ -553,7 +555,7 @@ describe('UserMergeScenariosTests', () => { count: 10, packageName: 'my-lil-website' }); - await setEmail('testuser123@test.com', true); + await setEmail('testuser123@test.com', false); expect(localStorageMock.removeItem).toHaveBeenCalledWith( SHARED_PREF_ANON_USER_ID ); @@ -567,7 +569,7 @@ describe('UserMergeScenariosTests', () => { jest.runAllTimers(); }); - it('criteria is met with merge default with setEmail', async () => { + it('criteria is met with disableEventReplay default with setEmail', async () => { (localStorage.getItem as jest.Mock).mockImplementation((key) => { if (key === SHARED_PREFS_EVENT_LIST_KEY) { return JSON.stringify([eventDataMatched]); @@ -608,7 +610,7 @@ describe('UserMergeScenariosTests', () => { jest.runAllTimers(); }); - it('current user identified with setEmail with merge false', async () => { + it('current user identified with setEmail with disableEventReplay true', async () => { const { setEmail, logout } = initializeWithConfig({ authToken: '123', configOptions: { enableAnonTracking: true } @@ -628,7 +630,7 @@ describe('UserMergeScenariosTests', () => { expect(localStorageMock.setItem).not.toHaveBeenCalledWith( SHARED_PREF_ANON_USER_ID ); - await setEmail('testuseranotheruser@test.com', false); + await setEmail('testuseranotheruser@test.com', true); const secondResponse = await getInAppMessages({ count: 10, packageName: 'my-lil-website' @@ -642,7 +644,7 @@ describe('UserMergeScenariosTests', () => { expect(mergePostRequestData).toBeUndefined(); // ensure that merge API Do NOT get called }); - it('current user identified with setEmail merge true', async () => { + it('current user identified with setEmail disableEventReplay false', async () => { const { setEmail, logout } = initializeWithConfig({ authToken: '123', configOptions: { enableAnonTracking: true } @@ -662,7 +664,7 @@ describe('UserMergeScenariosTests', () => { expect(localStorageMock.setItem).not.toHaveBeenCalledWith( SHARED_PREF_ANON_USER_ID ); - await setEmail('testuseranotheruser@test.com', true); + await setEmail('testuseranotheruser@test.com', false); const secondResponse = await getInAppMessages({ count: 10, packageName: 'my-lil-website' @@ -676,7 +678,7 @@ describe('UserMergeScenariosTests', () => { expect(mergePostRequestData).toBeDefined(); // ensure that merge API gets called }); - it('current user identified with setEmail merge default', async () => { + it('current user identified with setEmail disableEventReplay default', async () => { const { setEmail, logout } = initializeWithConfig({ authToken: '123', configOptions: { enableAnonTracking: true } @@ -707,7 +709,7 @@ describe('UserMergeScenariosTests', () => { const mergePostRequestData = mockRequest.history.post.find( (req) => req.url === ENDPOINT_MERGE_USER ); - expect(mergePostRequestData).toBeUndefined(); // ensure that merge API Do NOT get called + expect(mergePostRequestData).toBeDefined(); // ensure that merge API gets called }); }); }); diff --git a/src/authorization/authorization.ts b/src/authorization/authorization.ts index 3fd16897..e7ca34a6 100644 --- a/src/authorization/authorization.ts +++ b/src/authorization/authorization.ts @@ -62,8 +62,8 @@ const doesRequestUrlContain = (routeConfig: RouteConfig) => ); export interface WithJWT { clearRefresh: () => void; - setEmail: (email: string, merge?: boolean) => Promise; - setUserID: (userId: string, merge?: boolean) => Promise; + setEmail: (email: string, disableEventReplay?: boolean) => Promise; + setUserID: (userId: string, disableEventReplay?: boolean) => Promise; logout: () => void; refreshJwtToken: (authTypes: string) => Promise; } @@ -71,8 +71,8 @@ export interface WithJWT { export interface WithoutJWT { setNewAuthToken: (newToken?: string) => void; clearAuthToken: () => void; - setEmail: (email: string, merge?: boolean) => Promise; - setUserID: (userId: string, merge?: boolean) => Promise; + setEmail: (email: string, disableEventReplay?: boolean) => Promise; + setUserID: (userId: string, disableEventReplay?: boolean) => Promise; logout: () => void; } @@ -108,10 +108,13 @@ const getAnonUserId = () => { } }; -const initializeUserIdAndSync = (userId: string, merge: boolean) => { +const initializeUserIdAndSync = ( + userId: string, + disableEventReplay?: boolean +) => { addUserIdToRequest(userId); clearAnonymousUser(); - if (merge) { + if (!disableEventReplay) { syncEvents(); } }; @@ -211,10 +214,13 @@ const addUserIdToRequest = (userId: string) => { }); }; -const initializeEmailUserAndSync = (email: string, merge: boolean) => { +const initializeEmailUserAndSync = ( + email: string, + disableEventReplay?: boolean +) => { addEmailToRequest(email); clearAnonymousUser(); - if (merge) { + if (!disableEventReplay) { syncEvents(); } }; @@ -417,28 +423,12 @@ export function initialize( } }; - const getMergeDefaultValue = (merge?: boolean) => { - const doesAnonUserExist = getAnonUserId() === null; - if (merge === undefined) { - if ( - (authIdentifier === null && typeOfAuth === null && doesAnonUserExist) || // Criteria is not yet met (default merge is true) - (authIdentifier !== null && typeOfAuth !== null && !doesAnonUserExist) - ) { - // // Criteria is met (Iterable profile created with an autogenerated identity)(default merge is true) - return true; - } else { - return false; // Current logged in user is identified (default merge is false) - } - } else { - return merge; - } - }; - const tryMergeUser = async ( emailOrUserId: string, isEmail: boolean, - merge: boolean + disableEventReplay?: boolean ): Promise => { + const enableAnonTracking = config.getConfig('enableAnonTracking'); const sourceUserIdOrEmail = authIdentifier === null ? getAnonUserId() : authIdentifier; const sourceUserId = typeOfAuth === 'email' ? null : sourceUserIdOrEmail; @@ -446,7 +436,11 @@ export function initialize( const destinationUserId = isEmail ? null : emailOrUserId; const destinationEmail = isEmail ? emailOrUserId : null; // This function will try to merge if anon user exists - if ((getAnonUserId() !== null || authIdentifier !== null) && merge) { + if ( + (getAnonUserId() !== null || authIdentifier !== null) && + !disableEventReplay && + enableAnonTracking + ) { const anonymousUserMerge = new AnonymousUserMerge(); try { await anonymousUserMerge.mergeUser( @@ -485,13 +479,12 @@ export function initialize( baseAxiosRequest.interceptors.request.eject(authInterceptor); } }, - setEmail: async (email: string, merge?: boolean) => { + setEmail: async (email: string, disableEventReplay?: boolean) => { clearMessages(); try { - merge = getMergeDefaultValue(merge); - const result = await tryMergeUser(email, true, merge); + const result = await tryMergeUser(email, true, disableEventReplay); if (result) { - initializeEmailUserAndSync(email, merge); + initializeEmailUserAndSync(email, disableEventReplay); return Promise.resolve(); } } catch (error) { @@ -499,13 +492,12 @@ export function initialize( return Promise.reject(`merging failed: ${error}`); } }, - setUserID: async (userId: string, merge?: boolean) => { + setUserID: async (userId: string, disableEventReplay?: boolean) => { clearMessages(); try { - merge = getMergeDefaultValue(merge); - const result = await tryMergeUser(userId, false, merge); + const result = await tryMergeUser(userId, false, disableEventReplay); if (result) { - initializeUserIdAndSync(userId, merge); + initializeUserIdAndSync(userId, disableEventReplay); return Promise.resolve(); } } catch (error) { @@ -791,14 +783,13 @@ export function initialize( /* this will just clear the existing timeout */ handleTokenExpiration(''); }, - setEmail: async (email: string, merge?: boolean) => { + setEmail: async (email: string, disableEventReplay?: boolean) => { /* clear previous user */ clearMessages(); try { - merge = getMergeDefaultValue(merge); - const result = await tryMergeUser(email, true, merge); + const result = await tryMergeUser(email, true, disableEventReplay); if (result) { - initializeEmailUserAndSync(email, merge); + initializeEmailUserAndSync(email, disableEventReplay); try { return doRequest({ email }).catch((e) => { if (logLevel === 'verbose') { @@ -818,13 +809,12 @@ export function initialize( return Promise.reject(`merging failed: ${error}`); } }, - setUserID: async (userId: string, merge?: boolean) => { + setUserID: async (userId: string, disableEventReplay?: boolean) => { clearMessages(); try { - merge = getMergeDefaultValue(merge); - const result = await tryMergeUser(userId, false, merge); + const result = await tryMergeUser(userId, false, disableEventReplay); if (result) { - initializeUserIdAndSync(userId, merge); + initializeUserIdAndSync(userId, disableEventReplay); try { return doRequest({ userID: userId }) .then(async (token) => { From 1963926cfea30bf5229a995df97a633f35a5b111 Mon Sep 17 00:00:00 2001 From: darshan-iterable Date: Wed, 18 Sep 2024 19:03:09 +0530 Subject: [PATCH 76/88] MOB-9307 Add test to validate object created by custom event and user update api calls (#455) --- src/anonymousUserTracking/tests/constants.ts | 102 +++++ .../validateCustomEventUserUpdateAPI.test.ts | 414 ++++++++++++++++++ 2 files changed, 516 insertions(+) create mode 100644 src/anonymousUserTracking/tests/validateCustomEventUserUpdateAPI.test.ts diff --git a/src/anonymousUserTracking/tests/constants.ts b/src/anonymousUserTracking/tests/constants.ts index 9f3ff44f..2c192977 100644 --- a/src/anonymousUserTracking/tests/constants.ts +++ b/src/anonymousUserTracking/tests/constants.ts @@ -917,3 +917,105 @@ export const IS_NOT_ONE_OF_CRITERIA = { } ] }; + +export const CUSTOM_EVENT_API_TEST_CRITERIA = { + count: 1, + criterias: [ + { + criteriaId: '6', + name: 'EventCriteria', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'eventName', + comparatorType: 'Equals', + value: 'animal-found', + fieldType: 'string' + }, + { + dataType: 'customEvent', + field: 'animal-found.type', + comparatorType: 'Equals', + value: 'cat', + fieldType: 'string' + }, + { + dataType: 'customEvent', + field: 'animal-found.count', + comparatorType: 'Equals', + value: '6', + fieldType: 'string' + }, + { + dataType: 'customEvent', + field: 'animal-found.vaccinated', + comparatorType: 'Equals', + value: 'true', + fieldType: 'boolean' + } + ] + } + } + ] + } + ] + } + } + ] +}; + +export const USER_UPDATE_API_TEST_CRITERIA = { + count: 1, + criterias: [ + { + criteriaId: '6', + name: 'UserCriteria', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + field: 'furniture.furnitureType', + comparatorType: 'Equals', + value: 'Sofa', + fieldType: 'string' + }, + { + dataType: 'user', + field: 'furniture.furnitureColor', + comparatorType: 'Equals', + value: 'White', + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] +}; diff --git a/src/anonymousUserTracking/tests/validateCustomEventUserUpdateAPI.test.ts b/src/anonymousUserTracking/tests/validateCustomEventUserUpdateAPI.test.ts new file mode 100644 index 00000000..00599dc1 --- /dev/null +++ b/src/anonymousUserTracking/tests/validateCustomEventUserUpdateAPI.test.ts @@ -0,0 +1,414 @@ +import MockAdapter from 'axios-mock-adapter'; +import { baseAxiosRequest } from '../../request'; +import { + SHARED_PREFS_ANON_SESSIONS, + SHARED_PREFS_EVENT_LIST_KEY, + SHARED_PREFS_CRITERIA, + ENDPOINT_MERGE_USER, + ENDPOINT_TRACK_ANON_SESSION, + GET_CRITERIA_PATH, + GETMESSAGES_PATH +} from '../../constants'; +import { track } from '../../events'; +import { initializeWithConfig } from '../../authorization'; +import CriteriaCompletionChecker from '../criteriaCompletionChecker'; +import { updateUser } from '../../users'; +import { + CUSTOM_EVENT_API_TEST_CRITERIA, + USER_UPDATE_API_TEST_CRITERIA +} from './constants'; + +const localStorageMock = { + getItem: jest.fn(), + setItem: jest.fn(), + removeItem: jest.fn() +}; + +declare global { + function uuidv4(): string; + function getEmail(): string; + function getUserID(): string; + function setUserID(): string; +} + +// SUCCESS +const eventDataMatched = { + eventName: 'animal-found', + dataFields: { + type: 'cat', + count: 6, + vaccinated: true + }, + createNewFields: true, + eventType: 'customEvent' +}; + +// FAIL +const eventData = { + eventName: 'animal-found', + dataFields: { + type: 'cat', + count: 6, + vaccinated: true + }, + type: 'cat', + count: 6, + vaccinated: true, + createNewFields: true, + eventType: 'customEvent' +}; + +// SUCCESS +const userDataMatched = { + dataFields: { + furniture: { + furnitureType: 'Sofa', + furnitureColor: 'White' + } + }, + eventType: 'user' +}; + +// FAIL +const userData = { + dataFields: { + furniture: { + furnitureType: 'Sofa', + furnitureColor: 'White' + } + }, + furnitureType: 'Sofa', + furnitureColor: 'White', + eventType: 'user' +}; + +const initialAnonSessionInfo = { + itbl_anon_sessions: { + number_of_sessions: 1, + first_session: 123456789, + last_session: expect.any(Number) + } +}; + +const mockRequest = new MockAdapter(baseAxiosRequest); + +describe('validateCustomEventUserUpdateAPI', () => { + beforeAll(() => { + (global as any).localStorage = localStorageMock; + global.window = Object.create({ location: { hostname: 'google.com' } }); + mockRequest.onGet(GETMESSAGES_PATH).reply(200, { + data: 'something' + }); + mockRequest.onPost('/events/track').reply(200, {}); + mockRequest.onPost('/users/update').reply(200, {}); + mockRequest.onPost(ENDPOINT_MERGE_USER).reply(200, {}); + mockRequest.onGet(GET_CRITERIA_PATH).reply(200, {}); + mockRequest.onPost(ENDPOINT_TRACK_ANON_SESSION).reply(200, {}); + }); + + beforeEach(() => { + mockRequest.reset(); + mockRequest.resetHistory(); + mockRequest.onGet(GETMESSAGES_PATH).reply(200, { + data: 'something' + }); + mockRequest.onPost('/events/track').reply(200, {}); + mockRequest.onPost('/users/update').reply(200, {}); + mockRequest.onPost(ENDPOINT_MERGE_USER).reply(200, {}); + mockRequest.onGet(GET_CRITERIA_PATH).reply(200, {}); + mockRequest.onPost(ENDPOINT_TRACK_ANON_SESSION).reply(200, {}); + jest.resetAllMocks(); + jest.useFakeTimers(); + }); + + it('should not have unnecessary extra nesting when locally stored user update fields are sent to server', async () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + ...userDataMatched.dataFields, + eventType: userDataMatched.eventType + } + ]); + } + if (key === SHARED_PREFS_CRITERIA) { + return JSON.stringify(USER_UPDATE_API_TEST_CRITERIA); + } + if (key === SHARED_PREFS_ANON_SESSIONS) { + return JSON.stringify(initialAnonSessionInfo); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const localStoredCriterias = localStorage.getItem(SHARED_PREFS_CRITERIA); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + + const result = checker.getMatchedCriteria(localStoredCriterias!); + expect(result).toEqual('6'); + + const { setUserID, logout } = initializeWithConfig({ + authToken: '123', + configOptions: { enableAnonTracking: true } + }); + logout(); // logout to remove logged in users before this test + + try { + await updateUser(); + } catch (e) { + console.log(''); + } + expect(localStorage.setItem).toHaveBeenCalledWith( + SHARED_PREFS_EVENT_LIST_KEY, + expect.any(String) + ); + await setUserID('testuser123'); + + const trackEvents = mockRequest.history.post.filter( + (req) => req.url === '/users/update' + ); + + expect(trackEvents.length > 0).toBe(true); + + trackEvents.forEach((req) => { + const requestData = JSON.parse(String(req?.data)); + + expect(requestData).toHaveProperty( + 'dataFields', + userDataMatched.dataFields + ); + expect(requestData.dataFields).toHaveProperty( + 'furniture', + userDataMatched.dataFields.furniture + ); + expect(requestData.dataFields).toHaveProperty( + 'furniture.furnitureType', + userDataMatched.dataFields.furniture.furnitureType + ); + expect(requestData.dataFields).toHaveProperty( + 'furniture.furnitureColor', + userDataMatched.dataFields.furniture.furnitureColor + ); + + expect(requestData).not.toHaveProperty('furniture'); + expect(requestData).not.toHaveProperty('furnitureType'); + expect(requestData).not.toHaveProperty('furnitureColor'); + expect(requestData).not.toHaveProperty('furniture.furnitureType'); + expect(requestData).not.toHaveProperty('furniture.furnitureColor'); + }); + }); + + it('should not have unnecessary extra nesting when locally stored user update fields are sent to server - Fail', async () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + ...userData, + ...userData.dataFields, + eventType: userData.eventType + } + ]); + } + if (key === SHARED_PREFS_CRITERIA) { + return JSON.stringify(USER_UPDATE_API_TEST_CRITERIA); + } + if (key === SHARED_PREFS_ANON_SESSIONS) { + return JSON.stringify(initialAnonSessionInfo); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const localStoredCriterias = localStorage.getItem(SHARED_PREFS_CRITERIA); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + + const result = checker.getMatchedCriteria(localStoredCriterias!); + expect(result).toEqual('6'); + + const { setUserID, logout } = initializeWithConfig({ + authToken: '123', + configOptions: { enableAnonTracking: true } + }); + logout(); // logout to remove logged in users before this test + + try { + await updateUser(); + } catch (e) { + console.log(''); + } + expect(localStorage.setItem).toHaveBeenCalledWith( + SHARED_PREFS_EVENT_LIST_KEY, + expect.any(String) + ); + await setUserID('testuser123'); + + const trackEvents = mockRequest.history.post.filter( + (req) => req.url === '/users/update' + ); + + expect(trackEvents.length > 0).toBe(true); + + trackEvents.forEach((req) => { + const requestData = JSON.parse(String(req?.data)); + + expect(requestData).toHaveProperty('dataFields'); + expect(requestData.dataFields).toHaveProperty('furniture'); + expect(requestData.dataFields).toHaveProperty('furniture.furnitureType'); + expect(requestData.dataFields).toHaveProperty('furniture.furnitureColor'); + + expect(requestData).not.toHaveProperty('furniture'); + expect(requestData).not.toHaveProperty('furniture.furnitureType'); + expect(requestData).not.toHaveProperty('furniture.furnitureColor'); + expect(requestData.dataFields).toHaveProperty('furnitureType'); + expect(requestData.dataFields).toHaveProperty('furnitureColor'); + }); + }); + + it('should not have unnecessary extra nesting when locally stored custom event fields are sent to server', async () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([eventDataMatched]); + } + if (key === SHARED_PREFS_CRITERIA) { + return JSON.stringify(CUSTOM_EVENT_API_TEST_CRITERIA); + } + if (key === SHARED_PREFS_ANON_SESSIONS) { + return JSON.stringify(initialAnonSessionInfo); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const localStoredCriterias = localStorage.getItem(SHARED_PREFS_CRITERIA); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + + const result = checker.getMatchedCriteria(localStoredCriterias!); + + expect(result).toEqual('6'); + + const { setUserID, logout } = initializeWithConfig({ + authToken: '123', + configOptions: { enableAnonTracking: true } + }); + logout(); // logout to remove logged in users before this test + + try { + await track(eventDataMatched); + } catch (e) { + console.log(''); + } + expect(localStorage.setItem).toHaveBeenCalledWith( + SHARED_PREFS_EVENT_LIST_KEY, + expect.any(String) + ); + await setUserID('testuser123'); + + const trackEvents = mockRequest.history.post.filter( + (req) => req.url === '/events/track' + ); + + trackEvents.forEach((req) => { + const requestData = JSON.parse(String(req?.data)); + + expect(requestData).toHaveProperty( + 'eventName', + eventDataMatched.eventName + ); + expect(requestData).toHaveProperty( + 'dataFields', + eventDataMatched.dataFields + ); + + expect(requestData).not.toHaveProperty(eventDataMatched.eventName); + expect(requestData).not.toHaveProperty('type'); + expect(requestData).not.toHaveProperty('count'); + expect(requestData).not.toHaveProperty('vaccinated'); + expect(requestData).not.toHaveProperty('animal-found.type'); + expect(requestData).not.toHaveProperty('animal-found.count'); + expect(requestData).not.toHaveProperty('animal-found.vaccinated'); + }); + }); + + it('should not have unnecessary extra nesting when locally stored custom event fields are sent to server - Fail', async () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([eventData]); + } + if (key === SHARED_PREFS_CRITERIA) { + return JSON.stringify(CUSTOM_EVENT_API_TEST_CRITERIA); + } + if (key === SHARED_PREFS_ANON_SESSIONS) { + return JSON.stringify(initialAnonSessionInfo); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const localStoredCriterias = localStorage.getItem(SHARED_PREFS_CRITERIA); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(localStoredCriterias) + ); + expect(result).toBeNull(); + + const { setUserID, logout } = initializeWithConfig({ + authToken: '123', + configOptions: { enableAnonTracking: true } + }); + logout(); // logout to remove logged in users before this test + + try { + await track(eventData); + } catch (e) { + console.log(''); + } + expect(localStorage.setItem).toHaveBeenCalledWith( + SHARED_PREFS_EVENT_LIST_KEY, + expect.any(String) + ); + await setUserID('testuser123'); + + const trackEvents = mockRequest.history.post.filter( + (req) => req.url === '/events/track' + ); + + trackEvents.forEach((req) => { + const requestData = JSON.parse(String(req?.data)); + + expect(requestData).toHaveProperty('eventName', eventData.eventName); + expect(requestData).toHaveProperty('dataFields', eventData.dataFields); + + expect(requestData).not.toHaveProperty(eventData.eventName); + expect(requestData).toHaveProperty('type'); + expect(requestData).toHaveProperty('count'); + expect(requestData).toHaveProperty('vaccinated'); + expect(requestData).not.toHaveProperty('animal-found.type'); + expect(requestData).not.toHaveProperty('animal-found.count'); + expect(requestData).not.toHaveProperty('animal-found.vaccinated'); + }); + }); +}); From aa2a1246bf708116a7cc90aeb4f199a04e8aa3b8 Mon Sep 17 00:00:00 2001 From: darshan-iterable Date: Mon, 23 Sep 2024 20:10:44 +0530 Subject: [PATCH 77/88] [MOB-9402] update user should not be a separate call (#453) * [MOB-9402] update user should not be a separate call * feat: test cases update user should not be a separate call --- .../anonymousUserEventManager.ts | 22 ++- .../tests/userUpdate.test.ts | 142 ++++++++++++++++++ 2 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 src/anonymousUserTracking/tests/userUpdate.test.ts diff --git a/src/anonymousUserTracking/anonymousUserEventManager.ts b/src/anonymousUserTracking/anonymousUserEventManager.ts index 52920573..e1f4ac5d 100644 --- a/src/anonymousUserTracking/anonymousUserEventManager.ts +++ b/src/anonymousUserTracking/anonymousUserEventManager.ts @@ -170,6 +170,16 @@ export class AnonymousUserEventManager { private async createKnownUser(criteriaId: string) { const userData = localStorage.getItem(SHARED_PREFS_ANON_SESSIONS); + const eventList = localStorage.getItem(SHARED_PREFS_EVENT_LIST_KEY); + const events = eventList ? JSON.parse(eventList) : []; + + const dataFields = { + ...events.find( + (event: any) => event[SHARED_PREFS_EVENT_TYPE] === UPDATE_USER + ) + }; + delete dataFields[SHARED_PREFS_EVENT_TYPE]; + const userId = uuidv4(); if (userData) { @@ -179,7 +189,8 @@ export class AnonymousUserEventManager { user: { userId, mergeNestedObjects: true, - createNewFields: true + createNewFields: true, + dataFields }, createdAt: this.getCurrentTime(), deviceInfo: { @@ -206,6 +217,15 @@ export class AnonymousUserEventManager { } }); if (response?.status === 200) { + // Update local storage, remove updateUser from local storage + localStorage.setItem( + SHARED_PREFS_EVENT_LIST_KEY, + JSON.stringify( + events.filter( + (event: any) => event[SHARED_PREFS_EVENT_TYPE] !== UPDATE_USER + ) + ) + ); if (anonUserIdSetter !== null) { await anonUserIdSetter(userId); } diff --git a/src/anonymousUserTracking/tests/userUpdate.test.ts b/src/anonymousUserTracking/tests/userUpdate.test.ts new file mode 100644 index 00000000..a899dc58 --- /dev/null +++ b/src/anonymousUserTracking/tests/userUpdate.test.ts @@ -0,0 +1,142 @@ +import MockAdapter from 'axios-mock-adapter'; +import { baseAxiosRequest } from '../../request'; +import { + SHARED_PREFS_ANON_SESSIONS, + SHARED_PREFS_EVENT_LIST_KEY, + SHARED_PREFS_CRITERIA, + GET_CRITERIA_PATH, + ENDPOINT_TRACK_ANON_SESSION, + ENDPOINT_MERGE_USER +} from '../../constants'; +import { updateUser } from '../../users'; +import { initializeWithConfig } from '../../authorization'; +import { CUSTOM_EVENT_API_TEST_CRITERIA } from './constants'; + +const localStorageMock = { + getItem: jest.fn(), + setItem: jest.fn(), + removeItem: jest.fn() +}; + +declare global { + function uuidv4(): string; + function getEmail(): string; + function getUserID(): string; + function setUserID(): string; +} + +const eventDataMatched = { + eventName: 'animal-found', + dataFields: { + type: 'cat', + count: 6, + vaccinated: true + }, + createNewFields: true, + eventType: 'customEvent' +}; + +const userDataMatched = { + dataFields: { + furniture: { + furnitureType: 'Sofa', + furnitureColor: 'White' + } + }, + eventType: 'user' +}; + +const initialAnonSessionInfo = { + itbl_anon_sessions: { + number_of_sessions: 1, + first_session: 123456789, + last_session: expect.any(Number) + } +}; + +const mockRequest = new MockAdapter(baseAxiosRequest); + +describe('UserUpdate', () => { + beforeAll(() => { + (global as any).localStorage = localStorageMock; + global.window = Object.create({ location: { hostname: 'google.com' } }); + mockRequest.onPost('/events/track').reply(200, {}); + mockRequest.onPost('/users/update').reply(200, {}); + mockRequest.onGet(GET_CRITERIA_PATH).reply(200, {}); + mockRequest.onPost(ENDPOINT_TRACK_ANON_SESSION).reply(200, {}); + }); + + beforeEach(() => { + mockRequest.reset(); + mockRequest.resetHistory(); + mockRequest.onPost('/events/track').reply(200, {}); + mockRequest.onPost('/users/update').reply(200, {}); + mockRequest.onPost(ENDPOINT_MERGE_USER).reply(200, {}); + mockRequest.onGet(GET_CRITERIA_PATH).reply(200, {}); + mockRequest.onPost(ENDPOINT_TRACK_ANON_SESSION).reply(200, {}); + jest.resetAllMocks(); + jest.useFakeTimers(); + }); + + it('should not have unnecessary extra nesting when locally stored user update fields are sent to server', async () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + ...userDataMatched.dataFields, + eventType: userDataMatched.eventType + }, + eventDataMatched + ]); + } + if (key === SHARED_PREFS_CRITERIA) { + return JSON.stringify(CUSTOM_EVENT_API_TEST_CRITERIA); + } + if (key === SHARED_PREFS_ANON_SESSIONS) { + return JSON.stringify(initialAnonSessionInfo); + } + return null; + }); + + const { logout } = initializeWithConfig({ + authToken: '123', + configOptions: { enableAnonTracking: true } + }); + logout(); // logout to remove logged in users before this test + + try { + await updateUser(); + } catch (e) { + console.log(''); + } + expect(localStorage.setItem).toHaveBeenCalledWith( + SHARED_PREFS_EVENT_LIST_KEY, + expect.any(String) + ); + + const trackEvents = mockRequest.history.post.filter( + (req) => req.url === '/anonymoususer/events/session' + ); + + expect(trackEvents.length > 0).toBe(true); + + trackEvents.forEach((req) => { + const requestData = JSON.parse(String(req?.data)); + + expect(requestData).toHaveProperty('user'); + expect(requestData.user).toHaveProperty( + 'dataFields', + userDataMatched.dataFields + ); + expect(requestData.user.dataFields).toHaveProperty( + 'furniture', + userDataMatched.dataFields.furniture + ); + }); + + const trackEventsUserUpdate = mockRequest.history.post.filter( + (req) => req.url === '/users/update' + ); + expect(trackEventsUserUpdate.length === 0).toBe(true); + }); +}); From 51bd3a23f157fb916f61fc65c5e0bed12e99d874 Mon Sep 17 00:00:00 2001 From: darshan-iterable Date: Wed, 25 Sep 2024 20:04:50 +0530 Subject: [PATCH 78/88] [MOB-9568] update "criterias" to "criteriaSets" (#456) --- .../complexCriteria.test.ts | 16 +-- .../criteriaCompletionChecker.ts | 4 +- .../tests/anonymousUserEventManager.test.ts | 41 ++++--- .../tests/combinationLogicCriteria.test.ts | 48 ++++---- .../tests/compareArrayDataTypes.test.ts | 1 - .../tests/complexCriteria.test.ts | 22 ++-- src/anonymousUserTracking/tests/constants.ts | 79 +++++++++---- .../tests/criteriaCompletionChecker.test.ts | 46 ++++---- .../tests/userMergeScenarios.test.ts | 111 ++++++++---------- .../validateCustomEventUserUpdateAPI.test.ts | 16 +-- 10 files changed, 207 insertions(+), 177 deletions(-) diff --git a/src/anonymousUserTracking/complexCriteria.test.ts b/src/anonymousUserTracking/complexCriteria.test.ts index 7aba642f..8d8eb7cd 100644 --- a/src/anonymousUserTracking/complexCriteria.test.ts +++ b/src/anonymousUserTracking/complexCriteria.test.ts @@ -51,7 +51,7 @@ describe('complexCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '98', name: 'Custom Event', @@ -215,7 +215,7 @@ describe('complexCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '98', name: 'Custom Event', @@ -378,7 +378,7 @@ describe('complexCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '99', name: 'Custom Event', @@ -539,7 +539,7 @@ describe('complexCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '99', name: 'Custom Event', @@ -725,7 +725,7 @@ describe('complexCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '100', name: 'Custom Event', @@ -893,7 +893,7 @@ describe('complexCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '100', name: 'Custom Event', @@ -1044,7 +1044,7 @@ describe('complexCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '101', name: 'Complex Criteria 4: (NOT 9) AND 10', @@ -1143,7 +1143,7 @@ describe('complexCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '101', name: 'Complex Criteria 4: (NOT 9) AND 10', diff --git a/src/anonymousUserTracking/criteriaCompletionChecker.ts b/src/anonymousUserTracking/criteriaCompletionChecker.ts index 5fad1d94..db2121a8 100644 --- a/src/anonymousUserTracking/criteriaCompletionChecker.ts +++ b/src/anonymousUserTracking/criteriaCompletionChecker.ts @@ -47,8 +47,8 @@ class CriteriaCompletionChecker { try { const json = JSON.parse(criteriaData); - if (json.criterias) { - criteriaId = this.findMatchedCriteria(json.criterias); + if (json.criteriaSets) { + criteriaId = this.findMatchedCriteria(json.criteriaSets); } } catch (e) { this.handleJSONException(e); diff --git a/src/anonymousUserTracking/tests/anonymousUserEventManager.test.ts b/src/anonymousUserTracking/tests/anonymousUserEventManager.test.ts index caac0570..dc55abf1 100644 --- a/src/anonymousUserTracking/tests/anonymousUserEventManager.test.ts +++ b/src/anonymousUserTracking/tests/anonymousUserEventManager.test.ts @@ -14,11 +14,11 @@ const localStorageMock = { removeItem: jest.fn() }; -jest.mock('../criteriaCompletionChecker', () => { - return jest.fn().mockImplementation(() => ({ +jest.mock('../criteriaCompletionChecker', () => + jest.fn().mockImplementation(() => ({ getMatchedCriteria: jest.fn() - })); -}); + })) +); jest.mock('../../request', () => ({ baseIterableRequest: jest.fn() @@ -88,7 +88,7 @@ describe('AnonymousUserEventManager', () => { if (key === 'criteria') { return JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '6', name: 'EventCriteria', @@ -122,7 +122,8 @@ describe('AnonymousUserEventManager', () => { } ] }); - } else if (key === SHARED_PREFS_ANON_SESSIONS) { + } + if (key === SHARED_PREFS_ANON_SESSIONS) { return JSON.stringify(userData); } return null; @@ -150,7 +151,7 @@ describe('AnonymousUserEventManager', () => { if (key === 'criteria') { return JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '6', name: 'EventCriteria', @@ -184,7 +185,8 @@ describe('AnonymousUserEventManager', () => { } ] }); - } else if (key === SHARED_PREFS_EVENT_LIST_KEY) { + } + if (key === SHARED_PREFS_EVENT_LIST_KEY) { return JSON.stringify(eventData); } return null; @@ -210,7 +212,7 @@ describe('AnonymousUserEventManager', () => { if (key === 'criteria') { return JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '6', name: 'EventCriteria', @@ -244,7 +246,8 @@ describe('AnonymousUserEventManager', () => { } ] }); - } else if (key === SHARED_PREFS_EVENT_LIST_KEY) { + } + if (key === SHARED_PREFS_EVENT_LIST_KEY) { return JSON.stringify(eventData); } return null; @@ -269,7 +272,8 @@ describe('AnonymousUserEventManager', () => { (localStorage.getItem as jest.Mock).mockImplementation((key) => { if (key === 'criteria') { return null; - } else if (key === SHARED_PREFS_EVENT_LIST_KEY) { + } + if (key === SHARED_PREFS_EVENT_LIST_KEY) { return JSON.stringify(eventData); } return null; @@ -295,7 +299,7 @@ describe('AnonymousUserEventManager', () => { if (key === 'criteria') { return JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '6', name: 'UpdateUserCriteria', @@ -329,7 +333,8 @@ describe('AnonymousUserEventManager', () => { } ] }); - } else if (key === SHARED_PREFS_EVENT_LIST_KEY) { + } + if (key === SHARED_PREFS_EVENT_LIST_KEY) { return JSON.stringify(userData); } return null; @@ -371,7 +376,7 @@ describe('AnonymousUserEventManager', () => { if (key === 'criteria') { return JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '6', name: 'shoppingCartItemsCriteria', @@ -412,7 +417,8 @@ describe('AnonymousUserEventManager', () => { } ] }); - } else if (key === SHARED_PREFS_EVENT_LIST_KEY) { + } + if (key === SHARED_PREFS_EVENT_LIST_KEY) { return JSON.stringify(userData); } return null; @@ -454,7 +460,7 @@ describe('AnonymousUserEventManager', () => { if (key === 'criteria') { return JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '6', name: 'CartUpdateItemsCriteria', @@ -495,7 +501,8 @@ describe('AnonymousUserEventManager', () => { } ] }); - } else if (key === SHARED_PREFS_EVENT_LIST_KEY) { + } + if (key === SHARED_PREFS_EVENT_LIST_KEY) { return JSON.stringify(userData); } return null; diff --git a/src/anonymousUserTracking/tests/combinationLogicCriteria.test.ts b/src/anonymousUserTracking/tests/combinationLogicCriteria.test.ts index ca808aa7..17d339bb 100644 --- a/src/anonymousUserTracking/tests/combinationLogicCriteria.test.ts +++ b/src/anonymousUserTracking/tests/combinationLogicCriteria.test.ts @@ -39,7 +39,7 @@ describe('CombinationLogicCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '1', name: 'Combination Logic - Contact Property AND Custom Event', @@ -121,7 +121,7 @@ describe('CombinationLogicCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '1', name: 'Combination Logic - Contact Property AND Custom Event', @@ -203,7 +203,7 @@ describe('CombinationLogicCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '2', name: 'Combination Logic - Contact Property OR Custom Event', @@ -285,7 +285,7 @@ describe('CombinationLogicCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '2', name: 'Combination Logic - Contact Property OR Custom Event', @@ -370,7 +370,7 @@ describe('CombinationLogicCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '3', name: 'Combination Logic - Contact Property NOR (NOT) Custom Event', @@ -452,7 +452,7 @@ describe('CombinationLogicCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '3', name: 'Combination Logic - Contact Property NOR (NOT) Custom Event', @@ -534,7 +534,7 @@ describe('CombinationLogicCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '4', name: 'Combination Logic - UpdateCart AND Contact Property', @@ -616,7 +616,7 @@ describe('CombinationLogicCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '4', name: 'Combination Logic - UpdateCart AND Contact Property', @@ -698,7 +698,7 @@ describe('CombinationLogicCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '5', name: 'Combination Logic - UpdateCart OR Contact Property', @@ -780,7 +780,7 @@ describe('CombinationLogicCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '5', name: 'Combination Logic - UpdateCart OR Contact Property', @@ -862,7 +862,7 @@ describe('CombinationLogicCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '6', name: 'Combination Logic - UpdateCart NOR (NOT) Contact Property', @@ -944,7 +944,7 @@ describe('CombinationLogicCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '6', name: 'Combination Logic - UpdateCart NOR (NOT) Contact Property', @@ -1026,7 +1026,7 @@ describe('CombinationLogicCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '7', name: 'Combination Logic - Purchase AND UpdateCart', @@ -1108,7 +1108,7 @@ describe('CombinationLogicCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '7', name: 'Combination Logic - Purchase AND UpdateCart', @@ -1190,7 +1190,7 @@ describe('CombinationLogicCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '8', name: 'Combination Logic - Purchase OR UpdateCart', @@ -1272,7 +1272,7 @@ describe('CombinationLogicCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '8', name: 'Combination Logic - Purchase OR UpdateCart', @@ -1354,7 +1354,7 @@ describe('CombinationLogicCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '9', name: 'Combination Logic - Purchase NOR (NOT) UpdateCart', @@ -1436,7 +1436,7 @@ describe('CombinationLogicCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '9', name: 'Combination Logic - Purchase NOR (NOT) UpdateCart', @@ -1518,7 +1518,7 @@ describe('CombinationLogicCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '10', name: 'Combination Logic - Custom Event AND Purchase', @@ -1600,7 +1600,7 @@ describe('CombinationLogicCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '10', name: 'Combination Logic - Custom Event AND Purchase', @@ -1682,7 +1682,7 @@ describe('CombinationLogicCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '11', name: 'Combination Logic - Custom Event OR Purchase', @@ -1764,7 +1764,7 @@ describe('CombinationLogicCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '11', name: 'Combination Logic - Custom Event OR Purchase', @@ -1846,7 +1846,7 @@ describe('CombinationLogicCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '12', name: 'Combination Logic - Custom Event NOR (NOT) Purchase', @@ -1928,7 +1928,7 @@ describe('CombinationLogicCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '12', name: 'Combination Logic - Custom Event NOR (NOT) Purchase', diff --git a/src/anonymousUserTracking/tests/compareArrayDataTypes.test.ts b/src/anonymousUserTracking/tests/compareArrayDataTypes.test.ts index a0fe2b31..4440ca14 100644 --- a/src/anonymousUserTracking/tests/compareArrayDataTypes.test.ts +++ b/src/anonymousUserTracking/tests/compareArrayDataTypes.test.ts @@ -66,7 +66,6 @@ describe('compareArrayDataTypes', () => { expect(result).toEqual('285'); }); - it('should return criteriaId null (compare array Equal - No match)', () => { (localStorage.getItem as jest.Mock).mockImplementation((key) => { if (key === SHARED_PREFS_EVENT_LIST_KEY) { diff --git a/src/anonymousUserTracking/tests/complexCriteria.test.ts b/src/anonymousUserTracking/tests/complexCriteria.test.ts index 03b4de27..06f5cbdf 100644 --- a/src/anonymousUserTracking/tests/complexCriteria.test.ts +++ b/src/anonymousUserTracking/tests/complexCriteria.test.ts @@ -51,7 +51,7 @@ describe('complexCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '98', name: 'Custom Event', @@ -215,7 +215,7 @@ describe('complexCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '98', name: 'Custom Event', @@ -378,7 +378,7 @@ describe('complexCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '99', name: 'Custom Event', @@ -539,7 +539,7 @@ describe('complexCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '99', name: 'Custom Event', @@ -725,7 +725,7 @@ describe('complexCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '100', name: 'Custom Event', @@ -893,7 +893,7 @@ describe('complexCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '100', name: 'Custom Event', @@ -1044,7 +1044,7 @@ describe('complexCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '101', name: 'Complex Criteria 4: (NOT 9) AND 10', @@ -1143,7 +1143,7 @@ describe('complexCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '101', name: 'Complex Criteria 4: (NOT 9) AND 10', @@ -1248,7 +1248,7 @@ describe('complexCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '134', name: 'Min-Max 2', @@ -1336,7 +1336,7 @@ describe('complexCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '151', name: 'test criteria', @@ -1483,7 +1483,7 @@ describe('complexCriteria', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '151', name: 'test criteria', diff --git a/src/anonymousUserTracking/tests/constants.ts b/src/anonymousUserTracking/tests/constants.ts index 2c192977..c99ed181 100644 --- a/src/anonymousUserTracking/tests/constants.ts +++ b/src/anonymousUserTracking/tests/constants.ts @@ -2,7 +2,7 @@ export const DATA_TYPE_COMPARATOR_EQUALS = { count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '285', name: 'Criteria_EventTimeStamp_3_Long', @@ -60,7 +60,7 @@ export const DATA_TYPE_COMPARATOR_EQUALS = { export const DATA_TYPE_COMPARATOR_DOES_NOT_EQUAL = { count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '285', name: 'Criteria_EventTimeStamp_3_Long', @@ -111,7 +111,7 @@ export const DATA_TYPE_COMPARATOR_DOES_NOT_EQUAL = { export const DATA_TYPE_COMPARATOR_LESS_THAN = { count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '289', name: 'Criteria_EventTimeStamp_3_Long', @@ -155,7 +155,7 @@ export const DATA_TYPE_COMPARATOR_LESS_THAN = { export const DATA_TYPE_COMPARATOR_LESS_THAN_OR_EQUAL_TO = { count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '290', name: 'Criteria_EventTimeStamp_3_Long', @@ -199,7 +199,7 @@ export const DATA_TYPE_COMPARATOR_LESS_THAN_OR_EQUAL_TO = { export const DATA_TYPE_COMPARATOR_GREATER_THAN = { count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '290', name: 'Criteria_EventTimeStamp_3_Long', @@ -243,7 +243,7 @@ export const DATA_TYPE_COMPARATOR_GREATER_THAN = { export const DATA_TYPE_COMPARATOR_GREATER_THAN_OR_EQUAL_TO = { count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '291', name: 'Criteria_EventTimeStamp_3_Long', @@ -287,7 +287,7 @@ export const DATA_TYPE_COMPARATOR_GREATER_THAN_OR_EQUAL_TO = { export const DATA_TYPE_COMPARATOR_IS_SET = { count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '285', name: 'Criteria_EventTimeStamp_3_Long', @@ -345,7 +345,7 @@ export const DATA_TYPE_COMPARATOR_IS_SET = { export const ARRAY_EQUAL_CRITERIA = { count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '285', name: 'Criteria_Array_Equal', @@ -432,7 +432,7 @@ export const ARRAY_EQUAL_CRITERIA = { export const ARRAY_DOES_NOT_EQUAL_CRITERIA = { count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '285', name: 'Criteria_Array_DoesNotEqual', @@ -519,7 +519,7 @@ export const ARRAY_DOES_NOT_EQUAL_CRITERIA = { export const ARRAY_GREATER_THAN_CRITERIA = { count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '285', name: 'Criteria_EventTimeStamp_3_Long', @@ -557,7 +557,7 @@ export const ARRAY_GREATER_THAN_CRITERIA = { export const ARRAY_LESS_THAN_CRITERIA = { count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '285', name: 'Criteria_EventTimeStamp_3_Long', @@ -595,7 +595,7 @@ export const ARRAY_LESS_THAN_CRITERIA = { export const ARRAY_GREATER_THAN_EQUAL_TO_CRITERIA = { count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '285', name: 'Criteria_EventTimeStamp_3_Long', @@ -633,7 +633,7 @@ export const ARRAY_GREATER_THAN_EQUAL_TO_CRITERIA = { export const ARRAY_LESS_THAN_EQUAL_TO_CRITERIA = { count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '285', name: 'Criteria_EventTimeStamp_3_Long', @@ -671,7 +671,7 @@ export const ARRAY_LESS_THAN_EQUAL_TO_CRITERIA = { export const ARRAY_CONTAINS_CRITERIA = { count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '285', name: 'Criteria_EventTimeStamp_3_Long', @@ -709,7 +709,7 @@ export const ARRAY_CONTAINS_CRITERIA = { export const ARRAY_STARTSWITH_CRITERIA = { count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '285', name: 'Criteria_EventTimeStamp_3_Long', @@ -747,7 +747,7 @@ export const ARRAY_STARTSWITH_CRITERIA = { export const ARRAY_MATCHREGEX_CRITERIA = { count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '285', name: 'Criteria_EventTimeStamp_3_Long', @@ -785,7 +785,7 @@ export const ARRAY_MATCHREGEX_CRITERIA = { export const NESTED_CRITERIA = { count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '168', name: 'nested testing', @@ -836,7 +836,7 @@ export const NESTED_CRITERIA = { export const IS_ONE_OF_CRITERIA = { count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '299', name: 'Criteria_Is_One_of', @@ -878,7 +878,7 @@ export const IS_ONE_OF_CRITERIA = { export const IS_NOT_ONE_OF_CRITERIA = { count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '299', name: 'Criteria_Is_Not_One_of', @@ -920,7 +920,7 @@ export const IS_NOT_ONE_OF_CRITERIA = { export const CUSTOM_EVENT_API_TEST_CRITERIA = { count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '6', name: 'EventCriteria', @@ -978,7 +978,7 @@ export const CUSTOM_EVENT_API_TEST_CRITERIA = { export const USER_UPDATE_API_TEST_CRITERIA = { count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '6', name: 'UserCriteria', @@ -1019,3 +1019,40 @@ export const USER_UPDATE_API_TEST_CRITERIA = { } ] }; + +export const USER_MERGE_SCENARIO_CRITERIA = { + count: 1, + criteriaSets: [ + { + criteriaId: '6', + name: 'EventCriteria', + createdAt: 1704754280210, + updatedAt: 1704754280210, + searchQuery: { + combinator: 'Or', + searchQueries: [ + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'eventName', + comparatorType: 'Equals', + value: 'testEvent', + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] +}; diff --git a/src/anonymousUserTracking/tests/criteriaCompletionChecker.test.ts b/src/anonymousUserTracking/tests/criteriaCompletionChecker.test.ts index 3e3cf4b5..931a188c 100644 --- a/src/anonymousUserTracking/tests/criteriaCompletionChecker.test.ts +++ b/src/anonymousUserTracking/tests/criteriaCompletionChecker.test.ts @@ -63,7 +63,7 @@ describe('CriteriaCompletionChecker', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '6', name: 'EventCriteria', @@ -134,7 +134,7 @@ describe('CriteriaCompletionChecker', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '6', name: 'EventCriteria', @@ -208,7 +208,7 @@ describe('CriteriaCompletionChecker', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '6', name: 'EventCriteria', @@ -280,7 +280,7 @@ describe('CriteriaCompletionChecker', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '6', name: 'EventCriteria', @@ -359,7 +359,7 @@ describe('CriteriaCompletionChecker', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '6', name: 'EventCriteria', @@ -437,7 +437,7 @@ describe('CriteriaCompletionChecker', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '6', name: 'EventCriteria', @@ -516,7 +516,7 @@ describe('CriteriaCompletionChecker', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '6', name: 'shoppingCartItemsCriteria', @@ -587,7 +587,7 @@ describe('CriteriaCompletionChecker', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '6', name: 'EventCriteria', @@ -627,7 +627,7 @@ describe('CriteriaCompletionChecker', () => { const result1 = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '6', name: 'EventCriteria', @@ -691,7 +691,7 @@ describe('CriteriaCompletionChecker', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '6', name: 'EventCriteria', @@ -758,7 +758,7 @@ describe('CriteriaCompletionChecker', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '97', name: 'User', @@ -849,7 +849,7 @@ describe('CriteriaCompletionChecker', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '97', name: 'User', @@ -942,7 +942,7 @@ describe('CriteriaCompletionChecker', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '94', name: 'Custom Event', @@ -1034,7 +1034,7 @@ describe('CriteriaCompletionChecker', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '94', name: 'Custom Event', @@ -1129,7 +1129,7 @@ describe('CriteriaCompletionChecker', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '96', name: 'Purchase', @@ -1222,7 +1222,7 @@ describe('CriteriaCompletionChecker', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '96', name: 'Purchase', @@ -1308,7 +1308,7 @@ describe('CriteriaCompletionChecker', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '95', name: 'UpdateCart: isSet Comparator', @@ -1391,7 +1391,7 @@ describe('CriteriaCompletionChecker', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '95', name: 'UpdateCart: isSet Comparator', @@ -1477,7 +1477,7 @@ describe('CriteriaCompletionChecker', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '100', name: 'User', @@ -1550,7 +1550,7 @@ describe('CriteriaCompletionChecker', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '194', name: 'Contact: Phone Number != 57688559', @@ -1614,7 +1614,7 @@ describe('CriteriaCompletionChecker', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '293', name: 'Contact: subscribed != false', @@ -1683,7 +1683,7 @@ describe('CriteriaCompletionChecker', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '297', name: 'Purchase: shoppingCartItems.quantity != 12345678', @@ -1752,7 +1752,7 @@ describe('CriteriaCompletionChecker', () => { const result = checker.getMatchedCriteria( JSON.stringify({ count: 1, - criterias: [ + criteriaSets: [ { criteriaId: '298', name: 'Purchase: shoppingCartItems.price != 105', diff --git a/src/anonymousUserTracking/tests/userMergeScenarios.test.ts b/src/anonymousUserTracking/tests/userMergeScenarios.test.ts index 5451b761..d7a5dd4b 100644 --- a/src/anonymousUserTracking/tests/userMergeScenarios.test.ts +++ b/src/anonymousUserTracking/tests/userMergeScenarios.test.ts @@ -13,44 +13,9 @@ import { import { track } from '../../events'; import { getInAppMessages } from '../../inapp'; import { baseAxiosRequest } from '../../request'; -jest.setTimeout(20000); // Set the timeout to 10 seconds +import { USER_MERGE_SCENARIO_CRITERIA } from './constants'; -const mockCriteria = { - count: 1, - criterias: [ - { - criteriaId: '6', - name: 'EventCriteria', - createdAt: 1704754280210, - updatedAt: 1704754280210, - searchQuery: { - combinator: 'Or', - searchQueries: [ - { - combinator: 'Or', - searchQueries: [ - { - dataType: 'customEvent', - searchCombo: { - combinator: 'And', - searchQueries: [ - { - dataType: 'customEvent', - field: 'eventName', - comparatorType: 'Equals', - value: 'testEvent', - fieldType: 'string' - } - ] - } - } - ] - } - ] - } - } - ] -}; +jest.setTimeout(20000); // Set the timeout to 10 seconds const localStorageMock = { getItem: jest.fn(), @@ -87,7 +52,7 @@ declare global { function setUserID(): string; } const mockRequest = new MockAdapter(baseAxiosRequest); -//const mockOnPostSpy = jest.spyOn(mockRequest, 'onPost'); +// const mockOnPostSpy = jest.spyOn(mockRequest, 'onPost'); describe('UserMergeScenariosTests', () => { beforeAll(() => { @@ -116,9 +81,11 @@ describe('UserMergeScenariosTests', () => { (localStorage.getItem as jest.Mock).mockImplementation((key) => { if (key === SHARED_PREFS_EVENT_LIST_KEY) { return JSON.stringify([eventData]); - } else if (key === SHARED_PREFS_CRITERIA) { - return JSON.stringify(mockCriteria); - } else if (key === SHARED_PREFS_ANON_SESSIONS) { + } + if (key === SHARED_PREFS_CRITERIA) { + return JSON.stringify(USER_MERGE_SCENARIO_CRITERIA); + } + if (key === SHARED_PREFS_ANON_SESSIONS) { return JSON.stringify(initialAnonSessionInfo); } return null; @@ -185,7 +152,9 @@ describe('UserMergeScenariosTests', () => { (call) => call[0] === SHARED_PREFS_EVENT_LIST_KEY ); // count 2 means it removed items and so syncEvents was called - // because removeItem gets called one time for the key in case of logout and 2nd time on syncevents + + // because removeItem gets called one time for + // the key in case of logout and 2nd time on syncevents expect(removeItemCalls.length).toBe(2); const mergePostRequestData = mockRequest.history.post.find( (req) => req.url === ENDPOINT_MERGE_USER @@ -218,7 +187,9 @@ describe('UserMergeScenariosTests', () => { (call) => call[0] === SHARED_PREFS_EVENT_LIST_KEY ); // count 2 means it removed items and so syncEvents was called - // because removeItem gets called one time for the key in case of logout and 2nd time on syncevents + + // because removeItem gets called one time for + // the key in case of logout and 2nd time on syncevents expect(removeItemCalls.length).toBe(2); const mergePostRequestData = mockRequest.history.post.find( (req) => req.url === ENDPOINT_MERGE_USER @@ -230,9 +201,11 @@ describe('UserMergeScenariosTests', () => { (localStorage.getItem as jest.Mock).mockImplementation((key) => { if (key === SHARED_PREFS_EVENT_LIST_KEY) { return JSON.stringify([eventDataMatched]); - } else if (key === SHARED_PREFS_CRITERIA) { - return JSON.stringify(mockCriteria); - } else if (key === SHARED_PREFS_ANON_SESSIONS) { + } + if (key === SHARED_PREFS_CRITERIA) { + return JSON.stringify(USER_MERGE_SCENARIO_CRITERIA); + } + if (key === SHARED_PREFS_ANON_SESSIONS) { return JSON.stringify(initialAnonSessionInfo); } return null; @@ -266,9 +239,11 @@ describe('UserMergeScenariosTests', () => { (localStorage.getItem as jest.Mock).mockImplementation((key) => { if (key === SHARED_PREFS_EVENT_LIST_KEY) { return JSON.stringify([eventDataMatched]); - } else if (key === SHARED_PREFS_CRITERIA) { - return JSON.stringify(mockCriteria); - } else if (key === SHARED_PREFS_ANON_SESSIONS) { + } + if (key === SHARED_PREFS_CRITERIA) { + return JSON.stringify(USER_MERGE_SCENARIO_CRITERIA); + } + if (key === SHARED_PREFS_ANON_SESSIONS) { return JSON.stringify(initialAnonSessionInfo); } return null; @@ -306,11 +281,14 @@ describe('UserMergeScenariosTests', () => { (localStorage.getItem as jest.Mock).mockImplementation((key) => { if (key === SHARED_PREFS_EVENT_LIST_KEY) { return JSON.stringify([eventDataMatched]); - } else if (key === SHARED_PREFS_CRITERIA) { - return JSON.stringify(mockCriteria); - } else if (key === SHARED_PREFS_ANON_SESSIONS) { + } + if (key === SHARED_PREFS_CRITERIA) { + return JSON.stringify(USER_MERGE_SCENARIO_CRITERIA); + } + if (key === SHARED_PREFS_ANON_SESSIONS) { return JSON.stringify(initialAnonSessionInfo); - } else if (key === SHARED_PREF_ANON_USER_ID) { + } + if (key === SHARED_PREF_ANON_USER_ID) { return '123e4567-e89b-12d3-a456-426614174000'; } return null; @@ -489,7 +467,9 @@ describe('UserMergeScenariosTests', () => { (call) => call[0] === SHARED_PREFS_EVENT_LIST_KEY ); // count 2 means it removed items and so syncEvents was called - // because removeItem gets called one time for the key in case of logout and 2nd time on syncevents + + // because removeItem gets called one time for + // the key in case of logout and 2nd time on syncevents expect(removeItemCalls.length).toBe(2); const mergePostRequestData = mockRequest.history.post.find( (req) => req.url === ENDPOINT_MERGE_USER @@ -522,7 +502,9 @@ describe('UserMergeScenariosTests', () => { (call) => call[0] === SHARED_PREFS_EVENT_LIST_KEY ); // count 2 means it removed items and so syncEvents was called - // because removeItem gets called one time for the key in case of logout and 2nd time on syncevents + + // because removeItem gets called one time for + // the key in case of logout and 2nd time on syncevents expect(removeItemCalls.length).toBe(2); const mergePostRequestData = mockRequest.history.post.find( (req) => req.url === ENDPOINT_MERGE_USER @@ -534,9 +516,11 @@ describe('UserMergeScenariosTests', () => { (localStorage.getItem as jest.Mock).mockImplementation((key) => { if (key === SHARED_PREFS_EVENT_LIST_KEY) { return JSON.stringify([eventDataMatched]); - } else if (key === SHARED_PREFS_CRITERIA) { - return JSON.stringify(mockCriteria); - } else if (key === SHARED_PREFS_ANON_SESSIONS) { + } + if (key === SHARED_PREFS_CRITERIA) { + return JSON.stringify(USER_MERGE_SCENARIO_CRITERIA); + } + if (key === SHARED_PREFS_ANON_SESSIONS) { return JSON.stringify(initialAnonSessionInfo); } return null; @@ -573,11 +557,14 @@ describe('UserMergeScenariosTests', () => { (localStorage.getItem as jest.Mock).mockImplementation((key) => { if (key === SHARED_PREFS_EVENT_LIST_KEY) { return JSON.stringify([eventDataMatched]); - } else if (key === SHARED_PREFS_CRITERIA) { - return JSON.stringify(mockCriteria); - } else if (key === SHARED_PREFS_ANON_SESSIONS) { + } + if (key === SHARED_PREFS_CRITERIA) { + return JSON.stringify(USER_MERGE_SCENARIO_CRITERIA); + } + if (key === SHARED_PREFS_ANON_SESSIONS) { return JSON.stringify(initialAnonSessionInfo); - } else if (key === SHARED_PREF_ANON_USER_ID) { + } + if (key === SHARED_PREF_ANON_USER_ID) { return '123e4567-e89b-12d3-a456-426614174000'; } return null; diff --git a/src/anonymousUserTracking/tests/validateCustomEventUserUpdateAPI.test.ts b/src/anonymousUserTracking/tests/validateCustomEventUserUpdateAPI.test.ts index 00599dc1..3d98ff6e 100644 --- a/src/anonymousUserTracking/tests/validateCustomEventUserUpdateAPI.test.ts +++ b/src/anonymousUserTracking/tests/validateCustomEventUserUpdateAPI.test.ts @@ -144,13 +144,13 @@ describe('validateCustomEventUserUpdateAPI', () => { SHARED_PREFS_EVENT_LIST_KEY ); - const localStoredCriterias = localStorage.getItem(SHARED_PREFS_CRITERIA); + const localStoredCriteriaSets = localStorage.getItem(SHARED_PREFS_CRITERIA); const checker = new CriteriaCompletionChecker( localStoredEventList === null ? '' : localStoredEventList ); - const result = checker.getMatchedCriteria(localStoredCriterias!); + const result = checker.getMatchedCriteria(localStoredCriteriaSets!); expect(result).toEqual('6'); const { setUserID, logout } = initializeWithConfig({ @@ -228,13 +228,13 @@ describe('validateCustomEventUserUpdateAPI', () => { SHARED_PREFS_EVENT_LIST_KEY ); - const localStoredCriterias = localStorage.getItem(SHARED_PREFS_CRITERIA); + const localStoredCriteriaSets = localStorage.getItem(SHARED_PREFS_CRITERIA); const checker = new CriteriaCompletionChecker( localStoredEventList === null ? '' : localStoredEventList ); - const result = checker.getMatchedCriteria(localStoredCriterias!); + const result = checker.getMatchedCriteria(localStoredCriteriaSets!); expect(result).toEqual('6'); const { setUserID, logout } = initializeWithConfig({ @@ -294,13 +294,13 @@ describe('validateCustomEventUserUpdateAPI', () => { SHARED_PREFS_EVENT_LIST_KEY ); - const localStoredCriterias = localStorage.getItem(SHARED_PREFS_CRITERIA); + const localStoredCriteriaSets = localStorage.getItem(SHARED_PREFS_CRITERIA); const checker = new CriteriaCompletionChecker( localStoredEventList === null ? '' : localStoredEventList ); - const result = checker.getMatchedCriteria(localStoredCriterias!); + const result = checker.getMatchedCriteria(localStoredCriteriaSets!); expect(result).toEqual('6'); @@ -365,13 +365,13 @@ describe('validateCustomEventUserUpdateAPI', () => { SHARED_PREFS_EVENT_LIST_KEY ); - const localStoredCriterias = localStorage.getItem(SHARED_PREFS_CRITERIA); + const localStoredCriteriaSets = localStorage.getItem(SHARED_PREFS_CRITERIA); const checker = new CriteriaCompletionChecker( localStoredEventList === null ? '' : localStoredEventList ); const result = checker.getMatchedCriteria( - JSON.stringify(localStoredCriterias) + JSON.stringify(localStoredCriteriaSets) ); expect(result).toBeNull(); From 5b7ffa0b65c247691a4345339f441fe61f3b169c Mon Sep 17 00:00:00 2001 From: darshan-iterable Date: Wed, 2 Oct 2024 20:48:19 +0530 Subject: [PATCH 79/88] [MOB-9578] implements identity resolution (#458) --- .../tests/userMergeScenarios.test.ts | 184 +++++++++++++----- src/authorization/authorization.ts | 66 ++++--- src/utils/config.ts | 16 +- 3 files changed, 189 insertions(+), 77 deletions(-) diff --git a/src/anonymousUserTracking/tests/userMergeScenarios.test.ts b/src/anonymousUserTracking/tests/userMergeScenarios.test.ts index d7a5dd4b..2e040590 100644 --- a/src/anonymousUserTracking/tests/userMergeScenarios.test.ts +++ b/src/anonymousUserTracking/tests/userMergeScenarios.test.ts @@ -94,10 +94,16 @@ describe('UserMergeScenariosTests', () => { }); describe('UserMergeScenariosTests with setUserID', () => { - it('criteria not met with disableEventReplay true with setUserId', async () => { + it('criteria not met with merge false with setUserId', async () => { const { setUserID, logout } = initializeWithConfig({ authToken: '123', - configOptions: { enableAnonTracking: true } + configOptions: { + enableAnonTracking: true, + identityResolution: { + replayOnVisitorToKnown: false, + mergeOnAnonymousToKnown: false + } + } }); logout(); // logout to remove logged in users before this test try { @@ -109,7 +115,7 @@ describe('UserMergeScenariosTests', () => { SHARED_PREFS_EVENT_LIST_KEY, expect.any(String) ); - await setUserID('testuser123', true); + await setUserID('testuser123'); const response = await getInAppMessages({ count: 10, packageName: 'my-lil-website' @@ -127,10 +133,16 @@ describe('UserMergeScenariosTests', () => { expect(mergePostRequestData).toBeUndefined(); // ensure that merge API Do NOT get called }); - it('criteria not met with disableEventReplay false with setUserId', async () => { + it('criteria not met with merge true with setUserId', async () => { const { setUserID, logout } = initializeWithConfig({ authToken: '123', - configOptions: { enableAnonTracking: true } + configOptions: { + enableAnonTracking: true, + identityResolution: { + replayOnVisitorToKnown: true, + mergeOnAnonymousToKnown: true + } + } }); logout(); // logout to remove logged in users before this test try { @@ -142,7 +154,7 @@ describe('UserMergeScenariosTests', () => { SHARED_PREFS_EVENT_LIST_KEY, expect.any(String) ); - await setUserID('testuser123', false); + await setUserID('testuser123'); const response = await getInAppMessages({ count: 10, packageName: 'my-lil-website' @@ -162,10 +174,16 @@ describe('UserMergeScenariosTests', () => { expect(mergePostRequestData).toBeUndefined(); // ensure that merge API Do NOT get called }); - it('criteria not met with disableEventReplay default value with setUserId', async () => { + it('criteria not met with merge default value with setUserId', async () => { const { setUserID, logout } = initializeWithConfig({ authToken: '123', - configOptions: { enableAnonTracking: true } + configOptions: { + enableAnonTracking: true, + identityResolution: { + replayOnVisitorToKnown: true, + mergeOnAnonymousToKnown: false + } + } }); logout(); // logout to remove logged in users before this test try { @@ -197,7 +215,7 @@ describe('UserMergeScenariosTests', () => { expect(mergePostRequestData).toBeUndefined(); // ensure that merge API Do NOT get called }); - it('criteria is met with disableEventReplay true with setUserId', async () => { + it('criteria is met with merge false with setUserId', async () => { (localStorage.getItem as jest.Mock).mockImplementation((key) => { if (key === SHARED_PREFS_EVENT_LIST_KEY) { return JSON.stringify([eventDataMatched]); @@ -212,7 +230,13 @@ describe('UserMergeScenariosTests', () => { }); const { setUserID, logout } = initializeWithConfig({ authToken: '123', - configOptions: { enableAnonTracking: true } + configOptions: { + enableAnonTracking: true, + identityResolution: { + replayOnVisitorToKnown: true, + mergeOnAnonymousToKnown: false + } + } }); logout(); // logout to remove logged in users before this test try { @@ -220,7 +244,7 @@ describe('UserMergeScenariosTests', () => { } catch (e) { console.log(''); } - await setUserID('testuser123', true); + await setUserID('testuser123'); expect(localStorageMock.removeItem).toHaveBeenCalledWith( SHARED_PREF_ANON_USER_ID ); @@ -235,7 +259,7 @@ describe('UserMergeScenariosTests', () => { expect(mergePostRequestData).toBeUndefined(); // ensure that merge API Do NOT get called }); - it('criteria is met with disableEventReplay false with setUserId', async () => { + it('criteria is met with merge true with setUserId', async () => { (localStorage.getItem as jest.Mock).mockImplementation((key) => { if (key === SHARED_PREFS_EVENT_LIST_KEY) { return JSON.stringify([eventDataMatched]); @@ -250,7 +274,13 @@ describe('UserMergeScenariosTests', () => { }); const { setUserID, logout } = initializeWithConfig({ authToken: '123', - configOptions: { enableAnonTracking: true } + configOptions: { + enableAnonTracking: true, + identityResolution: { + replayOnVisitorToKnown: true, + mergeOnAnonymousToKnown: true + } + } }); logout(); // logout to remove logged in users before this test try { @@ -263,7 +293,7 @@ describe('UserMergeScenariosTests', () => { count: 10, packageName: 'my-lil-website' }); - await setUserID('testuser123', false); + await setUserID('testuser123'); expect(localStorageMock.removeItem).toHaveBeenCalledWith( SHARED_PREF_ANON_USER_ID ); @@ -277,7 +307,7 @@ describe('UserMergeScenariosTests', () => { jest.runAllTimers(); }); - it('criteria is met with disableEventReplay default with setUserId', async () => { + it('criteria is met with merge default with setUserId', async () => { (localStorage.getItem as jest.Mock).mockImplementation((key) => { if (key === SHARED_PREFS_EVENT_LIST_KEY) { return JSON.stringify([eventDataMatched]); @@ -295,7 +325,9 @@ describe('UserMergeScenariosTests', () => { }); const { setUserID, logout } = initializeWithConfig({ authToken: '123', - configOptions: { enableAnonTracking: true } + configOptions: { + enableAnonTracking: true + } }); logout(); // logout to remove logged in users before this test try { @@ -321,10 +353,16 @@ describe('UserMergeScenariosTests', () => { jest.runAllTimers(); }); - it('current user identified with setUserId disableEventReplay true', async () => { + it('current user identified with setUserId merge false', async () => { const { setUserID, logout } = initializeWithConfig({ authToken: '123', - configOptions: { enableAnonTracking: true } + configOptions: { + enableAnonTracking: true, + identityResolution: { + replayOnVisitorToKnown: true, + mergeOnAnonymousToKnown: false + } + } }); logout(); // logout to remove logged in users before this test await setUserID('testuser123'); @@ -341,7 +379,7 @@ describe('UserMergeScenariosTests', () => { expect(localStorageMock.setItem).not.toHaveBeenCalledWith( SHARED_PREF_ANON_USER_ID ); - await setUserID('testuseranotheruser', true); + await setUserID('testuseranotheruser'); const secondResponse = await getInAppMessages({ count: 10, packageName: 'my-lil-website' @@ -353,10 +391,16 @@ describe('UserMergeScenariosTests', () => { expect(mergePostRequestData).toBeUndefined(); // ensure that merge API Do NOT get called }); - it('current user identified with setUserId disableEventReplay false', async () => { + it('current user identified with setUserId merge true', async () => { const { setUserID, logout } = initializeWithConfig({ authToken: '123', - configOptions: { enableAnonTracking: true } + configOptions: { + enableAnonTracking: true, + identityResolution: { + replayOnVisitorToKnown: true, + mergeOnAnonymousToKnown: true + } + } }); logout(); // logout to remove logged in users before this test await setUserID('testuser123'); @@ -368,7 +412,7 @@ describe('UserMergeScenariosTests', () => { expect(localStorageMock.setItem).not.toHaveBeenCalledWith( SHARED_PREF_ANON_USER_ID ); - await setUserID('testuseranotheruser', false); + await setUserID('testuseranotheruser'); const secondResponse = await getInAppMessages({ count: 10, packageName: 'my-lil-website' @@ -380,10 +424,16 @@ describe('UserMergeScenariosTests', () => { expect(mergePostRequestData).toBeDefined(); // ensure that merge API gets called }); - it('current user identified with setUserId disableEventReplay default', async () => { + it('current user identified with setUserId merge default', async () => { const { setUserID, logout } = initializeWithConfig({ authToken: '123', - configOptions: { enableAnonTracking: true } + configOptions: { + enableAnonTracking: true, + identityResolution: { + replayOnVisitorToKnown: true, + mergeOnAnonymousToKnown: false + } + } }); logout(); // logout to remove logged in users before this test await setUserID('testuser123'); @@ -404,15 +454,21 @@ describe('UserMergeScenariosTests', () => { const mergePostRequestData = mockRequest.history.post.find( (req) => req.url === ENDPOINT_MERGE_USER ); - expect(mergePostRequestData).toBeDefined(); // ensure that merge API gets called + expect(mergePostRequestData).toBeUndefined(); // ensure that merge API Do NOT get called }); }); describe('UserMergeScenariosTests with setEmail', () => { - it('criteria not met with disableEventReplay true with setEmail', async () => { + it('criteria not met with merge false with setEmail', async () => { const { setEmail, logout } = initializeWithConfig({ authToken: '123', - configOptions: { enableAnonTracking: true } + configOptions: { + enableAnonTracking: true, + identityResolution: { + replayOnVisitorToKnown: false, + mergeOnAnonymousToKnown: false + } + } }); logout(); // logout to remove logged in users before this test try { @@ -424,7 +480,7 @@ describe('UserMergeScenariosTests', () => { SHARED_PREFS_EVENT_LIST_KEY, expect.any(String) ); - await setEmail('testuser123@test.com', true); + await setEmail('testuser123@test.com'); const response = await getInAppMessages({ count: 10, packageName: 'my-lil-website' @@ -442,10 +498,16 @@ describe('UserMergeScenariosTests', () => { expect(mergePostRequestData).toBeUndefined(); // ensure that merge API Do NOT get called }); - it('criteria not met with disableEventReplay false with setEmail', async () => { + it('criteria not met with merge true with setEmail', async () => { const { setEmail, logout } = initializeWithConfig({ authToken: '123', - configOptions: { enableAnonTracking: true } + configOptions: { + enableAnonTracking: true, + identityResolution: { + replayOnVisitorToKnown: true, + mergeOnAnonymousToKnown: true + } + } }); logout(); // logout to remove logged in users before this test try { @@ -457,7 +519,7 @@ describe('UserMergeScenariosTests', () => { SHARED_PREFS_EVENT_LIST_KEY, expect.any(String) ); - await setEmail('testuser123@test.com', false); + await setEmail('testuser123@test.com'); const response = await getInAppMessages({ count: 10, packageName: 'my-lil-website' @@ -477,10 +539,12 @@ describe('UserMergeScenariosTests', () => { expect(mergePostRequestData).toBeUndefined(); // ensure that merge API Do NOT get called }); - it('criteria not met with disableEventReplay default value with setEmail', async () => { + it('criteria not met with merge default value with setEmail', async () => { const { setEmail, logout } = initializeWithConfig({ authToken: '123', - configOptions: { enableAnonTracking: true } + configOptions: { + enableAnonTracking: true + } }); logout(); // logout to remove logged in users before this test try { @@ -512,7 +576,7 @@ describe('UserMergeScenariosTests', () => { expect(mergePostRequestData).toBeUndefined(); // ensure that merge API Do NOT get called }); - it('criteria is met with disableEventReplay false with setEmail', async () => { + it('criteria is met with merge true with setEmail', async () => { (localStorage.getItem as jest.Mock).mockImplementation((key) => { if (key === SHARED_PREFS_EVENT_LIST_KEY) { return JSON.stringify([eventDataMatched]); @@ -527,7 +591,13 @@ describe('UserMergeScenariosTests', () => { }); const { setEmail, logout } = initializeWithConfig({ authToken: '123', - configOptions: { enableAnonTracking: true } + configOptions: { + enableAnonTracking: true, + identityResolution: { + replayOnVisitorToKnown: true, + mergeOnAnonymousToKnown: true + } + } }); logout(); // logout to remove logged in users before this test try { @@ -539,7 +609,7 @@ describe('UserMergeScenariosTests', () => { count: 10, packageName: 'my-lil-website' }); - await setEmail('testuser123@test.com', false); + await setEmail('testuser123@test.com'); expect(localStorageMock.removeItem).toHaveBeenCalledWith( SHARED_PREF_ANON_USER_ID ); @@ -553,7 +623,7 @@ describe('UserMergeScenariosTests', () => { jest.runAllTimers(); }); - it('criteria is met with disableEventReplay default with setEmail', async () => { + it('criteria is met with merge default with setEmail', async () => { (localStorage.getItem as jest.Mock).mockImplementation((key) => { if (key === SHARED_PREFS_EVENT_LIST_KEY) { return JSON.stringify([eventDataMatched]); @@ -571,7 +641,9 @@ describe('UserMergeScenariosTests', () => { }); const { setEmail, logout } = initializeWithConfig({ authToken: '123', - configOptions: { enableAnonTracking: true } + configOptions: { + enableAnonTracking: true + } }); logout(); // logout to remove logged in users before this test try { @@ -597,10 +669,16 @@ describe('UserMergeScenariosTests', () => { jest.runAllTimers(); }); - it('current user identified with setEmail with disableEventReplay true', async () => { + it('current user identified with setEmail with merge false', async () => { const { setEmail, logout } = initializeWithConfig({ authToken: '123', - configOptions: { enableAnonTracking: true } + configOptions: { + enableAnonTracking: true, + identityResolution: { + replayOnVisitorToKnown: true, + mergeOnAnonymousToKnown: false + } + } }); logout(); // logout to remove logged in users before this test await setEmail('testuser123@test.com'); @@ -617,7 +695,7 @@ describe('UserMergeScenariosTests', () => { expect(localStorageMock.setItem).not.toHaveBeenCalledWith( SHARED_PREF_ANON_USER_ID ); - await setEmail('testuseranotheruser@test.com', true); + await setEmail('testuseranotheruser@test.com'); const secondResponse = await getInAppMessages({ count: 10, packageName: 'my-lil-website' @@ -631,10 +709,16 @@ describe('UserMergeScenariosTests', () => { expect(mergePostRequestData).toBeUndefined(); // ensure that merge API Do NOT get called }); - it('current user identified with setEmail disableEventReplay false', async () => { + it('current user identified with setEmail merge true', async () => { const { setEmail, logout } = initializeWithConfig({ authToken: '123', - configOptions: { enableAnonTracking: true } + configOptions: { + enableAnonTracking: true, + identityResolution: { + replayOnVisitorToKnown: true, + mergeOnAnonymousToKnown: true + } + } }); logout(); // logout to remove logged in users before this test await setEmail('testuser123@test.com'); @@ -651,7 +735,7 @@ describe('UserMergeScenariosTests', () => { expect(localStorageMock.setItem).not.toHaveBeenCalledWith( SHARED_PREF_ANON_USER_ID ); - await setEmail('testuseranotheruser@test.com', false); + await setEmail('testuseranotheruser@test.com'); const secondResponse = await getInAppMessages({ count: 10, packageName: 'my-lil-website' @@ -665,10 +749,16 @@ describe('UserMergeScenariosTests', () => { expect(mergePostRequestData).toBeDefined(); // ensure that merge API gets called }); - it('current user identified with setEmail disableEventReplay default', async () => { + it('current user identified with setEmail merge default', async () => { const { setEmail, logout } = initializeWithConfig({ authToken: '123', - configOptions: { enableAnonTracking: true } + configOptions: { + enableAnonTracking: true, + identityResolution: { + replayOnVisitorToKnown: true, + mergeOnAnonymousToKnown: false + } + } }); logout(); // logout to remove logged in users before this test await setEmail('testuser123@test.com'); @@ -696,7 +786,7 @@ describe('UserMergeScenariosTests', () => { const mergePostRequestData = mockRequest.history.post.find( (req) => req.url === ENDPOINT_MERGE_USER ); - expect(mergePostRequestData).toBeDefined(); // ensure that merge API gets called + expect(mergePostRequestData).toBeUndefined(); // ensure that merge API Do NOT get called }); }); }); diff --git a/src/authorization/authorization.ts b/src/authorization/authorization.ts index e7ca34a6..1e48d0eb 100644 --- a/src/authorization/authorization.ts +++ b/src/authorization/authorization.ts @@ -62,8 +62,8 @@ const doesRequestUrlContain = (routeConfig: RouteConfig) => ); export interface WithJWT { clearRefresh: () => void; - setEmail: (email: string, disableEventReplay?: boolean) => Promise; - setUserID: (userId: string, disableEventReplay?: boolean) => Promise; + setEmail: (email: string) => Promise; + setUserID: (userId: string) => Promise; logout: () => void; refreshJwtToken: (authTypes: string) => Promise; } @@ -71,8 +71,8 @@ export interface WithJWT { export interface WithoutJWT { setNewAuthToken: (newToken?: string) => void; clearAuthToken: () => void; - setEmail: (email: string, disableEventReplay?: boolean) => Promise; - setUserID: (userId: string, disableEventReplay?: boolean) => Promise; + setEmail: (email: string) => Promise; + setUserID: (userId: string) => Promise; logout: () => void; } @@ -108,13 +108,10 @@ const getAnonUserId = () => { } }; -const initializeUserIdAndSync = ( - userId: string, - disableEventReplay?: boolean -) => { +const initializeUserIdAndSync = (userId: string, replay?: boolean) => { addUserIdToRequest(userId); clearAnonymousUser(); - if (!disableEventReplay) { + if (replay) { syncEvents(); } }; @@ -214,13 +211,10 @@ const addUserIdToRequest = (userId: string) => { }); }; -const initializeEmailUserAndSync = ( - email: string, - disableEventReplay?: boolean -) => { +const initializeEmailUserAndSync = (email: string, replay?: boolean) => { addEmailToRequest(email); clearAnonymousUser(); - if (!disableEventReplay) { + if (replay) { syncEvents(); } }; @@ -426,7 +420,7 @@ export function initialize( const tryMergeUser = async ( emailOrUserId: string, isEmail: boolean, - disableEventReplay?: boolean + merge?: boolean ): Promise => { const enableAnonTracking = config.getConfig('enableAnonTracking'); const sourceUserIdOrEmail = @@ -438,7 +432,7 @@ export function initialize( // This function will try to merge if anon user exists if ( (getAnonUserId() !== null || authIdentifier !== null) && - !disableEventReplay && + merge && enableAnonTracking ) { const anonymousUserMerge = new AnonymousUserMerge(); @@ -479,12 +473,16 @@ export function initialize( baseAxiosRequest.interceptors.request.eject(authInterceptor); } }, - setEmail: async (email: string, disableEventReplay?: boolean) => { + setEmail: async (email: string) => { clearMessages(); try { - const result = await tryMergeUser(email, true, disableEventReplay); + const identityResolution = config.getConfig('identityResolution'); + const merge = identityResolution?.mergeOnAnonymousToKnown; + const replay = identityResolution?.replayOnVisitorToKnown; + + const result = await tryMergeUser(email, true, merge); if (result) { - initializeEmailUserAndSync(email, disableEventReplay); + initializeEmailUserAndSync(email, replay); return Promise.resolve(); } } catch (error) { @@ -492,12 +490,16 @@ export function initialize( return Promise.reject(`merging failed: ${error}`); } }, - setUserID: async (userId: string, disableEventReplay?: boolean) => { + setUserID: async (userId: string) => { clearMessages(); try { - const result = await tryMergeUser(userId, false, disableEventReplay); + const identityResolution = config.getConfig('identityResolution'); + const merge = identityResolution?.mergeOnAnonymousToKnown; + const replay = identityResolution?.replayOnVisitorToKnown; + + const result = await tryMergeUser(userId, false, merge); if (result) { - initializeUserIdAndSync(userId, disableEventReplay); + initializeUserIdAndSync(userId, replay); return Promise.resolve(); } } catch (error) { @@ -783,13 +785,17 @@ export function initialize( /* this will just clear the existing timeout */ handleTokenExpiration(''); }, - setEmail: async (email: string, disableEventReplay?: boolean) => { + setEmail: async (email: string) => { /* clear previous user */ clearMessages(); try { - const result = await tryMergeUser(email, true, disableEventReplay); + const identityResolution = config.getConfig('identityResolution'); + const merge = identityResolution?.mergeOnAnonymousToKnown; + const replay = identityResolution?.replayOnVisitorToKnown; + + const result = await tryMergeUser(email, true, merge); if (result) { - initializeEmailUserAndSync(email, disableEventReplay); + initializeEmailUserAndSync(email, replay); try { return doRequest({ email }).catch((e) => { if (logLevel === 'verbose') { @@ -809,12 +815,16 @@ export function initialize( return Promise.reject(`merging failed: ${error}`); } }, - setUserID: async (userId: string, disableEventReplay?: boolean) => { + setUserID: async (userId: string) => { clearMessages(); try { - const result = await tryMergeUser(userId, false, disableEventReplay); + const identityResolution = config.getConfig('identityResolution'); + const merge = identityResolution?.mergeOnAnonymousToKnown; + const replay = identityResolution?.replayOnVisitorToKnown; + + const result = await tryMergeUser(userId, false, merge); if (result) { - initializeUserIdAndSync(userId, disableEventReplay); + initializeUserIdAndSync(userId, replay); try { return doRequest({ userID: userId }) .then(async (token) => { diff --git a/src/utils/config.ts b/src/utils/config.ts index 0c66ee24..ea518dfb 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -7,6 +7,10 @@ export type Options = { isEuIterableService: boolean; dangerouslyAllowJsPopups: boolean; eventThresholdLimit?: number; + identityResolution?: { + replayOnVisitorToKnown?: boolean; + mergeOnAnonymousToKnown?: boolean; + }; }; const _config = () => { @@ -16,7 +20,11 @@ const _config = () => { enableAnonTracking: false, isEuIterableService: false, dangerouslyAllowJsPopups: false, - eventThresholdLimit: DEFAULT_EVENT_THRESHOLD_LIMIT + eventThresholdLimit: DEFAULT_EVENT_THRESHOLD_LIMIT, + identityResolution: { + replayOnVisitorToKnown: true, + mergeOnAnonymousToKnown: true + } }; const getConfig = (option: K) => options[option]; @@ -26,7 +34,11 @@ const _config = () => { setConfig: (newOptions: Partial) => { options = { ...options, - ...newOptions + ...newOptions, + identityResolution: { + ...options.identityResolution, + ...newOptions.identityResolution + } }; } }; From 604ea663f9658ee35c2287b389f5fd5c36c4ae67 Mon Sep 17 00:00:00 2001 From: darshan-iterable Date: Wed, 2 Oct 2024 20:58:27 +0530 Subject: [PATCH 80/88] MOB-9650 Added support for nested criteria match a.b.c (#457) * MOB-9650 Added support for nested criteria match a.b.c * fix: removed updatecart from nested criteria --- .../criteriaCompletionChecker.ts | 4 +- src/anonymousUserTracking/tests/constants.ts | 37 +++++ .../tests/nestedTesting.test.ts | 154 +++++++++++++++++- 3 files changed, 191 insertions(+), 4 deletions(-) diff --git a/src/anonymousUserTracking/criteriaCompletionChecker.ts b/src/anonymousUserTracking/criteriaCompletionChecker.ts index db2121a8..2ec9958b 100644 --- a/src/anonymousUserTracking/criteriaCompletionChecker.ts +++ b/src/anonymousUserTracking/criteriaCompletionChecker.ts @@ -383,9 +383,9 @@ class CriteriaCompletionChecker { } private getFieldValue(data: any, field: string): any { - let fields = field.split('.'); + const fields: string[] = field.split('.'); if (data?.eventType === TRACK_EVENT && data?.eventName === fields[0]) { - fields = [fields[fields.length - 1]]; + fields.shift(); } return fields.reduce( (value, currentField) => diff --git a/src/anonymousUserTracking/tests/constants.ts b/src/anonymousUserTracking/tests/constants.ts index c99ed181..c49530f2 100644 --- a/src/anonymousUserTracking/tests/constants.ts +++ b/src/anonymousUserTracking/tests/constants.ts @@ -834,6 +834,43 @@ export const NESTED_CRITERIA = { ] }; +export const NESTED_CRITERIA_MULTI_LEVEL = { + count: 1, + criteriaSets: [ + { + criteriaId: '425', + name: 'Multi level Nested field criteria', + createdAt: 1721251169153, + updatedAt: 1723488175352, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'button-clicked.browserVisit.website.domain', + comparatorType: 'Equals', + value: 'https://mybrand.com/socks', + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] +}; + export const IS_ONE_OF_CRITERIA = { count: 1, criteriaSets: [ diff --git a/src/anonymousUserTracking/tests/nestedTesting.test.ts b/src/anonymousUserTracking/tests/nestedTesting.test.ts index c1a85db9..68cffb8d 100644 --- a/src/anonymousUserTracking/tests/nestedTesting.test.ts +++ b/src/anonymousUserTracking/tests/nestedTesting.test.ts @@ -1,6 +1,6 @@ import { SHARED_PREFS_EVENT_LIST_KEY } from '../../constants'; import CriteriaCompletionChecker from '../criteriaCompletionChecker'; -import { NESTED_CRITERIA } from './constants'; +import { NESTED_CRITERIA, NESTED_CRITERIA_MULTI_LEVEL } from './constants'; const localStorageMock = { getItem: jest.fn(), @@ -53,7 +53,7 @@ describe('nestedTesting', () => { expect(result).toEqual('168'); }); - it('should return criteriaId 168 (nested field - No match)', () => { + it('should return criteriaId null (nested field - No match)', () => { (localStorage.getItem as jest.Mock).mockImplementation((key) => { if (key === SHARED_PREFS_EVENT_LIST_KEY) { return JSON.stringify([ @@ -92,4 +92,154 @@ describe('nestedTesting', () => { const result = checker.getMatchedCriteria(JSON.stringify(NESTED_CRITERIA)); expect(result).toEqual(null); }); + + it('should return criteriaId 425 (Multi level Nested field criteria)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + eventName: 'button-clicked', + dataFields: { + browserVisit: { website: { domain: 'https://mybrand.com/socks' } } + }, + eventType: 'customEvent' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(NESTED_CRITERIA_MULTI_LEVEL) + ); + expect(result).toEqual('425'); + }); + + it('should return criteriaId 425 (Multi level Nested field criteria - No match)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + eventName: 'button-clicked', + dataFields: { + 'button-clicked': { + browserVisit: { + website: { domain: 'https://mybrand.com/socks' } + } + } + }, + eventType: 'customEvent' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(NESTED_CRITERIA_MULTI_LEVEL) + ); + expect(result).toEqual(null); + }); + + it('should return criteriaId null (Multi level Nested field criteria - No match)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + eventName: 'button-clicked', + dataFields: { + 'browserVisit.website.domain': 'https://mybrand.com/socks' + }, + eventType: 'customEvent' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(NESTED_CRITERIA_MULTI_LEVEL) + ); + expect(result).toEqual(null); + }); + + it('should return criteriaId null (Multi level Nested field criteria - No match)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + eventName: 'button-clicked', + dataFields: { + browserVisit: { website: { domain: 'https://mybrand.com' } } + }, + eventType: 'customEvent' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(NESTED_CRITERIA_MULTI_LEVEL) + ); + expect(result).toEqual(null); + }); + + it('should return criteriaId null (Multi level Nested field criteria - No match)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + eventName: 'button-clicked', + dataFields: { + quantity: 11, + domain: 'https://mybrand.com/socks' + }, + eventType: 'customEvent' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(NESTED_CRITERIA_MULTI_LEVEL) + ); + expect(result).toEqual(null); + }); }); From c720d4f8061bfdc1e2bcebc459d28f074aec8d86 Mon Sep 17 00:00:00 2001 From: darshan-iterable Date: Mon, 7 Oct 2024 19:58:43 +0530 Subject: [PATCH 81/88] [MOB-9652] support for nested JSON array (#459) * MOB-9650 Added support for nested criteria match a.b.c * fix: removed updatecart from nested criteria * [MOB-9652] support for nested JSON array * [MOB-9652] customEvent test case for nested JSON array * [MOB-9168] Automated unit tests against complex criteria (#461) * [MOB-9168] Automated unit tests against complex criteria * [MOB-9168] Automated unit tests against complex criteria --- .../criteriaCompletionChecker.ts | 45 +- .../tests/complexCriteria.test.ts | 253 +++++++++ src/anonymousUserTracking/tests/constants.ts | 486 ++++++++++++++++++ .../tests/nestedTesting.test.ts | 173 ++++++- 4 files changed, 946 insertions(+), 11 deletions(-) diff --git a/src/anonymousUserTracking/criteriaCompletionChecker.ts b/src/anonymousUserTracking/criteriaCompletionChecker.ts index 2ec9958b..59368e16 100644 --- a/src/anonymousUserTracking/criteriaCompletionChecker.ts +++ b/src/anonymousUserTracking/criteriaCompletionChecker.ts @@ -350,16 +350,41 @@ class CriteriaCompletionChecker { ); if (field.includes('.')) { - const fields = field.split('.'); - const firstElement = eventData?.[fields[0]]; - if (Array.isArray(firstElement)) { - return firstElement?.some((item: any) => { - const data = { - ...eventData, - [fields[0]]: item - }; - return this.evaluateFieldLogic(searchQueries, data); - }); + const splitField = field.split('.') as string[]; + const fields = + eventData?.eventType === TRACK_EVENT && + eventData?.eventName === splitField[0] + ? splitField.slice(1) + : splitField; + + let fieldValue = eventData; + let isSubFieldArray = false; + let isSubMatch = false; + + fields.forEach((subField) => { + const subFieldValue = fieldValue[subField]; + if (Array.isArray(subFieldValue)) { + isSubFieldArray = true; + isSubMatch = subFieldValue.some((item: any) => { + const data = fields.reduceRight((acc: any, key) => { + if (key === subField) { + return { [key]: item }; + } + return { [key]: acc }; + }, {}); + + return this.evaluateFieldLogic(searchQueries, { + ...eventData, + ...data + }); + }); + } else { + fieldValue = subFieldValue; + } + }); + + if (isSubFieldArray) { + return isSubMatch; } const valueFromObj = this.getFieldValue(eventData, field); diff --git a/src/anonymousUserTracking/tests/complexCriteria.test.ts b/src/anonymousUserTracking/tests/complexCriteria.test.ts index 06f5cbdf..973262eb 100644 --- a/src/anonymousUserTracking/tests/complexCriteria.test.ts +++ b/src/anonymousUserTracking/tests/complexCriteria.test.ts @@ -1,5 +1,10 @@ import { SHARED_PREFS_EVENT_LIST_KEY } from '../../constants'; import CriteriaCompletionChecker from '../criteriaCompletionChecker'; +import { + COMPLEX_CRITERIA_1, + COMPLEX_CRITERIA_2, + COMPLEX_CRITERIA_3 +} from './constants'; const localStorageMock = { getItem: jest.fn(), @@ -1598,4 +1603,252 @@ describe('complexCriteria', () => { ); expect(result).toEqual(null); }); + + // MARK: Complex criteria #1 + it('should return criteriaId 290 if (1 OR 2 OR 3) AND (4 AND 5) AND (6 NOT 7) matched', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + saved_cars: { color: 'black' }, + 'animal-found': { vaccinated: true }, + eventName: 'birthday' + }, + eventType: 'customEvent' + }, + { + dataFields: { reason: 'testing', total: 30 }, + eventType: 'purchase' + }, + { + dataFields: { firstName: 'Adam' }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(COMPLEX_CRITERIA_1) + ); + expect(result).toEqual('290'); + }); + + it('should return criteriaId null if (1 OR 2 OR 3) AND (4 AND 5) AND (6 NOT 7) - No match', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + eventType: 'user', + dataFields: { + firstName: 'Alex' + } + }, + { + eventType: 'customEvent', + eventName: 'saved_cars', + dataFields: { + color: '' + } + }, + { + eventType: 'customEvent', + eventName: 'animal-found', + dataFields: { + vaccinated: true + } + }, + { + eventType: 'purchase', + dataFields: { + total: 30, + reason: 'testing' + } + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(COMPLEX_CRITERIA_1) + ); + expect(result).toEqual(null); + }); + + // MARK: Complex criteria #2 + it('should return criteriaId 291 if (6 OR 7) OR (4 AND 5) OR (1 NOT 2 NOT 3) matched', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + eventType: 'user', + dataFields: { + firstName: 'xcode' + } + }, + { + eventType: 'customEvent', + eventName: 'saved_cars', + dataFields: { + color: 'black' + } + }, + { + eventType: 'customEvent', + eventName: 'animal-found', + dataFields: { + vaccinated: true + } + }, + { + eventType: 'purchase', + dataFields: { + total: 110, + reason: 'testing' + } + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(COMPLEX_CRITERIA_2) + ); + expect(result).toEqual('291'); + }); + + it('should return criteriaId null if (6 OR 7) OR (4 AND 5) OR (1 NOT 2 NOT 3) - No match', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + eventType: 'user', + dataFields: { + firstName: 'Alex' + } + }, + { + eventType: 'purchase', + dataFields: { + total: 10, + reason: 'null' + } + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(COMPLEX_CRITERIA_2) + ); + expect(result).toEqual(null); + }); + + // MARK: Complex criteria #3 + it('should return criteriaId 292 if (1 AND 2) NOR (3 OR 4 OR 5) NOR (6 NOR 7) matched', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataType: 'user', + dataFields: { + firstName: 'xcode', + lastName: 'ssr' + } + }, + { + dataType: 'customEvent', + eventName: 'animal-found', + dataFields: { + vaccinated: true, + count: 10 + } + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(COMPLEX_CRITERIA_3) + ); + expect(result).toEqual('292'); + }); + + it('should return criteriaId null if (1 AND 2) NOR (3 OR 4 OR 5) NOR (6 NOR 7) - No match', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + eventType: 'user', + dataFields: { + firstName: 'Alex', + lastName: 'Aris' + } + }, + { + eventType: 'customEvent', + eventName: 'animal-found', + dataFields: { + vaccinated: false, + count: 4 + } + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(COMPLEX_CRITERIA_3) + ); + expect(result).toEqual(null); + }); }); diff --git a/src/anonymousUserTracking/tests/constants.ts b/src/anonymousUserTracking/tests/constants.ts index c49530f2..b8f1c047 100644 --- a/src/anonymousUserTracking/tests/constants.ts +++ b/src/anonymousUserTracking/tests/constants.ts @@ -871,6 +871,93 @@ export const NESTED_CRITERIA_MULTI_LEVEL = { ] }; +export const NESTED_CRITERIA_MULTI_LEVEL_ARRAY = { + count: 1, + criteriaSets: [ + { + criteriaId: '436', + name: 'Criteria 2.1 - 09252024 Bug Bash', + createdAt: 1727286807360, + updatedAt: 1727445082036, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + field: 'furniture.material.type', + comparatorType: 'Contains', + value: 'table', + fieldType: 'string' + }, + { + dataType: 'user', + field: 'furniture.material.color', + comparatorType: 'Equals', + values: ['black'] + } + ] + } + } + ] + } + ] + } + } + ] +}; + +export const NESTED_CRITERIA_MULTI_LEVEL_ARRAY_TRACK_EVENT = { + count: 1, + criteriaSets: [ + { + criteriaId: '459', + name: 'event a.h.b=d && a.h.c=g', + createdAt: 1721251169153, + updatedAt: 1723488175352, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'TopLevelArrayObject.a.h.b', + comparatorType: 'Equals', + value: 'd', + fieldType: 'string' + }, + { + dataType: 'customEvent', + field: 'TopLevelArrayObject.a.h.c', + comparatorType: 'Equals', + value: 'g', + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] +}; + export const IS_ONE_OF_CRITERIA = { count: 1, criteriaSets: [ @@ -1093,3 +1180,402 @@ export const USER_MERGE_SCENARIO_CRITERIA = { } ] }; + +// MARK:Complex Criteria + +export const COMPLEX_CRITERIA_1 = { + count: 1, + criteriaSets: [ + { + criteriaId: '290', + name: 'Complex Criteria Unit Test #1', + createdAt: 1722532861551, + updatedAt: 1722532861551, + searchQuery: { + combinator: 'And', + searchQueries: [ + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + field: 'firstName', + comparatorType: 'StartsWith', + value: 'A', + fieldType: 'string' + } + ] + } + }, + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + field: 'firstName', + comparatorType: 'StartsWith', + value: 'B', + fieldType: 'string' + } + ] + } + }, + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + field: 'firstName', + comparatorType: 'StartsWith', + value: 'C', + fieldType: 'string' + } + ] + } + } + ] + }, + { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'eventName', + comparatorType: 'IsSet', + value: '', + fieldType: 'string' + }, + { + dataType: 'customEvent', + field: 'saved_cars.color', + comparatorType: 'IsSet', + value: '', + fieldType: 'string' + } + ] + } + }, + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'eventName', + comparatorType: 'IsSet', + value: '', + fieldType: 'string' + }, + { + dataType: 'customEvent', + field: 'animal-found.vaccinated', + comparatorType: 'Equals', + value: 'true', + fieldType: 'boolean' + } + ] + } + } + ] + }, + { + combinator: 'Not', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + field: 'total', + comparatorType: 'LessThanOrEqualTo', + value: '100', + fieldType: 'double' + } + ] + } + }, + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + field: 'reason', + comparatorType: 'Equals', + value: 'null', + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] +}; + +export const COMPLEX_CRITERIA_2 = { + count: 1, + criteriaSets: [ + { + criteriaId: '291', + name: 'Complex Criteria Unit Test #2', + createdAt: 1722533473263, + updatedAt: 1722533473263, + searchQuery: { + combinator: 'Or', + searchQueries: [ + { + combinator: 'Not', + searchQueries: [ + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + field: 'firstName', + comparatorType: 'StartsWith', + value: 'A', + fieldType: 'string' + } + ] + } + }, + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + field: 'firstName', + comparatorType: 'StartsWith', + value: 'B', + fieldType: 'string' + } + ] + } + }, + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + field: 'firstName', + comparatorType: 'StartsWith', + value: 'C', + fieldType: 'string' + } + ] + } + } + ] + }, + { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'eventName', + comparatorType: 'IsSet', + value: '', + fieldType: 'string' + }, + { + dataType: 'customEvent', + field: 'saved_cars.color', + comparatorType: 'IsSet', + value: '', + fieldType: 'string' + } + ] + } + }, + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'animal-found.vaccinated', + comparatorType: 'Equals', + value: 'true', + fieldType: 'boolean' + } + ] + } + } + ] + }, + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + field: 'total', + comparatorType: 'GreaterThanOrEqualTo', + value: '100', + fieldType: 'double' + } + ] + } + }, + { + dataType: 'purchase', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'purchase', + field: 'reason', + comparatorType: 'DoesNotEqual', + value: 'null', + fieldType: 'string' + } + ] + } + } + ] + } + ] + } + } + ] +}; + +export const COMPLEX_CRITERIA_3 = { + count: 1, + criteriaSets: [ + { + criteriaId: '292', + name: 'Complex Criteria Unit Test #3', + createdAt: 1722533789589, + updatedAt: 1722533838989, + searchQuery: { + combinator: 'Not', + searchQueries: [ + { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + field: 'firstName', + comparatorType: 'StartsWith', + value: 'A', + fieldType: 'string' + } + ] + } + }, + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + field: 'lastName', + comparatorType: 'StartsWith', + value: 'A', + fieldType: 'string' + } + ] + } + } + ] + }, + { + combinator: 'Or', + searchQueries: [ + { + dataType: 'user', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'user', + field: 'firstName', + comparatorType: 'StartsWith', + value: 'C', + fieldType: 'string' + } + ] + } + }, + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'animal-found.vaccinated', + comparatorType: 'Equals', + value: 'false', + fieldType: 'boolean' + } + ] + } + }, + { + dataType: 'customEvent', + searchCombo: { + combinator: 'And', + searchQueries: [ + { + dataType: 'customEvent', + field: 'animal-found.count', + comparatorType: 'LessThan', + value: '5', + fieldType: 'long' + } + ] + } + } + ] + } + ] + } + } + ] +}; diff --git a/src/anonymousUserTracking/tests/nestedTesting.test.ts b/src/anonymousUserTracking/tests/nestedTesting.test.ts index 68cffb8d..be149ffc 100644 --- a/src/anonymousUserTracking/tests/nestedTesting.test.ts +++ b/src/anonymousUserTracking/tests/nestedTesting.test.ts @@ -1,6 +1,11 @@ import { SHARED_PREFS_EVENT_LIST_KEY } from '../../constants'; import CriteriaCompletionChecker from '../criteriaCompletionChecker'; -import { NESTED_CRITERIA, NESTED_CRITERIA_MULTI_LEVEL } from './constants'; +import { + NESTED_CRITERIA, + NESTED_CRITERIA_MULTI_LEVEL, + NESTED_CRITERIA_MULTI_LEVEL_ARRAY, + NESTED_CRITERIA_MULTI_LEVEL_ARRAY_TRACK_EVENT +} from './constants'; const localStorageMock = { getItem: jest.fn(), @@ -242,4 +247,170 @@ describe('nestedTesting', () => { ); expect(result).toEqual(null); }); + + it('should return criteriaId 436 (Multi level Nested field criteria)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + furniture: { + material: [ + { + type: 'table', + color: 'black', + lengthInches: 40, + widthInches: 60 + }, + { + type: 'Sofa', + color: 'Gray', + lengthInches: 20, + widthInches: 30 + } + ] + } + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(NESTED_CRITERIA_MULTI_LEVEL_ARRAY) + ); + expect(result).toEqual('436'); + }); + + it('should return criteriaId null (Multi level Nested field criteria - No match)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + dataFields: { + furniture: { + material: [ + { + type: 'table', + color: 'Gray', + lengthInches: 40, + widthInches: 60 + }, + { + type: 'Sofa', + color: 'black', + lengthInches: 20, + widthInches: 30 + } + ] + } + }, + eventType: 'user' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(NESTED_CRITERIA_MULTI_LEVEL_ARRAY) + ); + expect(result).toEqual(null); + }); + + it('should return criteriaId 459 (Multi level Nested field criteria)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + eventName: 'TopLevelArrayObject', + dataFields: { + a: { + h: [ + { + b: 'e', + c: 'h' + }, + { + b: 'd', + c: 'g' + } + ] + } + }, + eventType: 'customEvent' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(NESTED_CRITERIA_MULTI_LEVEL_ARRAY_TRACK_EVENT) + ); + expect(result).toEqual('459'); + }); + + it('should return criteriaId null (Multi level Nested field criteria - No match)', () => { + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_EVENT_LIST_KEY) { + return JSON.stringify([ + { + eventName: 'TopLevelArrayObject', + dataFields: { + a: { + h: [ + { + b: 'd', + c: 'h' + }, + { + b: 'e', + c: 'g' + } + ] + } + }, + eventType: 'customEvent' + } + ]); + } + return null; + }); + + const localStoredEventList = localStorage.getItem( + SHARED_PREFS_EVENT_LIST_KEY + ); + + const checker = new CriteriaCompletionChecker( + localStoredEventList === null ? '' : localStoredEventList + ); + const result = checker.getMatchedCriteria( + JSON.stringify(NESTED_CRITERIA_MULTI_LEVEL_ARRAY_TRACK_EVENT) + ); + expect(result).toEqual(null); + }); }); From 275b46097fa3fd0ddf04d31f19cd22f07a2c52e7 Mon Sep 17 00:00:00 2001 From: darshan-iterable Date: Mon, 7 Oct 2024 19:59:11 +0530 Subject: [PATCH 82/88] [MOB-9639] Added handler for notifying customer app of a newly created anon userid (#460) * [MOB-9578] implements identity resolution * [MOB-9639] Added handler for notifying customer app of a newly created anon userid * moved onAnonUserCreated in identityResolution --- react-example/src/indexWithoutJWT.tsx | 7 ++++++- .../anonymousUserEventManager.ts | 7 +++++++ src/utils/config.ts | 11 +++++++---- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/react-example/src/indexWithoutJWT.tsx b/react-example/src/indexWithoutJWT.tsx index 5d89d194..36208eae 100644 --- a/react-example/src/indexWithoutJWT.tsx +++ b/react-example/src/indexWithoutJWT.tsx @@ -46,7 +46,12 @@ const HomeLink = styled(Link)` configOptions: { isEuIterableService: false, dangerouslyAllowJsPopups: true, - enableAnonTracking: true + enableAnonTracking: true, + identityResolution: { + onAnonUserCreated: (userId: string) => { + console.log('onAnonUserCreated', userId); + } + } } }; diff --git a/src/anonymousUserTracking/anonymousUserEventManager.ts b/src/anonymousUserTracking/anonymousUserEventManager.ts index e1f4ac5d..fcec85d9 100644 --- a/src/anonymousUserTracking/anonymousUserEventManager.ts +++ b/src/anonymousUserTracking/anonymousUserEventManager.ts @@ -226,6 +226,13 @@ export class AnonymousUserEventManager { ) ) ); + + const onAnonUserCreated = + config.getConfig('identityResolution')?.onAnonUserCreated; + + if (onAnonUserCreated) { + onAnonUserCreated(userId); + } if (anonUserIdSetter !== null) { await anonUserIdSetter(userId); } diff --git a/src/utils/config.ts b/src/utils/config.ts index ea518dfb..241d2e12 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -1,5 +1,11 @@ import { BASE_URL, DEFAULT_EVENT_THRESHOLD_LIMIT } from '../constants'; +type IdentityResolution = { + replayOnVisitorToKnown?: boolean; + mergeOnAnonymousToKnown?: boolean; + onAnonUserCreated?: (userId: string) => void; +}; + export type Options = { logLevel: 'none' | 'verbose'; baseURL: string; @@ -7,10 +13,7 @@ export type Options = { isEuIterableService: boolean; dangerouslyAllowJsPopups: boolean; eventThresholdLimit?: number; - identityResolution?: { - replayOnVisitorToKnown?: boolean; - mergeOnAnonymousToKnown?: boolean; - }; + identityResolution?: IdentityResolution; }; const _config = () => { From 1d2869501c9ba6bd998aacc5695a502a271904c1 Mon Sep 17 00:00:00 2001 From: darshan-iterable Date: Tue, 15 Oct 2024 19:57:39 +0530 Subject: [PATCH 83/88] [MOB-9640] Keep AUT off until concent to track is granted (#462) * [MOB-9640] Keep AUT off until concent to track is granted * rename concent to consent * fixed eslint issue * added consent support for with-jwt config --- react-example/.eslintrc | 13 ++-- react-example/src/index.tsx | 17 ++++- react-example/src/indexWithoutJWT.tsx | 10 ++- react-example/src/styles/index.css | 43 +++++++++++-- react-example/src/views/AUTTesting.tsx | 27 +++++++- .../anonymousUserEventManager.ts | 27 +++++++- .../tests/anonymousUserEventManager.test.ts | 15 +++-- .../tests/userMergeScenarios.test.ts | 12 +++- .../tests/userUpdate.test.ts | 6 +- .../validateCustomEventUserUpdateAPI.test.ts | 15 ++++- src/authorization/authorization.ts | 63 ++++++++++++++++++- src/constants.ts | 1 + 12 files changed, 218 insertions(+), 31 deletions(-) diff --git a/react-example/.eslintrc b/react-example/.eslintrc index 1583a522..1f749a6a 100644 --- a/react-example/.eslintrc +++ b/react-example/.eslintrc @@ -1,13 +1,8 @@ { - "extends": [ - "../.eslintrc", - "plugin:react/recommended" - ], + "extends": ["../.eslintrc", "plugin:react/recommended"], "rules": { "@typescript-eslint/no-empty-interface": "off", - "react/react-in-jsx-scope": "off", + "react/react-in-jsx-scope": "off" }, - "ignorePatterns": [ - "node_modules/" - ] -} \ No newline at end of file + "ignorePatterns": ["node_modules/"] +} diff --git a/react-example/src/index.tsx b/react-example/src/index.tsx index e9c455bd..4767eff4 100644 --- a/react-example/src/index.tsx +++ b/react-example/src/index.tsx @@ -67,8 +67,16 @@ const HomeLink = styled(Link)` ) .then((response: any) => response.data?.token) }; - const { setEmail, setUserID, logout, refreshJwtToken } = - initializeWithConfig(initializeParams); + const { + setEmail, + setUserID, + logout, + refreshJwtToken, + toggleAnonUserTrackingConsent + } = initializeWithConfig(initializeParams); + + const handleConsent = (consent?: boolean) => + toggleAnonUserTrackingConsent(consent); const container = document.getElementById('root'); const root = createRoot(container); @@ -100,7 +108,10 @@ const HomeLink = styled(Link)` path="/embedded-msgs-impression-tracker" element={} /> - } /> + } + /> diff --git a/react-example/src/indexWithoutJWT.tsx b/react-example/src/indexWithoutJWT.tsx index 36208eae..b5e5d8a6 100644 --- a/react-example/src/indexWithoutJWT.tsx +++ b/react-example/src/indexWithoutJWT.tsx @@ -55,9 +55,12 @@ const HomeLink = styled(Link)` } }; - const { setUserID, logout, setEmail } = + const { setUserID, logout, setEmail, toggleAnonUserTrackingConsent } = initializeWithConfig(initializeParams); + const handleConsent = (consent?: boolean) => + toggleAnonUserTrackingConsent(consent); + // eslint-disable-next-line react/no-deprecated ReactDOM.render( @@ -86,7 +89,10 @@ const HomeLink = styled(Link)` path="/embedded-msgs-impression-tracker" element={} /> - } /> + } + /> diff --git a/react-example/src/styles/index.css b/react-example/src/styles/index.css index 28642755..e6033d05 100644 --- a/react-example/src/styles/index.css +++ b/react-example/src/styles/index.css @@ -1,4 +1,5 @@ -html, body { +html, +body { margin: 0; padding: 0; } @@ -30,7 +31,7 @@ html, body { } #change-email-form input { - margin-top: .5em; + margin-top: 0.5em; flex-grow: 1; padding: 1em; } @@ -42,17 +43,17 @@ html, body { flex-flow: column; justify-content: center; } - + .input-wrapper { margin-right: 0; transform: translateY(0); } - + #change-email-form button { width: 100%; margin-top: 1em; } - + #change-email-form input { height: 50px; } @@ -62,4 +63,34 @@ footer { display: flex; justify-content: flex-end; align-items: flex-end; -} \ No newline at end of file +} + +#cookie-consent-container { + display: flex; + justify-content: center; + flex-direction: column; + + position: fixed; + bottom: 0; + right: 0; + + box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.2); + padding: 1em; + background: #fff; + margin: 1em; + max-width: 400px; + + h3 { + margin-top: 0; + margin-bottom: 0.5em; + } + + p { + margin-top: 0; + } + + div { + display: flex; + gap: 0.5em; + } +} diff --git a/react-example/src/views/AUTTesting.tsx b/react-example/src/views/AUTTesting.tsx index 53838c08..e0a0ab2c 100644 --- a/react-example/src/views/AUTTesting.tsx +++ b/react-example/src/views/AUTTesting.tsx @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-unused-vars */ import { FC, FormEvent, useState } from 'react'; import { updateCart, @@ -19,9 +20,11 @@ import { Response } from './Components.styled'; -interface Props {} +interface Props { + setConsent?: (accept: boolean) => void; +} -export const AUTTesting: FC = () => { +export const AUTTesting: FC = ({ setConsent }) => { const [updateCartResponse, setUpdateCartResponse] = useState( 'Endpoint JSON goes here' ); @@ -200,6 +203,25 @@ export const AUTTesting: FC = () => { const inputAttr = { 'data-qa-track-input': true }; const responseAttr = { 'data-qa-track-response': true }; + const acceptCookie = () => setConsent(true); + + const declineCookie = () => setConsent(false); + + const renderCookieConsent = setConsent && ( + + ); + return ( <>

Commerce Endpoints

@@ -274,6 +296,7 @@ export const AUTTesting: FC = () => { {trackResponse} + {renderCookieConsent} ); }; diff --git a/src/anonymousUserTracking/anonymousUserEventManager.ts b/src/anonymousUserTracking/anonymousUserEventManager.ts index fcec85d9..dbe48653 100644 --- a/src/anonymousUserTracking/anonymousUserEventManager.ts +++ b/src/anonymousUserTracking/anonymousUserEventManager.ts @@ -25,7 +25,8 @@ import { WEB_PLATFORM, KEY_PREFER_USERID, ENDPOINTS, - DEFAULT_EVENT_THRESHOLD_LIMIT + DEFAULT_EVENT_THRESHOLD_LIMIT, + SHARED_PREF_ANON_USAGE_TRACKED } from '../constants'; import { baseIterableRequest } from '../request'; import { IterableResponse } from '../types'; @@ -48,9 +49,21 @@ let anonUserIdSetter: AnonUserFunction | null = null; export function registerAnonUserIdSetter(setterFunction: AnonUserFunction) { anonUserIdSetter = setterFunction; } + +export function isAnonymousUsageTracked(): boolean { + const anonymousUsageTracked = localStorage.getItem( + SHARED_PREF_ANON_USAGE_TRACKED + ); + return anonymousUsageTracked === 'true'; +} + export class AnonymousUserEventManager { updateAnonSession() { try { + const anonymousUsageTracked = isAnonymousUsageTracked(); + + if (!anonymousUsageTracked) return; + const strAnonSessionInfo = localStorage.getItem( SHARED_PREFS_ANON_SESSIONS ); @@ -91,6 +104,10 @@ export class AnonymousUserEventManager { } getAnonCriteria() { + const anonymousUsageTracked = isAnonymousUsageTracked(); + + if (!anonymousUsageTracked) return; + baseIterableRequest({ method: 'GET', url: GET_CRITERIA_PATH, @@ -169,6 +186,10 @@ export class AnonymousUserEventManager { } private async createKnownUser(criteriaId: string) { + const anonymousUsageTracked = isAnonymousUsageTracked(); + + if (!anonymousUsageTracked) return; + const userData = localStorage.getItem(SHARED_PREFS_ANON_SESSIONS); const eventList = localStorage.getItem(SHARED_PREFS_EVENT_LIST_KEY); const events = eventList ? JSON.parse(eventList) : []; @@ -293,6 +314,10 @@ export class AnonymousUserEventManager { >, shouldOverWrite: boolean ) { + const anonymousUsageTracked = isAnonymousUsageTracked(); + + if (!anonymousUsageTracked) return; + const strTrackEventList = localStorage.getItem(SHARED_PREFS_EVENT_LIST_KEY); let previousDataArray = []; diff --git a/src/anonymousUserTracking/tests/anonymousUserEventManager.test.ts b/src/anonymousUserTracking/tests/anonymousUserEventManager.test.ts index dc55abf1..64826966 100644 --- a/src/anonymousUserTracking/tests/anonymousUserEventManager.test.ts +++ b/src/anonymousUserTracking/tests/anonymousUserEventManager.test.ts @@ -3,7 +3,8 @@ import { baseIterableRequest } from '../../request'; import { SHARED_PREFS_ANON_SESSIONS, SHARED_PREFS_EVENT_LIST_KEY, - SHARED_PREFS_CRITERIA + SHARED_PREFS_CRITERIA, + SHARED_PREF_ANON_USAGE_TRACKED } from '../../constants'; import { UpdateUserParams } from '../../users'; import { TrackPurchaseRequestParams } from '../../commerce'; @@ -52,9 +53,15 @@ describe('AnonymousUserEventManager', () => { } }; - localStorageMock.getItem.mockReturnValue( - JSON.stringify(initialAnonSessionInfo) - ); + (localStorage.getItem as jest.Mock).mockImplementation((key) => { + if (key === SHARED_PREFS_ANON_SESSIONS) { + return JSON.stringify(initialAnonSessionInfo); + } + if (key === SHARED_PREF_ANON_USAGE_TRACKED) { + return 'true'; + } + return null; + }); anonUserEventManager.updateAnonSession(); diff --git a/src/anonymousUserTracking/tests/userMergeScenarios.test.ts b/src/anonymousUserTracking/tests/userMergeScenarios.test.ts index 2e040590..a5546eec 100644 --- a/src/anonymousUserTracking/tests/userMergeScenarios.test.ts +++ b/src/anonymousUserTracking/tests/userMergeScenarios.test.ts @@ -8,7 +8,8 @@ import { GET_CRITERIA_PATH, SHARED_PREFS_ANON_SESSIONS, ENDPOINT_MERGE_USER, - SHARED_PREF_ANON_USER_ID + SHARED_PREF_ANON_USER_ID, + SHARED_PREF_ANON_USAGE_TRACKED } from '../../constants'; import { track } from '../../events'; import { getInAppMessages } from '../../inapp'; @@ -88,6 +89,9 @@ describe('UserMergeScenariosTests', () => { if (key === SHARED_PREFS_ANON_SESSIONS) { return JSON.stringify(initialAnonSessionInfo); } + if (key === SHARED_PREF_ANON_USAGE_TRACKED) { + return 'true'; + } return null; }); jest.useFakeTimers(); @@ -270,6 +274,9 @@ describe('UserMergeScenariosTests', () => { if (key === SHARED_PREFS_ANON_SESSIONS) { return JSON.stringify(initialAnonSessionInfo); } + if (key === SHARED_PREF_ANON_USAGE_TRACKED) { + return 'true'; + } return null; }); const { setUserID, logout } = initializeWithConfig({ @@ -587,6 +594,9 @@ describe('UserMergeScenariosTests', () => { if (key === SHARED_PREFS_ANON_SESSIONS) { return JSON.stringify(initialAnonSessionInfo); } + if (key === SHARED_PREF_ANON_USAGE_TRACKED) { + return 'true'; + } return null; }); const { setEmail, logout } = initializeWithConfig({ diff --git a/src/anonymousUserTracking/tests/userUpdate.test.ts b/src/anonymousUserTracking/tests/userUpdate.test.ts index a899dc58..6d1f84c8 100644 --- a/src/anonymousUserTracking/tests/userUpdate.test.ts +++ b/src/anonymousUserTracking/tests/userUpdate.test.ts @@ -6,7 +6,8 @@ import { SHARED_PREFS_CRITERIA, GET_CRITERIA_PATH, ENDPOINT_TRACK_ANON_SESSION, - ENDPOINT_MERGE_USER + ENDPOINT_MERGE_USER, + SHARED_PREF_ANON_USAGE_TRACKED } from '../../constants'; import { updateUser } from '../../users'; import { initializeWithConfig } from '../../authorization'; @@ -95,6 +96,9 @@ describe('UserUpdate', () => { if (key === SHARED_PREFS_ANON_SESSIONS) { return JSON.stringify(initialAnonSessionInfo); } + if (key === SHARED_PREF_ANON_USAGE_TRACKED) { + return 'true'; + } return null; }); diff --git a/src/anonymousUserTracking/tests/validateCustomEventUserUpdateAPI.test.ts b/src/anonymousUserTracking/tests/validateCustomEventUserUpdateAPI.test.ts index 3d98ff6e..0acbceb4 100644 --- a/src/anonymousUserTracking/tests/validateCustomEventUserUpdateAPI.test.ts +++ b/src/anonymousUserTracking/tests/validateCustomEventUserUpdateAPI.test.ts @@ -7,7 +7,8 @@ import { ENDPOINT_MERGE_USER, ENDPOINT_TRACK_ANON_SESSION, GET_CRITERIA_PATH, - GETMESSAGES_PATH + GETMESSAGES_PATH, + SHARED_PREF_ANON_USAGE_TRACKED } from '../../constants'; import { track } from '../../events'; import { initializeWithConfig } from '../../authorization'; @@ -137,6 +138,9 @@ describe('validateCustomEventUserUpdateAPI', () => { if (key === SHARED_PREFS_ANON_SESSIONS) { return JSON.stringify(initialAnonSessionInfo); } + if (key === SHARED_PREF_ANON_USAGE_TRACKED) { + return 'true'; + } return null; }); @@ -221,6 +225,9 @@ describe('validateCustomEventUserUpdateAPI', () => { if (key === SHARED_PREFS_ANON_SESSIONS) { return JSON.stringify(initialAnonSessionInfo); } + if (key === SHARED_PREF_ANON_USAGE_TRACKED) { + return 'true'; + } return null; }); @@ -287,6 +294,9 @@ describe('validateCustomEventUserUpdateAPI', () => { if (key === SHARED_PREFS_ANON_SESSIONS) { return JSON.stringify(initialAnonSessionInfo); } + if (key === SHARED_PREF_ANON_USAGE_TRACKED) { + return 'true'; + } return null; }); @@ -358,6 +368,9 @@ describe('validateCustomEventUserUpdateAPI', () => { if (key === SHARED_PREFS_ANON_SESSIONS) { return JSON.stringify(initialAnonSessionInfo); } + if (key === SHARED_PREF_ANON_USAGE_TRACKED) { + return 'true'; + } return null; }); diff --git a/src/authorization/authorization.ts b/src/authorization/authorization.ts index 1e48d0eb..a6bbeef5 100644 --- a/src/authorization/authorization.ts +++ b/src/authorization/authorization.ts @@ -7,7 +7,9 @@ import { STATIC_HEADERS, SHARED_PREF_ANON_USER_ID, ENDPOINTS, - RouteConfig + RouteConfig, + SHARED_PREF_ANON_USAGE_TRACKED, + SHARED_PREFS_CRITERIA } from 'src/constants'; import { cancelAxiosRequestAndMakeFetch, @@ -21,6 +23,7 @@ import { import { AnonymousUserMerge } from 'src/anonymousUserTracking/anonymousUserMerge'; import { AnonymousUserEventManager, + isAnonymousUsageTracked, registerAnonUserIdSetter } from 'src/anonymousUserTracking/anonymousUserEventManager'; import { Options, config } from 'src/utils/config'; @@ -66,6 +69,7 @@ export interface WithJWT { setUserID: (userId: string) => Promise; logout: () => void; refreshJwtToken: (authTypes: string) => Promise; + toggleAnonUserTrackingConsent: (consent: boolean) => void; } export interface WithoutJWT { @@ -74,9 +78,14 @@ export interface WithoutJWT { setEmail: (email: string) => Promise; setUserID: (userId: string) => Promise; logout: () => void; + toggleAnonUserTrackingConsent: (consent: boolean) => void; } export const setAnonUserId = async (userId: string) => { + const anonymousUsageTracked = isAnonymousUsageTracked(); + + if (!anonymousUsageTracked) return; + let token: null | string = null; if (generateJWTGlobal) { token = await generateJWTGlobal({ userID: userId }); @@ -523,6 +532,32 @@ export function initialize( /* stop adding JWT to requests */ baseAxiosRequest.interceptors.request.eject(userInterceptor); } + }, + toggleAnonUserTrackingConsent: (consent: boolean) => { + /* if consent is true, we want to clear anon user data and start tracking from point forward */ + if (consent) { + anonUserManager.removeAnonSessionCriteriaData(); + localStorage.removeItem(SHARED_PREFS_CRITERIA); + + localStorage.setItem(SHARED_PREF_ANON_USAGE_TRACKED, 'true'); + enableAnonymousTracking(); + } else { + /* if consent is false, we want to stop tracking and clear anon user data */ + const anonymousUsageTracked = isAnonymousUsageTracked(); + if (anonymousUsageTracked) { + anonUserManager.removeAnonSessionCriteriaData(); + + localStorage.removeItem(SHARED_PREFS_CRITERIA); + localStorage.removeItem(SHARED_PREF_ANON_USER_ID); + localStorage.removeItem(SHARED_PREF_ANON_USAGE_TRACKED); + + typeOfAuth = null; + authIdentifier = null; + /* clear fetched in-app messages */ + clearMessages(); + } + localStorage.setItem(SHARED_PREF_ANON_USAGE_TRACKED, 'false'); + } } }; } @@ -878,6 +913,32 @@ export function initialize( console.warn('Could not refresh JWT. Try Refresh the JWT again.'); } }); + }, + toggleAnonUserTrackingConsent: (consent: boolean) => { + /* if consent is true, we want to clear anon user data and start tracking from point forward */ + if (consent) { + anonUserManager.removeAnonSessionCriteriaData(); + localStorage.removeItem(SHARED_PREFS_CRITERIA); + + localStorage.setItem(SHARED_PREF_ANON_USAGE_TRACKED, 'true'); + enableAnonymousTracking(); + } else { + /* if consent is false, we want to stop tracking and clear anon user data */ + const anonymousUsageTracked = isAnonymousUsageTracked(); + if (anonymousUsageTracked) { + anonUserManager.removeAnonSessionCriteriaData(); + + localStorage.removeItem(SHARED_PREFS_CRITERIA); + localStorage.removeItem(SHARED_PREF_ANON_USER_ID); + localStorage.removeItem(SHARED_PREF_ANON_USAGE_TRACKED); + + typeOfAuth = null; + authIdentifier = null; + /* clear fetched in-app messages */ + clearMessages(); + } + localStorage.setItem(SHARED_PREF_ANON_USAGE_TRACKED, 'false'); + } } }; } diff --git a/src/constants.ts b/src/constants.ts index 11dd7792..54a768e5 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -285,6 +285,7 @@ export const SHARED_PREFS_EVENT_LIST_KEY = 'itbl_event_list'; export const SHARED_PREFS_CRITERIA = 'criteria'; export const SHARED_PREFS_ANON_SESSIONS = 'itbl_anon_sessions'; export const SHARED_PREF_ANON_USER_ID = 'anon_userId'; +export const SHARED_PREF_ANON_USAGE_TRACKED = 'itbl_anonymous_usage_tracked'; export const KEY_EVENT_NAME = 'eventName'; export const KEY_CREATED_AT = 'createdAt'; From 66dc2a01313d1c5fa789cd08045c93efa18b3398 Mon Sep 17 00:00:00 2001 From: Mitch Prewitt <44011584+mprew97@users.noreply.github.com> Date: Tue, 15 Oct 2024 10:58:30 -0400 Subject: [PATCH 84/88] [MOB-9899]: Add Auth Checks Before API Calls (#463) * AUT bug bash settings * block api calls if typeOfAuth not set * auth checks and tests * branch fix reversions * fix most of tests --------- Co-authored-by: jyu115 --- src/authorization/authorization.test.ts | 71 ++++++++++++++------- src/authorization/authorization.ts | 7 ++- src/commerce/commerce.test.ts | 4 ++ src/commerce/commerce.ts | 11 ++++ src/constants.ts | 5 +- src/embedded/embeddedManager.test.ts | 7 ++- src/embedded/embeddedManager.ts | 16 ++++- src/events/embedded/events.ts | 24 +++++++- src/events/events.test.ts | 82 ++++++++++++++++++++++++- src/events/events.ts | 4 ++ src/events/inapp/events.ts | 23 ++++++- src/inapp/request.ts | 11 +++- src/inapp/tests/inapp.test.ts | 3 +- src/inapp/tests/utils.test.ts | 5 ++ src/users/users.test.ts | 4 ++ src/users/users.ts | 18 +++++- 16 files changed, 255 insertions(+), 40 deletions(-) diff --git a/src/authorization/authorization.test.ts b/src/authorization/authorization.test.ts index 67d5ee70..88311c45 100644 --- a/src/authorization/authorization.test.ts +++ b/src/authorization/authorization.test.ts @@ -1,6 +1,6 @@ import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; -import { initialize } from './authorization'; +import { initialize, setTypeOfAuthForTestingOnly } from './authorization'; import { baseAxiosRequest } from '../request'; import { getInAppMessages } from '../inapp'; import { track, trackInAppClose } from '../events'; @@ -41,6 +41,8 @@ describe('API Key Interceptors', () => { }); beforeEach(() => { + setTypeOfAuthForTestingOnly('userID'); + mockRequest.onPost('/users/update').reply(200, { data: 'something' }); @@ -342,6 +344,8 @@ describe('API Key Interceptors', () => { describe('User Identification', () => { beforeEach(() => { + setTypeOfAuthForTestingOnly('userID'); + /* clear any interceptors already configured */ [ ...Array( @@ -354,6 +358,7 @@ describe('User Identification', () => { describe('non-JWT auth', () => { beforeAll(() => { + (global as any).localStorage = localStorageMock; mockRequest = new MockAdapter(baseAxiosRequest); mockRequest.onPost('/users/update').reply(200, {}); @@ -369,11 +374,16 @@ describe('User Identification', () => { await setEmail('hello@gmail.com'); logout(); - const response = await getInAppMessages({ - count: 10, - packageName: 'my-lil-website' - }); - expect(response.config.params.email).toBeUndefined(); + try { + await getInAppMessages({ + count: 10, + packageName: 'my-lil-website' + }); + } catch (e) { + expect(e).toStrictEqual( + new Error('Cannot make API request until a user is signed in') + ); + } }); it('logout method removes the userId field from requests', async () => { @@ -382,11 +392,16 @@ describe('User Identification', () => { await setUserID('hello@gmail.com'); logout(); - const response = await getInAppMessages({ - count: 10, - packageName: 'my-lil-website' - }); - expect(response.config.params.userId).toBeUndefined(); + try { + await getInAppMessages({ + count: 10, + packageName: 'my-lil-website' + }); + } catch (e) { + expect(e).toStrictEqual( + new Error('Cannot make API request until a user is signed in') + ); + } }); }); @@ -693,11 +708,16 @@ describe('User Identification', () => { await setEmail('hello@gmail.com'); logout(); - const response = await getInAppMessages({ - count: 10, - packageName: 'my-lil-website' - }); - expect(response.config.params.email).toBeUndefined(); + try { + await getInAppMessages({ + count: 10, + packageName: 'my-lil-website' + }); + } catch (e) { + expect(e).toStrictEqual( + new Error('Cannot make API request until a user is signed in') + ); + } }); it('logout method removes the userId field from requests', async () => { @@ -707,11 +727,16 @@ describe('User Identification', () => { await setUserID('hello@gmail.com'); logout(); - const response = await getInAppMessages({ - count: 10, - packageName: 'my-lil-website' - }); - expect(response.config.params.userId).toBeUndefined(); + try { + await getInAppMessages({ + count: 10, + packageName: 'my-lil-website' + }); + } catch (e) { + expect(e).toStrictEqual( + new Error('Cannot make API request until a user is signed in') + ); + } }); }); @@ -1092,8 +1117,8 @@ describe('User Identification', () => { .fn() .mockReturnValue(Promise.resolve(MOCK_JWT_KEY)); const { refreshJwtToken } = initialize('123', mockGenerateJWT); - await refreshJwtToken('hello@gmail.com'); - + const res = await refreshJwtToken('hello@gmail.com'); + console.log({ res }); expect(mockGenerateJWT).toHaveBeenCalledTimes(1); jest.advanceTimersByTime(60000 * 4.1); expect(mockGenerateJWT).toHaveBeenCalledTimes(2); diff --git a/src/authorization/authorization.ts b/src/authorization/authorization.ts index a6bbeef5..0a50f9e6 100644 --- a/src/authorization/authorization.ts +++ b/src/authorization/authorization.ts @@ -42,7 +42,8 @@ const MAX_TIMEOUT = ONE_DAY; doesn't _need_ to support email-signed JWTs if they don't want and purely want to issue the tokens by user ID. */ -export let typeOfAuth: null | 'email' | 'userID' = null; +export type TypeOfAuth = null | 'email' | 'userID' +export let typeOfAuth: TypeOfAuth = null; /* this will be the literal user ID or email they choose to auth with */ let authIdentifier: null | string = null; let userInterceptor: number | null = null; @@ -973,3 +974,7 @@ export function initializeWithConfig(initializeParams: InitializeParams) { ? initialize(authToken, generateJWT) : initialize(authToken); } + +export function setTypeOfAuthForTestingOnly(authType: TypeOfAuth) { + typeOfAuth = authType +} diff --git a/src/commerce/commerce.test.ts b/src/commerce/commerce.test.ts index 24645400..3f20b023 100644 --- a/src/commerce/commerce.test.ts +++ b/src/commerce/commerce.test.ts @@ -3,10 +3,14 @@ import { baseAxiosRequest } from '../request'; import { trackPurchase, updateCart } from './commerce'; // import { SDK_VERSION, WEB_PLATFORM } from '../constants'; import { createClientError } from '../utils/testUtils'; +import { setTypeOfAuthForTestingOnly } from '../authorization'; const mockRequest = new MockAdapter(baseAxiosRequest); describe('Users Requests', () => { + beforeEach(() => { + setTypeOfAuthForTestingOnly('email'); + }); it('should set params and return the correct payload for updateCart', async () => { mockRequest.onPost('/commerce/updateCart').reply(200, { msg: 'hello' diff --git a/src/commerce/commerce.ts b/src/commerce/commerce.ts index 011b02b2..10f26d70 100644 --- a/src/commerce/commerce.ts +++ b/src/commerce/commerce.ts @@ -6,6 +6,7 @@ import { IterableResponse } from '../types'; import { updateCartSchema, trackPurchaseSchema } from './commerce.schema'; import { AnonymousUserEventManager } from '../anonymousUserTracking/anonymousUserEventManager'; import { canTrackAnonUser } from '../utils/commonFunctions'; +import { typeOfAuth } from '../authorization'; export const updateCart = (payload: UpdateCartRequestParams) => { /* a customer could potentially send these up if they're not using TypeScript */ @@ -18,6 +19,11 @@ export const updateCart = (payload: UpdateCartRequestParams) => { anonymousUserEventManager.trackAnonUpdateCart(payload); return Promise.reject(INITIALIZE_ERROR); } + + if (typeOfAuth === null) { + return Promise.reject(INITIALIZE_ERROR); + } + return baseIterableRequest({ method: 'POST', url: ENDPOINTS.commerce_update_cart.route, @@ -45,6 +51,11 @@ export const trackPurchase = (payload: TrackPurchaseRequestParams) => { anonymousUserEventManager.trackAnonPurchaseEvent(payload); return Promise.reject(INITIALIZE_ERROR); } + + if (typeOfAuth === null) { + return Promise.reject(INITIALIZE_ERROR); + } + return baseIterableRequest({ method: 'POST', url: ENDPOINTS.commerce_track_purchase.route, diff --git a/src/constants.ts b/src/constants.ts index 54a768e5..673be044 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -307,5 +307,6 @@ export const UPDATECART_ITEM_PREFIX = 'updateCart.updatedShoppingCartItems.'; export const PURCHASE_ITEM_PREFIX = `${PURCHASE_ITEM}.`; export const MERGE_SUCCESSFULL = 'MERGE_SUCCESSFULL'; -export const INITIALIZE_ERROR = - 'Iterable SDK must be initialized with an API key and user email/userId before calling SDK methods'; +export const INITIALIZE_ERROR = new Error( + 'Iterable SDK must be initialized with an API key and user email/userId before calling SDK methods' +); diff --git a/src/embedded/embeddedManager.test.ts b/src/embedded/embeddedManager.test.ts index 904f1d2d..724a5890 100644 --- a/src/embedded/embeddedManager.test.ts +++ b/src/embedded/embeddedManager.test.ts @@ -1,4 +1,5 @@ import { IterableEmbeddedManager } from './embeddedManager'; +import { setTypeOfAuthForTestingOnly } from '../authorization'; // Mock the baseIterableRequest function jest.mock('../request', () => ({ @@ -11,6 +12,9 @@ jest.mock('..', () => ({ })); describe('EmbeddedManager', () => { + beforeEach(() => { + setTypeOfAuthForTestingOnly('email'); + }); const appPackageName = 'my-website'; describe('syncMessages', () => { it('should call syncMessages and callback', async () => { @@ -28,7 +32,8 @@ describe('EmbeddedManager', () => { const embeddedManager = new IterableEmbeddedManager(appPackageName); async function mockTest() { - return new Promise(function (resolve, reject) { + return new Promise((resolve, reject) => { + // eslint-disable-next-line prefer-promise-reject-errors reject('Invalid API Key'); }); } diff --git a/src/embedded/embeddedManager.ts b/src/embedded/embeddedManager.ts index e5078480..e97faa25 100644 --- a/src/embedded/embeddedManager.ts +++ b/src/embedded/embeddedManager.ts @@ -7,9 +7,15 @@ import { import { IterableResponse } from '../types'; import { EmbeddedMessagingProcessor } from './embeddedMessageProcessor'; import { ErrorMessage } from './consts'; -import { SDK_VERSION, WEB_PLATFORM, ENDPOINTS } from '../constants'; +import { + SDK_VERSION, + WEB_PLATFORM, + ENDPOINTS, + INITIALIZE_ERROR +} from '../constants'; import { trackEmbeddedReceived } from '../events/embedded/events'; import { handleEmbeddedClick } from './utils'; +import { typeOfAuth } from '../authorization'; export class IterableEmbeddedManager { public appPackageName: string; @@ -27,8 +33,12 @@ export class IterableEmbeddedManager { callback: () => void, placementIds?: number[] ) { - await this.retrieveEmbeddedMessages(packageName, placementIds || []); - callback(); + if (typeOfAuth !== null) { + await this.retrieveEmbeddedMessages(packageName, placementIds || []); + callback(); + } else { + Promise.reject(INITIALIZE_ERROR); + } } private async retrieveEmbeddedMessages( diff --git a/src/events/embedded/events.ts b/src/events/embedded/events.ts index fafcb542..77a6d5d9 100644 --- a/src/events/embedded/events.ts +++ b/src/events/embedded/events.ts @@ -1,4 +1,4 @@ -import { WEB_PLATFORM, ENDPOINTS } from '../../constants'; +import { WEB_PLATFORM, ENDPOINTS, INITIALIZE_ERROR } from '../../constants'; import { baseIterableRequest } from '../../request'; import { IterableEmbeddedDismissRequestPayload, @@ -12,12 +12,17 @@ import { embeddedDismissSchema, embeddedSessionSchema } from './events.schema'; +import { typeOfAuth } from '../../authorization'; export const trackEmbeddedReceived = ( messageId: string, appPackageName: string -) => - baseIterableRequest({ +) => { + if (typeOfAuth === null) { + return Promise.reject(INITIALIZE_ERROR); + } + + return baseIterableRequest({ method: 'POST', url: ENDPOINTS.msg_received_event_track.route, data: { @@ -32,12 +37,17 @@ export const trackEmbeddedReceived = ( data: trackEmbeddedSchema } }); +}; export const trackEmbeddedClick = ( payload: IterableEmbeddedClickRequestPayload ) => { const { appPackageName, ...rest } = payload; + if (typeOfAuth === null) { + return Promise.reject(INITIALIZE_ERROR); + } + return baseIterableRequest({ method: 'POST', url: ENDPOINTS.msg_click_event_track.route, @@ -61,6 +71,10 @@ export const trackEmbeddedDismiss = ( ) => { const { appPackageName, ...rest } = payload; + if (typeOfAuth === null) { + return Promise.reject(INITIALIZE_ERROR); + } + return baseIterableRequest({ method: 'POST', url: ENDPOINTS.msg_dismiss.route, @@ -84,6 +98,10 @@ export const trackEmbeddedSession = ( ) => { const { appPackageName, ...rest } = payload; + if (typeOfAuth === null) { + return Promise.reject(INITIALIZE_ERROR); + } + return baseIterableRequest({ method: 'POST', url: ENDPOINTS.msg_session_event_track.route, diff --git a/src/events/events.test.ts b/src/events/events.test.ts index 7c3eac95..6bc13844 100644 --- a/src/events/events.test.ts +++ b/src/events/events.test.ts @@ -13,8 +13,9 @@ import { trackInAppDelivery, trackInAppOpen } from './inapp/events'; -import { WEB_PLATFORM } from '../constants'; +import { INITIALIZE_ERROR, WEB_PLATFORM } from '../constants'; import { createClientError } from '../utils/testUtils'; +import { setTypeOfAuthForTestingOnly } from '../authorization'; const mockRequest = new MockAdapter(baseAxiosRequest); const localStorageMock = { @@ -60,6 +61,10 @@ describe('Events Requests', () => { }); }); + beforeEach(() => { + setTypeOfAuthForTestingOnly('userID'); + }); + it('return the correct payload for track', async () => { const response = await track({ eventName: 'test' }); @@ -258,6 +263,54 @@ describe('Events Requests', () => { ); } }); + it('should fail trackInAppOpen if not authed', async () => { + try { + setTypeOfAuthForTestingOnly(null); + await trackInAppOpen({} as any); + } catch (e) { + expect(e).toBe(INITIALIZE_ERROR); + } + }); + it('should fail trackInAppClose if not authed', async () => { + try { + setTypeOfAuthForTestingOnly(null); + await trackInAppClose({} as any); + } catch (e) { + expect(e).toBe(INITIALIZE_ERROR); + } + }); + it('should fail trackInAppClick if not authed', async () => { + try { + setTypeOfAuthForTestingOnly(null); + await trackInAppClick({} as any); + } catch (e) { + expect(e).toBe(INITIALIZE_ERROR); + } + }); + it('should fail trackInAppConsume if not authed', async () => { + try { + setTypeOfAuthForTestingOnly(null); + await trackInAppConsume({} as any); + } catch (e) { + expect(e).toBe(INITIALIZE_ERROR); + } + }); + it('should fail trackInAppDelivery if not authed', async () => { + try { + setTypeOfAuthForTestingOnly(null); + await trackInAppDelivery({} as any); + } catch (e) { + expect(e).toBe(INITIALIZE_ERROR); + } + }); + it('should fail trackInAppOpen if not authed', async () => { + try { + setTypeOfAuthForTestingOnly(null); + await trackInAppOpen({} as any); + } catch (e) { + expect(e).toBe(INITIALIZE_ERROR); + } + }); it('return the correct payload for embedded message received', async () => { const response = await trackEmbeddedReceived('abc123', 'packageName'); @@ -477,4 +530,31 @@ describe('Events Requests', () => { expect(JSON.parse(trackSessionResponse.config.data).email).toBeUndefined(); expect(JSON.parse(trackSessionResponse.config.data).userId).toBeUndefined(); }); + + it('should fail if no auth type set for embedded received', async () => { + setTypeOfAuthForTestingOnly(null); + try { + await trackEmbeddedReceived('abc123', 'packageName'); + } catch (e) { + expect(e).toBe(INITIALIZE_ERROR); + } + }); + + it('should fail if no auth type set for embedded click', async () => { + setTypeOfAuthForTestingOnly(null); + try { + await trackEmbeddedClick({} as any); + } catch (e) { + expect(e).toBe(INITIALIZE_ERROR); + } + }); + + it('should fail if no auth type set for embedded session', async () => { + setTypeOfAuthForTestingOnly(null); + try { + await trackEmbeddedSession({} as any); + } catch (e) { + expect(e).toBe(INITIALIZE_ERROR); + } + }); }); diff --git a/src/events/events.ts b/src/events/events.ts index 486cb61f..155b0ffe 100644 --- a/src/events/events.ts +++ b/src/events/events.ts @@ -6,6 +6,7 @@ import { IterableResponse } from '../types'; import { trackSchema } from './events.schema'; import { AnonymousUserEventManager } from '../anonymousUserTracking/anonymousUserEventManager'; import { canTrackAnonUser } from '../utils/commonFunctions'; +import { typeOfAuth } from '../authorization'; export const track = (payload: InAppTrackRequestParams) => { /* a customer could potentially send these up if they're not using TypeScript */ @@ -16,6 +17,9 @@ export const track = (payload: InAppTrackRequestParams) => { anonymousUserEventManager.trackAnonEvent(payload); return Promise.reject(INITIALIZE_ERROR); } + if (typeOfAuth === null) { + return Promise.reject(INITIALIZE_ERROR); + } return baseIterableRequest({ method: 'POST', url: ENDPOINTS.event_track.route, diff --git a/src/events/inapp/events.ts b/src/events/inapp/events.ts index 16c21d34..cf66a5cb 100644 --- a/src/events/inapp/events.ts +++ b/src/events/inapp/events.ts @@ -2,14 +2,19 @@ import { baseIterableRequest } from '../../request'; import { InAppEventRequestParams } from './types'; import { IterableResponse } from '../../types'; -import { ENDPOINTS, WEB_PLATFORM } from '../../constants'; +import { ENDPOINTS, INITIALIZE_ERROR, WEB_PLATFORM } from '../../constants'; import { eventRequestSchema } from './events.schema'; +import { typeOfAuth } from '../../authorization'; 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; + if (typeOfAuth === null) { + return Promise.reject(INITIALIZE_ERROR); + } + return baseIterableRequest({ method: 'POST', url: ENDPOINTS.track_app_close.route, @@ -37,6 +42,10 @@ export const trackInAppOpen = ( delete (payload as any).userId; delete (payload as any).email; + if (typeOfAuth === null) { + return Promise.reject(INITIALIZE_ERROR); + } + return baseIterableRequest({ method: 'POST', url: ENDPOINTS.track_app_open.route, @@ -66,6 +75,10 @@ export const trackInAppClick = ( delete (payload as any).userId; delete (payload as any).email; + if (typeOfAuth === null) { + return Promise.reject(INITIALIZE_ERROR); + } + return baseIterableRequest({ method: 'POST', url: ENDPOINTS.track_app_click.route, @@ -94,6 +107,10 @@ export const trackInAppDelivery = ( delete (payload as any).userId; delete (payload as any).email; + if (typeOfAuth === null) { + return Promise.reject(INITIALIZE_ERROR); + } + return baseIterableRequest({ method: 'POST', url: ENDPOINTS.track_app_delivery.route, @@ -125,6 +142,10 @@ export const trackInAppConsume = ( delete (payload as any).userId; delete (payload as any).email; + if (typeOfAuth === null) { + return Promise.reject(INITIALIZE_ERROR); + } + return baseIterableRequest({ method: 'POST', url: ENDPOINTS.track_app_consume.route, diff --git a/src/inapp/request.ts b/src/inapp/request.ts index bffe2f1a..4fd8c7f4 100644 --- a/src/inapp/request.ts +++ b/src/inapp/request.ts @@ -1,5 +1,6 @@ /* eslint-disable no-unreachable */ import { delMany, entries } from 'idb-keyval'; +import { typeOfAuth } from '../authorization'; import { GETMESSAGES_PATH, SDK_VERSION, WEB_PLATFORM } from '../constants'; import { baseIterableRequest } from '../request'; import { addNewMessagesToCache, getCachedMessagesToDelete } from './cache'; @@ -19,8 +20,13 @@ type RequestInAppMessagesProps = { export const requestInAppMessages = ({ latestCachedMessageId, payload -}: RequestInAppMessagesProps) => - baseIterableRequest({ +}: RequestInAppMessagesProps) => { + if (typeOfAuth === null) { + return Promise.reject( + new Error('Cannot make API request until a user is signed in') + ); + } + return baseIterableRequest({ method: 'GET', /** @note TBD: Parameter will be enabled once new endpoint is ready */ // url: options?.useLocalCache ? CACHE_ENABLED_GETMESSAGES_PATH : GETMESSAGES_PATH, @@ -33,6 +39,7 @@ export const requestInAppMessages = ({ latestCachedMessageId } }); +}; type RequestMessagesProps = { payload: InAppMessagesRequestParams; diff --git a/src/inapp/tests/inapp.test.ts b/src/inapp/tests/inapp.test.ts index 5c40c7bc..86793a72 100644 --- a/src/inapp/tests/inapp.test.ts +++ b/src/inapp/tests/inapp.test.ts @@ -3,7 +3,7 @@ */ import MockAdapter from 'axios-mock-adapter'; import { messages } from '../../__data__/inAppMessages'; -import { initialize } from '../../authorization'; +import { initialize, setTypeOfAuthForTestingOnly } from '../../authorization'; import { GETMESSAGES_PATH, SDK_VERSION, WEB_PLATFORM } from '../../constants'; import { baseAxiosRequest } from '../../request'; import { createClientError } from '../../utils/testUtils'; @@ -20,6 +20,7 @@ describe('getInAppMessages', () => { beforeEach(() => { jest.clearAllMocks(); jest.resetAllMocks(); + setTypeOfAuthForTestingOnly('email'); mockRequest.resetHistory(); mockRequest.onGet(GETMESSAGES_PATH).reply(200, { diff --git a/src/inapp/tests/utils.test.ts b/src/inapp/tests/utils.test.ts index c80633e9..00778d42 100644 --- a/src/inapp/tests/utils.test.ts +++ b/src/inapp/tests/utils.test.ts @@ -19,6 +19,7 @@ import { sortInAppMessages, trackMessagesDelivered } from '../utils'; +import { setTypeOfAuthForTestingOnly } from '../../authorization'; jest.mock('../../utils/srSpeak', () => ({ srSpeak: jest.fn() @@ -33,6 +34,9 @@ const mockMarkup = ` `; describe('Utils', () => { + beforeEach(() => { + setTypeOfAuthForTestingOnly('email'); + }); describe('filterHiddenInAppMessages', () => { it('should filter out read messages', () => { expect(filterHiddenInAppMessages()).toEqual([]); @@ -869,6 +873,7 @@ describe('Utils', () => { expect(el.getAttribute('aria-label')).toBe('hello'); expect(el.getAttribute('role')).toBe('button'); + // eslint-disable-next-line no-script-url expect(el.getAttribute('href')).toBe('javascript:undefined'); }); diff --git a/src/users/users.test.ts b/src/users/users.test.ts index 48187aa7..b4cfc9a5 100644 --- a/src/users/users.test.ts +++ b/src/users/users.test.ts @@ -2,11 +2,15 @@ import MockAdapter from 'axios-mock-adapter'; import { baseAxiosRequest } from '../request'; import { updateSubscriptions, updateUser, updateUserEmail } from './users'; import { createClientError } from '../utils/testUtils'; +import { setTypeOfAuthForTestingOnly } from '../authorization'; // import { SDK_VERSION, WEB_PLATFORM } from '../constants'; const mockRequest = new MockAdapter(baseAxiosRequest); describe('Users Requests', () => { + beforeEach(() => { + setTypeOfAuthForTestingOnly('email'); + }); it('should set params and return the correct payload for updateUser', async () => { mockRequest.onPost('/users/update').reply(200, { msg: 'hello' diff --git a/src/users/users.ts b/src/users/users.ts index 538637d7..42e9c8c0 100644 --- a/src/users/users.ts +++ b/src/users/users.ts @@ -7,9 +7,15 @@ import { updateSubscriptionsSchema, updateUserSchema } from './users.schema'; import { AnonymousUserEventManager } from '../anonymousUserTracking/anonymousUserEventManager'; import { canTrackAnonUser } from '../utils/commonFunctions'; import { INITIALIZE_ERROR, ENDPOINTS } from '../constants'; +import { typeOfAuth } from '../authorization'; -export const updateUserEmail = (newEmail: string) => - baseIterableRequest({ +export const updateUserEmail = (newEmail: string) => { + if (typeOfAuth === null) { + return Promise.reject( + new Error('Cannot make API request until a user is signed in') + ); + } + return baseIterableRequest({ method: 'POST', url: ENDPOINTS.update_email.route, data: { @@ -21,6 +27,7 @@ export const updateUserEmail = (newEmail: string) => }) } }); +}; export const updateUser = (payloadParam: UpdateUserParams = {}) => { /* a customer could potentially send these up if they're not using TypeScript */ @@ -33,6 +40,9 @@ export const updateUser = (payloadParam: UpdateUserParams = {}) => { anonymousUserEventManager.trackAnonUpdateUser(payload); return Promise.reject(INITIALIZE_ERROR); } + if (typeOfAuth === null) { + return Promise.reject(INITIALIZE_ERROR); + } return baseIterableRequest({ method: 'POST', url: ENDPOINTS.users_update.route, @@ -54,6 +64,10 @@ export const updateSubscriptions = ( delete (payload as any).userId; delete (payload as any).email; + if (typeOfAuth === null) { + return Promise.reject(INITIALIZE_ERROR); + } + return baseIterableRequest({ method: 'POST', url: ENDPOINTS.users_update_subscriptions.route, From fdf8dd73bf87f93289a64ab3540bb2582a3a676f Mon Sep 17 00:00:00 2001 From: Mitch Prewitt <44011584+mprew97@users.noreply.github.com> Date: Tue, 15 Oct 2024 15:34:32 -0400 Subject: [PATCH 85/88] [MOB-9955]: Allow IdentityResolution Overrides and Move onAnonUserCreated in Config (#464) * shuffle onAnonUserCreated * allow identity resolution overrides on setEmail/setUserId --- react-example/src/indexWithoutJWT.tsx | 6 ++-- .../anonymousUserEventManager.ts | 3 +- src/authorization/authorization.ts | 34 +++++++++---------- src/utils/config.ts | 4 +-- 4 files changed, 22 insertions(+), 25 deletions(-) diff --git a/react-example/src/indexWithoutJWT.tsx b/react-example/src/indexWithoutJWT.tsx index b5e5d8a6..bdedcbc7 100644 --- a/react-example/src/indexWithoutJWT.tsx +++ b/react-example/src/indexWithoutJWT.tsx @@ -47,10 +47,8 @@ const HomeLink = styled(Link)` isEuIterableService: false, dangerouslyAllowJsPopups: true, enableAnonTracking: true, - identityResolution: { - onAnonUserCreated: (userId: string) => { - console.log('onAnonUserCreated', userId); - } + onAnonUserCreated: (userId: string) => { + console.log('onAnonUserCreated', userId); } } }; diff --git a/src/anonymousUserTracking/anonymousUserEventManager.ts b/src/anonymousUserTracking/anonymousUserEventManager.ts index dbe48653..d55d48b3 100644 --- a/src/anonymousUserTracking/anonymousUserEventManager.ts +++ b/src/anonymousUserTracking/anonymousUserEventManager.ts @@ -248,8 +248,7 @@ export class AnonymousUserEventManager { ) ); - const onAnonUserCreated = - config.getConfig('identityResolution')?.onAnonUserCreated; + const onAnonUserCreated = config.getConfig('onAnonUserCreated'); if (onAnonUserCreated) { onAnonUserCreated(userId); diff --git a/src/authorization/authorization.ts b/src/authorization/authorization.ts index 0a50f9e6..b5020216 100644 --- a/src/authorization/authorization.ts +++ b/src/authorization/authorization.ts @@ -26,7 +26,7 @@ import { isAnonymousUsageTracked, registerAnonUserIdSetter } from 'src/anonymousUserTracking/anonymousUserEventManager'; -import { Options, config } from 'src/utils/config'; +import { IdentityResolution, Options, config } from 'src/utils/config'; const MAX_TIMEOUT = ONE_DAY; /* @@ -483,12 +483,12 @@ export function initialize( baseAxiosRequest.interceptors.request.eject(authInterceptor); } }, - setEmail: async (email: string) => { + setEmail: async (email: string, identityResolution?: IdentityResolution) => { clearMessages(); try { - const identityResolution = config.getConfig('identityResolution'); - const merge = identityResolution?.mergeOnAnonymousToKnown; - const replay = identityResolution?.replayOnVisitorToKnown; + const identityResolutionConfig = config.getConfig('identityResolution'); + const merge = identityResolution?.mergeOnAnonymousToKnown || identityResolutionConfig?.mergeOnAnonymousToKnown; + const replay = identityResolution?.replayOnVisitorToKnown || identityResolutionConfig?.replayOnVisitorToKnown; const result = await tryMergeUser(email, true, merge); if (result) { @@ -500,12 +500,12 @@ export function initialize( return Promise.reject(`merging failed: ${error}`); } }, - setUserID: async (userId: string) => { + setUserID: async (userId: string, identityResolution?: IdentityResolution) => { clearMessages(); try { - const identityResolution = config.getConfig('identityResolution'); - const merge = identityResolution?.mergeOnAnonymousToKnown; - const replay = identityResolution?.replayOnVisitorToKnown; + const identityResolutionConfig = config.getConfig('identityResolution'); + const merge = identityResolution?.mergeOnAnonymousToKnown || identityResolutionConfig?.mergeOnAnonymousToKnown; + const replay = identityResolution?.replayOnVisitorToKnown || identityResolutionConfig?.replayOnVisitorToKnown; const result = await tryMergeUser(userId, false, merge); if (result) { @@ -821,13 +821,13 @@ export function initialize( /* this will just clear the existing timeout */ handleTokenExpiration(''); }, - setEmail: async (email: string) => { + setEmail: async (email: string, identityResolution?: IdentityResolution) => { /* clear previous user */ clearMessages(); try { - const identityResolution = config.getConfig('identityResolution'); - const merge = identityResolution?.mergeOnAnonymousToKnown; - const replay = identityResolution?.replayOnVisitorToKnown; + const identityResolutionConfig = config.getConfig('identityResolution'); + const merge = identityResolution?.mergeOnAnonymousToKnown || identityResolutionConfig?.mergeOnAnonymousToKnown; + const replay = identityResolution?.replayOnVisitorToKnown || identityResolutionConfig?.replayOnVisitorToKnown; const result = await tryMergeUser(email, true, merge); if (result) { @@ -851,12 +851,12 @@ export function initialize( return Promise.reject(`merging failed: ${error}`); } }, - setUserID: async (userId: string) => { + setUserID: async (userId: string, identityResolution?: IdentityResolution) => { clearMessages(); try { - const identityResolution = config.getConfig('identityResolution'); - const merge = identityResolution?.mergeOnAnonymousToKnown; - const replay = identityResolution?.replayOnVisitorToKnown; + const identityResolutionConfig = config.getConfig('identityResolution'); + const merge = identityResolution?.mergeOnAnonymousToKnown || identityResolutionConfig?.mergeOnAnonymousToKnown; + const replay = identityResolution?.replayOnVisitorToKnown || identityResolutionConfig?.replayOnVisitorToKnown; const result = await tryMergeUser(userId, false, merge); if (result) { diff --git a/src/utils/config.ts b/src/utils/config.ts index 241d2e12..5cc7e65e 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -1,9 +1,8 @@ import { BASE_URL, DEFAULT_EVENT_THRESHOLD_LIMIT } from '../constants'; -type IdentityResolution = { +export type IdentityResolution = { replayOnVisitorToKnown?: boolean; mergeOnAnonymousToKnown?: boolean; - onAnonUserCreated?: (userId: string) => void; }; export type Options = { @@ -13,6 +12,7 @@ export type Options = { isEuIterableService: boolean; dangerouslyAllowJsPopups: boolean; eventThresholdLimit?: number; + onAnonUserCreated?: (userId: string) => void; identityResolution?: IdentityResolution; }; From 1cf59eee0b21e14b7e4d2b884fa51d70bfb7337f Mon Sep 17 00:00:00 2001 From: Mitch Prewitt <44011584+mprew97@users.noreply.github.com> Date: Wed, 16 Oct 2024 09:35:00 -0400 Subject: [PATCH 86/88] fix replay issue with JWT (#465) --- src/authorization/authorization.ts | 38 ++++++++++++++++++------------ 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/src/authorization/authorization.ts b/src/authorization/authorization.ts index b5020216..d2de01dc 100644 --- a/src/authorization/authorization.ts +++ b/src/authorization/authorization.ts @@ -118,13 +118,10 @@ const getAnonUserId = () => { } }; -const initializeUserIdAndSync = (userId: string, replay?: boolean) => { +const initializeUserId = (userId: string) => { addUserIdToRequest(userId); clearAnonymousUser(); - if (replay) { - syncEvents(); - } -}; +} const addUserIdToRequest = (userId: string) => { typeOfAuth = 'userID'; @@ -221,13 +218,10 @@ const addUserIdToRequest = (userId: string) => { }); }; -const initializeEmailUserAndSync = (email: string, replay?: boolean) => { +const initializeEmailUser = (email: string) => { addEmailToRequest(email); clearAnonymousUser(); - if (replay) { - syncEvents(); - } -}; +} const syncEvents = () => { if (config.getConfig('enableAnonTracking')) { @@ -492,7 +486,10 @@ export function initialize( const result = await tryMergeUser(email, true, merge); if (result) { - initializeEmailUserAndSync(email, replay); + initializeEmailUser(email); + if (replay) { + syncEvents(); + } return Promise.resolve(); } } catch (error) { @@ -509,7 +506,10 @@ export function initialize( const result = await tryMergeUser(userId, false, merge); if (result) { - initializeUserIdAndSync(userId, replay); + initializeUserId(userId); + if (replay) { + syncEvents(); + } return Promise.resolve(); } } catch (error) { @@ -831,9 +831,14 @@ export function initialize( const result = await tryMergeUser(email, true, merge); if (result) { - initializeEmailUserAndSync(email, replay); + initializeEmailUser(email); try { - return doRequest({ email }).catch((e) => { + return doRequest({ email }).then((token) => { + if (replay) { + syncEvents(); + } + return token; + }).catch((e) => { if (logLevel === 'verbose') { console.warn( 'Could not generate JWT after calling setEmail. Please try calling setEmail again.' @@ -860,10 +865,13 @@ export function initialize( const result = await tryMergeUser(userId, false, merge); if (result) { - initializeUserIdAndSync(userId, replay); + initializeUserId(userId); try { return doRequest({ userID: userId }) .then(async (token) => { + if (replay) { + syncEvents(); + } return token; }) .catch((e) => { From b3f50c4c330b2f8aad7872b2e4d28e2bacccd964 Mon Sep 17 00:00:00 2001 From: Mitch Prewitt <44011584+mprew97@users.noreply.github.com> Date: Thu, 17 Oct 2024 09:41:24 -0400 Subject: [PATCH 87/88] [MOB-9954]: fix userMergeScenario test (#466) * fix replay issue with JWT * fix spec --- .../tests/userMergeScenarios.test.ts | 51 ++++++++++++------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/src/anonymousUserTracking/tests/userMergeScenarios.test.ts b/src/anonymousUserTracking/tests/userMergeScenarios.test.ts index a5546eec..6d15ad4a 100644 --- a/src/anonymousUserTracking/tests/userMergeScenarios.test.ts +++ b/src/anonymousUserTracking/tests/userMergeScenarios.test.ts @@ -296,10 +296,14 @@ describe('UserMergeScenariosTests', () => { console.log('', e); } // this function call is needed for putting some delay before executing setUserId - await getInAppMessages({ - count: 10, - packageName: 'my-lil-website' - }); + try { + await getInAppMessages({ + count: 10, + packageName: 'my-lil-website' + }); + } catch (e) { + console.log(e); + } await setUserID('testuser123'); expect(localStorageMock.removeItem).toHaveBeenCalledWith( SHARED_PREF_ANON_USER_ID @@ -342,10 +346,14 @@ describe('UserMergeScenariosTests', () => { } catch (e) { console.log('', e); } - await getInAppMessages({ - count: 10, - packageName: 'my-lil-website' - }); + try { + await getInAppMessages({ + count: 10, + packageName: 'my-lil-website' + }); + } catch (e) { + console.log(e); + } await setUserID('testuser123'); expect(localStorageMock.removeItem).toHaveBeenCalledWith( SHARED_PREF_ANON_USER_ID @@ -599,7 +607,7 @@ describe('UserMergeScenariosTests', () => { } return null; }); - const { setEmail, logout } = initializeWithConfig({ + const { setEmail } = initializeWithConfig({ authToken: '123', configOptions: { enableAnonTracking: true, @@ -609,16 +617,19 @@ describe('UserMergeScenariosTests', () => { } } }); - logout(); // logout to remove logged in users before this test try { await track({ eventName: 'testEvent' }); } catch (e) { console.log('', e); } - await getInAppMessages({ - count: 10, - packageName: 'my-lil-website' - }); + try { + await getInAppMessages({ + count: 10, + packageName: 'my-lil-website' + }); + } catch (e) { + console.log(e); + } await setEmail('testuser123@test.com'); expect(localStorageMock.removeItem).toHaveBeenCalledWith( SHARED_PREF_ANON_USER_ID @@ -661,10 +672,14 @@ describe('UserMergeScenariosTests', () => { } catch (e) { console.log('', e); } - await getInAppMessages({ - count: 10, - packageName: 'my-lil-website' - }); + try { + await getInAppMessages({ + count: 10, + packageName: 'my-lil-website' + }); + } catch (e) { + console.log(e); + } await setEmail('testuser123@test.com'); expect(localStorageMock.removeItem).toHaveBeenCalledWith( SHARED_PREF_ANON_USER_ID From 061b42cd14c94a937fd17b4a766105b3fd3a5801 Mon Sep 17 00:00:00 2001 From: Mitch Prewitt <44011584+mprew97@users.noreply.github.com> Date: Thu, 17 Oct 2024 09:54:36 -0400 Subject: [PATCH 88/88] [MOB-9954]: Fix Circular Dependency and Clean Up Auth Checks on API Calls (#467) * fix replay issue with JWT * fix spec * move initialize check over * add type of auth util * clean up auth checks and circular deps * add getter * fix * lets see * add additional endpoints --- src/authorization/authorization.test.ts | 21 ++++---------- src/authorization/authorization.ts | 38 +++++++++---------------- src/commerce/commerce.ts | 9 ------ src/embedded/embeddedManager.ts | 16 ++--------- src/events/embedded/events.ts | 24 ++-------------- src/events/events.ts | 4 --- src/events/inapp/events.ts | 23 +-------------- src/inapp/request.ts | 11 ++----- src/request.ts | 26 ++++++++++++++++- src/users/users.ts | 18 ++---------- src/utils/commonFunctions.ts | 4 +-- src/utils/typeOfAuth.ts | 24 ++++++++++++++++ 12 files changed, 82 insertions(+), 136 deletions(-) create mode 100644 src/utils/typeOfAuth.ts diff --git a/src/authorization/authorization.test.ts b/src/authorization/authorization.test.ts index 88311c45..2e51bef4 100644 --- a/src/authorization/authorization.test.ts +++ b/src/authorization/authorization.test.ts @@ -6,7 +6,7 @@ import { getInAppMessages } from '../inapp'; import { track, trackInAppClose } from '../events'; import { updateSubscriptions, updateUser, updateUserEmail } from '../users'; import { trackPurchase, updateCart } from '../commerce'; -import { GETMESSAGES_PATH } from '../constants'; +import { GETMESSAGES_PATH, INITIALIZE_ERROR } from '../constants'; const localStorageMock = { getItem: jest.fn(), @@ -380,9 +380,7 @@ describe('User Identification', () => { packageName: 'my-lil-website' }); } catch (e) { - expect(e).toStrictEqual( - new Error('Cannot make API request until a user is signed in') - ); + expect(e).toStrictEqual(INITIALIZE_ERROR); } }); @@ -398,9 +396,7 @@ describe('User Identification', () => { packageName: 'my-lil-website' }); } catch (e) { - expect(e).toStrictEqual( - new Error('Cannot make API request until a user is signed in') - ); + expect(e).toStrictEqual(INITIALIZE_ERROR); } }); }); @@ -714,9 +710,7 @@ describe('User Identification', () => { packageName: 'my-lil-website' }); } catch (e) { - expect(e).toStrictEqual( - new Error('Cannot make API request until a user is signed in') - ); + expect(e).toStrictEqual(INITIALIZE_ERROR); } }); @@ -733,9 +727,7 @@ describe('User Identification', () => { packageName: 'my-lil-website' }); } catch (e) { - expect(e).toStrictEqual( - new Error('Cannot make API request until a user is signed in') - ); + expect(e).toStrictEqual(INITIALIZE_ERROR); } }); }); @@ -1117,8 +1109,7 @@ describe('User Identification', () => { .fn() .mockReturnValue(Promise.resolve(MOCK_JWT_KEY)); const { refreshJwtToken } = initialize('123', mockGenerateJWT); - const res = await refreshJwtToken('hello@gmail.com'); - console.log({ res }); + await refreshJwtToken('hello@gmail.com'); expect(mockGenerateJWT).toHaveBeenCalledTimes(1); jest.advanceTimersByTime(60000 * 4.1); expect(mockGenerateJWT).toHaveBeenCalledTimes(2); diff --git a/src/authorization/authorization.ts b/src/authorization/authorization.ts index d2de01dc..f2d31000 100644 --- a/src/authorization/authorization.ts +++ b/src/authorization/authorization.ts @@ -27,24 +27,9 @@ import { registerAnonUserIdSetter } from 'src/anonymousUserTracking/anonymousUserEventManager'; import { IdentityResolution, Options, config } from 'src/utils/config'; +import { getTypeOfAuth, setTypeOfAuth, TypeOfAuth } from 'src/utils/typeOfAuth'; const MAX_TIMEOUT = ONE_DAY; -/* - AKA did the user auth with their email (setEmail) or user ID (setUserID) - - we're going to use this variable for one circumstance - when calling _updateUserEmail_. - Essentially, when we call the Iterable API to update a user's email address and we get a - successful 200 request, we're going to request a new JWT token, since it might need to - be re-signed with the new email address; however, if the customer code never authorized the - user with an email and instead a user ID, we'll just continue to sign the JWT with the user ID. - - This is mainly just a quality-of-life feature, so that the customer's JWT generation code - doesn't _need_ to support email-signed JWTs if they don't want and purely want to issue the - tokens by user ID. - */ -export type TypeOfAuth = null | 'email' | 'userID' -export let typeOfAuth: TypeOfAuth = null; -/* this will be the literal user ID or email they choose to auth with */ let authIdentifier: null | string = null; let userInterceptor: number | null = null; let apiKey: null | string = null; @@ -124,7 +109,7 @@ const initializeUserId = (userId: string) => { } const addUserIdToRequest = (userId: string) => { - typeOfAuth = 'userID'; + setTypeOfAuth('userID'); authIdentifier = userId; if (typeof userInterceptor === 'number') { @@ -230,7 +215,7 @@ const syncEvents = () => { }; const addEmailToRequest = (email: string) => { - typeOfAuth = 'email'; + setTypeOfAuth('email'); authIdentifier = email; if (typeof userInterceptor === 'number') { @@ -426,6 +411,7 @@ export function initialize( isEmail: boolean, merge?: boolean ): Promise => { + const typeOfAuth = getTypeOfAuth(); const enableAnonTracking = config.getConfig('enableAnonTracking'); const sourceUserIdOrEmail = authIdentifier === null ? getAnonUserId() : authIdentifier; @@ -519,7 +505,7 @@ export function initialize( }, logout: () => { anonUserManager.removeAnonSessionCriteriaData(); - typeOfAuth = null; + setTypeOfAuth(null); authIdentifier = null; /* clear fetched in-app messages */ clearMessages(); @@ -552,7 +538,7 @@ export function initialize( localStorage.removeItem(SHARED_PREF_ANON_USER_ID); localStorage.removeItem(SHARED_PREF_ANON_USAGE_TRACKED); - typeOfAuth = null; + setTypeOfAuth(null); authIdentifier = null; /* clear fetched in-app messages */ clearMessages(); @@ -633,7 +619,7 @@ export function initialize( const newEmail = JSON.parse(config.config.data)?.newEmail; const payloadToPass = - typeOfAuth === 'email' + getTypeOfAuth() === 'email' ? { email: newEmail } : { userID: authIdentifier! }; @@ -894,7 +880,7 @@ export function initialize( }, logout: () => { anonUserManager.removeAnonSessionCriteriaData(); - typeOfAuth = null; + setTypeOfAuth(null); authIdentifier = null; /* clear fetched in-app messages */ clearMessages(); @@ -941,7 +927,7 @@ export function initialize( localStorage.removeItem(SHARED_PREF_ANON_USER_ID); localStorage.removeItem(SHARED_PREF_ANON_USAGE_TRACKED); - typeOfAuth = null; + setTypeOfAuth(null); authIdentifier = null; /* clear fetched in-app messages */ clearMessages(); @@ -984,5 +970,9 @@ export function initializeWithConfig(initializeParams: InitializeParams) { } export function setTypeOfAuthForTestingOnly(authType: TypeOfAuth) { - typeOfAuth = authType + if (!authType) { + setTypeOfAuth(null); + } else { + setTypeOfAuth(authType); + } } diff --git a/src/commerce/commerce.ts b/src/commerce/commerce.ts index 10f26d70..523568d8 100644 --- a/src/commerce/commerce.ts +++ b/src/commerce/commerce.ts @@ -6,7 +6,6 @@ import { IterableResponse } from '../types'; import { updateCartSchema, trackPurchaseSchema } from './commerce.schema'; import { AnonymousUserEventManager } from '../anonymousUserTracking/anonymousUserEventManager'; import { canTrackAnonUser } from '../utils/commonFunctions'; -import { typeOfAuth } from '../authorization'; export const updateCart = (payload: UpdateCartRequestParams) => { /* a customer could potentially send these up if they're not using TypeScript */ @@ -20,10 +19,6 @@ export const updateCart = (payload: UpdateCartRequestParams) => { return Promise.reject(INITIALIZE_ERROR); } - if (typeOfAuth === null) { - return Promise.reject(INITIALIZE_ERROR); - } - return baseIterableRequest({ method: 'POST', url: ENDPOINTS.commerce_update_cart.route, @@ -52,10 +47,6 @@ export const trackPurchase = (payload: TrackPurchaseRequestParams) => { return Promise.reject(INITIALIZE_ERROR); } - if (typeOfAuth === null) { - return Promise.reject(INITIALIZE_ERROR); - } - return baseIterableRequest({ method: 'POST', url: ENDPOINTS.commerce_track_purchase.route, diff --git a/src/embedded/embeddedManager.ts b/src/embedded/embeddedManager.ts index e97faa25..e5078480 100644 --- a/src/embedded/embeddedManager.ts +++ b/src/embedded/embeddedManager.ts @@ -7,15 +7,9 @@ import { import { IterableResponse } from '../types'; import { EmbeddedMessagingProcessor } from './embeddedMessageProcessor'; import { ErrorMessage } from './consts'; -import { - SDK_VERSION, - WEB_PLATFORM, - ENDPOINTS, - INITIALIZE_ERROR -} from '../constants'; +import { SDK_VERSION, WEB_PLATFORM, ENDPOINTS } from '../constants'; import { trackEmbeddedReceived } from '../events/embedded/events'; import { handleEmbeddedClick } from './utils'; -import { typeOfAuth } from '../authorization'; export class IterableEmbeddedManager { public appPackageName: string; @@ -33,12 +27,8 @@ export class IterableEmbeddedManager { callback: () => void, placementIds?: number[] ) { - if (typeOfAuth !== null) { - await this.retrieveEmbeddedMessages(packageName, placementIds || []); - callback(); - } else { - Promise.reject(INITIALIZE_ERROR); - } + await this.retrieveEmbeddedMessages(packageName, placementIds || []); + callback(); } private async retrieveEmbeddedMessages( diff --git a/src/events/embedded/events.ts b/src/events/embedded/events.ts index 77a6d5d9..fafcb542 100644 --- a/src/events/embedded/events.ts +++ b/src/events/embedded/events.ts @@ -1,4 +1,4 @@ -import { WEB_PLATFORM, ENDPOINTS, INITIALIZE_ERROR } from '../../constants'; +import { WEB_PLATFORM, ENDPOINTS } from '../../constants'; import { baseIterableRequest } from '../../request'; import { IterableEmbeddedDismissRequestPayload, @@ -12,17 +12,12 @@ import { embeddedDismissSchema, embeddedSessionSchema } from './events.schema'; -import { typeOfAuth } from '../../authorization'; export const trackEmbeddedReceived = ( messageId: string, appPackageName: string -) => { - if (typeOfAuth === null) { - return Promise.reject(INITIALIZE_ERROR); - } - - return baseIterableRequest({ +) => + baseIterableRequest({ method: 'POST', url: ENDPOINTS.msg_received_event_track.route, data: { @@ -37,17 +32,12 @@ export const trackEmbeddedReceived = ( data: trackEmbeddedSchema } }); -}; export const trackEmbeddedClick = ( payload: IterableEmbeddedClickRequestPayload ) => { const { appPackageName, ...rest } = payload; - if (typeOfAuth === null) { - return Promise.reject(INITIALIZE_ERROR); - } - return baseIterableRequest({ method: 'POST', url: ENDPOINTS.msg_click_event_track.route, @@ -71,10 +61,6 @@ export const trackEmbeddedDismiss = ( ) => { const { appPackageName, ...rest } = payload; - if (typeOfAuth === null) { - return Promise.reject(INITIALIZE_ERROR); - } - return baseIterableRequest({ method: 'POST', url: ENDPOINTS.msg_dismiss.route, @@ -98,10 +84,6 @@ export const trackEmbeddedSession = ( ) => { const { appPackageName, ...rest } = payload; - if (typeOfAuth === null) { - return Promise.reject(INITIALIZE_ERROR); - } - return baseIterableRequest({ method: 'POST', url: ENDPOINTS.msg_session_event_track.route, diff --git a/src/events/events.ts b/src/events/events.ts index 155b0ffe..486cb61f 100644 --- a/src/events/events.ts +++ b/src/events/events.ts @@ -6,7 +6,6 @@ import { IterableResponse } from '../types'; import { trackSchema } from './events.schema'; import { AnonymousUserEventManager } from '../anonymousUserTracking/anonymousUserEventManager'; import { canTrackAnonUser } from '../utils/commonFunctions'; -import { typeOfAuth } from '../authorization'; export const track = (payload: InAppTrackRequestParams) => { /* a customer could potentially send these up if they're not using TypeScript */ @@ -17,9 +16,6 @@ export const track = (payload: InAppTrackRequestParams) => { anonymousUserEventManager.trackAnonEvent(payload); return Promise.reject(INITIALIZE_ERROR); } - if (typeOfAuth === null) { - return Promise.reject(INITIALIZE_ERROR); - } return baseIterableRequest({ method: 'POST', url: ENDPOINTS.event_track.route, diff --git a/src/events/inapp/events.ts b/src/events/inapp/events.ts index cf66a5cb..16c21d34 100644 --- a/src/events/inapp/events.ts +++ b/src/events/inapp/events.ts @@ -2,19 +2,14 @@ import { baseIterableRequest } from '../../request'; import { InAppEventRequestParams } from './types'; import { IterableResponse } from '../../types'; -import { ENDPOINTS, INITIALIZE_ERROR, WEB_PLATFORM } from '../../constants'; +import { ENDPOINTS, WEB_PLATFORM } from '../../constants'; import { eventRequestSchema } from './events.schema'; -import { typeOfAuth } from '../../authorization'; 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; - if (typeOfAuth === null) { - return Promise.reject(INITIALIZE_ERROR); - } - return baseIterableRequest({ method: 'POST', url: ENDPOINTS.track_app_close.route, @@ -42,10 +37,6 @@ export const trackInAppOpen = ( delete (payload as any).userId; delete (payload as any).email; - if (typeOfAuth === null) { - return Promise.reject(INITIALIZE_ERROR); - } - return baseIterableRequest({ method: 'POST', url: ENDPOINTS.track_app_open.route, @@ -75,10 +66,6 @@ export const trackInAppClick = ( delete (payload as any).userId; delete (payload as any).email; - if (typeOfAuth === null) { - return Promise.reject(INITIALIZE_ERROR); - } - return baseIterableRequest({ method: 'POST', url: ENDPOINTS.track_app_click.route, @@ -107,10 +94,6 @@ export const trackInAppDelivery = ( delete (payload as any).userId; delete (payload as any).email; - if (typeOfAuth === null) { - return Promise.reject(INITIALIZE_ERROR); - } - return baseIterableRequest({ method: 'POST', url: ENDPOINTS.track_app_delivery.route, @@ -142,10 +125,6 @@ export const trackInAppConsume = ( delete (payload as any).userId; delete (payload as any).email; - if (typeOfAuth === null) { - return Promise.reject(INITIALIZE_ERROR); - } - return baseIterableRequest({ method: 'POST', url: ENDPOINTS.track_app_consume.route, diff --git a/src/inapp/request.ts b/src/inapp/request.ts index 4fd8c7f4..bffe2f1a 100644 --- a/src/inapp/request.ts +++ b/src/inapp/request.ts @@ -1,6 +1,5 @@ /* eslint-disable no-unreachable */ import { delMany, entries } from 'idb-keyval'; -import { typeOfAuth } from '../authorization'; import { GETMESSAGES_PATH, SDK_VERSION, WEB_PLATFORM } from '../constants'; import { baseIterableRequest } from '../request'; import { addNewMessagesToCache, getCachedMessagesToDelete } from './cache'; @@ -20,13 +19,8 @@ type RequestInAppMessagesProps = { export const requestInAppMessages = ({ latestCachedMessageId, payload -}: RequestInAppMessagesProps) => { - if (typeOfAuth === null) { - return Promise.reject( - new Error('Cannot make API request until a user is signed in') - ); - } - return baseIterableRequest({ +}: RequestInAppMessagesProps) => + baseIterableRequest({ method: 'GET', /** @note TBD: Parameter will be enabled once new endpoint is ready */ // url: options?.useLocalCache ? CACHE_ENABLED_GETMESSAGES_PATH : GETMESSAGES_PATH, @@ -39,7 +33,6 @@ export const requestInAppMessages = ({ latestCachedMessageId } }); -}; type RequestMessagesProps = { payload: InAppMessagesRequestParams; diff --git a/src/request.ts b/src/request.ts index 8badc433..53ff86ac 100644 --- a/src/request.ts +++ b/src/request.ts @@ -1,9 +1,18 @@ import Axios, { AxiosRequestConfig } from 'axios'; import qs from 'qs'; import { AnySchema, ValidationError } from 'yup'; -import { BASE_URL, STATIC_HEADERS, EU_ITERABLE_API } from './constants'; +import { + BASE_URL, + STATIC_HEADERS, + EU_ITERABLE_API, + GET_CRITERIA_PATH, + INITIALIZE_ERROR, + ENDPOINT_MERGE_USER, + ENDPOINT_TRACK_ANON_SESSION +} from './constants'; import { IterablePromise, IterableResponse } from './types'; import { config } from './utils/config'; +import { getTypeOfAuth } from './utils/typeOfAuth'; interface ExtendedRequestConfig extends AxiosRequestConfig { validation?: { @@ -20,6 +29,12 @@ interface ClientError extends IterableResponse { }[]; } +const ENDPOINTS_REQUIRING_SET_USER = [ + GET_CRITERIA_PATH, + ENDPOINT_MERGE_USER, + ENDPOINT_TRACK_ANON_SESSION +]; + export const baseAxiosRequest = Axios.create({ baseURL: BASE_URL }); @@ -28,6 +43,15 @@ export const baseIterableRequest = ( payload: ExtendedRequestConfig ): IterablePromise => { try { + const endpoint = payload?.url ?? ''; + + // for most Iterable API endpoints, we require a user to be initialized in the SDK. + if ( + !ENDPOINTS_REQUIRING_SET_USER.includes(endpoint) && + getTypeOfAuth() === null + ) { + return Promise.reject(INITIALIZE_ERROR); + } if (payload.validation?.data && payload.data) { payload.validation.data.validateSync(payload.data, { abortEarly: false }); } diff --git a/src/users/users.ts b/src/users/users.ts index 42e9c8c0..538637d7 100644 --- a/src/users/users.ts +++ b/src/users/users.ts @@ -7,15 +7,9 @@ import { updateSubscriptionsSchema, updateUserSchema } from './users.schema'; import { AnonymousUserEventManager } from '../anonymousUserTracking/anonymousUserEventManager'; import { canTrackAnonUser } from '../utils/commonFunctions'; import { INITIALIZE_ERROR, ENDPOINTS } from '../constants'; -import { typeOfAuth } from '../authorization'; -export const updateUserEmail = (newEmail: string) => { - if (typeOfAuth === null) { - return Promise.reject( - new Error('Cannot make API request until a user is signed in') - ); - } - return baseIterableRequest({ +export const updateUserEmail = (newEmail: string) => + baseIterableRequest({ method: 'POST', url: ENDPOINTS.update_email.route, data: { @@ -27,7 +21,6 @@ export const updateUserEmail = (newEmail: string) => { }) } }); -}; export const updateUser = (payloadParam: UpdateUserParams = {}) => { /* a customer could potentially send these up if they're not using TypeScript */ @@ -40,9 +33,6 @@ export const updateUser = (payloadParam: UpdateUserParams = {}) => { anonymousUserEventManager.trackAnonUpdateUser(payload); return Promise.reject(INITIALIZE_ERROR); } - if (typeOfAuth === null) { - return Promise.reject(INITIALIZE_ERROR); - } return baseIterableRequest({ method: 'POST', url: ENDPOINTS.users_update.route, @@ -64,10 +54,6 @@ export const updateSubscriptions = ( delete (payload as any).userId; delete (payload as any).email; - if (typeOfAuth === null) { - return Promise.reject(INITIALIZE_ERROR); - } - return baseIterableRequest({ method: 'POST', url: ENDPOINTS.users_update_subscriptions.route, diff --git a/src/utils/commonFunctions.ts b/src/utils/commonFunctions.ts index 24b3715b..ecf94eab 100644 --- a/src/utils/commonFunctions.ts +++ b/src/utils/commonFunctions.ts @@ -1,5 +1,5 @@ -import { typeOfAuth } from '../authorization/authorization'; import config from './config'; +import { getTypeOfAuth } from './typeOfAuth'; export const canTrackAnonUser = (): boolean => - config.getConfig('enableAnonTracking') && typeOfAuth === null; + config.getConfig('enableAnonTracking') && getTypeOfAuth() === null; diff --git a/src/utils/typeOfAuth.ts b/src/utils/typeOfAuth.ts new file mode 100644 index 00000000..38092020 --- /dev/null +++ b/src/utils/typeOfAuth.ts @@ -0,0 +1,24 @@ +/* eslint-disable import/no-mutable-exports */ + +/* + AKA did the user auth with their email (setEmail) or user ID (setUserID) + + we're going to use this variable for one circumstance - when calling _updateUserEmail_. + Essentially, when we call the Iterable API to update a user's email address and we get a + successful 200 request, we're going to request a new JWT token, since it might need to + be re-signed with the new email address; however, if the customer code never authorized the + user with an email and instead a user ID, we'll just continue to sign the JWT with the user ID. + + This is mainly just a quality-of-life feature, so that the customer's JWT generation code + doesn't _need_ to support email-signed JWTs if they don't want and purely want to issue the + tokens by user ID. + */ +/* this will be the literal user ID or email they choose to auth with */ + +export type TypeOfAuth = null | 'email' | 'userID'; +let typeOfAuth: TypeOfAuth = null; +export const setTypeOfAuth = (value: TypeOfAuth) => { + typeOfAuth = value; +}; + +export const getTypeOfAuth = () => typeOfAuth;