@react-native-firebase/analytics 26.4.0 → 27.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/RNFBAnalytics.podspec +49 -2
- package/dist/module/index.js +2 -2
- package/dist/module/index.js.map +1 -1
- package/dist/module/version.js +1 -1
- package/dist/typescript/lib/index.d.ts.map +1 -1
- package/dist/typescript/lib/version.d.ts +1 -1
- package/ios/RNFBAnalytics/RNFBAnalyticsConsentSettingsMapper.swift +98 -0
- package/ios/RNFBAnalytics/RNFBAnalyticsHelper.h +111 -0
- package/ios/RNFBAnalytics/RNFBAnalyticsHelper.m +338 -0
- package/ios/RNFBAnalytics/RNFBAnalyticsJavascriptParamsCleaner.swift +167 -0
- package/ios/RNFBAnalytics/RNFBAnalyticsModule.mm +32 -282
- package/ios/RNFBAnalytics.xcodeproj/project.pbxproj +16 -2
- package/ios/RNFBAnalyticsUnitTests/HostStubs/Firebase/FIRAnalyticsHostStub.m +304 -0
- package/ios/RNFBAnalyticsUnitTests/HostStubs/Firebase/Firebase.h +71 -0
- package/ios/RNFBAnalyticsUnitTests/HostStubs/RNFBAnalytics/RNFBAnalyticsLogTransactionHostStub.h +20 -0
- package/ios/RNFBAnalyticsUnitTests/HostStubs/RNFBAnalytics/RNFBAnalyticsLogTransactionHostStub.m +43 -0
- package/ios/RNFBAnalyticsUnitTests/HostStubs/RNFBApp/RNFBSharedUtils.h +21 -0
- package/ios/RNFBAnalyticsUnitTests/HostStubs/RNFBApp/RNFBSharedUtilsHostStub.m +26 -0
- package/ios/RNFBAnalyticsUnitTests/HostStubs/React/RCTBridgeModule.h +10 -0
- package/ios/RNFBAnalyticsUnitTests/RNFBAnalyticsConsentSettingsMapperHostStubTests.m +62 -0
- package/ios/RNFBAnalyticsUnitTests/RNFBAnalyticsConsentSettingsMapperTests.swift +91 -0
- package/ios/RNFBAnalyticsUnitTests/RNFBAnalyticsHelperHostStubTests.m +542 -0
- package/ios/RNFBAnalyticsUnitTests/RNFBAnalyticsJavascriptParamsCleanerHostStubTests.m +59 -0
- package/ios/RNFBAnalyticsUnitTests/RNFBAnalyticsJavascriptParamsCleanerTests.swift +205 -0
- package/ios/RNFBAnalyticsUnitTests/RNFBAnalyticsNullSentinelDecodeTests.m +111 -0
- package/ios/RNFBAnalyticsUnitTests/RNFBAnalyticsUnitTests.xcodeproj/project.pbxproj +302 -0
- package/ios/RNFBAnalyticsUnitTests/RNFBAnalyticsUnitTests.xcodeproj/xcshareddata/xcschemes/RNFBAnalyticsUnitTests.xcscheme +69 -0
- package/lib/index.ts +1 -1
- package/lib/version.ts +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2016-present Invertase Limited & Contributors
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this library except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
#if __has_include(<Firebase/Firebase.h>)
|
|
19
|
+
#import <Firebase/Firebase.h>
|
|
20
|
+
#elif __has_include(<FirebaseAnalytics/FirebaseAnalytics.h>)
|
|
21
|
+
#import <FirebaseAnalytics/FirebaseAnalytics.h>
|
|
22
|
+
#import <FirebaseCore/FirebaseCore.h>
|
|
23
|
+
#else
|
|
24
|
+
@import FirebaseCore;
|
|
25
|
+
@import FirebaseAnalytics;
|
|
26
|
+
#endif
|
|
27
|
+
|
|
28
|
+
#if __has_include(<RNFBAnalytics/RNFBAnalytics-Swift.h>)
|
|
29
|
+
#import <RNFBAnalytics/RNFBAnalytics-Swift.h>
|
|
30
|
+
#elif __has_include("RNFBAnalytics-Swift.h")
|
|
31
|
+
#import "RNFBAnalytics-Swift.h"
|
|
32
|
+
#elif __has_include("RNFBAnalyticsJavascriptParamsCleaner-Swift.inc")
|
|
33
|
+
#import "RNFBAnalyticsJavascriptParamsCleaner-Swift.inc"
|
|
34
|
+
#if __has_include("RNFBAnalytics/RNFBAnalyticsLogTransactionHostStub.h")
|
|
35
|
+
#import "RNFBAnalytics/RNFBAnalyticsLogTransactionHostStub.h"
|
|
36
|
+
#endif
|
|
37
|
+
#else
|
|
38
|
+
#error "RNFBAnalytics Swift interface not found"
|
|
39
|
+
#endif
|
|
40
|
+
|
|
41
|
+
#if __has_include(<RNFBApp/RNFBApp-Swift.h>)
|
|
42
|
+
#import <RNFBApp/RNFBApp-Swift.h>
|
|
43
|
+
#elif __has_include("RNFBApp-Swift.h")
|
|
44
|
+
#import "RNFBApp-Swift.h"
|
|
45
|
+
#elif __has_include("RNFBHandleMapStorage-Swift.inc")
|
|
46
|
+
#import "RNFBHandleMapStorage-Swift.inc"
|
|
47
|
+
#endif
|
|
48
|
+
|
|
49
|
+
#import "RNFBAnalyticsHelper.h"
|
|
50
|
+
#import "RNFBApp/RNFBSharedUtils.h"
|
|
51
|
+
|
|
52
|
+
@implementation RNFBAnalyticsHelper
|
|
53
|
+
|
|
54
|
+
+ (NSDictionary *)decodedParams:(NSDictionary *)params {
|
|
55
|
+
return [RNFBNullSentinelDecoder decodeNullSentinels:params];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
+ (NSDictionary *)logEventParams:(NSDictionary *)params {
|
|
59
|
+
return [self dictionaryByDecodingAndOmittingNSNull:params];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
+ (NSDictionary *)decodedUserProperties:(NSDictionary *)properties {
|
|
63
|
+
return [self dictionaryByDecodingAndOmittingNSNull:properties];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
+ (NSDictionary *)dictionaryByDecodingAndOmittingNSNull:(NSDictionary *)params {
|
|
67
|
+
return [self dictionaryByOmittingNSNull:[RNFBNullSentinelDecoder decodeNullSentinels:params]];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
+ (NSDictionary *)dictionaryByOmittingNSNull:(id)decoded {
|
|
71
|
+
if (decoded == nil || decoded == [NSNull null]) {
|
|
72
|
+
return nil;
|
|
73
|
+
}
|
|
74
|
+
if (![decoded isKindOfClass:[NSDictionary class]]) {
|
|
75
|
+
return nil;
|
|
76
|
+
}
|
|
77
|
+
return [self valueByOmittingNSNull:decoded];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
+ (id)valueByOmittingNSNull:(id)value {
|
|
81
|
+
if (value == nil || value == [NSNull null]) {
|
|
82
|
+
return nil;
|
|
83
|
+
}
|
|
84
|
+
if ([value isKindOfClass:[NSDictionary class]]) {
|
|
85
|
+
NSDictionary *dict = (NSDictionary *)value;
|
|
86
|
+
NSMutableDictionary *normalized = [NSMutableDictionary dictionaryWithCapacity:dict.count];
|
|
87
|
+
for (id key in dict) {
|
|
88
|
+
id child = [self valueByOmittingNSNull:dict[key]];
|
|
89
|
+
if (child != nil) {
|
|
90
|
+
normalized[key] = child;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return normalized;
|
|
94
|
+
}
|
|
95
|
+
if ([value isKindOfClass:[NSArray class]]) {
|
|
96
|
+
NSArray *array = (NSArray *)value;
|
|
97
|
+
NSMutableArray *normalized = [NSMutableArray arrayWithCapacity:array.count];
|
|
98
|
+
for (id child in array) {
|
|
99
|
+
id processed = [self valueByOmittingNSNull:child];
|
|
100
|
+
if (processed != nil) {
|
|
101
|
+
[normalized addObject:processed];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return normalized;
|
|
105
|
+
}
|
|
106
|
+
return value;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
+ (void)logEvent:(NSString *)name
|
|
110
|
+
params:(NSDictionary *)params
|
|
111
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
112
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
113
|
+
@try {
|
|
114
|
+
NSDictionary *decoded = [self logEventParams:params];
|
|
115
|
+
[FIRAnalytics
|
|
116
|
+
logEventWithName:name
|
|
117
|
+
parameters:[RNFBAnalyticsJavascriptParamsCleaner cleanJavascriptParams:decoded]];
|
|
118
|
+
} @catch (NSException *exception) {
|
|
119
|
+
return [RNFBSharedUtils rejectPromiseWithExceptionDict:reject exception:exception];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return resolve([NSNull null]);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
+ (void)setAnalyticsCollectionEnabled:(BOOL)enabled
|
|
126
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
127
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
128
|
+
@try {
|
|
129
|
+
[FIRAnalytics setAnalyticsCollectionEnabled:enabled];
|
|
130
|
+
} @catch (NSException *exception) {
|
|
131
|
+
return [RNFBSharedUtils rejectPromiseWithExceptionDict:reject exception:exception];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return resolve([NSNull null]);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
+ (void)setUserId:(NSString *)userId
|
|
138
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
139
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
140
|
+
@try {
|
|
141
|
+
[FIRAnalytics setUserID:[RNFBAnalyticsJavascriptParamsCleaner convertNSNullToNil:userId]];
|
|
142
|
+
} @catch (NSException *exception) {
|
|
143
|
+
return [RNFBSharedUtils rejectPromiseWithExceptionDict:reject exception:exception];
|
|
144
|
+
}
|
|
145
|
+
return resolve([NSNull null]);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
+ (void)setUserProperty:(NSString *)name
|
|
149
|
+
value:(NSString *)value
|
|
150
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
151
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
152
|
+
@try {
|
|
153
|
+
[FIRAnalytics
|
|
154
|
+
setUserPropertyString:[RNFBAnalyticsJavascriptParamsCleaner convertNSNullToNil:value]
|
|
155
|
+
forName:name];
|
|
156
|
+
} @catch (NSException *exception) {
|
|
157
|
+
return [RNFBSharedUtils rejectPromiseWithExceptionDict:reject exception:exception];
|
|
158
|
+
}
|
|
159
|
+
return resolve([NSNull null]);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
+ (void)setUserProperties:(NSDictionary *)properties
|
|
163
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
164
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
165
|
+
@try {
|
|
166
|
+
// Seam omits NSNull keys (subscript → nil). Iterate the original keys so a cleared
|
|
167
|
+
// property still reaches setUserPropertyString:forName: (nil removes the property).
|
|
168
|
+
NSDictionary *decoded = [self decodedUserProperties:properties];
|
|
169
|
+
for (NSString *key in properties) {
|
|
170
|
+
[FIRAnalytics setUserPropertyString:decoded[key] forName:key];
|
|
171
|
+
}
|
|
172
|
+
} @catch (NSException *exception) {
|
|
173
|
+
return [RNFBSharedUtils rejectPromiseWithExceptionDict:reject exception:exception];
|
|
174
|
+
}
|
|
175
|
+
return resolve([NSNull null]);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
+ (void)resetAnalyticsData:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
|
|
179
|
+
@try {
|
|
180
|
+
[FIRAnalytics resetAnalyticsData];
|
|
181
|
+
} @catch (NSException *exception) {
|
|
182
|
+
return [RNFBSharedUtils rejectPromiseWithExceptionDict:reject exception:exception];
|
|
183
|
+
}
|
|
184
|
+
return resolve([NSNull null]);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
+ (void)setSessionTimeoutDuration:(double)milliseconds
|
|
188
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
189
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
190
|
+
(void)reject;
|
|
191
|
+
[FIRAnalytics setSessionTimeoutInterval:milliseconds / 1000];
|
|
192
|
+
return resolve([NSNull null]);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
+ (void)getAppInstanceId:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
|
|
196
|
+
(void)reject;
|
|
197
|
+
return resolve([FIRAnalytics appInstanceID]);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
+ (void)getSessionId:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
|
|
201
|
+
(void)reject;
|
|
202
|
+
__block BOOL completed = NO;
|
|
203
|
+
const int64_t timeoutNs = (int64_t)(60 * NSEC_PER_SEC);
|
|
204
|
+
|
|
205
|
+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, timeoutNs), dispatch_get_main_queue(), ^{
|
|
206
|
+
if (completed) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
completed = YES;
|
|
210
|
+
DLog(@"getSessionId timed_out: no SDK callback within 60 seconds");
|
|
211
|
+
resolve([NSNull null]);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
[FIRAnalytics sessionIDWithCompletion:^(int64_t sessionID, NSError *_Nullable error) {
|
|
215
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
216
|
+
if (completed) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
completed = YES;
|
|
220
|
+
|
|
221
|
+
// Occasionally sessionID is 0 despite nil error, treat as if it were an error
|
|
222
|
+
// https://github.com/firebase/firebase-ios-sdk/issues/15258
|
|
223
|
+
if (!error && sessionID == 0) {
|
|
224
|
+
DLog(@"getSessionId zero_without_error: sessionID=0 (firebase-ios-sdk#15258)");
|
|
225
|
+
return resolve([NSNull null]);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (error) {
|
|
229
|
+
DLog(@"getSessionId sdk_error: domain=%@ code=%ld description=%@", error.domain,
|
|
230
|
+
(long)error.code, error.localizedDescription ?: @"(none)");
|
|
231
|
+
return resolve([NSNull null]);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
DLog(@"getSessionId success: sessionID=%lld", sessionID);
|
|
235
|
+
return resolve([NSNumber numberWithLongLong:sessionID]);
|
|
236
|
+
});
|
|
237
|
+
}];
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
+ (void)setDefaultEventParameters:(NSDictionary *)params
|
|
241
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
242
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
243
|
+
@try {
|
|
244
|
+
NSDictionary *decoded = [self decodedParams:params];
|
|
245
|
+
[FIRAnalytics setDefaultEventParameters:[RNFBAnalyticsJavascriptParamsCleaner
|
|
246
|
+
cleanJavascriptParams:decoded]];
|
|
247
|
+
} @catch (NSException *exception) {
|
|
248
|
+
return [RNFBSharedUtils rejectPromiseWithExceptionDict:reject exception:exception];
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return resolve([NSNull null]);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
+ (void)initiateOnDeviceConversionMeasurementWithEmailAddress:(NSString *)emailAddress
|
|
255
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
256
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
257
|
+
@try {
|
|
258
|
+
[FIRAnalytics initiateOnDeviceConversionMeasurementWithEmailAddress:emailAddress];
|
|
259
|
+
} @catch (NSException *exception) {
|
|
260
|
+
return [RNFBSharedUtils rejectPromiseWithExceptionDict:reject exception:exception];
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return resolve([NSNull null]);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
+ (void)initiateOnDeviceConversionMeasurementWithHashedEmailAddress:(NSString *)hashedEmailAddress
|
|
267
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
268
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
269
|
+
@try {
|
|
270
|
+
NSData *emailAddress =
|
|
271
|
+
[RNFBAnalyticsJavascriptParamsCleaner dataFromSHA256HexString:hashedEmailAddress];
|
|
272
|
+
if (emailAddress == nil) {
|
|
273
|
+
reject(@"firebase_analytics", @"Expected a 64-character SHA-256 hex string", nil);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
[FIRAnalytics initiateOnDeviceConversionMeasurementWithHashedEmailAddress:emailAddress];
|
|
277
|
+
} @catch (NSException *exception) {
|
|
278
|
+
return [RNFBSharedUtils rejectPromiseWithExceptionDict:reject exception:exception];
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return resolve([NSNull null]);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
+ (void)initiateOnDeviceConversionMeasurementWithPhoneNumber:(NSString *)phoneNumber
|
|
285
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
286
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
287
|
+
@try {
|
|
288
|
+
[FIRAnalytics initiateOnDeviceConversionMeasurementWithPhoneNumber:phoneNumber];
|
|
289
|
+
} @catch (NSException *exception) {
|
|
290
|
+
return [RNFBSharedUtils rejectPromiseWithExceptionDict:reject exception:exception];
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return resolve([NSNull null]);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
+ (void)initiateOnDeviceConversionMeasurementWithHashedPhoneNumber:(NSString *)hashedPhoneNumber
|
|
297
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
298
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
299
|
+
@try {
|
|
300
|
+
NSData *phoneNumber =
|
|
301
|
+
[RNFBAnalyticsJavascriptParamsCleaner dataFromSHA256HexString:hashedPhoneNumber];
|
|
302
|
+
if (phoneNumber == nil) {
|
|
303
|
+
reject(@"firebase_analytics", @"Expected a 64-character SHA-256 hex string", nil);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
[FIRAnalytics initiateOnDeviceConversionMeasurementWithHashedPhoneNumber:phoneNumber];
|
|
307
|
+
} @catch (NSException *exception) {
|
|
308
|
+
return [RNFBSharedUtils rejectPromiseWithExceptionDict:reject exception:exception];
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return resolve([NSNull null]);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
+ (void)logTransaction:(NSString *)transactionId
|
|
315
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
316
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
317
|
+
if (@available(iOS 15.0, macOS 12.0, *)) {
|
|
318
|
+
RNFBAnalyticsLogTransaction *handler = [[RNFBAnalyticsLogTransaction alloc] init];
|
|
319
|
+
[handler logTransactionWithTransactionId:transactionId resolve:resolve reject:reject];
|
|
320
|
+
} else {
|
|
321
|
+
reject(@"firebase_analytics", @"logTransaction() is only supported on iOS 15.0 or newer", nil);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
+ (void)setConsent:(NSDictionary *)consentSettings
|
|
326
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
327
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
328
|
+
@try {
|
|
329
|
+
NSDictionary *consent =
|
|
330
|
+
[RNFBAnalyticsConsentSettingsMapper consentDictionaryFromSettings:consentSettings];
|
|
331
|
+
[FIRAnalytics setConsent:consent];
|
|
332
|
+
} @catch (NSException *exception) {
|
|
333
|
+
return [RNFBSharedUtils rejectPromiseWithExceptionDict:reject exception:exception];
|
|
334
|
+
}
|
|
335
|
+
return resolve([NSNull null]);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
@end
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2016-present Invertase Limited & Contributors
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this library except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import Foundation
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Foundation-friendly Analytics JS param helpers previously inlined in
|
|
21
|
+
* `RNFBAnalyticsModule.mm`.
|
|
22
|
+
*
|
|
23
|
+
* Pre-port contracts:
|
|
24
|
+
* - NSNull over the bridge → nil (`convertNSNullToNil`)
|
|
25
|
+
* - 64 hex chars → 32-byte `Data` or nil (`dataFromSHA256HexString`)
|
|
26
|
+
* - Long-numeric GA4 keys coerced via `integerValue`
|
|
27
|
+
* - `success` string/bool coercion to 0/1 `NSNumber`
|
|
28
|
+
* - `extend_session` `@1` → `@YES`
|
|
29
|
+
*
|
|
30
|
+
* Parameter key strings match Firebase Analytics `kFIRParameter*` values
|
|
31
|
+
* (Foundation-only so unit tests do not link the Analytics SDK).
|
|
32
|
+
*/
|
|
33
|
+
@objc(RNFBAnalyticsJavascriptParamsCleaner)
|
|
34
|
+
public final class RNFBAnalyticsJavascriptParamsCleaner: NSObject {
|
|
35
|
+
// MARK: - Firebase Analytics parameter key strings (kFIRParameter*)
|
|
36
|
+
|
|
37
|
+
private static let parameterQuantity = "quantity"
|
|
38
|
+
private static let parameterIndex = "index"
|
|
39
|
+
private static let parameterLevel = "level"
|
|
40
|
+
private static let parameterNumberOfNights = "number_of_nights"
|
|
41
|
+
private static let parameterNumberOfPassengers = "number_of_passengers"
|
|
42
|
+
private static let parameterNumberOfRooms = "number_of_rooms"
|
|
43
|
+
private static let parameterScore = "score"
|
|
44
|
+
private static let parameterItems = "items"
|
|
45
|
+
private static let parameterSuccess = "success"
|
|
46
|
+
private static let parameterExtendSession = "extend_session"
|
|
47
|
+
|
|
48
|
+
/// Same keys as pre-port `RNFBAnalyticsLongNumericParameterKeys`.
|
|
49
|
+
@objc public static var longNumericParameterKeys: [String] {
|
|
50
|
+
[
|
|
51
|
+
parameterQuantity,
|
|
52
|
+
parameterIndex,
|
|
53
|
+
parameterLevel,
|
|
54
|
+
parameterNumberOfNights,
|
|
55
|
+
parameterNumberOfPassengers,
|
|
56
|
+
parameterNumberOfRooms,
|
|
57
|
+
parameterScore,
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// MARK: - NSNull bridge
|
|
62
|
+
|
|
63
|
+
/// Converts null values received over the bridge from NSNull to nil.
|
|
64
|
+
@objc(convertNSNullToNil:)
|
|
65
|
+
public static func convertNSNullToNil(_ value: Any?) -> String? {
|
|
66
|
+
// Pre-port: `[value isEqual:[NSNull null]] ? nil : value`
|
|
67
|
+
if let value, (value as AnyObject).isEqual(NSNull()) {
|
|
68
|
+
return nil
|
|
69
|
+
}
|
|
70
|
+
return value as? String
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// MARK: - SHA-256 hex
|
|
74
|
+
|
|
75
|
+
private static func hexDigit(_ character: unichar) -> Int {
|
|
76
|
+
if character >= 0x30 && character <= 0x39 {
|
|
77
|
+
return Int(character) - 0x30
|
|
78
|
+
}
|
|
79
|
+
if character >= 0x61 && character <= 0x66 {
|
|
80
|
+
return Int(character) - 0x61 + 10
|
|
81
|
+
}
|
|
82
|
+
if character >= 0x41 && character <= 0x46 {
|
|
83
|
+
return Int(character) - 0x41 + 10
|
|
84
|
+
}
|
|
85
|
+
return -1
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/// 64 hex characters → 32-byte `Data`, or nil when length/hex is invalid.
|
|
89
|
+
@objc(dataFromSHA256HexString:)
|
|
90
|
+
public static func dataFromSHA256HexString(_ hexString: String?) -> Data? {
|
|
91
|
+
guard let hexString, (hexString as NSString).length == 64 else {
|
|
92
|
+
return nil
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
var bytes = [UInt8](repeating: 0, count: 32)
|
|
96
|
+
let nsString = hexString as NSString
|
|
97
|
+
for i in 0..<32 {
|
|
98
|
+
let high = hexDigit(nsString.character(at: i * 2))
|
|
99
|
+
let low = hexDigit(nsString.character(at: i * 2 + 1))
|
|
100
|
+
if high < 0 || low < 0 {
|
|
101
|
+
return nil
|
|
102
|
+
}
|
|
103
|
+
bytes[i] = UInt8((high << 4) | low)
|
|
104
|
+
}
|
|
105
|
+
return Data(bytes)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// MARK: - JS params cleaning
|
|
109
|
+
|
|
110
|
+
@objc(cleanJavascriptParams:)
|
|
111
|
+
public static func cleanJavascriptParams(_ params: NSDictionary?) -> NSDictionary? {
|
|
112
|
+
guard let params else {
|
|
113
|
+
return nil
|
|
114
|
+
}
|
|
115
|
+
let newParams = NSMutableDictionary(dictionary: params)
|
|
116
|
+
|
|
117
|
+
if newParams[parameterItems] != nil {
|
|
118
|
+
let newItems = NSMutableArray()
|
|
119
|
+
let items = newParams[parameterItems] as! NSArray
|
|
120
|
+
items.enumerateObjects { obj, _, _ in
|
|
121
|
+
let item = (obj as AnyObject).mutableCopy() as! NSMutableDictionary
|
|
122
|
+
coerceLongNumericParameters(in: item)
|
|
123
|
+
newItems.add(item.copy())
|
|
124
|
+
}
|
|
125
|
+
newParams[parameterItems] = newItems.copy()
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
coerceLongNumericParameters(in: newParams)
|
|
129
|
+
coerceSuccessParameter(in: newParams)
|
|
130
|
+
|
|
131
|
+
if let extendSession = newParams.value(forKey: parameterExtendSession) as? NSNumber,
|
|
132
|
+
extendSession.isEqual(to: NSNumber(value: 1))
|
|
133
|
+
{
|
|
134
|
+
newParams[parameterExtendSession] = NSNumber(value: true)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return newParams.copy() as? NSDictionary
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private static func coerceLongNumericParameters(in dict: NSMutableDictionary) {
|
|
141
|
+
for key in longNumericParameterKeys {
|
|
142
|
+
guard let value = dict[key], !(value is NSNull) else {
|
|
143
|
+
continue
|
|
144
|
+
}
|
|
145
|
+
// Pre-port: `dict[key] = @([value integerValue]);`
|
|
146
|
+
dict[key] = NSNumber(value: (value as AnyObject).integerValue)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private static func coerceSuccessParameter(in dict: NSMutableDictionary) {
|
|
151
|
+
guard let value = dict[parameterSuccess], !(value is NSNull) else {
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
var success = 0
|
|
156
|
+
if let string = value as? String {
|
|
157
|
+
let lower = string.lowercased()
|
|
158
|
+
if lower == "true" || lower == "yes" || lower == "1" {
|
|
159
|
+
success = 1
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
// Pre-port: `success = [value boolValue] ? 1 : 0;`
|
|
163
|
+
success = (value as AnyObject).boolValue ? 1 : 0
|
|
164
|
+
}
|
|
165
|
+
dict[parameterSuccess] = NSNumber(value: success)
|
|
166
|
+
}
|
|
167
|
+
}
|