craft-native 0.0.236 → 0.0.238

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/README.md +71 -0
  2. package/dist/android/src/emulator.d.ts +23 -0
  3. package/dist/android/src/index.d.ts +7 -1
  4. package/dist/android/src/index.js +280 -98
  5. package/dist/android/templates/CraftBridge.kt.template +65 -54
  6. package/dist/android/templates/CraftNative.kt.template +27 -0
  7. package/dist/android/templates/CraftNativeCapabilities.kt.template +55 -6
  8. package/dist/android/templates/CraftNativeServices.kt.template +45 -35
  9. package/dist/android/templates/CraftNativeShell.kt.template +103 -20
  10. package/dist/android/templates/LocationRecordingService.kt.template +252 -46
  11. package/dist/android/templates/MainActivityNative.kt.template +163 -5
  12. package/dist/android-adb.d.ts +2 -0
  13. package/dist/api/index.d.ts +1 -1
  14. package/dist/api/mobile.d.ts +49 -14
  15. package/dist/browser/storage.d.ts +3 -0
  16. package/dist/browser.js +163 -51
  17. package/dist/cli.js +1 -1
  18. package/dist/components/index.d.ts +33 -0
  19. package/dist/index.cjs +126 -12
  20. package/dist/index.js +126 -12
  21. package/dist/ios/src/app-shortcuts.d.ts +11 -0
  22. package/dist/ios/src/index.d.ts +7 -0
  23. package/dist/ios/src/index.js +65 -1
  24. package/dist/ios/src/testing.d.ts +2 -0
  25. package/dist/ios/templates/CraftApp.swift +392 -88
  26. package/dist/ios/templates/CraftHeartRate.swift +141 -44
  27. package/dist/ios/templates/CraftNativeActions.swift +46 -48
  28. package/dist/ios/templates/CraftNativeScreen.swift +177 -10
  29. package/dist/ios/templates/CraftPersistentStorage.swift +99 -0
  30. package/dist/ios/templates/CraftRecordingArchive.swift +85 -0
  31. package/dist/ios/templates/testing/DriverTests.swift.template +8 -1
  32. package/dist/mobile-testing.js +30 -4
  33. package/dist/mobile.js +169 -17
  34. package/package.json +1 -1
package/dist/mobile.js CHANGED
@@ -355,6 +355,107 @@ var secureStorage = {
355
355
  Object.keys(localStorage).filter((k) => k.startsWith("secure_")).forEach((k) => localStorage.removeItem(k));
356
356
  }
357
357
  };
