@absolutejs/absolute 0.20.0-beta.55 → 0.20.0-beta.56

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.
@@ -815,7 +815,7 @@ import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
815
815
  import { extname as extname3, join as join13, relative as relative9, resolve as resolve11 } from "path";
816
816
  import { fileURLToPath } from "url";
817
817
  import ts from "typescript";
818
- var DEVICES_PACKAGE = "@absolutejs/devices", ADAPTERS, SOURCE_GLOB, IGNORED_DIRECTORIES, IDENTIFIER_PATTERN, providerModulePattern = (provider) => new RegExp(`^@absolutejs/devices-${provider}/[a-z][a-z0-9-]*$`, "u"), providerPackagePattern = (provider) => provider === "capacitor" ? /^@capacitor\/[a-z][a-z0-9-]*@\d+\.\d+\.\d+$/u : /^(?:expo-[a-z][a-z0-9-]*|@react-native-[a-z0-9-]+\/[a-z][a-z0-9-]*)@\d+\.\d+\.\d+$/u, providerLabel = (provider) => provider === "capacitor" ? "Capacitor" : "Expo", ANDROID_PERMISSION_PATTERN, IOS_USAGE_DESCRIPTIONS, IOS_PRIVACY_ACCESSED_API_REASONS, IOS_PRIVACY_ACCESSED_APIS, object2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), readJson = (path) => {
818
+ var DEVICES_PACKAGE = "@absolutejs/devices", ABSOLUTE_MOBILE_SHELL_DEVICE_CAPABILITIES, ADAPTERS, SOURCE_GLOB, IGNORED_DIRECTORIES, IDENTIFIER_PATTERN, providerModulePattern = (provider) => new RegExp(`^@absolutejs/devices-${provider}/[a-z][a-z0-9-]*$`, "u"), providerPackagePattern = (provider) => provider === "capacitor" ? /^@capacitor\/[a-z][a-z0-9-]*@\d+\.\d+\.\d+$/u : /^(?:expo-[a-z][a-z0-9-]*|@react-native-[a-z0-9-]+\/[a-z][a-z0-9-]*)@\d+\.\d+\.\d+$/u, providerLabel = (provider) => provider === "capacitor" ? "Capacitor" : "Expo", ANDROID_PERMISSION_PATTERN, IOS_USAGE_DESCRIPTIONS, IOS_PRIVACY_ACCESSED_API_REASONS, IOS_PRIVACY_ACCESSED_APIS, object2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), readJson = (path) => {
819
819
  const value = JSON.parse(readFileSync4(path, "utf8"));
820
820
  if (!object2(value))
821
821
  throw new TypeError(`${path} must contain an object.`);
@@ -859,12 +859,12 @@ var DEVICES_PACKAGE = "@absolutejs/devices", ADAPTERS, SOURCE_GLOB, IGNORED_DIRE
859
859
  const {
860
860
  privacyAccessedApis,
861
861
  pushNotifications,
862
- systemBars,
862
+ systemBars: systemBars2,
863
863
  usageDescriptions
864
864
  } = value;
865
865
  if (pushNotifications !== undefined && pushNotifications !== true)
866
866
  throw new TypeError(`${field2}.pushNotifications must be true.`);
867
- if (systemBars !== undefined && systemBars !== true)
867
+ if (systemBars2 !== undefined && systemBars2 !== true)
868
868
  throw new TypeError(`${field2}.systemBars must be true.`);
869
869
  if (usageDescriptions !== undefined && (!Array.isArray(usageDescriptions) || !usageDescriptions.every((purpose) => typeof purpose === "string" && IOS_USAGE_DESCRIPTIONS.has(purpose))))
870
870
  throw new TypeError(`${field2}.usageDescriptions contains an unsupported purpose.`);
@@ -872,7 +872,7 @@ var DEVICES_PACKAGE = "@absolutejs/devices", ADAPTERS, SOURCE_GLOB, IGNORED_DIRE
872
872
  return {
873
873
  ...privacy === undefined ? {} : { privacyAccessedApis: privacy },
874
874
  ...pushNotifications === true ? { pushNotifications: true } : {},
875
- ...systemBars === true ? { systemBars: true } : {},
875
+ ...systemBars2 === true ? { systemBars: true } : {},
876
876
  ...usageDescriptions === undefined ? {} : { usageDescriptions: [...usageDescriptions] }
877
877
  };
878
878
  }, parseProvider = (name, value, providerName) => {
@@ -1046,6 +1046,11 @@ var DEVICES_PACKAGE = "@absolutejs/devices", ADAPTERS, SOURCE_GLOB, IGNORED_DIRE
1046
1046
  }, resolveAbsoluteDeviceCapabilityPlan = (projectRoot, provider = "capacitor") => {
1047
1047
  const allProviders = loadAbsoluteDeviceCapabilityProviders(projectRoot, provider);
1048
1048
  const capabilities = discoverAbsoluteDeviceCapabilities(projectRoot, allProviders);
1049
+ for (const capability of ABSOLUTE_MOBILE_SHELL_DEVICE_CAPABILITIES) {
1050
+ if (allProviders[capability] && !capabilities.includes(capability))
1051
+ capabilities.push(capability);
1052
+ }
1053
+ capabilities.sort();
1049
1054
  const providers = {};
1050
1055
  for (const name of capabilities) {
1051
1056
  const capabilityProvider = allProviders[name];
@@ -1061,6 +1066,10 @@ var DEVICES_PACKAGE = "@absolutejs/devices", ADAPTERS, SOURCE_GLOB, IGNORED_DIRE
1061
1066
  };
1062
1067
  };
