1717// ============================================================
1818
1919import { 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.
2123import { 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`).
107106type 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