Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AUT Feature #404

Open
wants to merge 94 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
94 commits
Select commit Hold shift + click to select a range
0266240
Integrate AUT feature (#301)
hani-iterable Mar 4, 2024
287aa89
Add user object to trackanonsession and create user after live/valid …
hardikmashru Apr 23, 2024
8a178d6
Merge branch 'main' into AUT_main
hardikmashru May 24, 2024
e1a9651
aut changes
hardikmashru May 30, 2024
cff5e8b
pushed files temporarily
hardikmashru May 30, 2024
0f3861e
some updates for AUT fixes
hardikmashru May 30, 2024
bc57c34
updates
hardikmashru Jun 3, 2024
1cf0763
Criteria bugs fixed
Jun 4, 2024
c0c9f17
Criteria bugs fixed
Jun 4, 2024
a9ccb27
Criteria bugs fixed
Jun 4, 2024
04155d3
Criteria bugs fixed
Jun 4, 2024
8b20a65
Merge branch 'main' into AUT_main
hardikmashru Jun 4, 2024
bf750bc
event test undo
hardikmashru Jun 4, 2024
2c8c031
Update events.ts
hardikmashru Jun 4, 2024
44999e3
updateuser changes
hardikmashru Jun 4, 2024
72eb4a3
updates
hardikmashru Jun 4, 2024
6ffc682
more fixes and updates
hardikmashru Jun 4, 2024
ec08641
Authorization test file fixed
Jun 5, 2024
39e1729
Fixed destination email case
Jun 6, 2024
594b1cc
Sync events in case of anon user not created and developer tries to c…
Jun 6, 2024
4d4a6f3
Handle updateCart flow separately for Anon event and when syncing data
Jun 6, 2024
492addb
Fixed all the test file with authorixation and annon user event manager
Jun 7, 2024
f123483
Fixed JWT case
Jun 7, 2024
9691279
removed logs and did some improvements
hardikmashru Jun 7, 2024
b953345
fixed some circular dependencies
hardikmashru Jun 7, 2024
27b4792
keep current identity of anon user if merge fails
hardikmashru Jun 7, 2024
1f35dd1
logic changes for criteria
hardikmashru Jun 13, 2024
c2dcfcb
Simple min match works
Jun 14, 2024
8525a72
Not check event again when matched criteria
Jun 17, 2024
8bc20a3
Fixed event stored in local as it is and remove criteria id when sync
Jun 17, 2024
ea4031c
remove criteria id from events if not available in criteria list when…
Jun 17, 2024
0abef34
JWT changes added in example
Jun 17, 2024
3a905db
Merge branch 'main' into AUT_main
Jun 17, 2024
24f8ac0
Authorization changes pushed
Jun 17, 2024
e6cfdd7
Authorization test fxed
Jun 17, 2024
0f2fcab
Fixed comments
Jun 17, 2024
9a02a89
Create new AUT example
Jun 17, 2024
e672975
Merge branch 'AUT_main' into AUT_main_logicchanges2
Jun 17, 2024
0ece6da
Update setUserId
Jun 17, 2024
0bafce5
webpack updated
Jun 17, 2024
3dd3ab7
Fixed test files
Jun 17, 2024
065e5cf
Merge branch 'AUT_main' into AUT_main_logicchanges2
Jun 17, 2024
9b69989
remove criteria from utils
Jun 18, 2024
e511b7e
Test file is fixed and criteria matched
Jun 18, 2024
38863fd
Remove logs
Jun 18, 2024
f1ab176
Revert Users and Commerce changes
Jun 18, 2024
78069c8
Fixed import circular dependency
Jun 18, 2024
3a4a4bc
Fixed circular dependancy
Jun 18, 2024
8caeaae
Update imports
Jun 18, 2024
0c4597e
Reverted eventform
Jun 18, 2024
6a75ded
Fixed circular dependency
Jun 18, 2024
263bd46
more fixes
hardikmashru Jun 18, 2024
909f7b0
Update webpack.config.js
hardikmashru Jun 18, 2024
9a8911d
fixed some suggestions
hardikmashru Jun 18, 2024
db1c7ae
Fixed comment
Jun 19, 2024
0b1480e
Fixed single item matches code (#410)
hardikmashru Jul 1, 2024
6d50998
Add merge param (#414)
hardikmashru Jul 4, 2024
41ffd79
MOB 8960 (#415)
hardikmashru Jul 11, 2024
06c55d9
Isset purchase update fix (#416)
hardikmashru Jul 12, 2024
7c04dba
Fixed bools needs to be string (#422)
darshan-iterable Jul 31, 2024
c403521
MOB-9055: Resolve nested criteria match issue (#423)
darshan-iterable Aug 5, 2024
85a2d22
MOB-9138: Resolves DoesNotEqual criteria match issue (#426)
darshan-iterable Aug 6, 2024
2606561
Merge branch 'main' into AUT_main
hani-iterable Aug 9, 2024
6102cd3
Merge branch 'main' into AUT_main 2606561
hani-iterable Aug 9, 2024
80ceb0e
revert back eslint rule
hani-iterable Aug 9, 2024
c8d6dbf
Resolve path related error
hani-iterable Aug 12, 2024
5d60f23
[MOB-9258] fixed nested IsSet matching (#427)
darshan-iterable Aug 12, 2024
55e06f7
MOB-9305: fixed events createdAt timeStamps (#431)
darshan-iterable Aug 19, 2024
9061631
MOB-9168: Written automated unit tests against Combination logic with…
darshan-iterable Aug 19, 2024
7ae6910
MOB-8824: Added limitation to event storage (#430)
darshan-iterable Aug 19, 2024
fc1d7ec
Nested custom event check related change (#432)
hani-iterable Aug 19, 2024
4197cb2
MOB 9328 - Verify AUT works with JWT (#437)
hani-iterable Aug 22, 2024
2eda85d
MOB 9149 - Sample app object updates not formatted properly (#438)
hani-iterable Aug 23, 2024
904f602
MOB-9308: supports nested field types (#439)
darshan-iterable Aug 29, 2024
0218166
MOB-9081: Written automated unit tests for different field types and …
darshan-iterable Aug 29, 2024
93e1b0f
fully supports comparison for data in Array data with all comparator …
darshan-iterable Sep 4, 2024
16baf6c
MOB 9328 add JWT in response (#441)
hani-iterable Sep 4, 2024
4013132
updated nested field logic (#445)
darshan-iterable Sep 5, 2024
25fcde1
MOB-9145: support isOneOf and isNotOneOf comparator (#446)
darshan-iterable Sep 6, 2024
8f6e6a7
[MOB-9522]: Fix JWT UserID Support (#452)
mprew97 Sep 12, 2024
c9a304a
[MOB-9505] rename merge parameter (#450)
darshan-iterable Sep 17, 2024
1963926
MOB-9307 Add test to validate object created by custom event and user…
darshan-iterable Sep 18, 2024
aa2a124
[MOB-9402] update user should not be a separate call (#453)
darshan-iterable Sep 23, 2024
51bd3a2
[MOB-9568] update "criterias" to "criteriaSets" (#456)
darshan-iterable Sep 25, 2024
5b7ffa0
[MOB-9578] implements identity resolution (#458)
darshan-iterable Oct 2, 2024
604ea66
MOB-9650 Added support for nested criteria match a.b.c (#457)
darshan-iterable Oct 2, 2024
c720d4f
[MOB-9652] support for nested JSON array (#459)
darshan-iterable Oct 7, 2024
275b460
[MOB-9639] Added handler for notifying customer app of a newly create…
darshan-iterable Oct 7, 2024
1d28695
[MOB-9640] Keep AUT off until concent to track is granted (#462)
darshan-iterable Oct 15, 2024
66dc2a0
[MOB-9899]: Add Auth Checks Before API Calls (#463)
mprew97 Oct 15, 2024
fdf8dd7
[MOB-9955]: Allow IdentityResolution Overrides and Move onAnonUserCre…
mprew97 Oct 15, 2024
1cf59ee
fix replay issue with JWT (#465)
mprew97 Oct 16, 2024
b3f50c4
[MOB-9954]: fix userMergeScenario test (#466)
mprew97 Oct 17, 2024
061b42c
[MOB-9954]: Fix Circular Dependency and Clean Up Auth Checks on API C…
mprew97 Oct 17, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 138 additions & 0 deletions AnonymousUserEventTracking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# Anonymous User Event Tracking Iterable's Web SDK
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bradumbaugh do we want to keep/update this readme?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mprew97 no, let's please pull the readme from the 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
<script src="https://unpkg.com/@iterable/web-sdk/index.js"></script>
```

# 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();
}
```
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"axios": "^1.6.2",
"buffer": "^6.0.3",
"idb-keyval": "^6.2.0",
"moment": "^2.29.4",
mprew97 marked this conversation as resolved.
Show resolved Hide resolved
"throttle-debounce": "^3.0.1",
"yup": "^0.32.9"
},
Expand Down Expand Up @@ -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",
Expand Down
5 changes: 4 additions & 1 deletion react-example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,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"
}
}
81 changes: 59 additions & 22 deletions react-example/src/components/EventsForm.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import { FC, FormEvent, useState } from 'react';
import { v4 as uuidv4 } from 'uuid';
import {
Button,
EndpointWrapper,
Form,
Heading,
Response
} from '../views/Components.styled';
import { IterablePromise, IterableResponse } from '@iterable/web-sdk';
import {
InAppTrackRequestParams,
initialize,
IterablePromise,
IterableResponse
} from '@iterable/web-sdk';
import TextField from 'src/components/TextField';
import { useUser } from 'src/context/Users';

interface Props {
endpointName: string;
Expand All @@ -22,35 +29,61 @@ export const EventsForm: FC<Props> = ({
heading,
needsEventName
}) => {
const { loggedInUser, setLoggedInUser } = useUser();
const [trackResponse, setTrackResponse] = useState<string>(
'Endpoint JSON goes here'
);

const [trackEvent, setTrackEvent] = useState<string>('');

const eventInput =
endpointName === 'track'
? '{"eventName":"button-clicked", "dataFields": {"browserVisit.website.domain":"https://mybrand.com/socks"}}'
: '';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why shouldn't this just be a JSON object instead?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have considered json string as default input value for code consistencty because user will also input the json string from in sample app and so we have to basically parse it to JSON Object in both the scenarios.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, i think from the sample app, i'd also expect it to be a json object.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mprew97 Can you please explain how it can be a JSON object when any input we pass is considered a String?

const [trackEvent, setTrackEvent] = useState<string>(eventInput);
const [isTrackingEvent, setTrackingEvent] = useState<boolean>(false);

const handleTrack = (e: FormEvent<HTMLFormElement>) => {
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<HTMLFormElement>) => {
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);
})
.catch((e) => {
setTrackResponse(JSON.stringify(e.response.data));
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);
})
.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 };
Expand All @@ -63,13 +96,17 @@ export const EventsForm: FC<Props> = ({
<EndpointWrapper>
<Form onSubmit={handleTrack} {...formAttr}>
<label htmlFor="item-1">
{needsEventName ? 'Enter Event Name' : 'Enter Message ID'}
{needsEventName ? 'Enter valid JSON' : 'Enter Message ID'}
</label>
<TextField
value={trackEvent}
onChange={(e) => 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}
/>
<Button disabled={isTrackingEvent} type="submit">
Expand Down
33 changes: 17 additions & 16 deletions react-example/src/components/LoginForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,14 @@ const Form = styled.form`
`;

interface Props {
setEmail: (email: string) => Promise<string>;
setUserId: (userId: string) => Promise<void>;
logout: () => void;
refreshJwt: (authTypes: string) => Promise<string>;
}

export const LoginForm: FC<Props> = ({ setEmail, logout, refreshJwt }) => {
const [email, updateEmail] = useState<string>(process.env.LOGIN_EMAIL || '');
export const LoginForm: FC<Props> = ({ setUserId, logout }) => {
const [userId, updateUserId] = useState<string>(
process.env.LOGIN_EMAIL || ''
);

const [isEditingUser, setEditingUser] = useState<boolean>(false);

Expand All @@ -42,12 +43,12 @@ export const LoginForm: FC<Props> = ({ setEmail, logout, refreshJwt }) => {
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
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 = () => {
Expand All @@ -56,16 +57,16 @@ export const LoginForm: FC<Props> = ({ setEmail, logout, refreshJwt }) => {
};

const handleJwtRefresh = () => {
refreshJwt(email);
//refreshJwt(userId);
};

const handleEditUser = () => {
updateEmail(loggedInUser);
updateUserId(loggedInUser);
setEditingUser(true);
};

const handleCancelEditUser = () => {
updateEmail('');
updateUserId('');
setEditingUser(false);
};

Expand All @@ -78,10 +79,10 @@ export const LoginForm: FC<Props> = ({ setEmail, logout, refreshJwt }) => {
isEditingUser ? (
<Form onSubmit={handleSubmit}>
<TextField
onChange={(e) => updateEmail(e.target.value)}
value={email}
onChange={(e) => updateUserId(e.target.value)}
value={userId}
placeholder="e.g. hello@gmail.com"
type="email"
//type="email"
mprew97 marked this conversation as resolved.
Show resolved Hide resolved
required
/>
<Button type="submit">Change</Button>
Expand All @@ -101,10 +102,10 @@ export const LoginForm: FC<Props> = ({ setEmail, logout, refreshJwt }) => {
) : (
<Form onSubmit={handleSubmit} data-qa-login-form>
<TextField
onChange={(e) => updateEmail(e.target.value)}
value={email}
onChange={(e) => updateUserId(e.target.value)}
value={userId}
placeholder="e.g. hello@gmail.com"
type="email"
//type="email"
mprew97 marked this conversation as resolved.
Show resolved Hide resolved
required
data-qa-login-input
/>
Expand Down
Loading
Loading