peachsim 0.1.939 → 0.1.1399

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.
Files changed (396) hide show
  1. package/README.md +28 -22
  2. package/cli/app-catalog.ts +29 -14
  3. package/cli/app-fonts.ts +5 -3
  4. package/cli/bridge-flow-runner.ts +40 -29
  5. package/cli/cloud-client.ts +57 -21
  6. package/cli/cloud-test-tunnel.ts +12 -18
  7. package/cli/commands/agent.ts +1 -1
  8. package/cli/commands/app-fonts.ts +3 -3
  9. package/cli/commands/assert.ts +8 -8
  10. package/cli/commands/auth.ts +8 -3
  11. package/cli/commands/box/bundle-plane.ts +13 -1
  12. package/cli/commands/box.ts +16 -5
  13. package/cli/commands/compat.ts +1 -1
  14. package/cli/commands/config.ts +2 -2
  15. package/cli/commands/control.ts +125 -71
  16. package/cli/commands/cpu-profile.ts +45 -1
  17. package/cli/commands/daemon.ts +131 -33
  18. package/cli/commands/debug.ts +24 -17
  19. package/cli/commands/desktop.ts +7 -1
  20. package/cli/commands/detect.ts +101 -0
  21. package/cli/commands/detox.ts +6 -6
  22. package/cli/commands/device.ts +12 -12
  23. package/cli/commands/electron.ts +32 -14
  24. package/cli/commands/flow.ts +49 -41
  25. package/cli/commands/guest-host.ts +39 -1
  26. package/cli/commands/inspect/core.ts +14 -8
  27. package/cli/commands/inspect/list.ts +1 -1
  28. package/cli/commands/inspect/settle.ts +5 -3
  29. package/cli/commands/inspect/settling.ts +229 -44
  30. package/cli/commands/inspect/wait-idle.ts +3 -2
  31. package/cli/commands/inspect.ts +105 -93
  32. package/cli/commands/install-cli.ts +36 -15
  33. package/cli/commands/install-desktop.ts +68 -195
  34. package/cli/commands/kotlin.ts +6 -5
  35. package/cli/commands/login.ts +5 -5
  36. package/cli/commands/maestro-compare.ts +554 -0
  37. package/cli/commands/maestro-generate.ts +6 -3
  38. package/cli/commands/maestro.ts +22 -0
  39. package/cli/commands/mode.ts +2 -2
  40. package/cli/commands/no-bridge-hint.ts +6 -2
  41. package/cli/commands/platform.ts +35 -6
  42. package/cli/commands/preview.ts +6 -0
  43. package/cli/commands/project-report.ts +10 -12
  44. package/cli/commands/record.ts +37 -37
  45. package/cli/commands/runtime.ts +9 -9
  46. package/cli/commands/screenshot-layers.ts +4 -4
  47. package/cli/commands/screenshot-mode.ts +2 -2
  48. package/cli/commands/screenshot.ts +8 -8
  49. package/cli/commands/screenshots-capture.ts +2 -2
  50. package/cli/commands/serve.ts +9 -12
  51. package/cli/commands/setup.ts +29 -14
  52. package/cli/commands/shell-boolean-mode.ts +2 -2
  53. package/cli/commands/show-touches-mode.ts +2 -2
  54. package/cli/commands/skills.ts +11 -11
  55. package/cli/commands/swift.ts +29 -11
  56. package/cli/commands/telemetry.ts +2 -2
  57. package/cli/commands/test.ts +5 -0
  58. package/cli/commands/three-mode.ts +3 -3
  59. package/cli/commands/timeline.ts +4 -4
  60. package/cli/commands/upgrade.ts +2 -2
  61. package/cli/commands/upload.ts +15 -15
  62. package/cli/commands/what-happened.ts +2 -2
  63. package/cli/current-sim.ts +6 -3
  64. package/cli/desktop-companion.ts +55 -149
  65. package/cli/dev-build-freshness.ts +2 -2
  66. package/cli/drivers/desktop.ts +17 -9
  67. package/cli/drivers/electron.ts +1 -2
  68. package/cli/drivers/playwright-sim-host.ts +11 -2
  69. package/cli/drivers/playwright.ts +35 -2
  70. package/cli/drivers/types.ts +8 -0
  71. package/cli/maestro-compare.ts +670 -0
  72. package/cli/outbound-endpoints.ts +46 -8
  73. package/cli/recording-access.ts +6 -6
  74. package/cli/run-registry.ts +1 -1
  75. package/cli/runtime-compat.ts +5 -1
  76. package/cli/send-to-box.ts +3 -3
  77. package/cli/shell-init.ts +2 -1
  78. package/cli/ws-bridge.ts +9 -5
  79. package/detox/element-types.ts +1 -0
  80. package/detox/gestures.ts +3 -3
  81. package/detox/index.ts +67 -38
  82. package/detox/navigation-lifecycle.ts +9 -6
  83. package/detox/proof-frame.cjs +6 -4
  84. package/dist-lib/agent-daemon-client.cjs +1 -1
  85. package/dist-lib/agent-events.cjs +1 -1
  86. package/dist-lib/attached-projects.cjs +1 -1
  87. package/dist-lib/auth/shared-session.cjs +1 -1
  88. package/dist-lib/backend-origin.cjs +1 -1
  89. package/dist-lib/beta.cjs +1 -1
  90. package/dist-lib/beta.mjs +1 -1
  91. package/dist-lib/bridge-constants.cjs +1 -1
  92. package/dist-lib/bridge-contract-input.cjs +42 -17
  93. package/dist-lib/bridge-contract-input.mjs +41 -16
  94. package/dist-lib/bridge-contract.cjs +1 -4
  95. package/dist-lib/bridge-contract.mjs +1 -3
  96. package/dist-lib/bundle-channel.cjs +2918 -550
  97. package/dist-lib/capture-contract.cjs +65 -24
  98. package/dist-lib/capture-contract.mjs +63 -24
  99. package/dist-lib/cloud-contract.cjs +2 -2
  100. package/dist-lib/cloud-contract.mjs +2 -2
  101. package/dist-lib/cloud-test-args.cjs +1 -1
  102. package/dist-lib/cloud-test-args.mjs +1 -1
  103. package/dist-lib/cloud.cjs +3 -3
  104. package/dist-lib/cloud.mjs +3 -3
  105. package/dist-lib/config.cjs +15 -5
  106. package/dist-lib/config.mjs +154 -0
  107. package/dist-lib/detox/index.cjs +495 -139
  108. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  109. package/dist-lib/expo-swift-bundle.cjs +1168 -238
  110. package/dist-lib/expo-swift-metadata.cjs +34 -23
  111. package/dist-lib/expo-swift-metadata.mjs +32 -23
  112. package/dist-lib/home-paths.cjs +2 -2
  113. package/dist-lib/host/bridge-host.cjs +2938 -654
  114. package/dist-lib/host/fetch-proxy-handler.cjs +14 -1
  115. package/dist-lib/host/fetch-proxy-overrides.cjs +7 -1
  116. package/dist-lib/host/fetch-proxy-overrides.mjs +5 -1
  117. package/dist-lib/host/replacement-module-handler.cjs +2 -2
  118. package/dist-lib/host/websocket-proxy.cjs +1 -1
  119. package/dist-lib/index.cjs +999 -353
  120. package/dist-lib/jump-to-source-babel.cjs +1 -1
  121. package/dist-lib/jump-to-source-native.cjs +1 -1
  122. package/dist-lib/kotlin-host.cjs +332 -19
  123. package/dist-lib/menu.cjs +1 -1
  124. package/dist-lib/menu.mjs +1 -1
  125. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  126. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  127. package/dist-lib/metro-production-bundle.cjs +7 -7
  128. package/dist-lib/metro-production-bundle.mjs +5 -5
  129. package/dist-lib/metro.cjs +688 -250
  130. package/dist-lib/native-app-identity-project.cjs +2647 -0
  131. package/dist-lib/native-app-identity-project.mjs +1257 -0
  132. package/dist-lib/native-app-identity.cjs +325 -25
  133. package/dist-lib/native-app-identity.mjs +323 -25
  134. package/dist-lib/native-firebase-metadata.cjs +1 -1
  135. package/dist-lib/native-firebase-metadata.mjs +1 -1
  136. package/dist-lib/plist.cjs +1 -1
  137. package/dist-lib/plist.mjs +1 -1
  138. package/dist-lib/profiles.cjs +1 -1
  139. package/dist-lib/public-brand.cjs +1 -1
  140. package/dist-lib/react-native-host-modules.cjs +5 -1
  141. package/dist-lib/react-native-host-modules.mjs +5 -1
  142. package/dist-lib/scripts/dev-server-scanner.cjs +57 -32
  143. package/dist-lib/sdk.cjs +231 -51
  144. package/dist-lib/sdk.mjs +231 -51
  145. package/dist-lib/skills.cjs +4107 -1374
  146. package/dist-lib/swift-compile.cjs +290 -30
  147. package/dist-lib/swift-compile.mjs +290 -30
  148. package/dist-lib/swift-package.cjs +143 -12
  149. package/dist-lib/swift-package.mjs +137 -11
  150. package/dist-lib/swift-submit.cjs +47 -5
  151. package/dist-lib/swift-submit.mjs +42 -5
  152. package/dist-lib/swift-toolchain.cjs +7 -2
  153. package/dist-lib/swift.cjs +1 -1
  154. package/dist-lib/vite-swift.cjs +1392 -458
  155. package/dist-lib/vite.cjs +13 -17
  156. package/dist-lib/worklet-executable.cjs +248 -33
  157. package/dist-lib/worklet-executable.mjs +248 -33
  158. package/install.sh +10 -1
  159. package/kotlin/{AppSources → AndroidViewSources}/android/content/res/Resources.kt +9 -9
  160. package/kotlin/AndroidViewSources/android/graphics/Canvas.kt +20 -0
  161. package/kotlin/AndroidViewSources/android/view/View.kt +211 -0
  162. package/kotlin/AndroidViewSources/android/widget/FrameLayout.kt +61 -0
  163. package/kotlin/AndroidViewSources/android/widget/LinearLayout.kt +49 -0
  164. package/kotlin/AppSources/android/view/Window.kt +36 -0
  165. package/kotlin/AppSources/android/view/WindowInsets.kt +9 -0
  166. package/kotlin/AppSources/android/view/WindowInsetsController.kt +5 -0
  167. package/kotlin/AppSources/android/window/OnBackInvokedDispatcher.kt +48 -0
  168. package/kotlin/AppSources/androidx/activity/OnBackPressedDispatcher.kt +20 -5
  169. package/kotlin/AppSources/androidx/compose/foundation/layout/WindowInsets.kt +15 -0
  170. package/kotlin/AppSources/androidx/compose/ui/platform/ComposeView.kt +2 -1
  171. package/kotlin/AppSources/dev/peach/app/PeachApp.kt +42 -50
  172. package/kotlin/ComposeSources/PeachComposeHost.kt +200 -84
  173. package/kotlin/ExpoComposeSources/PeachAndroidView.kt +87 -0
  174. package/kotlin/ExpoComposeSources/PeachExpoViewMount.kt +24 -0
  175. package/kotlin/ExpoComposeSources/dev/peach/expo/PeachExpoCompose.kt +28 -0
  176. package/kotlin/ExpoComposeSources/expo/modules/kotlin/views/ExpoComposeView.kt +70 -0
  177. package/kotlin/ExpoSources/dev/peach/expo/PeachExpoRuntime.kt +513 -0
  178. package/kotlin/ExpoSources/dev/peach/expo/PeachExpoTypes.kt +173 -0
  179. package/kotlin/ExpoSources/expo/modules/kotlin/AppContext.kt +20 -0
  180. package/kotlin/ExpoSources/expo/modules/kotlin/Promise.kt +19 -0
  181. package/kotlin/ExpoSources/expo/modules/kotlin/classcomponent/ClassComponentBuilder.kt +198 -0
  182. package/kotlin/ExpoSources/expo/modules/kotlin/exception/CodedException.kt +126 -0
  183. package/kotlin/ExpoSources/expo/modules/kotlin/functions/Functions.kt +132 -0
  184. package/kotlin/ExpoSources/expo/modules/kotlin/jni/JavaScriptTypedArray.kt +81 -0
  185. package/kotlin/ExpoSources/expo/modules/kotlin/modules/Module.kt +98 -0
  186. package/kotlin/ExpoSources/expo/modules/kotlin/objects/ObjectDefinitionBuilder.kt +236 -0
  187. package/kotlin/ExpoSources/expo/modules/kotlin/records/Record.kt +12 -0
  188. package/kotlin/ExpoSources/expo/modules/kotlin/runtime/Runtime.kt +9 -0
  189. package/kotlin/ExpoSources/expo/modules/kotlin/sharedobjects/SharedObject.kt +50 -0
  190. package/kotlin/ExpoSources/expo/modules/kotlin/sharedobjects/SharedObjectRegistry.kt +57 -0
  191. package/kotlin/ExpoSources/expo/modules/kotlin/sharedobjects/SharedRef.kt +19 -0
  192. package/kotlin/ExpoSources/expo/modules/kotlin/typedarray/ConcreteTypedArrays.kt +158 -0
  193. package/kotlin/ExpoSources/expo/modules/kotlin/typedarray/GenericTypedArray.kt +8 -0
  194. package/kotlin/ExpoSources/expo/modules/kotlin/typedarray/TypedArray.kt +29 -0
  195. package/kotlin/ExpoSources/expo/modules/kotlin/typedarray/TypedArrayIterator.kt +9 -0
  196. package/kotlin/ExpoSources/expo/modules/kotlin/types/AnyType.kt +155 -0
  197. package/kotlin/ExpoSources/expo/modules/kotlin/types/Either.kt +91 -0
  198. package/kotlin/ExpoSources/expo/modules/kotlin/types/Enumerable.kt +6 -0
  199. package/kotlin/ExpoSources/expo/modules/kotlin/viewevent/ViewEvent.kt +54 -0
  200. package/kotlin/ExpoSources/expo/modules/kotlin/views/ExpoView.kt +23 -0
  201. package/kotlin/ExpoSources/expo/modules/kotlin/views/ViewDefinitionBuilder.kt +71 -0
  202. package/kotlin/ExpoSources/expo/modules/kotlin/views/ViewProps.kt +35 -0
  203. package/kotlin/README.md +13 -3
  204. package/kotlin/Sources/android/os/Build.kt +17 -0
  205. package/kotlin/Sources/android/os/Handler.kt +35 -0
  206. package/kotlin/Sources/android/os/Looper.kt +17 -0
  207. package/kotlin/Sources/android/os/MessageQueue.kt +56 -0
  208. package/kotlin/Sources/android/os/SystemClock.kt +14 -0
  209. package/kotlin/Sources/android/util/Log.kt +64 -0
  210. package/kotlin/Sources/java/security/MessageDigest.kt +114 -0
  211. package/kotlin/Sources/kotlin/text/StringFormat.kt +9 -0
  212. package/kotlin/Sources/org/json/JSON.kt +44 -0
  213. package/kotlin/fixtures/counter/Counter.d.kt.ts +1 -0
  214. package/kotlin/fixtures/counter/Counter.kt +10 -3
  215. package/kotlin/fixtures/counter/CounterApp.native.tsx +1 -1
  216. package/kotlin/fixtures/counter/vite.config.ts +2 -2
  217. package/kotlin/fixtures/headless-counter/Headless.kt +7 -6
  218. package/kotlin/fixtures/import-animation/Animation.d.kt.ts +7 -0
  219. package/kotlin/fixtures/import-animation/Animation.kt +80 -0
  220. package/kotlin/fixtures/import-animation/ImportAnimation.native.tsx +13 -0
  221. package/kotlin/fixtures/import-animation/README.md +24 -0
  222. package/kotlin/fixtures/import-animation/app/_layout.tsx +4 -0
  223. package/kotlin/fixtures/import-animation/app/index+spa.tsx +1 -0
  224. package/kotlin/fixtures/import-animation/app/routes.d.ts +18 -0
  225. package/kotlin/fixtures/import-animation/package.json +23 -0
  226. package/kotlin/fixtures/import-animation/tsconfig.json +20 -0
  227. package/kotlin/fixtures/import-animation/vite.config.ts +20 -0
  228. package/kotlin/fixtures/import-editor/Editor.d.kt.ts +7 -0
  229. package/kotlin/fixtures/import-editor/Editor.kt +59 -0
  230. package/kotlin/fixtures/import-editor/ImportEditor.native.tsx +23 -0
  231. package/kotlin/fixtures/import-editor/README.md +27 -0
  232. package/kotlin/fixtures/import-editor/app/_layout.tsx +4 -0
  233. package/kotlin/fixtures/import-editor/app/index+spa.tsx +1 -0
  234. package/kotlin/fixtures/import-editor/package.json +23 -0
  235. package/kotlin/fixtures/import-editor/tsconfig.json +20 -0
  236. package/kotlin/fixtures/import-editor/vite.config.ts +20 -0
  237. package/kotlin/fixtures/import-effects/Effects.d.kt.ts +7 -0
  238. package/kotlin/fixtures/import-effects/Effects.kt +87 -0
  239. package/kotlin/fixtures/import-effects/ImportEffects.native.tsx +13 -0
  240. package/kotlin/fixtures/import-effects/README.md +19 -0
  241. package/kotlin/fixtures/import-effects/app/_layout.tsx +4 -0
  242. package/kotlin/fixtures/import-effects/app/index+spa.tsx +1 -0
  243. package/kotlin/fixtures/import-effects/package.json +23 -0
  244. package/kotlin/fixtures/import-effects/tsconfig.json +20 -0
  245. package/kotlin/fixtures/import-effects/vite.config.ts +20 -0
  246. package/kotlin/fixtures/import-layout/ImportLayout.native.tsx +13 -0
  247. package/kotlin/fixtures/import-layout/Layout.d.kt.ts +7 -0
  248. package/kotlin/fixtures/import-layout/Layout.kt +72 -0
  249. package/kotlin/fixtures/import-layout/README.md +24 -0
  250. package/kotlin/fixtures/import-layout/app/_layout.tsx +4 -0
  251. package/kotlin/fixtures/import-layout/app/index+spa.tsx +1 -0
  252. package/kotlin/fixtures/import-layout/package.json +23 -0
  253. package/kotlin/fixtures/import-layout/tsconfig.json +20 -0
  254. package/kotlin/fixtures/import-layout/vite.config.ts +20 -0
  255. package/kotlin/fixtures/import-navigation/ImportNavigation.native.tsx +18 -0
  256. package/kotlin/fixtures/import-navigation/Navigation.d.kt.ts +7 -0
  257. package/kotlin/fixtures/import-navigation/Navigation.kt +104 -0
  258. package/kotlin/fixtures/import-navigation/README.md +31 -0
  259. package/kotlin/fixtures/import-navigation/app/_layout.tsx +4 -0
  260. package/kotlin/fixtures/import-navigation/app/index+spa.tsx +1 -0
  261. package/kotlin/fixtures/import-navigation/package.json +23 -0
  262. package/kotlin/fixtures/import-navigation/tsconfig.json +20 -0
  263. package/kotlin/fixtures/import-navigation/vite.config.ts +20 -0
  264. package/kotlin/fixtures/import-overlays/ImportOverlays.native.tsx +29 -0
  265. package/kotlin/fixtures/import-overlays/Overlays.d.kt.ts +7 -0
  266. package/kotlin/fixtures/import-overlays/Overlays.kt +93 -0
  267. package/kotlin/fixtures/import-overlays/README.md +34 -0
  268. package/kotlin/fixtures/import-overlays/app/_layout.tsx +4 -0
  269. package/kotlin/fixtures/import-overlays/app/index+spa.tsx +1 -0
  270. package/kotlin/fixtures/import-overlays/package.json +23 -0
  271. package/kotlin/fixtures/import-overlays/tsconfig.json +20 -0
  272. package/kotlin/fixtures/import-overlays/vite.config.ts +20 -0
  273. package/kotlin/fixtures/import-scroll/ImportScroll.native.tsx +13 -0
  274. package/kotlin/fixtures/import-scroll/README.md +23 -0
  275. package/kotlin/fixtures/import-scroll/Scroll.d.kt.ts +7 -0
  276. package/kotlin/fixtures/import-scroll/Scroll.kt +91 -0
  277. package/kotlin/fixtures/import-scroll/app/_layout.tsx +4 -0
  278. package/kotlin/fixtures/import-scroll/app/index+spa.tsx +1 -0
  279. package/kotlin/fixtures/import-scroll/package.json +23 -0
  280. package/kotlin/fixtures/import-scroll/tsconfig.json +20 -0
  281. package/kotlin/fixtures/import-scroll/vite.config.ts +20 -0
  282. package/kotlin/fixtures/import-selection/ImportSelection.native.tsx +44 -0
  283. package/kotlin/fixtures/import-selection/README.md +32 -0
  284. package/kotlin/fixtures/import-selection/Selection.d.kt.ts +8 -0
  285. package/kotlin/fixtures/import-selection/Selection.kt +78 -0
  286. package/kotlin/fixtures/import-selection/app/_layout.tsx +4 -0
  287. package/kotlin/fixtures/import-selection/app/index+spa.tsx +1 -0
  288. package/kotlin/fixtures/import-selection/package.json +23 -0
  289. package/kotlin/fixtures/import-selection/tsconfig.json +20 -0
  290. package/kotlin/fixtures/import-selection/vite.config.ts +20 -0
  291. package/kotlin/fixtures/import-surfaces/ImportSurfaces.native.tsx +13 -0
  292. package/kotlin/fixtures/import-surfaces/README.md +22 -0
  293. package/kotlin/fixtures/import-surfaces/Surfaces.d.kt.ts +7 -0
  294. package/kotlin/fixtures/import-surfaces/Surfaces.kt +64 -0
  295. package/kotlin/fixtures/import-surfaces/app/_layout.tsx +4 -0
  296. package/kotlin/fixtures/import-surfaces/app/index+spa.tsx +1 -0
  297. package/kotlin/fixtures/import-surfaces/package.json +23 -0
  298. package/kotlin/fixtures/import-surfaces/tsconfig.json +20 -0
  299. package/kotlin/fixtures/import-surfaces/vite.config.ts +20 -0
  300. package/kotlin/fixtures/import-values/ImportValues.native.tsx +35 -0
  301. package/kotlin/fixtures/import-values/README.md +23 -0
  302. package/kotlin/fixtures/import-values/Values.d.kt.ts +11 -0
  303. package/kotlin/fixtures/import-values/Values.kt +66 -0
  304. package/kotlin/fixtures/import-values/app/_layout.tsx +4 -0
  305. package/kotlin/fixtures/import-values/app/index+spa.tsx +1 -0
  306. package/kotlin/fixtures/import-values/package.json +23 -0
  307. package/kotlin/fixtures/import-values/tsconfig.json +20 -0
  308. package/kotlin/fixtures/import-values/vite.config.ts +20 -0
  309. package/kotlin/fixtures/kotlin-app-variants/BarBehavior.kt +38 -0
  310. package/kotlin/fixtures/kotlin-app-variants/BarVisibility.kt +36 -0
  311. package/kotlin/fixtures/kotlin-app-variants/Ime.kt +3 -1
  312. package/kotlin/fixtures/kotlin-app-variants/TransientInsets.kt +50 -0
  313. package/kotlin/fork/NOTICE +6 -1
  314. package/kotlin/fork/README.md +15 -7
  315. package/kotlin/fork/build.sh +2 -0
  316. package/kotlin/fork/compose-worker-globals.patch +186 -4
  317. package/kotlin/fork/fork.json +2 -2
  318. package/kotlin/fork/skiko-worker-clock.patch +12 -0
  319. package/kotlin/toolchain.json +12 -12
  320. package/kotlin/tools/expo-types.main.kts +115 -0
  321. package/package.json +17 -23
  322. package/scripts/dev-server-scanner.ts +92 -66
  323. package/skills/contrast/SKILL.md +2 -2
  324. package/skills/peach-debug/SKILL.md +1 -1
  325. package/skills/peach-perf/SKILL.md +1 -1
  326. package/skills/peach-setup/SKILL.md +2 -1
  327. package/skills/peach-test/SKILL.md +8 -0
  328. package/src/assetCatalogContract.ts +43 -1
  329. package/src/bridge-contract-input.ts +46 -19
  330. package/src/bridge-contract.ts +0 -1
  331. package/src/bundle-channel.mjs +197 -84
  332. package/src/bundleGlobalFooter.ts +38 -0
  333. package/src/capture-contract.ts +107 -33
  334. package/src/cloud-contract.ts +5 -1
  335. package/src/cloud.ts +5 -2
  336. package/src/config.ts +22 -3
  337. package/src/configured-bundle-producer.ts +1 -1
  338. package/src/desktop-host-delivery.ts +95 -1
  339. package/src/expo-kotlin-bundle.ts +29 -0
  340. package/src/expo-kotlin-host.ts +201 -0
  341. package/src/expo-swift-host.ts +96 -76
  342. package/src/expoKotlinContract.ts +70 -0
  343. package/src/expoSwiftContract.ts +4 -25
  344. package/src/home-paths.ts +7 -4
  345. package/src/host/bridge-host.ts +23 -3
  346. package/src/host/fetch-proxy-handler.ts +13 -0
  347. package/src/host/fetch-proxy-overrides.ts +11 -0
  348. package/src/host/replacement-module-handler.ts +5 -2
  349. package/src/index.ts +3 -1
  350. package/src/kotlin-host.ts +463 -19
  351. package/src/metro-plugin.ts +56 -28
  352. package/src/metro-production-bundle.ts +2 -2
  353. package/src/native-app-identity-project.ts +190 -0
  354. package/src/native-app-identity.ts +398 -34
  355. package/src/native-firebase-config.ts +69 -15
  356. package/src/native-seam-manifest.ts +6 -0
  357. package/src/one-widget-discovery.ts +2 -1
  358. package/src/openstep-plist.ts +107 -0
  359. package/src/react-native-host-modules.ts +4 -0
  360. package/src/runtime-assets.ts +2 -2
  361. package/src/sim-client.ts +2 -2
  362. package/src/swift-binary-modules.ts +3 -1
  363. package/src/swift-compile.ts +286 -38
  364. package/src/swift-distribution-contract.ts +4 -0
  365. package/src/swift-distribution.lock.json +123 -12
  366. package/src/swift-distribution.ts +7 -3
  367. package/src/swift-host.ts +298 -83
  368. package/src/swift-package-manifest.ts +29 -2
  369. package/src/swift-package.ts +180 -10
  370. package/src/swift-packages.ts +4 -0
  371. package/src/swift-standin-emit.ts +4 -1
  372. package/src/swift-standins.ts +274 -15
  373. package/src/swift-submit.ts +62 -7
  374. package/src/swift-toolchain.ts +6 -1
  375. package/src/swiftContract.ts +126 -3
  376. package/src/vite-plugin-one.ts +13 -16
  377. package/src/vite-plugin-swift.ts +4 -5
  378. package/src/vite-plugin.ts +360 -144
  379. package/src/vite.d.ts +1 -1
  380. package/src/worker-system-chunks.ts +221 -0
  381. package/src/worklet-executable.ts +348 -38
  382. package/src/worklets-babel.ts +1 -1
  383. package/src/xcode-project.ts +243 -127
  384. package/dist-lib/agent-identity.cjs +0 -39
  385. package/dist-lib/agent-sessions.cjs +0 -760
  386. package/dist-lib/cli-constants.cjs +0 -32
  387. package/dist-lib/render-mode.cjs +0 -44
  388. package/kotlin/AppSources/android/view/View.kt +0 -74
  389. package/kotlin/AppSources/android/widget/FrameLayout.kt +0 -6
  390. package/src/render-mode.ts +0 -15
  391. /package/kotlin/{AppSources → AndroidViewSources}/android/content/Context.kt +0 -0
  392. /package/kotlin/{AppSources → AndroidViewSources}/android/content/res/Configuration.kt +0 -0
  393. /package/kotlin/{AppSources → AndroidViewSources}/android/graphics/Color.kt +0 -0
  394. /package/kotlin/{AppSources → AndroidViewSources}/android/util/DisplayMetrics.kt +0 -0
  395. /package/kotlin/{AppSources → AndroidViewSources}/dev/peach/app/JavaFormat.kt +0 -0
  396. /package/kotlin/{AppSources → Sources}/java/lang/Runnable.kt +0 -0
