@absolutejs/absolute 0.20.0-beta.39 → 0.20.0-beta.40

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.
@@ -862,7 +862,7 @@ var init_deviceCapabilities = __esm(() => {
862
862
  // src/cli/scripts/telemetry.ts
863
863
  import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync5, writeFileSync } from "fs";
864
864
  import { homedir as homedir3 } from "os";
865
- import { join as join15 } from "path";
865
+ import { join as join16 } from "path";
866
866
  var configDir, configPath, getTelemetryConfig = () => {
867
867
  try {
868
868
  if (!existsSync4(configPath))
@@ -875,14 +875,14 @@ var configDir, configPath, getTelemetryConfig = () => {
875
875
  }
876
876
  };
877
877
  var init_telemetry = __esm(() => {
878
- configDir = join15(homedir3(), ".absolutejs");
879
- configPath = join15(configDir, "telemetry.json");
878
+ configDir = join16(homedir3(), ".absolutejs");
879
+ configPath = join16(configDir, "telemetry.json");
880
880
  });
881
881
 
882
882
  // src/cli/telemetryEvent.ts
883
883
  import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
884
884
  import { arch, platform } from "os";
885
- import { dirname as dirname11, join as join16, parse } from "path";
885
+ import { dirname as dirname12, join as join17, parse } from "path";
886
886
  var checkCandidate = (candidate) => {
887
887
  if (!existsSync5(candidate)) {
888
888
  return null;
@@ -902,12 +902,12 @@ var checkCandidate = (candidate) => {
902
902
  }, findPackageVersion = () => {
903
903
  let { dir } = import.meta;
904
904
  while (dir !== parse(dir).root) {
905
- const candidate = join16(dir, "package.json");
905
+ const candidate = join17(dir, "package.json");
906
906
  const version = checkCandidate(candidate);
907
907
  if (version) {
908
908
  return version;
909
909
  }
910
- dir = dirname11(dir);
910
+ dir = dirname12(dir);
911
911
  }
912
912
  return "unknown";
913
913
  }, sendTelemetryEvent = (event, payload) => {
@@ -4535,6 +4535,26 @@ var normalizeCertificateFingerprints = (values) => [
4535
4535
  return value.match(/.{2}/g)?.join(":") ?? value;
4536
4536
  }))
4537
4537
  ].sort();
4538
+ var normalizeExpoNativeRoutes = (config, projectRoot) => {
4539
+ if (config.engine !== "expo")
4540
+ return {};
4541
+ const routes = config.routes?.native ?? {};
4542
+ const normalized = {};
4543
+ for (const [route, module] of Object.entries(routes)) {
4544
+ const path = normalizeEntry(route);
4545
+ if (path.includes("?") || path.includes("#") || path !== "/" && path.endsWith("/")) {
4546
+ throw new TypeError(`mobile.routes.native route ${path} must be a canonical path without a query, fragment, or trailing slash.`);
4547
+ }
4548
+ if (path === "/__absolute/native") {
4549
+ throw new TypeError("mobile.routes.native reserves /__absolute/native for the Expo diagnostic screen.");
4550
+ }
4551
+ if (path.includes("*") || path.includes(":")) {
4552
+ throw new TypeError(`mobile.routes.native route ${path} must be static during the Expo experiment; parameters and wildcards are not supported yet.`);
4553
+ }
4554
+ normalized[path] = resolveProjectPath(projectRoot, requireText(module, `mobile.routes.native[${path}]`), `mobile.routes.native[${path}]`);
4555
+ }
4556
+ return Object.fromEntries(Object.entries(normalized).sort(([left], [right]) => left.localeCompare(right)));
4557
+ };
4538
4558
  var normalizeAbsoluteMobileConfig = (config, projectRoot) => {
4539
4559
  const appId = requireText(config.appId, "mobile.appId");
4540
4560
  if (!APP_ID_PATTERN.test(appId)) {
@@ -4553,10 +4573,12 @@ var normalizeAbsoluteMobileConfig = (config, projectRoot) => {
4553
4573
  bundleDirectory: resolveProjectPath(projectRoot, config.bundleDirectory ?? ".absolutejs/mobile/web", "mobile.bundleDirectory"),
4554
4574
  deepLinkHosts: normalizeHosts(config.deepLinks?.hosts, productionOrigin),
4555
4575
  deepLinkScheme,
4556
- engine: "capacitor",
4576
+ engine: config.engine ?? "capacitor",
4557
4577
  entry: normalizeEntry(config.entry),
4578
+ expoNativeRoutes: normalizeExpoNativeRoutes(config, projectRoot),
4579
+ ...config.engine === "expo" ? { expoSdkVersion: config.expo?.sdkVersion ?? 57 } : {},
4558
4580
  iosVersion: normalizeIosVersion(config.ios?.version),
4559
- nativeProjectDirectory: resolveProjectPath(projectRoot, config.nativeProject?.directory ?? "mobile", "mobile.nativeProject.directory"),
4581
+ nativeProjectDirectory: resolveProjectPath(projectRoot, config.nativeProject?.directory ?? (config.engine === "expo" ? ".absolutejs/mobile/expo" : "mobile"), "mobile.nativeProject.directory"),
4560
4582
  platforms: normalizePlatforms(config.platforms),
4561
4583
  productionOrigin,
4562
4584
  pushAndroidGoogleServicesFile: resolveProjectPath(projectRoot, config.pushNotifications?.android?.googleServicesFile ?? "google-services.json", "mobile.pushNotifications.android.googleServicesFile")
@@ -4805,8 +4827,8 @@ var parseAbsoluteMobileBuildPageMetadata = (value) => {
4805
4827
  };
4806
4828
  };
4807
4829
  // src/mobile/buildPipeline.ts
4808
- import { readFile as readFile14 } from "fs/promises";
4809
- import { join as join14, resolve as resolve12 } from "path";
4830
+ import { readFile as readFile15 } from "fs/promises";
4831
+ import { join as join15, resolve as resolve13 } from "path";
4810
4832
  import { pathToFileURL as pathToFileURL2 } from "url";
4811
4833
 
4812
4834
  // src/mobile/buildRelease.ts
@@ -5460,6 +5482,12 @@ var shellPushModule = () => {
5460
5482
  return candidate;
5461
5483
  throw new TypeError("AbsoluteJS mobile push shell module is missing.");
5462
5484
  };
5485
+ var shellExpoDevicesModule = () => {
5486
+ const candidate = ["js", "ts"].map((extension) => join9(import.meta.dir, `shellExpoDevices.${extension}`)).find(existsSync2);
5487
+ if (candidate)
5488
+ return candidate;
5489
+ throw new TypeError("AbsoluteJS Expo device bridge module is missing.");
5490
+ };
5463
5491
  var escapeHtml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
5464
5492
  var contentSecurityPolicy = (productionOrigin) => {
5465
5493
  const backend = new URL(productionOrigin);
@@ -5520,18 +5548,20 @@ var resolveProjectImport = async (projectRoot, specifier) => {
5520
5548
  throw new TypeError(`${specifier} has an unsafe import entry.`);
5521
5549
  return resolved;
5522
5550
  };
5523
- var buildShellBootstrap = async (staging, auth, sync, storagePrefix, deviceCapabilities, projectRoot) => {
5551
+ var buildShellBootstrap = async (staging, auth, sync, storagePrefix, engine, deviceCapabilities, projectRoot) => {
5552
+ const capacitor = engine !== "expo";
5553
+ const shellCapabilities = capacitor ? deviceCapabilities.capabilities : [];
5524
5554
  const modulePath = shellBootstrapModule();
5525
5555
  const authImport = auth ? `import { createAbsoluteMobileShellAuth } from ${JSON.stringify(shellAuthModule())};
5526
5556
  ` : "";
5527
5557
  const options = auth ? `{ createAuth: createAbsoluteMobileShellAuth${sync ? ", installSync: installAbsoluteMobileShellSync" : ""} }` : "";
5528
5558
  const syncImport = sync ? `import { installAbsoluteMobileShellSync } from ${JSON.stringify(shellSyncModule())};
5529
5559
  ` : "";
5530
- const pushIndex = deviceCapabilities.capabilities.indexOf("pushNotifications");
5560
+ const pushIndex = shellCapabilities.indexOf("pushNotifications");
5531
5561
  const push = pushIndex !== -1;
5532
5562
  const pushImport = push ? `import { createAbsoluteMobileShellPush } from ${JSON.stringify(shellPushModule())};
5533
5563
  ` : "";
5534
- const capabilityImports = (await Promise.all(deviceCapabilities.capabilities.map(async (name, index) => {
5564
+ const capabilityImports = (await Promise.all(shellCapabilities.map(async (name, index) => {
5535
5565
  const provider = deviceCapabilities.providers[name];
5536
5566
  if (!provider)
5537
5567
  throw new TypeError(`Missing device capability provider ${name}.`);
@@ -5541,14 +5571,22 @@ var buildShellBootstrap = async (staging, auth, sync, storagePrefix, deviceCapab
5541
5571
  const pushSetup = push ? `const absoluteMobilePush = createAbsoluteMobileShellPush();
5542
5572
  const absoluteMobilePushCapability = absoluteDeviceCapability${pushIndex}(absoluteMobilePush.capabilityOptions);
5543
5573
  ` : "";
5544
- const capabilityOptions = deviceCapabilities.capabilities.map((name, index) => `${JSON.stringify(name)}: ${name === "pushNotifications" ? "absoluteMobilePushCapability" : `absoluteDeviceCapability${index}()`}`).join(", ");
5574
+ const capabilityOptions = shellCapabilities.map((name, index) => `${JSON.stringify(name)}: ${name === "pushNotifications" ? "absoluteMobilePushCapability" : `absoluteDeviceCapability${index}()`}`).join(", ");
5545
5575
  const entryPath = join9(staging, ".absolute-mobile-entry.ts");
5546
- const baseAdapterModule = await resolveProjectImport(projectRoot, "@absolutejs/devices-capacitor");
5576
+ const baseAdapterModule = capacitor ? await resolveProjectImport(projectRoot, "@absolutejs/devices-capacitor") : shellExpoDevicesModule();
5577
+ const adapterImport = capacitor ? `import { installCapacitorDeviceAdapterIfNative } from ${JSON.stringify(baseAdapterModule)};` : `import { createAbsoluteExpoBridgeFetch, installAbsoluteExpoWebDeviceAdapter } from ${JSON.stringify(baseAdapterModule)};`;
5578
+ const adapterInstall = capacitor ? `installCapacitorDeviceAdapterIfNative({ storagePrefix: ${JSON.stringify(storagePrefix)}${capabilityOptions ? `, ${capabilityOptions}` : ""} });` : "installAbsoluteExpoWebDeviceAdapter();";
5579
+ let shellOptions = "{ createFetch: createAbsoluteExpoBridgeFetch }";
5580
+ if (capacitor)
5581
+ shellOptions = options;
5582
+ if (push) {
5583
+ shellOptions = `{ createAuth: (config, options) => createAbsoluteMobileShellAuth(config, options), beforeSignOut: absoluteMobilePush.beforeSignOut, connectPush: (auth) => absoluteMobilePush.connect(auth, absoluteMobilePushCapability)${sync ? ", installSync: installAbsoluteMobileShellSync" : ""} }`;
5584
+ }
5547
5585
  await writeFile10(entryPath, `import { startAbsoluteMobileShell } from ${JSON.stringify(modulePath)};
5548
- import { installCapacitorDeviceAdapterIfNative } from ${JSON.stringify(baseAdapterModule)};
5586
+ ${adapterImport}
5549
5587
  ${authImport}${syncImport}${pushImport}${capabilityImports}
5550
- ${pushSetup}installCapacitorDeviceAdapterIfNative({ storagePrefix: ${JSON.stringify(storagePrefix)}${capabilityOptions ? `, ${capabilityOptions}` : ""} });
5551
- void startAbsoluteMobileShell(${push ? `{ createAuth: (config, options) => createAbsoluteMobileShellAuth(config, options), beforeSignOut: absoluteMobilePush.beforeSignOut, connectPush: (auth) => absoluteMobilePush.connect(auth, absoluteMobilePushCapability)${sync ? ", installSync: installAbsoluteMobileShellSync" : ""} }` : options});
5588
+ ${pushSetup}${adapterInstall}
5589
+ void startAbsoluteMobileShell(${shellOptions});
5552
5590
  `);
5553
5591
  const build = await Bun.build({
5554
5592
  entrypoints: [entryPath],
@@ -5712,7 +5750,7 @@ var materializeAbsoluteCapacitorWebBundle = async (options) => {
5712
5750
  writeFile10(join9(staging, MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
5713
5751
  `),
5714
5752
  writeFile10(join9(staging, INDEX_FILE), indexHtml(options.config.appName, options.config.productionOrigin)),
5715
- buildShellBootstrap(staging, options.auth !== undefined, options.auth !== undefined && options.sync === true, `absolutejs.${options.auth?.clientId ?? options.config.appId}.`, options.deviceCapabilities, options.projectRoot)
5753
+ buildShellBootstrap(staging, options.auth !== undefined, options.auth !== undefined && options.sync === true, `absolutejs.${options.auth?.clientId ?? options.config.appId}.`, options.config.engine, options.deviceCapabilities, options.projectRoot)
5716
5754
  ]);
5717
5755
  await installBundle(staging, destination);
5718
5756
  return manifest;
@@ -5954,6 +5992,540 @@ var serializeAbsoluteMobileAuthEnvironment = (config, auth) => auth === undefine
5954
5992
  // src/mobile/buildPipeline.ts
5955
5993
  init_syncSchema();
5956
5994
  init_deviceCapabilities();
5995
+
5996
+ // src/mobile/expoProject.ts
5997
+ import {
5998
+ access as access9,
5999
+ cp as cp2,
6000
+ mkdir as mkdir11,
6001
+ mkdtemp as mkdtemp6,
6002
+ readdir as readdir4,
6003
+ readFile as readFile14,
6004
+ rename as rename11,
6005
+ rm as rm9,
6006
+ writeFile as writeFile12
6007
+ } from "fs/promises";
6008
+ import { createHash as createHash10 } from "crypto";
6009
+ import { basename as basename4, dirname as dirname10, join as join14, relative as relative10, resolve as resolve12 } from "path";
6010
+ var EXPO_GENERATED_HEADER = `// Generated by AbsoluteJS. Edit absolute.config.ts, then run absolute mobile sync.
6011
+ `;
6012
+ var EXPO_ASSET_EXTENSION = ".absasset";
6013
+ var EXPO_PROJECT_MARKER = ".absolutejs-expo-project";
6014
+ var exists3 = async (path) => {
6015
+ try {
6016
+ await access9(path);
6017
+ return true;
6018
+ } catch {
6019
+ return false;
6020
+ }
6021
+ };
6022
+ var portableRelative2 = (from, destination) => {
6023
+ const value = relative10(from, destination).replaceAll("\\", "/");
6024
+ return value.startsWith(".") ? value : `./${value}`;
6025
+ };
6026
+ var routeSegments = (route) => route.split("/").filter(Boolean).map((segment) => {
6027
+ if (segment.startsWith(":"))
6028
+ return `[${segment.slice(1)}]`;
6029
+ if (segment === "." || segment === ".." || !/^[A-Za-z0-9._~-]+$/u.test(segment)) {
6030
+ throw new TypeError(`Expo native route ${route} contains an unsupported segment ${segment}.`);
6031
+ }
6032
+ return segment;
6033
+ });
6034
+ var routeFile = (project, route) => join14(project, "app", ...routeSegments(route), "index.tsx");
6035
+ var expoPackage = () => ({
6036
+ dependencies: {
6037
+ expo: "~57.0.9",
6038
+ "expo-asset": "~57.0.15",
6039
+ "expo-constants": "~57.0.16",
6040
+ "expo-file-system": "~57.0.6",
6041
+ "expo-haptics": "~57.0.2",
6042
+ "expo-linking": "~57.0.8",
6043
+ "expo-router": "~57.0.17",
6044
+ react: "19.2.3",
6045
+ "react-native": "0.86.3",
6046
+ "react-native-safe-area-context": "~5.7.0",
6047
+ "react-native-screens": "4.26.0",
6048
+ "react-native-webview": "13.16.1"
6049
+ },
6050
+ devDependencies: {
6051
+ "@types/react": "~19.2.2",
6052
+ typescript: "~6.0.3"
6053
+ },
6054
+ main: "expo-router/entry",
6055
+ name: "absolutejs-expo-shell",
6056
+ private: true,
6057
+ scripts: {
6058
+ android: "expo run:android",
6059
+ ios: "expo run:ios",
6060
+ start: "expo start --dev-client"
6061
+ },
6062
+ version: "0.0.0"
6063
+ });
6064
+ var expoAppConfig = (config) => ({
6065
+ expo: {
6066
+ android: {
6067
+ intentFilters: config.deepLinkHosts.map((host2) => ({
6068
+ action: "VIEW",
6069
+ autoVerify: true,
6070
+ category: ["BROWSABLE", "DEFAULT"],
6071
+ data: [{ host: host2, pathPrefix: "/", scheme: "https" }]
6072
+ })),
6073
+ package: config.appId
6074
+ },
6075
+ experiments: { typedRoutes: true },
6076
+ ios: {
6077
+ associatedDomains: config.deepLinkHosts.map((host2) => `applinks:${host2}`),
6078
+ bundleIdentifier: config.appId,
6079
+ ...config.iosVersion ? { buildNumber: config.iosVersion } : {}
6080
+ },
6081
+ name: config.appName,
6082
+ plugins: ["expo-router"],
6083
+ runtimeVersion: { policy: "appVersion" },
6084
+ scheme: config.deepLinkScheme,
6085
+ slug: config.appId.toLowerCase().replaceAll(".", "-"),
6086
+ version: config.iosVersion ?? "0.1.0"
6087
+ }
6088
+ });
6089
+ var metroConfig = (projectRoot) => `${EXPO_GENERATED_HEADER}const { getDefaultConfig } = require('expo/metro-config');
6090
+ const path = require('node:path');
6091
+
6092
+ const projectRoot = __dirname;
6093
+ const appRoot = ${JSON.stringify(projectRoot)};
6094
+ const config = getDefaultConfig(projectRoot);
6095
+ config.resolver.assetExts.push('absasset');
6096
+ config.resolver.nodeModulesPaths = [
6097
+ path.join(projectRoot, 'node_modules'),
6098
+ path.join(appRoot, 'node_modules')
6099
+ ];
6100
+ config.watchFolders = [appRoot];
6101
+
6102
+ module.exports = config;
6103
+ `;
6104
+ var layoutSource = `${EXPO_GENERATED_HEADER}import { Stack } from 'expo-router';
6105
+
6106
+ export default function AbsoluteLayout() {
6107
+ return <Stack screenOptions={{ headerShown: false }} />;
6108
+ }
6109
+ `;
6110
+ var nativeDiagnosticSource = `${EXPO_GENERATED_HEADER}import * as Haptics from 'expo-haptics';
6111
+ import { Link } from 'expo-router';
6112
+ import { Pressable, SafeAreaView, StyleSheet, Text, View } from 'react-native';
6113
+
6114
+ export default function AbsoluteNativeDiagnostic() {
6115
+ return (
6116
+ <SafeAreaView style={styles.page}>
6117
+ <View style={styles.card}>
6118
+ <Text style={styles.eyebrow}>ABSOLUTEJS \xB7 EXPO EXPERIMENT</Text>
6119
+ <Text style={styles.title}>This screen is native React UI.</Text>
6120
+ <Text style={styles.body}>Ordinary AbsoluteJS routes remain embedded web routes. Only explicitly owned routes use React Native.</Text>
6121
+ <Pressable style={styles.button} onPress={() => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium)}>
6122
+ <Text style={styles.buttonText}>Test native haptics</Text>
6123
+ </Pressable>
6124
+ <Link href="/" style={styles.link}>Open the AbsoluteJS app</Link>
6125
+ </View>
6126
+ </SafeAreaView>
6127
+ );
6128
+ }
6129
+
6130
+ const styles = StyleSheet.create({
6131
+ body: { color: '#cbd5e1', fontSize: 16, lineHeight: 24 },
6132
+ button: { backgroundColor: '#f8fafc', borderRadius: 12, padding: 14 },
6133
+ buttonText: { color: '#020617', fontSize: 16, fontWeight: '700', textAlign: 'center' },
6134
+ card: { backgroundColor: '#0f172a', borderRadius: 24, gap: 20, maxWidth: 560, padding: 28, width: '100%' },
6135
+ eyebrow: { color: '#38bdf8', fontSize: 12, fontWeight: '800', letterSpacing: 1.5 },
6136
+ link: { color: '#7dd3fc', fontSize: 16, textAlign: 'center' },
6137
+ page: { alignItems: 'center', backgroundColor: '#020617', flex: 1, justifyContent: 'center', padding: 20 },
6138
+ title: { color: '#f8fafc', fontSize: 32, fontWeight: '800' }
6139
+ });
6140
+ `;
6141
+ var webHostSource = (config) => {
6142
+ const nativeRoutes = [
6143
+ "/__absolute/native",
6144
+ ...Object.keys(config.expoNativeRoutes)
6145
+ ];
6146
+ return `${EXPO_GENERATED_HEADER}import * as Haptics from 'expo-haptics';
6147
+ import * as Linking from 'expo-linking';
6148
+ import { router, usePathname } from 'expo-router';
6149
+ import { useEffect, useRef, useState } from 'react';
6150
+ import { ActivityIndicator, BackHandler, StyleSheet, View } from 'react-native';
6151
+ import { WebView, type WebViewMessageEvent } from 'react-native-webview';
6152
+ import { materializeAbsoluteWebBundle } from './webAssets';
6153
+
6154
+ const BRIDGE_FORMAT = 1;
6155
+ const MAX_MESSAGE_BYTES = 64 * 1024;
6156
+ const MAX_HTTP_BODY_BYTES = 48 * 1024;
6157
+ const NATIVE_ROUTES = new Set(${JSON.stringify(nativeRoutes)});
6158
+ const PRODUCTION_ORIGIN = ${JSON.stringify(config.productionOrigin)};
6159
+
6160
+ const bridgeBootstrap = (path: string) => \`(() => {
6161
+ const pending = new Map();
6162
+ let sequence = 0;
6163
+ let currentPath = \${JSON.stringify(path)};
6164
+ const send = value => {
6165
+ const source = JSON.stringify(value);
6166
+ if (new TextEncoder().encode(source).byteLength > 65536) throw new Error('Expo bridge message exceeds 64 KiB.');
6167
+ window.ReactNativeWebView.postMessage(source);
6168
+ };
6169
+ globalThis.__absoluteExpoReceive = source => {
6170
+ const message = JSON.parse(source);
6171
+ const operation = pending.get(message.id);
6172
+ if (!operation) return;
6173
+ pending.delete(message.id);
6174
+ clearTimeout(operation.timer);
6175
+ message.error ? operation.reject(new Error(message.error.message)) : operation.resolve(message.result);
6176
+ };
6177
+ globalThis.__absoluteExpoBridge = {
6178
+ request(method, params) {
6179
+ const id = 'web_' + Date.now().toString(36) + '_' + (++sequence).toString(36);
6180
+ send({ format: 1, id, kind: 'request', method, params, path: currentPath });
6181
+ return new Promise((resolve, reject) => {
6182
+ const timer = setTimeout(() => {
6183
+ pending.delete(id);
6184
+ reject(new Error('Expo bridge request timed out.'));
6185
+ }, 10000);
6186
+ pending.set(id, { reject, resolve, timer });
6187
+ });
6188
+ },
6189
+ setPath(path) {
6190
+ currentPath = path;
6191
+ send({ format: 1, kind: 'event', event: 'navigation', path });
6192
+ }
6193
+ };
6194
+ document.addEventListener('click', event => {
6195
+ const anchor = event.target instanceof Element ? event.target.closest('a[href]') : null;
6196
+ if (!anchor) return;
6197
+ const url = new URL(anchor.href, location.href);
6198
+ if (!${JSON.stringify(nativeRoutes)}.includes(url.pathname)) return;
6199
+ event.preventDefault();
6200
+ send({ format: 1, kind: 'event', event: 'navigation', path: url.pathname + url.search + url.hash });
6201
+ }, true);
6202
+ send({ format: 1, kind: 'event', event: 'ready', path: \${JSON.stringify(path)} });
6203
+ })(); true;\`;
6204
+
6205
+ const impact = async (params: Record<string, unknown>) => {
6206
+ const style = params.style;
6207
+ if (style === 'selection') return Haptics.selectionAsync();
6208
+ if (style === 'success' || style === 'warning' || style === 'error') {
6209
+ const value = style === 'success' ? Haptics.NotificationFeedbackType.Success : style === 'warning' ? Haptics.NotificationFeedbackType.Warning : Haptics.NotificationFeedbackType.Error;
6210
+ return Haptics.notificationAsync(value);
6211
+ }
6212
+ const value = style === 'light' ? Haptics.ImpactFeedbackStyle.Light : style === 'heavy' ? Haptics.ImpactFeedbackStyle.Heavy : Haptics.ImpactFeedbackStyle.Medium;
6213
+ return Haptics.impactAsync(value);
6214
+ };
6215
+
6216
+ const bridgeFetch = async (params: Record<string, unknown>) => {
6217
+ if (params.method !== 'GET' || typeof params.url !== 'string' || typeof params.headers !== 'object' || params.headers === null || Array.isArray(params.headers)) throw new Error('Expo bridge HTTP request is invalid.');
6218
+ const url = new URL(params.url);
6219
+ if (url.origin !== PRODUCTION_ORIGIN || url.username || url.password) throw new Error('Expo bridge HTTP request left productionOrigin.');
6220
+ const headers = new Headers();
6221
+ for (const [name, value] of Object.entries(params.headers as Record<string, unknown>)) {
6222
+ const normalized = name.toLowerCase();
6223
+ if (typeof value !== 'string' || (normalized !== 'accept' && !normalized.startsWith('x-absolute-mobile-'))) throw new Error('Expo bridge HTTP header is not allowed.');
6224
+ headers.set(normalized, value);
6225
+ }
6226
+ const response = await fetch(url, { headers, method: 'GET', redirect: 'manual' });
6227
+ if (new URL(response.url || url.href).origin !== PRODUCTION_ORIGIN || response.status >= 300 && response.status < 400) throw new Error('Expo bridge HTTP redirects are not allowed.');
6228
+ const body = await response.text();
6229
+ if (new TextEncoder().encode(body).byteLength > MAX_HTTP_BODY_BYTES) throw new Error('Expo bridge HTTP response exceeds 48 KiB.');
6230
+ const responseHeaders: Record<string, string> = {};
6231
+ for (const name of ['cache-control', 'content-type']) {
6232
+ const value = response.headers.get(name);
6233
+ if (value) responseHeaders[name] = value;
6234
+ }
6235
+ return { body, headers: responseHeaders, status: response.status };
6236
+ };
6237
+
6238
+ export function AbsoluteWebHost() {
6239
+ const pathname = usePathname() || '/';
6240
+ const webView = useRef<WebView>(null);
6241
+ const [indexUri, setIndexUri] = useState<string>();
6242
+ const [canGoBack, setCanGoBack] = useState(false);
6243
+ const activeWebPath = useRef(pathname);
6244
+
6245
+ useEffect(() => { void materializeAbsoluteWebBundle().then(setIndexUri); }, []);
6246
+ useEffect(() => {
6247
+ const subscription = BackHandler.addEventListener('hardwareBackPress', () => {
6248
+ if (!canGoBack) return false;
6249
+ webView.current?.goBack();
6250
+ return true;
6251
+ });
6252
+ return () => subscription.remove();
6253
+ }, [canGoBack]);
6254
+
6255
+ const respond = (message: Record<string, unknown>) => {
6256
+ const source = JSON.stringify(message).replaceAll('\\u2028', '\\\\u2028').replaceAll('\\u2029', '\\\\u2029');
6257
+ if (new TextEncoder().encode(source).byteLength > MAX_MESSAGE_BYTES) throw new Error('Expo bridge response exceeds 64 KiB.');
6258
+ webView.current?.injectJavaScript(\`globalThis.__absoluteExpoReceive(\${JSON.stringify(source)}); true;\`);
6259
+ };
6260
+ const onMessage = async (event: WebViewMessageEvent) => {
6261
+ const source = event.nativeEvent.data;
6262
+ if (new TextEncoder().encode(source).byteLength > MAX_MESSAGE_BYTES) return;
6263
+ let message: Record<string, unknown>;
6264
+ try { message = JSON.parse(source); } catch { return; }
6265
+ if (message.format !== BRIDGE_FORMAT || typeof message.path !== 'string' || !message.path.startsWith('/') || message.path.startsWith('//')) return;
6266
+ if (message.kind === 'event' && (message.event === 'navigation' || message.event === 'ready')) {
6267
+ const target = new URL(message.path, PRODUCTION_ORIGIN);
6268
+ if (target.origin !== PRODUCTION_ORIGIN) return;
6269
+ if (NATIVE_ROUTES.has(target.pathname)) router.push(message.path as never);
6270
+ else activeWebPath.current = message.path;
6271
+ return;
6272
+ }
6273
+ if (message.kind !== 'request' || typeof message.id !== 'string' || message.path !== activeWebPath.current) return;
6274
+ try {
6275
+ if (typeof message.params !== 'object' || message.params === null || Array.isArray(message.params)) throw new Error('Expo bridge method params are invalid.');
6276
+ if (message.method === 'devices.haptics.impact') {
6277
+ const style = (message.params as Record<string, unknown>).style;
6278
+ if (typeof style !== 'string' || !['error', 'heavy', 'light', 'medium', 'selection', 'success', 'vibrate', 'warning'].includes(style)) throw new Error('Expo bridge haptics style is invalid.');
6279
+ await impact(message.params as Record<string, unknown>);
6280
+ respond({ format: BRIDGE_FORMAT, id: message.id, kind: 'response', result: null });
6281
+ } else if (message.method === 'http.fetch') {
6282
+ respond({ format: BRIDGE_FORMAT, id: message.id, kind: 'response', result: await bridgeFetch(message.params as Record<string, unknown>) });
6283
+ } else {
6284
+ throw new Error('Expo bridge method is not allowed.');
6285
+ }
6286
+ } catch (error) {
6287
+ respond({ error: { code: 'failed', message: error instanceof Error ? error.message : 'Native operation failed.' }, format: BRIDGE_FORMAT, id: message.id, kind: 'response' });
6288
+ }
6289
+ };
6290
+
6291
+ if (!indexUri) return <View style={styles.loading}><ActivityIndicator /></View>;
6292
+ return <WebView
6293
+ allowFileAccess
6294
+ allowFileAccessFromFileURLs
6295
+ allowUniversalAccessFromFileURLs={false}
6296
+ allowingReadAccessToURL={indexUri.slice(0, indexUri.lastIndexOf('/') + 1)}
6297
+ injectedJavaScriptBeforeContentLoaded={bridgeBootstrap(pathname)}
6298
+ onMessage={onMessage}
6299
+ onNavigationStateChange={state => setCanGoBack(state.canGoBack)}
6300
+ onShouldStartLoadWithRequest={request => {
6301
+ if (request.url.startsWith('file:') || request.url.startsWith(PRODUCTION_ORIGIN)) return true;
6302
+ void Linking.openURL(request.url);
6303
+ return false;
6304
+ }}
6305
+ ref={webView}
6306
+ source={{ uri: indexUri + '?absolutePath=' + encodeURIComponent(pathname) }}
6307
+ style={styles.web}
6308
+ />;
6309
+ }
6310
+
6311
+ const styles = StyleSheet.create({ loading: { alignItems: 'center', flex: 1, justifyContent: 'center' }, web: { flex: 1 } });
6312
+ `;
6313
+ };
6314
+ var webRouteSource = `${EXPO_GENERATED_HEADER}import { AbsoluteWebHost } from '../src/generated/AbsoluteWebHost';
6315
+
6316
+ export default AbsoluteWebHost;
6317
+ `;
6318
+ var catchAllRouteSource = `${EXPO_GENERATED_HEADER}import { AbsoluteWebHost } from '../src/generated/AbsoluteWebHost';
6319
+
6320
+ export default AbsoluteWebHost;
6321
+ `;
6322
+ var nativeWrapperSource = (wrapper, module) => `${EXPO_GENERATED_HEADER}export { default } from ${JSON.stringify(portableRelative2(dirname10(wrapper), module).replace(/\.(?:[cm]?[jt]sx?)$/u, ""))};
6323
+ `;
6324
+ var expoTsConfig = (projectRoot, project) => ({
6325
+ compilerOptions: {
6326
+ paths: {
6327
+ "*": [
6328
+ "./node_modules/*",
6329
+ `${portableRelative2(project, projectRoot)}/node_modules/*`
6330
+ ],
6331
+ react: ["./node_modules/@types/react/index.d.ts"],
6332
+ "react/*": ["./node_modules/@types/react/*"]
6333
+ },
6334
+ strict: true
6335
+ },
6336
+ extends: "expo/tsconfig.base"
6337
+ });
6338
+ var writeManagedFile = async (path, source, force) => {
6339
+ await mkdir11(dirname10(path), { recursive: true });
6340
+ if (await exists3(path)) {
6341
+ const current = await readFile14(path, "utf8");
6342
+ if (current === source)
6343
+ return false;
6344
+ if (!force && !current.startsWith(EXPO_GENERATED_HEADER)) {
6345
+ throw new TypeError(`Expo project file ${path} is not AbsoluteJS-managed; rerun with --force only after reviewing it.`);
6346
+ }
6347
+ }
6348
+ const temporary = `${path}.${crypto.randomUUID()}.tmp`;
6349
+ await writeFile12(temporary, source, { flag: "wx" });
6350
+ await rename11(temporary, path);
6351
+ return true;
6352
+ };
6353
+ var jsonSource = (value) => `${JSON.stringify(value, null, "\t")}
6354
+ `;
6355
+ var writeAbsoluteExpoProject = async (config, options) => {
6356
+ if (config.engine !== "expo")
6357
+ throw new TypeError("Expo project generation requires mobile.engine: expo.");
6358
+ const projectRoot = resolve12(options.projectRoot);
6359
+ const project = config.nativeProjectDirectory;
6360
+ const routeModules = Object.entries(config.expoNativeRoutes);
6361
+ const moduleChecks = await Promise.all(routeModules.map(async ([route, module]) => ({
6362
+ exists: await exists3(module),
6363
+ module,
6364
+ route
6365
+ })));
6366
+ const missing = moduleChecks.find((entry) => !entry.exists);
6367
+ if (missing) {
6368
+ throw new TypeError(`Expo native route ${missing.route} references missing module ${missing.module}.`);
6369
+ }
6370
+ const marker = join14(project, EXPO_PROJECT_MARKER);
6371
+ if (await exists3(project) && !await exists3(marker)) {
6372
+ const entries = await readdir4(project);
6373
+ if (entries.length > 0 && !options.force) {
6374
+ throw new TypeError(`Expo project directory ${project} is not AbsoluteJS-managed; rerun with --force only after reviewing it.`);
6375
+ }
6376
+ }
6377
+ await mkdir11(project, { recursive: true });
6378
+ await writeFile12(marker, `format=1
6379
+ `);
6380
+ const files = new Map([
6381
+ [
6382
+ join14(project, ".gitignore"),
6383
+ `.expo/
6384
+ android/
6385
+ ios/
6386
+ node_modules/
6387
+ `
6388
+ ],
6389
+ [join14(project, "app.json"), jsonSource(expoAppConfig(config))],
6390
+ [join14(project, "package.json"), jsonSource(expoPackage())],
6391
+ [join14(project, "metro.config.js"), metroConfig(projectRoot)],
6392
+ [
6393
+ join14(project, "tsconfig.json"),
6394
+ jsonSource(expoTsConfig(projectRoot, project))
6395
+ ],
6396
+ [join14(project, "app", "_layout.tsx"), layoutSource],
6397
+ [
6398
+ join14(project, "app", "__absolute", "native", "index.tsx"),
6399
+ nativeDiagnosticSource
6400
+ ],
6401
+ [
6402
+ join14(project, "src", "generated", "AbsoluteWebHost.tsx"),
6403
+ webHostSource(config)
6404
+ ]
6405
+ ]);
6406
+ if (!config.expoNativeRoutes["/"]) {
6407
+ files.set(join14(project, "app", "index.tsx"), webRouteSource);
6408
+ }
6409
+ files.set(join14(project, "app", "[...absolute].tsx"), catchAllRouteSource);
6410
+ for (const [route, module] of routeModules) {
6411
+ const wrapper = route === "/" ? join14(project, "app", "index.tsx") : routeFile(project, route);
6412
+ files.set(wrapper, nativeWrapperSource(wrapper, module));
6413
+ }
6414
+ const changes = await Promise.all([...files].map(([path, source]) => writeManagedFile(path, source, true)));
6415
+ const changed = changes.filter(Boolean).length;
6416
+ return { changed, path: project, written: [...files.keys()] };
6417
+ };
6418
+ var walkFiles = async (root, directory = root) => {
6419
+ const entries = await readdir4(directory, { withFileTypes: true });
6420
+ const nested = await Promise.all(entries.map((entry) => {
6421
+ const path = join14(directory, entry.name);
6422
+ if (entry.isDirectory())
6423
+ return walkFiles(root, path);
6424
+ if (entry.isFile())
6425
+ return [path];
6426
+ throw new TypeError(`Expo embedded bundle cannot contain a symbolic link or special file: ${path}.`);
6427
+ }));
6428
+ return nested.flat().sort();
6429
+ };
6430
+ var renameIfPresent = async (source, destination) => {
6431
+ try {
6432
+ await rename11(source, destination);
6433
+ return true;
6434
+ } catch (error) {
6435
+ if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT") {
6436
+ return false;
6437
+ }
6438
+ throw error;
6439
+ }
6440
+ };
6441
+ var installStagedDirectory = async (staging, destination) => {
6442
+ const backup = `${destination}.previous-${crypto.randomUUID()}`;
6443
+ const moved = await renameIfPresent(destination, backup);
6444
+ try {
6445
+ await rename11(staging, destination);
6446
+ if (moved)
6447
+ await rm9(backup, { force: true, recursive: true });
6448
+ } catch (error) {
6449
+ if (moved)
6450
+ await rename11(backup, destination);
6451
+ throw error;
6452
+ }
6453
+ };
6454
+ var assetModuleSource = (assets, bundleId) => `${EXPO_GENERATED_HEADER}import { Asset } from 'expo-asset';
6455
+ import { Directory, File, Paths } from 'expo-file-system';
6456
+
6457
+ declare const require: (path: string) => number;
6458
+ const BUNDLE_ID = ${JSON.stringify(bundleId)};
6459
+ const ASSETS = [
6460
+ ${assets.map(({ asset, path }) => ` { module: require(${JSON.stringify(asset)}), path: ${JSON.stringify(path)} }`).join(`,
6461
+ `)}
6462
+ ] as const;
6463
+
6464
+ export const materializeAbsoluteWebBundle = async () => {
6465
+ const root = new Directory(Paths.document, 'absolutejs-web', BUNDLE_ID);
6466
+ root.create({ idempotent: true, intermediates: true });
6467
+ for (const entry of ASSETS) {
6468
+ const parts = entry.path.split('/');
6469
+ const name = parts.pop();
6470
+ if (!name) throw new Error('AbsoluteJS embedded asset path is invalid.');
6471
+ const directory = new Directory(root, ...parts);
6472
+ directory.create({ idempotent: true, intermediates: true });
6473
+ const destination = new File(directory, name);
6474
+ if (destination.exists) continue;
6475
+ const asset = await Asset.fromModule(entry.module).downloadAsync();
6476
+ if (!asset.localUri) throw new Error('Expo did not materialize an embedded AbsoluteJS asset.');
6477
+ new File(asset.localUri).copy(destination);
6478
+ }
6479
+
6480
+ return new File(root, 'index.html').uri;
6481
+ };
6482
+ `;
6483
+ var syncAbsoluteExpoWebAssets = async (config) => {
6484
+ if (config.engine !== "expo")
6485
+ throw new TypeError("Expo asset sync requires mobile.engine: expo.");
6486
+ const marker = join14(config.nativeProjectDirectory, EXPO_PROJECT_MARKER);
6487
+ if (!await exists3(marker)) {
6488
+ throw new TypeError("Expo asset sync requires an AbsoluteJS-managed Expo project. Run mobile init first.");
6489
+ }
6490
+ const manifestPath = join14(config.bundleDirectory, "absolute-mobile-manifest.json");
6491
+ const manifest = JSON.parse(await readFile14(manifestPath, "utf8"));
6492
+ const appBuild = typeof manifest === "object" && manifest !== null && typeof Reflect.get(manifest, "appBuild") === "string" ? String(Reflect.get(manifest, "appBuild")) : undefined;
6493
+ if (!appBuild)
6494
+ throw new TypeError("AbsoluteJS mobile manifest has no appBuild.");
6495
+ const files = await walkFiles(config.bundleDirectory);
6496
+ const bundleHash = createHash10("sha256");
6497
+ const filesWithContents = await Promise.all(files.map(async (file) => ({ contents: await readFile14(file), file })));
6498
+ filesWithContents.forEach(({ contents, file }) => {
6499
+ bundleHash.update(relative10(config.bundleDirectory, file).replaceAll("\\", "/"));
6500
+ bundleHash.update("\x00");
6501
+ bundleHash.update(contents);
6502
+ bundleHash.update("\x00");
6503
+ });
6504
+ const bundleId = `amexpo_${bundleHash.digest("hex")}`;
6505
+ const destination = join14(config.nativeProjectDirectory, "assets", "absolute");
6506
+ await mkdir11(dirname10(destination), { recursive: true });
6507
+ const staging = await mkdtemp6(join14(dirname10(destination), `.${basename4(destination)}.stage-`));
6508
+ let assets;
6509
+ try {
6510
+ assets = await Promise.all(files.map(async (source, index) => {
6511
+ const name = `${String(index).padStart(6, "0")}${EXPO_ASSET_EXTENSION}`;
6512
+ await cp2(source, join14(staging, name));
6513
+ return {
6514
+ asset: portableRelative2(join14(config.nativeProjectDirectory, "src", "generated"), join14(destination, name)),
6515
+ path: relative10(config.bundleDirectory, source).replaceAll("\\", "/")
6516
+ };
6517
+ }));
6518
+ await installStagedDirectory(staging, destination);
6519
+ } catch (error) {
6520
+ await rm9(staging, { force: true, recursive: true });
6521
+ throw error;
6522
+ }
6523
+ const generated = join14(config.nativeProjectDirectory, "src", "generated", "webAssets.ts");
6524
+ await writeManagedFile(generated, assetModuleSource(assets, bundleId), true);
6525
+ return { appBuild, assets: assets.length, bundleId, path: destination };
6526
+ };
6527
+
6528
+ // src/mobile/buildPipeline.ts
5957
6529
  var isElysiaApp = (value) => typeof value === "object" && value !== null && typeof Reflect.get(value, "compile") === "function" && Array.isArray(Reflect.get(value, "routes"));
5958
6530
  var isStringRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && Object.values(value).every((entry) => typeof entry === "string");
5959
6531
  var serverExportName = (loaded, app) => {
@@ -5987,11 +6559,11 @@ var loadServerApp = async (producerPath) => {
5987
6559
  return { app, exportName };
5988
6560
  };
5989
6561
  var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
5990
- const buildDirectory = resolve12(options.buildDirectory);
6562
+ const buildDirectory = resolve13(options.buildDirectory);
5991
6563
  const mobile = normalizeAbsoluteMobileConfig(options.mobile, options.projectRoot);
5992
- const root = join14(buildDirectory, ".absolutejs", "mobile-compatibility");
6564
+ const root = join15(buildDirectory, ".absolutejs", "mobile-compatibility");
5993
6565
  const [manifestSource, previous] = await Promise.all([
5994
- readFile14(join14(buildDirectory, "manifest.json"), "utf8"),
6566
+ readFile15(join15(buildDirectory, "manifest.json"), "utf8"),
5995
6567
  readAbsoluteMobileMaterializedReleases(root)
5996
6568
  ]);
5997
6569
  const manifest = JSON.parse(manifestSource);
@@ -6004,11 +6576,11 @@ var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
6004
6576
  process.env.ABSOLUTE_BUILD_DIR = buildDirectory;
6005
6577
  process.env.ABSOLUTE_COMPILED_RUNTIME = "1";
6006
6578
  if (options.configPath) {
6007
- process.env.ABSOLUTE_CONFIG = resolve12(options.projectRoot, options.configPath);
6579
+ process.env.ABSOLUTE_CONFIG = resolve13(options.projectRoot, options.configPath);
6008
6580
  }
6009
6581
  let loaded;
6010
6582
  try {
6011
- loaded = await loadServerApp(resolve12(options.producerPath));
6583
+ loaded = await loadServerApp(resolve13(options.producerPath));
6012
6584
  } finally {
6013
6585
  restoreEnvironmentVariable("ABSOLUTE_BUILD_DIR", previousBuildDirectory);
6014
6586
  restoreEnvironmentVariable("ABSOLUTE_COMPILED_RUNTIME", previousCompiledRuntime);
@@ -6021,19 +6593,27 @@ var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
6021
6593
  manifest,
6022
6594
  previousArtifacts: previous.map(({ artifact }) => artifact),
6023
6595
  producerExport: loaded.exportName,
6024
- producerPath: resolve12(options.producerPath),
6596
+ producerPath: resolve13(options.producerPath),
6025
6597
  runtime: String(ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION)
6026
6598
  });
6027
6599
  const auth = resolveAbsoluteMobileAuthManifest(options.projectRoot, mobile);
6600
+ if (mobile.engine === "expo" && auth) {
6601
+ throw new TypeError("Expo mobile Auth is not released yet. The experimental Expo shell cannot safely substitute Capacitor credentials; remove engine: 'expo' or wait for @absolutejs/auth-expo.");
6602
+ }
6028
6603
  const sync = auth !== undefined && projectUsesAbsoluteSync(options.projectRoot);
6029
6604
  const syncSchema = sync ? discoverAbsoluteSyncSchema(options.projectRoot) : undefined;
6030
6605
  const deviceCapabilities = resolveAbsoluteDeviceCapabilityPlan(options.projectRoot);
6031
6606
  const usesPush = deviceCapabilities.capabilities.includes("pushNotifications");
6607
+ if (mobile.engine === "expo" && deviceCapabilities.capabilities.some((capability) => capability !== "haptics")) {
6608
+ throw new TypeError("Experimental Expo builds currently bridge only @absolutejs/devices haptics. Other detected device capabilities require their Expo adapters.");
6609
+ }
6032
6610
  if (usesPush && !auth)
6033
6611
  throw new TypeError("Portable push notifications require @absolutejs/auth so provider tokens can be registered without exposing identity controls to page code.");
6034
6612
  if (usesPush && !loaded.app.routes.some((route) => route.path === "/auth/push" || route.path === "/auth/mobile/push"))
6035
6613
  throw new TypeError("@absolutejs/devices pushNotifications is used, but Auth push is not configured. Pass a trusted server-side registrar to auth({ push: ... }).");
6036
- assertAbsoluteDeviceCapabilityPackages(options.projectRoot, deviceCapabilities);
6614
+ if (mobile.engine === "capacitor") {
6615
+ assertAbsoluteDeviceCapabilityPackages(options.projectRoot, deviceCapabilities);
6616
+ }
6037
6617
  if (auth && !loaded.app.routes.some((route) => route.path === "/.well-known/openid-configuration")) {
6038
6618
  throw new TypeError("@absolutejs/auth is installed, but its OIDC provider is not mounted. Native authentication requires the auth oidc configuration so AbsoluteJS can provision a public PKCE client.");
6039
6619
  }
@@ -6057,6 +6637,12 @@ var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
6057
6637
  ...sync ? { sync: true } : {},
6058
6638
  ...syncSchema ? { syncSchema: { components: syncSchema.components } } : {}
6059
6639
  });
6640
+ if (mobile.engine === "expo") {
6641
+ await writeAbsoluteExpoProject(mobile, {
6642
+ projectRoot: options.projectRoot
6643
+ });
6644
+ await syncAbsoluteExpoWebAssets(mobile);
6645
+ }
6060
6646
  return current.artifact;
6061
6647
  };
6062
6648
 
@@ -6174,10 +6760,152 @@ var installAbsoluteMobileSyncRemediation = (bridge = {
6174
6760
  // src/mobile/index.ts
6175
6761
  init_deviceCapabilities();
6176
6762
 
6763
+ // src/mobile/expoBridge.ts
6764
+ var ABSOLUTE_EXPO_BRIDGE_FORMAT = 1;
6765
+ var ABSOLUTE_EXPO_BRIDGE_MAX_BYTES = 64 * 1024;
6766
+ var ABSOLUTE_EXPO_BRIDGE_METHODS = [
6767
+ "devices.haptics.impact",
6768
+ "http.fetch"
6769
+ ];
6770
+ var isRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
6771
+ var validId = (value) => typeof value === "string" && /^[A-Za-z0-9_-]{1,80}$/u.test(value);
6772
+ var validPath = (value) => typeof value === "string" && value.startsWith("/") && !value.startsWith("//") && value.length <= 4096;
6773
+ var parseRequest = (value) => {
6774
+ if (typeof value.id !== "string" || !validId(value.id))
6775
+ throw new TypeError("Expo bridge request id is invalid.");
6776
+ const method = ABSOLUTE_EXPO_BRIDGE_METHODS.find((candidate) => candidate === value.method);
6777
+ if (!method) {
6778
+ throw new TypeError("Expo bridge method is not allowed.");
6779
+ }
6780
+ if (!isRecord8(value.params))
6781
+ throw new TypeError("Expo bridge request params must be an object.");
6782
+ if (typeof value.path !== "string" || !validPath(value.path))
6783
+ throw new TypeError("Expo bridge request path is invalid.");
6784
+ if (method === "devices.haptics.impact") {
6785
+ const styles = new Set([
6786
+ "error",
6787
+ "heavy",
6788
+ "light",
6789
+ "medium",
6790
+ "selection",
6791
+ "success",
6792
+ "vibrate",
6793
+ "warning"
6794
+ ]);
6795
+ if (typeof value.params.style !== "string" || !styles.has(value.params.style))
6796
+ throw new TypeError("Expo bridge haptics style is invalid.");
6797
+ if (value.params.durationMs !== undefined && (typeof value.params.durationMs !== "number" || !Number.isFinite(value.params.durationMs) || value.params.durationMs < 0 || value.params.durationMs > 1e4)) {
6798
+ throw new TypeError("Expo bridge haptics duration is invalid.");
6799
+ }
6800
+ }
6801
+ if (method === "http.fetch") {
6802
+ if (typeof value.params.url !== "string")
6803
+ throw new TypeError("Expo bridge HTTP URL is invalid.");
6804
+ const url = new URL(value.params.url);
6805
+ const loopbackHttp = url.protocol === "http:" && (url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]");
6806
+ if (url.protocol !== "https:" && !loopbackHttp)
6807
+ throw new TypeError("Expo bridge HTTP URL must use HTTPS.");
6808
+ if (value.params.method !== "GET")
6809
+ throw new TypeError("Expo bridge HTTP method is not allowed.");
6810
+ if (!isRecord8(value.params.headers))
6811
+ throw new TypeError("Expo bridge HTTP headers must be an object.");
6812
+ const headersAllowed = Object.entries(value.params.headers).every(([name, header]) => typeof header === "string" && (name.toLowerCase() === "accept" || name.toLowerCase().startsWith("x-absolute-mobile-")));
6813
+ if (!headersAllowed) {
6814
+ throw new TypeError("Expo bridge HTTP header is not allowed.");
6815
+ }
6816
+ }
6817
+ return {
6818
+ format: ABSOLUTE_EXPO_BRIDGE_FORMAT,
6819
+ id: value.id,
6820
+ kind: "request",
6821
+ method,
6822
+ params: value.params,
6823
+ path: value.path
6824
+ };
6825
+ };
6826
+ var parseResponse = (value) => {
6827
+ if (typeof value.id !== "string" || !validId(value.id))
6828
+ throw new TypeError("Expo bridge response id is invalid.");
6829
+ if (value.error !== undefined) {
6830
+ if (!isRecord8(value.error) || typeof value.error.code !== "string" || typeof value.error.message !== "string") {
6831
+ throw new TypeError("Expo bridge response error is invalid.");
6832
+ }
6833
+ if (value.result !== undefined)
6834
+ throw new TypeError("Expo bridge response cannot contain result and error.");
6835
+ }
6836
+ if (isRecord8(value.error)) {
6837
+ return {
6838
+ error: {
6839
+ code: String(value.error.code),
6840
+ message: String(value.error.message)
6841
+ },
6842
+ format: ABSOLUTE_EXPO_BRIDGE_FORMAT,
6843
+ id: value.id,
6844
+ kind: "response"
6845
+ };
6846
+ }
6847
+ return {
6848
+ format: ABSOLUTE_EXPO_BRIDGE_FORMAT,
6849
+ id: value.id,
6850
+ kind: "response",
6851
+ result: value.result
6852
+ };
6853
+ };
6854
+ var parseEvent = (value) => {
6855
+ if (value.event !== "ready" && value.event !== "navigation") {
6856
+ throw new TypeError("Expo bridge event is not allowed.");
6857
+ }
6858
+ if (typeof value.path !== "string" || !validPath(value.path))
6859
+ throw new TypeError("Expo bridge event path is invalid.");
6860
+ if (value.payload !== undefined && !isRecord8(value.payload)) {
6861
+ throw new TypeError("Expo bridge event payload must be an object.");
6862
+ }
6863
+ return {
6864
+ event: value.event,
6865
+ format: ABSOLUTE_EXPO_BRIDGE_FORMAT,
6866
+ kind: "event",
6867
+ path: value.path,
6868
+ ...value.payload ? { payload: value.payload } : {}
6869
+ };
6870
+ };
6871
+ var createAbsoluteExpoBridgeError = (id, code, message) => parseResponse({
6872
+ error: { code, message },
6873
+ format: ABSOLUTE_EXPO_BRIDGE_FORMAT,
6874
+ id,
6875
+ kind: "response"
6876
+ });
6877
+ var createAbsoluteExpoBridgeResponse = (id, result) => parseResponse({
6878
+ format: ABSOLUTE_EXPO_BRIDGE_FORMAT,
6879
+ id,
6880
+ kind: "response",
6881
+ result
6882
+ });
6883
+ var parseAbsoluteExpoBridgeMessage = (source) => {
6884
+ if (new TextEncoder().encode(source).byteLength > ABSOLUTE_EXPO_BRIDGE_MAX_BYTES)
6885
+ throw new TypeError("Expo bridge message exceeds 64 KiB.");
6886
+ let parsed;
6887
+ try {
6888
+ parsed = JSON.parse(source);
6889
+ } catch (cause) {
6890
+ throw new TypeError("Expo bridge message is not valid JSON.", {
6891
+ cause
6892
+ });
6893
+ }
6894
+ if (!isRecord8(parsed) || parsed.format !== ABSOLUTE_EXPO_BRIDGE_FORMAT) {
6895
+ throw new TypeError("Expo bridge message format is unsupported.");
6896
+ }
6897
+ if (parsed.kind === "request")
6898
+ return parseRequest(parsed);
6899
+ if (parsed.kind === "response")
6900
+ return parseResponse(parsed);
6901
+ if (parsed.kind === "event")
6902
+ return parseEvent(parsed);
6903
+ throw new TypeError("Expo bridge message kind is unsupported.");
6904
+ };
6177
6905
  // src/mobile/ciWorkflow.ts
6178
6906
  import { existsSync as existsSync3 } from "fs";
6179
- import { access as access9, mkdir as mkdir11, readFile as readFile15, writeFile as writeFile12 } from "fs/promises";
6180
- import { dirname as dirname10, extname as extname4, relative as relative10, resolve as resolve13, sep as sep6 } from "path";
6907
+ import { access as access10, mkdir as mkdir12, readFile as readFile16, writeFile as writeFile13 } from "fs/promises";
6908
+ import { dirname as dirname11, extname as extname4, relative as relative11, resolve as resolve14, sep as sep6 } from "path";
6181
6909
  var ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT = 1;
6182
6910
  var SECRET_NAME_PATTERN = /^[A-Z][A-Z0-9_]*$/u;
6183
6911
  var CI_ENV_INDENTATION = 6;
@@ -6196,9 +6924,9 @@ var RESERVED_SECRET_NAMES = new Set([
6196
6924
  "APP_STORE_CONNECT_KEY_ID",
6197
6925
  "APP_STORE_CONNECT_PRIVATE_KEY_BASE64"
6198
6926
  ]);
6199
- var exists3 = async (path) => {
6927
+ var exists4 = async (path) => {
6200
6928
  try {
6201
- await access9(path);
6929
+ await access10(path);
6202
6930
  return true;
6203
6931
  } catch {
6204
6932
  return false;
@@ -6206,9 +6934,9 @@ var exists3 = async (path) => {
6206
6934
  };
6207
6935
  var yamlString = (value) => `'${value.replaceAll("'", "''")}'`;
6208
6936
  var projectPath = (projectRoot, value, field2, options = {}) => {
6209
- const root = resolve13(projectRoot);
6210
- const path = resolve13(root, value);
6211
- const portable = relative10(root, path).replaceAll("\\", "/");
6937
+ const root = resolve14(projectRoot);
6938
+ const path = resolve14(root, value);
6939
+ const portable = relative11(root, path).replaceAll("\\", "/");
6212
6940
  if (portable === ".." || portable.startsWith(`..${sep6}`) || portable.startsWith("../") || portable === "") {
6213
6941
  throw new TypeError(`${field2} must remain inside the project root.`);
6214
6942
  }
@@ -6219,10 +6947,10 @@ var projectPath = (projectRoot, value, field2, options = {}) => {
6219
6947
  return portable;
6220
6948
  };
6221
6949
  var workflowOutputPath = (projectRoot, value) => {
6222
- const root = resolve13(projectRoot);
6223
- const workflows = resolve13(root, ".github/workflows");
6224
- const path = resolve13(root, value ?? ".github/workflows/absolute-mobile.yml");
6225
- const portable = relative10(workflows, path);
6950
+ const root = resolve14(projectRoot);
6951
+ const workflows = resolve14(root, ".github/workflows");
6952
+ const path = resolve14(root, value ?? ".github/workflows/absolute-mobile.yml");
6953
+ const portable = relative11(workflows, path);
6226
6954
  if (portable === ".." || portable.startsWith(`..${sep6}`) || extname4(path) !== ".yml" && extname4(path) !== ".yaml") {
6227
6955
  throw new TypeError("mobile ci github --output must be a .yml or .yaml file inside .github/workflows.");
6228
6956
  }
@@ -6652,12 +7380,12 @@ ${bundleAuditSteps}${platforms.includes("android") ? androidJob({ customSecrets,
6652
7380
  var writeAbsoluteMobileGithubWorkflow = async (options) => {
6653
7381
  const path = workflowOutputPath(options.projectRoot, options.outputPath);
6654
7382
  const generated = createAbsoluteMobileGithubWorkflow(options);
6655
- const previous = await exists3(path) ? await readFile15(path, "utf8") : undefined;
7383
+ const previous = await exists4(path) ? await readFile16(path, "utf8") : undefined;
6656
7384
  if (previous !== undefined && previous !== generated.workflow && !options.force)
6657
- throw new TypeError(`${relative10(options.projectRoot, path)} already exists and differs. Rerun with --force to replace the generated workflow.`);
7385
+ throw new TypeError(`${relative11(options.projectRoot, path)} already exists and differs. Rerun with --force to replace the generated workflow.`);
6658
7386
  if (previous !== generated.workflow) {
6659
- await mkdir11(dirname10(path), { recursive: true });
6660
- await writeFile12(path, generated.workflow);
7387
+ await mkdir12(dirname11(path), { recursive: true });
7388
+ await writeFile13(path, generated.workflow);
6661
7389
  }
6662
7390
  return {
6663
7391
  changed: previous !== generated.workflow,
@@ -6817,7 +7545,7 @@ init_telemetryEvent();
6817
7545
  import { Elysia as Elysia3 } from "elysia";
6818
7546
  var ABSOLUTE_MOBILE_PREVIEW_PATH = "/__absolute/mobile-preview";
6819
7547
  var escapeHtml2 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
6820
- var isRecord8 = (value) => typeof value === "object" && value !== null;
7548
+ var isRecord9 = (value) => typeof value === "object" && value !== null;
6821
7549
  var normalizeEntry2 = (entry) => {
6822
7550
  const parsed = new URL(entry ?? "/", "https://absolute.invalid");
6823
7551
  return `${parsed.pathname}${parsed.search}${parsed.hash}`;
@@ -6868,7 +7596,7 @@ var createAbsoluteMobilePreviewPlugin = (mobile) => {
6868
7596
  "X-Robots-Tag": "noindex, nofollow"
6869
7597
  }
6870
7598
  })).post("/__absolute/mobile-preview-telemetry", ({ body, status }) => {
6871
- const value = isRecord8(body) ? body : undefined;
7599
+ const value = isRecord9(body) ? body : undefined;
6872
7600
  const durationMs = value?.durationMs;
6873
7601
  const platform2 = value?.platform;
6874
7602
  if (typeof durationMs !== "number" || !Number.isFinite(durationMs) || durationMs < 0 || durationMs > 300000 || platform2 !== "android" && platform2 !== "ios") {
@@ -6884,20 +7612,20 @@ var createAbsoluteMobilePreviewPlugin = (mobile) => {
6884
7612
  });
6885
7613
  };
6886
7614
  // src/mobile/nativeDeepLinks.ts
6887
- import { readFile as readFile16, rename as rename11, writeFile as writeFile13 } from "fs/promises";
6888
- import { join as join17 } from "path";
7615
+ import { readFile as readFile17, rename as rename12, writeFile as writeFile14 } from "fs/promises";
7616
+ import { join as join18 } from "path";
6889
7617
  var START_MARKER = "<!-- absolutejs:deep-links:start -->";
6890
7618
  var END_MARKER = "<!-- absolutejs:deep-links:end -->";
6891
7619
  var IOS_ENTITLEMENTS = "App/AbsoluteJS.entitlements";
6892
7620
  var NOT_FOUND = -1;
6893
7621
  var escapeXml = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
6894
7622
  var writeChangedFile = async (path, source) => {
6895
- const current = await readFile16(path, "utf8");
7623
+ const current = await readFile17(path, "utf8");
6896
7624
  if (current === source)
6897
7625
  return false;
6898
7626
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
6899
- await writeFile13(temporary, source, { flag: "wx" });
6900
- await rename11(temporary, path);
7627
+ await writeFile14(temporary, source, { flag: "wx" });
7628
+ await rename12(temporary, path);
6901
7629
  return true;
6902
7630
  };
6903
7631
  var replaceManagedRegion = (source, region, insertAt) => {
@@ -6942,8 +7670,8 @@ ${hosts}
6942
7670
  `;
6943
7671
  };
6944
7672
  var configureAndroid = async (config) => {
6945
- const path = join17(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
6946
- const source = await readFile16(path, "utf8");
7673
+ const path = join18(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
7674
+ const source = await readFile17(path, "utf8");
6947
7675
  const mainActivity = source.indexOf('android:name=".MainActivity"');
6948
7676
  if (mainActivity === NOT_FOUND) {
6949
7677
  throw new TypeError("Android MainActivity was not found.");
@@ -6968,8 +7696,8 @@ var iosSchemeRegion = (scheme) => ` ${START_MARKER}
6968
7696
  ${END_MARKER}
6969
7697
  `;
6970
7698
  var configureIosInfo = async (config) => {
6971
- const path = join17(config.nativeProjectDirectory, "ios/App/App/Info.plist");
6972
- const source = await readFile16(path, "utf8");
7699
+ const path = join18(config.nativeProjectDirectory, "ios/App/App/Info.plist");
7700
+ const source = await readFile17(path, "utf8");
6973
7701
  const region = config.deepLinkScheme ? iosSchemeRegion(config.deepLinkScheme) : ` ${START_MARKER}
6974
7702
  ${END_MARKER}
6975
7703
  `;
@@ -6992,10 +7720,10 @@ ${domains}
6992
7720
  `;
6993
7721
  };
6994
7722
  var configureIosEntitlements = async (config) => {
6995
- const path = join17(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
7723
+ const path = join18(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
6996
7724
  let current = "";
6997
7725
  try {
6998
- current = await readFile16(path, "utf8");
7726
+ current = await readFile17(path, "utf8");
6999
7727
  } catch (error) {
7000
7728
  if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
7001
7729
  throw error;
@@ -7005,13 +7733,13 @@ var configureIosEntitlements = async (config) => {
7005
7733
  if (current === source)
7006
7734
  return false;
7007
7735
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
7008
- await writeFile13(temporary, source, { flag: "wx" });
7009
- await rename11(temporary, path);
7736
+ await writeFile14(temporary, source, { flag: "wx" });
7737
+ await rename12(temporary, path);
7010
7738
  return true;
7011
7739
  };
7012
7740
  var configureIosProject = async (config) => {
7013
- const path = join17(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
7014
- const source = await readFile16(path, "utf8");
7741
+ const path = join18(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
7742
+ const source = await readFile17(path, "utf8");
7015
7743
  const declarations = [
7016
7744
  ...source.matchAll(/CODE_SIGN_ENTITLEMENTS = ([^;]+);/g)
7017
7745
  ].map((match) => match[1]);
@@ -7050,8 +7778,8 @@ var applyAbsoluteNativeDeepLinks = async (config, platforms = config.platforms)
7050
7778
  };
7051
7779
  // src/mobile/nativeDeviceCapabilities.ts
7052
7780
  init_deviceCapabilities();
7053
- import { readFile as readFile17, rename as rename12, writeFile as writeFile14 } from "fs/promises";
7054
- import { join as join18 } from "path";
7781
+ import { readFile as readFile18, rename as rename13, writeFile as writeFile15 } from "fs/promises";
7782
+ import { join as join19 } from "path";
7055
7783
  var START_MARKER2 = "<!-- absolutejs:device-capabilities:start -->";
7056
7784
  var END_MARKER2 = "<!-- absolutejs:device-capabilities:end -->";
7057
7785
  var NOT_FOUND2 = -1;
@@ -7061,12 +7789,12 @@ var PUSH_START_MARKER = "absolutejs:push-notifications:start";
7061
7789
  var PUSH_END_MARKER = "absolutejs:push-notifications:end";
7062
7790
  var escapeXml2 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
7063
7791
  var writeChangedFile2 = async (path, source) => {
7064
- const current = await readFile17(path, "utf8");
7792
+ const current = await readFile18(path, "utf8");
7065
7793
  if (current === source)
7066
7794
  return false;
7067
7795
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
7068
- await writeFile14(temporary, source, { flag: "wx" });
7069
- await rename12(temporary, path);
7796
+ await writeFile15(temporary, source, { flag: "wx" });
7797
+ await rename13(temporary, path);
7070
7798
  return true;
7071
7799
  };
7072
7800
  var writeOptionalChangedFile = async (path, source) => {
@@ -7074,14 +7802,14 @@ var writeOptionalChangedFile = async (path, source) => {
7074
7802
  if (current === source)
7075
7803
  return false;
7076
7804
  if (current === null) {
7077
- await writeFile14(path, source, { flag: "wx" });
7805
+ await writeFile15(path, source, { flag: "wx" });
7078
7806
  return true;
7079
7807
  }
7080
7808
  return writeChangedFile2(path, source);
7081
7809
  };
7082
7810
  var optionalSource = async (path) => {
7083
7811
  try {
7084
- return await readFile17(path, "utf8");
7812
+ return await readFile18(path, "utf8");
7085
7813
  } catch (error) {
7086
7814
  if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
7087
7815
  return null;
@@ -7195,14 +7923,14 @@ var writeIosPrivacyManifest = async (path, current, source) => {
7195
7923
  return false;
7196
7924
  if (current !== null)
7197
7925
  return writeChangedFile2(path, source);
7198
- await writeFile14(path, source, { flag: "wx" });
7926
+ await writeFile15(path, source, { flag: "wx" });
7199
7927
  return true;
7200
7928
  };
7201
7929
  var configureIosPrivacyProject = async (config, requirements) => {
7202
7930
  if (requirements.iosPrivacyAccessedApis.length === 0)
7203
7931
  return false;
7204
- const projectPath2 = join18(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
7205
- const project = await readFile17(projectPath2, "utf8");
7932
+ const projectPath2 = join19(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
7933
+ const project = await readFile18(projectPath2, "utf8");
7206
7934
  return writeChangedFile2(projectPath2, addIosPrivacyProjectReference(project));
7207
7935
  };
7208
7936
  var addIosPrivacyProjectReference = (source) => {
@@ -7254,8 +7982,8 @@ ${next.slice(index)}`;
7254
7982
  return next;
7255
7983
  };
7256
7984
  var configureIos2 = async (config, plan) => {
7257
- const path = join18(config.nativeProjectDirectory, "ios/App/App/Info.plist");
7258
- const source = await readFile17(path, "utf8");
7985
+ const path = join19(config.nativeProjectDirectory, "ios/App/App/Info.plist");
7986
+ const source = await readFile18(path, "utf8");
7259
7987
  const requirements = absoluteDeviceNativeRequirements(plan);
7260
7988
  const existingSystemBars = source.match(/<key>UIViewControllerBasedStatusBarAppearance<\/key>\s*<(true|false)\s*\/>/u);
7261
7989
  const ownedStart = source.indexOf(START_MARKER2);
@@ -7275,7 +8003,7 @@ ${content}
7275
8003
  ${END_MARKER2}
7276
8004
  ` : "";
7277
8005
  const infoChanged = await writeChangedFile2(path, managed(source, region, source.lastIndexOf("</dict>")));
7278
- const privacyPath = join18(config.nativeProjectDirectory, "ios/App/App/PrivacyInfo.xcprivacy");
8006
+ const privacyPath = join19(config.nativeProjectDirectory, "ios/App/App/PrivacyInfo.xcprivacy");
7279
8007
  const privacyCurrent = await optionalSource(privacyPath);
7280
8008
  const privacySource = privacyManifestSource(privacyCurrent, requirements);
7281
8009
  const [privacyChanged, projectChanged, pushChanged] = await Promise.all([
@@ -7286,7 +8014,7 @@ ${content}
7286
8014
  return infoChanged || privacyChanged || projectChanged || pushChanged;
7287
8015
  };
7288
8016
  var configureIosPushNotifications = async (config, enabled) => {
7289
- const entitlementsPath = join18(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
8017
+ const entitlementsPath = join19(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
7290
8018
  const entitlements = await optionalSource(entitlementsPath);
7291
8019
  if (entitlements === null && !enabled)
7292
8020
  return false;
@@ -7298,7 +8026,7 @@ var configureIosPushNotifications = async (config, enabled) => {
7298
8026
  <!-- ${PUSH_END_MARKER} -->
7299
8027
  ` : "";
7300
8028
  const nextEntitlements = replacePushRegion(entitlements, entitlementRegion, entitlements.lastIndexOf("</dict>"));
7301
- const delegatePath = join18(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
8029
+ const delegatePath = join19(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
7302
8030
  const delegate = await optionalSource(delegatePath);
7303
8031
  if (delegate === null && !enabled)
7304
8032
  return false;
@@ -7347,8 +8075,8 @@ var replacePushRegion = (source, region, insertion) => {
7347
8075
  return `${source.slice(0, insertion)}${region}${source.slice(insertion)}`;
7348
8076
  };
7349
8077
  var configureAndroid2 = async (config, plan) => {
7350
- const path = join18(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
7351
- const source = await readFile17(path, "utf8");
8078
+ const path = join19(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
8079
+ const source = await readFile18(path, "utf8");
7352
8080
  const permissions = absoluteDeviceNativeRequirements(plan).androidPermissions;
7353
8081
  const content = permissions.map((permission) => ` <uses-permission android:name="${escapeXml2(permission)}" />`).join(`
7354
8082
  `);
@@ -7383,7 +8111,7 @@ ${content}
7383
8111
  throw new TypeError(`Android google-services.json does not contain package ${config.appId}.`);
7384
8112
  const [manifestChanged, firebaseChanged] = await Promise.all([
7385
8113
  writeChangedFile2(path, nextManifest),
7386
- writeOptionalChangedFile(join18(config.nativeProjectDirectory, "android/app/google-services.json"), firebaseSource)
8114
+ writeOptionalChangedFile(join19(config.nativeProjectDirectory, "android/app/google-services.json"), firebaseSource)
7387
8115
  ]);
7388
8116
  return manifestChanged || firebaseChanged;
7389
8117
  };
@@ -7397,8 +8125,8 @@ var applyAbsoluteNativeDeviceCapabilities = async (projectRoot, config, platform
7397
8125
  };
7398
8126
  };
7399
8127
  // src/mobile/releasePublisher.ts
7400
- import { access as access10 } from "fs/promises";
7401
- import { isAbsolute as isAbsolute6, relative as relative11, resolve as resolve14, sep as sep7 } from "path";
8128
+ import { access as access11 } from "fs/promises";
8129
+ import { isAbsolute as isAbsolute6, relative as relative12, resolve as resolve15, sep as sep7 } from "path";
7402
8130
  import { pathToFileURL as pathToFileURL3 } from "url";
7403
8131
  var prepareAbsoluteIosRelease = async (publisher, options) => {
7404
8132
  if (typeof publisher.prepareIosRelease !== "function") {
@@ -7421,12 +8149,12 @@ var prepareAbsoluteAndroidRelease = async (publisher, options) => {
7421
8149
  }
7422
8150
  return versionCode;
7423
8151
  };
7424
- var isRecord9 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
7425
- var isPublisher = (value) => isRecord9(value) && typeof value.publish === "function";
8152
+ var isRecord10 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
8153
+ var isPublisher = (value) => isRecord10(value) && typeof value.publish === "function";
7426
8154
  var publisherModulePath = (projectRoot, requested) => {
7427
- const root = resolve14(projectRoot);
7428
- const path = resolve14(root, requested);
7429
- const projectRelative = relative11(root, path);
8155
+ const root = resolve15(projectRoot);
8156
+ const path = resolve15(root, requested);
8157
+ const projectRelative = relative12(root, path);
7430
8158
  if (projectRelative === ".." || projectRelative.startsWith(`..${sep7}`) || isAbsolute6(projectRelative)) {
7431
8159
  throw new TypeError("mobile publish --registry must remain inside the project.");
7432
8160
  }
@@ -7434,11 +8162,11 @@ var publisherModulePath = (projectRoot, requested) => {
7434
8162
  };
7435
8163
  var loadAbsoluteNativeReleasePublisher = async (projectRoot, requestedModulePath) => {
7436
8164
  const modulePath = publisherModulePath(projectRoot, requestedModulePath);
7437
- await access10(modulePath).catch(() => {
8165
+ await access11(modulePath).catch(() => {
7438
8166
  throw new TypeError(`Native release registry module does not exist: ${modulePath}`);
7439
8167
  });
7440
8168
  const loaded = await import(pathToFileURL3(modulePath).href);
7441
- const publisher = isRecord9(loaded) ? loaded.default ?? loaded.registry : undefined;
8169
+ const publisher = isRecord10(loaded) ? loaded.default ?? loaded.registry : undefined;
7442
8170
  if (!isPublisher(publisher)) {
7443
8171
  throw new TypeError("Native release registry module must default-export a registry with publish(options).");
7444
8172
  }
@@ -7496,7 +8224,7 @@ var publishAbsoluteIosRelease = async (options) => {
7496
8224
  };
7497
8225
  // src/mobile/routeMetadataTransform.ts
7498
8226
  import { existsSync as existsSync6, readFileSync as readFileSync7 } from "fs";
7499
- import { dirname as dirname12, extname as extname5, relative as relative12, resolve as resolve15 } from "path";
8227
+ import { dirname as dirname13, extname as extname5, relative as relative13, resolve as resolve16 } from "path";
7500
8228
  import ts2 from "typescript";
7501
8229
  var ROUTE_METHODS = new Set(["get", "head"]);
7502
8230
  var SOURCE_FILTER = /\.[cm]?[jt]sx?$/;
@@ -7547,7 +8275,7 @@ var PAGE_HANDLERS = new Map([
7547
8275
  ]
7548
8276
  ]);
7549
8277
  var posixPath = (value) => value.replace(/\\/g, "/");
7550
- var findTsconfig = (entry, projectRoot) => ts2.findConfigFile(dirname12(entry), existsSync6, "tsconfig.json") ?? ts2.findConfigFile(projectRoot, existsSync6, "tsconfig.json");
8278
+ var findTsconfig = (entry, projectRoot) => ts2.findConfigFile(dirname13(entry), existsSync6, "tsconfig.json") ?? ts2.findConfigFile(projectRoot, existsSync6, "tsconfig.json");
7551
8279
  var createProgram = (entry, projectRoot) => {
7552
8280
  const configPath2 = findTsconfig(entry, projectRoot);
7553
8281
  if (!configPath2) {
@@ -7559,7 +8287,7 @@ var createProgram = (entry, projectRoot) => {
7559
8287
  target: ts2.ScriptTarget.ESNext
7560
8288
  });
7561
8289
  }
7562
- const parsed = ts2.parseJsonConfigFileContent(ts2.readConfigFile(configPath2, (path) => readFileSync7(path, "utf8")).config, ts2.sys, dirname12(configPath2));
8290
+ const parsed = ts2.parseJsonConfigFileContent(ts2.readConfigFile(configPath2, (path) => readFileSync7(path, "utf8")).config, ts2.sys, dirname13(configPath2));
7563
8291
  if (!parsed.fileNames.includes(entry))
7564
8292
  parsed.fileNames.push(entry);
7565
8293
  return ts2.createProgram(parsed.fileNames, parsed.options);
@@ -7665,7 +8393,7 @@ var resolvePageIdentity = (expression, sourceFile, checker, projectRoot) => {
7665
8393
  const declaration = symbol?.declarations?.[0];
7666
8394
  const file = declaration?.getSourceFile().fileName ?? sourceFile.fileName;
7667
8395
  const exportedName = symbol?.name ?? expression.getText(sourceFile);
7668
- const source = posixPath(relative12(projectRoot, file));
8396
+ const source = posixPath(relative13(projectRoot, file));
7669
8397
  return `${source}#${exportedName}`;
7670
8398
  };
7671
8399
  var resolveAlias = (symbol, checker) => {
@@ -7904,7 +8632,7 @@ var analyzeProgram = (program, projectRoot) => {
7904
8632
  const checker = program.getTypeChecker();
7905
8633
  const analyzed = new Map;
7906
8634
  for (const sourceFile of program.getSourceFiles()) {
7907
- const resolvedFile = resolve15(sourceFile.fileName);
8635
+ const resolvedFile = resolve16(sourceFile.fileName);
7908
8636
  if (!isProjectSource(sourceFile, resolvedFile, projectRoot))
7909
8637
  continue;
7910
8638
  const analysis = analyzeSourceFile(sourceFile, checker, projectRoot);
@@ -7997,14 +8725,14 @@ var transformFile = (source, fileName, analysis) => {
7997
8725
  };
7998
8726
  var ABSOLUTE_MOBILE_TRANSFORM_PROTOCOL = ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION;
7999
8727
  var createAbsoluteMobileRouteMetadataPlugin = (options) => {
8000
- const projectRoot = resolve15(options.projectRoot ?? process.cwd());
8001
- const entry = resolve15(options.entry);
8728
+ const projectRoot = resolve16(options.projectRoot ?? process.cwd());
8729
+ const entry = resolve16(options.entry);
8002
8730
  const analyzed = analyzeProgram(createProgram(entry, projectRoot), projectRoot);
8003
8731
  return {
8004
8732
  name: "absolute-mobile-route-metadata",
8005
8733
  setup(build) {
8006
8734
  build.onLoad({ filter: SOURCE_FILTER }, async ({ path }) => {
8007
- const analysis = analyzed.get(resolve15(path));
8735
+ const analysis = analyzed.get(resolve16(path));
8008
8736
  if (!analysis)
8009
8737
  return;
8010
8738
  const source = await Bun.file(path).text();
@@ -8017,11 +8745,11 @@ var createAbsoluteMobileRouteMetadataPlugin = (options) => {
8017
8745
  };
8018
8746
  };
8019
8747
  var inspectAbsoluteMobileRouteMetadata = (options) => {
8020
- const projectRoot = resolve15(options.projectRoot ?? process.cwd());
8021
- const entry = resolve15(options.entry);
8748
+ const projectRoot = resolve16(options.projectRoot ?? process.cwd());
8749
+ const entry = resolve16(options.entry);
8022
8750
  const analyzed = analyzeProgram(createProgram(entry, projectRoot), projectRoot);
8023
8751
  return [...analyzed.entries()].flatMap(([file, analysis]) => [...analysis.byRouteCall.values()].map(({ metadata }) => ({
8024
- file: posixPath(relative12(projectRoot, file)),
8752
+ file: posixPath(relative13(projectRoot, file)),
8025
8753
  metadata
8026
8754
  })));
8027
8755
  };
@@ -8230,6 +8958,7 @@ var http = createAbsoluteHttpClient();
8230
8958
  var installAbsoluteMobileShellHttp = (origin, fetch2 = globalThis.fetch) => installAbsoluteHttpTransport(createAbsoluteHttpTransport({ fetch: fetch2, origin, runtime: "native" }));
8231
8959
  export {
8232
8960
  writeAbsoluteMobileGithubWorkflow,
8961
+ writeAbsoluteExpoProject,
8233
8962
  writeAbsoluteCapacitorConfig,
8234
8963
  waitForAbsoluteIosHmrLog,
8235
8964
  verifyAbsoluteMobileCompatibilityProducer,
@@ -8238,6 +8967,7 @@ export {
8238
8967
  validateAbsoluteRemoteMacProfileName,
8239
8968
  testAbsoluteIosPhysicalDevice,
8240
8969
  syncAbsoluteRemoteMacProject,
8970
+ syncAbsoluteExpoWebAssets,
8241
8971
  startAbsoluteRemoteIosDevSession,
8242
8972
  startAbsoluteIosTcpRelay,
8243
8973
  startAbsoluteIosDevSession,
@@ -8273,6 +9003,7 @@ export {
8273
9003
  parseAbsoluteMobileBuildPageMetadata,
8274
9004
  parseAbsoluteIosLogLine,
8275
9005
  parseAbsoluteIosHmrLog,
9006
+ parseAbsoluteExpoBridgeMessage,
8276
9007
  parseAbsoluteAndroidInstalledApp,
8277
9008
  pairAbsoluteRemoteMac,
8278
9009
  normalizeAbsoluteMobileConfig,
@@ -8327,6 +9058,8 @@ export {
8327
9058
  createAbsoluteMobileAssociationPlugin,
8328
9059
  createAbsoluteMobileAssociationDocuments,
8329
9060
  createAbsoluteIosNativeWatcher,
9061
+ createAbsoluteExpoBridgeResponse,
9062
+ createAbsoluteExpoBridgeError,
8330
9063
  carryForwardAbsoluteMobileCompatibilityReleases,
8331
9064
  captureAbsoluteRemoteMacCommand,
8332
9065
  captureAbsoluteMobileRouteGraph,
@@ -8364,9 +9097,12 @@ export {
8364
9097
  ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT,
8365
9098
  ABSOLUTE_IOS_SIMULATOR_NAME,
8366
9099
  ABSOLUTE_IOS_RELEASE_FORMAT,
9100
+ ABSOLUTE_EXPO_BRIDGE_METHODS,
9101
+ ABSOLUTE_EXPO_BRIDGE_MAX_BYTES,
9102
+ ABSOLUTE_EXPO_BRIDGE_FORMAT,
8367
9103
  ABSOLUTE_AUTH_PACKAGE,
8368
9104
  ABSOLUTE_ANDROID_RELEASE_FORMAT
8369
9105
  };
8370
9106
 
8371
- //# debugId=1C9BEF9E4B461BFB64756E2164756E21
9107
+ //# debugId=81AD994206FFE2B464756E2164756E21
8372
9108
  //# sourceMappingURL=index.js.map