craft-native 0.0.109 → 0.0.111

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.
@@ -25,6 +25,8 @@ export interface CraftAndroidConfig {
25
25
  enableHealthConnect?: boolean;
26
26
  enableKeepAwake?: boolean;
27
27
  enableDeepLinks?: boolean;
28
+ /** Reach the person's own devices on the local network over plain HTTP (a Mac companion). */
29
+ enableLocalNetwork?: boolean;
28
30
  urlSchemes?: string[];
29
31
  trustedOrigins?: string[];
30
32
  appIconPath?: string;
@@ -445,7 +445,7 @@ async function init(options) {
445
445
  const healthTemplate = readFileSync(join(TEMPLATES_DIR, config.enableHealthConnect ? "CraftHealthConnect.kt.template" : "CraftHealthConnectStub.kt.template"), "utf-8");
446
446
  writeFileSync(join(output, "app/src/main/java", packagePath, "CraftHealthConnect.kt"), healthTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName));
447
447
  const manifestTemplate = readFileSync(join(TEMPLATES_DIR, "AndroidManifest.xml.template"), "utf-8");
448
- 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://") ? "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(/\{\{HEALTH_CONNECT_QUERIES\}\}/g, config.enableHealthConnect ? ` <queries>
448
+ 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(/\{\{HEALTH_CONNECT_QUERIES\}\}/g, config.enableHealthConnect ? ` <queries>
449
449
  <package android:name="com.google.android.apps.healthdata" />
450
450
  </queries>` : "").replace(/\{\{HEALTH_CONNECT_RATIONALE\}\}/g, config.enableHealthConnect ? ` <intent-filter>
451
451
  <action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
package/dist/cli.js CHANGED
@@ -3303,7 +3303,7 @@ function pinCraftNativeDependency(packagePath, craftVersion) {
3303
3303
  `);
3304
3304
  }
3305
3305
  // package.json
3306
- var version = "0.0.109";
3306
+ var version = "0.0.111";
3307
3307
 
3308
3308
  // bin/cli.ts
3309
3309
  var spawnedFrom = process6.env[CRAFT_CLI_SPAWN_MARKER];
package/dist/index.cjs CHANGED
@@ -310,7 +310,7 @@ function craftBinaryNotFoundMessage(triedPath) {
310
310
  `);
311
311
  }
312
312
  // package.json
313
- var version = "0.0.109";
313
+ var version = "0.0.111";
314
314
 
315
315
  // src/sdk-version.ts
316
316
  var SDK_VERSION = version;
package/dist/index.js CHANGED
@@ -58,7 +58,7 @@ function craftBinaryNotFoundMessage(triedPath) {
58
58
  `);
59
59
  }
60
60
  // package.json
61
- var version = "0.0.109";
61
+ var version = "0.0.111";
62
62
 
63
63
  // src/sdk-version.ts
64
64
  var SDK_VERSION = version;
