craft-native 0.0.237 → 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.
- package/README.md +71 -0
- package/dist/android/src/emulator.d.ts +23 -0
- package/dist/android/src/index.d.ts +7 -1
- package/dist/android/src/index.js +280 -98
- package/dist/android/templates/CraftBridge.kt.template +65 -54
- package/dist/android/templates/CraftNative.kt.template +27 -0
- package/dist/android/templates/CraftNativeCapabilities.kt.template +36 -5
- package/dist/android/templates/CraftNativeServices.kt.template +45 -35
- package/dist/android/templates/CraftNativeShell.kt.template +103 -20
- package/dist/android/templates/LocationRecordingService.kt.template +252 -46
- package/dist/android-adb.d.ts +2 -0
- package/dist/api/index.d.ts +1 -1
- package/dist/api/mobile.d.ts +49 -14
- package/dist/browser/storage.d.ts +3 -0
- package/dist/browser.js +163 -51
- package/dist/cli.js +1 -1
- package/dist/index.cjs +126 -12
- package/dist/index.js +126 -12
- package/dist/ios/src/index.d.ts +5 -0
- package/dist/ios/src/index.js +9 -1
- package/dist/ios/src/testing.d.ts +2 -0
- package/dist/ios/templates/CraftApp.swift +376 -88
- package/dist/ios/templates/CraftHeartRate.swift +141 -44
- package/dist/ios/templates/CraftNativeActions.swift +46 -48
- package/dist/ios/templates/CraftNativeScreen.swift +11 -4
- package/dist/ios/templates/CraftPersistentStorage.swift +99 -0
- package/dist/ios/templates/CraftRecordingArchive.swift +85 -0
- package/dist/ios/templates/testing/DriverTests.swift.template +8 -1
- package/dist/mobile-testing.js +30 -4
- package/dist/mobile.js +169 -17
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -57,7 +57,7 @@ function craftBinaryNotFoundMessage(triedPath) {
|
|
|
57
57
|
`);
|
|
58
58
|
}
|
|
59
59
|
// package.json
|
|
60
|
-
var version = "0.0.
|
|
60
|
+
var version = "0.0.238";
|
|
61
61
|
|
|
62
62
|
// src/sdk-version.ts
|
|
63
63
|
var SDK_VERSION = version;
|
|
@@ -4981,6 +4981,92 @@ var secureStorage = {
|
|
|
4981
4981
|
Object.keys(localStorage).filter((k) => k.startsWith("secure_")).forEach((k) => localStorage.removeItem(k));
|
|
4982
4982
|
}
|
|
4983
4983
|
};
|
|
4984
|
+
function storageHost(method) {
|
|
4985
|
+
const host2 = getCraftRoot()?.storage;
|
|
4986
|
+
if (typeof host2?.[method] !== "function")
|
|
4987
|
+
throw Object.assign(new Error("This host does not provide native storage"), { code: "STORAGE_UNSUPPORTED" });
|
|
4988
|
+
return host2;
|
|
4989
|
+
}
|
|
4990
|
+
var storage2 = {
|
|
4991
|
+
supportsSetIfAbsent() {
|
|
4992
|
+
const root = getCraftRoot();
|
|
4993
|
+
const caps = root?.capabilities;
|
|
4994
|
+
const supported = Array.isArray(caps) ? caps.includes("storageSetIfAbsent") : caps?.storageSetIfAbsent === true;
|
|
4995
|
+
return supported && typeof root?.storage?.setIfAbsent === "function";
|
|
4996
|
+
},
|
|
4997
|
+
supportsCompareAndSet() {
|
|
4998
|
+
const root = getCraftRoot();
|
|
4999
|
+
const caps = root?.capabilities;
|
|
5000
|
+
const supported = Array.isArray(caps) ? caps.includes("storageCompareAndSet") : caps?.storageCompareAndSet === true;
|
|
5001
|
+
return supported && typeof root?.storage?.compareAndSet === "function";
|
|
5002
|
+
},
|
|
5003
|
+
async get(key) {
|
|
5004
|
+
return storageHost("get").get(key);
|
|
5005
|
+
},
|
|
5006
|
+
async set(key, value) {
|
|
5007
|
+
return storageHost("set").set(key, value);
|
|
5008
|
+
},
|
|
5009
|
+
async remove(key) {
|
|
5010
|
+
return storageHost("remove").remove(key);
|
|
5011
|
+
},
|
|
5012
|
+
async clear() {
|
|
5013
|
+
return storageHost("clear").clear();
|
|
5014
|
+
},
|
|
5015
|
+
async keys() {
|
|
5016
|
+
return storageHost("keys").keys();
|
|
5017
|
+
},
|
|
5018
|
+
async setIfAbsent(key, value) {
|
|
5019
|
+
if (!storage2.supportsSetIfAbsent())
|
|
5020
|
+
throw Object.assign(new Error("This app build cannot atomically register an intent"), { code: "STORAGE_SET_IF_ABSENT_UNSUPPORTED" });
|
|
5021
|
+
if (typeof key !== "string" || !key.trim() || JSON.stringify(value) === undefined)
|
|
5022
|
+
throw Object.assign(new Error("setIfAbsent needs a key and JSON value"), { code: "INVALID_ARGUMENT" });
|
|
5023
|
+
return storageHost("setIfAbsent").setIfAbsent(key, value);
|
|
5024
|
+
},
|
|
5025
|
+
async compareAndSet(key, expectedValue, nextValue) {
|
|
5026
|
+
if (!storage2.supportsCompareAndSet())
|
|
5027
|
+
throw Object.assign(new Error("This app build cannot atomically checkpoint existing storage"), { code: "STORAGE_COMPARE_AND_SET_UNSUPPORTED" });
|
|
5028
|
+
if (typeof key !== "string" || !key.trim() || JSON.stringify(expectedValue) === undefined || JSON.stringify(nextValue) === undefined)
|
|
5029
|
+
throw Object.assign(new Error("compareAndSet needs a key and JSON values"), { code: "INVALID_ARGUMENT" });
|
|
5030
|
+
return storageHost("compareAndSet").compareAndSet(key, expectedValue, nextValue);
|
|
5031
|
+
}
|
|
5032
|
+
};
|
|
5033
|
+
function recordingOwnershipSupported(namespace) {
|
|
5034
|
+
const root = getCraftRoot();
|
|
5035
|
+
const flag = namespace === "location" ? "locationRecordingOwnership" : "heartRateRecordingOwnership";
|
|
5036
|
+
const caps = root?.capabilities;
|
|
5037
|
+
const advertised = Array.isArray(caps) ? caps.includes(flag) : caps?.[flag] === true;
|
|
5038
|
+
return advertised && ["pauseRecording", "resumeRecording", "stopRecording", "readRecording", "getRecordingState", "acknowledgeRecording"].every((method) => typeof root?.[namespace]?.[method] === "function");
|
|
5039
|
+
}
|
|
5040
|
+
function recordingIntentSupported(namespace) {
|
|
5041
|
+
const root = getCraftRoot();
|
|
5042
|
+
const flag = namespace === "location" ? "locationRecordingIntentIdentity" : "heartRateRecordingIntentIdentity";
|
|
5043
|
+
const caps = root?.capabilities;
|
|
5044
|
+
const advertised = Array.isArray(caps) ? caps.includes(flag) : caps?.[flag] === true;
|
|
5045
|
+
return advertised && recordingOwnershipSupported(namespace) && typeof root?.[namespace]?.startRecording === "function";
|
|
5046
|
+
}
|
|
5047
|
+
function validateRecordingStart(namespace, options) {
|
|
5048
|
+
if (!Object.prototype.hasOwnProperty.call(options, "recordingId"))
|
|
5049
|
+
return;
|
|
5050
|
+
if (typeof options.recordingId !== "string" || !options.recordingId.trim())
|
|
5051
|
+
throw Object.assign(new Error("recordingId must be a nonempty recording ID"), { code: "INVALID_ARGUMENT" });
|
|
5052
|
+
if (!recordingIntentSupported(namespace))
|
|
5053
|
+
throw Object.assign(new Error("This app build cannot start the persisted recording intent"), { code: "RECORDING_INTENT_IDENTITY_UNSUPPORTED" });
|
|
5054
|
+
}
|
|
5055
|
+
function verifyRecordingStart(options, result) {
|
|
5056
|
+
if (options.recordingId !== undefined && result.id !== options.recordingId)
|
|
5057
|
+
throw Object.assign(new Error("The native recording does not match the persisted intent"), { code: "RECORDING_CONFLICT" });
|
|
5058
|
+
return result;
|
|
5059
|
+
}
|
|
5060
|
+
function validateRecordingOwnership(namespace, options) {
|
|
5061
|
+
if (!options || !Object.prototype.hasOwnProperty.call(options, "expectedId"))
|
|
5062
|
+
return;
|
|
5063
|
+
if (typeof options.expectedId !== "string" || !options.expectedId.trim())
|
|
5064
|
+
throw Object.assign(new Error("expectedId must be a nonempty recording ID"), { code: "INVALID_ARGUMENT" });
|
|
5065
|
+
if ((getCraftRoot() || namespace === "heartRate") && !recordingOwnershipSupported(namespace))
|
|
5066
|
+
throw Object.assign(new Error("This app build cannot enforce recording ownership"), { code: "RECORDING_OWNERSHIP_UNSUPPORTED" });
|
|
5067
|
+
if (namespace === "location" && !getCraftRoot() && options.expectedId !== webLocationRecording.id)
|
|
5068
|
+
throw Object.assign(new Error("The active location recording has changed"), { code: "RECORDING_CONFLICT" });
|
|
5069
|
+
}
|
|
4984
5070
|
var webLocationRecording = {
|
|
4985
5071
|
id: null,
|
|
4986
5072
|
active: false,
|
|
@@ -4990,6 +5076,8 @@ var webLocationRecording = {
|
|
|
4990
5076
|
};
|
|
4991
5077
|
var webLocationRecordingWatchId = null;
|
|
4992
5078
|
var location = {
|
|
5079
|
+
supportsRecordingOwnership: () => recordingOwnershipSupported("location"),
|
|
5080
|
+
supportsRecordingIntentIdentity: () => recordingIntentSupported("location"),
|
|
4993
5081
|
async getCurrentPosition(options = {}) {
|
|
4994
5082
|
const craft = getCraftMobile();
|
|
4995
5083
|
if (typeof window !== "undefined" && craft?.location) {
|
|
@@ -5038,9 +5126,12 @@ var location = {
|
|
|
5038
5126
|
navigator.geolocation.clearWatch(watchId);
|
|
5039
5127
|
},
|
|
5040
5128
|
async startRecording(options = {}) {
|
|
5129
|
+
validateRecordingStart("location", options);
|
|
5041
5130
|
const craft = getCraftMobile();
|
|
5042
5131
|
if (typeof window !== "undefined" && craft?.location?.startRecording)
|
|
5043
|
-
return craft.location.startRecording(options);
|
|
5132
|
+
return verifyRecordingStart(options, await craft.location.startRecording(options));
|
|
5133
|
+
if (webLocationRecording.active)
|
|
5134
|
+
throw Object.assign(new Error("A location recording is already active"), { code: "RECORDING_ALREADY_ACTIVE" });
|
|
5044
5135
|
if (webLocationRecordingWatchId !== null)
|
|
5045
5136
|
navigator.geolocation.clearWatch(webLocationRecordingWatchId);
|
|
5046
5137
|
webLocationRecording = {
|
|
@@ -5056,24 +5147,27 @@ var location = {
|
|
|
5056
5147
|
}, options);
|
|
5057
5148
|
return { ...webLocationRecording, sampleCount: 0 };
|
|
5058
5149
|
},
|
|
5059
|
-
async pauseRecording() {
|
|
5150
|
+
async pauseRecording(options) {
|
|
5151
|
+
validateRecordingOwnership("location", options);
|
|
5060
5152
|
const craft = getCraftMobile();
|
|
5061
5153
|
if (typeof window !== "undefined" && craft?.location?.pauseRecording)
|
|
5062
|
-
return craft.location.pauseRecording();
|
|
5154
|
+
return craft.location.pauseRecording(options);
|
|
5063
5155
|
webLocationRecording.paused = true;
|
|
5064
5156
|
return { ...webLocationRecording, sampleCount: webLocationRecording.locations.length };
|
|
5065
5157
|
},
|
|
5066
|
-
async resumeRecording() {
|
|
5158
|
+
async resumeRecording(options) {
|
|
5159
|
+
validateRecordingOwnership("location", options);
|
|
5067
5160
|
const craft = getCraftMobile();
|
|
5068
5161
|
if (typeof window !== "undefined" && craft?.location?.resumeRecording)
|
|
5069
|
-
return craft.location.resumeRecording();
|
|
5162
|
+
return craft.location.resumeRecording(options);
|
|
5070
5163
|
webLocationRecording.paused = false;
|
|
5071
5164
|
return { ...webLocationRecording, sampleCount: webLocationRecording.locations.length };
|
|
5072
5165
|
},
|
|
5073
|
-
async stopRecording() {
|
|
5166
|
+
async stopRecording(options) {
|
|
5167
|
+
validateRecordingOwnership("location", options);
|
|
5074
5168
|
const craft = getCraftMobile();
|
|
5075
5169
|
if (typeof window !== "undefined" && craft?.location?.stopRecording)
|
|
5076
|
-
return craft.location.stopRecording();
|
|
5170
|
+
return craft.location.stopRecording(options);
|
|
5077
5171
|
if (webLocationRecordingWatchId !== null) {
|
|
5078
5172
|
location.clearWatch(webLocationRecordingWatchId);
|
|
5079
5173
|
webLocationRecordingWatchId = null;
|
|
@@ -5082,16 +5176,30 @@ var location = {
|
|
|
5082
5176
|
webLocationRecording.paused = false;
|
|
5083
5177
|
return { ...webLocationRecording, locations: [...webLocationRecording.locations] };
|
|
5084
5178
|
},
|
|
5085
|
-
async getRecordingState() {
|
|
5179
|
+
async getRecordingState(options) {
|
|
5180
|
+
validateRecordingOwnership("location", options);
|
|
5086
5181
|
const craft = getCraftMobile();
|
|
5087
5182
|
if (typeof window !== "undefined" && craft?.location?.getRecordingState)
|
|
5088
|
-
return craft.location.getRecordingState();
|
|
5183
|
+
return craft.location.getRecordingState(options);
|
|
5089
5184
|
return { ...webLocationRecording, sampleCount: webLocationRecording.locations.length };
|
|
5090
5185
|
},
|
|
5091
|
-
async
|
|
5186
|
+
async acknowledgeRecording(options) {
|
|
5187
|
+
if (!options || !Object.prototype.hasOwnProperty.call(options, "expectedId"))
|
|
5188
|
+
throw Object.assign(new Error("Acknowledgement needs expectedId"), { code: "INVALID_ARGUMENT" });
|
|
5189
|
+
validateRecordingOwnership("location", options);
|
|
5190
|
+
const craft = getCraftMobile();
|
|
5191
|
+
if (craft?.location?.acknowledgeRecording)
|
|
5192
|
+
return craft.location.acknowledgeRecording(options);
|
|
5193
|
+
if (webLocationRecording.active)
|
|
5194
|
+
throw Object.assign(new Error("The recording is still active"), { code: "RECORDING_ALREADY_ACTIVE" });
|
|
5195
|
+
webLocationRecording = { id: null, active: false, paused: false, startedAt: null, locations: [] };
|
|
5196
|
+
return true;
|
|
5197
|
+
},
|
|
5198
|
+
async readRecording(options) {
|
|
5199
|
+
validateRecordingOwnership("location", options);
|
|
5092
5200
|
const craft = getCraftMobile();
|
|
5093
5201
|
if (typeof window !== "undefined" && craft?.location?.readRecording)
|
|
5094
|
-
return craft.location.readRecording();
|
|
5202
|
+
return craft.location.readRecording(options);
|
|
5095
5203
|
return [...webLocationRecording.locations];
|
|
5096
5204
|
}
|
|
5097
5205
|
};
|
|
@@ -5213,6 +5321,11 @@ function onCraftEvent(eventName, callback) {
|
|
|
5213
5321
|
globalThis.addEventListener(eventName, listener);
|
|
5214
5322
|
return () => globalThis.removeEventListener(eventName, listener);
|
|
5215
5323
|
}
|
|
5324
|
+
function getCraftRoot() {
|
|
5325
|
+
if (typeof window === "undefined")
|
|
5326
|
+
return;
|
|
5327
|
+
return window.craft;
|
|
5328
|
+
}
|
|
5216
5329
|
function detectPlatform2() {
|
|
5217
5330
|
if (typeof window === "undefined")
|
|
5218
5331
|
return "linux";
|
|
@@ -11296,6 +11409,7 @@ export {
|
|
|
11296
11409
|
styles,
|
|
11297
11410
|
style,
|
|
11298
11411
|
storeKit,
|
|
11412
|
+
storage2 as storage,
|
|
11299
11413
|
stat,
|
|
11300
11414
|
startupTask,
|
|
11301
11415
|
stageManager,
|
package/dist/ios/src/index.d.ts
CHANGED
|
@@ -274,6 +274,11 @@ export interface CraftTab {
|
|
|
274
274
|
title: string;
|
|
275
275
|
/** An SF Symbol name. */
|
|
276
276
|
symbol?: string;
|
|
277
|
+
/** Native selected-tab icon overrides; retained pushed screens keep their owning tab's policy. */
|
|
278
|
+
selectedSymbol?: string;
|
|
279
|
+
selectedIconTint?: string;
|
|
280
|
+
/** False suppresses the legacy pulse while selected; omission uses the legacy policy. */
|
|
281
|
+
selectedIconPulse?: boolean;
|
|
277
282
|
}
|
|
278
283
|
/** `/a//b/?q#h` → `/a/b`, the way the shell and the page compare paths. Null for a relative path. */
|
|
279
284
|
export declare function normalizeNativePath(raw: string): string | null;
|
package/dist/ios/src/index.js
CHANGED
|
@@ -133,13 +133,19 @@ function initSimulatorDriver(options) {
|
|
|
133
133
|
throw new Error("The simulator driver requires iOS 17.0 or newer");
|
|
134
134
|
if (!["http:", "https:"].includes(new URL(control).protocol))
|
|
135
135
|
throw new Error("controlUrl must use HTTP or HTTPS");
|
|
136
|
+
const tabIdentifiers = Object.entries(options.tabIdentifiers ?? {});
|
|
137
|
+
for (const [name2, identifier] of tabIdentifiers) {
|
|
138
|
+
if (!name2.trim() || !identifier.trim())
|
|
139
|
+
throw new Error("Tab names and native identifiers must be nonempty");
|
|
140
|
+
}
|
|
136
141
|
const replacements = {
|
|
137
142
|
PROJECT_NAME: name,
|
|
138
143
|
BUNDLE_PREFIX: prefix,
|
|
139
144
|
IOS_VERSION: version,
|
|
140
145
|
BUNDLE_ID: swiftString(bundleId),
|
|
141
146
|
CONTROL_URL: swiftString(control),
|
|
142
|
-
TABS: `[${(options.tabs ?? []).map(swiftString).join(", ")}]
|
|
147
|
+
TABS: `[${(options.tabs ?? []).map(swiftString).join(", ")}]`,
|
|
148
|
+
TAB_IDENTIFIERS: tabIdentifiers.length ? `[${tabIdentifiers.map(([name2, identifier]) => `${swiftString(name2)}: ${swiftString(identifier)}`).join(", ")}]` : "[:]"
|
|
143
149
|
};
|
|
144
150
|
for (const [source, destination] of [
|
|
145
151
|
["HostApp.swift", "Host/HostApp.swift"],
|
|
@@ -1141,6 +1147,8 @@ function writeShell(output, config) {
|
|
|
1141
1147
|
cpSync(join3(TEMPLATES_DIR, "CraftMusic.swift"), join3(output, "Sources", "CraftMusic.swift"));
|
|
1142
1148
|
cpSync(join3(TEMPLATES_DIR, "CraftBluetooth.swift"), join3(output, "Sources", "CraftBluetooth.swift"));
|
|
1143
1149
|
cpSync(join3(TEMPLATES_DIR, "CraftHeartRate.swift"), join3(output, "Sources", "CraftHeartRate.swift"));
|
|
1150
|
+
cpSync(join3(TEMPLATES_DIR, "CraftRecordingArchive.swift"), join3(output, "Sources", "CraftRecordingArchive.swift"));
|
|
1151
|
+
cpSync(join3(TEMPLATES_DIR, "CraftPersistentStorage.swift"), join3(output, "Sources", "CraftPersistentStorage.swift"));
|
|
1144
1152
|
cpSync(join3(TEMPLATES_DIR, "CraftScheduledWorkouts.swift"), join3(output, "Sources", "CraftScheduledWorkouts.swift"));
|
|
1145
1153
|
cpSync(join3(TEMPLATES_DIR, "CraftNativeMutation.swift"), join3(output, "Sources", "CraftNativeMutation.swift"));
|
|
1146
1154
|
cpSync(join3(TEMPLATES_DIR, "CraftNativeUI.swift"), join3(output, "Sources", "CraftNativeUI.swift"));
|
|
@@ -7,6 +7,8 @@ export interface SimulatorDriverOptions {
|
|
|
7
7
|
deploymentTarget?: string;
|
|
8
8
|
controlUrl?: string;
|
|
9
9
|
tabs?: readonly string[];
|
|
10
|
+
/** Stable native accessibility identifiers for tabs whose labels can change. */
|
|
11
|
+
tabIdentifiers?: Readonly<Record<string, string>>;
|
|
10
12
|
/** Bundle a wire-protocol journey and save native XCTest attachments. No control server is needed on physical devices. */
|
|
11
13
|
script?: string;
|
|
12
14
|
}
|