-
Notifications
You must be signed in to change notification settings - Fork 9
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
base: main
Are you sure you want to change the base?
AUT Feature #404
Changes from 26 commits
0266240
287aa89
8a178d6
e1a9651
cff5e8b
0f3861e
bc57c34
1cf0763
c0c9f17
a9ccb27
04155d3
8b20a65
bf750bc
2c8c031
44999e3
72eb4a3
6ffc682
ec08641
39e1729
594b1cc
4d4a6f3
492addb
f123483
9691279
b953345
27b4792
1f35dd1
c2dcfcb
8525a72
8bc20a3
ea4031c
0abef34
3a905db
24f8ac0
e6cfdd7
0f2fcab
9a02a89
e672975
0ece6da
0bafce5
3dd3ab7
065e5cf
9b69989
e511b7e
38863fd
f1ab176
78069c8
3a4a4bc
8caeaae
0c4597e
6a75ded
263bd46
909f7b0
9a8911d
db1c7ae
0b1480e
6d50998
41ffd79
06c55d9
7c04dba
c403521
85a2d22
2606561
6102cd3
80ceb0e
c8d6dbf
5d60f23
55e06f7
9061631
7ae6910
fc1d7ec
4197cb2
2eda85d
904f602
0218166
93e1b0f
16baf6c
4013132
25fcde1
8f6e6a7
c9a304a
1963926
aa2a124
51bd3a2
5b7ffa0
604ea66
c720d4f
275b460
1d28695
66dc2a0
fdf8dd7
1cf59ee
b3f50c4
061b42c
9c41772
1387a2d
fd7d971
7699b03
2f3792e
baa3f7c
f95cc67
0c0fd2d
c293ab4
ebd61bf
4ceae33
4b1829a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
<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(); | ||
} | ||
``` |
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; | ||
|
@@ -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"}}' | ||
: ''; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why shouldn't this just be a JSON object instead? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 }; | ||
|
@@ -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"> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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); | ||
|
||
|
@@ -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 = () => { | ||
|
@@ -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); | ||
}; | ||
|
||
|
@@ -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. [email protected]" | ||
type="email" | ||
//type="email" | ||
mprew97 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
required | ||
/> | ||
<Button type="submit">Change</Button> | ||
|
@@ -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. [email protected]" | ||
type="email" | ||
//type="email" | ||
mprew97 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
required | ||
data-qa-login-input | ||
/> | ||
|
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.