@react-native-firebase/analytics 26.4.0 → 27.0.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.
Files changed (31) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/RNFBAnalytics.podspec +49 -2
  3. package/dist/module/index.js +2 -2
  4. package/dist/module/index.js.map +1 -1
  5. package/dist/module/version.js +1 -1
  6. package/dist/typescript/lib/index.d.ts.map +1 -1
  7. package/dist/typescript/lib/version.d.ts +1 -1
  8. package/ios/RNFBAnalytics/RNFBAnalyticsConsentSettingsMapper.swift +98 -0
  9. package/ios/RNFBAnalytics/RNFBAnalyticsHelper.h +111 -0
  10. package/ios/RNFBAnalytics/RNFBAnalyticsHelper.m +338 -0
  11. package/ios/RNFBAnalytics/RNFBAnalyticsJavascriptParamsCleaner.swift +167 -0
  12. package/ios/RNFBAnalytics/RNFBAnalyticsModule.mm +32 -282
  13. package/ios/RNFBAnalytics.xcodeproj/project.pbxproj +16 -2
  14. package/ios/RNFBAnalyticsUnitTests/HostStubs/Firebase/FIRAnalyticsHostStub.m +304 -0
  15. package/ios/RNFBAnalyticsUnitTests/HostStubs/Firebase/Firebase.h +71 -0
  16. package/ios/RNFBAnalyticsUnitTests/HostStubs/RNFBAnalytics/RNFBAnalyticsLogTransactionHostStub.h +20 -0
  17. package/ios/RNFBAnalyticsUnitTests/HostStubs/RNFBAnalytics/RNFBAnalyticsLogTransactionHostStub.m +43 -0
  18. package/ios/RNFBAnalyticsUnitTests/HostStubs/RNFBApp/RNFBSharedUtils.h +21 -0
  19. package/ios/RNFBAnalyticsUnitTests/HostStubs/RNFBApp/RNFBSharedUtilsHostStub.m +26 -0
  20. package/ios/RNFBAnalyticsUnitTests/HostStubs/React/RCTBridgeModule.h +10 -0
  21. package/ios/RNFBAnalyticsUnitTests/RNFBAnalyticsConsentSettingsMapperHostStubTests.m +62 -0
  22. package/ios/RNFBAnalyticsUnitTests/RNFBAnalyticsConsentSettingsMapperTests.swift +91 -0
  23. package/ios/RNFBAnalyticsUnitTests/RNFBAnalyticsHelperHostStubTests.m +542 -0
  24. package/ios/RNFBAnalyticsUnitTests/RNFBAnalyticsJavascriptParamsCleanerHostStubTests.m +59 -0
  25. package/ios/RNFBAnalyticsUnitTests/RNFBAnalyticsJavascriptParamsCleanerTests.swift +205 -0
  26. package/ios/RNFBAnalyticsUnitTests/RNFBAnalyticsNullSentinelDecodeTests.m +111 -0
  27. package/ios/RNFBAnalyticsUnitTests/RNFBAnalyticsUnitTests.xcodeproj/project.pbxproj +302 -0
  28. package/ios/RNFBAnalyticsUnitTests/RNFBAnalyticsUnitTests.xcodeproj/xcshareddata/xcschemes/RNFBAnalyticsUnitTests.xcscheme +69 -0
  29. package/lib/index.ts +1 -1
  30. package/lib/version.ts +1 -1
  31. 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
+ }