craft-native 0.0.244 → 0.0.246
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/dist/android/src/index.d.ts +20 -0
- package/dist/android/src/index.js +5 -0
- package/dist/android/templates/CraftNativeBackground.kt.template +36 -2
- package/dist/android/templates/CraftNativeHaptics.kt.template +124 -0
- package/dist/android/templates/CraftNativeShell.kt.template +282 -72
- package/dist/android/templates/MainActivityNative.kt.template +8 -7
- package/dist/api/mobile.d.ts +71 -3
- package/dist/browser/storage.d.ts +11 -0
- package/dist/browser.d.ts +9 -0
- package/dist/browser.js +66 -13
- package/dist/cli.js +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/ios/src/index.d.ts +32 -1
- package/dist/ios/src/index.js +6 -0
- package/dist/ios/templates/CraftApp.swift +8 -0
- package/dist/ios/templates/CraftHybrid.swift +192 -16
- package/dist/ios/templates/CraftNativeActions.swift +63 -5
- package/dist/ios/templates/CraftNativeFlatList.swift +13 -6
- package/dist/ios/templates/CraftNativeScreen.swift +396 -66
- package/dist/ios/templates/CraftWebAudio.swift +21 -13
- package/dist/mobile-testing.js +9 -3
- package/dist/mobile.js +33 -3
- package/package.json +1 -1
|
@@ -1376,23 +1376,32 @@ final class CraftWebAudio {
|
|
|
1376
1376
|
/// JavaScript, before its bundle runs.
|
|
1377
1377
|
func install(in context: JSContext) {
|
|
1378
1378
|
script = context
|
|
1379
|
+
// The graph lives on the main thread. A script on its own thread
|
|
1380
|
+
// (CraftNativeScriptThread) reaches it there, waiting for the answer
|
|
1381
|
+
// its synchronous calls return; arguments are read off the JSValues
|
|
1382
|
+
// where the script runs.
|
|
1379
1383
|
let open: @convention(block) (Int, Double) -> NSDictionary? = { [weak self] id, rate in
|
|
1380
|
-
self?.open(id, requestedRate: rate) as NSDictionary?
|
|
1384
|
+
CraftNativeScriptThread.onMain { self?.open(id, requestedRate: rate) as NSDictionary? }
|
|
1381
1385
|
}
|
|
1382
1386
|
let send: @convention(block) (JSValue) -> Void = { [weak self] batch in
|
|
1383
|
-
|
|
1387
|
+
let commands = batch.toArray() ?? []
|
|
1388
|
+
CraftNativeScriptThread.onMain { self?.receive(commands) }
|
|
1384
1389
|
}
|
|
1385
1390
|
let time: @convention(block) (Int) -> Double = { [weak self] id in
|
|
1386
|
-
self?.contexts[id]?.graph.currentTime ?? 0
|
|
1391
|
+
CraftNativeScriptThread.onMain { self?.contexts[id]?.graph.currentTime ?? 0 }
|
|
1387
1392
|
}
|
|
1388
1393
|
let read: @convention(block) (Int, Int, String) -> Double = { [weak self] contextId, nodeId, key in
|
|
1389
|
-
self?.read(contextId, nodeId, key) ?? .nan
|
|
1394
|
+
CraftNativeScriptThread.onMain { self?.read(contextId, nodeId, key) ?? .nan }
|
|
1390
1395
|
}
|
|
1391
1396
|
let state: @convention(block) (Int, String) -> Bool = { [weak self] id, state in
|
|
1392
|
-
self?.setState(id, state) ?? false
|
|
1397
|
+
CraftNativeScriptThread.onMain { self?.setState(id, state) ?? false }
|
|
1393
1398
|
}
|
|
1394
1399
|
let buffer: @convention(block) (Int, Double, Int, JSValue) -> Bool = { [weak self] id, rate, length, channels in
|
|
1395
|
-
|
|
1400
|
+
// The samples are copied out of the script's typed arrays where
|
|
1401
|
+
// the script runs, then kept beside the graph.
|
|
1402
|
+
guard let data = Self.copyBuffer(rate, length, channels) else { return false }
|
|
1403
|
+
CraftNativeScriptThread.onMain { self?.buffers[id] = data }
|
|
1404
|
+
return true
|
|
1396
1405
|
}
|
|
1397
1406
|
context.setObject(open, forKeyedSubscript: "craftNativeAudioOpen" as NSString)
|
|
1398
1407
|
context.setObject(send, forKeyedSubscript: "craftNativeAudioSend" as NSString)
|
|
@@ -1637,10 +1646,10 @@ final class CraftWebAudio {
|
|
|
1637
1646
|
}
|
|
1638
1647
|
|
|
1639
1648
|
/// An AudioBuffer's channels, copied straight out of its Float32Arrays.
|
|
1640
|
-
private func
|
|
1641
|
-
guard length > 0, rate > 0, let context = JSContext.current() else { return
|
|
1649
|
+
private static func copyBuffer(_ rate: Double, _ length: Int, _ channels: JSValue) -> CraftAudioBufferData? {
|
|
1650
|
+
guard length > 0, rate > 0, let context = JSContext.current() else { return nil }
|
|
1642
1651
|
let count = Int(channels.objectForKeyedSubscript("length")?.toInt32() ?? 0)
|
|
1643
|
-
guard count > 0 else { return
|
|
1652
|
+
guard count > 0 else { return nil }
|
|
1644
1653
|
let data = CraftAudioBufferData(sampleRate: rate, length: length, channelCount: count)
|
|
1645
1654
|
let script = context.jsGlobalContextRef
|
|
1646
1655
|
for index in 0..<count {
|
|
@@ -1651,8 +1660,7 @@ final class CraftWebAudio {
|
|
|
1651
1660
|
let available = JSObjectGetTypedArrayLength(script, object, nil)
|
|
1652
1661
|
data.channels[index].update(from: bytes.assumingMemoryBound(to: Float.self), count: min(length, available))
|
|
1653
1662
|
}
|
|
1654
|
-
|
|
1655
|
-
return true
|
|
1663
|
+
return data
|
|
1656
1664
|
}
|
|
1657
1665
|
|
|
1658
1666
|
// MARK: Notices
|
|
@@ -1675,8 +1683,8 @@ final class CraftWebAudio {
|
|
|
1675
1683
|
}
|
|
1676
1684
|
}
|
|
1677
1685
|
}
|
|
1678
|
-
guard !ended.isEmpty else { return }
|
|
1679
|
-
script
|
|
1686
|
+
guard !ended.isEmpty, let script else { return }
|
|
1687
|
+
CraftNativeScriptThread.run { script.objectForKeyedSubscript("__craftNativeAudioEvents")?.call(withArguments: [ended]) }
|
|
1680
1688
|
}
|
|
1681
1689
|
|
|
1682
1690
|
// MARK: Tests
|
package/dist/mobile-testing.js
CHANGED
|
@@ -188,9 +188,12 @@ ${await Bun.file(join(fixtures, "media-cache.swift.fixture")).text()}`);
|
|
|
188
188
|
if (!/^emulator-\d+$/.test(serial))
|
|
189
189
|
throw new Error("Select a disposable Android emulator with ANDROID_SERIAL");
|
|
190
190
|
const shell = await Bun.file(join(root, "android/templates/CraftNativeShell.kt.template")).text();
|
|
191
|
-
const
|
|
192
|
-
if (!
|
|
191
|
+
const pieces = ["internal object CraftNativeMedia {", "internal class CraftResumableDownload(", "internal class CraftNativeMediaWorker("].map((start) => shell.match(new RegExp(`${start.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?\\n\\}`))?.[0]);
|
|
192
|
+
if (pieces.some((piece) => !piece))
|
|
193
193
|
throw new Error("Android media implementation missing");
|
|
194
|
+
const media = pieces.join(`
|
|
195
|
+
|
|
196
|
+
`);
|
|
194
197
|
const android = join(scratch, "android");
|
|
195
198
|
const app = join(android, "app");
|
|
196
199
|
const sources = join(app, "src/main/java/dev/craft/mediaprobe");
|
|
@@ -214,15 +217,18 @@ android { namespace = "${packageName}";
|
|
|
214
217
|
targetSdk = 35 };
|
|
215
218
|
compileOptions { sourceCompatibility = JavaVersion.VERSION_17;
|
|
216
219
|
targetCompatibility = JavaVersion.VERSION_17 };
|
|
217
|
-
kotlinOptions { jvmTarget = "17" } }
|
|
220
|
+
kotlinOptions { jvmTarget = "17" } }
|
|
221
|
+
dependencies { implementation("androidx.work:work-runtime-ktx:2.9.1") }`);
|
|
218
222
|
await Bun.write(join(app, "src/main/AndroidManifest.xml"), `<manifest xmlns:android="http://schemas.android.com/apk/res/android"><uses-permission android:name="android.permission.INTERNET"/><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/><application android:usesCleartextTraffic="true" android:label="HQ Download Probe"/><instrumentation android:name=".DownloadProbe" android:targetPackage="${packageName}"/></manifest>`);
|
|
219
223
|
await Bun.write(join(sources, "DownloadProbe.kt"), `package ${packageName}
|
|
220
224
|
import android.content.Context
|
|
225
|
+
import android.content.SharedPreferences
|
|
221
226
|
import android.net.Uri
|
|
222
227
|
import android.os.Handler
|
|
223
228
|
import android.os.Looper
|
|
224
229
|
import org.json.JSONObject
|
|
225
230
|
import java.io.File
|
|
231
|
+
import java.util.concurrent.ConcurrentHashMap
|
|
226
232
|
import java.util.concurrent.Executors
|
|
227
233
|
internal data class CraftNativeCapabilityResult(val data: Any? = null, val code: String? = null, val message: String? = null)
|
|
228
234
|
${media}
|
package/dist/mobile.js
CHANGED
|
@@ -355,6 +355,32 @@ var secureStorage = {
|
|
|
355
355
|
Object.keys(localStorage).filter((k) => k.startsWith("secure_")).forEach((k) => localStorage.removeItem(k));
|
|
356
356
|
}
|
|
357
357
|
};
|
|
358
|
+
var SESSION_EVENT = "craftsession";
|
|
359
|
+
var SESSION_MESSAGE = "SESSION_CHANGED";
|
|
360
|
+
var DEFAULT_SESSION_TOKEN_KEY = "auth.token";
|
|
361
|
+
var sessionToken = (value) => typeof value === "string" && value !== "" ? value : null;
|
|
362
|
+
var session = {
|
|
363
|
+
token() {
|
|
364
|
+
const host = getCraftRoot()?.session;
|
|
365
|
+
if (typeof host?.token !== "function")
|
|
366
|
+
return null;
|
|
367
|
+
try {
|
|
368
|
+
return sessionToken(host.token());
|
|
369
|
+
} catch {
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
onChange(callback) {
|
|
374
|
+
if (typeof callback !== "function")
|
|
375
|
+
throw new TypeError("session.onChange needs a function");
|
|
376
|
+
const host = getCraftRoot()?.session;
|
|
377
|
+
if (typeof host?.onChange === "function") {
|
|
378
|
+
const stop = host.onChange((token, previous) => callback(sessionToken(token), sessionToken(previous)));
|
|
379
|
+
return typeof stop === "function" ? stop : () => {};
|
|
380
|
+
}
|
|
381
|
+
return onCraftEvent(SESSION_EVENT, (detail) => callback(sessionToken(detail.token), sessionToken(detail.previous)));
|
|
382
|
+
}
|
|
383
|
+
};
|
|
358
384
|
function storageHost(method) {
|
|
359
385
|
const host = getCraftRoot()?.storage;
|
|
360
386
|
if (typeof host?.[method] !== "function")
|
|
@@ -1643,11 +1669,11 @@ var health = {
|
|
|
1643
1669
|
throw new Error("Reading sleep intervals is unavailable");
|
|
1644
1670
|
return craft.health.getSleepIntervals(options);
|
|
1645
1671
|
},
|
|
1646
|
-
async saveMindfulSession(
|
|
1672
|
+
async saveMindfulSession(session2) {
|
|
1647
1673
|
const craft = getCraftRoot();
|
|
1648
1674
|
if (!craft?.health?.saveMindfulSession)
|
|
1649
1675
|
throw new Error("Saving mindful sessions is unavailable");
|
|
1650
|
-
return craft.health.saveMindfulSession(
|
|
1676
|
+
return craft.health.saveMindfulSession(session2);
|
|
1651
1677
|
},
|
|
1652
1678
|
async getMindfulSessions(options = {}) {
|
|
1653
1679
|
const craft = getCraftRoot();
|
|
@@ -1802,6 +1828,7 @@ export {
|
|
|
1802
1828
|
speech,
|
|
1803
1829
|
snapshots,
|
|
1804
1830
|
share,
|
|
1831
|
+
session,
|
|
1805
1832
|
secureStorage,
|
|
1806
1833
|
scheduledWorkoutsUnsupported,
|
|
1807
1834
|
scheduledWorkouts,
|
|
@@ -1843,5 +1870,8 @@ export {
|
|
|
1843
1870
|
appearance,
|
|
1844
1871
|
appReview,
|
|
1845
1872
|
_webFallbackDeviceId,
|
|
1846
|
-
_resetWebFallbackDeviceIdForTests
|
|
1873
|
+
_resetWebFallbackDeviceIdForTests,
|
|
1874
|
+
SESSION_MESSAGE,
|
|
1875
|
+
SESSION_EVENT,
|
|
1876
|
+
DEFAULT_SESSION_TOKEN_KEY
|
|
1847
1877
|
};
|