@@ -40,6 +40,8 @@ export interface CraftConfig {
40
40
  enableOrientationLock?: boolean;
41
41
  enableDeepLinks?: boolean;
42
42
  enableQRScanner?: boolean;
43
+ /** Reach devices on the local network (a Mac on the same Wi-Fi); iOS asks first. */
44
+ enableLocalNetwork?: boolean;
43
45
  enableFilePicker?: boolean;
44
46
  enableFileDownload?: boolean;
45
47
  enableSocialAuth?: boolean;
@@ -27,6 +27,7 @@ var DEFAULT_CONFIG = {
27
27
  enableOrientationLock: false,
28
28
  enableDeepLinks: false,
29
29
  enableQRScanner: false,
30
+ enableLocalNetwork: false,
30
31
  enableFilePicker: false,
31
32
  enableFileDownload: false,
32
33
  enableSocialAuth: false,
@@ -91,6 +92,7 @@ function renderUsageDescriptions(config) {
91
92
  [config.enableBluetooth, "NSBluetoothAlwaysUsageDescription", `${config.appName} uses Bluetooth to connect to nearby devices.`],
92
93
  [config.enableMotionSensors, "NSMotionUsageDescription", `${config.appName} uses motion data for activity features.`],
93
94
  [config.enableNFC, "NFCReaderUsageDescription", `${config.appName} reads NFC tags when you start a scan.`],
95
+ [config.enableLocalNetwork, "NSLocalNetworkUsageDescription", `${config.appName} connects to your own devices on this network.`],
94
96
  [config.enableHealthKit, "NSHealthShareUsageDescription", `${config.appName} reads health data you choose to share.`],
95
97
  [config.enableHealthKit, "NSHealthUpdateUsageDescription", `${config.appName} writes health data only with your permission.`],
96
98
  [config.enableBiometric, "NSFaceIDUsageDescription", `${config.appName} uses Face ID to protect your account.`]
@@ -3392,6 +3392,11 @@ struct CraftWebView: UIViewRepresentable {
3392
3392
  getBiometricType: function() { return Promise.resolve(craft.capabilities.biometric ? 'faceId' : null); },
3393
3393
  authenticate: function(reason) { return legacyAuthenticate(reason); }
3394
3394
  };
3395
+ // The app's own SQLite database (Documents/craft.db), as on Android.
3396
+ craft.db = {
3397
+ execute: function(sql, params) { return craft._invoke('dbExecute', { sql: sql, params: params || [] }); },
3398
+ query: function(sql, params) { return craft._invoke('dbQuery', { sql: sql, params: params || [] }); }
3399
+ };
3395
3400
  craft.secureStorage = {
3396
3401
  set: function(key, value) { return legacySecureStore.set(key, value).then(function() {}); },
3397
3402
  get: function(key) { return legacySecureStore.get(key); },
@@ -5044,6 +5049,34 @@ struct CraftWebView: UIViewRepresentable {
5044
5049
  }
5045
5050
 
5046
5051
  // MARK: - Local Database (SQLite)
5052
+ /// SQLite copies what it is given only when told to (SQLITE_TRANSIENT);
5053
+ /// with a nil destructor it keeps a pointer to a Swift string that is
5054
+ /// freed as soon as the bind call returns, and stores whatever is there
5055
+ /// later.
5056
+ private let sqliteTransient = unsafeBitCast(-1, to: sqlite3_destructor_type.self)
5057
+
5058
+ /// Binds JSON-bridged values: strings, integers as 64-bit (JS numbers
5059
+ /// that are whole, such as millisecond timestamps), other numbers as
5060
+ /// doubles, booleans as 0/1, and null.
5061
+ private func bindParameters(_ statement: OpaquePointer?, _ params: [Any]) {
5062
+ for (index, param) in params.enumerated() {
5063
+ let idx = Int32(index + 1)
5064
+ if let str = param as? String {
5065
+ sqlite3_bind_text(statement, idx, str, -1, sqliteTransient)
5066
+ } else if let number = param as? NSNumber {
5067
+ if CFGetTypeID(number) == CFBooleanGetTypeID() {
5068
+ sqlite3_bind_int64(statement, idx, number.boolValue ? 1 : 0)
5069
+ } else if CFNumberIsFloatType(number), number.doubleValue.rounded() != number.doubleValue {
5070
+ sqlite3_bind_double(statement, idx, number.doubleValue)
5071
+ } else {
5072
+ sqlite3_bind_int64(statement, idx, number.int64Value)
5073
+ }
5074
+ } else {
5075
+ sqlite3_bind_null(statement, idx)
5076
+ }
5077
+ }
5078
+ }
5079
+
5047
5080
  private func dbExecute(sql: String, params: [Any]?, callbackId: String?) {
5048
5081
  guard let db = db else {
5049
5082
  rejectCallback(callbackId, error: "Database not initialized")
@@ -5054,18 +5087,7 @@ struct CraftWebView: UIViewRepresentable {
5054
5087
  if sqlite3_prepare_v2(db, sql, -1, &statement, nil) == SQLITE_OK {
5055
5088
  // Bind parameters
5056
5089
  if let params = params {
5057
- for (index, param) in params.enumerated() {
5058
- let idx = Int32(index + 1)
5059
- if let str = param as? String {
5060
- sqlite3_bind_text(statement, idx, str, -1, nil)
5061
- } else if let int = param as? Int {
5062
- sqlite3_bind_int(statement, idx, Int32(int))
5063
- } else if let double = param as? Double {
5064
- sqlite3_bind_double(statement, idx, double)
5065
- } else {
5066
- sqlite3_bind_null(statement, idx)
5067
- }
5068
- }
5090
+ bindParameters(statement, params)
5069
5091
  }
5070
5092
 
5071
5093
  if sqlite3_step(statement) == SQLITE_DONE {
@@ -5095,18 +5117,7 @@ struct CraftWebView: UIViewRepresentable {
5095
5117
  if sqlite3_prepare_v2(db, sql, -1, &statement, nil) == SQLITE_OK {
5096
5118
  // Bind parameters
5097
5119
  if let params = params {
5098
- for (index, param) in params.enumerated() {
5099
- let idx = Int32(index + 1)
5100
- if let str = param as? String {
5101
- sqlite3_bind_text(statement, idx, str, -1, nil)
5102
- } else if let int = param as? Int {
5103
- sqlite3_bind_int(statement, idx, Int32(int))
5104
- } else if let double = param as? Double {
5105
- sqlite3_bind_double(statement, idx, double)
5106
- } else {
5107
- sqlite3_bind_null(statement, idx)
5108
- }
5109
- }
5120
+ bindParameters(statement, params)
5110
5121
  }
5111
5122
 
5112
5123
  let columnCount = sqlite3_column_count(statement)
@@ -5119,7 +5130,8 @@ struct CraftWebView: UIViewRepresentable {
5119
5130
 
5120
5131
  switch type {
5121
5132
  case SQLITE_INTEGER:
5122
- row[columnName] = sqlite3_column_int(statement, i)
5133
+ // 64-bit: millisecond timestamps overflow Int32.
5134
+ row[columnName] = sqlite3_column_int64(statement, i)
5123
5135
  case SQLITE_FLOAT:
5124
5136
  row[columnName] = sqlite3_column_double(statement, i)
5125
5137
  case SQLITE_TEXT:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "craft-native",
3
- "version": "0.0.109",
3
+ "version": "0.0.111",
4
4
  "type": "module",
5
5
  "description": "Build desktop apps with web languages - TypeScript SDK for Craft",
6
6
  "author": "Chris Breuer <chris@stacksjs.org>",