Skip to content

Commit 2790e95

Browse files
fix: SDKE-846 - User Identity through selectPlacements
1 parent 1bd7d52 commit 2790e95

2 files changed

Lines changed: 281 additions & 1 deletion

File tree

src/roktManager.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,18 +229,45 @@ export default class RoktManager {
229229
resolve();
230230
});
231231
});
232+
233+
// After identify completes, refresh current user identities
234+
this.currentUser = this.identityService.getCurrentUser();
232235
} catch (error) {
233236
this.logger.error('Failed to identify user with new email: ' + JSON.stringify(error));
234237
}
235238
}
239+
240+
// Refresh current user identities one more time before creating enrichedAttributes
241+
this.currentUser = this.identityService.getCurrentUser();
242+
const finalUserIdentities = this.currentUser?.getUserIdentities()?.userIdentities || {};
236243

237244
this.setUserAttributes(mappedAttributes);
238245

239-
const enrichedAttributes = {
246+
const enrichedAttributes: IRoktPartnerAttributes = {
240247
...mappedAttributes,
241248
...(sandboxValue !== null ? { sandbox: sandboxValue } : {}),
242249
};
243250

251+
// Propagate email from current user identities if not already in attributes
252+
if (finalUserIdentities.email && !enrichedAttributes.email) {
253+
enrichedAttributes.email = finalUserIdentities.email;
254+
}
255+
256+
// Propagate emailsha256 from current user identities if not already in attributes
257+
if (this.mappedEmailShaIdentityType) {
258+
try {
259+
const identityType = IdentityType.getIdentityType(this.mappedEmailShaIdentityType);
260+
if (identityType !== false && identityType !== null && identityType !== undefined) {
261+
const hashedEmail = finalUserIdentities[this.mappedEmailShaIdentityType];
262+
if (hashedEmail && !enrichedAttributes.emailsha256 && !enrichedAttributes[this.mappedEmailShaIdentityType]) {
263+
enrichedAttributes.emailsha256 = hashedEmail;
264+
}
265+
}
266+
} catch (error) {
267+
// If IdentityType.getIdentityType fails, skip emailsha256 propagation
268+
}
269+
}
270+
244271
const enrichedOptions = {
245272
...options,
246273
attributes: enrichedAttributes,

test/jest/roktManager.spec.ts

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1407,6 +1407,259 @@ describe('RoktManager', () => {
14071407
expect(mockMPInstance.Logger.warning).not.toHaveBeenCalled();
14081408
});
14091409

1410+
it('should propagate email from current user identities to kit.selectPlacements when not in attributes', async () => {
1411+
const kit: Partial<IRoktKit> = {
1412+
launcher: {
1413+
selectPlacements: jest.fn(),
1414+
hashAttributes: jest.fn(),
1415+
use: jest.fn(),
1416+
},
1417+
selectPlacements: jest.fn().mockResolvedValue({}),
1418+
hashAttributes: jest.fn(),
1419+
setExtensionData: jest.fn(),
1420+
};
1421+
1422+
roktManager['placementAttributesMapping'] = [];
1423+
roktManager.kit = kit as IRoktKit;
1424+
1425+
const mockIdentity = {
1426+
getCurrentUser: jest.fn().mockReturnValue({
1427+
getUserIdentities: () => ({
1428+
userIdentities: {
1429+
email: 'user@example.com'
1430+
}
1431+
}),
1432+
setUserAttributes: jest.fn()
1433+
}),
1434+
identify: jest.fn()
1435+
} as unknown as SDKIdentityApi;
1436+
1437+
roktManager['identityService'] = mockIdentity;
1438+
1439+
const options: IRoktSelectPlacementsOptions = {
1440+
attributes: {
1441+
firstname: 'John',
1442+
lastname: 'Doe'
1443+
}
1444+
};
1445+
1446+
await roktManager.selectPlacements(options);
1447+
1448+
expect(mockIdentity.identify).not.toHaveBeenCalled();
1449+
expect(kit.selectPlacements).toHaveBeenCalledWith(
1450+
expect.objectContaining({
1451+
attributes: expect.objectContaining({
1452+
email: 'user@example.com',
1453+
firstname: 'John',
1454+
lastname: 'Doe'
1455+
})
1456+
})
1457+
);
1458+
});
1459+
1460+
it('should propagate emailsha256 from current user identities to kit.selectPlacements when not in attributes', async () => {
1461+
const kit: Partial<IRoktKit> = {
1462+
launcher: {
1463+
selectPlacements: jest.fn(),
1464+
hashAttributes: jest.fn(),
1465+
use: jest.fn(),
1466+
},
1467+
selectPlacements: jest.fn().mockResolvedValue({}),
1468+
hashAttributes: jest.fn(),
1469+
setExtensionData: jest.fn(),
1470+
};
1471+
1472+
roktManager.init(
1473+
{
1474+
settings: {
1475+
hashedEmailUserIdentityType: 'other5'
1476+
}
1477+
} as unknown as IKitConfigs,
1478+
{} as IMParticleUser,
1479+
mockMPInstance.Identity,
1480+
mockMPInstance._Store,
1481+
mockMPInstance.Logger,
1482+
);
1483+
roktManager['placementAttributesMapping'] = [];
1484+
roktManager.kit = kit as IRoktKit;
1485+
1486+
const mockIdentity = {
1487+
getCurrentUser: jest.fn().mockReturnValue({
1488+
getUserIdentities: () => ({
1489+
userIdentities: {
1490+
email: 'user@example.com',
1491+
other5: 'hashed-email-value-12345'
1492+
}
1493+
}),
1494+
setUserAttributes: jest.fn()
1495+
}),
1496+
identify: jest.fn()
1497+
} as unknown as SDKIdentityApi;
1498+
1499+
roktManager['identityService'] = mockIdentity;
1500+
1501+
const options: IRoktSelectPlacementsOptions = {
1502+
attributes: {
1503+
firstname: 'John'
1504+
}
1505+
};
1506+
1507+
await roktManager.selectPlacements(options);
1508+
1509+
expect(mockIdentity.identify).not.toHaveBeenCalled();
1510+
expect(kit.selectPlacements).toHaveBeenCalledWith(
1511+
expect.objectContaining({
1512+
attributes: expect.objectContaining({
1513+
email: 'user@example.com',
1514+
emailsha256: 'hashed-email-value-12345',
1515+
firstname: 'John'
1516+
})
1517+
})
1518+
);
1519+
});
1520+
1521+
it('should propagate email after identify completes when identify is called during init', async () => {
1522+
const kit: Partial<IRoktKit> = {
1523+
launcher: {
1524+
selectPlacements: jest.fn(),
1525+
hashAttributes: jest.fn(),
1526+
use: jest.fn(),
1527+
},
1528+
selectPlacements: jest.fn().mockResolvedValue({}),
1529+
hashAttributes: jest.fn(),
1530+
setExtensionData: jest.fn(),
1531+
};
1532+
1533+
roktManager['placementAttributesMapping'] = [];
1534+
roktManager.kit = kit as IRoktKit;
1535+
1536+
let identifyCallback: (() => void) | null = null;
1537+
let identifyResolve: (() => void) | null = null;
1538+
const identifyPromise = new Promise<void>((resolve) => {
1539+
identifyResolve = resolve;
1540+
});
1541+
1542+
const mockIdentity = {
1543+
getCurrentUser: jest.fn()
1544+
.mockReturnValueOnce({
1545+
getUserIdentities: () => ({
1546+
userIdentities: {}
1547+
}),
1548+
setUserAttributes: jest.fn()
1549+
})
1550+
.mockReturnValueOnce({
1551+
getUserIdentities: () => ({
1552+
userIdentities: {
1553+
email: 'user@example.com'
1554+
}
1555+
}),
1556+
setUserAttributes: jest.fn()
1557+
}),
1558+
identify: jest.fn().mockImplementation((data, callback) => {
1559+
identifyCallback = callback;
1560+
setTimeout(() => {
1561+
if (identifyCallback) {
1562+
identifyCallback();
1563+
}
1564+
if (identifyResolve) {
1565+
identifyResolve();
1566+
}
1567+
}, 10);
1568+
})
1569+
} as unknown as SDKIdentityApi;
1570+
1571+
roktManager['identityService'] = mockIdentity;
1572+
1573+
mockIdentity.identify({
1574+
userIdentities: {
1575+
email: 'user@example.com'
1576+
}
1577+
}, () => {});
1578+
1579+
const options: IRoktSelectPlacementsOptions = {
1580+
attributes: {
1581+
firstname: 'John'
1582+
}
1583+
};
1584+
1585+
const selectPlacementsPromise = roktManager.selectPlacements(options);
1586+
1587+
await identifyPromise;
1588+
await selectPlacementsPromise;
1589+
1590+
expect(kit.selectPlacements).toHaveBeenCalledWith(
1591+
expect.objectContaining({
1592+
attributes: expect.objectContaining({
1593+
email: 'user@example.com',
1594+
firstname: 'John'
1595+
})
1596+
})
1597+
);
1598+
});
1599+
1600+
it('should not override email in attributes with current user email', async () => {
1601+
// Reset mappedEmailShaIdentityType to avoid issues from previous tests
1602+
roktManager['mappedEmailShaIdentityType'] = undefined;
1603+
1604+
const kit: Partial<IRoktKit> = {
1605+
launcher: {
1606+
selectPlacements: jest.fn(),
1607+
hashAttributes: jest.fn(),
1608+
use: jest.fn(),
1609+
},
1610+
selectPlacements: jest.fn().mockResolvedValue({}),
1611+
hashAttributes: jest.fn(),
1612+
setExtensionData: jest.fn(),
1613+
};
1614+
1615+
roktManager['placementAttributesMapping'] = [];
1616+
roktManager.kit = kit as IRoktKit;
1617+
1618+
const mockIdentity = {
1619+
getCurrentUser: jest.fn().mockReturnValue({
1620+
getUserIdentities: () => ({
1621+
userIdentities: {
1622+
email: 'current@example.com'
1623+
}
1624+
}),
1625+
setUserAttributes: jest.fn()
1626+
}),
1627+
identify: jest.fn().mockImplementation((data, callback) => {
1628+
// Call the callback immediately to resolve the Promise
1629+
if (callback) {
1630+
callback();
1631+
}
1632+
})
1633+
} as unknown as SDKIdentityApi;
1634+
1635+
roktManager['identityService'] = mockIdentity;
1636+
1637+
const options: IRoktSelectPlacementsOptions = {
1638+
attributes: {
1639+
email: 'explicit@example.com',
1640+
firstname: 'John'
1641+
}
1642+
};
1643+
1644+
await roktManager.selectPlacements(options);
1645+
1646+
expect(kit.selectPlacements).toHaveBeenCalledWith(
1647+
expect.objectContaining({
1648+
attributes: expect.objectContaining({
1649+
email: 'explicit@example.com',
1650+
firstname: 'John'
1651+
})
1652+
})
1653+
);
1654+
expect(kit.selectPlacements).not.toHaveBeenCalledWith(
1655+
expect.objectContaining({
1656+
attributes: expect.objectContaining({
1657+
email: 'current@example.com'
1658+
})
1659+
})
1660+
);
1661+
});
1662+
14101663
it('should log error when identify fails with a 500 but continue execution', async () => {
14111664
const kit: Partial<IRoktKit> = {
14121665
launcher: {

0 commit comments

Comments
 (0)