craft-native 0.0.90 → 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.
Files changed (31) hide show
  1. package/dist/android/src/index.d.ts +36 -1
  2. package/dist/android/src/index.js +332 -44
  3. package/dist/android/src/promise-runtime.d.ts +3 -0
  4. package/dist/android/templates/CraftBridge.kt.template +2167 -1177
  5. package/dist/android/templates/CraftHealthConnect.kt.template +45 -6
  6. package/dist/android/templates/CraftHealthConnectStub.kt.template +7 -1
  7. package/dist/android/templates/CraftNative.kt.template +2250 -0
  8. package/dist/android/templates/LocationRecordingService.kt.template +34 -9
  9. package/dist/android/templates/MainActivity.kt.template +42 -8
  10. package/dist/android/templates/proguard-rules.pro.template +4 -1
  11. package/dist/android/templates/test-bridges.html +10 -33
  12. package/dist/api/index.d.ts +1 -1
  13. package/dist/api/ios-advanced.d.ts +8 -5
  14. package/dist/api/live-activity-handle.d.ts +6 -0
  15. package/dist/api/mobile.d.ts +25 -7
  16. package/dist/api/window.d.ts +2 -0
  17. package/dist/cli.js +452 -129
  18. package/dist/index.cjs +77 -22
  19. package/dist/index.js +77 -22
  20. package/dist/ios/src/index.d.ts +1 -1
  21. package/dist/ios/src/index.js +23 -5
  22. package/dist/ios/templates/CraftApp.swift +706 -92
  23. package/dist/ios/templates/CraftWatchApp.swift.template +8 -0
  24. package/dist/ios/templates/WatchApp.Info.plist.template +2 -0
  25. package/dist/ios/templates/WatchExtension.Info.plist.template +34 -0
  26. package/dist/ios/templates/project.yml.template +10 -4
  27. package/dist/mobile.js +52 -21
  28. package/dist/scaffold-version.d.ts +5 -0
  29. package/package.json +1 -1
  30. package/dist/android/templates/CraftBridgeExtensions.kt.template +0 -383
  31. package/dist/android/templates/CraftWidgetProvider.kt.template +0 -246
package/dist/index.cjs CHANGED
@@ -2341,6 +2341,13 @@ function webkitRequest(bucket, message, options = {}) {
2341
2341
  }
2342
2342
 
2343
2343
  // src/api/window.ts
