1717// ============================================================
1818
1919import { Batch , KitInterface , IMParticleUser , SDKEvent } from '@mparticle/web-sdk/internal' ;
20+ import type { IUserIdentities } from '@mparticle/web-sdk' ;
2021// BaseEvent not re-exported from @mparticle/web-sdk/internal, so we import directly from @mparticle/event-models.
2122import { BaseEvent } from '@mparticle/event-models' ;
2223
@@ -80,12 +81,9 @@ interface RoktGlobal {
8081 setExtensionData ( data : Record < string , unknown > ) : void ;
8182}
8283
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- }
84+ // FilteredUser is the IMParticleUser shape we receive after kit filtering.
85+ // `getMPID` and `getUserIdentities` are inherited from the SDK's `User` base type.
86+ type FilteredUser = IMParticleUser ;
8987
9088// TODO: Replace with `IIdentitySearchResult` from `@mparticle/web-sdk` once
9189// a version that exports it is published (currently on a feature branch in
@@ -106,7 +104,7 @@ interface WorkspaceIdSyncResult {
106104// `@mparticle/web-sdk` once published (mirrors `SDKIdentityApi.search`).
107105type WorkspaceIdSyncSearcher = (
108106 apiKey : string ,
109- knownIdentities : { email : string } ,
107+ knownIdentities : IUserIdentities ,
110108 callback : ( result : WorkspaceIdSyncResult ) => void ,
111109) => void ;
112110
@@ -727,11 +725,14 @@ class RoktKit implements KitInterface {
727725 // can wait for the HTTP response before reading userIdentifiedInWorkspace;
728726 // — otherwise the first placement call ships without the flag.
729727 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 ;
728+ // Stable serialization of the identifier set sent in the most recent
729+ // successful search dispatch. If a subsequent identification arrives with
730+ // an identical set, we skip the network call (the flag is still correct
731+ // from the prior search). Keyed over the full IUserIdentities map — not
732+ // just email — so partners passing hashed email through `other`/`other2-10`
733+ // or any other identifier benefit from the same dedupe. Cleared on logout
734+ // so a re-login re-evaluates fresh.
735+ private _workspaceLastSearchedIdentitiesKey ?: string ;
735736
736737 // ---- Private helpers ----
737738
@@ -836,7 +837,7 @@ class RoktKit implements KitInterface {
836837 return { } ;
837838 }
838839
839- const userIdentities = filteredUser . getUserIdentities ( ) . userIdentities ;
840+ const userIdentities : IUserIdentities = filteredUser . getUserIdentities ( ) . userIdentities ;
840841
841842 return this . replaceOtherIdentityWithEmailsha256 ( userIdentities ) ;
842843 }
@@ -851,11 +852,11 @@ class RoktKit implements KitInterface {
851852 return mp ( ) . Rokt . getLocalSessionAttributes ! ( ) ;
852853 }
853854
854- private replaceOtherIdentityWithEmailsha256 ( userIdentities : Record < string , string > ) : Record < string , string > {
855+ private replaceOtherIdentityWithEmailsha256 ( userIdentities : IUserIdentities ) : Record < string , string > {
855856 const newUserIdentities : Record < string , string > = { ...( userIdentities || { } ) } ;
856857 const key = this . _mappedEmailSha256Key ;
857- if ( key && userIdentities [ key ] ) {
858- newUserIdentities [ RoktKit . EMAIL_SHA256_KEY ] = userIdentities [ key ] ;
858+ if ( key && userIdentities [ key as keyof IUserIdentities ] ) {
859+ newUserIdentities [ RoktKit . EMAIL_SHA256_KEY ] = userIdentities [ key as keyof IUserIdentities ] as string ;
859860 }
860861 if ( key ) {
861862 delete newUserIdentities [ key ] ;
@@ -1255,48 +1256,76 @@ class RoktKit implements KitInterface {
12551256 const apiKey = this . _workspaceIdSyncApiKey ;
12561257 if ( ! apiKey ) {
12571258 this . userIdentifiedInWorkspace = false ;
1258- this . _workspaceLastSearchedEmail = undefined ;
1259+ this . _workspaceLastSearchedIdentitiesKey = undefined ;
12591260 return Promise . resolve ( ) ;
12601261 }
12611262 const search = mp ( ) . Identity ?. search ;
12621263 if ( typeof search !== 'function' ) {
12631264 this . userIdentifiedInWorkspace = false ;
1264- this . _workspaceLastSearchedEmail = undefined ;
1265+ this . _workspaceLastSearchedIdentitiesKey = undefined ;
12651266 return Promise . resolve ( ) ;
12661267 }
1267- const userIdentities = filteredUser . getUserIdentities ? filteredUser . getUserIdentities ( ) . userIdentities : null ;
1268- const email = userIdentities ?. email ;
1269- if ( ! email || ! isString ( email ) ) {
1268+
1269+ const userIdentities : IUserIdentities | null = filteredUser . getUserIdentities
1270+ ? filteredUser . getUserIdentities ( ) . userIdentities
1271+ : null ;
1272+
1273+ // Forward every non-empty string identifier the user has — email,
1274+ // customerid, other/other2-10 (commonly used for hashed email),
1275+ // mobile_number, facebook, etc. The host SDK's Identity.search accepts
1276+ // the full IUserIdentities surface and the server validates it.
1277+ const knownIdentities : Record < string , string > = { } ;
1278+ if ( userIdentities ) {
1279+ for ( const key of Object . keys ( userIdentities ) as Array < keyof IUserIdentities > ) {
1280+ const value = userIdentities [ key ] ;
1281+ if ( isString ( value ) && value . length > 0 ) {
1282+ knownIdentities [ key ] = value ;
1283+ }
1284+ }
1285+ }
1286+
1287+ const identityKeys = Object . keys ( knownIdentities ) ;
1288+ if ( identityKeys . length === 0 ) {
12701289 this . userIdentifiedInWorkspace = false ;
1271- this . _workspaceLastSearchedEmail = undefined ;
1290+ this . _workspaceLastSearchedIdentitiesKey = undefined ;
12721291 return Promise . resolve ( ) ;
12731292 }
12741293
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 ) {
1294+ // Stable cache key: sort keys so insertion-order differences don't
1295+ // cause false misses. The values are partner-supplied strings; no
1296+ // hashing needed — equality on this serialization is sufficient.
1297+ const identitiesKey = identityKeys
1298+ . sort ( )
1299+ . map ( ( k ) => `${ k } =${ knownIdentities [ k ] } ` )
1300+ . join ( '&' ) ;
1301+
1302+ // Same identifier set as the last successful dispatch → skip the
1303+ // network call. The current flag value still reflects the correct
1304+ // match status.
1305+ if ( identitiesKey === this . _workspaceLastSearchedIdentitiesKey ) {
12781306 return Promise . resolve ( ) ;
12791307 }
12801308
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.
1309+ // New / different identifier set → reset and re-search. Cache the key
1310+ // up front so a second concurrent invocation with the same set also
1311+ // dedupes.
12831312 this . userIdentifiedInWorkspace = false ;
1284- this . _workspaceLastSearchedEmail = email ;
1313+ this . _workspaceLastSearchedIdentitiesKey = identitiesKey ;
12851314
12861315 return new Promise < void > ( ( resolve ) => {
12871316 try {
1288- search ( apiKey , { email } , ( result : WorkspaceIdSyncResult ) => {
1317+ search ( apiKey , knownIdentities as IUserIdentities , ( result : WorkspaceIdSyncResult ) => {
12891318 if ( result ?. httpCode === 200 ) {
12901319 this . userIdentifiedInWorkspace = true ;
12911320 }
12921321 resolve ( ) ;
12931322 } ) ;
12941323 } catch ( err ) {
12951324 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 ;
1325+ // Dispatch failed — clear the cache so the same identifier set
1326+ // can retry on the next identification rather than being stuck
1327+ // behind a poisoned entry that short-circuits future searches.
1328+ this . _workspaceLastSearchedIdentitiesKey = undefined ;
13001329 resolve ( ) ;
13011330 }
13021331 } ) ;
@@ -1308,12 +1337,12 @@ class RoktKit implements KitInterface {
13081337
13091338 public onLogoutComplete ( user : IMParticleUser , _filteredIdentityRequest : unknown ) : string {
13101339 // 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.
1340+ // Clear the flag explicitly here. Also clear the identities cache so a
1341+ // re-login (possibly with the same identifiers ) dispatches a fresh
1342+ // search rather than reusing a stale answer.
13141343 this . userIdentifiedInWorkspace = false ;
13151344 this . _workspaceSearchInFlightPromise = null ;
1316- this . _workspaceLastSearchedEmail = undefined ;
1345+ this . _workspaceLastSearchedIdentitiesKey = undefined ;
13171346 return this . handleIdentityComplete ( user , ROKT_IDENTITY_EVENT_TYPE . LOGOUT , 'onLogoutComplete' ) ;
13181347 }
13191348
0 commit comments