Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/analytics/__tests__/analytics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1390,7 +1390,7 @@ describe('Analytics', function () {
() =>
analytics.logLevelEnd({
level: 12,
success: 'true',
success: true,
}),
'logLevelEnd',
);
Expand All @@ -1401,7 +1401,7 @@ describe('Analytics', function () {
() =>
logLevelEnd(analytics, {
level: 12,
success: 'true',
success: true,
}),
'logLevelEnd',
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,27 @@
import com.google.firebase.analytics.FirebaseAnalytics;
import io.invertase.firebase.common.ReactNativeFirebaseModule;
import java.util.ArrayList;
import java.util.Locale;
import javax.annotation.Nullable;

public class ReactNativeFirebaseAnalyticsModule extends ReactNativeFirebaseModule {
private static final String SERVICE_NAME = "Analytics";

/**
* GA4 parameters that must be sent as long values. React Native's bridge stores JS numbers as
* doubles in {@link Bundle}; Firebase Analytics expects integral types for these keys.
*/
private static final String[] LONG_NUMERIC_PARAM_KEYS =
new String[] {
FirebaseAnalytics.Param.QUANTITY,
FirebaseAnalytics.Param.INDEX,
FirebaseAnalytics.Param.LEVEL,
FirebaseAnalytics.Param.NUMBER_OF_NIGHTS,
FirebaseAnalytics.Param.NUMBER_OF_PASSENGERS,
FirebaseAnalytics.Param.NUMBER_OF_ROOMS,
FirebaseAnalytics.Param.SCORE,
};

private final UniversalFirebaseAnalyticsModule module;

ReactNativeFirebaseAnalyticsModule(ReactApplicationContext reactContext) {
Expand Down Expand Up @@ -207,10 +224,7 @@ private Bundle toBundle(ReadableMap readableMap) {
for (Object item : itemsArray) {
if (item instanceof Bundle) {
Bundle itemBundle = (Bundle) item;
if (itemBundle.containsKey(FirebaseAnalytics.Param.QUANTITY)) {
double number = itemBundle.getDouble(FirebaseAnalytics.Param.QUANTITY);
itemBundle.putInt(FirebaseAnalytics.Param.QUANTITY, (int) number);
}
coerceLongNumericParams(itemBundle);
validBundles.add(itemBundle);
}
}
Expand All @@ -219,10 +233,40 @@ private Bundle toBundle(ReadableMap readableMap) {
}
}

coerceLongNumericParams(bundle);
coerceSuccessParamToLong(bundle);

if (bundle.containsKey(FirebaseAnalytics.Param.EXTEND_SESSION)) {
double number = bundle.getDouble(FirebaseAnalytics.Param.EXTEND_SESSION);
bundle.putLong(FirebaseAnalytics.Param.EXTEND_SESSION, (long) number);
}
return bundle;
}

private static void coerceLongNumericParams(Bundle bundle) {
for (String key : LONG_NUMERIC_PARAM_KEYS) {
if (bundle.containsKey(key)) {
double number = bundle.getDouble(key);
bundle.putLong(key, (long) number);
}
}
}

private static void coerceSuccessParamToLong(Bundle bundle) {
if (!bundle.containsKey(FirebaseAnalytics.Param.SUCCESS)) {
return;
}
Object value = bundle.get(FirebaseAnalytics.Param.SUCCESS);
bundle.remove(FirebaseAnalytics.Param.SUCCESS);
long asLong = 0L;
if (value instanceof Boolean) {
asLong = (Boolean) value ? 1L : 0L;
} else if (value instanceof Number) {
asLong = ((Number) value).longValue() != 0L ? 1L : 0L;
} else if (value instanceof String) {
String s = ((String) value).trim().toLowerCase(Locale.ROOT);
asLong = ("1".equals(s) || "true".equals(s) || "yes".equals(s)) ? 1L : 0L;
}
bundle.putLong(FirebaseAnalytics.Param.SUCCESS, asLong);
}
}
4 changes: 2 additions & 2 deletions packages/analytics/e2e/analytics.e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ describe('analytics()', function () {
it('calls logLevelEnd', async function () {
await firebase.analytics().logLevelEnd({
level: 123,
success: 'yes',
success: true,
});
});
});
Expand Down Expand Up @@ -753,7 +753,7 @@ describe('analytics()', function () {
const { getAnalytics, logLevelEnd } = analyticsModular;
await logLevelEnd(getAnalytics(), {
level: 123,
success: 'yes',
success: true,
});
});
});
Expand Down
51 changes: 48 additions & 3 deletions packages/analytics/ios/RNFBAnalytics/RNFBAnalyticsModule.m
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,24 @@
#import <RNFBApp/RNFBSharedUtils.h>
#import "RNFBAnalyticsModule.h"

/** GA4 parameters that must be sent as integer NSNumber values (not doubles from JS). */
static NSArray<NSString *> *RNFBAnalyticsLongNumericParameterKeys(void) {
static NSArray<NSString *> *keys;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
keys = @[
kFIRParameterQuantity,
kFIRParameterIndex,
kFIRParameterLevel,
kFIRParameterNumberOfNights,
kFIRParameterNumberOfPassengers,
kFIRParameterNumberOfRooms,
kFIRParameterScore,
];
});
return keys;
}

