@craft-native/android 0.0.224 → 0.0.226

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.
package/README.md CHANGED
@@ -265,6 +265,12 @@ rejected before a project is generated.
265
265
  before parameters), a tab's path selecting its tab, a path no screen draws
266
266
  opening the site in a Custom Tab; deep links and tapped notifications route
267
267
  the same way;
268
+ - `craft.navigation.replace('/m/claim?kind=club&slug=rappid')` finishes a flow
269
+ atomically: the host resolves the path and query, closes the current screen,
270
+ and opens or reuses the destination. Android and iOS also keep supporting
271
+ `replace(screenName, params)` for screens without URL routes. A closing isolate
272
+ cannot send a second navigation message, so use this operation when completing
273
+ sign-in instead of sending `popToRoot()` followed by `open(path)`;
268
274
  - the app script (`nativeApp`) in its own isolate for the app's life, and in a
269
275
  WorkManager job for `backgroundRefresh` and silent pushes
270
276
  (`craftBackgroundRefresh`, `craftSilentPush`, answered with
@@ -272,6 +278,7 @@ rejected before a project is generated.
272
278
  the app script may open a matched path through `craft.navigation.open`, but
273
279
  has no native stack of its own, so `push`, `replace`, `back`, and
274
280
  `popToRoot` are no-ops;
281
+
275
282
  - `craft.storage.getSync`, `craft.secureStorage.getSync` and `craft.snapshots`:
276
283
  JavaScriptSandbox cannot call the host synchronously, so every isolate starts
277
284
  with a copy of these stores and the host keeps each copy current. Timers and
@@ -898,6 +905,19 @@ project is removed after execution. This is also how Craft and consuming apps
898
905
  execute the native input reconciler regressions, without assuming a particular
899
906
  Gradle cache layout.
900
907
 
908
+ ## Native image caching
909
+
910
+ Native `Image` keeps anonymous responses marked `public` with a positive
911
+ `Cache-Control: max-age` in a bounded disk cache, so photos remain available
912
+ after an offline app restart. The cache holds at most 32 MiB of encoded pixels
913
+ and 128 entries, with an 8 MiB limit per image. Freshness and pixels are written
914
+ atomically; failed cache writes do not fail a successful image download.
915
+
916
+ Requests with custom headers, cookie responses, `no-store`, `no-cache`, and
917
+ account-dependent variants are not persisted. Expired entries are fetched
918
+ again. Image pixels honor authored rounded corners without requiring a
919
+ container to hide overflow.
920
+
901
921
  ## License
902
922
 
903
923
  MIT
package/dist/index.js CHANGED
@@ -549,6 +549,7 @@ function writeNativeShellSources(output, packagePath, packageName, config) {
549
549
  };
550
550
  write("CraftBluetooth.kt.template", "CraftBluetooth.kt");
551
551
  write("CraftNativeShell.kt.template", "CraftNativeShell.kt");
552
+ write("CraftNativeImageCache.kt.template", "CraftNativeImageCache.kt");
552
553
  write("CraftNativeBackground.kt.template", "CraftNativeBackground.kt");
553
554
  write(config.enableHealthConnect ? "CraftNativeHealth.kt.template" : "CraftNativeHealthStub.kt.template", "CraftNativeHealth.kt");
554
555
  write("CraftNativeServices.kt.template", "CraftNativeServices.kt", (source) => source.replace("{{NATIVE_PUSH_IMPORTS}}", config.enablePushNotifications ? "import com.google.firebase.messaging.FirebaseMessaging" : "").replace("{{NATIVE_PUSH_REGISTER}}", config.enablePushNotifications ? ` try {
@@ -1080,4 +1081,4 @@ export {
1080
1081
  ANDROID_MIN_SDK
1081
1082
  };
1082
1083
 
1083
- //# debugId=D95CCF804E0EBBCF64756E2164756E21
1084
+ //# debugId=E085D9D53817707064756E2164756E21
package/dist/index.js.map CHANGED
@@ -2,11 +2,11 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts", "../src/promise-runtime.ts", "../src/testing.ts"],
4
4
  "sourcesContent": [
5
- "/**\n * Craft Android Builder\n *\n * Generates native Android apps from web content using WebView.\n */\n\nimport { cpSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs'\nimport { dirname, extname, isAbsolute, join, relative, resolve, sep } from 'node:path'\nimport { $ } from 'bun'\nimport { renderAndroidPromiseRuntime } from './promise-runtime'\n\nexport { runKotlinFixture } from './testing.js'\n\nconst TEMPLATES_DIR = join(dirname(import.meta.dir), 'templates')\n\n/**\n * This builder's version, which the crash reporter names as its SDK version.\n * Walks up from here because a bundled build puts this module below the\n * package root.\n */\nfunction resolveCraftAndroidVersion(startDir: string = import.meta.dir): string {\n let directory = resolve(startDir)\n while (true) {\n try {\n const pkg = JSON.parse(readFileSync(join(directory, 'package.json'), 'utf8')) as { name?: unknown, version?: unknown }\n if ((pkg.name === '@craft-native/android' || pkg.name === 'craft-native') && typeof pkg.version === 'string' && pkg.version)\n return pkg.version\n }\n catch {\n // Keep walking.\n }\n const parent = dirname(directory)\n if (parent === directory) return '0.0.0'\n directory = parent\n }\n}\n\nconst CRAFT_ANDROID_VERSION = resolveCraftAndroidVersion()\nconst LOCAL_DEVELOPMENT_HOSTS = new Set(['localhost', '127.0.0.1', '10.0.2.2'])\nconst KOTLIN_KEYWORDS = new Set([\n 'as',\n 'break',\n 'class',\n 'continue',\n 'do',\n 'else',\n 'false',\n 'for',\n 'fun',\n 'if',\n 'in',\n 'interface',\n 'is',\n 'null',\n 'object',\n 'package',\n 'return',\n 'super',\n 'this',\n 'throw',\n 'true',\n 'try',\n 'typealias',\n 'typeof',\n 'val',\n 'var',\n 'when',\n 'while',\n])\n\n/** Oldest Android API level supported by the generated Kotlin runtime. */\nexport const ANDROID_MIN_SDK = 26\n\nexport interface CraftAndroidConfig {\n appName: string\n packageName: string\n renderer?: 'web' | 'native'\n /** \"system\" follows the device; light/dark pin the generated host. */\n appearance?: 'light' | 'dark' | 'system'\n version?: string\n versionCode?: number\n darkMode?: boolean\n backgroundColor?: string\n backgroundColorDark?: string\n enableSpeechRecognition?: boolean\n enableHaptics?: boolean\n enableShare?: boolean\n enableCamera?: boolean\n enableBiometric?: boolean\n enablePushNotifications?: boolean\n enableSecureStorage?: boolean\n enableGeolocation?: boolean\n enableBackgroundLocation?: boolean\n enableBluetooth?: boolean\n enableHealthConnect?: boolean\n /**\n * The Health Connect data the app reads and writes, by the shared type\n * names (`steps`, `sleep`, `workouts`...). The manifest declares only\n * these, as Play's health permissions review requires; without it, the\n * original set (steps, heart rate, energy, distance, workouts).\n */\n healthConnect?: {\n read?: HealthConnectType[]\n write?: HealthConnectType[]\n /** Read while the app is in the background (a WorkManager refresh): Android 15's READ_HEALTH_DATA_IN_BACKGROUND. */\n background?: boolean\n /** Read further back than the 30 days before the first grant: READ_HEALTH_DATA_HISTORY. */\n history?: boolean\n }\n enableHealthConnectBackground?: boolean\n enableHealthConnectHistory?: boolean\n /** The app's privacy policy, which Health Connect links to from its permission screen. */\n privacyPolicyURL?: string\n enableKeepAwake?: boolean\n enableDeepLinks?: boolean\n /** Reach the person's own devices on the local network over plain HTTP (a Mac companion). */\n enableLocalNetwork?: boolean\n urlSchemes?: string[]\n /** HTTPS hosts owned by the app; path rules are ordered, with `*` matching any path text. */\n /** Ordered path rules: * matches zero or more characters, ? one, and ?* one or more. */\n appLinks?: Array<{ host: string, paths?: Array<{ pattern: string, exclude?: boolean }> }>\n trustedOrigins?: string[]\n appIconPath?: string\n googleServicesFile?: string\n /** Google Maps Android SDK key used by native `Map` screens. */\n mapsApiKey?: string\n devServerURL?: string\n hasBundledFallback?: boolean\n /** Android API level, at least ANDROID_MIN_SDK (26). */\n minSdk?: number\n compileSdk?: number\n targetSdk?: number\n /**\n * Native renderer: the app's paths and the screen drawing each\n * (`/m/workout/:id` → `Workout`), its tabs, and the bundle route of the\n * script that runs beside the screens. The same keys as the iOS builder.\n */\n nativeScreens?: Record<string, string>\n tabs?: Array<{ id: string, title: string, symbol?: string }>\n nativeApp?: string\n /** Wake the app script every so often (`craftBackgroundRefresh`), through WorkManager. */\n backgroundRefresh?: { enabled?: boolean, minimumIntervalMinutes?: number }\n /**\n * Crash and error reporting to bughq (CraftCrashReporter.kt): uncaught JVM\n * exceptions, ANRs and native crashes (ApplicationExitInfo), uncaught\n * JavaScript errors, and what the page sends through `craft.crashReporter`.\n * Queued on disk and sent when online. Release builds are minified: upload\n * `app/build/outputs/mapping/release/mapping.txt` so bughq can retrace.\n */\n crashReporting?: {\n /** The bughq project's public ingest key. */\n key: string\n /** Default https://bughq.org. */\n host?: string\n /** Default `production`. */\n environment?: string\n /** Default `<versionName>+<versionCode>`, e.g. `1.0.0+41`. */\n release?: string\n /** One session per launch, for crash-free sessions. Default on. */\n sessions?: boolean\n }\n}\n\n/** The shared health data types (the iOS builder's HealthKit names). */\nexport type HealthConnectType = 'steps' | 'heartRate' | 'activeEnergy' | 'distance' | 'workouts' | 'restingHeartRate'\n | 'heartRateVariability' | 'bodyMass' | 'sleep' | 'mindful' | 'dietaryEnergy' | 'dietaryProtein'\n | 'dietaryCarbohydrates' | 'dietaryFat'\n\n/**\n * The Health Connect permissions behind each type, for reading and for\n * writing. CraftNativeHealth.kt asks for the same ones; a permission the\n * manifest does not declare is refused without a prompt.\n */\nexport const HEALTH_CONNECT_PERMISSIONS: Record<HealthConnectType, { read: string[], write: string[] }> = {\n steps: { read: ['READ_STEPS'], write: [] },\n heartRate: { read: ['READ_HEART_RATE'], write: [] },\n activeEnergy: { read: ['READ_ACTIVE_CALORIES_BURNED'], write: ['WRITE_ACTIVE_CALORIES_BURNED'] },\n distance: { read: ['READ_DISTANCE'], write: ['WRITE_DISTANCE'] },\n workouts: {\n read: ['READ_EXERCISE', 'READ_HEART_RATE', 'READ_ACTIVE_CALORIES_BURNED', 'READ_DISTANCE'],\n write: ['WRITE_EXERCISE', 'WRITE_ACTIVE_CALORIES_BURNED', 'WRITE_DISTANCE'],\n },\n restingHeartRate: { read: ['READ_RESTING_HEART_RATE'], write: [] },\n heartRateVariability: { read: ['READ_HEART_RATE_VARIABILITY'], write: [] },\n bodyMass: { read: ['READ_WEIGHT'], write: [] },\n sleep: { read: ['READ_SLEEP'], write: [] },\n mindful: { read: ['READ_MINDFULNESS'], write: ['WRITE_MINDFULNESS'] },\n dietaryEnergy: { read: ['READ_NUTRITION'], write: ['WRITE_NUTRITION'] },\n dietaryProtein: { read: ['READ_NUTRITION'], write: ['WRITE_NUTRITION'] },\n dietaryCarbohydrates: { read: ['READ_NUTRITION'], write: ['WRITE_NUTRITION'] },\n dietaryFat: { read: ['READ_NUTRITION'], write: ['WRITE_NUTRITION'] },\n}\n\nconst DEFAULT_HEALTH_CONNECT = { read: ['steps', 'heartRate', 'activeEnergy', 'distance', 'workouts'], write: ['workouts'] } as const\n\n/** Every `android.permission.health.*` the app's Health Connect types need. */\nexport function healthConnectPermissions(config: Pick<CraftAndroidConfig, 'healthConnect'>): string[] {\n const read = config.healthConnect?.read ?? DEFAULT_HEALTH_CONNECT.read\n const write = config.healthConnect?.write ?? (config.healthConnect ? [] : DEFAULT_HEALTH_CONNECT.write)\n const permissions = new Set<string>()\n for (const type of read) {\n const known = HEALTH_CONNECT_PERMISSIONS[type as HealthConnectType]\n if (!known) throw new Error(`Unknown Health Connect type: ${type}`)\n known.read.forEach(permission => permissions.add(`android.permission.health.${permission}`))\n }\n for (const type of write) {\n const known = HEALTH_CONNECT_PERMISSIONS[type as HealthConnectType]\n if (!known) throw new Error(`Unknown Health Connect type: ${type}`)\n known.write.forEach(permission => permissions.add(`android.permission.health.${permission}`))\n }\n if (config.healthConnect?.background) permissions.add('android.permission.health.READ_HEALTH_DATA_IN_BACKGROUND')\n if (config.healthConnect?.history) permissions.add('android.permission.health.READ_HEALTH_DATA_HISTORY')\n return [...permissions]\n}\n\nexport interface InitOptions {\n name: string\n packageName?: string\n output: string\n config?: Partial<CraftAndroidConfig>\n /**\n * Where to find the Zig runtime's `<abi>/libcraft.so`.\n *\n * `null` means no runtime regardless of the environment; `undefined` falls\n * through to `CRAFT_ANDROID_RUNTIME`. Same shape as the iOS builder's\n * `runtimeDir`.\n */\n runtimeDir?: string | null\n}\n\nexport interface BuildOptions {\n htmlPath?: string\n nativeBundlePath?: string\n /**\n * Native renderer: the app's icons as vectors (`{ viewBox, strokeWidth,\n * icons: { name: svgBody } }`, keyed by Iconify name and SF Symbol), which\n * `<Icon>`, the tab bar and the bar buttons draw. Without it they fall\n * back to the glyphs Android's fonts have.\n */\n iconsPath?: string\n devServer?: string\n output: string\n release?: boolean\n /**\n * An Android App Bundle (`bundleRelease`, `.aab`), what Google Play takes,\n * rather than an APK. Signed with CRAFT_ANDROID_KEYSTORE,\n * CRAFT_ANDROID_KEYSTORE_PASSWORD, CRAFT_ANDROID_KEY_ALIAS and\n * CRAFT_ANDROID_KEY_PASSWORD when they are set.\n */\n bundle?: boolean\n compile?: boolean\n /** Same meaning as `InitOptions.runtimeDir`; refreshes what init installed. */\n runtimeDir?: string | null\n}\n\nexport interface OpenOptions {\n output: string\n}\n\nexport interface RunOptions {\n device?: string\n output: string\n}\n\nconst DEFAULT_CONFIG: Omit<CraftAndroidConfig, 'appName' | 'packageName'> = {\n renderer: 'web',\n version: '1.0.0',\n versionCode: 1,\n darkMode: true,\n backgroundColor: '#0b1712',\n enableSpeechRecognition: false,\n enableHaptics: false,\n enableShare: false,\n enableCamera: false,\n enableBiometric: false,\n enablePushNotifications: false,\n enableSecureStorage: false,\n enableGeolocation: false,\n enableBackgroundLocation: false,\n enableBluetooth: false,\n enableHealthConnect: false,\n enableKeepAwake: false,\n enableDeepLinks: false,\n urlSchemes: [],\n appLinks: [],\n trustedOrigins: [],\n minSdk: ANDROID_MIN_SDK,\n compileSdk: 36,\n targetSdk: 35,\n}\n\nfunction escapeXml(value: string): string {\n return value\n .replaceAll('&', '&amp;')\n .replaceAll('<', '&lt;')\n .replaceAll('>', '&gt;')\n .replaceAll('\"', '&quot;')\n .replaceAll('\\'', '&apos;')\n}\n\nfunction escapeKotlinString(value: string): string {\n return value\n .replaceAll('\\\\', '\\\\\\\\')\n .replaceAll('\"', '\\\\\"')\n .replaceAll('$', '\\\\$')\n .replaceAll('\\r', '\\\\r')\n .replaceAll('\\n', '\\\\n')\n}\n\nfunction generatedPackageSegment(name: string): string {\n const normalized = name.toLowerCase().replace(/[^a-z0-9_]/g, '')\n if (!normalized) return 'app'\n return /^[a-z_]/.test(normalized) ? normalized : `app${normalized}`\n}\n\nfunction generatedGradleProjectName(name: string): string {\n const invalidCharacters = ['/', '\\\\', ':', '<', '>', '\"', '?', '*', '|']\n const normalized = invalidCharacters.reduce(\n (value, character) => value.replaceAll(character, '-'),\n name,\n ).trim()\n return normalized || 'craft-app'\n}\n\nfunction requireRegularFile(path: string, label: string): void {\n if (!existsSync(path)) throw new Error(`${label} not found: ${path}`)\n if (!statSync(path).isFile()) throw new Error(`${label} must be a file: ${path}`)\n}\n\nfunction containsPath(parent: string, candidate: string): boolean {\n const relativePath = relative(parent, candidate)\n return relativePath === ''\n || (relativePath !== '..' && !relativePath.startsWith(`..${sep}`) && !isAbsolute(relativePath))\n}\n\nfunction validateGoogleServicesFile(path: string, packageName: string): void {\n requireRegularFile(path, 'Google services file')\n\n let document: {\n client?: Array<{\n client_info?: { android_client_info?: { package_name?: string } }\n }>\n }\n try {\n document = JSON.parse(readFileSync(path, 'utf8'))\n }\n catch (error) {\n throw new Error(`Google services file must contain valid JSON: ${path}`, { cause: error })\n }\n\n const matchingClient = document.client?.some((client) => {\n return client.client_info?.android_client_info?.package_name === packageName\n })\n if (!matchingClient) {\n throw new Error(`Google services file has no client for Android package ${packageName}: ${path}`)\n }\n}\n\nfunction androidWebUrl(value: string, field: string): URL {\n let url: URL\n try {\n url = new URL(value)\n }\n catch {\n throw new Error(`Invalid ${field}: ${value}`)\n }\n\n const localDevelopment = url.protocol === 'http:' && LOCAL_DEVELOPMENT_HOSTS.has(url.hostname)\n if ((url.protocol !== 'https:' && !localDevelopment) || url.username || url.password) {\n throw new Error(`${field} must use HTTPS or local HTTP without credentials: ${value}`)\n }\n return url\n}\n\nfunction normalizeAndroidNetworkConfig(config: CraftAndroidConfig): void {\n const schemes = config.urlSchemes?.map(value => value.trim().toLowerCase()) ?? []\n if (schemes.some(value => !/^[a-z][a-z0-9+.-]*$/.test(value))) {\n throw new Error('Android deep-link schemes must be valid URI schemes')\n }\n config.urlSchemes = [...new Set(schemes)]\n config.appLinks = (config.appLinks ?? []).map((link) => {\n const host = link.host.trim().toLowerCase()\n const parsed = new URL(`https://${host}`)\n if (!host || parsed.hostname !== host || parsed.port || parsed.username || parsed.password || parsed.pathname !== '/' || parsed.search || parsed.hash)\n throw new Error('Android App Links need a hostname without a scheme, port or path')\n for (const rule of link.paths ?? []) {\n if (!rule.pattern.startsWith('/') || /[\\x00-\\x1f]/.test(rule.pattern) || (rule.exclude !== undefined && typeof rule.exclude !== 'boolean'))\n throw new Error('Android App Link path rules need an absolute path pattern and a boolean exclude')\n }\n return { ...link, host }\n })\n if (new Set(config.appLinks.map(link => link.host)).size !== config.appLinks.length)\n throw new Error('Android App Link hosts must be unique')\n if (config.enableDeepLinks && config.urlSchemes.length === 0 && config.appLinks.length === 0) {\n throw new Error('Android deep links require at least one URL scheme or an App Link host')\n }\n\n const trustedOrigins = (config.trustedOrigins ?? []).map((value) => {\n return androidWebUrl(value, 'Android trusted origin').origin\n })\n if (config.devServerURL) {\n const devServer = androidWebUrl(config.devServerURL, 'Android dev server URL')\n config.devServerURL = devServer.toString()\n trustedOrigins.push(devServer.origin)\n }\n config.trustedOrigins = [...new Set(trustedOrigins)]\n}\n\nfunction validateAndroidConfig(config: CraftAndroidConfig): void {\n if (!config.appName.trim()) throw new Error('Android app name must not be empty')\n if (config.renderer !== undefined && config.renderer !== 'web' && config.renderer !== 'native') {\n throw new Error(`Unknown Android renderer: ${config.renderer}. Expected web or native.`)\n }\n if (config.appearance !== undefined && !['light', 'dark', 'system'].includes(config.appearance)) {\n throw new Error(`Unknown Android appearance: ${config.appearance}. Expected light, dark, or system.`)\n }\n\n const packageSegments = config.packageName.split('.')\n if (packageSegments.length < 2\n || packageSegments.some(segment => !/^[A-Za-z_][A-Za-z0-9_]*$/.test(segment) || KOTLIN_KEYWORDS.has(segment))) {\n throw new Error(`Invalid Android package name: ${config.packageName}`)\n }\n\n if (!/^#(?:[\\dA-F]{3,4}|[\\dA-F]{6}|[\\dA-F]{8})$/i.test(config.backgroundColor ?? '')) {\n throw new Error(`Invalid Android background color: ${config.backgroundColor}`)\n }\n\n if (config.backgroundColorDark !== undefined && !config.backgroundColorDark.match(/^#[\\dA-F]{3,4}$|^#[\\dA-F]{6}$|^#[\\dA-F]{8}$/i)) {\n throw new Error(`Invalid Android dark background color: ${config.backgroundColorDark}`)\n }\n\n if (config.mapsApiKey !== undefined && !config.mapsApiKey.trim()) {\n throw new Error('Android mapsApiKey must not be empty')\n }\n\n for (const [name, value] of [\n ['versionCode', config.versionCode],\n ['minSdk', config.minSdk],\n ['compileSdk', config.compileSdk],\n ['targetSdk', config.targetSdk],\n ] as const) {\n if (!Number.isInteger(value) || Number(value) < 1) {\n throw new Error(`Android ${name} must be a positive integer`)\n }\n }\n\n if (Number(config.minSdk) < ANDROID_MIN_SDK) {\n throw new Error(`Android minSdk must be at least ${ANDROID_MIN_SDK} (Android 8.0) for the Craft runtime`)\n }\n if (Number(config.minSdk) > Number(config.targetSdk)) {\n throw new Error('Android minSdk must not exceed targetSdk')\n }\n if (Number(config.targetSdk) > Number(config.compileSdk)) {\n throw new Error('Android targetSdk must not exceed compileSdk')\n }\n}\n\nfunction normalizeNativeRoutePath(raw: unknown): string | null {\n if (typeof raw !== 'string') return null\n let path = raw\n const cut = path.search(/[?#]/)\n if (cut >= 0) path = path.slice(0, cut)\n if (!path.startsWith('/')) return null\n path = path.replace(/\\/{2,}/g, '/')\n if (path.length > 1 && path.endsWith('/')) path = path.slice(0, -1)\n return path\n}\n\nfunction nativeRouteSegments(path: string): string[] {\n return path === '/' ? [] : path.slice(1).split('/')\n}\n\nfunction matchesNativeRoute(nativeScreens: Record<string, string> | undefined, raw: string): boolean {\n const path = normalizeNativeRoutePath(raw)\n if (path === null) return false\n const parts = nativeRouteSegments(path)\n return Object.entries(nativeScreens ?? {}).some(([pattern, screen]) => {\n if (!screen) return false\n const normalized = normalizeNativeRoutePath(pattern)\n if (normalized === null) return false\n const route = nativeRouteSegments(normalized)\n for (let index = 0; index < route.length; index++) {\n const segment = route[index]\n if (segment === '*') return index === route.length - 1\n if (index >= parts.length) return false\n if (segment.startsWith(':')) {\n if (!parts[index]) return false\n }\n else if (segment !== parts[index]) return false\n }\n return route.length === parts.length\n })\n}\n\nfunction nativeStartPath(config: CraftAndroidConfig): string {\n if (!config.devServerURL) return '/'\n try {\n const url = new URL(config.devServerURL)\n return `${url.pathname || '/'}${url.search}`\n }\n catch {\n return '/'\n }\n}\n\nfunction validateNativeRouteConfig(config: CraftAndroidConfig): void {\n const screens = Object.entries(config.nativeScreens ?? {})\n for (const [pattern, screen] of screens) {\n if (normalizeNativeRoutePath(pattern) !== pattern || /[?#]/.test(pattern))\n throw new Error(`nativeScreens path \"${pattern}\" must be an absolute path with no query, hash or trailing slash.`)\n if (nativeRouteSegments(normalizeNativeRoutePath(pattern) ?? '/').slice(0, -1).includes('*'))\n throw new Error(`nativeScreens path \"${pattern}\" may only end in *.`)\n if (typeof screen !== 'string' || !/^[A-Za-z_$][\\w$-]*$/.test(screen))\n throw new Error(`nativeScreens screen \"${String(screen)}\" for \"${pattern}\" must be a screen name from the native bundle.`)\n }\n\n const ids = new Set<string>()\n for (const tab of config.tabs ?? []) {\n if (!tab || typeof tab.id !== 'string' || !tab.id || typeof tab.title !== 'string' || !tab.title)\n throw new Error('Each tab needs an id (its root path) and a title.')\n if (ids.has(tab.id)) throw new Error(`Tab \"${tab.id}\" is listed twice.`)\n ids.add(tab.id)\n }\n\n if (config.renderer === 'native' && screens.length > 0) {\n for (const id of ids) {\n if (!matchesNativeRoute(config.nativeScreens, id))\n throw new Error(`Tab \"${id}\" is not a nativeScreens path; with renderer: \"native\" every tab must be a native screen.`)\n }\n const start = nativeStartPath(config)\n if (!matchesNativeRoute(config.nativeScreens, start))\n throw new Error(`The start path \"${start}\" (from devServerURL) is not a nativeScreens path; with renderer: \"native\" the app must start on a native screen.`)\n }\n\n if (config.nativeApp !== undefined) {\n if (typeof config.nativeApp !== 'string' || !/^[A-Za-z_$][\\w$-]*$/.test(config.nativeApp))\n throw new Error(`nativeApp \"${String(config.nativeApp)}\" must be a route name from the native bundle.`)\n if (config.renderer !== 'native' && screens.length === 0)\n throw new Error('nativeApp runs in the native bundle; it needs nativeScreens or renderer: \"native\".')\n }\n}\n\nfunction writeAndroidConfig(output: string, config: CraftAndroidConfig): void {\n writeFileSync(join(output, 'craft.config.json'), JSON.stringify(config, null, 2))\n\n // Generator-only paths can reveal the developer machine's directory layout\n // and have no meaning to the installed app. Keep them in the project config\n // but never package them into the APK's assets.\n const runtimeConfig = { ...config }\n delete runtimeConfig.appIconPath\n delete runtimeConfig.googleServicesFile\n writeFileSync(\n join(output, 'app/src/main/assets/craft.config.json'),\n JSON.stringify(runtimeConfig, null, 2),\n )\n}\n\nexport function syncAndroidWebAssets(source: string, output: string): void {\n const sourcePath = resolve(source)\n if (!existsSync(sourcePath)) throw new Error(`Web asset path not found: ${source}`)\n const sourceStat = statSync(sourcePath)\n if (!sourceStat.isDirectory() && !sourceStat.isFile()) {\n throw new Error(`Web asset path must be a file or directory: ${source}`)\n }\n\n const assetsDir = resolve(output, 'app/src/main/assets')\n if (containsPath(sourcePath, assetsDir) || containsPath(assetsDir, sourcePath)) {\n throw new Error(`Web asset source must not overlap generated asset directory: ${source}`)\n }\n if (sourceStat.isDirectory()) {\n requireRegularFile(join(sourcePath, 'index.html'), 'Web asset directory entry point')\n }\n\n const configPath = join(assetsDir, 'craft.config.json')\n const config = existsSync(configPath) ? readFileSync(configPath) : undefined\n rmSync(assetsDir, { recursive: true, force: true })\n mkdirSync(assetsDir, { recursive: true })\n if (sourceStat.isDirectory()) cpSync(sourcePath, assetsDir, { recursive: true })\n else cpSync(sourcePath, join(assetsDir, 'index.html'))\n if (!existsSync(join(assetsDir, 'index.html'))) throw new Error(`Web asset directory must contain index.html: ${source}`)\n if (config) writeFileSync(configPath, config)\n}\n\nexport function renderAndroidPermissions(config: CraftAndroidConfig): string {\n const permissions = new Set(['android.permission.INTERNET', 'android.permission.ACCESS_NETWORK_STATE'])\n if (config.enableSpeechRecognition) permissions.add('android.permission.RECORD_AUDIO')\n if (config.enableCamera) permissions.add('android.permission.CAMERA')\n if (config.enableHaptics) permissions.add('android.permission.VIBRATE')\n if (config.enableBiometric) permissions.add('android.permission.USE_BIOMETRIC')\n if (config.enableGeolocation || config.enableBackgroundLocation) {\n permissions.add('android.permission.ACCESS_FINE_LOCATION')\n permissions.add('android.permission.ACCESS_COARSE_LOCATION')\n }\n if (config.enableBackgroundLocation) {\n permissions.add('android.permission.ACCESS_BACKGROUND_LOCATION')\n permissions.add('android.permission.FOREGROUND_SERVICE')\n permissions.add('android.permission.FOREGROUND_SERVICE_LOCATION')\n }\n if (config.enablePushNotifications) permissions.add('android.permission.POST_NOTIFICATIONS')\n if (config.enableKeepAwake) permissions.add('android.permission.WAKE_LOCK')\n if (config.enableHealthConnect) healthConnectPermissions(config).forEach(permission => permissions.add(permission))\n // A background refresh (WorkManager) after a restart.\n if (config.renderer === 'native' && config.backgroundRefresh?.enabled) permissions.add('android.permission.RECEIVE_BOOT_COMPLETED')\n const bluetooth = config.enableBluetooth ? [\n ' <uses-permission android:name=\"android.permission.BLUETOOTH\" android:maxSdkVersion=\"30\" />',\n ' <uses-permission android:name=\"android.permission.BLUETOOTH_ADMIN\" android:maxSdkVersion=\"30\" />',\n ' <uses-permission android:name=\"android.permission.BLUETOOTH_SCAN\" android:usesPermissionFlags=\"neverForLocation\" />',\n ' <uses-permission android:name=\"android.permission.BLUETOOTH_CONNECT\" />',\n ...(config.enableGeolocation ? [] : [' <uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\" android:maxSdkVersion=\"30\" />']),\n ].join('\\n') : ''\n return [bluetooth, ...[...permissions].map(permission => ` <uses-permission android:name=\"${permission}\" />`)].filter(Boolean).join('\\n')\n}\n\n/**\n * The native renderer's own sources: the app around the screens (routes,\n * stores, bars), its services (dialogs, browser, share, push, Health\n * Connect), background refresh, and Firebase messaging when push is on.\n */\nfunction writeNativeShellSources(output: string, packagePath: string, packageName: string, config: CraftAndroidConfig): void {\n const javaDir = join(output, 'app/src/main/java', packagePath)\n const write = (template: string, file: string, replace: (source: string) => string = source => source): void => {\n const source = readFileSync(join(TEMPLATES_DIR, template), 'utf-8').replace(/\\{\\{PACKAGE_NAME\\}\\}/g, packageName)\n writeFileSync(join(javaDir, file), replace(source))\n }\n write('CraftBluetooth.kt.template', 'CraftBluetooth.kt')\n write('CraftNativeShell.kt.template', 'CraftNativeShell.kt')\n write('CraftNativeBackground.kt.template', 'CraftNativeBackground.kt')\n write(config.enableHealthConnect ? 'CraftNativeHealth.kt.template' : 'CraftNativeHealthStub.kt.template', 'CraftNativeHealth.kt')\n write('CraftNativeServices.kt.template', 'CraftNativeServices.kt', source => source\n .replace('{{NATIVE_PUSH_IMPORTS}}', config.enablePushNotifications ? 'import com.google.firebase.messaging.FirebaseMessaging' : '')\n .replace('{{NATIVE_PUSH_REGISTER}}', config.enablePushNotifications\n ? ` try {\n FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->\n val token = if (task.isSuccessful) task.result else null\n if (token.isNullOrBlank()) done(failure(\"PUSH_UNAVAILABLE\", task.exception?.message ?: \"Firebase Cloud Messaging gave no token\"))\n else {\n context.getSharedPreferences(\"craft-native-push\", Context.MODE_PRIVATE).edit().putString(\"token\", token).apply()\n done(success(token))\n }\n }\n } catch (error: Exception) {\n done(failure(\"PUSH_UNAVAILABLE\", error.message ?: \"Firebase Cloud Messaging is not configured\"))\n }`\n : ' done(failure(\"CAPABILITY_DISABLED\", \"Push notifications are disabled\"))'))\n if (config.enablePushNotifications) write('CraftNativeMessagingService.kt.template', 'CraftNativeMessagingService.kt')\n else rmSync(join(javaDir, 'CraftNativeMessagingService.kt'), { force: true })\n // Files a screen shares (craft.files' copies, in the cache) go to other apps through this provider.\n mkdirSync(join(output, 'app/src/main/res/xml'), { recursive: true })\n writeFileSync(join(output, 'app/src/main/res/xml/craft_file_paths.xml'), `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<paths>\n <cache-path name=\"craft-files\" path=\"craft-files/\" />\n <files-path name=\"craft-media\" path=\"craft-media/\" />\n</paths>\n`)\n}\n\n/** What the native renderer adds to the manifest's application element. */\nfunction renderNativeShellManifest(config: CraftAndroidConfig): string {\n const parts = [\n ' <receiver android:name=\".CraftNativeNotificationReceiver\" android:exported=\"false\" />',\n ` <provider\n android:name=\"androidx.core.content.FileProvider\"\n android:authorities=\"${config.packageName}.craftfiles\"\n android:exported=\"false\"\n android:grantUriPermissions=\"true\">\n <meta-data android:name=\"android.support.FILE_PROVIDER_PATHS\" android:resource=\"@xml/craft_file_paths\" />\n </provider>`,\n ]\n if (config.enablePushNotifications) {\n parts.push(` <service android:name=\".CraftNativeMessagingService\" android:exported=\"false\">\n <intent-filter>\n <action android:name=\"com.google.firebase.MESSAGING_EVENT\" />\n </intent-filter>\n </service>`)\n }\n if (config.enableHealthConnect) {\n // Android 14 and later open the app's privacy policy from Health\n // Connect's permission screen through this alias.\n parts.push(` <activity-alias\n android:name=\"ViewPermissionUsageActivity\"\n android:exported=\"true\"\n android:targetActivity=\".MainActivity\"\n android:permission=\"android.permission.START_VIEW_PERMISSION_USAGE\">\n <intent-filter>\n <action android:name=\"android.intent.action.VIEW_PERMISSION_USAGE\" />\n <category android:name=\"android.intent.category.HEALTH_PERMISSIONS\" />\n </intent-filter>\n </activity-alias>`)\n }\n return parts.join('\\n')\n}\n\nexport function renderAndroidDeepLinks(config: CraftAndroidConfig): string {\n if (!config.enableDeepLinks) return ''\n const schemes = [...new Set(config.urlSchemes?.map(value => value.trim()).filter(value => /^[a-z][a-z0-9+.-]*$/i.test(value)) ?? [])]\n const custom = schemes.map(scheme => ` <intent-filter>\n <action android:name=\"android.intent.action.VIEW\" />\n <category android:name=\"android.intent.category.DEFAULT\" />\n <category android:name=\"android.intent.category.BROWSABLE\" />\n <data android:scheme=\"${scheme}\" />\n </intent-filter>`).join('\\n')\n const verified = (config.appLinks ?? []).map(link => {\n if (!/^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/i.test(link.host)) throw new Error('Invalid Android App Link host')\n return ` <intent-filter android:autoVerify=\"true\">\n <action android:name=\"android.intent.action.VIEW\" />\n <category android:name=\"android.intent.category.DEFAULT\" />\n <category android:name=\"android.intent.category.BROWSABLE\" />\n <data android:scheme=\"https\" android:host=\"${link.host}\" />\n </intent-filter>`\n }).join('\\n')\n return [custom, verified].filter(Boolean).join('\\n')\n}\n\n/**\n * Initialize a new Android project\n */\n/**\n * The ABI directories a runtime directory is expected to hold.\n *\n * These are Android's own names for the two architectures craft builds, and\n * they are also `zig build build-android-all`'s output layout — `zig-out/android/`\n * already contains `arm64-v8a/libcraft.so` and `x86_64/libcraft.so`, which is\n * exactly the shape `app/src/main/jniLibs/` wants. So the install below is a\n * copy and not a rearrangement.\n *\n * x86_64 is not optional-in-practice: every Android emulator anyone tests on\n * is x86_64, so a runtime shipped for arm64 alone is one that never runs in\n * CI and never runs on a developer's machine either.\n */\nconst RUNTIME_ABIS = ['arm64-v8a', 'x86_64'] as const\n\n/**\n * Where the runtime directory comes from when the caller does not say.\n *\n * The same shape as `CRAFT_BIN` and the iOS builder's `CRAFT_IOS_RUNTIME`: an\n * explicit override for the monorepo dev loop, not a lookup path. Shipping the\n * runtime to real apps means putting these libraries in the pantry package\n * beside the `craft` binary, which is a distribution decision this function\n * does not make.\n */\nexport function resolveRuntimeDir(override?: string | null): string | null {\n if (override === null) return null\n const dir = override ?? process.env.CRAFT_ANDROID_RUNTIME\n if (!dir) return null\n if (!existsSync(dir)) {\n const source = override === undefined ? 'CRAFT_ANDROID_RUNTIME points at' : 'runtimeDir is'\n throw new Error(`${source} ${dir}, which does not exist.`)\n }\n return dir\n}\n\n/**\n * Copy the Zig runtime into the generated project as\n * `app/src/main/jniLibs/<abi>/libcraft.so`.\n *\n * That path is AGP's default `jniLibs.srcDirs`, so nothing in the Gradle\n * templates has to know about it — the library is packaged into the APK and\n * `System.loadLibrary(\"craft\")` finds it because the file is named for the\n * `craft` it asks for.\n *\n * Until this existed, nothing put the library anywhere. `CraftNative` caught\n * the `UnsatisfiedLinkError`, set `isAvailable = false`, and every action fell\n * through to the Kotlin shim — by design, so an app with no runtime still\n * works, which is also why nobody noticed that *every* generated app was in\n * that state and the whole Android half of the Zig bridge had never run.\n *\n * Returns true when a runtime was installed.\n */\nexport function installRuntime(output: string, runtimeDir: string): boolean {\n // Every ABI resolved before anything is written, so a half-populated\n // runtime directory cannot leave a project carrying one architecture and\n // claiming both. A single-ABI APK installs fine and then fails to load on\n // the other kind of device, which is the break that only shows up on\n // somebody else's phone.\n const found = RUNTIME_ABIS.map(abi => ({ abi, source: join(runtimeDir, abi, 'libcraft.so') }))\n .filter(entry => existsSync(entry.source))\n\n if (found.length === 0) {\n throw new Error(\n `${runtimeDir} has no <abi>/libcraft.so for any of ${RUNTIME_ABIS.join(', ')}. `\n + 'Run `zig build build-android-all -Doptimize=ReleaseSafe` in packages/zig and point at its zig-out/android.',\n )\n }\n\n if (found.length < RUNTIME_ABIS.length) {\n const missing = RUNTIME_ABIS.filter(abi => !found.some(entry => entry.abi === abi))\n console.warn(\n ` ⚠ only ${found.map(entry => entry.abi).join(', ')} was found; ${missing.join(', ')} is missing. `\n + 'The app will fall back to the Kotlin shim on those devices.',\n )\n }\n\n const dest = join(output, 'app/src/main/jniLibs')\n // Wiped rather than merged, so an ABI dropped from a later build does not\n // linger in the project as a stale library the APK still ships.\n rmSync(dest, { force: true, recursive: true })\n\n for (const { abi, source } of found) {\n const abiDir = join(dest, abi)\n mkdirSync(abiDir, { recursive: true })\n cpSync(source, join(abiDir, 'libcraft.so'))\n }\n\n return true\n}\n\nexport async function init(options: InitOptions): Promise<void> {\n const { name, packageName, output } = options\n\n console.log(`\\n⚡ Initializing Craft Android project: ${name}`)\n console.log(` Output: ${output}\\n`)\n\n // Generate package name from app name if not provided\n const finalPackageName = packageName || `com.craft.${generatedPackageSegment(name)}`\n const packagePath = finalPackageName.replace(/\\./g, '/')\n\n // Command-level identity always wins over duplicate fields in `config`.\n // Normalize implied capabilities before validation so craft.config.json and\n // the generated Gradle source describe the same effective project.\n // Undefined keeps the default instead of erasing it (see the iOS builder).\n const given = Object.fromEntries(Object.entries(options.config ?? {}).filter(([, value]) => value !== undefined))\n const config: CraftAndroidConfig = {\n ...DEFAULT_CONFIG,\n ...given,\n appName: name,\n packageName: finalPackageName,\n }\n if (config.enableBackgroundLocation) config.enableGeolocation = true\n if (config.enableHealthConnect) {\n config.compileSdk = Math.max(config.compileSdk ?? 36, 36)\n config.enableHealthConnectBackground = Boolean(config.healthConnect?.background)\n config.enableHealthConnectHistory = Boolean(config.healthConnect?.history)\n healthConnectPermissions(config)\n }\n normalizeAndroidNetworkConfig(config)\n validateAndroidConfig(config)\n validateNativeRouteConfig(config)\n if (config.enablePushNotifications && !config.googleServicesFile) {\n throw new Error('Android push notifications require a googleServicesFile')\n }\n if (config.googleServicesFile) validateGoogleServicesFile(config.googleServicesFile, finalPackageName)\n if (config.appIconPath) requireRegularFile(config.appIconPath, 'App icon')\n const appIconExtension = config.appIconPath ? extname(config.appIconPath).toLowerCase() : undefined\n if (config.appIconPath && !['.gif', '.jpg', '.png', '.webp'].includes(appIconExtension ?? '')) {\n throw new Error(`Unsupported Android app icon format: ${appIconExtension || '(none)'}`)\n }\n\n // Create directory structure\n const dirs = [\n output,\n join(output, 'app/src/main/java', packagePath),\n join(output, 'app/src/main/res/layout'),\n join(output, 'app/src/main/res/values'),\n join(output, 'app/src/main/res/values-night'),\n join(output, 'app/src/main/res/drawable'),\n join(output, 'app/src/main/assets'),\n join(output, 'gradle/wrapper'),\n ]\n\n if (existsSync(output) && !statSync(output).isDirectory()) {\n throw new Error(`Android project output must be a directory: ${output}`)\n }\n\n for (const dir of dirs) {\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true })\n }\n }\n\n // Create craft.config.json\n writeAndroidConfig(output, config)\n\n const hasGoogleServices = Boolean(config.googleServicesFile)\n if (config.googleServicesFile) {\n cpSync(config.googleServicesFile, join(output, 'app/google-services.json'))\n }\n\n // Copy templates\n const mainActivityTemplate = readFileSync(join(\n TEMPLATES_DIR,\n config.renderer === 'native' ? 'MainActivityNative.kt.template' : 'MainActivity.kt.template',\n ), 'utf-8')\n const mainActivity = mainActivityTemplate\n .replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName)\n .replace(/\\{\\{APP_NAME\\}\\}/g, name)\n\n writeFileSync(join(output, 'app/src/main/java', packagePath, 'MainActivity.kt'), mainActivity)\n writeFileSync(\n join(output, 'app/src/main/java', packagePath, 'CraftAppLinks.kt'),\n readFileSync(join(TEMPLATES_DIR, 'CraftAppLinks.kt.template'), 'utf8').replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName),\n )\n // Both renderers: crash reporting does not depend on how screens are drawn.\n writeFileSync(\n join(output, 'app/src/main/java', packagePath, 'CraftCrashReporter.kt'),\n readFileSync(join(TEMPLATES_DIR, 'CraftCrashReporter.kt.template'), 'utf-8')\n .replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName)\n .replace(/\\{\\{CRAFT_VERSION\\}\\}/g, CRAFT_ANDROID_VERSION),\n )\n if (config.renderer === 'native') {\n const mutationTemplate = readFileSync(join(TEMPLATES_DIR, 'CraftNativeMutation.kt.template'), 'utf-8')\n const flatListTemplate = readFileSync(join(TEMPLATES_DIR, 'CraftNativeFlatList.kt.template'), 'utf-8')\n const refreshableFlatListTemplate = readFileSync(join(TEMPLATES_DIR, 'CraftNativeRefreshableFlatList.kt.template'), 'utf-8')\n const capabilityTemplate = readFileSync(join(TEMPLATES_DIR, 'CraftNativeCapabilities.kt.template'), 'utf-8')\n const notificationTemplate = readFileSync(join(TEMPLATES_DIR, 'CraftNativeNotificationReceiver.kt.template'), 'utf-8')\n writeFileSync(\n join(output, 'app/src/main/java', packagePath, 'CraftNativeMutation.kt'),\n mutationTemplate.replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName),\n )\n writeFileSync(\n join(output, 'app/src/main/java', packagePath, 'CraftNativeFlatList.kt'),\n flatListTemplate.replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName),\n )\n writeFileSync(\n join(output, 'app/src/main/java', packagePath, 'CraftNativeRefreshableFlatList.kt'),\n refreshableFlatListTemplate.replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName),\n )\n writeFileSync(\n join(output, 'app/src/main/java', packagePath, 'CraftNativeCapabilities.kt'),\n capabilityTemplate.replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName),\n )\n writeFileSync(\n join(output, 'app/src/main/java', packagePath, 'CraftNativeNotificationReceiver.kt'),\n notificationTemplate.replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName),\n )\n writeNativeShellSources(output, packagePath, finalPackageName, config)\n }\n\n // Create CraftBridge.kt\n const craftBridgeTemplate = readFileSync(join(TEMPLATES_DIR, 'CraftBridge.kt.template'), 'utf-8')\n const craftBridge = craftBridgeTemplate\n .replace(/\\{\\{PROMISE_RUNTIME\\}\\}/g, () => renderAndroidPromiseRuntime(' '))\n .replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName)\n .replace(/\\{\\{ENABLE_SPEECH\\}\\}/g, String(Boolean(config.enableSpeechRecognition)))\n .replace(/\\{\\{ENABLE_HAPTICS\\}\\}/g, String(Boolean(config.enableHaptics)))\n .replace(/\\{\\{ENABLE_SHARE\\}\\}/g, String(Boolean(config.enableShare)))\n .replace(/\\{\\{ENABLE_CAMERA\\}\\}/g, String(Boolean(config.enableCamera)))\n .replace(/\\{\\{ENABLE_BIOMETRIC\\}\\}/g, String(Boolean(config.enableBiometric)))\n .replace(/\\{\\{ENABLE_PUSH\\}\\}/g, String(Boolean(config.enablePushNotifications)))\n .replace(/\\{\\{ENABLE_SECURE_STORAGE\\}\\}/g, String(Boolean(config.enableSecureStorage)))\n .replace(/\\{\\{ENABLE_GEOLOCATION\\}\\}/g, String(Boolean(config.enableGeolocation)))\n .replace(/\\{\\{ENABLE_BACKGROUND_LOCATION\\}\\}/g, String(Boolean(config.enableBackgroundLocation)))\n .replace(/\\{\\{ENABLE_KEEP_AWAKE\\}\\}/g, String(Boolean(config.enableKeepAwake)))\n .replace(/\\{\\{ENABLE_DEEP_LINKS\\}\\}/g, String(Boolean(config.enableDeepLinks)))\n .replace(/\\{\\{ENABLE_HEALTH_CONNECT\\}\\}/g, String(Boolean(config.enableHealthConnect)))\n .replace(/\\{\\{FIREBASE_IMPORT\\}\\}/g, config.enablePushNotifications ? 'import com.google.firebase.messaging.FirebaseMessaging' : '')\n .replace(/\\{\\{REGISTER_PUSH_IMPLEMENTATION\\}\\}/g, config.enablePushNotifications\n ? `activity.runOnUiThread {\n if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU\n && ContextCompat.checkSelfPermission(activity, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {\n ActivityCompat.requestPermissions(activity, arrayOf(Manifest.permission.POST_NOTIFICATIONS), 4204)\n }\n try {\n FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->\n val token = if (task.isSuccessful) task.result else null\n val callback = if (token.isNullOrBlank()) \"window._craftPushReject\" else \"window._craftPushResolve\"\n val payload = JSONObject.quote(token ?: task.exception?.message ?: \"Firebase Cloud Messaging is not configured\")\n evaluatePromiseJavascript(\"$callback && $callback($payload)\")\n }\n } catch (error: Exception) {\n val payload = JSONObject.quote(error.message ?: \"Firebase Cloud Messaging is not configured\")\n evaluatePromiseJavascript(\"window._craftPushReject && window._craftPushReject($payload)\")\n }\n }`\n : `evaluatePromiseJavascript(\n \"window._craftPushReject && window._craftPushReject('Push notifications are disabled')\"\n )`)\n writeFileSync(join(output, 'app/src/main/java', packagePath, 'CraftBridge.kt'), craftBridge)\n\n // CraftNative goes to a fixed package, not the app's. The prebuilt\n // libcraft.so binds its natives by class name in JNI_OnLoad, and it cannot\n // know a name chosen here — so this one file is deliberately not templated\n // and deliberately not under packagePath.\n const craftNative = readFileSync(join(TEMPLATES_DIR, 'CraftNative.kt.template'), 'utf-8')\n const nativeDir = join(output, 'app/src/main/java/com/craft/runtime')\n mkdirSync(nativeDir, { recursive: true })\n writeFileSync(join(nativeDir, 'CraftNative.kt'), craftNative)\n // The bridge and native holder both reference the recording store/service,\n // even when the service is not registered in the manifest. Keep these\n // support types in the same fixed package as CraftNative so every generated\n // app compiles regardless of its chosen package name or feature flags.\n const serviceTemplate = readFileSync(join(TEMPLATES_DIR, 'LocationRecordingService.kt.template'), 'utf-8')\n writeFileSync(join(nativeDir, 'LocationRecordingService.kt'), serviceTemplate)\n const healthTemplate = readFileSync(join(\n TEMPLATES_DIR,\n config.enableHealthConnect ? 'CraftHealthConnect.kt.template' : 'CraftHealthConnectStub.kt.template',\n ), 'utf-8')\n writeFileSync(\n join(output, 'app/src/main/java', packagePath, 'CraftHealthConnect.kt'),\n healthTemplate.replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName),\n )\n // Create AndroidManifest.xml\n const manifestTemplate = readFileSync(join(TEMPLATES_DIR, 'AndroidManifest.xml.template'), 'utf-8')\n const manifest = manifestTemplate\n .replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName)\n .replace(/\\{\\{APP_NAME\\}\\}/g, name)\n .replace(/\\{\\{PERMISSIONS\\}\\}/g, renderAndroidPermissions(config))\n // Plain HTTP only for a local dev server, or an app that talks to the\n // person's own devices on the LAN (which have no public certificate).\n .replace(/\\{\\{USES_CLEARTEXT\\}\\}/g, config.devServerURL?.startsWith('http://') || config.enableLocalNetwork ? 'true' : 'false')\n .replace(/\\{\\{DEEP_LINK_INTENT_FILTERS\\}\\}/g, renderAndroidDeepLinks(config))\n .replace(/\\{\\{BACKGROUND_SERVICE\\}\\}/g, config.enableBackgroundLocation\n ? ' <service android:name=\"com.craft.runtime.LocationRecordingService\" android:exported=\"false\" android:foregroundServiceType=\"location\" android:stopWithTask=\"false\" />'\n : '')\n .replace(/\\{\\{NATIVE_NOTIFICATION_RECEIVER\\}\\}/g, config.renderer === 'native'\n ? renderNativeShellManifest(config)\n : '')\n // Starts the crash reporter before Application.onCreate, for every way\n // the process starts (CraftCrashReporter.kt). Inert without a key.\n .replace(/\\{\\{CRASH_REPORTER_PROVIDER\\}\\}/g, ` <provider android:name=\".CraftCrashReporterInit\" android:authorities=\"\\${applicationId}.craft-crash\" android:exported=\"false\" android:initOrder=\"1000\" />`)\n .replace(/\\{\\{GOOGLE_MAPS_API_KEY\\}\\}/g, config.mapsApiKey\n ? ` <meta-data android:name=\"com.google.android.geo.API_KEY\" android:value=\"${escapeXml(config.mapsApiKey)}\" />`\n : '')\n .replace(/\\{\\{HEALTH_CONNECT_QUERIES\\}\\}/g, config.enableHealthConnect\n ? ' <queries>\\n <package android:name=\"com.google.android.apps.healthdata\" />\\n </queries>'\n : '')\n .replace(/\\{\\{HEALTH_CONNECT_RATIONALE\\}\\}/g, config.enableHealthConnect\n ? ` <intent-filter>\n <action android:name=\"androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE\" />\n </intent-filter>${config.renderer === 'native' ? '' : `\n <intent-filter>\n <action android:name=\"android.intent.action.VIEW_PERMISSION_USAGE\" />\n <category android:name=\"android.intent.category.HEALTH_PERMISSIONS\" />\n </intent-filter>`}`\n : '')\n\n writeFileSync(join(output, 'app/src/main/AndroidManifest.xml'), manifest)\n\n // Create build.gradle.kts (project level)\n const projectGradleTemplate = readFileSync(join(TEMPLATES_DIR, 'build.gradle.kts.project.template'), 'utf-8')\n writeFileSync(join(output, 'build.gradle.kts'), projectGradleTemplate\n .replace(/\\{\\{GOOGLE_SERVICES_PLUGIN\\}\\}/g, hasGoogleServices ? ' id(\"com.google.gms.google-services\") version \"4.4.2\" apply false' : ''))\n\n // Create build.gradle.kts (app level)\n const appGradleTemplate = readFileSync(join(TEMPLATES_DIR, 'build.gradle.kts.app.template'), 'utf-8')\n const appGradle = appGradleTemplate\n .replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName)\n .replace(/\\{\\{VERSION_NAME\\}\\}/g, escapeKotlinString(config.version || '1.0.0'))\n .replace(/\\{\\{VERSION_CODE\\}\\}/g, String(config.versionCode || 1))\n .replace(/\\{\\{MIN_SDK\\}\\}/g, String(config.minSdk ?? ANDROID_MIN_SDK))\n .replace(/\\{\\{COMPILE_SDK\\}\\}/g, String(config.compileSdk || 36))\n .replace(/\\{\\{TARGET_SDK\\}\\}/g, String(config.targetSdk || 35))\n .replace(/\\{\\{GOOGLE_SERVICES_PLUGIN\\}\\}/g, hasGoogleServices ? ' id(\"com.google.gms.google-services\")' : '')\n .replace(/\\{\\{FIREBASE_MESSAGING_DEPENDENCY\\}\\}/g, config.enablePushNotifications\n ? ' implementation(\"com.google.firebase:firebase-messaging:24.1.0\")'\n : '')\n .replace(/\\{\\{REFRESH_LAYOUT_DEPENDENCY\\}\\}/g, config.renderer === 'native'\n ? ' implementation(\"androidx.swiperefreshlayout:swiperefreshlayout:1.1.0\")'\n : '')\n .replace(/\\{\\{GOOGLE_MAPS_DEPENDENCY\\}\\}/g, config.renderer === 'native'\n ? ' // Native STX Map overlays and trail routes\\n implementation(\"com.google.android.gms:play-services-maps:18.2.0\")'\n : '')\n .replace(/\\{\\{HEALTH_CONNECT_DEPENDENCIES\\}\\}/g, config.enableHealthConnect\n ? ` implementation(\"androidx.health.connect:connect-client:1.1.0\")\n implementation(\"org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1\")`\n : '')\n .replace(/\\{\\{JAVASCRIPT_ENGINE_DEPENDENCY\\}\\}/g, config.renderer === 'native'\n ? ` implementation(\"androidx.javascriptengine:javascriptengine:1.1.0\")\n // Native background refresh (WorkManager).\n implementation(\"androidx.work:work-runtime-ktx:2.9.1\")`\n : '')\n\n writeFileSync(join(output, 'app/build.gradle.kts'), appGradle)\n const proguardTemplate = readFileSync(join(TEMPLATES_DIR, 'proguard-rules.pro.template'), 'utf-8')\n writeFileSync(\n join(output, 'app/proguard-rules.pro'),\n proguardTemplate.replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName),\n )\n\n // Create settings.gradle.kts\n const settingsTemplate = readFileSync(join(TEMPLATES_DIR, 'settings.gradle.kts.template'), 'utf-8')\n const settings = settingsTemplate.replace(\n /\\{\\{APP_NAME\\}\\}/g,\n escapeKotlinString(generatedGradleProjectName(name)),\n )\n writeFileSync(join(output, 'settings.gradle.kts'), settings)\n\n // Create gradle.properties\n const gradleProperties = `org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8\nandroid.useAndroidX=true\nkotlin.code.style=official\nandroid.nonTransitiveRClass=true\n`\n writeFileSync(join(output, 'gradle.properties'), gradleProperties)\n\n // Create local.properties placeholder\n writeFileSync(join(output, 'local.properties'), '# SDK location will be set by Android Studio\\n')\n\n // Create gradle wrapper properties\n const gradleWrapperProps = `distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\\\://services.gradle.org/distributions/gradle-8.11.1-bin.zip\nnetworkTimeout=10000\nvalidateDistributionUrl=true\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n`\n writeFileSync(join(output, 'gradle/wrapper/gradle-wrapper.properties'), gradleWrapperProps)\n\n // Create res/values files\n const stringsXml = `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"app_name\">${escapeXml(name)}</string>\n</resources>\n`\n writeFileSync(join(output, 'app/src/main/res/values/strings.xml'), stringsXml)\n\n const colorsXml = `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <color name=\"primary\">#1a1a2e</color>\n <color name=\"primary_dark\">#16162b</color>\n <color name=\"accent\">#4ade80</color>\n <color name=\"background\">${config.backgroundColor}</color>\n</resources>\n`\n writeFileSync(join(output, 'app/src/main/res/values/colors.xml'), colorsXml)\n const darkColorsPath = join(output, 'app/src/main/res/values-night/colors.xml')\n if (config.backgroundColorDark) {\n writeFileSync(darkColorsPath, `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <color name=\"background\">${config.backgroundColorDark}</color>\n</resources>\n`)\n }\n else {\n rmSync(darkColorsPath, { force: true })\n }\n\n const appIconXml = `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"108dp\"\n android:height=\"108dp\"\n android:viewportWidth=\"108\"\n android:viewportHeight=\"108\">\n <path android:fillColor=\"${config.backgroundColor}\" android:pathData=\"M0,0h108v108h-108z\" />\n <path android:fillColor=\"#10B981\" android:pathData=\"M18,78L42,42l12,18l12,-12l24,30z\" />\n</vector>\n`\n if (config.appIconPath) {\n cpSync(\n config.appIconPath,\n join(output, `app/src/main/res/drawable/craft_app_icon${appIconExtension}`),\n )\n }\n else {\n writeFileSync(join(output, 'app/src/main/res/drawable/craft_app_icon.xml'), appIconXml)\n }\n\n const themesXml = `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <style name=\"Theme.CraftApp\" parent=\"Theme.Material3.DayNight.NoActionBar\">\n <item name=\"android:statusBarColor\">@color/primary_dark</item>\n <item name=\"android:navigationBarColor\">@color/primary</item>\n <item name=\"android:windowBackground\">@color/background</item>\n </style>\n</resources>\n`\n writeFileSync(join(output, 'app/src/main/res/values/themes.xml'), themesXml)\n\n // Create activity_main.xml\n const activityMainXml = config.renderer === 'native'\n ? `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:id=\"@+id/native_root\"\n android:layout_width=\"match_parent\"\n android:layout_height=\"match_parent\" />\n`\n : `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:layout_width=\"match_parent\"\n android:layout_height=\"match_parent\"\n android:fitsSystemWindows=\"true\">\n\n <WebView\n android:id=\"@+id/webview\"\n android:layout_width=\"match_parent\"\n android:layout_height=\"match_parent\" />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n`\n writeFileSync(join(output, 'app/src/main/res/layout/activity_main.xml'), activityMainXml)\n\n // Create placeholder index.html\n const placeholderHtml = `<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"viewport-fit=cover, width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\">\n <title>${escapeXml(name)}</title>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body {\n font-family: 'Roboto', sans-serif;\n background: ${config.backgroundColor};\n color: white;\n min-height: 100vh;\n display: flex;\n justify-content: center;\n align-items: center;\n }\n .container { text-align: center; padding: 2rem; }\n h1 { font-size: 2.5rem; margin-bottom: 1rem; }\n p { opacity: 0.7; }\n .ready { color: #4ade80; font-size: 0.9rem; margin-top: 2rem; }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <h1>⚡ ${escapeXml(name)}</h1>\n <p>Built with Craft Android</p>\n <p class=\"ready\" id=\"status\">Waiting for Craft bridge...</p>\n </div>\n <script>\n window.addEventListener('craftReady', (e) => {\n document.getElementById('status').textContent = \\`✓ Craft bridge ready (platform: \\${e.detail.platform})\\`;\n console.log('Craft capabilities:', e.detail.capabilities);\n });\n </script>\n</body>\n</html>`\n\n writeFileSync(join(output, 'app/src/main/assets/index.html'), placeholderHtml)\n\n // The Zig runtime, when one was configured. Absent, the generated app is the\n // Kotlin shim and behaves exactly as it always has — which is the fallback\n // `CraftNative` was written for, not a failure.\n const runtimeDir = resolveRuntimeDir(options.runtimeDir)\n if (runtimeDir) {\n installRuntime(output, runtimeDir)\n console.log(' Installed the Zig runtime from', runtimeDir)\n }\n\n console.log('✅ Project initialized')\n console.log('')\n console.log('Next steps:')\n console.log(` 1. cd ${output}`)\n if (config.renderer === 'native') {\n console.log(' 2. Compile your screen: stx native compile Screen.stx --format bundle --output screen.js')\n console.log(' 3. Run: craft android build --native-bundle screen.js')\n }\n else {\n console.log(' 2. Add your web content to app/src/main/assets/index.html')\n console.log(' 3. Run: craft android build')\n }\n console.log(' 4. Run: craft android open')\n console.log('')\n}\n\n/**\n * Build Android project\n */\nexport async function build(options: BuildOptions): Promise<void> {\n const { htmlPath, nativeBundlePath, devServer, output, release, bundle, compile = true } = options\n\n console.log('\\n📦 Building Craft Android project...')\n\n // Load config\n const configPath = join(output, 'craft.config.json')\n if (!existsSync(configPath)) {\n throw new Error(`No craft.config.json found in ${output}. Run 'craft android init' first.`)\n }\n\n const config: CraftAndroidConfig = JSON.parse(readFileSync(configPath, 'utf-8'))\n validateAndroidConfig(config)\n validateNativeRouteConfig(config)\n\n if (config.renderer === 'native') {\n if (htmlPath || devServer) throw new Error('A native Android screen cannot use --html-path or --dev-server.')\n const destination = join(output, 'app/src/main/assets/native-screen.js')\n if (nativeBundlePath && resolve(nativeBundlePath) !== resolve(destination)) cpSync(nativeBundlePath, destination)\n const icons = join(output, 'app/src/main/assets/craft-icons.json')\n if (options.iconsPath) {\n requireRegularFile(options.iconsPath, 'Icon table')\n if (resolve(options.iconsPath) !== resolve(icons)) cpSync(options.iconsPath, icons)\n }\n if (!existsSync(destination)) {\n throw new Error('Native Android mode needs app/src/main/assets/native-screen.js. Compile a .stx screen with `stx native compile` first.')\n }\n }\n else if (nativeBundlePath) {\n throw new Error('--native-bundle requires renderer: \"native\" in craft.config.json.')\n }\n\n // Refresh the runtime the same way the iOS builder does: only for a project\n // that already has one, so `craft android build` in a shim-only project does\n // not quietly acquire a native library it was never generated with.\n if (existsSync(join(output, 'app/src/main/jniLibs'))) {\n const runtimeDir = resolveRuntimeDir(options.runtimeDir)\n if (runtimeDir) {\n installRuntime(output, runtimeDir)\n console.log(' Refreshed the Zig runtime from', runtimeDir)\n }\n else {\n console.log(' Keeping the Zig runtime installed at init (no runtime directory configured)')\n }\n }\n\n // Update dev server URL if provided\n if (devServer) {\n const url = androidWebUrl(devServer, 'Android dev server URL')\n config.devServerURL = url.toString()\n config.trustedOrigins = [...new Set([\n ...(config.trustedOrigins ?? []).map(value => androidWebUrl(value, 'Android trusted origin').origin),\n url.origin,\n ])]\n writeAndroidConfig(output, config)\n console.log(` Dev server: ${devServer}`)\n }\n\n // Copy the complete web application if provided.\n if (htmlPath) {\n syncAndroidWebAssets(htmlPath, output)\n config.hasBundledFallback = Boolean(devServer)\n writeAndroidConfig(output, config)\n console.log(` Synced: ${htmlPath} → assets/`)\n }\n\n if (!compile) return\n\n // Check if gradlew exists\n const gradlewPath = join(output, 'gradlew')\n if (!existsSync(gradlewPath)) {\n console.log(' Generating Gradle wrapper...')\n try {\n await $`cd ${output} && gradle wrapper`.quiet()\n }\n catch (error) {\n throw new Error('Gradle is unavailable. Install Android Studio or Gradle before building Android.', { cause: error })\n }\n }\n\n // Build the project\n const variant = release ? 'Release' : 'Debug'\n const buildType = `${bundle ? 'bundle' : 'assemble'}${variant}`\n console.log(` Building ${release ? 'release' : 'debug'} ${bundle ? 'app bundle' : 'APK'}...`)\n if (release && !process.env.CRAFT_ANDROID_KEYSTORE)\n console.log(' CRAFT_ANDROID_KEYSTORE is not set: the release build is unsigned.')\n\n try {\n await $`cd ${output} && ./gradlew ${buildType}`\n const artifact = bundle\n ? `app/build/outputs/bundle/${variant.toLowerCase()}/app-${variant.toLowerCase()}.aab`\n : release\n ? 'app/build/outputs/apk/release/app-release.apk'\n : 'app/build/outputs/apk/debug/app-debug.apk'\n console.log(`✅ ${bundle ? 'App bundle' : 'APK'} built: ${artifact}`)\n }\ncatch (error) {\n console.error('Build failed. Open in Android Studio for details.')\n throw error\n }\n\n console.log('')\n}\n\n/**\n * Open Android project in Android Studio\n */\nexport async function open(options: OpenOptions): Promise<void> {\n const { output } = options\n\n if (!existsSync(output)) {\n throw new Error(`No Android project found in ${output}. Run 'craft android init' first.`)\n }\n\n console.log(`🚀 Opening Android project in Android Studio...`)\n\n // Try to open with Android Studio\n try {\n // macOS\n await $`open -a \"Android Studio\" ${output}`.quiet()\n }\ncatch {\n try {\n // Linux\n await $`studio ${output}`.quiet()\n }\ncatch {\n console.log('⚠️ Android Studio not found.')\n console.log(' Please open the project manually in Android Studio:')\n console.log(` ${output}`)\n }\n }\n}\n\n/**\n * Run on Android device or emulator\n */\nexport async function run(options: RunOptions): Promise<void> {\n const { device, output } = options\n\n // First build\n await build({ output })\n\n // Install and run\n console.log('📱 Installing and running on device...')\n\n try {\n const apkPath = join(output, 'app/build/outputs/apk/debug/app-debug.apk')\n\n if (device) {\n await $`adb -s ${device} install -r ${apkPath}`\n }\nelse {\n await $`adb install -r ${apkPath}`\n }\n\n // Get package name from config\n const config: CraftAndroidConfig = JSON.parse(\n readFileSync(join(output, 'craft.config.json'), 'utf-8')\n )\n\n // Launch the app\n const launchCmd = `${config.packageName}/.MainActivity`\n if (device) {\n await $`adb -s ${device} shell am start -n ${launchCmd}`\n }\nelse {\n await $`adb shell am start -n ${launchCmd}`\n }\n\n console.log('✅ App launched')\n }\ncatch (error) {\n console.error('Failed to install/run. Make sure:')\n console.error(' 1. ADB is installed and in PATH')\n console.error(' 2. A device/emulator is connected (run: adb devices)')\n throw error\n }\n}\n",
5
+ "/**\n * Craft Android Builder\n *\n * Generates native Android apps from web content using WebView.\n */\n\nimport { cpSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs'\nimport { dirname, extname, isAbsolute, join, relative, resolve, sep } from 'node:path'\nimport { $ } from 'bun'\nimport { renderAndroidPromiseRuntime } from './promise-runtime'\n\nexport { runKotlinFixture } from './testing.js'\n\nconst TEMPLATES_DIR = join(dirname(import.meta.dir), 'templates')\n\n/**\n * This builder's version, which the crash reporter names as its SDK version.\n * Walks up from here because a bundled build puts this module below the\n * package root.\n */\nfunction resolveCraftAndroidVersion(startDir: string = import.meta.dir): string {\n let directory = resolve(startDir)\n while (true) {\n try {\n const pkg = JSON.parse(readFileSync(join(directory, 'package.json'), 'utf8')) as { name?: unknown, version?: unknown }\n if ((pkg.name === '@craft-native/android' || pkg.name === 'craft-native') && typeof pkg.version === 'string' && pkg.version)\n return pkg.version\n }\n catch {\n // Keep walking.\n }\n const parent = dirname(directory)\n if (parent === directory) return '0.0.0'\n directory = parent\n }\n}\n\nconst CRAFT_ANDROID_VERSION = resolveCraftAndroidVersion()\nconst LOCAL_DEVELOPMENT_HOSTS = new Set(['localhost', '127.0.0.1', '10.0.2.2'])\nconst KOTLIN_KEYWORDS = new Set([\n 'as',\n 'break',\n 'class',\n 'continue',\n 'do',\n 'else',\n 'false',\n 'for',\n 'fun',\n 'if',\n 'in',\n 'interface',\n 'is',\n 'null',\n 'object',\n 'package',\n 'return',\n 'super',\n 'this',\n 'throw',\n 'true',\n 'try',\n 'typealias',\n 'typeof',\n 'val',\n 'var',\n 'when',\n 'while',\n])\n\n/** Oldest Android API level supported by the generated Kotlin runtime. */\nexport const ANDROID_MIN_SDK = 26\n\nexport interface CraftAndroidConfig {\n appName: string\n packageName: string\n renderer?: 'web' | 'native'\n /** \"system\" follows the device; light/dark pin the generated host. */\n appearance?: 'light' | 'dark' | 'system'\n version?: string\n versionCode?: number\n darkMode?: boolean\n backgroundColor?: string\n backgroundColorDark?: string\n enableSpeechRecognition?: boolean\n enableHaptics?: boolean\n enableShare?: boolean\n enableCamera?: boolean\n enableBiometric?: boolean\n enablePushNotifications?: boolean\n enableSecureStorage?: boolean\n enableGeolocation?: boolean\n enableBackgroundLocation?: boolean\n enableBluetooth?: boolean\n enableHealthConnect?: boolean\n /**\n * The Health Connect data the app reads and writes, by the shared type\n * names (`steps`, `sleep`, `workouts`...). The manifest declares only\n * these, as Play's health permissions review requires; without it, the\n * original set (steps, heart rate, energy, distance, workouts).\n */\n healthConnect?: {\n read?: HealthConnectType[]\n write?: HealthConnectType[]\n /** Read while the app is in the background (a WorkManager refresh): Android 15's READ_HEALTH_DATA_IN_BACKGROUND. */\n background?: boolean\n /** Read further back than the 30 days before the first grant: READ_HEALTH_DATA_HISTORY. */\n history?: boolean\n }\n enableHealthConnectBackground?: boolean\n enableHealthConnectHistory?: boolean\n /** The app's privacy policy, which Health Connect links to from its permission screen. */\n privacyPolicyURL?: string\n enableKeepAwake?: boolean\n enableDeepLinks?: boolean\n /** Reach the person's own devices on the local network over plain HTTP (a Mac companion). */\n enableLocalNetwork?: boolean\n urlSchemes?: string[]\n /** HTTPS hosts owned by the app; path rules are ordered, with `*` matching any path text. */\n /** Ordered path rules: * matches zero or more characters, ? one, and ?* one or more. */\n appLinks?: Array<{ host: string, paths?: Array<{ pattern: string, exclude?: boolean }> }>\n trustedOrigins?: string[]\n appIconPath?: string\n googleServicesFile?: string\n /** Google Maps Android SDK key used by native `Map` screens. */\n mapsApiKey?: string\n devServerURL?: string\n hasBundledFallback?: boolean\n /** Android API level, at least ANDROID_MIN_SDK (26). */\n minSdk?: number\n compileSdk?: number\n targetSdk?: number\n /**\n * Native renderer: the app's paths and the screen drawing each\n * (`/m/workout/:id` → `Workout`), its tabs, and the bundle route of the\n * script that runs beside the screens. The same keys as the iOS builder.\n */\n nativeScreens?: Record<string, string>\n tabs?: Array<{ id: string, title: string, symbol?: string }>\n nativeApp?: string\n /** Wake the app script every so often (`craftBackgroundRefresh`), through WorkManager. */\n backgroundRefresh?: { enabled?: boolean, minimumIntervalMinutes?: number }\n /**\n * Crash and error reporting to bughq (CraftCrashReporter.kt): uncaught JVM\n * exceptions, ANRs and native crashes (ApplicationExitInfo), uncaught\n * JavaScript errors, and what the page sends through `craft.crashReporter`.\n * Queued on disk and sent when online. Release builds are minified: upload\n * `app/build/outputs/mapping/release/mapping.txt` so bughq can retrace.\n */\n crashReporting?: {\n /** The bughq project's public ingest key. */\n key: string\n /** Default https://bughq.org. */\n host?: string\n /** Default `production`. */\n environment?: string\n /** Default `<versionName>+<versionCode>`, e.g. `1.0.0+41`. */\n release?: string\n /** One session per launch, for crash-free sessions. Default on. */\n sessions?: boolean\n }\n}\n\n/** The shared health data types (the iOS builder's HealthKit names). */\nexport type HealthConnectType = 'steps' | 'heartRate' | 'activeEnergy' | 'distance' | 'workouts' | 'restingHeartRate'\n | 'heartRateVariability' | 'bodyMass' | 'sleep' | 'mindful' | 'dietaryEnergy' | 'dietaryProtein'\n | 'dietaryCarbohydrates' | 'dietaryFat'\n\n/**\n * The Health Connect permissions behind each type, for reading and for\n * writing. CraftNativeHealth.kt asks for the same ones; a permission the\n * manifest does not declare is refused without a prompt.\n */\nexport const HEALTH_CONNECT_PERMISSIONS: Record<HealthConnectType, { read: string[], write: string[] }> = {\n steps: { read: ['READ_STEPS'], write: [] },\n heartRate: { read: ['READ_HEART_RATE'], write: [] },\n activeEnergy: { read: ['READ_ACTIVE_CALORIES_BURNED'], write: ['WRITE_ACTIVE_CALORIES_BURNED'] },\n distance: { read: ['READ_DISTANCE'], write: ['WRITE_DISTANCE'] },\n workouts: {\n read: ['READ_EXERCISE', 'READ_HEART_RATE', 'READ_ACTIVE_CALORIES_BURNED', 'READ_DISTANCE'],\n write: ['WRITE_EXERCISE', 'WRITE_ACTIVE_CALORIES_BURNED', 'WRITE_DISTANCE'],\n },\n restingHeartRate: { read: ['READ_RESTING_HEART_RATE'], write: [] },\n heartRateVariability: { read: ['READ_HEART_RATE_VARIABILITY'], write: [] },\n bodyMass: { read: ['READ_WEIGHT'], write: [] },\n sleep: { read: ['READ_SLEEP'], write: [] },\n mindful: { read: ['READ_MINDFULNESS'], write: ['WRITE_MINDFULNESS'] },\n dietaryEnergy: { read: ['READ_NUTRITION'], write: ['WRITE_NUTRITION'] },\n dietaryProtein: { read: ['READ_NUTRITION'], write: ['WRITE_NUTRITION'] },\n dietaryCarbohydrates: { read: ['READ_NUTRITION'], write: ['WRITE_NUTRITION'] },\n dietaryFat: { read: ['READ_NUTRITION'], write: ['WRITE_NUTRITION'] },\n}\n\nconst DEFAULT_HEALTH_CONNECT = { read: ['steps', 'heartRate', 'activeEnergy', 'distance', 'workouts'], write: ['workouts'] } as const\n\n/** Every `android.permission.health.*` the app's Health Connect types need. */\nexport function healthConnectPermissions(config: Pick<CraftAndroidConfig, 'healthConnect'>): string[] {\n const read = config.healthConnect?.read ?? DEFAULT_HEALTH_CONNECT.read\n const write = config.healthConnect?.write ?? (config.healthConnect ? [] : DEFAULT_HEALTH_CONNECT.write)\n const permissions = new Set<string>()\n for (const type of read) {\n const known = HEALTH_CONNECT_PERMISSIONS[type as HealthConnectType]\n if (!known) throw new Error(`Unknown Health Connect type: ${type}`)\n known.read.forEach(permission => permissions.add(`android.permission.health.${permission}`))\n }\n for (const type of write) {\n const known = HEALTH_CONNECT_PERMISSIONS[type as HealthConnectType]\n if (!known) throw new Error(`Unknown Health Connect type: ${type}`)\n known.write.forEach(permission => permissions.add(`android.permission.health.${permission}`))\n }\n if (config.healthConnect?.background) permissions.add('android.permission.health.READ_HEALTH_DATA_IN_BACKGROUND')\n if (config.healthConnect?.history) permissions.add('android.permission.health.READ_HEALTH_DATA_HISTORY')\n return [...permissions]\n}\n\nexport interface InitOptions {\n name: string\n packageName?: string\n output: string\n config?: Partial<CraftAndroidConfig>\n /**\n * Where to find the Zig runtime's `<abi>/libcraft.so`.\n *\n * `null` means no runtime regardless of the environment; `undefined` falls\n * through to `CRAFT_ANDROID_RUNTIME`. Same shape as the iOS builder's\n * `runtimeDir`.\n */\n runtimeDir?: string | null\n}\n\nexport interface BuildOptions {\n htmlPath?: string\n nativeBundlePath?: string\n /**\n * Native renderer: the app's icons as vectors (`{ viewBox, strokeWidth,\n * icons: { name: svgBody } }`, keyed by Iconify name and SF Symbol), which\n * `<Icon>`, the tab bar and the bar buttons draw. Without it they fall\n * back to the glyphs Android's fonts have.\n */\n iconsPath?: string\n devServer?: string\n output: string\n release?: boolean\n /**\n * An Android App Bundle (`bundleRelease`, `.aab`), what Google Play takes,\n * rather than an APK. Signed with CRAFT_ANDROID_KEYSTORE,\n * CRAFT_ANDROID_KEYSTORE_PASSWORD, CRAFT_ANDROID_KEY_ALIAS and\n * CRAFT_ANDROID_KEY_PASSWORD when they are set.\n */\n bundle?: boolean\n compile?: boolean\n /** Same meaning as `InitOptions.runtimeDir`; refreshes what init installed. */\n runtimeDir?: string | null\n}\n\nexport interface OpenOptions {\n output: string\n}\n\nexport interface RunOptions {\n device?: string\n output: string\n}\n\nconst DEFAULT_CONFIG: Omit<CraftAndroidConfig, 'appName' | 'packageName'> = {\n renderer: 'web',\n version: '1.0.0',\n versionCode: 1,\n darkMode: true,\n backgroundColor: '#0b1712',\n enableSpeechRecognition: false,\n enableHaptics: false,\n enableShare: false,\n enableCamera: false,\n enableBiometric: false,\n enablePushNotifications: false,\n enableSecureStorage: false,\n enableGeolocation: false,\n enableBackgroundLocation: false,\n enableBluetooth: false,\n enableHealthConnect: false,\n enableKeepAwake: false,\n enableDeepLinks: false,\n urlSchemes: [],\n appLinks: [],\n trustedOrigins: [],\n minSdk: ANDROID_MIN_SDK,\n compileSdk: 36,\n targetSdk: 35,\n}\n\nfunction escapeXml(value: string): string {\n return value\n .replaceAll('&', '&amp;')\n .replaceAll('<', '&lt;')\n .replaceAll('>', '&gt;')\n .replaceAll('\"', '&quot;')\n .replaceAll('\\'', '&apos;')\n}\n\nfunction escapeKotlinString(value: string): string {\n return value\n .replaceAll('\\\\', '\\\\\\\\')\n .replaceAll('\"', '\\\\\"')\n .replaceAll('$', '\\\\$')\n .replaceAll('\\r', '\\\\r')\n .replaceAll('\\n', '\\\\n')\n}\n\nfunction generatedPackageSegment(name: string): string {\n const normalized = name.toLowerCase().replace(/[^a-z0-9_]/g, '')\n if (!normalized) return 'app'\n return /^[a-z_]/.test(normalized) ? normalized : `app${normalized}`\n}\n\nfunction generatedGradleProjectName(name: string): string {\n const invalidCharacters = ['/', '\\\\', ':', '<', '>', '\"', '?', '*', '|']\n const normalized = invalidCharacters.reduce(\n (value, character) => value.replaceAll(character, '-'),\n name,\n ).trim()\n return normalized || 'craft-app'\n}\n\nfunction requireRegularFile(path: string, label: string): void {\n if (!existsSync(path)) throw new Error(`${label} not found: ${path}`)\n if (!statSync(path).isFile()) throw new Error(`${label} must be a file: ${path}`)\n}\n\nfunction containsPath(parent: string, candidate: string): boolean {\n const relativePath = relative(parent, candidate)\n return relativePath === ''\n || (relativePath !== '..' && !relativePath.startsWith(`..${sep}`) && !isAbsolute(relativePath))\n}\n\nfunction validateGoogleServicesFile(path: string, packageName: string): void {\n requireRegularFile(path, 'Google services file')\n\n let document: {\n client?: Array<{\n client_info?: { android_client_info?: { package_name?: string } }\n }>\n }\n try {\n document = JSON.parse(readFileSync(path, 'utf8'))\n }\n catch (error) {\n throw new Error(`Google services file must contain valid JSON: ${path}`, { cause: error })\n }\n\n const matchingClient = document.client?.some((client) => {\n return client.client_info?.android_client_info?.package_name === packageName\n })\n if (!matchingClient) {\n throw new Error(`Google services file has no client for Android package ${packageName}: ${path}`)\n }\n}\n\nfunction androidWebUrl(value: string, field: string): URL {\n let url: URL\n try {\n url = new URL(value)\n }\n catch {\n throw new Error(`Invalid ${field}: ${value}`)\n }\n\n const localDevelopment = url.protocol === 'http:' && LOCAL_DEVELOPMENT_HOSTS.has(url.hostname)\n if ((url.protocol !== 'https:' && !localDevelopment) || url.username || url.password) {\n throw new Error(`${field} must use HTTPS or local HTTP without credentials: ${value}`)\n }\n return url\n}\n\nfunction normalizeAndroidNetworkConfig(config: CraftAndroidConfig): void {\n const schemes = config.urlSchemes?.map(value => value.trim().toLowerCase()) ?? []\n if (schemes.some(value => !/^[a-z][a-z0-9+.-]*$/.test(value))) {\n throw new Error('Android deep-link schemes must be valid URI schemes')\n }\n config.urlSchemes = [...new Set(schemes)]\n config.appLinks = (config.appLinks ?? []).map((link) => {\n const host = link.host.trim().toLowerCase()\n const parsed = new URL(`https://${host}`)\n if (!host || parsed.hostname !== host || parsed.port || parsed.username || parsed.password || parsed.pathname !== '/' || parsed.search || parsed.hash)\n throw new Error('Android App Links need a hostname without a scheme, port or path')\n for (const rule of link.paths ?? []) {\n if (!rule.pattern.startsWith('/') || /[\\x00-\\x1f]/.test(rule.pattern) || (rule.exclude !== undefined && typeof rule.exclude !== 'boolean'))\n throw new Error('Android App Link path rules need an absolute path pattern and a boolean exclude')\n }\n return { ...link, host }\n })\n if (new Set(config.appLinks.map(link => link.host)).size !== config.appLinks.length)\n throw new Error('Android App Link hosts must be unique')\n if (config.enableDeepLinks && config.urlSchemes.length === 0 && config.appLinks.length === 0) {\n throw new Error('Android deep links require at least one URL scheme or an App Link host')\n }\n\n const trustedOrigins = (config.trustedOrigins ?? []).map((value) => {\n return androidWebUrl(value, 'Android trusted origin').origin\n })\n if (config.devServerURL) {\n const devServer = androidWebUrl(config.devServerURL, 'Android dev server URL')\n config.devServerURL = devServer.toString()\n trustedOrigins.push(devServer.origin)\n }\n config.trustedOrigins = [...new Set(trustedOrigins)]\n}\n\nfunction validateAndroidConfig(config: CraftAndroidConfig): void {\n if (!config.appName.trim()) throw new Error('Android app name must not be empty')\n if (config.renderer !== undefined && config.renderer !== 'web' && config.renderer !== 'native') {\n throw new Error(`Unknown Android renderer: ${config.renderer}. Expected web or native.`)\n }\n if (config.appearance !== undefined && !['light', 'dark', 'system'].includes(config.appearance)) {\n throw new Error(`Unknown Android appearance: ${config.appearance}. Expected light, dark, or system.`)\n }\n\n const packageSegments = config.packageName.split('.')\n if (packageSegments.length < 2\n || packageSegments.some(segment => !/^[A-Za-z_][A-Za-z0-9_]*$/.test(segment) || KOTLIN_KEYWORDS.has(segment))) {\n throw new Error(`Invalid Android package name: ${config.packageName}`)\n }\n\n if (!/^#(?:[\\dA-F]{3,4}|[\\dA-F]{6}|[\\dA-F]{8})$/i.test(config.backgroundColor ?? '')) {\n throw new Error(`Invalid Android background color: ${config.backgroundColor}`)\n }\n\n if (config.backgroundColorDark !== undefined && !config.backgroundColorDark.match(/^#[\\dA-F]{3,4}$|^#[\\dA-F]{6}$|^#[\\dA-F]{8}$/i)) {\n throw new Error(`Invalid Android dark background color: ${config.backgroundColorDark}`)\n }\n\n if (config.mapsApiKey !== undefined && !config.mapsApiKey.trim()) {\n throw new Error('Android mapsApiKey must not be empty')\n }\n\n for (const [name, value] of [\n ['versionCode', config.versionCode],\n ['minSdk', config.minSdk],\n ['compileSdk', config.compileSdk],\n ['targetSdk', config.targetSdk],\n ] as const) {\n if (!Number.isInteger(value) || Number(value) < 1) {\n throw new Error(`Android ${name} must be a positive integer`)\n }\n }\n\n if (Number(config.minSdk) < ANDROID_MIN_SDK) {\n throw new Error(`Android minSdk must be at least ${ANDROID_MIN_SDK} (Android 8.0) for the Craft runtime`)\n }\n if (Number(config.minSdk) > Number(config.targetSdk)) {\n throw new Error('Android minSdk must not exceed targetSdk')\n }\n if (Number(config.targetSdk) > Number(config.compileSdk)) {\n throw new Error('Android targetSdk must not exceed compileSdk')\n }\n}\n\nfunction normalizeNativeRoutePath(raw: unknown): string | null {\n if (typeof raw !== 'string') return null\n let path = raw\n const cut = path.search(/[?#]/)\n if (cut >= 0) path = path.slice(0, cut)\n if (!path.startsWith('/')) return null\n path = path.replace(/\\/{2,}/g, '/')\n if (path.length > 1 && path.endsWith('/')) path = path.slice(0, -1)\n return path\n}\n\nfunction nativeRouteSegments(path: string): string[] {\n return path === '/' ? [] : path.slice(1).split('/')\n}\n\nfunction matchesNativeRoute(nativeScreens: Record<string, string> | undefined, raw: string): boolean {\n const path = normalizeNativeRoutePath(raw)\n if (path === null) return false\n const parts = nativeRouteSegments(path)\n return Object.entries(nativeScreens ?? {}).some(([pattern, screen]) => {\n if (!screen) return false\n const normalized = normalizeNativeRoutePath(pattern)\n if (normalized === null) return false\n const route = nativeRouteSegments(normalized)\n for (let index = 0; index < route.length; index++) {\n const segment = route[index]\n if (segment === '*') return index === route.length - 1\n if (index >= parts.length) return false\n if (segment.startsWith(':')) {\n if (!parts[index]) return false\n }\n else if (segment !== parts[index]) return false\n }\n return route.length === parts.length\n })\n}\n\nfunction nativeStartPath(config: CraftAndroidConfig): string {\n if (!config.devServerURL) return '/'\n try {\n const url = new URL(config.devServerURL)\n return `${url.pathname || '/'}${url.search}`\n }\n catch {\n return '/'\n }\n}\n\nfunction validateNativeRouteConfig(config: CraftAndroidConfig): void {\n const screens = Object.entries(config.nativeScreens ?? {})\n for (const [pattern, screen] of screens) {\n if (normalizeNativeRoutePath(pattern) !== pattern || /[?#]/.test(pattern))\n throw new Error(`nativeScreens path \"${pattern}\" must be an absolute path with no query, hash or trailing slash.`)\n if (nativeRouteSegments(normalizeNativeRoutePath(pattern) ?? '/').slice(0, -1).includes('*'))\n throw new Error(`nativeScreens path \"${pattern}\" may only end in *.`)\n if (typeof screen !== 'string' || !/^[A-Za-z_$][\\w$-]*$/.test(screen))\n throw new Error(`nativeScreens screen \"${String(screen)}\" for \"${pattern}\" must be a screen name from the native bundle.`)\n }\n\n const ids = new Set<string>()\n for (const tab of config.tabs ?? []) {\n if (!tab || typeof tab.id !== 'string' || !tab.id || typeof tab.title !== 'string' || !tab.title)\n throw new Error('Each tab needs an id (its root path) and a title.')\n if (ids.has(tab.id)) throw new Error(`Tab \"${tab.id}\" is listed twice.`)\n ids.add(tab.id)\n }\n\n if (config.renderer === 'native' && screens.length > 0) {\n for (const id of ids) {\n if (!matchesNativeRoute(config.nativeScreens, id))\n throw new Error(`Tab \"${id}\" is not a nativeScreens path; with renderer: \"native\" every tab must be a native screen.`)\n }\n const start = nativeStartPath(config)\n if (!matchesNativeRoute(config.nativeScreens, start))\n throw new Error(`The start path \"${start}\" (from devServerURL) is not a nativeScreens path; with renderer: \"native\" the app must start on a native screen.`)\n }\n\n if (config.nativeApp !== undefined) {\n if (typeof config.nativeApp !== 'string' || !/^[A-Za-z_$][\\w$-]*$/.test(config.nativeApp))\n throw new Error(`nativeApp \"${String(config.nativeApp)}\" must be a route name from the native bundle.`)\n if (config.renderer !== 'native' && screens.length === 0)\n throw new Error('nativeApp runs in the native bundle; it needs nativeScreens or renderer: \"native\".')\n }\n}\n\nfunction writeAndroidConfig(output: string, config: CraftAndroidConfig): void {\n writeFileSync(join(output, 'craft.config.json'), JSON.stringify(config, null, 2))\n\n // Generator-only paths can reveal the developer machine's directory layout\n // and have no meaning to the installed app. Keep them in the project config\n // but never package them into the APK's assets.\n const runtimeConfig = { ...config }\n delete runtimeConfig.appIconPath\n delete runtimeConfig.googleServicesFile\n writeFileSync(\n join(output, 'app/src/main/assets/craft.config.json'),\n JSON.stringify(runtimeConfig, null, 2),\n )\n}\n\nexport function syncAndroidWebAssets(source: string, output: string): void {\n const sourcePath = resolve(source)\n if (!existsSync(sourcePath)) throw new Error(`Web asset path not found: ${source}`)\n const sourceStat = statSync(sourcePath)\n if (!sourceStat.isDirectory() && !sourceStat.isFile()) {\n throw new Error(`Web asset path must be a file or directory: ${source}`)\n }\n\n const assetsDir = resolve(output, 'app/src/main/assets')\n if (containsPath(sourcePath, assetsDir) || containsPath(assetsDir, sourcePath)) {\n throw new Error(`Web asset source must not overlap generated asset directory: ${source}`)\n }\n if (sourceStat.isDirectory()) {\n requireRegularFile(join(sourcePath, 'index.html'), 'Web asset directory entry point')\n }\n\n const configPath = join(assetsDir, 'craft.config.json')\n const config = existsSync(configPath) ? readFileSync(configPath) : undefined\n rmSync(assetsDir, { recursive: true, force: true })\n mkdirSync(assetsDir, { recursive: true })\n if (sourceStat.isDirectory()) cpSync(sourcePath, assetsDir, { recursive: true })\n else cpSync(sourcePath, join(assetsDir, 'index.html'))\n if (!existsSync(join(assetsDir, 'index.html'))) throw new Error(`Web asset directory must contain index.html: ${source}`)\n if (config) writeFileSync(configPath, config)\n}\n\nexport function renderAndroidPermissions(config: CraftAndroidConfig): string {\n const permissions = new Set(['android.permission.INTERNET', 'android.permission.ACCESS_NETWORK_STATE'])\n if (config.enableSpeechRecognition) permissions.add('android.permission.RECORD_AUDIO')\n if (config.enableCamera) permissions.add('android.permission.CAMERA')\n if (config.enableHaptics) permissions.add('android.permission.VIBRATE')\n if (config.enableBiometric) permissions.add('android.permission.USE_BIOMETRIC')\n if (config.enableGeolocation || config.enableBackgroundLocation) {\n permissions.add('android.permission.ACCESS_FINE_LOCATION')\n permissions.add('android.permission.ACCESS_COARSE_LOCATION')\n }\n if (config.enableBackgroundLocation) {\n permissions.add('android.permission.ACCESS_BACKGROUND_LOCATION')\n permissions.add('android.permission.FOREGROUND_SERVICE')\n permissions.add('android.permission.FOREGROUND_SERVICE_LOCATION')\n }\n if (config.enablePushNotifications) permissions.add('android.permission.POST_NOTIFICATIONS')\n if (config.enableKeepAwake) permissions.add('android.permission.WAKE_LOCK')\n if (config.enableHealthConnect) healthConnectPermissions(config).forEach(permission => permissions.add(permission))\n // A background refresh (WorkManager) after a restart.\n if (config.renderer === 'native' && config.backgroundRefresh?.enabled) permissions.add('android.permission.RECEIVE_BOOT_COMPLETED')\n const bluetooth = config.enableBluetooth ? [\n ' <uses-permission android:name=\"android.permission.BLUETOOTH\" android:maxSdkVersion=\"30\" />',\n ' <uses-permission android:name=\"android.permission.BLUETOOTH_ADMIN\" android:maxSdkVersion=\"30\" />',\n ' <uses-permission android:name=\"android.permission.BLUETOOTH_SCAN\" android:usesPermissionFlags=\"neverForLocation\" />',\n ' <uses-permission android:name=\"android.permission.BLUETOOTH_CONNECT\" />',\n ...(config.enableGeolocation ? [] : [' <uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\" android:maxSdkVersion=\"30\" />']),\n ].join('\\n') : ''\n return [bluetooth, ...[...permissions].map(permission => ` <uses-permission android:name=\"${permission}\" />`)].filter(Boolean).join('\\n')\n}\n\n/**\n * The native renderer's own sources: the app around the screens (routes,\n * stores, bars), its services (dialogs, browser, share, push, Health\n * Connect), background refresh, and Firebase messaging when push is on.\n */\nfunction writeNativeShellSources(output: string, packagePath: string, packageName: string, config: CraftAndroidConfig): void {\n const javaDir = join(output, 'app/src/main/java', packagePath)\n const write = (template: string, file: string, replace: (source: string) => string = source => source): void => {\n const source = readFileSync(join(TEMPLATES_DIR, template), 'utf-8').replace(/\\{\\{PACKAGE_NAME\\}\\}/g, packageName)\n writeFileSync(join(javaDir, file), replace(source))\n }\n write('CraftBluetooth.kt.template', 'CraftBluetooth.kt')\n write('CraftNativeShell.kt.template', 'CraftNativeShell.kt')\n write('CraftNativeImageCache.kt.template', 'CraftNativeImageCache.kt')\n write('CraftNativeBackground.kt.template', 'CraftNativeBackground.kt')\n write(config.enableHealthConnect ? 'CraftNativeHealth.kt.template' : 'CraftNativeHealthStub.kt.template', 'CraftNativeHealth.kt')\n write('CraftNativeServices.kt.template', 'CraftNativeServices.kt', source => source\n .replace('{{NATIVE_PUSH_IMPORTS}}', config.enablePushNotifications ? 'import com.google.firebase.messaging.FirebaseMessaging' : '')\n .replace('{{NATIVE_PUSH_REGISTER}}', config.enablePushNotifications\n ? ` try {\n FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->\n val token = if (task.isSuccessful) task.result else null\n if (token.isNullOrBlank()) done(failure(\"PUSH_UNAVAILABLE\", task.exception?.message ?: \"Firebase Cloud Messaging gave no token\"))\n else {\n context.getSharedPreferences(\"craft-native-push\", Context.MODE_PRIVATE).edit().putString(\"token\", token).apply()\n done(success(token))\n }\n }\n } catch (error: Exception) {\n done(failure(\"PUSH_UNAVAILABLE\", error.message ?: \"Firebase Cloud Messaging is not configured\"))\n }`\n : ' done(failure(\"CAPABILITY_DISABLED\", \"Push notifications are disabled\"))'))\n if (config.enablePushNotifications) write('CraftNativeMessagingService.kt.template', 'CraftNativeMessagingService.kt')\n else rmSync(join(javaDir, 'CraftNativeMessagingService.kt'), { force: true })\n // Files a screen shares (craft.files' copies, in the cache) go to other apps through this provider.\n mkdirSync(join(output, 'app/src/main/res/xml'), { recursive: true })\n writeFileSync(join(output, 'app/src/main/res/xml/craft_file_paths.xml'), `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<paths>\n <cache-path name=\"craft-files\" path=\"craft-files/\" />\n <files-path name=\"craft-media\" path=\"craft-media/\" />\n</paths>\n`)\n}\n\n/** What the native renderer adds to the manifest's application element. */\nfunction renderNativeShellManifest(config: CraftAndroidConfig): string {\n const parts = [\n ' <receiver android:name=\".CraftNativeNotificationReceiver\" android:exported=\"false\" />',\n ` <provider\n android:name=\"androidx.core.content.FileProvider\"\n android:authorities=\"${config.packageName}.craftfiles\"\n android:exported=\"false\"\n android:grantUriPermissions=\"true\">\n <meta-data android:name=\"android.support.FILE_PROVIDER_PATHS\" android:resource=\"@xml/craft_file_paths\" />\n </provider>`,\n ]\n if (config.enablePushNotifications) {\n parts.push(` <service android:name=\".CraftNativeMessagingService\" android:exported=\"false\">\n <intent-filter>\n <action android:name=\"com.google.firebase.MESSAGING_EVENT\" />\n </intent-filter>\n </service>`)\n }\n if (config.enableHealthConnect) {\n // Android 14 and later open the app's privacy policy from Health\n // Connect's permission screen through this alias.\n parts.push(` <activity-alias\n android:name=\"ViewPermissionUsageActivity\"\n android:exported=\"true\"\n android:targetActivity=\".MainActivity\"\n android:permission=\"android.permission.START_VIEW_PERMISSION_USAGE\">\n <intent-filter>\n <action android:name=\"android.intent.action.VIEW_PERMISSION_USAGE\" />\n <category android:name=\"android.intent.category.HEALTH_PERMISSIONS\" />\n </intent-filter>\n </activity-alias>`)\n }\n return parts.join('\\n')\n}\n\nexport function renderAndroidDeepLinks(config: CraftAndroidConfig): string {\n if (!config.enableDeepLinks) return ''\n const schemes = [...new Set(config.urlSchemes?.map(value => value.trim()).filter(value => /^[a-z][a-z0-9+.-]*$/i.test(value)) ?? [])]\n const custom = schemes.map(scheme => ` <intent-filter>\n <action android:name=\"android.intent.action.VIEW\" />\n <category android:name=\"android.intent.category.DEFAULT\" />\n <category android:name=\"android.intent.category.BROWSABLE\" />\n <data android:scheme=\"${scheme}\" />\n </intent-filter>`).join('\\n')\n const verified = (config.appLinks ?? []).map(link => {\n if (!/^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/i.test(link.host)) throw new Error('Invalid Android App Link host')\n return ` <intent-filter android:autoVerify=\"true\">\n <action android:name=\"android.intent.action.VIEW\" />\n <category android:name=\"android.intent.category.DEFAULT\" />\n <category android:name=\"android.intent.category.BROWSABLE\" />\n <data android:scheme=\"https\" android:host=\"${link.host}\" />\n </intent-filter>`\n }).join('\\n')\n return [custom, verified].filter(Boolean).join('\\n')\n}\n\n/**\n * Initialize a new Android project\n */\n/**\n * The ABI directories a runtime directory is expected to hold.\n *\n * These are Android's own names for the two architectures craft builds, and\n * they are also `zig build build-android-all`'s output layout — `zig-out/android/`\n * already contains `arm64-v8a/libcraft.so` and `x86_64/libcraft.so`, which is\n * exactly the shape `app/src/main/jniLibs/` wants. So the install below is a\n * copy and not a rearrangement.\n *\n * x86_64 is not optional-in-practice: every Android emulator anyone tests on\n * is x86_64, so a runtime shipped for arm64 alone is one that never runs in\n * CI and never runs on a developer's machine either.\n */\nconst RUNTIME_ABIS = ['arm64-v8a', 'x86_64'] as const\n\n/**\n * Where the runtime directory comes from when the caller does not say.\n *\n * The same shape as `CRAFT_BIN` and the iOS builder's `CRAFT_IOS_RUNTIME`: an\n * explicit override for the monorepo dev loop, not a lookup path. Shipping the\n * runtime to real apps means putting these libraries in the pantry package\n * beside the `craft` binary, which is a distribution decision this function\n * does not make.\n */\nexport function resolveRuntimeDir(override?: string | null): string | null {\n if (override === null) return null\n const dir = override ?? process.env.CRAFT_ANDROID_RUNTIME\n if (!dir) return null\n if (!existsSync(dir)) {\n const source = override === undefined ? 'CRAFT_ANDROID_RUNTIME points at' : 'runtimeDir is'\n throw new Error(`${source} ${dir}, which does not exist.`)\n }\n return dir\n}\n\n/**\n * Copy the Zig runtime into the generated project as\n * `app/src/main/jniLibs/<abi>/libcraft.so`.\n *\n * That path is AGP's default `jniLibs.srcDirs`, so nothing in the Gradle\n * templates has to know about it — the library is packaged into the APK and\n * `System.loadLibrary(\"craft\")` finds it because the file is named for the\n * `craft` it asks for.\n *\n * Until this existed, nothing put the library anywhere. `CraftNative` caught\n * the `UnsatisfiedLinkError`, set `isAvailable = false`, and every action fell\n * through to the Kotlin shim — by design, so an app with no runtime still\n * works, which is also why nobody noticed that *every* generated app was in\n * that state and the whole Android half of the Zig bridge had never run.\n *\n * Returns true when a runtime was installed.\n */\nexport function installRuntime(output: string, runtimeDir: string): boolean {\n // Every ABI resolved before anything is written, so a half-populated\n // runtime directory cannot leave a project carrying one architecture and\n // claiming both. A single-ABI APK installs fine and then fails to load on\n // the other kind of device, which is the break that only shows up on\n // somebody else's phone.\n const found = RUNTIME_ABIS.map(abi => ({ abi, source: join(runtimeDir, abi, 'libcraft.so') }))\n .filter(entry => existsSync(entry.source))\n\n if (found.length === 0) {\n throw new Error(\n `${runtimeDir} has no <abi>/libcraft.so for any of ${RUNTIME_ABIS.join(', ')}. `\n + 'Run `zig build build-android-all -Doptimize=ReleaseSafe` in packages/zig and point at its zig-out/android.',\n )\n }\n\n if (found.length < RUNTIME_ABIS.length) {\n const missing = RUNTIME_ABIS.filter(abi => !found.some(entry => entry.abi === abi))\n console.warn(\n ` ⚠ only ${found.map(entry => entry.abi).join(', ')} was found; ${missing.join(', ')} is missing. `\n + 'The app will fall back to the Kotlin shim on those devices.',\n )\n }\n\n const dest = join(output, 'app/src/main/jniLibs')\n // Wiped rather than merged, so an ABI dropped from a later build does not\n // linger in the project as a stale library the APK still ships.\n rmSync(dest, { force: true, recursive: true })\n\n for (const { abi, source } of found) {\n const abiDir = join(dest, abi)\n mkdirSync(abiDir, { recursive: true })\n cpSync(source, join(abiDir, 'libcraft.so'))\n }\n\n return true\n}\n\nexport async function init(options: InitOptions): Promise<void> {\n const { name, packageName, output } = options\n\n console.log(`\\n⚡ Initializing Craft Android project: ${name}`)\n console.log(` Output: ${output}\\n`)\n\n // Generate package name from app name if not provided\n const finalPackageName = packageName || `com.craft.${generatedPackageSegment(name)}`\n const packagePath = finalPackageName.replace(/\\./g, '/')\n\n // Command-level identity always wins over duplicate fields in `config`.\n // Normalize implied capabilities before validation so craft.config.json and\n // the generated Gradle source describe the same effective project.\n // Undefined keeps the default instead of erasing it (see the iOS builder).\n const given = Object.fromEntries(Object.entries(options.config ?? {}).filter(([, value]) => value !== undefined))\n const config: CraftAndroidConfig = {\n ...DEFAULT_CONFIG,\n ...given,\n appName: name,\n packageName: finalPackageName,\n }\n if (config.enableBackgroundLocation) config.enableGeolocation = true\n if (config.enableHealthConnect) {\n config.compileSdk = Math.max(config.compileSdk ?? 36, 36)\n config.enableHealthConnectBackground = Boolean(config.healthConnect?.background)\n config.enableHealthConnectHistory = Boolean(config.healthConnect?.history)\n healthConnectPermissions(config)\n }\n normalizeAndroidNetworkConfig(config)\n validateAndroidConfig(config)\n validateNativeRouteConfig(config)\n if (config.enablePushNotifications && !config.googleServicesFile) {\n throw new Error('Android push notifications require a googleServicesFile')\n }\n if (config.googleServicesFile) validateGoogleServicesFile(config.googleServicesFile, finalPackageName)\n if (config.appIconPath) requireRegularFile(config.appIconPath, 'App icon')\n const appIconExtension = config.appIconPath ? extname(config.appIconPath).toLowerCase() : undefined\n if (config.appIconPath && !['.gif', '.jpg', '.png', '.webp'].includes(appIconExtension ?? '')) {\n throw new Error(`Unsupported Android app icon format: ${appIconExtension || '(none)'}`)\n }\n\n // Create directory structure\n const dirs = [\n output,\n join(output, 'app/src/main/java', packagePath),\n join(output, 'app/src/main/res/layout'),\n join(output, 'app/src/main/res/values'),\n join(output, 'app/src/main/res/values-night'),\n join(output, 'app/src/main/res/drawable'),\n join(output, 'app/src/main/assets'),\n join(output, 'gradle/wrapper'),\n ]\n\n if (existsSync(output) && !statSync(output).isDirectory()) {\n throw new Error(`Android project output must be a directory: ${output}`)\n }\n\n for (const dir of dirs) {\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true })\n }\n }\n\n // Create craft.config.json\n writeAndroidConfig(output, config)\n\n const hasGoogleServices = Boolean(config.googleServicesFile)\n if (config.googleServicesFile) {\n cpSync(config.googleServicesFile, join(output, 'app/google-services.json'))\n }\n\n // Copy templates\n const mainActivityTemplate = readFileSync(join(\n TEMPLATES_DIR,\n config.renderer === 'native' ? 'MainActivityNative.kt.template' : 'MainActivity.kt.template',\n ), 'utf-8')\n const mainActivity = mainActivityTemplate\n .replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName)\n .replace(/\\{\\{APP_NAME\\}\\}/g, name)\n\n writeFileSync(join(output, 'app/src/main/java', packagePath, 'MainActivity.kt'), mainActivity)\n writeFileSync(\n join(output, 'app/src/main/java', packagePath, 'CraftAppLinks.kt'),\n readFileSync(join(TEMPLATES_DIR, 'CraftAppLinks.kt.template'), 'utf8').replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName),\n )\n // Both renderers: crash reporting does not depend on how screens are drawn.\n writeFileSync(\n join(output, 'app/src/main/java', packagePath, 'CraftCrashReporter.kt'),\n readFileSync(join(TEMPLATES_DIR, 'CraftCrashReporter.kt.template'), 'utf-8')\n .replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName)\n .replace(/\\{\\{CRAFT_VERSION\\}\\}/g, CRAFT_ANDROID_VERSION),\n )\n if (config.renderer === 'native') {\n const mutationTemplate = readFileSync(join(TEMPLATES_DIR, 'CraftNativeMutation.kt.template'), 'utf-8')\n const flatListTemplate = readFileSync(join(TEMPLATES_DIR, 'CraftNativeFlatList.kt.template'), 'utf-8')\n const refreshableFlatListTemplate = readFileSync(join(TEMPLATES_DIR, 'CraftNativeRefreshableFlatList.kt.template'), 'utf-8')\n const capabilityTemplate = readFileSync(join(TEMPLATES_DIR, 'CraftNativeCapabilities.kt.template'), 'utf-8')\n const notificationTemplate = readFileSync(join(TEMPLATES_DIR, 'CraftNativeNotificationReceiver.kt.template'), 'utf-8')\n writeFileSync(\n join(output, 'app/src/main/java', packagePath, 'CraftNativeMutation.kt'),\n mutationTemplate.replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName),\n )\n writeFileSync(\n join(output, 'app/src/main/java', packagePath, 'CraftNativeFlatList.kt'),\n flatListTemplate.replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName),\n )\n writeFileSync(\n join(output, 'app/src/main/java', packagePath, 'CraftNativeRefreshableFlatList.kt'),\n refreshableFlatListTemplate.replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName),\n )\n writeFileSync(\n join(output, 'app/src/main/java', packagePath, 'CraftNativeCapabilities.kt'),\n capabilityTemplate.replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName),\n )\n writeFileSync(\n join(output, 'app/src/main/java', packagePath, 'CraftNativeNotificationReceiver.kt'),\n notificationTemplate.replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName),\n )\n writeNativeShellSources(output, packagePath, finalPackageName, config)\n }\n\n // Create CraftBridge.kt\n const craftBridgeTemplate = readFileSync(join(TEMPLATES_DIR, 'CraftBridge.kt.template'), 'utf-8')\n const craftBridge = craftBridgeTemplate\n .replace(/\\{\\{PROMISE_RUNTIME\\}\\}/g, () => renderAndroidPromiseRuntime(' '))\n .replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName)\n .replace(/\\{\\{ENABLE_SPEECH\\}\\}/g, String(Boolean(config.enableSpeechRecognition)))\n .replace(/\\{\\{ENABLE_HAPTICS\\}\\}/g, String(Boolean(config.enableHaptics)))\n .replace(/\\{\\{ENABLE_SHARE\\}\\}/g, String(Boolean(config.enableShare)))\n .replace(/\\{\\{ENABLE_CAMERA\\}\\}/g, String(Boolean(config.enableCamera)))\n .replace(/\\{\\{ENABLE_BIOMETRIC\\}\\}/g, String(Boolean(config.enableBiometric)))\n .replace(/\\{\\{ENABLE_PUSH\\}\\}/g, String(Boolean(config.enablePushNotifications)))\n .replace(/\\{\\{ENABLE_SECURE_STORAGE\\}\\}/g, String(Boolean(config.enableSecureStorage)))\n .replace(/\\{\\{ENABLE_GEOLOCATION\\}\\}/g, String(Boolean(config.enableGeolocation)))\n .replace(/\\{\\{ENABLE_BACKGROUND_LOCATION\\}\\}/g, String(Boolean(config.enableBackgroundLocation)))\n .replace(/\\{\\{ENABLE_KEEP_AWAKE\\}\\}/g, String(Boolean(config.enableKeepAwake)))\n .replace(/\\{\\{ENABLE_DEEP_LINKS\\}\\}/g, String(Boolean(config.enableDeepLinks)))\n .replace(/\\{\\{ENABLE_HEALTH_CONNECT\\}\\}/g, String(Boolean(config.enableHealthConnect)))\n .replace(/\\{\\{FIREBASE_IMPORT\\}\\}/g, config.enablePushNotifications ? 'import com.google.firebase.messaging.FirebaseMessaging' : '')\n .replace(/\\{\\{REGISTER_PUSH_IMPLEMENTATION\\}\\}/g, config.enablePushNotifications\n ? `activity.runOnUiThread {\n if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU\n && ContextCompat.checkSelfPermission(activity, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {\n ActivityCompat.requestPermissions(activity, arrayOf(Manifest.permission.POST_NOTIFICATIONS), 4204)\n }\n try {\n FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->\n val token = if (task.isSuccessful) task.result else null\n val callback = if (token.isNullOrBlank()) \"window._craftPushReject\" else \"window._craftPushResolve\"\n val payload = JSONObject.quote(token ?: task.exception?.message ?: \"Firebase Cloud Messaging is not configured\")\n evaluatePromiseJavascript(\"$callback && $callback($payload)\")\n }\n } catch (error: Exception) {\n val payload = JSONObject.quote(error.message ?: \"Firebase Cloud Messaging is not configured\")\n evaluatePromiseJavascript(\"window._craftPushReject && window._craftPushReject($payload)\")\n }\n }`\n : `evaluatePromiseJavascript(\n \"window._craftPushReject && window._craftPushReject('Push notifications are disabled')\"\n )`)\n writeFileSync(join(output, 'app/src/main/java', packagePath, 'CraftBridge.kt'), craftBridge)\n\n // CraftNative goes to a fixed package, not the app's. The prebuilt\n // libcraft.so binds its natives by class name in JNI_OnLoad, and it cannot\n // know a name chosen here — so this one file is deliberately not templated\n // and deliberately not under packagePath.\n const craftNative = readFileSync(join(TEMPLATES_DIR, 'CraftNative.kt.template'), 'utf-8')\n const nativeDir = join(output, 'app/src/main/java/com/craft/runtime')\n mkdirSync(nativeDir, { recursive: true })\n writeFileSync(join(nativeDir, 'CraftNative.kt'), craftNative)\n // The bridge and native holder both reference the recording store/service,\n // even when the service is not registered in the manifest. Keep these\n // support types in the same fixed package as CraftNative so every generated\n // app compiles regardless of its chosen package name or feature flags.\n const serviceTemplate = readFileSync(join(TEMPLATES_DIR, 'LocationRecordingService.kt.template'), 'utf-8')\n writeFileSync(join(nativeDir, 'LocationRecordingService.kt'), serviceTemplate)\n const healthTemplate = readFileSync(join(\n TEMPLATES_DIR,\n config.enableHealthConnect ? 'CraftHealthConnect.kt.template' : 'CraftHealthConnectStub.kt.template',\n ), 'utf-8')\n writeFileSync(\n join(output, 'app/src/main/java', packagePath, 'CraftHealthConnect.kt'),\n healthTemplate.replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName),\n )\n // Create AndroidManifest.xml\n const manifestTemplate = readFileSync(join(TEMPLATES_DIR, 'AndroidManifest.xml.template'), 'utf-8')\n const manifest = manifestTemplate\n .replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName)\n .replace(/\\{\\{APP_NAME\\}\\}/g, name)\n .replace(/\\{\\{PERMISSIONS\\}\\}/g, renderAndroidPermissions(config))\n // Plain HTTP only for a local dev server, or an app that talks to the\n // person's own devices on the LAN (which have no public certificate).\n .replace(/\\{\\{USES_CLEARTEXT\\}\\}/g, config.devServerURL?.startsWith('http://') || config.enableLocalNetwork ? 'true' : 'false')\n .replace(/\\{\\{DEEP_LINK_INTENT_FILTERS\\}\\}/g, renderAndroidDeepLinks(config))\n .replace(/\\{\\{BACKGROUND_SERVICE\\}\\}/g, config.enableBackgroundLocation\n ? ' <service android:name=\"com.craft.runtime.LocationRecordingService\" android:exported=\"false\" android:foregroundServiceType=\"location\" android:stopWithTask=\"false\" />'\n : '')\n .replace(/\\{\\{NATIVE_NOTIFICATION_RECEIVER\\}\\}/g, config.renderer === 'native'\n ? renderNativeShellManifest(config)\n : '')\n // Starts the crash reporter before Application.onCreate, for every way\n // the process starts (CraftCrashReporter.kt). Inert without a key.\n .replace(/\\{\\{CRASH_REPORTER_PROVIDER\\}\\}/g, ` <provider android:name=\".CraftCrashReporterInit\" android:authorities=\"\\${applicationId}.craft-crash\" android:exported=\"false\" android:initOrder=\"1000\" />`)\n .replace(/\\{\\{GOOGLE_MAPS_API_KEY\\}\\}/g, config.mapsApiKey\n ? ` <meta-data android:name=\"com.google.android.geo.API_KEY\" android:value=\"${escapeXml(config.mapsApiKey)}\" />`\n : '')\n .replace(/\\{\\{HEALTH_CONNECT_QUERIES\\}\\}/g, config.enableHealthConnect\n ? ' <queries>\\n <package android:name=\"com.google.android.apps.healthdata\" />\\n </queries>'\n : '')\n .replace(/\\{\\{HEALTH_CONNECT_RATIONALE\\}\\}/g, config.enableHealthConnect\n ? ` <intent-filter>\n <action android:name=\"androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE\" />\n </intent-filter>${config.renderer === 'native' ? '' : `\n <intent-filter>\n <action android:name=\"android.intent.action.VIEW_PERMISSION_USAGE\" />\n <category android:name=\"android.intent.category.HEALTH_PERMISSIONS\" />\n </intent-filter>`}`\n : '')\n\n writeFileSync(join(output, 'app/src/main/AndroidManifest.xml'), manifest)\n\n // Create build.gradle.kts (project level)\n const projectGradleTemplate = readFileSync(join(TEMPLATES_DIR, 'build.gradle.kts.project.template'), 'utf-8')\n writeFileSync(join(output, 'build.gradle.kts'), projectGradleTemplate\n .replace(/\\{\\{GOOGLE_SERVICES_PLUGIN\\}\\}/g, hasGoogleServices ? ' id(\"com.google.gms.google-services\") version \"4.4.2\" apply false' : ''))\n\n // Create build.gradle.kts (app level)\n const appGradleTemplate = readFileSync(join(TEMPLATES_DIR, 'build.gradle.kts.app.template'), 'utf-8')\n const appGradle = appGradleTemplate\n .replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName)\n .replace(/\\{\\{VERSION_NAME\\}\\}/g, escapeKotlinString(config.version || '1.0.0'))\n .replace(/\\{\\{VERSION_CODE\\}\\}/g, String(config.versionCode || 1))\n .replace(/\\{\\{MIN_SDK\\}\\}/g, String(config.minSdk ?? ANDROID_MIN_SDK))\n .replace(/\\{\\{COMPILE_SDK\\}\\}/g, String(config.compileSdk || 36))\n .replace(/\\{\\{TARGET_SDK\\}\\}/g, String(config.targetSdk || 35))\n .replace(/\\{\\{GOOGLE_SERVICES_PLUGIN\\}\\}/g, hasGoogleServices ? ' id(\"com.google.gms.google-services\")' : '')\n .replace(/\\{\\{FIREBASE_MESSAGING_DEPENDENCY\\}\\}/g, config.enablePushNotifications\n ? ' implementation(\"com.google.firebase:firebase-messaging:24.1.0\")'\n : '')\n .replace(/\\{\\{REFRESH_LAYOUT_DEPENDENCY\\}\\}/g, config.renderer === 'native'\n ? ' implementation(\"androidx.swiperefreshlayout:swiperefreshlayout:1.1.0\")'\n : '')\n .replace(/\\{\\{GOOGLE_MAPS_DEPENDENCY\\}\\}/g, config.renderer === 'native'\n ? ' // Native STX Map overlays and trail routes\\n implementation(\"com.google.android.gms:play-services-maps:18.2.0\")'\n : '')\n .replace(/\\{\\{HEALTH_CONNECT_DEPENDENCIES\\}\\}/g, config.enableHealthConnect\n ? ` implementation(\"androidx.health.connect:connect-client:1.1.0\")\n implementation(\"org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1\")`\n : '')\n .replace(/\\{\\{JAVASCRIPT_ENGINE_DEPENDENCY\\}\\}/g, config.renderer === 'native'\n ? ` implementation(\"androidx.javascriptengine:javascriptengine:1.1.0\")\n // Native background refresh (WorkManager).\n implementation(\"androidx.work:work-runtime-ktx:2.9.1\")`\n : '')\n\n writeFileSync(join(output, 'app/build.gradle.kts'), appGradle)\n const proguardTemplate = readFileSync(join(TEMPLATES_DIR, 'proguard-rules.pro.template'), 'utf-8')\n writeFileSync(\n join(output, 'app/proguard-rules.pro'),\n proguardTemplate.replace(/\\{\\{PACKAGE_NAME\\}\\}/g, finalPackageName),\n )\n\n // Create settings.gradle.kts\n const settingsTemplate = readFileSync(join(TEMPLATES_DIR, 'settings.gradle.kts.template'), 'utf-8')\n const settings = settingsTemplate.replace(\n /\\{\\{APP_NAME\\}\\}/g,\n escapeKotlinString(generatedGradleProjectName(name)),\n )\n writeFileSync(join(output, 'settings.gradle.kts'), settings)\n\n // Create gradle.properties\n const gradleProperties = `org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8\nandroid.useAndroidX=true\nkotlin.code.style=official\nandroid.nonTransitiveRClass=true\n`\n writeFileSync(join(output, 'gradle.properties'), gradleProperties)\n\n // Create local.properties placeholder\n writeFileSync(join(output, 'local.properties'), '# SDK location will be set by Android Studio\\n')\n\n // Create gradle wrapper properties\n const gradleWrapperProps = `distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\\\://services.gradle.org/distributions/gradle-8.11.1-bin.zip\nnetworkTimeout=10000\nvalidateDistributionUrl=true\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n`\n writeFileSync(join(output, 'gradle/wrapper/gradle-wrapper.properties'), gradleWrapperProps)\n\n // Create res/values files\n const stringsXml = `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"app_name\">${escapeXml(name)}</string>\n</resources>\n`\n writeFileSync(join(output, 'app/src/main/res/values/strings.xml'), stringsXml)\n\n const colorsXml = `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <color name=\"primary\">#1a1a2e</color>\n <color name=\"primary_dark\">#16162b</color>\n <color name=\"accent\">#4ade80</color>\n <color name=\"background\">${config.backgroundColor}</color>\n</resources>\n`\n writeFileSync(join(output, 'app/src/main/res/values/colors.xml'), colorsXml)\n const darkColorsPath = join(output, 'app/src/main/res/values-night/colors.xml')\n if (config.backgroundColorDark) {\n writeFileSync(darkColorsPath, `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <color name=\"background\">${config.backgroundColorDark}</color>\n</resources>\n`)\n }\n else {\n rmSync(darkColorsPath, { force: true })\n }\n\n const appIconXml = `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"108dp\"\n android:height=\"108dp\"\n android:viewportWidth=\"108\"\n android:viewportHeight=\"108\">\n <path android:fillColor=\"${config.backgroundColor}\" android:pathData=\"M0,0h108v108h-108z\" />\n <path android:fillColor=\"#10B981\" android:pathData=\"M18,78L42,42l12,18l12,-12l24,30z\" />\n</vector>\n`\n if (config.appIconPath) {\n cpSync(\n config.appIconPath,\n join(output, `app/src/main/res/drawable/craft_app_icon${appIconExtension}`),\n )\n }\n else {\n writeFileSync(join(output, 'app/src/main/res/drawable/craft_app_icon.xml'), appIconXml)\n }\n\n const themesXml = `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <style name=\"Theme.CraftApp\" parent=\"Theme.Material3.DayNight.NoActionBar\">\n <item name=\"android:statusBarColor\">@color/primary_dark</item>\n <item name=\"android:navigationBarColor\">@color/primary</item>\n <item name=\"android:windowBackground\">@color/background</item>\n </style>\n</resources>\n`\n writeFileSync(join(output, 'app/src/main/res/values/themes.xml'), themesXml)\n\n // Create activity_main.xml\n const activityMainXml = config.renderer === 'native'\n ? `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:id=\"@+id/native_root\"\n android:layout_width=\"match_parent\"\n android:layout_height=\"match_parent\" />\n`\n : `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:layout_width=\"match_parent\"\n android:layout_height=\"match_parent\"\n android:fitsSystemWindows=\"true\">\n\n <WebView\n android:id=\"@+id/webview\"\n android:layout_width=\"match_parent\"\n android:layout_height=\"match_parent\" />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n`\n writeFileSync(join(output, 'app/src/main/res/layout/activity_main.xml'), activityMainXml)\n\n // Create placeholder index.html\n const placeholderHtml = `<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"viewport-fit=cover, width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\">\n <title>${escapeXml(name)}</title>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body {\n font-family: 'Roboto', sans-serif;\n background: ${config.backgroundColor};\n color: white;\n min-height: 100vh;\n display: flex;\n justify-content: center;\n align-items: center;\n }\n .container { text-align: center; padding: 2rem; }\n h1 { font-size: 2.5rem; margin-bottom: 1rem; }\n p { opacity: 0.7; }\n .ready { color: #4ade80; font-size: 0.9rem; margin-top: 2rem; }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <h1>⚡ ${escapeXml(name)}</h1>\n <p>Built with Craft Android</p>\n <p class=\"ready\" id=\"status\">Waiting for Craft bridge...</p>\n </div>\n <script>\n window.addEventListener('craftReady', (e) => {\n document.getElementById('status').textContent = \\`✓ Craft bridge ready (platform: \\${e.detail.platform})\\`;\n console.log('Craft capabilities:', e.detail.capabilities);\n });\n </script>\n</body>\n</html>`\n\n writeFileSync(join(output, 'app/src/main/assets/index.html'), placeholderHtml)\n\n // The Zig runtime, when one was configured. Absent, the generated app is the\n // Kotlin shim and behaves exactly as it always has — which is the fallback\n // `CraftNative` was written for, not a failure.\n const runtimeDir = resolveRuntimeDir(options.runtimeDir)\n if (runtimeDir) {\n installRuntime(output, runtimeDir)\n console.log(' Installed the Zig runtime from', runtimeDir)\n }\n\n console.log('✅ Project initialized')\n console.log('')\n console.log('Next steps:')\n console.log(` 1. cd ${output}`)\n if (config.renderer === 'native') {\n console.log(' 2. Compile your screen: stx native compile Screen.stx --format bundle --output screen.js')\n console.log(' 3. Run: craft android build --native-bundle screen.js')\n }\n else {\n console.log(' 2. Add your web content to app/src/main/assets/index.html')\n console.log(' 3. Run: craft android build')\n }\n console.log(' 4. Run: craft android open')\n console.log('')\n}\n\n/**\n * Build Android project\n */\nexport async function build(options: BuildOptions): Promise<void> {\n const { htmlPath, nativeBundlePath, devServer, output, release, bundle, compile = true } = options\n\n console.log('\\n📦 Building Craft Android project...')\n\n // Load config\n const configPath = join(output, 'craft.config.json')\n if (!existsSync(configPath)) {\n throw new Error(`No craft.config.json found in ${output}. Run 'craft android init' first.`)\n }\n\n const config: CraftAndroidConfig = JSON.parse(readFileSync(configPath, 'utf-8'))\n validateAndroidConfig(config)\n validateNativeRouteConfig(config)\n\n if (config.renderer === 'native') {\n if (htmlPath || devServer) throw new Error('A native Android screen cannot use --html-path or --dev-server.')\n const destination = join(output, 'app/src/main/assets/native-screen.js')\n if (nativeBundlePath && resolve(nativeBundlePath) !== resolve(destination)) cpSync(nativeBundlePath, destination)\n const icons = join(output, 'app/src/main/assets/craft-icons.json')\n if (options.iconsPath) {\n requireRegularFile(options.iconsPath, 'Icon table')\n if (resolve(options.iconsPath) !== resolve(icons)) cpSync(options.iconsPath, icons)\n }\n if (!existsSync(destination)) {\n throw new Error('Native Android mode needs app/src/main/assets/native-screen.js. Compile a .stx screen with `stx native compile` first.')\n }\n }\n else if (nativeBundlePath) {\n throw new Error('--native-bundle requires renderer: \"native\" in craft.config.json.')\n }\n\n // Refresh the runtime the same way the iOS builder does: only for a project\n // that already has one, so `craft android build` in a shim-only project does\n // not quietly acquire a native library it was never generated with.\n if (existsSync(join(output, 'app/src/main/jniLibs'))) {\n const runtimeDir = resolveRuntimeDir(options.runtimeDir)\n if (runtimeDir) {\n installRuntime(output, runtimeDir)\n console.log(' Refreshed the Zig runtime from', runtimeDir)\n }\n else {\n console.log(' Keeping the Zig runtime installed at init (no runtime directory configured)')\n }\n }\n\n // Update dev server URL if provided\n if (devServer) {\n const url = androidWebUrl(devServer, 'Android dev server URL')\n config.devServerURL = url.toString()\n config.trustedOrigins = [...new Set([\n ...(config.trustedOrigins ?? []).map(value => androidWebUrl(value, 'Android trusted origin').origin),\n url.origin,\n ])]\n writeAndroidConfig(output, config)\n console.log(` Dev server: ${devServer}`)\n }\n\n // Copy the complete web application if provided.\n if (htmlPath) {\n syncAndroidWebAssets(htmlPath, output)\n config.hasBundledFallback = Boolean(devServer)\n writeAndroidConfig(output, config)\n console.log(` Synced: ${htmlPath} → assets/`)\n }\n\n if (!compile) return\n\n // Check if gradlew exists\n const gradlewPath = join(output, 'gradlew')\n if (!existsSync(gradlewPath)) {\n console.log(' Generating Gradle wrapper...')\n try {\n await $`cd ${output} && gradle wrapper`.quiet()\n }\n catch (error) {\n throw new Error('Gradle is unavailable. Install Android Studio or Gradle before building Android.', { cause: error })\n }\n }\n\n // Build the project\n const variant = release ? 'Release' : 'Debug'\n const buildType = `${bundle ? 'bundle' : 'assemble'}${variant}`\n console.log(` Building ${release ? 'release' : 'debug'} ${bundle ? 'app bundle' : 'APK'}...`)\n if (release && !process.env.CRAFT_ANDROID_KEYSTORE)\n console.log(' CRAFT_ANDROID_KEYSTORE is not set: the release build is unsigned.')\n\n try {\n await $`cd ${output} && ./gradlew ${buildType}`\n const artifact = bundle\n ? `app/build/outputs/bundle/${variant.toLowerCase()}/app-${variant.toLowerCase()}.aab`\n : release\n ? 'app/build/outputs/apk/release/app-release.apk'\n : 'app/build/outputs/apk/debug/app-debug.apk'\n console.log(`✅ ${bundle ? 'App bundle' : 'APK'} built: ${artifact}`)\n }\ncatch (error) {\n console.error('Build failed. Open in Android Studio for details.')\n throw error\n }\n\n console.log('')\n}\n\n/**\n * Open Android project in Android Studio\n */\nexport async function open(options: OpenOptions): Promise<void> {\n const { output } = options\n\n if (!existsSync(output)) {\n throw new Error(`No Android project found in ${output}. Run 'craft android init' first.`)\n }\n\n console.log(`🚀 Opening Android project in Android Studio...`)\n\n // Try to open with Android Studio\n try {\n // macOS\n await $`open -a \"Android Studio\" ${output}`.quiet()\n }\ncatch {\n try {\n // Linux\n await $`studio ${output}`.quiet()\n }\ncatch {\n console.log('⚠️ Android Studio not found.')\n console.log(' Please open the project manually in Android Studio:')\n console.log(` ${output}`)\n }\n }\n}\n\n/**\n * Run on Android device or emulator\n */\nexport async function run(options: RunOptions): Promise<void> {\n const { device, output } = options\n\n // First build\n await build({ output })\n\n // Install and run\n console.log('📱 Installing and running on device...')\n\n try {\n const apkPath = join(output, 'app/build/outputs/apk/debug/app-debug.apk')\n\n if (device) {\n await $`adb -s ${device} install -r ${apkPath}`\n }\nelse {\n await $`adb install -r ${apkPath}`\n }\n\n // Get package name from config\n const config: CraftAndroidConfig = JSON.parse(\n readFileSync(join(output, 'craft.config.json'), 'utf-8')\n )\n\n // Launch the app\n const launchCmd = `${config.packageName}/.MainActivity`\n if (device) {\n await $`adb -s ${device} shell am start -n ${launchCmd}`\n }\nelse {\n await $`adb shell am start -n ${launchCmd}`\n }\n\n console.log('✅ App launched')\n }\ncatch (error) {\n console.error('Failed to install/run. Make sure:')\n console.error(' 1. ADB is installed and in PATH')\n console.error(' 2. A device/emulator is connected (run: adb devices)')\n throw error\n }\n}\n",
6
6
  "const ANDROID_PROMISE_RUNTIME = `\nif (window.__craftRejectPendingPromises) {\n window.__craftRejectPendingPromises('Android bridge reinitialized');\n}\nif (window.__craftRejectPermissionRequests) {\n window.__craftRejectPermissionRequests('Android bridge reinitialized');\n}\nwindow.__craftPromiseRuntimeClosed = false;\nwindow.__craftPendingPromises = Object.create(null);\nwindow.__craftPromise = function(channel, resolveName, rejectName, invoke, timeoutMs, timeoutError) {\n if (window.__craftPromiseRuntimeClosed) {\n return Promise.reject(new Error('Android bridge is closed'));\n }\n if (window.__craftPendingPromises[channel]) {\n return Promise.reject(new Error('A '.concat(channel, ' request is already in progress')));\n }\n\n return new Promise(function(resolve, reject) {\n var entry = {settled: false, timer: null, settle: null};\n var resolveCallback;\n var rejectCallback;\n\n entry.settle = function(succeeded, value) {\n if (entry.settled || window.__craftPendingPromises[channel] !== entry) return;\n entry.settled = true;\n if (entry.timer !== null) clearTimeout(entry.timer);\n if (window[resolveName] === resolveCallback) window[resolveName] = null;\n if (window[rejectName] === rejectCallback) window[rejectName] = null;\n delete window.__craftPendingPromises[channel];\n if (succeeded) resolve(value);\n else reject(value);\n };\n\n resolveCallback = function(value) {\n entry.settle(true, value);\n };\n rejectCallback = function(error) {\n entry.settle(false, error);\n };\n window[resolveName] = resolveCallback;\n window[rejectName] = rejectCallback;\n window.__craftPendingPromises[channel] = entry;\n\n if (timeoutMs > 0) {\n entry.timer = setTimeout(function() {\n entry.settle(false, timeoutError || new Error(channel.concat(' request timed out')));\n }, timeoutMs);\n }\n\n try {\n invoke();\n }\n catch (error) {\n entry.settle(false, error);\n }\n });\n};\n\nwindow.__craftRejectPendingPromises = function(message) {\n window.__craftPromiseRuntimeClosed = true;\n Object.keys(window.__craftPendingPromises).forEach(function(channel) {\n var entry = window.__craftPendingPromises[channel];\n if (entry) entry.settle(false, new Error(message || 'Android bridge closed'));\n });\n};\n`\n\nexport function renderAndroidPromiseRuntime(indent = ''): string {\n return ANDROID_PROMISE_RUNTIME\n .trim()\n .split('\\n')\n .map(line => `${indent}${line}`)\n .join('\\n')\n}\n\nexport { ANDROID_PROMISE_RUNTIME }\n",
7
7
  "/** Compile and execute a native Kotlin fixture with Gradle's dependency resolver. */\nimport { readFileSync } from 'node:fs'\nimport { mkdtemp, rm, writeFile } from 'node:fs/promises'\nimport { tmpdir } from 'node:os'\nimport { dirname, join } from 'node:path'\n\n/** Uses the Android builder's Kotlin version, including its transitive compiler dependencies. */\nexport async function runKotlinFixture(source: string): Promise<void> {\n const gradle = Bun.which('gradle')\n if (!gradle) throw new Error('Gradle is required to run native Kotlin fixtures')\n const template = readFileSync(join(dirname(import.meta.dir), 'templates/build.gradle.kts.project.template'), 'utf8')\n const version = template.match(/id\\(\"org.jetbrains.kotlin.android\"\\) version \"([\\d.]+)\"/)?.[1]\n if (!version) throw new Error('The Android builder Kotlin version is missing')\n const directory = await mkdtemp(join(tmpdir(), 'craft-kotlin-fixture-'))\n try {\n await writeFile(join(directory, 'Fixture.kt'), source)\n await writeFile(join(directory, 'settings.gradle'), \"rootProject.name = 'craft-kotlin-fixture'\\n\")\n await writeFile(join(directory, 'gradle.properties'), 'org.gradle.jvmargs=-Xmx512m\\n')\n await writeFile(join(directory, 'build.gradle'), `\nrepositories { mavenCentral() }\nconfigurations {\n kotlinCompiler\n kotlinRuntime\n}\ndependencies {\n kotlinCompiler 'org.jetbrains.kotlin:kotlin-compiler-embeddable:${version}'\n kotlinRuntime 'org.jetbrains.kotlin:kotlin-stdlib:${version}'\n}\ntasks.register('compileFixture', JavaExec) {\n classpath = configurations.kotlinCompiler\n mainClass = 'org.jetbrains.kotlin.cli.jvm.K2JVMCompiler'\n args '-no-stdlib', '-no-reflect', '-classpath', configurations.kotlinRuntime.asPath,\n '-d', layout.buildDirectory.dir('classes').get().asFile.absolutePath,\n file('Fixture.kt').absolutePath\n}\ntasks.register('runFixture', JavaExec) {\n dependsOn 'compileFixture'\n classpath = files(layout.buildDirectory.dir('classes')) + configurations.kotlinRuntime\n mainClass = 'FixtureKt'\n}\n`)\n const child = Bun.spawn([gradle, '--no-daemon', '--console=plain', '--max-workers=2', 'runFixture'], {\n cwd: directory,\n stdout: 'inherit',\n stderr: 'inherit',\n })\n const code = await child.exited\n if (code !== 0) throw new Error(`Native Kotlin fixture failed with exit code ${code}`)\n }\n finally {\n await rm(directory, { recursive: true, force: true })\n }\n}\n"
8
8
  ],
9
- "mappings": "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",
10
- "debugId": "D95CCF804E0EBBCF64756E2164756E21",
9
+ "mappings": "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",
10
+ "debugId": "E085D9D53817707064756E2164756E21",
11
11
  "names": []
12
12
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@craft-native/android",
3
- "version": "0.0.224",
3
+ "version": "0.0.226",
4
4
  "homepage": "https://github.com/craft-native/craft/tree/main/packages/android#readme",
5
5
  "repository": {
6
6
  "type": "git",
@@ -0,0 +1,142 @@
1
+ package {{PACKAGE_NAME}}
2
+
3
+ import java.io.File
4
+ import java.io.BufferedInputStream
5
+ import java.io.DataInputStream
6
+ import java.io.DataOutputStream
7
+ import java.io.IOException
8
+ import java.io.OutputStream
9
+ import java.security.MessageDigest
10
+ import java.util.Locale
11
+
12
+ internal data class CraftNativeImagePolicy(val expiresAt: Long)
13
+
14
+ internal fun craftNativeImagePolicy(
15
+ requestHeaders: Map<String, String>,
16
+ responseHeaders: Map<String, List<String>>,
17
+ now: Long,
18
+ ): CraftNativeImagePolicy? {
19
+ // Persist only the anonymous content the server explicitly calls public.
20
+ // A custom header can carry an account credential just like Authorization.
21
+ if (requestHeaders.isNotEmpty()) return null
22
+ fun values(name: String) = responseHeaders.entries
23
+ .filter { it.key.equals(name, ignoreCase = true) }.flatMap { it.value }
24
+ val variants = values("Vary").flatMap { it.split(',') }.map { it.trim().lowercase(Locale.ROOT) }
25
+ if (values("Set-Cookie").isNotEmpty() || variants.any { it in setOf("*", "cookie", "authorization") }) return null
26
+ val directives = values("Cache-Control").flatMap { it.split(',') }.map { it.trim().lowercase(Locale.ROOT) }
27
+ if ("public" !in directives || directives.any { it.substringBefore('=').trim() in setOf("private", "no-store", "no-cache") }) return null
28
+ val ages = directives.filter { it.substringBefore('=').trim() == "max-age" }
29
+ if (ages.isEmpty()) return null
30
+ val seconds = ages.map {
31
+ it.substringAfter('=', "").trim().removeSurrounding("\"").toLongOrNull()?.takeIf { age -> age > 0 } ?: return null
32
+ }.min().coerceAtMost(31_536_000L)
33
+ val ageHeader = values("Age").firstOrNull()
34
+ val age = if (ageHeader == null) 0L else ageHeader.trim().toLongOrNull()?.takeIf { it >= 0 } ?: return null
35
+ val remaining = seconds - age
36
+ if (remaining <= 0) return null
37
+ return runCatching { CraftNativeImagePolicy(Math.addExact(now, remaining * 1000)) }.getOrNull()
38
+ }
39
+
40
+ internal class CraftNativeImageCache(
41
+ private val root: File,
42
+ private val maxBytes: Long = 32L * 1024 * 1024,
43
+ private val maxEntryBytes: Long = 8L * 1024 * 1024,
44
+ private val maxEntries: Int = 128,
45
+ private val now: () -> Long = System::currentTimeMillis,
46
+ ) {
47
+ init {
48
+ require(maxBytes > 0 && maxEntryBytes > 0 && maxEntries > 0)
49
+ // A process killed while encoding can leave a staging file behind.
50
+ // All instances share this lock, so another writer cannot own it now.
51
+ synchronized(lock) {
52
+ runCatching {
53
+ root.listFiles().orEmpty().filter { it.name.endsWith(".tmp") }.forEach(File::delete)
54
+ prune()
55
+ }
56
+ }
57
+ }
58
+
59
+ private fun file(address: String): File {
60
+ val digest = MessageDigest.getInstance("SHA-256").digest(address.toByteArray(Charsets.UTF_8))
61
+ val name = digest.joinToString("") { (it.toInt() and 255).toString(16).padStart(2, '0') }
62
+ return File(root, "$name.cache")
63
+ }
64
+
65
+ fun read(address: String): ByteArray? = synchronized(lock) {
66
+ runCatching {
67
+ val target = file(address)
68
+ if (!target.isFile) return@runCatching null
69
+ if (target.length() <= HEADER_BYTES || target.length() > HEADER_BYTES + minOf(maxEntryBytes, maxBytes)) {
70
+ target.delete()
71
+ return@runCatching null
72
+ }
73
+ val data = DataInputStream(BufferedInputStream(target.inputStream())).use { input ->
74
+ if (input.readInt() != MAGIC || input.readLong() <= now()) null else input.readBytes()
75
+ }
76
+ if (data == null || data.isEmpty()) { target.delete(); null }
77
+ else { target.setLastModified(now()); data }
78
+ }.getOrNull()
79
+ }
80
+
81
+ fun write(address: String, policy: CraftNativeImagePolicy, encode: (OutputStream) -> Boolean): Boolean = synchronized(lock) {
82
+ var staging: File? = null
83
+ try {
84
+ if (policy.expiresAt <= now() || (!root.isDirectory && !root.mkdirs())) return@synchronized false
85
+ val target = file(address)
86
+ staging = File.createTempFile(target.name, ".tmp", root)
87
+ staging.outputStream().buffered().use { raw ->
88
+ val header = DataOutputStream(raw)
89
+ header.writeInt(MAGIC)
90
+ header.writeLong(policy.expiresAt)
91
+ val payload = BoundedImageOutput(raw, minOf(maxEntryBytes, maxBytes))
92
+ if (!encode(payload) || payload.written == 0L) throw IOException("Incomplete cached image")
93
+ payload.flush()
94
+ }
95
+ // Freshness and pixels are committed together; a failed write
96
+ // never replaces an earlier, complete image with a partial file.
97
+ if (!staging.renameTo(target)) return@synchronized false
98
+ target.setLastModified(now())
99
+ prune()
100
+ true
101
+ }
102
+ catch (error: Exception) { false }
103
+ catch (error: OutOfMemoryError) { false }
104
+ finally { staging?.delete() }
105
+ }
106
+
107
+ fun remove(address: String) = synchronized(lock) { runCatching { file(address).delete() }; Unit }
108
+
109
+ private fun prune() {
110
+ val entries = root.listFiles().orEmpty().filter { entryName.matches(it.name) }.sortedBy(File::lastModified)
111
+ var count = entries.size
112
+ var bytes = entries.sumOf { (it.length() - HEADER_BYTES).coerceAtLeast(0) }
113
+ for (entry in entries) {
114
+ if (count <= maxEntries && bytes <= maxBytes) break
115
+ val size = (entry.length() - HEADER_BYTES).coerceAtLeast(0)
116
+ if (entry.delete()) { count--; bytes -= size }
117
+ }
118
+ }
119
+
120
+ private class BoundedImageOutput(private val output: OutputStream, private val limit: Long) : OutputStream() {
121
+ var written = 0L
122
+ private set
123
+ override fun write(value: Int) {
124
+ if (written >= limit) throw IOException("Cached image is too large")
125
+ output.write(value)
126
+ written++
127
+ }
128
+ override fun write(bytes: ByteArray, offset: Int, length: Int) {
129
+ if (written + length > limit) throw IOException("Cached image is too large")
130
+ output.write(bytes, offset, length)
131
+ written += length
132
+ }
133
+ override fun flush() = output.flush()
134
+ }
135
+
136
+ private companion object {
137
+ const val MAGIC = 0x43494d31
138
+ const val HEADER_BYTES = 12L
139
+ val entryName = Regex("^[a-f0-9]{64}\\.cache$")
140
+ val lock = Any()
141
+ }
142
+ }
@@ -120,6 +120,7 @@ internal class CraftNativeServices(
120
120
  done(success(CraftNativeMedia.prune(context, (0 until keep.length()).map { keep.optString(it) })))
121
121
  }
122
122
  "usage" -> done(success(CraftNativeMedia.usage(context)))
123
+ "clear" -> done(success(CraftNativeMedia.clear(context)))
123
124
  else -> done(failure("UNKNOWN_ACTION", "Unsupported Media method"))
124
125
  }
125
126
  "Background" -> {
@@ -1021,7 +1021,8 @@ internal object CraftNativeSymbols {
1021
1021
  * A native Video given an address with a copy here plays the copy.
1022
1022
  */
1023
1023
  internal object CraftNativeMedia {
1024
- private val inFlight = HashMap<String, MutableList<(CraftNativeCapabilityResult) -> Unit>>()
1024
+ private data class Flight(val id: String, val waiting: MutableList<(CraftNativeCapabilityResult) -> Unit>)
1025
+ private val inFlight = HashMap<String, Flight>()
1025
1026
  private val executor = Executors.newFixedThreadPool(2)
1026
1027
  private val main = Handler(Looper.getMainLooper())
1027
1028
 
@@ -1054,12 +1055,12 @@ internal object CraftNativeMedia {
1054
1055
  if (!cellular && metered(context)) {
1055
1056
  return done(CraftNativeCapabilityResult(data = JSONObject().put("ok", false).put("url", address).put("reason", "cellular")))
1056
1057
  }
1057
- synchronized(inFlight) {
1058
+ val flight = synchronized(inFlight) {
1058
1059
  inFlight[address]?.let { waiting ->
1059
- waiting.add(done)
1060
+ waiting.waiting.add(done)
1060
1061
  return
1061
1062
  }
1062
- inFlight[address] = mutableListOf(done)
1063
+ Flight(java.util.UUID.randomUUID().toString(), mutableListOf(done)).also { inFlight[address] = it }
1063
1064
  }
1064
1065
  executor.execute {
1065
1066
  val result = runCatching {
@@ -1073,7 +1074,7 @@ internal object CraftNativeMedia {
1073
1074
  val target = file(context, address)
1074
1075
  target.parentFile?.mkdirs()
1075
1076
  // Written aside and moved into place, so a half-written file is never played.
1076
- val staging = File(target.parentFile, "${target.name}.part")
1077
+ val staging = File(target.parentFile, "${target.name}.${flight.id}.part")
1077
1078
  try {
1078
1079
  val size = connection.inputStream.use { input -> staging.outputStream().use { input.copyTo(it) } }
1079
1080
  val expected = connection.contentLengthLong
@@ -1081,6 +1082,9 @@ internal object CraftNativeMedia {
1081
1082
  return@runCatching CraftNativeCapabilityResult(code = "NETWORK_ERROR", message = "media.keep received an incomplete file")
1082
1083
  }
1083
1084
  synchronized(inFlight) {
1085
+ if (inFlight[address]?.id != flight.id) {
1086
+ return@runCatching CraftNativeCapabilityResult(code = "CANCELLED", message = "The video cache was cleared")
1087
+ }
1084
1088
  if (!staging.renameTo(target)) {
1085
1089
  return@runCatching CraftNativeCapabilityResult(code = "IO_ERROR", message = "media.keep could not store the file")
1086
1090
  }
@@ -1098,7 +1102,7 @@ internal object CraftNativeMedia {
1098
1102
  }
1099
1103
  CraftNativeCapabilityResult(code = if (full) "NO_SPACE" else if (error is java.net.SocketTimeoutException) "TIMEOUT" else "NETWORK_ERROR", message = error.message ?: "media.keep failed")
1100
1104
  }
1101
- val waiting = synchronized(inFlight) { inFlight.remove(address) ?: mutableListOf() }
1105
+ val waiting = synchronized(inFlight) { if (inFlight[address]?.id == flight.id) inFlight.remove(address)?.waiting.orEmpty() else emptyList() }
1102
1106
  main.post { waiting.forEach { it(result) } }
1103
1107
  }
1104
1108
  }
@@ -1115,12 +1119,31 @@ internal object CraftNativeMedia {
1115
1119
  var removed = 0
1116
1120
  synchronized(inFlight) {
1117
1121
  // A concurrent keep owns its target and staging file until it finishes.
1118
- val active = inFlight.keys.map { file(context, it).name }.toSet()
1122
+ val active = inFlight.flatMap { (address, flight) ->
1123
+ val name = file(context, address).name
1124
+ listOf(name, "$name.${flight.id}.part")
1125
+ }.toSet()
1126
+ root(context).listFiles()?.forEach { file ->
1127
+ if (file.name !in active && file.name !in wanted && file.delete()) removed++
1128
+ }
1129
+ }
1130
+ return removed
1131
+ }
1132
+
1133
+ /** Invalidates old transfers before deleting files; a later keep starts afresh. */
1134
+ fun clear(context: Context): Int {
1135
+ var removed = 0
1136
+ var failure: java.io.IOException? = null
1137
+ val waiting = synchronized(inFlight) {
1138
+ val callbacks = inFlight.values.flatMap { it.waiting }
1139
+ inFlight.clear()
1119
1140
  root(context).listFiles()?.forEach { file ->
1120
- val target = file.name.removeSuffix(".part")
1121
- if (target !in active && file.name !in wanted && file.delete()) removed++
1141
+ if (file.delete()) removed++ else failure = java.io.IOException("The video cache could not be cleared")
1122
1142
  }
1143
+ callbacks
1123
1144
  }
1145
+ main.post { waiting.forEach { it(CraftNativeCapabilityResult(code = "CANCELLED", message = "The video cache was cleared")) } }
1146
+ failure?.let { throw it }
1124
1147
  return removed
1125
1148
  }
1126
1149
 
@@ -122,6 +122,8 @@ import com.google.android.gms.maps.model.Polyline
122
122
  import com.google.android.gms.maps.model.PolylineOptions
123
123
  import org.json.JSONArray
124
124
  import org.json.JSONObject
125
+ import java.io.File
126
+ import java.net.HttpURLConnection
125
127
  import java.net.URL
126
128
  import java.text.SimpleDateFormat
127
129
  import java.util.Calendar
@@ -3805,6 +3807,7 @@ class MainActivity : AppCompatActivity() {
3805
3807
  private var windowSafeInsets = intArrayOf(0, 0, 0, 0)
3806
3808
  private var useMessagePorts = false
3807
3809
  private val imageExecutor = Executors.newFixedThreadPool(2)
3810
+ private val imageCache by lazy { CraftNativeImageCache(File(cacheDir, "craft-native-images")) }
3808
3811
  /** Lays out each isolate's prelude (the synchronous stores) off the main thread. */
3809
3812
  private val preludeExecutor = Executors.newSingleThreadExecutor()
3810
3813
  private val imageSources = mutableMapOf<ImageView, String>()
@@ -4088,6 +4091,7 @@ class MainActivity : AppCompatActivity() {
4088
4091
  show(target)
4089
4092
  } else {
4090
4093
  host.removeView(target.view)
4094
+ target.view.translationX = 0f
4091
4095
  current.view.translationX = 0f
4092
4096
  chrome?.selectTab(currentTab)
4093
4097
  tabSwipeTarget = null
@@ -4156,13 +4160,22 @@ class MainActivity : AppCompatActivity() {
4156
4160
  * page on the app's site in the in-app browser. Another site's address
4157
4161
  * opens in the browser too.
4158
4162
  */
4159
- private fun navigateOpen(target: String, fromLink: Boolean = false) {
4163
+ private fun navigateOpen(target: String, fromLink: Boolean = false, replace: Boolean = false) {
4160
4164
  val path = routes.appPath(target, urlSchemes)
4161
4165
  if (path == null) {
4162
4166
  openExternal(target)
4163
4167
  return
4164
4168
  }
4165
4169
  val match = routes.match(path)
4170
+ if (replace && match != null && screens.isNotEmpty()) {
4171
+ val stack = screens
4172
+ val removed = stack.removeAt(stack.lastIndex)
4173
+ val previous = stack.lastOrNull()
4174
+ closeScreen(removed) {
4175
+ if (screens === stack && stack.lastOrNull() === previous) navigateOpen(path, fromLink)
4176
+ }
4177
+ return
4178
+ }
4166
4179
  val tab = routes.tabFor(path)
4167
4180
  if (tab != null) {
4168
4181
  selectTab(routes.tabs.indexOf(tab))
@@ -4492,6 +4505,11 @@ class MainActivity : AppCompatActivity() {
4492
4505
  * screen shown before hears true (its `navigation.onAppear`).
4493
4506
  */
4494
4507
  private fun show(screen: Screen) {
4508
+ // A retained tab may have been the offscreen preview of a cancelled
4509
+ // swipe, or the outgoing view of a completed swipe. Its animator can
4510
+ // finish after the peer animator, so clear that ownership on display.
4511
+ screen.view.animate().withEndAction(null).setListener(null).cancel()
4512
+ screen.view.translationX = 0f
4495
4513
  // A navigation breadcrumb: the trail a crash report carries.
4496
4514
  if (visibleScreen !== screen) CraftCrashReporter.screenAppeared(screen.route ?: "home")
4497
4515
  val bars = chrome
@@ -4770,6 +4788,12 @@ class MainActivity : AppCompatActivity() {
4770
4788
  // app script has no stack of its own; it uses NAVIGATE_OPEN.
4771
4789
  if (visibleScreen !== screen) return
4772
4790
  val route = payload.optString("screen").takeIf { it.isNotBlank() } ?: return
4791
+ // Resolve and replace in one host operation. A pop followed by
4792
+ // open would send the second message from a closing isolate.
4793
+ if (message.optString("type") == "NAVIGATE_REPLACE" && route.startsWith("/")) {
4794
+ navigateOpen(route, replace = true)
4795
+ return
4796
+ }
4773
4797
  if (message.optString("type") == "NAVIGATE_REPLACE") {
4774
4798
  val stack = screens
4775
4799
  val removed = stack.removeAt(stack.lastIndex)
@@ -6101,7 +6125,9 @@ class MainActivity : AppCompatActivity() {
6101
6125
  null
6102
6126
  } else defaultBackground
6103
6127
  val overflow = craftNativeOptionalString(style.opt("overflow"))
6104
- view.clipToOutline = overflow == "hidden"
6128
+ // A replaced image clips its own pixels to border-radius, including
6129
+ // when its surrounding layout permits visible overflow.
6130
+ view.clipToOutline = overflow == "hidden" || (view is ImageView && (radius > 0 || hasCornerRadii))
6105
6131
  if (view is ViewGroup) view.clipChildren = overflow == "hidden"
6106
6132
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P &&
6107
6133
  (style.has("shadowColor") || style.has("shadowOpacity") || style.has("shadowRadius"))
@@ -6499,12 +6525,38 @@ class MainActivity : AppCompatActivity() {
6499
6525
  }
6500
6526
  imageJobs[view] = imageExecutor.submit {
6501
6527
  val bitmap = runCatching {
6502
- val connection = URL(uri).openConnection().apply {
6528
+ val publicRequest = headers.isEmpty() && Uri.parse(uri).userInfo.isNullOrEmpty()
6529
+ if (publicRequest) {
6530
+ val bytes = imageCache.read(uri)
6531
+ if (bytes != null) {
6532
+ val saved = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
6533
+ if (saved != null) return@runCatching saved
6534
+ imageCache.remove(uri)
6535
+ }
6536
+ }
6537
+ val connection = (URL(uri).openConnection() as HttpURLConnection).apply {
6503
6538
  connectTimeout = 10_000
6504
6539
  readTimeout = 10_000
6540
+ // This loader owns its bounded public cache. Do not let
6541
+ // an application's global ResponseCache retain requests.
6542
+ useCaches = false
6505
6543
  headers.forEach { (name, value) -> setRequestProperty(name, value) }
6506
6544
  }
6507
- connection.getInputStream().use(BitmapFactory::decodeStream)
6545
+ try {
6546
+ val downloaded = connection.inputStream.use(BitmapFactory::decodeStream)
6547
+ if (publicRequest) {
6548
+ val responseHeaders = connection.headerFields.entries.filter { it.key != null }
6549
+ .associate { it.key!! to it.value.orEmpty() }
6550
+ val policy = if (connection.responseCode == HttpURLConnection.HTTP_OK)
6551
+ craftNativeImagePolicy(headers, responseHeaders, System.currentTimeMillis()) else null
6552
+ if (downloaded != null && policy != null) {
6553
+ // Encode only after a complete decode, then commit
6554
+ // before onLoad so a cold offline reopen is safe.
6555
+ imageCache.write(uri, policy) { downloaded.compress(Bitmap.CompressFormat.PNG, 100, it) }
6556
+ } else imageCache.remove(uri)
6557
+ }
6558
+ downloaded
6559
+ } finally { connection.disconnect() }
6508
6560
  }.getOrNull()
6509
6561
  runOnUiThread {
6510
6562
  if (imageSources[view] != requestKey) return@runOnUiThread