@absolutejs/absolute 0.20.0-beta.67 → 0.20.0-beta.69

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.
@@ -17893,7 +17893,7 @@ var require_main = __commonJS(function(exports) {
17893
17893
  // src/cli/scripts/telemetry.ts
17894
17894
  import { existsSync as existsSync5, mkdirSync, readFileSync as readFileSync6, writeFileSync } from "fs";
17895
17895
  import { homedir as homedir3 } from "os";
17896
- import { join as join18 } from "path";
17896
+ import { join as join19 } from "path";
17897
17897
  var configDir, configPath, getTelemetryConfig = () => {
17898
17898
  try {
17899
17899
  if (!existsSync5(configPath))
@@ -17906,14 +17906,14 @@ var configDir, configPath, getTelemetryConfig = () => {
17906
17906
  }
17907
17907
  };
17908
17908
  var init_telemetry = __esm(() => {
17909
- configDir = join18(homedir3(), ".absolutejs");
17910
- configPath = join18(configDir, "telemetry.json");
17909
+ configDir = join19(homedir3(), ".absolutejs");
17910
+ configPath = join19(configDir, "telemetry.json");
17911
17911
  });
17912
17912
 
17913
17913
  // src/cli/telemetryEvent.ts
17914
17914
  import { existsSync as existsSync6, readFileSync as readFileSync7 } from "fs";
17915
17915
  import { arch, platform as platform3 } from "os";
17916
- import { dirname as dirname14, join as join19, parse } from "path";
17916
+ import { dirname as dirname14, join as join20, parse } from "path";
17917
17917
  var checkCandidate = (candidate) => {
17918
17918
  if (!existsSync6(candidate)) {
17919
17919
  return null;
@@ -17933,7 +17933,7 @@ var checkCandidate = (candidate) => {
17933
17933
  }, findPackageVersion = () => {
17934
17934
  let { dir } = import.meta;
17935
17935
  while (dir !== parse(dir).root) {
17936
- const candidate = join19(dir, "package.json");
17936
+ const candidate = join20(dir, "package.json");
17937
17937
  const version = checkCandidate(candidate);
17938
17938
  if (version) {
17939
17939
  return version;
@@ -24092,7 +24092,7 @@ var parseAbsoluteMobileBuildPageMetadata = (value) => {
24092
24092
  };
24093
24093
  // src/mobile/buildPipeline.ts
24094
24094
  import { readFile as readFile15 } from "fs/promises";
24095
- import { join as join15, resolve as resolve13 } from "path";
24095
+ import { join as join16, resolve as resolve13 } from "path";
24096
24096
  import { pathToFileURL as pathToFileURL2 } from "url";
24097
24097
 
24098
24098
  // src/mobile/buildRelease.ts
@@ -25330,7 +25330,7 @@ import {
25330
25330
  writeFile as writeFile12
25331
25331
  } from "fs/promises";
25332
25332
  import { createHash as createHash12 } from "crypto";
25333
- import { basename as basename4, dirname as dirname10, join as join14, relative as relative10, resolve as resolve12, sep as sep6 } from "path";
25333
+ import { basename as basename4, dirname as dirname10, join as join15, relative as relative10, resolve as resolve12, sep as sep6 } from "path";
25334
25334
 
25335
25335
  // src/mobile/updateRuntime.ts
25336
25336
  init_deviceCapabilities();
@@ -25498,6 +25498,426 @@ var resolveAbsoluteMobileUpdateRuntime = (config, projectRoot) => {
25498
25498
  };
25499
25499
  };
25500
25500
 
25501
+ // src/mobile/expoNativeObservability.ts
25502
+ import { join as join14 } from "path";
25503
+ var HEADER = `// Generated by AbsoluteJS. Edit absolute.config.ts, then run absolute mobile sync.
25504
+ `;
25505
+ var moduleConfig = `${JSON.stringify({
25506
+ android: {
25507
+ modules: [
25508
+ "expo.modules.absolutemobileobservability.AbsoluteMobileObservabilityModule"
25509
+ ]
25510
+ },
25511
+ apple: { modules: ["AbsoluteMobileObservabilityModule"] },
25512
+ platforms: ["apple", "android"]
25513
+ }, null, 2)}
25514
+ `;
25515
+ var podspec = `${HEADER}Pod::Spec.new do |s|
25516
+ s.name = 'AbsoluteMobileObservability'
25517
+ s.version = '0.0.1'
25518
+ s.summary = 'AbsoluteJS provider-neutral native process diagnostics'
25519
+ s.description = 'Generated MetricKit bridge for an AbsoluteJS Expo application.'
25520
+ s.author = 'AbsoluteJS'
25521
+ s.homepage = 'https://absolutejs.com'
25522
+ s.platforms = { :ios => '16.4' }
25523
+ s.source = { git: '' }
25524
+ s.static_framework = true
25525
+ s.dependency 'ExpoModulesCore'
25526
+ s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' }
25527
+ s.source_files = '**/*.{h,m,mm,swift,hpp,cpp}'
25528
+ end
25529
+ `;
25530
+ var iosModule = `${HEADER}import ExpoModulesCore
25531
+ import MetricKit
25532
+
25533
+ public final class AbsoluteMobileObservabilityModule: Module, MXMetricManagerSubscriber {
25534
+ private static let maximumReports = 8
25535
+ private static let maximumReportBytes = 64 * 1024
25536
+ private static let queue = DispatchQueue(label: "dev.absolute.expo-mobile-observability")
25537
+
25538
+ public func definition() -> ModuleDefinition {
25539
+ Name("AbsoluteMobileObservability")
25540
+
25541
+ OnCreate { MXMetricManager.shared.add(self) }
25542
+ OnDestroy { MXMetricManager.shared.remove(self) }
25543
+
25544
+ AsyncFunction("pending") { () -> [String: Any] in
25545
+ Self.queue.sync {
25546
+ let reports: [[String: Any]] = Self.files().compactMap { file in
25547
+ guard let data = try? Data(contentsOf: file),
25548
+ let value = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { return nil }
25549
+ return value
25550
+ }
25551
+ return ["reports": reports]
25552
+ }
25553
+ }
25554
+
25555
+ AsyncFunction("acknowledge") { (options: [String: Any]) in
25556
+ let accepted = Set(options["ids"] as? [String] ?? [])
25557
+ Self.queue.sync {
25558
+ for file in Self.files() {
25559
+ guard let data = try? Data(contentsOf: file),
25560
+ let value = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
25561
+ let id = value["id"] as? String,
25562
+ accepted.contains(id) else { continue }
25563
+ try? FileManager.default.removeItem(at: file)
25564
+ }
25565
+ }
25566
+ }
25567
+ }
25568
+
25569
+ private static func root() -> URL? {
25570
+ guard let support = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first else { return nil }
25571
+ let root = support.appendingPathComponent("AbsoluteMobileObservability", isDirectory: true)
25572
+ try? FileManager.default.createDirectory(at: root, withIntermediateDirectories: true)
25573
+ return root
25574
+ }
25575
+
25576
+ private static func files() -> [URL] {
25577
+ guard let root = root() else { return [] }
25578
+ return ((try? FileManager.default.contentsOfDirectory(at: root, includingPropertiesForKeys: nil)) ?? [])
25579
+ .filter { $0.pathExtension == "json" }
25580
+ .sorted { $0.lastPathComponent < $1.lastPathComponent }
25581
+ }
25582
+
25583
+ private static func enqueue(kind: String, occurredAt: Double, details: [AnyHashable: Any]) {
25584
+ guard let root = root() else { return }
25585
+ let id = UUID().uuidString.lowercased()
25586
+ var report: [String: Any] = [
25587
+ "details": Dictionary(uniqueKeysWithValues: details.map { (String(describing: $0.key), $0.value) }),
25588
+ "id": id,
25589
+ "kind": kind,
25590
+ "occurredAt": occurredAt,
25591
+ "platform": "ios"
25592
+ ]
25593
+ guard JSONSerialization.isValidJSONObject(report),
25594
+ let original = try? JSONSerialization.data(withJSONObject: report) else { return }
25595
+ if original.count > maximumReportBytes {
25596
+ report["details"] = [
25597
+ "diagnosticPrefix": String(decoding: original.prefix(24 * 1024), as: UTF8.self),
25598
+ "originalBytes": original.count,
25599
+ "truncated": true
25600
+ ]
25601
+ }
25602
+ guard let data = try? JSONSerialization.data(withJSONObject: report),
25603
+ data.count <= maximumReportBytes else { return }
25604
+ let timestamp = String(format: "%013.0f", occurredAt)
25605
+ try? data.write(to: root.appendingPathComponent("\\(timestamp)-\\(id).json"), options: .atomic)
25606
+ let overflow = max(0, files().count - maximumReports)
25607
+ for file in files().prefix(overflow) { try? FileManager.default.removeItem(at: file) }
25608
+ }
25609
+
25610
+ public func didReceive(_ payloads: [MXDiagnosticPayload]) {
25611
+ Self.queue.async {
25612
+ for payload in payloads {
25613
+ let at = payload.timeStampEnd.timeIntervalSince1970 * 1000
25614
+ for value in payload.crashDiagnostics ?? [] { Self.enqueue(kind: "crash", occurredAt: at, details: value.dictionaryRepresentation()) }
25615
+ for value in payload.hangDiagnostics ?? [] { Self.enqueue(kind: "hang", occurredAt: at, details: value.dictionaryRepresentation()) }
25616
+ for value in payload.cpuExceptionDiagnostics ?? [] { Self.enqueue(kind: "cpu-exception", occurredAt: at, details: value.dictionaryRepresentation()) }
25617
+ for value in payload.diskWriteExceptionDiagnostics ?? [] { Self.enqueue(kind: "disk-write-exception", occurredAt: at, details: value.dictionaryRepresentation()) }
25618
+ if #available(iOS 16.0, *) {
25619
+ for value in payload.appLaunchDiagnostics ?? [] { Self.enqueue(kind: "app-launch", occurredAt: at, details: value.dictionaryRepresentation()) }
25620
+ }
25621
+ }
25622
+ }
25623
+ }
25624
+ }
25625
+ `;
25626
+ var androidBuild = `${HEADER}plugins {
25627
+ id 'com.android.library'
25628
+ id 'expo-module-gradle-plugin'
25629
+ }
25630
+
25631
+ group = 'expo.modules.absolutemobileobservability'
25632
+ version = '0.0.1'
25633
+
25634
+ android {
25635
+ namespace 'expo.modules.absolutemobileobservability'
25636
+ defaultConfig {
25637
+ versionCode 1
25638
+ versionName '0.0.1'
25639
+ }
25640
+ lintOptions { abortOnError false }
25641
+ }
25642
+ `;
25643
+ var androidManifest = `${HEADER}<manifest xmlns:android="http://schemas.android.com/apk/res/android" />
25644
+ `;
25645
+ var androidModule = `${HEADER}package expo.modules.absolutemobileobservability
25646
+
25647
+ import android.app.ActivityManager
25648
+ import android.app.ApplicationExitInfo
25649
+ import android.os.Build
25650
+ import android.util.Base64
25651
+ import expo.modules.kotlin.modules.Module
25652
+ import expo.modules.kotlin.modules.ModuleDefinition
25653
+ import java.io.ByteArrayOutputStream
25654
+ import java.io.File
25655
+ import java.io.FileInputStream
25656
+ import java.io.FileOutputStream
25657
+ import java.io.InputStream
25658
+ import java.nio.charset.StandardCharsets
25659
+ import java.util.UUID
25660
+ import org.json.JSONArray
25661
+ import org.json.JSONObject
25662
+
25663
+ class AbsoluteMobileObservabilityModule : Module() {
25664
+ companion object {
25665
+ private const val MAX_REPORTS = 8
25666
+ private const val MAX_TRACE_BYTES = 32 * 1024
25667
+ private const val ACKNOWLEDGED_KEY = "acknowledged"
25668
+ private const val PREFERENCES = "AbsoluteMobileObservability"
25669
+ }
25670
+
25671
+ override fun definition() = ModuleDefinition {
25672
+ Name("AbsoluteMobileObservability")
25673
+
25674
+ AsyncFunction("pending") {
25675
+ collect()
25676
+ mapOf("reports" to files().mapNotNull { readReport(it) })
25677
+ }
25678
+
25679
+ AsyncFunction("acknowledge") { options: Map<String, List<String>> ->
25680
+ val accepted = options["ids"]?.toSet() ?: emptySet()
25681
+ val acknowledged = acknowledged().toMutableSet()
25682
+ files().forEach { file ->
25683
+ val report = readReport(file)
25684
+ val id = report?.get("id") as? String
25685
+ if (id != null && accepted.contains(id)) {
25686
+ acknowledged.add(id)
25687
+ file.delete()
25688
+ }
25689
+ }
25690
+ writeAcknowledged(acknowledged)
25691
+ }
25692
+ }
25693
+
25694
+ private fun context() = appContext.reactContext
25695
+
25696
+ private fun root(): File? {
25697
+ val context = context() ?: return null
25698
+ return File(context.filesDir, "absolute-mobile-observability").also { it.mkdirs() }
25699
+ }
25700
+
25701
+ private fun files(): List<File> = root()?.listFiles { _, name -> name.endsWith(".json") }
25702
+ ?.sortedBy { it.name } ?: emptyList()
25703
+
25704
+ private fun acknowledged(): Set<String> {
25705
+ val context = context() ?: return emptySet()
25706
+ return try {
25707
+ val values = JSONArray(context.getSharedPreferences(PREFERENCES, 0).getString(ACKNOWLEDGED_KEY, "[]"))
25708
+ (0 until values.length()).map { values.optString(it) }.toSet()
25709
+ } catch (_: Exception) { emptySet() }
25710
+ }
25711
+
25712
+ private fun writeAcknowledged(values: Set<String>) {
25713
+ val context = context() ?: return
25714
+ val bounded = values.sorted().takeLast(64)
25715
+ context.getSharedPreferences(PREFERENCES, 0).edit()
25716
+ .putString(ACKNOWLEDGED_KEY, JSONArray(bounded).toString()).apply()
25717
+ }
25718
+
25719
+ private fun kind(reason: Int): String? = when (reason) {
25720
+ ApplicationExitInfo.REASON_ANR -> "anr"
25721
+ ApplicationExitInfo.REASON_CRASH -> "crash"
25722
+ ApplicationExitInfo.REASON_CRASH_NATIVE -> "native-crash"
25723
+ ApplicationExitInfo.REASON_LOW_MEMORY -> "low-memory"
25724
+ ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE -> "resource-exhaustion"
25725
+ ApplicationExitInfo.REASON_INITIALIZATION_FAILURE -> "initialization-failure"
25726
+ ApplicationExitInfo.REASON_SIGNALED -> "signal"
25727
+ else -> null
25728
+ }
25729
+
25730
+ private fun trace(info: ApplicationExitInfo): ByteArray? = try {
25731
+ info.traceInputStream?.use { input ->
25732
+ val output = ByteArrayOutputStream()
25733
+ val buffer = ByteArray(4096)
25734
+ var total = 0
25735
+ while (true) {
25736
+ val count = input.read(buffer, 0, minOf(buffer.size, MAX_TRACE_BYTES + 1 - total))
25737
+ if (count <= 0) break
25738
+ output.write(buffer, 0, count)
25739
+ total += count
25740
+ if (total > MAX_TRACE_BYTES) return null
25741
+ }
25742
+ output.toByteArray()
25743
+ }
25744
+ } catch (_: Exception) { null }
25745
+
25746
+ private fun report(info: ApplicationExitInfo): JSONObject? {
25747
+ val kind = kind(info.reason) ?: return null
25748
+ val id = "android:" + info.timestamp + ":" + info.reason + ":" + info.status
25749
+ return try {
25750
+ val details = JSONObject()
25751
+ .put("importance", info.importance)
25752
+ .put("pssKb", info.pss)
25753
+ .put("reason", info.reason)
25754
+ .put("rssKb", info.rss)
25755
+ .put("status", info.status)
25756
+ info.description?.let { details.put("description", it) }
25757
+ trace(info)?.takeIf { it.isNotEmpty() }?.let { trace ->
25758
+ if (info.reason == ApplicationExitInfo.REASON_CRASH_NATIVE) {
25759
+ details.put("trace", Base64.encodeToString(trace, Base64.NO_WRAP))
25760
+ details.put("traceEncoding", "android-tombstone-protobuf-base64")
25761
+ } else {
25762
+ details.put("trace", String(trace, StandardCharsets.UTF_8))
25763
+ details.put("traceEncoding", "utf-8")
25764
+ }
25765
+ }
25766
+ JSONObject().put("details", details).put("id", id).put("kind", kind)
25767
+ .put("occurredAt", info.timestamp).put("platform", "android")
25768
+ } catch (_: Exception) { null }
25769
+ }
25770
+
25771
+ private fun collect() {
25772
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) return
25773
+ val context = context() ?: return
25774
+ val directory = root() ?: return
25775
+ val manager = context.getSystemService(ActivityManager::class.java) ?: return
25776
+ val ignored = (acknowledged() + files().mapNotNull { readReport(it)?.get("id") as? String }).toMutableSet()
25777
+ manager.getHistoricalProcessExitReasons(null, 0, 16).forEach { exit ->
25778
+ val report = report(exit) ?: return@forEach
25779
+ val id = report.optString("id")
25780
+ if (ignored.contains(id)) return@forEach
25781
+ val target = File(directory, String.format(java.util.Locale.ROOT, "%013d-%s.json", exit.timestamp, UUID.randomUUID()))
25782
+ try {
25783
+ FileOutputStream(target).use { it.write(report.toString().toByteArray(StandardCharsets.UTF_8)) }
25784
+ ignored.add(id)
25785
+ } catch (_: Exception) {}
25786
+ }
25787
+ files().dropLast(MAX_REPORTS).forEach { it.delete() }
25788
+ }
25789
+
25790
+ private fun read(input: InputStream): ByteArray {
25791
+ val output = ByteArrayOutputStream()
25792
+ val buffer = ByteArray(4096)
25793
+ while (true) {
25794
+ val count = input.read(buffer)
25795
+ if (count < 0) break
25796
+ output.write(buffer, 0, count)
25797
+ }
25798
+ return output.toByteArray()
25799
+ }
25800
+
25801
+ private fun readReport(file: File): Map<String, Any?>? = try {
25802
+ FileInputStream(file).use { JSONObject(String(read(it), StandardCharsets.UTF_8)).toMap() }
25803
+ } catch (_: Exception) { null }
25804
+
25805
+ private fun JSONObject.toMap(): Map<String, Any?> = keys().asSequence().associateWith { key ->
25806
+ when (val value = get(key)) {
25807
+ is JSONObject -> value.toMap()
25808
+ is JSONArray -> (0 until value.length()).map { index ->
25809
+ when (val item = value.get(index)) {
25810
+ is JSONObject -> item.toMap()
25811
+ else -> item
25812
+ }
25813
+ }
25814
+ JSONObject.NULL -> null
25815
+ else -> value
25816
+ }
25817
+ }
25818
+ }
25819
+ `;
25820
+ var expoNativeObservabilityRuntimeSource = `${HEADER}import { requireNativeModule } from 'expo';
25821
+ import { AppState, Platform } from 'react-native';
25822
+ import { ABSOLUTE_MOBILE_MANIFEST } from './webAssets';
25823
+
25824
+ type NativeObservabilityModule = {
25825
+ acknowledge(options: { ids: string[] }): Promise<void>;
25826
+ pending(): Promise<{ reports: unknown[] }>;
25827
+ };
25828
+ type NativeReport = { details: Record<string, unknown>; id: string; kind: string; occurredAt: number; platform: 'android' | 'ios' };
25829
+ const native = requireNativeModule<NativeObservabilityModule>('AbsoluteMobileObservability');
25830
+ const KINDS = new Set(['anr', 'app-launch', 'cpu-exception', 'crash', 'disk-write-exception', 'hang', 'initialization-failure', 'low-memory', 'native-crash', 'resource-exhaustion', 'signal']);
25831
+ const ID = /^[A-Za-z0-9._:-]{1,128}$/;
25832
+ const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === 'object' && value !== null && !Array.isArray(value);
25833
+ const redactText = (value: string) => value.slice(0, 8192)
25834
+ .replace(/\\b(Bearer)\\s+[A-Za-z0-9._~+/-]+=*/gi, '$1 [REDACTED]')
25835
+ .replace(/([?&](?:access_token|api_?key|authorization|code|id_token|password|refresh_token|secret|token)=)[^&#\\s]*/gi, '$1[REDACTED]')
25836
+ .replace(/\\b((?:access_?token|api_?key|authorization|client_?secret|cookie|id_?token|password|passwd|refresh_?token|secret|token)\\s*[:=]\\s*)[^,\\s;]+/gi, '$1[REDACTED]')
25837
+ .replace(/\\beyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\b/g, '[REDACTED]');
25838
+ const redact = (value: unknown, depth = 0): unknown => {
25839
+ if (depth >= 8) return '[TRUNCATED]';
25840
+ if (typeof value === 'string') return redactText(value);
25841
+ if (value === null || typeof value === 'number' || typeof value === 'boolean') return value;
25842
+ if (Array.isArray(value)) return value.slice(0, 64).map(entry => redact(entry, depth + 1));
25843
+ if (!isRecord(value)) return String(value).slice(0, 8192);
25844
+ return Object.fromEntries(Object.entries(value).slice(0, 128).map(([key, entry]) => [key, /(?:authorization|cookie|password|secret|token)/i.test(key) ? '[REDACTED]' : redact(entry, depth + 1)]));
25845
+ };
25846
+ const parse = (value: unknown): NativeReport | undefined => {
25847
+ if (!isRecord(value) || !isRecord(value.details) || typeof value.id !== 'string' || !ID.test(value.id) || typeof value.kind !== 'string' || !KINDS.has(value.kind) || typeof value.occurredAt !== 'number' || !Number.isFinite(value.occurredAt) || value.occurredAt <= 0 || value.platform !== 'android' && value.platform !== 'ios') return;
25848
+ const details = redact(value.details);
25849
+ if (!isRecord(details) || new TextEncoder().encode(JSON.stringify(details)).byteLength > 64 * 1024) return;
25850
+ return { details, id: value.id, kind: value.kind, occurredAt: value.occurredAt, platform: value.platform };
25851
+ };
25852
+ const sampled = (id: string, rate: number) => {
25853
+ if (rate >= 1) return true;
25854
+ let hash = 2166136261;
25855
+ for (const character of id) { hash ^= character.codePointAt(0) ?? 0; hash = Math.imul(hash, 16777619); }
25856
+ return (hash >>> 0) / 4294967296 < rate;
25857
+ };
25858
+ let drainPromise: Promise<void> | undefined;
25859
+ let started = false;
25860
+ const drain = () => {
25861
+ drainPromise ??= (async () => {
25862
+ const manifest = ABSOLUTE_MOBILE_MANIFEST;
25863
+ const config = manifest?.observability;
25864
+ if (!manifest || !config || Platform.OS !== 'android' && Platform.OS !== 'ios') return;
25865
+ const reports = (await native.pending()).reports.slice(0, 8).map(parse).filter((report): report is NativeReport => report !== undefined);
25866
+ const discarded = reports.filter(report => !sampled(report.id, config.sampleRate));
25867
+ if (discarded.length) await native.acknowledge({ ids: discarded.map(report => report.id) });
25868
+ const selected = reports.filter(report => sampled(report.id, config.sampleRate));
25869
+ if (!selected.length) return;
25870
+ const response = await fetch(config.endpoint, {
25871
+ body: JSON.stringify({ ...(config.environment ? { environment: config.environment } : {}), events: selected.map(report => ({ at: report.occurredAt, extra: { nativeDiagnostic: report.details, nativeDiagnosticId: report.id }, groupingKey: 'absolute-native:' + report.platform + ':' + report.kind, level: ['app-launch', 'cpu-exception', 'disk-write-exception', 'low-memory', 'resource-exhaustion', 'signal'].includes(report.kind) ? 'warning' : 'error', message: 'Native process diagnostic \u2014 ' + report.kind, name: 'AbsoluteMobileNativeDiagnostic', tags: { absoluteMobile: 'true', mobileAppBuild: manifest.appBuild, mobileEngine: 'expo', mobileFailurePhase: 'native-process', mobileManifestFormat: '1', mobileNativeRuntime: 'expo', mobilePlatform: report.platform, mobileRuntime: manifest.runtime } })), project: config.project, release: manifest.appBuild, v: 1 }),
25872
+ headers: { 'content-type': 'application/json' },
25873
+ method: 'POST'
25874
+ });
25875
+ if (response.ok) await native.acknowledge({ ids: selected.map(report => report.id) });
25876
+ })().catch(() => undefined).finally(() => { drainPromise = undefined; });
25877
+ return drainPromise;
25878
+ };
25879
+ export const startAbsoluteExpoNativeObservability = () => {
25880
+ if (!started) {
25881
+ started = true;
25882
+ AppState.addEventListener('change', state => { if (state === 'active') void drain(); });
25883
+ }
25884
+ return drain();
25885
+ };
25886
+ `;
25887
+ var absoluteExpoNativeObservabilityFiles = (project) => new Map([
25888
+ [
25889
+ join14(project, "modules/absolute-mobile-observability/expo-module.config.json"),
25890
+ moduleConfig
25891
+ ],
25892
+ [
25893
+ join14(project, "modules/absolute-mobile-observability/ios/AbsoluteMobileObservability.podspec"),
25894
+ podspec
25895
+ ],
25896
+ [
25897
+ join14(project, "modules/absolute-mobile-observability/ios/AbsoluteMobileObservabilityModule.swift"),
25898
+ iosModule
25899
+ ],
25900
+ [
25901
+ join14(project, "modules/absolute-mobile-observability/android/build.gradle"),
25902
+ androidBuild
25903
+ ],
25904
+ [
25905
+ join14(project, "modules/absolute-mobile-observability/android/src/main/AndroidManifest.xml"),
25906
+ androidManifest
25907
+ ],
25908
+ [
25909
+ join14(project, "modules/absolute-mobile-observability/android/src/main/java/expo/modules/absolutemobileobservability/AbsoluteMobileObservabilityModule.kt"),
25910
+ androidModule
25911
+ ],
25912
+ [
25913
+ join14(project, "src/generated/AbsoluteNativeObservability.ts"),
25914
+ expoNativeObservabilityRuntimeSource
25915
+ ]
25916
+ ]);
25917
+ var absoluteExpoNativeObservabilityPaths = (project) => [
25918
+ ...absoluteExpoNativeObservabilityFiles(project).keys()
25919
+ ];
25920
+
25501
25921
  // src/mobile/expoProject.ts
25502
25922
  var EXPO_GENERATED_HEADER = `// Generated by AbsoluteJS. Edit absolute.config.ts, then run absolute mobile sync.
25503
25923
  `;
@@ -25578,7 +25998,7 @@ var routeSegments = (route) => {
25578
25998
  return segment;
25579
25999
  });
25580
26000
  };
25581
- var routeFile = (project, route) => join14(project, "app", ...routeSegments(route), "index.tsx");
26001
+ var routeFile = (project, route) => join15(project, "app", ...routeSegments(route), "index.tsx");
25582
26002
  var packageDependencies = (plan) => Object.fromEntries(plan.requiredPackages.map((spec) => {
25583
26003
  const separator = spec.lastIndexOf("@");
25584
26004
  return [spec.slice(0, separator), spec.slice(separator + 1)];
@@ -25826,16 +26246,19 @@ config.watchFolders = [appRoot];
25826
26246
 
25827
26247
  module.exports = config;
25828
26248
  `;
25829
- var layoutSource = (auth, sync, updates) => `${EXPO_GENERATED_HEADER}import { Stack } from 'expo-router';
26249
+ var layoutSource = (auth, sync, updates, observability) => `${EXPO_GENERATED_HEADER}import { Stack } from 'expo-router';
25830
26250
  import '../src/generated/AbsoluteDevices';
25831
26251
  ${auth ? `import { useEffect, useState } from 'react';
25832
26252
  import { startAbsoluteExpoAuth } from '../src/generated/AbsoluteAuth';` : ""}
25833
26253
  ${sync ? "import { startAbsoluteExpoSync } from '../src/generated/AbsoluteSync';" : ""}
25834
26254
  ${updates ? `import { useEffect as useUpdateEffect } from 'react';
25835
26255
  import { startAbsoluteExpoUpdates } from '../src/generated/AbsoluteUpdates';` : ""}
26256
+ ${observability ? `import { useEffect as useNativeObservabilityEffect } from 'react';
26257
+ import { startAbsoluteExpoNativeObservability } from '../src/generated/AbsoluteNativeObservability';` : ""}
25836
26258
 
25837
26259
  export default function AbsoluteLayout() {
25838
26260
  ${updates ? "useUpdateEffect(() => { void startAbsoluteExpoUpdates(); }, []);" : ""}
26261
+ ${observability ? "useNativeObservabilityEffect(() => { void startAbsoluteExpoNativeObservability(); }, []);" : ""}
25839
26262
  ${auth ? `const [ready, setReady] = useState(false);
25840
26263
  useEffect(() => {
25841
26264
  let active = true;
@@ -26675,8 +27098,20 @@ var writeManagedFile = async (path, source, force) => {
26675
27098
  await rename11(temporary, path);
26676
27099
  return true;
26677
27100
  };
27101
+ var pruneManagedFiles = async (paths) => {
27102
+ const removed = await Promise.all(paths.map(async (path) => {
27103
+ if (!await exists3(path))
27104
+ return false;
27105
+ const source = await readFile14(path, "utf8");
27106
+ if (!source.startsWith(EXPO_GENERATED_HEADER) && !path.endsWith("expo-module.config.json"))
27107
+ return false;
27108
+ await rm10(path, { force: true });
27109
+ return true;
27110
+ }));
27111
+ return removed.filter(Boolean).length;
27112
+ };
26678
27113
  var pruneStaleManagedExpoRoutes = async (project, expected) => {
26679
- const appDirectory = join14(project, "app");
27114
+ const appDirectory = join15(project, "app");
26680
27115
  if (!await exists3(appDirectory))
26681
27116
  return 0;
26682
27117
  const files = await walkFiles(appDirectory);
@@ -26724,7 +27159,7 @@ var writeAbsoluteExpoProject = async (config, options) => {
26724
27159
  if (missing) {
26725
27160
  throw new TypeError(`Expo native route ${missing.route} references missing module ${missing.module}.`);
26726
27161
  }
26727
- const marker = join14(project, EXPO_PROJECT_MARKER);
27162
+ const marker = join15(project, EXPO_PROJECT_MARKER);
26728
27163
  if (await exists3(project) && !await exists3(marker)) {
26729
27164
  const entries = await readdir4(project);
26730
27165
  if (entries.length > 0 && !options.force) {
@@ -26737,7 +27172,7 @@ var writeAbsoluteExpoProject = async (config, options) => {
26737
27172
  const authEnabled = Boolean(auth);
26738
27173
  const files = new Map([
26739
27174
  [
26740
- join14(project, ".gitignore"),
27175
+ join15(project, ".gitignore"),
26741
27176
  `.expo/
26742
27177
  android/
26743
27178
  ios/
@@ -26745,78 +27180,83 @@ node_modules/
26745
27180
  `
26746
27181
  ],
26747
27182
  [
26748
- join14(project, "app.json"),
27183
+ join15(project, "app.json"),
26749
27184
  jsonSource(expoAppConfig(config, authEnabled, syncEnabled, devices, runtimeFingerprint))
26750
27185
  ],
26751
- [join14(project, "app.config.js"), expoDynamicAppConfig],
27186
+ [join15(project, "app.config.js"), expoDynamicAppConfig],
26752
27187
  [
26753
- join14(project, "package.json"),
27188
+ join15(project, "package.json"),
26754
27189
  jsonSource(expoPackage(authEnabled, syncEnabled, devices, config.updates !== undefined))
26755
27190
  ],
26756
- [join14(project, "metro.config.js"), metroConfig(projectRoot)],
27191
+ [join15(project, "metro.config.js"), metroConfig(projectRoot)],
26757
27192
  [
26758
- join14(project, "plugins", "withAbsoluteDevelopmentCa.js"),
27193
+ join15(project, "plugins", "withAbsoluteDevelopmentCa.js"),
26759
27194
  expoDevelopmentCaPlugin
26760
27195
  ],
26761
27196
  [
26762
- join14(project, "tsconfig.json"),
27197
+ join15(project, "tsconfig.json"),
26763
27198
  jsonSource(expoTsConfig(projectRoot, project, authEnabled, syncEnabled))
26764
27199
  ],
26765
27200
  [
26766
- join14(project, "app", "_layout.tsx"),
26767
- layoutSource(authEnabled, syncEnabled, config.updates !== undefined)
27201
+ join15(project, "app", "_layout.tsx"),
27202
+ layoutSource(authEnabled, syncEnabled, config.updates !== undefined, config.observability !== undefined)
26768
27203
  ],
26769
27204
  [
26770
- join14(project, "app", "__absolute", "native", "index.tsx"),
27205
+ join15(project, "app", "__absolute", "native", "index.tsx"),
26771
27206
  nativeDiagnosticSource
26772
27207
  ],
26773
27208
  [
26774
- join14(project, "src", "generated", "AbsoluteDevices.ts"),
27209
+ join15(project, "src", "generated", "AbsoluteDevices.ts"),
26775
27210
  devicesRuntimeSource(config, devices, authEnabled)
26776
27211
  ],
26777
27212
  [
26778
- join14(project, "src", "generated", "AbsoluteNativeRoute.tsx"),
27213
+ join15(project, "src", "generated", "AbsoluteNativeRoute.tsx"),
26779
27214
  nativeRouteRuntimeSource(authEnabled)
26780
27215
  ],
26781
27216
  [
26782
- join14(project, "src", "generated", "AbsoluteWebHost.tsx"),
27217
+ join15(project, "src", "generated", "AbsoluteWebHost.tsx"),
26783
27218
  webHostSource(config, auth, syncEnabled)
26784
27219
  ]
26785
27220
  ]);
27221
+ if (config.observability) {
27222
+ for (const [path, source] of absoluteExpoNativeObservabilityFiles(project))
27223
+ files.set(path, source);
27224
+ }
26786
27225
  if (config.updates) {
26787
- files.set(join14(project, "src", "generated", "AbsoluteUpdates.ts"), updatesRuntimeSource(config));
27226
+ files.set(join15(project, "src", "generated", "AbsoluteUpdates.ts"), updatesRuntimeSource(config));
26788
27227
  }
26789
27228
  const expoCodeSigning = config.updates?.expoCodeSigning;
26790
27229
  if (expoCodeSigning)
26791
- files.set(join14(project, "certs", "absolute-mobile-update.pem"), expoCodeSigning.certificatePem);
27230
+ files.set(join15(project, "certs", "absolute-mobile-update.pem"), expoCodeSigning.certificatePem);
26792
27231
  if (auth) {
26793
- files.set(join14(project, "src", "generated", "AbsoluteAuth.ts"), authRuntimeSource(auth, config.appId));
27232
+ files.set(join15(project, "src", "generated", "AbsoluteAuth.ts"), authRuntimeSource(auth, config.appId));
26794
27233
  }
26795
27234
  if (syncSchema) {
26796
- files.set(join14(project, "src", "generated", "AbsoluteSync.ts"), syncRuntimeSource(config, syncSchema));
27235
+ files.set(join15(project, "src", "generated", "AbsoluteSync.ts"), syncRuntimeSource(config, syncSchema));
26797
27236
  }
26798
- const webAssetsPath = join14(project, "src", "generated", "webAssets.ts");
27237
+ const webAssetsPath = join15(project, "src", "generated", "webAssets.ts");
26799
27238
  if (!await exists3(webAssetsPath)) {
26800
27239
  files.set(webAssetsPath, emptyWebAssetsSource);
26801
27240
  }
26802
27241
  if (!config.expoNativeRoutes["/"]) {
26803
- files.set(join14(project, "app", "index.tsx"), webRouteSource);
27242
+ files.set(join15(project, "app", "index.tsx"), webRouteSource);
26804
27243
  }
26805
- files.set(join14(project, "app", "[...absolute].tsx"), catchAllRouteSource);
26806
- const nativeRouteRuntime = join14(project, "src", "generated", "AbsoluteNativeRoute.tsx");
27244
+ files.set(join15(project, "app", "[...absolute].tsx"), catchAllRouteSource);
27245
+ const nativeRouteRuntime = join15(project, "src", "generated", "AbsoluteNativeRoute.tsx");
26807
27246
  for (const [route, module] of routeModules) {
26808
- const wrapper = route === "/" ? join14(project, "app", "index.tsx") : routeFile(project, route);
27247
+ const wrapper = route === "/" ? join15(project, "app", "index.tsx") : routeFile(project, route);
26809
27248
  files.set(wrapper, nativeWrapperSource(wrapper, module, nativeRouteRuntime, route));
26810
27249
  }
26811
- const removed = await pruneStaleManagedExpoRoutes(project, new Set([...files.keys()].filter((path) => path.startsWith(`${join14(project, "app")}${sep6}`))));
27250
+ const removedRoutes = await pruneStaleManagedExpoRoutes(project, new Set([...files.keys()].filter((path) => path.startsWith(`${join15(project, "app")}${sep6}`))));
27251
+ const removedObservability = config.observability ? 0 : await pruneManagedFiles(absoluteExpoNativeObservabilityPaths(project));
26812
27252
  const changes = await Promise.all([...files].map(([path, source]) => writeManagedFile(path, source, true)));
26813
- const changed = removed + changes.filter(Boolean).length;
27253
+ const changed = removedRoutes + removedObservability + changes.filter(Boolean).length;
26814
27254
  return { changed, path: project, written: [...files.keys()] };
26815
27255
  };
26816
27256
  var walkFiles = async (root, directory = root) => {
26817
27257
  const entries = await readdir4(directory, { withFileTypes: true });
26818
27258
  const nested = await Promise.all(entries.map((entry) => {
26819
- const path = join14(directory, entry.name);
27259
+ const path = join15(directory, entry.name);
26820
27260
  if (entry.isDirectory())
26821
27261
  return walkFiles(root, path);
26822
27262
  if (entry.isFile())
@@ -26883,11 +27323,11 @@ export const materializeAbsoluteWebBundle = async () => {
26883
27323
  var syncAbsoluteExpoWebAssets = async (config) => {
26884
27324
  if (config.engine !== "expo")
26885
27325
  throw new TypeError("Expo asset sync requires mobile.engine: expo.");
26886
- const marker = join14(config.nativeProjectDirectory, EXPO_PROJECT_MARKER);
27326
+ const marker = join15(config.nativeProjectDirectory, EXPO_PROJECT_MARKER);
26887
27327
  if (!await exists3(marker)) {
26888
27328
  throw new TypeError("Expo asset sync requires an AbsoluteJS-managed Expo project. Run mobile init first.");
26889
27329
  }
26890
- const manifestPath = join14(config.bundleDirectory, "absolute-mobile-manifest.json");
27330
+ const manifestPath = join15(config.bundleDirectory, "absolute-mobile-manifest.json");
26891
27331
  const manifest = JSON.parse(await readFile14(manifestPath, "utf8"));
26892
27332
  const nativeDataManifest = expoNativeDataManifest(manifest);
26893
27333
  const { appBuild } = nativeDataManifest;
@@ -26901,16 +27341,16 @@ var syncAbsoluteExpoWebAssets = async (config) => {
26901
27341
  bundleHash.update("\x00");
26902
27342
  });
26903
27343
  const bundleId = `amexpo_${bundleHash.digest("hex")}`;
26904
- const destination = join14(config.nativeProjectDirectory, "assets", "absolute");
27344
+ const destination = join15(config.nativeProjectDirectory, "assets", "absolute");
26905
27345
  await mkdir11(dirname10(destination), { recursive: true });
26906
- const staging = await mkdtemp7(join14(dirname10(destination), `.${basename4(destination)}.stage-`));
27346
+ const staging = await mkdtemp7(join15(dirname10(destination), `.${basename4(destination)}.stage-`));
26907
27347
  let assets;
26908
27348
  try {
26909
27349
  assets = await Promise.all(files.map(async (source, index) => {
26910
27350
  const name = `${String(index).padStart(6, "0")}${EXPO_ASSET_EXTENSION}`;
26911
- await cp2(source, join14(staging, name));
27351
+ await cp2(source, join15(staging, name));
26912
27352
  return {
26913
- asset: portableRelative2(join14(config.nativeProjectDirectory, "src", "generated"), join14(destination, name)),
27353
+ asset: portableRelative2(join15(config.nativeProjectDirectory, "src", "generated"), join15(destination, name)),
26914
27354
  path: relative10(config.bundleDirectory, source).replaceAll("\\", "/")
26915
27355
  };
26916
27356
  }));
@@ -26919,7 +27359,7 @@ var syncAbsoluteExpoWebAssets = async (config) => {
26919
27359
  await rm10(staging, { force: true, recursive: true });
26920
27360
  throw error;
26921
27361
  }
26922
- const generated = join14(config.nativeProjectDirectory, "src", "generated", "webAssets.ts");
27362
+ const generated = join15(config.nativeProjectDirectory, "src", "generated", "webAssets.ts");
26923
27363
  await writeManagedFile(generated, assetModuleSource(assets, bundleId, nativeDataManifest), true);
26924
27364
  return { appBuild, assets: assets.length, bundleId, path: destination };
26925
27365
  };
@@ -26960,9 +27400,9 @@ var loadServerApp = async (producerPath) => {
26960
27400
  var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
26961
27401
  const buildDirectory = resolve13(options.buildDirectory);
26962
27402
  const mobile = normalizeAbsoluteMobileConfig(options.mobile, options.projectRoot);
26963
- const root = join15(buildDirectory, ".absolutejs", "mobile-compatibility");
27403
+ const root = join16(buildDirectory, ".absolutejs", "mobile-compatibility");
26964
27404
  const [manifestSource, previous] = await Promise.all([
26965
- readFile15(join15(buildDirectory, "manifest.json"), "utf8"),
27405
+ readFile15(join16(buildDirectory, "manifest.json"), "utf8"),
26966
27406
  readAbsoluteMobileMaterializedReleases(root)
26967
27407
  ]);
26968
27408
  const manifest = JSON.parse(manifestSource);
@@ -27447,7 +27887,7 @@ var generateAbsoluteExpoCodeSigning = async (options) => {
27447
27887
  // src/mobile/expoDevController.ts
27448
27888
  import { spawn } from "child_process";
27449
27889
  import { access as access11 } from "fs/promises";
27450
- import { join as join16 } from "path";
27890
+ import { join as join17 } from "path";
27451
27891
  var METRO_READY_TIMEOUT_MS = 60000;
27452
27892
  var PROCESS_CLOSE_TIMEOUT_MS = 2000;
27453
27893
  var commandEnvironment = (options) => ({
@@ -27460,7 +27900,7 @@ var commandEnvironment = (options) => ({
27460
27900
  ...options.metroHost ? { REACT_NATIVE_PACKAGER_HOSTNAME: options.metroHost } : {}
27461
27901
  });
27462
27902
  var absoluteExpoExecutable = async (project) => {
27463
- const executable = join16(project, "node_modules", ".bin", "expo");
27903
+ const executable = join17(project, "node_modules", ".bin", "expo");
27464
27904
  try {
27465
27905
  await access11(executable);
27466
27906
  return executable;
@@ -27786,7 +28226,7 @@ var startAbsoluteExpoDevSession = async (options) => {
27786
28226
  };
27787
28227
  // src/mobile/expoUpdate.ts
27788
28228
  import { access as access12, mkdir as mkdir13, readFile as readFile16, writeFile as writeFile14 } from "fs/promises";
27789
- import { dirname as dirname12, extname as extname4, join as join17 } from "path";
28229
+ import { dirname as dirname12, extname as extname4, join as join18 } from "path";
27790
28230
  var ABSOLUTE_EXPO_UPDATE_DESCRIPTOR = "_absolute/expo-update.json";
27791
28231
  var ABSOLUTE_EXPO_UPDATE_FORMAT = 1;
27792
28232
  var object4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
@@ -27852,7 +28292,7 @@ var parseAbsoluteExpoUpdateDescriptor = (value) => {
27852
28292
  };
27853
28293
  };
27854
28294
  var finalizeAbsoluteExpoUpdateExport = async (options) => {
27855
- const metadata = JSON.parse(await readFile16(join17(options.exportDirectory, "metadata.json"), "utf8"));
28295
+ const metadata = JSON.parse(await readFile16(join18(options.exportDirectory, "metadata.json"), "utf8"));
27856
28296
  if (!object4(metadata) || !object4(metadata.fileMetadata))
27857
28297
  throw new TypeError("Expo export metadata.json is invalid.");
27858
28298
  const descriptor = parseAbsoluteExpoUpdateDescriptor({
@@ -27863,10 +28303,10 @@ var finalizeAbsoluteExpoUpdateExport = async (options) => {
27863
28303
  runtimeVersion: options.runtimeVersion
27864
28304
  });
27865
28305
  const referenced = Object.values(descriptor.platforms).flatMap((entry) => entry ? [entry.launchAsset, ...entry.assets] : []);
27866
- await Promise.all(referenced.map(({ path }) => access12(join17(options.exportDirectory, path)).catch(() => {
28306
+ await Promise.all(referenced.map(({ path }) => access12(join18(options.exportDirectory, path)).catch(() => {
27867
28307
  throw new TypeError(`Expo export metadata references missing asset ${path}.`);
27868
28308
  })));
27869
- const destination = join17(options.exportDirectory, ABSOLUTE_EXPO_UPDATE_DESCRIPTOR);
28309
+ const destination = join18(options.exportDirectory, ABSOLUTE_EXPO_UPDATE_DESCRIPTOR);
27870
28310
  await mkdir13(dirname12(destination), { recursive: true });
27871
28311
  await writeFile14(destination, `${JSON.stringify(descriptor, null, "\t")}
27872
28312
  `);
@@ -28587,7 +29027,7 @@ var createAbsoluteMobilePreviewPlugin = (mobile) => {
28587
29027
  };
28588
29028
  // src/mobile/nativeDeepLinks.ts
28589
29029
  import { readFile as readFile18, rename as rename12, writeFile as writeFile16 } from "fs/promises";
28590
- import { join as join20 } from "path";
29030
+ import { join as join21 } from "path";
28591
29031
  var START_MARKER = "<!-- absolutejs:deep-links:start -->";
28592
29032
  var END_MARKER = "<!-- absolutejs:deep-links:end -->";
28593
29033
  var IOS_ENTITLEMENTS = "App/AbsoluteJS.entitlements";
@@ -28644,7 +29084,7 @@ ${hosts}
28644
29084
  `;
28645
29085
  };
28646
29086
  var configureAndroid = async (config) => {
28647
- const path = join20(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
29087
+ const path = join21(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
28648
29088
  const source = await readFile18(path, "utf8");
28649
29089
  const mainActivity = source.indexOf('android:name=".MainActivity"');
28650
29090
  if (mainActivity === NOT_FOUND) {
@@ -28670,7 +29110,7 @@ var iosSchemeRegion = (scheme) => ` ${START_MARKER}
28670
29110
  ${END_MARKER}
28671
29111
  `;
28672
29112
  var configureIosInfo = async (config) => {
28673
- const path = join20(config.nativeProjectDirectory, "ios/App/App/Info.plist");
29113
+ const path = join21(config.nativeProjectDirectory, "ios/App/App/Info.plist");
28674
29114
  const source = await readFile18(path, "utf8");
28675
29115
  const region = config.deepLinkScheme ? iosSchemeRegion(config.deepLinkScheme) : ` ${START_MARKER}
28676
29116
  ${END_MARKER}
@@ -28694,7 +29134,7 @@ ${domains}
28694
29134
  `;
28695
29135
  };
28696
29136
  var configureIosEntitlements = async (config) => {
28697
- const path = join20(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
29137
+ const path = join21(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
28698
29138
  let current = "";
28699
29139
  try {
28700
29140
  current = await readFile18(path, "utf8");
@@ -28712,7 +29152,7 @@ var configureIosEntitlements = async (config) => {
28712
29152
  return true;
28713
29153
  };
28714
29154
  var configureIosProject = async (config) => {
28715
- const path = join20(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
29155
+ const path = join21(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
28716
29156
  const source = await readFile18(path, "utf8");
28717
29157
  const declarations = [
28718
29158
  ...source.matchAll(/CODE_SIGN_ENTITLEMENTS = ([^;]+);/g)
@@ -28753,7 +29193,7 @@ var applyAbsoluteNativeDeepLinks = async (config, platforms = config.platforms)
28753
29193
  // src/mobile/nativeDeviceCapabilities.ts
28754
29194
  init_deviceCapabilities();
28755
29195
  import { readFile as readFile19, rename as rename13, writeFile as writeFile17 } from "fs/promises";
28756
- import { join as join21 } from "path";
29196
+ import { join as join22 } from "path";
28757
29197
  var START_MARKER2 = "<!-- absolutejs:device-capabilities:start -->";
28758
29198
  var END_MARKER2 = "<!-- absolutejs:device-capabilities:end -->";
28759
29199
  var NOT_FOUND2 = -1;
@@ -28903,7 +29343,7 @@ var writeIosPrivacyManifest = async (path, current, source) => {
28903
29343
  var configureIosPrivacyProject = async (config, requirements) => {
28904
29344
  if (requirements.iosPrivacyAccessedApis.length === 0)
28905
29345
  return false;
28906
- const projectPath2 = join21(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
29346
+ const projectPath2 = join22(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
28907
29347
  const project = await readFile19(projectPath2, "utf8");
28908
29348
  return writeChangedFile2(projectPath2, addIosPrivacyProjectReference(project));
28909
29349
  };
@@ -28956,7 +29396,7 @@ ${next.slice(index)}`;
28956
29396
  return next;
28957
29397
  };
28958
29398
  var configureIos2 = async (config, plan) => {
28959
- const path = join21(config.nativeProjectDirectory, "ios/App/App/Info.plist");
29399
+ const path = join22(config.nativeProjectDirectory, "ios/App/App/Info.plist");
28960
29400
  const source = await readFile19(path, "utf8");
28961
29401
  const requirements = absoluteDeviceNativeRequirements(plan);
28962
29402
  const existingSystemBars = source.match(/<key>UIViewControllerBasedStatusBarAppearance<\/key>\s*<(true|false)\s*\/>/u);
@@ -28977,7 +29417,7 @@ ${content}
28977
29417
  ${END_MARKER2}
28978
29418
  ` : "";
28979
29419
  const infoChanged = await writeChangedFile2(path, managed(source, region, source.lastIndexOf("</dict>")));
28980
- const privacyPath = join21(config.nativeProjectDirectory, "ios/App/App/PrivacyInfo.xcprivacy");
29420
+ const privacyPath = join22(config.nativeProjectDirectory, "ios/App/App/PrivacyInfo.xcprivacy");
28981
29421
  const privacyCurrent = await optionalSource(privacyPath);
28982
29422
  const privacySource = privacyManifestSource(privacyCurrent, requirements);
28983
29423
  const [privacyChanged, projectChanged, pushChanged] = await Promise.all([
@@ -28988,7 +29428,7 @@ ${content}
28988
29428
  return infoChanged || privacyChanged || projectChanged || pushChanged;
28989
29429
  };
28990
29430
  var configureIosPushNotifications = async (config, enabled) => {
28991
- const entitlementsPath = join21(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
29431
+ const entitlementsPath = join22(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
28992
29432
  const entitlements = await optionalSource(entitlementsPath);
28993
29433
  if (entitlements === null && !enabled)
28994
29434
  return false;
@@ -29000,7 +29440,7 @@ var configureIosPushNotifications = async (config, enabled) => {
29000
29440
  <!-- ${PUSH_END_MARKER} -->
29001
29441
  ` : "";
29002
29442
  const nextEntitlements = replacePushRegion(entitlements, entitlementRegion, entitlements.lastIndexOf("</dict>"));
29003
- const delegatePath = join21(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
29443
+ const delegatePath = join22(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
29004
29444
  const delegate = await optionalSource(delegatePath);
29005
29445
  if (delegate === null && !enabled)
29006
29446
  return false;
@@ -29049,7 +29489,7 @@ var replacePushRegion = (source, region, insertion) => {
29049
29489
  return `${source.slice(0, insertion)}${region}${source.slice(insertion)}`;
29050
29490
  };
29051
29491
  var configureAndroid2 = async (config, plan) => {
29052
- const path = join21(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
29492
+ const path = join22(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
29053
29493
  const source = await readFile19(path, "utf8");
29054
29494
  const permissions = absoluteDeviceNativeRequirements(plan).androidPermissions;
29055
29495
  const content = permissions.map((permission) => ` <uses-permission android:name="${escapeXml2(permission)}" />`).join(`
@@ -29085,7 +29525,7 @@ ${content}
29085
29525
  throw new TypeError(`Android google-services.json does not contain package ${config.appId}.`);
29086
29526
  const [manifestChanged, firebaseChanged] = await Promise.all([
29087
29527
  writeChangedFile2(path, nextManifest),
29088
- writeOptionalChangedFile(join21(config.nativeProjectDirectory, "android/app/google-services.json"), firebaseSource)
29528
+ writeOptionalChangedFile(join22(config.nativeProjectDirectory, "android/app/google-services.json"), firebaseSource)
29089
29529
  ]);
29090
29530
  return manifestChanged || firebaseChanged;
29091
29531
  };
@@ -29111,7 +29551,7 @@ import {
29111
29551
  rm as rm11,
29112
29552
  writeFile as writeFile18
29113
29553
  } from "fs/promises";
29114
- import { dirname as dirname15, join as join22 } from "path";
29554
+ import { dirname as dirname15, join as join23 } from "path";
29115
29555
  var START = "// absolutejs:mobile-updates:start";
29116
29556
  var END = "// absolutejs:mobile-updates:end";
29117
29557
  var PLUGIN_START = "// absolutejs:mobile-update-plugin:start";
@@ -29278,8 +29718,8 @@ public final class AbsoluteMobileUpdateWatchdogPlugin: CAPPlugin, CAPBridgedPlug
29278
29718
  ${PLUGIN_END}
29279
29719
  `;
29280
29720
  var configureIos3 = async (config, enabled) => {
29281
- const path = join22(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
29282
- const capacitorConfigPath = join22(config.nativeProjectDirectory, "ios/App/App/capacitor.config.json");
29721
+ const path = join23(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
29722
+ const capacitorConfigPath = join23(config.nativeProjectDirectory, "ios/App/App/capacitor.config.json");
29283
29723
  let source = await readFile20(path, "utf8");
29284
29724
  const launch = source.indexOf("didFinishLaunchingWithOptions");
29285
29725
  const brace = launch < 0 ? -1 : source.indexOf("{", launch);
@@ -29443,91 +29883,650 @@ public final class AbsoluteMobileUpdateWatchdogPlugin extends Plugin {
29443
29883
  }
29444
29884
 
29445
29885
  @PluginMethod
29446
- public void confirm(PluginCall call) {
29447
- if (deadline != null) handler.removeCallbacks(deadline);
29448
- deadline = null;
29449
- call.resolve(new JSObject());
29886
+ public void confirm(PluginCall call) {
29887
+ if (deadline != null) handler.removeCallbacks(deadline);
29888
+ deadline = null;
29889
+ call.resolve(new JSObject());
29890
+ }
29891
+
29892
+ private static final class Recovery {
29893
+ final String previousPath;
29894
+ final boolean hasActive;
29895
+ Recovery(String previousPath, boolean hasActive) {
29896
+ this.previousPath = previousPath;
29897
+ this.hasActive = hasActive;
29898
+ }
29899
+ }
29900
+ }
29901
+ `;
29902
+ var androidActivityInRoot = async (root) => {
29903
+ let entries;
29904
+ try {
29905
+ entries = await readdir5(root, { recursive: true });
29906
+ } catch (error) {
29907
+ if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
29908
+ return;
29909
+ throw error;
29910
+ }
29911
+ const candidate = entries.find((entry) => entry.endsWith("/MainActivity.java") || entry.endsWith("/MainActivity.kt") || entry === "MainActivity.java" || entry === "MainActivity.kt");
29912
+ return candidate ? join23(root, candidate) : undefined;
29913
+ };
29914
+ var androidActivity = async (config) => {
29915
+ const roots = [
29916
+ join23(config.nativeProjectDirectory, "android/app/src/main/java"),
29917
+ join23(config.nativeProjectDirectory, "android/app/src/main/kotlin")
29918
+ ];
29919
+ const candidates = await Promise.all(roots.map(androidActivityInRoot));
29920
+ const candidate = candidates.find((value) => value !== undefined);
29921
+ if (candidate)
29922
+ return candidate;
29923
+ throw new TypeError("Android MainActivity.java or MainActivity.kt was not found.");
29924
+ };
29925
+ var androidManagedRegion = (kotlin, generatedMethod) => {
29926
+ if (generatedMethod)
29927
+ return kotlin ? ` ${START}
29928
+ override fun onCreate(savedInstanceState: android.os.Bundle?) {
29929
+ AbsoluteMobileUpdateWatchdogPlugin.recoverInterruptedBoot(this)
29930
+ registerPlugin(AbsoluteMobileUpdateWatchdogPlugin::class.java)
29931
+ super.onCreate(savedInstanceState)
29932
+ }
29933
+ ${END}
29934
+ ` : ` ${START}
29935
+ @Override
29936
+ public void onCreate(android.os.Bundle savedInstanceState) {
29937
+ AbsoluteMobileUpdateWatchdogPlugin.recoverInterruptedBoot(this);
29938
+ registerPlugin(AbsoluteMobileUpdateWatchdogPlugin.class);
29939
+ super.onCreate(savedInstanceState);
29940
+ }
29941
+ ${END}
29942
+ `;
29943
+ return kotlin ? ` ${START}
29944
+ AbsoluteMobileUpdateWatchdogPlugin.recoverInterruptedBoot(this)
29945
+ registerPlugin(AbsoluteMobileUpdateWatchdogPlugin::class.java)
29946
+ ${END}
29947
+ ` : ` ${START}
29948
+ AbsoluteMobileUpdateWatchdogPlugin.recoverInterruptedBoot(this);
29949
+ registerPlugin(AbsoluteMobileUpdateWatchdogPlugin.class);
29950
+ ${END}
29951
+ `;
29952
+ };
29953
+ var injectAndroidActivity = (source, enabled) => {
29954
+ const kotlin = /\bfun\s+onCreate\s*\(/u.test(source) || source.includes("BridgeActivity()");
29955
+ const existingStart = source.indexOf(START);
29956
+ const existingEnd = source.indexOf(END);
29957
+ if (existingStart < 0 !== existingEnd < 0)
29958
+ throw new TypeError("AbsoluteJS mobile update markers are malformed.");
29959
+ if (existingStart >= 0) {
29960
+ const from = source.lastIndexOf(`
29961
+ `, existingStart) + 1;
29962
+ const newline = source.indexOf(`
29963
+ `, existingEnd + END.length);
29964
+ const through = newline < 0 ? source.length : newline + 1;
29965
+ const owned = source.slice(from, through);
29966
+ const generatedMethod = owned.includes("onCreate(");
29967
+ if (!enabled)
29968
+ return `${source.slice(0, from)}${source.slice(through)}`;
29969
+ const region2 = androidManagedRegion(kotlin, generatedMethod);
29970
+ return `${source.slice(0, from)}${region2}${source.slice(through)}`;
29971
+ }
29972
+ if (!enabled)
29973
+ return source;
29974
+ const onCreate = kotlin ? source.search(/\boverride\s+fun\s+onCreate\s*\([^)]*\)\s*\{/u) : source.search(/\b(?:public|protected)\s+void\s+onCreate\s*\([^)]*\)\s*\{/u);
29975
+ if (onCreate >= 0) {
29976
+ const brace = source.indexOf("{", onCreate);
29977
+ const insert = source.indexOf(`
29978
+ `, brace) + 1;
29979
+ const region2 = kotlin ? ` ${START}
29980
+ AbsoluteMobileUpdateWatchdogPlugin.recoverInterruptedBoot(this)
29981
+ registerPlugin(AbsoluteMobileUpdateWatchdogPlugin::class.java)
29982
+ ${END}
29983
+ ` : ` ${START}
29984
+ AbsoluteMobileUpdateWatchdogPlugin.recoverInterruptedBoot(this);
29985
+ registerPlugin(AbsoluteMobileUpdateWatchdogPlugin.class);
29986
+ ${END}
29987
+ `;
29988
+ return `${source.slice(0, insert)}${region2}${source.slice(insert)}`;
29989
+ }
29990
+ const close = source.lastIndexOf("}");
29991
+ if (close < 0)
29992
+ throw new TypeError("Could not find the Android MainActivity class body.");
29993
+ const region = kotlin ? ` ${START}
29994
+ override fun onCreate(savedInstanceState: android.os.Bundle?) {
29995
+ AbsoluteMobileUpdateWatchdogPlugin.recoverInterruptedBoot(this)
29996
+ registerPlugin(AbsoluteMobileUpdateWatchdogPlugin::class.java)
29997
+ super.onCreate(savedInstanceState)
29998
+ }
29999
+ ${END}
30000
+ ` : ` ${START}
30001
+ @Override
30002
+ public void onCreate(android.os.Bundle savedInstanceState) {
30003
+ AbsoluteMobileUpdateWatchdogPlugin.recoverInterruptedBoot(this);
30004
+ registerPlugin(AbsoluteMobileUpdateWatchdogPlugin.class);
30005
+ super.onCreate(savedInstanceState);
30006
+ }
30007
+ ${END}
30008
+ `;
30009
+ const separator = source[close - 1] === `
30010
+ ` ? "" : `
30011
+ `;
30012
+ return `${source.slice(0, close)}${separator}${region}${source.slice(close)}`;
30013
+ };
30014
+ var configureAndroid3 = async (config, enabled) => {
30015
+ const activityPath = await androidActivity(config);
30016
+ const activity = await readFile20(activityPath, "utf8");
30017
+ const packageName = activity.match(/^\s*package\s+([A-Za-z0-9_.]+)\s*[;\n]/mu)?.[1];
30018
+ if (!packageName)
30019
+ throw new TypeError("Android MainActivity package declaration was not found.");
30020
+ const packagePath = packageName.replaceAll(".", "/");
30021
+ const pluginPath = join23(config.nativeProjectDirectory, "android/app/src/main/java", packagePath, ANDROID_PLUGIN);
30022
+ const changed = await Promise.all([
30023
+ writeChanged(activityPath, injectAndroidActivity(activity, enabled)),
30024
+ enabled ? writeChanged(pluginPath, androidPluginSource(packageName, config.updates?.bootTimeoutMs ?? 20000)) : removeOwnedFile(pluginPath)
30025
+ ]);
30026
+ return changed.some(Boolean);
30027
+ };
30028
+ var applyAbsoluteNativeUpdates = async (config, platforms = config.platforms) => {
30029
+ if (config.engine !== "capacitor")
30030
+ return { changed: false };
30031
+ const enabled = config.updates !== undefined;
30032
+ const changed = [];
30033
+ if (platforms.includes("ios"))
30034
+ changed.push(await configureIos3(config, enabled));
30035
+ if (platforms.includes("android"))
30036
+ changed.push(await configureAndroid3(config, enabled));
30037
+ return { changed: changed.some(Boolean) };
30038
+ };
30039
+ // src/mobile/nativeObservability.ts
30040
+ import {
30041
+ mkdir as mkdir16,
30042
+ readFile as readFile21,
30043
+ readdir as readdir6,
30044
+ rename as rename15,
30045
+ rm as rm12,
30046
+ writeFile as writeFile19
30047
+ } from "fs/promises";
30048
+ import { dirname as dirname16, join as join24 } from "path";
30049
+ var START2 = "// absolutejs:native-observability:start";
30050
+ var END2 = "// absolutejs:native-observability:end";
30051
+ var PLUGIN_START2 = "// absolutejs:native-observability-plugin:start";
30052
+ var PLUGIN_END2 = "// absolutejs:native-observability-plugin:end";
30053
+ var ANDROID_PLUGIN2 = "AbsoluteMobileObservabilityPlugin.java";
30054
+ var optionalSource3 = async (path) => {
30055
+ try {
30056
+ return await readFile21(path, "utf8");
30057
+ } catch (error) {
30058
+ if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
30059
+ return null;
30060
+ throw error;
30061
+ }
30062
+ };
30063
+ var writeChanged2 = async (path, source) => {
30064
+ const current = await optionalSource3(path);
30065
+ if (current === source)
30066
+ return false;
30067
+ await mkdir16(dirname16(path), { recursive: true });
30068
+ if (current === null) {
30069
+ await writeFile19(path, source, { flag: "wx" });
30070
+ return true;
30071
+ }
30072
+ const temporary = `${path}.${crypto.randomUUID()}.tmp`;
30073
+ await writeFile19(temporary, source, { flag: "wx" });
30074
+ await rename15(temporary, path);
30075
+ return true;
30076
+ };
30077
+ var removeOwnedFile2 = async (path) => {
30078
+ if (await optionalSource3(path) === null)
30079
+ return false;
30080
+ await rm12(path);
30081
+ return true;
30082
+ };
30083
+ var isRecord12 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
30084
+ var replaceMarkedRegion2 = (source, start, end, region, insertion, error) => {
30085
+ const existingStart = source.indexOf(start);
30086
+ const existingEnd = source.indexOf(end);
30087
+ if (existingStart < 0 !== existingEnd < 0 || existingStart >= 0 && existingEnd < existingStart)
30088
+ throw new TypeError("AbsoluteJS native observability markers are malformed.");
30089
+ if (existingStart >= 0) {
30090
+ const from = source.lastIndexOf(`
30091
+ `, existingStart) + 1;
30092
+ const newline = source.indexOf(`
30093
+ `, existingEnd + end.length);
30094
+ return `${source.slice(0, from)}${region}${source.slice(newline < 0 ? source.length : newline + 1)}`;
30095
+ }
30096
+ if (!region)
30097
+ return source;
30098
+ if (insertion < 0)
30099
+ throw new TypeError(error);
30100
+ return `${source.slice(0, insertion)}${region}${source.slice(insertion)}`;
30101
+ };
30102
+ var iosPluginRegion2 = `${PLUGIN_START2}
30103
+ @objc(AbsoluteMobileObservabilityPlugin)
30104
+ public final class AbsoluteMobileObservabilityPlugin: CAPPlugin, CAPBridgedPlugin, MXMetricManagerSubscriber {
30105
+ public let identifier = "AbsoluteMobileObservabilityPlugin"
30106
+ public let jsName = "AbsoluteMobileObservability"
30107
+ public let pluginMethods: [CAPPluginMethod] = [
30108
+ CAPPluginMethod(name: "acknowledge", returnType: CAPPluginReturnPromise),
30109
+ CAPPluginMethod(name: "pending", returnType: CAPPluginReturnPromise)
30110
+ ]
30111
+
30112
+ private static let maximumReports = 8
30113
+ private static let maximumReportBytes = 64 * 1024
30114
+ private static let queue = DispatchQueue(label: "dev.absolute.mobile-observability")
30115
+
30116
+ public override func load() {
30117
+ MXMetricManager.shared.add(self)
30118
+ }
30119
+
30120
+ deinit {
30121
+ MXMetricManager.shared.remove(self)
30122
+ }
30123
+
30124
+ private static func root() -> URL? {
30125
+ guard let support = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first else { return nil }
30126
+ let root = support.appendingPathComponent("AbsoluteMobileObservability", isDirectory: true)
30127
+ try? FileManager.default.createDirectory(at: root, withIntermediateDirectories: true)
30128
+ return root
30129
+ }
30130
+
30131
+ private static func files() -> [URL] {
30132
+ guard let root = root() else { return [] }
30133
+ return ((try? FileManager.default.contentsOfDirectory(at: root, includingPropertiesForKeys: nil)) ?? [])
30134
+ .filter { $0.pathExtension == "json" }
30135
+ .sorted { $0.lastPathComponent < $1.lastPathComponent }
30136
+ }
30137
+
30138
+ private static func trim() {
30139
+ let overflow = max(0, files().count - maximumReports)
30140
+ for file in files().prefix(overflow) { try? FileManager.default.removeItem(at: file) }
30141
+ }
30142
+
30143
+ private static func enqueue(kind: String, occurredAt: Double, details: [AnyHashable: Any]) {
30144
+ guard let root = root() else { return }
30145
+ let id = UUID().uuidString.lowercased()
30146
+ var value: [String: Any] = [
30147
+ "details": Dictionary(uniqueKeysWithValues: details.map { (String(describing: $0.key), $0.value) }),
30148
+ "id": id,
30149
+ "kind": kind,
30150
+ "occurredAt": occurredAt,
30151
+ "platform": "ios"
30152
+ ]
30153
+ guard JSONSerialization.isValidJSONObject(value),
30154
+ let original = try? JSONSerialization.data(withJSONObject: value) else { return }
30155
+ if original.count > maximumReportBytes {
30156
+ value["details"] = [
30157
+ "diagnosticPrefix": String(decoding: original.prefix(24 * 1024), as: UTF8.self),
30158
+ "originalBytes": original.count,
30159
+ "truncated": true
30160
+ ]
30161
+ }
30162
+ guard let data = try? JSONSerialization.data(withJSONObject: value),
30163
+ data.count <= maximumReportBytes else { return }
30164
+ let timestamp = String(format: "%013.0f", occurredAt)
30165
+ try? data.write(to: root.appendingPathComponent("\\(timestamp)-\\(id).json"), options: .atomic)
30166
+ trim()
30167
+ }
30168
+
30169
+ public func didReceive(_ payloads: [MXDiagnosticPayload]) {
30170
+ Self.queue.async {
30171
+ for payload in payloads {
30172
+ let at = payload.timeStampEnd.timeIntervalSince1970 * 1000
30173
+ for value in payload.crashDiagnostics ?? [] { Self.enqueue(kind: "crash", occurredAt: at, details: value.dictionaryRepresentation()) }
30174
+ for value in payload.hangDiagnostics ?? [] { Self.enqueue(kind: "hang", occurredAt: at, details: value.dictionaryRepresentation()) }
30175
+ for value in payload.cpuExceptionDiagnostics ?? [] { Self.enqueue(kind: "cpu-exception", occurredAt: at, details: value.dictionaryRepresentation()) }
30176
+ for value in payload.diskWriteExceptionDiagnostics ?? [] { Self.enqueue(kind: "disk-write-exception", occurredAt: at, details: value.dictionaryRepresentation()) }
30177
+ if #available(iOS 16.0, *) {
30178
+ for value in payload.appLaunchDiagnostics ?? [] { Self.enqueue(kind: "app-launch", occurredAt: at, details: value.dictionaryRepresentation()) }
30179
+ }
30180
+ }
30181
+ }
30182
+ }
30183
+
30184
+ @objc public func pending(_ call: CAPPluginCall) {
30185
+ Self.queue.async {
30186
+ let reports: [[String: Any]] = Self.files().compactMap { file in
30187
+ guard let data = try? Data(contentsOf: file),
30188
+ let value = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { return nil }
30189
+ return value
30190
+ }
30191
+ call.resolve(["reports": reports])
30192
+ }
30193
+ }
30194
+
30195
+ @objc public func acknowledge(_ call: CAPPluginCall) {
30196
+ guard let ids = call.getArray("ids", String.self) else {
30197
+ call.reject("Native observability acknowledgement requires report ids.")
30198
+ return
30199
+ }
30200
+ let accepted = Set(ids)
30201
+ Self.queue.async {
30202
+ for file in Self.files() {
30203
+ guard let data = try? Data(contentsOf: file),
30204
+ let value = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
30205
+ let id = value["id"] as? String,
30206
+ accepted.contains(id) else { continue }
30207
+ try? FileManager.default.removeItem(at: file)
30208
+ }
30209
+ call.resolve()
30210
+ }
30211
+ }
30212
+ }
30213
+ ${PLUGIN_END2}
30214
+ `;
30215
+ var configureIos4 = async (config, enabled) => {
30216
+ const path = join24(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
30217
+ const capacitorConfigPath = join24(config.nativeProjectDirectory, "ios/App/App/capacitor.config.json");
30218
+ let source = await readFile21(path, "utf8");
30219
+ if (enabled && !source.includes("import MetricKit")) {
30220
+ const capacitorImport = source.lastIndexOf("import Capacitor");
30221
+ if (capacitorImport < 0)
30222
+ throw new TypeError("Capacitor AppDelegate import was not found.");
30223
+ const newline = source.indexOf(`
30224
+ `, capacitorImport);
30225
+ source = `${source.slice(0, newline + 1)}import MetricKit
30226
+ ${source.slice(newline + 1)}`;
30227
+ }
30228
+ if (!enabled)
30229
+ source = source.replace(`import MetricKit
30230
+ `, "");
30231
+ source = replaceMarkedRegion2(source, PLUGIN_START2, PLUGIN_END2, enabled ? iosPluginRegion2 : "", source.length, "Could not find a safe iOS location for native observability.");
30232
+ const capacitorConfigSource2 = await readFile21(capacitorConfigPath, "utf8");
30233
+ let capacitorConfig;
30234
+ try {
30235
+ const value = JSON.parse(capacitorConfigSource2);
30236
+ if (!isRecord12(value))
30237
+ throw new TypeError;
30238
+ capacitorConfig = value;
30239
+ } catch {
30240
+ throw new TypeError("iOS capacitor.config.json is invalid; run Capacitor sync before projecting native observability.");
30241
+ }
30242
+ const packageClassList = Reflect.get(capacitorConfig, "packageClassList");
30243
+ if (!Array.isArray(packageClassList))
30244
+ throw new TypeError("iOS capacitor.config.json has no packageClassList; run Capacitor sync before projecting native observability.");
30245
+ const classes = packageClassList.filter((value) => typeof value === "string");
30246
+ const nextClasses = enabled ? [...new Set([...classes, "AbsoluteMobileObservabilityPlugin"])] : classes.filter((value) => value !== "AbsoluteMobileObservabilityPlugin");
30247
+ const nextCapacitorConfig = `${JSON.stringify({ ...capacitorConfig, packageClassList: nextClasses }, null, "\t")}
30248
+ `;
30249
+ const changed = await Promise.all([
30250
+ writeChanged2(path, source),
30251
+ writeChanged2(capacitorConfigPath, nextCapacitorConfig)
30252
+ ]);
30253
+ return changed.some(Boolean);
30254
+ };
30255
+ var androidPluginSource2 = (packageName) => `package ${packageName};
30256
+
30257
+ import android.app.ActivityManager;
30258
+ import android.app.ApplicationExitInfo;
30259
+ import android.os.Build;
30260
+ import android.util.Base64;
30261
+ import com.getcapacitor.JSArray;
30262
+ import com.getcapacitor.JSObject;
30263
+ import com.getcapacitor.Plugin;
30264
+ import com.getcapacitor.PluginCall;
30265
+ import com.getcapacitor.PluginMethod;
30266
+ import com.getcapacitor.annotation.CapacitorPlugin;
30267
+ import java.io.ByteArrayOutputStream;
30268
+ import java.io.File;
30269
+ import java.io.FileInputStream;
30270
+ import java.io.FileOutputStream;
30271
+ import java.io.InputStream;
30272
+ import java.nio.charset.StandardCharsets;
30273
+ import java.util.ArrayList;
30274
+ import java.util.HashSet;
30275
+ import java.util.List;
30276
+ import java.util.Set;
30277
+ import org.json.JSONArray;
30278
+ import org.json.JSONObject;
30279
+
30280
+ @CapacitorPlugin(name = "AbsoluteMobileObservability")
30281
+ public final class AbsoluteMobileObservabilityPlugin extends Plugin {
30282
+ private static final int MAX_REPORTS = 8;
30283
+ private static final int MAX_TRACE_BYTES = 32 * 1024;
30284
+ private static final String ACKNOWLEDGED_KEY = "acknowledged";
30285
+ private static final String PREFERENCES = "AbsoluteMobileObservability";
30286
+
30287
+ private File root() {
30288
+ File value = new File(getContext().getFilesDir(), "absolute-mobile-observability");
30289
+ value.mkdirs();
30290
+ return value;
30291
+ }
30292
+
30293
+ private Set<String> acknowledged() {
30294
+ Set<String> result = new HashSet<>();
30295
+ try {
30296
+ JSONArray values = new JSONArray(getContext().getSharedPreferences(PREFERENCES, 0).getString(ACKNOWLEDGED_KEY, "[]"));
30297
+ for (int index = 0; index < values.length(); index++) result.add(values.optString(index));
30298
+ } catch (Exception ignored) {}
30299
+ return result;
30300
+ }
30301
+
30302
+ private void writeAcknowledged(Set<String> values) {
30303
+ List<String> sorted = new ArrayList<>(values);
30304
+ sorted.sort(String::compareTo);
30305
+ if (sorted.size() > 64) sorted = sorted.subList(sorted.size() - 64, sorted.size());
30306
+ getContext().getSharedPreferences(PREFERENCES, 0).edit().putString(ACKNOWLEDGED_KEY, new JSONArray(sorted).toString()).apply();
30307
+ }
30308
+
30309
+ private static String kind(int reason) {
30310
+ if (reason == ApplicationExitInfo.REASON_ANR) return "anr";
30311
+ if (reason == ApplicationExitInfo.REASON_CRASH) return "crash";
30312
+ if (reason == ApplicationExitInfo.REASON_CRASH_NATIVE) return "native-crash";
30313
+ if (reason == ApplicationExitInfo.REASON_LOW_MEMORY) return "low-memory";
30314
+ if (reason == ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE) return "resource-exhaustion";
30315
+ if (reason == ApplicationExitInfo.REASON_INITIALIZATION_FAILURE) return "initialization-failure";
30316
+ if (reason == ApplicationExitInfo.REASON_SIGNALED) return "signal";
30317
+ return null;
30318
+ }
30319
+
30320
+ private static byte[] trace(ApplicationExitInfo info) {
30321
+ try (InputStream input = info.getTraceInputStream(); ByteArrayOutputStream output = new ByteArrayOutputStream()) {
30322
+ if (input == null) return null;
30323
+ byte[] buffer = new byte[4096];
30324
+ int total = 0;
30325
+ int read;
30326
+ while ((read = input.read(buffer, 0, Math.min(buffer.length, MAX_TRACE_BYTES + 1 - total))) > 0) {
30327
+ output.write(buffer, 0, read);
30328
+ total += read;
30329
+ if (total > MAX_TRACE_BYTES) return null;
30330
+ }
30331
+ return output.toByteArray();
30332
+ } catch (Exception ignored) {
30333
+ return null;
30334
+ }
30335
+ }
30336
+
30337
+ private static JSONObject report(ApplicationExitInfo info) {
30338
+ String kind = kind(info.getReason());
30339
+ if (kind == null) return null;
30340
+ String id = "android:" + info.getTimestamp() + ":" + info.getReason() + ":" + info.getStatus();
30341
+ JSONObject details = new JSONObject();
30342
+ try {
30343
+ details.put("importance", info.getImportance());
30344
+ details.put("pssKb", info.getPss());
30345
+ details.put("reason", info.getReason());
30346
+ details.put("rssKb", info.getRss());
30347
+ details.put("status", info.getStatus());
30348
+ String description = info.getDescription();
30349
+ if (description != null) details.put("description", description);
30350
+ byte[] trace = trace(info);
30351
+ if (trace != null && trace.length > 0) {
30352
+ if (info.getReason() == ApplicationExitInfo.REASON_CRASH_NATIVE) {
30353
+ details.put("trace", Base64.encodeToString(trace, Base64.NO_WRAP));
30354
+ details.put("traceEncoding", "android-tombstone-protobuf-base64");
30355
+ } else {
30356
+ details.put("trace", new String(trace, StandardCharsets.UTF_8));
30357
+ details.put("traceEncoding", "utf-8");
30358
+ }
30359
+ }
30360
+ return new JSONObject()
30361
+ .put("details", details)
30362
+ .put("id", id)
30363
+ .put("kind", kind)
30364
+ .put("occurredAt", info.getTimestamp())
30365
+ .put("platform", "android");
30366
+ } catch (Exception ignored) {
30367
+ return null;
30368
+ }
30369
+ }
30370
+
30371
+ private Set<String> queuedIds() {
30372
+ Set<String> ids = new HashSet<>();
30373
+ for (File file : files()) {
30374
+ try (InputStream input = new FileInputStream(file)) {
30375
+ JSONObject report = new JSONObject(new String(read(input), StandardCharsets.UTF_8));
30376
+ ids.add(report.optString("id"));
30377
+ } catch (Exception ignored) {}
30378
+ }
30379
+ return ids;
30380
+ }
30381
+
30382
+ private File[] files() {
30383
+ File[] values = root().listFiles((directory, name) -> name.endsWith(".json"));
30384
+ if (values == null) return new File[0];
30385
+ java.util.Arrays.sort(values, java.util.Comparator.comparing(File::getName));
30386
+ return values;
30387
+ }
30388
+
30389
+ private static byte[] read(InputStream input) throws java.io.IOException {
30390
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
30391
+ byte[] buffer = new byte[4096];
30392
+ int count;
30393
+ while ((count = input.read(buffer)) >= 0) output.write(buffer, 0, count);
30394
+ return output.toByteArray();
30395
+ }
30396
+
30397
+ private void collect() {
30398
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) return;
30399
+ ActivityManager manager = getContext().getSystemService(ActivityManager.class);
30400
+ if (manager == null) return;
30401
+ Set<String> ignored = acknowledged();
30402
+ ignored.addAll(queuedIds());
30403
+ List<ApplicationExitInfo> exits = manager.getHistoricalProcessExitReasons(null, 0, 16);
30404
+ for (ApplicationExitInfo exit : exits) {
30405
+ JSONObject report = report(exit);
30406
+ if (report == null || ignored.contains(report.optString("id"))) continue;
30407
+ String name = String.format(java.util.Locale.ROOT, "%013d-%s.json", exit.getTimestamp(), java.util.UUID.randomUUID());
30408
+ File target = new File(root(), name);
30409
+ try (FileOutputStream output = new FileOutputStream(target)) {
30410
+ output.write(report.toString().getBytes(StandardCharsets.UTF_8));
30411
+ ignored.add(report.optString("id"));
30412
+ } catch (Exception ignoredWrite) {}
30413
+ }
30414
+ File[] files = files();
30415
+ for (int index = 0; index < files.length - MAX_REPORTS; index++) files[index].delete();
30416
+ }
30417
+
30418
+ @PluginMethod
30419
+ public void pending(PluginCall call) {
30420
+ collect();
30421
+ JSArray reports = new JSArray();
30422
+ for (File file : files()) {
30423
+ try (InputStream input = new FileInputStream(file)) {
30424
+ reports.put(new JSONObject(new String(read(input), StandardCharsets.UTF_8)));
30425
+ } catch (Exception ignored) {}
30426
+ }
30427
+ call.resolve(new JSObject().put("reports", reports));
29450
30428
  }
29451
30429
 
29452
- private static final class Recovery {
29453
- final String previousPath;
29454
- final boolean hasActive;
29455
- Recovery(String previousPath, boolean hasActive) {
29456
- this.previousPath = previousPath;
29457
- this.hasActive = hasActive;
30430
+ @PluginMethod
30431
+ public void crashForTesting(PluginCall call) {
30432
+ boolean debuggable = (getContext().getApplicationInfo().flags & android.content.pm.ApplicationInfo.FLAG_DEBUGGABLE) != 0;
30433
+ if (!debuggable) {
30434
+ call.reject("Native crash injection is available only in debuggable builds.");
30435
+ return;
30436
+ }
30437
+ new android.os.Handler(android.os.Looper.getMainLooper()).post(() -> {
30438
+ throw new RuntimeException("AbsoluteJS native observability acceptance crash");
30439
+ });
30440
+ }
30441
+
30442
+ @PluginMethod
30443
+ public void acknowledge(PluginCall call) {
30444
+ JSArray ids = call.getArray("ids");
30445
+ if (ids == null) {
30446
+ call.reject("Native observability acknowledgement requires report ids.");
30447
+ return;
30448
+ }
30449
+ Set<String> accepted = new HashSet<>();
30450
+ for (int index = 0; index < ids.length(); index++) accepted.add(ids.optString(index));
30451
+ Set<String> acknowledged = acknowledged();
30452
+ for (File file : files()) {
30453
+ try (InputStream input = new FileInputStream(file)) {
30454
+ JSONObject report = new JSONObject(new String(read(input), StandardCharsets.UTF_8));
30455
+ String id = report.optString("id");
30456
+ if (accepted.contains(id)) {
30457
+ acknowledged.add(id);
30458
+ file.delete();
30459
+ }
30460
+ } catch (Exception ignored) {}
29458
30461
  }
30462
+ writeAcknowledged(acknowledged);
30463
+ call.resolve();
29459
30464
  }
29460
30465
  }
29461
30466
  `;
29462
- var androidActivityInRoot = async (root) => {
30467
+ var androidActivityInRoot2 = async (root) => {
29463
30468
  let entries;
29464
30469
  try {
29465
- entries = await readdir5(root, { recursive: true });
30470
+ entries = await readdir6(root, { recursive: true });
29466
30471
  } catch (error) {
29467
30472
  if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
29468
30473
  return;
29469
30474
  throw error;
29470
30475
  }
29471
30476
  const candidate = entries.find((entry) => entry.endsWith("/MainActivity.java") || entry.endsWith("/MainActivity.kt") || entry === "MainActivity.java" || entry === "MainActivity.kt");
29472
- return candidate ? join22(root, candidate) : undefined;
30477
+ return candidate ? join24(root, candidate) : undefined;
29473
30478
  };
29474
- var androidActivity = async (config) => {
30479
+ var androidActivity2 = async (config) => {
29475
30480
  const roots = [
29476
- join22(config.nativeProjectDirectory, "android/app/src/main/java"),
29477
- join22(config.nativeProjectDirectory, "android/app/src/main/kotlin")
30481
+ join24(config.nativeProjectDirectory, "android/app/src/main/java"),
30482
+ join24(config.nativeProjectDirectory, "android/app/src/main/kotlin")
29478
30483
  ];
29479
- const candidates = await Promise.all(roots.map(androidActivityInRoot));
30484
+ const candidates = await Promise.all(roots.map(androidActivityInRoot2));
29480
30485
  const candidate = candidates.find((value) => value !== undefined);
29481
30486
  if (candidate)
29482
30487
  return candidate;
29483
30488
  throw new TypeError("Android MainActivity.java or MainActivity.kt was not found.");
29484
30489
  };
29485
- var androidManagedRegion = (kotlin, generatedMethod) => {
30490
+ var androidRegion2 = (kotlin, generatedMethod) => {
29486
30491
  if (generatedMethod)
29487
- return kotlin ? ` ${START}
30492
+ return kotlin ? ` ${START2}
29488
30493
  override fun onCreate(savedInstanceState: android.os.Bundle?) {
29489
- AbsoluteMobileUpdateWatchdogPlugin.recoverInterruptedBoot(this)
29490
- registerPlugin(AbsoluteMobileUpdateWatchdogPlugin::class.java)
30494
+ registerPlugin(AbsoluteMobileObservabilityPlugin::class.java)
29491
30495
  super.onCreate(savedInstanceState)
29492
30496
  }
29493
- ${END}
29494
- ` : ` ${START}
30497
+ ${END2}
30498
+ ` : ` ${START2}
29495
30499
  @Override
29496
30500
  public void onCreate(android.os.Bundle savedInstanceState) {
29497
- AbsoluteMobileUpdateWatchdogPlugin.recoverInterruptedBoot(this);
29498
- registerPlugin(AbsoluteMobileUpdateWatchdogPlugin.class);
30501
+ registerPlugin(AbsoluteMobileObservabilityPlugin.class);
29499
30502
  super.onCreate(savedInstanceState);
29500
30503
  }
29501
- ${END}
30504
+ ${END2}
29502
30505
  `;
29503
- return kotlin ? ` ${START}
29504
- AbsoluteMobileUpdateWatchdogPlugin.recoverInterruptedBoot(this)
29505
- registerPlugin(AbsoluteMobileUpdateWatchdogPlugin::class.java)
29506
- ${END}
29507
- ` : ` ${START}
29508
- AbsoluteMobileUpdateWatchdogPlugin.recoverInterruptedBoot(this);
29509
- registerPlugin(AbsoluteMobileUpdateWatchdogPlugin.class);
29510
- ${END}
30506
+ return kotlin ? ` ${START2}
30507
+ registerPlugin(AbsoluteMobileObservabilityPlugin::class.java)
30508
+ ${END2}
30509
+ ` : ` ${START2}
30510
+ registerPlugin(AbsoluteMobileObservabilityPlugin.class);
30511
+ ${END2}
29511
30512
  `;
29512
30513
  };
29513
- var injectAndroidActivity = (source, enabled) => {
30514
+ var injectAndroidActivity2 = (source, enabled) => {
29514
30515
  const kotlin = /\bfun\s+onCreate\s*\(/u.test(source) || source.includes("BridgeActivity()");
29515
- const existingStart = source.indexOf(START);
29516
- const existingEnd = source.indexOf(END);
30516
+ const existingStart = source.indexOf(START2);
30517
+ const existingEnd = source.indexOf(END2);
29517
30518
  if (existingStart < 0 !== existingEnd < 0)
29518
- throw new TypeError("AbsoluteJS mobile update markers are malformed.");
30519
+ throw new TypeError("AbsoluteJS native observability markers are malformed.");
29519
30520
  if (existingStart >= 0) {
29520
30521
  const from = source.lastIndexOf(`
29521
30522
  `, existingStart) + 1;
29522
30523
  const newline = source.indexOf(`
29523
- `, existingEnd + END.length);
30524
+ `, existingEnd + END2.length);
29524
30525
  const through = newline < 0 ? source.length : newline + 1;
29525
- const owned = source.slice(from, through);
29526
- const generatedMethod = owned.includes("onCreate(");
30526
+ const generatedMethod = source.slice(from, through).includes("onCreate(");
29527
30527
  if (!enabled)
29528
30528
  return `${source.slice(0, from)}${source.slice(through)}`;
29529
- const region2 = androidManagedRegion(kotlin, generatedMethod);
29530
- return `${source.slice(0, from)}${region2}${source.slice(through)}`;
30529
+ return `${source.slice(0, from)}${androidRegion2(kotlin, generatedMethod)}${source.slice(through)}`;
29531
30530
  }
29532
30531
  if (!enabled)
29533
30532
  return source;
@@ -29536,64 +30535,38 @@ var injectAndroidActivity = (source, enabled) => {
29536
30535
  const brace = source.indexOf("{", onCreate);
29537
30536
  const insert = source.indexOf(`
29538
30537
  `, brace) + 1;
29539
- const region2 = kotlin ? ` ${START}
29540
- AbsoluteMobileUpdateWatchdogPlugin.recoverInterruptedBoot(this)
29541
- registerPlugin(AbsoluteMobileUpdateWatchdogPlugin::class.java)
29542
- ${END}
29543
- ` : ` ${START}
29544
- AbsoluteMobileUpdateWatchdogPlugin.recoverInterruptedBoot(this);
29545
- registerPlugin(AbsoluteMobileUpdateWatchdogPlugin.class);
29546
- ${END}
29547
- `;
29548
- return `${source.slice(0, insert)}${region2}${source.slice(insert)}`;
30538
+ return `${source.slice(0, insert)}${androidRegion2(kotlin, false)}${source.slice(insert)}`;
29549
30539
  }
29550
30540
  const close = source.lastIndexOf("}");
29551
30541
  if (close < 0)
29552
30542
  throw new TypeError("Could not find the Android MainActivity class body.");
29553
- const region = kotlin ? ` ${START}
29554
- override fun onCreate(savedInstanceState: android.os.Bundle?) {
29555
- AbsoluteMobileUpdateWatchdogPlugin.recoverInterruptedBoot(this)
29556
- registerPlugin(AbsoluteMobileUpdateWatchdogPlugin::class.java)
29557
- super.onCreate(savedInstanceState)
29558
- }
29559
- ${END}
29560
- ` : ` ${START}
29561
- @Override
29562
- public void onCreate(android.os.Bundle savedInstanceState) {
29563
- AbsoluteMobileUpdateWatchdogPlugin.recoverInterruptedBoot(this);
29564
- registerPlugin(AbsoluteMobileUpdateWatchdogPlugin.class);
29565
- super.onCreate(savedInstanceState);
29566
- }
29567
- ${END}
29568
- `;
29569
30543
  const separator = source[close - 1] === `
29570
30544
  ` ? "" : `
29571
30545
  `;
29572
- return `${source.slice(0, close)}${separator}${region}${source.slice(close)}`;
30546
+ return `${source.slice(0, close)}${separator}${androidRegion2(kotlin, true)}${source.slice(close)}`;
29573
30547
  };
29574
- var configureAndroid3 = async (config, enabled) => {
29575
- const activityPath = await androidActivity(config);
29576
- const activity = await readFile20(activityPath, "utf8");
30548
+ var configureAndroid4 = async (config, enabled) => {
30549
+ const activityPath = await androidActivity2(config);
30550
+ const activity = await readFile21(activityPath, "utf8");
29577
30551
  const packageName = activity.match(/^\s*package\s+([A-Za-z0-9_.]+)\s*[;\n]/mu)?.[1];
29578
30552
  if (!packageName)
29579
30553
  throw new TypeError("Android MainActivity package declaration was not found.");
29580
- const packagePath = packageName.replaceAll(".", "/");
29581
- const pluginPath = join22(config.nativeProjectDirectory, "android/app/src/main/java", packagePath, ANDROID_PLUGIN);
30554
+ const pluginPath = join24(config.nativeProjectDirectory, "android/app/src/main/java", packageName.replaceAll(".", "/"), ANDROID_PLUGIN2);
29582
30555
  const changed = await Promise.all([
29583
- writeChanged(activityPath, injectAndroidActivity(activity, enabled)),
29584
- enabled ? writeChanged(pluginPath, androidPluginSource(packageName, config.updates?.bootTimeoutMs ?? 20000)) : removeOwnedFile(pluginPath)
30556
+ writeChanged2(activityPath, injectAndroidActivity2(activity, enabled)),
30557
+ enabled ? writeChanged2(pluginPath, androidPluginSource2(packageName)) : removeOwnedFile2(pluginPath)
29585
30558
  ]);
29586
30559
  return changed.some(Boolean);
29587
30560
  };
29588
- var applyAbsoluteNativeUpdates = async (config, platforms = config.platforms) => {
30561
+ var applyAbsoluteNativeObservability = async (config, platforms = config.platforms) => {
29589
30562
  if (config.engine !== "capacitor")
29590
30563
  return { changed: false };
29591
- const enabled = config.updates !== undefined;
30564
+ const enabled = config.observability !== undefined;
29592
30565
  const changed = [];
29593
30566
  if (platforms.includes("ios"))
29594
- changed.push(await configureIos3(config, enabled));
30567
+ changed.push(await configureIos4(config, enabled));
29595
30568
  if (platforms.includes("android"))
29596
- changed.push(await configureAndroid3(config, enabled));
30569
+ changed.push(await configureAndroid4(config, enabled));
29597
30570
  return { changed: changed.some(Boolean) };
29598
30571
  };
29599
30572
  // src/mobile/releasePublisher.ts
@@ -29621,8 +30594,8 @@ var prepareAbsoluteAndroidRelease = async (publisher, options) => {
29621
30594
  }
29622
30595
  return versionCode;
29623
30596
  };
29624
- var isRecord12 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
29625
- var isPublisher = (value) => isRecord12(value) && typeof value.publish === "function";
30597
+ var isRecord13 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
30598
+ var isPublisher = (value) => isRecord13(value) && typeof value.publish === "function";
29626
30599
  var publisherModulePath = (projectRoot, requested) => {
29627
30600
  const root = resolve16(projectRoot);
29628
30601
  const path = resolve16(root, requested);
@@ -29638,7 +30611,7 @@ var loadAbsoluteNativeReleasePublisher = async (projectRoot, requestedModulePath
29638
30611
  throw new TypeError(`Native release registry module does not exist: ${modulePath}`);
29639
30612
  });
29640
30613
  const loaded = await import(pathToFileURL3(modulePath).href);
29641
- const publisher = isRecord12(loaded) ? loaded.default ?? loaded.registry : undefined;
30614
+ const publisher = isRecord13(loaded) ? loaded.default ?? loaded.registry : undefined;
29642
30615
  if (!isPublisher(publisher)) {
29643
30616
  throw new TypeError("Native release registry module must default-export a registry with publish(options).");
29644
30617
  }
@@ -29696,7 +30669,7 @@ var publishAbsoluteIosRelease = async (options) => {
29696
30669
  };
29697
30670
  // src/mobile/routeMetadataTransform.ts
29698
30671
  import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
29699
- import { dirname as dirname16, extname as extname6, relative as relative14, resolve as resolve17 } from "path";
30672
+ import { dirname as dirname17, extname as extname6, relative as relative14, resolve as resolve17 } from "path";
29700
30673
  import ts2 from "typescript";
29701
30674
  var ROUTE_METHODS = new Set(["get", "head"]);
29702
30675
  var SOURCE_FILTER = /\.[cm]?[jt]sx?$/;
@@ -29747,7 +30720,7 @@ var PAGE_HANDLERS = new Map([
29747
30720
  ]
29748
30721
  ]);
29749
30722
  var posixPath = (value) => value.replace(/\\/g, "/");
29750
- var findTsconfig = (entry, projectRoot) => ts2.findConfigFile(dirname16(entry), existsSync7, "tsconfig.json") ?? ts2.findConfigFile(projectRoot, existsSync7, "tsconfig.json");
30723
+ var findTsconfig = (entry, projectRoot) => ts2.findConfigFile(dirname17(entry), existsSync7, "tsconfig.json") ?? ts2.findConfigFile(projectRoot, existsSync7, "tsconfig.json");
29751
30724
  var createProgram = (entry, projectRoot) => {
29752
30725
  const configPath2 = findTsconfig(entry, projectRoot);
29753
30726
  if (!configPath2) {
@@ -29759,7 +30732,7 @@ var createProgram = (entry, projectRoot) => {
29759
30732
  target: ts2.ScriptTarget.ESNext
29760
30733
  });
29761
30734
  }
29762
- const parsed = ts2.parseJsonConfigFileContent(ts2.readConfigFile(configPath2, (path) => readFileSync8(path, "utf8")).config, ts2.sys, dirname16(configPath2));
30735
+ const parsed = ts2.parseJsonConfigFileContent(ts2.readConfigFile(configPath2, (path) => readFileSync8(path, "utf8")).config, ts2.sys, dirname17(configPath2));
29763
30736
  if (!parsed.fileNames.includes(entry))
29764
30737
  parsed.fileNames.push(entry);
29765
30738
  return ts2.createProgram(parsed.fileNames, parsed.options);
@@ -30428,6 +31401,627 @@ var http = createAbsoluteHttpClient();
30428
31401
 
30429
31402
  // src/mobile/shellHttp.ts
30430
31403
  var installAbsoluteMobileShellHttp = (origin, fetch2 = globalThis.fetch) => installAbsoluteHttpTransport(createAbsoluteHttpTransport({ fetch: fetch2, origin, runtime: "native" }));
31404
+ // node_modules/@capacitor/core/dist/index.js
31405
+ /*! Capacitor: https://capacitorjs.com/ - MIT License */
31406
+ var ExceptionCode;
31407
+ (function(ExceptionCode2) {
31408
+ ExceptionCode2["Unimplemented"] = "UNIMPLEMENTED";
31409
+ ExceptionCode2["Unavailable"] = "UNAVAILABLE";
31410
+ })(ExceptionCode || (ExceptionCode = {}));
31411
+
31412
+ class CapacitorException extends Error {
31413
+ constructor(message, code, data) {
31414
+ super(message);
31415
+ this.message = message;
31416
+ this.code = code;
31417
+ this.data = data;
31418
+ }
31419
+ }
31420
+ var getPlatformId = (win) => {
31421
+ var _a, _b;
31422
+ if (win === null || win === undefined ? undefined : win.androidBridge) {
31423
+ return "android";
31424
+ } else if ((_b = (_a = win === null || win === undefined ? undefined : win.webkit) === null || _a === undefined ? undefined : _a.messageHandlers) === null || _b === undefined ? undefined : _b.bridge) {
31425
+ return "ios";
31426
+ } else {
31427
+ return "web";
31428
+ }
31429
+ };
31430
+ var createCapacitor = (win) => {
31431
+ const capCustomPlatform = win.CapacitorCustomPlatform || null;
31432
+ const cap = win.Capacitor || {};
31433
+ const Plugins = cap.Plugins = cap.Plugins || {};
31434
+ const getPlatform = () => {
31435
+ return capCustomPlatform !== null ? capCustomPlatform.name : getPlatformId(win);
31436
+ };
31437
+ const isNativePlatform = () => getPlatform() !== "web";
31438
+ const isPluginAvailable = (pluginName) => {
31439
+ const plugin = registeredPlugins.get(pluginName);
31440
+ if (plugin === null || plugin === undefined ? undefined : plugin.platforms.has(getPlatform())) {
31441
+ return true;
31442
+ }
31443
+ if (getPluginHeader(pluginName)) {
31444
+ return true;
31445
+ }
31446
+ return false;
31447
+ };
31448
+ const getPluginHeader = (pluginName) => {
31449
+ var _a;
31450
+ return (_a = cap.PluginHeaders) === null || _a === undefined ? undefined : _a.find((h) => h.name === pluginName);
31451
+ };
31452
+ const handleError = (err) => win.console.error(err);
31453
+ const registeredPlugins = new Map;
31454
+ const registerPlugin = (pluginName, jsImplementations = {}) => {
31455
+ const registeredPlugin = registeredPlugins.get(pluginName);
31456
+ if (registeredPlugin) {
31457
+ console.warn(`Capacitor plugin "${pluginName}" already registered. Cannot register plugins twice.`);
31458
+ return registeredPlugin.proxy;
31459
+ }
31460
+ const platform4 = getPlatform();
31461
+ const pluginHeader = getPluginHeader(pluginName);
31462
+ let jsImplementation;
31463
+ const loadPluginImplementation = async () => {
31464
+ if (!jsImplementation && platform4 in jsImplementations) {
31465
+ jsImplementation = typeof jsImplementations[platform4] === "function" ? jsImplementation = await jsImplementations[platform4]() : jsImplementation = jsImplementations[platform4];
31466
+ } else if (capCustomPlatform !== null && !jsImplementation && "web" in jsImplementations) {
31467
+ jsImplementation = typeof jsImplementations["web"] === "function" ? jsImplementation = await jsImplementations["web"]() : jsImplementation = jsImplementations["web"];
31468
+ }
31469
+ return jsImplementation;
31470
+ };
31471
+ const createPluginMethod = (impl, prop) => {
31472
+ var _a, _b;
31473
+ if (pluginHeader) {
31474
+ const methodHeader = pluginHeader === null || pluginHeader === undefined ? undefined : pluginHeader.methods.find((m) => prop === m.name);
31475
+ if (methodHeader) {
31476
+ if (methodHeader.rtype === "promise") {
31477
+ return (options) => cap.nativePromise(pluginName, prop.toString(), options);
31478
+ } else {
31479
+ return (options, callback) => cap.nativeCallback(pluginName, prop.toString(), options, callback);
31480
+ }
31481
+ } else if (impl) {
31482
+ return (_a = impl[prop]) === null || _a === undefined ? undefined : _a.bind(impl);
31483
+ }
31484
+ } else if (impl) {
31485
+ return (_b = impl[prop]) === null || _b === undefined ? undefined : _b.bind(impl);
31486
+ } else {
31487
+ throw new CapacitorException(`"${pluginName}" plugin is not implemented on ${platform4}`, ExceptionCode.Unimplemented);
31488
+ }
31489
+ };
31490
+ const createPluginMethodWrapper = (prop) => {
31491
+ let remove;
31492
+ const wrapper = (...args) => {
31493
+ const p = loadPluginImplementation().then((impl) => {
31494
+ const fn = createPluginMethod(impl, prop);
31495
+ if (fn) {
31496
+ const p2 = fn(...args);
31497
+ remove = p2 === null || p2 === undefined ? undefined : p2.remove;
31498
+ return p2;
31499
+ } else {
31500
+ throw new CapacitorException(`"${pluginName}.${prop}()" is not implemented on ${platform4}`, ExceptionCode.Unimplemented);
31501
+ }
31502
+ });
31503
+ if (prop === "addListener") {
31504
+ p.remove = async () => remove();
31505
+ }
31506
+ return p;
31507
+ };
31508
+ wrapper.toString = () => `${prop.toString()}() { [capacitor code] }`;
31509
+ Object.defineProperty(wrapper, "name", {
31510
+ value: prop,
31511
+ writable: false,
31512
+ configurable: false
31513
+ });
31514
+ return wrapper;
31515
+ };
31516
+ const addListener = createPluginMethodWrapper("addListener");
31517
+ const removeListener = createPluginMethodWrapper("removeListener");
31518
+ const addListenerNative = (eventName, callback) => {
31519
+ const call = addListener({ eventName }, callback);
31520
+ const remove = async () => {
31521
+ const callbackId = await call;
31522
+ removeListener({
31523
+ eventName,
31524
+ callbackId
31525
+ }, callback);
31526
+ };
31527
+ const p = new Promise((resolve18) => call.then(() => resolve18({ remove })));
31528
+ p.remove = async () => {
31529
+ console.warn(`Using addListener() without 'await' is deprecated.`);
31530
+ await remove();
31531
+ };
31532
+ return p;
31533
+ };
31534
+ const proxy = new Proxy({}, {
31535
+ get(_, prop) {
31536
+ switch (prop) {
31537
+ case "$$typeof":
31538
+ return;
31539
+ case "toJSON":
31540
+ return () => ({});
31541
+ case "addListener":
31542
+ return pluginHeader ? addListenerNative : addListener;
31543
+ case "removeListener":
31544
+ return removeListener;
31545
+ default:
31546
+ return createPluginMethodWrapper(prop);
31547
+ }
31548
+ }
31549
+ });
31550
+ Plugins[pluginName] = proxy;
31551
+ registeredPlugins.set(pluginName, {
31552
+ name: pluginName,
31553
+ proxy,
31554
+ platforms: new Set([...Object.keys(jsImplementations), ...pluginHeader ? [platform4] : []])
31555
+ });
31556
+ return proxy;
31557
+ };
31558
+ if (!cap.convertFileSrc) {
31559
+ cap.convertFileSrc = (filePath) => filePath;
31560
+ }
31561
+ cap.getPlatform = getPlatform;
31562
+ cap.handleError = handleError;
31563
+ cap.isNativePlatform = isNativePlatform;
31564
+ cap.isPluginAvailable = isPluginAvailable;
31565
+ cap.registerPlugin = registerPlugin;
31566
+ cap.Exception = CapacitorException;
31567
+ cap.DEBUG = !!cap.DEBUG;
31568
+ cap.isLoggingEnabled = !!cap.isLoggingEnabled;
31569
+ return cap;
31570
+ };
31571
+ var initCapacitorGlobal = (win) => win.Capacitor = createCapacitor(win);
31572
+ var Capacitor = /* @__PURE__ */ initCapacitorGlobal(typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : {});
31573
+ var registerPlugin = Capacitor.registerPlugin;
31574
+
31575
+ class WebPlugin {
31576
+ constructor() {
31577
+ this.listeners = {};
31578
+ this.retainedEventArguments = {};
31579
+ this.windowListeners = {};
31580
+ }
31581
+ addListener(eventName, listenerFunc) {
31582
+ let firstListener = false;
31583
+ const listeners = this.listeners[eventName];
31584
+ if (!listeners) {
31585
+ this.listeners[eventName] = [];
31586
+ firstListener = true;
31587
+ }
31588
+ this.listeners[eventName].push(listenerFunc);
31589
+ const windowListener = this.windowListeners[eventName];
31590
+ if (windowListener && !windowListener.registered) {
31591
+ this.addWindowListener(windowListener);
31592
+ }
31593
+ if (firstListener) {
31594
+ this.sendRetainedArgumentsForEvent(eventName);
31595
+ }
31596
+ const remove = async () => this.removeListener(eventName, listenerFunc);
31597
+ const p = Promise.resolve({ remove });
31598
+ return p;
31599
+ }
31600
+ async removeAllListeners() {
31601
+ this.listeners = {};
31602
+ for (const listener in this.windowListeners) {
31603
+ this.removeWindowListener(this.windowListeners[listener]);
31604
+ }
31605
+ this.windowListeners = {};
31606
+ }
31607
+ notifyListeners(eventName, data, retainUntilConsumed) {
31608
+ const listeners = this.listeners[eventName];
31609
+ if (!listeners) {
31610
+ if (retainUntilConsumed) {
31611
+ let args = this.retainedEventArguments[eventName];
31612
+ if (!args) {
31613
+ args = [];
31614
+ }
31615
+ args.push(data);
31616
+ this.retainedEventArguments[eventName] = args;
31617
+ }
31618
+ return;
31619
+ }
31620
+ listeners.forEach((listener) => listener(data));
31621
+ }
31622
+ hasListeners(eventName) {
31623
+ var _a;
31624
+ return !!((_a = this.listeners[eventName]) === null || _a === undefined ? undefined : _a.length);
31625
+ }
31626
+ registerWindowListener(windowEventName, pluginEventName) {
31627
+ this.windowListeners[pluginEventName] = {
31628
+ registered: false,
31629
+ windowEventName,
31630
+ pluginEventName,
31631
+ handler: (event) => {
31632
+ this.notifyListeners(pluginEventName, event);
31633
+ }
31634
+ };
31635
+ }
31636
+ unimplemented(msg = "not implemented") {
31637
+ return new Capacitor.Exception(msg, ExceptionCode.Unimplemented);
31638
+ }
31639
+ unavailable(msg = "not available") {
31640
+ return new Capacitor.Exception(msg, ExceptionCode.Unavailable);
31641
+ }
31642
+ async removeListener(eventName, listenerFunc) {
31643
+ const listeners = this.listeners[eventName];
31644
+ if (!listeners) {
31645
+ return;
31646
+ }
31647
+ const index = listeners.indexOf(listenerFunc);
31648
+ this.listeners[eventName].splice(index, 1);
31649
+ if (!this.listeners[eventName].length) {
31650
+ this.removeWindowListener(this.windowListeners[eventName]);
31651
+ }
31652
+ }
31653
+ addWindowListener(handle) {
31654
+ window.addEventListener(handle.windowEventName, handle.handler);
31655
+ handle.registered = true;
31656
+ }
31657
+ removeWindowListener(handle) {
31658
+ if (!handle) {
31659
+ return;
31660
+ }
31661
+ window.removeEventListener(handle.windowEventName, handle.handler);
31662
+ handle.registered = false;
31663
+ }
31664
+ sendRetainedArgumentsForEvent(eventName) {
31665
+ const args = this.retainedEventArguments[eventName];
31666
+ if (!args) {
31667
+ return;
31668
+ }
31669
+ delete this.retainedEventArguments[eventName];
31670
+ args.forEach((arg) => {
31671
+ this.notifyListeners(eventName, arg);
31672
+ });
31673
+ }
31674
+ }
31675
+ var encode = (str) => encodeURIComponent(str).replace(/%(2[346B]|5E|60|7C)/g, decodeURIComponent).replace(/[()]/g, escape);
31676
+ var decode = (str) => str.replace(/(%[\dA-F]{2})+/gi, decodeURIComponent);
31677
+
31678
+ class CapacitorCookiesPluginWeb extends WebPlugin {
31679
+ async getCookies() {
31680
+ const cookies = document.cookie;
31681
+ const cookieMap = {};
31682
+ cookies.split(";").forEach((cookie) => {
31683
+ if (cookie.length <= 0)
31684
+ return;
31685
+ let [key, value] = cookie.replace(/=/, "CAP_COOKIE").split("CAP_COOKIE");
31686
+ key = decode(key).trim();
31687
+ value = decode(value).trim();
31688
+ cookieMap[key] = value;
31689
+ });
31690
+ return cookieMap;
31691
+ }
31692
+ async setCookie(options) {
31693
+ try {
31694
+ const encodedKey = encode(options.key);
31695
+ const encodedValue = encode(options.value);
31696
+ const expires = options.expires ? `; expires=${options.expires.replace("expires=", "")}` : "";
31697
+ const path = (options.path || "/").replace("path=", "");
31698
+ const domain = options.url != null && options.url.length > 0 ? `domain=${options.url}` : "";
31699
+ document.cookie = `${encodedKey}=${encodedValue || ""}${expires}; path=${path}; ${domain};`;
31700
+ } catch (error) {
31701
+ return Promise.reject(error);
31702
+ }
31703
+ }
31704
+ async deleteCookie(options) {
31705
+ try {
31706
+ document.cookie = `${options.key}=; Max-Age=0`;
31707
+ } catch (error) {
31708
+ return Promise.reject(error);
31709
+ }
31710
+ }
31711
+ async clearCookies() {
31712
+ try {
31713
+ const cookies = document.cookie.split(";") || [];
31714
+ for (const cookie of cookies) {
31715
+ document.cookie = cookie.replace(/^ +/, "").replace(/=.*/, `=;expires=${new Date().toUTCString()};path=/`);
31716
+ }
31717
+ } catch (error) {
31718
+ return Promise.reject(error);
31719
+ }
31720
+ }
31721
+ async clearAllCookies() {
31722
+ try {
31723
+ await this.clearCookies();
31724
+ } catch (error) {
31725
+ return Promise.reject(error);
31726
+ }
31727
+ }
31728
+ }
31729
+ var CapacitorCookies = registerPlugin("CapacitorCookies", {
31730
+ web: () => new CapacitorCookiesPluginWeb
31731
+ });
31732
+ var readBlobAsBase64 = async (blob) => new Promise((resolve18, reject) => {
31733
+ const reader = new FileReader;
31734
+ reader.onload = () => {
31735
+ const base64String = reader.result;
31736
+ resolve18(base64String.indexOf(",") >= 0 ? base64String.split(",")[1] : base64String);
31737
+ };
31738
+ reader.onerror = (error) => reject(error);
31739
+ reader.readAsDataURL(blob);
31740
+ });
31741
+ var normalizeHttpHeaders = (headers = {}) => {
31742
+ const originalKeys = Object.keys(headers);
31743
+ const loweredKeys = Object.keys(headers).map((k) => k.toLocaleLowerCase());
31744
+ const normalized = loweredKeys.reduce((acc, key, index) => {
31745
+ acc[key] = headers[originalKeys[index]];
31746
+ return acc;
31747
+ }, {});
31748
+ return normalized;
31749
+ };
31750
+ var buildUrlParams = (params, shouldEncode = true) => {
31751
+ if (!params)
31752
+ return null;
31753
+ const output = Object.entries(params).reduce((accumulator, entry) => {
31754
+ const [key, value] = entry;
31755
+ let encodedValue;
31756
+ let item;
31757
+ if (Array.isArray(value)) {
31758
+ item = "";
31759
+ value.forEach((str) => {
31760
+ encodedValue = shouldEncode ? encodeURIComponent(str) : str;
31761
+ item += `${key}=${encodedValue}&`;
31762
+ });
31763
+ item.slice(0, -1);
31764
+ } else {
31765
+ encodedValue = shouldEncode ? encodeURIComponent(value) : value;
31766
+ item = `${key}=${encodedValue}`;
31767
+ }
31768
+ return `${accumulator}&${item}`;
31769
+ }, "");
31770
+ return output.substr(1);
31771
+ };
31772
+ var buildRequestInit = (options, extra = {}) => {
31773
+ const output = Object.assign({ method: options.method || "GET", headers: options.headers }, extra);
31774
+ const headers = normalizeHttpHeaders(options.headers);
31775
+ const type = headers["content-type"] || "";
31776
+ if (typeof options.data === "string") {
31777
+ output.body = options.data;
31778
+ } else if (type.includes("application/x-www-form-urlencoded")) {
31779
+ const params = new URLSearchParams;
31780
+ for (const [key, value] of Object.entries(options.data || {})) {
31781
+ params.set(key, value);
31782
+ }
31783
+ output.body = params.toString();
31784
+ } else if (type.includes("multipart/form-data") || options.data instanceof FormData) {
31785
+ const form = new FormData;
31786
+ if (options.data instanceof FormData) {
31787
+ options.data.forEach((value, key) => {
31788
+ form.append(key, value);
31789
+ });
31790
+ } else {
31791
+ for (const key of Object.keys(options.data)) {
31792
+ form.append(key, options.data[key]);
31793
+ }
31794
+ }
31795
+ output.body = form;
31796
+ const headers2 = new Headers(output.headers);
31797
+ headers2.delete("content-type");
31798
+ output.headers = headers2;
31799
+ } else if (type.includes("application/json") || typeof options.data === "object") {
31800
+ output.body = JSON.stringify(options.data);
31801
+ }
31802
+ return output;
31803
+ };
31804
+
31805
+ class CapacitorHttpPluginWeb extends WebPlugin {
31806
+ async request(options) {
31807
+ const requestInit = buildRequestInit(options, options.webFetchExtra);
31808
+ const urlParams = buildUrlParams(options.params, options.shouldEncodeUrlParams);
31809
+ const url = urlParams ? `${options.url}?${urlParams}` : options.url;
31810
+ const response = await fetch(url, requestInit);
31811
+ const contentType = response.headers.get("content-type") || "";
31812
+ let { responseType = "text" } = response.ok ? options : {};
31813
+ if (contentType.includes("application/json")) {
31814
+ responseType = "json";
31815
+ }
31816
+ let data;
31817
+ let blob;
31818
+ switch (responseType) {
31819
+ case "arraybuffer":
31820
+ case "blob":
31821
+ blob = await response.blob();
31822
+ data = await readBlobAsBase64(blob);
31823
+ break;
31824
+ case "json":
31825
+ data = await response.json();
31826
+ break;
31827
+ case "document":
31828
+ case "text":
31829
+ default:
31830
+ data = await response.text();
31831
+ }
31832
+ const headers = {};
31833
+ response.headers.forEach((value, key) => {
31834
+ headers[key] = value;
31835
+ });
31836
+ return {
31837
+ data,
31838
+ headers,
31839
+ status: response.status,
31840
+ url: response.url
31841
+ };
31842
+ }
31843
+ async get(options) {
31844
+ return this.request(Object.assign(Object.assign({}, options), { method: "GET" }));
31845
+ }
31846
+ async post(options) {
31847
+ return this.request(Object.assign(Object.assign({}, options), { method: "POST" }));
31848
+ }
31849
+ async put(options) {
31850
+ return this.request(Object.assign(Object.assign({}, options), { method: "PUT" }));
31851
+ }
31852
+ async patch(options) {
31853
+ return this.request(Object.assign(Object.assign({}, options), { method: "PATCH" }));
31854
+ }
31855
+ async delete(options) {
31856
+ return this.request(Object.assign(Object.assign({}, options), { method: "DELETE" }));
31857
+ }
31858
+ }
31859
+ var CapacitorHttp = registerPlugin("CapacitorHttp", {
31860
+ web: () => new CapacitorHttpPluginWeb
31861
+ });
31862
+ var SystemBarsStyle;
31863
+ (function(SystemBarsStyle2) {
31864
+ SystemBarsStyle2["Dark"] = "DARK";
31865
+ SystemBarsStyle2["Light"] = "LIGHT";
31866
+ SystemBarsStyle2["Default"] = "DEFAULT";
31867
+ })(SystemBarsStyle || (SystemBarsStyle = {}));
31868
+ var SystemBarType;
31869
+ (function(SystemBarType2) {
31870
+ SystemBarType2["StatusBar"] = "StatusBar";
31871
+ SystemBarType2["NavigationBar"] = "NavigationBar";
31872
+ })(SystemBarType || (SystemBarType = {}));
31873
+
31874
+ class SystemBarsPluginWeb extends WebPlugin {
31875
+ async setStyle() {
31876
+ this.unavailable("not available for web");
31877
+ }
31878
+ async setAnimation() {
31879
+ this.unavailable("not available for web");
31880
+ }
31881
+ async show() {
31882
+ this.unavailable("not available for web");
31883
+ }
31884
+ async hide() {
31885
+ this.unavailable("not available for web");
31886
+ }
31887
+ }
31888
+ var SystemBars = registerPlugin("SystemBars", {
31889
+ web: () => new SystemBarsPluginWeb
31890
+ });
31891
+
31892
+ // src/mobile/shellNativeObservability.ts
31893
+ var MAX_REPORTS = 8;
31894
+ var MAX_DETAIL_BYTES = 64 * 1024;
31895
+ var MAX_STRING_LENGTH = 8 * 1024;
31896
+ var MAX_REDACTION_DEPTH = 8;
31897
+ var REPORT_ID = /^[A-Za-z0-9._:-]{1,128}$/u;
31898
+ var REPORT_KINDS = new Set([
31899
+ "anr",
31900
+ "app-launch",
31901
+ "cpu-exception",
31902
+ "crash",
31903
+ "disk-write-exception",
31904
+ "hang",
31905
+ "initialization-failure",
31906
+ "low-memory",
31907
+ "native-crash",
31908
+ "resource-exhaustion",
31909
+ "signal"
31910
+ ]);
31911
+ var nativeObservability = registerPlugin("AbsoluteMobileObservability");
31912
+ var isRecord14 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
31913
+ var redactText = (value) => value.slice(0, MAX_STRING_LENGTH).replace(/\b(Bearer)\s+[A-Za-z0-9._~+/-]+=*/giu, "$1 [REDACTED]").replace(/([?&](?:access_token|api_?key|authorization|code|id_token|password|refresh_token|secret|token)=)[^&#\s]*/giu, "$1[REDACTED]").replace(/\b((?:access_?token|api_?key|authorization|client_?secret|cookie|id_?token|password|passwd|refresh_?token|secret|token)\s*[:=]\s*)[^,\s;]+/giu, "$1[REDACTED]").replace(/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/gu, "[REDACTED]");
31914
+ var redactValue = (value, depth = 0) => {
31915
+ if (depth >= MAX_REDACTION_DEPTH)
31916
+ return "[TRUNCATED]";
31917
+ if (typeof value === "string")
31918
+ return redactText(value);
31919
+ if (value === null || typeof value === "number" || typeof value === "boolean")
31920
+ return value;
31921
+ if (Array.isArray(value))
31922
+ return value.slice(0, 64).map((entry) => redactValue(entry, depth + 1));
31923
+ if (!isRecord14(value))
31924
+ return String(value).slice(0, MAX_STRING_LENGTH);
31925
+ return Object.fromEntries(Object.entries(value).slice(0, 128).map(([key, entry]) => [
31926
+ key,
31927
+ /(?:authorization|cookie|password|secret|token)/iu.test(key) ? "[REDACTED]" : redactValue(entry, depth + 1)
31928
+ ]));
31929
+ };
31930
+ var parseReport = (value) => {
31931
+ if (!isRecord14(value) || !isRecord14(value.details))
31932
+ return;
31933
+ if (typeof value.id !== "string" || !REPORT_ID.test(value.id))
31934
+ return;
31935
+ if (typeof value.kind !== "string" || !REPORT_KINDS.has(value.kind))
31936
+ return;
31937
+ if (typeof value.occurredAt !== "number" || !Number.isFinite(value.occurredAt) || value.occurredAt <= 0)
31938
+ return;
31939
+ if (value.platform !== "android" && value.platform !== "ios")
31940
+ return;
31941
+ const details = redactValue(value.details);
31942
+ if (!isRecord14(details))
31943
+ return;
31944
+ if (new TextEncoder().encode(JSON.stringify(details)).byteLength > MAX_DETAIL_BYTES)
31945
+ return;
31946
+ return {
31947
+ details,
31948
+ id: value.id,
31949
+ kind: value.kind,
31950
+ occurredAt: value.occurredAt,
31951
+ platform: value.platform
31952
+ };
31953
+ };
31954
+ var sampled = (id, sampleRate) => {
31955
+ if (sampleRate >= 1)
31956
+ return true;
31957
+ let hash = 2166136261;
31958
+ for (const character of id) {
31959
+ hash ^= character.codePointAt(0) ?? 0;
31960
+ hash = Math.imul(hash, 16777619);
31961
+ }
31962
+ return (hash >>> 0) / 4294967296 < sampleRate;
31963
+ };
31964
+ var reportEvent = (report, manifest) => ({
31965
+ at: report.occurredAt,
31966
+ extra: {
31967
+ nativeDiagnostic: report.details,
31968
+ nativeDiagnosticId: report.id
31969
+ },
31970
+ groupingKey: `absolute-native:${report.platform}:${report.kind}`,
31971
+ level: [
31972
+ "app-launch",
31973
+ "cpu-exception",
31974
+ "disk-write-exception",
31975
+ "low-memory",
31976
+ "resource-exhaustion",
31977
+ "signal"
31978
+ ].includes(report.kind) ? "warning" : "error",
31979
+ message: `Native process diagnostic \u2014 ${report.kind}`,
31980
+ name: "AbsoluteMobileNativeDiagnostic",
31981
+ tags: {
31982
+ absoluteMobile: "true",
31983
+ mobileAppBuild: manifest.appBuild,
31984
+ mobileFailurePhase: "native-process",
31985
+ mobileManifestFormat: String(manifest.format),
31986
+ mobileNativeRuntime: manifest.nativeRuntime,
31987
+ mobilePlatform: report.platform,
31988
+ mobileRuntime: manifest.runtime
31989
+ }
31990
+ });
31991
+ var drainAbsoluteMobileNativeObservability = async (manifest, fetch2, options = {}) => {
31992
+ const config = manifest.observability;
31993
+ if (!config || !(options.native ?? Capacitor.isNativePlatform()))
31994
+ return 0;
31995
+ const plugin = options.plugin ?? nativeObservability;
31996
+ const pending = await plugin.pending();
31997
+ const reports = pending.reports.slice(0, MAX_REPORTS).map(parseReport).filter((report) => report !== undefined);
31998
+ const discarded = reports.filter((report) => !sampled(report.id, config.sampleRate));
31999
+ if (discarded.length > 0)
32000
+ await plugin.acknowledge({
32001
+ ids: discarded.map(({ id }) => id)
32002
+ });
32003
+ const selected = reports.filter((report) => sampled(report.id, config.sampleRate));
32004
+ if (selected.length === 0)
32005
+ return 0;
32006
+ const envelope = {
32007
+ ...config.environment ? { environment: config.environment } : {},
32008
+ events: selected.map((report) => reportEvent(report, manifest)),
32009
+ project: config.project,
32010
+ release: manifest.appBuild,
32011
+ v: 1
32012
+ };
32013
+ const response = await fetch2(config.endpoint, {
32014
+ body: JSON.stringify(envelope),
32015
+ headers: { "content-type": "application/json" },
32016
+ method: "POST"
32017
+ });
32018
+ if (!response.ok)
32019
+ return 0;
32020
+ await plugin.acknowledge({
32021
+ ids: selected.map(({ id }) => id)
32022
+ });
32023
+ return selected.length;
32024
+ };
30431
32025
  // node_modules/@absolutejs/beacon/dist/index.js
30432
32026
  var BEACON_SIGNAL = {
30433
32027
  AUTH_FAILURE_STORM: "auth_failure_storm",
@@ -30703,7 +32297,7 @@ var redactUrl = (value) => {
30703
32297
  };
30704
32298
  var redactUrlsInText = (value) => value.replace(/(?:[a-z][a-z\d+.-]*:\/\/|\/)[^\s"'<>]*[?#][^\s"'<>]*/gi, (candidate) => redactUrl(candidate));
30705
32299
  var redactString = (value) => redactUrlsInText(value).replace(/\b(Bearer)\s+[A-Za-z0-9._~+/-]+=*/gi, (_match, scheme) => `${scheme} ${REDACTED}`).replace(/([?&](?:access_token|api_?key|authorization|code|id_token|password|refresh_token|secret|token)=)[^&#\s]*/gi, `$1${REDACTED}`).replace(/\b((?:access_?token|api_?key|authorization|client_?secret|cookie|id_?token|password|passwd|refresh_?token|secret|token)\s*[:=]\s*)(?!Bearer\b|\[REDACTED\])[^,\s;]+/gi, `$1${REDACTED}`).replace(/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g, REDACTED);
30706
- var redactValue = (value, key, seen, depth = 0) => {
32300
+ var redactValue2 = (value, key, seen, depth = 0) => {
30707
32301
  if (key !== undefined && isSensitiveField(key))
30708
32302
  return REDACTED;
30709
32303
  if (typeof value === "string") {
@@ -30721,9 +32315,9 @@ var redactValue = (value, key, seen, depth = 0) => {
30721
32315
  if (seen.has(value))
30722
32316
  return "[Circular]";
30723
32317
  seen.add(value);
30724
- const redacted = Array.isArray(value) ? value.map((entry) => redactValue(entry, undefined, seen, depth + 1)) : Object.fromEntries(Object.entries(value).map(([field2, entry]) => [
32318
+ const redacted = Array.isArray(value) ? value.map((entry) => redactValue2(entry, undefined, seen, depth + 1)) : Object.fromEntries(Object.entries(value).map(([field2, entry]) => [
30725
32319
  field2,
30726
- redactValue(entry, field2, seen, depth + 1)
32320
+ redactValue2(entry, field2, seen, depth + 1)
30727
32321
  ]));
30728
32322
  seen.delete(value);
30729
32323
  return redacted;
@@ -30741,11 +32335,11 @@ var redactBeaconEvent = (event) => {
30741
32335
  if (event.tags !== undefined) {
30742
32336
  redacted.tags = Object.fromEntries(Object.entries(event.tags).map(([key, value]) => [
30743
32337
  key,
30744
- redactValue(value, key, new Set)
32338
+ redactValue2(value, key, new Set)
30745
32339
  ]));
30746
32340
  }
30747
32341
  if (event.extra !== undefined) {
30748
- redacted.extra = redactValue(event.extra, undefined, new Set);
32342
+ redacted.extra = redactValue2(event.extra, undefined, new Set);
30749
32343
  }
30750
32344
  return redacted;
30751
32345
  };
@@ -32790,9 +34384,9 @@ var createBeacon = (options) => {
32790
34384
  const viewportWidth = document.documentElement.clientWidth;
32791
34385
  const viewportHeight = document.documentElement.clientHeight;
32792
34386
  const controls = document.querySelectorAll(CONTROL_SELECTOR);
32793
- let sampled = 0;
34387
+ let sampled2 = 0;
32794
34388
  for (const control of Array.from(controls)) {
32795
- if (sampled >= OCCLUSION_SAMPLE_LIMIT)
34389
+ if (sampled2 >= OCCLUSION_SAMPLE_LIMIT)
32796
34390
  return;
32797
34391
  if (isScanExempt(control) || hasHiddenAncestor(control, true))
32798
34392
  continue;
@@ -32802,7 +34396,7 @@ var createBeacon = (options) => {
32802
34396
  const visibleRect = visibleControlRect(control, rect, viewportWidth, viewportHeight);
32803
34397
  if (visibleRect === null)
32804
34398
  continue;
32805
- sampled += 1;
34399
+ sampled2 += 1;
32806
34400
  const top = document.elementFromPoint((visibleRect.left + visibleRect.right) / 2, (visibleRect.top + visibleRect.bottom) / 2);
32807
34401
  if (top === null || top === control || control.contains(top) || top.contains(control) || crossesDialogBoundary(control, top) || isExtensionOwnedCover(top)) {
32808
34402
  continue;
@@ -32946,9 +34540,9 @@ var createBeacon = (options) => {
32946
34540
  ...Array.from(priorityCandidates),
32947
34541
  ...Array.from(proseCandidates)
32948
34542
  ];
32949
- let sampled = 0;
34543
+ let sampled2 = 0;
32950
34544
  for (const element of candidates) {
32951
- if (sampled >= INVISIBLE_TEXT_SAMPLE_LIMIT)
34545
+ if (sampled2 >= INVISIBLE_TEXT_SAMPLE_LIMIT)
32952
34546
  return;
32953
34547
  if (isScanExempt(element))
32954
34548
  continue;
@@ -32966,7 +34560,7 @@ var createBeacon = (options) => {
32966
34560
  const foreground = parseColor(style.color);
32967
34561
  if (foreground === null)
32968
34562
  continue;
32969
- sampled += 1;
34563
+ sampled2 += 1;
32970
34564
  const background = effectiveBackground(element);
32971
34565
  if (background === null)
32972
34566
  continue;
@@ -33009,11 +34603,11 @@ var createBeacon = (options) => {
33009
34603
  const roots = document.querySelectorAll(`[${BEACON_ATTRIBUTE.THEME}="adaptive"]`);
33010
34604
  const viewportWidth = document.documentElement.clientWidth;
33011
34605
  const viewportHeight = document.documentElement.clientHeight;
33012
- let sampled = 0;
34606
+ let sampled2 = 0;
33013
34607
  for (const root of Array.from(roots)) {
33014
34608
  const candidates = [root, ...Array.from(root.querySelectorAll("*"))];
33015
34609
  for (const element of candidates) {
33016
- if (sampled >= THEME_SURFACE_SAMPLE_LIMIT)
34610
+ if (sampled2 >= THEME_SURFACE_SAMPLE_LIMIT)
33017
34611
  return;
33018
34612
  if (element.closest(`[${BEACON_ATTRIBUTE.THEME}="allow"]`) !== null || isScanExempt(element) || element.matches("canvas, iframe, img, picture, svg, video")) {
33019
34613
  continue;
@@ -33035,7 +34629,7 @@ var createBeacon = (options) => {
33035
34629
  const surface = parseColor(style.backgroundColor);
33036
34630
  if (surface === null || surface.a < 0.95)
33037
34631
  continue;
33038
- sampled += 1;
34632
+ sampled2 += 1;
33039
34633
  const luminance = relativeLuminance(surface);
33040
34634
  const opposite = theme === "dark" ? luminance >= THEME_LIGHT_SURFACE_LUMINANCE_MIN : luminance <= THEME_DARK_SURFACE_LUMINANCE_MAX;
33041
34635
  if (!opposite)
@@ -35599,23 +37193,23 @@ import { pathToFileURL as pathToFileURL4 } from "url";
35599
37193
  import { createHash as createHash13, sign, verify } from "crypto";
35600
37194
  import {
35601
37195
  cp as cp3,
35602
- mkdir as mkdir16,
37196
+ mkdir as mkdir17,
35603
37197
  mkdtemp as mkdtemp8,
35604
- readdir as readdir6,
35605
- readFile as readFile21,
35606
- rename as rename15,
35607
- rm as rm12,
37198
+ readdir as readdir7,
37199
+ readFile as readFile22,
37200
+ rename as rename16,
37201
+ rm as rm13,
35608
37202
  stat as stat4,
35609
- writeFile as writeFile19
37203
+ writeFile as writeFile20
35610
37204
  } from "fs/promises";
35611
- import { dirname as dirname17, join as join23, relative as relative15, resolve as resolve18 } from "path";
37205
+ import { dirname as dirname18, join as join25, relative as relative15, resolve as resolve18 } from "path";
35612
37206
  var UPDATE_MANIFEST_FILE = "update.json";
35613
37207
  var UPDATE_FILES_DIRECTORY = "files";
35614
37208
  var sha2562 = (value) => createHash13("sha256").update(value).digest("hex");
35615
37209
  var listFiles = async (root, directory = root) => {
35616
- const entries = await readdir6(directory, { withFileTypes: true });
37210
+ const entries = await readdir7(directory, { withFileTypes: true });
35617
37211
  const paths = await Promise.all(entries.map(async (entry) => {
35618
- const path = join23(directory, entry.name);
37212
+ const path = join25(directory, entry.name);
35619
37213
  if (entry.isDirectory())
35620
37214
  return listFiles(root, path);
35621
37215
  if (!entry.isFile())
@@ -35625,10 +37219,10 @@ var listFiles = async (root, directory = root) => {
35625
37219
  return paths.flat().sort((left, right) => left.localeCompare(right));
35626
37220
  };
35627
37221
  var inspectFiles = async (root, paths) => Promise.all(paths.map(async (path) => {
35628
- const source = join23(root, path);
37222
+ const source = join25(root, path);
35629
37223
  const [metadata, contents] = await Promise.all([
35630
37224
  stat4(source),
35631
- readFile21(source)
37225
+ readFile22(source)
35632
37226
  ]);
35633
37227
  return { bytes: metadata.size, path, sha256: sha2562(contents) };
35634
37228
  }));
@@ -35663,26 +37257,26 @@ var buildAbsoluteMobileUpdate = async (options) => {
35663
37257
  value: signature.toString("base64")
35664
37258
  }
35665
37259
  });
35666
- await mkdir16(outputRoot, { recursive: true });
35667
- const outputDirectory = join23(outputRoot, manifest.releaseId);
35668
- const staging = await mkdtemp8(join23(outputRoot, ".stage-"));
37260
+ await mkdir17(outputRoot, { recursive: true });
37261
+ const outputDirectory = join25(outputRoot, manifest.releaseId);
37262
+ const staging = await mkdtemp8(join25(outputRoot, ".stage-"));
35669
37263
  try {
35670
- await cp3(bundleDirectory, join23(staging, UPDATE_FILES_DIRECTORY), {
37264
+ await cp3(bundleDirectory, join25(staging, UPDATE_FILES_DIRECTORY), {
35671
37265
  force: true,
35672
37266
  recursive: true
35673
37267
  });
35674
- await writeFile19(join23(staging, UPDATE_MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
37268
+ await writeFile20(join25(staging, UPDATE_MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
35675
37269
  `);
35676
- await rename15(staging, outputDirectory);
37270
+ await rename16(staging, outputDirectory);
35677
37271
  } catch (error) {
35678
- await rm12(staging, { force: true, recursive: true });
37272
+ await rm13(staging, { force: true, recursive: true });
35679
37273
  if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "EEXIST")
35680
37274
  throw new TypeError(`Mobile update ${manifest.releaseId} already exists.`, { cause: error });
35681
37275
  throw error;
35682
37276
  }
35683
37277
  return {
35684
37278
  manifest,
35685
- manifestPath: join23(outputDirectory, UPDATE_MANIFEST_FILE),
37279
+ manifestPath: join25(outputDirectory, UPDATE_MANIFEST_FILE),
35686
37280
  outputDirectory
35687
37281
  };
35688
37282
  };
@@ -35691,10 +37285,10 @@ var copyAbsoluteMobileUpdateFile = async (updateDirectory, path, destination) =>
35691
37285
  const root = resolve18(updateDirectory, UPDATE_FILES_DIRECTORY);
35692
37286
  if (!source.startsWith(`${root}/`))
35693
37287
  throw new TypeError("Update file escaped its release.");
35694
- await mkdir16(dirname17(destination), { recursive: true });
37288
+ await mkdir17(dirname18(destination), { recursive: true });
35695
37289
  await cp3(source, destination, { force: true });
35696
37290
  };
35697
- var readAbsoluteMobileUpdate = async (directory) => parseAbsoluteMobileUpdateManifest(JSON.parse(await readFile21(join23(resolve18(directory), UPDATE_MANIFEST_FILE), "utf8")));
37291
+ var readAbsoluteMobileUpdate = async (directory) => parseAbsoluteMobileUpdateManifest(JSON.parse(await readFile22(join25(resolve18(directory), UPDATE_MANIFEST_FILE), "utf8")));
35698
37292
  var verifyAbsoluteMobileUpdateSignature = (manifestValue, publicKey) => {
35699
37293
  const manifest = parseAbsoluteMobileUpdateManifest(manifestValue);
35700
37294
  const valid = verify("sha256", absoluteMobileUpdateSigningPayload(unsignedAbsoluteMobileUpdate(manifest)), { dsaEncoding: "ieee-p1363", key: publicKey }, Buffer.from(manifest.signature.value, "base64"));
@@ -35841,6 +37435,8 @@ export {
35841
37435
  MOBILE_PAGE_REQUEST_HEADERS,
35842
37436
  absoluteDeviceNativeRequirements,
35843
37437
  absoluteExpoExecutable,
37438
+ absoluteExpoNativeObservabilityFiles,
37439
+ absoluteExpoNativeObservabilityPaths,
35844
37440
  absoluteIosDeviceAcceptanceCommands,
35845
37441
  absoluteMobilePreviewDocument,
35846
37442
  absoluteMobileUpdateSigningPayload,
@@ -35853,6 +37449,7 @@ export {
35853
37449
  activateAbsoluteMobilePage,
35854
37450
  applyAbsoluteNativeDeepLinks,
35855
37451
  applyAbsoluteNativeDeviceCapabilities,
37452
+ applyAbsoluteNativeObservability,
35856
37453
  applyAbsoluteNativeUpdates,
35857
37454
  assertAbsoluteDeviceCapabilityPackages,
35858
37455
  buildAbsoluteAndroidRelease,
@@ -35892,6 +37489,8 @@ export {
35892
37489
  discoverAbsoluteDeviceCapabilities,
35893
37490
  discoverAbsoluteSyncSchema,
35894
37491
  disposeAbsoluteMobilePage,
37492
+ drainAbsoluteMobileNativeObservability,
37493
+ expoNativeObservabilityRuntimeSource,
35895
37494
  fetchAbsoluteMobilePage,
35896
37495
  finalizeAbsoluteExpoUpdateExport,
35897
37496
  finalizeAbsoluteMobileCompatibilityBuild,
@@ -36007,5 +37606,5 @@ export {
36007
37606
  writeAbsoluteMobileGithubWorkflow
36008
37607
  };
36009
37608
 
36010
- //# debugId=8E3E47B8F20AFE6B64756E2164756E21
37609
+ //# debugId=E4E383CA64D4F3BE64756E2164756E21
36011
37610
  //# sourceMappingURL=index.js.map