@implementation RNFBAnalyticsModule
#pragma mark -
#pragma mark Module Setup
Expand Down Expand Up @@ -268,20 +286,47 @@ - (NSDictionary *)cleanJavascriptParams:(NSDictionary *)params {
[(NSArray *)newParams[kFIRParameterItems]
enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {
NSMutableDictionary *item = [obj mutableCopy];
if (item[kFIRParameterQuantity]) {
item[kFIRParameterQuantity] = @([item[kFIRParameterQuantity] integerValue]);
}
[self rnfb_coerceLongNumericParametersInMutableDictionary:item];
[newItems addObject:[item copy]];
}];
newParams[kFIRParameterItems] = [newItems copy];
}
[self rnfb_coerceLongNumericParametersInMutableDictionary:newParams];
[self rnfb_coerceSuccessParameterInMutableDictionary:newParams];
NSNumber *extendSession = [newParams valueForKey:kFIRParameterExtendSession];
if ([extendSession isEqualToNumber:@1]) {
newParams[kFIRParameterExtendSession] = @YES;
}
return [newParams copy];
}

- (void)rnfb_coerceLongNumericParametersInMutableDictionary:(NSMutableDictionary *)dict {
for (NSString *key in RNFBAnalyticsLongNumericParameterKeys()) {
id value = dict[key];
if (value != nil && value != [NSNull null]) {
dict[key] = @([value integerValue]);
}
}
}

- (void)rnfb_coerceSuccessParameterInMutableDictionary:(NSMutableDictionary *)dict {
id value = dict[kFIRParameterSuccess];
if (value == nil || value == [NSNull null]) {
return;
}
int success = 0;
if ([value isKindOfClass:[NSString class]]) {
NSString *lower = [(NSString *)value lowercaseString];
if ([lower isEqualToString:@"true"] || [lower isEqualToString:@"yes"] ||
[lower isEqualToString:@"1"]) {
success = 1;
}
} else {
success = [value boolValue] ? 1 : 0;
}
dict[kFIRParameterSuccess] = @(success);
}

/// Converts null values received over the bridge from NSNull to nil
/// @param value Nullable string value
- (NSString *)convertNSNullToNil:(NSString *)value {
Expand Down
5 changes: 3 additions & 2 deletions packages/analytics/lib/structs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

import { object, string, number, array, optional, define, type } from 'superstruct';
import { object, string, number, boolean, array, optional, define, type } from 'superstruct';

const ShortDate = define(
'ShortDate',
Expand All @@ -36,6 +36,7 @@ const Item = type({
item_variant: optional(string()),
quantity: optional(number()),
price: optional(number()),
index: optional(number()),
});

export const ScreenView = type({
Expand Down Expand Up @@ -104,7 +105,7 @@ export const JoinGroup = object({

export const LevelEnd = object({
level: number(),
success: optional(string()),
success: optional(boolean()),
});

export const LevelStart = object({
Expand Down
2 changes: 1 addition & 1 deletion packages/analytics/lib/types/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ export interface LevelEndEventParameters {
/**
* The result of an operation.
*/
success?: string;
success?: boolean;
Comment thread
russellwheatley marked this conversation as resolved.
}

export interface LevelStartEventParameters {
Expand Down
2 changes: 1 addition & 1 deletion packages/analytics/type-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ const earnVirtualCurrencyParams: EarnVirtualCurrencyEventParameters = {
};
const generateLeadParams: GenerateLeadEventParameters = { value: 123, currency: 'USD' };
const joinGroupParams: JoinGroupEventParameters = { group_id: 'group1' };
const levelEndParams: LevelEndEventParameters = { level: 1, success: 'true' };
const levelEndParams: LevelEndEventParameters = { level: 1, success: true };
const levelStartParams: LevelStartEventParameters = { level: 1 };
const levelUpParams: LevelUpEventParameters = { level: 5, character: 'character1' };
const loginParams: LoginEventParameters = { method: 'email' };
Expand Down
Loading