craft-native 0.0.91 → 0.0.92

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.
@@ -44,7 +44,19 @@ class MainActivity : AppCompatActivity() {
44
44
 
45
45
  webView = findViewById(R.id.webview)
46
46
  craftBridge = CraftBridge(this, webView, CraftHealthConnect(this, webView))
47
- handleIncomingDeepLink(intent, dispatch = false)
47
+ // The link that launched the app (#215). Queued until the page's bridge
48
+ // is injected, then dispatched once with `initial: true`, so a page that
49
+ // subscribes rather than calling getInitialURL still hears it. A
50
+ // relaunch from Recents carries the old intent, and was not opened by it.
51
+ // A recreated activity restores what the bridge saved: its intent may
52
+ // be a later warm link that onNewIntent set.
53
+ if (savedInstanceState == null) {
54
+ val launchLink = intent.data?.toString()
55
+ val launchedFromHistory = intent.flags and Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY != 0
56
+ if (launchLink != null && !launchedFromHistory) craftBridge.receiveDeepLink(launchLink)
57
+ } else {
58
+ craftBridge.restoreDeepLinks(savedInstanceState)
59
+ }
48
60
  if (savedInstanceState == null) handleIncomingShortcut(intent)
49
61
 
50
62
  setupWebView()
@@ -150,12 +162,6 @@ class MainActivity : AppCompatActivity() {
150
162
  }
151
163
  }
152
164
 
153
- private fun handleIncomingDeepLink(source: Intent?, dispatch: Boolean) {
154
- val url = source?.data?.toString() ?: return
155
- craftBridge.setInitialURL(url)
156
- if (dispatch) craftBridge.dispatchDeepLink(url)
157
- }
158
-
159
165
  private fun handleIncomingShortcut(source: Intent?) {
160
166
  source?.getStringExtra(SHORTCUT_TYPE_EXTRA)?.let(craftBridge::dispatchShortcut)
161
167
  }
@@ -198,10 +204,15 @@ class MainActivity : AppCompatActivity() {
198
204
  return (uri.scheme == "https" || localDevelopment) && origin in trustedOrigins
199
205
  }
200
206
 
207
+ override fun onSaveInstanceState(outState: Bundle) {
208
+ super.onSaveInstanceState(outState)
209
+ craftBridge.saveDeepLinks(outState)
210
+ }
211
+
201
212
  override fun onNewIntent(intent: Intent) {
202
213
  super.onNewIntent(intent)
203
214
  setIntent(intent)
204
- handleIncomingDeepLink(intent, dispatch = true)
215
+ intent.data?.toString()?.let(craftBridge::receiveDeepLink)
205
216
  handleIncomingShortcut(intent)
206
217
  }
207
218
 
