Skip to content

Commit 89c5baa

Browse files
authored
feat: Add hashSha256 method for SHA-256 hashing (#1116)
1 parent 5cd5750 commit 89c5baa

2 files changed

Lines changed: 265 additions & 119 deletions

File tree

src/roktManager.ts

Lines changed: 97 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -252,15 +252,51 @@ export default class RoktManager {
252252
}
253253
}
254254

255-
public hashAttributes(attributes: IRoktPartnerAttributes): Promise<IRoktPartnerAttributes> {
256-
if (!this.isReady()) {
257-
return this.deferredCall<IRoktPartnerAttributes>('hashAttributes', attributes);
258-
}
259-
255+
/**
256+
* Hashes attributes and returns both original and hashed versions
257+
* with Rokt-compatible key names (like emailsha256, mobilesha256)
258+
*
259+
*
260+
* @param {IRoktPartnerAttributes} attributes - Attributes to hash
261+
* @returns {Promise<IRoktPartnerAttributes>} Object with both original and hashed attributes
262+
*
263+
*/
264+
public async hashAttributes(attributes: IRoktPartnerAttributes): Promise<IRoktPartnerAttributes> {
260265
try {
261-
return this.kit.hashAttributes(attributes);
266+
if (!attributes || typeof attributes !== 'object') {
267+
return {};
268+
}
269+
270+
// Get own property keys only
271+
const keys = Object.keys(attributes);
272+
273+
if (keys.length === 0) {
274+
return {};
275+
}
276+
277+
// Hash all attributes in parallel
278+
const hashPromises = keys.map(async (key) => {
279+
const attributeValue = attributes[key];
280+
const hashedValue = await this.hashSha256(attributeValue);
281+
return { key, attributeValue, hashedValue };
282+
});
283+
284+
const results = await Promise.all(hashPromises);
285+
286+
// Build the result object
287+
const hashedAttributes: IRoktPartnerAttributes = {};
288+
for (const { key, attributeValue, hashedValue } of results) {
289+
hashedAttributes[key] = attributeValue;
290+
if (hashedValue) {
291+
hashedAttributes[`${key}sha256`] = hashedValue;
292+
}
293+
}
294+
return hashedAttributes;
295+
262296
} catch (error) {
263-
return Promise.reject(error instanceof Error ? error : new Error('Unknown error occurred'));
297+
const errorMessage = error instanceof Error ? error.message : String(error);
298+
this.logger.error(`Failed to hashAttributes, returning an empty object: ${errorMessage}`);
299+
return {};
264300
}
265301
}
266302

@@ -290,6 +326,29 @@ export default class RoktManager {
290326
}
291327
}
292328

329+
/**
330+
* Hashes an attribute using SHA-256
331+
*
332+
* @param {string | number | boolean | undefined | null} attribute - The value to hash
333+
* @returns {Promise<string | undefined | null>} SHA-256 hashed value or undefined/null
334+
*
335+
*/
336+
public async hashSha256(attribute: string | number | boolean | undefined | null): Promise<string | undefined | null> {
337+
if (attribute === null || attribute === undefined) {
338+
this.logger.warning(`hashSha256 received null/undefined as input`);
339+
return attribute as null | undefined;
340+
}
341+
342+
try {
343+
const normalizedValue = String(attribute).trim().toLocaleLowerCase();
344+
return await this.sha256Hex(normalizedValue);
345+
} catch (error) {
346+
const errorMessage = error instanceof Error ? error.message : String(error);
347+
this.logger.error(`Failed to hashSha256, returning undefined: ${errorMessage}`);
348+
return undefined;
349+
}
350+
}
351+
293352
public getLocalSessionAttributes(): LocalSessionAttributes {
294353
return this.store.getLocalSessionAttributes();
295354
}
@@ -400,6 +459,37 @@ export default class RoktManager {
400459
this.messageQueue.delete(messageId);
401460
}
402461

462+
/**
463+
* Hashes a string input using SHA-256 and returns the hex digest
464+
* Uses the Web Crypto API for secure hashing
465+
*
466+
* @param {string} input - The string to hash
467+
* @returns {Promise<string>} The SHA-256 hash as a hexadecimal string
468+
*/
469+
private async sha256Hex(input: string): Promise<string> {
470+
const encoder = new TextEncoder();
471+
const encodedInput = encoder.encode(input);
472+
const digest = await crypto.subtle.digest('SHA-256', encodedInput);
473+
return this.arrayBufferToHex(digest);
474+
}
475+
476+
/**
477+
* Converts an ArrayBuffer to a hexadecimal string representation
478+
* Each byte is converted to a 2-character hex string with leading zeros
479+
*
480+
* @param {ArrayBuffer} buffer - The buffer to convert
481+
* @returns {string} The hexadecimal string representation
482+
*/
483+
private arrayBufferToHex(buffer: ArrayBuffer): string {
484+
const bytes = new Uint8Array(buffer);
485+
let hexString = '';
486+
for (let i = 0; i < bytes.length; i++) {
487+
const hexByte = bytes[i].toString(16).padStart(2, '0');
488+
hexString += hexByte;
489+
}
490+
return hexString;
491+
}
492+
403493
/**
404494
* Checks if an identity value has changed by comparing current and new values
405495
*

0 commit comments

Comments
 (0)