Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1944,7 +1944,7 @@ Configuration options to pass to [`initializeWithConfig`](#initializewithconfig)
type Options = {
logLevel: 'none' | 'verbose';
baseURL: string;
enableUnknownActivation: boolean;
enableUnknownUserActivation: boolean;
isEuIterableService: boolean;
dangerouslyAllowJsPopups: boolean;
eventThresholdLimit?: number;
Expand Down Expand Up @@ -2460,7 +2460,7 @@ import { initializeWithConfig } from '@iterable/web-sdk';
const { setEmail, setUserID, setVisitorUsageTracked, clearVisitorEventsAndUserData } = initializeWithConfig({
authToken: '<YOUR_API_KEY>',
configOptions: {
enableUnknownActivation: true,
enableUnknownUserActivation: true,
identityResolution: {
mergeOnUnknownToKnown: true, // default
replayOnVisitorToKnown: true // default
Expand All @@ -2487,15 +2487,17 @@ const { setEmail, setUserID, setVisitorUsageTracked, clearVisitorEventsAndUserDa
### Persistence and restoration across sessions

- Storage: the unknown user id is stored in `localStorage` under a project-scoped key. It has no TTL and persists across reloads and restarts until explicitly cleared.
- Automatic restoration: on initialize (with `enableUnknownActivation: true`), the SDK restores the unknown id from storage and automatically applies it to outgoing requests for supported endpoints.
- Automatic restoration: on initialize (with `enableUnknownUserActivation: true`), the SDK restores the unknown id from storage and automatically applies it to outgoing requests for supported endpoints.
- JWT mode: when using a JWT-enabled API key and consent is present, the SDK generates a JWT for the restored unknown id and attaches it so requests are authenticated.
- Lifecycle end: the unknown id is cleared when you identify (merge then clear), revoke consent via `setVisitorUsageTracked(false)`, call `clearVisitorEventsAndUserData()`, or when browser storage is cleared.

### Configuration options (UUA-specific)

```ts
type Options = {
enableUnknownActivation: boolean; // Enable UUA (default: false)
enableUnknownUserActivation: boolean; // Enable UUA (default: false)
/** @deprecated Use `enableUnknownUserActivation` instead. */
enableUnknownActivation?: boolean;
eventThresholdLimit?: number; // Queue flush threshold (default provided by SDK)
onUnknownUserCreated?: (userId: string) => void; // Callback when unknown user id is created
identityResolution?: {
Expand All @@ -2509,7 +2511,7 @@ type Options = {
### Logic flow (what happens under the hood)

1. Initialization
- When `enableUnknownActivation` is true, SDK fetches unknown user criteria and starts an unknown session.
- When `enableUnknownUserActivation` is true, SDK fetches unknown user criteria and starts an unknown session.
- If an unknown user id exists in storage, SDK restores it and authenticates appropriately.
2. Event collection (unknown)
- When consent is set via `setVisitorUsageTracked(true)`, SDK queues unknown events locally.
Expand All @@ -2521,7 +2523,7 @@ type Options = {
4. Replay (optional)
- If `replayOnVisitorToKnown` is true, SDK replays queued unknown events under the known identity and clears the unknown queue.
5. Cleanup
- After merge attempt (successful or skipped), SDK clears the stored unknown user id and related anonymous state.
- After merge attempt (successful or skipped), SDK clears the stored unknown user id and related unknown state.

Notes:
- UUA works for Events, In-App, Embedded, Commerce, and Users APIs; the SDK ensures requests are authenticated correctly pre/post identification.
Expand Down
2 changes: 1 addition & 1 deletion react-example/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ const HomeLink = styled(Link)`
configOptions: {
isEuIterableService: false,
dangerouslyAllowJsPopups: true,
enableUnknownActivation: true,
enableUnknownUserActivation: true,
identityResolution: {
replayOnVisitorToKnown: true,
mergeOnUnknownToKnown: true
Expand Down
2 changes: 1 addition & 1 deletion react-example/src/indexWithoutJWT.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const HomeLink = styled(Link)`
configOptions: {
isEuIterableService: false,
dangerouslyAllowJsPopups: true,
enableUnknownActivation: true,
enableUnknownUserActivation: true,
identityResolution: {
replayOnVisitorToKnown: true,
mergeOnUnknownToKnown: true
Expand Down
2 changes: 1 addition & 1 deletion src/authorization/authorization.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jest.mock('../utils/config', () => ({
...jest.requireActual('../utils/config'),
default: {
getConfig: jest.fn((key) => {
if (key === 'enableUnknownActivation') {
if (key === 'enableUnknownUserActivation') {
return false;
}
return undefined;
Expand Down
21 changes: 15 additions & 6 deletions src/authorization/authorization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import {
IS_PRODUCTION,
STATIC_HEADERS,
SHARED_PREF_UNKNOWN_USER_ID,
LEGACY_SHARED_PREF_UNKNOWN_USER_ID,
RouteConfig,
SHARED_PREFS_CRITERIA,
SHARED_PREF_CONSENT_TIMESTAMP,
SHARED_PREF_EMAIL,
SHARED_PREF_USER_ID,
RETRY_USER_ATTEMPTS
} from '../constants';
import { migrateLegacyKey } from '../utils/commonFunctions';
import { UnknownUserMerge } from '../unknownUserTracking/unknownUserMerge';
import {
UnknownUserEventManager,
Expand Down Expand Up @@ -227,7 +229,7 @@ const clearUnknownUser = () => {
};

const getUnknownUserId = () => {
if (config.getConfig('enableUnknownActivation')) {
if (config.getConfig('enableUnknownUserActivation')) {
const unknownUser = localStorage.getItem(SHARED_PREF_UNKNOWN_USER_ID);
return unknownUser === undefined ? null : unknownUser;
}
Expand Down Expand Up @@ -341,7 +343,7 @@ const initializeEmailUser = (email: string) => {
};

const syncEvents = () => {
if (config.getConfig('enableUnknownActivation')) {
if (config.getConfig('enableUnknownUserActivation')) {
unknownUserManager.syncEvents();
}
};
Expand All @@ -350,7 +352,7 @@ const handleConsentTracking = (
isUserKnown = false,
isMergeOperation = false
) => {
if (config.getConfig('enableUnknownActivation')) {
if (config.getConfig('enableUnknownUserActivation')) {
unknownUserManager.handleConsentTracking(isUserKnown, isMergeOperation);
}
};
Expand Down Expand Up @@ -396,6 +398,11 @@ export function initialize(
) {
apiKey = authToken;
generateJWTGlobal = generateJWT;
// One-shot migration from the legacy non-prefixed unknown user id key.
migrateLegacyKey(
LEGACY_SHARED_PREF_UNKNOWN_USER_ID,
SHARED_PREF_UNKNOWN_USER_ID
);
const logLevel = config.getConfig('logLevel');
if (!generateJWT && IS_PRODUCTION) {
/* only let people use non-JWT mode if running the app locally */
Expand Down Expand Up @@ -485,7 +492,7 @@ export function initialize(

const enableUnknownTracking = () => {
try {
if (config.getConfig('enableUnknownActivation')) {
if (config.getConfig('enableUnknownUserActivation')) {
unknownUserManager.getUnknownCriteria();
unknownUserManager.updateUnknownSession();
const unknownUserId = getUnknownUserId();
Expand All @@ -504,14 +511,16 @@ export function initialize(
isEmail: boolean,
merge?: boolean
): Promise<{ success: boolean; mergePerformed: boolean }> => {
const enableUnknownActivation = config.getConfig('enableUnknownActivation');
const enableUnknownUserActivation = config.getConfig(
'enableUnknownUserActivation'
);
const destinationUserId = isEmail ? null : emailOrUserId;
const destinationEmail = isEmail ? emailOrUserId : null;

// Only merge if there's an unknown user that was successfully created via /session
const unknownUserId = getUnknownUserId();

if (unknownUserId !== null && merge && enableUnknownActivation) {
if (unknownUserId !== null && merge && enableUnknownUserActivation) {
const unknownUserMerge = new UnknownUserMerge();
try {
await unknownUserMerge.mergeUnknownUser(
Expand Down
2 changes: 1 addition & 1 deletion src/authorization/authorization.unknown.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { updateUser } from '../users';

jest.mock('../utils/config', () => {
const getConfig = (key: string) => {
if (key === 'enableUnknownActivation') return true;
if (key === 'enableUnknownUserActivation') return true;
if (key === 'baseURL') return 'https://api.iterable.com';
if (key === 'logLevel') return 'none';
if (key === 'identityResolution') {
Expand Down
6 changes: 3 additions & 3 deletions src/commerce/commerce.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable no-param-reassign */
import { ENDPOINTS, AUA_WARNING } from '../constants';
import { ENDPOINTS, UUA_WARNING } from '../constants';
import { baseIterableRequest } from '../request';
import { TrackPurchaseRequestParams, UpdateCartRequestParams } from './types';
import { IterableResponse } from '../types';
Expand All @@ -10,13 +10,13 @@
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;

Check warning on line 13 in src/commerce/commerce.ts

View workflow job for this annotation

GitHub Actions / build

Unexpected any. Specify a different type

Check warning on line 13 in src/commerce/commerce.ts

View workflow job for this annotation

GitHub Actions / e2e-tests (chromium)

Unexpected any. Specify a different type

Check warning on line 13 in src/commerce/commerce.ts

View workflow job for this annotation

GitHub Actions / e2e-tests (firefox)

Unexpected any. Specify a different type

Check warning on line 13 in src/commerce/commerce.ts

View workflow job for this annotation

GitHub Actions / e2e-tests (webkit)

Unexpected any. Specify a different type
delete (payload as any).user.email;

Check warning on line 14 in src/commerce/commerce.ts

View workflow job for this annotation

GitHub Actions / build

Unexpected any. Specify a different type

Check warning on line 14 in src/commerce/commerce.ts

View workflow job for this annotation

GitHub Actions / e2e-tests (chromium)

Unexpected any. Specify a different type

Check warning on line 14 in src/commerce/commerce.ts

View workflow job for this annotation

GitHub Actions / e2e-tests (firefox)

Unexpected any. Specify a different type

Check warning on line 14 in src/commerce/commerce.ts

View workflow job for this annotation

GitHub Actions / e2e-tests (webkit)

Unexpected any. Specify a different type
}
if (canTrackUnknownUser()) {
const unknownUserEventManager = new UnknownUserEventManager();
unknownUserEventManager.trackUnknownUpdateCart(payload);
return Promise.reject(AUA_WARNING);
return Promise.reject(UUA_WARNING);
}

return baseIterableRequest<IterableResponse>({
Expand All @@ -38,13 +38,13 @@
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;

Check warning on line 41 in src/commerce/commerce.ts

View workflow job for this annotation

GitHub Actions / build

Unexpected any. Specify a different type

Check warning on line 41 in src/commerce/commerce.ts

View workflow job for this annotation

GitHub Actions / e2e-tests (chromium)

Unexpected any. Specify a different type

Check warning on line 41 in src/commerce/commerce.ts

View workflow job for this annotation

GitHub Actions / e2e-tests (firefox)

Unexpected any. Specify a different type

Check warning on line 41 in src/commerce/commerce.ts

View workflow job for this annotation

GitHub Actions / e2e-tests (webkit)

Unexpected any. Specify a different type
delete (payload as any).user.email;

Check warning on line 42 in src/commerce/commerce.ts

View workflow job for this annotation

GitHub Actions / build

Unexpected any. Specify a different type

Check warning on line 42 in src/commerce/commerce.ts

View workflow job for this annotation

GitHub Actions / e2e-tests (chromium)

Unexpected any. Specify a different type

Check warning on line 42 in src/commerce/commerce.ts

View workflow job for this annotation

GitHub Actions / e2e-tests (firefox)

Unexpected any. Specify a different type

Check warning on line 42 in src/commerce/commerce.ts

View workflow job for this annotation

GitHub Actions / e2e-tests (webkit)

Unexpected any. Specify a different type
}
if (canTrackUnknownUser()) {
const unknownUserEventManager = new UnknownUserEventManager();
unknownUserEventManager.trackUnknownPurchaseEvent(payload);
return Promise.reject(AUA_WARNING);
return Promise.reject(UUA_WARNING);
}

return baseIterableRequest<IterableResponse>({
Expand Down
8 changes: 6 additions & 2 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,8 @@ export const SHARED_PREFS_EVENT_LIST_KEY = 'itbl_event_list';
export const SHARED_PREFS_USER_UPDATE_OBJECT_KEY = 'itbl_user_update_object';
export const SHARED_PREFS_CRITERIA = 'criteria';
export const SHARED_PREFS_UNKNOWN_SESSIONS = 'itbl_unknown_sessions';
export const SHARED_PREF_UNKNOWN_USER_ID = 'unknown_userId';
export const SHARED_PREF_UNKNOWN_USER_ID = 'itbl_userid_unknown';
export const LEGACY_SHARED_PREF_UNKNOWN_USER_ID = 'unknown_userId';
export const SHARED_PREF_UNKNOWN_USAGE_TRACKED = 'itbl_unknown_usage_tracked';
export const SHARED_PREF_CONSENT_TIMESTAMP = 'itbl_consent_timestamp';
export const SHARED_PREF_USER_TOKEN = 'itbl_auth_token';
Expand All @@ -313,6 +314,9 @@ export const PURCHASE_ITEM_PREFIX = `${PURCHASE_ITEM}.`;
export const INITIALIZE_ERROR = new Error(
'Iterable SDK must be initialized with an API key and user email/userId before calling SDK methods'
);
export const AUA_WARNING = new Error(
export const UUA_WARNING = new Error(
'This event was stored locally because you have Unknown User Activation enabled. If this was unintentional, please check your SDK configuration settings.'
);

/** @deprecated Use `UUA_WARNING` instead. */
export const AUA_WARNING = UUA_WARNING;
4 changes: 2 additions & 2 deletions src/events/events.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable no-param-reassign */
import { ENDPOINTS, AUA_WARNING } from '../constants';
import { ENDPOINTS, UUA_WARNING } from '../constants';
import { baseIterableRequest } from '../request';
import { InAppTrackRequestParams } from './inapp/types';
import { IterableResponse } from '../types';
Expand All @@ -14,7 +14,7 @@ export const track = (payload: InAppTrackRequestParams) => {
if (canTrackUnknownUser()) {
const unknownUserEventManager = new UnknownUserEventManager();
unknownUserEventManager.trackUnknownEvent(payload);
return Promise.reject(AUA_WARNING);
return Promise.reject(UUA_WARNING);
}
return baseIterableRequest<IterableResponse>({
method: 'POST',
Expand Down
2 changes: 1 addition & 1 deletion src/unknownUserTracking/tests/consentTracking.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ describe('Consent Tracking', () => {
mergeOnUnknownToKnown: true
};
}
if (key === 'enableUnknownActivation') {
if (key === 'enableUnknownUserActivation') {
return true;
}
return undefined;
Expand Down
Loading
Loading