Skip to content

Commit 1526325

Browse files
authored
fix: Support more identities in search (#93)
1 parent f742616 commit 1526325

2 files changed

Lines changed: 267 additions & 47 deletions

File tree

src/Rokt-Kit.ts

Lines changed: 68 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
// ============================================================
1818

1919
import { Batch, KitInterface, IMParticleUser, SDKEvent } from '@mparticle/web-sdk/internal';
20+
import type { IUserIdentities } from '@mparticle/web-sdk';
21+
2022
// BaseEvent not re-exported from @mparticle/web-sdk/internal, so we import directly from @mparticle/event-models.
2123
import { BaseEvent } from '@mparticle/event-models';
2224

@@ -80,12 +82,9 @@ interface RoktGlobal {
8082
setExtensionData(data: Record<string, unknown>): void;
8183
}
8284

83-
// TODO: getMPID and getUserIdentities exist on the User base type but are not re-exported from
84-
// @mparticle/web-sdk/internal, so we redeclare them here until the internal types expose them.
85-
interface FilteredUser extends IMParticleUser {
86-
getMPID(): string;
87-
getUserIdentities?: () => { userIdentities: Record<string, string> };
88-
}
85+
// FilteredUser is the IMParticleUser shape we receive after kit filtering.
86+
// `getMPID` and `getUserIdentities` are inherited from the SDK's `User` base type.
87+
type FilteredUser = IMParticleUser;
8988

9089
// TODO: Replace with `IIdentitySearchResult` from `@mparticle/web-sdk` once
9190
// a version that exports it is published (currently on a feature branch in
@@ -106,7 +105,7 @@ interface WorkspaceIdSyncResult {
106105
// `@mparticle/web-sdk` once published (mirrors `SDKIdentityApi.search`).
107106
type WorkspaceIdSyncSearcher = (
108107
apiKey: string,
109-
knownIdentities: { email: string },
108+
knownIdentities: IUserIdentities,
110109
callback: (result: WorkspaceIdSyncResult) => void,
111110
) => void;
112111

@@ -727,11 +726,14 @@ class RoktKit implements KitInterface {
727726
// can wait for the HTTP response before reading userIdentifiedInWorkspace;
728727
// — otherwise the first placement call ships without the flag.
729728
private _workspaceSearchInFlightPromise: Promise<void> | null = null;
730-
// The email value sent in the most recent successful search
731-
// dispatch. If a subsequent identification arrives with the same email,
732-
// we skip the network call (the flag is still correct from the prior
733-
// search). Cleared on logout so a re-login re-evaluates fresh.
734-
private _workspaceLastSearchedEmail?: string;
729+
// Stable serialization of the identifier set sent in the most recent
730+
// successful search dispatch. If a subsequent identification arrives with
731+
// an identical set, we skip the network call (the flag is still correct
732+
// from the prior search). Keyed over the full IUserIdentities map — not
733+
// just email — so partners passing hashed email through `other`/`other2-10`
734+
// or any other identifier benefit from the same dedupe. Cleared on logout
735+
// so a re-login re-evaluates fresh.
736+
private _workspaceLastSearchedIdentitiesKey?: string;
735737

736738
// ---- Private helpers ----
737739

@@ -836,7 +838,7 @@ class RoktKit implements KitInterface {
836838
return {};
837839
}
838840

839-
const userIdentities = filteredUser.getUserIdentities().userIdentities;
841+
const userIdentities: IUserIdentities = filteredUser.getUserIdentities().userIdentities;
840842

841843
return this.replaceOtherIdentityWithEmailsha256(userIdentities);
842844
}
@@ -851,11 +853,11 @@ class RoktKit implements KitInterface {
851853
return mp().Rokt.getLocalSessionAttributes!();
852854
}
853855

854-
private replaceOtherIdentityWithEmailsha256(userIdentities: Record<string, string>): Record<string, string> {
856+
private replaceOtherIdentityWithEmailsha256(userIdentities: IUserIdentities): Record<string, string> {
855857
const newUserIdentities: Record<string, string> = { ...(userIdentities || {}) };
856858
const key = this._mappedEmailSha256Key;
857-
if (key && userIdentities[key]) {
858-
newUserIdentities[RoktKit.EMAIL_SHA256_KEY] = userIdentities[key];
859+
if (key && userIdentities[key as keyof IUserIdentities]) {
860+
newUserIdentities[RoktKit.EMAIL_SHA256_KEY] = userIdentities[key as keyof IUserIdentities] as string;
859861
}
860862
if (key) {
861863
delete newUserIdentities[key];
@@ -1070,7 +1072,7 @@ class RoktKit implements KitInterface {
10701072
_service: unknown,
10711073
testMode: boolean,
10721074
_trackerId: unknown,
1073-
filteredUserAttributes: Record<string, unknown>,
1075+
filteredUserAttributes?: Record<string, unknown>,
10741076
): string {
10751077
const kitSettings = settings as unknown as RoktKitSettings;
10761078
const accountId = kitSettings.accountId;
@@ -1255,48 +1257,76 @@ class RoktKit implements KitInterface {
12551257
const apiKey = this._workspaceIdSyncApiKey;
12561258
if (!apiKey) {
12571259
this.userIdentifiedInWorkspace = false;
1258-
this._workspaceLastSearchedEmail = undefined;
1260+
this._workspaceLastSearchedIdentitiesKey = undefined;
12591261
return Promise.resolve();
12601262
}
12611263
const search = mp().Identity?.search;
12621264
if (typeof search !== 'function') {
12631265
this.userIdentifiedInWorkspace = false;
1264-
this._workspaceLastSearchedEmail = undefined;
1266+
this._workspaceLastSearchedIdentitiesKey = undefined;
12651267
return Promise.resolve();
12661268
}
1267-
const userIdentities = filteredUser.getUserIdentities ? filteredUser.getUserIdentities().userIdentities : null;
1268-
const email = userIdentities?.email;
1269-
if (!email || !isString(email)) {
1269+
1270+
const userIdentities: IUserIdentities | null = filteredUser.getUserIdentities
1271+
? filteredUser.getUserIdentities().userIdentities
1272+
: null;
1273+
1274+
// Forward every non-empty string identifier the user has — email,
1275+
// customerid, other/other2-10 (commonly used for hashed email),
1276+
// mobile_number, facebook, etc. The host SDK's Identity.search accepts
1277+
// the full IUserIdentities surface and the server validates it.
1278+
const knownIdentities: Record<string, string> = {};
1279+
if (userIdentities) {
1280+
for (const key of Object.keys(userIdentities) as Array<keyof IUserIdentities>) {
1281+
const value = userIdentities[key];
1282+
if (isString(value) && value.length > 0) {
1283+
knownIdentities[key] = value;
1284+
}
1285+
}
1286+
}
1287+
1288+
const identityKeys = Object.keys(knownIdentities);
1289+
if (identityKeys.length === 0) {
12701290
this.userIdentifiedInWorkspace = false;
1271-
this._workspaceLastSearchedEmail = undefined;
1291+
this._workspaceLastSearchedIdentitiesKey = undefined;
12721292
return Promise.resolve();
12731293
}
12741294

1275-
// Same email as the last successful dispatch → skip the network call.
1276-
// The current flag value still reflects the correct match status.
1277-
if (email === this._workspaceLastSearchedEmail) {
1295+
// Stable cache key: sort keys so insertion-order differences don't
1296+
// cause false misses. The values are partner-supplied strings; no
1297+
// hashing needed — equality on this serialization is sufficient.
1298+
const identitiesKey = identityKeys
1299+
.sort()
1300+
.map((k) => `${k}=${knownIdentities[k]}`)
1301+
.join('&');
1302+
1303+
// Same identifier set as the last successful dispatch → skip the
1304+
// network call. The current flag value still reflects the correct
1305+
// match status.
1306+
if (identitiesKey === this._workspaceLastSearchedIdentitiesKey) {
12781307
return Promise.resolve();
12791308
}
12801309

1281-
// New / different email → reset and re-search. Cache the email up front
1282-
// so a second concurrent invocation with the same email also dedupes.
1310+
// New / different identifier set → reset and re-search. Cache the key
1311+
// up front so a second concurrent invocation with the same set also
1312+
// dedupes.
12831313
this.userIdentifiedInWorkspace = false;
1284-
this._workspaceLastSearchedEmail = email;
1314+
this._workspaceLastSearchedIdentitiesKey = identitiesKey;
12851315

12861316
return new Promise<void>((resolve) => {
12871317
try {
1288-
search(apiKey, { email }, (result: WorkspaceIdSyncResult) => {
1318+
search(apiKey, knownIdentities as IUserIdentities, (result: WorkspaceIdSyncResult) => {
12891319
if (result?.httpCode === 200) {
12901320
this.userIdentifiedInWorkspace = true;
12911321
}
12921322
resolve();
12931323
});
12941324
} catch (err) {
12951325
console.error('Rokt Kit: Workspace IDSync search failed', err);
1296-
// Dispatch failed — clear the cache so the same email can retry on
1297-
// the next identification rather than being stuck behind a poisoned
1298-
// entry that short-circuits future searches.
1299-
this._workspaceLastSearchedEmail = undefined;
1326+
// Dispatch failed — clear the cache so the same identifier set
1327+
// can retry on the next identification rather than being stuck
1328+
// behind a poisoned entry that short-circuits future searches.
1329+
this._workspaceLastSearchedIdentitiesKey = undefined;
13001330
resolve();
13011331
}
13021332
});
@@ -1308,12 +1338,12 @@ class RoktKit implements KitInterface {
13081338

13091339
public onLogoutComplete(user: IMParticleUser, _filteredIdentityRequest: unknown): string {
13101340
// Anonymous sessions must not carry the previous user's match forward.
1311-
// Clear the flag explicitly here. Also clear the email cache so a
1312-
// re-login (possibly the same email) dispatches a fresh search rather
1313-
// than reusing a stale answer.
1341+
// Clear the flag explicitly here. Also clear the identities cache so a
1342+
// re-login (possibly with the same identifiers) dispatches a fresh
1343+
// search rather than reusing a stale answer.
13141344
this.userIdentifiedInWorkspace = false;
13151345
this._workspaceSearchInFlightPromise = null;
1316-
this._workspaceLastSearchedEmail = undefined;
1346+
this._workspaceLastSearchedIdentitiesKey = undefined;
13171347
return this.handleIdentityComplete(user, ROKT_IDENTITY_EVENT_TYPE.LOGOUT, 'onLogoutComplete');
13181348
}
13191349

0 commit comments

Comments
 (0)