@apex-inc/capacitor-plugin 2.4.1 → 2.5.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/README.md +41 -4
- package/android/src/main/java/inc/apex/capacitor/ApexCapacitorPlugin.kt +80 -5
- package/android/src/main/java/inc/apex/capacitor/ApexEvents.kt +11 -4
- package/android/src/main/java/inc/apex/capacitor/DeepLinkManager.kt +27 -0
- package/dist/deferred-link.d.ts +73 -0
- package/dist/deferred-link.d.ts.map +1 -0
- package/dist/deferred-link.js +71 -0
- package/dist/deferred-link.js.map +1 -0
- package/dist/definitions.d.ts +76 -4
- package/dist/definitions.d.ts.map +1 -1
- package/dist/esm/deferred-link.d.ts +73 -0
- package/dist/esm/deferred-link.d.ts.map +1 -0
- package/dist/esm/deferred-link.js +68 -0
- package/dist/esm/deferred-link.js.map +1 -0
- package/dist/esm/definitions.d.ts +76 -4
- package/dist/esm/definitions.d.ts.map +1 -1
- package/dist/esm/events.d.ts +8 -1
- package/dist/esm/events.d.ts.map +1 -1
- package/dist/esm/events.js +8 -1
- package/dist/esm/events.js.map +1 -1
- package/dist/esm/index.d.ts +9 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +157 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/referrer-link.d.ts +36 -0
- package/dist/esm/referrer-link.d.ts.map +1 -0
- package/dist/esm/referrer-link.js +103 -0
- package/dist/esm/referrer-link.js.map +1 -0
- package/dist/esm/skan-conversion-values.d.ts +175 -0
- package/dist/esm/skan-conversion-values.d.ts.map +1 -0
- package/dist/esm/skan-conversion-values.js +323 -0
- package/dist/esm/skan-conversion-values.js.map +1 -0
- package/dist/esm/web.d.ts +6 -1
- package/dist/esm/web.d.ts.map +1 -1
- package/dist/esm/web.js +26 -3
- package/dist/esm/web.js.map +1 -1
- package/dist/events.d.ts +8 -1
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +8 -1
- package/dist/events.js.map +1 -1
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +166 -1
- package/dist/index.js.map +1 -1
- package/dist/referrer-link.d.ts +36 -0
- package/dist/referrer-link.d.ts.map +1 -0
- package/dist/referrer-link.js +107 -0
- package/dist/referrer-link.js.map +1 -0
- package/dist/skan-conversion-values.d.ts +175 -0
- package/dist/skan-conversion-values.d.ts.map +1 -0
- package/dist/skan-conversion-values.js +331 -0
- package/dist/skan-conversion-values.js.map +1 -0
- package/dist/web.d.ts +6 -1
- package/dist/web.d.ts.map +1 -1
- package/dist/web.js +26 -3
- package/dist/web.js.map +1 -1
- package/ios/Sources/ApexCapacitorPlugin/ApexEvents.swift +11 -4
- package/ios/Sources/ApexCapacitorPlugin/DeepLinkManager.swift +16 -2
- package/ios/Sources/ApexCapacitorPlugin/SkanManager.swift +18 -4
- package/ios/Sources/ApexCapacitorPluginBridge/ApexCapacitorPlugin.swift +22 -6
- package/ios/Tests/ApexCapacitorPluginTests/DeepLinkManagerTests.swift +26 -0
- package/ios/Tests/ApexCapacitorPluginTests/SkanManagerTests.swift +22 -1
- package/package.json +1 -1
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
// Wraps SKAdNetwork conversion value updates.
|
|
6
6
|
//
|
|
7
7
|
// - SKAN 3.0 (iOS 11.3-14.x): `SKAdNetwork.updateConversionValue(Int)` — fine value 0-63.
|
|
8
|
-
// - SKAN 4.0 (iOS 16.1+): `SKAdNetwork.updatePostbackConversionValue(_:coarseValue:completionHandler:)`
|
|
9
|
-
// — fine value 0-63 plus optional coarse value (`.low`, `.medium`, `.high`)
|
|
8
|
+
// - SKAN 4.0 (iOS 16.1+): `SKAdNetwork.updatePostbackConversionValue(_:coarseValue:lockWindow:completionHandler:)`
|
|
9
|
+
// — fine value 0-63 plus optional coarse value (`.low`, `.medium`, `.high`)
|
|
10
|
+
// and an optional `lockWindow` that asks Apple to send the current
|
|
11
|
+
// window's postback without waiting the window out.
|
|
10
12
|
//
|
|
11
13
|
// The class validates inputs (0-63 fine) and routes to the correct API
|
|
12
14
|
// based on iOS availability and whether a coarse value is provided.
|
|
@@ -45,6 +47,7 @@ public final class SkanManager {
|
|
|
45
47
|
public func update(
|
|
46
48
|
fineValue: Int,
|
|
47
49
|
coarseValue: SkanCoarseValue? = nil,
|
|
50
|
+
lockWindow: Bool = false,
|
|
48
51
|
completion: @escaping (Error?) -> Void
|
|
49
52
|
) {
|
|
50
53
|
do {
|
|
@@ -53,7 +56,12 @@ public final class SkanManager {
|
|
|
53
56
|
completion(error)
|
|
54
57
|
return
|
|
55
58
|
}
|
|
56
|
-
updater.updateConversionValue(
|
|
59
|
+
updater.updateConversionValue(
|
|
60
|
+
fineValue: fineValue,
|
|
61
|
+
coarseValue: coarseValue,
|
|
62
|
+
lockWindow: lockWindow,
|
|
63
|
+
completion: completion
|
|
64
|
+
)
|
|
57
65
|
}
|
|
58
66
|
}
|
|
59
67
|
|
|
@@ -61,6 +69,7 @@ public protocol SkanUpdaterProtocol {
|
|
|
61
69
|
func updateConversionValue(
|
|
62
70
|
fineValue: Int,
|
|
63
71
|
coarseValue: SkanCoarseValue?,
|
|
72
|
+
lockWindow: Bool,
|
|
64
73
|
completion: @escaping (Error?) -> Void
|
|
65
74
|
)
|
|
66
75
|
}
|
|
@@ -71,6 +80,7 @@ public final class DefaultSkanUpdater: SkanUpdaterProtocol {
|
|
|
71
80
|
public func updateConversionValue(
|
|
72
81
|
fineValue: Int,
|
|
73
82
|
coarseValue: SkanCoarseValue?,
|
|
83
|
+
lockWindow: Bool,
|
|
74
84
|
completion: @escaping (Error?) -> Void
|
|
75
85
|
) {
|
|
76
86
|
if let coarse = coarseValue, #available(iOS 16.1, *) {
|
|
@@ -80,7 +90,11 @@ public final class DefaultSkanUpdater: SkanUpdaterProtocol {
|
|
|
80
90
|
case .medium: apiCoarse = .medium
|
|
81
91
|
case .high: apiCoarse = .high
|
|
82
92
|
}
|
|
83
|
-
SKAdNetwork.updatePostbackConversionValue(
|
|
93
|
+
SKAdNetwork.updatePostbackConversionValue(
|
|
94
|
+
fineValue,
|
|
95
|
+
coarseValue: apiCoarse,
|
|
96
|
+
lockWindow: lockWindow
|
|
97
|
+
) { error in
|
|
84
98
|
completion(error)
|
|
85
99
|
}
|
|
86
100
|
} else if #available(iOS 15.4, *) {
|
|
@@ -116,7 +116,10 @@ public class ApexCapacitorPlugin: CAPPlugin, CAPBridgedPlugin, UNUserNotificatio
|
|
|
116
116
|
defaults.set(visitorId, forKey: "apex.visitorId")
|
|
117
117
|
|
|
118
118
|
deepLinks.setWarmLinkHandler { [weak self] url in
|
|
119
|
-
self?.notifyListeners("deepLink", data: [
|
|
119
|
+
self?.notifyListeners("deepLink", data: [
|
|
120
|
+
"url": url.absoluteString,
|
|
121
|
+
"deepLinkHint": DeepLinkManager.apexHint(from: url) ?? NSNull(),
|
|
122
|
+
])
|
|
120
123
|
}
|
|
121
124
|
|
|
122
125
|
// MMP-177 — observe foreground pushes posted by the app's
|
|
@@ -514,8 +517,10 @@ public class ApexCapacitorPlugin: CAPPlugin, CAPBridgedPlugin, UNUserNotificatio
|
|
|
514
517
|
}
|
|
515
518
|
|
|
516
519
|
@objc public func getInstallReferrer(_ call: CAPPluginCall) {
|
|
517
|
-
// iOS has no equivalent of Play Install Referrer
|
|
518
|
-
|
|
520
|
+
// iOS has no equivalent of Play Install Referrer: the App Store
|
|
521
|
+
// forwards nothing across an install, so deferred deep linking
|
|
522
|
+
// here falls back to the probabilistic server match.
|
|
523
|
+
call.resolve(["referrer": NSNull(), "clickTimestamp": 0])
|
|
519
524
|
}
|
|
520
525
|
|
|
521
526
|
@objc public func getVisitorId(_ call: CAPPluginCall) {
|
|
@@ -618,7 +623,8 @@ public class ApexCapacitorPlugin: CAPPlugin, CAPBridgedPlugin, UNUserNotificatio
|
|
|
618
623
|
if let raw = call.getString("coarseValue") {
|
|
619
624
|
coarse = SkanCoarseValue(rawValue: raw)
|
|
620
625
|
}
|
|
621
|
-
|
|
626
|
+
let lockWindow = call.getBool("lockWindow") ?? false
|
|
627
|
+
skanManager.update(fineValue: fineValue, coarseValue: coarse, lockWindow: lockWindow) { error in
|
|
622
628
|
if let error = error {
|
|
623
629
|
call.reject("SKAN update failed: \(error.localizedDescription)")
|
|
624
630
|
} else {
|
|
@@ -631,9 +637,12 @@ public class ApexCapacitorPlugin: CAPPlugin, CAPBridgedPlugin, UNUserNotificatio
|
|
|
631
637
|
|
|
632
638
|
@objc public func getInitialDeepLink(_ call: CAPPluginCall) {
|
|
633
639
|
if let url = deepLinks.consumeInitialUrl() {
|
|
634
|
-
call.resolve([
|
|
640
|
+
call.resolve([
|
|
641
|
+
"url": url.absoluteString,
|
|
642
|
+
"deepLinkHint": DeepLinkManager.apexHint(from: url) ?? NSNull(),
|
|
643
|
+
])
|
|
635
644
|
} else {
|
|
636
|
-
call.resolve(["url": NSNull()])
|
|
645
|
+
call.resolve(["url": NSNull(), "deepLinkHint": NSNull()])
|
|
637
646
|
}
|
|
638
647
|
}
|
|
639
648
|
|
|
@@ -853,6 +862,7 @@ public class ApexCapacitorPlugin: CAPPlugin, CAPBridgedPlugin, UNUserNotificatio
|
|
|
853
862
|
request.setValue("sdk", forHTTPHeaderField: "x-apex-source")
|
|
854
863
|
request.setValue(visitorId, forHTTPHeaderField: "x-apex-visitor-id")
|
|
855
864
|
request.setValue(workspaceKey, forHTTPHeaderField: "x-apex-workspace")
|
|
865
|
+
request.setValue(releaseChannel, forHTTPHeaderField: "x-apex-release-channel")
|
|
856
866
|
if let key = apiKey {
|
|
857
867
|
request.setValue(key, forHTTPHeaderField: "x-apex-api-key")
|
|
858
868
|
}
|
|
@@ -898,12 +908,18 @@ public class ApexCapacitorPlugin: CAPPlugin, CAPBridgedPlugin, UNUserNotificatio
|
|
|
898
908
|
/// mobile slot / testMode). Batched through the offline queue.
|
|
899
909
|
private func enqueueExposure(experimentId: String, variant: String) {
|
|
900
910
|
guard let queue = offlineQueue else { return }
|
|
911
|
+
// The app version that saw the variant — the mobile equivalent of the
|
|
912
|
+
// web build id. On mobile the experiment logic often ships inside the
|
|
913
|
+
// binary, so "which version was exposed" is what makes results honest
|
|
914
|
+
// during a staged rollout + enables version-gating (Phase 5, 2026-07-20).
|
|
915
|
+
let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.0.0"
|
|
901
916
|
var payload: [String: AnyCodable] = [:]
|
|
902
917
|
payload["type"] = AnyCodable("experiment_exposure")
|
|
903
918
|
payload["data"] = AnyCodable([
|
|
904
919
|
"experiment_id": experimentId,
|
|
905
920
|
"variant_key": variant,
|
|
906
921
|
"surface": "mobile",
|
|
922
|
+
"app_version": appVersion,
|
|
907
923
|
])
|
|
908
924
|
if testMode {
|
|
909
925
|
payload["testMode"] = AnyCodable(true)
|
|
@@ -66,4 +66,30 @@ final class DeepLinkManagerTests: XCTestCase {
|
|
|
66
66
|
func testIsValidDeepLinkRejectsFileUrl() {
|
|
67
67
|
XCTAssertFalse(DeepLinkManager.isValidDeepLink(URL(string: "file:///tmp/test")!))
|
|
68
68
|
}
|
|
69
|
+
|
|
70
|
+
func testApexHintReadsParam() {
|
|
71
|
+
let url = URL(string: "https://acme.example/summer?apex_dl=%2Foffers%2Fsummer")!
|
|
72
|
+
XCTAssertEqual(DeepLinkManager.apexHint(from: url), "/offers/summer")
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
func testApexHintFindsParamAmongOthers() {
|
|
76
|
+
let url = URL(
|
|
77
|
+
string: "https://acme.example/s?utm_source=linkedin&apex_dl=screen-42&utm_medium=social"
|
|
78
|
+
)!
|
|
79
|
+
XCTAssertEqual(DeepLinkManager.apexHint(from: url), "screen-42")
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
func testApexHintNilWhenAbsent() {
|
|
83
|
+
let url = URL(string: "https://acme.example/summer?utm_source=x")!
|
|
84
|
+
XCTAssertNil(DeepLinkManager.apexHint(from: url))
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
func testApexHintNilWithNoQuery() {
|
|
88
|
+
XCTAssertNil(DeepLinkManager.apexHint(from: URL(string: "https://acme.example/s")!))
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
func testApexHintNilWhenEmpty() {
|
|
92
|
+
let url = URL(string: "https://acme.example/s?apex_dl=")!
|
|
93
|
+
XCTAssertNil(DeepLinkManager.apexHint(from: url))
|
|
94
|
+
}
|
|
69
95
|
}
|
|
@@ -9,6 +9,7 @@ final class RecordingSkanUpdater: SkanUpdaterProtocol {
|
|
|
9
9
|
struct Call: Equatable {
|
|
10
10
|
let fineValue: Int
|
|
11
11
|
let coarseValue: SkanCoarseValue?
|
|
12
|
+
let lockWindow: Bool
|
|
12
13
|
}
|
|
13
14
|
var calls: [Call] = []
|
|
14
15
|
var nextError: Error?
|
|
@@ -16,9 +17,10 @@ final class RecordingSkanUpdater: SkanUpdaterProtocol {
|
|
|
16
17
|
func updateConversionValue(
|
|
17
18
|
fineValue: Int,
|
|
18
19
|
coarseValue: SkanCoarseValue?,
|
|
20
|
+
lockWindow: Bool,
|
|
19
21
|
completion: @escaping (Error?) -> Void
|
|
20
22
|
) {
|
|
21
|
-
calls.append(Call(fineValue: fineValue, coarseValue: coarseValue))
|
|
23
|
+
calls.append(Call(fineValue: fineValue, coarseValue: coarseValue, lockWindow: lockWindow))
|
|
22
24
|
completion(nextError)
|
|
23
25
|
}
|
|
24
26
|
}
|
|
@@ -88,4 +90,23 @@ final class SkanManagerTests: XCTestCase {
|
|
|
88
90
|
XCTAssertEqual(SkanCoarseValue.medium.rawValue, "medium")
|
|
89
91
|
XCTAssertEqual(SkanCoarseValue.high.rawValue, "high")
|
|
90
92
|
}
|
|
93
|
+
|
|
94
|
+
func testUpdatePassesLockWindowThrough() {
|
|
95
|
+
let updater = RecordingSkanUpdater()
|
|
96
|
+
let manager = SkanManager(updater: updater)
|
|
97
|
+
let exp = expectation(description: "update")
|
|
98
|
+
manager.update(fineValue: 32, coarseValue: .high, lockWindow: true) { _ in exp.fulfill() }
|
|
99
|
+
wait(for: [exp], timeout: 1)
|
|
100
|
+
XCTAssertEqual(updater.calls.count, 1)
|
|
101
|
+
XCTAssertTrue(updater.calls[0].lockWindow)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
func testUpdateDefaultsLockWindowToFalse() {
|
|
105
|
+
let updater = RecordingSkanUpdater()
|
|
106
|
+
let manager = SkanManager(updater: updater)
|
|
107
|
+
let exp = expectation(description: "update")
|
|
108
|
+
manager.update(fineValue: 8, coarseValue: .medium) { _ in exp.fulfill() }
|
|
109
|
+
wait(for: [exp], timeout: 1)
|
|
110
|
+
XCTAssertFalse(updater.calls[0].lockWindow)
|
|
111
|
+
}
|
|
91
112
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apex-inc/capacitor-plugin",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
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",
|