@@ -0,0 +1,513 @@
1
+ @file:OptIn(kotlin.io.encoding.ExperimentalEncodingApi::class)
2
+
3
+ package dev.peach.expo
4
+
5
+ import android.content.Context
6
+ import android.content.res.Configuration
7
+ import android.content.res.Resources
8
+ import android.util.DisplayMetrics
9
+ import kotlin.math.roundToInt
10
+ import android.util.Log
11
+ import expo.modules.kotlin.AppContext
12
+ import expo.modules.kotlin.Promise
13
+ import expo.modules.kotlin.classcomponent.ClassDefinitionData
14
+ import expo.modules.kotlin.exception.CodedException
15
+ import expo.modules.kotlin.exception.FunctionCallException
16
+ import expo.modules.kotlin.exception.UnexpectedException
17
+ import expo.modules.kotlin.exception.exceptionDecorator
18
+ import expo.modules.kotlin.exception.toCodedException
19
+ import expo.modules.kotlin.functions.AnyFunction
20
+ import expo.modules.kotlin.functions.AsyncFunctionWithPromiseComponent
21
+ import expo.modules.kotlin.functions.SuspendFunctionComponent
22
+ import expo.modules.kotlin.functions.SyncFunctionComponent
23
+ import expo.modules.kotlin.functions.UntypedAsyncFunctionComponent
24
+ import expo.modules.kotlin.jni.JavaScriptTypedArray
25
+ import expo.modules.kotlin.jni.TypedArrayKind
26
+ import expo.modules.kotlin.modules.Module
27
+ import expo.modules.kotlin.modules.ModuleDefinitionData
28
+ import expo.modules.kotlin.objects.ObjectDefinitionData
29
+ import expo.modules.kotlin.sharedobjects.SharedObject
30
+ import expo.modules.kotlin.sharedobjects.SharedObjectClass
31
+ import expo.modules.kotlin.sharedobjects.SharedObjectId
32
+ import expo.modules.kotlin.sharedobjects.SharedRef
33
+ import expo.modules.kotlin.typedarray.RawTypedArrayHolder
34
+ import expo.modules.kotlin.views.ExpoView
35
+ import expo.modules.kotlin.views.ViewManagerDefinition
36
+ import kotlin.io.encoding.Base64
37
+ import kotlinx.coroutines.launch
38
+ import org.json.JSONArray
39
+ import org.json.JSONObject
40
+
41
+ // the module registry of one Kotlin/Wasm process (ModuleRegistry and
42
+ // ModuleHolder), answering the tenant's Expo requests over the Expo wire that
43
+ // ExpoWire in sootsim-swift's ExpoModulesCore owns. The tenant installer is
44
+ // shared with the Swift process; only this side differs.
45
+ object PeachExpoRuntime {
46
+ private class Holder(val name: String, val module: Module, val definition: ModuleDefinitionData) {
47
+ val functions: Map<String, AnyFunction> = functionsOf(definition.objectDefinition)
48
+ val classes: Map<String, ClassDefinitionData> = definition.classData.associateBy { it.name }
49
+ val views: Map<String, ViewManagerDefinition> = definition.views.associateBy { it.name }
50
+ }
51
+
52
+ // a view React Native created, with the props it last received
53
+ private class MountedView(val module: String, val definition: ViewManagerDefinition, val view: ExpoView) {
54
+ var props: Map<*, *>? = null
55
+ }
56
+
57
+ private val viewResources = Resources(Configuration(), DisplayMetrics(), emptyMap(), emptyMap(), emptyMap(), emptyMap())
58
+ private object PeachContext : Context() {
59
+ override val resources: Resources get() = viewResources
60
+ override val theme = Resources.Theme()
61
+ }
62
+
63
+ internal fun configureViewDevice(density: Float, locale: String, night: Boolean) {
64
+ val metrics = viewResources.displayMetrics.apply {
65
+ densityDpi = (density * DisplayMetrics.DENSITY_DEFAULT).roundToInt()
66
+ this.density = densityDpi / DisplayMetrics.DENSITY_DEFAULT.toFloat()
67
+ }
68
+ val configuration = Configuration().apply {
69
+ localeTag = locale
70
+ uiMode = Configuration.UI_MODE_TYPE_NORMAL or
71
+ if (night) Configuration.UI_MODE_NIGHT_YES else Configuration.UI_MODE_NIGHT_NO
72
+ }
73
+ viewResources.configuration.setTo(configuration)
74
+ }
75
+
76
+ private fun functionsOf(definition: ObjectDefinitionData): Map<String, AnyFunction> =
77
+ definition.syncFunctions + definition.asyncFunctions
78
+
79
+ private sealed class Settled {
80
+ class Value(val value: Any?) : Settled()
81
+ class Failure(val error: CodedException) : Settled()
82
+ }
83
+
84
+ private val appContext = AppContext()
85
+ private val sharedObjects = appContext.runtime.sharedObjectRegistry
86
+ // the typed arrays the current request decoded, in wire order
87
+ private val requestArrays = mutableListOf<JavaScriptTypedArray>()
88
+ private val holders = LinkedHashMap<String, Holder>()
89
+ private val mountedViews = HashMap<String, MountedView>()
90
+ private val events = mutableListOf<String>()
91
+ private val settled = mutableMapOf<Int, Settled>()
92
+ private val pending = mutableSetOf<Int>()
93
+ private var nextCall = 1
94
+ private var notify: (() -> Unit)? = null
95
+
96
+ fun register(modules: List<Pair<() -> Module, String?>>) {
97
+ for ((create, override) in modules) {
98
+ val module = create()
99
+ module.context = appContext
100
+ val definition = module.definition()
101
+ val name = override ?: definition.name
102
+ check(name !in holders) { "duplicate Expo module: $name" }
103
+ val holder = Holder(name, module, definition)
104
+ module.emitter = { event, body ->
105
+ events.add(wireObject(listOf("module" to name, "name" to event, "body" to body)))
106
+ notify?.invoke()
107
+ }
108
+ holders[name] = holder
109
+ for (data in definition.classData) {
110
+ if (data.ownerClass != Unit::class) sharedObjects.addClass(data.ownerClass, SharedObjectClass(name, data.name))
111
+ }
112
+ for (body in definition.onCreate) body()
113
+ }
114
+ sharedObjects.emitter = { native, event, payload ->
115
+ events.add(wireObject(listOf("object" to native, "name" to event, "args" to payload)))
116
+ notify?.invoke()
117
+ }
118
+ }
119
+
120
+ internal fun sharedObject(id: SharedObjectId): SharedObject = sharedObjects.toNativeObject(id)
121
+
122
+ internal fun mountedView(id: String): ExpoView =
123
+ mountedViews[id]?.view ?: throw CodedException("Unable to find the Expo view $id")
124
+
125
+ // the tenant's wake-up: an event or a settled call is waiting
126
+ fun listen(listener: () -> Unit) {
127
+ notify = listener
128
+ }
129
+
130
+ fun dispatch(payload: String): String {
131
+ requestArrays.clear()
132
+ val reply = try {
133
+ "\"ok\":true,\"value\":${request(decode(JSONArray("[$payload]").opt(0)))}"
134
+ } catch (error: Throwable) {
135
+ val coded = error.toCodedException()
136
+ "\"ok\":false,\"error\":{\"message\":${quote(coded.message ?: "")},\"code\":${quote(coded.code)}}"
137
+ }
138
+ // JSI typed arrays share the caller's memory: what a synchronous body
139
+ // wrote reaches the caller's array before the call returns
140
+ val writes = requestArrays.filter { it.dirty }
141
+ requestArrays.clear()
142
+ if (writes.isEmpty()) return "{$reply}"
143
+ return "{$reply,\"writes\":${writes.joinToString(",", "[", "]") { "[${it.ordinal},${quote(Base64.encode(it.bytes))}]" }}}"
144
+ }
145
+
146
+ private fun request(value: Any?): String {
147
+ val fields = value as? Map<*, *> ?: throw UnexpectedException("invalid Expo request")
148
+ val operation = fields["operation"] as? String
149
+ when (operation) {
150
+ "metadata" -> return metadata()
151
+ "events" -> {
152
+ val drained = events.joinToString(",", "[", "]")
153
+ events.clear()
154
+ return drained
155
+ }
156
+ "dropView" -> {
157
+ val mounted = mountedViews.remove(fields["id"] as? String ?: throw UnexpectedException("invalid Expo view"))
158
+ mounted?.definition?.onViewDestroys?.invoke(mounted.view)
159
+ return "null"
160
+ }
161
+ "poll" -> {
162
+ val id = (fields["id"] as? Double)?.toInt() ?: throw UnexpectedException("invalid Expo poll")
163
+ if (id in pending) return wireObject(listOf("pending" to id.toDouble()))
164
+ return reply(settled.remove(id) ?: throw UnexpectedException("unknown Expo call $id"))
165
+ }
166
+ }
167
+ if (operation == "release") {
168
+ val id = (fields["id"] as? Double)?.toInt() ?: throw UnexpectedException("invalid Expo release")
169
+ sharedObjects.delete(SharedObjectId(id))
170
+ return "null"
171
+ }
172
+ val name = fields["module"] as? String ?: throw UnexpectedException("invalid Expo request")
173
+ val holder = holders[name] ?: throw CodedException("ERR_MODULE_NOT_FOUND", "Module '$name' not found", null)
174
+ if ("view" in fields) return viewRequest(holder, operation, fields)
175
+ val className = fields["class"] as? String
176
+ if (className != null) return classRequest(holder, className, operation, fields)
177
+ val member = fields["member"] as? String ?: throw UnexpectedException("invalid Expo request")
178
+ val definition = holder.definition.objectDefinition
179
+ return when (operation) {
180
+ "get" -> {
181
+ definition.constants[member]?.let { constant ->
182
+ if (!constant.evaluated) {
183
+ constant.value = constant.getter?.body?.invoke(emptyArray())
184
+ constant.evaluated = true
185
+ }
186
+ return wireObject(listOf("value" to constant.value))
187
+ }
188
+ val getter = definition.properties[member]?.getter ?: return wireObject(listOf("value" to Undefined))
189
+ wireObject(listOf("value" to getter.body(emptyArray())))
190
+ }
191
+ "set" -> {
192
+ val setter = definition.properties[member]?.setter ?: return wireObject(listOf("value" to Undefined))
193
+ setter.body(setter.convertArgs(listOf(fields["value"])))
194
+ wireObject(listOf("value" to Undefined))
195
+ }
196
+ "call" -> {
197
+ val function = holder.functions[member]
198
+ ?: throw CodedException("ERR_FUNCTION_NOT_FOUND", "Function '$member' not found in module '$name'", null)
199
+ val args = fields["args"] as? List<*> ?: throw UnexpectedException("invalid Expo arguments")
200
+ call(name, function, args)
201
+ }
202
+ else -> throw UnexpectedException("invalid Expo operation $operation")
203
+ }
204
+ }
205
+
206
+ // a view's props, which create the view the first time, or one of its
207
+ // async functions, whose first argument is the view when it takes the owner
208
+ private fun viewRequest(holder: Holder, operation: String?, fields: Map<*, *>): String {
209
+ val id = fields["id"] as? String ?: throw UnexpectedException("invalid Expo view")
210
+ val viewName = fields["view"] as? String ?: throw UnexpectedException("invalid Expo view")
211
+ if (operation == "viewProps") {
212
+ configureViewDevice(
213
+ (fields["density"] as? Double)?.toFloat() ?: throw UnexpectedException("missing Expo view device density"),
214
+ fields["locale"] as? String ?: throw UnexpectedException("missing Expo view device locale"),
215
+ fields["night"] as? Boolean ?: throw UnexpectedException("missing Expo view device theme"),
216
+ )
217
+ val props = fields["props"] as? Map<*, *> ?: throw UnexpectedException("invalid Expo view props")
218
+ val mounted = mountedViews.getOrPut(id) {
219
+ val definition = holder.views[viewName] ?: throw UnexpectedException("View '$viewName' not found in module '${holder.name}'")
220
+ val view = definition.createView(PeachContext, appContext)
221
+ view.callbacks = definition.callbacks
222
+ view.emitter = { event, body ->
223
+ events.add(wireObject(listOf("view" to id, "module" to holder.name, "name" to event, "body" to body)))
224
+ notify?.invoke()
225
+ }
226
+ MountedView(holder.name, definition, view)
227
+ }
228
+ updateView(mounted, props)
229
+ return "null"
230
+ }
231
+ val mounted = mountedViews[id]?.takeIf { it.module == holder.name && it.definition.name == viewName }
232
+ ?: throw CodedException("Unable to find the $viewName view $id")
233
+ val member = fields["member"] as? String ?: throw UnexpectedException("invalid Expo request")
234
+ if (operation != "call") throw UnexpectedException("invalid Expo operation $operation")
235
+ val function = mounted.definition.asyncFunctions[member]
236
+ ?: throw CodedException("ERR_FUNCTION_NOT_FOUND", "Function '$member' not found in view '$viewName'", null)
237
+ val args = fields["args"] as? List<*> ?: throw UnexpectedException("invalid Expo arguments")
238
+ return call("${holder.name}.$viewName", function, if (function.takesOwner) listOf(mounted.view) + args else args)
239
+ }
240
+
241
+ // Fabric's first props hold only the props React set; later ones each
242
+ // prop that changed, a removed one as null. a failed prop is logged and
243
+ // the rest still apply, then the view hears that its props updated
244
+ private fun updateView(mounted: MountedView, props: Map<*, *>) {
245
+ val last = mounted.props
246
+ val changed = mounted.definition.props.values.filter { prop ->
247
+ if (last == null) prop.name in props else last[prop.name] != props[prop.name]
248
+ }
249
+ mounted.props = props
250
+ if (last != null && changed.isEmpty()) return
251
+ for (prop in changed) {
252
+ try {
253
+ prop.set(props[prop.name], mounted.view)
254
+ } catch (error: Throwable) {
255
+ Log.e("ExpoModulesCore", error.message ?: error.toString())
256
+ }
257
+ }
258
+ mounted.definition.onViewDidUpdateProps?.invoke(mounted.view)
259
+ }
260
+
261
+ // a Class member: the constructor, a static function, or an instance's
262
+ // function or property, whose first argument is `this` when it takes the owner
263
+ private fun classRequest(holder: Holder, className: String, operation: String?, fields: Map<*, *>): String {
264
+ val data = holder.classes[className] ?: throw UnexpectedException("Class '$className' not found in module '${holder.name}'")
265
+ val args = fields["args"] as? List<*> ?: emptyList<Any?>()
266
+ val owner = fields["this"]
267
+ if (operation == "construct") {
268
+ val constructor = data.constructor ?: return "[0,null]"
269
+ val native = exceptionDecorator({ FunctionCallException("constructor", "${holder.name}.$className", it) }) {
270
+ constructor.body(constructor.convertArgs(args))
271
+ }
272
+ if (native !is SharedObject) return "[0,null]"
273
+ if (native.runtime == null) native.runtime = appContext.runtime
274
+ val id = sharedObjects.add(native).value
275
+ return "[$id,${encode((native as? SharedRef<*>)?.nativeRefType)}]"
276
+ }
277
+ val member = fields["member"] as? String ?: throw UnexpectedException("invalid Expo request")
278
+ val withOwner = { function: AnyFunction, rest: List<Any?> -> if (function.takesOwner) listOf(owner) + rest else rest }
279
+ return when (operation) {
280
+ "get" -> {
281
+ val getter = data.objectDefinition.properties[member]?.getter ?: return wireObject(listOf("value" to Undefined))
282
+ wireObject(listOf("value" to getter.body(getter.convertArgs(withOwner(getter, emptyList())))))
283
+ }
284
+ "set" -> {
285
+ val setter = data.objectDefinition.properties[member]?.setter ?: return wireObject(listOf("value" to Undefined))
286
+ setter.body(setter.convertArgs(withOwner(setter, listOf(fields["value"]))))
287
+ wireObject(listOf("value" to Undefined))
288
+ }
289
+ "call" -> {
290
+ val function = if ("this" in fields) functionsOf(data.objectDefinition)[member]
291
+ else data.staticSyncFunctions[member] ?: data.staticAsyncFunctions[member]
292
+ function ?: throw CodedException("ERR_FUNCTION_NOT_FOUND", "Function '$member' not found in class '$className'", null)
293
+ call("${holder.name}.$className", function, withOwner(function, args))
294
+ }
295
+ else -> throw UnexpectedException("invalid Expo operation $operation")
296
+ }
297
+ }
298
+
299
+ private fun call(module: String, function: AnyFunction, args: List<Any?>): String {
300
+ if (function is SyncFunctionComponent) {
301
+ val value = exceptionDecorator({ FunctionCallException(function.name, module, it) }) {
302
+ function.body(function.convertArgs(args))
303
+ }
304
+ return wireObject(listOf("value" to value))
305
+ }
306
+ val id = nextCall++
307
+ pending.add(id)
308
+ val settle = { result: Settled ->
309
+ if (pending.remove(id)) {
310
+ settled[id] = result
311
+ notify?.invoke()
312
+ }
313
+ }
314
+ val promise = object : Promise {
315
+ override fun resolve(value: Any?) = settle(Settled.Value(value))
316
+ override fun reject(code: String?, message: String?, cause: Throwable?) =
317
+ settle(Settled.Failure(CodedException(code ?: "UnknownCode", message, cause)))
318
+ }
319
+ val fail = { error: Throwable ->
320
+ settle(Settled.Failure(FunctionCallException(function.name, module, error.toCodedException())))
321
+ }
322
+ // android runs async bodies on its modules queue, after the call returns
323
+ appContext.modulesQueue.launch {
324
+ try {
325
+ val converted = function.convertArgs(args)
326
+ when (function) {
327
+ is UntypedAsyncFunctionComponent -> promise.resolve(function.body(converted))
328
+ is AsyncFunctionWithPromiseComponent -> function.body(converted, promise)
329
+ is SuspendFunctionComponent -> promise.resolve(function.body(this, converted))
330
+ }
331
+ } catch (error: Throwable) {
332
+ fail(error)
333
+ }
334
+ }
335
+ return wireObject(listOf("pending" to id.toDouble()))
336
+ }
337
+
338
+ private fun reply(result: Settled): String = when (result) {
339
+ is Settled.Value -> wireObject(listOf("value" to result.value))
340
+ is Settled.Failure -> throw result.error
341
+ }
342
+
343
+ private fun functionMetadata(functions: Map<String, AnyFunction>): List<Any?> =
344
+ functions.keys.sorted().map { name ->
345
+ val function = functions.getValue(name)
346
+ Wire(wireObject(listOf("name" to name, "async" to (function !is SyncFunctionComponent), "enumerable" to function.isEnumerable)))
347
+ }
348
+
349
+ private fun metadata(): String = holders.keys.sorted().map { holders.getValue(it) }.map { holder ->
350
+ val definition = holder.definition.objectDefinition
351
+ val properties = definition.constants.keys.sorted().map { Wire(wireObject(listOf("name" to it, "get" to true, "set" to false))) } +
352
+ definition.properties.keys.sorted().map { name ->
353
+ val property = definition.properties.getValue(name)
354
+ Wire(wireObject(listOf("name" to name, "get" to (property.getter != null), "set" to (property.setter != null))))
355
+ }
356
+ Wire(wireObject(listOf(
357
+ "name" to holder.name,
358
+ "definitionName" to holder.definition.name,
359
+ "constants" to Wire(wireObject(definition.legacyConstantsProvider().entries.sortedBy { it.key }.map { it.key to it.value })),
360
+ "functions" to functionMetadata(holder.functions),
361
+ "properties" to properties,
362
+ "events" to definition.eventNames,
363
+ "views" to holder.definition.views.map { view ->
364
+ Wire(wireObject(listOf(
365
+ "name" to view.name,
366
+ "props" to view.props.keys.sorted(),
367
+ "events" to view.callbacks,
368
+ "functions" to functionMetadata(view.asyncFunctions),
369
+ )))
370
+ },
371
+ "classes" to holder.definition.classData.sortedBy { it.name }.map { data ->
372
+ val instance = data.objectDefinition
373
+ Wire(wireObject(listOf(
374
+ "name" to data.name,
375
+ "functions" to functionMetadata(functionsOf(instance)),
376
+ "staticFunctions" to functionMetadata(data.staticSyncFunctions + data.staticAsyncFunctions),
377
+ "properties" to instance.properties.keys.sorted().map { name ->
378
+ val property = instance.properties.getValue(name)
379
+ Wire(wireObject(listOf("name" to name, "get" to (property.getter != null), "set" to (property.setter != null))))
380
+ },
381
+ )))
382
+ },
383
+ )))
384
+ }.let { encode(it) }
385
+
386
+ // javascript undefined, and an already encoded wire value
387
+ private object Undefined
388
+ private class Wire(val json: String)
389
+
390
+ private fun decode(value: Any?): Any? = when (value) {
391
+ null, JSONObject.NULL -> null
392
+ is Boolean, is String -> value
393
+ is Number -> value.toDouble()
394
+ is JSONArray -> List(value.length()) { decode(value.opt(it)) }
395
+ is JSONObject -> {
396
+ val keys = value.keys().asSequence().toList()
397
+ if (keys.size != 1) throw UnexpectedException("invalid Expo wire value")
398
+ val raw = value.opt(keys[0])
399
+ when (keys[0]) {
400
+ "k" -> {
401
+ val pairs = raw as? JSONArray ?: throw UnexpectedException("invalid Expo wire object")
402
+ val fields = LinkedHashMap<String, Any?>()
403
+ for (index in 0 until pairs.length()) {
404
+ val pair = pairs.opt(index) as? JSONArray ?: throw UnexpectedException("invalid Expo wire object")
405
+ fields[pair.opt(0) as? String ?: throw UnexpectedException("invalid Expo wire object")] = decode(pair.opt(1))
406
+ }
407
+ fields
408
+ }
409
+ // a missing argument reaches the converters as null
410
+ "u" -> null
411
+ "n" -> when (raw) {
412
+ "-0" -> -0.0
413
+ "NaN" -> Double.NaN
414
+ "Infinity" -> Double.POSITIVE_INFINITY
415
+ "-Infinity" -> Double.NEGATIVE_INFINITY
416
+ else -> throw UnexpectedException("invalid Expo wire number")
417
+ }
418
+ "ab" -> Base64.decode(raw as? String ?: throw UnexpectedException("invalid Expo wire bytes"))
419
+ "t" -> {
420
+ val pair = raw as? JSONArray ?: throw UnexpectedException("invalid Expo wire typed array")
421
+ val kind = TypedArrayKind.entries.firstOrNull { it.name == pair.opt(0) }
422
+ ?: throw UnexpectedException("invalid Expo wire typed array")
423
+ val bytes = Base64.decode(pair.opt(1) as? String ?: throw UnexpectedException("invalid Expo wire bytes"))
424
+ val offset = pair.opt(2) as? Number ?: throw UnexpectedException("invalid Expo wire typed array")
425
+ val byteOffset = offset.toInt()
426
+ val width = JavaScriptTypedArray.elementSize(kind)
427
+ if (pair.length() != 3 || offset.toDouble() != byteOffset.toDouble() || byteOffset < 0 || byteOffset % width != 0 || bytes.size % width != 0)
428
+ throw UnexpectedException("invalid Expo wire typed array")
429
+ JavaScriptTypedArray(kind, bytes, requestArrays.size, byteOffset).also { requestArrays.add(it) }
430
+ }
431
+ "so" -> SharedObjectId((raw as? Number)?.toInt() ?: throw UnexpectedException("invalid Expo shared object"))
432
+ else -> throw UnexpectedException("Expo wire value '${keys[0]}' is not supported in Kotlin yet")
433
+ }
434
+ }
435
+ else -> throw UnexpectedException("invalid Expo wire value")
436
+ }
437
+
438
+ private fun wireObject(fields: List<Pair<String, Any?>>): String =
439
+ fields.joinToString(",", "{\"k\":[", "]}") { (key, value) -> "[${quote(key)},${encode(value)}]" }
440
+
441
+ // JSTypeConverterProvider's conversions to javascript
442
+ private fun encode(value: Any?): String = when (value) {
443
+ null, Unit -> "null"
444
+ Undefined -> "{\"u\":0}"
445
+ is Wire -> value.json
446
+ is String -> quote(value)
447
+ is Boolean -> value.toString()
448
+ is Char -> quote(value.toString())
449
+ is Number -> number(value.toDouble())
450
+ is Map<*, *> -> wireObject(value.entries.map { (key, item) -> key.toString() to item })
451
+ is Collection<*> -> value.joinToString(",", "[", "]") { encode(it) }
452
+ is Array<*> -> value.joinToString(",", "[", "]") { encode(it) }
453
+ is Pair<*, *> -> "[${encode(value.first)},${encode(value.second)}]"
454
+ is IntArray -> value.joinToString(",", "[", "]") { number(it.toDouble()) }
455
+ is LongArray -> value.joinToString(",", "[", "]") { number(it.toDouble()) }
456
+ is DoubleArray -> value.joinToString(",", "[", "]") { number(it) }
457
+ is FloatArray -> value.joinToString(",", "[", "]") { number(it.toDouble()) }
458
+ is BooleanArray -> value.joinToString(",", "[", "]") { it.toString() }
459
+ // a ByteArray reaches JavaScript as a Uint8Array, as on Android
460
+ is ByteArray -> typedArray(TypedArrayKind.Uint8Array, value, 0)
461
+ is JavaScriptTypedArray -> typedArray(value.kind, value.bytes, value.byteOffset)
462
+ is RawTypedArrayHolder -> typedArray(value.rawArray.kind, value.rawArray.bytes, value.rawArray.byteOffset)
463
+ is SharedObject -> sharedObjectWire(value)
464
+ else -> {
465
+ val converted = PeachExpoTypes.toJS(value)
466
+ if (converted === PeachExpoTypes.Unconverted)
467
+ throw UnexpectedException("Cannot convert '${value::class.simpleName}' to a JavaScript value")
468
+ encode(converted)
469
+ }
470
+ }
471
+
472
+ private fun typedArray(kind: TypedArrayKind, bytes: ByteArray, byteOffset: Int): String =
473
+ "{\"t\":[${quote(kind.name)},${quote(Base64.encode(bytes))},$byteOffset]}"
474
+
475
+ // a native object JavaScript already holds is that object; a new one gets an
476
+ // instance of its exported class, or a plain object for an unexported
477
+ // SharedRef, as convertSharedObject does
478
+ private fun sharedObjectWire(native: SharedObject): String {
479
+ val exported = sharedObjects.classOf(native)
480
+ if (native.sharedObjectId.value == 0) {
481
+ if (exported == null && native !is SharedRef<*>)
482
+ throw UnexpectedException("Could not find JavaScript class for shared object: $native")
483
+ if (native.runtime == null) native.runtime = appContext.runtime
484
+ sharedObjects.add(native)
485
+ }
486
+ val refType = (native as? SharedRef<*>)?.nativeRefType
487
+ return "{\"so\":[${native.sharedObjectId.value},${encode(exported?.module)},${encode(exported?.name)},${encode(refType)}]}"
488
+ }
489
+
490
+ private fun number(value: Double): String = when {
491
+ value.isNaN() -> "{\"n\":\"NaN\"}"
492
+ value == Double.POSITIVE_INFINITY -> "{\"n\":\"Infinity\"}"
493
+ value == Double.NEGATIVE_INFINITY -> "{\"n\":\"-Infinity\"}"
494
+ value == 0.0 && 1.0 / value < 0 -> "{\"n\":\"-0\"}"
495
+ value == value.toLong().toDouble() && kotlin.math.abs(value) < 1e15 -> value.toLong().toString()
496
+ else -> value.toString()
497
+ }
498
+
499
+ private fun quote(value: String): String {
500
+ val out = StringBuilder("\"")
501
+ for (c in value) {
502
+ when (c) {
503
+ '"' -> out.append("\\\"")
504
+ '\\' -> out.append("\\\\")
505
+ '\n' -> out.append("\\n")
506
+ '\r' -> out.append("\\r")
507
+ '\t' -> out.append("\\t")
508
+ else -> if (c.code < 0x20) out.append("\\u").append(c.code.toString(16).padStart(4, '0')) else out.append(c)
509
+ }
510
+ }
511
+ return out.append('"').toString()
512
+ }
513
+ }