2344
+ var CURRENT_WINDOW_ID = "main";
2345
+ function getInjectedWindowBridge() {
2346
+ if (typeof globalThis.window === "undefined")
2347
+ return;
2348
+ return globalThis.window.craft?.window;
2349
+ }
2350
+
2344
2351
  class Window {
2345
2352
  _id;
2346
2353
  _listeners = new Map;
@@ -2357,7 +2364,9 @@ class Window {
2357
2364
  return this._closed;
2358
2365
  }
2359
2366
  _setupEventListeners() {
2360
- if (typeof window2 !== "undefined") {
2367
+ if (this._domListeners.length > 0)
2368
+ return;
2369
+ if (typeof globalThis.window !== "undefined") {
2361
2370
  const eventTypes = [
2362
2371
  "show",
2363
2372
  "hide",
@@ -2378,7 +2387,11 @@ class Window {
2378
2387
  eventTypes.forEach((type) => {
2379
2388
  const handler = (event) => {
2380
2389
  if (event.detail?.windowId === this._id || !event.detail?.windowId) {
2381
- this._emit(type, event.detail?.data);
2390
+ if (type === "close" || type === "closed")
2391
+ this._closed = true;
2392
+ else if (type === "show" || type === "focus")
2393
+ this._closed = false;
2394
+ this._emit(type, event.detail?.data ?? event.detail);
2382
2395
  }
2383
2396
  };
2384
2397
  const eventName = `craft:window:${type}`;
@@ -2388,13 +2401,17 @@ class Window {
2388
2401
  }
2389
2402
  }
2390
2403
  _cleanupEventListeners() {
2391
- if (typeof window2 !== "undefined") {
2404
+ if (typeof globalThis.window !== "undefined") {
2392
2405
  for (const { type, handler } of this._domListeners) {
2393
2406
  globalThis.window.removeEventListener(type, handler);
2394
2407
  }
2395
2408
  this._domListeners = [];
2396
2409
  }
2397
2410
  }
2411
+ _markOpen() {
2412
+ this._closed = false;
2413
+ this._setupEventListeners();
2414
+ }
2398
2415
  _emit(event, data) {
2399
2416
  const listeners = this._listeners.get(event);
2400
2417
  if (listeners) {
@@ -2422,15 +2439,19 @@ class Window {
2422
2439
  }
2423
2440
  async show() {
2424
2441
  await this._call("show");
2442
+ this._markOpen();
2425
2443
  }
2426
2444
  async hide() {
2427
2445
  await this._call("hide");
2428
2446
  }
2429
2447
  async toggle() {
2430
2448
  await this._call("toggle");
2449
+ if (this._closed)
2450
+ this._markOpen();
2431
2451
  }
2432
2452
  async focus() {
2433
2453
  await this._call("focus");
2454
+ this._markOpen();
2434
2455
  }
2435
2456
  async blur() {
2436
2457
  await this._call("blur");
@@ -2450,7 +2471,6 @@ class Window {
2450
2471
  async close() {
2451
2472
  await this._call("close");
2452
2473
  this._closed = true;
2453
- this._cleanupEventListeners();
2454
2474
  }
2455
2475
  async destroy() {
2456
2476
  await this._call("destroy");
@@ -2576,12 +2596,11 @@ class Window {
2576
2596
  }
2577
2597
  async _call(action, data) {
2578
2598
  if (isWebKitHost()) {
2579
- return webkitRequest(`window.${action}`, {
2580
- type: "window",
2581
- action,
2582
- windowId: this._id,
2583
- data
2584
- });
2599
+ const injected = getInjectedWindowBridge();
2600
+ if (!injected?._call) {
2601
+ throw new Error("Craft window bridge is unavailable");
2602
+ }
2603
+ return injected._call(action, data, this._id);
2585
2604
  }
2586
2605
  const bridge = getBridge();
2587
2606
  return bridge.request(`window.${action}`, { windowId: this._id, ...data });
@@ -2613,16 +2632,38 @@ class WindowManager {
2613
2632
  }
2614
2633
  async create(options = {}) {
2615
2634
  const id = options.id || `window_${++this._idCounter}_${Date.now()}`;
2635
+ if (id === CURRENT_WINDOW_ID) {
2636
+ throw new Error(`Window id "${CURRENT_WINDOW_ID}" is reserved for the current window`);
2637
+ }
2616
2638
  const existing = this._windows.get(id);
2617
- const bridge = getBridge();
2618
- await bridge.request("window.create", { ...options, id });
2619
- if (existing)
2639
+ if (isWebKitHost()) {
2640
+ const injected = getInjectedWindowBridge();
2641
+ if (!injected?.open) {
2642
+ throw new Error("Craft window bridge is unavailable");
2643
+ }
2644
+ await injected.open({ ...options, id });
2645
+ } else {
2646
+ const bridge = getBridge();
2647
+ await bridge.request("window.create", { ...options, id });
2648
+ }
2649
+ if (existing) {
2650
+ const retained = existing;
2651
+ retained._markOpen();
2620
2652
  return existing;
2653
+ }
2621
2654
  const win = new Window(id);
2622
2655
  this._windows.set(id, win);
2623
2656
  return win;
2624
2657
  }
2625
2658
  async getFocused() {
2659
+ if (isWebKitHost()) {
2660
+ const injected = getInjectedWindowBridge();
2661
+ if (!injected?._call) {
2662
+ throw new Error("Craft window bridge is unavailable");
2663
+ }
2664
+ const id2 = await injected._call("getFocused", undefined, "main");
2665
+ return id2 ? this._windows.get(id2) || null : null;
2666
+ }
2626
2667
  const bridge = getBridge();
2627
2668
  const id = await bridge.request("window.getFocused");
2628
2669
  return id ? this._windows.get(id) || null : null;
@@ -4613,6 +4654,15 @@ async function open(target) {
4613
4654
  throw new Error(`Unsupported platform: ${platform}`);
4614
4655
  }
4615
4656
  }
4657
+ // src/api/live-activity-handle.ts
4658
+ function createLiveActivityHandle(id, update, end) {
4659
+ return {
4660
+ id,
4661
+ update,
4662
+ end
4663
+ };
4664
+ }
4665
+
4616
4666
  // src/api/mobile.ts
4617
4667
  var WEB_DEVICE_ID_KEY = "__craft_web_device_id__";
4618
4668
  function _webFallbackDeviceId() {
@@ -5039,11 +5089,18 @@ var share = {
5039
5089
  return craft.share.share(options);
5040
5090
  }
5041
5091
  if (navigator.share) {
5042
- await navigator.share({
5043
- title: options.title,
5044
- text: options.text,
5045
- url: options.url
5046
- });
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
+ }
5047
5104
  } else {
5048
5105
  throw new Error("Share API not available");
5049
5106
  }
@@ -5250,10 +5307,8 @@ var liveActivities = {
5250
5307
  return window.craft?.liveActivities?.areEnabled?.() ?? false;
5251
5308
  },
5252
5309
  async start(config) {
5253
- if (!isCraft() || getPlatform() !== "ios") {
5254
- return { id: `mock-${Date.now()}` };
5255
- }
5256
- return window.craft?.liveActivities?.start?.(config) ?? { id: `mock-${Date.now()}` };
5310
+ const result = !isCraft() || getPlatform() !== "ios" ? { id: `mock-${Date.now()}` } : await (window.craft?.liveActivities?.start?.(config) ?? { id: `mock-${Date.now()}` });
5311
+ return createLiveActivityHandle(result.id, (state) => liveActivities.update(result.id, state), (finalState) => liveActivities.end(result.id, finalState));
5257
5312
  },
5258
5313
  async update(activityId, contentState, alertConfig) {
5259
5314
  if (!isCraft() || getPlatform() !== "ios")
package/dist/index.js CHANGED
@@ -2102,6 +2102,13 @@ function webkitRequest(bucket, message, options = {}) {
2102
2102
  }
2103
2103
 
2104
2104
  // src/api/window.ts
2105
+ var CURRENT_WINDOW_ID = "main";
2106
+ function getInjectedWindowBridge() {
2107
+ if (typeof globalThis.window === "undefined")
2108
+ return;
2109
+ return globalThis.window.craft?.window;
2110
+ }
2111
+
2105
2112
  class Window {
2106
2113
  _id;
2107
2114
  _listeners = new Map;
@@ -2118,7 +2125,9 @@ class Window {
2118
2125
  return this._closed;
2119
2126
  }
2120
2127
  _setupEventListeners() {
2121
- if (typeof window2 !== "undefined") {
2128
+ if (this._domListeners.length > 0)
2129
+ return;
2130
+ if (typeof globalThis.window !== "undefined") {
2122
2131
  const eventTypes = [
2123
2132
  "show",
2124
2133
  "hide",
@@ -2139,7 +2148,11 @@ class Window {
2139
2148
  eventTypes.forEach((type) => {
2140
2149
  const handler = (event) => {
2141
2150
  if (event.detail?.windowId === this._id || !event.detail?.windowId) {
2142
- this._emit(type, event.detail?.data);
2151
+ if (type === "close" || type === "closed")
2152
+ this._closed = true;
2153
+ else if (type === "show" || type === "focus")
2154
+ this._closed = false;
2155
+ this._emit(type, event.detail?.data ?? event.detail);
2143
2156
  }
2144
2157
  };
2145
2158
  const eventName = `craft:window:${type}`;
@@ -2149,13 +2162,17 @@ class Window {
2149
2162
  }
2150
2163
  }
2151
2164
  _cleanupEventListeners() {
2152
- if (typeof window2 !== "undefined") {
2165
+ if (typeof globalThis.window !== "undefined") {
2153
2166
  for (const { type, handler } of this._domListeners) {
2154
2167
  globalThis.window.removeEventListener(type, handler);
2155
2168
  }
2156
2169
  this._domListeners = [];
2157
2170
  }
2158
2171
  }
2172
+ _markOpen() {
2173
+ this._closed = false;
2174
+ this._setupEventListeners();
2175
+ }
2159
2176
  _emit(event, data) {
2160
2177
  const listeners = this._listeners.get(event);
2161
2178
  if (listeners) {
@@ -2183,15 +2200,19 @@ class Window {
2183
2200
  }
2184
2201
  async show() {
2185
2202
  await this._call("show");
2203
+ this._markOpen();
2186
2204
  }
2187
2205
  async hide() {
2188
2206
  await this._call("hide");
2189
2207
  }
2190
2208
  async toggle() {
2191
2209
  await this._call("toggle");
2210
+ if (this._closed)
2211
+ this._markOpen();
2192
2212
  }
2193
2213
  async focus() {
2194
2214
  await this._call("focus");
2215
+ this._markOpen();
2195
2216
  }
2196
2217
  async blur() {
2197
2218
  await this._call("blur");
@@ -2211,7 +2232,6 @@ class Window {
2211
2232
  async close() {
2212
2233
  await this._call("close");
2213
2234
  this._closed = true;
2214
- this._cleanupEventListeners();
2215
2235
  }
2216
2236
  async destroy() {
2217
2237
  await this._call("destroy");
@@ -2337,12 +2357,11 @@ class Window {
2337
2357
  }
2338
2358
  async _call(action, data) {
2339
2359
  if (isWebKitHost()) {
2340
- return webkitRequest(`window.${action}`, {
2341
- type: "window",
2342
- action,
2343
- windowId: this._id,
2344
- data
2345
- });
2360
+ const injected = getInjectedWindowBridge();
2361
+ if (!injected?._call) {
2362
+ throw new Error("Craft window bridge is unavailable");
2363
+ }
2364
+ return injected._call(action, data, this._id);
2346
2365
  }
2347
2366
  const bridge = getBridge();
2348
2367
  return bridge.request(`window.${action}`, { windowId: this._id, ...data });
@@ -2374,16 +2393,38 @@ class WindowManager {
2374
2393
  }
2375
2394
  async create(options = {}) {
2376
2395
  const id = options.id || `window_${++this._idCounter}_${Date.now()}`;
2396
+ if (id === CURRENT_WINDOW_ID) {
2397
+ throw new Error(`Window id "${CURRENT_WINDOW_ID}" is reserved for the current window`);
2398
+ }
2377
2399
  const existing = this._windows.get(id);
2378
- const bridge = getBridge();
2379
- await bridge.request("window.create", { ...options, id });
2380
- if (existing)
2400
+ if (isWebKitHost()) {
2401
+ const injected = getInjectedWindowBridge();
2402
+ if (!injected?.open) {
2403
+ throw new Error("Craft window bridge is unavailable");
2404
+ }
2405
+ await injected.open({ ...options, id });
2406
+ } else {
2407
+ const bridge = getBridge();
2408
+ await bridge.request("window.create", { ...options, id });
2409
+ }
2410
+ if (existing) {
2411
+ const retained = existing;
2412
+ retained._markOpen();
2381
2413
  return existing;
2414
+ }
2382
2415
  const win = new Window(id);
2383
2416
  this._windows.set(id, win);
2384
2417
  return win;
2385
2418
  }
2386
2419
  async getFocused() {
2420
+ if (isWebKitHost()) {
2421
+ const injected = getInjectedWindowBridge();
2422
+ if (!injected?._call) {
2423
+ throw new Error("Craft window bridge is unavailable");
2424
+ }
2425
+ const id2 = await injected._call("getFocused", undefined, "main");
2426
+ return id2 ? this._windows.get(id2) || null : null;
2427
+ }
2387
2428
  const bridge = getBridge();
2388
2429
  const id = await bridge.request("window.getFocused");
2389
2430
  return id ? this._windows.get(id) || null : null;
@@ -4374,6 +4415,15 @@ async function open(target) {
4374
4415
  throw new Error(`Unsupported platform: ${platform}`);
4375
4416
  }
4376
4417
  }
4418
+ // src/api/live-activity-handle.ts
4419
+ function createLiveActivityHandle(id, update, end) {
4420
+ return {
4421
+ id,
4422
+ update,
4423
+ end
4424
+ };
4425
+ }
4426
+
4377
4427
  // src/api/mobile.ts
4378
4428
  var WEB_DEVICE_ID_KEY = "__craft_web_device_id__";
4379
4429
  function _webFallbackDeviceId() {
@@ -4800,11 +4850,18 @@ var share = {
4800
4850
  return craft.share.share(options);
4801
4851
  }
4802
4852
  if (navigator.share) {
4803
- await navigator.share({
4804
- title: options.title,
4805
- text: options.text,
4806
- url: options.url
4807
- });
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
+ }
4808
4865
  } else {
4809
4866
  throw new Error("Share API not available");
4810
4867
  }
@@ -5011,10 +5068,8 @@ var liveActivities = {
5011
5068
  return window.craft?.liveActivities?.areEnabled?.() ?? false;
5012
5069
  },
5013
5070
  async start(config) {
5014
- if (!isCraft() || getPlatform() !== "ios") {
5015
- return { id: `mock-${Date.now()}` };
5016
- }
5017
- return window.craft?.liveActivities?.start?.(config) ?? { id: `mock-${Date.now()}` };
5071
+ const result = !isCraft() || getPlatform() !== "ios" ? { id: `mock-${Date.now()}` } : await (window.craft?.liveActivities?.start?.(config) ?? { id: `mock-${Date.now()}` });
5072
+ return createLiveActivityHandle(result.id, (state) => liveActivities.update(result.id, state), (finalState) => liveActivities.end(result.id, finalState));
5018
5073
  },
5019
5074
  async update(activityId, contentState, alertConfig) {
5020
5075
  if (!isCraft() || getPlatform() !== "ios")
@@ -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.
@@ -150,7 +150,7 @@ ${plistArray(config.appGroups)}
150
150
  }
151
151
  if (config.enablePushNotifications) {
152
152
  entries.push(` <key>aps-environment</key>
153
- <string>development</string>`);
153
+ <string>$(CRAFT_APNS_ENVIRONMENT)</string>`);
154
154
  }
155
155
  return `<?xml version="1.0" encoding="UTF-8"?>
156
156
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -348,7 +348,7 @@ async function init(options) {
348
348
  `);
349
349
  const dirs = [output, join(output, "Sources"), join(output, "Shared"), join(output, "dist")];
350
350
  if (options.config?.enableWatchApp)
351
- dirs.push(join(output, "WatchApp"));
351
+ dirs.push(join(output, "WatchApp"), join(output, "WatchExtension"));
352
352
  for (const dir of dirs) {
353
353
  if (!existsSync(dir)) {
354
354
  mkdirSync(dir, { recursive: true });
@@ -400,7 +400,7 @@ async function init(options) {
400
400
  }
401
401
  if (config.enableWatchApp) {
402
402
  nativeTargets.push(` ${name}Watch:
403
- type: application
403
+ type: application.watchapp2
404
404
  platform: watchOS
405
405
  deploymentTarget: "${config.watchosVersion || "9.0"}"
406
406
  sources:
@@ -410,6 +410,21 @@ async function init(options) {
410
410
  CODE_SIGN_ENTITLEMENTS: WatchApp/Watch.entitlements
411
411
  PRODUCT_BUNDLE_IDENTIFIER: ${finalBundleId}.watchkitapp
412
412
  SWIFT_VERSION: "5.0"
413
+ SKIP_INSTALL: YES
414
+ dependencies:
415
+ - target: ${name}WatchExtension
416
+ ${name}WatchExtension:
417
+ type: watchkit2-extension
418
+ platform: watchOS
419
+ deploymentTarget: "${config.watchosVersion || "9.0"}"
420
+ sources:
421
+ - WatchExtension
422
+ settings:
423
+ INFOPLIST_FILE: WatchExtension/Info.plist
424
+ CODE_SIGN_ENTITLEMENTS: WatchExtension/Watch.entitlements
425
+ PRODUCT_BUNDLE_IDENTIFIER: ${finalBundleId}.watchkitapp.watchkitextension
426
+ SWIFT_VERSION: "5.0"
427
+ APPLICATION_EXTENSION_API_ONLY: YES
413
428
  SKIP_INSTALL: YES`);
414
429
  }
415
430
  const runtimeDir = resolveRuntimeDir(options.runtimeDir);
@@ -437,10 +452,13 @@ ${nativeDependencies.join(`
437
452
  }
438
453
  if (config.enableWatchApp) {
439
454
  const watchSource = readFileSync(join(TEMPLATES_DIR, "CraftWatchApp.swift.template"), "utf8").replace(/\{\{APP_NAME\}\}/g, name);
440
- writeFileSync(join(output, "WatchApp", `${name}WatchApp.swift`), watchSource);
455
+ writeFileSync(join(output, "WatchExtension", `${name}WatchApp.swift`), watchSource);
441
456
  const watchInfo = readFileSync(join(TEMPLATES_DIR, "WatchApp.Info.plist.template"), "utf8").replace(/\{\{APP_NAME\}\}/g, name).replace(/\{\{BUNDLE_ID\}\}/g, finalBundleId);
442
457
  writeFileSync(join(output, "WatchApp", "Info.plist"), watchInfo);
443
458
  writeFileSync(join(output, "WatchApp", "Watch.entitlements"), renderWatchEntitlements(config));
459
+ const watchExtensionInfo = readFileSync(join(TEMPLATES_DIR, "WatchExtension.Info.plist.template"), "utf8").replace(/\{\{APP_NAME\}\}/g, name).replace(/\{\{BUNDLE_ID\}\}/g, finalBundleId);
460
+ writeFileSync(join(output, "WatchExtension", "Info.plist"), watchExtensionInfo);
461
+ writeFileSync(join(output, "WatchExtension", "Watch.entitlements"), renderWatchEntitlements(config));
444
462
  }
445
463
  const placeholderHtml = `<!DOCTYPE html>
446
464
  <html>
@@ -541,7 +559,7 @@ async function open(options) {
541
559
  await $`open ${projectPath}`;
542
560
  }
543
561
  function orderSimulators(devices) {
544
- return [...devices].sort((left, right) => {
562
+ return devices.filter((device) => device.runtime.startsWith("iOS")).sort((left, right) => {
545
563
  const booted = Number(right.state === "Booted") - Number(left.state === "Booted");
546
564
  if (booted !== 0)
547
565
  return booted;