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

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,638 @@ 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 acknowledge(PluginCall call) {
30432
+ JSArray ids = call.getArray("ids");
30433
+ if (ids == null) {
30434
+ call.reject("Native observability acknowledgement requires report ids.");
30435
+ return;
30436
+ }
30437
+ Set<String> accepted = new HashSet<>();
30438
+ for (int index = 0; index < ids.length(); index++) accepted.add(ids.optString(index));
30439
+ Set<String> acknowledged = acknowledged();
30440
+ for (File file : files()) {
30441
+ try (InputStream input = new FileInputStream(file)) {
30442
+ JSONObject report = new JSONObject(new String(read(input), StandardCharsets.UTF_8));
30443
+ String id = report.optString("id");
30444
+ if (accepted.contains(id)) {
30445
+ acknowledged.add(id);
30446
+ file.delete();
30447
+ }
30448
+ } catch (Exception ignored) {}
29458
30449
  }
30450
+ writeAcknowledged(acknowledged);
30451
+ call.resolve();
29459
30452
  }
29460
30453
  }
29461
30454
  `;
29462
- var androidActivityInRoot = async (root) => {
30455
+ var androidActivityInRoot2 = async (root) => {
29463
30456
  let entries;
29464
30457
  try {
29465
- entries = await readdir5(root, { recursive: true });
30458
+ entries = await readdir6(root, { recursive: true });
29466
30459
  } catch (error) {
29467
30460
  if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
29468
30461
  return;
29469
30462
  throw error;
29470
30463
  }
29471
30464
  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;
30465
+ return candidate ? join24(root, candidate) : undefined;
29473
30466
  };
29474
- var androidActivity = async (config) => {
30467
+ var androidActivity2 = async (config) => {
29475
30468
  const roots = [
29476
- join22(config.nativeProjectDirectory, "android/app/src/main/java"),
29477
- join22(config.nativeProjectDirectory, "android/app/src/main/kotlin")
30469
+ join24(config.nativeProjectDirectory, "android/app/src/main/java"),
30470
+ join24(config.nativeProjectDirectory, "android/app/src/main/kotlin")
29478
30471
  ];
29479
- const candidates = await Promise.all(roots.map(androidActivityInRoot));
30472
+ const candidates = await Promise.all(roots.map(androidActivityInRoot2));
29480
30473
  const candidate = candidates.find((value) => value !== undefined);
29481
30474
  if (candidate)
29482
30475
  return candidate;
29483
30476
  throw new TypeError("Android MainActivity.java or MainActivity.kt was not found.");
29484
30477
  };
29485
- var androidManagedRegion = (kotlin, generatedMethod) => {
30478
+ var androidRegion2 = (kotlin, generatedMethod) => {
29486
30479
  if (generatedMethod)
29487
- return kotlin ? ` ${START}
30480
+ return kotlin ? ` ${START2}
29488
30481
  override fun onCreate(savedInstanceState: android.os.Bundle?) {
29489
- AbsoluteMobileUpdateWatchdogPlugin.recoverInterruptedBoot(this)
29490
- registerPlugin(AbsoluteMobileUpdateWatchdogPlugin::class.java)
30482
+ registerPlugin(AbsoluteMobileObservabilityPlugin::class.java)
29491
30483
  super.onCreate(savedInstanceState)
29492
30484
  }
29493
- ${END}
29494
- ` : ` ${START}
30485
+ ${END2}
30486
+ ` : ` ${START2}
29495
30487
  @Override
29496
30488
  public void onCreate(android.os.Bundle savedInstanceState) {
29497
- AbsoluteMobileUpdateWatchdogPlugin.recoverInterruptedBoot(this);
29498
- registerPlugin(AbsoluteMobileUpdateWatchdogPlugin.class);
30489
+ registerPlugin(AbsoluteMobileObservabilityPlugin.class);
29499
30490
  super.onCreate(savedInstanceState);
29500
30491
  }
29501
- ${END}
30492
+ ${END2}
29502
30493
  `;
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}
30494
+ return kotlin ? ` ${START2}
30495
+ registerPlugin(AbsoluteMobileObservabilityPlugin::class.java)
30496
+ ${END2}
30497
+ ` : ` ${START2}
30498
+ registerPlugin(AbsoluteMobileObservabilityPlugin.class);
30499
+ ${END2}
29511
30500
  `;
29512
30501
  };
