@glassly/crust 0.1.0-dev.99 → 0.2.8-dev.445
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/android/build.gradle +3 -3
- package/android/src/main/AndroidManifest.xml +1 -1
- package/android/src/main/java/com/glassly/crust/CrustModule.kt +82 -14
- package/android/src/main/java/com/glassly/crust/jsc/JSCDispatcher.kt +4 -4
- package/android/src/main/java/com/glassly/crust/jsc/JSCPolyfillBridge.kt +1 -1
- package/android/src/main/java/com/glassly/crust/jsc/JSCRuntime.kt +12 -12
- package/android/src/main/java/com/glassly/crust/services/NotificationListener.kt +1 -1
- package/android/src/main/res/values/strings.xml +1 -1
- package/build/Crust.types.d.ts +11 -0
- package/build/Crust.types.d.ts.map +1 -1
- package/build/Crust.types.js.map +1 -1
- package/build/CrustModule.d.ts +5 -3
- package/build/CrustModule.d.ts.map +1 -1
- package/build/CrustModule.js.map +1 -1
- package/build/CrustModule.web.d.ts +4 -0
- package/build/CrustModule.web.d.ts.map +1 -1
- package/build/CrustModule.web.js +3 -0
- package/build/CrustModule.web.js.map +1 -1
- package/ios/Crust.podspec +1 -1
- package/ios/CrustModule.swift +47 -5
- package/ios/Resources/startup.js +2 -2
- package/ios/Source/JSCDispatcher.swift +9 -9
- package/ios/Source/JSCPolyfillBridge.swift +2 -2
- package/ios/Source/JSCRuntime.swift +19 -19
- package/ios/navigation/NavigationManager.swift +28 -8
- package/package.json +2 -2
- package/src/Crust.types.ts +12 -0
- package/src/CrustModule.ts +6 -4
- package/src/CrustModule.web.ts +3 -0
package/android/build.gradle
CHANGED
|
@@ -9,7 +9,7 @@ applyKotlinExpoModulesCorePlugin()
|
|
|
9
9
|
useCoreDependencies()
|
|
10
10
|
useExpoPublishing()
|
|
11
11
|
|
|
12
|
-
//
|
|
12
|
+
// JSRuntime Android runtime: QuickJS via dokar3/quickjs-kt.
|
|
13
13
|
//
|
|
14
14
|
// Zipline's `bind(name, service)` registers services in an internal Kotlin
|
|
15
15
|
// map keyed by name for the Kotlin/JS-compiled `Zipline.take()` API; it does
|
|
@@ -51,7 +51,7 @@ android {
|
|
|
51
51
|
versionName "0.1.0"
|
|
52
52
|
multiDexEnabled true
|
|
53
53
|
}
|
|
54
|
-
// Pull the
|
|
54
|
+
// Pull the JSRuntime polyfill bundle from the sibling @glassly/jspolyfill
|
|
55
55
|
// module's `assets/` directory — single committed source of truth. The
|
|
56
56
|
// file is read at runtime via `appContext.assets.open("startup.js")`.
|
|
57
57
|
// Cross-module sourceSet entry beats checking the same artifact into git
|
|
@@ -117,7 +117,7 @@ dependencies {
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
dependencies {
|
|
120
|
-
//
|
|
120
|
+
// JSRuntime — QuickJS for per-miniapp JS contexts. dokar3/quickjs-kt is a
|
|
121
121
|
// KMP wrapper around Bellard QuickJS that exposes `function(name) { ... }`
|
|
122
122
|
// for installing Kotlin lambdas as JS globals via `JS_SetPropertyStr`.
|
|
123
123
|
// Mirrors iOS Apple JSC's `setObject(_, forKeyedSubscript:)` pattern.
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<application>
|
|
8
8
|
<service
|
|
9
9
|
android:name="com.glassly.crust.services.NotificationListenerServiceImpl"
|
|
10
|
-
android:label="@string/
|
|
10
|
+
android:label="@string/crust_notification_listener_label"
|
|
11
11
|
android:enabled="false"
|
|
12
12
|
android:exported="false"
|
|
13
13
|
android:process=":notif"
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
package com.glassly.crust
|
|
2
2
|
|
|
3
|
+
import android.app.KeyguardManager
|
|
3
4
|
import android.content.BroadcastReceiver
|
|
5
|
+
import android.content.Context
|
|
6
|
+
import android.content.Intent
|
|
7
|
+
import android.content.IntentFilter
|
|
8
|
+
import android.os.Build
|
|
9
|
+
import android.os.PowerManager
|
|
4
10
|
import android.util.Log
|
|
5
11
|
import com.glassly.crust.services.NotificationListener
|
|
6
12
|
import com.glassly.crust.services.NotificationProcessBridge
|
|
@@ -13,6 +19,8 @@ import com.glassly.crust.heading.HeadingManager
|
|
|
13
19
|
import com.glassly.crust.jsc.JSCRuntime
|
|
14
20
|
import com.glassly.crust.jsc.InstalledMiniappManifest
|
|
15
21
|
import com.glassly.crust.jsc.JSCPolyfillBridge
|
|
22
|
+
import com.glassly.crust.utils.ImageProcessor
|
|
23
|
+
import com.glassly.crust.utils.VideoStabilizer
|
|
16
24
|
|
|
17
25
|
class CrustModule : Module() {
|
|
18
26
|
companion object {
|
|
@@ -78,6 +86,47 @@ class CrustModule : Module() {
|
|
|
78
86
|
@Volatile private var runtimeInstalled: Boolean = false
|
|
79
87
|
private var notificationEventReceiver: BroadcastReceiver? = null
|
|
80
88
|
private var notificationBridgeContext: android.content.Context? = null
|
|
89
|
+
private var lockStateReceiver: BroadcastReceiver? = null
|
|
90
|
+
private var lockStateContext: Context? = null
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Phone lock state as seen by the app: `unlocked` = screen on and no keyguard
|
|
94
|
+
* showing; `passcodeSet` = the keyguard is secured (PIN/pattern/biometric).
|
|
95
|
+
* Mirrors the iOS protected-data signal so the engine can treat both alike.
|
|
96
|
+
*/
|
|
97
|
+
private fun phoneLockState(context: Context): Map<String, Any> {
|
|
98
|
+
val keyguard = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
|
|
99
|
+
val power = context.getSystemService(Context.POWER_SERVICE) as PowerManager
|
|
100
|
+
val unlocked = power.isInteractive && !keyguard.isKeyguardLocked
|
|
101
|
+
return mapOf("unlocked" to unlocked, "passcodeSet" to keyguard.isDeviceSecure)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private fun registerLockStateReceiverIfPossible(): Boolean {
|
|
105
|
+
if (lockStateReceiver != null) return true
|
|
106
|
+
val context = appContext.reactContext ?: appContext.currentActivity ?: return false
|
|
107
|
+
val applicationContext = context.applicationContext
|
|
108
|
+
val receiver =
|
|
109
|
+
object : BroadcastReceiver() {
|
|
110
|
+
override fun onReceive(ctx: Context, intent: Intent) {
|
|
111
|
+
emitEvent("phone_lock_state", phoneLockState(ctx.applicationContext))
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
val filter =
|
|
115
|
+
IntentFilter().apply {
|
|
116
|
+
addAction(Intent.ACTION_SCREEN_ON)
|
|
117
|
+
addAction(Intent.ACTION_SCREEN_OFF)
|
|
118
|
+
addAction(Intent.ACTION_USER_PRESENT)
|
|
119
|
+
}
|
|
120
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
121
|
+
applicationContext.registerReceiver(receiver, filter, Context.RECEIVER_NOT_EXPORTED)
|
|
122
|
+
} else {
|
|
123
|
+
@Suppress("UnspecifiedRegisterReceiverFlag")
|
|
124
|
+
applicationContext.registerReceiver(receiver, filter)
|
|
125
|
+
}
|
|
126
|
+
lockStateReceiver = receiver
|
|
127
|
+
lockStateContext = applicationContext
|
|
128
|
+
return true
|
|
129
|
+
}
|
|
81
130
|
|
|
82
131
|
private fun registerNotificationBridgeIfPossible(): Boolean {
|
|
83
132
|
if (notificationEventReceiver != null) return true
|
|
@@ -98,16 +147,16 @@ class CrustModule : Module() {
|
|
|
98
147
|
if (runtimeInstalled) return true
|
|
99
148
|
val ctx = appContext.reactContext ?: appContext.currentActivity
|
|
100
149
|
if (ctx == null) {
|
|
101
|
-
Log.w(TAG, "
|
|
150
|
+
Log.w(TAG, "JSRuntime: install deferred ($reason) — no context yet")
|
|
102
151
|
return false
|
|
103
152
|
}
|
|
104
153
|
val runtime = JSCRuntime.shared(ctx)
|
|
105
154
|
runtime.onOutbound = { msg ->
|
|
106
|
-
sendEvent("
|
|
155
|
+
sendEvent("js_message", msg.payload)
|
|
107
156
|
}
|
|
108
157
|
JSCPolyfillBridge.install(runtime)
|
|
109
158
|
runtimeInstalled = true
|
|
110
|
-
Log.i(TAG, "
|
|
159
|
+
Log.i(TAG, "JSRuntime: runtime installed ($reason)")
|
|
111
160
|
return true
|
|
112
161
|
}
|
|
113
162
|
|
|
@@ -129,14 +178,17 @@ class CrustModule : Module() {
|
|
|
129
178
|
"onNavRoute",
|
|
130
179
|
"onNavOffRoute",
|
|
131
180
|
"onHeading",
|
|
132
|
-
//
|
|
181
|
+
// Fires on screen on/off and unlock. Payload matches getPhoneLockState().
|
|
182
|
+
"phone_lock_state",
|
|
183
|
+
// JSRuntime — per-miniapp JSContext outbound message bus.
|
|
133
184
|
// JSRouter subscribes via Crust.addListener.
|
|
134
|
-
"
|
|
185
|
+
"js_message",
|
|
135
186
|
)
|
|
136
187
|
|
|
137
188
|
OnCreate {
|
|
138
189
|
eventEmitter = { eventName, data -> sendEvent(eventName, data) }
|
|
139
190
|
registerNotificationBridgeIfPossible()
|
|
191
|
+
registerLockStateReceiverIfPossible()
|
|
140
192
|
installRuntimeIfPossible("OnCreate")
|
|
141
193
|
}
|
|
142
194
|
|
|
@@ -148,6 +200,13 @@ class CrustModule : Module() {
|
|
|
148
200
|
}
|
|
149
201
|
notificationEventReceiver = null
|
|
150
202
|
notificationBridgeContext = null
|
|
203
|
+
val lockCtx = lockStateContext
|
|
204
|
+
val lockReceiver = lockStateReceiver
|
|
205
|
+
if (lockCtx != null && lockReceiver != null) {
|
|
206
|
+
runCatching { lockCtx.unregisterReceiver(lockReceiver) }
|
|
207
|
+
}
|
|
208
|
+
lockStateReceiver = null
|
|
209
|
+
lockStateContext = null
|
|
151
210
|
eventEmitter = null
|
|
152
211
|
}
|
|
153
212
|
|
|
@@ -220,6 +279,15 @@ class CrustModule : Module() {
|
|
|
220
279
|
NotificationListener.hasNotificationListenerPermission(context)
|
|
221
280
|
}
|
|
222
281
|
|
|
282
|
+
AsyncFunction("getPhoneLockState") {
|
|
283
|
+
registerLockStateReceiverIfPossible()
|
|
284
|
+
val context =
|
|
285
|
+
appContext.reactContext
|
|
286
|
+
?: appContext.currentActivity
|
|
287
|
+
?: throw IllegalStateException("No context available")
|
|
288
|
+
phoneLockState(context)
|
|
289
|
+
}
|
|
290
|
+
|
|
223
291
|
AsyncFunction("refreshNotificationListener") {
|
|
224
292
|
val context =
|
|
225
293
|
appContext.reactContext
|
|
@@ -237,13 +305,13 @@ class CrustModule : Module() {
|
|
|
237
305
|
true
|
|
238
306
|
}
|
|
239
307
|
|
|
240
|
-
// MARK: -
|
|
308
|
+
// MARK: - JS Runtime
|
|
241
309
|
|
|
242
310
|
AsyncFunction("jsSpawn") { packageName: String, polyfillBundle: String, miniappJs: String ->
|
|
243
311
|
val ctx =
|
|
244
312
|
appContext.reactContext
|
|
245
313
|
?: appContext.currentActivity
|
|
246
|
-
?: throw IllegalStateException("
|
|
314
|
+
?: throw IllegalStateException("JSRuntime: no context")
|
|
247
315
|
// Safety net for the OnCreate-too-early race: if the runtime wasn't
|
|
248
316
|
// wired during module creation (no reactContext yet), do it now —
|
|
249
317
|
// otherwise every host-bound __dispatch from this miniapp's JSContext
|
|
@@ -260,7 +328,7 @@ class CrustModule : Module() {
|
|
|
260
328
|
val ctx =
|
|
261
329
|
appContext.reactContext
|
|
262
330
|
?: appContext.currentActivity
|
|
263
|
-
?: throw IllegalStateException("
|
|
331
|
+
?: throw IllegalStateException("JSRuntime: no context")
|
|
264
332
|
JSCRuntime.shared(ctx).evaluate(packageName, source)
|
|
265
333
|
}
|
|
266
334
|
|
|
@@ -268,7 +336,7 @@ class CrustModule : Module() {
|
|
|
268
336
|
val ctx =
|
|
269
337
|
appContext.reactContext
|
|
270
338
|
?: appContext.currentActivity
|
|
271
|
-
?: throw IllegalStateException("
|
|
339
|
+
?: throw IllegalStateException("JSRuntime: no context")
|
|
272
340
|
JSCRuntime.shared(ctx).kill(packageName)
|
|
273
341
|
}
|
|
274
342
|
|
|
@@ -276,7 +344,7 @@ class CrustModule : Module() {
|
|
|
276
344
|
val ctx =
|
|
277
345
|
appContext.reactContext
|
|
278
346
|
?: appContext.currentActivity
|
|
279
|
-
?: throw IllegalStateException("
|
|
347
|
+
?: throw IllegalStateException("JSRuntime: no context")
|
|
280
348
|
val json = org.json.JSONObject(envelope as Map<*, *>).toString()
|
|
281
349
|
JSCRuntime.shared(ctx).dispatchToJs(packageName, json)
|
|
282
350
|
}
|
|
@@ -285,7 +353,7 @@ class CrustModule : Module() {
|
|
|
285
353
|
val ctx =
|
|
286
354
|
appContext.reactContext
|
|
287
355
|
?: appContext.currentActivity
|
|
288
|
-
?: throw IllegalStateException("
|
|
356
|
+
?: throw IllegalStateException("JSRuntime: no context")
|
|
289
357
|
JSCRuntime.shared(ctx).dispatcher.setManifest(
|
|
290
358
|
packageName,
|
|
291
359
|
InstalledMiniappManifest(permissions.toSet()),
|
|
@@ -482,7 +550,7 @@ class CrustModule : Module() {
|
|
|
482
550
|
val colorCorrection = options["colorCorrection"] as? Boolean ?: true
|
|
483
551
|
|
|
484
552
|
val processingTimeMs =
|
|
485
|
-
|
|
553
|
+
ImageProcessor.process(
|
|
486
554
|
inputPath,
|
|
487
555
|
outputPath,
|
|
488
556
|
lensCorrection,
|
|
@@ -514,7 +582,7 @@ class CrustModule : Module() {
|
|
|
514
582
|
outputPath: String ->
|
|
515
583
|
try {
|
|
516
584
|
val processingTimeMs =
|
|
517
|
-
|
|
585
|
+
ImageProcessor.mergeHdr(
|
|
518
586
|
underPath,
|
|
519
587
|
normalPath,
|
|
520
588
|
overPath,
|
|
@@ -550,7 +618,7 @@ class CrustModule : Module() {
|
|
|
550
618
|
}
|
|
551
619
|
|
|
552
620
|
val processingTimeMs =
|
|
553
|
-
|
|
621
|
+
VideoStabilizer.stabilize(inputPath, imuPath, outputPath)
|
|
554
622
|
|
|
555
623
|
if (processingTimeMs >= 0) {
|
|
556
624
|
mapOf(
|
|
@@ -7,7 +7,7 @@ import java.security.SecureRandom
|
|
|
7
7
|
import org.json.JSONArray
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
10
|
+
* JSRuntime dispatch table + permission gate for the per-miniapp
|
|
11
11
|
* `__dispatch(iface, method, args)` bridge. Symmetric with iOS
|
|
12
12
|
* [JSCDispatcher.swift].
|
|
13
13
|
*
|
|
@@ -26,7 +26,7 @@ sealed class JSCDispatchOutcome {
|
|
|
26
26
|
object Async : JSCDispatchOutcome()
|
|
27
27
|
/** Refused. JS sees a thrown Error with `code` + `message`. */
|
|
28
28
|
data class Error(val code: String, val message: String?) : JSCDispatchOutcome()
|
|
29
|
-
/** Forward to RN via the `
|
|
29
|
+
/** Forward to RN via the `js_message` Expo event. */
|
|
30
30
|
data class ForwardToRn(val payload: Map<String, Any?>) : JSCDispatchOutcome()
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -34,7 +34,7 @@ data class InstalledMiniappManifest(val permissions: Set<String>)
|
|
|
34
34
|
|
|
35
35
|
class JSCDispatcher(private val appContext: Context) {
|
|
36
36
|
companion object {
|
|
37
|
-
private const val TAG = "
|
|
37
|
+
private const val TAG = "JSRuntime.Dispatcher"
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
private val routes = mutableMapOf<String, Handler>()
|
|
@@ -184,6 +184,6 @@ class JSCDispatcher(private val appContext: Context) {
|
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
private fun prefs(packageName: String): SharedPreferences {
|
|
187
|
-
return appContext.getSharedPreferences("
|
|
187
|
+
return appContext.getSharedPreferences("JSRuntime-$packageName", Context.MODE_PRIVATE)
|
|
188
188
|
}
|
|
189
189
|
}
|
|
@@ -35,7 +35,7 @@ import android.util.Base64
|
|
|
35
35
|
* `JS_ExecutePendingJob` — see the spec's Android microtask section.
|
|
36
36
|
*/
|
|
37
37
|
object JSCPolyfillBridge {
|
|
38
|
-
private const val TAG = "
|
|
38
|
+
private const val TAG = "JSRuntime.PolyfillBridge"
|
|
39
39
|
|
|
40
40
|
private val httpClient: OkHttpClient by lazy {
|
|
41
41
|
OkHttpClient.Builder()
|
|
@@ -15,7 +15,7 @@ import kotlinx.coroutines.Dispatchers
|
|
|
15
15
|
import kotlinx.coroutines.runBlocking
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
* Thrown from the `__dispatch` binding to propagate a structured
|
|
18
|
+
* Thrown from the `__dispatch` binding to propagate a structured JSRuntime
|
|
19
19
|
* dispatch failure (PERMISSION_NOT_DECLARED / INVALID_ARGS / etc.) back to
|
|
20
20
|
* the calling JS frame. Dokar3 surfaces Kotlin throwables from bindings as
|
|
21
21
|
* JS-side `throw new Error(message)`, so the SDK's send-request Promise
|
|
@@ -24,7 +24,7 @@ import kotlinx.coroutines.runBlocking
|
|
|
24
24
|
class JSDispatchError(val code: String, message: String) : RuntimeException("$code: $message")
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
*
|
|
27
|
+
* JSRuntime — per-miniapp QuickJS runtime host on Android.
|
|
28
28
|
*
|
|
29
29
|
* Owns N QuickJs instances (via dokar3/quickjs-kt) keyed by packageName.
|
|
30
30
|
* Each context gets:
|
|
@@ -66,7 +66,7 @@ class JSDispatchError(val code: String, message: String) : RuntimeException("$co
|
|
|
66
66
|
*/
|
|
67
67
|
class JSCRuntime private constructor(private val appContext: Context) {
|
|
68
68
|
companion object {
|
|
69
|
-
private const val TAG = "
|
|
69
|
+
private const val TAG = "JSRuntime"
|
|
70
70
|
|
|
71
71
|
// NACK cold-start timeout (15s — covers polyfill + init on slow devices).
|
|
72
72
|
const val COLD_START_NACK_TIMEOUT_MS: Long = 15_000
|
|
@@ -87,7 +87,7 @@ class JSCRuntime private constructor(private val appContext: Context) {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
/**
|
|
90
|
-
* Subscribe to outbound `
|
|
90
|
+
* Subscribe to outbound `js_message` events. Set by [CrustModule]
|
|
91
91
|
* during `OnCreate` (or lazily on `jsSpawn` if reactContext was
|
|
92
92
|
* null at OnCreate time). The runtime fires this for every __dispatch
|
|
93
93
|
* call that has no matching local route in [JSCDispatcher]. If this is
|
|
@@ -139,7 +139,7 @@ class JSCRuntime private constructor(private val appContext: Context) {
|
|
|
139
139
|
// QuickJs single-thread executor, so timer fires happen on the JS thread.
|
|
140
140
|
private val timerScheduler: ScheduledExecutorService =
|
|
141
141
|
Executors.newScheduledThreadPool(1) { r ->
|
|
142
|
-
Thread(r, "
|
|
142
|
+
Thread(r, "JSRuntime-timer-scheduler").apply { isDaemon = true }
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
private inner class ContextRecord(
|
|
@@ -211,7 +211,7 @@ class JSCRuntime private constructor(private val appContext: Context) {
|
|
|
211
211
|
fun alivePackages(): List<String> = contexts.keys.toList()
|
|
212
212
|
|
|
213
213
|
/**
|
|
214
|
-
* Read the bundled
|
|
214
|
+
* Read the bundled JSRuntime polyfill (`assets/startup.js`) shipped
|
|
215
215
|
* inside the host APK. The file is sourced from the sibling
|
|
216
216
|
* @glassly/jspolyfill module via `sourceSets.main.assets.srcDirs`
|
|
217
217
|
* in this module's build.gradle. Cached after first read.
|
|
@@ -240,7 +240,7 @@ class JSCRuntime private constructor(private val appContext: Context) {
|
|
|
240
240
|
kill(packageName)
|
|
241
241
|
}
|
|
242
242
|
val executor = Executors.newSingleThreadExecutor { r ->
|
|
243
|
-
Thread(r, "
|
|
243
|
+
Thread(r, "JSRuntime-$packageName").apply { isDaemon = true }
|
|
244
244
|
}
|
|
245
245
|
|
|
246
246
|
// Create the QuickJs context ON the executor thread. We pass
|
|
@@ -288,10 +288,10 @@ class JSCRuntime private constructor(private val appContext: Context) {
|
|
|
288
288
|
runBlocking {
|
|
289
289
|
installGlobals(qjs, packageName)
|
|
290
290
|
if (bundle.isNotEmpty()) {
|
|
291
|
-
qjs.evaluate<Any?>(bundle, filename = "
|
|
291
|
+
qjs.evaluate<Any?>(bundle, filename = "jsruntime:startup.js")
|
|
292
292
|
}
|
|
293
293
|
if (miniappJs.isNotEmpty()) {
|
|
294
|
-
qjs.evaluate<Any?>(miniappJs, filename = "
|
|
294
|
+
qjs.evaluate<Any?>(miniappJs, filename = "jsruntime:miniapp.js")
|
|
295
295
|
}
|
|
296
296
|
}
|
|
297
297
|
Log.i(TAG, "spawned $packageName")
|
|
@@ -417,7 +417,7 @@ class JSCRuntime private constructor(private val appContext: Context) {
|
|
|
417
417
|
try {
|
|
418
418
|
record.qjs.evaluate<Any?>(
|
|
419
419
|
"globalThis.__deliverTimer && globalThis.__deliverTimer($token);",
|
|
420
|
-
filename = "
|
|
420
|
+
filename = "jsruntime:timer-$token.js",
|
|
421
421
|
)
|
|
422
422
|
} catch (e: Throwable) {
|
|
423
423
|
Log.w(TAG, "timer fire threw in $packageName: ${e.message}")
|
|
@@ -443,7 +443,7 @@ class JSCRuntime private constructor(private val appContext: Context) {
|
|
|
443
443
|
runBlocking {
|
|
444
444
|
record.qjs.evaluate<Any?>(
|
|
445
445
|
source,
|
|
446
|
-
filename = "
|
|
446
|
+
filename = "jsruntime:eval-${System.nanoTime()}.js",
|
|
447
447
|
)
|
|
448
448
|
}
|
|
449
449
|
}.get()
|
|
@@ -493,7 +493,7 @@ class JSCRuntime private constructor(private val appContext: Context) {
|
|
|
493
493
|
try {
|
|
494
494
|
val source = "globalThis.__deliver(${jsStringLiteral(envelopeJson)});"
|
|
495
495
|
runBlocking {
|
|
496
|
-
record.qjs.evaluate<Any?>(source, filename = "
|
|
496
|
+
record.qjs.evaluate<Any?>(source, filename = "jsruntime:deliver.js")
|
|
497
497
|
}
|
|
498
498
|
// Successful delivery — context is responsive.
|
|
499
499
|
record.readyNackTimer?.cancel(false)
|
|
@@ -19,7 +19,7 @@ import java.util.concurrent.ConcurrentHashMap
|
|
|
19
19
|
class NotificationListener private constructor(private val context: Context) {
|
|
20
20
|
companion object {
|
|
21
21
|
private const val TAG = "CrustNotificationListener"
|
|
22
|
-
private const val PREFS_NAME = "
|
|
22
|
+
private const val PREFS_NAME = "crust_notification_prefs"
|
|
23
23
|
private const val PREF_LISTENER_ENABLED = "notification_listener_enabled"
|
|
24
24
|
private const val PREF_NOTIFICATIONS_BLOCKLIST = "notifications_blocklist"
|
|
25
25
|
|
package/build/Crust.types.d.ts
CHANGED
|
@@ -12,6 +12,17 @@ export type CrustModuleEvents = {
|
|
|
12
12
|
onNavRoute: (params: NavRoutePayload) => void;
|
|
13
13
|
onNavOffRoute: (params: NavOffRoutePayload) => void;
|
|
14
14
|
onHeading: (params: HeadingPayload) => void;
|
|
15
|
+
phone_lock_state: (params: PhoneLockState) => void;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Whether the user can see the phone right now. iOS derives `unlocked` from
|
|
19
|
+
* protected-data availability, Android from screen on + keyguard hidden.
|
|
20
|
+
* Without a passcode iOS never protects data, so `unlocked` is always true
|
|
21
|
+
* there and carries no information; check `passcodeSet` first.
|
|
22
|
+
*/
|
|
23
|
+
export type PhoneLockState = {
|
|
24
|
+
unlocked: boolean;
|
|
25
|
+
passcodeSet: boolean;
|
|
15
26
|
};
|
|
16
27
|
export type NavOffRoutePayload = {
|
|
17
28
|
/** Approximate perpendicular distance in meters from the route. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Crust.types.d.ts","sourceRoot":"","sources":["../src/Crust.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,SAAS,EAAE,SAAS,EAAC,MAAM,cAAc,CAAA;AAEtD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,GAAG,EAAE,MAAM,CAAA;CACZ,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,CAAA;IAC9C,aAAa,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,CAAA;IACnD,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,IAAI,CAAA;IACvD,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,IAAI,CAAA;IACrD,UAAU,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,IAAI,CAAA;IAC7C,aAAa,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,CAAA;IACnD,UAAU,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,IAAI,CAAA;IAC7C,aAAa,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,CAAA;IACnD,SAAS,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,IAAI,CAAA;
|
|
1
|
+
{"version":3,"file":"Crust.types.d.ts","sourceRoot":"","sources":["../src/Crust.types.ts"],"names":[],"mappings":"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"}
|
package/build/Crust.types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Crust.types.js","sourceRoot":"","sources":["../src/Crust.types.ts"],"names":[],"mappings":"","sourcesContent":["import type {StyleProp, ViewStyle} from \"react-native\"\n\nexport type OnLoadEventPayload = {\n url: string\n}\n\nexport type CrustModuleEvents = {\n onChange: (params: ChangeEventPayload) => void\n onNavManeuver: (params: NavManeuverPayload) => void\n onNavRerouting: (params: Record<string, never>) => void\n onNavArrived: (params: Record<string, never>) => void\n onNavError: (params: NavErrorPayload) => void\n onNavLocation: (params: NavLocationPayload) => void\n onNavRoute: (params: NavRoutePayload) => void\n onNavOffRoute: (params: NavOffRoutePayload) => void\n onHeading: (params: HeadingPayload) => void\n}\n\nexport type NavOffRoutePayload = {\n /** Approximate perpendicular distance in meters from the route. */\n offRouteDistanceMeters: number\n}\n\nexport type HeadingPayload = {\n /** Compass heading in degrees, 0 = north, 90 = east. */\n degrees: number\n}\n\nexport type NavRoutePayload = {\n points: Array<{lat: number; lng: number}>\n /**\n * Ordered step list along the route, when supplied by the host. Each\n * step is one navigable segment — typically one straight stretch of\n * road ending in a maneuver. Optional for back-compat with older\n * hosts that only ship the polyline. The SDK's pivot module consumes\n * this to enrich pivots with `fromRoad` / `toRoad` metadata.\n */\n steps?: NavRouteStepPayload[]\n}\n\nexport type NavRouteStepPayload = {\n /** Coordinate where this step begins (== end of the previous step). */\n lat: number\n lng: number\n /**\n * Index into `NavRoutePayload.points[]` where this step starts.\n * Lets consumers correlate step boundaries with polyline geometry.\n */\n routeIndex: number\n /** Name of the road traversed during this step. Null when the engine has no name. */\n road?: string | null\n /**\n * Categorical maneuver performed at the END of this step (i.e. how\n * the user exits this step). Same vocabulary as `NavManeuverPayload.maneuverType`.\n */\n maneuver: string\n /** Length of this step in meters. */\n distanceMeters: number\n}\n\nexport type NavLocationPayload = {\n lat: number\n lng: number\n /** Horizontal accuracy in meters, if reported by the platform. */\n accuracy: number | null\n /** Unix ms timestamp of the fix. */\n timestamp: number\n phone_notification: (event: PhoneNotificationEvent) => void\n phone_notification_dismissed: (event: PhoneNotificationDismissedEvent) => void\n captions_tester_incident: (event: CaptionsTesterIncidentEvent) => void\n}\n\nexport type ChangeEventPayload = {\n value: string\n}\n\nexport type NavManeuverPayload = {\n /**\n * Categorical type of the upcoming maneuver. One of: STRAIGHT,\n * SLIGHT_LEFT, SLIGHT_RIGHT, TURN_LEFT, TURN_RIGHT, SHARP_LEFT,\n * SHARP_RIGHT, U_TURN, ARRIVE.\n */\n maneuverType: string\n /** Distance in meters from the user's current position to that maneuver. -1 if unknown. */\n distanceMeters: number\n /** Road the user is currently on, per the Nav SDK. Null if unavailable. */\n fromRoad?: string | null\n /**\n * Legacy \"next road\" field — historically populated from the same\n * source as `fromRoad` (the current step's road, NOT the road\n * after the upcoming turn). Kept for back-compat. New consumers\n * should read `nextStepRoad` instead.\n */\n toRoad?: string | null\n /**\n * Road the user will be on AFTER the upcoming maneuver, sourced\n * from the Nav SDK's `remainingSteps[0]`. This is the value to\n * show as the \"next street\" headline on the maneuver card. Null\n * when the SDK hasn't surfaced a remaining step yet (final leg,\n * pre-first-NavInfo, etc.).\n */\n nextStepRoad?: string | null\n /** Total remaining distance to final destination, meters. -1 if unknown. */\n distanceToDestinationMeters?: number\n /** Total remaining travel time, seconds. -1 if unknown. */\n timeToDestinationSeconds?: number\n /** Current speed in m/s. Null if unavailable. */\n currentSpeedMps?: number | null\n /** Speed limit on the current road segment in m/s. Null if unknown / not regulated. */\n speedLimitMps?: number | null\n /** Bearing along the route at the user's current position, 0–360. Null if unknown. */\n routeHeadingDeg?: number | null\n}\n\nexport type NavErrorPayload = {\n message: string\n}\n\nexport type InstalledApp = {\n packageName: string\n appName: string\n isBlocked: boolean\n icon: string | null\n}\n\nexport type PhoneNotificationEvent = {\n notificationId: string\n app: string\n title: string\n content: string\n /**\n * Android notification priority: PRIORITY_MIN (-2) through PRIORITY_MAX (2).\n * Derived from the channel importance when the ranking is available, since\n * Notification.priority reads 0 for channel-based notifications.\n */\n priority: number\n timestamp: number\n packageName: string\n}\n\nexport type PhoneNotificationDismissedEvent = {\n notificationKey: string\n packageName: string\n notificationId: string\n}\n\nexport type CaptionsTesterIncidentEvent = {\n action?: string\n timestamp?: number\n failure_code?: string\n failure_message?: string\n test_run_id?: string\n scenario_name?: string\n source?: string\n [key: string]: unknown\n}\n\nexport type CrustViewProps = {\n url: string\n onLoad: (event: {nativeEvent: OnLoadEventPayload}) => void\n style?: StyleProp<ViewStyle>\n}\n"]}
|
|
1
|
+
{"version":3,"file":"Crust.types.js","sourceRoot":"","sources":["../src/Crust.types.ts"],"names":[],"mappings":"","sourcesContent":["import type {StyleProp, ViewStyle} from \"react-native\"\n\nexport type OnLoadEventPayload = {\n url: string\n}\n\nexport type CrustModuleEvents = {\n onChange: (params: ChangeEventPayload) => void\n onNavManeuver: (params: NavManeuverPayload) => void\n onNavRerouting: (params: Record<string, never>) => void\n onNavArrived: (params: Record<string, never>) => void\n onNavError: (params: NavErrorPayload) => void\n onNavLocation: (params: NavLocationPayload) => void\n onNavRoute: (params: NavRoutePayload) => void\n onNavOffRoute: (params: NavOffRoutePayload) => void\n onHeading: (params: HeadingPayload) => void\n phone_lock_state: (params: PhoneLockState) => void\n}\n\n/**\n * Whether the user can see the phone right now. iOS derives `unlocked` from\n * protected-data availability, Android from screen on + keyguard hidden.\n * Without a passcode iOS never protects data, so `unlocked` is always true\n * there and carries no information; check `passcodeSet` first.\n */\nexport type PhoneLockState = {\n unlocked: boolean\n passcodeSet: boolean\n}\n\nexport type NavOffRoutePayload = {\n /** Approximate perpendicular distance in meters from the route. */\n offRouteDistanceMeters: number\n}\n\nexport type HeadingPayload = {\n /** Compass heading in degrees, 0 = north, 90 = east. */\n degrees: number\n}\n\nexport type NavRoutePayload = {\n points: Array<{lat: number; lng: number}>\n /**\n * Ordered step list along the route, when supplied by the host. Each\n * step is one navigable segment — typically one straight stretch of\n * road ending in a maneuver. Optional for back-compat with older\n * hosts that only ship the polyline. The SDK's pivot module consumes\n * this to enrich pivots with `fromRoad` / `toRoad` metadata.\n */\n steps?: NavRouteStepPayload[]\n}\n\nexport type NavRouteStepPayload = {\n /** Coordinate where this step begins (== end of the previous step). */\n lat: number\n lng: number\n /**\n * Index into `NavRoutePayload.points[]` where this step starts.\n * Lets consumers correlate step boundaries with polyline geometry.\n */\n routeIndex: number\n /** Name of the road traversed during this step. Null when the engine has no name. */\n road?: string | null\n /**\n * Categorical maneuver performed at the END of this step (i.e. how\n * the user exits this step). Same vocabulary as `NavManeuverPayload.maneuverType`.\n */\n maneuver: string\n /** Length of this step in meters. */\n distanceMeters: number\n}\n\nexport type NavLocationPayload = {\n lat: number\n lng: number\n /** Horizontal accuracy in meters, if reported by the platform. */\n accuracy: number | null\n /** Unix ms timestamp of the fix. */\n timestamp: number\n phone_notification: (event: PhoneNotificationEvent) => void\n phone_notification_dismissed: (event: PhoneNotificationDismissedEvent) => void\n captions_tester_incident: (event: CaptionsTesterIncidentEvent) => void\n}\n\nexport type ChangeEventPayload = {\n value: string\n}\n\nexport type NavManeuverPayload = {\n /**\n * Categorical type of the upcoming maneuver. One of: STRAIGHT,\n * SLIGHT_LEFT, SLIGHT_RIGHT, TURN_LEFT, TURN_RIGHT, SHARP_LEFT,\n * SHARP_RIGHT, U_TURN, ARRIVE.\n */\n maneuverType: string\n /** Distance in meters from the user's current position to that maneuver. -1 if unknown. */\n distanceMeters: number\n /** Road the user is currently on, per the Nav SDK. Null if unavailable. */\n fromRoad?: string | null\n /**\n * Legacy \"next road\" field — historically populated from the same\n * source as `fromRoad` (the current step's road, NOT the road\n * after the upcoming turn). Kept for back-compat. New consumers\n * should read `nextStepRoad` instead.\n */\n toRoad?: string | null\n /**\n * Road the user will be on AFTER the upcoming maneuver, sourced\n * from the Nav SDK's `remainingSteps[0]`. This is the value to\n * show as the \"next street\" headline on the maneuver card. Null\n * when the SDK hasn't surfaced a remaining step yet (final leg,\n * pre-first-NavInfo, etc.).\n */\n nextStepRoad?: string | null\n /** Total remaining distance to final destination, meters. -1 if unknown. */\n distanceToDestinationMeters?: number\n /** Total remaining travel time, seconds. -1 if unknown. */\n timeToDestinationSeconds?: number\n /** Current speed in m/s. Null if unavailable. */\n currentSpeedMps?: number | null\n /** Speed limit on the current road segment in m/s. Null if unknown / not regulated. */\n speedLimitMps?: number | null\n /** Bearing along the route at the user's current position, 0–360. Null if unknown. */\n routeHeadingDeg?: number | null\n}\n\nexport type NavErrorPayload = {\n message: string\n}\n\nexport type InstalledApp = {\n packageName: string\n appName: string\n isBlocked: boolean\n icon: string | null\n}\n\nexport type PhoneNotificationEvent = {\n notificationId: string\n app: string\n title: string\n content: string\n /**\n * Android notification priority: PRIORITY_MIN (-2) through PRIORITY_MAX (2).\n * Derived from the channel importance when the ranking is available, since\n * Notification.priority reads 0 for channel-based notifications.\n */\n priority: number\n timestamp: number\n packageName: string\n}\n\nexport type PhoneNotificationDismissedEvent = {\n notificationKey: string\n packageName: string\n notificationId: string\n}\n\nexport type CaptionsTesterIncidentEvent = {\n action?: string\n timestamp?: number\n failure_code?: string\n failure_message?: string\n test_run_id?: string\n scenario_name?: string\n source?: string\n [key: string]: unknown\n}\n\nexport type CrustViewProps = {\n url: string\n onLoad: (event: {nativeEvent: OnLoadEventPayload}) => void\n style?: StyleProp<ViewStyle>\n}\n"]}
|
package/build/CrustModule.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NativeModule } from "expo";
|
|
2
|
-
import type { CrustModuleEvents, InstalledApp } from "./Crust.types";
|
|
2
|
+
import type { CrustModuleEvents, InstalledApp, PhoneLockState } from "./Crust.types";
|
|
3
3
|
declare class CrustModule extends NativeModule<CrustModuleEvents> {
|
|
4
4
|
PI: number;
|
|
5
5
|
hello(): string;
|
|
@@ -27,6 +27,8 @@ declare class CrustModule extends NativeModule<CrustModuleEvents> {
|
|
|
27
27
|
hasNotificationListenerPermission(): Promise<boolean>;
|
|
28
28
|
refreshNotificationListener(): Promise<boolean>;
|
|
29
29
|
openNotificationListenerSettings(): Promise<boolean>;
|
|
30
|
+
/** Current lock state; changes arrive as `phone_lock_state` events. */
|
|
31
|
+
getPhoneLockState(): Promise<PhoneLockState>;
|
|
30
32
|
isBetaBuild(): Promise<boolean>;
|
|
31
33
|
showLocationServicesDialog(): Promise<boolean>;
|
|
32
34
|
isLocationServicesEnabled(): Promise<boolean>;
|
|
@@ -154,7 +156,7 @@ declare class CrustModule extends NativeModule<CrustModuleEvents> {
|
|
|
154
156
|
* with the same packageName is killed first. Returns true if the
|
|
155
157
|
* polyfill bundle + miniapp source evaluated without throwing.
|
|
156
158
|
*
|
|
157
|
-
* The polyfill bundle is `mobile/modules/
|
|
159
|
+
* The polyfill bundle is `mobile/modules/jspolyfill/dist/startup.js`
|
|
158
160
|
* (shipped inside the host binary). It installs window-style globals
|
|
159
161
|
* (console, timers, fetch, localStorage, crypto) atop the JSC runtime.
|
|
160
162
|
*/
|
|
@@ -174,7 +176,7 @@ declare class CrustModule extends NativeModule<CrustModuleEvents> {
|
|
|
174
176
|
/** Diagnostic — force a GC cycle on the named context. */
|
|
175
177
|
jsDebugForceGC(packageName: string): Promise<boolean>;
|
|
176
178
|
/**
|
|
177
|
-
* Read the bundled
|
|
179
|
+
* Read the bundled JSRuntime polyfill (startup.js) shipped inside the
|
|
178
180
|
* host binary. Synchronous — host RN code calls this once on app boot
|
|
179
181
|
* and caches the string, then passes it to every jsSpawn so
|
|
180
182
|
* every JSContext starts with the same polyfill ABI.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CrustModule.d.ts","sourceRoot":"","sources":["../src/CrustModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAsB,MAAM,MAAM,CAAA;AAEtD,OAAO,KAAK,EAAC,iBAAiB,EAAE,YAAY,EAAC,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"CrustModule.d.ts","sourceRoot":"","sources":["../src/CrustModule.ts"],"names":[],"mappings":"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"}
|
package/build/CrustModule.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CrustModule.js","sourceRoot":"","sources":["../src/CrustModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAE,mBAAmB,EAAC,MAAM,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"CrustModule.js","sourceRoot":"","sources":["../src/CrustModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAE,mBAAmB,EAAC,MAAM,MAAM,CAAA;AAkMtD,yDAAyD;AACzD,eAAe,mBAAmB,CAAc,OAAO,CAAC,CAAA","sourcesContent":["import {NativeModule, requireNativeModule} from \"expo\"\n\nimport type {CrustModuleEvents, InstalledApp, PhoneLockState} from \"./Crust.types\"\n\ndeclare class CrustModule extends NativeModule<CrustModuleEvents> {\n PI: number\n hello(): string\n setValueAsync(value: string): Promise<void>\n nativeHttpRequest(\n method: string,\n url: string,\n headers: Record<string, string>,\n body?: string | null,\n ): Promise<{status: number; statusText: string; headers: Record<string, string>; body: string}>\n showAVRoutePicker(tintColor?: string | null): void\n\n /**\n * iOS: configure `preferredScreenEdgesDeferringSystemGestures`. When an\n * edge is deferred, the first swipe across that edge is consumed by the\n * app and the system gesture (Control Center, Notification Center, Home\n * indicator) only fires on a second swipe — i.e. a two-swipe-to-exit UX.\n *\n * Pass `[]` to restore default behavior. Android: no-op (Android has no\n * per-app equivalent; system gestures are configured at the OS level).\n */\n setDeferredSystemGestures(edges: Array<\"top\" | \"bottom\" | \"left\" | \"right\" | \"all\">): Promise<void>\n\n // Glassly Notification Commands\n setNotificationConfig(listenerEnabled: boolean, blocklist: string[]): Promise<void>\n getInstalledApps(): Promise<InstalledApp[]>\n getInstalledAppsForNotifications(): Promise<InstalledApp[]>\n hasNotificationListenerPermission(): Promise<boolean>\n refreshNotificationListener(): Promise<boolean>\n openNotificationListenerSettings(): Promise<boolean>\n /** Current lock state; changes arrive as `phone_lock_state` events. */\n getPhoneLockState(): Promise<PhoneLockState>\n isBetaBuild(): Promise<boolean>\n // location services commands\n showLocationServicesDialog(): Promise<boolean>\n isLocationServicesEnabled(): Promise<boolean>\n openLocationSettings(): Promise<boolean>\n openAppSettings(): Promise<boolean>\n openBluetoothSettings(): Promise<boolean>\n\n // Image Processing Commands\n processGalleryImage(\n inputPath: string,\n outputPath: string,\n options: {\n lensCorrection?: boolean\n colorCorrection?: boolean\n },\n ): Promise<{\n success: boolean\n outputPath?: string\n processingTimeMs?: number\n error?: string\n }>\n\n mergeHdrBrackets(\n underPath: string,\n normalPath: string,\n overPath: string,\n outputPath: string,\n ): Promise<{\n success: boolean\n outputPath?: string\n processingTimeMs?: number\n error?: string\n }>\n\n stabilizeVideo(\n inputPath: string,\n imuPath: string,\n outputPath: string,\n ): Promise<{\n success: boolean\n outputPath?: string\n processingTimeMs?: number\n error?: string\n }>\n\n // Media Library Commands\n saveToGalleryWithDate(\n filePath: string,\n captureTimeMillis?: number,\n displayName?: string,\n ): Promise<{\n success: boolean\n uri?: string\n identifier?: string\n existing?: boolean\n error?: string\n }>\n\n // Navigation (both platforms via Mapbox; the dev toggles below are Android-only)\n /**\n * Override the Mapbox access token the native Navigation SDK\n * authenticates with. A non-empty token wins over the one baked at build\n * time (AndroidManifest `com.mapbox.token` / Info.plist `MBXAccessToken`);\n * \"\" clears the override and restores the baked token. Call before\n * `startNavigation` — the engine pushes the user's Settings > Keys token\n * here per trip start.\n */\n setMapboxAccessToken(token: string): Promise<{ok: boolean; error?: string}>\n startNavigation(\n lat: number,\n lng: number,\n options?: {\n simulate?: boolean\n speedMultiplier?: number\n /** Optional multi-stop list. When present takes precedence over lat/lng. Last entry is the final destination. */\n stops?: Array<{lat: number; lng: number}>\n /** \"walking\" | \"driving\" | \"cycling\" | \"two_wheeler\". Defaults driving. */\n mode?: string\n avoid?: {highways?: boolean; tolls?: boolean; ferries?: boolean}\n /** Force a reroute when the user is more than N meters past a pivot they didn't take. */\n missedTurnRerouteMeters?: number\n },\n ): Promise<{ok: boolean; error?: string}>\n stopNavigation(): Promise<{ok: boolean; error?: string}>\n\n /**\n * Kept for API parity with the Google-era contract. Mapbox has no\n * Terms & Conditions dialog, so no dialog is shown; resolves with\n * `{accepted: true}` once permission handling completes. Idempotent —\n * still safe (and intended) to call eagerly.\n */\n requestNavigationPermission(): Promise<{ok: boolean; accepted: boolean; error?: string}>\n\n /**\n * Dev-only: push the simulated user position off-route so the Nav SDK\n * reroutes. Only meaningful in simulate mode. Both platforms;\n * `offsetMeters` tunes the jump on Android and is ignored on iOS.\n */\n simulateDeviation(offsetMeters?: number): Promise<{ok: boolean; error?: string}>\n\n /**\n * Dev toggle. When enabled, the native NavigationManager shifts every\n * reported location ~8m perpendicular to the route bearing, simulating\n * a pedestrian walking on the wrong sidewalk. Only meaningful in\n * simulate mode; lets us verify the SDK's along-path pivot trigger\n * fires even when the user never crosses the 7m pivot point radius.\n * Android-only today; iOS is a no-op stub.\n */\n setWrongSidewalkOffset(enabled: boolean): Promise<{ok: boolean; error?: string}>\n\n /**\n * Dev toggle. When enabled, the native NavigationManager takes over\n * from the Mapbox simulator and walks the user along a modified\n * polyline that omits crossing micro-steps — reproducing the\n * wrong-sidewalk-then-missed-the-turn scenario. Android-only today;\n * iOS is a no-op stub.\n */\n setSkipCrossings(enabled: boolean): Promise<{ok: boolean; error?: string}>\n\n // Heading / compass (both platforms)\n startHeading(): Promise<{ok: boolean; error?: string}>\n stopHeading(): Promise<{ok: boolean; error?: string}>\n\n // JS Runtime — per-miniapp JSContext lifecycle.\n /**\n * Spawn a per-miniapp JS context. Re-spawn is allowed: a live context\n * with the same packageName is killed first. Returns true if the\n * polyfill bundle + miniapp source evaluated without throwing.\n *\n * The polyfill bundle is `mobile/modules/jspolyfill/dist/startup.js`\n * (shipped inside the host binary). It installs window-style globals\n * (console, timers, fetch, localStorage, crypto) atop the JSC runtime.\n */\n jsSpawn(packageName: string, polyfillBundle: string, miniappJs: string): Promise<boolean>\n jsEvaluate(packageName: string, source: string): Promise<unknown>\n jsKill(packageName: string): Promise<void>\n /**\n * Push a `{kind: \"event\"|\"response\", …}` envelope into the named\n * context's globalThis.__deliver. Returns when the underlying\n * evaluateScript completes (the JS handler runs synchronously on the\n * context's queue).\n */\n jsDispatchToJs(packageName: string, envelope: Record<string, unknown>): Promise<void>\n jsSetManifest(packageName: string, permissions: string[]): Promise<void>\n /** Diagnostic — returns the packageNames of every live JSContext. */\n jsAlivePackages(): string[]\n /** Diagnostic — force a GC cycle on the named context. */\n jsDebugForceGC(packageName: string): Promise<boolean>\n /**\n * Read the bundled JSRuntime polyfill (startup.js) shipped inside the\n * host binary. Synchronous — host RN code calls this once on app boot\n * and caches the string, then passes it to every jsSpawn so\n * every JSContext starts with the same polyfill ABI.\n */\n jsLoadPolyfillBundle(): string\n}\n\n// This call loads the native module object from the JSI.\nexport default requireNativeModule<CrustModule>(\"Crust\")\n"]}
|
|
@@ -20,6 +20,10 @@ declare class CrustModule extends NativeModule<CrustModuleEvents> {
|
|
|
20
20
|
hasNotificationListenerPermission(): Promise<boolean>;
|
|
21
21
|
refreshNotificationListener(): Promise<boolean>;
|
|
22
22
|
openNotificationListenerSettings(): Promise<boolean>;
|
|
23
|
+
getPhoneLockState(): Promise<{
|
|
24
|
+
unlocked: boolean;
|
|
25
|
+
passcodeSet: boolean;
|
|
26
|
+
}>;
|
|
23
27
|
isBetaBuild(): Promise<boolean>;
|
|
24
28
|
setMapboxAccessToken(_token: string): Promise<{
|
|
25
29
|
ok: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CrustModule.web.d.ts","sourceRoot":"","sources":["../src/CrustModule.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,YAAY,EAAC,MAAM,MAAM,CAAA;AAEpD,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,eAAe,CAAA;AAEpD,cAAM,WAAY,SAAQ,YAAY,CAAC,iBAAiB,CAAC;IACvD,EAAE,SAAU;IACN,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAG3C,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI;;;;;;;;IAS1G,KAAK;IAGL,iBAAiB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI;IACtC,yBAAyB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAC1D,qBAAqB,CAAC,gBAAgB,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IACrF,gBAAgB;IAGhB,gCAAgC;IAGhC,iCAAiC;IAGjC,2BAA2B;IAG3B,gCAAgC;IAGhC,WAAW;IAGX,oBAAoB,CAAC,MAAM,EAAE,MAAM;;;;IAGnC,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM;IAG3D,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;IAGrC,MAAM,CAAC,IAAI,EAAE,MAAM;IAGnB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAG1D,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE;IAGlD,eAAe;IAGT,cAAc,CAAC,IAAI,EAAE,MAAM;IAGjC,oBAAoB;CAGrB;;AAED,wBAA4D"}
|
|
1
|
+
{"version":3,"file":"CrustModule.web.d.ts","sourceRoot":"","sources":["../src/CrustModule.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,YAAY,EAAC,MAAM,MAAM,CAAA;AAEpD,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,eAAe,CAAA;AAEpD,cAAM,WAAY,SAAQ,YAAY,CAAC,iBAAiB,CAAC;IACvD,EAAE,SAAU;IACN,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAG3C,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI;;;;;;;;IAS1G,KAAK;IAGL,iBAAiB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI;IACtC,yBAAyB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAC1D,qBAAqB,CAAC,gBAAgB,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IACrF,gBAAgB;IAGhB,gCAAgC;IAGhC,iCAAiC;IAGjC,2BAA2B;IAG3B,gCAAgC;IAGhC,iBAAiB;;;;IAGjB,WAAW;IAGX,oBAAoB,CAAC,MAAM,EAAE,MAAM;;;;IAGnC,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM;IAG3D,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;IAGrC,MAAM,CAAC,IAAI,EAAE,MAAM;IAGnB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAG1D,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE;IAGlD,eAAe;IAGT,cAAc,CAAC,IAAI,EAAE,MAAM;IAGjC,oBAAoB;CAGrB;;AAED,wBAA4D"}
|
package/build/CrustModule.web.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CrustModule.web.js","sourceRoot":"","sources":["../src/CrustModule.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,iBAAiB,EAAE,YAAY,EAAC,MAAM,MAAM,CAAA;AAIpD,MAAM,WAAY,SAAQ,YAA+B;IACvD,EAAE,GAAG,IAAI,CAAC,EAAE,CAAA;IACZ,KAAK,CAAC,aAAa,CAAC,KAAa;QAC/B,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,EAAC,KAAK,EAAC,CAAC,CAAA;IAChC,CAAC;IACD,KAAK,CAAC,iBAAiB,CAAC,MAAc,EAAE,GAAW,EAAE,OAA+B,EAAE,IAAoB;QACxG,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,EAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAC,CAAC,CAAA;QAC1D,OAAO;YACL,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,UAAU,EAAE,QAAQ,CAAC,UAAU;YAC/B,OAAO,EAAE,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;YACvD,IAAI,EAAE,MAAM,QAAQ,CAAC,IAAI,EAAE;SAC5B,CAAA;IACH,CAAC;IACD,KAAK;QACH,OAAO,iBAAiB,CAAA;IAC1B,CAAC;IACD,iBAAiB,CAAC,UAA0B,IAAG,CAAC;IAChD,KAAK,CAAC,yBAAyB,CAAC,MAAgB,IAAkB,CAAC;IACnE,KAAK,CAAC,qBAAqB,CAAC,gBAAyB,EAAE,UAAoB,IAAkB,CAAC;IAC9F,KAAK,CAAC,gBAAgB;QACpB,OAAO,EAAE,CAAA;IACX,CAAC;IACD,KAAK,CAAC,gCAAgC;QACpC,OAAO,EAAE,CAAA;IACX,CAAC;IACD,KAAK,CAAC,iCAAiC;QACrC,OAAO,KAAK,CAAA;IACd,CAAC;IACD,KAAK,CAAC,2BAA2B;QAC/B,OAAO,KAAK,CAAA;IACd,CAAC;IACD,KAAK,CAAC,gCAAgC;QACpC,OAAO,KAAK,CAAA;IACd,CAAC;IACD,KAAK,CAAC,WAAW;QACf,OAAO,KAAK,CAAA;IACd,CAAC;IACD,KAAK,CAAC,oBAAoB,CAAC,MAAc;QACvC,OAAO,EAAC,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,sBAAsB,EAAC,CAAA;IACnD,CAAC;IACD,KAAK,CAAC,OAAO,CAAC,IAAY,EAAE,SAAiB,EAAE,UAAkB;QAC/D,OAAO,KAAK,CAAA;IACd,CAAC;IACD,KAAK,CAAC,UAAU,CAAC,IAAY,EAAE,IAAY;QACzC,OAAO,IAAI,CAAA;IACb,CAAC;IACD,KAAK,CAAC,MAAM,CAAC,IAAY;QACvB,OAAM;IACR,CAAC;IACD,KAAK,CAAC,cAAc,CAAC,IAAY,EAAE,IAA6B;QAC9D,OAAM;IACR,CAAC;IACD,KAAK,CAAC,aAAa,CAAC,IAAY,EAAE,MAAgB;QAChD,OAAM;IACR,CAAC;IACD,eAAe;QACb,OAAO,EAAE,CAAA;IACX,CAAC;IACD,KAAK,CAAC,cAAc,CAAC,IAAY;QAC/B,OAAO,KAAK,CAAA;IACd,CAAC;IACD,oBAAoB;QAClB,OAAO,EAAE,CAAA;IACX,CAAC;CACF;AAED,eAAe,iBAAiB,CAAC,WAAW,EAAE,aAAa,CAAC,CAAA","sourcesContent":["import {registerWebModule, NativeModule} from \"expo\"\n\nimport type {CrustModuleEvents} from \"./Crust.types\"\n\nclass CrustModule extends NativeModule<CrustModuleEvents> {\n PI = Math.PI\n async setValueAsync(value: string): Promise<void> {\n this.emit(\"onChange\", {value})\n }\n async nativeHttpRequest(method: string, url: string, headers: Record<string, string>, body?: string | null) {\n const response = await fetch(url, {method, headers, body})\n return {\n status: response.status,\n statusText: response.statusText,\n headers: Object.fromEntries(response.headers.entries()),\n body: await response.text(),\n }\n }\n hello() {\n return \"Hello world! 👋\"\n }\n showAVRoutePicker(_tintColor?: string | null) {}\n async setDeferredSystemGestures(_edges: string[]): Promise<void> {}\n async setNotificationConfig(_listenerEnabled: boolean, _blocklist: string[]): Promise<void> {}\n async getInstalledApps() {\n return []\n }\n async getInstalledAppsForNotifications() {\n return []\n }\n async hasNotificationListenerPermission() {\n return false\n }\n async refreshNotificationListener() {\n return false\n }\n async openNotificationListenerSettings() {\n return false\n }\n async isBetaBuild() {\n return false\n }\n async setMapboxAccessToken(_token: string) {\n return {ok: false, error: \"Not supported on web\"}\n }\n async jsSpawn(_pkg: string, _polyfill: string, _miniappJs: string) {\n return false\n }\n async jsEvaluate(_pkg: string, _src: string) {\n return null\n }\n async jsKill(_pkg: string) {\n return\n }\n async jsDispatchToJs(_pkg: string, _env: Record<string, unknown>) {\n return\n }\n async jsSetManifest(_pkg: string, _perms: string[]) {\n return\n }\n jsAlivePackages() {\n return []\n }\n async jsDebugForceGC(_pkg: string) {\n return false\n }\n jsLoadPolyfillBundle() {\n return \"\"\n }\n}\n\nexport default registerWebModule(CrustModule, \"CrustModule\")\n"]}
|
|
1
|
+
{"version":3,"file":"CrustModule.web.js","sourceRoot":"","sources":["../src/CrustModule.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,iBAAiB,EAAE,YAAY,EAAC,MAAM,MAAM,CAAA;AAIpD,MAAM,WAAY,SAAQ,YAA+B;IACvD,EAAE,GAAG,IAAI,CAAC,EAAE,CAAA;IACZ,KAAK,CAAC,aAAa,CAAC,KAAa;QAC/B,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,EAAC,KAAK,EAAC,CAAC,CAAA;IAChC,CAAC;IACD,KAAK,CAAC,iBAAiB,CAAC,MAAc,EAAE,GAAW,EAAE,OAA+B,EAAE,IAAoB;QACxG,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,EAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAC,CAAC,CAAA;QAC1D,OAAO;YACL,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,UAAU,EAAE,QAAQ,CAAC,UAAU;YAC/B,OAAO,EAAE,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;YACvD,IAAI,EAAE,MAAM,QAAQ,CAAC,IAAI,EAAE;SAC5B,CAAA;IACH,CAAC;IACD,KAAK;QACH,OAAO,iBAAiB,CAAA;IAC1B,CAAC;IACD,iBAAiB,CAAC,UAA0B,IAAG,CAAC;IAChD,KAAK,CAAC,yBAAyB,CAAC,MAAgB,IAAkB,CAAC;IACnE,KAAK,CAAC,qBAAqB,CAAC,gBAAyB,EAAE,UAAoB,IAAkB,CAAC;IAC9F,KAAK,CAAC,gBAAgB;QACpB,OAAO,EAAE,CAAA;IACX,CAAC;IACD,KAAK,CAAC,gCAAgC;QACpC,OAAO,EAAE,CAAA;IACX,CAAC;IACD,KAAK,CAAC,iCAAiC;QACrC,OAAO,KAAK,CAAA;IACd,CAAC;IACD,KAAK,CAAC,2BAA2B;QAC/B,OAAO,KAAK,CAAA;IACd,CAAC;IACD,KAAK,CAAC,gCAAgC;QACpC,OAAO,KAAK,CAAA;IACd,CAAC;IACD,KAAK,CAAC,iBAAiB;QACrB,OAAO,EAAC,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAC,CAAA;IAC7C,CAAC;IACD,KAAK,CAAC,WAAW;QACf,OAAO,KAAK,CAAA;IACd,CAAC;IACD,KAAK,CAAC,oBAAoB,CAAC,MAAc;QACvC,OAAO,EAAC,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,sBAAsB,EAAC,CAAA;IACnD,CAAC;IACD,KAAK,CAAC,OAAO,CAAC,IAAY,EAAE,SAAiB,EAAE,UAAkB;QAC/D,OAAO,KAAK,CAAA;IACd,CAAC;IACD,KAAK,CAAC,UAAU,CAAC,IAAY,EAAE,IAAY;QACzC,OAAO,IAAI,CAAA;IACb,CAAC;IACD,KAAK,CAAC,MAAM,CAAC,IAAY;QACvB,OAAM;IACR,CAAC;IACD,KAAK,CAAC,cAAc,CAAC,IAAY,EAAE,IAA6B;QAC9D,OAAM;IACR,CAAC;IACD,KAAK,CAAC,aAAa,CAAC,IAAY,EAAE,MAAgB;QAChD,OAAM;IACR,CAAC;IACD,eAAe;QACb,OAAO,EAAE,CAAA;IACX,CAAC;IACD,KAAK,CAAC,cAAc,CAAC,IAAY;QAC/B,OAAO,KAAK,CAAA;IACd,CAAC;IACD,oBAAoB;QAClB,OAAO,EAAE,CAAA;IACX,CAAC;CACF;AAED,eAAe,iBAAiB,CAAC,WAAW,EAAE,aAAa,CAAC,CAAA","sourcesContent":["import {registerWebModule, NativeModule} from \"expo\"\n\nimport type {CrustModuleEvents} from \"./Crust.types\"\n\nclass CrustModule extends NativeModule<CrustModuleEvents> {\n PI = Math.PI\n async setValueAsync(value: string): Promise<void> {\n this.emit(\"onChange\", {value})\n }\n async nativeHttpRequest(method: string, url: string, headers: Record<string, string>, body?: string | null) {\n const response = await fetch(url, {method, headers, body})\n return {\n status: response.status,\n statusText: response.statusText,\n headers: Object.fromEntries(response.headers.entries()),\n body: await response.text(),\n }\n }\n hello() {\n return \"Hello world! 👋\"\n }\n showAVRoutePicker(_tintColor?: string | null) {}\n async setDeferredSystemGestures(_edges: string[]): Promise<void> {}\n async setNotificationConfig(_listenerEnabled: boolean, _blocklist: string[]): Promise<void> {}\n async getInstalledApps() {\n return []\n }\n async getInstalledAppsForNotifications() {\n return []\n }\n async hasNotificationListenerPermission() {\n return false\n }\n async refreshNotificationListener() {\n return false\n }\n async openNotificationListenerSettings() {\n return false\n }\n async getPhoneLockState() {\n return {unlocked: true, passcodeSet: false}\n }\n async isBetaBuild() {\n return false\n }\n async setMapboxAccessToken(_token: string) {\n return {ok: false, error: \"Not supported on web\"}\n }\n async jsSpawn(_pkg: string, _polyfill: string, _miniappJs: string) {\n return false\n }\n async jsEvaluate(_pkg: string, _src: string) {\n return null\n }\n async jsKill(_pkg: string) {\n return\n }\n async jsDispatchToJs(_pkg: string, _env: Record<string, unknown>) {\n return\n }\n async jsSetManifest(_pkg: string, _perms: string[]) {\n return\n }\n jsAlivePackages() {\n return []\n }\n async jsDebugForceGC(_pkg: string) {\n return false\n }\n jsLoadPolyfillBundle() {\n return \"\"\n }\n}\n\nexport default registerWebModule(CrustModule, \"CrustModule\")\n"]}
|
package/ios/Crust.podspec
CHANGED
|
@@ -51,7 +51,7 @@ Pod::Spec.new do |s|
|
|
|
51
51
|
|
|
52
52
|
s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}"
|
|
53
53
|
|
|
54
|
-
# Ship the
|
|
54
|
+
# Ship the JSRuntime polyfill bundle inside the pod's resource bundle so
|
|
55
55
|
# JSCRuntime can read it at runtime via Bundle.main. The source of
|
|
56
56
|
# truth lives at `@glassly/jspolyfill/assets/startup.js`, but
|
|
57
57
|
# CocoaPods silently drops `..` paths from `resource_bundles` — so the
|
package/ios/CrustModule.swift
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import AVKit
|
|
2
2
|
import CoreLocation
|
|
3
3
|
import ExpoModulesCore
|
|
4
|
+
import LocalAuthentication
|
|
4
5
|
import Photos
|
|
5
6
|
|
|
6
7
|
/// User-visible album in Apple Photos for glasses sync (matches dedicated-folder behavior on Android).
|
|
@@ -62,6 +63,19 @@ private struct PhotoLibrarySaveResult {
|
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
public class CrustModule: Module {
|
|
66
|
+
private var protectedDataObservers: [NSObjectProtocol] = []
|
|
67
|
+
|
|
68
|
+
/// Phone lock state as seen by the app. Protected data (complete-protection
|
|
69
|
+
/// files) is only readable/writable while the phone is unlocked, which makes
|
|
70
|
+
/// it the standard proxy for "the user can see the phone right now".
|
|
71
|
+
/// Without a passcode iOS never protects data, so `unlocked` is always true
|
|
72
|
+
/// there — callers use `passcodeSet` to know whether the signal means anything.
|
|
73
|
+
private func phoneLockState(unlocked: Bool? = nil) -> [String: Any] {
|
|
74
|
+
let unlocked = unlocked ?? UIApplication.shared.isProtectedDataAvailable
|
|
75
|
+
let passcodeSet = LAContext().canEvaluatePolicy(.deviceOwnerAuthentication, error: nil)
|
|
76
|
+
return ["unlocked": unlocked, "passcodeSet": passcodeSet]
|
|
77
|
+
}
|
|
78
|
+
|
|
65
79
|
public func definition() -> ModuleDefinition {
|
|
66
80
|
Name("Crust")
|
|
67
81
|
|
|
@@ -82,26 +96,50 @@ public class CrustModule: Module {
|
|
|
82
96
|
"onNavLocation",
|
|
83
97
|
"onNavRoute",
|
|
84
98
|
"onHeading",
|
|
85
|
-
//
|
|
99
|
+
// Fires on lock / unlock (protected data availability). Payload
|
|
100
|
+
// matches getPhoneLockState().
|
|
101
|
+
"phone_lock_state",
|
|
102
|
+
// JSRuntime — fires whenever a per-miniapp JSContext calls
|
|
86
103
|
// __dispatch(iface, method, args). RN-side JSRouter
|
|
87
104
|
// subscribes to route by packageName.
|
|
88
|
-
"
|
|
105
|
+
"js_message"
|
|
89
106
|
)
|
|
90
107
|
|
|
91
108
|
OnCreate {
|
|
109
|
+
// "will become unavailable" fires before isProtectedDataAvailable
|
|
110
|
+
// flips, so the state is taken from which notification fired.
|
|
111
|
+
let center = NotificationCenter.default
|
|
112
|
+
for (name, unlocked) in [
|
|
113
|
+
(UIApplication.protectedDataDidBecomeAvailableNotification, true),
|
|
114
|
+
(UIApplication.protectedDataWillBecomeUnavailableNotification, false),
|
|
115
|
+
] {
|
|
116
|
+
protectedDataObservers.append(
|
|
117
|
+
center.addObserver(forName: name, object: nil, queue: .main) { [weak self] _ in
|
|
118
|
+
guard let self else { return }
|
|
119
|
+
self.sendEvent("phone_lock_state", self.phoneLockState(unlocked: unlocked))
|
|
120
|
+
}
|
|
121
|
+
)
|
|
122
|
+
}
|
|
92
123
|
// Wire the JSCRuntime's outbound event sink to the Expo
|
|
93
124
|
// event emitter. The JSCDispatcher fires `forwardToRn` for
|
|
94
125
|
// anything that isn't a built-in route (localStorage, fetch,
|
|
95
126
|
// crypto.getRandomBytes, __runtime.ready), and the runtime's
|
|
96
127
|
// exception/log/error handlers route through the same sink.
|
|
97
128
|
JSCRuntime.shared.onOutbound = { [weak self] message in
|
|
98
|
-
self?.sendEvent("
|
|
129
|
+
self?.sendEvent("js_message", message.payload)
|
|
99
130
|
}
|
|
100
131
|
// Install the polyfill bridge once, lazily on first module
|
|
101
132
|
// creation. JSCPolyfillBridge.install is idempotent.
|
|
102
133
|
JSCPolyfillBridge.install(into: JSCRuntime.shared.dispatcherTable)
|
|
103
134
|
}
|
|
104
135
|
|
|
136
|
+
OnDestroy {
|
|
137
|
+
for observer in protectedDataObservers {
|
|
138
|
+
NotificationCenter.default.removeObserver(observer)
|
|
139
|
+
}
|
|
140
|
+
protectedDataObservers.removeAll()
|
|
141
|
+
}
|
|
142
|
+
|
|
105
143
|
Function("hello") {
|
|
106
144
|
"Hello world! 👋"
|
|
107
145
|
}
|
|
@@ -266,7 +304,11 @@ public class CrustModule: Module {
|
|
|
266
304
|
return false
|
|
267
305
|
}
|
|
268
306
|
|
|
269
|
-
|
|
307
|
+
AsyncFunction("getPhoneLockState") { () -> [String: Any] in
|
|
308
|
+
await MainActor.run { self.phoneLockState() }
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// MARK: - JS Runtime
|
|
270
312
|
|
|
271
313
|
/// Spawn a per-miniapp JS context. Re-spawn is allowed: a live
|
|
272
314
|
/// context with the same packageName is killed first.
|
|
@@ -321,7 +363,7 @@ public class CrustModule: Module {
|
|
|
321
363
|
return JSCRuntime.shared.debugForceGC(packageName: packageName)
|
|
322
364
|
}
|
|
323
365
|
|
|
324
|
-
/// Read the bundled
|
|
366
|
+
/// Read the bundled JSRuntime polyfill (startup.js) from the iOS
|
|
325
367
|
/// pod's resource bundle. The host calls this once on app boot,
|
|
326
368
|
/// caches the string, and passes it to every jsSpawn so
|
|
327
369
|
/// every JSContext starts with the same polyfill ABI.
|
package/ios/Resources/startup.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @generated
|
|
1
|
+
// @generated JSRuntime polyfill bundle — see mobile/modules/jspolyfill
|
|
2
2
|
"use strict";
|
|
3
3
|
(() => {
|
|
4
4
|
// src/startup.ts
|
|
@@ -424,7 +424,7 @@
|
|
|
424
424
|
if (!cryptoNs.subtle) {
|
|
425
425
|
const notImplemented = () => {
|
|
426
426
|
throw new Error(
|
|
427
|
-
"crypto.subtle is not yet implemented in
|
|
427
|
+
"crypto.subtle is not yet implemented in JSRuntime \u2014 see notes/glasslyjs-two-layer-miniapp-architecture.md (Polyfill strategy section). Use a pure-JS hash/encrypt library for now."
|
|
428
428
|
);
|
|
429
429
|
};
|
|
430
430
|
cryptoNs.subtle = new Proxy(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
//
|
|
2
2
|
// JSCDispatcher.swift
|
|
3
|
-
//
|
|
3
|
+
// JSRuntime — dispatch table + permission gate for the per-miniapp
|
|
4
4
|
// __dispatch(iface, method, args) bridge.
|
|
5
5
|
//
|
|
6
6
|
// Every JSContext spawned by JSCRuntime gets a single Swift block bound
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// which:
|
|
9
9
|
// 1. Looks up the (iface, method) handler from `routes` — local
|
|
10
10
|
// handlers serve dispatches synchronously, RN-forwarded ones return
|
|
11
|
-
// .forwardToRn and the runtime emits
|
|
11
|
+
// .forwardToRn and the runtime emits a `js_message` event.
|
|
12
12
|
// 2. Consults the InstalledManifest to gate OS-sensitive calls.
|
|
13
13
|
// PERMISSION_NOT_DECLARED errors propagate back to JS as thrown
|
|
14
14
|
// Error objects. There is no JIT permission modal — declared-in-
|
|
@@ -33,7 +33,7 @@ public enum JSCDispatchOutcome {
|
|
|
33
33
|
case async
|
|
34
34
|
/// Handler refused; JS sees a thrown Error with `code` + `message`.
|
|
35
35
|
case error(code: String, message: String?)
|
|
36
|
-
/// Forward to RN as `
|
|
36
|
+
/// Forward to RN as `js_message`; downstream RN code handles it
|
|
37
37
|
/// and (for request/response calls) eventually calls dispatchToJs back.
|
|
38
38
|
case forwardToRn([String: Any])
|
|
39
39
|
}
|
|
@@ -153,7 +153,7 @@ public final class JSCDispatcher {
|
|
|
153
153
|
// miniapp gets its own UserDefaults suite scoped on packageName.
|
|
154
154
|
register(iface: "localStorage", method: "getItem") { packageName, args, _ in
|
|
155
155
|
guard let key = args.first as? String,
|
|
156
|
-
let defaults = UserDefaults(suiteName: "
|
|
156
|
+
let defaults = UserDefaults(suiteName: "JSRuntime-\(packageName)") else {
|
|
157
157
|
return .sync(NSNull())
|
|
158
158
|
}
|
|
159
159
|
let v = defaults.string(forKey: key)
|
|
@@ -163,7 +163,7 @@ public final class JSCDispatcher {
|
|
|
163
163
|
guard args.count >= 2,
|
|
164
164
|
let key = args[0] as? String,
|
|
165
165
|
let value = args[1] as? String,
|
|
166
|
-
let defaults = UserDefaults(suiteName: "
|
|
166
|
+
let defaults = UserDefaults(suiteName: "JSRuntime-\(packageName)") else {
|
|
167
167
|
return .error(code: "INVALID_ARGS", message: "localStorage.setItem expects (key, value) strings")
|
|
168
168
|
}
|
|
169
169
|
defaults.set(value, forKey: key)
|
|
@@ -171,14 +171,14 @@ public final class JSCDispatcher {
|
|
|
171
171
|
}
|
|
172
172
|
register(iface: "localStorage", method: "removeItem") { packageName, args, _ in
|
|
173
173
|
guard let key = args.first as? String,
|
|
174
|
-
let defaults = UserDefaults(suiteName: "
|
|
174
|
+
let defaults = UserDefaults(suiteName: "JSRuntime-\(packageName)") else {
|
|
175
175
|
return .error(code: "INVALID_ARGS", message: "localStorage.removeItem expects (key)")
|
|
176
176
|
}
|
|
177
177
|
defaults.removeObject(forKey: key)
|
|
178
178
|
return .sync(NSNull())
|
|
179
179
|
}
|
|
180
180
|
register(iface: "localStorage", method: "clear") { packageName, _, _ in
|
|
181
|
-
guard let defaults = UserDefaults(suiteName: "
|
|
181
|
+
guard let defaults = UserDefaults(suiteName: "JSRuntime-\(packageName)") else {
|
|
182
182
|
return .sync(NSNull())
|
|
183
183
|
}
|
|
184
184
|
for (k, _) in defaults.dictionaryRepresentation() {
|
|
@@ -187,14 +187,14 @@ public final class JSCDispatcher {
|
|
|
187
187
|
return .sync(NSNull())
|
|
188
188
|
}
|
|
189
189
|
register(iface: "localStorage", method: "length") { packageName, _, _ in
|
|
190
|
-
guard let defaults = UserDefaults(suiteName: "
|
|
190
|
+
guard let defaults = UserDefaults(suiteName: "JSRuntime-\(packageName)") else {
|
|
191
191
|
return .sync(0)
|
|
192
192
|
}
|
|
193
193
|
return .sync(defaults.dictionaryRepresentation().count)
|
|
194
194
|
}
|
|
195
195
|
register(iface: "localStorage", method: "key") { packageName, args, _ in
|
|
196
196
|
guard let idx = args.first as? Int,
|
|
197
|
-
let defaults = UserDefaults(suiteName: "
|
|
197
|
+
let defaults = UserDefaults(suiteName: "JSRuntime-\(packageName)") else {
|
|
198
198
|
return .sync(NSNull())
|
|
199
199
|
}
|
|
200
200
|
let keys = Array(defaults.dictionaryRepresentation().keys).sorted()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
//
|
|
2
2
|
// JSCPolyfillBridge.swift
|
|
3
|
-
//
|
|
3
|
+
// JSRuntime — native handlers for the browser-flavoured polyfills that
|
|
4
4
|
// can't be implemented in pure JS (fetch, WebSocket).
|
|
5
5
|
//
|
|
6
6
|
// Built-in JS shims (console, timers, localStorage, crypto.randomUUID)
|
|
@@ -81,7 +81,7 @@ public enum JSCPolyfillBridge {
|
|
|
81
81
|
JSCPolyfillBridge.dropSocket(sid: wrapper.sid)
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
|
-
private static let log = OSLog(subsystem: "com.tetramo.glassly", category: "
|
|
84
|
+
private static let log = OSLog(subsystem: "com.tetramo.glassly", category: "JSRuntime.fetch")
|
|
85
85
|
|
|
86
86
|
public static func install(into dispatcher: JSCDispatcher) {
|
|
87
87
|
installFetch(dispatcher)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
//
|
|
2
2
|
// JSCRuntime.swift
|
|
3
|
-
//
|
|
3
|
+
// JSRuntime — per-miniapp JavaScriptCore runtime host (iOS).
|
|
4
4
|
//
|
|
5
5
|
// Owns N JSContexts keyed by `packageName`. Each context gets:
|
|
6
6
|
// - its own JSVirtualMachine (heap isolation)
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// jsEvaluate(packageName, src) → Any?
|
|
17
17
|
// jsKill(packageName) → Void
|
|
18
18
|
// jsDispatchToJs(packageName, channel, payloadJson) → Void
|
|
19
|
-
// Event "
|
|
19
|
+
// Event "js_message" — fired when JS calls __dispatch back.
|
|
20
20
|
//
|
|
21
21
|
|
|
22
22
|
import Foundation
|
|
@@ -31,10 +31,10 @@ public final class JSCRuntime: NSObject {
|
|
|
31
31
|
public static let shared = JSCRuntime()
|
|
32
32
|
|
|
33
33
|
/// Tag for os_log lines; visible via Console.app under
|
|
34
|
-
/// `subsystem == "com.tetramo.glassly" && category == "
|
|
35
|
-
static let log = OSLog(subsystem: "com.tetramo.glassly", category: "
|
|
34
|
+
/// `subsystem == "com.tetramo.glassly" && category == "JSRuntime"`.
|
|
35
|
+
static let log = OSLog(subsystem: "com.tetramo.glassly", category: "JSRuntime")
|
|
36
36
|
|
|
37
|
-
/// Message emitted back to RN via `Crust.addListener("
|
|
37
|
+
/// Message emitted back to RN via `Crust.addListener("js_message", …)`.
|
|
38
38
|
/// `payload` is JSON-friendly so the bridge can ship it verbatim.
|
|
39
39
|
public struct OutboundMessage {
|
|
40
40
|
public let packageName: String
|
|
@@ -135,7 +135,7 @@ public final class JSCRuntime: NSObject {
|
|
|
135
135
|
let str = try? String(contentsOfFile: path, encoding: .utf8) {
|
|
136
136
|
return str
|
|
137
137
|
}
|
|
138
|
-
os_log("
|
|
138
|
+
os_log("JSRuntime: polyfill bundle not found in resources", log: Self.log, type: .error)
|
|
139
139
|
return ""
|
|
140
140
|
}
|
|
141
141
|
|
|
@@ -153,7 +153,7 @@ public final class JSCRuntime: NSObject {
|
|
|
153
153
|
/// - packageName: stable id (e.g. "com.alex.notes"). Must be unique
|
|
154
154
|
/// within the host process. Re-spawning a live id kills the old
|
|
155
155
|
/// context first.
|
|
156
|
-
/// - polyfillBundle: the contents of `
|
|
156
|
+
/// - polyfillBundle: the contents of `jspolyfill/dist/startup.js`.
|
|
157
157
|
/// Evaluated first, before `miniappJs`.
|
|
158
158
|
/// - miniappJs: the miniapp's `background/index.js` source. Evaluated
|
|
159
159
|
/// immediately after the polyfill installs.
|
|
@@ -167,9 +167,9 @@ public final class JSCRuntime: NSObject {
|
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
let vm = JSVirtualMachine()!
|
|
170
|
-
let queue = DispatchQueue(label: "com.tetramo.
|
|
170
|
+
let queue = DispatchQueue(label: "com.tetramo.glassly.jsruntime.\(packageName)", qos: .userInitiated)
|
|
171
171
|
let ctx = JSContext(virtualMachine: vm)!
|
|
172
|
-
ctx.name = "
|
|
172
|
+
ctx.name = "JSRuntime: \(packageName)"
|
|
173
173
|
#if DEBUG
|
|
174
174
|
if #available(iOS 16.4, *) {
|
|
175
175
|
ctx.isInspectable = true
|
|
@@ -200,7 +200,7 @@ public final class JSCRuntime: NSObject {
|
|
|
200
200
|
if !success {
|
|
201
201
|
kill(packageName: packageName)
|
|
202
202
|
} else {
|
|
203
|
-
os_log("
|
|
203
|
+
os_log("JSRuntime: spawned %{public}@", log: Self.log, type: .info, packageName)
|
|
204
204
|
}
|
|
205
205
|
return success
|
|
206
206
|
}
|
|
@@ -226,7 +226,7 @@ public final class JSCRuntime: NSObject {
|
|
|
226
226
|
timer.setEventHandler { [weak self, weak record] in
|
|
227
227
|
guard let self, let record else { return }
|
|
228
228
|
let phase = cold ? "cold-start" : "steady-state"
|
|
229
|
-
os_log("
|
|
229
|
+
os_log("JSRuntime NACK: %{public}@ %{public}@ ready signal not received in %.0fs",
|
|
230
230
|
log: Self.log, type: .error,
|
|
231
231
|
record.packageName, phase, timeoutSeconds)
|
|
232
232
|
// Surface to RN as a recoverable error frame so the
|
|
@@ -282,7 +282,7 @@ public final class JSCRuntime: NSObject {
|
|
|
282
282
|
record.context.exception = nil
|
|
283
283
|
let raw = record.context.evaluateScript(source)
|
|
284
284
|
if let exception = record.context.exception {
|
|
285
|
-
os_log("
|
|
285
|
+
os_log("JSRuntime [%{public}@] evaluate threw: %{public}@",
|
|
286
286
|
log: Self.log, type: .error,
|
|
287
287
|
record.packageName, exception.toString() ?? "unknown")
|
|
288
288
|
record.context.exception = nil
|
|
@@ -302,7 +302,7 @@ public final class JSCRuntime: NSObject {
|
|
|
302
302
|
public func dispatchToJs(packageName: String, envelope: [String: Any]) {
|
|
303
303
|
guard let record = lock.withLock({ contexts[packageName] }) else { return }
|
|
304
304
|
guard let json = Self.jsonString(from: envelope) else {
|
|
305
|
-
os_log("
|
|
305
|
+
os_log("JSRuntime: bad envelope, drop", log: Self.log, type: .error)
|
|
306
306
|
return
|
|
307
307
|
}
|
|
308
308
|
// Steady-state NACK: re-arm the timer so a wedged JSContext
|
|
@@ -338,7 +338,7 @@ public final class JSCRuntime: NSObject {
|
|
|
338
338
|
/// timer runs on a dedicated dispatch queue (NOT the per-context
|
|
339
339
|
/// one) so it can observe the queue being wedged.
|
|
340
340
|
private static let watchdogScheduler = DispatchQueue(
|
|
341
|
-
label: "com.tetramo.
|
|
341
|
+
label: "com.tetramo.glassly.jsruntime.watchdog",
|
|
342
342
|
qos: .utility,
|
|
343
343
|
)
|
|
344
344
|
|
|
@@ -349,7 +349,7 @@ public final class JSCRuntime: NSObject {
|
|
|
349
349
|
timer.schedule(deadline: .now() + warn, repeating: .never)
|
|
350
350
|
timer.setEventHandler { [weak self, weak record] in
|
|
351
351
|
guard let self, let record else { return }
|
|
352
|
-
os_log("
|
|
352
|
+
os_log("JSRuntime watchdog: %{public}@ %{public}@ blocked >%.0fs",
|
|
353
353
|
log: Self.log, type: .info,
|
|
354
354
|
record.packageName, label, warn)
|
|
355
355
|
// Schedule the kill timer immediately.
|
|
@@ -357,7 +357,7 @@ public final class JSCRuntime: NSObject {
|
|
|
357
357
|
killTimer.schedule(deadline: .now() + (kill - warn))
|
|
358
358
|
killTimer.setEventHandler { [weak self, weak record] in
|
|
359
359
|
guard let self, let record else { return }
|
|
360
|
-
os_log("
|
|
360
|
+
os_log("JSRuntime watchdog: %{public}@ blocked >%.0fs, killing",
|
|
361
361
|
log: Self.log, type: .error,
|
|
362
362
|
record.packageName, kill)
|
|
363
363
|
self.lock.withLock { self._onOutbound }?(
|
|
@@ -428,7 +428,7 @@ public final class JSCRuntime: NSObject {
|
|
|
428
428
|
// Heap Helper Thread.
|
|
429
429
|
JSGarbageCollect(record.context.jsGlobalContextRef)
|
|
430
430
|
}
|
|
431
|
-
os_log("
|
|
431
|
+
os_log("JSRuntime: killed %{public}@", log: Self.log, type: .info, packageName)
|
|
432
432
|
}
|
|
433
433
|
|
|
434
434
|
// MARK: - Internals
|
|
@@ -592,7 +592,7 @@ public final class JSCRuntime: NSObject {
|
|
|
592
592
|
],
|
|
593
593
|
)
|
|
594
594
|
)
|
|
595
|
-
os_log("
|
|
595
|
+
os_log("JSRuntime exception in %{public}@: %{public}@",
|
|
596
596
|
log: Self.log, type: .error,
|
|
597
597
|
record.packageName, message)
|
|
598
598
|
}
|
|
@@ -608,7 +608,7 @@ public final class JSCRuntime: NSObject {
|
|
|
608
608
|
_ = record.context.evaluateScript(source)
|
|
609
609
|
if let exception = record.context.exception {
|
|
610
610
|
let msg = exception.toString() ?? "unknown"
|
|
611
|
-
os_log("
|
|
611
|
+
os_log("JSRuntime [%{public}@] %{public}@ eval threw: %{public}@",
|
|
612
612
|
log: Self.log, type: .error,
|
|
613
613
|
record.packageName, label, msg)
|
|
614
614
|
record.context.exception = nil
|
|
@@ -112,15 +112,22 @@ final class NavigationManager: NSObject {
|
|
|
112
112
|
applyAccessToken()
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
///
|
|
116
|
-
/// the baked Info.plist token (
|
|
117
|
-
private func
|
|
118
|
-
if !accessTokenOverride.isEmpty {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
!baked.isEmpty {
|
|
122
|
-
MapboxOptions.accessToken = baked
|
|
115
|
+
/// The token the next provider build must use: the override when set, else
|
|
116
|
+
/// the baked Info.plist token, else "" (nothing to authenticate with).
|
|
117
|
+
private func resolvedAccessToken() -> String {
|
|
118
|
+
if !accessTokenOverride.isEmpty { return accessTokenOverride }
|
|
119
|
+
if let baked = Bundle.main.object(forInfoDictionaryKey: "MBXAccessToken") as? String, !baked.isEmpty {
|
|
120
|
+
return baked
|
|
123
121
|
}
|
|
122
|
+
return ""
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/// Point MapboxOptions.accessToken at the resolved token. Setting the global
|
|
126
|
+
/// alone is NOT enough for routing — see makeProvider — but map/telemetry
|
|
127
|
+
/// components read it, so keep it in step.
|
|
128
|
+
private func applyAccessToken() {
|
|
129
|
+
let token = resolvedAccessToken()
|
|
130
|
+
if !token.isEmpty { MapboxOptions.accessToken = token }
|
|
124
131
|
}
|
|
125
132
|
|
|
126
133
|
// MARK: - Permissions
|
|
@@ -230,7 +237,20 @@ final class NavigationManager: NSObject {
|
|
|
230
237
|
let routingConfig = RoutingConfig(
|
|
231
238
|
rerouteConfig: RerouteConfig(detectsReroute: true)
|
|
232
239
|
)
|
|
240
|
+
// The token MUST ride on CoreConfig.credentials. The SDK's default
|
|
241
|
+
// credentials read MBXAccessToken from Info.plist (not the runtime
|
|
242
|
+
// MapboxOptions.accessToken), and MapboxNavigationProvider.apply(coreConfig:)
|
|
243
|
+
// then writes that default BACK into MapboxOptions.accessToken. Release
|
|
244
|
+
// builds bake no token by design (the cloud serves it at runtime), so with
|
|
245
|
+
// the default credentials every route request went out unauthenticated
|
|
246
|
+
// and surfaced as "route request failed: … the server returned an empty
|
|
247
|
+
// response". Local builds masked it because the dev .env bakes a token.
|
|
248
|
+
let token = resolvedAccessToken()
|
|
249
|
+
if token.isEmpty {
|
|
250
|
+
print("[NavMgr] no Mapbox token (no runtime override, MBXAccessToken empty) — routing will not authenticate")
|
|
251
|
+
}
|
|
233
252
|
let coreConfig = CoreConfig(
|
|
253
|
+
credentials: token.isEmpty ? .init() : NavigationCoreApiConfiguration(accessToken: token),
|
|
234
254
|
routingConfig: routingConfig,
|
|
235
255
|
locationSource: simulate ? .simulation(initialLocation: nil) : .live
|
|
236
256
|
)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glassly/crust",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.8-dev.445",
|
|
4
4
|
"description": "Glassly Native Module",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"license": "MIT",
|
|
36
36
|
"homepage": "https://github.com/fossephate/crust#readme",
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@glassly/jspolyfill": "0.
|
|
38
|
+
"@glassly/jspolyfill": "0.2.8-dev.445"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@expo/config-plugins": ">=8.0.0",
|
package/src/Crust.types.ts
CHANGED
|
@@ -14,6 +14,18 @@ export type CrustModuleEvents = {
|
|
|
14
14
|
onNavRoute: (params: NavRoutePayload) => void
|
|
15
15
|
onNavOffRoute: (params: NavOffRoutePayload) => void
|
|
16
16
|
onHeading: (params: HeadingPayload) => void
|
|
17
|
+
phone_lock_state: (params: PhoneLockState) => void
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Whether the user can see the phone right now. iOS derives `unlocked` from
|
|
22
|
+
* protected-data availability, Android from screen on + keyguard hidden.
|
|
23
|
+
* Without a passcode iOS never protects data, so `unlocked` is always true
|
|
24
|
+
* there and carries no information; check `passcodeSet` first.
|
|
25
|
+
*/
|
|
26
|
+
export type PhoneLockState = {
|
|
27
|
+
unlocked: boolean
|
|
28
|
+
passcodeSet: boolean
|
|
17
29
|
}
|
|
18
30
|
|
|
19
31
|
export type NavOffRoutePayload = {
|
package/src/CrustModule.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {NativeModule, requireNativeModule} from "expo"
|
|
2
2
|
|
|
3
|
-
import type {CrustModuleEvents, InstalledApp} from "./Crust.types"
|
|
3
|
+
import type {CrustModuleEvents, InstalledApp, PhoneLockState} from "./Crust.types"
|
|
4
4
|
|
|
5
5
|
declare class CrustModule extends NativeModule<CrustModuleEvents> {
|
|
6
6
|
PI: number
|
|
@@ -32,6 +32,8 @@ declare class CrustModule extends NativeModule<CrustModuleEvents> {
|
|
|
32
32
|
hasNotificationListenerPermission(): Promise<boolean>
|
|
33
33
|
refreshNotificationListener(): Promise<boolean>
|
|
34
34
|
openNotificationListenerSettings(): Promise<boolean>
|
|
35
|
+
/** Current lock state; changes arrive as `phone_lock_state` events. */
|
|
36
|
+
getPhoneLockState(): Promise<PhoneLockState>
|
|
35
37
|
isBetaBuild(): Promise<boolean>
|
|
36
38
|
// location services commands
|
|
37
39
|
showLocationServicesDialog(): Promise<boolean>
|
|
@@ -156,13 +158,13 @@ declare class CrustModule extends NativeModule<CrustModuleEvents> {
|
|
|
156
158
|
startHeading(): Promise<{ok: boolean; error?: string}>
|
|
157
159
|
stopHeading(): Promise<{ok: boolean; error?: string}>
|
|
158
160
|
|
|
159
|
-
//
|
|
161
|
+
// JS Runtime — per-miniapp JSContext lifecycle.
|
|
160
162
|
/**
|
|
161
163
|
* Spawn a per-miniapp JS context. Re-spawn is allowed: a live context
|
|
162
164
|
* with the same packageName is killed first. Returns true if the
|
|
163
165
|
* polyfill bundle + miniapp source evaluated without throwing.
|
|
164
166
|
*
|
|
165
|
-
* The polyfill bundle is `mobile/modules/
|
|
167
|
+
* The polyfill bundle is `mobile/modules/jspolyfill/dist/startup.js`
|
|
166
168
|
* (shipped inside the host binary). It installs window-style globals
|
|
167
169
|
* (console, timers, fetch, localStorage, crypto) atop the JSC runtime.
|
|
168
170
|
*/
|
|
@@ -182,7 +184,7 @@ declare class CrustModule extends NativeModule<CrustModuleEvents> {
|
|
|
182
184
|
/** Diagnostic — force a GC cycle on the named context. */
|
|
183
185
|
jsDebugForceGC(packageName: string): Promise<boolean>
|
|
184
186
|
/**
|
|
185
|
-
* Read the bundled
|
|
187
|
+
* Read the bundled JSRuntime polyfill (startup.js) shipped inside the
|
|
186
188
|
* host binary. Synchronous — host RN code calls this once on app boot
|
|
187
189
|
* and caches the string, then passes it to every jsSpawn so
|
|
188
190
|
* every JSContext starts with the same polyfill ABI.
|
package/src/CrustModule.web.ts
CHANGED