Skip to content

Commit

Permalink
feat(analytics): initiate on device measurement with sha256-hashed va…
Browse files Browse the repository at this point in the history
…lues (#7963)
  • Loading branch information
matinzd authored Aug 20, 2024
1 parent c1ac022 commit 5ce51e1
Show file tree
Hide file tree
Showing 7 changed files with 245 additions and 8 deletions.
59 changes: 59 additions & 0 deletions packages/analytics/__tests__/analytics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ import {
logViewSearchResults,
setDefaultEventParameters,
initiateOnDeviceConversionMeasurementWithEmailAddress,
initiateOnDeviceConversionMeasurementWithHashedEmailAddress,
initiateOnDeviceConversionMeasurementWithPhoneNumber,
initiateOnDeviceConversionMeasurementWithHashedPhoneNumber,
isSupported,
setConsent,
settings,
Expand Down Expand Up @@ -650,6 +652,38 @@ describe('Analytics', function () {
});
});

describe('initiateOnDeviceConversionMeasurementWithHashedEmailAddress()', function () {
it('throws if not a string', function () {
expect(() =>
// @ts-ignore
firebase.analytics().initiateOnDeviceConversionMeasurementWithHashedEmailAddress(true),
).toThrowError(
"firebase.analytics().initiateOnDeviceConversionMeasurementWithHashedEmailAddress(*) 'hashedEmailAddress' expected a string value.",
);
});
});

describe('initiateOnDeviceConversionMeasurementWithHashedPhoneNumber()', function () {
it('throws if not a string', function () {
expect(() =>
// @ts-ignore
firebase.analytics().initiateOnDeviceConversionMeasurementWithHashedPhoneNumber(1234),
).toThrowError(
"firebase.analytics().initiateOnDeviceConversionMeasurementWithHashedPhoneNumber(*) 'hashedPhoneNumber' expected a string value.",
);
});

it('throws if hashed value is a phone number in E.164 format', function () {
expect(() =>
firebase
.analytics()
.initiateOnDeviceConversionMeasurementWithHashedPhoneNumber('+1234567890'),
).toThrowError(
"firebase.analytics().initiateOnDeviceConversionMeasurementWithHashedPhoneNumber(*) 'hashedPhoneNumber' expected a sha256-hashed value of a phone number in E.164 format.",
);
});
});