358
+ function storageHost(method) {
359
+ const host = getCraftRoot()?.storage;
360
+ if (typeof host?.[method] !== "function")
361
+ throw Object.assign(new Error("This host does not provide native storage"), { code: "STORAGE_UNSUPPORTED" });
362
+ return host;
363
+ }
364
+ var crypto = {
365
+ async randomUUID() {
366
+ const root = getCraftRoot();
367
+ if (!root)
368
+ return secureUUID();
369
+ const caps = root.capabilities;
370
+ const advertised = Array.isArray(caps) ? caps.includes("crypto") : caps?.crypto === true;
371
+ if (!advertised || typeof root.crypto?.randomUUID !== "function")
372
+ throw Object.assign(new Error("This host does not provide secure UUIDs"), { code: "CRYPTO_UNSUPPORTED" });
373
+ const id = await root.crypto.randomUUID();
374
+ if (typeof id !== "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(id))
375
+ throw Object.assign(new Error("The host returned an invalid secure UUID"), { code: "CRYPTO_INVALID_RESPONSE" });
376
+ return id;
377
+ }
378
+ };
379
+ var storage = {
380
+ supportsSetIfAbsent() {
381
+ const root = getCraftRoot();
382
+ const caps = root?.capabilities;
383
+ const supported = Array.isArray(caps) ? caps.includes("storageSetIfAbsent") : caps?.storageSetIfAbsent === true;
384
+ return supported && typeof root?.storage?.setIfAbsent === "function";
385
+ },
386
+ supportsCompareAndSet() {
387
+ const root = getCraftRoot();
388
+ const caps = root?.capabilities;
389
+ const supported = Array.isArray(caps) ? caps.includes("storageCompareAndSet") : caps?.storageCompareAndSet === true;
390
+ return supported && typeof root?.storage?.compareAndSet === "function";
391
+ },
392
+ async get(key) {
393
+ return storageHost("get").get(key);
394
+ },
395
+ async set(key, value) {
396
+ return storageHost("set").set(key, value);
397
+ },
398
+ async remove(key) {
399
+ return storageHost("remove").remove(key);
400
+ },
401
+ async clear() {
402
+ return storageHost("clear").clear();
403
+ },
404
+ async keys() {
405
+ return storageHost("keys").keys();
406
+ },
407
+ async setIfAbsent(key, value) {
408
+ if (!storage.supportsSetIfAbsent())
409
+ throw Object.assign(new Error("This app build cannot atomically register an intent"), { code: "STORAGE_SET_IF_ABSENT_UNSUPPORTED" });
410
+ if (typeof key !== "string" || !key.trim() || JSON.stringify(value) === undefined)
411
+ throw Object.assign(new Error("setIfAbsent needs a key and JSON value"), { code: "INVALID_ARGUMENT" });
412
+ return storageHost("setIfAbsent").setIfAbsent(key, value);
413
+ },
414
+ async compareAndSet(key, expectedValue, nextValue) {
415
+ if (!storage.supportsCompareAndSet())
416
+ throw Object.assign(new Error("This app build cannot atomically checkpoint existing storage"), { code: "STORAGE_COMPARE_AND_SET_UNSUPPORTED" });
417
+ if (typeof key !== "string" || !key.trim() || JSON.stringify(expectedValue) === undefined || JSON.stringify(nextValue) === undefined)
418
+ throw Object.assign(new Error("compareAndSet needs a key and JSON values"), { code: "INVALID_ARGUMENT" });
419
+ return storageHost("compareAndSet").compareAndSet(key, expectedValue, nextValue);
420
+ }
421
+ };
422
+ function recordingOwnershipSupported(namespace) {
423
+ const root = getCraftRoot();
424
+ const flag = namespace === "location" ? "locationRecordingOwnership" : "heartRateRecordingOwnership";
425
+ const caps = root?.capabilities;
426
+ const advertised = Array.isArray(caps) ? caps.includes(flag) : caps?.[flag] === true;
427
+ return advertised && ["pauseRecording", "resumeRecording", "stopRecording", "readRecording", "getRecordingState", "acknowledgeRecording"].every((method) => typeof root?.[namespace]?.[method] === "function");
428
+ }
429
+ function recordingIntentSupported(namespace) {
430
+ const root = getCraftRoot();
431
+ const flag = namespace === "location" ? "locationRecordingIntentIdentity" : "heartRateRecordingIntentIdentity";
432
+ const caps = root?.capabilities;
433
+ const advertised = Array.isArray(caps) ? caps.includes(flag) : caps?.[flag] === true;
434
+ return advertised && recordingOwnershipSupported(namespace) && typeof root?.[namespace]?.startRecording === "function";
435
+ }
436
+ function validateRecordingStart(namespace, options) {
437
+ if (!Object.prototype.hasOwnProperty.call(options, "recordingId"))
438
+ return;
439
+ if (typeof options.recordingId !== "string" || !options.recordingId.trim())
440
+ throw Object.assign(new Error("recordingId must be a nonempty recording ID"), { code: "INVALID_ARGUMENT" });
441
+ if (!recordingIntentSupported(namespace))
442
+ throw Object.assign(new Error("This app build cannot start the persisted recording intent"), { code: "RECORDING_INTENT_IDENTITY_UNSUPPORTED" });
443
+ }
444
+ function verifyRecordingStart(options, result) {
445
+ if (options.recordingId !== undefined && result.id !== options.recordingId)
446
+ throw Object.assign(new Error("The native recording does not match the persisted intent"), { code: "RECORDING_CONFLICT" });
447
+ return result;
448
+ }
449
+ function validateRecordingOwnership(namespace, options) {
450
+ if (!options || !Object.prototype.hasOwnProperty.call(options, "expectedId"))
451
+ return;
452
+ if (typeof options.expectedId !== "string" || !options.expectedId.trim())
453
+ throw Object.assign(new Error("expectedId must be a nonempty recording ID"), { code: "INVALID_ARGUMENT" });
454
+ if ((getCraftRoot() || namespace === "heartRate") && !recordingOwnershipSupported(namespace))
455
+ throw Object.assign(new Error("This app build cannot enforce recording ownership"), { code: "RECORDING_OWNERSHIP_UNSUPPORTED" });
456
+ if (namespace === "location" && !getCraftRoot() && options.expectedId !== webLocationRecording.id)
457
+ throw Object.assign(new Error("The active location recording has changed"), { code: "RECORDING_CONFLICT" });
458
+ }
358
459
  var webLocationRecording = {
359
460
  id: null,
360
461
  active: false,
@@ -364,6 +465,8 @@ var webLocationRecording = {
364
465
  };
365
466
  var webLocationRecordingWatchId = null;
366
467
  var location = {
468
+ supportsRecordingOwnership: () => recordingOwnershipSupported("location"),
469
+ supportsRecordingIntentIdentity: () => recordingIntentSupported("location"),
367
470
  async getCurrentPosition(options = {}) {
368
471
  const craft = getCraftMobile();
369
472
  if (typeof window !== "undefined" && craft?.location) {
@@ -412,9 +515,12 @@ var location = {
412
515
  navigator.geolocation.clearWatch(watchId);
413
516
  },
414
517
  async startRecording(options = {}) {
518
+ validateRecordingStart("location", options);
415
519
  const craft = getCraftMobile();
416
520
  if (typeof window !== "undefined" && craft?.location?.startRecording)
417
- return craft.location.startRecording(options);
521
+ return verifyRecordingStart(options, await craft.location.startRecording(options));
522
+ if (webLocationRecording.active)
523
+ throw Object.assign(new Error("A location recording is already active"), { code: "RECORDING_ALREADY_ACTIVE" });
418
524
  if (webLocationRecordingWatchId !== null)
419
525
  navigator.geolocation.clearWatch(webLocationRecordingWatchId);
420
526
  webLocationRecording = {
@@ -430,24 +536,27 @@ var location = {
430
536
  }, options);
431
537
  return { ...webLocationRecording, sampleCount: 0 };
432
538
  },
433
- async pauseRecording() {
539
+ async pauseRecording(options) {
540
+ validateRecordingOwnership("location", options);
434
541
  const craft = getCraftMobile();
435
542
  if (typeof window !== "undefined" && craft?.location?.pauseRecording)
436
- return craft.location.pauseRecording();
543
+ return craft.location.pauseRecording(options);
437
544
  webLocationRecording.paused = true;
438
545
  return { ...webLocationRecording, sampleCount: webLocationRecording.locations.length };
439
546
  },
440
- async resumeRecording() {
547
+ async resumeRecording(options) {
548
+ validateRecordingOwnership("location", options);
441
549
  const craft = getCraftMobile();
442
550
  if (typeof window !== "undefined" && craft?.location?.resumeRecording)
443
- return craft.location.resumeRecording();
551
+ return craft.location.resumeRecording(options);
444
552
  webLocationRecording.paused = false;
445
553
  return { ...webLocationRecording, sampleCount: webLocationRecording.locations.length };
446
554
  },
447
- async stopRecording() {
555
+ async stopRecording(options) {
556
+ validateRecordingOwnership("location", options);
448
557
  const craft = getCraftMobile();
449
558
  if (typeof window !== "undefined" && craft?.location?.stopRecording)
450
- return craft.location.stopRecording();
559
+ return craft.location.stopRecording(options);
451
560
  if (webLocationRecordingWatchId !== null) {
452
561
  location.clearWatch(webLocationRecordingWatchId);
453
562
  webLocationRecordingWatchId = null;
@@ -456,16 +565,30 @@ var location = {
456
565
  webLocationRecording.paused = false;
457
566
  return { ...webLocationRecording, locations: [...webLocationRecording.locations] };
458
567
  },
459
- async getRecordingState() {
568
+ async getRecordingState(options) {
569
+ validateRecordingOwnership("location", options);
460
570
  const craft = getCraftMobile();
461
571
  if (typeof window !== "undefined" && craft?.location?.getRecordingState)
462
- return craft.location.getRecordingState();
572
+ return craft.location.getRecordingState(options);
463
573
  return { ...webLocationRecording, sampleCount: webLocationRecording.locations.length };
464
574
  },
465
- async readRecording() {
575
+ async acknowledgeRecording(options) {
576
+ if (!options || !Object.prototype.hasOwnProperty.call(options, "expectedId"))
577
+ throw Object.assign(new Error("Acknowledgement needs expectedId"), { code: "INVALID_ARGUMENT" });
578
+ validateRecordingOwnership("location", options);
579
+ const craft = getCraftMobile();
580
+ if (craft?.location?.acknowledgeRecording)
581
+ return craft.location.acknowledgeRecording(options);
582
+ if (webLocationRecording.active)
583
+ throw Object.assign(new Error("The recording is still active"), { code: "RECORDING_ALREADY_ACTIVE" });
584
+ webLocationRecording = { id: null, active: false, paused: false, startedAt: null, locations: [] };
585
+ return true;
586
+ },
587
+ async readRecording(options) {
588
+ validateRecordingOwnership("location", options);
466
589
  const craft = getCraftMobile();
467
590
  if (typeof window !== "undefined" && craft?.location?.readRecording)
468
- return craft.location.readRecording();
591
+ return craft.location.readRecording(options);
469
592
  return [...webLocationRecording.locations];
470
593
  }
471
594
  };
@@ -1023,6 +1146,8 @@ async function heartRateCall(method, fallback, normalize, ...args) {
1023
1146
  }
1024
1147
  var noRecording = () => normalizeHeartRateRecording({});
1025
1148
  var heartRate = {
1149
+ supportsRecordingOwnership: () => recordingOwnershipSupported("heartRate"),
1150
+ supportsRecordingIntentIdentity: () => recordingIntentSupported("heartRate"),
1026
1151
  isAvailable() {
1027
1152
  return typeof nativeHeartRate()?.getState === "function";
1028
1153
  },
@@ -1037,12 +1162,37 @@ var heartRate = {
1037
1162
  connect: (id) => heartRateCall("connect", noHeartRate, normalizeHeartRateState, id),
1038
1163
  reconnect: () => heartRateCall("reconnect", noHeartRate, normalizeHeartRateState),
1039
1164
  disconnect: (options = {}) => heartRateCall("disconnect", noHeartRate, normalizeHeartRateState, options),
1040
- startRecording: () => heartRateCall("startRecording", noRecording, normalizeHeartRateRecording),
1041
- pauseRecording: () => heartRateCall("pauseRecording", noRecording, normalizeHeartRateRecording),
1042
- resumeRecording: () => heartRateCall("resumeRecording", noRecording, normalizeHeartRateRecording),
1043
- stopRecording: () => heartRateCall("stopRecording", () => ({ ...noRecording(), samples: [] }), (value) => ({ ...normalizeHeartRateRecording(value), samples: normalizeHeartRateSamples(hrRecord(value).samples) })),
1044
- getRecordingState: () => heartRateCall("getRecordingState", noRecording, normalizeHeartRateRecording),
1045
- readRecording: (options = {}) => heartRateCall("readRecording", () => [], normalizeHeartRateSamples, options),
1165
+ async startRecording(options) {
1166
+ const supplied = options ?? {};
1167
+ validateRecordingStart("heartRate", supplied);
1168
+ return verifyRecordingStart(supplied, await heartRateCall("startRecording", noRecording, normalizeHeartRateRecording, ...options === undefined ? [] : [options]));
1169
+ },
1170
+ async pauseRecording(options) {
1171
+ validateRecordingOwnership("heartRate", options);
1172
+ return heartRateCall("pauseRecording", noRecording, normalizeHeartRateRecording, ...options === undefined ? [] : [options]);
1173
+ },
1174
+ async resumeRecording(options) {
1175
+ validateRecordingOwnership("heartRate", options);
1176
+ return heartRateCall("resumeRecording", noRecording, normalizeHeartRateRecording, ...options === undefined ? [] : [options]);
1177
+ },
1178
+ async stopRecording(options) {
1179
+ validateRecordingOwnership("heartRate", options);
1180
+ return heartRateCall("stopRecording", () => ({ ...noRecording(), samples: [] }), (value) => ({ ...normalizeHeartRateRecording(value), samples: normalizeHeartRateSamples(hrRecord(value).samples) }), ...options === undefined ? [] : [options]);
1181
+ },
1182
+ async getRecordingState(options) {
1183
+ validateRecordingOwnership("heartRate", options);
1184
+ return heartRateCall("getRecordingState", noRecording, normalizeHeartRateRecording, ...options === undefined ? [] : [options]);
1185
+ },
1186
+ async acknowledgeRecording(options) {
1187
+ if (!options || !Object.prototype.hasOwnProperty.call(options, "expectedId"))
1188
+ throw Object.assign(new Error("Acknowledgement needs expectedId"), { code: "INVALID_ARGUMENT" });
1189
+ validateRecordingOwnership("heartRate", options);
1190
+ return heartRateCall("acknowledgeRecording", () => false, (value) => value === true, options);
1191
+ },
1192
+ async readRecording(options = {}) {
1193
+ validateRecordingOwnership("heartRate", options);
1194
+ return heartRateCall("readRecording", () => [], normalizeHeartRateSamples, options);
1195
+ },
1046
1196
  onReading: (callback) => hrSubscribe(heartRateWatch.readings, callback),
1047
1197
  onDevice: (callback) => hrSubscribe(heartRateWatch.devices, callback),
1048
1198
  onState: (callback) => hrSubscribe(heartRateWatch.states, callback)
@@ -1646,6 +1796,7 @@ export {
1646
1796
  watchConnectivity,
1647
1797
  tabBar,
1648
1798
  symbols,
1799
+ storage,
1649
1800
  statusBar,
1650
1801
  splash,
1651
1802
  speech,
@@ -1679,6 +1830,7 @@ export {
1679
1830
  dialog,
1680
1831
  device,
1681
1832
  deepLinks,
1833
+ crypto,
1682
1834
  crashReporter,
1683
1835
  contextMenu,
1684
1836
  chrome,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "craft-native",
3
- "version": "0.0.236",
3
+ "version": "0.0.238",
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>",