1063
1068
  var init_deviceCapabilities = __esm(() => {
1069
+ ABSOLUTE_MOBILE_SHELL_DEVICE_CAPABILITIES = [
1070
+ "keyboard",
1071
+ "systemBars"
1072
+ ];
1064
1073
  ADAPTERS = {
1065
1074
  capacitor: "@absolutejs/devices-capacitor",
1066
1075
  expo: "@absolutejs/devices-expo"
@@ -1116,7 +1125,7 @@ var init_telemetry = __esm(() => {
1116
1125
 
1117
1126
  // src/cli/telemetryEvent.ts
1118
1127
  import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
1119
- import { arch, platform } from "os";
1128
+ import { arch, platform as platform2 } from "os";
1120
1129
  import { dirname as dirname12, join as join18, parse } from "path";
1121
1130
  var checkCandidate = (candidate) => {
1122
1131
  if (!existsSync6(candidate)) {
@@ -1157,7 +1166,7 @@ var checkCandidate = (candidate) => {
1157
1166
  arch: arch(),
1158
1167
  bunVersion: Bun.version,
1159
1168
  event,
1160
- os: platform(),
1169
+ os: platform2(),
1161
1170
  payload,
1162
1171
  timestamp: new Date().toISOString(),
1163
1172
  version: getVersion()
@@ -1645,6 +1654,1254 @@ var verifyAbsoluteMobileCompatibilityProducer = async (release, maxProducerBytes
1645
1654
  }
1646
1655
  return release;
1647
1656
  };
1657
+ // node_modules/@absolutejs/devices/dist/index.js
1658
+ class DeviceError extends Error {
1659
+ code;
1660
+ cause;
1661
+ constructor(code, message, options) {
1662
+ super(message);
1663
+ this.name = "DeviceError";
1664
+ this.code = code;
1665
+ this.cause = options?.cause;
1666
+ }
1667
+ }
1668
+ var DEFAULT_DEVICE_DOCUMENT_MAX_BYTES = 64 * 1024 * 1024;
1669
+ var SHARE_PROTOCOLS = new Set(["http:", "https:"]);
1670
+ var MAX_NOTIFICATION_ID = 2147483647;
1671
+ var MAX_NOTIFICATION_DATA_BYTES = 16 * 1024;
1672
+ var availableCapability = (fidelity, native) => ({
1673
+ available: true,
1674
+ fidelity,
1675
+ ...native === undefined ? {} : { native }
1676
+ });
1677
+ var unavailableCapability = (reason, message, native) => ({
1678
+ available: false,
1679
+ reason,
1680
+ ...message === undefined ? {} : { message },
1681
+ ...native === undefined ? {} : { native }
1682
+ });
1683
+ var requireFiniteNonNegative = (value, field) => {
1684
+ if (!Number.isFinite(value) || value < 0)
1685
+ throw new TypeError(`${field} must be a finite non-negative number.`);
1686
+ };
1687
+ var requireFinitePositive = (value, field) => {
1688
+ if (!Number.isFinite(value) || value <= 0)
1689
+ throw new TypeError(`${field} must be a finite positive number.`);
1690
+ };
1691
+ var validateDeviceLocationOptions = (options) => {
1692
+ const watchOptions = options;
1693
+ if (options?.maximumAgeMs !== undefined)
1694
+ requireFiniteNonNegative(options.maximumAgeMs, "Location maximumAgeMs");
1695
+ if (options?.timeoutMs !== undefined)
1696
+ requireFinitePositive(options.timeoutMs, "Location timeoutMs");
1697
+ if (watchOptions?.intervalMs !== undefined)
1698
+ requireFinitePositive(watchOptions.intervalMs, "Location intervalMs");
1699
+ if (watchOptions?.minimumUpdateIntervalMs !== undefined)
1700
+ requireFinitePositive(watchOptions.minimumUpdateIntervalMs, "Location minimumUpdateIntervalMs");
1701
+ return options;
1702
+ };
1703
+ var normalizeDeviceLocalNotification = (notification) => {
1704
+ if (!Number.isSafeInteger(notification.id) || notification.id < 1 || notification.id > MAX_NOTIFICATION_ID)
1705
+ throw new TypeError(`Local notification id must be an integer from 1 to ${MAX_NOTIFICATION_ID}.`);
1706
+ if (typeof notification.title !== "string" || notification.title.trim().length === 0 || notification.title.length > 200)
1707
+ throw new TypeError("Local notification title must contain 1 to 200 characters.");
1708
+ if (typeof notification.body !== "string" || notification.body.trim().length === 0 || notification.body.length > 4000)
1709
+ throw new TypeError("Local notification body must contain 1 to 4000 characters.");
1710
+ if (notification.scheduledAtMs !== undefined && (!Number.isFinite(notification.scheduledAtMs) || Number.isNaN(new Date(notification.scheduledAtMs).getTime())))
1711
+ throw new TypeError("Local notification scheduledAtMs must be a valid Unix timestamp.");
1712
+ let data;
1713
+ if (notification.data !== undefined) {
1714
+ const prototype = Object.getPrototypeOf(notification.data);
1715
+ if (typeof notification.data !== "object" || notification.data === null || prototype !== Object.prototype && prototype !== null)
1716
+ throw new TypeError("Local notification data must be a plain string record.");
1717
+ const entries = Object.entries(notification.data);
1718
+ if (entries.length > 32 || entries.some(([key, value]) => key.length === 0 || key.length > 128 || typeof value !== "string" || value.length > 4000))
1719
+ throw new TypeError("Local notification data supports up to 32 non-empty keys and string values up to 4000 characters.");
1720
+ data = Object.fromEntries(entries);
1721
+ if (new TextEncoder().encode(JSON.stringify(data)).byteLength > MAX_NOTIFICATION_DATA_BYTES)
1722
+ throw new TypeError("Local notification data cannot exceed 16 KiB.");
1723
+ }
1724
+ return {
1725
+ body: notification.body,
1726
+ ...data === undefined ? {} : { data },
1727
+ id: notification.id,
1728
+ ...notification.scheduledAtMs === undefined ? {} : { scheduledAtMs: notification.scheduledAtMs },
1729
+ title: notification.title
1730
+ };
1731
+ };
1732
+ var runtimeCapability = (runtime) => {
1733
+ if (runtime === "ssr")
1734
+ return unavailableCapability("unavailable", "Device capabilities are unavailable during server rendering.");
1735
+ if (runtime === "web")
1736
+ return availableCapability("web");
1737
+ if (runtime === "test")
1738
+ return availableCapability("emulated");
1739
+ return availableCapability("native");
1740
+ };
1741
+ var normalizeDeviceShareContent = (content) => {
1742
+ const normalized = Object.fromEntries(Object.entries(content).filter(([, value]) => typeof value === "string" && value.length > 0));
1743
+ if (Object.keys(normalized).length === 0)
1744
+ throw new TypeError("Device share content cannot be empty.");
1745
+ if (normalized.url) {
1746
+ const url = new URL(normalized.url);
1747
+ if (url.username || url.password || !SHARE_PROTOCOLS.has(url.protocol))
1748
+ throw new TypeError("Device share URLs must be credential-free HTTP(S) URLs.");
1749
+ normalized.url = url.href;
1750
+ }
1751
+ return normalized;
1752
+ };
1753
+ var noopSubscription = () => () => {
1754
+ return;
1755
+ };
1756
+ var unavailable = (capability) => new DeviceError("unavailable", `${capability} is unavailable during server rendering.`);
1757
+ var createSsrDeviceAdapter = () => ({
1758
+ runtime: "ssr",
1759
+ camera: {
1760
+ capability: async () => ({
1761
+ available: false,
1762
+ reason: "unavailable",
1763
+ message: "Camera is unavailable during server rendering."
1764
+ }),
1765
+ queryPermission: async () => ({ canRequest: false, state: "unavailable" }),
1766
+ requestPermission: async () => ({
1767
+ canRequest: false,
1768
+ state: "unavailable"
1769
+ }),
1770
+ takePhoto: async () => {
1771
+ throw unavailable("Camera");
1772
+ }
1773
+ },
1774
+ clipboard: {
1775
+ capability: async () => ({
1776
+ available: false,
1777
+ reason: "unavailable",
1778
+ message: "Clipboard is unavailable during server rendering."
1779
+ }),
1780
+ readText: async () => {
1781
+ throw unavailable("Clipboard");
1782
+ },
1783
+ writeText: async () => {
1784
+ throw unavailable("Clipboard");
1785
+ }
1786
+ },
1787
+ documents: {
1788
+ capability: async () => ({
1789
+ available: false,
1790
+ reason: "unavailable",
1791
+ message: "Documents are unavailable during server rendering."
1792
+ }),
1793
+ export: async () => {
1794
+ throw unavailable("Documents");
1795
+ },
1796
+ open: async () => {
1797
+ throw unavailable("Documents");
1798
+ },
1799
+ pick: async () => {
1800
+ throw unavailable("Documents");
1801
+ }
1802
+ },
1803
+ haptics: {
1804
+ capability: async () => ({
1805
+ available: false,
1806
+ reason: "unavailable",
1807
+ message: "Haptics are unavailable during server rendering."
1808
+ }),
1809
+ impact: async () => {
1810
+ return;
1811
+ },
1812
+ notification: async () => {
1813
+ return;
1814
+ },
1815
+ selectionChanged: async () => {
1816
+ return;
1817
+ },
1818
+ vibrate: async () => {
1819
+ return;
1820
+ }
1821
+ },
1822
+ keyboard: {
1823
+ capability: async () => ({
1824
+ available: false,
1825
+ reason: "unavailable",
1826
+ message: "Keyboard state is unavailable during server rendering."
1827
+ }),
1828
+ dismiss: async () => {
1829
+ throw unavailable("Keyboard");
1830
+ },
1831
+ getState: async () => ({ heightPx: 0, visible: false }),
1832
+ onChange: async () => noopSubscription()
1833
+ },
1834
+ localNotifications: {
1835
+ cancel: async () => {
1836
+ throw unavailable("Local notifications");
1837
+ },
1838
+ capability: async () => ({
1839
+ available: false,
1840
+ reason: "unavailable",
1841
+ message: "Local notifications are unavailable during server rendering."
1842
+ }),
1843
+ onAction: async () => noopSubscription(),
1844
+ onReceived: async () => noopSubscription(),
1845
+ pending: async () => [],
1846
+ queryPermission: async () => ({
1847
+ canRequest: false,
1848
+ state: "unavailable"
1849
+ }),
1850
+ requestPermission: async () => ({
1851
+ canRequest: false,
1852
+ state: "unavailable"
1853
+ }),
1854
+ schedule: async () => {
1855
+ throw unavailable("Local notifications");
1856
+ }
1857
+ },
1858
+ pushNotifications: {
1859
+ capability: async () => ({
1860
+ available: false,
1861
+ reason: "unavailable",
1862
+ message: "Push notifications are unavailable during server rendering."
1863
+ }),
1864
+ disable: async () => {
1865
+ return;
1866
+ },
1867
+ enable: async () => {
1868
+ throw unavailable("Push notifications");
1869
+ },
1870
+ onAction: async () => noopSubscription(),
1871
+ onReceived: async () => noopSubscription(),
1872
+ queryPermission: async () => ({
1873
+ canRequest: false,
1874
+ state: "unavailable"
1875
+ }),
1876
+ requestPermission: async () => ({
1877
+ canRequest: false,
1878
+ state: "unavailable"
1879
+ })
1880
+ },
1881
+ platform: {
1882
+ getInfo: async () => ({
1883
+ formFactor: "unknown",
1884
+ isNative: false,
1885
+ os: "unknown",
1886
+ prefersReducedMotion: false,
1887
+ runtime: "ssr",
1888
+ safeAreaInsets: { bottom: 0, left: 0, right: 0, top: 0 }
1889
+ })
1890
+ },
1891
+ lifecycle: {
1892
+ getState: async () => "inactive",
1893
+ onChange: async () => noopSubscription(),
1894
+ onRestoredOperation: async () => noopSubscription(),
1895
+ onResume: async () => noopSubscription()
1896
+ },
1897
+ links: {
1898
+ getLaunchUrl: async () => null,
1899
+ onOpen: async () => noopSubscription(),
1900
+ openExternal: async () => {
1901
+ throw unavailable("External links");
1902
+ }
1903
+ },
1904
+ location: {
1905
+ capability: async () => ({
1906
+ available: false,
1907
+ reason: "unavailable",
1908
+ message: "Location is unavailable during server rendering."
1909
+ }),
1910
+ current: async () => {
1911
+ throw unavailable("Location");
1912
+ },
1913
+ queryPermission: async () => ({
1914
+ canRequest: false,
1915
+ precision: "unknown",
1916
+ state: "unavailable"
1917
+ }),
1918
+ requestPermission: async () => ({
1919
+ canRequest: false,
1920
+ precision: "unknown",
1921
+ state: "unavailable"
1922
+ }),
1923
+ watch: async () => {
1924
+ throw unavailable("Location");
1925
+ }
1926
+ },
1927
+ network: {
1928
+ getStatus: async () => ({ connected: false, connectionType: "unknown" }),
1929
+ onChange: async () => noopSubscription()
1930
+ },
1931
+ photos: {
1932
+ capability: async () => ({
1933
+ available: false,
1934
+ reason: "unavailable",
1935
+ message: "Photo picker is unavailable during server rendering."
1936
+ }),
1937
+ pick: async () => {
1938
+ throw unavailable("Photo picker");
1939
+ }
1940
+ },
1941
+ share: {
1942
+ capability: async () => ({
1943
+ available: false,
1944
+ reason: "unavailable",
1945
+ message: "Sharing is unavailable during server rendering."
1946
+ }),
1947
+ share: async () => {
1948
+ throw unavailable("Sharing");
1949
+ }
1950
+ },
1951
+ storage: {
1952
+ clear: async () => {
1953
+ throw unavailable("Storage");
1954
+ },
1955
+ get: async () => null,
1956
+ keys: async () => [],
1957
+ remove: async () => {
1958
+ throw unavailable("Storage");
1959
+ },
1960
+ set: async () => {
1961
+ throw unavailable("Storage");
1962
+ }
1963
+ },
1964
+ systemBars: {
1965
+ capability: async () => ({
1966
+ available: false,
1967
+ reason: "unavailable",
1968
+ message: "System bars are unavailable during server rendering."
1969
+ }),
1970
+ setAppearance: async () => {
1971
+ throw unavailable("System bars");
1972
+ },
1973
+ setVisible: async () => {
1974
+ throw unavailable("System bars");
1975
+ }
1976
+ }
1977
+ });
1978
+ var COARSE_TABLET_MIN_WIDTH = 768;
1979
+ var detectOs = () => {
1980
+ const platform = navigator.userAgent.toLowerCase();
1981
+ if (platform.includes("android"))
1982
+ return "android";
1983
+ if (/iphone|ipad|ipod/.test(platform))
1984
+ return "ios";
1985
+ if (platform.includes("win"))
1986
+ return "windows";
1987
+ if (platform.includes("mac"))
1988
+ return "macos";
1989
+ if (platform.includes("linux"))
1990
+ return "linux";
1991
+ return "unknown";
1992
+ };
1993
+ var networkStatus = () => {
1994
+ const connection = Reflect.get(navigator, "connection");
1995
+ const type = connection?.type;
1996
+ const connectionType = type === "wifi" || type === "cellular" || type === "ethernet" ? type : navigator.onLine ? "unknown" : "none";
1997
+ return { connected: navigator.onLine, connectionType };
1998
+ };
1999
+ var safeAreaInsets = () => {
2000
+ if (!document.body)
2001
+ return { bottom: 0, left: 0, right: 0, top: 0 };
2002
+ const probe = document.createElement("div");
2003
+ probe.style.cssText = [
2004
+ "position:fixed",
2005
+ "visibility:hidden",
2006
+ "pointer-events:none",
2007
+ "padding-top:env(safe-area-inset-top, 0px)",
2008
+ "padding-right:env(safe-area-inset-right, 0px)",
2009
+ "padding-bottom:env(safe-area-inset-bottom, 0px)",
2010
+ "padding-left:env(safe-area-inset-left, 0px)"
2011
+ ].join(";");
2012
+ document.body.append(probe);
2013
+ const style = getComputedStyle(probe);
2014
+ const insets = {
2015
+ bottom: Number.parseFloat(style.paddingBottom) || 0,
2016
+ left: Number.parseFloat(style.paddingLeft) || 0,
2017
+ right: Number.parseFloat(style.paddingRight) || 0,
2018
+ top: Number.parseFloat(style.paddingTop) || 0
2019
+ };
2020
+ probe.remove();
2021
+ return insets;
2022
+ };
2023
+ var matches = (query) => typeof matchMedia === "function" && matchMedia(query).matches;
2024
+ var editableElement = (value) => value instanceof HTMLInputElement || value instanceof HTMLTextAreaElement || value instanceof HTMLElement && value.isContentEditable;
2025
+ var webKeyboardState = () => {
2026
+ const viewport = window.visualViewport;
2027
+ const heightPx = viewport ? Math.max(0, Math.round(window.innerHeight - viewport.height - viewport.offsetTop)) : 0;
2028
+ return {
2029
+ heightPx,
2030
+ visible: editableElement(document.activeElement) && heightPx > 50
2031
+ };
2032
+ };
2033
+ var requireStorage = () => {
2034
+ try {
2035
+ return window.localStorage;
2036
+ } catch (cause) {
2037
+ throw new DeviceError("unavailable", "Browser storage is unavailable.", {
2038
+ cause
2039
+ });
2040
+ }
2041
+ };
2042
+ var webFailure = (error, message) => {
2043
+ const name = typeof error === "object" && error !== null ? Reflect.get(error, "name") : undefined;
2044
+ if (name === "AbortError")
2045
+ return new DeviceError("cancelled", "The device action was cancelled.", {
2046
+ cause: error
2047
+ });
2048
+ if (name === "NotAllowedError")
2049
+ return new DeviceError("permission-denied", message, { cause: error });
2050
+ return new DeviceError("failed", message, { cause: error });
2051
+ };
2052
+ var webOperation = async (message, operation) => {
2053
+ try {
2054
+ return await operation();
2055
+ } catch (error) {
2056
+ throw webFailure(error, message);
2057
+ }
2058
+ };
2059
+ var requireMaximumBytes = (value) => {
2060
+ const maximumBytes = value ?? DEFAULT_DEVICE_DOCUMENT_MAX_BYTES;
2061
+ if (!Number.isSafeInteger(maximumBytes) || maximumBytes < 1)
2062
+ throw new TypeError("Document maximumBytes must be a positive safe integer.");
2063
+ return maximumBytes;
2064
+ };
2065
+ var requireDocumentName = (name) => {
2066
+ if (name.length === 0 || name === "." || name === ".." || /[/\\\u0000-\u001f\u007f]/.test(name))
2067
+ throw new TypeError("Document names must be safe leaf filenames.");
2068
+ return name;
2069
+ };
2070
+ var requireDocumentAccept = (accept) => {
2071
+ if (accept === undefined)
2072
+ return;
2073
+ if (accept.length === 0 || accept.some((value) => !/^\.[a-z0-9]+$/i.test(value) && !/^[a-z0-9!#$&^_.+-]+\/(?:[a-z0-9!#$&^_.+*-]+)$/i.test(value)))
2074
+ throw new TypeError("Document accept entries must be MIME types or file extensions.");
2075
+ return accept.join(",");
2076
+ };
2077
+ var requireDocumentLimit = (limit) => {
2078
+ const value = limit ?? 1;
2079
+ if (!Number.isSafeInteger(value) || value < 1)
2080
+ throw new TypeError("Document limit must be a positive safe integer.");
2081
+ return value;
2082
+ };
2083
+ var documentBlob = (options) => {
2084
+ requireDocumentName(options.name);
2085
+ const mimeType = options.mimeType ?? (options.content instanceof Blob && options.content.type ? options.content.type : typeof options.content === "string" ? "text/plain;charset=utf-8" : "application/octet-stream");
2086
+ const blob = options.content instanceof Blob ? options.content : new Blob([options.content], { type: mimeType });
2087
+ if (blob.size > requireMaximumBytes(options.maximumBytes))
2088
+ throw new DeviceError("failed", `Document ${options.name} exceeds the configured byte limit.`);
2089
+ return { blob, mimeType, name: options.name, sizeBytes: blob.size };
2090
+ };
2091
+ var releaseObjectUrl = (url) => setTimeout(() => URL.revokeObjectURL(url), 0);
2092
+ var pickDocuments = async (options = {}) => {
2093
+ if (typeof document === "undefined" || !document.body)
2094
+ throw new DeviceError("unsupported", "Browser document selection is unavailable.");
2095
+ const limit = requireDocumentLimit(options.limit);
2096
+ const maximumBytes = requireMaximumBytes(options.maximumBytes);
2097
+ const input = document.createElement("input");
2098
+ input.type = "file";
2099
+ input.multiple = limit > 1;
2100
+ input.style.display = "none";
2101
+ const accept = requireDocumentAccept(options.accept);
2102
+ if (accept)
2103
+ input.accept = accept;
2104
+ document.body.append(input);
2105
+ try {
2106
+ const files = await new Promise((resolve, reject) => {
2107
+ const cleanup = () => {
2108
+ input.removeEventListener("change", changed);
2109
+ input.removeEventListener("cancel", cancelled);
2110
+ };
2111
+ const changed = () => {
2112
+ cleanup();
2113
+ resolve(Array.from(input.files ?? []));
2114
+ };
2115
+ const cancelled = () => {
2116
+ cleanup();
2117
+ resolve([]);
2118
+ };
2119
+ input.addEventListener("change", changed, { once: true });
2120
+ input.addEventListener("cancel", cancelled, { once: true });
2121
+ try {
2122
+ input.click();
2123
+ } catch (error) {
2124
+ cleanup();
2125
+ reject(error);
2126
+ }
2127
+ });
2128
+ if (files.length === 0)
2129
+ throw new DeviceError("cancelled", "Document selection was cancelled.");
2130
+ const selected = files.slice(0, limit);
2131
+ const oversized = selected.find((file) => file.size > maximumBytes);
2132
+ if (oversized)
2133
+ throw new DeviceError("failed", `Document ${oversized.name} exceeds the configured byte limit.`);
2134
+ return selected.map((file) => ({
2135
+ blob: file,
2136
+ ...Number.isFinite(file.lastModified) ? { lastModifiedMs: file.lastModified } : {},
2137
+ mimeType: file.type || "application/octet-stream",
2138
+ name: file.name,
2139
+ sizeBytes: file.size
2140
+ }));
2141
+ } finally {
2142
+ input.remove();
2143
+ }
2144
+ };
2145
+ var locationFailure = (error) => {
2146
+ const code = typeof error === "object" && error !== null ? Reflect.get(error, "code") : undefined;
2147
+ if (code === 1)
2148
+ return new DeviceError("permission-denied", "Browser location permission was denied.", { cause: error });
2149
+ if (code === 2)
2150
+ return new DeviceError("temporarily-unavailable", "The browser could not determine the current location.", { cause: error });
2151
+ if (code === 3)
2152
+ return new DeviceError("temporarily-unavailable", "The browser location request timed out.", { cause: error });
2153
+ return webFailure(error, "Browser location failed.");
2154
+ };
2155
+ var locationPosition = (value) => {
2156
+ const { coords } = value;
2157
+ if (!Number.isFinite(coords.latitude) || !Number.isFinite(coords.longitude) || !Number.isFinite(coords.accuracy) || !Number.isFinite(value.timestamp))
2158
+ throw new DeviceError("failed", "The browser returned an invalid location position.");
2159
+ return {
2160
+ accuracyMeters: coords.accuracy,
2161
+ ...coords.altitudeAccuracy === null ? {} : { altitudeAccuracyMeters: coords.altitudeAccuracy },
2162
+ ...coords.altitude === null ? {} : { altitudeMeters: coords.altitude },
2163
+ ...coords.heading === null ? {} : { headingDegrees: coords.heading },
2164
+ latitude: coords.latitude,
2165
+ longitude: coords.longitude,
2166
+ native: value,
2167
+ ...coords.speed === null ? {} : { speedMetersPerSecond: coords.speed },
2168
+ timestampMs: value.timestamp
2169
+ };
2170
+ };
2171
+ var webLocationOptions = (options) => ({
2172
+ enableHighAccuracy: options?.accuracy === "high",
2173
+ ...options?.maximumAgeMs === undefined ? {} : { maximumAge: options.maximumAgeMs },
2174
+ ...options?.timeoutMs === undefined ? {} : { timeout: options.timeoutMs }
2175
+ });
2176
+ var clipboardStatus = (operation) => {
2177
+ const method = operation === "read" ? "readText" : "writeText";
2178
+ return typeof navigator.clipboard?.[method] === "function" ? availableCapability("web") : unavailableCapability("unsupported", `Clipboard ${operation} is not supported by this browser context.`);
2179
+ };
2180
+ var shareStatus = (content) => {
2181
+ if (typeof navigator.share !== "function")
2182
+ return unavailableCapability("unsupported", "The Web Share API is not supported by this browser.");
2183
+ if (content && typeof navigator.canShare === "function") {
2184
+ const normalized = normalizeDeviceShareContent(content);
2185
+ if (!navigator.canShare(normalized))
2186
+ return unavailableCapability("unsupported", "This browser cannot share the requested content.");
2187
+ }
2188
+ return availableCapability("web");
2189
+ };
2190
+ var vibrate = (durationMs) => {
2191
+ if (typeof navigator.vibrate === "function")
2192
+ navigator.vibrate(durationMs);
2193
+ };
2194
+ var requirePhotoOptions = (options) => {
2195
+ if (options?.limit !== undefined && (!Number.isInteger(options.limit) || options.limit < 1))
2196
+ throw new TypeError("Photo picker limit must be a positive integer.");
2197
+ const transform = options?.transform;
2198
+ if (transform && (!Number.isInteger(transform.width) || transform.width < 1 || !Number.isInteger(transform.height) || transform.height < 1 || transform.quality !== undefined && (!Number.isInteger(transform.quality) || transform.quality < 0 || transform.quality > 100)))
2199
+ throw new TypeError("Photo transforms require positive integer dimensions and quality from 0 to 100.");
2200
+ };
2201
+ var pickImages = async (options) => {
2202
+ if (typeof document === "undefined" || typeof URL.createObjectURL !== "function")
2203
+ throw new DeviceError("unsupported", "Browser photo selection is unavailable.");
2204
+ const input = document.createElement("input");
2205
+ input.type = "file";
2206
+ input.accept = "image/*";
2207
+ input.multiple = options.multiple ?? false;
2208
+ if (options.capture)
2209
+ input.setAttribute("capture", options.capture);
2210
+ input.style.display = "none";
2211
+ document.body?.append(input);
2212
+ try {
2213
+ const files = await new Promise((resolve, reject) => {
2214
+ const finish = (selected) => {
2215
+ input.removeEventListener("change", changed);
2216
+ input.removeEventListener("cancel", cancelled);
2217
+ resolve(selected);
2218
+ };
2219
+ const changed = () => finish(Array.from(input.files ?? []));
2220
+ const cancelled = () => finish([]);
2221
+ input.addEventListener("change", changed, { once: true });
2222
+ input.addEventListener("cancel", cancelled, { once: true });
2223
+ try {
2224
+ input.click();
2225
+ } catch (error) {
2226
+ reject(error);
2227
+ }
2228
+ });
2229
+ if (files.length === 0)
2230
+ throw new DeviceError("cancelled", "Photo selection was cancelled.");
2231
+ return files.slice(0, options.limit).map((file) => ({
2232
+ ...file.type ? { format: file.type.replace(/^image\//, "") } : {},
2233
+ name: file.name,
2234
+ sizeBytes: file.size,
2235
+ webPath: URL.createObjectURL(file)
2236
+ }));
2237
+ } finally {
2238
+ input.remove();
2239
+ }
2240
+ };
2241
+ var createWebDeviceAdapter = () => {
2242
+ let cameraAuthorized = false;
2243
+ let locationAuthorized = false;
2244
+ const notificationActions = new Set;
2245
+ const notificationReceived = new Set;
2246
+ const pendingNotifications = new Map;
2247
+ const notificationPermission = () => {
2248
+ if (typeof Notification === "undefined")
2249
+ return { canRequest: false, state: "unavailable" };
2250
+ return Notification.permission === "granted" ? { canRequest: false, state: "granted" } : Notification.permission === "denied" ? { canRequest: false, state: "denied" } : { canRequest: true, state: "prompt" };
2251
+ };
2252
+ const showNotification = (notification) => {
2253
+ let displayed;
2254
+ try {
2255
+ displayed = new Notification(notification.title, {
2256
+ body: notification.body,
2257
+ data: notification.data,
2258
+ tag: `absolutejs:${notification.id}`
2259
+ });
2260
+ } catch (error) {
2261
+ throw webFailure(error, "Browser notification display failed.");
2262
+ }
2263
+ for (const listener of notificationReceived)
2264
+ listener(notification);
2265
+ displayed.onclick = () => {
2266
+ for (const listener of notificationActions)
2267
+ listener({ actionId: "tap", notification });
2268
+ window.focus();
2269
+ };
2270
+ };
2271
+ const queryLocationPermission = async () => {
2272
+ if (typeof navigator.geolocation === "undefined")
2273
+ return {
2274
+ canRequest: false,
2275
+ precision: "unknown",
2276
+ state: "unavailable"
2277
+ };
2278
+ if (locationAuthorized)
2279
+ return { canRequest: false, precision: "unknown", state: "granted" };
2280
+ if (typeof navigator.permissions?.query !== "function")
2281
+ return { canRequest: true, precision: "unknown", state: "prompt" };
2282
+ try {
2283
+ const status = await navigator.permissions.query({
2284
+ name: "geolocation"
2285
+ });
2286
+ return {
2287
+ canRequest: status.state === "prompt",
2288
+ precision: "unknown",
2289
+ state: status.state
2290
+ };
2291
+ } catch {
2292
+ return { canRequest: true, precision: "unknown", state: "prompt" };
2293
+ }
2294
+ };
2295
+ const currentLocation = async (options) => {
2296
+ validateDeviceLocationOptions(options);
2297
+ if (typeof navigator.geolocation === "undefined")
2298
+ throw new DeviceError("unsupported", "Browser geolocation is unavailable.");
2299
+ return new Promise((resolve, reject) => navigator.geolocation.getCurrentPosition((position) => {
2300
+ try {
2301
+ resolve(locationPosition(position));
2302
+ } catch (error) {
2303
+ reject(error);
2304
+ }
2305
+ }, (error) => reject(locationFailure(error)), webLocationOptions(options)));
2306
+ };
2307
+ return {
2308
+ runtime: "web",
2309
+ camera: {
2310
+ capability: async () => typeof document !== "undefined" ? availableCapability("web") : unavailableCapability("unsupported", "Browser camera capture is unavailable."),
2311
+ queryPermission: async () => ({
2312
+ canRequest: !cameraAuthorized,
2313
+ state: cameraAuthorized ? "granted" : "prompt"
2314
+ }),
2315
+ requestPermission: async () => {
2316
+ cameraAuthorized = true;
2317
+ return { canRequest: false, state: "granted" };
2318
+ },
2319
+ takePhoto: async (options) => {
2320
+ requirePhotoOptions(options);
2321
+ return (await pickImages({
2322
+ capture: options?.direction === "front" ? "user" : "environment"
2323
+ }))[0];
2324
+ }
2325
+ },
2326
+ clipboard: {
2327
+ capability: async (operation = "write") => clipboardStatus(operation),
2328
+ readText: async () => {
2329
+ if (!clipboardStatus("read").available)
2330
+ throw new DeviceError("unsupported", "Clipboard read is unavailable.");
2331
+ return webOperation("Browser clipboard read was denied.", () => navigator.clipboard.readText());
2332
+ },
2333
+ writeText: async (value) => {
2334
+ if (!clipboardStatus("write").available)
2335
+ throw new DeviceError("unsupported", "Clipboard write is unavailable.");
2336
+ await webOperation("Browser clipboard write was denied.", () => navigator.clipboard.writeText(value));
2337
+ }
2338
+ },
2339
+ documents: {
2340
+ capability: async () => typeof document !== "undefined" && typeof URL.createObjectURL === "function" ? availableCapability("web") : unavailableCapability("unsupported", "Browser document handling is unavailable."),
2341
+ export: async (options) => {
2342
+ const result = documentBlob(options);
2343
+ const url = URL.createObjectURL(result.blob);
2344
+ try {
2345
+ const anchor = document.createElement("a");
2346
+ anchor.href = url;
2347
+ anchor.download = result.name;
2348
+ anchor.rel = "noopener";
2349
+ anchor.click();
2350
+ return {
2351
+ mimeType: result.mimeType,
2352
+ name: result.name,
2353
+ sizeBytes: result.sizeBytes
2354
+ };
2355
+ } finally {
2356
+ releaseObjectUrl(url);
2357
+ }
2358
+ },
2359
+ open: async (options) => {
2360
+ const result = documentBlob(options);
2361
+ const url = URL.createObjectURL(result.blob);
2362
+ const opened = window.open(url, "_blank", "noopener,noreferrer");
2363
+ if (opened === null) {
2364
+ URL.revokeObjectURL(url);
2365
+ throw new DeviceError("failed", "The browser blocked the document preview window.");
2366
+ }
2367
+ releaseObjectUrl(url);
2368
+ },
2369
+ pick: pickDocuments
2370
+ },
2371
+ haptics: {
2372
+ capability: async () => typeof navigator.vibrate === "function" ? availableCapability("web") : unavailableCapability("unsupported", "Vibration feedback is not supported by this browser."),
2373
+ impact: async (style = "medium") => vibrate(style === "light" ? 8 : style === "heavy" ? 24 : 14),
2374
+ notification: async (type = "success") => vibrate(type === "error" ? 40 : type === "warning" ? 28 : 18),
2375
+ selectionChanged: async () => vibrate(6),
2376
+ vibrate: async (durationMs = 300) => vibrate(durationMs)
2377
+ },
2378
+ keyboard: {
2379
+ capability: async () => typeof window.visualViewport === "undefined" ? unavailableCapability("unsupported", "This browser does not expose keyboard viewport changes.") : availableCapability("web"),
2380
+ dismiss: async () => {
2381
+ const active = document.activeElement;
2382
+ if (active instanceof HTMLElement)
2383
+ active.blur();
2384
+ },
2385
+ getState: async () => webKeyboardState(),
2386
+ onChange: async (listener) => {
2387
+ let previous = webKeyboardState();
2388
+ const emit = () => {
2389
+ const next = webKeyboardState();
2390
+ if (next.visible === previous.visible && next.heightPx === previous.heightPx)
2391
+ return;
2392
+ previous = next;
2393
+ listener(next);
2394
+ };
2395
+ window.visualViewport?.addEventListener("resize", emit);
2396
+ document.addEventListener("focusin", emit);
2397
+ document.addEventListener("focusout", emit);
2398
+ return () => {
2399
+ window.visualViewport?.removeEventListener("resize", emit);
2400
+ document.removeEventListener("focusin", emit);
2401
+ document.removeEventListener("focusout", emit);
2402
+ };
2403
+ }
2404
+ },
2405
+ localNotifications: {
2406
+ cancel: async (ids) => {
2407
+ for (const id of ids) {
2408
+ const pending = pendingNotifications.get(id);
2409
+ if (!pending)
2410
+ continue;
2411
+ clearTimeout(pending.timer);
2412
+ pendingNotifications.delete(id);
2413
+ }
2414
+ },
2415
+ capability: async () => typeof Notification === "undefined" ? unavailableCapability("unsupported", "Browser notifications are unavailable.") : availableCapability("emulated", {
2416
+ durableScheduling: false,
2417
+ maximumDelayMs: 2147483647
2418
+ }),
2419
+ onAction: async (listener) => {
2420
+ notificationActions.add(listener);
2421
+ return () => {
2422
+ notificationActions.delete(listener);
2423
+ };
2424
+ },
2425
+ onReceived: async (listener) => {
2426
+ notificationReceived.add(listener);
2427
+ return () => {
2428
+ notificationReceived.delete(listener);
2429
+ };
2430
+ },
2431
+ pending: async () => Array.from(pendingNotifications.values(), ({ notification }) => notification),
2432
+ queryPermission: async () => notificationPermission(),
2433
+ requestPermission: async () => {
2434
+ if (typeof Notification === "undefined")
2435
+ return { canRequest: false, state: "unavailable" };
2436
+ try {
2437
+ await Notification.requestPermission();
2438
+ return notificationPermission();
2439
+ } catch (error) {
2440
+ throw webFailure(error, "Browser notification permission failed.");
2441
+ }
2442
+ },
2443
+ schedule: async (input) => {
2444
+ const permission = notificationPermission();
2445
+ if (permission.state !== "granted")
2446
+ throw new DeviceError(permission.state === "denied" ? "permission-denied" : permission.state === "unavailable" ? "unavailable" : "permission-required", "Notification permission must be explicitly granted before scheduling.");
2447
+ const notification = normalizeDeviceLocalNotification(input);
2448
+ const delay = Math.max(0, (notification.scheduledAtMs ?? Date.now()) - Date.now());
2449
+ if (delay > 2147483647)
2450
+ throw new DeviceError("unsupported", "Browser notification scheduling cannot exceed 24 days and is not durable across reloads.");
2451
+ const existing = pendingNotifications.get(notification.id);
2452
+ if (existing)
2453
+ clearTimeout(existing.timer);
2454
+ if (delay === 0) {
2455
+ pendingNotifications.delete(notification.id);
2456
+ showNotification(notification);
2457
+ return notification;
2458
+ }
2459
+ const timer = setTimeout(() => {
2460
+ pendingNotifications.delete(notification.id);
2461
+ showNotification(notification);
2462
+ }, delay);
2463
+ pendingNotifications.set(notification.id, { notification, timer });
2464
+ return notification;
2465
+ }
2466
+ },
2467
+ platform: {
2468
+ getInfo: async () => ({
2469
+ formFactor: matches("(pointer: coarse)") ? innerWidth >= COARSE_TABLET_MIN_WIDTH ? "tablet" : "phone" : "desktop",
2470
+ isNative: false,
2471
+ language: navigator.language,
2472
+ locale: Intl.DateTimeFormat().resolvedOptions().locale,
2473
+ os: detectOs(),
2474
+ prefersReducedMotion: matches("(prefers-reduced-motion: reduce)"),
2475
+ runtime: "web",
2476
+ safeAreaInsets: safeAreaInsets()
2477
+ })
2478
+ },
2479
+ lifecycle: {
2480
+ getState: async () => document.visibilityState === "visible" ? "active" : "background",
2481
+ onChange: async (listener) => {
2482
+ const handler = () => listener(document.visibilityState === "visible" ? "active" : "background");
2483
+ document.addEventListener("visibilitychange", handler);
2484
+ return () => document.removeEventListener("visibilitychange", handler);
2485
+ },
2486
+ onRestoredOperation: async () => () => {
2487
+ return;
2488
+ },
2489
+ onResume: async (listener) => {
2490
+ const handler = () => {
2491
+ if (document.visibilityState === "visible")
2492
+ listener();
2493
+ };
2494
+ document.addEventListener("visibilitychange", handler);
2495
+ return () => document.removeEventListener("visibilitychange", handler);
2496
+ }
2497
+ },
2498
+ links: {
2499
+ getLaunchUrl: async () => location.href,
2500
+ onOpen: async (listener) => {
2501
+ let lastUrl = location.href;
2502
+ const handler = () => {
2503
+ if (location.href === lastUrl)
2504
+ return;
2505
+ lastUrl = location.href;
2506
+ listener(lastUrl);
2507
+ };
2508
+ addEventListener("popstate", handler);
2509
+ addEventListener("hashchange", handler);
2510
+ return () => {
2511
+ removeEventListener("popstate", handler);
2512
+ removeEventListener("hashchange", handler);
2513
+ };
2514
+ },
2515
+ openExternal: async (url) => {
2516
+ const parsed = new URL(url);
2517
+ if (!["http:", "https:", "mailto:", "tel:"].includes(parsed.protocol)) {
2518
+ throw new DeviceError("failed", `External URL protocol ${parsed.protocol} is not allowed.`);
2519
+ }
2520
+ window.open(parsed.href, "_blank", "noopener,noreferrer");
2521
+ }
2522
+ },
2523
+ location: {
2524
+ capability: async () => typeof navigator.geolocation === "undefined" ? unavailableCapability("unsupported", "Browser geolocation is unavailable.") : availableCapability("web"),
2525
+ current: currentLocation,
2526
+ queryPermission: queryLocationPermission,
2527
+ requestPermission: async () => {
2528
+ await currentLocation();
2529
+ locationAuthorized = true;
2530
+ const status = await queryLocationPermission();
2531
+ return { ...status, state: "granted" };
2532
+ },
2533
+ watch: async (listener, options) => {
2534
+ validateDeviceLocationOptions(options);
2535
+ if (typeof navigator.geolocation === "undefined")
2536
+ throw new DeviceError("unsupported", "Browser geolocation is unavailable.");
2537
+ let active = true;
2538
+ const id = navigator.geolocation.watchPosition((position) => {
2539
+ if (!active)
2540
+ return;
2541
+ try {
2542
+ listener({
2543
+ position: locationPosition(position),
2544
+ type: "position"
2545
+ });
2546
+ } catch (error) {
2547
+ listener({ error: locationFailure(error), type: "error" });
2548
+ }
2549
+ }, (error) => {
2550
+ if (active)
2551
+ listener({ error: locationFailure(error), type: "error" });
2552
+ }, webLocationOptions(options));
2553
+ return () => {
2554
+ if (!active)
2555
+ return;
2556
+ active = false;
2557
+ navigator.geolocation.clearWatch(id);
2558
+ };
2559
+ }
2560
+ },
2561
+ network: {
2562
+ getStatus: async () => networkStatus(),
2563
+ onChange: async (listener) => {
2564
+ const handler = () => listener(networkStatus());
2565
+ addEventListener("online", handler);
2566
+ addEventListener("offline", handler);
2567
+ return () => {
2568
+ removeEventListener("online", handler);
2569
+ removeEventListener("offline", handler);
2570
+ };
2571
+ }
2572
+ },
2573
+ photos: {
2574
+ capability: async () => typeof document !== "undefined" ? availableCapability("web") : unavailableCapability("unsupported", "Browser photo selection is unavailable."),
2575
+ pick: async (options) => {
2576
+ requirePhotoOptions(options);
2577
+ return pickImages({
2578
+ limit: options?.limit,
2579
+ multiple: options?.limit !== 1
2580
+ });
2581
+ }
2582
+ },
2583
+ share: {
2584
+ capability: async (content) => shareStatus(content),
2585
+ share: async (content) => {
2586
+ const normalized = normalizeDeviceShareContent(content);
2587
+ if (!shareStatus(normalized).available)
2588
+ throw new DeviceError("unsupported", "Web sharing is unavailable.");
2589
+ await webOperation("Browser sharing failed.", () => navigator.share(normalized));
2590
+ return {};
2591
+ }
2592
+ },
2593
+ storage: {
2594
+ clear: async () => requireStorage().clear(),
2595
+ get: async (key) => requireStorage().getItem(key),
2596
+ keys: async () => {
2597
+ const storage = requireStorage();
2598
+ return Array.from({ length: storage.length }, (_, index) => storage.key(index)).filter((key) => key !== null);
2599
+ },
2600
+ remove: async (key) => requireStorage().removeItem(key),
2601
+ set: async (key, value) => requireStorage().setItem(key, value)
2602
+ },
2603
+ systemBars: {
2604
+ capability: async (operation = "appearance") => operation === "appearance" ? availableCapability("emulated", {
2605
+ targetedBars: false
2606
+ }) : unavailableCapability("unsupported", "Browser chrome visibility cannot be controlled reliably."),
2607
+ setAppearance: async (appearance) => {
2608
+ document.documentElement.style.colorScheme = appearance === "automatic" ? "normal" : appearance === "light" ? "dark" : "light";
2609
+ },
2610
+ setVisible: async () => {
2611
+ throw new DeviceError("unsupported", "Browser chrome visibility cannot be controlled reliably.");
2612
+ }
2613
+ }
2614
+ };
2615
+ };
2616
+ var RUNTIME_REGISTRY = Symbol.for("@absolutejs/devices/runtime");
2617
+ var registryHost = globalThis;
2618
+ var isRuntimeRegistry = (value) => typeof value === "object" && value !== null && Array.isArray(Reflect.get(value, "installations"));
2619
+ var runtimeRegistry = (() => {
2620
+ const existing = registryHost[RUNTIME_REGISTRY];
2621
+ if (isRuntimeRegistry(existing))
2622
+ return existing;
2623
+ const created = { installations: [] };
2624
+ Object.defineProperty(registryHost, RUNTIME_REGISTRY, {
2625
+ configurable: false,
2626
+ enumerable: false,
2627
+ value: created,
2628
+ writable: false
2629
+ });
2630
+ return created;
2631
+ })();
2632
+ var defaultAdapter = () => typeof window === "undefined" || typeof navigator === "undefined" ? createSsrDeviceAdapter() : createWebDeviceAdapter();
2633
+ var getDeviceAdapter = () => runtimeRegistry.installations.at(-1)?.adapter ?? (runtimeRegistry.fallbackAdapter ??= defaultAdapter());
2634
+ var unavailableOptional = (capability) => unavailableCapability("unsupported", `${capability} is not installed for this runtime.`);
2635
+ var requireOptional = (value, capability) => {
2636
+ if (value !== undefined)
2637
+ return value;
2638
+ throw new DeviceError("unsupported", `${capability} is not installed for this runtime.`);
2639
+ };
2640
+ var keyboard = {
2641
+ capability: () => getDeviceAdapter().keyboard?.capability() ?? Promise.resolve(unavailableOptional("Keyboard")),
2642
+ dismiss: () => requireOptional(getDeviceAdapter().keyboard, "Keyboard").dismiss(),
2643
+ getState: () => requireOptional(getDeviceAdapter().keyboard, "Keyboard").getState(),
2644
+ onChange: (listener) => requireOptional(getDeviceAdapter().keyboard, "Keyboard").onChange(listener),
2645
+ state: () => requireOptional(getDeviceAdapter().keyboard, "Keyboard").getState()
2646
+ };
2647
+ var platform = {
2648
+ capability: () => Promise.resolve(runtimeCapability(getDeviceAdapter().runtime)),
2649
+ getInfo: () => getDeviceAdapter().platform.getInfo(),
2650
+ info: () => getDeviceAdapter().platform.getInfo()
2651
+ };
2652
+ var network = {
2653
+ capability: () => Promise.resolve(runtimeCapability(getDeviceAdapter().runtime)),
2654
+ getStatus: () => getDeviceAdapter().network.getStatus(),
2655
+ status: () => getDeviceAdapter().network.getStatus(),
2656
+ onChange: (...args) => getDeviceAdapter().network.onChange(...args)
2657
+ };
2658
+ var systemBars = {
2659
+ capability: (operation) => getDeviceAdapter().systemBars?.capability(operation) ?? Promise.resolve(unavailableOptional("System bars")),
2660
+ setAppearance: (appearance, bar) => requireOptional(getDeviceAdapter().systemBars, "System bars").setAppearance(appearance, bar),
2661
+ setVisible: (visible, bar) => requireOptional(getDeviceAdapter().systemBars, "System bars").setVisible(visible, bar)
2662
+ };
2663
+
2664
+ // src/mobile/adaptiveShell.ts
2665
+ var STYLE_ID = "absolute-mobile-adaptive-shell";
2666
+ var ANNOUNCER_ID = "absolute-mobile-announcer";
2667
+ var HOST_METRICS_EVENT = "absolute:native-host-metrics";
2668
+ var STATUS_STYLE = `
2669
+ [data-absolute-mobile-status] {
2670
+ display: grid;
2671
+ min-height: var(--absolute-available-height, 100dvh);
2672
+ place-items: center;
2673
+ padding: max(1.5rem, var(--absolute-safe-area-inset-top, 0px)) max(1.5rem, var(--absolute-safe-area-inset-right, 0px)) max(1.5rem, var(--absolute-safe-area-inset-bottom, 0px)) max(1.5rem, var(--absolute-safe-area-inset-left, 0px));
2674
+ box-sizing: border-box;
2675
+ color: CanvasText;
2676
+ background: Canvas;
2677
+ font: 500 1rem/1.5 system-ui, sans-serif;
2678
+ text-align: center;
2679
+ }
2680
+ [data-absolute-mobile-announcer] {
2681
+ position: fixed;
2682
+ width: 1px;
2683
+ height: 1px;
2684
+ padding: 0;
2685
+ margin: -1px;
2686
+ overflow: hidden;
2687
+ clip: rect(0, 0, 0, 0);
2688
+ white-space: nowrap;
2689
+ border: 0;
2690
+ }
2691
+ `;
2692
+ var DEFAULT_KEYBOARD = {
2693
+ heightPx: 0,
2694
+ visible: false
2695
+ };
2696
+ var DEFAULT_NETWORK = {
2697
+ connected: true,
2698
+ connectionType: "unknown"
2699
+ };
2700
+ var DEFAULT_PLATFORM = {
2701
+ formFactor: "unknown",
2702
+ isNative: true,
2703
+ os: "unknown",
2704
+ runtime: "capacitor"
2705
+ };
2706
+ var finitePixels = (value) => Number.isFinite(value) ? Math.max(0, Math.round(value ?? 0)) : 0;
2707
+ var isFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value);
2708
+ var viewportSize = () => ({
2709
+ height: finitePixels(globalThis.visualViewport?.height ?? innerHeight),
2710
+ width: finitePixels(globalThis.visualViewport?.width ?? innerWidth)
2711
+ });
2712
+ var setPixels = (name, value) => document.documentElement.style.setProperty(name, `${finitePixels(value)}px`);
2713
+ var ensureStyle = () => {
2714
+ let style = document.getElementById(STYLE_ID);
2715
+ if (!(style instanceof HTMLStyleElement)) {
2716
+ style = document.createElement("style");
2717
+ style.id = STYLE_ID;
2718
+ style.textContent = STATUS_STYLE;
2719
+ document.head.append(style);
2720
+ }
2721
+ };
2722
+ var ensureViewport = () => {
2723
+ let viewport = document.head.querySelector('meta[name="viewport"]');
2724
+ if (!viewport) {
2725
+ viewport = document.createElement("meta");
2726
+ viewport.name = "viewport";
2727
+ document.head.prepend(viewport);
2728
+ }
2729
+ const values = new Set(viewport.content.split(",").map((value) => value.trim()).filter(Boolean));
2730
+ values.add("width=device-width");
2731
+ values.add("initial-scale=1");
2732
+ values.add("viewport-fit=cover");
2733
+ viewport.content = [...values].join(",");
2734
+ };
2735
+ var ensureAnnouncer = () => {
2736
+ let announcer = document.getElementById(ANNOUNCER_ID);
2737
+ if (!(announcer instanceof HTMLElement)) {
2738
+ announcer = document.createElement("div");
2739
+ announcer.id = ANNOUNCER_ID;
2740
+ announcer.dataset.absoluteMobileAnnouncer = "";
2741
+ announcer.setAttribute("aria-atomic", "true");
2742
+ announcer.setAttribute("aria-live", "polite");
2743
+ announcer.setAttribute("role", "status");
2744
+ document.body.append(announcer);
2745
+ }
2746
+ return announcer;
2747
+ };
2748
+ var dispatchState = (state) => dispatchEvent(new CustomEvent("absolute:adaptive-shell-change", { detail: state }));
2749
+ var defaultDevices = {
2750
+ keyboard,
2751
+ network,
2752
+ platform,
2753
+ systemBars
2754
+ };
2755
+ var hostSafeAreaInsets = (value) => {
2756
+ if (typeof value !== "object" || value === null)
2757
+ return;
2758
+ const candidate = Reflect.get(value, "safeAreaInsets");
2759
+ if (typeof candidate !== "object" || candidate === null)
2760
+ return;
2761
+ const insets = {
2762
+ bottom: Reflect.get(candidate, "bottom"),
2763
+ left: Reflect.get(candidate, "left"),
2764
+ right: Reflect.get(candidate, "right"),
2765
+ top: Reflect.get(candidate, "top")
2766
+ };
2767
+ if (!isFiniteNumber(insets.bottom) || !isFiniteNumber(insets.left) || !isFiniteNumber(insets.right) || !isFiniteNumber(insets.top))
2768
+ return;
2769
+ return {
2770
+ bottom: finitePixels(insets.bottom),
2771
+ left: finitePixels(insets.left),
2772
+ right: finitePixels(insets.right),
2773
+ top: finitePixels(insets.top)
2774
+ };
2775
+ };
2776
+ var installAbsoluteMobileAdaptiveShell = async (devices = defaultDevices) => {
2777
+ let disposed = false;
2778
+ let keyboardState = { ...DEFAULT_KEYBOARD };
2779
+ let networkState = { ...DEFAULT_NETWORK };
2780
+ let platformInfo = { ...DEFAULT_PLATFORM };
2781
+ let viewport = viewportSize();
2782
+ const subscriptions = [];
2783
+ const state = () => ({
2784
+ availableHeight: globalThis.visualViewport ? viewport.height : Math.max(0, viewport.height - keyboardState.heightPx),
2785
+ keyboard: keyboardState,
2786
+ network: networkState,
2787
+ platform: platformInfo,
2788
+ viewportHeight: viewport.height,
2789
+ viewportWidth: viewport.width
2790
+ });
2791
+ const refreshDocument = () => {
2792
+ if (disposed || !document.head || !document.body)
2793
+ return;
2794
+ ensureViewport();
2795
+ ensureStyle();
2796
+ const root = document.documentElement;
2797
+ const current = state();
2798
+ root.dataset.absoluteMobile = "";
2799
+ root.dataset.absoluteRuntime = current.platform.runtime;
2800
+ root.dataset.absolutePlatform = current.platform.os;
2801
+ root.dataset.absoluteFormFactor = current.platform.formFactor;
2802
+ root.dataset.absoluteKeyboard = current.keyboard.visible ? "visible" : "hidden";
2803
+ root.dataset.absoluteNetwork = current.network.connected ? "online" : "offline";
2804
+ root.dataset.absoluteConnection = current.network.connectionType;
2805
+ root.dataset.absoluteReducedMotion = current.platform.prefersReducedMotion ? "reduce" : "no-preference";
2806
+ const insets = current.platform.safeAreaInsets ?? {
2807
+ bottom: 0,
2808
+ left: 0,
2809
+ right: 0,
2810
+ top: 0
2811
+ };
2812
+ setPixels("--absolute-safe-area-inset-top", insets.top);
2813
+ setPixels("--absolute-safe-area-inset-right", insets.right);
2814
+ setPixels("--absolute-safe-area-inset-bottom", insets.bottom);
2815
+ setPixels("--absolute-safe-area-inset-left", insets.left);
2816
+ setPixels("--absolute-keyboard-height", current.keyboard.heightPx);
2817
+ setPixels("--absolute-viewport-height", current.viewportHeight);
2818
+ setPixels("--absolute-viewport-width", current.viewportWidth);
2819
+ setPixels("--absolute-available-height", current.availableHeight);
2820
+ ensureAnnouncer();
2821
+ };
2822
+ const publish = () => {
2823
+ refreshDocument();
2824
+ dispatchState(state());
2825
+ };
2826
+ const updateViewport = () => {
2827
+ viewport = viewportSize();
2828
+ devices.platform.getInfo().then((info) => {
2829
+ platformInfo = info;
2830
+ publish();
2831
+ return;
2832
+ }).catch(() => publish());
2833
+ };
2834
+ const updateKeyboard = (next) => {
2835
+ keyboardState = {
2836
+ heightPx: finitePixels(next.heightPx),
2837
+ visible: next.visible
2838
+ };
2839
+ publish();
2840
+ };
2841
+ const updateNetwork = (next) => {
2842
+ const changed = networkState.connected !== next.connected;
2843
+ networkState = next;
2844
+ publish();
2845
+ if (changed || !next.connected)
2846
+ ensureAnnouncer().textContent = next.connected ? "Connection restored." : "You are offline.";
2847
+ };
2848
+ const updateHostMetrics = (event) => {
2849
+ const insets = hostSafeAreaInsets(Reflect.get(event, "detail"));
2850
+ if (!insets)
2851
+ return;
2852
+ platformInfo = { ...platformInfo, safeAreaInsets: insets };
2853
+ publish();
2854
+ };
2855
+ const installKeyboard = async () => {
2856
+ const capability = await devices.keyboard.capability();
2857
+ if (!capability.available)
2858
+ return;
2859
+ keyboardState = await devices.keyboard.getState();
2860
+ subscriptions.push(await devices.keyboard.onChange(updateKeyboard));
2861
+ };
2862
+ try {
2863
+ platformInfo = await devices.platform.getInfo();
2864
+ } catch {}
2865
+ const initialHostInsets = hostSafeAreaInsets(Reflect.get(globalThis, "__absoluteNativeHostMetrics"));
2866
+ if (initialHostInsets)
2867
+ platformInfo = {
2868
+ ...platformInfo,
2869
+ safeAreaInsets: initialHostInsets
2870
+ };
2871
+ try {
2872
+ networkState = await devices.network.getStatus();
2873
+ subscriptions.push(await devices.network.onChange(updateNetwork));
2874
+ } catch {}
2875
+ try {
2876
+ await installKeyboard();
2877
+ } catch {}
2878
+ try {
2879
+ const capability = await devices.systemBars.capability("appearance");
2880
+ if (capability.available)
2881
+ await devices.systemBars.setAppearance("automatic");
2882
+ } catch {}
2883
+ addEventListener("resize", updateViewport);
2884
+ addEventListener(HOST_METRICS_EVENT, updateHostMetrics);
2885
+ globalThis.visualViewport?.addEventListener("resize", updateViewport);
2886
+ globalThis.visualViewport?.addEventListener("scroll", updateViewport);
2887
+ publish();
2888
+ if (!networkState.connected)
2889
+ ensureAnnouncer().textContent = "You are offline.";
2890
+ return {
2891
+ refreshDocument,
2892
+ state,
2893
+ dispose: async () => {
2894
+ if (disposed)
2895
+ return;
2896
+ disposed = true;
2897
+ removeEventListener("resize", updateViewport);
2898
+ removeEventListener(HOST_METRICS_EVENT, updateHostMetrics);
2899
+ globalThis.visualViewport?.removeEventListener("resize", updateViewport);
2900
+ globalThis.visualViewport?.removeEventListener("scroll", updateViewport);
2901
+ await Promise.all(subscriptions.map((unsubscribe) => unsubscribe()));
2902
+ }
2903
+ };
2904
+ };
1648
2905
  // src/mobile/androidRelease.ts
1649
2906
  import { createHash as createHash4 } from "crypto";
1650
2907
  import {
@@ -1747,12 +3004,12 @@ var pathExists = async (path) => {
1747
3004
  return false;
1748
3005
  }
1749
3006
  };
1750
- var detectAbsoluteMobileHost = (platform = process.platform, wsl = isWSLEnvironment()) => {
1751
- if (platform === "darwin")
3007
+ var detectAbsoluteMobileHost = (platform2 = process.platform, wsl = isWSLEnvironment()) => {
3008
+ if (platform2 === "darwin")
1752
3009
  return "macos";
1753
- if (platform === "win32")
3010
+ if (platform2 === "win32")
1754
3011
  return "windows";
1755
- if (platform === "linux" && wsl)
3012
+ if (platform2 === "linux" && wsl)
1756
3013
  return "wsl";
1757
3014
  return "linux";
1758
3015
  };
@@ -1775,16 +3032,16 @@ var findExecutable = async (name, paths, options, host) => {
1775
3032
  }
1776
3033
  return;
1777
3034
  };
1778
- var toolCheck = (id, label, platform, path, remediation) => path ? {
3035
+ var toolCheck = (id, label, platform2, path, remediation) => path ? {
1779
3036
  id,
1780
3037
  label,
1781
3038
  path,
1782
- platform,
3039
+ platform: platform2,
1783
3040
  status: "pass"
1784
3041
  } : {
1785
3042
  id,
1786
3043
  label,
1787
- platform,
3044
+ platform: platform2,
1788
3045
  remediation,
1789
3046
  status: "fail"
1790
3047
  };
@@ -2639,7 +3896,7 @@ var findByExtension = async (root, extension) => {
2639
3896
  if (!await pathExists4(root))
2640
3897
  return;
2641
3898
  const entries = await readdir3(root, { withFileTypes: true });
2642
- const matches = await Promise.all(entries.map(async (entry) => {
3899
+ const matches2 = await Promise.all(entries.map(async (entry) => {
2643
3900
  const path = join5(root, entry.name);
2644
3901
  if (entry.isDirectory() && entry.name.endsWith(extension))
2645
3902
  return path;
@@ -2647,13 +3904,13 @@ var findByExtension = async (root, extension) => {
2647
3904
  return path;
2648
3905
  return entry.isDirectory() ? findByExtension(path, extension) : undefined;
2649
3906
  }));
2650
- return matches.find((match) => match !== undefined);
3907
+ return matches2.find((match) => match !== undefined);
2651
3908
  };
2652
3909
  var findAllByExtension = async (root, extension, options = {}) => {
2653
3910
  if (!await pathExists4(root))
2654
3911
  return [];
2655
3912
  const entries = await readdir3(root, { withFileTypes: true });
2656
- const matches = await Promise.all(entries.map(async (entry) => {
3913
+ const matches2 = await Promise.all(entries.map(async (entry) => {
2657
3914
  const path = join5(root, entry.name);
2658
3915
  if (entry.name.endsWith(extension))
2659
3916
  return [path];
@@ -2661,7 +3918,7 @@ var findAllByExtension = async (root, extension, options = {}) => {
2661
3918
  return [];
2662
3919
  return findAllByExtension(path, extension, options);
2663
3920
  }));
2664
- return matches.flat().sort();
3921
+ return matches2.flat().sort();
2665
3922
  };
2666
3923
  var resolveAbsoluteIosXcodeProject = async (nativeDirectory, options = {}) => {
2667
3924
  const requestedWorkspace = options.workspacePath ? resolve4(nativeDirectory, options.workspacePath) : undefined;
@@ -5295,7 +6552,7 @@ var HTTP_OK = 200;
5295
6552
  var VERIFY_TIMEOUT_MS = 1e4;
5296
6553
  var ANDROID_ASSOCIATION_PATH = "/.well-known/assetlinks.json";
5297
6554
  var APPLE_ASSOCIATION_PATH = "/.well-known/apple-app-site-association";
5298
- var missingIdentity = (field2, platform) => new TypeError(`${field2} is required to publish ${platform} deep-link association files.`);
6555
+ var missingIdentity = (field2, platform2) => new TypeError(`${field2} is required to publish ${platform2} deep-link association files.`);
5299
6556
  var createAppleDocument = (config, requireAll) => {
5300
6557
  if (!config.platforms.includes("ios"))
5301
6558
  return;
@@ -5419,9 +6676,9 @@ var publishGeneratedDirectory = async (temporary, root) => {
5419
6676
  var materializeHost = async (root, host, files) => {
5420
6677
  const directory = resolve7(root, host, ".well-known");
5421
6678
  await mkdir7(directory, { recursive: true });
5422
- return Promise.all(files.map(async ([name, document]) => {
6679
+ return Promise.all(files.map(async ([name, document2]) => {
5423
6680
  const path = resolve7(directory, name);
5424
- await writeAtomic(path, `${JSON.stringify(document, null, 2)}
6681
+ await writeAtomic(path, `${JSON.stringify(document2, null, 2)}
5425
6682
  `);
5426
6683
  return path;
5427
6684
  }));
@@ -5486,13 +6743,13 @@ var verifyAbsoluteMobileAssociationFiles = async (config, request = globalThis.f
5486
6743
  if (!contentType.toLowerCase().startsWith("application/json")) {
5487
6744
  throw new TypeError(`${endpoint.platform} association verification failed for ${url}: expected application/json.`);
5488
6745
  }
5489
- let document;
6746
+ let document2;
5490
6747
  try {
5491
- document = await response.json();
6748
+ document2 = await response.json();
5492
6749
  } catch {
5493
6750
  throw new TypeError(`${endpoint.platform} association verification failed for ${url}: invalid JSON.`);
5494
6751
  }
5495
- if (JSON.stringify(document) !== JSON.stringify(endpoint.document)) {
6752
+ if (JSON.stringify(document2) !== JSON.stringify(endpoint.document)) {
5496
6753
  throw new TypeError(`${endpoint.platform} association verification failed for ${url}: the hosted identity does not match mobile config.`);
5497
6754
  }
5498
6755
  return { platform: endpoint.platform, url };
@@ -6169,8 +7426,8 @@ var resolveAbsoluteMobileNavigation = (manifest, value, localOrigin) => {
6169
7426
  const url = new URL(value, `${localOrigin}/`);
6170
7427
  const local = new URL(localOrigin);
6171
7428
  const production = new URL(manifest.productionOrigin);
6172
- const matches = (candidate, allowed) => candidate.protocol === allowed.protocol && candidate.host === allowed.host;
6173
- if (!matches(url, local) && !matches(url, production)) {
7429
+ const matches2 = (candidate, allowed) => candidate.protocol === allowed.protocol && candidate.host === allowed.host;
7430
+ if (!matches2(url, local) && !matches2(url, production)) {
6174
7431
  return;
6175
7432
  }
6176
7433
  return `${url.pathname}${url.search}${url.hash}`;
@@ -7263,6 +8520,7 @@ var webHostSource = (config, auth, sync) => {
7263
8520
  import { router, usePathname } from 'expo-router';
7264
8521
  import { useEffect, useRef, useState } from 'react';
7265
8522
  import { ActivityIndicator, BackHandler, Platform, StyleSheet, View } from 'react-native';
8523
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
7266
8524
  import { WebView, type WebViewMessageEvent } from 'react-native-webview';
7267
8525
  import { materializeAbsoluteWebBundle } from './webAssets';
7268
8526
  import { createExpoDevicesBridgeHost } from '@absolutejs/devices-expo/bridge';
@@ -7295,7 +8553,10 @@ const isNativeRoute = (pathname: string) => {
7295
8553
  });
7296
8554
  };
7297
8555
 
7298
- const bridgeBootstrap = (path: string) => {
8556
+ const hostMetricsScript = (safeAreaInsets: { bottom: number; left: number; right: number; top: number }) =>
8557
+ \`globalThis.__absoluteNativeHostMetrics = \${JSON.stringify({ safeAreaInsets })}; dispatchEvent(new CustomEvent('absolute:native-host-metrics', { detail: globalThis.__absoluteNativeHostMetrics })); true;\`;
8558
+
8559
+ const bridgeBootstrap = (path: string, safeAreaInsets: { bottom: number; left: number; right: number; top: number }) => {
7299
8560
  const initialPath = DEV_ORIGIN
7300
8561
  ? 'location.pathname + location.search + location.hash'
7301
8562
  : JSON.stringify(path);
@@ -7304,6 +8565,7 @@ const bridgeBootstrap = (path: string) => {
7304
8565
  const listeners = new Map();
7305
8566
  let sequence = 0;
7306
8567
  let currentPath = \${initialPath};
8568
+ globalThis.__absoluteNativeHostMetrics = \${JSON.stringify({ safeAreaInsets })};
7307
8569
  const send = value => {
7308
8570
  const source = JSON.stringify(value);
7309
8571
  if (new TextEncoder().encode(source).byteLength > 65536) throw new Error('Expo bridge message exceeds 64 KiB.');
@@ -7408,6 +8670,7 @@ const authStatus = async () => {
7408
8670
 
7409
8671
  export function AbsoluteWebHost() {
7410
8672
  const pathname = usePathname() || '/';
8673
+ const safeAreaInsets = useSafeAreaInsets();
7411
8674
  const webView = useRef<WebView>(null);
7412
8675
  const devicesBridge = useRef<{ close(): void | Promise<void>; request(method: string, params: Record<string, unknown>): Promise<unknown> } | undefined>(undefined);
7413
8676
  const syncBridge = useRef<{ close(): void | Promise<void>; request(method: string, params: Record<string, unknown>): Promise<unknown> } | undefined>(undefined);
@@ -7450,6 +8713,9 @@ export function AbsoluteWebHost() {
7450
8713
  }
7451
8714
  void materializeAbsoluteWebBundle().then(uri => setIndexUri(uri + '?absolutePath=' + encodeURIComponent(pathname)));
7452
8715
  }, [pathname]);
8716
+ useEffect(() => {
8717
+ webView.current?.injectJavaScript(hostMetricsScript(safeAreaInsets));
8718
+ }, [safeAreaInsets.bottom, safeAreaInsets.left, safeAreaInsets.right, safeAreaInsets.top]);
7453
8719
  useEffect(() => {
7454
8720
  const subscription = BackHandler.addEventListener('hardwareBackPress', () => {
7455
8721
  if (!canGoBack) return false;
@@ -7533,7 +8799,7 @@ export function AbsoluteWebHost() {
7533
8799
  allowFileAccessFromFileURLs
7534
8800
  allowUniversalAccessFromFileURLs={false}
7535
8801
  allowingReadAccessToURL={indexUri.slice(0, indexUri.lastIndexOf('/') + 1)}
7536
- injectedJavaScriptBeforeContentLoaded={bridgeBootstrap(pathname)}
8802
+ injectedJavaScriptBeforeContentLoaded={bridgeBootstrap(pathname, safeAreaInsets)}
7537
8803
  onMessage={onMessage}
7538
8804
  onNavigationStateChange={state => setCanGoBack(state.canGoBack)}
7539
8805
  onShouldStartLoadWithRequest={request => {
@@ -8495,18 +9761,18 @@ var planAbsoluteExpoDevSession = (config, options) => {
8495
9761
  env,
8496
9762
  role: "native-prepare"
8497
9763
  };
8498
- const native = options.platforms.map((platform) => {
8499
- const device = platform === "android" ? options.androidDevice : options.iosDevice;
9764
+ const native = options.platforms.map((platform2) => {
9765
+ const device = platform2 === "android" ? options.androidDevice : options.iosDevice;
8500
9766
  const command = {
8501
9767
  args: [
8502
- `run:${platform}`,
9768
+ `run:${platform2}`,
8503
9769
  "--no-bundler",
8504
9770
  "--port",
8505
9771
  String(options.metroPort),
8506
9772
  ...device ? ["--device", device] : []
8507
9773
  ],
8508
9774
  env,
8509
- platform,
9775
+ platform: platform2,
8510
9776
  role: "native-build"
8511
9777
  };
8512
9778
  return command;
@@ -8678,10 +9944,10 @@ var startAbsoluteExpoDevSession = async (options) => {
8678
9944
  const runNativeCommand = async (command) => {
8679
9945
  if (options.signal?.aborted)
8680
9946
  throw abortError();
8681
- const { platform } = command;
8682
- if (!platform)
9947
+ const { platform: platform2 } = command;
9948
+ if (!platform2)
8683
9949
  throw new TypeError("Expo native build command is missing a platform.");
8684
- const state = platform === "android" ? "building-android" : "building-ios";
9950
+ const state = platform2 === "android" ? "building-android" : "building-ios";
8685
9951
  setState(state);
8686
9952
  const started = performance.now();
8687
9953
  const child = run(executable, command.args, {
@@ -8691,7 +9957,7 @@ var startAbsoluteExpoDevSession = async (options) => {
8691
9957
  });
8692
9958
  forwardLines(child, (line) => {
8693
9959
  if (line)
8694
- log(`[${platform}] ${line}`);
9960
+ log(`[${platform2}] ${line}`);
8695
9961
  });
8696
9962
  const abortChild = () => void stopProcess(child);
8697
9963
  options.signal?.addEventListener("abort", abortChild, { once: true });
@@ -8700,9 +9966,9 @@ var startAbsoluteExpoDevSession = async (options) => {
8700
9966
  if (options.signal?.aborted)
8701
9967
  throw abortError();
8702
9968
  if (exitCode !== 0) {
8703
- throw new Error(`Expo ${platform} development build exited with status ${exitCode}.`);
9969
+ throw new Error(`Expo ${platform2} development build exited with status ${exitCode}.`);
8704
9970
  }
8705
- if (platform === "ios" && options.certificateAuthorityPath && options.iosOrigin && new URL(options.iosOrigin).protocol === "https:" && !options.iosDevice) {
9971
+ if (platform2 === "ios" && options.certificateAuthorityPath && options.iosOrigin && new URL(options.iosOrigin).protocol === "https:" && !options.iosDevice) {
8706
9972
  setState("enrolling-trust");
8707
9973
  const trustStarted = performance.now();
8708
9974
  const utilityOptions = {
@@ -8889,13 +10155,13 @@ var bundleAuditSteps = ` - name: Prepare production mobile bundle
8889
10155
  - name: Enforce mobile bundle audit
8890
10156
  if: steps.mobile-bundle-audit.outcome != 'success'
8891
10157
  run: exit 1`;
8892
- var releaseAuditSteps = (platform) => ` - name: Run redacted mobile release audit
10158
+ var releaseAuditSteps = (platform2) => ` - name: Run redacted mobile release audit
8893
10159
  id: mobile-release-audit
8894
10160
  continue-on-error: true
8895
10161
  shell: bash
8896
10162
  run: |
8897
10163
  mkdir -p .absolutejs/mobile-ci
8898
- args=(bunx absolute mobile doctor release ${platform} --json)
10164
+ args=(bunx absolute mobile doctor release ${platform2} --json)
8899
10165
  ${appendConfigArgument}
8900
10166
  "\${args[@]}" > .absolutejs/mobile-ci/compliance.json
8901
10167
  - name: Upload mobile compliance report
@@ -8962,7 +10228,7 @@ var publishingInputs = (platforms, includePublishing) => {
8962
10228
  ${fields.join(`
8963
10229
  `)}`;
8964
10230
  };
8965
- var jobCondition = (platform) => `github.event_name == 'workflow_dispatch' && (inputs.platform == 'all' || inputs.platform == '${platform}')`;
10231
+ var jobCondition = (platform2) => `github.event_name == 'workflow_dispatch' && (inputs.platform == 'all' || inputs.platform == '${platform2}')`;
8966
10232
  var androidJob = (options) => {
8967
10233
  const custom = customSecretEnvironment(options.customSecrets);
8968
10234
  const publishEnvironment = options.includePublishing ? `
@@ -9272,7 +10538,7 @@ var writeAbsoluteMobileGithubWorkflow = async (options) => {
9272
10538
  changed: previous !== generated.workflow,
9273
10539
  format: ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT,
9274
10540
  path,
9275
- platforms: options.config.engine === "expo" ? options.config.platforms.filter((platform) => platform === "android") : [...options.config.platforms].sort(),
10541
+ platforms: options.config.engine === "expo" ? options.config.platforms.filter((platform2) => platform2 === "android") : [...options.config.platforms].sort(),
9276
10542
  publishing: options.includePublishing === true,
9277
10543
  requiredSecrets: generated.requiredSecrets
9278
10544
  };
@@ -9479,13 +10745,13 @@ var createAbsoluteMobilePreviewPlugin = (mobile) => {
9479
10745
  })).post("/__absolute/mobile-preview-telemetry", ({ body, status }) => {
9480
10746
  const value = isRecord10(body) ? body : undefined;
9481
10747
  const durationMs = value?.durationMs;
9482
- const platform2 = value?.platform;
9483
- if (typeof durationMs !== "number" || !Number.isFinite(durationMs) || durationMs < 0 || durationMs > 300000 || platform2 !== "android" && platform2 !== "ios") {
10748
+ const platform3 = value?.platform;
10749
+ if (typeof durationMs !== "number" || !Number.isFinite(durationMs) || durationMs < 0 || durationMs > 300000 || platform3 !== "android" && platform3 !== "ios") {
9484
10750
  return status(400, { error: "invalid-preview-telemetry" });
9485
10751
  }
9486
10752
  sendTelemetryEvent("mobile:preview-ready", {
9487
10753
  durationMs: Math.round(durationMs),
9488
- platform: platform2,
10754
+ platform: platform3,
9489
10755
  provider: "capacitor",
9490
10756
  target: "mobile-preview"
9491
10757
  });
@@ -9649,12 +10915,12 @@ var configureIos = async (config) => {
9649
10915
  return changed.some(Boolean);
9650
10916
  };
9651
10917
  var applyAbsoluteNativeDeepLinks = async (config, platforms = config.platforms) => {
9652
- const results = await Promise.all(platforms.map(async (platform2) => {
9653
- const didChange = platform2 === "android" ? await configureAndroid(config) : await configureIos(config);
9654
- return { didChange, platform: platform2 };
10918
+ const results = await Promise.all(platforms.map(async (platform3) => {
10919
+ const didChange = platform3 === "android" ? await configureAndroid(config) : await configureIos(config);
10920
+ return { didChange, platform: platform3 };
9655
10921
  }));
9656
10922
  return {
9657
- changed: results.filter(({ didChange }) => didChange).map(({ platform: platform2 }) => platform2)
10923
+ changed: results.filter(({ didChange }) => didChange).map(({ platform: platform3 }) => platform3)
9658
10924
  };
9659
10925
  };
9660
10926
  // src/mobile/nativeDeviceCapabilities.ts
@@ -9997,12 +11263,12 @@ ${content}
9997
11263
  return manifestChanged || firebaseChanged;
9998
11264
  };
9999
11265
  var applyAbsoluteNativeDeviceCapabilities = async (projectRoot, config, platforms = config.platforms, plan = resolveAbsoluteDeviceCapabilityPlan(projectRoot)) => {
10000
- const results = await Promise.all(platforms.map(async (platform2) => ({
10001
- didChange: platform2 === "ios" ? await configureIos2(config, plan) : await configureAndroid2(config, plan),
10002
- platform: platform2
11266
+ const results = await Promise.all(platforms.map(async (platform3) => ({
11267
+ didChange: platform3 === "ios" ? await configureIos2(config, plan) : await configureAndroid2(config, plan),
11268
+ platform: platform3
10003
11269
  })));
10004
11270
  return {
10005
- changed: results.filter(({ didChange }) => didChange).map(({ platform: platform2 }) => platform2)
11271
+ changed: results.filter(({ didChange }) => didChange).map(({ platform: platform3 }) => platform3)
10006
11272
  };
10007
11273
  };
10008
11274
 
@@ -10705,15 +11971,15 @@ var createWebHttpTransport = (options = {}) => {
10705
11971
  runtime: "web"
10706
11972
  });
10707
11973
  };
10708
- var RUNTIME_REGISTRY = Symbol.for("@absolutejs/http/runtime");
10709
- var registryHost = globalThis;
10710
- var isRuntimeRegistry = (value) => typeof value === "object" && value !== null && Array.isArray(Reflect.get(value, "installations"));
10711
- var runtimeRegistry = (() => {
10712
- const existing = registryHost[RUNTIME_REGISTRY];
10713
- if (isRuntimeRegistry(existing))
11974
+ var RUNTIME_REGISTRY2 = Symbol.for("@absolutejs/http/runtime");
11975
+ var registryHost2 = globalThis;
11976
+ var isRuntimeRegistry2 = (value) => typeof value === "object" && value !== null && Array.isArray(Reflect.get(value, "installations"));
11977
+ var runtimeRegistry2 = (() => {
11978
+ const existing = registryHost2[RUNTIME_REGISTRY2];
11979
+ if (isRuntimeRegistry2(existing))
10714
11980
  return existing;
10715
11981
  const created = { installations: [] };
10716
- Object.defineProperty(registryHost, RUNTIME_REGISTRY, {
11982
+ Object.defineProperty(registryHost2, RUNTIME_REGISTRY2, {
10717
11983
  configurable: false,
10718
11984
  enumerable: false,
10719
11985
  value: created,
@@ -10726,14 +11992,14 @@ var fallbackTransport = () => {
10726
11992
  return createWebHttpTransport();
10727
11993
  throw new AbsoluteHttpError("not-configured", "Absolute HTTP requires a request-scoped SSR transport outside a browser.");
10728
11994
  };
10729
- var getAbsoluteHttpTransport = () => runtimeRegistry.installations.at(-1)?.transport ?? (runtimeRegistry.fallback ??= fallbackTransport());
11995
+ var getAbsoluteHttpTransport = () => runtimeRegistry2.installations.at(-1)?.transport ?? (runtimeRegistry2.fallback ??= fallbackTransport());
10730
11996
  var installAbsoluteHttpTransport = (transport) => {
10731
11997
  const installation = { transport };
10732
- runtimeRegistry.installations.push(installation);
11998
+ runtimeRegistry2.installations.push(installation);
10733
11999
  return () => {
10734
- const index = runtimeRegistry.installations.indexOf(installation);
12000
+ const index = runtimeRegistry2.installations.indexOf(installation);
10735
12001
  if (index >= 0)
10736
- runtimeRegistry.installations.splice(index, 1);
12002
+ runtimeRegistry2.installations.splice(index, 1);
10737
12003
  };
10738
12004
  };
10739
12005
  var forbiddenCredentialHeaders = new Set([
@@ -10864,6 +12130,7 @@ export {
10864
12130
  ABSOLUTE_MOBILE_PREVIEW_PATH,
10865
12131
  ABSOLUTE_MOBILE_RETAINED_GENERATIONS,
10866
12132
  ABSOLUTE_MOBILE_ROUTE_DETAIL,
12133
+ ABSOLUTE_MOBILE_SHELL_DEVICE_CAPABILITIES,
10867
12134
  ABSOLUTE_MOBILE_TRANSFORM_PROTOCOL,
10868
12135
  ABSOLUTE_NATIVE_AUTH_CLIENTS_ENV,
10869
12136
  ABSOLUTE_NATIVE_AUTH_SCOPES,
@@ -10935,6 +12202,7 @@ export {
10935
12202
  inspectAbsoluteRemoteMacLanHost,
10936
12203
  inspectAbsoluteRemoteMacWorkspace,
10937
12204
  installAbsoluteIosRelease,
12205
+ installAbsoluteMobileAdaptiveShell,
10938
12206
  installAbsoluteMobileAuthEnvironment,
10939
12207
  installAbsoluteMobileShellHttp,
10940
12208
  installAbsoluteMobileSyncRemediation,
@@ -11011,5 +12279,5 @@ export {
11011
12279
  writeAbsoluteMobileGithubWorkflow
11012
12280
  };
11013
12281
 
11014
- //# debugId=61B4FE527637B49864756E2164756E21
12282
+ //# debugId=15034BCB4D45F19A64756E2164756E21
11015
12283
  //# sourceMappingURL=index.js.map