29513
- var injectAndroidActivity = (source, enabled) => {
30502
+ var injectAndroidActivity2 = (source, enabled) => {
29514
30503
  const kotlin = /\bfun\s+onCreate\s*\(/u.test(source) || source.includes("BridgeActivity()");
29515
- const existingStart = source.indexOf(START);
29516
- const existingEnd = source.indexOf(END);
30504
+ const existingStart = source.indexOf(START2);
30505
+ const existingEnd = source.indexOf(END2);
29517
30506
  if (existingStart < 0 !== existingEnd < 0)
29518
- throw new TypeError("AbsoluteJS mobile update markers are malformed.");
30507
+ throw new TypeError("AbsoluteJS native observability markers are malformed.");
29519
30508
  if (existingStart >= 0) {
29520
30509
  const from = source.lastIndexOf(`
29521
30510
  `, existingStart) + 1;
29522
30511
  const newline = source.indexOf(`
29523
- `, existingEnd + END.length);
30512
+ `, existingEnd + END2.length);
29524
30513
  const through = newline < 0 ? source.length : newline + 1;
29525
- const owned = source.slice(from, through);
29526
- const generatedMethod = owned.includes("onCreate(");
30514
+ const generatedMethod = source.slice(from, through).includes("onCreate(");
29527
30515
  if (!enabled)
29528
30516
  return `${source.slice(0, from)}${source.slice(through)}`;
29529
- const region2 = androidManagedRegion(kotlin, generatedMethod);
29530
- return `${source.slice(0, from)}${region2}${source.slice(through)}`;
30517
+ return `${source.slice(0, from)}${androidRegion2(kotlin, generatedMethod)}${source.slice(through)}`;
29531
30518
  }
29532
30519
  if (!enabled)
29533
30520
  return source;
@@ -29536,64 +30523,38 @@ var injectAndroidActivity = (source, enabled) => {
29536
30523
  const brace = source.indexOf("{", onCreate);
29537
30524
  const insert = source.indexOf(`
29538
30525
  `, 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)}`;
30526
+ return `${source.slice(0, insert)}${androidRegion2(kotlin, false)}${source.slice(insert)}`;
29549
30527
  }
29550
30528
  const close = source.lastIndexOf("}");
29551
30529
  if (close < 0)
29552
30530
  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
30531
  const separator = source[close - 1] === `
29570
30532
  ` ? "" : `
29571
30533
  `;
29572
- return `${source.slice(0, close)}${separator}${region}${source.slice(close)}`;
30534
+ return `${source.slice(0, close)}${separator}${androidRegion2(kotlin, true)}${source.slice(close)}`;
29573
30535
  };
29574
- var configureAndroid3 = async (config, enabled) => {
29575
- const activityPath = await androidActivity(config);
29576
- const activity = await readFile20(activityPath, "utf8");
30536
+ var configureAndroid4 = async (config, enabled) => {
30537
+ const activityPath = await androidActivity2(config);
30538
+ const activity = await readFile21(activityPath, "utf8");
29577
30539
  const packageName = activity.match(/^\s*package\s+([A-Za-z0-9_.]+)\s*[;\n]/mu)?.[1];
29578
30540
  if (!packageName)
29579
30541
  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);
30542
+ const pluginPath = join24(config.nativeProjectDirectory, "android/app/src/main/java", packageName.replaceAll(".", "/"), ANDROID_PLUGIN2);
29582
30543
  const changed = await Promise.all([
29583
- writeChanged(activityPath, injectAndroidActivity(activity, enabled)),
29584
- enabled ? writeChanged(pluginPath, androidPluginSource(packageName, config.updates?.bootTimeoutMs ?? 20000)) : removeOwnedFile(pluginPath)
30544
+ writeChanged2(activityPath, injectAndroidActivity2(activity, enabled)),
30545
+ enabled ? writeChanged2(pluginPath, androidPluginSource2(packageName)) : removeOwnedFile2(pluginPath)
29585
30546
  ]);
29586
30547
  return changed.some(Boolean);
29587
30548
  };
29588
- var applyAbsoluteNativeUpdates = async (config, platforms = config.platforms) => {
30549
+ var applyAbsoluteNativeObservability = async (config, platforms = config.platforms) => {
29589
30550
  if (config.engine !== "capacitor")
29590
30551
  return { changed: false };
29591
- const enabled = config.updates !== undefined;
30552
+ const enabled = config.observability !== undefined;
29592
30553
  const changed = [];
29593
30554
  if (platforms.includes("ios"))
29594
- changed.push(await configureIos3(config, enabled));
30555
+ changed.push(await configureIos4(config, enabled));
29595
30556
  if (platforms.includes("android"))
29596
- changed.push(await configureAndroid3(config, enabled));
30557
+ changed.push(await configureAndroid4(config, enabled));
29597
30558
  return { changed: changed.some(Boolean) };
29598
30559
  };
29599
30560
  // src/mobile/releasePublisher.ts
@@ -29621,8 +30582,8 @@ var prepareAbsoluteAndroidRelease = async (publisher, options) => {
29621
30582
  }
29622
30583
  return versionCode;
29623
30584
  };
29624
- var isRecord12 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
29625
- var isPublisher = (value) => isRecord12(value) && typeof value.publish === "function";
30585
+ var isRecord13 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
30586
+ var isPublisher = (value) => isRecord13(value) && typeof value.publish === "function";
29626
30587
  var publisherModulePath = (projectRoot, requested) => {
29627
30588
  const root = resolve16(projectRoot);
29628
30589
  const path = resolve16(root, requested);
@@ -29638,7 +30599,7 @@ var loadAbsoluteNativeReleasePublisher = async (projectRoot, requestedModulePath
29638
30599
  throw new TypeError(`Native release registry module does not exist: ${modulePath}`);
29639
30600
  });
29640
30601
  const loaded = await import(pathToFileURL3(modulePath).href);
29641
- const publisher = isRecord12(loaded) ? loaded.default ?? loaded.registry : undefined;
30602
+ const publisher = isRecord13(loaded) ? loaded.default ?? loaded.registry : undefined;
29642
30603
  if (!isPublisher(publisher)) {
29643
30604
  throw new TypeError("Native release registry module must default-export a registry with publish(options).");
29644
30605
  }
@@ -29696,7 +30657,7 @@ var publishAbsoluteIosRelease = async (options) => {
29696
30657
  };
29697
30658
  // src/mobile/routeMetadataTransform.ts
29698
30659
  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";
30660
+ import { dirname as dirname17, extname as extname6, relative as relative14, resolve as resolve17 } from "path";
29700
30661
  import ts2 from "typescript";
29701
30662
  var ROUTE_METHODS = new Set(["get", "head"]);
29702
30663
  var SOURCE_FILTER = /\.[cm]?[jt]sx?$/;
@@ -29747,7 +30708,7 @@ var PAGE_HANDLERS = new Map([
29747
30708
  ]
29748
30709
  ]);
29749
30710
  var posixPath = (value) => value.replace(/\\/g, "/");
29750
- var findTsconfig = (entry, projectRoot) => ts2.findConfigFile(dirname16(entry), existsSync7, "tsconfig.json") ?? ts2.findConfigFile(projectRoot, existsSync7, "tsconfig.json");
30711
+ var findTsconfig = (entry, projectRoot) => ts2.findConfigFile(dirname17(entry), existsSync7, "tsconfig.json") ?? ts2.findConfigFile(projectRoot, existsSync7, "tsconfig.json");
29751
30712
  var createProgram = (entry, projectRoot) => {
29752
30713
  const configPath2 = findTsconfig(entry, projectRoot);
29753
30714
  if (!configPath2) {
@@ -29759,7 +30720,7 @@ var createProgram = (entry, projectRoot) => {
29759
30720
  target: ts2.ScriptTarget.ESNext
29760
30721
  });
29761
30722
  }
29762
- const parsed = ts2.parseJsonConfigFileContent(ts2.readConfigFile(configPath2, (path) => readFileSync8(path, "utf8")).config, ts2.sys, dirname16(configPath2));
30723
+ const parsed = ts2.parseJsonConfigFileContent(ts2.readConfigFile(configPath2, (path) => readFileSync8(path, "utf8")).config, ts2.sys, dirname17(configPath2));
29763
30724
  if (!parsed.fileNames.includes(entry))
29764
30725
  parsed.fileNames.push(entry);
29765
30726
  return ts2.createProgram(parsed.fileNames, parsed.options);
@@ -30428,6 +31389,627 @@ var http = createAbsoluteHttpClient();
30428
31389
 
30429
31390
  // src/mobile/shellHttp.ts
30430
31391
  var installAbsoluteMobileShellHttp = (origin, fetch2 = globalThis.fetch) => installAbsoluteHttpTransport(createAbsoluteHttpTransport({ fetch: fetch2, origin, runtime: "native" }));
31392
+ // node_modules/@capacitor/core/dist/index.js
31393
+ /*! Capacitor: https://capacitorjs.com/ - MIT License */
31394
+ var ExceptionCode;
31395
+ (function(ExceptionCode2) {
31396
+ ExceptionCode2["Unimplemented"] = "UNIMPLEMENTED";
31397
+ ExceptionCode2["Unavailable"] = "UNAVAILABLE";
31398
+ })(ExceptionCode || (ExceptionCode = {}));
31399
+
31400
+ class CapacitorException extends Error {
31401
+ constructor(message, code, data) {
31402
+ super(message);
31403
+ this.message = message;
31404
+ this.code = code;
31405
+ this.data = data;
31406
+ }
31407
+ }
31408
+ var getPlatformId = (win) => {
31409
+ var _a, _b;
31410
+ if (win === null || win === undefined ? undefined : win.androidBridge) {
31411
+ return "android";
31412
+ } else if ((_b = (_a = win === null || win === undefined ? undefined : win.webkit) === null || _a === undefined ? undefined : _a.messageHandlers) === null || _b === undefined ? undefined : _b.bridge) {
31413
+ return "ios";
31414
+ } else {
31415
+ return "web";
31416
+ }
31417
+ };
31418
+ var createCapacitor = (win) => {
31419
+ const capCustomPlatform = win.CapacitorCustomPlatform || null;
31420
+ const cap = win.Capacitor || {};
31421
+ const Plugins = cap.Plugins = cap.Plugins || {};
31422
+ const getPlatform = () => {
31423
+ return capCustomPlatform !== null ? capCustomPlatform.name : getPlatformId(win);
31424
+ };
31425
+ const isNativePlatform = () => getPlatform() !== "web";
31426
+ const isPluginAvailable = (pluginName) => {
31427
+ const plugin = registeredPlugins.get(pluginName);
31428
+ if (plugin === null || plugin === undefined ? undefined : plugin.platforms.has(getPlatform())) {
31429
+ return true;
31430
+ }
31431
+ if (getPluginHeader(pluginName)) {
31432
+ return true;
31433
+ }
31434
+ return false;
31435
+ };
31436
+ const getPluginHeader = (pluginName) => {
31437
+ var _a;
31438
+ return (_a = cap.PluginHeaders) === null || _a === undefined ? undefined : _a.find((h) => h.name === pluginName);
31439
+ };
31440
+ const handleError = (err) => win.console.error(err);
31441
+ const registeredPlugins = new Map;
31442
+ const registerPlugin = (pluginName, jsImplementations = {}) => {
31443
+ const registeredPlugin = registeredPlugins.get(pluginName);
31444
+ if (registeredPlugin) {
31445
+ console.warn(`Capacitor plugin "${pluginName}" already registered. Cannot register plugins twice.`);
31446
+ return registeredPlugin.proxy;
31447
+ }
31448
+ const platform4 = getPlatform();
31449
+ const pluginHeader = getPluginHeader(pluginName);
31450
+ let jsImplementation;
31451
+ const loadPluginImplementation = async () => {
31452
+ if (!jsImplementation && platform4 in jsImplementations) {
31453
+ jsImplementation = typeof jsImplementations[platform4] === "function" ? jsImplementation = await jsImplementations[platform4]() : jsImplementation = jsImplementations[platform4];
31454
+ } else if (capCustomPlatform !== null && !jsImplementation && "web" in jsImplementations) {
31455
+ jsImplementation = typeof jsImplementations["web"] === "function" ? jsImplementation = await jsImplementations["web"]() : jsImplementation = jsImplementations["web"];
31456
+ }
31457
+ return jsImplementation;
31458
+ };
31459
+ const createPluginMethod = (impl, prop) => {
31460
+ var _a, _b;
31461
+ if (pluginHeader) {
31462
+ const methodHeader = pluginHeader === null || pluginHeader === undefined ? undefined : pluginHeader.methods.find((m) => prop === m.name);
31463
+ if (methodHeader) {
31464
+ if (methodHeader.rtype === "promise") {
31465
+ return (options) => cap.nativePromise(pluginName, prop.toString(), options);
31466
+ } else {
31467
+ return (options, callback) => cap.nativeCallback(pluginName, prop.toString(), options, callback);
31468
+ }
31469
+ } else if (impl) {
31470
+ return (_a = impl[prop]) === null || _a === undefined ? undefined : _a.bind(impl);
31471
+ }
31472
+ } else if (impl) {
31473
+ return (_b = impl[prop]) === null || _b === undefined ? undefined : _b.bind(impl);
31474
+ } else {
31475
+ throw new CapacitorException(`"${pluginName}" plugin is not implemented on ${platform4}`, ExceptionCode.Unimplemented);
31476
+ }
31477
+ };
31478
+ const createPluginMethodWrapper = (prop) => {
31479
+ let remove;
31480
+ const wrapper = (...args) => {
31481
+ const p = loadPluginImplementation().then((impl) => {
31482
+ const fn = createPluginMethod(impl, prop);
31483
+ if (fn) {
31484
+ const p2 = fn(...args);
31485
+ remove = p2 === null || p2 === undefined ? undefined : p2.remove;
31486
+ return p2;
31487
+ } else {
31488
+ throw new CapacitorException(`"${pluginName}.${prop}()" is not implemented on ${platform4}`, ExceptionCode.Unimplemented);
31489
+ }
31490
+ });
31491
+ if (prop === "addListener") {
31492
+ p.remove = async () => remove();
31493
+ }
31494
+ return p;
31495
+ };
31496
+ wrapper.toString = () => `${prop.toString()}() { [capacitor code] }`;
31497
+ Object.defineProperty(wrapper, "name", {
31498
+ value: prop,
31499
+ writable: false,
31500
+ configurable: false
31501
+ });
31502
+ return wrapper;
31503
+ };
31504
+ const addListener = createPluginMethodWrapper("addListener");
31505
+ const removeListener = createPluginMethodWrapper("removeListener");
31506
+ const addListenerNative = (eventName, callback) => {
31507
+ const call = addListener({ eventName }, callback);
31508
+ const remove = async () => {
31509
+ const callbackId = await call;
31510
+ removeListener({
31511
+ eventName,
31512
+ callbackId
31513
+ }, callback);
31514
+ };
31515
+ const p = new Promise((resolve18) => call.then(() => resolve18({ remove })));
31516
+ p.remove = async () => {
31517
+ console.warn(`Using addListener() without 'await' is deprecated.`);
31518
+ await remove();
31519
+ };
31520
+ return p;
31521
+ };
31522
+ const proxy = new Proxy({}, {
31523
+ get(_, prop) {
31524
+ switch (prop) {
31525
+ case "$$typeof":
31526
+ return;
31527
+ case "toJSON":
31528
+ return () => ({});
31529
+ case "addListener":
31530
+ return pluginHeader ? addListenerNative : addListener;
31531
+ case "removeListener":
31532
+ return removeListener;
31533
+ default:
31534
+ return createPluginMethodWrapper(prop);
31535
+ }
31536
+ }
31537
+ });
31538
+ Plugins[pluginName] = proxy;
31539
+ registeredPlugins.set(pluginName, {
31540
+ name: pluginName,
31541
+ proxy,
31542
+ platforms: new Set([...Object.keys(jsImplementations), ...pluginHeader ? [platform4] : []])
31543
+ });
31544
+ return proxy;
31545
+ };
31546
+ if (!cap.convertFileSrc) {
31547
+ cap.convertFileSrc = (filePath) => filePath;
31548
+ }
31549
+ cap.getPlatform = getPlatform;
31550
+ cap.handleError = handleError;
31551
+ cap.isNativePlatform = isNativePlatform;
31552
+ cap.isPluginAvailable = isPluginAvailable;
31553
+ cap.registerPlugin = registerPlugin;
31554
+ cap.Exception = CapacitorException;
31555
+ cap.DEBUG = !!cap.DEBUG;
31556
+ cap.isLoggingEnabled = !!cap.isLoggingEnabled;
31557
+ return cap;
31558
+ };
31559
+ var initCapacitorGlobal = (win) => win.Capacitor = createCapacitor(win);
31560
+ var Capacitor = /* @__PURE__ */ initCapacitorGlobal(typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : {});
31561
+ var registerPlugin = Capacitor.registerPlugin;
31562
+
31563
+ class WebPlugin {
31564
+ constructor() {
31565
+ this.listeners = {};
31566
+ this.retainedEventArguments = {};
31567
+ this.windowListeners = {};
31568
+ }
31569
+ addListener(eventName, listenerFunc) {
31570
+ let firstListener = false;
31571
+ const listeners = this.listeners[eventName];
31572
+ if (!listeners) {
31573
+ this.listeners[eventName] = [];
31574
+ firstListener = true;
31575
+ }
31576
+ this.listeners[eventName].push(listenerFunc);
31577
+ const windowListener = this.windowListeners[eventName];
31578
+ if (windowListener && !windowListener.registered) {
31579
+ this.addWindowListener(windowListener);
31580
+ }
31581
+ if (firstListener) {
31582
+ this.sendRetainedArgumentsForEvent(eventName);
31583
+ }
31584
+ const remove = async () => this.removeListener(eventName, listenerFunc);
31585
+ const p = Promise.resolve({ remove });
31586
+ return p;
31587
+ }
31588
+ async removeAllListeners() {
31589
+ this.listeners = {};
31590
+ for (const listener in this.windowListeners) {
31591
+ this.removeWindowListener(this.windowListeners[listener]);
31592
+ }
31593
+ this.windowListeners = {};
31594
+ }
31595
+ notifyListeners(eventName, data, retainUntilConsumed) {
31596
+ const listeners = this.listeners[eventName];
31597
+ if (!listeners) {
31598
+ if (retainUntilConsumed) {
31599
+ let args = this.retainedEventArguments[eventName];
31600
+ if (!args) {
31601
+ args = [];
31602
+ }
31603
+ args.push(data);
31604
+ this.retainedEventArguments[eventName] = args;
31605
+ }
31606
+ return;
31607
+ }
31608
+ listeners.forEach((listener) => listener(data));
31609
+ }
31610
+ hasListeners(eventName) {
31611
+ var _a;
31612
+ return !!((_a = this.listeners[eventName]) === null || _a === undefined ? undefined : _a.length);
31613
+ }
31614
+ registerWindowListener(windowEventName, pluginEventName) {
31615
+ this.windowListeners[pluginEventName] = {
31616
+ registered: false,
31617
+ windowEventName,
31618
+ pluginEventName,
31619
+ handler: (event) => {
31620
+ this.notifyListeners(pluginEventName, event);
31621
+ }
31622
+ };
31623
+ }
31624
+ unimplemented(msg = "not implemented") {
31625
+ return new Capacitor.Exception(msg, ExceptionCode.Unimplemented);
31626
+ }
31627
+ unavailable(msg = "not available") {
31628
+ return new Capacitor.Exception(msg, ExceptionCode.Unavailable);
31629
+ }
31630
+ async removeListener(eventName, listenerFunc) {
31631
+ const listeners = this.listeners[eventName];
31632
+ if (!listeners) {
31633
+ return;
31634
+ }
31635
+ const index = listeners.indexOf(listenerFunc);
31636
+ this.listeners[eventName].splice(index, 1);
31637
+ if (!this.listeners[eventName].length) {
31638
+ this.removeWindowListener(this.windowListeners[eventName]);
31639
+ }
31640
+ }
31641
+ addWindowListener(handle) {
31642
+ window.addEventListener(handle.windowEventName, handle.handler);
31643
+ handle.registered = true;
31644
+ }
31645
+ removeWindowListener(handle) {
31646
+ if (!handle) {
31647
+ return;
31648
+ }
31649
+ window.removeEventListener(handle.windowEventName, handle.handler);
31650
+ handle.registered = false;
31651
+ }
31652
+ sendRetainedArgumentsForEvent(eventName) {
31653
+ const args = this.retainedEventArguments[eventName];
31654
+ if (!args) {
31655
+ return;
31656
+ }
31657
+ delete this.retainedEventArguments[eventName];
31658
+ args.forEach((arg) => {
31659
+ this.notifyListeners(eventName, arg);
31660
+ });
31661
+ }
31662
+ }
31663
+ var encode = (str) => encodeURIComponent(str).replace(/%(2[346B]|5E|60|7C)/g, decodeURIComponent).replace(/[()]/g, escape);
31664
+ var decode = (str) => str.replace(/(%[\dA-F]{2})+/gi, decodeURIComponent);
31665
+
31666
+ class CapacitorCookiesPluginWeb extends WebPlugin {
31667
+ async getCookies() {
31668
+ const cookies = document.cookie;
31669
+ const cookieMap = {};
31670
+ cookies.split(";").forEach((cookie) => {
31671
+ if (cookie.length <= 0)
31672
+ return;
31673
+ let [key, value] = cookie.replace(/=/, "CAP_COOKIE").split("CAP_COOKIE");
31674
+ key = decode(key).trim();
31675
+ value = decode(value).trim();
31676
+ cookieMap[key] = value;
31677
+ });
31678
+ return cookieMap;
31679
+ }
31680
+ async setCookie(options) {
31681
+ try {
31682
+ const encodedKey = encode(options.key);
31683
+ const encodedValue = encode(options.value);
31684
+ const expires = options.expires ? `; expires=${options.expires.replace("expires=", "")}` : "";
31685
+ const path = (options.path || "/").replace("path=", "");
31686
+ const domain = options.url != null && options.url.length > 0 ? `domain=${options.url}` : "";
31687
+ document.cookie = `${encodedKey}=${encodedValue || ""}${expires}; path=${path}; ${domain};`;
31688
+ } catch (error) {
31689
+ return Promise.reject(error);
31690
+ }
31691
+ }
31692
+ async deleteCookie(options) {
31693
+ try {
31694
+ document.cookie = `${options.key}=; Max-Age=0`;
31695
+ } catch (error) {
31696
+ return Promise.reject(error);
31697
+ }
31698
+ }
31699
+ async clearCookies() {
31700
+ try {
31701
+ const cookies = document.cookie.split(";") || [];
31702
+ for (const cookie of cookies) {
31703
+ document.cookie = cookie.replace(/^ +/, "").replace(/=.*/, `=;expires=${new Date().toUTCString()};path=/`);
31704
+ }
31705
+ } catch (error) {
31706
+ return Promise.reject(error);
31707
+ }
31708
+ }
31709
+ async clearAllCookies() {
31710
+ try {
31711
+ await this.clearCookies();
31712
+ } catch (error) {
31713
+ return Promise.reject(error);
31714
+ }
31715
+ }
31716
+ }
31717
+ var CapacitorCookies = registerPlugin("CapacitorCookies", {
31718
+ web: () => new CapacitorCookiesPluginWeb
31719
+ });
31720
+ var readBlobAsBase64 = async (blob) => new Promise((resolve18, reject) => {
31721
+ const reader = new FileReader;
31722
+ reader.onload = () => {
31723
+ const base64String = reader.result;
31724
+ resolve18(base64String.indexOf(",") >= 0 ? base64String.split(",")[1] : base64String);
31725
+ };
31726
+ reader.onerror = (error) => reject(error);
31727
+ reader.readAsDataURL(blob);
31728
+ });
31729
+ var normalizeHttpHeaders = (headers = {}) => {
31730
+ const originalKeys = Object.keys(headers);
31731
+ const loweredKeys = Object.keys(headers).map((k) => k.toLocaleLowerCase());
31732
+ const normalized = loweredKeys.reduce((acc, key, index) => {
31733
+ acc[key] = headers[originalKeys[index]];
31734
+ return acc;
31735
+ }, {});
31736
+ return normalized;
31737
+ };
31738
+ var buildUrlParams = (params, shouldEncode = true) => {
31739
+ if (!params)
31740
+ return null;
31741
+ const output = Object.entries(params).reduce((accumulator, entry) => {
31742
+ const [key, value] = entry;
31743
+ let encodedValue;
31744
+ let item;
31745
+ if (Array.isArray(value)) {
31746
+ item = "";
31747
+ value.forEach((str) => {
31748
+ encodedValue = shouldEncode ? encodeURIComponent(str) : str;
31749
+ item += `${key}=${encodedValue}&`;
31750
+ });
31751
+ item.slice(0, -1);
31752
+ } else {
31753
+ encodedValue = shouldEncode ? encodeURIComponent(value) : value;
31754
+ item = `${key}=${encodedValue}`;
31755
+ }
31756
+ return `${accumulator}&${item}`;
31757
+ }, "");
31758
+ return output.substr(1);
31759
+ };
31760
+ var buildRequestInit = (options, extra = {}) => {
31761
+ const output = Object.assign({ method: options.method || "GET", headers: options.headers }, extra);
31762
+ const headers = normalizeHttpHeaders(options.headers);
31763
+ const type = headers["content-type"] || "";
31764
+ if (typeof options.data === "string") {
31765
+ output.body = options.data;
31766
+ } else if (type.includes("application/x-www-form-urlencoded")) {
31767
+ const params = new URLSearchParams;
31768
+ for (const [key, value] of Object.entries(options.data || {})) {
31769
+ params.set(key, value);
31770
+ }
31771
+ output.body = params.toString();
31772
+ } else if (type.includes("multipart/form-data") || options.data instanceof FormData) {
31773
+ const form = new FormData;
31774
+ if (options.data instanceof FormData) {
31775
+ options.data.forEach((value, key) => {
31776
+ form.append(key, value);
31777
+ });
31778
+ } else {
31779
+ for (const key of Object.keys(options.data)) {
31780
+ form.append(key, options.data[key]);
31781
+ }
31782
+ }
31783
+ output.body = form;
31784
+ const headers2 = new Headers(output.headers);
31785
+ headers2.delete("content-type");
31786
+ output.headers = headers2;
31787
+ } else if (type.includes("application/json") || typeof options.data === "object") {
31788
+ output.body = JSON.stringify(options.data);
31789
+ }
31790
+ return output;
31791
+ };
31792
+
31793
+ class CapacitorHttpPluginWeb extends WebPlugin {
31794
+ async request(options) {
31795
+ const requestInit = buildRequestInit(options, options.webFetchExtra);
31796
+ const urlParams = buildUrlParams(options.params, options.shouldEncodeUrlParams);
31797
+ const url = urlParams ? `${options.url}?${urlParams}` : options.url;
31798
+ const response = await fetch(url, requestInit);
31799
+ const contentType = response.headers.get("content-type") || "";
31800
+ let { responseType = "text" } = response.ok ? options : {};
31801
+ if (contentType.includes("application/json")) {
31802
+ responseType = "json";
31803
+ }
31804
+ let data;
31805
+ let blob;
31806
+ switch (responseType) {
31807
+ case "arraybuffer":
31808
+ case "blob":
31809
+ blob = await response.blob();
31810
+ data = await readBlobAsBase64(blob);
31811
+ break;
31812
+ case "json":
31813
+ data = await response.json();
31814
+ break;
31815
+ case "document":
31816
+ case "text":
31817
+ default:
31818
+ data = await response.text();
31819
+ }
31820
+ const headers = {};
31821
+ response.headers.forEach((value, key) => {
31822
+ headers[key] = value;
31823
+ });
31824
+ return {
31825
+ data,
31826
+ headers,
31827
+ status: response.status,
31828
+ url: response.url
31829
+ };
31830
+ }
31831
+ async get(options) {
31832
+ return this.request(Object.assign(Object.assign({}, options), { method: "GET" }));
31833
+ }
31834
+ async post(options) {
31835
+ return this.request(Object.assign(Object.assign({}, options), { method: "POST" }));
31836
+ }
31837
+ async put(options) {
31838
+ return this.request(Object.assign(Object.assign({}, options), { method: "PUT" }));
31839
+ }
31840
+ async patch(options) {
31841
+ return this.request(Object.assign(Object.assign({}, options), { method: "PATCH" }));
31842
+ }
31843
+ async delete(options) {
31844
+ return this.request(Object.assign(Object.assign({}, options), { method: "DELETE" }));
31845
+ }
31846
+ }
31847
+ var CapacitorHttp = registerPlugin("CapacitorHttp", {
31848
+ web: () => new CapacitorHttpPluginWeb
31849
+ });
31850
+ var SystemBarsStyle;
31851
+ (function(SystemBarsStyle2) {
31852
+ SystemBarsStyle2["Dark"] = "DARK";
31853
+ SystemBarsStyle2["Light"] = "LIGHT";
31854
+ SystemBarsStyle2["Default"] = "DEFAULT";
31855
+ })(SystemBarsStyle || (SystemBarsStyle = {}));
31856
+ var SystemBarType;
31857
+ (function(SystemBarType2) {
31858
+ SystemBarType2["StatusBar"] = "StatusBar";
31859
+ SystemBarType2["NavigationBar"] = "NavigationBar";
31860
+ })(SystemBarType || (SystemBarType = {}));
31861
+
31862
+ class SystemBarsPluginWeb extends WebPlugin {
31863
+ async setStyle() {
31864
+ this.unavailable("not available for web");
31865
+ }
31866
+ async setAnimation() {
31867
+ this.unavailable("not available for web");
31868
+ }
31869
+ async show() {
31870
+ this.unavailable("not available for web");
31871
+ }
31872
+ async hide() {
31873
+ this.unavailable("not available for web");
31874
+ }
31875
+ }
31876
+ var SystemBars = registerPlugin("SystemBars", {
31877
+ web: () => new SystemBarsPluginWeb
31878
+ });
31879
+
31880
+ // src/mobile/shellNativeObservability.ts
31881
+ var MAX_REPORTS = 8;
31882
+ var MAX_DETAIL_BYTES = 64 * 1024;
31883
+ var MAX_STRING_LENGTH = 8 * 1024;
31884
+ var MAX_REDACTION_DEPTH = 8;
31885
+ var REPORT_ID = /^[A-Za-z0-9._:-]{1,128}$/u;
31886
+ var REPORT_KINDS = new Set([
31887
+ "anr",
31888
+ "app-launch",
31889
+ "cpu-exception",
31890
+ "crash",
31891
+ "disk-write-exception",
31892
+ "hang",
31893
+ "initialization-failure",
31894
+ "low-memory",
31895
+ "native-crash",
31896
+ "resource-exhaustion",
31897
+ "signal"
31898
+ ]);
31899
+ var nativeObservability = registerPlugin("AbsoluteMobileObservability");
31900
+ var isRecord14 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
31901
+ 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]");
31902
+ var redactValue = (value, depth = 0) => {
31903
+ if (depth >= MAX_REDACTION_DEPTH)
31904
+ return "[TRUNCATED]";
31905
+ if (typeof value === "string")
31906
+ return redactText(value);
31907
+ if (value === null || typeof value === "number" || typeof value === "boolean")
31908
+ return value;
31909
+ if (Array.isArray(value))
31910
+ return value.slice(0, 64).map((entry) => redactValue(entry, depth + 1));
31911
+ if (!isRecord14(value))
31912
+ return String(value).slice(0, MAX_STRING_LENGTH);
31913
+ return Object.fromEntries(Object.entries(value).slice(0, 128).map(([key, entry]) => [
31914
+ key,
31915
+ /(?:authorization|cookie|password|secret|token)/iu.test(key) ? "[REDACTED]" : redactValue(entry, depth + 1)
31916
+ ]));
31917
+ };
31918
+ var parseReport = (value) => {
31919
+ if (!isRecord14(value) || !isRecord14(value.details))
31920
+ return;
31921
+ if (typeof value.id !== "string" || !REPORT_ID.test(value.id))
31922
+ return;
31923
+ if (typeof value.kind !== "string" || !REPORT_KINDS.has(value.kind))
31924
+ return;
31925
+ if (typeof value.occurredAt !== "number" || !Number.isFinite(value.occurredAt) || value.occurredAt <= 0)
31926
+ return;
31927
+ if (value.platform !== "android" && value.platform !== "ios")
31928
+ return;
31929
+ const details = redactValue(value.details);
31930
+ if (!isRecord14(details))
31931
+ return;
31932
+ if (new TextEncoder().encode(JSON.stringify(details)).byteLength > MAX_DETAIL_BYTES)
31933
+ return;
31934
+ return {
31935
+ details,
31936
+ id: value.id,
31937
+ kind: value.kind,
31938
+ occurredAt: value.occurredAt,
31939
+ platform: value.platform
31940
+ };
31941
+ };
31942
+ var sampled = (id, sampleRate) => {
31943
+ if (sampleRate >= 1)
31944
+ return true;
31945
+ let hash = 2166136261;
31946
+ for (const character of id) {
31947
+ hash ^= character.codePointAt(0) ?? 0;
31948
+ hash = Math.imul(hash, 16777619);
31949
+ }
31950
+ return (hash >>> 0) / 4294967296 < sampleRate;
31951
+ };
31952
+ var reportEvent = (report, manifest) => ({
31953
+ at: report.occurredAt,
31954
+ extra: {
31955
+ nativeDiagnostic: report.details,
31956
+ nativeDiagnosticId: report.id
31957
+ },
31958
+ groupingKey: `absolute-native:${report.platform}:${report.kind}`,
31959
+ level: [
31960
+ "app-launch",
31961
+ "cpu-exception",
31962
+ "disk-write-exception",
31963
+ "low-memory",
31964
+ "resource-exhaustion",
31965
+ "signal"
31966
+ ].includes(report.kind) ? "warning" : "error",
31967
+ message: `Native process diagnostic \u2014 ${report.kind}`,
31968
+ name: "AbsoluteMobileNativeDiagnostic",
31969
+ tags: {
31970
+ absoluteMobile: "true",
31971
+ mobileAppBuild: manifest.appBuild,
31972
+ mobileFailurePhase: "native-process",
31973
+ mobileManifestFormat: String(manifest.format),
31974
+ mobileNativeRuntime: manifest.nativeRuntime,
31975
+ mobilePlatform: report.platform,
31976
+ mobileRuntime: manifest.runtime
31977
+ }
31978
+ });
31979
+ var drainAbsoluteMobileNativeObservability = async (manifest, fetch2, options = {}) => {
31980
+ const config = manifest.observability;
31981
+ if (!config || !(options.native ?? Capacitor.isNativePlatform()))
31982
+ return 0;
31983
+ const plugin = options.plugin ?? nativeObservability;
31984
+ const pending = await plugin.pending();
31985
+ const reports = pending.reports.slice(0, MAX_REPORTS).map(parseReport).filter((report) => report !== undefined);
31986
+ const discarded = reports.filter((report) => !sampled(report.id, config.sampleRate));
31987
+ if (discarded.length > 0)
31988
+ await plugin.acknowledge({
31989
+ ids: discarded.map(({ id }) => id)
31990
+ });
31991
+ const selected = reports.filter((report) => sampled(report.id, config.sampleRate));
31992
+ if (selected.length === 0)
31993
+ return 0;
31994
+ const envelope = {
31995
+ ...config.environment ? { environment: config.environment } : {},
31996
+ events: selected.map((report) => reportEvent(report, manifest)),
31997
+ project: config.project,
31998
+ release: manifest.appBuild,
31999
+ v: 1
32000
+ };
32001
+ const response = await fetch2(config.endpoint, {
32002
+ body: JSON.stringify(envelope),
32003
+ headers: { "content-type": "application/json" },
32004
+ method: "POST"
32005
+ });
32006
+ if (!response.ok)
32007
+ return 0;
32008
+ await plugin.acknowledge({
32009
+ ids: selected.map(({ id }) => id)
32010
+ });
32011
+ return selected.length;
32012
+ };
30431
32013
  // node_modules/@absolutejs/beacon/dist/index.js
