craft-native 0.0.175 → 0.0.177

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 (34) hide show
  1. package/dist/android/src/index.d.ts +19 -0
  2. package/dist/android/src/index.js +19 -2
  3. package/dist/android/templates/AndroidManifest.xml.template +1 -0
  4. package/dist/android/templates/CraftBridge.kt.template +51 -0
  5. package/dist/android/templates/CraftCrashReporter.kt.template +544 -0
  6. package/dist/android/templates/CraftNative.kt.template +17 -0
  7. package/dist/android/templates/CraftNativeFlatList.kt.template +20 -9
  8. package/dist/android/templates/CraftNativeMutation.kt.template +4 -1
  9. package/dist/android/templates/CraftNativeServices.kt.template +31 -1
  10. package/dist/android/templates/MainActivityNative.kt.template +986 -304
  11. package/dist/android/templates/build.gradle.kts.app.template +4 -2
  12. package/dist/api/mobile.d.ts +31 -0
  13. package/dist/cli.js +1 -1
  14. package/dist/components/index.d.ts +203 -2
  15. package/dist/index.cjs +1 -1
  16. package/dist/index.d.cts +1 -1
  17. package/dist/index.d.ts +1 -1
  18. package/dist/index.js +1 -1
  19. package/dist/ios/src/index.d.ts +19 -0
  20. package/dist/ios/src/index.js +7 -1
  21. package/dist/ios/src/testing.d.ts +5 -0
  22. package/dist/ios/templates/CraftApp.swift +51 -1
  23. package/dist/ios/templates/CraftCrashReporter.swift +1010 -0
  24. package/dist/ios/templates/CraftHybrid.swift +10 -0
  25. package/dist/ios/templates/CraftNativeActions.swift +49 -1
  26. package/dist/ios/templates/CraftNativeFlatList.swift +30 -28
  27. package/dist/ios/templates/CraftNativeMutation.swift +1 -1
  28. package/dist/ios/templates/CraftNativeScreen.swift +559 -86
  29. package/dist/ios/templates/project.yml.template +1 -0
  30. package/dist/ios/templates/testing/DriverTests.swift.template +8 -0
  31. package/dist/macos/templates/CraftNativeMutation.swift +1 -1
  32. package/dist/macos/templates/native_layout.zig +389 -17
  33. package/dist/mobile.js +66 -0
  34. package/package.json +1 -1
@@ -77,6 +77,25 @@ export interface CraftAndroidConfig {
77
77
  enabled?: boolean;
78
78
  minimumIntervalMinutes?: number;
79
79
  };
80
+ /**
81
+ * Crash and error reporting to bughq (CraftCrashReporter.kt): uncaught JVM
82
+ * exceptions, ANRs and native crashes (ApplicationExitInfo), uncaught
83
+ * JavaScript errors, and what the page sends through `craft.crashReporter`.
84
+ * Queued on disk and sent when online. Release builds are minified: upload
85
+ * `app/build/outputs/mapping/release/mapping.txt` so bughq can retrace.
86
+ */
87
+ crashReporting?: {
88
+ /** The bughq project's public ingest key. */
89
+ key: string;
90
+ /** Default https://bughq.org. */
91
+ host?: string;
92
+ /** Default `production`. */
93
+ environment?: string;
94
+ /** Default `<versionName>+<versionCode>`, e.g. `1.0.0+41`. */
95
+ release?: string;
96
+ /** One session per launch, for crash-free sessions. Default on. */
97
+ sessions?: boolean;
98
+ };
80
99
  }
81
100
  /** The shared health data types (the iOS builder's HealthKit names). */
82
101
  export type HealthConnectType = 'steps' | 'heartRate' | 'activeEnergy' | 'distance' | 'workouts' | 'restingHeartRate' | 'heartRateVariability' | 'bodyMass' | 'sleep' | 'mindful' | 'dietaryEnergy' | 'dietaryProtein' | 'dietaryCarbohydrates' | 'dietaryFat';
