@apex-inc/capacitor-plugin 2.2.5 → 2.2.6
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.
|
@@ -14,6 +14,7 @@ import com.getcapacitor.PluginMethod
|
|
|
14
14
|
import com.getcapacitor.annotation.CapacitorPlugin
|
|
15
15
|
import com.google.android.gms.ads.identifier.AdvertisingIdClient
|
|
16
16
|
import com.google.firebase.messaging.FirebaseMessaging
|
|
17
|
+
import org.json.JSONObject
|
|
17
18
|
import java.io.File
|
|
18
19
|
import java.util.Locale
|
|
19
20
|
import java.util.TimeZone
|
|
@@ -373,7 +374,7 @@ class ApexCapacitorPlugin : Plugin() {
|
|
|
373
374
|
|
|
374
375
|
@PluginMethod
|
|
375
376
|
fun getInstallReferrer(call: PluginCall) {
|
|
376
|
-
val data = JSObject().put("referrer", lastInstallReferrer ?:
|
|
377
|
+
val data = JSObject().put("referrer", lastInstallReferrer ?: JSONObject.NULL)
|
|
377
378
|
call.resolve(data)
|
|
378
379
|
}
|
|
379
380
|
|
|
@@ -484,32 +485,103 @@ class ApexCapacitorPlugin : Plugin() {
|
|
|
484
485
|
val commitSha = call.getString("commitSha")
|
|
485
486
|
|
|
486
487
|
// Bitmap the activity's decor view on the main thread, JPEG-encode
|
|
487
|
-
// + base64, then POST off the main thread.
|
|
488
|
+
// + base64, then POST off the main thread. PixelCopy (API 26+) is
|
|
489
|
+
// the correct way to snapshot a hardware-accelerated window — it
|
|
490
|
+
// copies the actual rendered surface, so SurfaceView / video /
|
|
491
|
+
// GL content is captured rather than coming out black. Older API
|
|
492
|
+
// levels fall back to the software `view.draw(Canvas)` path.
|
|
488
493
|
activity.runOnUiThread {
|
|
489
494
|
try {
|
|
490
|
-
// TODO(native-unverified): decor-view bitmap capture cannot
|
|
491
|
-
// be compiled/verified in this environment.
|
|
492
495
|
val decor = activity.window.decorView
|
|
496
|
+
val width = decor.width.coerceAtLeast(1)
|
|
497
|
+
val height = decor.height.coerceAtLeast(1)
|
|
493
498
|
val bitmap = android.graphics.Bitmap.createBitmap(
|
|
494
|
-
|
|
495
|
-
|
|
499
|
+
width,
|
|
500
|
+
height,
|
|
496
501
|
android.graphics.Bitmap.Config.ARGB_8888,
|
|
497
502
|
)
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
503
|
+
|
|
504
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
505
|
+
val location = IntArray(2)
|
|
506
|
+
decor.getLocationInWindow(location)
|
|
507
|
+
val rect = android.graphics.Rect(
|
|
508
|
+
location[0],
|
|
509
|
+
location[1],
|
|
510
|
+
location[0] + width,
|
|
511
|
+
location[1] + height,
|
|
512
|
+
)
|
|
513
|
+
try {
|
|
514
|
+
android.view.PixelCopy.request(
|
|
515
|
+
activity.window,
|
|
516
|
+
rect,
|
|
517
|
+
bitmap,
|
|
518
|
+
{ copyResult ->
|
|
519
|
+
if (copyResult == android.view.PixelCopy.SUCCESS) {
|
|
520
|
+
encodeAndUploadScreenshot(
|
|
521
|
+
call, experimentId, variantKey, bitmap, label, commitSha,
|
|
522
|
+
)
|
|
523
|
+
} else {
|
|
524
|
+
// PixelCopy refused (e.g. window not
|
|
525
|
+
// backed by a surface yet) — fall back
|
|
526
|
+
// to the software draw path.
|
|
527
|
+
drawViewToBitmap(decor, bitmap)
|
|
528
|
+
encodeAndUploadScreenshot(
|
|
529
|
+
call, experimentId, variantKey, bitmap, label, commitSha,
|
|
530
|
+
)
|
|
531
|
+
}
|
|
532
|
+
},
|
|
533
|
+
android.os.Handler(android.os.Looper.getMainLooper()),
|
|
534
|
+
)
|
|
535
|
+
} catch (e: IllegalArgumentException) {
|
|
536
|
+
// PixelCopy throws if the source isn't ready — software fallback.
|
|
537
|
+
drawViewToBitmap(decor, bitmap)
|
|
538
|
+
encodeAndUploadScreenshot(
|
|
539
|
+
call, experimentId, variantKey, bitmap, label, commitSha,
|
|
540
|
+
)
|
|
541
|
+
}
|
|
542
|
+
} else {
|
|
543
|
+
drawViewToBitmap(decor, bitmap)
|
|
544
|
+
encodeAndUploadScreenshot(
|
|
545
|
+
call, experimentId, variantKey, bitmap, label, commitSha,
|
|
546
|
+
)
|
|
547
|
+
}
|
|
507
548
|
} catch (e: Exception) {
|
|
508
549
|
call.resolve(JSObject().put("captured", false).put("reason", "encode_failed"))
|
|
509
550
|
}
|
|
510
551
|
}
|
|
511
552
|
}
|
|
512
553
|
|
|
554
|
+
/** Software-rasterize a view's current content into [bitmap]. */
|
|
555
|
+
private fun drawViewToBitmap(view: android.view.View, bitmap: android.graphics.Bitmap) {
|
|
556
|
+
val canvas = android.graphics.Canvas(bitmap)
|
|
557
|
+
view.draw(canvas)
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* JPEG-encode + base64 a captured bitmap and hand it off to the
|
|
562
|
+
* background uploader. Resolves `encode_failed` if compression throws.
|
|
563
|
+
*/
|
|
564
|
+
private fun encodeAndUploadScreenshot(
|
|
565
|
+
call: PluginCall,
|
|
566
|
+
experimentId: String,
|
|
567
|
+
variantKey: String,
|
|
568
|
+
bitmap: android.graphics.Bitmap,
|
|
569
|
+
label: String?,
|
|
570
|
+
commitSha: String?,
|
|
571
|
+
) {
|
|
572
|
+
try {
|
|
573
|
+
val stream = java.io.ByteArrayOutputStream()
|
|
574
|
+
bitmap.compress(android.graphics.Bitmap.CompressFormat.JPEG, 70, stream)
|
|
575
|
+
val imageBase64 = android.util.Base64.encodeToString(
|
|
576
|
+
stream.toByteArray(),
|
|
577
|
+
android.util.Base64.NO_WRAP,
|
|
578
|
+
)
|
|
579
|
+
uploadVariantScreenshot(call, experimentId, variantKey, imageBase64, label, commitSha)
|
|
580
|
+
} catch (e: Exception) {
|
|
581
|
+
call.resolve(JSObject().put("captured", false).put("reason", "encode_failed"))
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
513
585
|
/**
|
|
514
586
|
* POST a captured screenshot to `${apiBaseUrl}/api/experiments/:id/
|
|
515
587
|
* assets` with `source: "device"`. Networking mirrors `identify()`.
|
|
@@ -590,7 +662,7 @@ class ApexCapacitorPlugin : Plugin() {
|
|
|
590
662
|
@PluginMethod
|
|
591
663
|
fun getInitialDeepLink(call: PluginCall) {
|
|
592
664
|
val url = deepLinks.consumeInitialUrl()
|
|
593
|
-
val data = JSObject().put("url", url ?:
|
|
665
|
+
val data = JSObject().put("url", url ?: JSONObject.NULL)
|
|
594
666
|
call.resolve(data)
|
|
595
667
|
}
|
|
596
668
|
|
|
@@ -639,11 +711,11 @@ class ApexCapacitorPlugin : Plugin() {
|
|
|
639
711
|
fun getCurrentSession(call: PluginCall) {
|
|
640
712
|
val current = sessionManager.getCurrent()
|
|
641
713
|
val data = JSObject()
|
|
642
|
-
.put("sessionId", current?.sessionId ?:
|
|
714
|
+
.put("sessionId", current?.sessionId ?: JSONObject.NULL)
|
|
643
715
|
.put(
|
|
644
716
|
"startedAt",
|
|
645
717
|
current?.startedAtMs?.let { java.time.Instant.ofEpochMilli(it).toString() }
|
|
646
|
-
?:
|
|
718
|
+
?: JSONObject.NULL
|
|
647
719
|
)
|
|
648
720
|
call.resolve(data)
|
|
649
721
|
}
|
|
@@ -705,14 +777,14 @@ class ApexCapacitorPlugin : Plugin() {
|
|
|
705
777
|
fun getQueueSize(call: PluginCall) {
|
|
706
778
|
val queue = offlineQueue
|
|
707
779
|
if (queue == null) {
|
|
708
|
-
call.resolve(JSObject().put("count", 0).put("oldestEventAt",
|
|
780
|
+
call.resolve(JSObject().put("count", 0).put("oldestEventAt", JSONObject.NULL))
|
|
709
781
|
return
|
|
710
782
|
}
|
|
711
783
|
val oldest = queue.oldestEventAtMs()
|
|
712
784
|
?.let { java.time.Instant.ofEpochMilli(it).toString() }
|
|
713
785
|
val data = JSObject()
|
|
714
786
|
.put("count", queue.size())
|
|
715
|
-
.put("oldestEventAt", oldest ?:
|
|
787
|
+
.put("oldestEventAt", oldest ?: JSONObject.NULL)
|
|
716
788
|
call.resolve(data)
|
|
717
789
|
}
|
|
718
790
|
|
|
@@ -839,8 +911,3 @@ private class FileQueueStorage(private val file: File) : QueueStorage {
|
|
|
839
911
|
file.writeText(contents)
|
|
840
912
|
}
|
|
841
913
|
}
|
|
842
|
-
|
|
843
|
-
/** Sentinel for passing JSON null to Capacitor's JSObject helpers. */
|
|
844
|
-
private object JSONull {
|
|
845
|
-
override fun toString(): String = "null"
|
|
846
|
-
}
|
|
@@ -686,9 +686,25 @@ public class ApexCapacitorPlugin: CAPPlugin, CAPBridgedPlugin, UNUserNotificatio
|
|
|
686
686
|
// UIKit snapshot API.
|
|
687
687
|
DispatchQueue.main.async { [weak self] in
|
|
688
688
|
guard let self = self else { return }
|
|
689
|
-
//
|
|
690
|
-
//
|
|
691
|
-
|
|
689
|
+
// Modern key-window discovery: walk the connected scenes for
|
|
690
|
+
// the foreground-active UIWindowScene, then take its key
|
|
691
|
+
// window. `UIApplication.shared.windows` /
|
|
692
|
+
// `UIApplication.shared.keyWindow` are deprecated as of iOS
|
|
693
|
+
// 15; scanning scene windows works on the iOS 13+ deployment
|
|
694
|
+
// target without tripping the deprecation.
|
|
695
|
+
let windowScene = UIApplication.shared.connectedScenes
|
|
696
|
+
.first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene
|
|
697
|
+
?? UIApplication.shared.connectedScenes.first(where: { $0 is UIWindowScene }) as? UIWindowScene
|
|
698
|
+
let keyWindow: UIWindow?
|
|
699
|
+
if #available(iOS 15.0, *) {
|
|
700
|
+
keyWindow = windowScene?.keyWindow
|
|
701
|
+
?? windowScene?.windows.first(where: { $0.isKeyWindow })
|
|
702
|
+
?? windowScene?.windows.first
|
|
703
|
+
} else {
|
|
704
|
+
keyWindow = windowScene?.windows.first(where: { $0.isKeyWindow })
|
|
705
|
+
?? windowScene?.windows.first
|
|
706
|
+
}
|
|
707
|
+
guard let window = keyWindow,
|
|
692
708
|
let rootView = window.rootViewController?.view else {
|
|
693
709
|
call.resolve(["captured": false, "reason": "no_window"])
|
|
694
710
|
return
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apex-inc/capacitor-plugin",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.6",
|
|
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",
|