30432
32014
  var BEACON_SIGNAL = {
30433
32015
  AUTH_FAILURE_STORM: "auth_failure_storm",
@@ -30703,7 +32285,7 @@ var redactUrl = (value) => {
30703
32285
  };
30704
32286
  var redactUrlsInText = (value) => value.replace(/(?:[a-z][a-z\d+.-]*:\/\/|\/)[^\s"'<>]*[?#][^\s"'<>]*/gi, (candidate) => redactUrl(candidate));
30705
32287
  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) => {
32288
+ var redactValue2 = (value, key, seen, depth = 0) => {
30707
32289
  if (key !== undefined && isSensitiveField(key))
30708
32290
  return REDACTED;
30709
32291
  if (typeof value === "string") {
@@ -30721,9 +32303,9 @@ var redactValue = (value, key, seen, depth = 0) => {
30721
32303
  if (seen.has(value))
30722
32304
  return "[Circular]";
30723
32305
  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]) => [
32306
+ const redacted = Array.isArray(value) ? value.map((entry) => redactValue2(entry, undefined, seen, depth + 1)) : Object.fromEntries(Object.entries(value).map(([field2, entry]) => [
30725
32307
  field2,
30726
- redactValue(entry, field2, seen, depth + 1)
32308
+ redactValue2(entry, field2, seen, depth + 1)
30727
32309
  ]));
30728
32310
  seen.delete(value);
30729
32311
  return redacted;
@@ -30741,11 +32323,11 @@ var redactBeaconEvent = (event) => {
30741
32323
  if (event.tags !== undefined) {
30742
32324
  redacted.tags = Object.fromEntries(Object.entries(event.tags).map(([key, value]) => [
30743
32325
  key,
30744
- redactValue(value, key, new Set)
32326
+ redactValue2(value, key, new Set)
30745
32327
  ]));
30746
32328
  }
30747
32329
  if (event.extra !== undefined) {
30748
- redacted.extra = redactValue(event.extra, undefined, new Set);
32330
+ redacted.extra = redactValue2(event.extra, undefined, new Set);
30749
32331
  }
30750
32332
  return redacted;
30751
32333
  };
@@ -32790,9 +34372,9 @@ var createBeacon = (options) => {
32790
34372
  const viewportWidth = document.documentElement.clientWidth;
32791
34373
  const viewportHeight = document.documentElement.clientHeight;
32792
34374
  const controls = document.querySelectorAll(CONTROL_SELECTOR);
32793
- let sampled = 0;
34375
+ let sampled2 = 0;
32794
34376
  for (const control of Array.from(controls)) {
32795
- if (sampled >= OCCLUSION_SAMPLE_LIMIT)
34377
+ if (sampled2 >= OCCLUSION_SAMPLE_LIMIT)
32796
34378
  return;
32797
34379
  if (isScanExempt(control) || hasHiddenAncestor(control, true))
32798
34380
  continue;
@@ -32802,7 +34384,7 @@ var createBeacon = (options) => {
32802
34384
  const visibleRect = visibleControlRect(control, rect, viewportWidth, viewportHeight);
32803
34385
  if (visibleRect === null)
32804
34386
  continue;
32805
- sampled += 1;
34387
+ sampled2 += 1;
32806
34388
  const top = document.elementFromPoint((visibleRect.left + visibleRect.right) / 2, (visibleRect.top + visibleRect.bottom) / 2);
32807
34389
  if (top === null || top === control || control.contains(top) || top.contains(control) || crossesDialogBoundary(control, top) || isExtensionOwnedCover(top)) {
32808
34390
  continue;
@@ -32946,9 +34528,9 @@ var createBeacon = (options) => {
32946
34528
  ...Array.from(priorityCandidates),
32947
34529
  ...Array.from(proseCandidates)
32948
34530
  ];
32949
- let sampled = 0;
34531
+ let sampled2 = 0;
32950
34532
  for (const element of candidates) {
32951
- if (sampled >= INVISIBLE_TEXT_SAMPLE_LIMIT)
34533
+ if (sampled2 >= INVISIBLE_TEXT_SAMPLE_LIMIT)
32952
34534
  return;
32953
34535
  if (isScanExempt(element))
32954
34536
  continue;
@@ -32966,7 +34548,7 @@ var createBeacon = (options) => {
32966
34548
  const foreground = parseColor(style.color);
32967
34549
  if (foreground === null)
32968
34550
  continue;
32969
- sampled += 1;
34551
+ sampled2 += 1;
32970
34552
  const background = effectiveBackground(element);
32971
34553
  if (background === null)
32972
34554
  continue;
@@ -33009,11 +34591,11 @@ var createBeacon = (options) => {
33009
34591
  const roots = document.querySelectorAll(`[${BEACON_ATTRIBUTE.THEME}="adaptive"]`);
33010
34592
  const viewportWidth = document.documentElement.clientWidth;
33011
34593
  const viewportHeight = document.documentElement.clientHeight;
33012
- let sampled = 0;
34594
+ let sampled2 = 0;
33013
34595
  for (const root of Array.from(roots)) {
33014
34596
  const candidates = [root, ...Array.from(root.querySelectorAll("*"))];
33015
34597
  for (const element of candidates) {
33016
- if (sampled >= THEME_SURFACE_SAMPLE_LIMIT)
34598
+ if (sampled2 >= THEME_SURFACE_SAMPLE_LIMIT)
33017
34599
  return;
33018
34600
  if (element.closest(`[${BEACON_ATTRIBUTE.THEME}="allow"]`) !== null || isScanExempt(element) || element.matches("canvas, iframe, img, picture, svg, video")) {
33019
34601
  continue;
@@ -33035,7 +34617,7 @@ var createBeacon = (options) => {
33035
34617
  const surface = parseColor(style.backgroundColor);
33036
34618
  if (surface === null || surface.a < 0.95)
33037
34619
  continue;
33038
- sampled += 1;
34620
+ sampled2 += 1;
33039
34621
  const luminance = relativeLuminance(surface);
33040
34622
  const opposite = theme === "dark" ? luminance >= THEME_LIGHT_SURFACE_LUMINANCE_MIN : luminance <= THEME_DARK_SURFACE_LUMINANCE_MAX;
33041
34623
  if (!opposite)
@@ -35599,23 +37181,23 @@ import { pathToFileURL as pathToFileURL4 } from "url";
35599
37181
  import { createHash as createHash13, sign, verify } from "crypto";
35600
37182
  import {
35601
37183
  cp as cp3,
35602
- mkdir as mkdir16,
37184
+ mkdir as mkdir17,
35603
37185
  mkdtemp as mkdtemp8,
35604
- readdir as readdir6,
35605
- readFile as readFile21,
35606
- rename as rename15,
35607
- rm as rm12,
37186
+ readdir as readdir7,
37187
+ readFile as readFile22,
37188
+ rename as rename16,
37189
+ rm as rm13,
35608
37190
  stat as stat4,
35609
- writeFile as writeFile19
37191
+ writeFile as writeFile20
35610
37192
  } from "fs/promises";
35611
- import { dirname as dirname17, join as join23, relative as relative15, resolve as resolve18 } from "path";
37193
+ import { dirname as dirname18, join as join25, relative as relative15, resolve as resolve18 } from "path";
35612
37194
  var UPDATE_MANIFEST_FILE = "update.json";
35613
37195
  var UPDATE_FILES_DIRECTORY = "files";
35614
37196
  var sha2562 = (value) => createHash13("sha256").update(value).digest("hex");
35615
37197
  var listFiles = async (root, directory = root) => {
35616
- const entries = await readdir6(directory, { withFileTypes: true });
37198
+ const entries = await readdir7(directory, { withFileTypes: true });
35617
37199
  const paths = await Promise.all(entries.map(async (entry) => {
35618
- const path = join23(directory, entry.name);
37200
+ const path = join25(directory, entry.name);
35619
37201
  if (entry.isDirectory())
35620
37202
  return listFiles(root, path);
35621
37203
  if (!entry.isFile())
@@ -35625,10 +37207,10 @@ var listFiles = async (root, directory = root) => {
35625
37207
  return paths.flat().sort((left, right) => left.localeCompare(right));
35626
37208
  };
35627
37209
  var inspectFiles = async (root, paths) => Promise.all(paths.map(async (path) => {
35628
- const source = join23(root, path);
37210
+ const source = join25(root, path);
35629
37211
  const [metadata, contents] = await Promise.all([
35630
37212
  stat4(source),
35631
- readFile21(source)
37213
+ readFile22(source)
35632
37214
  ]);
35633
37215
  return { bytes: metadata.size, path, sha256: sha2562(contents) };
35634
37216
  }));
@@ -35663,26 +37245,26 @@ var buildAbsoluteMobileUpdate = async (options) => {
35663
37245
  value: signature.toString("base64")
35664
37246
  }
35665
37247
  });
35666
- await mkdir16(outputRoot, { recursive: true });
35667
- const outputDirectory = join23(outputRoot, manifest.releaseId);
35668
- const staging = await mkdtemp8(join23(outputRoot, ".stage-"));
37248
+ await mkdir17(outputRoot, { recursive: true });
37249
+ const outputDirectory = join25(outputRoot, manifest.releaseId);
37250
+ const staging = await mkdtemp8(join25(outputRoot, ".stage-"));
35669
37251
  try {
35670
- await cp3(bundleDirectory, join23(staging, UPDATE_FILES_DIRECTORY), {
37252
+ await cp3(bundleDirectory, join25(staging, UPDATE_FILES_DIRECTORY), {
35671
37253
  force: true,
35672
37254
  recursive: true
35673
37255
  });
35674
- await writeFile19(join23(staging, UPDATE_MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
37256
+ await writeFile20(join25(staging, UPDATE_MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
35675
37257
  `);
35676
- await rename15(staging, outputDirectory);
37258
+ await rename16(staging, outputDirectory);
35677
37259
  } catch (error) {
35678
- await rm12(staging, { force: true, recursive: true });
37260
+ await rm13(staging, { force: true, recursive: true });
35679
37261
  if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "EEXIST")
35680
37262
  throw new TypeError(`Mobile update ${manifest.releaseId} already exists.`, { cause: error });
35681
37263
  throw error;
35682
37264
  }
35683
37265
  return {
35684
37266
  manifest,
35685
- manifestPath: join23(outputDirectory, UPDATE_MANIFEST_FILE),
37267
+ manifestPath: join25(outputDirectory, UPDATE_MANIFEST_FILE),
35686
37268
  outputDirectory
35687
37269
  };
35688
37270
  };
@@ -35691,10 +37273,10 @@ var copyAbsoluteMobileUpdateFile = async (updateDirectory, path, destination) =>
35691
37273
  const root = resolve18(updateDirectory, UPDATE_FILES_DIRECTORY);
35692
37274
  if (!source.startsWith(`${root}/`))
35693
37275
  throw new TypeError("Update file escaped its release.");
35694
- await mkdir16(dirname17(destination), { recursive: true });
37276
+ await mkdir17(dirname18(destination), { recursive: true });
35695
37277
  await cp3(source, destination, { force: true });
35696
37278
  };
35697
- var readAbsoluteMobileUpdate = async (directory) => parseAbsoluteMobileUpdateManifest(JSON.parse(await readFile21(join23(resolve18(directory), UPDATE_MANIFEST_FILE), "utf8")));
37279
+ var readAbsoluteMobileUpdate = async (directory) => parseAbsoluteMobileUpdateManifest(JSON.parse(await readFile22(join25(resolve18(directory), UPDATE_MANIFEST_FILE), "utf8")));
35698
37280
  var verifyAbsoluteMobileUpdateSignature = (manifestValue, publicKey) => {
35699
37281
  const manifest = parseAbsoluteMobileUpdateManifest(manifestValue);
35700
37282
  const valid = verify("sha256", absoluteMobileUpdateSigningPayload(unsignedAbsoluteMobileUpdate(manifest)), { dsaEncoding: "ieee-p1363", key: publicKey }, Buffer.from(manifest.signature.value, "base64"));
@@ -35841,6 +37423,8 @@ export {
35841
37423
  MOBILE_PAGE_REQUEST_HEADERS,
35842
37424
  absoluteDeviceNativeRequirements,
35843
37425
  absoluteExpoExecutable,
37426
+ absoluteExpoNativeObservabilityFiles,
37427
+ absoluteExpoNativeObservabilityPaths,
35844
37428
  absoluteIosDeviceAcceptanceCommands,
35845
37429
  absoluteMobilePreviewDocument,
35846
37430
  absoluteMobileUpdateSigningPayload,
@@ -35853,6 +37437,7 @@ export {
35853
37437
  activateAbsoluteMobilePage,
35854
37438
  applyAbsoluteNativeDeepLinks,
35855
37439
  applyAbsoluteNativeDeviceCapabilities,
37440
+ applyAbsoluteNativeObservability,
35856
37441
  applyAbsoluteNativeUpdates,
35857
37442
  assertAbsoluteDeviceCapabilityPackages,
35858
37443
  buildAbsoluteAndroidRelease,
@@ -35892,6 +37477,8 @@ export {
35892
37477
  discoverAbsoluteDeviceCapabilities,
35893
37478
  discoverAbsoluteSyncSchema,
35894
37479
  disposeAbsoluteMobilePage,
37480
+ drainAbsoluteMobileNativeObservability,
37481
+ expoNativeObservabilityRuntimeSource,
35895
37482
  fetchAbsoluteMobilePage,
35896
37483
  finalizeAbsoluteExpoUpdateExport,
35897
37484
  finalizeAbsoluteMobileCompatibilityBuild,
@@ -36007,5 +37594,5 @@ export {
36007
37594
  writeAbsoluteMobileGithubWorkflow
36008
37595
  };
36009
37596
 
36010
- //# debugId=8E3E47B8F20AFE6B64756E2164756E21
37597
+ //# debugId=D5AE0A09F73FBA9E64756E2164756E21
36011
37598
  //# sourceMappingURL=index.js.map