@npaw/forge-react-native 9.6.1
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/LICENSE +10 -0
- package/NPAWForgeReactNative.podspec +44 -0
- package/README.md +51 -0
- package/android/build.gradle +77 -0
- package/android/src/main/AndroidManifest.xml +14 -0
- package/android/src/main/java/com/npaw/forge/reactnative/ForgeDtoMapper.java +295 -0
- package/android/src/main/java/com/npaw/forge/reactnative/ForgeInstanceRegistry.java +61 -0
- package/android/src/main/java/com/npaw/forge/reactnative/NPAWForgeModule.java +735 -0
- package/android/src/main/java/com/npaw/forge/reactnative/NPAWForgePackage.java +53 -0
- package/android/src/test/java/com/npaw/forge/reactnative/ForgeDtoMapperTest.java +133 -0
- package/ios/ForgeApple/ForgeKernelFFI.xcframework/Info.plist +82 -0
- package/ios/ForgeApple/ForgeKernelFFI.xcframework/ios-arm64/Headers/ForgeKernelFFI.h +17 -0
- package/ios/ForgeApple/ForgeKernelFFI.xcframework/ios-arm64/Headers/forge_kernelFFI.h +873 -0
- package/ios/ForgeApple/ForgeKernelFFI.xcframework/ios-arm64/Headers/module.modulemap +7 -0
- package/ios/ForgeApple/ForgeKernelFFI.xcframework/ios-arm64/libForgeKernelFFI.a +0 -0
- package/ios/ForgeApple/ForgeKernelFFI.xcframework/ios-arm64_x86_64-simulator/Headers/ForgeKernelFFI.h +17 -0
- package/ios/ForgeApple/ForgeKernelFFI.xcframework/ios-arm64_x86_64-simulator/Headers/forge_kernelFFI.h +873 -0
- package/ios/ForgeApple/ForgeKernelFFI.xcframework/ios-arm64_x86_64-simulator/Headers/module.modulemap +7 -0
- package/ios/ForgeApple/ForgeKernelFFI.xcframework/ios-arm64_x86_64-simulator/libForgeKernelFFI.a +0 -0
- package/ios/ForgeApple/ForgeKernelFFI.xcframework/tvos-arm64/Headers/ForgeKernelFFI.h +17 -0
- package/ios/ForgeApple/ForgeKernelFFI.xcframework/tvos-arm64/Headers/forge_kernelFFI.h +873 -0
- package/ios/ForgeApple/ForgeKernelFFI.xcframework/tvos-arm64/Headers/module.modulemap +7 -0
- package/ios/ForgeApple/ForgeKernelFFI.xcframework/tvos-arm64/libForgeKernelFFI.a +0 -0
- package/ios/ForgeApple/ForgeKernelFFI.xcframework/tvos-arm64-simulator/Headers/ForgeKernelFFI.h +17 -0
- package/ios/ForgeApple/ForgeKernelFFI.xcframework/tvos-arm64-simulator/Headers/forge_kernelFFI.h +873 -0
- package/ios/ForgeApple/ForgeKernelFFI.xcframework/tvos-arm64-simulator/Headers/module.modulemap +7 -0
- package/ios/ForgeApple/ForgeKernelFFI.xcframework/tvos-arm64-simulator/libForgeKernelFFI.a +0 -0
- package/ios/ForgeApple/Sources/ForgeKernelUniFFI/forge_kernel.swift +3931 -0
- package/ios/ForgeApple/Sources/ForgeKernelUniFFI/forge_kernelFFI.h +873 -0
- package/ios/ForgeApple/Sources/ForgeKernelUniFFI/forge_kernelFFI.modulemap +7 -0
- package/ios/ForgeApple/Sources/NPAWForgeAVPlayerAdapter/AVPlayerAccessLogFacts.swift +152 -0
- package/ios/ForgeApple/Sources/NPAWForgeAVPlayerAdapter/AVPlayerAdapter.swift +877 -0
- package/ios/ForgeApple/Sources/NPAWForgeCapture/AttributionCapture.swift +158 -0
- package/ios/ForgeApple/Sources/NPAWForgeCapture/CapturePlanGate.swift +189 -0
- package/ios/ForgeApple/Sources/NPAWForgeCapture/CrashCapture.swift +120 -0
- package/ios/ForgeApple/Sources/NPAWForgeCapture/ForgeAutomaticCapture.swift +639 -0
- package/ios/ForgeApple/Sources/NPAWForgeCapture/SectionsCapture.swift +1176 -0
- package/ios/ForgeApple/Sources/NPAWForgeCapture/SectionsEngine.swift +674 -0
- package/ios/ForgeApple/Sources/NPAWForgeCapture/SectionsMarkers.swift +105 -0
- package/ios/ForgeApple/Sources/NPAWForgeCapture/SectionsSwiftUI.swift +284 -0
- package/ios/ForgeApple/Sources/NPAWForgeCapture/SwiftUICapture.swift +44 -0
- package/ios/ForgeApple/Sources/NPAWForgeCore/AdAdapter.swift +199 -0
- package/ios/ForgeApple/Sources/NPAWForgeCore/AdSemantics.swift +136 -0
- package/ios/ForgeApple/Sources/NPAWForgeCore/ForgeApple.swift +596 -0
- package/ios/ForgeApple/Sources/NPAWForgeCore/ForgeCapturePlan.swift +297 -0
- package/ios/ForgeApple/Sources/NPAWForgeCore/ForgeClock.swift +29 -0
- package/ios/ForgeApple/Sources/NPAWForgeCore/ForgeLogging.swift +109 -0
- package/ios/ForgeApple/Sources/NPAWForgeCore/ForgeOutcome.swift +89 -0
- package/ios/ForgeApple/Sources/NPAWForgeCore/ForgeRuntime.swift +291 -0
- package/ios/ForgeApple/Sources/NPAWForgeCore/ForgeStorage.swift +70 -0
- package/ios/ForgeApple/Sources/NPAWForgeCore/ManualAdReporter.swift +180 -0
- package/ios/ForgeApple/Sources/NPAWForgeCore/NPAWForge.swift +453 -0
- package/ios/ForgeApple/Sources/NPAWForgeCore/NativeUniFFIRuntime.swift +594 -0
- package/ios/ForgeApple/Sources/NPAWForgeCore/NetworkRequestRecord.swift +191 -0
- package/ios/ForgeApple/Sources/NPAWForgeCore/ObjectiveCWrappers.swift +191 -0
- package/ios/ForgeApple/Sources/NPAWForgeCore/PlayerAdapter.swift +299 -0
- package/ios/ForgeApple/Sources/NPAWForgeCore/PlayerSemantics.swift +266 -0
- package/ios/ForgeApple/Sources/NPAWForgeCore/PrivacySanitizer.swift +59 -0
- package/ios/ForgeApple/Sources/NPAWForgeCore/VideoAdapter.swift +478 -0
- package/ios/ForgeApple/Sources/NPAWForgeCore/VideoOptions.swift +431 -0
- package/ios/ForgeApple/Sources/NPAWForgeNetworkCapture/ForgeNetworkCapture.swift +116 -0
- package/ios/ForgeApple/Sources/NPAWForgeNetworkCaptureObjC/NPAWForgeNetworkSwizzler.m +293 -0
- package/ios/ForgeApple/Sources/NPAWForgeNetworkCaptureObjC/include/NPAWForgeNetworkSwizzler.h +33 -0
- package/ios/ForgeModuleImpl.swift +1131 -0
- package/ios/NPAWForgeTurboModule.mm +399 -0
- package/package.json +33 -0
- package/src/ForgeAdReporter.ts +101 -0
- package/src/ForgeVideo.ts +169 -0
- package/src/NPAWForge.ts +177 -0
- package/src/capture.ts +192 -0
- package/src/events.ts +40 -0
- package/src/index.ts +37 -0
- package/src/specs/NativeNPAWForge.ts +168 -0
- package/src/types.ts +114 -0
|
@@ -0,0 +1,1131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (C) NPAW - All Rights Reserved
|
|
3
|
+
*
|
|
4
|
+
* This source code is protected under international copyright law. All rights
|
|
5
|
+
* reserved and protected by the copyright holders.
|
|
6
|
+
* This file is confidential and only available to authorized individuals with the
|
|
7
|
+
* permission of the copyright holders. If you encounter this file and do not have
|
|
8
|
+
* permission, please contact the copyright holders and delete this file.
|
|
9
|
+
*
|
|
10
|
+
*
|
|
11
|
+
* @author: Victor Soto <victor.soto@nicepeopleatwork.com>
|
|
12
|
+
* @description: Swift implementation behind the core Forge TurboModule - full facade surface over the Swift NPAWForge facade (sessions, identity, raw/business, network, diagnostics, video fire*, ad reporters, kernel event feed).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import Foundation
|
|
16
|
+
#if canImport(NPAWForgeCore)
|
|
17
|
+
import NPAWForgeCore
|
|
18
|
+
#elseif canImport(NPAWForgeApple)
|
|
19
|
+
import NPAWForgeApple
|
|
20
|
+
#endif
|
|
21
|
+
#if canImport(NPAWForgeNetworkCapture)
|
|
22
|
+
import NPAWForgeNetworkCapture
|
|
23
|
+
#endif
|
|
24
|
+
|
|
25
|
+
/// Bridge-friendly resolve/reject shapes so this file never imports React.
|
|
26
|
+
public typealias ForgeResolveBlock = (Any?) -> Void
|
|
27
|
+
public typealias ForgeRejectBlock = (String, String) -> Void
|
|
28
|
+
|
|
29
|
+
@objc(ForgeModuleImpl)
|
|
30
|
+
public final class ForgeModuleImpl: NSObject {
|
|
31
|
+
|
|
32
|
+
@objc public static let shared = ForgeModuleImpl()
|
|
33
|
+
|
|
34
|
+
/// Set by the TurboModule shell; receives the channel body for every kernel event.
|
|
35
|
+
@objc public var eventSink: (([String: Any]) -> Void)?
|
|
36
|
+
|
|
37
|
+
private let lock = NSLock()
|
|
38
|
+
private var instances: [String: NPAWForge] = [:]
|
|
39
|
+
private var subscriptions: [String: ForgeEventSubscription] = [:]
|
|
40
|
+
private var adReporters: [Int: ManualAdReporter] = [:]
|
|
41
|
+
private var nextInstanceId = 1
|
|
42
|
+
private var nextReporterId = 1
|
|
43
|
+
private var listenerCount = 0
|
|
44
|
+
|
|
45
|
+
private static let errUnknownInstance = "E_FORGE_UNKNOWN_INSTANCE"
|
|
46
|
+
private static let errUnknownReporter = "E_FORGE_UNKNOWN_REPORTER"
|
|
47
|
+
private static let errBadArgument = "E_FORGE_BAD_ARGUMENT"
|
|
48
|
+
|
|
49
|
+
private override init() {
|
|
50
|
+
super.init()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// MARK: - Registry seam for the video package
|
|
54
|
+
|
|
55
|
+
public func resolveInstance(_ instanceId: String) -> NPAWForge? {
|
|
56
|
+
lock.lock()
|
|
57
|
+
defer { lock.unlock() }
|
|
58
|
+
return instances[instanceId]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// MARK: - Lifecycle
|
|
62
|
+
|
|
63
|
+
@objc public func create(
|
|
64
|
+
_ accountCode: String,
|
|
65
|
+
config: [String: Any]?,
|
|
66
|
+
resolve: @escaping ForgeResolveBlock,
|
|
67
|
+
reject: @escaping ForgeRejectBlock
|
|
68
|
+
) {
|
|
69
|
+
guard !accountCode.trimmingCharacters(in: .whitespaces).isEmpty else {
|
|
70
|
+
reject(Self.errBadArgument, "accountCode must not be blank")
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
let builder = NPAWForge.Builder(accountCode: accountCode)
|
|
74
|
+
applyConfig(builder, config: config)
|
|
75
|
+
let forge = builder.build()
|
|
76
|
+
|
|
77
|
+
lock.lock()
|
|
78
|
+
let instanceId = "forge-\(nextInstanceId)"
|
|
79
|
+
nextInstanceId += 1
|
|
80
|
+
instances[instanceId] = forge
|
|
81
|
+
let shouldSubscribe = listenerCount > 0
|
|
82
|
+
lock.unlock()
|
|
83
|
+
|
|
84
|
+
if shouldSubscribe {
|
|
85
|
+
subscribeInstanceEvents(instanceId, forge: forge)
|
|
86
|
+
}
|
|
87
|
+
resolve(instanceId)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@objc public func destroy(
|
|
91
|
+
_ instanceId: String,
|
|
92
|
+
resolve: @escaping ForgeResolveBlock,
|
|
93
|
+
reject: @escaping ForgeRejectBlock
|
|
94
|
+
) {
|
|
95
|
+
lock.lock()
|
|
96
|
+
let forge = instances.removeValue(forKey: instanceId)
|
|
97
|
+
let subscription = subscriptions.removeValue(forKey: instanceId)
|
|
98
|
+
lock.unlock()
|
|
99
|
+
|
|
100
|
+
guard let forge else {
|
|
101
|
+
reject(Self.errUnknownInstance, "No NPAWForge instance '\(instanceId)'")
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
subscription?.cancel()
|
|
105
|
+
resolve(Self.outcomeToDictionary(forge.destroy()))
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@objc public func setLogLevel(
|
|
109
|
+
_ instanceId: String,
|
|
110
|
+
level: String,
|
|
111
|
+
resolve: @escaping ForgeResolveBlock,
|
|
112
|
+
reject: @escaping ForgeRejectBlock
|
|
113
|
+
) {
|
|
114
|
+
withInstance(instanceId, resolve: resolve, reject: reject) { $0.setLogLevel(level) }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// MARK: - App session
|
|
118
|
+
|
|
119
|
+
@objc public func startSession(
|
|
120
|
+
_ instanceId: String,
|
|
121
|
+
metadata: [String: Any]?,
|
|
122
|
+
resolve: @escaping ForgeResolveBlock,
|
|
123
|
+
reject: @escaping ForgeRejectBlock
|
|
124
|
+
) {
|
|
125
|
+
withInstance(instanceId, resolve: resolve, reject: reject) { $0.startSession(metadata ?? [:]) }
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@objc public func endSession(
|
|
129
|
+
_ instanceId: String,
|
|
130
|
+
metadata: [String: Any]?,
|
|
131
|
+
resolve: @escaping ForgeResolveBlock,
|
|
132
|
+
reject: @escaping ForgeRejectBlock
|
|
133
|
+
) {
|
|
134
|
+
withInstance(instanceId, resolve: resolve, reject: reject) { $0.endSession(metadata ?? [:]) }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
@objc public func trackSessionNavigation(
|
|
138
|
+
_ instanceId: String,
|
|
139
|
+
metadata: [String: Any]?,
|
|
140
|
+
resolve: @escaping ForgeResolveBlock,
|
|
141
|
+
reject: @escaping ForgeRejectBlock
|
|
142
|
+
) {
|
|
143
|
+
withInstance(instanceId, resolve: resolve, reject: reject) {
|
|
144
|
+
$0.trackSessionNavigation(metadata ?? [:])
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
@objc public func trackSessionEvent(
|
|
149
|
+
_ instanceId: String,
|
|
150
|
+
name: String,
|
|
151
|
+
metadata: [String: Any]?,
|
|
152
|
+
resolve: @escaping ForgeResolveBlock,
|
|
153
|
+
reject: @escaping ForgeRejectBlock
|
|
154
|
+
) {
|
|
155
|
+
withInstance(instanceId, resolve: resolve, reject: reject) {
|
|
156
|
+
$0.trackSessionEvent(name: name, metadata: metadata ?? [:])
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
@objc public func trackSessionEventStop(
|
|
161
|
+
_ instanceId: String,
|
|
162
|
+
name: String,
|
|
163
|
+
metadata: [String: Any]?,
|
|
164
|
+
resolve: @escaping ForgeResolveBlock,
|
|
165
|
+
reject: @escaping ForgeRejectBlock
|
|
166
|
+
) {
|
|
167
|
+
withInstance(instanceId, resolve: resolve, reject: reject) {
|
|
168
|
+
$0.trackSessionEventStop(name: name, metadata: metadata ?? [:])
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
@objc public func trackSessionError(
|
|
173
|
+
_ instanceId: String,
|
|
174
|
+
errorCode: String,
|
|
175
|
+
errorMessage: String,
|
|
176
|
+
metadata: [String: Any]?,
|
|
177
|
+
resolve: @escaping ForgeResolveBlock,
|
|
178
|
+
reject: @escaping ForgeRejectBlock
|
|
179
|
+
) {
|
|
180
|
+
withInstance(instanceId, resolve: resolve, reject: reject) {
|
|
181
|
+
$0.trackSessionError(errorCode: errorCode, errorMessage: errorMessage, metadata: metadata ?? [:])
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// MARK: - Identity
|
|
186
|
+
|
|
187
|
+
@objc public func loginSuccess(
|
|
188
|
+
_ instanceId: String,
|
|
189
|
+
userId: String,
|
|
190
|
+
options: [String: Any]?,
|
|
191
|
+
resolve: @escaping ForgeResolveBlock,
|
|
192
|
+
reject: @escaping ForgeRejectBlock
|
|
193
|
+
) {
|
|
194
|
+
withInstance(instanceId, resolve: resolve, reject: reject) {
|
|
195
|
+
$0.loginSuccess(userId: userId, options: Self.stringMap(options))
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
@objc public func loginFailed(
|
|
200
|
+
_ instanceId: String,
|
|
201
|
+
options: [String: Any]?,
|
|
202
|
+
resolve: @escaping ForgeResolveBlock,
|
|
203
|
+
reject: @escaping ForgeRejectBlock
|
|
204
|
+
) {
|
|
205
|
+
withInstance(instanceId, resolve: resolve, reject: reject) {
|
|
206
|
+
$0.loginFailed(options: Self.stringMap(options))
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
@objc public func logout(
|
|
211
|
+
_ instanceId: String,
|
|
212
|
+
options: [String: Any]?,
|
|
213
|
+
resolve: @escaping ForgeResolveBlock,
|
|
214
|
+
reject: @escaping ForgeRejectBlock
|
|
215
|
+
) {
|
|
216
|
+
withInstance(instanceId, resolve: resolve, reject: reject) {
|
|
217
|
+
$0.logout(options: Self.stringMap(options))
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
@objc public func profileCreated(
|
|
222
|
+
_ instanceId: String,
|
|
223
|
+
profileId: String,
|
|
224
|
+
options: [String: Any]?,
|
|
225
|
+
resolve: @escaping ForgeResolveBlock,
|
|
226
|
+
reject: @escaping ForgeRejectBlock
|
|
227
|
+
) {
|
|
228
|
+
withInstance(instanceId, resolve: resolve, reject: reject) {
|
|
229
|
+
$0.profileCreated(profileId: profileId, options: Self.stringMap(options))
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
@objc public func profileSelected(
|
|
234
|
+
_ instanceId: String,
|
|
235
|
+
profileId: String,
|
|
236
|
+
options: [String: Any]?,
|
|
237
|
+
resolve: @escaping ForgeResolveBlock,
|
|
238
|
+
reject: @escaping ForgeRejectBlock
|
|
239
|
+
) {
|
|
240
|
+
withInstance(instanceId, resolve: resolve, reject: reject) {
|
|
241
|
+
$0.profileSelected(profileId: profileId, options: Self.stringMap(options))
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
@objc public func profileDeleted(
|
|
246
|
+
_ instanceId: String,
|
|
247
|
+
profileId: String,
|
|
248
|
+
resolve: @escaping ForgeResolveBlock,
|
|
249
|
+
reject: @escaping ForgeRejectBlock
|
|
250
|
+
) {
|
|
251
|
+
withInstance(instanceId, resolve: resolve, reject: reject) {
|
|
252
|
+
$0.profileDeleted(profileId: profileId)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// MARK: - Raw / business
|
|
257
|
+
|
|
258
|
+
@objc public func trackRawEvent(
|
|
259
|
+
_ instanceId: String,
|
|
260
|
+
name: String,
|
|
261
|
+
params: [String: Any]?,
|
|
262
|
+
resolve: @escaping ForgeResolveBlock,
|
|
263
|
+
reject: @escaping ForgeRejectBlock
|
|
264
|
+
) {
|
|
265
|
+
withInstance(instanceId, resolve: resolve, reject: reject) {
|
|
266
|
+
$0.trackRawEvent(name: name, params: params ?? [:])
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
@objc public func trackSearch(
|
|
271
|
+
_ instanceId: String,
|
|
272
|
+
query: String,
|
|
273
|
+
resultCount: Double,
|
|
274
|
+
resolve: @escaping ForgeResolveBlock,
|
|
275
|
+
reject: @escaping ForgeRejectBlock
|
|
276
|
+
) {
|
|
277
|
+
withInstance(instanceId, resolve: resolve, reject: reject) {
|
|
278
|
+
$0.trackSearch(query: query, resultCount: resultCount < 0 ? nil : Int(resultCount))
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
@objc public func trackSearchResultClick(
|
|
283
|
+
_ instanceId: String,
|
|
284
|
+
query: String,
|
|
285
|
+
options: [String: Any]?,
|
|
286
|
+
resolve: @escaping ForgeResolveBlock,
|
|
287
|
+
reject: @escaping ForgeRejectBlock
|
|
288
|
+
) {
|
|
289
|
+
let map = Self.stringMap(options)
|
|
290
|
+
let typed = ForgeSearchResultClickOptions(
|
|
291
|
+
contentId: map["contentId"],
|
|
292
|
+
position: map["position"].flatMap { Int($0) }
|
|
293
|
+
)
|
|
294
|
+
withInstance(instanceId, resolve: resolve, reject: reject) {
|
|
295
|
+
$0.trackSearchResultClick(query: query, options: typed)
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
@objc public func trackBusinessEvent(
|
|
300
|
+
_ instanceId: String,
|
|
301
|
+
name: String,
|
|
302
|
+
attributes: [String: Any]?,
|
|
303
|
+
resolve: @escaping ForgeResolveBlock,
|
|
304
|
+
reject: @escaping ForgeRejectBlock
|
|
305
|
+
) {
|
|
306
|
+
withInstance(instanceId, resolve: resolve, reject: reject) {
|
|
307
|
+
$0.trackBusinessEvent(name: name, attributes: attributes ?? [:])
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
@objc public func trackAttribution(
|
|
312
|
+
_ instanceId: String,
|
|
313
|
+
options: [String: Any]?,
|
|
314
|
+
resolve: @escaping ForgeResolveBlock,
|
|
315
|
+
reject: @escaping ForgeRejectBlock
|
|
316
|
+
) {
|
|
317
|
+
let map = Self.stringMap(options)
|
|
318
|
+
let typed = ForgeAttributionOptions(
|
|
319
|
+
utmSource: map["utmSource"],
|
|
320
|
+
utmMedium: map["utmMedium"],
|
|
321
|
+
utmCampaign: map["utmCampaign"],
|
|
322
|
+
utmTerm: map["utmTerm"],
|
|
323
|
+
utmContent: map["utmContent"],
|
|
324
|
+
utmUrl: map["utmUrl"]
|
|
325
|
+
)
|
|
326
|
+
withInstance(instanceId, resolve: resolve, reject: reject) {
|
|
327
|
+
$0.trackAttribution(options: typed)
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// MARK: - Network
|
|
332
|
+
|
|
333
|
+
@objc public func trackNetworkRequest(
|
|
334
|
+
_ instanceId: String,
|
|
335
|
+
record: [String: Any],
|
|
336
|
+
resolve: @escaping ForgeResolveBlock,
|
|
337
|
+
reject: @escaping ForgeRejectBlock
|
|
338
|
+
) {
|
|
339
|
+
withInstance(instanceId, resolve: resolve, reject: reject) {
|
|
340
|
+
$0.trackNetworkRequest(Self.networkRecord(from: record))
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
@objc public func installNetworkCapture(
|
|
345
|
+
_ instanceId: String,
|
|
346
|
+
options: [String: Any]?,
|
|
347
|
+
resolve: @escaping ForgeResolveBlock,
|
|
348
|
+
reject: @escaping ForgeRejectBlock
|
|
349
|
+
) {
|
|
350
|
+
lock.lock()
|
|
351
|
+
let forge = instances[instanceId]
|
|
352
|
+
lock.unlock()
|
|
353
|
+
guard let forge else {
|
|
354
|
+
reject(Self.errUnknownInstance, "No NPAWForge instance '\(instanceId)'")
|
|
355
|
+
return
|
|
356
|
+
}
|
|
357
|
+
// URLSession swizzling captures React Native's networking directly.
|
|
358
|
+
// FORGE_RN_HYDRATED marks the staged self-contained pod, where the
|
|
359
|
+
// NetworkCapture sources compile into this module (no import needed).
|
|
360
|
+
#if canImport(NPAWForgeNetworkCapture) || FORGE_RN_HYDRATED
|
|
361
|
+
let outcome = ForgeNetworkCapture.install(forge: forge)
|
|
362
|
+
resolve(Self.outcomeToDictionary(outcome))
|
|
363
|
+
#else
|
|
364
|
+
_ = forge
|
|
365
|
+
resolve(
|
|
366
|
+
Self.outcomeToDictionary(
|
|
367
|
+
ForgeOutcome(
|
|
368
|
+
code: .unsupportedCapability,
|
|
369
|
+
reason: "NPAWForgeNetworkCapture module not present in this build"
|
|
370
|
+
)
|
|
371
|
+
)
|
|
372
|
+
)
|
|
373
|
+
#endif
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// MARK: - Diagnostics
|
|
377
|
+
|
|
378
|
+
@objc public func snapshotEvents(
|
|
379
|
+
_ instanceId: String,
|
|
380
|
+
resolve: @escaping ForgeResolveBlock,
|
|
381
|
+
reject: @escaping ForgeRejectBlock
|
|
382
|
+
) {
|
|
383
|
+
lock.lock()
|
|
384
|
+
let forge = instances[instanceId]
|
|
385
|
+
lock.unlock()
|
|
386
|
+
guard let forge else {
|
|
387
|
+
reject(Self.errUnknownInstance, "No NPAWForge instance '\(instanceId)'")
|
|
388
|
+
return
|
|
389
|
+
}
|
|
390
|
+
let items = forge.snapshotEvents().map {
|
|
391
|
+
Self.recordedEventToDictionary(instanceId: instanceId, event: $0)
|
|
392
|
+
}
|
|
393
|
+
resolve(["items": items])
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// MARK: - Video
|
|
397
|
+
|
|
398
|
+
@objc public func createVideo(
|
|
399
|
+
_ instanceId: String,
|
|
400
|
+
identifier: String,
|
|
401
|
+
options: [String: Any]?,
|
|
402
|
+
resolve: @escaping ForgeResolveBlock,
|
|
403
|
+
reject: @escaping ForgeRejectBlock
|
|
404
|
+
) {
|
|
405
|
+
lock.lock()
|
|
406
|
+
let forge = instances[instanceId]
|
|
407
|
+
lock.unlock()
|
|
408
|
+
guard let forge else {
|
|
409
|
+
reject(Self.errUnknownInstance, "No NPAWForge instance '\(instanceId)'")
|
|
410
|
+
return
|
|
411
|
+
}
|
|
412
|
+
let videoOptions = VideoOptions.Builder().metadata(Self.stringMap(options)).build()
|
|
413
|
+
_ = forge.videoBuilder().setIdentifier(identifier).setOptions(videoOptions).build()
|
|
414
|
+
resolve(Self.outcomeToDictionary(ForgeOutcome.accepted))
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
@objc public func videoDestroy(
|
|
418
|
+
_ instanceId: String,
|
|
419
|
+
identifier: String,
|
|
420
|
+
resolve: @escaping ForgeResolveBlock,
|
|
421
|
+
reject: @escaping ForgeRejectBlock
|
|
422
|
+
) {
|
|
423
|
+
withVideo(instanceId, identifier: identifier, resolve: resolve, reject: reject) { $0.destroy() }
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
@objc public func videoFireInit(
|
|
427
|
+
_ instanceId: String,
|
|
428
|
+
identifier: String,
|
|
429
|
+
params: [String: Any]?,
|
|
430
|
+
resolve: @escaping ForgeResolveBlock,
|
|
431
|
+
reject: @escaping ForgeRejectBlock
|
|
432
|
+
) {
|
|
433
|
+
withVideo(instanceId, identifier: identifier, resolve: resolve, reject: reject) {
|
|
434
|
+
$0.fireInit(params: Self.stringMap(params))
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
@objc public func videoFireStart(
|
|
439
|
+
_ instanceId: String,
|
|
440
|
+
identifier: String,
|
|
441
|
+
params: [String: Any]?,
|
|
442
|
+
forceInit: Bool,
|
|
443
|
+
resolve: @escaping ForgeResolveBlock,
|
|
444
|
+
reject: @escaping ForgeRejectBlock
|
|
445
|
+
) {
|
|
446
|
+
withVideo(instanceId, identifier: identifier, resolve: resolve, reject: reject) {
|
|
447
|
+
$0.fireStart(forceInit: forceInit, params: Self.stringMap(params))
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
@objc public func videoFireJoin(
|
|
452
|
+
_ instanceId: String,
|
|
453
|
+
identifier: String,
|
|
454
|
+
params: [String: Any]?,
|
|
455
|
+
resolve: @escaping ForgeResolveBlock,
|
|
456
|
+
reject: @escaping ForgeRejectBlock
|
|
457
|
+
) {
|
|
458
|
+
withVideo(instanceId, identifier: identifier, resolve: resolve, reject: reject) {
|
|
459
|
+
$0.fireJoin(params: Self.stringMap(params))
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
@objc public func videoFirePause(
|
|
464
|
+
_ instanceId: String,
|
|
465
|
+
identifier: String,
|
|
466
|
+
params: [String: Any]?,
|
|
467
|
+
resolve: @escaping ForgeResolveBlock,
|
|
468
|
+
reject: @escaping ForgeRejectBlock
|
|
469
|
+
) {
|
|
470
|
+
withVideo(instanceId, identifier: identifier, resolve: resolve, reject: reject) {
|
|
471
|
+
$0.firePause(params: Self.stringMap(params))
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
@objc public func videoFireResume(
|
|
476
|
+
_ instanceId: String,
|
|
477
|
+
identifier: String,
|
|
478
|
+
params: [String: Any]?,
|
|
479
|
+
resolve: @escaping ForgeResolveBlock,
|
|
480
|
+
reject: @escaping ForgeRejectBlock
|
|
481
|
+
) {
|
|
482
|
+
withVideo(instanceId, identifier: identifier, resolve: resolve, reject: reject) {
|
|
483
|
+
$0.fireResume(params: Self.stringMap(params))
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
@objc public func videoFireSeekBegin(
|
|
488
|
+
_ instanceId: String,
|
|
489
|
+
identifier: String,
|
|
490
|
+
params: [String: Any]?,
|
|
491
|
+
convertFromBuffer: Bool,
|
|
492
|
+
resolve: @escaping ForgeResolveBlock,
|
|
493
|
+
reject: @escaping ForgeRejectBlock
|
|
494
|
+
) {
|
|
495
|
+
withVideo(instanceId, identifier: identifier, resolve: resolve, reject: reject) {
|
|
496
|
+
$0.fireSeekBegin(convertFromBuffer: convertFromBuffer, params: Self.stringMap(params))
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
@objc public func videoFireSeekEnd(
|
|
501
|
+
_ instanceId: String,
|
|
502
|
+
identifier: String,
|
|
503
|
+
params: [String: Any]?,
|
|
504
|
+
resolve: @escaping ForgeResolveBlock,
|
|
505
|
+
reject: @escaping ForgeRejectBlock
|
|
506
|
+
) {
|
|
507
|
+
withVideo(instanceId, identifier: identifier, resolve: resolve, reject: reject) {
|
|
508
|
+
$0.fireSeekEnd(params: Self.stringMap(params))
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
@objc public func videoFireBufferBegin(
|
|
513
|
+
_ instanceId: String,
|
|
514
|
+
identifier: String,
|
|
515
|
+
params: [String: Any]?,
|
|
516
|
+
convertFromSeek: Bool,
|
|
517
|
+
resolve: @escaping ForgeResolveBlock,
|
|
518
|
+
reject: @escaping ForgeRejectBlock
|
|
519
|
+
) {
|
|
520
|
+
withVideo(instanceId, identifier: identifier, resolve: resolve, reject: reject) {
|
|
521
|
+
$0.fireBufferBegin(convertFromSeek: convertFromSeek, params: Self.stringMap(params))
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
@objc public func videoFireBufferEnd(
|
|
526
|
+
_ instanceId: String,
|
|
527
|
+
identifier: String,
|
|
528
|
+
params: [String: Any]?,
|
|
529
|
+
resolve: @escaping ForgeResolveBlock,
|
|
530
|
+
reject: @escaping ForgeRejectBlock
|
|
531
|
+
) {
|
|
532
|
+
withVideo(instanceId, identifier: identifier, resolve: resolve, reject: reject) {
|
|
533
|
+
$0.fireBufferEnd(params: Self.stringMap(params))
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
@objc public func videoFireStop(
|
|
538
|
+
_ instanceId: String,
|
|
539
|
+
identifier: String,
|
|
540
|
+
params: [String: Any]?,
|
|
541
|
+
resolve: @escaping ForgeResolveBlock,
|
|
542
|
+
reject: @escaping ForgeRejectBlock
|
|
543
|
+
) {
|
|
544
|
+
withVideo(instanceId, identifier: identifier, resolve: resolve, reject: reject) {
|
|
545
|
+
$0.fireStop(params: Self.stringMap(params))
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
@objc public func videoFireError(
|
|
550
|
+
_ instanceId: String,
|
|
551
|
+
identifier: String,
|
|
552
|
+
errorCode: String,
|
|
553
|
+
errorMessage: String,
|
|
554
|
+
errorMetadata: String,
|
|
555
|
+
resolve: @escaping ForgeResolveBlock,
|
|
556
|
+
reject: @escaping ForgeRejectBlock
|
|
557
|
+
) {
|
|
558
|
+
withVideo(instanceId, identifier: identifier, resolve: resolve, reject: reject) {
|
|
559
|
+
$0.fireError(
|
|
560
|
+
errorCode: Self.emptyToNil(errorCode),
|
|
561
|
+
errorMessage: Self.emptyToNil(errorMessage),
|
|
562
|
+
errorMetadata: Self.emptyToNil(errorMetadata)
|
|
563
|
+
)
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
@objc public func videoFireFatalError(
|
|
568
|
+
_ instanceId: String,
|
|
569
|
+
identifier: String,
|
|
570
|
+
errorCode: String,
|
|
571
|
+
errorMessage: String,
|
|
572
|
+
errorMetadata: String,
|
|
573
|
+
resolve: @escaping ForgeResolveBlock,
|
|
574
|
+
reject: @escaping ForgeRejectBlock
|
|
575
|
+
) {
|
|
576
|
+
withVideo(instanceId, identifier: identifier, resolve: resolve, reject: reject) {
|
|
577
|
+
$0.fireFatalError(
|
|
578
|
+
errorCode: Self.emptyToNil(errorCode),
|
|
579
|
+
errorMessage: Self.emptyToNil(errorMessage),
|
|
580
|
+
errorMetadata: Self.emptyToNil(errorMetadata)
|
|
581
|
+
)
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
@objc public func videoFireDestroy(
|
|
586
|
+
_ instanceId: String,
|
|
587
|
+
identifier: String,
|
|
588
|
+
resolve: @escaping ForgeResolveBlock,
|
|
589
|
+
reject: @escaping ForgeRejectBlock
|
|
590
|
+
) {
|
|
591
|
+
withVideo(instanceId, identifier: identifier, resolve: resolve, reject: reject) {
|
|
592
|
+
$0.fireDestroy()
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
@objc public func videoFireEvent(
|
|
597
|
+
_ instanceId: String,
|
|
598
|
+
identifier: String,
|
|
599
|
+
eventName: String,
|
|
600
|
+
dimensions: [String: Any]?,
|
|
601
|
+
values: [String: Any]?,
|
|
602
|
+
topLevelDimensions: [String: Any]?,
|
|
603
|
+
hasEndDatetime: Bool,
|
|
604
|
+
resolve: @escaping ForgeResolveBlock,
|
|
605
|
+
reject: @escaping ForgeRejectBlock
|
|
606
|
+
) {
|
|
607
|
+
withVideo(instanceId, identifier: identifier, resolve: resolve, reject: reject) {
|
|
608
|
+
$0.fireEvent(
|
|
609
|
+
eventName: eventName,
|
|
610
|
+
dimensions: Self.stringMap(dimensions),
|
|
611
|
+
values: Self.doubleMap(values),
|
|
612
|
+
topLevelDimensions: Self.stringMap(topLevelDimensions),
|
|
613
|
+
hasEndDatetime: hasEndDatetime
|
|
614
|
+
)
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
@objc public func videoFireEventEnd(
|
|
619
|
+
_ instanceId: String,
|
|
620
|
+
identifier: String,
|
|
621
|
+
eventName: String,
|
|
622
|
+
dimensions: [String: Any]?,
|
|
623
|
+
values: [String: Any]?,
|
|
624
|
+
topLevelDimensions: [String: Any]?,
|
|
625
|
+
resolve: @escaping ForgeResolveBlock,
|
|
626
|
+
reject: @escaping ForgeRejectBlock
|
|
627
|
+
) {
|
|
628
|
+
withVideo(instanceId, identifier: identifier, resolve: resolve, reject: reject) {
|
|
629
|
+
$0.fireEventEnd(
|
|
630
|
+
eventName: eventName,
|
|
631
|
+
dimensions: Self.stringMap(dimensions),
|
|
632
|
+
values: Self.doubleMap(values),
|
|
633
|
+
topLevelDimensions: Self.stringMap(topLevelDimensions)
|
|
634
|
+
)
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
@objc public func videoFireMetadataUpdate(
|
|
639
|
+
_ instanceId: String,
|
|
640
|
+
identifier: String,
|
|
641
|
+
resolve: @escaping ForgeResolveBlock,
|
|
642
|
+
reject: @escaping ForgeRejectBlock
|
|
643
|
+
) {
|
|
644
|
+
withVideo(instanceId, identifier: identifier, resolve: resolve, reject: reject) {
|
|
645
|
+
$0.fireMetadataUpdate()
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// MARK: - Ads
|
|
650
|
+
|
|
651
|
+
@objc public func createAdReporter(
|
|
652
|
+
_ instanceId: String,
|
|
653
|
+
identifier: String,
|
|
654
|
+
adapterVersion: String,
|
|
655
|
+
resolve: @escaping ForgeResolveBlock,
|
|
656
|
+
reject: @escaping ForgeRejectBlock
|
|
657
|
+
) {
|
|
658
|
+
lock.lock()
|
|
659
|
+
let forge = instances[instanceId]
|
|
660
|
+
lock.unlock()
|
|
661
|
+
guard let forge else {
|
|
662
|
+
reject(Self.errUnknownInstance, "No NPAWForge instance '\(instanceId)'")
|
|
663
|
+
return
|
|
664
|
+
}
|
|
665
|
+
guard let video = forge.getVideoAdapter(identifier: identifier) else {
|
|
666
|
+
reject(Self.errBadArgument, "No video '\(identifier)' on instance '\(instanceId)'")
|
|
667
|
+
return
|
|
668
|
+
}
|
|
669
|
+
let reporter = video.createAdReporter(adapterVersion: Self.emptyToNil(adapterVersion))
|
|
670
|
+
lock.lock()
|
|
671
|
+
let reporterId = nextReporterId
|
|
672
|
+
nextReporterId += 1
|
|
673
|
+
adReporters[reporterId] = reporter
|
|
674
|
+
lock.unlock()
|
|
675
|
+
resolve(reporterId)
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
@objc public func releaseAdReporter(
|
|
679
|
+
_ reporterId: Double,
|
|
680
|
+
resolve: @escaping ForgeResolveBlock,
|
|
681
|
+
reject: @escaping ForgeRejectBlock
|
|
682
|
+
) {
|
|
683
|
+
lock.lock()
|
|
684
|
+
let removed = adReporters.removeValue(forKey: Int(reporterId))
|
|
685
|
+
lock.unlock()
|
|
686
|
+
guard removed != nil else {
|
|
687
|
+
reject(Self.errUnknownReporter, "No ad reporter \(Int(reporterId))")
|
|
688
|
+
return
|
|
689
|
+
}
|
|
690
|
+
resolve(Self.outcomeToDictionary(ForgeOutcome.accepted))
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
@objc public func adFireManifest(
|
|
694
|
+
_ reporterId: Double,
|
|
695
|
+
params: [String: Any]?,
|
|
696
|
+
resolve: @escaping ForgeResolveBlock,
|
|
697
|
+
reject: @escaping ForgeRejectBlock
|
|
698
|
+
) {
|
|
699
|
+
withReporter(reporterId, resolve: resolve, reject: reject) {
|
|
700
|
+
$0.fireManifest(params: Self.stringMap(params))
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
@objc public func adFireBreakStart(
|
|
705
|
+
_ reporterId: Double,
|
|
706
|
+
params: [String: Any]?,
|
|
707
|
+
resolve: @escaping ForgeResolveBlock,
|
|
708
|
+
reject: @escaping ForgeRejectBlock
|
|
709
|
+
) {
|
|
710
|
+
withReporter(reporterId, resolve: resolve, reject: reject) {
|
|
711
|
+
$0.fireBreakStart(params: Self.stringMap(params))
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
@objc public func adFireAdInit(
|
|
716
|
+
_ reporterId: Double,
|
|
717
|
+
params: [String: Any]?,
|
|
718
|
+
resolve: @escaping ForgeResolveBlock,
|
|
719
|
+
reject: @escaping ForgeRejectBlock
|
|
720
|
+
) {
|
|
721
|
+
withReporter(reporterId, resolve: resolve, reject: reject) {
|
|
722
|
+
$0.fireAdInit(params: Self.stringMap(params))
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
@objc public func adFireStart(
|
|
727
|
+
_ reporterId: Double,
|
|
728
|
+
params: [String: Any]?,
|
|
729
|
+
resolve: @escaping ForgeResolveBlock,
|
|
730
|
+
reject: @escaping ForgeRejectBlock
|
|
731
|
+
) {
|
|
732
|
+
withReporter(reporterId, resolve: resolve, reject: reject) {
|
|
733
|
+
$0.fireStart(params: Self.stringMap(params))
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
@objc public func adFireJoin(
|
|
738
|
+
_ reporterId: Double,
|
|
739
|
+
params: [String: Any]?,
|
|
740
|
+
resolve: @escaping ForgeResolveBlock,
|
|
741
|
+
reject: @escaping ForgeRejectBlock
|
|
742
|
+
) {
|
|
743
|
+
withReporter(reporterId, resolve: resolve, reject: reject) {
|
|
744
|
+
$0.fireJoin(params: Self.stringMap(params))
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
@objc public func adFirePause(
|
|
749
|
+
_ reporterId: Double,
|
|
750
|
+
params: [String: Any]?,
|
|
751
|
+
resolve: @escaping ForgeResolveBlock,
|
|
752
|
+
reject: @escaping ForgeRejectBlock
|
|
753
|
+
) {
|
|
754
|
+
withReporter(reporterId, resolve: resolve, reject: reject) {
|
|
755
|
+
$0.firePause(params: Self.stringMap(params))
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
@objc public func adFireResume(
|
|
760
|
+
_ reporterId: Double,
|
|
761
|
+
params: [String: Any]?,
|
|
762
|
+
resolve: @escaping ForgeResolveBlock,
|
|
763
|
+
reject: @escaping ForgeRejectBlock
|
|
764
|
+
) {
|
|
765
|
+
withReporter(reporterId, resolve: resolve, reject: reject) {
|
|
766
|
+
$0.fireResume(params: Self.stringMap(params))
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
@objc public func adFireBufferBegin(
|
|
771
|
+
_ reporterId: Double,
|
|
772
|
+
params: [String: Any]?,
|
|
773
|
+
resolve: @escaping ForgeResolveBlock,
|
|
774
|
+
reject: @escaping ForgeRejectBlock
|
|
775
|
+
) {
|
|
776
|
+
withReporter(reporterId, resolve: resolve, reject: reject) {
|
|
777
|
+
$0.fireBufferBegin(params: Self.stringMap(params))
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
@objc public func adFireBufferEnd(
|
|
782
|
+
_ reporterId: Double,
|
|
783
|
+
params: [String: Any]?,
|
|
784
|
+
resolve: @escaping ForgeResolveBlock,
|
|
785
|
+
reject: @escaping ForgeRejectBlock
|
|
786
|
+
) {
|
|
787
|
+
withReporter(reporterId, resolve: resolve, reject: reject) {
|
|
788
|
+
$0.fireBufferEnd(params: Self.stringMap(params))
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
@objc public func adFireQuartile(
|
|
793
|
+
_ reporterId: Double,
|
|
794
|
+
quartile: Double,
|
|
795
|
+
resolve: @escaping ForgeResolveBlock,
|
|
796
|
+
reject: @escaping ForgeRejectBlock
|
|
797
|
+
) {
|
|
798
|
+
withReporter(reporterId, resolve: resolve, reject: reject) {
|
|
799
|
+
$0.fireQuartile(quartile < 0 ? nil : Int(quartile))
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
@objc public func adFireClick(
|
|
804
|
+
_ reporterId: Double,
|
|
805
|
+
params: [String: Any]?,
|
|
806
|
+
resolve: @escaping ForgeResolveBlock,
|
|
807
|
+
reject: @escaping ForgeRejectBlock
|
|
808
|
+
) {
|
|
809
|
+
withReporter(reporterId, resolve: resolve, reject: reject) {
|
|
810
|
+
$0.fireClick(params: Self.stringMap(params))
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
@objc public func adFireSkip(
|
|
815
|
+
_ reporterId: Double,
|
|
816
|
+
resolve: @escaping ForgeResolveBlock,
|
|
817
|
+
reject: @escaping ForgeRejectBlock
|
|
818
|
+
) {
|
|
819
|
+
withReporter(reporterId, resolve: resolve, reject: reject) { $0.fireSkip() }
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
@objc public func adFireStop(
|
|
823
|
+
_ reporterId: Double,
|
|
824
|
+
params: [String: Any]?,
|
|
825
|
+
resolve: @escaping ForgeResolveBlock,
|
|
826
|
+
reject: @escaping ForgeRejectBlock
|
|
827
|
+
) {
|
|
828
|
+
withReporter(reporterId, resolve: resolve, reject: reject) {
|
|
829
|
+
$0.fireStop(params: Self.stringMap(params))
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
@objc public func adFireError(
|
|
834
|
+
_ reporterId: Double,
|
|
835
|
+
errorCode: String,
|
|
836
|
+
errorMessage: String,
|
|
837
|
+
errorMetadata: String,
|
|
838
|
+
resolve: @escaping ForgeResolveBlock,
|
|
839
|
+
reject: @escaping ForgeRejectBlock
|
|
840
|
+
) {
|
|
841
|
+
withReporter(reporterId, resolve: resolve, reject: reject) {
|
|
842
|
+
$0.fireError(
|
|
843
|
+
errorCode: Self.emptyToNil(errorCode),
|
|
844
|
+
errorMessage: Self.emptyToNil(errorMessage),
|
|
845
|
+
errorMetadata: Self.emptyToNil(errorMetadata)
|
|
846
|
+
)
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
@objc public func adFireFatalError(
|
|
851
|
+
_ reporterId: Double,
|
|
852
|
+
errorCode: String,
|
|
853
|
+
errorMessage: String,
|
|
854
|
+
errorMetadata: String,
|
|
855
|
+
resolve: @escaping ForgeResolveBlock,
|
|
856
|
+
reject: @escaping ForgeRejectBlock
|
|
857
|
+
) {
|
|
858
|
+
withReporter(reporterId, resolve: resolve, reject: reject) {
|
|
859
|
+
$0.fireFatalError(
|
|
860
|
+
errorCode: Self.emptyToNil(errorCode),
|
|
861
|
+
errorMessage: Self.emptyToNil(errorMessage),
|
|
862
|
+
errorMetadata: Self.emptyToNil(errorMetadata)
|
|
863
|
+
)
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
@objc public func adFireBreakStop(
|
|
868
|
+
_ reporterId: Double,
|
|
869
|
+
params: [String: Any]?,
|
|
870
|
+
resolve: @escaping ForgeResolveBlock,
|
|
871
|
+
reject: @escaping ForgeRejectBlock
|
|
872
|
+
) {
|
|
873
|
+
withReporter(reporterId, resolve: resolve, reject: reject) {
|
|
874
|
+
$0.fireBreakStop(params: Self.stringMap(params))
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
@objc public func adFireDestroy(
|
|
879
|
+
_ reporterId: Double,
|
|
880
|
+
resolve: @escaping ForgeResolveBlock,
|
|
881
|
+
reject: @escaping ForgeRejectBlock
|
|
882
|
+
) {
|
|
883
|
+
withReporter(reporterId, resolve: resolve, reject: reject) { $0.fireDestroy() }
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
// MARK: - Event feed
|
|
887
|
+
|
|
888
|
+
@objc public func addListener() {
|
|
889
|
+
lock.lock()
|
|
890
|
+
listenerCount += 1
|
|
891
|
+
let shouldSubscribe = listenerCount == 1
|
|
892
|
+
let live = shouldSubscribe ? instances : [:]
|
|
893
|
+
lock.unlock()
|
|
894
|
+
|
|
895
|
+
if shouldSubscribe {
|
|
896
|
+
live.forEach { subscribeInstanceEvents($0.key, forge: $0.value) }
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
@objc public func removeListeners(_ count: Int) {
|
|
901
|
+
lock.lock()
|
|
902
|
+
listenerCount = max(0, listenerCount - count)
|
|
903
|
+
let shouldCancel = listenerCount == 0
|
|
904
|
+
let cancelled = shouldCancel ? Array(subscriptions.values) : []
|
|
905
|
+
if shouldCancel { subscriptions.removeAll() }
|
|
906
|
+
lock.unlock()
|
|
907
|
+
|
|
908
|
+
cancelled.forEach { $0.cancel() }
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
@objc public func invalidate() {
|
|
912
|
+
lock.lock()
|
|
913
|
+
let cancelled = Array(subscriptions.values)
|
|
914
|
+
let live = Array(instances.values)
|
|
915
|
+
subscriptions.removeAll()
|
|
916
|
+
instances.removeAll()
|
|
917
|
+
adReporters.removeAll()
|
|
918
|
+
listenerCount = 0
|
|
919
|
+
lock.unlock()
|
|
920
|
+
|
|
921
|
+
cancelled.forEach { $0.cancel() }
|
|
922
|
+
live.forEach { _ = $0.destroy() }
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// MARK: - Internals
|
|
926
|
+
|
|
927
|
+
private func withInstance(
|
|
928
|
+
_ instanceId: String,
|
|
929
|
+
resolve: ForgeResolveBlock,
|
|
930
|
+
reject: ForgeRejectBlock,
|
|
931
|
+
block: (NPAWForge) -> ForgeOutcome
|
|
932
|
+
) {
|
|
933
|
+
lock.lock()
|
|
934
|
+
let forge = instances[instanceId]
|
|
935
|
+
lock.unlock()
|
|
936
|
+
guard let forge else {
|
|
937
|
+
reject(Self.errUnknownInstance, "No NPAWForge instance '\(instanceId)'")
|
|
938
|
+
return
|
|
939
|
+
}
|
|
940
|
+
resolve(Self.outcomeToDictionary(block(forge)))
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
private func withVideo(
|
|
944
|
+
_ instanceId: String,
|
|
945
|
+
identifier: String,
|
|
946
|
+
resolve: ForgeResolveBlock,
|
|
947
|
+
reject: ForgeRejectBlock,
|
|
948
|
+
block: (VideoAdapter) -> ForgeOutcome
|
|
949
|
+
) {
|
|
950
|
+
lock.lock()
|
|
951
|
+
let forge = instances[instanceId]
|
|
952
|
+
lock.unlock()
|
|
953
|
+
guard let forge else {
|
|
954
|
+
reject(Self.errUnknownInstance, "No NPAWForge instance '\(instanceId)'")
|
|
955
|
+
return
|
|
956
|
+
}
|
|
957
|
+
guard let video = forge.getVideoAdapter(identifier: identifier) else {
|
|
958
|
+
// The video may have been legitimately destroyed; report facade
|
|
959
|
+
// state semantics instead of a programmer error.
|
|
960
|
+
resolve(Self.outcomeToDictionary(ForgeOutcome.ignoredInvalidState))
|
|
961
|
+
return
|
|
962
|
+
}
|
|
963
|
+
resolve(Self.outcomeToDictionary(block(video)))
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
private func withReporter(
|
|
967
|
+
_ reporterId: Double,
|
|
968
|
+
resolve: ForgeResolveBlock,
|
|
969
|
+
reject: ForgeRejectBlock,
|
|
970
|
+
block: (ManualAdReporter) -> ForgeOutcome
|
|
971
|
+
) {
|
|
972
|
+
lock.lock()
|
|
973
|
+
let reporter = adReporters[Int(reporterId)]
|
|
974
|
+
lock.unlock()
|
|
975
|
+
guard let reporter else {
|
|
976
|
+
reject(Self.errUnknownReporter, "No ad reporter \(Int(reporterId))")
|
|
977
|
+
return
|
|
978
|
+
}
|
|
979
|
+
resolve(Self.outcomeToDictionary(block(reporter)))
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
private func subscribeInstanceEvents(_ instanceId: String, forge: NPAWForge) {
|
|
983
|
+
lock.lock()
|
|
984
|
+
if subscriptions[instanceId] != nil {
|
|
985
|
+
lock.unlock()
|
|
986
|
+
return
|
|
987
|
+
}
|
|
988
|
+
lock.unlock()
|
|
989
|
+
|
|
990
|
+
let subscription = forge.subscribeEvents { [weak self] event, _ in
|
|
991
|
+
guard let self, let sink = self.eventSink else { return }
|
|
992
|
+
sink(Self.recordedEventToDictionary(instanceId: instanceId, event: event))
|
|
993
|
+
}
|
|
994
|
+
lock.lock()
|
|
995
|
+
subscriptions[instanceId] = subscription
|
|
996
|
+
lock.unlock()
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
private func applyConfig(_ builder: NPAWForge.Builder, config: [String: Any]?) {
|
|
1000
|
+
guard let config else { return }
|
|
1001
|
+
for (key, value) in config {
|
|
1002
|
+
switch key {
|
|
1003
|
+
case "appName":
|
|
1004
|
+
builder.setAppName(value as? String)
|
|
1005
|
+
case "appReleaseVersion":
|
|
1006
|
+
builder.setAppReleaseVersion(value as? String)
|
|
1007
|
+
case "buildVariant":
|
|
1008
|
+
builder.setBuildVariant(value as? String)
|
|
1009
|
+
case "lmaEndpoint":
|
|
1010
|
+
builder.setLmaEndpoint(value as? String)
|
|
1011
|
+
case "analyticsUserAgent":
|
|
1012
|
+
builder.setAnalyticsUserAgent(value as? String)
|
|
1013
|
+
case "logLevel":
|
|
1014
|
+
builder.setLogLevel(value)
|
|
1015
|
+
case "startSessionOnInitialize":
|
|
1016
|
+
builder.setStartSessionOnInitialize(value as? Bool ?? false)
|
|
1017
|
+
case "processEventsBeforeSessionStart":
|
|
1018
|
+
builder.setProcessEventsBeforeSessionStart(value as? Bool ?? false)
|
|
1019
|
+
case "preSessionEventQueueLimitEvents":
|
|
1020
|
+
builder.setPreSessionEventQueueLimitEvents((value as? NSNumber)?.intValue ?? 100)
|
|
1021
|
+
case "preSessionEventQueueMaxAgeMs":
|
|
1022
|
+
builder.setPreSessionEventQueueMaxAgeMs((value as? NSNumber)?.int64Value ?? 0)
|
|
1023
|
+
case "allowLocalHttpForTesting":
|
|
1024
|
+
builder.setLocalPlaintextTransportForTesting(value as? Bool ?? false)
|
|
1025
|
+
case "deviceUuid", "autoDeviceIdentityEnabled":
|
|
1026
|
+
// Android-only today: the Swift Builder lacks these setters.
|
|
1027
|
+
NSLog("[NPAWForgeRN] ForgeConfig key '%@' is Android-only; ignored on iOS", key)
|
|
1028
|
+
default:
|
|
1029
|
+
NSLog("[NPAWForgeRN] Unknown ForgeConfig key '%@' ignored (forward compatibility)", key)
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
private static func networkRecord(from map: [String: Any]) -> NetworkRequestRecord {
|
|
1035
|
+
var record = NetworkRequestRecord(
|
|
1036
|
+
targetUrl: map["targetUrl"] as? String ?? "",
|
|
1037
|
+
method: map["method"] as? String ?? "GET",
|
|
1038
|
+
responseStatusCode: (map["responseStatusCode"] as? NSNumber)?.intValue ?? 0,
|
|
1039
|
+
durationMs: int64(map["durationMs"]) ?? 0,
|
|
1040
|
+
outcome: NetworkRequestOutcome(rawValue: map["outcome"] as? String ?? "unknown") ?? .unknown
|
|
1041
|
+
)
|
|
1042
|
+
record.responseStatusText = map["responseStatusText"] as? String
|
|
1043
|
+
record.errorMessage = map["errorMessage"] as? String
|
|
1044
|
+
record.traceparent = map["traceparent"] as? String
|
|
1045
|
+
record.dnsDurationMs = int64(map["dnsDurationMs"])
|
|
1046
|
+
record.connectDurationMs = int64(map["connectDurationMs"])
|
|
1047
|
+
record.sslDurationMs = int64(map["sslDurationMs"])
|
|
1048
|
+
record.requestDurationMs = int64(map["requestDurationMs"])
|
|
1049
|
+
record.timeToFirstByteMs = int64(map["timeToFirstByteMs"])
|
|
1050
|
+
record.responseDownloadDurationMs = int64(map["responseDownloadDurationMs"])
|
|
1051
|
+
record.redirectDurationMs = int64(map["redirectDurationMs"])
|
|
1052
|
+
record.redirectCount = int64(map["redirectCount"])
|
|
1053
|
+
record.networkProtocol = map["protocol"] as? String
|
|
1054
|
+
record.requestBytes = int64(map["requestBytes"])
|
|
1055
|
+
record.responseBytes = int64(map["responseBytes"])
|
|
1056
|
+
return record
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
private static func int64(_ value: Any?) -> Int64? {
|
|
1060
|
+
(value as? NSNumber)?.int64Value
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
private static func emptyToNil(_ value: String) -> String? {
|
|
1064
|
+
value.isEmpty ? nil : value
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
private static func stringMap(_ map: [String: Any]?) -> [String: String] {
|
|
1068
|
+
guard let map else { return [:] }
|
|
1069
|
+
var result: [String: String] = [:]
|
|
1070
|
+
for (key, value) in map {
|
|
1071
|
+
switch value {
|
|
1072
|
+
case let string as String:
|
|
1073
|
+
result[key] = string
|
|
1074
|
+
case let number as NSNumber:
|
|
1075
|
+
if number === kCFBooleanTrue || number === kCFBooleanFalse {
|
|
1076
|
+
result[key] = number.boolValue ? "true" : "false"
|
|
1077
|
+
} else if number.doubleValue == number.doubleValue.rounded()
|
|
1078
|
+
&& abs(number.doubleValue) < 9_007_199_254_740_992 {
|
|
1079
|
+
result[key] = String(number.int64Value)
|
|
1080
|
+
} else {
|
|
1081
|
+
result[key] = number.stringValue
|
|
1082
|
+
}
|
|
1083
|
+
default:
|
|
1084
|
+
result[key] = String(describing: value)
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
return result
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
private static func doubleMap(_ map: [String: Any]?) -> [String: Double] {
|
|
1091
|
+
guard let map else { return [:] }
|
|
1092
|
+
var result: [String: Double] = [:]
|
|
1093
|
+
for (key, value) in map {
|
|
1094
|
+
if let number = value as? NSNumber {
|
|
1095
|
+
result[key] = number.doubleValue
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
return result
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
static func outcomeToDictionary(_ outcome: ForgeOutcome) -> [String: Any] {
|
|
1102
|
+
var result: [String: Any] = [
|
|
1103
|
+
"code": outcome.code.wireName,
|
|
1104
|
+
"isAccepted": outcome.isAccepted,
|
|
1105
|
+
]
|
|
1106
|
+
if let reason = outcome.reason {
|
|
1107
|
+
result["reason"] = reason
|
|
1108
|
+
}
|
|
1109
|
+
if !outcome.details.isEmpty {
|
|
1110
|
+
result["details"] = outcome.details
|
|
1111
|
+
}
|
|
1112
|
+
return result
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
static func recordedEventToDictionary(
|
|
1116
|
+
instanceId: String,
|
|
1117
|
+
event: ForgeRecordedEvent
|
|
1118
|
+
) -> [String: Any] {
|
|
1119
|
+
var result: [String: Any] = [
|
|
1120
|
+
"instanceId": instanceId,
|
|
1121
|
+
"eventName": event.eventName,
|
|
1122
|
+
"timestampMs": Double(event.timestampMs),
|
|
1123
|
+
"payload": event.payload,
|
|
1124
|
+
"urgent": event.urgent,
|
|
1125
|
+
]
|
|
1126
|
+
if let viewKey = event.viewKey {
|
|
1127
|
+
result["viewKey"] = viewKey
|
|
1128
|
+
}
|
|
1129
|
+
return result
|
|
1130
|
+
}
|
|
1131
|
+
}
|