@apex-inc/capacitor-plugin 2.5.1 → 2.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/ApexIncCapacitorPlugin.podspec +1 -0
- package/README.md +52 -1
- package/android/src/main/AndroidManifest.xml +5 -0
- package/android/src/main/java/inc/apex/capacitor/ApexCapacitorPlugin.kt +200 -18
- package/android/src/main/java/inc/apex/capacitor/FirstLaunchInstall.kt +28 -0
- package/android/src/main/java/inc/apex/capacitor/MetaInstallReferrer.kt +75 -0
- package/android/src/main/java/inc/apex/capacitor/MobileContext.kt +43 -0
- package/dist/definitions.d.ts +31 -5
- package/dist/definitions.d.ts.map +1 -1
- package/dist/esm/definitions.d.ts +31 -5
- package/dist/esm/definitions.d.ts.map +1 -1
- package/dist/esm/web.d.ts.map +1 -1
- package/dist/esm/web.js +10 -9
- package/dist/esm/web.js.map +1 -1
- package/dist/web.d.ts.map +1 -1
- package/dist/web.js +10 -9
- package/dist/web.js.map +1 -1
- package/ios/Sources/ApexCapacitorPlugin/FirstLaunchInstall.swift +50 -0
- package/ios/Sources/ApexCapacitorPlugin/MobileContext.swift +49 -0
- package/ios/Sources/ApexCapacitorPluginBridge/ApexCapacitorPlugin.swift +86 -8
- package/ios/Sources/PrivacyInfo.xcprivacy +72 -0
- package/ios/Tests/ApexCapacitorPluginTests/FirstLaunchInstallTests.swift +66 -0
- package/ios/Tests/ApexCapacitorPluginTests/MobileContextTests.swift +56 -0
- package/package.json +1 -1
|
@@ -55,6 +55,7 @@ public class ApexCapacitorPlugin: CAPPlugin, CAPBridgedPlugin, UNUserNotificatio
|
|
|
55
55
|
private var attManager = AttManager()
|
|
56
56
|
private var skanManager = SkanManager()
|
|
57
57
|
private var deviceInfo = DeviceInfo()
|
|
58
|
+
private lazy var deviceMeta: DeviceMetadata = deviceInfo.collect()
|
|
58
59
|
private var deepLinks = DeepLinkManager()
|
|
59
60
|
private var adIds = AdvertisingIdProvider()
|
|
60
61
|
private lazy var pushManager = PushNotificationManager()
|
|
@@ -101,6 +102,9 @@ public class ApexCapacitorPlugin: CAPPlugin, CAPBridgedPlugin, UNUserNotificatio
|
|
|
101
102
|
// Phase 4a — consent gate. Defaults granted; set false by
|
|
102
103
|
// initialize({ requireConsent: true }) until setConsent({ granted:true }).
|
|
103
104
|
private var consentGranted = true
|
|
105
|
+
// The install is sent once per install, by the plugin or the app, never both.
|
|
106
|
+
private var autoTrackInstall = true
|
|
107
|
+
private let installLock = NSLock()
|
|
104
108
|
// Phase 3 — per-launch dedup so each (experiment, variant) exposure
|
|
105
109
|
// fires once, matching the JS/web fallback.
|
|
106
110
|
private var firedExposures = Set<String>()
|
|
@@ -216,6 +220,7 @@ public class ApexCapacitorPlugin: CAPPlugin, CAPBridgedPlugin, UNUserNotificatio
|
|
|
216
220
|
apiKey = rawKey.isEmpty ? nil : rawKey
|
|
217
221
|
// Phase 4a — consent gate.
|
|
218
222
|
consentGranted = !(call.getBool("requireConsent") ?? false)
|
|
223
|
+
autoTrackInstall = call.getBool("autoTrackInstall") ?? true
|
|
219
224
|
|
|
220
225
|
// MMP-061 — release channel detection. iOS uses the receipt URL
|
|
221
226
|
// + StoreKit 2 AppTransaction. Any JS-supplied override on iOS
|
|
@@ -271,10 +276,62 @@ public class ApexCapacitorPlugin: CAPPlugin, CAPBridgedPlugin, UNUserNotificatio
|
|
|
271
276
|
self?.handleTokenArrived(token)
|
|
272
277
|
}
|
|
273
278
|
pushManager.silentRegisterIfAuthorized()
|
|
279
|
+
sendInstallIfFirstLaunch()
|
|
274
280
|
|
|
275
281
|
call.resolve()
|
|
276
282
|
}
|
|
277
283
|
|
|
284
|
+
/// Send `app_install` on the first launch of a fresh install. Marks the
|
|
285
|
+
/// install as handled for someone who had the app before Apex, so the
|
|
286
|
+
/// update that adds Apex reports no one as new.
|
|
287
|
+
private func sendInstallIfFirstLaunch() {
|
|
288
|
+
guard autoTrackInstall, consentGranted, let queue = offlineQueue else { return }
|
|
289
|
+
let defaults = UserDefaults.standard
|
|
290
|
+
installLock.lock()
|
|
291
|
+
let decision = FirstLaunchInstall.decide(
|
|
292
|
+
alreadySent: defaults.bool(forKey: FirstLaunchInstall.defaultsKey),
|
|
293
|
+
installedAt: FirstLaunchInstall.installedAt(),
|
|
294
|
+
versionInstalledAt: FirstLaunchInstall.versionInstalledAt(),
|
|
295
|
+
now: Date()
|
|
296
|
+
)
|
|
297
|
+
if decision != .alreadySent { defaults.set(true, forKey: FirstLaunchInstall.defaultsKey) }
|
|
298
|
+
installLock.unlock()
|
|
299
|
+
if debug { print("[apex-capacitor] first-launch install: \(decision)") }
|
|
300
|
+
guard decision == .send else { return }
|
|
301
|
+
|
|
302
|
+
var payload: [String: AnyCodable] = [:]
|
|
303
|
+
payload["type"] = AnyCodable("app_install")
|
|
304
|
+
payload["data"] = AnyCodable(["source": "apex_first_launch"])
|
|
305
|
+
if testMode { payload["testMode"] = AnyCodable(true) }
|
|
306
|
+
payload["platform"] = AnyCodable("ios")
|
|
307
|
+
payload["timestamp"] = AnyCodable(ISO8601DateFormatter().string(from: Date()))
|
|
308
|
+
payload["visitorId"] = AnyCodable(visitorId)
|
|
309
|
+
var mobileSlot: [String: Any] = [
|
|
310
|
+
"releaseChannel": releaseChannel,
|
|
311
|
+
"releaseChannelSource": releaseChannelSource,
|
|
312
|
+
]
|
|
313
|
+
stampDevice(&mobileSlot)
|
|
314
|
+
payload["mobile"] = AnyCodable(mobileSlot)
|
|
315
|
+
let event = NativeQueuedEvent(
|
|
316
|
+
id: UUID().uuidString.lowercased(),
|
|
317
|
+
payload: payload,
|
|
318
|
+
attempts: 0,
|
|
319
|
+
enqueuedAt: Date()
|
|
320
|
+
)
|
|
321
|
+
try? queue.enqueue(event)
|
|
322
|
+
kickFlush(reason: "track")
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/// True the first time an install is claimed; the app's own `app_install` after that is dropped.
|
|
326
|
+
private func claimInstall() -> Bool {
|
|
327
|
+
installLock.lock()
|
|
328
|
+
defer { installLock.unlock() }
|
|
329
|
+
let defaults = UserDefaults.standard
|
|
330
|
+
if defaults.bool(forKey: FirstLaunchInstall.defaultsKey) { return false }
|
|
331
|
+
defaults.set(true, forKey: FirstLaunchInstall.defaultsKey)
|
|
332
|
+
return true
|
|
333
|
+
}
|
|
334
|
+
|
|
278
335
|
/// Forward a freshly-arrived APNs device token to JS listeners and
|
|
279
336
|
/// register it with the Apex backend. Shared by the explicit
|
|
280
337
|
/// `registerForPushNotifications()` path and the silent re-register
|
|
@@ -552,12 +609,6 @@ public class ApexCapacitorPlugin: CAPPlugin, CAPBridgedPlugin, UNUserNotificatio
|
|
|
552
609
|
/// guarantees than firing a track event, and merchants tend to
|
|
553
610
|
/// wire identify behind a one-shot boundary.
|
|
554
611
|
@objc public func identify(_ call: CAPPluginCall) {
|
|
555
|
-
// Phase 4a — identify sends PII; collect nothing until consent given.
|
|
556
|
-
if !consentGranted {
|
|
557
|
-
if debug { print("[apex-capacitor] consent not granted; skipping identify") }
|
|
558
|
-
call.resolve()
|
|
559
|
-
return
|
|
560
|
-
}
|
|
561
612
|
guard let email = call.getString("email")?.trimmingCharacters(in: .whitespaces),
|
|
562
613
|
!email.isEmpty else {
|
|
563
614
|
call.reject("email is required")
|
|
@@ -571,11 +622,19 @@ public class ApexCapacitorPlugin: CAPPlugin, CAPBridgedPlugin, UNUserNotificatio
|
|
|
571
622
|
return
|
|
572
623
|
}
|
|
573
624
|
var body: [String: Any] = [
|
|
574
|
-
"visitorId": visitorId,
|
|
575
625
|
"email": email,
|
|
576
626
|
"workspaceKey": workspaceKey,
|
|
577
627
|
"timestamp": ISO8601DateFormatter().string(from: Date()),
|
|
578
628
|
]
|
|
629
|
+
// Signing up is its own reason to know the person. Without consent
|
|
630
|
+
// only the identify goes: no device id, and Apex records advertising
|
|
631
|
+
// as denied so no ad platform is sent them.
|
|
632
|
+
if consentGranted {
|
|
633
|
+
body["visitorId"] = visitorId
|
|
634
|
+
} else {
|
|
635
|
+
if debug { print("[apex-capacitor] consent not granted; identifying without a device id") }
|
|
636
|
+
body["noBrowserConsent"] = true
|
|
637
|
+
}
|
|
579
638
|
if let userId = userId, !userId.isEmpty {
|
|
580
639
|
body["userId"] = userId
|
|
581
640
|
}
|
|
@@ -930,10 +989,11 @@ public class ApexCapacitorPlugin: CAPPlugin, CAPBridgedPlugin, UNUserNotificatio
|
|
|
930
989
|
payload["platform"] = AnyCodable("ios")
|
|
931
990
|
payload["timestamp"] = AnyCodable(ISO8601DateFormatter().string(from: Date()))
|
|
932
991
|
payload["visitorId"] = AnyCodable(visitorId)
|
|
933
|
-
|
|
992
|
+
var mobileSlot: [String: Any] = [
|
|
934
993
|
"releaseChannel": releaseChannel,
|
|
935
994
|
"releaseChannelSource": releaseChannelSource,
|
|
936
995
|
]
|
|
996
|
+
stampDevice(&mobileSlot)
|
|
937
997
|
payload["mobile"] = AnyCodable(mobileSlot)
|
|
938
998
|
let event = NativeQueuedEvent(
|
|
939
999
|
id: UUID().uuidString.lowercased(),
|
|
@@ -947,6 +1007,17 @@ public class ApexCapacitorPlugin: CAPPlugin, CAPBridgedPlugin, UNUserNotificatio
|
|
|
947
1007
|
|
|
948
1008
|
// MARK: - Events & Queue
|
|
949
1009
|
|
|
1010
|
+
/// The device, the tracking answer, and the IDFA when the person allowed
|
|
1011
|
+
/// it. Read per event: the answer can change in Settings mid-session.
|
|
1012
|
+
private func stampDevice(_ slot: inout [String: Any]) {
|
|
1013
|
+
MobileContext.fill(
|
|
1014
|
+
&slot,
|
|
1015
|
+
device: deviceMeta,
|
|
1016
|
+
att: attManager.currentStatus(),
|
|
1017
|
+
adId: adIds.current()
|
|
1018
|
+
)
|
|
1019
|
+
}
|
|
1020
|
+
|
|
950
1021
|
@objc public func track(_ call: CAPPluginCall) {
|
|
951
1022
|
guard let queue = offlineQueue else {
|
|
952
1023
|
call.reject("Plugin not initialized — call initialize() first")
|
|
@@ -958,6 +1029,11 @@ public class ApexCapacitorPlugin: CAPPlugin, CAPBridgedPlugin, UNUserNotificatio
|
|
|
958
1029
|
call.resolve()
|
|
959
1030
|
return
|
|
960
1031
|
}
|
|
1032
|
+
if call.getString("type") == "app_install" && !claimInstall() {
|
|
1033
|
+
if debug { print("[apex-capacitor] app_install already sent for this install; dropping") }
|
|
1034
|
+
call.resolve()
|
|
1035
|
+
return
|
|
1036
|
+
}
|
|
961
1037
|
|
|
962
1038
|
let id = call.getString("id") ?? UUID().uuidString.lowercased()
|
|
963
1039
|
let payloadDict = call.options.compactMapValues { $0 }
|
|
@@ -1004,6 +1080,7 @@ public class ApexCapacitorPlugin: CAPPlugin, CAPBridgedPlugin, UNUserNotificatio
|
|
|
1004
1080
|
}
|
|
1005
1081
|
mobileSlot["releaseChannel"] = releaseChannel
|
|
1006
1082
|
mobileSlot["releaseChannelSource"] = releaseChannelSource
|
|
1083
|
+
stampDevice(&mobileSlot)
|
|
1007
1084
|
codablePayload["mobile"] = AnyCodable(mobileSlot)
|
|
1008
1085
|
let event = NativeQueuedEvent(
|
|
1009
1086
|
id: id,
|
|
@@ -1066,6 +1143,7 @@ public class ApexCapacitorPlugin: CAPPlugin, CAPBridgedPlugin, UNUserNotificatio
|
|
|
1066
1143
|
if granted {
|
|
1067
1144
|
// Resume: flush anything captured before this point.
|
|
1068
1145
|
kickFlush(reason: "consent")
|
|
1146
|
+
sendInstallIfFirstLaunch()
|
|
1069
1147
|
} else {
|
|
1070
1148
|
// Revoke: drop anything queued but not yet sent.
|
|
1071
1149
|
try? offlineQueue?.clear()
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<!--
|
|
4
|
+
What @apex-inc/capacitor-plugin collects, merged by Xcode into the app's
|
|
5
|
+
privacy report. Installs and purchases count as tracking because Apex
|
|
6
|
+
sends them to the ad networks the app connects to measure ads. The IDFA
|
|
7
|
+
is read only after the person allows tracking in the ATT prompt. No
|
|
8
|
+
tracking domains are listed: iOS blocks listed domains
|
|
9
|
+
outright when tracking is denied, and Apex's own endpoint also serves
|
|
10
|
+
measurement that needs no permission.
|
|
11
|
+
-->
|
|
12
|
+
<plist version="1.0">
|
|
13
|
+
<dict>
|
|
14
|
+
<key>NSPrivacyTracking</key>
|
|
15
|
+
<true/>
|
|
16
|
+
<key>NSPrivacyTrackingDomains</key>
|
|
17
|
+
<array/>
|
|
18
|
+
<key>NSPrivacyCollectedDataTypes</key>
|
|
19
|
+
<array>
|
|
20
|
+
<dict>
|
|
21
|
+
<key>NSPrivacyCollectedDataType</key>
|
|
22
|
+
<string>NSPrivacyCollectedDataTypeDeviceID</string>
|
|
23
|
+
<key>NSPrivacyCollectedDataTypeLinked</key>
|
|
24
|
+
<true/>
|
|
25
|
+
<key>NSPrivacyCollectedDataTypeTracking</key>
|
|
26
|
+
<true/>
|
|
27
|
+
<key>NSPrivacyCollectedDataTypePurposes</key>
|
|
28
|
+
<array>
|
|
29
|
+
<string>NSPrivacyCollectedDataTypePurposeThirdPartyAdvertising</string>
|
|
30
|
+
<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
|
|
31
|
+
</array>
|
|
32
|
+
</dict>
|
|
33
|
+
<dict>
|
|
34
|
+
<key>NSPrivacyCollectedDataType</key>
|
|
35
|
+
<string>NSPrivacyCollectedDataTypeProductInteraction</string>
|
|
36
|
+
<key>NSPrivacyCollectedDataTypeLinked</key>
|
|
37
|
+
<true/>
|
|
38
|
+
<key>NSPrivacyCollectedDataTypeTracking</key>
|
|
39
|
+
<true/>
|
|
40
|
+
<key>NSPrivacyCollectedDataTypePurposes</key>
|
|
41
|
+
<array>
|
|
42
|
+
<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
|
|
43
|
+
<string>NSPrivacyCollectedDataTypePurposeThirdPartyAdvertising</string>
|
|
44
|
+
</array>
|
|
45
|
+
</dict>
|
|
46
|
+
<dict>
|
|
47
|
+
<key>NSPrivacyCollectedDataType</key>
|
|
48
|
+
<string>NSPrivacyCollectedDataTypePurchaseHistory</string>
|
|
49
|
+
<key>NSPrivacyCollectedDataTypeLinked</key>
|
|
50
|
+
<true/>
|
|
51
|
+
<key>NSPrivacyCollectedDataTypeTracking</key>
|
|
52
|
+
<true/>
|
|
53
|
+
<key>NSPrivacyCollectedDataTypePurposes</key>
|
|
54
|
+
<array>
|
|
55
|
+
<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
|
|
56
|
+
<string>NSPrivacyCollectedDataTypePurposeThirdPartyAdvertising</string>
|
|
57
|
+
</array>
|
|
58
|
+
</dict>
|
|
59
|
+
</array>
|
|
60
|
+
<key>NSPrivacyAccessedAPITypes</key>
|
|
61
|
+
<array>
|
|
62
|
+
<dict>
|
|
63
|
+
<key>NSPrivacyAccessedAPIType</key>
|
|
64
|
+
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
|
65
|
+
<key>NSPrivacyAccessedAPITypeReasons</key>
|
|
66
|
+
<array>
|
|
67
|
+
<string>CA92.1</string>
|
|
68
|
+
</array>
|
|
69
|
+
</dict>
|
|
70
|
+
</array>
|
|
71
|
+
</dict>
|
|
72
|
+
</plist>
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
//
|
|
2
|
+
// FirstLaunchInstallTests.swift
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import XCTest
|
|
6
|
+
@testable import ApexCapacitorPlugin
|
|
7
|
+
|
|
8
|
+
final class FirstLaunchInstallTests: XCTestCase {
|
|
9
|
+
|
|
10
|
+
private let now = Date(timeIntervalSince1970: 1_790_000_000)
|
|
11
|
+
private let day: TimeInterval = 24 * 3600
|
|
12
|
+
|
|
13
|
+
func testSendsOnTheFirstLaunchOfAFreshInstall() {
|
|
14
|
+
let installed = now.addingTimeInterval(-60)
|
|
15
|
+
XCTAssertEqual(
|
|
16
|
+
FirstLaunchInstall.decide(alreadySent: false, installedAt: installed, versionInstalledAt: installed, now: now),
|
|
17
|
+
.send
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
func testSendsOnceOnly() {
|
|
22
|
+
XCTAssertEqual(
|
|
23
|
+
FirstLaunchInstall.decide(alreadySent: true, installedAt: now, versionInstalledAt: now, now: now),
|
|
24
|
+
.alreadySent
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
func testAnUpdateThatAddsApexIsNotAnInstall() {
|
|
29
|
+
XCTAssertEqual(
|
|
30
|
+
FirstLaunchInstall.decide(
|
|
31
|
+
alreadySent: false,
|
|
32
|
+
installedAt: now.addingTimeInterval(-90 * day),
|
|
33
|
+
versionInstalledAt: now.addingTimeInterval(-day),
|
|
34
|
+
now: now
|
|
35
|
+
),
|
|
36
|
+
.existingUser
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
func testARecentInstallStillCountsAfterAQuickUpdate() {
|
|
41
|
+
XCTAssertEqual(
|
|
42
|
+
FirstLaunchInstall.decide(
|
|
43
|
+
alreadySent: false,
|
|
44
|
+
installedAt: now.addingTimeInterval(-3 * day),
|
|
45
|
+
versionInstalledAt: now.addingTimeInterval(-day),
|
|
46
|
+
now: now
|
|
47
|
+
),
|
|
48
|
+
.send
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
func testALateFirstOpenOfANeverUpdatedInstallCounts() {
|
|
53
|
+
let installed = now.addingTimeInterval(-20 * day)
|
|
54
|
+
XCTAssertEqual(
|
|
55
|
+
FirstLaunchInstall.decide(alreadySent: false, installedAt: installed, versionInstalledAt: installed, now: now),
|
|
56
|
+
.send
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
func testSendsWhenTheInstallTimeIsUnknown() {
|
|
61
|
+
XCTAssertEqual(
|
|
62
|
+
FirstLaunchInstall.decide(alreadySent: false, installedAt: nil, versionInstalledAt: nil, now: now),
|
|
63
|
+
.send
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
//
|
|
2
|
+
// MobileContextTests.swift
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import XCTest
|
|
6
|
+
@testable import ApexCapacitorPlugin
|
|
7
|
+
|
|
8
|
+
final class MobileContextTests: XCTestCase {
|
|
9
|
+
|
|
10
|
+
private let device = DeviceMetadata(
|
|
11
|
+
platform: "ios",
|
|
12
|
+
osVersion: "18.1",
|
|
13
|
+
model: "iPhone16,1",
|
|
14
|
+
appVersion: "3.1.0",
|
|
15
|
+
bundleId: "com.skillshot.app",
|
|
16
|
+
timezone: "America/Chicago",
|
|
17
|
+
locale: "en-US"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
func testStampsTheDeviceAndTheIdfaWhenTrackingIsAllowed() {
|
|
21
|
+
let fields = MobileContext.fields(
|
|
22
|
+
device: device,
|
|
23
|
+
att: .authorized,
|
|
24
|
+
adId: AdvertisingIdResult(id: "6d92078a-8246-4ba4-ae5b-76104861e7dc", fallback: nil)
|
|
25
|
+
)
|
|
26
|
+
XCTAssertEqual(fields["bundleId"] as? String, "com.skillshot.app")
|
|
27
|
+
XCTAssertEqual(fields["deviceModel"] as? String, "iPhone16,1")
|
|
28
|
+
XCTAssertEqual(fields["attStatus"] as? String, "authorized")
|
|
29
|
+
XCTAssertEqual(fields["idfa"] as? String, "6d92078a-8246-4ba4-ae5b-76104861e7dc")
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
func testNeverSendsTheIdfvAsTheAdvertisingId() {
|
|
33
|
+
let fields = MobileContext.fields(
|
|
34
|
+
device: device,
|
|
35
|
+
att: .denied,
|
|
36
|
+
adId: AdvertisingIdResult(id: "idfv-value", fallback: "idfv")
|
|
37
|
+
)
|
|
38
|
+
XCTAssertNil(fields["idfa"])
|
|
39
|
+
XCTAssertNil(fields["idfv"])
|
|
40
|
+
XCTAssertEqual(fields["attStatus"] as? String, "denied")
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
func testKeepsWhatTheAppSet() {
|
|
44
|
+
var slot: [String: Any] = ["releaseChannel": "app-store", "locale": "fr-FR"]
|
|
45
|
+
MobileContext.fill(
|
|
46
|
+
&slot,
|
|
47
|
+
device: device,
|
|
48
|
+
att: .notDetermined,
|
|
49
|
+
adId: AdvertisingIdResult(id: nil, fallback: nil)
|
|
50
|
+
)
|
|
51
|
+
XCTAssertEqual(slot["locale"] as? String, "fr-FR")
|
|
52
|
+
XCTAssertEqual(slot["releaseChannel"] as? String, "app-store")
|
|
53
|
+
XCTAssertEqual(slot["appVersion"] as? String, "3.1.0")
|
|
54
|
+
XCTAssertEqual(slot["attStatus"] as? String, "not-determined")
|
|
55
|
+
}
|
|
56
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apex-inc/capacitor-plugin",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.1",
|
|
4
4
|
"description": "Apex Capacitor plugin — iOS/Android attribution, events, deep linking, SKAN, and offline-tolerant tracking for Capacitor apps.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|