Skip to content

Commit 35ede2e

Browse files
feat: Map events to an attribute flag for selectPlacements (#1060)
1 parent 79b1a2d commit 35ede2e

15 files changed

+474
-434
lines changed

src/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ const Constants = {
152152
csm: 1,
153153
sa: 1,
154154
ss: 1,
155+
lsa: 1,
155156
ua: 1,
156157
ui: 1,
157158
csd: 1,

src/kitFilterHelper.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,5 +58,4 @@ export default class KitFilterHelper {
5858
const hashedUserAttribute = this.hashUserAttribute(userAttributeKey);
5959
return filterList && inArray(filterList, hashedUserAttribute);
6060
}
61-
6261
}

src/mp-instance.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1413,6 +1413,7 @@ function completeSDKInitialization(apiKey, config, mpInstance) {
14131413
roktConfig,
14141414
roktFilteredUser,
14151415
mpInstance.Identity,
1416+
mpInstance._Store,
14161417
mpInstance.Logger,
14171418
roktOptions
14181419
);

src/persistence.interfaces.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
IntegrationAttributes,
1111
ServerSettings,
1212
SessionAttributes,
13+
LocalSessionAttributes,
1314
} from './store';
1415
import { Dictionary } from './utils';
1516
import { IMinifiedConsentJSONObject } from './consent';
@@ -26,6 +27,7 @@ export interface IGlobalStoreV2MinifiedKeys {
2627
sid: string; // Session ID
2728
ie: boolean; // Is Enabled
2829
sa: SessionAttributes;
30+
lsa?: LocalSessionAttributes;
2931
ss: ServerSettings;
3032
dt: string; // Dev Token
3133
av: string; // App Version

src/persistence.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,8 @@ export default function _Persistence(mpInstance) {
197197
: mpInstance._Store.isEnabled;
198198
mpInstance._Store.sessionAttributes =
199199
obj.gs.sa || mpInstance._Store.sessionAttributes;
200+
mpInstance._Store.localSessionAttributes =
201+
obj.gs.lsa || mpInstance._Store.localSessionAttributes;
200202
mpInstance._Store.serverSettings =
201203
obj.gs.ss || mpInstance._Store.serverSettings;
202204
mpInstance._Store.devToken =
@@ -399,6 +401,7 @@ export default function _Persistence(mpInstance) {
399401
data.gs.sid = store.sessionId;
400402
data.gs.ie = store.isEnabled;
401403
data.gs.sa = store.sessionAttributes;
404+
data.gs.lsa = store.localSessionAttributes;
402405
data.gs.ss = store.serverSettings;
403406
data.gs.dt = store.devToken;
404407
data.gs.les = store.dateLastEventSent
@@ -427,6 +430,7 @@ export default function _Persistence(mpInstance) {
427430
),
428431
obj = {},
429432
j;
433+
430434
if (localStorageData) {
431435
localStorageData = JSON.parse(localStorageData);
432436
for (j in localStorageData) {

src/roktManager.ts

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,16 @@ import { IKitConfigs } from "./configAPIClient";
22
import { UserAttributeFilters } from "./forwarders.interfaces";
33
import { IMParticleUser } from "./identity-user-interfaces";
44
import KitFilterHelper from "./kitFilterHelper";
5-
import { Dictionary, parseSettingsString, generateUniqueId, isFunction } from "./utils";
5+
import {
6+
Dictionary,
7+
parseSettingsString,
8+
generateUniqueId,
9+
isFunction,
10+
AttributeValue,
11+
} from "./utils";
612
import { SDKIdentityApi } from "./identity.interfaces";
713
import { SDKLoggerApi } from "./sdkRuntimeModels";
14+
import { IStore, LocalSessionAttributes } from "./store";
815

916
// https://docs.rokt.com/developers/integration-guides/web/library/attributes
1017
export interface IRoktPartnerAttributes {
@@ -83,6 +90,7 @@ export default class RoktManager {
8390
private sandbox: boolean | null = null;
8491
private placementAttributesMapping: Dictionary<string>[] = [];
8592
private identityService: SDKIdentityApi;
93+
private store: IStore;
8694
private launcherOptions?: IRoktLauncherOptions;
8795
private logger: SDKLoggerApi;
8896
private domain?: string;
@@ -101,19 +109,15 @@ export default class RoktManager {
101109
roktConfig: IKitConfigs,
102110
filteredUser: IMParticleUser,
103111
identityService: SDKIdentityApi,
112+
store: IStore,
104113
logger?: SDKLoggerApi,
105114
options?: IRoktOptions
106115
): void {
107116
const { userAttributeFilters, settings } = roktConfig || {};
108117
const { placementAttributesMapping } = settings || {};
109118

110-
try {
111-
this.placementAttributesMapping = parseSettingsString(placementAttributesMapping);
112-
} catch (error) {
113-
console.error('Error parsing placement attributes mapping from config', error);
114-
}
115-
116119
this.identityService = identityService;
120+
this.store = store;
117121
this.logger = logger;
118122

119123
this.filters = {
@@ -122,6 +126,12 @@ export default class RoktManager {
122126
filteredUser: filteredUser,
123127
};
124128

129+
try {
130+
this.placementAttributesMapping = parseSettingsString(placementAttributesMapping);
131+
} catch (error) {
132+
this.logger.error('Error parsing placement attributes mapping from config: ' + error);
133+
}
134+
125135
// This is the global setting for sandbox mode
126136
// It is set here and passed in to the createLauncher method in the Rokt Kit
127137
// This is not to be confused for the `sandbox` flag in the selectPlacements attributes
@@ -241,6 +251,14 @@ export default class RoktManager {
241251
}
242252
}
243253

254+
public getLocalSessionAttributes(): LocalSessionAttributes {
255+
return this.store.getLocalSessionAttributes();
256+
}
257+
258+
public setLocalSessionAttribute(key: string, value: AttributeValue): void {
259+
this.store.setLocalSessionAttribute(key, value);
260+
}
261+
244262
private isReady(): boolean {
245263
// The Rokt Manager is ready when a kit is attached and has a launcher
246264
return Boolean(this.kit && this.kit.launcher);
@@ -259,7 +277,7 @@ export default class RoktManager {
259277
try {
260278
this.currentUser.setUserAttributes(filteredAttributes);
261279
} catch (error) {
262-
console.error('Error setting user attributes', error);
280+
this.logger.error('Error setting user attributes: ' + error);
263281
}
264282
}
265283

src/store.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ import {
2828
moveElementToEnd,
2929
parseNumber,
3030
returnConvertedBoolean,
31+
isValidAttributeValue,
32+
findKeyInObject,
33+
AttributeValue,
3134
} from './utils';
3235
import { IMinifiedConsentJSONObject, SDKConsentState } from './consent';
3336
import { ConfiguredKit, MPForwarder, UnregisteredKit } from './forwarders.interfaces';
@@ -40,6 +43,8 @@ import { CookieSyncDates, IPixelConfiguration } from './cookieSyncManager';
4043
import { IMParticleWebSDKInstance } from './mp-instance';
4144
import ForegroundTimer from './foregroundTimeTracker';
4245

46+
const { Messages } = Constants;
47+
4348
// This represents the runtime configuration of the SDK AFTER
4449
// initialization has been complete and all settings and
4550
// configurations have been stitched together.
@@ -122,6 +127,7 @@ function createSDKConfig(config: SDKInitConfig): SDKConfig {
122127
// to TypeScript
123128
export type ServerSettings = Dictionary;
124129
export type SessionAttributes = Dictionary;
130+
export type LocalSessionAttributes = Dictionary;
125131
export type IntegrationAttribute = Dictionary<string>;
126132
export type IntegrationAttributes = Dictionary<IntegrationAttribute>;
127133
export type WrapperSDKTypes = 'flutter' | 'none';
@@ -146,7 +152,15 @@ export interface IFeatureFlags {
146152
export interface IStore {
147153
isEnabled: boolean;
148154
isInitialized: boolean;
155+
156+
// Session Attributes are persistent attributes that are tied to the current session and
157+
// are uploaded then cleared when the session ends.
149158
sessionAttributes: SessionAttributes;
159+
160+
// Local Session Attributes are persistent session attributes that are cleared when the
161+
// session ends, but are NOT uploaded to the server when the session ends.
162+
localSessionAttributes: LocalSessionAttributes;
163+
150164
currentSessionMPIDs: MPID[];
151165
consentState: SDKConsentState | null;
152166
sessionId: string | null;
@@ -201,6 +215,8 @@ export interface IStore {
201215
setFirstSeenTime?(mpid: MPID, time?: number): void;
202216
getLastSeenTime?(mpid: MPID): number;
203217
setLastSeenTime?(mpid: MPID, time?: number): void;
218+
getLocalSessionAttributes?(): LocalSessionAttributes;
219+
setLocalSessionAttribute?(key: string, value: AttributeValue): void;
204220
getUserAttributes?(mpid: MPID): UserAttributes;
205221
setUserAttributes?(mpid: MPID, attributes: UserAttributes): void;
206222
getUserIdentities?(mpid: MPID): UserIdentities;
@@ -230,6 +246,7 @@ export default function Store(
230246
const defaultStore: Partial<IStore> = {
231247
isEnabled: true,
232248
sessionAttributes: {},
249+
localSessionAttributes: {},
233250
currentSessionMPIDs: [],
234251
consentState: null,
235252
sessionId: null,
@@ -615,6 +632,15 @@ export default function Store(
615632
this._setPersistence(mpid, 'lst', time);
616633
};
617634

635+
this.getLocalSessionAttributes = (): LocalSessionAttributes =>
636+
this.localSessionAttributes || {};
637+
638+
this.setLocalSessionAttribute = (key: string, value: AttributeValue) => {
639+
this.localSessionAttributes[key] = value;
640+
this.persistenceData.gs.lsa = { ...(this.persistenceData.gs.lsa || {}), [key]: value };
641+
mpInstance._Persistence.savePersistence(this.persistenceData);
642+
}
643+
618644
this.syncPersistenceData = () => {
619645
const persistenceData = mpInstance._Persistence.getPersistence();
620646

@@ -660,6 +686,7 @@ export default function Store(
660686
this.sessionId = null;
661687
this.dateLastEventSent = null;
662688
this.sessionAttributes = {};
689+
this.localSessionAttributes = {};
663690
mpInstance._Persistence.update();
664691
};
665692

src/utils.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ const { Messages } = Constants;
77

88
type valueof<T> = T[keyof T];
99

10+
type AttributeValue = string | number | boolean | null | undefined;
11+
1012
// Placeholder for Dictionary-like Types
1113
export type Dictionary<V = any> = Record<string, V>;
1214

@@ -241,6 +243,9 @@ const isString = (value: any): boolean => typeof value === 'string';
241243
const isNumber = (value: any): boolean => typeof value === 'number';
242244
const isBoolean = (value: any): boolean => typeof value === 'boolean';
243245
const isFunction = (fn: any): boolean => typeof fn === 'function';
246+
const isValidAttributeValue = (value: any): boolean => {
247+
return value !== undefined && !isObject(value) && !Array.isArray(value);
248+
}
244249
const isValidCustomFlagProperty = (value: any): boolean =>
245250
isNumber(value) || isString(value) || isBoolean(value);
246251

@@ -415,6 +420,7 @@ export {
415420
revertCookieString,
416421
createCookieSyncUrl,
417422
valueof,
423+
AttributeValue,
418424
converted,
419425
decoded,
420426
filterDictionaryWithHash,
@@ -441,6 +447,7 @@ export {
441447
isFunction,
442448
isDataPlanSlug,
443449
isEmpty,
450+
isValidAttributeValue,
444451
isValidCustomFlagProperty,
445452
mergeObjects,
446453
moveElementToEnd,

src/validators.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
isNumber,
55
isObject,
66
isStringOrNumber,
7+
isValidAttributeValue,
78
valueof,
89
} from './utils';
910
import Constants from './constants';
@@ -25,9 +26,7 @@ const Validators = {
2526
isStringOrNumber,
2627

2728
// Validator Functions
28-
isValidAttributeValue: function(value: any): boolean {
29-
return value !== undefined && !isObject(value) && !Array.isArray(value);
30-
},
29+
isValidAttributeValue: isValidAttributeValue,
3130

3231
// Validator Functions
3332
// Neither null nor undefined can be a valid Key

test/jest/roktManager.spec.ts

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ describe('RoktManager', () => {
3030
is_logged_in: false,
3131
}),
3232
},
33+
_Store: {
34+
setLocalSessionAttributes: jest.fn(),
35+
getLocalSessionAttributes: jest.fn().mockReturnValue({}),
36+
},
3337
Logger: {
3438
verbose: jest.fn(),
3539
error: jest.fn(),
@@ -51,10 +55,16 @@ describe('RoktManager', () => {
5155
{} as IKitConfigs,
5256
{} as IMParticleUser,
5357
mockMPInstance.Identity,
54-
mockMPInstance.Logger
58+
mockMPInstance._Store,
59+
mockMPInstance.Logger,
60+
undefined,
5561
);
5662
});
5763

64+
afterEach(() => {
65+
jest.clearAllMocks();
66+
});
67+
5868
describe('constructor', () => {
5969
it('should be initialized', () => {
6070
expect(roktManager).toBeDefined();
@@ -195,7 +205,13 @@ describe('RoktManager', () => {
195205

196206
describe('#init', () => {
197207
it('should initialize the manager with defaults when no config is provided', () => {
198-
roktManager.init({} as IKitConfigs, {} as IMParticleUser, mockMPInstance.Identity);
208+
roktManager.init(
209+
{} as IKitConfigs,
210+
{} as IMParticleUser,
211+
mockMPInstance.Identity,
212+
mockMPInstance._Store,
213+
mockMPInstance.Logger,
214+
);
199215
expect(roktManager['kit']).toBeNull();
200216
expect(roktManager['filters']).toEqual({
201217
userAttributeFilters: undefined,
@@ -212,7 +228,13 @@ describe('RoktManager', () => {
212228
userAttributeFilters: [816506310, 1463937872, 36300687],
213229
};
214230

215-
roktManager.init(kitConfig as IKitConfigs, {} as IMParticleUser, mockMPInstance.Identity);
231+
roktManager.init(
232+
kitConfig as IKitConfigs,
233+
{} as IMParticleUser,
234+
mockMPInstance.Identity,
235+
mockMPInstance._Store,
236+
mockMPInstance.Logger,
237+
);
216238
expect(roktManager['filters']).toEqual({
217239
userAttributeFilters: [816506310, 1463937872, 36300687],
218240
filterUserAttributes: expect.any(Function),
@@ -225,6 +247,7 @@ describe('RoktManager', () => {
225247
{} as IKitConfigs,
226248
undefined,
227249
mockMPInstance.Identity,
250+
mockMPInstance._Store,
228251
undefined,
229252
{
230253
sandbox: true,
@@ -254,7 +277,13 @@ describe('RoktManager', () => {
254277
])
255278
},
256279
};
257-
roktManager.init(kitConfig as IKitConfigs, {} as IMParticleUser, mockMPInstance.Identity);
280+
roktManager.init(
281+
kitConfig as IKitConfigs,
282+
{} as IMParticleUser,
283+
mockMPInstance.Identity,
284+
mockMPInstance._Store,
285+
mockMPInstance.Logger,
286+
);
258287
expect(roktManager['placementAttributesMapping']).toEqual([
259288
{
260289
jsmap: null,
@@ -282,6 +311,7 @@ describe('RoktManager', () => {
282311
{} as IKitConfigs,
283312
undefined,
284313
mockMPInstance.Identity,
314+
mockMPInstance._Store,
285315
mockMPInstance.Logger,
286316
{
287317
launcherOptions
@@ -301,6 +331,7 @@ describe('RoktManager', () => {
301331
{} as IKitConfigs,
302332
undefined,
303333
mockMPInstance.Identity,
334+
mockMPInstance._Store,
304335
mockMPInstance.Logger,
305336
undefined,
306337
);
@@ -318,6 +349,7 @@ describe('RoktManager', () => {
318349
{} as IKitConfigs,
319350
undefined,
320351
mockMPInstance.Identity,
352+
mockMPInstance._Store,
321353
mockMPInstance.Logger,
322354
{
323355
domain,

0 commit comments

Comments
 (0)