describe('modular', function () {
it('`getAnalytics` function is properly exposed to end user', function () {
expect(getAnalytics).toBeDefined();
Expand Down Expand Up @@ -843,10 +877,35 @@ describe('Analytics', function () {
expect(initiateOnDeviceConversionMeasurementWithEmailAddress).toBeDefined();
});

it('`initiateOnDeviceConversionMeasurementWithHashedEmailAddress` function is properly exposed to end user', function () {
expect(initiateOnDeviceConversionMeasurementWithHashedEmailAddress).toBeDefined();
});

it('`initiateOnDeviceConversionMeasurementWithHashedEmailAddress` throws if not a string', function () {
expect(() =>
// @ts-ignore
initiateOnDeviceConversionMeasurementWithHashedEmailAddress(getAnalytics(), true),
).toThrowError(
"firebase.analytics().initiateOnDeviceConversionMeasurementWithHashedEmailAddress(*) 'hashedEmailAddress' expected a string value.",
);
});

it('`initiateOnDeviceConversionMeasurementWithHashedPhoneNumber` should throw if the value is in E.164 format', function () {
expect(() =>
initiateOnDeviceConversionMeasurementWithHashedPhoneNumber(getAnalytics(), '+1234567890'),
).toThrowError(
"firebase.analytics().initiateOnDeviceConversionMeasurementWithHashedPhoneNumber(*) 'hashedPhoneNumber' expected a sha256-hashed value of a phone number in E.164 format.",
);
});

it('`initiateOnDeviceConversionMeasurementWithPhoneNumber` function is properly exposed to end user', function () {
expect(initiateOnDeviceConversionMeasurementWithPhoneNumber).toBeDefined();
});

it('`initiateOnDeviceConversionMeasurementWithHashedPhoneNumber` function is properly exposed to end user', function () {
expect(initiateOnDeviceConversionMeasurementWithHashedPhoneNumber).toBeDefined();
});

it('`isSupported` function is properly exposed to end user', function () {
expect(isSupported).toBeDefined();
});
Expand Down
19 changes: 19 additions & 0 deletions packages/analytics/e2e/analytics.e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,17 @@ describe('analytics() modular', function () {
.analytics()
.initiateOnDeviceConversionMeasurementWithEmailAddress('[email protected]');
});

it('calls native API successfully with hashed email', async function () {
// Normalized email address: '[email protected]'
// echo -n '[email protected]' | shasum -a 256

await firebase
.analytics()
.initiateOnDeviceConversionMeasurementWithHashedEmailAddress(
'73914334417d04bc2922331e5fb3b3572ab88debfa0c63beb0c56f7b31d4aaed',
);
});
});

// Test this last so it does not stop delivery to DebugView
Expand All @@ -499,6 +510,14 @@ describe('analytics() modular', function () {
.initiateOnDeviceConversionMeasurementWithPhoneNumber('+14155551212');
});

it('calls native API successfully with hashed phone', async function () {
await firebase
.analytics()
.initiateOnDeviceConversionMeasurementWithHashedPhoneNumber(
'5dce05f429bc23dbd9e2caa03f336b56d4ee2aa374d8708f4f12eb4e10204c2b',
);
});

it('handles mal-formatted phone number', async function () {
try {
await firebase
Expand Down
28 changes: 28 additions & 0 deletions packages/analytics/ios/RNFBAnalytics/RNFBAnalyticsModule.m
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,20 @@ - (dispatch_queue_t)methodQueue {
return resolve([NSNull null]);
}

RCT_EXPORT_METHOD(initiateOnDeviceConversionMeasurementWithHashedEmailAddress
: (NSString *)hashedEmailAddress resolver
: (RCTPromiseResolveBlock)resolve rejecter
: (RCTPromiseRejectBlock)reject) {
@try {
NSData *emailAddress = [hashedEmailAddress dataUsingEncoding:NSUTF8StringEncoding];
[FIRAnalytics initiateOnDeviceConversionMeasurementWithHashedEmailAddress:emailAddress];
} @catch (NSException *exception) {
return [RNFBSharedUtils rejectPromiseWithExceptionDict:reject exception:exception];
}

return resolve([NSNull null]);
}

RCT_EXPORT_METHOD(initiateOnDeviceConversionMeasurementWithPhoneNumber
: (NSString *)phoneNumber resolver
: (RCTPromiseResolveBlock)resolve rejecter
Expand All @@ -177,6 +191,20 @@ - (dispatch_queue_t)methodQueue {
return resolve([NSNull null]);
}

RCT_EXPORT_METHOD(initiateOnDeviceConversionMeasurementWithHashedPhoneNumber
: (NSString *)hashedPhoneNumber resolver
: (RCTPromiseResolveBlock)resolve rejecter
: (RCTPromiseRejectBlock)reject) {
@try {
NSData *phoneNumber = [hashedPhoneNumber dataUsingEncoding:NSUTF8StringEncoding];
[FIRAnalytics initiateOnDeviceConversionMeasurementWithHashedPhoneNumber:phoneNumber];
} @catch (NSException *exception) {
return [RNFBSharedUtils rejectPromiseWithExceptionDict:reject exception:exception];
}

return resolve([NSNull null]);
}

RCT_EXPORT_METHOD(setConsent
: (NSDictionary *)consentSettings resolver
: (RCTPromiseResolveBlock)resolve rejecter
Expand Down
36 changes: 32 additions & 4 deletions packages/analytics/lib/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1755,23 +1755,51 @@ export namespace FirebaseAnalyticsTypes {
setDefaultEventParameters(params?: { [key: string]: any }): Promise<void>;

/**
* start privacy-sensitive on-device conversion management.
* Start privacy-sensitive on-device conversion management.
* This is iOS-only.
* This is a no-op if you do not include '$RNFirebaseAnalyticsGoogleAppMeasurementOnDeviceConversion = true' in your Podfile
* This is a no-op if you do not include '$RNFirebaseAnalyticsGoogleAppMeasurementOnDeviceConversion = true' in your Podfile.
*
* @platform ios
* @param emailAddress email address, properly formatted complete with domain name e.g, '[email protected]'
*/
initiateOnDeviceConversionMeasurementWithEmailAddress(emailAddress: string): Promise<void>;

/**
* start privacy-sensitive on-device conversion management.
* Start privacy-sensitive on-device conversion management.
* This is iOS-only.
* This is a no-op if you do not include '$RNFirebaseAnalyticsGoogleAppMeasurementOnDeviceConversion = true' in your Podfile
* This is a no-op if you do not include '$RNFirebaseAnalyticsGoogleAppMeasurementOnDeviceConversion = true' in your Podfile.
* You need to pass the sha256-hashed of normalized email address to this function. See [this link](https://firebase.google.com/docs/tutorials/ads-ios-on-device-measurement/step-3#use-hashed-credentials) for more information.
*
* @platform ios
* @param hashedEmailAddress sha256-hashed of normalized email address, properly formatted complete with domain name e.g, '[email protected]'
*/
initiateOnDeviceConversionMeasurementWithHashedEmailAddress(
hashedEmailAddress: string,
): Poromise<void>;

/**
* Start privacy-sensitive on-device conversion management.
* This is iOS-only.
* This is a no-op if you do not include '$RNFirebaseAnalyticsGoogleAppMeasurementOnDeviceConversion = true' in your Podfile.
*
* @platform ios
* @param phoneNumber phone number in E.164 format - that is a leading + sign, then up to 15 digits, no dashes or spaces.
*/
initiateOnDeviceConversionMeasurementWithPhoneNumber(phoneNumber: string): Promise<void>;

/**
* Start privacy-sensitive on-device conversion management.
* This is iOS-only.
* This is a no-op if you do not include '$RNFirebaseAnalyticsGoogleAppMeasurementOnDeviceConversion = true' in your Podfile.
* You need to pass the sha256-hashed of phone number in E.164 format. See [this link](https://firebase.google.com/docs/tutorials/ads-ios-on-device-measurement/step-3#use-hashed-credentials) for more information.
*
* @platform ios
* @param hashedPhoneNumber sha256-hashed of normalized phone number in E.164 format - that is a leading + sign, then up to 15 digits, no dashes or spaces.
*/
initiateOnDeviceConversionMeasurementWithHashedPhoneNumber(
hashedPhoneNumber: string,
): Promise<void>;

/**
* For Consent Mode!
*
Expand Down
38 changes: 38 additions & 0 deletions packages/analytics/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,22 @@ class FirebaseAnalyticsModule extends FirebaseModule {
return this.native.initiateOnDeviceConversionMeasurementWithEmailAddress(emailAddress);
}

initiateOnDeviceConversionMeasurementWithHashedEmailAddress(hashedEmailAddress) {
if (!isString(hashedEmailAddress)) {
throw new Error(
"firebase.analytics().initiateOnDeviceConversionMeasurementWithHashedEmailAddress(*) 'hashedEmailAddress' expected a string value.",
);
}

if (!isIOS) {
return;
}

return this.native.initiateOnDeviceConversionMeasurementWithHashedEmailAddress(
hashedEmailAddress,
);
}

initiateOnDeviceConversionMeasurementWithPhoneNumber(phoneNumber) {
if (!isE164PhoneNumber(phoneNumber)) {
throw new Error(
Expand All @@ -756,6 +772,28 @@ class FirebaseAnalyticsModule extends FirebaseModule {

return this.native.initiateOnDeviceConversionMeasurementWithPhoneNumber(phoneNumber);
}

initiateOnDeviceConversionMeasurementWithHashedPhoneNumber(hashedPhoneNumber) {
if (isE164PhoneNumber(hashedPhoneNumber)) {
throw new Error(
"firebase.analytics().initiateOnDeviceConversionMeasurementWithHashedPhoneNumber(*) 'hashedPhoneNumber' expected a sha256-hashed value of a phone number in E.164 format.",
);
}

if (!isString(hashedPhoneNumber)) {
throw new Error(
"firebase.analytics().initiateOnDeviceConversionMeasurementWithHashedPhoneNumber(*) 'hashedPhoneNumber' expected a string value.",
);
}

if (!isIOS) {
return;
}

return this.native.initiateOnDeviceConversionMeasurementWithHashedPhoneNumber(
hashedPhoneNumber,
);
}
}

// import { SDK_VERSION } from '@react-native-firebase/analytics';
Expand Down
41 changes: 37 additions & 4 deletions packages/analytics/lib/modular/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1143,6 +1143,7 @@ export function logViewSearchResults(
* For Web, the values passed persist on the current page and are passed with all
* subsequent events.
*
* @platform ios
* @param analytics Analytics instance.
* @param params Parameters to be added to the map of parameters added to every event.
*/
Expand All @@ -1152,10 +1153,11 @@ export function setDefaultEventParameters(
): Promise<void>;

/**
* start privacy-sensitive on-device conversion management.
* Start privacy-sensitive on-device conversion management.
* This is iOS-only.
* This is a no-op if you do not include '$RNFirebaseAnalyticsGoogleAppMeasurementOnDeviceConversion = true' in your Podfile
* This is a no-op if you do not include '$RNFirebaseAnalyticsGoogleAppMeasurementOnDeviceConversion = true' in your Podfile.
*
* @platform ios
* @param analytics Analytics instance.
* @param emailAddress email address, properly formatted complete with domain name e.g, '[email protected]'
*/
Expand All @@ -1165,10 +1167,26 @@ export function initiateOnDeviceConversionMeasurementWithEmailAddress(
): Promise<void>;

/**
* start privacy-sensitive on-device conversion management.
* Start privacy-sensitive on-device conversion management.
* This is iOS-only.
* This is a no-op if you do not include '$RNFirebaseAnalyticsGoogleAppMeasurementOnDeviceConversion = true' in your Podfile
* This is a no-op if you do not include '$RNFirebaseAnalyticsGoogleAppMeasurementOnDeviceConversion = true' in your Podfile.
* You need to pass the sha256-hashed of normalized email address to this function. See [this link](https://firebase.google.com/docs/tutorials/ads-ios-on-device-measurement/step-3#use-hashed-credentials) for more information.
*
* @platform ios
* @param analytics Analytics instance.
* @param hashedEmailAddress sha256-hashed of normalized email address, properly formatted complete with domain name e.g, '[email protected]'
*/
export function initiateOnDeviceConversionMeasurementWithHashedEmailAddress(
analytics: Analytics,
hashedEmailAddress: string,
): Poromise<void>;

/**
* Start privacy-sensitive on-device conversion management.
* This is iOS-only.
* This is a no-op if you do not include '$RNFirebaseAnalyticsGoogleAppMeasurementOnDeviceConversion = true' in your Podfile.
*
* @platform ios
* @param analytics Analytics instance.
* @param phoneNumber phone number in E.164 format - that is a leading + sign, then up to 15 digits, no dashes or spaces.
*/
Expand All @@ -1177,6 +1195,21 @@ export function initiateOnDeviceConversionMeasurementWithPhoneNumber(
phoneNumber: string,
): Promise<void>;

/**
* Start privacy-sensitive on-device conversion management.
* This is iOS-only.
* This is a no-op if you do not include '$RNFirebaseAnalyticsGoogleAppMeasurementOnDeviceConversion = true' in your Podfile.
* You need to pass the sha256-hashed of phone number in E.164 format. See [this link](https://firebase.google.com/docs/tutorials/ads-ios-on-device-measurement/step-3#use-hashed-credentials) for more information.
*
* @platform ios
* @param analytics Analytics instance.
* @param hashedPhoneNumber sha256-hashed of normalized phone number in E.164 format - that is a leading + sign, then up to 15 digits, no dashes or spaces.
*/
export function initiateOnDeviceConversionMeasurementWithHashedPhoneNumber(
analytics: Analytics,
hashedPhoneNumber: string,
): Promise<void>;

/**
* Checks four different things.
* 1. Checks if it's not a browser extension environment.
Expand Down
32 changes: 32 additions & 0 deletions packages/analytics/lib/modular/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,22 @@ export function initiateOnDeviceConversionMeasurementWithEmailAddress(analytics,
return analytics.initiateOnDeviceConversionMeasurementWithEmailAddress(emailAddress);
}

/**
* start privacy-sensitive on-device conversion management.
* This is iOS-only.
* This is a no-op if you do not include '$RNFirebaseAnalyticsGoogleAppMeasurementOnDeviceConversion = true' in your Podfile
*
* @param analytics Analytics instance.
* @param hashedEmailAddress sha256-hashed of normalized email address, properly formatted complete with domain name e.g, '[email protected]'
* @link https://firebase.google.com/docs/tutorials/ads-ios-on-device-measurement/step-3#use-hashed-credentials
*/
export function initiateOnDeviceConversionMeasurementWithHashedEmailAddress(
analytics,
hashedEmailAddress,
) {
return analytics.initiateOnDeviceConversionMeasurementWithHashedEmailAddress(hashedEmailAddress);
}

/**
* start privacy-sensitive on-device conversion management.
* This is iOS-only.
Expand All @@ -535,6 +551,22 @@ export function initiateOnDeviceConversionMeasurementWithPhoneNumber(analytics,
return analytics.initiateOnDeviceConversionMeasurementWithPhoneNumber(phoneNumber);
}

/**
* start privacy-sensitive on-device conversion management.
* This is iOS-only.
* This is a no-op if you do not include '$RNFirebaseAnalyticsGoogleAppMeasurementOnDeviceConversion = true' in your Podfile
*
* @param analytics Analytics instance.
* @param hashedPhoneNumber sha256-hashed of normalized phone number in E.164 format - that is a leading + sign, then up to 15 digits, no dashes or spaces.
* @link https://firebase.google.com/docs/tutorials/ads-ios-on-device-measurement/step-3#use-hashed-credentials
*/
export function initiateOnDeviceConversionMeasurementWithHashedPhoneNumber(
analytics,
hashedPhoneNumber,
) {
return analytics.initiateOnDeviceConversionMeasurementWithHashedPhoneNumber(hashedPhoneNumber);
}

/**
* Checks four different things.
* 1. Checks if it's not a browser extension environment.
Expand Down

0 comments on commit 5ce51e1

Please sign in to comment.