@@ -550,9 +550,12 @@ export declare const share: {
550
550
  /**
551
551
  * Open native share dialog.
552
552
  *
553
+ * Resolves `true` when the person shared and `false` when they dismissed the
554
+ * dialog, on the native bridges and in the browser alike.
555
+ *
553
556
  * @param options - Share options
554
557
  */
555
- share(options: ShareOptions): Promise<void>;
558
+ share(options: ShareOptions): Promise<boolean>;
556
559
  /**
557
560
  * Check if sharing is available.
558
561
  */
@@ -670,7 +673,14 @@ export declare const keepAwake: {
670
673
  };
671
674
  export declare const deepLinks: {
672
675
  getInitialURL(): Promise<string | null>;
673
- onLink(callback: (url: string) => void): () => void;
676
+ /**
677
+ * The second argument says whether this is the link that launched the app.
678
+ * A page that also calls `getInitialURL()` after an await gets that link
679
+ * from both, and can skip it here when `initial` is true.
680
+ */
681
+ onLink(callback: (url: string, link: {
682
+ initial: boolean;
683
+ }) => void): () => void;
674
684
  };
675
685
  export declare function normalizeDeepLinkURL(value: unknown): string | null;
676
686
  export declare const network: {
package/dist/cli.js CHANGED
@@ -1575,7 +1575,7 @@ async function open(options) {
1575
1575
  await $`open ${projectPath}`;
1576
1576
  }
1577
1577
  function orderSimulators(devices) {
1578
- return [...devices].sort((left, right) => {
1578
+ return devices.filter((device) => device.runtime.startsWith("iOS")).sort((left, right) => {
1579
1579
  const booted = Number(right.state === "Booted") - Number(left.state === "Booted");
1580
1580
  if (booted !== 0)
1581
1581
  return booted;
@@ -1730,9 +1730,11 @@ var exports_src2 = {};
1730
1730
  __export(exports_src2, {
1731
1731
  syncAndroidWebAssets: () => syncAndroidWebAssets,
1732
1732
  run: () => run2,
1733
+ resolveRuntimeDir: () => resolveRuntimeDir2,
1733
1734
  renderAndroidPermissions: () => renderAndroidPermissions,
1734
1735
  renderAndroidDeepLinks: () => renderAndroidDeepLinks,
1735
1736
  open: () => open2,
1737
+ installRuntime: () => installRuntime2,
1736
1738
  init: () => init2,
1737
1739
  build: () => build2
1738
1740
  });
@@ -1928,6 +1930,36 @@ function renderAndroidDeepLinks(config) {
1928
1930
  </intent-filter>`).join(`
1929
1931
  `);
1930
1932
  }
1933
+ function resolveRuntimeDir2(override) {
1934
+ if (override === null)
1935
+ return null;
1936
+ const dir = override ?? process.env.CRAFT_ANDROID_RUNTIME;
1937
+ if (!dir)
1938
+ return null;
1939
+ if (!existsSync5(dir)) {
1940
+ const source = override === undefined ? "CRAFT_ANDROID_RUNTIME points at" : "runtimeDir is";
1941
+ throw new Error(`${source} ${dir}, which does not exist.`);
1942
+ }
1943
+ return dir;
1944
+ }
1945
+ function installRuntime2(output, runtimeDir) {
1946
+ const found = RUNTIME_ABIS.map((abi) => ({ abi, source: join3(runtimeDir, abi, "libcraft.so") })).filter((entry) => existsSync5(entry.source));
1947
+ if (found.length === 0) {
1948
+ throw new Error(`${runtimeDir} has no <abi>/libcraft.so for any of ${RUNTIME_ABIS.join(", ")}. ` + "Run `zig build build-android-all -Doptimize=ReleaseSafe` in packages/zig and point at its zig-out/android.");
1949
+ }
1950
+ if (found.length < RUNTIME_ABIS.length) {
1951
+ const missing = RUNTIME_ABIS.filter((abi) => !found.some((entry) => entry.abi === abi));
1952
+ console.warn(` \u26A0 only ${found.map((entry) => entry.abi).join(", ")} was found; ${missing.join(", ")} is missing. ` + "The app will fall back to the Kotlin shim on those devices.");
1953
+ }
1954
+ const dest = join3(output, "app/src/main/jniLibs");
1955
+ rmSync3(dest, { force: true, recursive: true });
1956
+ for (const { abi, source } of found) {
1957
+ const abiDir = join3(dest, abi);
1958
+ mkdirSync3(abiDir, { recursive: true });
1959
+ cpSync3(source, join3(abiDir, "libcraft.so"));
1960
+ }
1961
+ return true;
1962
+ }
1931
1963
  async function init2(options) {
1932
1964
  const { name, packageName, output } = options;
1933
1965
  console.log(`
@@ -2147,6 +2179,11 @@ zipStorePath=wrapper/dists
2147
2179
  </body>
2148
2180
  </html>`;
2149
2181
  writeFileSync4(join3(output, "app/src/main/assets/index.html"), placeholderHtml);
2182
+ const runtimeDir = resolveRuntimeDir2(options.runtimeDir);
2183
+ if (runtimeDir) {
2184
+ installRuntime2(output, runtimeDir);
2185
+ console.log(" Installed the Zig runtime from", runtimeDir);
2186
+ }
2150
2187
  console.log("\u2705 Project initialized");
2151
2188
  console.log("");
2152
2189
  console.log("Next steps:");
@@ -2165,6 +2202,15 @@ async function build2(options) {
2165
2202
  throw new Error(`No craft.config.json found in ${output}. Run 'craft android init' first.`);
2166
2203
  }
2167
2204
  const config = JSON.parse(readFileSync4(configPath, "utf-8"));
2205
+ if (existsSync5(join3(output, "app/src/main/jniLibs"))) {
2206
+ const runtimeDir = resolveRuntimeDir2(options.runtimeDir);
2207
+ if (runtimeDir) {
2208
+ installRuntime2(output, runtimeDir);
2209
+ console.log(" Refreshed the Zig runtime from", runtimeDir);
2210
+ } else {
2211
+ console.log(" Keeping the Zig runtime installed at init (no runtime directory configured)");
2212
+ }
2213
+ }
2168
2214
  if (devServer) {
2169
2215
  const url = androidWebUrl(devServer, "Android dev server URL");
2170
2216
  config.devServerURL = url.toString();
@@ -2313,7 +2359,7 @@ window.__craftRejectPendingPromises = function(message) {
2313
2359
  if (entry) entry.settle(false, new Error(message || 'Android bridge closed'));
2314
2360
  });
2315
2361
  };
2316
- `, TEMPLATES_DIR2, LOCAL_DEVELOPMENT_HOSTS, KOTLIN_KEYWORDS, DEFAULT_CONFIG2;
2362
+ `, TEMPLATES_DIR2, LOCAL_DEVELOPMENT_HOSTS, KOTLIN_KEYWORDS, DEFAULT_CONFIG2, RUNTIME_ABIS;
2317
2363
  var init_src2 = __esm(() => {
2318
2364
  ({ $: $2 } = globalThis.Bun);
2319
2365
  TEMPLATES_DIR2 = join3(dirname2(import.meta.dir), "templates");
@@ -2371,6 +2417,7 @@ var init_src2 = __esm(() => {
2371
2417
  compileSdk: 36,
2372
2418
  targetSdk: 35
2373
2419
  };
2420
+ RUNTIME_ABIS = ["arm64-v8a", "x86_64"];
2374
2421
  });
2375
2422
 
2376
2423
  // ../../node_modules/@stacksjs/clapp/dist/index.js
@@ -4537,7 +4584,7 @@ function pinCraftNativeDependency(packagePath, craftVersion) {
4537
4584
  `);
4538
4585
  }
4539
4586
  // package.json
4540
- var version = "0.0.91";
4587
+ var version = "0.0.92";
4541
4588
 
4542
4589
  // bin/cli.ts
4543
4590
  var spawnedFrom = process6.env[CRAFT_CLI_SPAWN_MARKER];
package/dist/index.cjs CHANGED
@@ -5089,11 +5089,18 @@ var share = {
5089
5089
  return craft.share.share(options);
5090
5090
  }
5091
5091
  if (navigator.share) {
5092
- await navigator.share({
5093
- title: options.title,
5094
- text: options.text,
5095
- url: options.url
5096
- });
5092
+ try {
5093
+ await navigator.share({
5094
+ title: options.title,
5095
+ text: options.text,
5096
+ url: options.url
5097
+ });
5098
+ return true;
5099
+ } catch (error) {
5100
+ if (error instanceof DOMException && error.name === "AbortError")
5101
+ return false;
5102
+ throw error;
5103
+ }
5097
5104
  } else {
5098
5105
  throw new Error("Share API not available");
5099
5106
  }
package/dist/index.js CHANGED
@@ -4850,11 +4850,18 @@ var share = {
4850
4850
  return craft.share.share(options);
4851
4851
  }
4852
4852
  if (navigator.share) {
4853
- await navigator.share({
4854
- title: options.title,
4855
- text: options.text,
4856
- url: options.url
4857
- });
4853
+ try {
4854
+ await navigator.share({
4855
+ title: options.title,
4856
+ text: options.text,
4857
+ url: options.url
4858
+ });
4859
+ return true;
4860
+ } catch (error) {
4861
+ if (error instanceof DOMException && error.name === "AbortError")
4862
+ return false;
4863
+ throw error;
4864
+ }
4858
4865
  } else {
4859
4866
  throw new Error("Share API not available");
4860
4867
  }
@@ -201,7 +201,7 @@ export interface SimulatorDevice {
201
201
  runtime: string;
202
202
  }
203
203
  /**
204
- * Every available simulator, newest runtime first, iPhones before iPads.
204
+ * Every available *iOS* simulator, newest runtime first, iPhones before iPads.
205
205
  *
206
206
  * Exported because the choice is worth testing without a simulator present -
207
207
  * `pickSimulator` is the policy and this is the data it reads.
@@ -559,7 +559,7 @@ async function open(options) {
559
559
  await $`open ${projectPath}`;
560
560
  }
561
561
  function orderSimulators(devices) {
562
- return [...devices].sort((left, right) => {
562
+ return devices.filter((device) => device.runtime.startsWith("iOS")).sort((left, right) => {
563
563
  const booted = Number(right.state === "Booted") - Number(left.state === "Booted");
564
564
  if (booted !== 0)
565
565
  return booted;