@mentra/crust 3.2.1-dev.314 → 3.3.0-beta.1000
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 +27 -0
- package/android/build.gradle +27 -9
- package/android/src/main/AndroidManifest.xml +7 -0
- package/android/src/main/java/com/mentra/crust/CrustModule.kt +76 -22
- package/android/src/main/java/com/mentra/crust/http/NativeHttpFailure.kt +31 -0
- package/android/src/main/java/com/mentra/crust/http/NativeHttpRequests.kt +86 -0
- package/android/src/main/java/com/mentra/crust/jsc/JSCPolyfillBridge.kt +70 -60
- package/android/src/main/java/com/mentra/crust/jsc/JSCRuntime.kt +65 -8
- package/android/src/main/java/com/mentra/crust/jsc/JSDeliveryDiagnostics.kt +44 -0
- package/android/src/main/java/com/mentra/crust/navigation/NavigationManager.kt +74 -10
- package/android/src/main/java/com/mentra/crust/navigation/NavigationSessionGuard.kt +74 -0
- package/android/src/main/java/com/mentra/crust/preview/PixelCopyPreview.kt +76 -0
- package/android/src/main/java/com/mentra/crust/services/RuntimeRecoveryService.kt +12 -0
- package/android/src/test/java/com/mentra/crust/http/NativeHttpFailureTest.kt +44 -0
- package/android/src/test/java/com/mentra/crust/http/NativeHttpRequestsTest.kt +133 -0
- package/android/src/test/java/com/mentra/crust/jsc/FetchBridgeTest.kt +199 -0
- package/android/src/test/java/com/mentra/crust/jsc/HttpRequestTest.kt +32 -0
- package/android/src/test/java/com/mentra/crust/jsc/JSDeliveryDiagnosticsTest.kt +54 -0
- package/android/src/test/java/com/mentra/crust/jsc/QuickJsLifecycleTest.kt +87 -0
- package/android/src/test/java/com/mentra/crust/navigation/NavigationSessionGuardTest.kt +68 -0
- package/build/Crust.types.d.ts +25 -11
- package/build/Crust.types.d.ts.map +1 -1
- package/build/Crust.types.js.map +1 -1
- package/build/CrustModule.d.ts +11 -2
- package/build/CrustModule.d.ts.map +1 -1
- package/build/CrustModule.js.map +1 -1
- package/build/CrustModule.web.d.ts +3 -1
- package/build/CrustModule.web.d.ts.map +1 -1
- package/build/CrustModule.web.js +20 -8
- package/build/CrustModule.web.js.map +1 -1
- package/build/index.d.ts +1 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -0
- package/build/index.js.map +1 -1
- package/build/nativeHttpRequest.d.ts +13 -0
- package/build/nativeHttpRequest.d.ts.map +1 -0
- package/build/nativeHttpRequest.js +51 -0
- package/build/nativeHttpRequest.js.map +1 -0
- package/build/nativeHttpRequest.test.d.ts +2 -0
- package/build/nativeHttpRequest.test.d.ts.map +1 -0
- package/build/nativeHttpRequest.test.js +88 -0
- package/build/nativeHttpRequest.test.js.map +1 -0
- package/ios/Crust.podspec +1 -1
- package/ios/CrustModule.swift +22 -7
- package/ios/PermissionStatusReceipt.swift +105 -0
- package/ios/Resources/startup.js +154 -68
- package/ios/Source/JSCPolyfillBridge.swift +31 -14
- package/package.json +2 -2
- package/plugin/build/index.d.ts +2 -0
- package/plugin/build/index.js +2 -0
- package/plugin/build/withAndroid.js +39 -0
- package/src/Crust.types.ts +26 -12
- package/src/CrustModule.ts +11 -1
- package/src/CrustModule.web.ts +18 -7
- package/src/index.ts +1 -0
- package/src/nativeHttpRequest.test.ts +94 -0
- package/src/nativeHttpRequest.ts +74 -0
- package/android/src/internal/AndroidManifest.xml +0 -9
- package/android/src/internal/java/com/mentra/crust/receivers/CaptionsTesterIncidentReceiver.kt +0 -46
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 phone_notification: (event: PhoneNotificationEvent) => void\n phone_notification_dismissed: (event: PhoneNotificationDismissedEvent) => void\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_notification: (event: PhoneNotificationEvent) => void\n phone_notification_dismissed: (event: PhoneNotificationDismissedEvent) => 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}\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 /** True only when native navigation has ended; absent for recoverable reroute errors. */\n terminal?: boolean\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 CrustViewProps = {\n url: string\n onLoad: (event: {nativeEvent: OnLoadEventPayload}) => void\n style?: StyleProp<ViewStyle>\n}\n\n/** Passive OS observations for the currently executing Mentra App. */\nexport type AppPermissionStatus = {\n rawStatus: string | null\n status: \"granted\" | \"denied\" | \"blocked\" | \"unavailable\" | \"unknown\"\n}\n\nexport type AppPermissionSnapshot = {\n schemaVersion: 1\n observedAt: string\n identity: {\n pid: number\n uid: number\n bundleId: string\n bundlePath: string\n executablePath: string\n version: string\n build: string\n }\n locationServicesEnabled: boolean\n permissions: Record<\"location\" | \"camera\" | \"microphone\" | \"bluetooth\" | \"localNetwork\", AppPermissionStatus>\n}\n\nexport type AppPermissionReceipt = AppPermissionSnapshot & {requestId: string}\n"]}
|
package/build/CrustModule.d.ts
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
import { NativeModule } from "expo";
|
|
2
|
-
import { CrustModuleEvents, InstalledApp } from "./Crust.types";
|
|
2
|
+
import { AppPermissionReceipt, AppPermissionSnapshot, CrustModuleEvents, InstalledApp } from "./Crust.types";
|
|
3
3
|
declare class CrustModule extends NativeModule<CrustModuleEvents> {
|
|
4
|
+
/** True for the native iOS app running on an Apple Silicon Mac. */
|
|
5
|
+
readonly isIOSAppOnMac?: boolean;
|
|
6
|
+
/** iOS: passive actual-app authorization status; never requests consent. */
|
|
7
|
+
getPermissionStatus(requestId: null): Promise<AppPermissionSnapshot>;
|
|
8
|
+
/** iOS: a nonce publishes fresh status at the fixed managed-cache path. */
|
|
9
|
+
getPermissionStatus(requestId: string): Promise<AppPermissionReceipt>;
|
|
4
10
|
PI: number;
|
|
5
11
|
hello(): string;
|
|
6
12
|
setValueAsync(value: string): Promise<void>;
|
|
7
|
-
|
|
13
|
+
/** Android: copy the displayed view's window pixels to a temporary JPEG URI. */
|
|
14
|
+
captureMiniappPreview(viewTag: number): Promise<string>;
|
|
15
|
+
nativeHttpRequest(requestId: string, method: string, url: string, headers: Record<string, string>, body?: string | null): Promise<{
|
|
8
16
|
status: number;
|
|
9
17
|
statusText: string;
|
|
10
18
|
headers: Record<string, string>;
|
|
11
19
|
body: string;
|
|
12
20
|
}>;
|
|
21
|
+
cancelNativeHttpRequest(requestId: string): Promise<void>;
|
|
13
22
|
showAVRoutePicker(tintColor?: string | null): void;
|
|
14
23
|
/**
|
|
15
24
|
* iOS: configure `preferredScreenEdgesDeferringSystemGestures`. When an
|
|
@@ -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,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,EAAe,mBAAmB,EAAC,MAAM,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"CrustModule.js","sourceRoot":"","sources":["../src/CrustModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,mBAAmB,EAAC,MAAM,MAAM,CAAA;AAwMtD,yDAAyD;AACzD,eAAe,mBAAmB,CAAc,OAAO,CAAC,CAAA","sourcesContent":["import {NativeModule, requireNativeModule} from \"expo\"\n\nimport {AppPermissionReceipt, AppPermissionSnapshot, CrustModuleEvents, InstalledApp} from \"./Crust.types\"\n\ndeclare class CrustModule extends NativeModule<CrustModuleEvents> {\n /** True for the native iOS app running on an Apple Silicon Mac. */\n readonly isIOSAppOnMac?: boolean\n /** iOS: passive actual-app authorization status; never requests consent. */\n getPermissionStatus(requestId: null): Promise<AppPermissionSnapshot>\n /** iOS: a nonce publishes fresh status at the fixed managed-cache path. */\n getPermissionStatus(requestId: string): Promise<AppPermissionReceipt>\n PI: number\n hello(): string\n setValueAsync(value: string): Promise<void>\n /** Android: copy the displayed view's window pixels to a temporary JPEG URI. */\n captureMiniappPreview(viewTag: number): Promise<string>\n nativeHttpRequest(\n requestId: string,\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 cancelNativeHttpRequest(requestId: string): Promise<void>\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 // MentraOS 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 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 (Android only — iOS stubs return error)\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 * Show the Google Nav SDK Terms & Conditions dialog if not already\n * accepted. Idempotent — resolves immediately with `{accepted: true}`\n * when the user has already accepted (cached in-process / on-disk /\n * inside the SDK).\n */\n requestNavigationPermission(): Promise<{ok: boolean; accepted: boolean; error?: string}>\n\n /**\n * Dev-only: clear cached T&C acceptance (SDK flag + on-disk pref +\n * in-process flag) so the next requestNavigationPermission() re-shows\n * the dialog. Android only; iOS returns {ok: false, error: \"not\n * supported on iOS\"}.\n */\n resetNavigationPermission(): Promise<{ok: boolean; error?: string}>\n\n /**\n * Dev-only: nudge the simulated user position ~offsetMeters perpendicular\n * to the route so the Nav SDK reroutes. Default 20m. Android only.\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 Google 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 (Android only)\n startHeading(): Promise<{ok: boolean; error?: string}>\n stopHeading(): Promise<{ok: boolean; error?: string}>\n\n // MentraJS 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/mentrajs-runtime/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 mentraJsSpawn(packageName: string, polyfillBundle: string, miniappJs: string): Promise<boolean>\n mentraJsEvaluate(packageName: string, source: string): Promise<unknown>\n mentraJsKill(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 mentraJsDispatchToJs(packageName: string, envelope: Record<string, unknown>): Promise<void>\n mentraJsSetManifest(packageName: string, permissions: string[]): Promise<void>\n /** Diagnostic — returns the packageNames of every live JSContext. */\n mentraJsAlivePackages(): string[]\n /** Diagnostic — force a GC cycle on the named context. */\n mentraJsDebugForceGC(packageName: string): Promise<boolean>\n /**\n * Read the bundled MentraJS 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 mentraJsSpawn so\n * every JSContext starts with the same polyfill ABI.\n */\n mentraJsLoadPolyfillBundle(): string\n}\n\n// This call loads the native module object from the JSI.\nexport default requireNativeModule<CrustModule>(\"Crust\")\n"]}
|
|
@@ -2,8 +2,9 @@ import { NativeModule } from "expo";
|
|
|
2
2
|
import { CrustModuleEvents } from "./Crust.types";
|
|
3
3
|
declare class CrustModule extends NativeModule<CrustModuleEvents> {
|
|
4
4
|
PI: number;
|
|
5
|
+
private readonly httpRequests;
|
|
5
6
|
setValueAsync(value: string): Promise<void>;
|
|
6
|
-
nativeHttpRequest(method: string, url: string, headers: Record<string, string>, body?: string | null): Promise<{
|
|
7
|
+
nativeHttpRequest(requestId: string, method: string, url: string, headers: Record<string, string>, body?: string | null): Promise<{
|
|
7
8
|
status: number;
|
|
8
9
|
statusText: string;
|
|
9
10
|
headers: {
|
|
@@ -11,6 +12,7 @@ declare class CrustModule extends NativeModule<CrustModuleEvents> {
|
|
|
11
12
|
};
|
|
12
13
|
body: string;
|
|
13
14
|
}>;
|
|
15
|
+
cancelNativeHttpRequest(requestId: string): Promise<void>;
|
|
14
16
|
hello(): string;
|
|
15
17
|
showAVRoutePicker(_tintColor?: string | null): void;
|
|
16
18
|
setDeferredSystemGestures(_edges: string[]): Promise<void>;
|
|
@@ -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,EAAC,iBAAiB,EAAC,MAAM,eAAe,CAAA;AAE/C,cAAM,WAAY,SAAQ,YAAY,CAAC,iBAAiB,CAAC;IACvD,EAAE,SAAU;
|
|
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,EAAC,iBAAiB,EAAC,MAAM,eAAe,CAAA;AAE/C,cAAM,WAAY,SAAQ,YAAY,CAAC,iBAAiB,CAAC;IACvD,EAAE,SAAU;IACZ,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAqC;IAC5D,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAG3C,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI;;;;;;;;IAevH,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/D,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,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM;IAGjE,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;IAG3C,YAAY,CAAC,IAAI,EAAE,MAAM;IAGzB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAGhE,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE;IAGxD,qBAAqB;IAGf,oBAAoB,CAAC,IAAI,EAAE,MAAM;IAGvC,0BAA0B;CAG3B;;AAED,wBAA4D"}
|
package/build/CrustModule.web.js
CHANGED
|
@@ -1,17 +1,29 @@
|
|
|
1
1
|
import { registerWebModule, NativeModule } from "expo";
|
|
2
2
|
class CrustModule extends NativeModule {
|
|
3
3
|
PI = Math.PI;
|
|
4
|
+
httpRequests = new Map();
|
|
4
5
|
async setValueAsync(value) {
|
|
5
6
|
this.emit("onChange", { value });
|
|
6
7
|
}
|
|
7
|
-
async nativeHttpRequest(method, url, headers, body) {
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
async nativeHttpRequest(requestId, method, url, headers, body) {
|
|
9
|
+
const controller = new AbortController();
|
|
10
|
+
this.httpRequests.set(requestId, controller);
|
|
11
|
+
try {
|
|
12
|
+
const response = await fetch(url, { method, headers, body, signal: controller.signal });
|
|
13
|
+
return {
|
|
14
|
+
status: response.status,
|
|
15
|
+
statusText: response.statusText,
|
|
16
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
17
|
+
body: await response.text(),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
finally {
|
|
21
|
+
this.httpRequests.delete(requestId);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async cancelNativeHttpRequest(requestId) {
|
|
25
|
+
this.httpRequests.get(requestId)?.abort();
|
|
26
|
+
this.httpRequests.delete(requestId);
|
|
15
27
|
}
|
|
16
28
|
hello() {
|
|
17
29
|
return "Hello world! 👋";
|
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"CrustModule.web.js","sourceRoot":"","sources":["../src/CrustModule.web.ts"],"names":[],"mappings":"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","sourcesContent":["import {registerWebModule, NativeModule} from \"expo\"\n\nimport {CrustModuleEvents} from \"./Crust.types\"\n\nclass CrustModule extends NativeModule<CrustModuleEvents> {\n PI = Math.PI\n private readonly httpRequests = new Map<string, AbortController>()\n async setValueAsync(value: string): Promise<void> {\n this.emit(\"onChange\", {value})\n }\n async nativeHttpRequest(requestId: string, method: string, url: string, headers: Record<string, string>, body?: string | null) {\n const controller = new AbortController()\n this.httpRequests.set(requestId, controller)\n try {\n const response = await fetch(url, {method, headers, body, signal: controller.signal})\n return {\n status: response.status,\n statusText: response.statusText,\n headers: Object.fromEntries(response.headers.entries()),\n body: await response.text(),\n }\n } finally {\n this.httpRequests.delete(requestId)\n }\n }\n async cancelNativeHttpRequest(requestId: string): Promise<void> {\n this.httpRequests.get(requestId)?.abort()\n this.httpRequests.delete(requestId)\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 mentraJsSpawn(_pkg: string, _polyfill: string, _miniappJs: string) {\n return false\n }\n async mentraJsEvaluate(_pkg: string, _src: string) {\n return null\n }\n async mentraJsKill(_pkg: string) {\n return\n }\n async mentraJsDispatchToJs(_pkg: string, _env: Record<string, unknown>) {\n return\n }\n async mentraJsSetManifest(_pkg: string, _perms: string[]) {\n return\n }\n mentraJsAlivePackages() {\n return []\n }\n async mentraJsDebugForceGC(_pkg: string) {\n return false\n }\n mentraJsLoadPolyfillBundle() {\n return \"\"\n }\n}\n\nexport default registerWebModule(CrustModule, \"CrustModule\")\n"]}
|
package/build/index.d.ts
CHANGED
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,OAAO,EAAC,MAAM,eAAe,CAAA;AACrC,OAAO,EAAC,OAAO,IAAI,SAAS,EAAC,MAAM,aAAa,CAAA;AAChD,cAAc,eAAe,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,OAAO,EAAC,MAAM,eAAe,CAAA;AACrC,OAAO,EAAC,OAAO,IAAI,SAAS,EAAC,MAAM,aAAa,CAAA;AAChD,cAAc,eAAe,CAAA;AAC7B,OAAO,EAAC,iBAAiB,EAAC,MAAM,qBAAqB,CAAA"}
|
package/build/index.js
CHANGED
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,4CAA4C;AAC5C,OAAO,EAAC,OAAO,EAAC,MAAM,eAAe,CAAA;AACrC,OAAO,EAAC,OAAO,IAAI,SAAS,EAAC,MAAM,aAAa,CAAA;AAChD,cAAc,eAAe,CAAA","sourcesContent":["// Reexport the native module. On web, it will be resolved to CrustModule.web.ts\n// and on native platforms to CrustModule.ts\nexport {default} from \"./CrustModule\"\nexport {default as CrustView} from \"./CrustView\"\nexport * from \"./Crust.types\"\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,4CAA4C;AAC5C,OAAO,EAAC,OAAO,EAAC,MAAM,eAAe,CAAA;AACrC,OAAO,EAAC,OAAO,IAAI,SAAS,EAAC,MAAM,aAAa,CAAA;AAChD,cAAc,eAAe,CAAA;AAC7B,OAAO,EAAC,iBAAiB,EAAC,MAAM,qBAAqB,CAAA","sourcesContent":["// Reexport the native module. On web, it will be resolved to CrustModule.web.ts\n// and on native platforms to CrustModule.ts\nexport {default} from \"./CrustModule\"\nexport {default as CrustView} from \"./CrustView\"\nexport * from \"./Crust.types\"\nexport {nativeHttpRequest} from \"./nativeHttpRequest\"\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface NativeHttpResult {
|
|
2
|
+
status: number;
|
|
3
|
+
statusText: string;
|
|
4
|
+
headers: Record<string, string>;
|
|
5
|
+
body: string;
|
|
6
|
+
}
|
|
7
|
+
export interface NativeHttpTransport {
|
|
8
|
+
nativeHttpRequest(requestId: string, method: string, url: string, headers: Record<string, string>, body: string | null): Promise<NativeHttpResult>;
|
|
9
|
+
cancelNativeHttpRequest(requestId: string): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
/** Keep Android's background-safe HTTP path while cancelling its actual native request. */
|
|
12
|
+
export declare function nativeHttpRequest(native: NativeHttpTransport, method: string, url: string, headers: Record<string, string>, body: string | null, signal?: AbortSignal): Promise<NativeHttpResult>;
|
|
13
|
+
//# sourceMappingURL=nativeHttpRequest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nativeHttpRequest.d.ts","sourceRoot":"","sources":["../src/nativeHttpRequest.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,mBAAmB;IAClC,iBAAiB,CACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,IAAI,EAAE,MAAM,GAAG,IAAI,GAClB,OAAO,CAAC,gBAAgB,CAAC,CAAA;IAC5B,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC1D;AAWD,2FAA2F;AAC3F,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,mBAAmB,EAC3B,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,IAAI,EAAE,MAAM,GAAG,IAAI,EACnB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,gBAAgB,CAAC,CAsC3B"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
let requestSequence = 0;
|
|
2
|
+
function abortReason(signal) {
|
|
3
|
+
if (signal.reason !== undefined)
|
|
4
|
+
return signal.reason;
|
|
5
|
+
const error = new Error("Native HTTP request aborted");
|
|
6
|
+
error.name = "AbortError";
|
|
7
|
+
return error;
|
|
8
|
+
}
|
|
9
|
+
/** Keep Android's background-safe HTTP path while cancelling its actual native request. */
|
|
10
|
+
export function nativeHttpRequest(native, method, url, headers, body, signal) {
|
|
11
|
+
if (signal?.aborted)
|
|
12
|
+
return Promise.reject(abortReason(signal));
|
|
13
|
+
const requestId = `http-${Date.now().toString(36)}-${++requestSequence}`;
|
|
14
|
+
// Start first, then subscribe: Expo delivers start and cancel on the same serial queue.
|
|
15
|
+
const request = native.nativeHttpRequest(requestId, method, url, headers, body);
|
|
16
|
+
if (!signal)
|
|
17
|
+
return request;
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
let settled = false;
|
|
20
|
+
let cancelling = false;
|
|
21
|
+
const finish = (action) => {
|
|
22
|
+
if (settled)
|
|
23
|
+
return;
|
|
24
|
+
settled = true;
|
|
25
|
+
signal.removeEventListener("abort", onAbort);
|
|
26
|
+
action();
|
|
27
|
+
};
|
|
28
|
+
const onAbort = () => {
|
|
29
|
+
if (settled || cancelling)
|
|
30
|
+
return;
|
|
31
|
+
cancelling = true;
|
|
32
|
+
void native.cancelNativeHttpRequest(requestId).then(() => finish(() => reject(abortReason(signal))), (error) => finish(() => reject(error)));
|
|
33
|
+
};
|
|
34
|
+
request.then((result) => {
|
|
35
|
+
if (signal.aborted)
|
|
36
|
+
onAbort();
|
|
37
|
+
else
|
|
38
|
+
finish(() => resolve(result));
|
|
39
|
+
}, (error) => {
|
|
40
|
+
if (signal.aborted)
|
|
41
|
+
onAbort();
|
|
42
|
+
else
|
|
43
|
+
finish(() => reject(error));
|
|
44
|
+
});
|
|
45
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
46
|
+
// Handles an abort fired synchronously during native registration.
|
|
47
|
+
if (signal.aborted)
|
|
48
|
+
onAbort();
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=nativeHttpRequest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nativeHttpRequest.js","sourceRoot":"","sources":["../src/nativeHttpRequest.ts"],"names":[],"mappings":"AAkBA,IAAI,eAAe,GAAG,CAAC,CAAA;AAEvB,SAAS,WAAW,CAAC,MAAmB;IACtC,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,MAAM,CAAA;IACrD,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAA;IACtD,KAAK,CAAC,IAAI,GAAG,YAAY,CAAA;IACzB,OAAO,KAAK,CAAA;AACd,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,iBAAiB,CAC/B,MAA2B,EAC3B,MAAc,EACd,GAAW,EACX,OAA+B,EAC/B,IAAmB,EACnB,MAAoB;IAEpB,IAAI,MAAM,EAAE,OAAO;QAAE,OAAO,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAA;IAC/D,MAAM,SAAS,GAAG,QAAQ,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,eAAe,EAAE,CAAA;IACxE,wFAAwF;IACxF,MAAM,OAAO,GAAG,MAAM,CAAC,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,CAAC,CAAA;IAC/E,IAAI,CAAC,MAAM;QAAE,OAAO,OAAO,CAAA;IAE3B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,IAAI,OAAO,GAAG,KAAK,CAAA;QACnB,IAAI,UAAU,GAAG,KAAK,CAAA;QACtB,MAAM,MAAM,GAAG,CAAC,MAAkB,EAAE,EAAE;YACpC,IAAI,OAAO;gBAAE,OAAM;YACnB,OAAO,GAAG,IAAI,CAAA;YACd,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;YAC5C,MAAM,EAAE,CAAA;QACV,CAAC,CAAA;QACD,MAAM,OAAO,GAAG,GAAG,EAAE;YACnB,IAAI,OAAO,IAAI,UAAU;gBAAE,OAAM;YACjC,UAAU,GAAG,IAAI,CAAA;YACjB,KAAK,MAAM,CAAC,uBAAuB,CAAC,SAAS,CAAC,CAAC,IAAI,CACjD,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAC/C,CAAC,KAAc,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAChD,CAAA;QACH,CAAC,CAAA;QACD,OAAO,CAAC,IAAI,CACV,CAAC,MAAM,EAAE,EAAE;YACT,IAAI,MAAM,CAAC,OAAO;gBAAE,OAAO,EAAE,CAAA;;gBACxB,MAAM,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAA;QACpC,CAAC,EACD,CAAC,KAAc,EAAE,EAAE;YACjB,IAAI,MAAM,CAAC,OAAO;gBAAE,OAAO,EAAE,CAAA;;gBACxB,MAAM,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;QAClC,CAAC,CACF,CAAA;QACD,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAC,IAAI,EAAE,IAAI,EAAC,CAAC,CAAA;QACvD,mEAAmE;QACnE,IAAI,MAAM,CAAC,OAAO;YAAE,OAAO,EAAE,CAAA;IAC/B,CAAC,CAAC,CAAA;AACJ,CAAC","sourcesContent":["export interface NativeHttpResult {\n status: number\n statusText: string\n headers: Record<string, string>\n body: string\n}\n\nexport interface NativeHttpTransport {\n nativeHttpRequest(\n requestId: string,\n method: string,\n url: string,\n headers: Record<string, string>,\n body: string | null,\n ): Promise<NativeHttpResult>\n cancelNativeHttpRequest(requestId: string): Promise<void>\n}\n\nlet requestSequence = 0\n\nfunction abortReason(signal: AbortSignal): unknown {\n if (signal.reason !== undefined) return signal.reason\n const error = new Error(\"Native HTTP request aborted\")\n error.name = \"AbortError\"\n return error\n}\n\n/** Keep Android's background-safe HTTP path while cancelling its actual native request. */\nexport function nativeHttpRequest(\n native: NativeHttpTransport,\n method: string,\n url: string,\n headers: Record<string, string>,\n body: string | null,\n signal?: AbortSignal,\n): Promise<NativeHttpResult> {\n if (signal?.aborted) return Promise.reject(abortReason(signal))\n const requestId = `http-${Date.now().toString(36)}-${++requestSequence}`\n // Start first, then subscribe: Expo delivers start and cancel on the same serial queue.\n const request = native.nativeHttpRequest(requestId, method, url, headers, body)\n if (!signal) return request\n\n return new Promise((resolve, reject) => {\n let settled = false\n let cancelling = false\n const finish = (action: () => void) => {\n if (settled) return\n settled = true\n signal.removeEventListener(\"abort\", onAbort)\n action()\n }\n const onAbort = () => {\n if (settled || cancelling) return\n cancelling = true\n void native.cancelNativeHttpRequest(requestId).then(\n () => finish(() => reject(abortReason(signal))),\n (error: unknown) => finish(() => reject(error)),\n )\n }\n request.then(\n (result) => {\n if (signal.aborted) onAbort()\n else finish(() => resolve(result))\n },\n (error: unknown) => {\n if (signal.aborted) onAbort()\n else finish(() => reject(error))\n },\n )\n signal.addEventListener(\"abort\", onAbort, {once: true})\n // Handles an abort fired synchronously during native registration.\n if (signal.aborted) onAbort()\n })\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nativeHttpRequest.test.d.ts","sourceRoot":"","sources":["../src/nativeHttpRequest.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { nativeHttpRequest } from "./nativeHttpRequest";
|
|
2
|
+
const response = { status: 200, statusText: "OK", headers: {}, body: "{}" };
|
|
3
|
+
function deferred() {
|
|
4
|
+
let resolve;
|
|
5
|
+
let reject;
|
|
6
|
+
const promise = new Promise((yes, no) => {
|
|
7
|
+
resolve = yes;
|
|
8
|
+
reject = no;
|
|
9
|
+
});
|
|
10
|
+
return { promise, resolve, reject };
|
|
11
|
+
}
|
|
12
|
+
function transport(request = deferred()) {
|
|
13
|
+
const native = {
|
|
14
|
+
nativeHttpRequest: jest.fn(() => request.promise),
|
|
15
|
+
cancelNativeHttpRequest: jest.fn(async () => { }),
|
|
16
|
+
};
|
|
17
|
+
return { native, request };
|
|
18
|
+
}
|
|
19
|
+
describe("nativeHttpRequest", () => {
|
|
20
|
+
test("pre-aborted requests do not start or cancel native work", async () => {
|
|
21
|
+
const { native } = transport();
|
|
22
|
+
const controller = new AbortController();
|
|
23
|
+
const reason = new Error("deadline");
|
|
24
|
+
controller.abort(reason);
|
|
25
|
+
await expect(nativeHttpRequest(native, "GET", "https://example.com", {}, null, controller.signal)).rejects.toBe(reason);
|
|
26
|
+
expect(native.nativeHttpRequest).not.toHaveBeenCalled();
|
|
27
|
+
expect(native.cancelNativeHttpRequest).not.toHaveBeenCalled();
|
|
28
|
+
});
|
|
29
|
+
test("abort cancels only this request and waits for native cancellation acceptance", async () => {
|
|
30
|
+
const { native, request } = transport();
|
|
31
|
+
const cancellation = deferred();
|
|
32
|
+
native.cancelNativeHttpRequest = jest.fn(() => cancellation.promise);
|
|
33
|
+
const controller = new AbortController();
|
|
34
|
+
const remove = jest.spyOn(controller.signal, "removeEventListener");
|
|
35
|
+
const promise = nativeHttpRequest(native, "POST", "https://example.com", { test: "header" }, "body", controller.signal);
|
|
36
|
+
const rejected = expect(promise).rejects.toMatchObject({ name: "AbortError" });
|
|
37
|
+
controller.abort();
|
|
38
|
+
const id = native.nativeHttpRequest.mock.calls[0][0];
|
|
39
|
+
expect(native.cancelNativeHttpRequest).toHaveBeenCalledWith(id);
|
|
40
|
+
expect(remove).not.toHaveBeenCalled();
|
|
41
|
+
request.reject(new Error("cancelled socket"));
|
|
42
|
+
cancellation.resolve();
|
|
43
|
+
await rejected;
|
|
44
|
+
expect(native.cancelNativeHttpRequest).toHaveBeenCalledTimes(1);
|
|
45
|
+
expect(remove).toHaveBeenCalledWith("abort", expect.any(Function));
|
|
46
|
+
});
|
|
47
|
+
test("an abort during native start is cancelled after start and removes its listener", async () => {
|
|
48
|
+
const { native } = transport();
|
|
49
|
+
const controller = new AbortController();
|
|
50
|
+
const order = [];
|
|
51
|
+
native.nativeHttpRequest = jest.fn(() => {
|
|
52
|
+
order.push("start");
|
|
53
|
+
controller.abort();
|
|
54
|
+
return new Promise(() => { });
|
|
55
|
+
});
|
|
56
|
+
native.cancelNativeHttpRequest = jest.fn(async () => { order.push("cancel"); });
|
|
57
|
+
await expect(nativeHttpRequest(native, "GET", "https://example.com", {}, null, controller.signal)).rejects.toMatchObject({ name: "AbortError" });
|
|
58
|
+
expect(order).toEqual(["start", "cancel"]);
|
|
59
|
+
});
|
|
60
|
+
test.each(["success", "failure"])("%s removes its abort listener and a later abort does not cancel", async (outcome) => {
|
|
61
|
+
const { native, request } = transport();
|
|
62
|
+
const controller = new AbortController();
|
|
63
|
+
const remove = jest.spyOn(controller.signal, "removeEventListener");
|
|
64
|
+
const promise = nativeHttpRequest(native, "GET", "https://example.com", {}, null, controller.signal);
|
|
65
|
+
if (outcome === "success") {
|
|
66
|
+
request.resolve(response);
|
|
67
|
+
await expect(promise).resolves.toBe(response);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
request.reject(new Error("offline"));
|
|
71
|
+
await expect(promise).rejects.toThrow("offline");
|
|
72
|
+
}
|
|
73
|
+
controller.abort();
|
|
74
|
+
expect(remove).toHaveBeenCalledTimes(1);
|
|
75
|
+
expect(native.cancelNativeHttpRequest).not.toHaveBeenCalled();
|
|
76
|
+
});
|
|
77
|
+
test("independent calls have separate IDs and unsignalled calls keep the native path", async () => {
|
|
78
|
+
const { native, request } = transport();
|
|
79
|
+
const first = nativeHttpRequest(native, "GET", "https://example.com/first", {}, null);
|
|
80
|
+
const second = nativeHttpRequest(native, "GET", "https://example.com/second", {}, null);
|
|
81
|
+
const calls = native.nativeHttpRequest.mock.calls;
|
|
82
|
+
expect(calls[0][0]).not.toBe(calls[1][0]);
|
|
83
|
+
request.resolve(response);
|
|
84
|
+
await Promise.all([first, second]);
|
|
85
|
+
expect(native.cancelNativeHttpRequest).not.toHaveBeenCalled();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
//# sourceMappingURL=nativeHttpRequest.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nativeHttpRequest.test.js","sourceRoot":"","sources":["../src/nativeHttpRequest.test.ts"],"names":[],"mappings":"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","sourcesContent":["import {nativeHttpRequest, type NativeHttpResult, type NativeHttpTransport} from \"./nativeHttpRequest\"\n\nconst response: NativeHttpResult = {status: 200, statusText: \"OK\", headers: {}, body: \"{}\"}\n\nfunction deferred<T>() {\n let resolve!: (value: T) => void\n let reject!: (error: unknown) => void\n const promise = new Promise<T>((yes, no) => {\n resolve = yes\n reject = no\n })\n return {promise, resolve, reject}\n}\n\nfunction transport(request = deferred<NativeHttpResult>()) {\n const native: NativeHttpTransport = {\n nativeHttpRequest: jest.fn(() => request.promise),\n cancelNativeHttpRequest: jest.fn(async () => {}),\n }\n return {native, request}\n}\n\ndescribe(\"nativeHttpRequest\", () => {\n test(\"pre-aborted requests do not start or cancel native work\", async () => {\n const {native} = transport()\n const controller = new AbortController()\n const reason = new Error(\"deadline\")\n controller.abort(reason)\n await expect(nativeHttpRequest(native, \"GET\", \"https://example.com\", {}, null, controller.signal)).rejects.toBe(reason)\n expect(native.nativeHttpRequest).not.toHaveBeenCalled()\n expect(native.cancelNativeHttpRequest).not.toHaveBeenCalled()\n })\n\n test(\"abort cancels only this request and waits for native cancellation acceptance\", async () => {\n const {native, request} = transport()\n const cancellation = deferred<void>()\n native.cancelNativeHttpRequest = jest.fn(() => cancellation.promise)\n const controller = new AbortController()\n const remove = jest.spyOn(controller.signal, \"removeEventListener\")\n const promise = nativeHttpRequest(native, \"POST\", \"https://example.com\", {test: \"header\"}, \"body\", controller.signal)\n const rejected = expect(promise).rejects.toMatchObject({name: \"AbortError\"})\n controller.abort()\n const id = (native.nativeHttpRequest as jest.Mock).mock.calls[0][0]\n expect(native.cancelNativeHttpRequest).toHaveBeenCalledWith(id)\n expect(remove).not.toHaveBeenCalled()\n request.reject(new Error(\"cancelled socket\"))\n cancellation.resolve()\n await rejected\n expect(native.cancelNativeHttpRequest).toHaveBeenCalledTimes(1)\n expect(remove).toHaveBeenCalledWith(\"abort\", expect.any(Function))\n })\n\n test(\"an abort during native start is cancelled after start and removes its listener\", async () => {\n const {native} = transport()\n const controller = new AbortController()\n const order: string[] = []\n native.nativeHttpRequest = jest.fn(() => {\n order.push(\"start\")\n controller.abort()\n return new Promise<NativeHttpResult>(() => {})\n })\n native.cancelNativeHttpRequest = jest.fn(async () => {order.push(\"cancel\")})\n await expect(nativeHttpRequest(native, \"GET\", \"https://example.com\", {}, null, controller.signal)).rejects.toMatchObject({name: \"AbortError\"})\n expect(order).toEqual([\"start\", \"cancel\"])\n })\n\n test.each([\"success\", \"failure\"])(\"%s removes its abort listener and a later abort does not cancel\", async (outcome) => {\n const {native, request} = transport()\n const controller = new AbortController()\n const remove = jest.spyOn(controller.signal, \"removeEventListener\")\n const promise = nativeHttpRequest(native, \"GET\", \"https://example.com\", {}, null, controller.signal)\n if (outcome === \"success\") {\n request.resolve(response)\n await expect(promise).resolves.toBe(response)\n } else {\n request.reject(new Error(\"offline\"))\n await expect(promise).rejects.toThrow(\"offline\")\n }\n controller.abort()\n expect(remove).toHaveBeenCalledTimes(1)\n expect(native.cancelNativeHttpRequest).not.toHaveBeenCalled()\n })\n\n test(\"independent calls have separate IDs and unsignalled calls keep the native path\", async () => {\n const {native, request} = transport()\n const first = nativeHttpRequest(native, \"GET\", \"https://example.com/first\", {}, null)\n const second = nativeHttpRequest(native, \"GET\", \"https://example.com/second\", {}, null)\n const calls = (native.nativeHttpRequest as jest.Mock).mock.calls\n expect(calls[0][0]).not.toBe(calls[1][0])\n request.resolve(response)\n await Promise.all([first, second])\n expect(native.cancelNativeHttpRequest).not.toHaveBeenCalled()\n })\n})\n"]}
|
package/ios/Crust.podspec
CHANGED
|
@@ -29,7 +29,7 @@ Pod::Spec.new do |s|
|
|
|
29
29
|
# is intentionally NO `s.dependency 'MapboxNavigationCore'` here.
|
|
30
30
|
|
|
31
31
|
# iOS frameworks required for media processing + navigation + heading
|
|
32
|
-
s.frameworks = 'AVFoundation', 'Photos', 'CoreImage', 'CoreGraphics', 'UIKit', 'CoreLocation', 'CoreMotion'
|
|
32
|
+
s.frameworks = 'AVFoundation', 'Photos', 'CoreImage', 'CoreGraphics', 'UIKit', 'CoreLocation', 'CoreMotion', 'CoreBluetooth'
|
|
33
33
|
|
|
34
34
|
# Swift/Objective-C compatibility + Mapbox SPM module visibility.
|
|
35
35
|
#
|
package/ios/CrustModule.swift
CHANGED
|
@@ -61,10 +61,26 @@ private struct PhotoLibrarySaveResult {
|
|
|
61
61
|
let timedOut: Bool
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
/// MentraJS calls get their own serial queue. Expo runs every module's default-queue
|
|
65
|
+
/// AsyncFunction on one shared serial queue, so a blocking call in another module held every
|
|
66
|
+
/// host→miniapp message behind it: the ~25s offline STT model extraction after login kept
|
|
67
|
+
/// miniapps from spawning and starved running ones of PINGs until the host respawned them.
|
|
68
|
+
/// Serial, so spawn, dispatch and kill still run in the order JS issued them.
|
|
69
|
+
private let mentraJsQueue = DispatchQueue(label: "com.mentra.mentrajs-bridge", qos: .userInitiated)
|
|
70
|
+
|
|
64
71
|
public class CrustModule: Module {
|
|
65
72
|
public func definition() -> ModuleDefinition {
|
|
66
73
|
Name("Crust")
|
|
67
74
|
|
|
75
|
+
Constant("isIOSAppOnMac") {
|
|
76
|
+
ProcessInfo.processInfo.isiOSAppOnMac
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
AsyncFunction("getPermissionStatus") { (requestId: String?) -> [String: Any] in
|
|
80
|
+
if let requestId { return try await PermissionStatusReceipt.publish(requestId: requestId) }
|
|
81
|
+
return await PermissionStatusReceipt.current()
|
|
82
|
+
}
|
|
83
|
+
|
|
68
84
|
Constant("PI") {
|
|
69
85
|
Double.pi
|
|
70
86
|
}
|
|
@@ -73,7 +89,6 @@ public class CrustModule: Module {
|
|
|
73
89
|
"onChange",
|
|
74
90
|
"phone_notification",
|
|
75
91
|
"phone_notification_dismissed",
|
|
76
|
-
"captions_tester_incident",
|
|
77
92
|
"onNavManeuver",
|
|
78
93
|
"onNavRerouting",
|
|
79
94
|
"onNavArrived",
|
|
@@ -279,7 +294,7 @@ public class CrustModule: Module {
|
|
|
279
294
|
polyfillBundle: polyfillBundle,
|
|
280
295
|
miniappJs: miniappJs
|
|
281
296
|
)
|
|
282
|
-
}
|
|
297
|
+
}.runOnQueue(mentraJsQueue)
|
|
283
298
|
|
|
284
299
|
/// Evaluate an arbitrary script inside the named context. Returns
|
|
285
300
|
/// the JS return value bridged to a JSON-friendly Swift type, or
|
|
@@ -287,19 +302,19 @@ public class CrustModule: Module {
|
|
|
287
302
|
/// + tests; production code paths use mentraJsDispatchToJs.
|
|
288
303
|
AsyncFunction("mentraJsEvaluate") { (packageName: String, source: String) -> Any? in
|
|
289
304
|
return JSCRuntime.shared.evaluate(packageName: packageName, source: source)
|
|
290
|
-
}
|
|
305
|
+
}.runOnQueue(mentraJsQueue)
|
|
291
306
|
|
|
292
307
|
/// Tear down a JS context. Cancels timers, drops refs, forces GC.
|
|
293
308
|
AsyncFunction("mentraJsKill") { (packageName: String) -> Void in
|
|
294
309
|
JSCRuntime.shared.kill(packageName: packageName)
|
|
295
|
-
}
|
|
310
|
+
}.runOnQueue(mentraJsQueue)
|
|
296
311
|
|
|
297
312
|
/// Push an event / response envelope into the named context's
|
|
298
313
|
/// globalThis.__deliver. Used by MentraJSRouter for
|
|
299
314
|
/// glasses-status broadcasts and request/response correlation.
|
|
300
315
|
AsyncFunction("mentraJsDispatchToJs") { (packageName: String, envelope: [String: Any]) -> Void in
|
|
301
316
|
JSCRuntime.shared.dispatchToJs(packageName: packageName, envelope: envelope)
|
|
302
|
-
}
|
|
317
|
+
}.runOnQueue(mentraJsQueue)
|
|
303
318
|
|
|
304
319
|
/// Set the installed manifest for a miniapp so the dispatcher's
|
|
305
320
|
/// permission gate can authorize sensitive `__dispatch` calls.
|
|
@@ -308,7 +323,7 @@ public class CrustModule: Module {
|
|
|
308
323
|
packageName: packageName,
|
|
309
324
|
manifest: InstalledMiniappManifest(permissions: Set(permissions))
|
|
310
325
|
)
|
|
311
|
-
}
|
|
326
|
+
}.runOnQueue(mentraJsQueue)
|
|
312
327
|
|
|
313
328
|
/// Diagnostic: list all live packageNames.
|
|
314
329
|
Function("mentraJsAlivePackages") { () -> [String] in
|
|
@@ -320,7 +335,7 @@ public class CrustModule: Module {
|
|
|
320
335
|
/// the context is dead.
|
|
321
336
|
AsyncFunction("mentraJsDebugForceGC") { (packageName: String) -> Bool in
|
|
322
337
|
return JSCRuntime.shared.debugForceGC(packageName: packageName)
|
|
323
|
-
}
|
|
338
|
+
}.runOnQueue(mentraJsQueue)
|
|
324
339
|
|
|
325
340
|
/// Read the bundled MentraJS polyfill (startup.js) from the iOS
|
|
326
341
|
/// pod's resource bundle. The host calls this once on app boot,
|