@@ -79,6 +79,21 @@ function renderAndroidPromiseRuntime(indent = "") {
79
79
 
80
80
  // ../android/src/index.ts
81
81
  var TEMPLATES_DIR = join(dirname(import.meta.dir), "templates");
82
+ function resolveCraftAndroidVersion(startDir = import.meta.dir) {
83
+ let directory = resolve(startDir);
84
+ while (true) {
85
+ try {
86
+ const pkg = JSON.parse(readFileSync(join(directory, "package.json"), "utf8"));
87
+ if ((pkg.name === "@craft-native/android" || pkg.name === "craft-native") && typeof pkg.version === "string" && pkg.version)
88
+ return pkg.version;
89
+ } catch {}
90
+ const parent = dirname(directory);
91
+ if (parent === directory)
92
+ return "0.0.0";
93
+ directory = parent;
94
+ }
95
+ }
96
+ var CRAFT_ANDROID_VERSION = resolveCraftAndroidVersion();
82
97
  var LOCAL_DEVELOPMENT_HOSTS = new Set(["localhost", "127.0.0.1", "10.0.2.2"]);
83
98
  var KOTLIN_KEYWORDS = new Set([
84
99
  "as",
@@ -530,6 +545,7 @@ async function init(options) {
530
545
  const mainActivityTemplate = readFileSync(join(TEMPLATES_DIR, config.renderer === "native" ? "MainActivityNative.kt.template" : "MainActivity.kt.template"), "utf-8");
531
546
  const mainActivity = mainActivityTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName).replace(/\{\{APP_NAME\}\}/g, name);
532
547
  writeFileSync(join(output, "app/src/main/java", packagePath, "MainActivity.kt"), mainActivity);
548
+ writeFileSync(join(output, "app/src/main/java", packagePath, "CraftCrashReporter.kt"), readFileSync(join(TEMPLATES_DIR, "CraftCrashReporter.kt.template"), "utf-8").replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName).replace(/\{\{CRAFT_VERSION\}\}/g, CRAFT_ANDROID_VERSION));
533
549
  if (config.renderer === "native") {
534
550
  const mutationTemplate = readFileSync(join(TEMPLATES_DIR, "CraftNativeMutation.kt.template"), "utf-8");
535
551
  const flatListTemplate = readFileSync(join(TEMPLATES_DIR, "CraftNativeFlatList.kt.template"), "utf-8");
@@ -573,7 +589,7 @@ async function init(options) {
573
589
  const healthTemplate = readFileSync(join(TEMPLATES_DIR, config.enableHealthConnect ? "CraftHealthConnect.kt.template" : "CraftHealthConnectStub.kt.template"), "utf-8");
574
590
  writeFileSync(join(output, "app/src/main/java", packagePath, "CraftHealthConnect.kt"), healthTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName));
575
591
  const manifestTemplate = readFileSync(join(TEMPLATES_DIR, "AndroidManifest.xml.template"), "utf-8");
576
- const manifest = manifestTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName).replace(/\{\{APP_NAME\}\}/g, name).replace(/\{\{PERMISSIONS\}\}/g, renderAndroidPermissions(config)).replace(/\{\{USES_CLEARTEXT\}\}/g, config.devServerURL?.startsWith("http://") || config.enableLocalNetwork ? "true" : "false").replace(/\{\{DEEP_LINK_INTENT_FILTERS\}\}/g, renderAndroidDeepLinks(config)).replace(/\{\{BACKGROUND_SERVICE\}\}/g, config.enableBackgroundLocation ? ' <service android:name="com.craft.runtime.LocationRecordingService" android:exported="false" android:foregroundServiceType="location" android:stopWithTask="false" />' : "").replace(/\{\{NATIVE_NOTIFICATION_RECEIVER\}\}/g, config.renderer === "native" ? renderNativeShellManifest(config) : "").replace(/\{\{GOOGLE_MAPS_API_KEY\}\}/g, config.mapsApiKey ? ` <meta-data android:name="com.google.android.geo.API_KEY" android:value="${escapeXml(config.mapsApiKey)}" />` : "").replace(/\{\{HEALTH_CONNECT_QUERIES\}\}/g, config.enableHealthConnect ? ` <queries>
592
+ const manifest = manifestTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName).replace(/\{\{APP_NAME\}\}/g, name).replace(/\{\{PERMISSIONS\}\}/g, renderAndroidPermissions(config)).replace(/\{\{USES_CLEARTEXT\}\}/g, config.devServerURL?.startsWith("http://") || config.enableLocalNetwork ? "true" : "false").replace(/\{\{DEEP_LINK_INTENT_FILTERS\}\}/g, renderAndroidDeepLinks(config)).replace(/\{\{BACKGROUND_SERVICE\}\}/g, config.enableBackgroundLocation ? ' <service android:name="com.craft.runtime.LocationRecordingService" android:exported="false" android:foregroundServiceType="location" android:stopWithTask="false" />' : "").replace(/\{\{NATIVE_NOTIFICATION_RECEIVER\}\}/g, config.renderer === "native" ? renderNativeShellManifest(config) : "").replace(/\{\{CRASH_REPORTER_PROVIDER\}\}/g, ` <provider android:name=".CraftCrashReporterInit" android:authorities="\${applicationId}.craft-crash" android:exported="false" android:initOrder="1000" />`).replace(/\{\{GOOGLE_MAPS_API_KEY\}\}/g, config.mapsApiKey ? ` <meta-data android:name="com.google.android.geo.API_KEY" android:value="${escapeXml(config.mapsApiKey)}" />` : "").replace(/\{\{HEALTH_CONNECT_QUERIES\}\}/g, config.enableHealthConnect ? ` <queries>
577
593
  <package android:name="com.google.android.apps.healthdata" />
578
594
  </queries>` : "").replace(/\{\{HEALTH_CONNECT_RATIONALE\}\}/g, config.enableHealthConnect ? ` <intent-filter>
579
595
  <action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
@@ -586,7 +602,8 @@ async function init(options) {
586
602
  const projectGradleTemplate = readFileSync(join(TEMPLATES_DIR, "build.gradle.kts.project.template"), "utf-8");
587
603
  writeFileSync(join(output, "build.gradle.kts"), projectGradleTemplate.replace(/\{\{GOOGLE_SERVICES_PLUGIN\}\}/g, hasGoogleServices ? ' id("com.google.gms.google-services") version "4.4.2" apply false' : ""));
588
604
  const appGradleTemplate = readFileSync(join(TEMPLATES_DIR, "build.gradle.kts.app.template"), "utf-8");
589
- const appGradle = appGradleTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName).replace(/\{\{VERSION_NAME\}\}/g, escapeKotlinString(config.version || "1.0.0")).replace(/\{\{VERSION_CODE\}\}/g, String(config.versionCode || 1)).replace(/\{\{MIN_SDK\}\}/g, String(config.minSdk ?? ANDROID_MIN_SDK)).replace(/\{\{COMPILE_SDK\}\}/g, String(config.compileSdk || 36)).replace(/\{\{TARGET_SDK\}\}/g, String(config.targetSdk || 35)).replace(/\{\{GOOGLE_SERVICES_PLUGIN\}\}/g, hasGoogleServices ? ' id("com.google.gms.google-services")' : "").replace(/\{\{FIREBASE_MESSAGING_DEPENDENCY\}\}/g, config.enablePushNotifications ? ' implementation("com.google.firebase:firebase-messaging:24.1.0")' : "").replace(/\{\{REFRESH_LAYOUT_DEPENDENCY\}\}/g, config.renderer === "native" ? ' implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")' : "").replace(/\{\{HEALTH_CONNECT_DEPENDENCIES\}\}/g, config.enableHealthConnect ? ` implementation("androidx.health.connect:connect-client:1.1.0")
605
+ const appGradle = appGradleTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName).replace(/\{\{VERSION_NAME\}\}/g, escapeKotlinString(config.version || "1.0.0")).replace(/\{\{VERSION_CODE\}\}/g, String(config.versionCode || 1)).replace(/\{\{MIN_SDK\}\}/g, String(config.minSdk ?? ANDROID_MIN_SDK)).replace(/\{\{COMPILE_SDK\}\}/g, String(config.compileSdk || 36)).replace(/\{\{TARGET_SDK\}\}/g, String(config.targetSdk || 35)).replace(/\{\{GOOGLE_SERVICES_PLUGIN\}\}/g, hasGoogleServices ? ' id("com.google.gms.google-services")' : "").replace(/\{\{FIREBASE_MESSAGING_DEPENDENCY\}\}/g, config.enablePushNotifications ? ' implementation("com.google.firebase:firebase-messaging:24.1.0")' : "").replace(/\{\{REFRESH_LAYOUT_DEPENDENCY\}\}/g, config.renderer === "native" ? ' implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")' : "").replace(/\{\{GOOGLE_MAPS_DEPENDENCY\}\}/g, config.renderer === "native" ? ` // Native STX Map overlays and trail routes
606
+ implementation("com.google.android.gms:play-services-maps:18.2.0")` : "").replace(/\{\{HEALTH_CONNECT_DEPENDENCIES\}\}/g, config.enableHealthConnect ? ` implementation("androidx.health.connect:connect-client:1.1.0")
590
607
  implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1")` : "").replace(/\{\{JAVASCRIPT_ENGINE_DEPENDENCY\}\}/g, config.renderer === "native" ? ` implementation("androidx.javascriptengine:javascriptengine:1.1.0")
591
608
  // The in-app browser (Custom Tabs) and background refresh (WorkManager).
592
609
  implementation("androidx.browser:browser:1.8.0")
@@ -48,6 +48,7 @@
48
48
 
49
49
  {{BACKGROUND_SERVICE}}
50
50
  {{NATIVE_NOTIFICATION_RECEIVER}}
51
+ {{CRASH_REPORTER_PROVIDER}}
51
52
 
52
53
  </application>
53
54
 
@@ -418,6 +418,7 @@ class CraftBridge(
418
418
  appReview: true,
419
419
  flashlight: {{ENABLE_CAMERA}} && ${hasFlashlight()},
420
420
  speech: true,
421
+ crashReporter: ${CraftCrashReporter.enabled},
421
422
  brightness: true,
422
423
  openURL: true,
423
424
  vibrationPattern: true,
@@ -1748,6 +1749,26 @@ class CraftBridge(
1748
1749
  return Promise.resolve().then(function() { CraftAndroid.stopSpeaking(); });
1749
1750
  }
1750
1751
  };
1752
+ // Crash and error reporting (CraftCrashReporter.kt): the page's
1753
+ // error SDK hands its events here, so they share the app's
1754
+ // on-disk outbox, its session and the device's own context.
1755
+ craft.crashReporter = {
1756
+ capture: function(event) {
1757
+ return Promise.resolve().then(function() { return CraftAndroid.crashReporterCapture(JSON.stringify(event || {})); });
1758
+ },
1759
+ setUser: function(id) {
1760
+ return Promise.resolve().then(function() { return CraftAndroid.crashReporterSetUser(id == null ? '' : String(id)); });
1761
+ },
1762
+ addBreadcrumb: function(breadcrumb) {
1763
+ return Promise.resolve().then(function() { return CraftAndroid.crashReporterAddBreadcrumb(JSON.stringify(breadcrumb || {})); });
1764
+ },
1765
+ flush: function() {
1766
+ return Promise.resolve().then(function() { return CraftAndroid.crashReporterFlush(); });
1767
+ },
1768
+ context: function() {
1769
+ return Promise.resolve().then(function() { return JSON.parse(CraftAndroid.crashReporterContext()); });
1770
+ }
1771
+ };
1751
1772
  craft.location = {
1752
1773
  getCurrentPosition: craft.getCurrentPosition,
1753
1774
  watchPosition: craft.watchPosition,
@@ -1859,6 +1880,36 @@ class CraftBridge(
1859
1880
  return true
1860
1881
  }
1861
1882
 
1883
+ // ==================== Crash reporting ====================
1884
+
1885
+ /** Queues an event the page built; false when reporting is off or the event has no message. */
1886
+ @JavascriptInterface
1887
+ fun crashReporterCapture(eventJson: String): Boolean =
1888
+ runCatching { CraftCrashReporter.capture(JSONObject(eventJson)) }.getOrDefault(false)
1889
+
1890
+ @JavascriptInterface
1891
+ fun crashReporterSetUser(id: String): Boolean {
1892
+ CraftCrashReporter.setUser(id)
1893
+ return CraftCrashReporter.enabled
1894
+ }
1895
+
1896
+ @JavascriptInterface
1897
+ fun crashReporterAddBreadcrumb(breadcrumbJson: String): Boolean {
1898
+ val crumb = runCatching { JSONObject(breadcrumbJson) }.getOrNull() ?: return false
1899
+ CraftCrashReporter.addBreadcrumb(crumb)
1900
+ return CraftCrashReporter.enabled
1901
+ }
1902
+
1903
+ @JavascriptInterface
1904
+ fun crashReporterFlush(): Boolean {
1905
+ CraftCrashReporter.requestFlush()
1906
+ return CraftCrashReporter.enabled
1907
+ }
1908
+
1909
+ @JavascriptInterface
1910
+ fun crashReporterContext(): String =
1911
+ if (CraftCrashReporter.enabled) CraftCrashReporter.context().toString() else JSONObject().put("enabled", false).toString()
1912
+
1862
1913
  // ==================== Speech Recognition ====================
1863
1914
 
1864
1915
  /**