@mentra/crust 3.3.0-dev.944 → 3.4.0-beta.1237
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 +16 -4
- package/android/src/main/java/com/mentra/crust/CrustModule.kt +6 -0
- package/android/src/main/java/com/mentra/crust/jsc/JSCPolyfillBridge.kt +83 -30
- package/android/src/main/java/com/mentra/crust/jsc/MiniappNetworkPolicy.kt +25 -0
- package/android/src/main/java/com/mentra/crust/utils/CanonicalFilePath.kt +22 -0
- package/android/src/test/java/com/mentra/crust/jsc/FetchBridgeTest.kt +277 -0
- package/android/src/test/java/com/mentra/crust/jsc/MiniappNetworkPolicyTest.kt +45 -0
- package/android/src/test/java/com/mentra/crust/utils/CanonicalFilePathTest.kt +39 -0
- package/build/CrustModule.d.ts +6 -0
- package/build/CrustModule.d.ts.map +1 -1
- package/build/CrustModule.js.map +1 -1
- package/build/CrustModule.web.d.ts +2 -0
- package/build/CrustModule.web.d.ts.map +1 -1
- package/build/CrustModule.web.js +4 -0
- package/build/CrustModule.web.js.map +1 -1
- package/ios/Crust.podspec +3 -0
- package/ios/CrustModule.swift +5 -0
- package/ios/MiniappPolicyKit/Package.swift +13 -0
- package/ios/MiniappPolicyKit/Sources/MiniappPolicy/CanonicalFilePath.swift +13 -0
- package/ios/MiniappPolicyKit/Sources/MiniappPolicy/MiniappNetworkPolicy.swift +24 -0
- package/ios/MiniappPolicyKit/Tests/MiniappPolicyTests/MiniappPolicyTests.swift +117 -0
- package/ios/MiniappPolicyKit/Tests/MiniappPolicyTests/RedirectServer.swift +33 -0
- package/ios/PermissionStatusReceipt.swift +72 -1
- package/ios/Resources/startup.js +199 -87
- package/ios/Source/JSCPolyfillBridge.swift +56 -14
- package/package.json +4 -2
- package/src/CrustModule.ts +6 -0
- package/src/CrustModule.web.ts +4 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CrustModule.d.ts","sourceRoot":"","sources":["../src/CrustModule.ts"],"names":[],"mappings":"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"}
|
|
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;AA8MtD,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 /**\n * The canonical path of an existing file or directory: absolute, with `.`,\n * `..` and every symlink resolved. Null when nothing is there. The engine\n * checks that a path a miniapp names stays inside the directory it may use.\n */\n canonicalFilePath(path: string): string | null\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"]}
|
|
@@ -13,6 +13,8 @@ declare class CrustModule extends NativeModule<CrustModuleEvents> {
|
|
|
13
13
|
body: string;
|
|
14
14
|
}>;
|
|
15
15
|
cancelNativeHttpRequest(requestId: string): Promise<void>;
|
|
16
|
+
/** The web has no local files to resolve. */
|
|
17
|
+
canonicalFilePath(_path: string): string | null;
|
|
16
18
|
hello(): string;
|
|
17
19
|
showAVRoutePicker(_tintColor?: string | null): void;
|
|
18
20
|
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;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"}
|
|
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,6CAA6C;IAC7C,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAG/C,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
|
@@ -25,6 +25,10 @@ class CrustModule extends NativeModule {
|
|
|
25
25
|
this.httpRequests.get(requestId)?.abort();
|
|
26
26
|
this.httpRequests.delete(requestId);
|
|
27
27
|
}
|
|
28
|
+
/** The web has no local files to resolve. */
|
|
29
|
+
canonicalFilePath(_path) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
28
32
|
hello() {
|
|
29
33
|
return "Hello world! 👋";
|
|
30
34
|
}
|
|
@@ -1 +1 @@
|
|
|
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"]}
|
|
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 /** The web has no local files to resolve. */\n canonicalFilePath(_path: string): string | null {\n return null\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/ios/Crust.podspec
CHANGED
|
@@ -50,6 +50,9 @@ Pod::Spec.new do |s|
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}"
|
|
53
|
+
# MiniappPolicyKit's sources compile into this pod; its manifest, tests and
|
|
54
|
+
# local `swift test` output are only for testing them on a Mac.
|
|
55
|
+
s.exclude_files = "MiniappPolicyKit/Package.swift", "MiniappPolicyKit/Tests/**/*", "MiniappPolicyKit/.build/**/*"
|
|
53
56
|
|
|
54
57
|
# Ship the MentraJS polyfill bundle inside the pod's resource bundle so
|
|
55
58
|
# JSCRuntime can read it at runtime via Bundle.main. The source of
|
package/ios/CrustModule.swift
CHANGED
|
@@ -121,6 +121,11 @@ public class CrustModule: Module {
|
|
|
121
121
|
"Hello world! 👋"
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
// Where a path really is, symlinks resolved; the engine checks miniapp-named paths with it.
|
|
125
|
+
Function("canonicalFilePath") { (path: String) -> String? in
|
|
126
|
+
CanonicalFilePath.of(path)
|
|
127
|
+
}
|
|
128
|
+
|
|
124
129
|
AsyncFunction("setValueAsync") { (value: String) in
|
|
125
130
|
self.sendEvent("onChange", [
|
|
126
131
|
"value": value,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// swift-tools-version: 5.9
|
|
2
|
+
import PackageDescription
|
|
3
|
+
|
|
4
|
+
// The Crust pod compiles Sources/MiniappPolicy into its own module; this package
|
|
5
|
+
// exists so the rules can be unit tested on a Mac with `swift test`.
|
|
6
|
+
let package = Package(
|
|
7
|
+
name: "MiniappPolicyKit", platforms: [.macOS(.v13), .iOS(.v15)],
|
|
8
|
+
products: [.library(name: "MiniappPolicy", targets: ["MiniappPolicy"])],
|
|
9
|
+
targets: [
|
|
10
|
+
.target(name: "MiniappPolicy"),
|
|
11
|
+
.testTarget(name: "MiniappPolicyTests", dependencies: ["MiniappPolicy"]),
|
|
12
|
+
]
|
|
13
|
+
)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/// The real location of a path, for checking that a path a miniapp names stays inside the
|
|
4
|
+
/// directory the host allows: absolute, with `.`, `..` and every symlink resolved.
|
|
5
|
+
public enum CanonicalFilePath {
|
|
6
|
+
/// The canonical path of an existing file or directory, or nil when there is none.
|
|
7
|
+
public static func of(_ path: String) -> String? {
|
|
8
|
+
guard path.hasPrefix("/"), !path.contains("\u{0}") else { return nil }
|
|
9
|
+
guard let resolved = realpath(path, nil) else { return nil }
|
|
10
|
+
defer { free(resolved) }
|
|
11
|
+
return String(cString: resolved)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/// Which URLs miniapp code may open through the polyfill's network bridge.
|
|
4
|
+
///
|
|
5
|
+
/// Miniapp code is untrusted, so `fetch` and `WebSocket` reach only the network.
|
|
6
|
+
/// URLSession also reads `file:` URLs, which would hand a miniapp the Mentra App's
|
|
7
|
+
/// own files, and `data:` URLs. Mirrors Android's `MiniappNetworkPolicy.kt` and the
|
|
8
|
+
/// polyfill's own check, which miniapp code can skip by calling the bridge directly.
|
|
9
|
+
public enum MiniappNetworkPolicy {
|
|
10
|
+
public static let fetchRefusal = "fetch: only http: and https: URLs are allowed"
|
|
11
|
+
public static let webSocketRefusal = "ws.open: only ws:, wss:, http: and https: URLs are allowed"
|
|
12
|
+
|
|
13
|
+
private static let fetchSchemes: Set<String> = ["http", "https"]
|
|
14
|
+
private static let webSocketSchemes: Set<String> = ["ws", "wss", "http", "https"]
|
|
15
|
+
|
|
16
|
+
/// `fetch`, and every redirect it follows.
|
|
17
|
+
public static func allowsFetch(_ url: URL) -> Bool {
|
|
18
|
+
fetchSchemes.contains(url.scheme?.lowercased() ?? "")
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public static func allowsWebSocket(_ url: URL) -> Bool {
|
|
22
|
+
webSocketSchemes.contains(url.scheme?.lowercased() ?? "")
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
@testable import MiniappPolicy
|
|
3
|
+
import XCTest
|
|
4
|
+
|
|
5
|
+
final class MiniappNetworkPolicyTests: XCTestCase {
|
|
6
|
+
func testFetchReachesOnlyHttpAndHttps() {
|
|
7
|
+
for url in ["http://example.com", "https://example.com/a?b", "HTTPS://EXAMPLE.COM", "http://192.168.1.20:8080/x"] {
|
|
8
|
+
XCTAssertTrue(MiniappNetworkPolicy.allowsFetch(URL(string: url)!), url)
|
|
9
|
+
}
|
|
10
|
+
// URLSession reads file: URLs, so this is what keeps miniapp code out of the app's files.
|
|
11
|
+
for url in [
|
|
12
|
+
"file:///var/mobile/Containers/Data/Application/X/Documents/mmkv/mmkv.default",
|
|
13
|
+
"FILE:///etc/hosts",
|
|
14
|
+
"file:/etc/hosts",
|
|
15
|
+
"data:text/plain,hi",
|
|
16
|
+
"ftp://example.com/x",
|
|
17
|
+
"ws://example.com",
|
|
18
|
+
"/etc/hosts",
|
|
19
|
+
"relative/path",
|
|
20
|
+
] {
|
|
21
|
+
XCTAssertFalse(MiniappNetworkPolicy.allowsFetch(URL(string: url)!), url)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/// Why the gate exists: the session the bridge uses hands back a local file's bytes.
|
|
26
|
+
func testUrlSessionWouldReadAFileUrl() throws {
|
|
27
|
+
let file = FileManager.default.temporaryDirectory.appendingPathComponent("\(UUID().uuidString).txt")
|
|
28
|
+
try Data("refresh-token".utf8).write(to: file)
|
|
29
|
+
defer { try? FileManager.default.removeItem(at: file) }
|
|
30
|
+
let done = expectation(description: "read")
|
|
31
|
+
var body: Data?
|
|
32
|
+
URLSession(configuration: .default).dataTask(with: file) { data, _, _ in
|
|
33
|
+
body = data
|
|
34
|
+
done.fulfill()
|
|
35
|
+
}.resume()
|
|
36
|
+
wait(for: [done], timeout: 5)
|
|
37
|
+
XCTAssertEqual(body, Data("refresh-token".utf8))
|
|
38
|
+
XCTAssertFalse(MiniappNetworkPolicy.allowsFetch(file))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// The host's own downloads (blob.setFromUrl, system.download, meeting stills) check only
|
|
42
|
+
/// the first URL and rely on this: URLSession does not follow a redirect to a file: URL.
|
|
43
|
+
func testUrlSessionRefusesARedirectToAFileUrl() throws {
|
|
44
|
+
let file = FileManager.default.temporaryDirectory.appendingPathComponent("\(UUID().uuidString).txt")
|
|
45
|
+
try Data("refresh-token".utf8).write(to: file)
|
|
46
|
+
defer { try? FileManager.default.removeItem(at: file) }
|
|
47
|
+
let server = try RedirectServer(location: file.absoluteString)
|
|
48
|
+
defer { server.stop() }
|
|
49
|
+
|
|
50
|
+
for kind in ["data", "download"] {
|
|
51
|
+
let done = expectation(description: kind)
|
|
52
|
+
var read: String?
|
|
53
|
+
var failure: NSError?
|
|
54
|
+
let session = URLSession(configuration: .ephemeral)
|
|
55
|
+
let finish: (Data?, Error?) -> Void = { data, error in
|
|
56
|
+
read = data.map { String(decoding: $0, as: UTF8.self) }
|
|
57
|
+
failure = error as NSError?
|
|
58
|
+
done.fulfill()
|
|
59
|
+
}
|
|
60
|
+
if kind == "data" {
|
|
61
|
+
session.dataTask(with: server.url) { data, _, error in finish(data, error) }.resume()
|
|
62
|
+
} else {
|
|
63
|
+
session.downloadTask(with: server.url) { location, _, error in
|
|
64
|
+
finish(location.flatMap { try? Data(contentsOf: $0) }, error)
|
|
65
|
+
}.resume()
|
|
66
|
+
}
|
|
67
|
+
wait(for: [done], timeout: 10)
|
|
68
|
+
XCTAssertNotEqual(read, "refresh-token", kind)
|
|
69
|
+
XCTAssertEqual(failure?.code, NSURLErrorNoPermissionsToReadFile, kind)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
func testWebSocketsReachOnlyNetworkSchemes() {
|
|
74
|
+
for url in ["ws://10.0.0.2:9000", "WSS://example.com/s", "http://example.com", "https://example.com"] {
|
|
75
|
+
XCTAssertTrue(MiniappNetworkPolicy.allowsWebSocket(URL(string: url)!), url)
|
|
76
|
+
}
|
|
77
|
+
for url in ["file:///etc/hosts", "data:,x", "/socket"] {
|
|
78
|
+
XCTAssertFalse(MiniappNetworkPolicy.allowsWebSocket(URL(string: url)!), url)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
final class CanonicalFilePathTests: XCTestCase {
|
|
84
|
+
private var root: URL!
|
|
85
|
+
|
|
86
|
+
override func setUpWithError() throws {
|
|
87
|
+
root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
|
|
88
|
+
try FileManager.default.createDirectory(at: root, withIntermediateDirectories: true)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
override func tearDownWithError() throws {
|
|
92
|
+
try? FileManager.default.removeItem(at: root)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
func testResolvesDotSegmentsAndSymlinks() throws {
|
|
96
|
+
let sandbox = root.appendingPathComponent("sandbox")
|
|
97
|
+
try FileManager.default.createDirectory(at: sandbox, withIntermediateDirectories: true)
|
|
98
|
+
let outside = root.appendingPathComponent("outside.txt")
|
|
99
|
+
try Data("secret".utf8).write(to: outside)
|
|
100
|
+
let link = sandbox.appendingPathComponent("link.txt")
|
|
101
|
+
try FileManager.default.createSymbolicLink(at: link, withDestinationURL: outside)
|
|
102
|
+
|
|
103
|
+
let realOutside = try XCTUnwrap(CanonicalFilePath.of(outside.path))
|
|
104
|
+
XCTAssertEqual(CanonicalFilePath.of(link.path), realOutside)
|
|
105
|
+
XCTAssertEqual(CanonicalFilePath.of(sandbox.path + "/../outside.txt"), realOutside)
|
|
106
|
+
// The temporary directory sits under /var, itself a symlink to /private/var, like an
|
|
107
|
+
// iOS app's container: both sides of a containment check must be resolved.
|
|
108
|
+
XCTAssertTrue(realOutside.hasPrefix("/private/"), realOutside)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
func testNilForMissingRelativeOrMalformedPaths() {
|
|
112
|
+
XCTAssertNil(CanonicalFilePath.of(root.appendingPathComponent("missing.txt").path))
|
|
113
|
+
XCTAssertNil(CanonicalFilePath.of("relative/path"))
|
|
114
|
+
XCTAssertNil(CanonicalFilePath.of(root.path + "/a\u{0}b"))
|
|
115
|
+
XCTAssertNil(CanonicalFilePath.of(""))
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Network
|
|
3
|
+
|
|
4
|
+
/// A one-route HTTP server on 127.0.0.1 that answers every request with a 302 to `location`.
|
|
5
|
+
final class RedirectServer {
|
|
6
|
+
let url: URL
|
|
7
|
+
private let listener: NWListener
|
|
8
|
+
private let queue = DispatchQueue(label: "RedirectServer")
|
|
9
|
+
|
|
10
|
+
init(location: String) throws {
|
|
11
|
+
listener = try NWListener(using: .tcp, on: .any)
|
|
12
|
+
let response = "HTTP/1.1 302 Found\r\nLocation: \(location)\r\nContent-Length: 0\r\nConnection: close\r\n\r\n"
|
|
13
|
+
listener.newConnectionHandler = { [queue] connection in
|
|
14
|
+
connection.start(queue: queue)
|
|
15
|
+
connection.receive(minimumIncompleteLength: 1, maximumLength: 65536) { _, _, _, _ in
|
|
16
|
+
connection.send(content: Data(response.utf8), completion: .contentProcessed { _ in connection.cancel() })
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
let ready = DispatchSemaphore(value: 0)
|
|
20
|
+
listener.stateUpdateHandler = { state in
|
|
21
|
+
if case .ready = state { ready.signal() }
|
|
22
|
+
}
|
|
23
|
+
listener.start(queue: queue)
|
|
24
|
+
guard ready.wait(timeout: .now() + 5) == .success, let port = listener.port?.rawValue else {
|
|
25
|
+
throw URLError(.cannotConnectToHost)
|
|
26
|
+
}
|
|
27
|
+
url = URL(string: "http://127.0.0.1:\(port)/credentials")!
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
func stop() {
|
|
31
|
+
listener.cancel()
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -3,6 +3,70 @@ import CoreBluetooth
|
|
|
3
3
|
import CoreLocation
|
|
4
4
|
import Foundation
|
|
5
5
|
|
|
6
|
+
/// A new manager's getter can precede Core Location's initial authorization callback.
|
|
7
|
+
/// Keep the manager and delegate alive until that callback, without requesting consent or location updates.
|
|
8
|
+
@MainActor
|
|
9
|
+
private final class PassiveLocationAuthorization: NSObject, @preconcurrency CLLocationManagerDelegate {
|
|
10
|
+
private var manager: CLLocationManager?
|
|
11
|
+
private var completed: ((CLAuthorizationStatus) -> Void)?
|
|
12
|
+
|
|
13
|
+
func start(_ completed: @escaping (CLAuthorizationStatus) -> Void) {
|
|
14
|
+
self.completed = completed
|
|
15
|
+
let manager = CLLocationManager()
|
|
16
|
+
self.manager = manager
|
|
17
|
+
manager.delegate = self
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
func stop() {
|
|
21
|
+
manager?.delegate = nil
|
|
22
|
+
manager = nil
|
|
23
|
+
completed = nil
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
|
|
27
|
+
guard manager === self.manager else { return }
|
|
28
|
+
completed?(manager.authorizationStatus)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/// One finite initial callback. Missing initialization stays unknown, including when the task is canceled.
|
|
33
|
+
@MainActor
|
|
34
|
+
final class InitialLocationAuthorization {
|
|
35
|
+
private var continuation: CheckedContinuation<CLAuthorizationStatus?, Never>?
|
|
36
|
+
private var timeout: Task<Void, Never>?
|
|
37
|
+
private var stop: (() -> Void)?
|
|
38
|
+
|
|
39
|
+
func observe(timeoutMs: UInt64 = 5000,
|
|
40
|
+
start: (@escaping (CLAuthorizationStatus) -> Void) -> Void,
|
|
41
|
+
stop: @escaping () -> Void) async -> CLAuthorizationStatus? {
|
|
42
|
+
self.stop = stop
|
|
43
|
+
return await withTaskCancellationHandler {
|
|
44
|
+
await withCheckedContinuation { continuation in
|
|
45
|
+
self.continuation = continuation
|
|
46
|
+
if Task.isCancelled { finish(nil); return }
|
|
47
|
+
timeout = Task { @MainActor in
|
|
48
|
+
do { try await Task.sleep(nanoseconds: timeoutMs * 1_000_000) }
|
|
49
|
+
catch { return }
|
|
50
|
+
self.finish(nil)
|
|
51
|
+
}
|
|
52
|
+
start { self.finish($0) }
|
|
53
|
+
}
|
|
54
|
+
} onCancel: {
|
|
55
|
+
Task { @MainActor in self.finish(nil) }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private func finish(_ status: CLAuthorizationStatus?) {
|
|
60
|
+
guard let continuation else { return }
|
|
61
|
+
self.continuation = nil
|
|
62
|
+
timeout?.cancel()
|
|
63
|
+
timeout = nil
|
|
64
|
+
stop?()
|
|
65
|
+
stop = nil
|
|
66
|
+
continuation.resume(returning: status)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
6
70
|
/// Passive app-owned observations. None of these getters requests consent,
|
|
7
71
|
/// starts capture, creates a Bluetooth central, or joins a network.
|
|
8
72
|
enum PermissionStatusReceipt {
|
|
@@ -43,10 +107,17 @@ enum PermissionStatusReceipt {
|
|
|
43
107
|
}
|
|
44
108
|
}
|
|
45
109
|
|
|
110
|
+
@MainActor
|
|
111
|
+
private static func initializedLocationStatus() async -> CLAuthorizationStatus? {
|
|
112
|
+
let source = PassiveLocationAuthorization()
|
|
113
|
+
return await InitialLocationAuthorization().observe(start: source.start, stop: source.stop)
|
|
114
|
+
}
|
|
115
|
+
|
|
46
116
|
static func current() async -> [String: Any] {
|
|
47
117
|
// The global service getter may block; only the manager's authorization
|
|
48
118
|
// observation belongs to the main actor, which owns Core Location objects.
|
|
49
119
|
let servicesEnabled = CLLocationManager.locationServicesEnabled()
|
|
120
|
+
let locationStatus = await initializedLocationStatus()
|
|
50
121
|
return await MainActor.run {
|
|
51
122
|
let bundle = Bundle.main
|
|
52
123
|
let formatter = ISO8601DateFormatter()
|
|
@@ -65,7 +136,7 @@ enum PermissionStatusReceipt {
|
|
|
65
136
|
] as [String: Any],
|
|
66
137
|
"locationServicesEnabled": servicesEnabled,
|
|
67
138
|
"permissions": [
|
|
68
|
-
"location": location(
|
|
139
|
+
"location": locationStatus.map(location) ?? state("initializationUnconfirmed", "unknown"),
|
|
69
140
|
"camera": capture(AVCaptureDevice.authorizationStatus(for: .video)),
|
|
70
141
|
"microphone": capture(AVCaptureDevice.authorizationStatus(for: .audio)),
|
|
71
142
|
"bluetooth": bluetooth(CBManager.authorization),
|