@dimina-kit/devtools 0.4.0 → 0.5.0-dev.20260908120530

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 (507) hide show
  1. package/README.md +51 -660
  2. package/dist/main/api.d.ts +2 -1
  3. package/dist/main/app/app.d.ts +41 -12
  4. package/dist/main/app/app.js +299 -514
  5. package/dist/main/app/bootstrap.d.ts +8 -5
  6. package/dist/main/app/bootstrap.js +15 -15
  7. package/dist/main/app/editor-view.d.ts +21 -0
  8. package/dist/main/app/editor-view.js +71 -0
  9. package/dist/main/app/global-mirrors.d.ts +23 -0
  10. package/dist/main/app/global-mirrors.js +37 -0
  11. package/dist/main/app/host-sidebar-default.d.ts +19 -0
  12. package/dist/main/app/host-sidebar-default.js +44 -0
  13. package/dist/main/app/lifecycle.d.ts +12 -1
  14. package/dist/main/app/lifecycle.js +27 -1
  15. package/dist/main/app/menu-setup.d.ts +13 -0
  16. package/dist/main/app/menu-setup.js +49 -0
  17. package/dist/main/app/native-overview.d.ts +1 -1
  18. package/dist/main/app/open-project-ipc.d.ts +13 -0
  19. package/dist/main/app/open-project-ipc.js +21 -0
  20. package/dist/main/app/project-list-surface.d.ts +31 -0
  21. package/dist/main/app/project-list-surface.js +20 -0
  22. package/dist/main/app/project-window.d.ts +36 -0
  23. package/dist/main/app/project-window.js +93 -0
  24. package/dist/main/app/servers.d.ts +17 -0
  25. package/dist/main/app/servers.js +60 -0
  26. package/dist/main/app/ui-extension-targets.d.ts +42 -0
  27. package/dist/main/app/ui-extension-targets.js +101 -0
  28. package/dist/main/app/window-close-reveal.harness.d.ts +34 -0
  29. package/dist/main/app/window-close-reveal.harness.js +65 -0
  30. package/dist/main/app/window-events.d.ts +15 -0
  31. package/dist/main/app/window-events.js +45 -0
  32. package/dist/main/app/window-runtime-services.d.ts +41 -0
  33. package/dist/main/app/window-runtime-services.js +197 -0
  34. package/dist/main/app/workbench-modules.d.ts +24 -0
  35. package/dist/main/app/workbench-modules.js +51 -0
  36. package/dist/main/app/workbench-window.d.ts +85 -0
  37. package/dist/main/app/workbench-window.js +310 -0
  38. package/dist/main/index.bundle.js +11712 -9986
  39. package/dist/main/index.js +49 -1
  40. package/dist/main/ipc/app-lifecycle.d.ts +1 -31
  41. package/dist/main/ipc/app-lifecycle.js +1 -53
  42. package/dist/main/ipc/app.d.ts +4 -1
  43. package/dist/main/ipc/app.js +4 -3
  44. package/dist/main/ipc/bridge-router.d.ts +19 -116
  45. package/dist/main/ipc/bridge-router.js +28 -2128
  46. package/dist/main/ipc/device-picker.d.ts +16 -0
  47. package/dist/main/ipc/device-picker.js +27 -0
  48. package/dist/main/ipc/index.d.ts +5 -0
  49. package/dist/main/ipc/index.js +5 -0
  50. package/dist/main/ipc/internal-devtools.d.ts +16 -0
  51. package/dist/main/ipc/internal-devtools.js +10 -0
  52. package/dist/main/ipc/page-scroll.d.ts +1 -13
  53. package/dist/main/ipc/page-scroll.js +1 -16
  54. package/dist/main/ipc/popover.d.ts +4 -1
  55. package/dist/main/ipc/popover.js +26 -19
  56. package/dist/main/ipc/project-create.d.ts +16 -0
  57. package/dist/main/ipc/project-create.js +27 -0
  58. package/dist/main/ipc/project-fs.d.ts +38 -3
  59. package/dist/main/ipc/project-fs.js +53 -22
  60. package/dist/main/ipc/projects.d.ts +3 -3
  61. package/dist/main/ipc/projects.js +47 -11
  62. package/dist/main/ipc/session-listener-bag.d.ts +1 -40
  63. package/dist/main/ipc/session-listener-bag.js +1 -39
  64. package/dist/main/ipc/session.d.ts +4 -1
  65. package/dist/main/ipc/session.js +26 -26
  66. package/dist/main/ipc/settings.d.ts +4 -1
  67. package/dist/main/ipc/settings.js +17 -19
  68. package/dist/main/ipc/simulator-module.d.ts +9 -3
  69. package/dist/main/ipc/simulator-module.js +13 -10
  70. package/dist/main/ipc/simulator.d.ts +4 -1
  71. package/dist/main/ipc/simulator.js +12 -18
  72. package/dist/main/ipc/tooltip.d.ts +14 -0
  73. package/dist/main/ipc/tooltip.js +29 -0
  74. package/dist/main/ipc/views.d.ts +19 -2
  75. package/dist/main/ipc/views.js +76 -8
  76. package/dist/main/runtime/devtools-backend-before-quit.harness.d.ts +43 -0
  77. package/dist/main/runtime/devtools-backend-before-quit.harness.js +87 -0
  78. package/dist/main/runtime/devtools-backend.js +60 -4
  79. package/dist/main/runtime/devtools-runtime-mock.harness.d.ts +21 -0
  80. package/dist/main/runtime/devtools-runtime-mock.harness.js +313 -0
  81. package/dist/main/runtime/miniapp-runtime.d.ts +91 -22
  82. package/dist/main/services/app-services.d.ts +62 -0
  83. package/dist/main/services/app-services.js +64 -0
  84. package/dist/main/services/automation/connection-target.d.ts +36 -0
  85. package/dist/main/services/automation/connection-target.js +29 -0
  86. package/dist/main/services/automation/console-bridge.d.ts +28 -0
  87. package/dist/main/services/automation/console-bridge.js +91 -0
  88. package/dist/main/services/automation/handlers/app.js +4 -4
  89. package/dist/main/services/automation/handlers/element.js +55 -21
  90. package/dist/main/services/automation/index.d.ts +12 -1
  91. package/dist/main/services/automation/index.js +54 -133
  92. package/dist/main/services/cdp-session/index.d.ts +87 -0
  93. package/dist/main/services/cdp-session/index.js +173 -0
  94. package/dist/main/services/compile-standby.js +4 -0
  95. package/dist/main/services/console-forward/cdp-inject.d.ts +18 -0
  96. package/dist/main/services/console-forward/cdp-inject.js +30 -0
  97. package/dist/main/services/console-forward/global-console-mirror.d.ts +33 -0
  98. package/dist/main/services/console-forward/global-console-mirror.js +71 -0
  99. package/dist/main/services/console-forward/global-diagnostics-mirror.d.ts +33 -0
  100. package/dist/main/services/console-forward/global-diagnostics-mirror.js +73 -0
  101. package/dist/main/services/console-forward/index.d.ts +1 -46
  102. package/dist/main/services/console-forward/index.js +1 -185
  103. package/dist/main/services/console-forward/internal-log.d.ts +2 -0
  104. package/dist/main/services/console-forward/internal-log.js +2 -0
  105. package/dist/main/services/console-forward/open-gated-relay.d.ts +67 -0
  106. package/dist/main/services/console-forward/open-gated-relay.js +55 -0
  107. package/dist/main/services/default-adapter.js +1 -1
  108. package/dist/main/services/diagnostics/index.d.ts +1 -48
  109. package/dist/main/services/diagnostics/index.js +1 -60
  110. package/dist/main/services/dimina-resource-server.d.ts +1 -6
  111. package/dist/main/services/dimina-resource-server.js +1 -98
  112. package/dist/main/services/elements-forward/index.d.ts +132 -15
  113. package/dist/main/services/elements-forward/index.js +155 -304
  114. package/dist/main/services/fs-bridge-protocol.d.ts +61 -0
  115. package/dist/main/services/fs-bridge-protocol.js +34 -0
  116. package/dist/main/services/fs-watch-sse.d.ts +40 -0
  117. package/dist/main/services/fs-watch-sse.js +113 -0
  118. package/dist/main/services/http-json.d.ts +9 -0
  119. package/dist/main/services/http-json.js +6 -0
  120. package/dist/main/services/layout/index.d.ts +11 -0
  121. package/dist/main/services/layout/index.js +23 -0
  122. package/dist/main/services/mcp/connection-owner.d.ts +38 -0
  123. package/dist/main/services/mcp/connection-owner.js +79 -0
  124. package/dist/main/services/mcp/index.d.ts +3 -1
  125. package/dist/main/services/mcp/index.js +4 -3
  126. package/dist/main/services/mcp/opened-project.d.ts +88 -0
  127. package/dist/main/services/mcp/opened-project.js +60 -0
  128. package/dist/main/services/mcp/server.d.ts +2 -1
  129. package/dist/main/services/mcp/server.js +29 -4
  130. package/dist/main/services/mcp/target-manager.d.ts +71 -23
  131. package/dist/main/services/mcp/target-manager.js +269 -138
  132. package/dist/main/services/mcp/target-selection.d.ts +47 -0
  133. package/dist/main/services/mcp/target-selection.js +71 -0
  134. package/dist/main/services/mcp/tools/context-tools.js +20 -5
  135. package/dist/main/services/mcp/tools/project-tools.d.ts +72 -0
  136. package/dist/main/services/mcp/tools/project-tools.js +133 -0
  137. package/dist/main/services/module.d.ts +13 -5
  138. package/dist/main/services/network-forward/body-cache.d.ts +72 -0
  139. package/dist/main/services/network-forward/body-cache.js +179 -0
  140. package/dist/main/services/network-forward/frontend-dispatch.d.ts +91 -0
  141. package/dist/main/services/network-forward/frontend-dispatch.js +148 -0
  142. package/dist/main/services/network-forward/global-body-gate.d.ts +46 -0
  143. package/dist/main/services/network-forward/global-body-gate.js +93 -0
  144. package/dist/main/services/network-forward/global-mirror-test-fixtures.d.ts +48 -0
  145. package/dist/main/services/network-forward/global-mirror-test-fixtures.js +96 -0
  146. package/dist/main/services/network-forward/http.d.ts +47 -0
  147. package/dist/main/services/network-forward/http.js +162 -0
  148. package/dist/main/services/network-forward/index.d.ts +135 -15
  149. package/dist/main/services/network-forward/index.js +854 -224
  150. package/dist/main/services/network-forward/request-ids.d.ts +5 -0
  151. package/dist/main/services/network-forward/request-ids.js +5 -0
  152. package/dist/main/services/network-forward/user-facing.d.ts +21 -0
  153. package/dist/main/services/network-forward/user-facing.js +50 -0
  154. package/dist/main/services/network-forward/websocket.d.ts +36 -0
  155. package/dist/main/services/network-forward/websocket.js +120 -0
  156. package/dist/main/services/notifications/renderer-notifier.d.ts +71 -9
  157. package/dist/main/services/notifications/renderer-notifier.js +45 -8
  158. package/dist/main/services/projects/create-project-service.js +19 -5
  159. package/dist/main/services/projects/local-provider.js +8 -4
  160. package/dist/main/services/projects/project-repository.d.ts +59 -7
  161. package/dist/main/services/projects/project-repository.js +285 -51
  162. package/dist/main/services/projects/types.d.ts +44 -34
  163. package/dist/main/services/render-inspect/index.d.ts +12 -2
  164. package/dist/main/services/render-inspect/index.js +21 -102
  165. package/dist/main/services/safe-area/index.d.ts +21 -6
  166. package/dist/main/services/safe-area/index.js +92 -46
  167. package/dist/main/services/service-host-pool/pool.d.ts +1 -196
  168. package/dist/main/services/service-host-pool/pool.js +1 -363
  169. package/dist/main/services/settings/index.d.ts +7 -1
  170. package/dist/main/services/settings/index.js +22 -2
  171. package/dist/main/services/simulator/custom-apis.d.ts +1 -42
  172. package/dist/main/services/simulator/custom-apis.js +1 -46
  173. package/dist/main/services/simulator/referer.d.ts +1 -9
  174. package/dist/main/services/simulator/referer.js +1 -19
  175. package/dist/main/services/simulator/ui-extensions.d.ts +21 -0
  176. package/dist/main/services/simulator/ui-extensions.js +176 -0
  177. package/dist/main/services/simulator-appdata/index.d.ts +6 -0
  178. package/dist/main/services/simulator-appdata/index.js +21 -2
  179. package/dist/main/services/simulator-current-page/index.d.ts +5 -3
  180. package/dist/main/services/simulator-current-page/index.js +8 -1
  181. package/dist/main/services/simulator-storage/index.d.ts +34 -5
  182. package/dist/main/services/simulator-storage/index.js +73 -71
  183. package/dist/main/services/simulator-storage/service-storage-ops.d.ts +1 -39
  184. package/dist/main/services/simulator-storage/service-storage-ops.js +1 -66
  185. package/dist/main/services/simulator-storage/window-scope.d.ts +20 -0
  186. package/dist/main/services/simulator-storage/window-scope.js +20 -0
  187. package/dist/main/services/simulator-temp-files/disk.d.ts +1 -54
  188. package/dist/main/services/simulator-temp-files/disk.js +1 -237
  189. package/dist/main/services/simulator-temp-files/fs-channels.d.ts +1 -68
  190. package/dist/main/services/simulator-temp-files/fs-channels.js +1 -107
  191. package/dist/main/services/simulator-temp-files/index.d.ts +1 -32
  192. package/dist/main/services/simulator-temp-files/index.js +1 -181
  193. package/dist/main/services/simulator-temp-files/request-handler.d.ts +1 -29
  194. package/dist/main/services/simulator-temp-files/request-handler.js +1 -177
  195. package/dist/main/services/simulator-temp-files/resolver.d.ts +1 -21
  196. package/dist/main/services/simulator-temp-files/resolver.js +1 -21
  197. package/dist/main/services/simulator-temp-files/store.d.ts +1 -13
  198. package/dist/main/services/simulator-temp-files/store.js +1 -23
  199. package/dist/main/services/update/github-release-checker.js +22 -3
  200. package/dist/main/services/update/update-manager.d.ts +21 -4
  201. package/dist/main/services/update/update-manager.js +55 -11
  202. package/dist/main/services/views/clear-console-filter.d.ts +44 -0
  203. package/dist/main/services/views/clear-console-filter.js +86 -0
  204. package/dist/main/services/views/console-filter-test-fixtures.d.ts +94 -0
  205. package/dist/main/services/views/console-filter-test-fixtures.js +82 -0
  206. package/dist/main/services/views/console-filter.d.ts +94 -0
  207. package/dist/main/services/views/console-filter.js +190 -0
  208. package/dist/main/services/views/devtools-tabs.d.ts +24 -4
  209. package/dist/main/services/views/devtools-tabs.js +40 -21
  210. package/dist/main/services/views/frontend-bootstrap-gate.d.ts +59 -0
  211. package/dist/main/services/views/frontend-bootstrap-gate.js +117 -0
  212. package/dist/main/services/views/host-dialog-port-channel.d.ts +18 -0
  213. package/dist/main/services/views/host-dialog-port-channel.js +15 -0
  214. package/dist/main/services/views/host-dialog-session-runtime.d.ts +10 -0
  215. package/dist/main/services/views/host-dialog-session-runtime.js +17 -0
  216. package/dist/main/services/views/host-dialog-view.d.ts +95 -0
  217. package/dist/main/services/views/host-dialog-view.js +147 -0
  218. package/dist/main/services/views/host-sidebar-port-channel.d.ts +18 -0
  219. package/dist/main/services/views/host-sidebar-port-channel.js +15 -0
  220. package/dist/main/services/views/host-sidebar-session-runtime.d.ts +10 -0
  221. package/dist/main/services/views/host-sidebar-session-runtime.js +17 -0
  222. package/dist/main/services/views/host-sidebar-view.d.ts +59 -0
  223. package/dist/main/services/views/host-sidebar-view.js +44 -0
  224. package/dist/main/services/views/host-slot-port-channel.d.ts +124 -0
  225. package/dist/main/services/views/host-slot-port-channel.js +289 -0
  226. package/dist/main/services/views/host-slot-session-runtime.d.ts +40 -0
  227. package/dist/main/services/views/host-slot-session-runtime.js +50 -0
  228. package/dist/main/services/views/host-slot-view.d.ts +109 -0
  229. package/dist/main/services/views/host-slot-view.js +147 -0
  230. package/dist/main/services/views/host-toolbar-port-channel.d.ts +6 -91
  231. package/dist/main/services/views/host-toolbar-port-channel.js +10 -243
  232. package/dist/main/services/views/host-toolbar-session-runtime.d.ts +5 -30
  233. package/dist/main/services/views/host-toolbar-session-runtime.js +9 -47
  234. package/dist/main/services/views/host-toolbar-view.d.ts +34 -7
  235. package/dist/main/services/views/host-toolbar-view.js +30 -237
  236. package/dist/main/services/views/managed-web-contents-view.d.ts +48 -0
  237. package/dist/main/services/views/managed-web-contents-view.js +143 -0
  238. package/dist/main/services/views/miniapp-partition.d.ts +1 -89
  239. package/dist/main/services/views/miniapp-partition.js +1 -158
  240. package/dist/main/services/views/native-simulator-devtools-host.js +206 -68
  241. package/dist/main/services/views/native-simulator-view.d.ts +6 -0
  242. package/dist/main/services/views/native-simulator-view.js +47 -40
  243. package/dist/main/services/views/native-view-tree.d.ts +25 -0
  244. package/dist/main/services/views/native-view-tree.js +69 -0
  245. package/dist/main/services/views/overlay-panels-view.d.ts +63 -4
  246. package/dist/main/services/views/overlay-panels-view.js +257 -108
  247. package/dist/main/services/views/placement-reconciler.d.ts +18 -18
  248. package/dist/main/services/views/placement-reconciler.js +91 -29
  249. package/dist/main/services/views/placement-test-driver.d.ts +27 -0
  250. package/dist/main/services/views/placement-test-driver.js +29 -0
  251. package/dist/main/services/views/refresh-styles.d.ts +10 -0
  252. package/dist/main/services/views/refresh-styles.js +57 -0
  253. package/dist/main/services/views/simulator-session-policy.d.ts +1 -27
  254. package/dist/main/services/views/simulator-session-policy.js +1 -98
  255. package/dist/main/services/views/view-manager-devtools-host-test-fixtures.d.ts +76 -0
  256. package/dist/main/services/views/view-manager-devtools-host-test-fixtures.js +184 -0
  257. package/dist/main/services/views/view-manager.d.ts +69 -88
  258. package/dist/main/services/views/view-manager.js +62 -7
  259. package/dist/main/services/views/workbench-view.d.ts +2 -0
  260. package/dist/main/services/views/workbench-view.js +104 -17
  261. package/dist/main/services/window-contexts/context-router.d.ts +36 -0
  262. package/dist/main/services/window-contexts/context-router.js +55 -0
  263. package/dist/main/services/workbench-coi-host.d.ts +52 -0
  264. package/dist/main/services/workbench-coi-host.js +227 -0
  265. package/dist/main/services/workbench-coi-server.d.ts +11 -0
  266. package/dist/main/services/workbench-coi-server.js +126 -126
  267. package/dist/main/services/workbench-coi-shutdown.d.ts +40 -0
  268. package/dist/main/services/workbench-coi-shutdown.js +69 -0
  269. package/dist/main/services/workbench-coi-static.d.ts +23 -0
  270. package/dist/main/services/workbench-coi-static.js +87 -0
  271. package/dist/main/services/workbench-context.d.ts +88 -8
  272. package/dist/main/services/workbench-context.js +54 -6
  273. package/dist/main/services/workspace/compile-log-buffer.d.ts +40 -0
  274. package/dist/main/services/workspace/compile-log-buffer.js +41 -0
  275. package/dist/main/services/workspace/compile-mode-adapter.d.ts +18 -0
  276. package/dist/main/services/workspace/compile-mode-adapter.js +57 -0
  277. package/dist/main/services/workspace/compile-mode-store.d.ts +25 -0
  278. package/dist/main/services/workspace/compile-mode-store.js +71 -0
  279. package/dist/main/services/workspace/open-project-guards.d.ts +16 -0
  280. package/dist/main/services/workspace/open-project-guards.js +42 -0
  281. package/dist/main/services/workspace/rebuild-status.d.ts +37 -0
  282. package/dist/main/services/workspace/rebuild-status.js +36 -0
  283. package/dist/main/services/workspace/session-status-store.d.ts +47 -0
  284. package/dist/main/services/workspace/session-status-store.js +73 -0
  285. package/dist/main/services/workspace/status-tap.d.ts +20 -0
  286. package/dist/main/services/workspace/status-tap.js +32 -0
  287. package/dist/main/services/workspace/workspace-compile-modes.d.ts +59 -0
  288. package/dist/main/services/workspace/workspace-compile-modes.js +93 -0
  289. package/dist/main/services/workspace/workspace-lifecycle-race.testutil.d.ts +22 -0
  290. package/dist/main/services/workspace/workspace-lifecycle-race.testutil.js +62 -0
  291. package/dist/main/services/workspace/workspace-referer.d.ts +17 -0
  292. package/dist/main/services/workspace/workspace-referer.js +26 -0
  293. package/dist/main/services/workspace/workspace-service.d.ts +7 -5
  294. package/dist/main/services/workspace/workspace-service.js +96 -135
  295. package/dist/main/services/workspace/workspace-thumbnails.d.ts +22 -0
  296. package/dist/main/services/workspace/workspace-thumbnails.js +67 -0
  297. package/dist/main/utils/ipc-context-source.d.ts +40 -0
  298. package/dist/main/utils/ipc-context-source.js +23 -0
  299. package/dist/main/utils/ipc-registry.d.ts +46 -21
  300. package/dist/main/utils/ipc-registry.js +166 -76
  301. package/dist/main/utils/paths.d.ts +18 -0
  302. package/dist/main/utils/paths.js +18 -0
  303. package/dist/main/utils/sender-policy.d.ts +10 -1
  304. package/dist/main/utils/sender-policy.js +42 -4
  305. package/dist/main/utils/theme.d.ts +18 -8
  306. package/dist/main/utils/theme.js +19 -9
  307. package/dist/main/windows/internal-devtools-window/index.d.ts +66 -0
  308. package/dist/main/windows/internal-devtools-window/index.js +160 -0
  309. package/dist/main/windows/main-window/create.d.ts +16 -0
  310. package/dist/main/windows/main-window/create.js +19 -11
  311. package/dist/main/windows/main-window/events.d.ts +1 -1
  312. package/dist/main/windows/main-window/events.js +26 -13
  313. package/dist/main/windows/main-window/index.d.ts +1 -1
  314. package/dist/main/windows/main-window/index.js +1 -1
  315. package/dist/main/windows/service-host-window/create.d.ts +1 -92
  316. package/dist/main/windows/service-host-window/create.js +1 -154
  317. package/dist/native-host/common/common.js +173 -53
  318. package/dist/native-host/container/pageFrame.css +1 -1
  319. package/dist/native-host/render/render.js +8278 -3659
  320. package/dist/native-host/service/service.js +2 -2
  321. package/dist/preload/index.d.ts +2 -0
  322. package/dist/preload/index.js +1 -0
  323. package/dist/preload/instrumentation/app-data.d.ts +2 -2
  324. package/dist/preload/instrumentation/app-data.js +1 -1
  325. package/dist/preload/instrumentation/wxml.js +1 -1
  326. package/dist/preload/runtime/bridge.d.ts +3 -3
  327. package/dist/preload/runtime/bridge.js +2 -2
  328. package/dist/preload/runtime/clipboard.d.ts +6 -0
  329. package/dist/preload/runtime/clipboard.js +12 -0
  330. package/dist/preload/runtime/host-dialog-advertiser.d.ts +22 -0
  331. package/dist/preload/runtime/host-dialog-advertiser.js +37 -0
  332. package/dist/preload/runtime/host-dialog-port.d.ts +44 -0
  333. package/dist/preload/runtime/host-dialog-port.js +53 -0
  334. package/dist/preload/runtime/host-dialog-runtime.d.ts +28 -0
  335. package/dist/preload/runtime/host-dialog-runtime.js +36 -0
  336. package/dist/preload/runtime/host-sidebar-advertiser.d.ts +19 -0
  337. package/dist/preload/runtime/host-sidebar-advertiser.js +35 -0
  338. package/dist/preload/runtime/host-sidebar-port.d.ts +44 -0
  339. package/dist/preload/runtime/host-sidebar-port.js +53 -0
  340. package/dist/preload/runtime/host-sidebar-runtime.d.ts +28 -0
  341. package/dist/preload/runtime/host-sidebar-runtime.js +36 -0
  342. package/dist/preload/runtime/host-slot-port-bridge.d.ts +18 -0
  343. package/dist/preload/runtime/host-slot-port-bridge.js +112 -0
  344. package/dist/preload/runtime/host-toolbar-advertiser.d.ts +5 -13
  345. package/dist/preload/runtime/host-toolbar-advertiser.js +18 -38
  346. package/dist/preload/runtime/host-toolbar-port.d.ts +9 -0
  347. package/dist/preload/runtime/host-toolbar-port.js +11 -95
  348. package/dist/preload/runtime/native-host.d.ts +14 -0
  349. package/dist/preload/runtime/native-host.js +5 -8
  350. package/dist/preload/runtime/node-bindings.d.ts +18 -0
  351. package/dist/preload/runtime/node-bindings.js +44 -0
  352. package/dist/preload/runtime/temp-files.js +9 -2
  353. package/dist/preload/runtime/wait-for-slot-root.d.ts +23 -0
  354. package/dist/preload/runtime/wait-for-slot-root.js +80 -0
  355. package/dist/preload/shared/api-compat.js +63 -41
  356. package/dist/preload/windows/host-dialog-runtime.cjs +360 -0
  357. package/dist/preload/windows/host-dialog-runtime.cjs.map +7 -0
  358. package/dist/preload/windows/host-dialog-runtime.d.ts +2 -0
  359. package/dist/preload/windows/host-dialog-runtime.js +13 -0
  360. package/dist/preload/windows/host-sidebar-runtime.cjs +361 -0
  361. package/dist/preload/windows/host-sidebar-runtime.cjs.map +7 -0
  362. package/dist/preload/windows/host-sidebar-runtime.d.ts +2 -0
  363. package/dist/preload/windows/host-sidebar-runtime.js +13 -0
  364. package/dist/preload/windows/host-toolbar-runtime.cjs +142 -52
  365. package/dist/preload/windows/host-toolbar-runtime.cjs.map +3 -3
  366. package/dist/preload/windows/main.cjs +4922 -1246
  367. package/dist/preload/windows/main.cjs.map +4 -4
  368. package/dist/preload/windows/main.css +428 -0
  369. package/dist/preload/windows/main.css.map +7 -0
  370. package/dist/preload/windows/simulator.cjs +376 -322
  371. package/dist/preload/windows/simulator.cjs.map +4 -4
  372. package/dist/preload/windows/simulator.js +376 -322
  373. package/dist/render-host/pageFrame.html +4 -4
  374. package/dist/render-host/preload.cjs +30 -0
  375. package/dist/render-host/render-inspect.js +184 -115
  376. package/dist/renderer/assets/button-CB2MWjHY.js +2 -0
  377. package/dist/renderer/assets/constants-CkoGPQTS.js +2 -0
  378. package/dist/renderer/assets/constants-zQZ2-y21.js +2 -0
  379. package/dist/renderer/assets/createLucideIcon-cZ9FlZZw.js +2 -0
  380. package/dist/renderer/assets/devicePicker-H2eIO4e1.js +2 -0
  381. package/dist/renderer/assets/dialog-pHzEA8Vx.js +46 -0
  382. package/dist/renderer/assets/dist-B5HADirS.js +2 -0
  383. package/dist/renderer/assets/dist-DIFlRMKe.js +2 -0
  384. package/dist/renderer/assets/hostSidebarDefault-DRWh6OCt.js +2 -0
  385. package/dist/renderer/assets/index-CEL_XFge.js +2 -0
  386. package/dist/renderer/assets/input-CAObw95L.js +2 -0
  387. package/dist/renderer/assets/ipc-channels-overlays-gIPUFnqc.js +2 -0
  388. package/dist/renderer/assets/jsx-runtime-DICEQH_0.css +1 -0
  389. package/dist/renderer/assets/jsx-runtime-DIw5wnYl.js +9 -0
  390. package/dist/renderer/assets/plus-BQ9u5EbL.js +2 -0
  391. package/dist/renderer/assets/popover-Cw7G2Ctp.js +2 -0
  392. package/dist/renderer/assets/presets-DMZOb9ay.js +2 -0
  393. package/dist/renderer/assets/project-api-YTOr_Yvo.js +2 -0
  394. package/dist/renderer/assets/projectCreateDialog-BmLqiYxn.js +2 -0
  395. package/dist/renderer/assets/search-BMvuYF48.js +2 -0
  396. package/dist/renderer/assets/select-nFP95TxM.js +2 -0
  397. package/dist/renderer/assets/settings-CnpVfSWt.js +2 -0
  398. package/dist/renderer/assets/settings-api-Ccsf-Nsj.js +2 -0
  399. package/dist/renderer/assets/settings-tab-bar-CAzrv32m.js +2 -0
  400. package/dist/renderer/assets/smartphone-Bb2GdIKy.js +2 -0
  401. package/dist/renderer/assets/tabs-CPKN3Mmq.js +2 -0
  402. package/dist/renderer/assets/tooltip-D6AxXRk5.js +2 -0
  403. package/dist/renderer/assets/types-Bh0hA7El.js +2 -0
  404. package/dist/renderer/assets/updateDialog-CwndJ0Bl.js +2 -0
  405. package/dist/renderer/assets/utils-sEB5OZEQ.js +2 -0
  406. package/dist/renderer/assets/view-api-B-2zPBR5.js +2 -0
  407. package/dist/renderer/assets/view-ids-BfaDVV5P.js +2 -0
  408. package/dist/renderer/assets/workbench-C0Jkdlps.js +5 -0
  409. package/dist/renderer/assets/workbenchSettings-YD0Ctbm_.js +8 -0
  410. package/dist/renderer/entries/device-picker/index.html +28 -0
  411. package/dist/renderer/entries/host-sidebar-default/index.html +22 -0
  412. package/dist/renderer/entries/main/index.html +16 -7
  413. package/dist/renderer/entries/popover/index.html +16 -6
  414. package/dist/renderer/entries/project-create-dialog/index.html +27 -0
  415. package/dist/renderer/entries/settings/index.html +10 -5
  416. package/dist/renderer/entries/tooltip/index.html +21 -0
  417. package/dist/renderer/entries/update-dialog/index.html +25 -0
  418. package/dist/renderer/entries/workbench/index.html +49 -0
  419. package/dist/renderer/entries/workbench-settings/index.html +12 -5
  420. package/dist/service-host/appdata-set-data.cjs +39 -0
  421. package/dist/service-host/host-env-update.cjs +23 -0
  422. package/dist/service-host/preload.cjs +54 -48
  423. package/dist/service-host/service-message-queue.cjs +71 -0
  424. package/dist/service-host/sync-impls/menu-button-geometry.js +16 -5
  425. package/dist/service-host/sync-impls/menu-button.js +5 -3
  426. package/dist/service-host/sync-impls/system-info.js +31 -9
  427. package/dist/shared/bridge-channels.d.ts +1 -416
  428. package/dist/shared/bridge-channels.js +1 -89
  429. package/dist/shared/compile-mode-state.d.ts +73 -0
  430. package/dist/shared/compile-mode-state.js +100 -0
  431. package/dist/shared/compile-modes.d.ts +152 -0
  432. package/dist/shared/compile-modes.js +237 -0
  433. package/dist/shared/constants.d.ts +22 -0
  434. package/dist/shared/constants.js +22 -0
  435. package/dist/shared/dmb-resource-url.d.ts +2 -0
  436. package/dist/shared/dmb-resource-url.js +2 -0
  437. package/dist/shared/ipc-channels-overlays.d.ts +162 -0
  438. package/dist/shared/ipc-channels-overlays.js +203 -0
  439. package/dist/shared/ipc-channels.d.ts +24 -154
  440. package/dist/shared/ipc-channels.js +48 -96
  441. package/dist/shared/ipc-schemas.d.ts +143 -54
  442. package/dist/shared/ipc-schemas.js +137 -45
  443. package/dist/shared/node-bindings.d.ts +2 -0
  444. package/dist/shared/node-bindings.js +2 -0
  445. package/dist/shared/project-window-types.d.ts +42 -0
  446. package/dist/shared/project-window-types.js +2 -0
  447. package/dist/shared/simulator-route.d.ts +8 -0
  448. package/dist/shared/simulator-route.js +15 -0
  449. package/dist/shared/simulator-ui.d.ts +61 -0
  450. package/dist/shared/simulator-ui.js +12 -0
  451. package/dist/shared/types.d.ts +126 -41
  452. package/dist/shared/view-ids.d.ts +10 -0
  453. package/dist/shared/view-ids.js +23 -0
  454. package/dist/simulator/assets/bridge-channels-B3JAmRfl.js +2 -0
  455. package/dist/simulator/assets/device-shell-ATEhHV2_.css +1 -0
  456. package/dist/simulator/assets/device-shell-DucLepkm.js +431 -0
  457. package/dist/simulator/assets/simulator-B4rbcOVt.js +10 -0
  458. package/dist/simulator/assets/simulator-mini-app-DFo7fhi5.js +2 -0
  459. package/dist/simulator/assets/simulator-ui-BupATgKo.css +1 -0
  460. package/dist/simulator/assets/simulator-ui-i3GCc7YJ.js +2 -0
  461. package/dist/simulator/simulator.html +4 -3
  462. package/dist/simulator/temp-files.d.ts +35 -0
  463. package/dist/simulator/temp-files.js +121 -0
  464. package/dist/simulator/types.d.ts +50 -0
  465. package/dist/simulator/types.js +2 -0
  466. package/dist/vscode-workbench/assets/__vite-browser-external-JIkWewFf.js +1 -0
  467. package/dist/vscode-workbench/assets/{dist-TpGpmMGi.js → dist-CXG-u1VB.js} +3 -3
  468. package/dist/vscode-workbench/assets/fs-core-worker-urls-DZ1nod84.js +1 -0
  469. package/dist/vscode-workbench/assets/fs-core.worker-Bnmpp6fo.js +1 -0
  470. package/dist/vscode-workbench/assets/fs-query.worker-cZ-7Fjch.js +2 -0
  471. package/dist/vscode-workbench/assets/{iconv-lite-umd-C9tiCAJa.js → iconv-lite-umd-Dxx9Qyb4.js} +1 -1
  472. package/dist/vscode-workbench/assets/{index-DJ1HyMZ7.js → index-6VJMsaWj.js} +475 -475
  473. package/dist/vscode-workbench/assets/{jschardet-DE1jV513.js → jschardet-Ba3D16vt.js} +1 -1
  474. package/dist/vscode-workbench/index.html +21 -1
  475. package/package.json +23 -13
  476. package/dist/main/services/views/destroy-child-view.d.ts +0 -9
  477. package/dist/main/services/views/destroy-child-view.js +0 -23
  478. package/dist/preload/instrumentation/wxml-extract.d.ts +0 -11
  479. package/dist/preload/instrumentation/wxml-extract.js +0 -382
  480. package/dist/preload/shared/sid-registry.d.ts +0 -11
  481. package/dist/preload/shared/sid-registry.js +0 -38
  482. package/dist/renderer/assets/constants-EAMiwL9_.js +0 -2
  483. package/dist/renderer/assets/index-BMqyDx71.js +0 -49
  484. package/dist/renderer/assets/input-CDAEwpBO.js +0 -2
  485. package/dist/renderer/assets/ipc-transport-B7eFOR68.css +0 -1
  486. package/dist/renderer/assets/ipc-transport-_G1k5V1p.js +0 -9
  487. package/dist/renderer/assets/popover-DEdjK14m.js +0 -2
  488. package/dist/renderer/assets/select-DpKPCh3y.js +0 -2
  489. package/dist/renderer/assets/settings-M5q2elUr.js +0 -2
  490. package/dist/renderer/assets/settings-api-CDlZoHiO.js +0 -2
  491. package/dist/renderer/assets/workbenchSettings-CT_H1WhV.js +0 -8
  492. package/dist/shared/appdata-accumulator.d.ts +0 -95
  493. package/dist/shared/appdata-accumulator.js +0 -218
  494. package/dist/shared/request-core.d.ts +0 -84
  495. package/dist/shared/request-core.js +0 -168
  496. package/dist/shared/simulator-api-metadata.d.ts +0 -26
  497. package/dist/shared/simulator-api-metadata.js +0 -59
  498. package/dist/shared/vpath.d.ts +0 -42
  499. package/dist/shared/vpath.js +0 -105
  500. package/dist/simulator/assets/bridge-channels-zhsumfky.js +0 -2
  501. package/dist/simulator/assets/device-shell-C-wcFq3Z.js +0 -2
  502. package/dist/simulator/assets/device-shell-CS1rqAQz.css +0 -1
  503. package/dist/simulator/assets/jsx-runtime-BDTY6fEq.js +0 -2
  504. package/dist/simulator/assets/simulator-DU3-fS3v.js +0 -10
  505. package/dist/simulator/assets/simulator-mini-app-DzfMfnVM.js +0 -2
  506. package/dist/simulator/device-shell/menu-button-geometry.js +0 -8
  507. package/dist/vscode-workbench/assets/__vite-browser-external-CXi6Kz9W.js +0 -1
@@ -1,2133 +1,33 @@
1
- import { app, ipcMain, protocol, session as electronSession, webContents } from 'electron';
2
- import path from 'node:path';
3
- import { pathToFileURL } from 'node:url';
4
- import { BRIDGE_CHANNELS as C, SIMULATOR_EVENTS as E, deviceInfoToHostEnv } from '../../shared/bridge-channels.js';
5
- import { apiCallWatchdogMs, isPersistentSimulatorApi } from '../../shared/simulator-api-metadata.js';
6
- import { devtoolsPackageRoot } from '../utils/paths.js';
7
- import { createSessionListenerBag } from './session-listener-bag.js';
8
- import { createDebugTap } from '@dimina-kit/electron-deck/main';
9
- import { startDiminaResourceServer } from '../services/dimina-resource-server.js';
10
- import { buildServiceHostSpawnUrl, createServiceHostWindow, navigateServiceHost, serviceHostSpec, } from '../windows/service-host-window/create.js';
11
- import { ServiceHostPool } from '../services/service-host-pool/pool.js';
12
- import { registerMiniappSessionConfigurator, SHARED_MINIAPP_PARTITION, } from '../services/views/miniapp-partition.js';
13
- import { createConsoleForwarder } from '../services/console-forward/index.js';
14
- import { createDiagnosticsBus } from '../services/diagnostics/index.js';
15
- import { STORAGE_API_NAMES } from '../services/simulator-storage/index.js';
16
- import { buildPageScrollScript } from './page-scroll.js';
17
- import { createAppLifecycleController, } from './app-lifecycle.js';
18
- // The compiled `logic.js` ships a RELATIVE `//# sourceMappingURL=logic.js.map`.
19
- // `injectLogicBundle` loads it via `executeJavaScript`, which gives the injected
20
- // script no base URL of its own — so DevTools resolves that relative map against
21
- // the service-host DOCUMENT and 404s, leaving console frames / Sources links
22
- // pointing at the compiled bundle instead of the developer's source. This rewrite
23
- // turns the directive absolute.
24
- //
25
- // Inlined (not imported from `service-host/sourcemap-rewrite.cjs`) ON PURPOSE:
26
- // the shipped main entry is the FLAT esbuild bundle (`dist/main/index.bundle.js`,
27
- // see electron-builder.yml `extraMetadata.main`). A runtime `createRequire(...)`
28
- // of the relative `.cjs` resolved against `import.meta.url`, which is the bundle
29
- // at `dist/main/` (one level shallower than the tsc tree `dist/main/ipc/`), so
30
- // the path 404'd and the PACKAGED app crashed on load. Bundling the logic inline
31
- // is bundler-proof. KEEP IN SYNC with `service-host/sourcemap-rewrite.cjs` (the
32
- // copied-not-bundled service-host preload still requires that one).
33
- function rewriteSourceMappingUrl(source, scriptUrl) {
34
- if (typeof source !== 'string' || !source)
35
- return source;
36
- const re = /(^|\n)[ \t]*\/\/[#@][ \t]*sourceMappingURL=([^\n]*)/g;
37
- let lastIndex = -1;
38
- let lastValue = '';
39
- let m;
40
- while ((m = re.exec(source)) !== null) {
41
- lastIndex = m.index + m[1].length;
42
- lastValue = (m[2] || '').trim();
43
- }
44
- if (lastIndex < 0 || !lastValue)
45
- return source;
46
- // Already absolute (scheme://, protocol-relative //host, or data:)? Leave it.
47
- if (/^(?:[a-z][a-z0-9+.-]*:)?\/\//i.test(lastValue) || /^data:/i.test(lastValue))
48
- return source;
49
- let absolute;
50
- try {
51
- absolute = new URL(lastValue, scriptUrl).toString();
52
- }
53
- catch {
54
- return source;
55
- }
56
- return `${source.slice(0, lastIndex)}\n//# sourceMappingURL=${absolute}`;
57
- }
58
- const STACK_ID = 'stack_0';
1
+ export * from '@dimina-kit/electron-runtime/main/bridge-router';
2
+ import { installBridgeRouter as installRuntimeBridgeRouter } from '@dimina-kit/electron-runtime/main/bridge-router';
3
+ import { createRuntimeEvents, } from '@dimina-kit/electron-runtime/main/runtime-context';
4
+ import { runtimeAssetPaths } from '../utils/paths.js';
59
5
  /**
60
- * Watchdog window a spawned session gets to reach `'running'` (root page
61
- * `domReady`) before `handleSpawn`'s launch timer reports `'launch-failed'`
62
- * with `code: 'timeout'`. Exported so tests can assert against the real
63
- * constant instead of a magic number.
6
+ * DevTools compatibility assembly. New contexts already own RuntimeEvents;
7
+ * narrow historical test/downstream contexts are upgraded here so the runtime
8
+ * package itself stays unaware of panels and renderer notifications.
64
9
  */
65
- export const LAUNCH_TIMEOUT_MS = 20_000;
66
- /** Hard ceiling on the pre-warm pool, mirroring ServiceHostPool/doc §3.3. */
67
- const PREWARM_MAX_POOL_SIZE = 4;
68
- /**
69
- * Resolve the pre-warm pool size from env (doc §6). Returns 0 (OFF) unless
70
- * `DIMINA_PREWARM_POOL_SIZE` is a positive integer and `DIMINA_PREWARM_DISABLE`
71
- * is not set. Default OFF — pooling is opt-in so the spawn path is unchanged
72
- * unless explicitly enabled.
73
- */
74
- function resolvePrewarmPoolSize() {
75
- if (process.env.DIMINA_PREWARM_DISABLE === '1')
76
- return 0;
77
- const raw = process.env.DIMINA_PREWARM_POOL_SIZE;
78
- if (!raw)
79
- return 0;
80
- const n = Number.parseInt(raw, 10);
81
- if (!Number.isFinite(n) || n <= 0)
82
- return 0;
83
- return Math.min(n, PREWARM_MAX_POOL_SIZE);
84
- }
85
- /** debugTap (§7) is opt-in via `DIMINA_DEBUG_TAP=1` — off everywhere else. */
86
- function resolveDebugTapEnabled() {
87
- return process.env.DIMINA_DEBUG_TAP === '1';
88
- }
89
- /**
90
- * Compact one-line summary of a bridge payload for debugTap — the inner
91
- * `msg.type` + `bridgeId`, never the full (potentially large) payload blob.
92
- */
93
- function summarizeBridgeMsg(payload) {
94
- if (!payload || typeof payload !== 'object')
95
- return undefined;
96
- const p = payload;
97
- const bridgeId = typeof p.bridgeId === 'string' ? p.bridgeId : undefined;
98
- const msg = p.msg;
99
- const type = msg && typeof msg.type === 'string' ? msg.type : undefined;
100
- const parts = [type, bridgeId ? `bridge=${bridgeId}` : undefined].filter(Boolean);
101
- return parts.length > 0 ? parts.join(' ') : undefined;
102
- }
103
- // Same-appId matches prefer the MOST RECENT spawn (Maps preserve insertion
104
- // order): after a respawn/reopen the newest session is the live one — the
105
- // first match could be a just-superseded session mid-teardown. The loop must
106
- // scan every session (no early break) so the LAST inserted match wins.
107
- function findAppSessionByAppId(state, appId) {
108
- let match;
109
- for (const ap of state.appSessions.values())
110
- if (ap.appId === appId)
111
- match = ap;
112
- return match;
113
- }
114
- // No appId hint and no workspace session to disambiguate. Picking by appId is
115
- // impossible, so fall back to the most-recent spawn — but ONLY when every live
116
- // session belongs to the same app. Same-appId multiples are a respawn/reopen
117
- // in progress where the newest is the live one (insertion order = spawn
118
- // order). Multiple DISTINCT appIds mean a previous project is mid-teardown
119
- // alongside the new one; any pick is a guess that can resolve the WRONG
120
- // project's content (the screenshot/inspect-stale-app bug class), so prefer
121
- // null over a wrong guess.
122
- function resolveFallbackAppSession(state) {
123
- let last;
124
- const distinctAppIds = new Set();
125
- for (const ap of state.appSessions.values()) {
126
- last = ap;
127
- distinctAppIds.add(ap.appId);
128
- }
129
- return distinctAppIds.size <= 1 ? last : undefined;
130
- }
131
- /**
132
- * Resolve the "current" app session: prefer an explicit appId, else the
133
- * workspace's active project, else the only / most-recently-created session.
134
- * Native-host can host multiple AppSessions, so this picks one deterministically
135
- * rather than assuming a single session.
136
- */
137
- function resolveCurrentApp(state, ctx, appId) {
138
- if (appId) {
139
- const match = findAppSessionByAppId(state, appId);
140
- if (match)
141
- return match;
142
- }
143
- const appInfo = ctx.workspace?.getSession?.()?.appInfo;
144
- const activeAppId = appInfo?.appId;
145
- if (activeAppId) {
146
- const match = findAppSessionByAppId(state, activeAppId);
147
- if (match)
148
- return match;
149
- }
150
- // During a project close the workspace nulls its session BEFORE the bridge app
151
- // session is torn down (disposeSession runs before disposeAll). Resolving here
152
- // would hand a consumer the closing project's dying guest, so refuse to guess
153
- // while a close is in flight.
154
- if (ctx.workspace?.isClosing?.())
155
- return undefined;
156
- return resolveFallbackAppSession(state);
157
- }
158
10
  export function installBridgeRouter(ctx) {
159
- const state = {
160
- appSessions: new Map(),
161
- pageSessions: new Map(),
162
- serviceWcIdToAppSessionId: new Map(),
163
- simulatorWcIdToAppSessionIds: new Map(),
164
- wcIdToBridgeId: new Map(),
165
- pendingApiCalls: new Map(),
166
- pool: null,
167
- emitRenderEvent: () => { },
168
- connections: ctx.connections,
169
- debugTap: createDebugTap({ enabled: resolveDebugTapEnabled() }),
170
- appLifecycle: createAppLifecycleController(),
171
- evictAppDataBridges: (ap) => {
172
- if (!ctx.appData)
173
- return;
174
- for (const page of ap.pages.values())
175
- ctx.appData.evictBridge(ap.appId, page.bridgeId);
176
- },
177
- };
178
- // Opt-in (default OFF) pre-warm pool for service-host windows. When enabled,
179
- // handleSpawn acquires a warm window instead of constructing one per spawn.
180
- const prewarmPoolSize = resolvePrewarmPoolSize();
181
- if (prewarmPoolSize > 0) {
182
- const pool = new ServiceHostPool();
183
- state.pool = pool;
184
- // Defer warm-up off the cold-start critical path (doc §3.3: app.ready +
185
- // idle). Warming eagerly here races main-window startup — it creates an
186
- // extra hidden BrowserWindow before the workbench renderer has settled,
187
- // which made the e2e's first-window/preload-readiness flaky. The timer is
188
- // cancelled on teardown so it can't warm a disposed pool.
189
- const warmTimer = setTimeout(() => {
190
- void pool
191
- .init({ defaultPoolSize: prewarmPoolSize, defaultSpec: serviceHostSpec(), maxPoolSize: PREWARM_MAX_POOL_SIZE })
192
- .catch((error) => {
193
- console.warn('[bridge-router] webview pool warm-up failed:', error);
194
- });
195
- }, 500);
196
- ctx.registry.add(() => clearTimeout(warmTimer));
197
- ctx.registry.add(() => state.pool?.dispose());
198
- }
199
- installAppLifecycleDriver(ctx, state);
200
- installResourceProtocolHandlers(ctx, state);
201
- // The currently-selected device (renderer toolbar). Cached here so it can ride
202
- // the NATIVE_HOST_ENABLED reply (race-free DeviceShell init) and so the
203
- // safe-area service can read it when a render-host guest attaches.
204
- let currentDevice = null;
205
- // Native-host enablement query. The simulator webview's preload can't read the
206
- // launch `process.env` (and additionalArguments don't reach webview guests),
207
- // nor can it compute file paths (no node:path/url in the guest preload), so it
208
- // asks main synchronously at install time for the flag + the render-host URLs.
209
- const onNativeHostQuery = (event) => {
210
- // native-host is the sole runtime: always reply enabled with the render-host
211
- // file:// URLs the simulator preload needs. `device` rides along so the
212
- // DeviceShell mounts with the right bezel size synchronously (the renderer
213
- // pushes SetDeviceInfo before AttachNative, so the cache is populated by the
214
- // time the simulator preload runs this sendSync).
215
- const reply = {
216
- enabled: true,
217
- renderHostHtmlUrl: pathToFileURL(path.join(devtoolsPackageRoot, 'dist/render-host/pageFrame.html')).toString(),
218
- renderPreloadUrl: pathToFileURL(path.join(devtoolsPackageRoot, 'dist/render-host/preload.cjs')).toString(),
219
- device: currentDevice ?? undefined,
220
- };
221
- event.returnValue = reply;
222
- };
223
- ipcMain.on(C.NATIVE_HOST_ENABLED, onNativeHostQuery);
224
- ctx.registry.add(() => { ipcMain.removeListener(C.NATIVE_HOST_ENABLED, onNativeHostQuery); });
225
- // Subscribers to render-side activity (domReady / active-page). Panels that
226
- // pull from the active render guest (WXML) re-read on these.
227
- const renderEventListeners = new Set();
228
- const emitRenderEvent = (event) => {
229
- for (const listener of renderEventListeners) {
230
- try {
231
- listener(event);
232
- }
233
- catch (error) {
234
- console.warn('[bridge-router] render-event listener threw:', error);
235
- }
236
- }
237
- };
238
- state.emitRenderEvent = emitRenderEvent;
239
- // Expose a thin accessor over RouterState so other main services (storage,
240
- // automation, appdata) can resolve live render/service WebContents without
241
- // owning router state. Getters resolve fresh — the pre-warm pool can swap
242
- // windows on respawn, so cached handles go stale.
243
- const bridgeHandle = {
244
- isNativeHost: () => true,
245
- resolveRenderWc: (bridgeId) => {
246
- const page = state.pageSessions.get(bridgeId);
247
- return page?.renderWc && !page.renderWc.isDestroyed() ? page.renderWc : null;
248
- },
249
- getServiceWc: (appId) => {
250
- const ap = resolveCurrentApp(state, ctx, appId);
251
- return ap && !ap.serviceWc.isDestroyed() ? ap.serviceWc : null;
252
- },
253
- getServiceWcForBridge: (bridgeId) => {
254
- const page = state.pageSessions.get(bridgeId);
255
- if (!page)
256
- return null;
257
- const ap = state.appSessions.get(page.appSessionId);
258
- return ap && !ap.serviceWc.isDestroyed() ? ap.serviceWc : null;
259
- },
260
- getActiveBridgeId: (appId) => {
261
- const ap = resolveCurrentApp(state, ctx, appId);
262
- if (!ap)
263
- return null;
264
- // Fall back to the root page (= appSessionId) before the first signal.
265
- return ap.activeBridgeId ?? ap.appSessionId;
266
- },
267
- getPageStack: (appId) => {
268
- const ap = resolveCurrentApp(state, ctx, appId);
269
- return ap?.pageStack ?? null;
270
- },
271
- getActiveRenderWc: (appId) => {
272
- const ap = resolveCurrentApp(state, ctx, appId);
273
- if (!ap)
274
- return null;
275
- const page = state.pageSessions.get(ap.activeBridgeId ?? ap.appSessionId);
276
- return page?.renderWc && !page.renderWc.isDestroyed() ? page.renderWc : null;
277
- },
278
- onRenderEvent: (listener) => {
279
- renderEventListeners.add(listener);
280
- return () => renderEventListeners.delete(listener);
281
- },
282
- getDevice: () => currentDevice,
283
- setDevice: (device) => {
284
- currentDevice = device;
285
- // Push to the live simulator WC(s) so a mounted DeviceShell re-renders the
286
- // bezel/status-bar/notch. Pre-spawn there is no session yet — the initial
287
- // device rides NATIVE_HOST_ENABLED instead. Dedupe across app sessions
288
- // that share one simulator WCV.
289
- const seen = new Set();
290
- for (const ap of state.appSessions.values()) {
291
- const wc = ap.simulatorWc;
292
- if (wc && !wc.isDestroyed() && !seen.has(wc.id)) {
293
- seen.add(wc.id);
294
- wc.send(E.DEVICE_CHANGE, device);
295
- }
296
- }
297
- },
298
- disposeSessionsForSimulator: (simulatorWcId) => {
299
- // Snapshot ids first: disposeAppSession mutates state.appSessions.
300
- const ids = [];
301
- for (const [id, ap] of state.appSessions) {
302
- if (ap.simulatorWc.id === simulatorWcId)
303
- ids.push(id);
304
- }
305
- // disposeAppSession's synchronous prefix clears every map + closes the
306
- // render guests + the service window, so the bridge is clean the moment
307
- // this is called. The joined promise resolves after the async tail
308
- // (pool.release / resourceServer.close) so a caller can sequence a reopen
309
- // after full teardown. disposeAppSession logs those tail failures
310
- // internally, so this resolves rather than rejecting on them — it is a
311
- // completion signal, not an error channel.
312
- return Promise.all(ids.map((id) => disposeAppSession(state, id))).then(() => { });
313
- },
314
- debugTap: state.debugTap,
315
- census: () => {
316
- const simulatorDestroyedListeners = {};
317
- let simulatorWcBindings = 0;
318
- for (const ids of state.simulatorWcIdToAppSessionIds.values())
319
- simulatorWcBindings += ids.size;
320
- for (const ap of state.appSessions.values()) {
321
- const wc = ap.simulatorWc;
322
- if (!wc.isDestroyed() && simulatorDestroyedListeners[wc.id] === undefined) {
323
- simulatorDestroyedListeners[wc.id] = wc.listenerCount('destroyed');
324
- }
325
- }
326
- return {
327
- appSessions: state.appSessions.size,
328
- pageSessions: state.pageSessions.size,
329
- serviceWcBindings: state.serviceWcIdToAppSessionId.size,
330
- simulatorWcs: state.simulatorWcIdToAppSessionIds.size,
331
- simulatorWcBindings,
332
- renderWcBindings: state.wcIdToBridgeId.size,
333
- pendingApiCalls: state.pendingApiCalls.size,
334
- simulatorDestroyedListeners,
335
- };
336
- },
337
- };
338
- ctx.bridge = bridgeHandle;
339
- // e2e resource-census probe: Playwright's electronApp.evaluate() reads the
340
- // router's ledger straight off this main-process global — no IPC surface
341
- // needed. Test builds only.
342
- if (process.env.NODE_ENV === 'test') {
343
- ;
344
- globalThis.__diminaResourceCensus = () => bridgeHandle.census?.();
345
- }
346
- // Always-on guest console fan-out. Owns `ctx.guestConsole` (the sink the
347
- // consoleLog case below routes to) so that render-layer console output is
348
- // mirrored into the service host's own console — surfacing it in the embedded
349
- // Chrome DevTools (attached to the service host) prefixed `[视图]` — regardless
350
- // of whether an automation client is connected. Automation now SUBSCRIBES to
351
- // this forwarder instead of clobbering `ctx.guestConsole`.
352
- // Authoritative diagnostics bus (see workbench-context.ts): the single place
353
- // main-synthesized diagnostics are born. The forwarder subscribes to it so
354
- // every diagnostic also lands in the owning session's embedded DevTools
355
- // Console panel, not just the main-process log / automation subscribers.
356
- // A caller (host embedding, or a test) may have already installed one on
357
- // `ctx` before `installBridgeRouter` runs — respect it instead of
358
- // clobbering it with a fresh bus; only a bus WE create here is ours to
359
- // dispose on teardown.
360
- const ownsDiagnosticsBus = ctx.diagnostics === undefined;
361
- const diagnosticsBus = ctx.diagnostics ?? createDiagnosticsBus();
362
- ctx.diagnostics = diagnosticsBus;
363
- const consoleForwarder = createConsoleForwarder(bridgeHandle, diagnosticsBus);
364
- ctx.consoleForwarder = consoleForwarder;
365
- ctx.guestConsole = consoleForwarder;
366
- ctx.registry.add(() => {
367
- void consoleForwarder.dispose();
368
- ctx.consoleForwarder = undefined;
369
- ctx.guestConsole = undefined;
370
- if (ownsDiagnosticsBus) {
371
- diagnosticsBus.dispose();
372
- ctx.diagnostics = undefined;
373
- }
374
- });
375
- // DeviceShell → main: record the visible top-of-stack page bridgeId so the
376
- // accessor above can resolve "the active page". Sender-validated against the
377
- // app that owns it; ignored for unknown apps/pages.
378
- const onActivePage = (event, payload) => {
379
- const ap = state.appSessions.get(payload.appSessionId);
380
- if (!ap)
381
- return;
382
- if (!senderBoundToSession(state, event.sender, ap))
383
- return;
384
- if (ap.pages.has(payload.bridgeId)) {
385
- ap.activeBridgeId = payload.bridgeId;
386
- const pagePath = state.pageSessions.get(payload.bridgeId)?.pagePath;
387
- emitRenderEvent({ kind: 'activePage', appId: ap.appId, bridgeId: payload.bridgeId, pagePath });
388
- }
389
- };
390
- ipcMain.on(C.ACTIVE_PAGE, onActivePage);
391
- ctx.registry.add(() => { ipcMain.removeListener(C.ACTIVE_PAGE, onActivePage); });
392
- // DeviceShell → main: the full ordered page stack (bottom→top). Stored so
393
- // automation's App.getPageStack can report a multi-page stack (main has no
394
- // stack of its own). Sender-validated against the owning app.
395
- const onPageStack = (event, payload) => {
396
- const ap = state.appSessions.get(payload.appSessionId);
397
- if (!ap)
398
- return;
399
- if (!senderBoundToSession(state, event.sender, ap))
400
- return;
401
- ap.pageStack = payload.stack;
402
- };
403
- ipcMain.on(C.PAGE_STACK, onPageStack);
404
- ctx.registry.add(() => { ipcMain.removeListener(C.PAGE_STACK, onPageStack); });
405
- ipcMain.handle(C.SPAWN, async (event, opts) => {
406
- return handleSpawn(state, ctx, event, opts);
407
- });
408
- ctx.registry.add(() => { ipcMain.removeHandler(C.SPAWN); });
409
- ipcMain.handle(C.PAGE_OPEN, async (event, opts) => {
410
- return handlePageOpen(state, event, opts);
411
- });
412
- ctx.registry.add(() => { ipcMain.removeHandler(C.PAGE_OPEN); });
413
- const onPageClose = (event, payload) => {
414
- handlePageClose(state, event.sender, payload);
415
- };
416
- ipcMain.on(C.PAGE_CLOSE, onPageClose);
417
- ctx.registry.add(() => { ipcMain.removeListener(C.PAGE_CLOSE, onPageClose); });
418
- const onPageLifecycle = (event, payload) => {
419
- handlePageLifecycle(state, event.sender, payload);
420
- // Native-host AppData: evict the bridge on page teardown so the panel drops
421
- // its tab (mirrors the default path's postMessage(pageUnload) eviction).
422
- if (payload.event === 'pageUnload' && ctx.appData) {
423
- const ap = state.appSessions.get(payload.appSessionId);
424
- if (ap && senderBoundToSession(state, event.sender, ap)) {
425
- ctx.appData.evictBridge(ap.appId, payload.bridgeId);
426
- }
427
- }
428
- };
429
- ipcMain.on(C.PAGE_LIFECYCLE, onPageLifecycle);
430
- ctx.registry.add(() => { ipcMain.removeListener(C.PAGE_LIFECYCLE, onPageLifecycle); });
431
- const onNavCallback = (event, payload) => {
432
- handleNavCallback(state, event.sender, payload);
433
- };
434
- ipcMain.on(C.NAV_CALLBACK, onNavCallback);
435
- ctx.registry.add(() => { ipcMain.removeListener(C.NAV_CALLBACK, onNavCallback); });
436
- const onDispose = (event, payload) => {
437
- const target = resolveAppByBridgeId(state, payload.bridgeId);
438
- if (!target)
439
- return;
440
- // Only the app's service window or simulator window can issue dispose.
441
- // (Unknown senders pass — lenient by design; a simulator WCV is authorized
442
- // for every session it hosts, so an older session's own dispose stays
443
- // valid while a newer spawn shares the wc.)
444
- if (!senderBoundToSession(state, event.sender, target) && appByWc(state, event.sender)) {
445
- console.warn(`[bridge-router] DISPOSE rejected: sender not bound to target ${target.appSessionId}`);
446
- return;
447
- }
448
- // AppData bridge eviction happens inside disposeAppSession (single
449
- // chokepoint shared with the simulator-WCV 'destroyed' path).
450
- void disposeAppSession(state, target.appSessionId);
451
- };
452
- ipcMain.on(C.DISPOSE, onDispose);
453
- ctx.registry.add(() => { ipcMain.removeListener(C.DISPOSE, onDispose); });
454
- // debugTap (§7) ingress recorder — near-free no-op unless DIMINA_DEBUG_TAP=1.
455
- // Hung on the bridge dispatch chokepoint so the cross-wc message flow is
456
- // inspectable (the first real consumer of the workbench debugTap primitive).
457
- const tapIn = (channel, sender, payload) => {
458
- if (!state.debugTap.enabled)
459
- return;
460
- state.debugTap.record({
461
- ts: Date.now(),
462
- channel,
463
- direction: 'in',
464
- connectionId: sender.id,
465
- appSessionId: appByWc(state, sender)?.appSessionId,
466
- summary: summarizeBridgeMsg(payload),
467
- });
468
- };
469
- const onServiceInvoke = (event, payload) => {
470
- tapIn(C.SERVICE_INVOKE, event.sender, payload);
471
- const ap = appByWc(state, event.sender);
472
- if (!ap)
473
- return;
474
- const page = state.pageSessions.get(payload.bridgeId) ?? state.pageSessions.get(ap.appSessionId);
475
- if (!page)
476
- return;
477
- routeFromService(state, ap, page, payload.msg, ctx);
478
- };
479
- ipcMain.on(C.SERVICE_INVOKE, onServiceInvoke);
480
- ctx.registry.add(() => { ipcMain.removeListener(C.SERVICE_INVOKE, onServiceInvoke); });
481
- const onServicePublish = (event, payload) => {
482
- tapIn(C.SERVICE_PUBLISH, event.sender, payload);
483
- const ap = appByWc(state, event.sender);
484
- if (!ap)
485
- return;
486
- forwardToRender(ap, payload.msg, payload.targetBridgeId);
487
- // Native-host AppData panel: tap the service→render setData stream centrally
488
- // (the simulator guest has no Worker to sniff under native-host). Cheap —
489
- // the tap ignores non-ub/non-page_* messages.
490
- ctx.appData?.onServiceToRender(ap.appId, payload.msg);
491
- };
492
- ipcMain.on(C.SERVICE_PUBLISH, onServicePublish);
493
- ctx.registry.add(() => { ipcMain.removeListener(C.SERVICE_PUBLISH, onServicePublish); });
494
- const onRenderInvoke = (event, payload) => {
495
- tapIn(C.RENDER_INVOKE, event.sender, payload);
496
- const page = ensureRenderBound(state, event.sender, payload.bridgeId);
497
- if (!page)
498
- return;
499
- const ap = state.appSessions.get(page.appSessionId);
500
- if (!ap)
501
- return;
502
- routeFromRender(state, ap, page, payload.msg, ctx);
503
- };
504
- ipcMain.on(C.RENDER_INVOKE, onRenderInvoke);
505
- ctx.registry.add(() => { ipcMain.removeListener(C.RENDER_INVOKE, onRenderInvoke); });
506
- const onRenderPublish = (event, payload) => {
507
- tapIn(C.RENDER_PUBLISH, event.sender, payload);
508
- const page = ensureRenderBound(state, event.sender, payload.bridgeId);
509
- if (!page)
510
- return;
511
- const ap = state.appSessions.get(page.appSessionId);
512
- if (!ap)
513
- return;
514
- forwardToService(ap, payload.msg);
515
- };
516
- ipcMain.on(C.RENDER_PUBLISH, onRenderPublish);
517
- ctx.registry.add(() => { ipcMain.removeListener(C.RENDER_PUBLISH, onRenderPublish); });
518
- ipcMain.handle(C.SIMULATOR_API, async (_event, payload) => {
519
- return ctx.simulatorApis.invoke(payload.name, payload.params);
520
- });
521
- ctx.registry.add(() => { ipcMain.removeHandler(C.SIMULATOR_API); });
522
- const onApiResponse = (event, payload) => {
523
- tapIn(C.API_RESPONSE, event.sender, payload);
524
- handleApiResponse(state, event.sender, payload);
525
- };
526
- ipcMain.on(C.API_RESPONSE, onApiResponse);
527
- ctx.registry.add(() => { ipcMain.removeListener(C.API_RESPONSE, onApiResponse); });
528
- ctx.registry.add(async () => {
529
- // Clear any in-flight pending API timers before tearing down sessions so a
530
- // late simulator response cannot fire a callback against a torn-down app.
531
- for (const pending of state.pendingApiCalls.values())
532
- clearTimeout(pending.timer);
533
- state.pendingApiCalls.clear();
534
- });
535
- ctx.registry.add(async () => {
536
- await Promise.all(Array.from(state.appSessions.keys()).map(id => disposeAppSession(state, id)));
537
- });
538
- }
539
- // ── Spawn / Open / Close ─────────────────────────────────────────────────────
540
- /** Clear a session's launch-timeout watchdog, if still armed. Idempotent. */
541
- function clearLaunchTimer(ap) {
542
- if (ap.launchTimer === null)
543
- return;
544
- clearTimeout(ap.launchTimer);
545
- ap.launchTimer = null;
546
- }
547
- /**
548
- * Arm the `LAUNCH_TIMEOUT_MS` watchdog for a freshly spawned session. Fires
549
- * `'launch-failed'` (`code: 'timeout'`) exactly once, and only if the session
550
- * is both still registered (not disposed) and has not already reached
551
- * `'running'` — both settle paths clear the timer themselves, but a timer
552
- * queued just before either lands could still fire on the old macrotask queue.
553
- */
554
- function startLaunchTimer(state, ctx, ap) {
555
- ap.launchTimer = setTimeout(() => {
556
- ap.launchTimer = null;
557
- if (ap.running)
558
- return;
559
- if (state.appSessions.get(ap.appSessionId) !== ap)
560
- return;
561
- const reason = `Service host did not report readiness within ${LAUNCH_TIMEOUT_MS}ms`;
562
- ctx.diagnostics?.report({
563
- severity: 'error',
564
- code: 'launch-timeout',
565
- message: reason,
566
- appSessionId: ap.appSessionId,
567
- });
568
- pushRuntimeStatus(ctx, ap, { phase: 'launch-failed', code: 'timeout', reason });
569
- }, LAUNCH_TIMEOUT_MS);
570
- }
571
- /**
572
- * Flip a session to `'running'` on its root page's first `domReady` — the
573
- * single source-of-truth transition out of `'launching'`. Guarded by
574
- * `ap.running` so a later re-render of the same root page (or any non-root
575
- * page) never re-fires the notification or re-clears an already-cleared timer.
576
- */
577
- function markSessionRunning(ctx, ap, page) {
578
- if (!page.isRoot || ap.running)
579
- return;
580
- ap.running = true;
581
- clearLaunchTimer(ap);
582
- pushRuntimeStatus(ctx, ap, { phase: 'running' });
583
- }
584
- /**
585
- * The single chokepoint for session runtime-status pushes. Every event
586
- * carries the session's launch-round facts (appId + pageFallback) alongside
587
- * the phase change — the renderer replaces its runtimeStatus wholesale, so an
588
- * event that omitted a still-true fallback would silently blank the fallback
589
- * banner the moment the phase advances past 'launching'.
590
- */
591
- function pushRuntimeStatus(ctx, session, status) {
592
- ctx.notify?.sessionRuntimeStatus?.({
593
- appId: session.appId,
594
- ...status,
595
- ...(session.pageFallback ? { pageFallback: session.pageFallback } : {}),
596
- });
597
- }
598
- async function handleSpawn(state, ctx, event, opts) {
599
- const appId = opts.appId;
600
- if (!appId)
601
- throw new Error('[bridge-router] spawn requires appId');
602
- const bridgeId = opts.bridgeId || newBridgeId();
603
- const appSessionId = bridgeId;
604
- const simulatorWc = resolveSimulatorWebContents(ctx, opts.simulatorWcId, event.sender);
605
- const pagePath = normalizePagePath(opts.pagePath || 'pages/index/index');
606
- const workspaceProjectPath = typeof ctx.workspace.getProjectPath === 'function'
607
- ? ctx.workspace.getProjectPath()
608
- : '';
609
- const pkgRoot = path.resolve(opts.pkgRoot || workspaceProjectPath || process.cwd());
610
- const root = opts.root || 'main';
611
- // Host-config custom API namespaces (WorkbenchContext is the single owner).
612
- // Threaded into the service-host spawn URL so its preload can install the
613
- // namespace globals; the simulator-supplied `opts.apiNamespaces` is derived
614
- // from the same host config and is not authoritative here.
615
- const apiNamespaces = ctx.apiNamespaces ?? [];
616
- // Resource base resolution. Preferred: the simulator-supplied dev-server
617
- // origin, which statically serves the compiled `<appId>/<root>/…` tree (same
618
- // source the default dimina-fe `<webview>` reads). Fallback (no dev server —
619
- // legacy/unit tests): a local server rooted at `pkgRoot/root`. Either way
620
- // `resourceBaseUrl` is the single base all fetches resolve against.
621
- let resourceServer = null;
622
- let resourceBaseUrl;
623
- if (opts.resourceBaseUrl) {
624
- resourceBaseUrl = opts.resourceBaseUrl.endsWith('/') ? opts.resourceBaseUrl : `${opts.resourceBaseUrl}/`;
625
- }
626
- else {
627
- resourceServer = await startDiminaResourceServer(path.resolve(pkgRoot, root));
628
- resourceBaseUrl = resourceServer.baseUrl;
629
- }
630
- // The selected device (renderer toolbar) is the authoritative source for the
631
- // logical dims a spawn must report. The simulator-supplied `hostEnvSnapshot`
632
- // is derived from the device baked into the simulator at BOOT time, so on a
633
- // RESPAWN after a live device change it still carries the boot device. Layer
634
- // the live `currentDevice` on top so every spawn/respawn reports the selected
635
- // device — matching what the live `SetDeviceInfo` HostEnvUpdate pushes to an
636
- // already-running service host. Pre-selection (null) → simulator snapshot wins.
637
- const selectedDevice = ctx.bridge?.getDevice?.() ?? null;
638
- const hostEnv = makeHostEnv({
639
- ...opts.hostEnvSnapshot,
640
- ...(selectedDevice ? deviceInfoToHostEnv(selectedDevice) : {}),
641
- });
642
- // app-config.json lives at `<base><appId>/<root>/app-config.json` on the dev
643
- // server, or at the local server root for the fallback path.
644
- const appConfig = await loadAppConfig(resourceServer ? resourceServer.baseUrl : `${resourceBaseUrl}${appId}/${root}/`, ({ url, error }) => {
645
- ctx.diagnostics?.report({
646
- severity: 'error',
647
- code: 'app-config-unreachable',
648
- message: `app-config.json unreachable at ${url}: ${String(error)}`,
649
- appSessionId,
650
- });
651
- });
652
- const manifest = buildAppManifest(appConfig, pagePath);
653
- const { resolvedPagePath, pageFallbackApplied } = resolveRootPagePath(manifest, pagePath);
654
- if (pageFallbackApplied) {
655
- reportPageNotFound(ctx, appSessionId, pagePath, resolvedPagePath);
656
- }
657
- const rootWindowConfig = resolvePageWindowConfig(appConfig, resolvedPagePath);
658
- const isTab = isTabPage(appConfig, resolvedPagePath);
659
- // Acquire a pre-warmed service-host window when pooling is enabled; otherwise
660
- // construct one fresh (default). A pooled/fallback window is warmed on
661
- // about:blank and must be navigated to the spawn URL below; the fresh path
662
- // navigates inside createServiceHostWindow.
663
- const usedPool = state.pool !== null;
664
- let poolEntryId = null;
665
- let serviceWindow;
666
- // Shared spawn-navigation failure handler (fresh AND pooled paths): without
667
- // it a loadURL rejection is fully swallowed — did-finish-load never fires and
668
- // the only signal is the launch watchdog's late timeout instead of the cause.
669
- const reportServiceHostNavigationFailed = (spawnUrl, err) => {
670
- const message = `Failed to navigate service host to ${spawnUrl}: ${String(err)}`;
671
- ctx.diagnostics?.report({
672
- severity: 'error',
673
- code: 'service-host-navigation-failed',
674
- message,
675
- appSessionId,
676
- });
677
- // The session registers after window construction; resolve it fresh so the
678
- // fresh-path callback (created pre-registration) still clears the watchdog.
679
- const ap = state.appSessions.get(appSessionId);
680
- if (ap)
681
- clearLaunchTimer(ap);
682
- pushRuntimeStatus(ctx, ap ?? { appId, pageFallback: pageFallbackApplied ? { requested: pagePath, resolved: resolvedPagePath } : null }, { phase: 'launch-failed', code: 'service-host-navigation-failed', reason: message });
683
- };
684
- if (state.pool) {
685
- const acquired = await state.pool.acquire(serviceHostSpec());
686
- serviceWindow = acquired.win;
687
- poolEntryId = acquired.entryId;
688
- }
689
- else {
690
- const freshWindowOptions = {
691
- bridgeId,
692
- appId,
693
- // Same (appId, projectPath) pair the simulator WCV uses, so this project's
694
- // service host and render guests land on ONE partition while a different
695
- // project with the same appId is isolated. Use the path captured at spawn
696
- // ENTRY (workspaceProjectPath), not a re-read: a project switch during the
697
- // awaits above would otherwise give the service host a different path than
698
- // the simulator WCV was built with, splitting the partition.
699
- projectPath: workspaceProjectPath || undefined,
700
- pagePath: resolvedPagePath,
701
- pkgRoot,
702
- root,
703
- resourceBaseUrl,
704
- hostEnvSnapshot: hostEnv,
705
- apiNamespaces,
706
- };
707
- serviceWindow = createServiceHostWindow({
708
- ...freshWindowOptions,
709
- onLoadFailed: err => reportServiceHostNavigationFailed(buildServiceHostSpawnUrl(freshWindowOptions), err),
710
- });
711
- }
712
- const appSession = {
713
- appSessionId,
714
- appId,
715
- pkgRoot,
716
- root,
717
- scene: opts.scene ?? 1001,
718
- serviceWindow,
719
- serviceWc: serviceWindow.webContents,
720
- simulatorWc,
721
- serviceLoaded: false,
722
- logicInjected: null,
723
- resourceBaseUrl,
724
- resourceServer,
725
- hostEnv,
726
- appConfig,
727
- manifest,
728
- pages: new Map(),
729
- activeBridgeId: null,
730
- poolEntryId,
731
- onServiceBoot: null,
732
- listenerBag: createSessionListenerBag(),
733
- registryHandle: null,
734
- running: false,
735
- launchTimer: null,
736
- pageFallback: pageFallbackApplied ? { requested: pagePath, resolved: resolvedPagePath } : null,
737
- };
738
- const rootPage = {
739
- bridgeId,
740
- appSessionId,
741
- pagePath: resolvedPagePath,
742
- query: opts.query ?? {},
743
- isRoot: true,
744
- isTab,
745
- renderWc: null,
746
- renderLoaded: false,
747
- resourceLoadedSent: false,
748
- renderLoadPending: false,
749
- windowConfig: rootWindowConfig,
750
- };
751
- state.appSessions.set(appSessionId, appSession);
752
- state.pageSessions.set(bridgeId, rootPage);
753
- appSession.pages.set(bridgeId, rootPage);
754
- bindWc(state.serviceWcIdToAppSessionId, serviceWindow.webContents, appSessionId);
755
- bindSimulatorWc(state.simulatorWcIdToAppSessionIds, simulatorWc, appSessionId);
756
- // Track the service-host webContents as a Connection (foundation.md §4.3) and
757
- // own this session's serviceWc→appSessionId binding on its CURRENT lifetime
758
- // segment. On a pooled-window REUSE, `disposeAppSession` calls
759
- // `connections.reset(serviceWc.id)` → this owned cleanup fires + a fresh
760
- // segment opens, so the next session rebinding the same wc starts clean; on a
761
- // real destroy the connection closes and fires it too. The cleanup is guarded
762
- // by `appSessionId` so it only clears a binding that is still THIS session's.
763
- // (The simulatorWc binding stays manual in `disposeAppSession`: the simulator
764
- // wc outlives individual app sessions — cross-connection state, §4.4 — so it
765
- // cannot hang off any single session's segment.)
766
- state.connections.acquire(serviceWindow.webContents).own(() => {
767
- if (state.serviceWcIdToAppSessionId.get(appSession.serviceWc.id) === appSessionId) {
768
- state.serviceWcIdToAppSessionId.delete(appSession.serviceWc.id);
769
- }
770
- });
771
- appSession.registryHandle = ctx.registry.add(() => disposeAppSession(state, appSessionId));
772
- const onServiceClosed = () => {
773
- void disposeAppSession(state, appSessionId, { serviceAlreadyClosed: true });
774
- };
775
- appSession.listenerBag.once(serviceWindow, 'closed', onServiceClosed);
776
- // The simulator WCV owns the app's UI lifetime. When it is destroyed —
777
- // project close (`views.disposeAll`/detach) or a DeviceShell respawn
778
- // (`attachNativeSimulator`, e.g. watcher hot reload) — the guest never gets
779
- // to send its graceful `C.DISPOSE`, so without this hook the app session and
780
- // its hidden service-host window leak, and `resolveCurrentApp` keeps
781
- // resolving the STALE session for the same appId: `getActiveRenderWc` then
782
- // dereferences dead pages and every panel pull (WXML/elements) returns null
783
- // for the whole next session. Disposing here is idempotent with the graceful
784
- // path (`disposeAppSession` early-returns once the session is gone).
785
- const onSimulatorDestroyed = () => {
786
- void disposeAppSession(state, appSessionId);
787
- };
788
- appSession.listenerBag.once(simulatorWc, 'destroyed', onSimulatorDestroyed);
789
- // The simulator WCV may have been torn down DURING this spawn's awaits
790
- // (loadAppConfig / resource server / service-host creation) — e.g. a project
791
- // close/switch that ran while we were mid-flight. In that case its 'destroyed'
792
- // event already fired and the once() above will never run, so the freshly
793
- // registered session would be a zombie owned by a dead simulator (the
794
- // teardown's disposeSessionsForSimulator snapshot taken before appSessions.set
795
- // could not have included it). Dispose now — idempotent with the graceful
796
- // path — so a closed/superseded simulator can't leave a resurrected session.
797
- if (simulatorWc.isDestroyed()) {
798
- void disposeAppSession(state, appSessionId);
799
- }
800
- if (usedPool) {
801
- // A pooled/fallback window passes through about:blank (warm load or the
802
- // fallback's initial load). Boot only once the REAL service.html navigation
803
- // finishes — filtering by URL so an about:blank did-finish-load can't fire a
804
- // premature boot. The listener removes itself after the first match.
805
- const bootOnServiceLoad = () => {
806
- if (serviceWindow.isDestroyed())
807
- return;
808
- // This session was disposed before its service.html settled: self-evict so
809
- // a recycled window doesn't carry a stale listener into the next spawn.
810
- if (state.appSessions.get(appSessionId) !== appSession) {
811
- serviceWindow.webContents.removeListener('did-finish-load', bootOnServiceLoad);
812
- return;
813
- }
814
- // Ignore the warm/about:blank load; only the real service.html boots.
815
- if (!serviceWindow.webContents.getURL().includes('service.html'))
816
- return;
817
- serviceWindow.webContents.removeListener('did-finish-load', bootOnServiceLoad);
818
- void bootServiceHost(state, appSession, ctx);
819
- };
820
- appSession.onServiceBoot = bootOnServiceLoad;
821
- serviceWindow.webContents.on('did-finish-load', bootOnServiceLoad);
822
- const spawnUrl = buildServiceHostSpawnUrl({
823
- bridgeId,
824
- appId,
825
- pagePath: resolvedPagePath,
826
- pkgRoot,
827
- root,
828
- resourceBaseUrl,
829
- hostEnvSnapshot: hostEnv,
830
- apiNamespaces,
831
- });
832
- void navigateServiceHost(serviceWindow, spawnUrl, {
833
- onLoadFailed: err => reportServiceHostNavigationFailed(spawnUrl, err),
834
- });
835
- }
836
- else {
837
- // Fresh window: its only navigation is service.html (issued inside
838
- // createServiceHostWindow), so the first did-finish-load is the spawn load.
839
- serviceWindow.webContents.once('did-finish-load', () => {
840
- void bootServiceHost(state, appSession, ctx);
841
- });
842
- }
843
- // Service-host crash (renderer process gone): a session-scoped hook on the
844
- // service webContents, bound via the listener bag so `disposeAppSession`
845
- // detaches it (matching the 'closed'/'destroyed' hooks registered above).
846
- // Not a `.once` — a pooled service window can recover and crash again
847
- // across its lifetime, and each crash is independently worth surfacing.
848
- // Fires whether the crash lands before or after 'running'; only the launch
849
- // timer needs clearing (it may already be null) since a running session no
850
- // longer has one armed.
851
- const onServiceCrashed = () => {
852
- clearLaunchTimer(appSession);
853
- ctx.diagnostics?.report({
854
- severity: 'error',
855
- code: 'service-host-crashed',
856
- message: `Service host renderer process gone for appSessionId=${appSessionId}`,
857
- appSessionId,
858
- });
859
- pushRuntimeStatus(ctx, appSession, { phase: 'crashed', code: 'service-host-crashed' });
860
- };
861
- appSession.listenerBag.on(serviceWindow.webContents, 'render-process-gone', onServiceCrashed);
862
- pushRuntimeStatus(ctx, appSession, { phase: 'launching' });
863
- startLaunchTimer(state, ctx, appSession);
864
- return {
865
- appSessionId,
866
- bridgeId,
867
- pagePath,
868
- resolvedPagePath,
869
- pageFallbackApplied,
870
- serviceWcId: serviceWindow.webContents.id,
871
- resourceBaseUrl,
872
- manifest,
873
- rootWindowConfig,
874
- };
875
- }
876
- async function handlePageOpen(state, event, opts) {
877
- const ap = state.appSessions.get(opts.appSessionId);
878
- if (!ap)
879
- throw new Error(`[bridge-router] PAGE_OPEN unknown appSession ${opts.appSessionId}`);
880
- // Only the simulator window owning the app can open additional pages.
881
- if (!senderBoundToSession(state, event.sender, ap)) {
882
- throw new Error('[bridge-router] PAGE_OPEN rejected: caller not bound to app session');
883
- }
884
- const pagePath = normalizePagePath(opts.pagePath);
885
- // Defense-in-depth against a page absent from the compiled manifest: the
886
- // nav gate (handleNavActionApi) already blocks normal navigateTo/switchTab
887
- // traffic upstream, but PAGE_OPEN is reachable directly (any IPC caller), so
888
- // this refuses to register a PageSession main can never load resources for.
889
- // Only enforced against a real compiled manifest ('app-config') — a
890
- // 'fallback' manifest has no compiled truth to validate membership against.
891
- if (ap.manifest.source === 'app-config' && !ap.manifest.pages.includes(pagePath)) {
892
- throw new Error(`[bridge-router] PAGE_OPEN rejected: page-not-found "${pagePath}" is not in the compiled manifest`);
893
- }
894
- const bridgeId = opts.bridgeId || newBridgeId();
895
- const windowConfig = resolvePageWindowConfig(ap.appConfig, pagePath);
896
- const isTab = isTabPage(ap.appConfig, pagePath);
897
- const page = {
898
- bridgeId,
899
- appSessionId: opts.appSessionId,
900
- pagePath,
901
- query: opts.query ?? {},
902
- isRoot: false,
903
- isTab,
904
- renderWc: null,
905
- renderLoaded: false,
906
- resourceLoadedSent: false,
907
- renderLoadPending: false,
908
- windowConfig,
909
- };
910
- state.pageSessions.set(bridgeId, page);
911
- ap.pages.set(bridgeId, page);
912
- return { bridgeId, pagePath, windowConfig, isTab };
913
- }
914
- function handlePageClose(state, sender, payload) {
915
- const page = state.pageSessions.get(payload.bridgeId);
916
- if (!page)
917
- return;
918
- if (page.isRoot) {
919
- console.warn('[bridge-router] PAGE_CLOSE refused on root page; use DISPOSE');
920
- return;
921
- }
922
- const ap = state.appSessions.get(page.appSessionId);
923
- if (!ap)
924
- return;
925
- if (!senderBoundToSession(state, sender, ap)) {
926
- console.warn('[bridge-router] PAGE_CLOSE rejected: caller not bound to app session');
927
- return;
928
- }
929
- disposePageSession(state, ap, page);
930
- }
931
- function handlePageLifecycle(state, sender, payload) {
932
- const ap = state.appSessions.get(payload.appSessionId);
933
- if (!ap)
934
- return;
935
- if (!senderBoundToSession(state, sender, ap))
936
- return;
937
- forwardToService(ap, {
938
- type: payload.event,
939
- target: 'service',
940
- body: { bridgeId: payload.bridgeId },
941
- });
942
- }
943
- function handleNavCallback(state, sender, payload) {
944
- const ap = state.appSessions.get(payload.appSessionId);
945
- if (!ap)
946
- return;
947
- if (!senderBoundToSession(state, sender, ap))
948
- return;
949
- const result = { errMsg: payload.errMsg };
950
- if (payload.ok) {
951
- sendCallback(ap, payload.callbacks.success, result);
952
- }
953
- else {
954
- sendCallback(ap, payload.callbacks.fail, result);
955
- }
956
- sendCallback(ap, payload.callbacks.complete, result);
957
- }
958
- // ── Service-host boot & per-page resource handshake ──────────────────────────
959
- async function bootServiceHost(state, ap, ctx) {
960
- // Liveness guard: never boot a session that was already disposed. With pooling,
961
- // the service window is recycled, so a stale did-finish-load listener from an
962
- // early-disposed prior owner could otherwise fire here and inject the wrong
963
- // app's logic.js into the next spawn (the recycled webContents is shared).
964
- if (state.appSessions.get(ap.appSessionId) !== ap)
965
- return;
966
- // The service wc just did-finish-load'd (that's how this got invoked) — flush
967
- // any diagnostic queued for this session (or the global bucket) into its now
968
- // resolvable console. Safe to call even when nothing is queued.
969
- ctx.consoleForwarder?.notifyServiceHostReady?.(ap.appSessionId);
970
- ap.logicInjected = await injectLogicBundle(ap);
971
- if (!ap.logicInjected) {
972
- // The compiled logic.js never executed, so `modDefine` registered nothing.
973
- // Sending `loadResource` now would run `modRequire('app')` against an empty
974
- // registry and throw the cryptic `module app not found` — masking the real
975
- // cause (unreachable resource tree / failed compile / unresolved appId).
976
- // Skip it and surface one actionable diagnostic instead; the render side
977
- // gates on the same flag in `routeFromRender`.
978
- const reason = reportLogicLoadFailure(ap, ctx);
979
- clearLaunchTimer(ap);
980
- pushRuntimeStatus(ctx, ap, { phase: 'launch-failed', code: 'logic-bundle-unreachable', reason });
981
- return;
982
- }
983
- // A root page absent from the compiled manifest — most commonly a page the
984
- // developer deleted, then hot-reloaded to — is not registered in logic.js.
985
- // BOTH the service runtime AND the render runtime eager-`modRequire(pagePath)`
986
- // it and throw `module <pagePath> not found`, which aborts the launch and
987
- // leaves the simulator permanently blank. Gate BOTH sends and surface a
988
- // WeChat-style diagnostic instead: the shell stays alive (an empty phone frame
989
- // + a clear Console error) rather than a silent dead blank. Choosing a valid
990
- // fallback page belongs at the renderer reload source (it owns the pagePath
991
- // the render guest is spawned with); here we only refuse a load guaranteed to
992
- // throw, so main and render never disagree about which page is live.
993
- const rootPage = ap.pages.get(ap.appSessionId);
994
- const rootMissing = !!rootPage && !pageInManifest(ap, rootPage.pagePath);
995
- if (rootMissing) {
996
- reportPageNotFound(ctx, ap.appSessionId, rootPage.pagePath);
997
- }
998
- else if (rootPage) {
999
- // serviceLoaded is flipped only when service responds with
1000
- // `serviceResourceLoaded`; see handleContainerMsg. Setting it here would
1001
- // race a per-page `resourceLoaded` ahead of the service-side handler.
1002
- forwardToService(ap, makeLoadResource(ap, rootPage, 'service'));
1003
- }
1004
- // Flush any render `loadResource` that arrived (renderHostReady) while
1005
- // injection was in-flight — now that the bundle is confirmed present.
1006
- // `sendRenderLoadResource` refuses a page absent from the manifest, so a
1007
- // missing root never reaches the throwing render `modRequire`.
1008
- for (const page of ap.pages.values()) {
1009
- if (!page.renderLoadPending)
1010
- continue;
1011
- page.renderLoadPending = false;
1012
- sendRenderLoadResource(ap, page);
1013
- }
1014
- }
1015
- function sendRenderLoadResource(ap, page) {
1016
- if (!page.renderWc || page.renderWc.isDestroyed())
1017
- return;
1018
- // Single choke point for BOTH the boot flush above and the late
1019
- // `renderHostReady` direct-send in `routeFromRender`: never send a render load
1020
- // for a page absent from the compiled manifest. The render runtime
1021
- // eager-`modRequire`s the pagePath and would throw `module <pagePath> not
1022
- // found`, blanking the simulator (a page the developer deleted, then
1023
- // hot-reloaded to). bootServiceHost surfaces the one-shot diagnostic.
1024
- if (!pageInManifest(ap, page.pagePath))
1025
- return;
1026
- page.renderWc.send(C.TO_RENDER, { msg: makeLoadResource(ap, page, 'render') });
1027
- }
1028
- /** The compiled logic.js URL `injectLogicBundle` fetches (mode-dependent). */
1029
- function logicBundleUrl(ap) {
1030
- return ap.resourceServer
1031
- ? new URL('logic.js', ap.resourceBaseUrl).toString()
1032
- : new URL(`${ap.appId}/${ap.root}/logic.js`, ap.resourceBaseUrl).toString();
1033
- }
1034
- async function injectLogicBundle(ap) {
1035
- // Fetch the compiled service logic over HTTP from the same base the render
1036
- // host reads (`<base><appId>/<root>/logic.js`). The fallback local server
1037
- // serves its root, so `<base>logic.js` resolves there too — both are http.
1038
- const logicUrl = logicBundleUrl(ap);
1039
- try {
1040
- const res = await fetch(logicUrl);
1041
- if (!res.ok)
1042
- throw new Error(`logic.js fetch ${res.status} at ${logicUrl}`);
1043
- // Rewrite the relative `sourceMappingURL` to an absolute dev-server URL
1044
- // BEFORE injecting — `executeJavaScript` gives the script no base URL, so a
1045
- // relative map would 404 and break sourcemapped console frames / Sources.
1046
- const logicContent = rewriteSourceMappingUrl(await res.text(), logicUrl);
1047
- await ap.serviceWc.executeJavaScript(`${logicContent}\n//# sourceURL=${logicUrl}`, true);
1048
- return true;
1049
- }
1050
- catch (error) {
1051
- console.warn('[bridge-router] unable to inject service logic.js:', error);
1052
- return false;
1053
- }
1054
- }
1055
- /**
1056
- * Emit one actionable diagnostic when the compiled logic bundle could not be
1057
- * loaded — the single point where "the app's compiled resource tree is
1058
- * unreachable" is known with certainty. Reported through `ctx.diagnostics`,
1059
- * which mirrors it to the main-process log AND — via `consoleForwarder`'s
1060
- * subscription — injects it into the service host's own console, so it
1061
- * actually surfaces in the embedded DevTools Console panel where the
1062
- * developer would otherwise see only the misleading `module app not found`.
1063
- * `guestConsole.emit` is kept alongside for the automation `App.logAdded`
1064
- * subscriber, which does not consume the diagnostics bus.
1065
- *
1066
- * Returns the short (pre-hint) form of the message so `bootServiceHost` can
1067
- * reuse it verbatim as the `sessionRuntimeStatus` `reason` instead of
1068
- * duplicating the wording.
1069
- */
1070
- function reportLogicLoadFailure(ap, ctx) {
1071
- const hint = ap.appId === 'unknown'
1072
- ? ' appId could not be resolved (it fell back to "unknown") — the mini-program likely failed to compile or its project manifest/app config is missing.'
1073
- : '';
1074
- const shortReason = `[dimina-kit] Failed to load the mini-program logic bundle from ${logicBundleUrl(ap)}.`;
1075
- const message = `${shortReason} `
1076
- + 'The service runtime has no registered modules, so no page can mount.'
1077
- + hint
1078
- + ` Verify the project compiled successfully and that the resource server serves "${ap.appId}/${ap.root}/".`;
1079
- ctx.diagnostics?.report({
1080
- severity: 'error',
1081
- code: 'logic-bundle-unreachable',
1082
- message,
1083
- appSessionId: ap.appSessionId,
1084
- });
1085
- ctx.guestConsole?.emit({ source: 'service', level: 'error', args: [message] });
1086
- return shortReason;
1087
- }
1088
- /** Whether `pagePath` still exists in the app's freshly compiled manifest. */
1089
- function pageInManifest(ap, pagePath) {
1090
- // A 'fallback' manifest (app-config.json unreachable) holds only the spawn
1091
- // request — no compiled truth to check membership against. Every mount gate
1092
- // keyed on manifest membership must agree: the nav/PAGE_OPEN front gates
1093
- // already let a fallback manifest through, so this back gate (render
1094
- // loadResource) must too, or a page they admitted silently never loads.
1095
- if (ap.manifest.source !== 'app-config')
1096
- return true;
1097
- return ap.manifest.pages.includes(normalizePagePath(pagePath));
1098
- }
1099
- /**
1100
- * WeChat-devtools-style "page does not exist" diagnostic. Reported through
1101
- * `ctx.diagnostics` (main log + injected into the embedded Console panel via
1102
- * `consoleForwarder`) when a mount targets a pagePath that is not in the
1103
- * compiled manifest — most commonly a page the developer deleted, then
1104
- * hot-reloaded. `guestConsole.emit` is kept alongside for the automation
1105
- * subscriber.
1106
- */
1107
- function reportPageNotFound(ctx, appSessionId, pagePath, fallbackTo) {
1108
- const base = `Page[${pagePath}] not found. May be caused by: 1. Forgetting to add page route in app.json. `
1109
- + '2. Invoking Page() in async task.';
1110
- const message = fallbackTo ? `${base} Falling back to "${fallbackTo}".` : base;
1111
- ctx.diagnostics?.report({
1112
- severity: 'error',
1113
- code: 'page-not-found',
1114
- message,
1115
- appSessionId,
1116
- });
1117
- ctx.guestConsole?.emit({ source: 'service', level: 'error', args: [message] });
1118
- }
1119
- function maybeSendResourceLoaded(ap, page) {
1120
- if (page.resourceLoadedSent || !ap.serviceLoaded || !page.renderLoaded)
1121
- return;
1122
- page.resourceLoadedSent = true;
1123
- // Non-root pages (navigateTo / redirectTo / reLaunch / non-cached switchTab)
1124
- // were never sent a SERVICE-side loadResource — only the root gets one at
1125
- // bootServiceHost. Without it the service never `modRequire`s the page module,
1126
- // so `getModuleByPath` misses and `createInstance` no-ops: the page mounts on
1127
- // the render side but its service instance — and thus its AppData / WXML /
1128
- // `Page.getData` — never exist. Mirror the render side, which sends
1129
- // loadResource per page on `renderHostReady`. `loadResource` only registers
1130
- // the module definition (no firstRender), and same-channel FIFO ordering
1131
- // guarantees the module is registered before the `resourceLoaded` below runs
1132
- // `createInstance`. Safety invariants 1-4 reviewed in
1133
- // docs/native-host-abstractions.md.
1134
- if (!page.isRoot) {
1135
- forwardToService(ap, makeLoadResource(ap, page, 'service'));
1136
- }
1137
- forwardToService(ap, {
1138
- type: 'resourceLoaded',
1139
- target: 'service',
1140
- body: {
1141
- bridgeId: page.bridgeId,
1142
- scene: ap.scene,
1143
- pagePath: page.pagePath,
1144
- query: page.query,
1145
- stackId: STACK_ID,
1146
- },
1147
- });
1148
- }
1149
- // ── Message routing ──────────────────────────────────────────────────────────
1150
- function routeFromService(state, ap, defaultPage, msg, ctx) {
1151
- if (msg.type === 'serviceResourceLoaded') {
1152
- handleContainerMsg(ap, defaultPage, msg, ctx, state);
1153
- return;
1154
- }
1155
- if (msg.target === 'render') {
1156
- forwardToRender(ap, msg);
1157
- return;
1158
- }
1159
- if (msg.target === 'container') {
1160
- const page = pageFromMsg(state, ap, msg) ?? defaultPage;
1161
- handleContainerMsg(ap, page, msg, ctx, state);
1162
- }
1163
- }
1164
- function routeFromRender(state, ap, page, msg, ctx) {
1165
- if (msg.type === 'renderHostReady') {
1166
- // Logic injection definitively failed: the render bundle shares the same
1167
- // (unreachable) resource tree, so `loadResource` here would only produce a
1168
- // second cryptic `module <pagePath> not found`. bootServiceHost already
1169
- // surfaced the actionable diagnostic — stay silent.
1170
- if (ap.logicInjected === false)
1171
- return;
1172
- // Injection still in-flight: the render guest fires `renderHostReady` on its
1173
- // own DOMContentLoaded, which routinely beats the async fetch+inject. Hold
1174
- // the render `loadResource` and let `bootServiceHost` flush it once the
1175
- // bundle settles, so a failed bundle never reaches the render side.
1176
- if (ap.logicInjected === null) {
1177
- page.renderLoadPending = true;
1178
- return;
1179
- }
1180
- sendRenderLoadResource(ap, page);
1181
- return;
1182
- }
1183
- if (msg.type === 'renderResourceLoaded') {
1184
- handleContainerMsg(ap, page, msg, ctx, state);
1185
- return;
1186
- }
1187
- if (msg.target === 'service') {
1188
- forwardToService(ap, msg);
1189
- return;
1190
- }
1191
- if (msg.target === 'container') {
1192
- handleContainerMsg(ap, page, msg, ctx, state);
1193
- }
1194
- }
1195
- /**
1196
- * Report 'service-uncaught-error' for a consoleLog(source:'service') body —
1197
- * the service preload's uncaught error/unhandledrejection post. CDP's
1198
- * `Runtime.consoleAPICalled` (services/service-console) never observes these
1199
- * (they never call `console.*`), so this diagnostics report is the only way
1200
- * one reaches the Console panel / main log.
1201
- */
1202
- function reportServiceUncaughtError(ctx, ap, body) {
1203
- const severity = body.level === 'error' ? 'error' : body.level === 'warn' ? 'warn' : 'info';
1204
- const message = Array.isArray(body.args) ? body.args.map(a => String(a)).join(' ') : String(body.args ?? '');
1205
- ctx.diagnostics?.report({ severity, code: 'service-uncaught-error', message, appSessionId: ap.appSessionId });
1206
- }
1207
- function handleContainerMsg(ap, page, msg, ctx, state) {
1208
- switch (msg.type) {
1209
- case 'serviceResourceLoaded':
1210
- ap.serviceLoaded = true;
1211
- for (const p of ap.pages.values())
1212
- maybeSendResourceLoaded(ap, p);
1213
- break;
1214
- case 'renderResourceLoaded': {
1215
- const target = readBridgeId(msg);
1216
- const p = (target && ap.pages.get(target)) || page;
1217
- p.renderLoaded = true;
1218
- maybeSendResourceLoaded(ap, p);
1219
- break;
1220
- }
1221
- case 'domReady':
1222
- if (!ap.simulatorWc.isDestroyed()) {
1223
- ap.simulatorWc.send(E.DOM_READY, { bridgeId: page.bridgeId });
1224
- }
1225
- state.emitRenderEvent({ kind: 'domReady', appId: ap.appId, bridgeId: page.bridgeId });
1226
- markSessionRunning(ctx, ap, page);
1227
- break;
1228
- case 'invokeAPI':
1229
- void handleSimulatorApi(state, ap, page, msg.body, ctx);
1230
- break;
1231
- case 'serviceHostError': {
1232
- // Forward the error to every registered `wx.onError` listener. (The dimina
1233
- // service runtime's App lifecycle is only onLaunch/onShow/onHide, so it
1234
- // does not dispatch `App.onError` — `wx.onError` is the supported path.)
1235
- // The service preload's `deliver` try/catch already reports errors thrown
1236
- // during lifecycle/event dispatch here.
1237
- const errBody = msg.body;
1238
- const errArg = errBody?.message ?? msg.body;
1239
- // Reported through the diagnostics bus (main log + Console-panel
1240
- // injection); `ctx.diagnostics.report` replaces the bare `console.warn`
1241
- // this case used to make directly.
1242
- ctx.diagnostics?.report({
1243
- severity: 'error',
1244
- code: 'service-host-error',
1245
- message: String(errArg),
1246
- appSessionId: ap.appSessionId,
1247
- });
1248
- for (const id of state.appLifecycle.listeners(ap.appSessionId, 'onError')) {
1249
- sendCallback(ap, id, errArg);
1250
- }
1251
- break;
1252
- }
1253
- case 'consoleLog': {
1254
- // Native-host console capture: the render-host / service-host guest preloads
1255
- // monkeypatch console.* and post each entry here (one case handles both —
1256
- // distinguish via msg.body.source). Forward to the console sink (set by the
1257
- // automation server under native-host) so it can rebroadcast as App.logAdded.
1258
- // Guarded + non-throwing: console capture must never break message routing.
1259
- ctx.guestConsole?.emit(msg.body);
1260
- const body = msg.body;
1261
- if (body?.source === 'service')
1262
- reportServiceUncaughtError(ctx, ap, body);
1263
- break;
1264
- }
1265
- case 'storageChanged':
1266
- // Native-host SYNC storage liveness: the service-host's `setStorageSync`/etc.
1267
- // write `localStorage` directly (no main round-trip), so they post the change
1268
- // here for the Storage panel to stay live. Trust `ap.appId` (sender-resolved),
1269
- // not any appId in the body. Guarded + non-throwing.
1270
- ctx.onServiceStorageChanged?.(ap.appId, msg.body);
1271
- break;
1272
- case 'wxmlChanged':
1273
- // Native-host WXML liveness: the render-guest MutationObserver posts this when
1274
- // the active page's DOM mutated in place (setData). Surface it as a render
1275
- // event so the WXML panel service re-pulls + pushes — same pipeline as
1276
- // domReady/activePage. Trust `page.bridgeId` (sender-resolved), not the body.
1277
- state.emitRenderEvent({ kind: 'domMutated', appId: ap.appId, bridgeId: page.bridgeId });
1278
- break;
1279
- default:
1280
- break;
1281
- }
1282
- }
1283
- // ── Simulator-window-resident APIs ──────────────────────────────────────────
1284
- const NAV_BAR_API_NAMES = new Set([
1285
- 'setNavigationBarTitle',
1286
- 'setNavigationBarColor',
1287
- 'showNavigationBarLoading',
1288
- 'hideNavigationBarLoading',
1289
- 'hideHomeButton',
1290
- ]);
1291
- const NAV_ACTION_NAMES = new Set([
1292
- 'navigateTo',
1293
- 'navigateBack',
1294
- 'redirectTo',
1295
- 'reLaunch',
1296
- 'switchTab',
1297
- ]);
1298
- const TAB_ACTION_NAMES = new Set([
1299
- 'setTabBarStyle',
1300
- 'setTabBarItem',
1301
- 'showTabBar',
1302
- 'hideTabBar',
1303
- 'setTabBarBadge',
1304
- 'removeTabBarBadge',
1305
- 'showTabBarRedDot',
1306
- 'hideTabBarRedDot',
1307
- ]);
1308
- /**
1309
- * Drive app foreground/background from the main window's visibility. Minimizing
1310
- * or hiding the window backgrounds the mini-program (App.onHide + wx.onAppHide);
1311
- * restoring or showing it foregrounds it (App.onShow + wx.onAppShow). Focus/blur
1312
- * is deliberately NOT used — clicking a DevTools panel would falsely fire it.
1313
- * The initial App.onShow fires once at spawn (service `invokeSomeLifecycle`), so
1314
- * the driver only handles subsequent transitions.
1315
- */
1316
- function installAppLifecycleDriver(ctx, state) {
1317
- const win = ctx.windows?.mainWindow;
1318
- // Guard against a headless/stub mainWindow (unit tests) that isn't a real
1319
- // event-emitting BrowserWindow.
1320
- if (!win || typeof win.on !== 'function')
1321
- return;
1322
- const emit = (serviceEvent, listenerEvent) => {
1323
- for (const ap of state.appSessions.values()) {
1324
- // App.onShow / onHide: the service runtime already listens for these.
1325
- forwardToService(ap, { type: serviceEvent, target: 'service', body: {} });
1326
- // wx.onAppShow / onAppHide imperative listeners.
1327
- for (const id of state.appLifecycle.listeners(ap.appSessionId, listenerEvent)) {
1328
- sendCallback(ap, id, {});
1329
- }
1330
- }
1331
- };
1332
- const onHide = () => emit('appHide', 'onAppHide');
1333
- const onShow = () => emit('appShow', 'onAppShow');
1334
- win.on('hide', onHide);
1335
- win.on('minimize', onHide);
1336
- win.on('show', onShow);
1337
- win.on('restore', onShow);
1338
- ctx.registry.add(() => {
1339
- win.off('hide', onHide);
1340
- win.off('minimize', onHide);
1341
- win.off('show', onShow);
1342
- win.off('restore', onShow);
1343
- });
1344
- }
1345
- // wx app-event API name → the lifecycle event whose listeners it (de)registers.
1346
- const APP_LIFECYCLE_REGISTER = {
1347
- onAppShow: 'onAppShow',
1348
- onAppHide: 'onAppHide',
1349
- onError: 'onError',
1350
- };
1351
- const APP_LIFECYCLE_UNREGISTER = {
1352
- offAppShow: 'onAppShow',
1353
- offAppHide: 'onAppHide',
1354
- offError: 'onError',
1355
- };
1356
- function handleNavBarApi(ap, page, name, params) {
1357
- if (!ap.simulatorWc.isDestroyed()) {
1358
- ap.simulatorWc.send(E.NAV_BAR, {
1359
- bridgeId: page.bridgeId,
1360
- name,
1361
- params,
1362
- });
1363
- }
1364
- const successResult = { errMsg: `${name}:ok` };
1365
- sendCallback(ap, params.success, successResult);
1366
- sendCallback(ap, params.complete, successResult);
1367
- }
1368
- // Fails a nav/tab action's service-side callback through the same success/
1369
- // complete dispatch every other API branch uses — the one mechanism for
1370
- // answering a call that never reaches (or never should reach) the simulator.
1371
- function failActionCallback(ap, params, errMsg) {
1372
- const fail = { errMsg };
1373
- sendCallback(ap, params.fail, fail);
1374
- sendCallback(ap, params.complete, fail);
1375
- }
1376
- // Shared by handleNavActionApi/handleTabActionApi: both forward a same-shaped
1377
- // payload to the simulator window, differing only in the event name and the
1378
- // payload's static `name` union. Simulator-destroyed fails the callback
1379
- // directly since there is no ack path to fail through otherwise.
1380
- function sendActionOrFail(ap, eventName, payload, name, params) {
1381
- if (!ap.simulatorWc.isDestroyed()) {
1382
- ap.simulatorWc.send(eventName, payload);
1383
- return;
1384
- }
1385
- failActionCallback(ap, params, `${name}:fail simulator window destroyed`);
1386
- }
1387
- /** The bare page path a nav/tab action targets (query-stripped, normalized), or undefined for an action carrying none (navigateBack). */
1388
- function extractNavTargetPagePath(params) {
1389
- const raw = params.url;
1390
- if (typeof raw !== 'string' || !raw)
1391
- return undefined;
1392
- return normalizePagePath(raw.split('?')[0] ?? raw);
1393
- }
1394
- /**
1395
- * Whether a nav/tab action's target page is mountable — checked ONLY against
1396
- * a real compiled manifest (`source === 'app-config'`); a `'fallback'`
1397
- * manifest can't tell mountable from not, so it lets everything through.
1398
- */
1399
- function checkNavTarget(ap, name, targetPagePath) {
1400
- if (ap.manifest.source !== 'app-config')
1401
- return { ok: true };
1402
- if (!ap.manifest.pages.includes(targetPagePath)) {
1403
- return { ok: false, errMsg: `${name}:fail page "${targetPagePath}" is not found`, reportNotFound: true };
1404
- }
1405
- if (name === 'switchTab') {
1406
- const inTabBar = ap.manifest.tabBar?.list.some(item => normalizePagePath(item.pagePath) === targetPagePath) ?? false;
1407
- if (!inTabBar)
1408
- return { ok: false, errMsg: 'switchTab:fail can not switch to no-tabBar page' };
1409
- }
1410
- return { ok: true };
1411
- }
1412
- function handleNavActionApi(ap, ctx, page, name, params) {
1413
- const targetPagePath = extractNavTargetPagePath(params);
1414
- if (targetPagePath !== undefined) {
1415
- const verdict = checkNavTarget(ap, name, targetPagePath);
1416
- if (!verdict.ok) {
1417
- if (verdict.reportNotFound)
1418
- reportPageNotFound(ctx, ap.appSessionId, targetPagePath);
1419
- failActionCallback(ap, params, verdict.errMsg);
1420
- return;
1421
- }
1422
- }
1423
- const payload = {
1424
- appSessionId: ap.appSessionId,
1425
- bridgeId: page.bridgeId,
1426
- name: name,
1427
- params,
1428
- callbacks: extractCallbacks(params),
1429
- };
1430
- sendActionOrFail(ap, E.NAV_ACTION, payload, name, params);
1431
- }
1432
- function handleTabActionApi(ap, page, name, params) {
1433
- const payload = {
1434
- appSessionId: ap.appSessionId,
1435
- bridgeId: page.bridgeId,
1436
- name: name,
1437
- params,
1438
- callbacks: extractCallbacks(params),
1439
- };
1440
- sendActionOrFail(ap, E.TAB_ACTION, payload, name, params);
1441
- }
1442
- // App-level lifecycle listeners (wx.onAppShow / onAppHide / onError + off*).
1443
- // The service encodes the listener as a keep callback id in `params.success`;
1444
- // we store it and re-fire on main-window foreground/background (the driver in
1445
- // installBridgeRouter) and on serviceHostError. `off*` clears the event's
1446
- // listeners for the session. Returns true when `name` matched a lifecycle
1447
- // register/unregister call (fully handled), false to fall through.
1448
- function handleAppLifecycleToggle(state, ap, name, params) {
1449
- const lifecycleRegister = APP_LIFECYCLE_REGISTER[name];
1450
- if (lifecycleRegister) {
1451
- state.appLifecycle.register(ap.appSessionId, lifecycleRegister, params.success);
1452
- return true;
1453
- }
1454
- const lifecycleUnregister = APP_LIFECYCLE_UNREGISTER[name];
1455
- if (lifecycleUnregister) {
1456
- // off(cb) carries the same evtId as the original on(cb) → removes just that
1457
- // listener; off() with no callback carries no id → clears the whole event.
1458
- state.appLifecycle.unregister(ap.appSessionId, lifecycleUnregister, params.success);
1459
- return true;
1460
- }
1461
- return false;
1462
- }
1463
- // pageScrollTo acts on the page's render guest (scroll its document), which
1464
- // only the main process can reach — run the scroll script in the invoking
1465
- // page's render webContents rather than forwarding to the simulator.
1466
- function handlePageScrollApi(ap, page, params) {
1467
- const renderWc = page.renderWc;
1468
- if (renderWc && !renderWc.isDestroyed()) {
1469
- void renderWc.executeJavaScript(buildPageScrollScript(params)).catch(() => { });
1470
- }
1471
- const successResult = { errMsg: 'pageScrollTo:ok' };
1472
- sendCallback(ap, params.success, successResult);
1473
- sendCallback(ap, params.complete, successResult);
1474
- }
1475
- // Shared try/invoke/callback pattern used by the storageApi and
1476
- // ctx.simulatorApis branches: relay the resolved value through
1477
- // success+complete, or the thrown error through fail+complete with a
1478
- // namespaced errMsg.
1479
- async function invokeSimulatorApiAndCallback(ap, name, params, invoke) {
1480
- try {
1481
- const result = await invoke();
1482
- sendCallback(ap, params.success, result);
1483
- sendCallback(ap, params.complete, result);
1484
- }
1485
- catch (error) {
1486
- const failResult = { errMsg: `${name}:fail ${error instanceof Error ? error.message : String(error)}` };
1487
- sendCallback(ap, params.fail, failResult);
1488
- sendCallback(ap, params.complete, failResult);
1489
- }
1490
- }
1491
- async function handleSimulatorApi(state, ap, page, body, ctx) {
1492
- const name = String(body.name ?? '');
1493
- const params = normalizeParams(body.params);
1494
- if (NAV_BAR_API_NAMES.has(name)) {
1495
- handleNavBarApi(ap, page, name, params);
1496
- return;
1497
- }
1498
- if (NAV_ACTION_NAMES.has(name)) {
1499
- handleNavActionApi(ap, ctx, page, name, params);
1500
- return;
1501
- }
1502
- if (TAB_ACTION_NAMES.has(name)) {
1503
- handleTabActionApi(ap, page, name, params);
1504
- return;
1505
- }
1506
- if (handleAppLifecycleToggle(state, ap, name, params))
1507
- return;
1508
- if (name === 'pageScrollTo') {
1509
- handlePageScrollApi(ap, page, params);
1510
- return;
1511
- }
1512
- // Native-host storage unification: route async wx.setStorage/getStorage/etc.
1513
- // to the service-host window's file:// store (the same store the *Sync APIs +
1514
- // the Storage panel use), instead of forwarding to the simulator guest's
1515
- // http:// origin. Without this, async writes and sync writes diverge across
1516
- // two origins even for the running mini-app.
1517
- if (ctx.storageApi && STORAGE_API_NAMES.has(name)) {
1518
- const storageApi = ctx.storageApi;
1519
- await invokeSimulatorApiAndCallback(ap, name, params, () => storageApi.invoke(ap.appId, name, params));
1520
- return;
1521
- }
1522
- // Main-process registry (downstream-registered host APIs via
1523
- // instance.registerSimulatorApi). If the name is not registered here, fall
1524
- // through to the simulator-window forwarding branch — the simulator-resident
1525
- // MiniApp owns the DOM-touching defaults (wx.getSystemInfo, chooseImage,
1526
- // chooseMedia, fs.*, …) and the bridge-router can't run those itself.
1527
- if (ctx.simulatorApis.has(name)) {
1528
- await invokeSimulatorApiAndCallback(ap, name, params, () => ctx.simulatorApis.invoke(name, params));
1529
- return;
1530
- }
1531
- forwardApiCallToSimulator(state, ap, page, name, params);
1532
- }
1533
- function forwardApiCallToSimulator(state, ap, page, name, params) {
1534
- const callbacks = extractCallbacks(params);
1535
- if (ap.simulatorWc.isDestroyed()) {
1536
- const fail = { errMsg: `${name}:fail simulator window destroyed` };
1537
- sendCallback(ap, callbacks.fail, fail);
1538
- sendCallback(ap, callbacks.complete, fail);
1539
- return;
1540
- }
1541
- const requestId = newRequestId();
1542
- // Persistent subscriptions (`keep: true`, e.g. audioListen) bind their event
1543
- // bridge synchronously but only emit their FIRST response when the first
1544
- // event fires (which can be well past the no-handler window). Arming the
1545
- // one-shot timeout would tear the subscription down before it ever delivers,
1546
- // so keep calls run without a timeout and are reaped on page/app teardown.
1547
- // Recognise persistent subscriptions BY NAME (`audioListen`) too: the service
1548
- // host strips the original `keep: true` before forwarding, so `params.keep` is
1549
- // gone by the time we route the call. See shared/simulator-api-metadata.ts.
1550
- //
1551
- // The watchdog window is per-call (apiCallWatchdogMs): network-budget APIs
1552
- // (request/downloadFile/uploadFile) get their wx timeout budget + grace so a
1553
- // slow-but-alive HTTP call is never torn down before its handler's own
1554
- // deadline; everything else keeps the flat 5s missing-handler window.
1555
- const keep = params.keep === true || isPersistentSimulatorApi(name);
1556
- const timer = keep
1557
- ? undefined
1558
- : setTimeout(() => {
1559
- const pending = state.pendingApiCalls.get(requestId);
1560
- if (!pending)
1561
- return;
1562
- state.pendingApiCalls.delete(requestId);
1563
- const target = state.appSessions.get(pending.appSessionId);
1564
- if (!target)
1565
- return;
1566
- const fail = { errMsg: `${pending.name}:fail no handler (timeout)` };
1567
- sendCallback(target, pending.callbacks.fail, fail);
1568
- sendCallback(target, pending.callbacks.complete, fail);
1569
- }, apiCallWatchdogMs(name, params));
1570
- state.pendingApiCalls.set(requestId, {
1571
- appSessionId: ap.appSessionId,
1572
- callbacks,
1573
- name,
1574
- timer,
1575
- });
1576
- // Strip the callback ids before forwarding: callback ids are
1577
- // service-host-side identifiers and the simulator runs its own
1578
- // capture-based callback shim. Forwarding them would be both noisy
1579
- // and a potential id-namespace collision.
1580
- const forwardedParams = {};
1581
- for (const [k, v] of Object.entries(params)) {
1582
- if (k === 'success' || k === 'fail' || k === 'complete')
1583
- continue;
1584
- forwardedParams[k] = v;
1585
- }
1586
- const payload = {
1587
- appSessionId: ap.appSessionId,
1588
- bridgeId: page.bridgeId,
1589
- requestId,
1590
- name,
1591
- params: forwardedParams,
1592
- // Echoed back unchanged in the API_RESPONSE so main can ack against the
1593
- // original service-side callback ids without keeping them in pending.
1594
- callbacks,
1595
- };
1596
- ap.simulatorWc.send(E.API_CALL, payload);
1597
- }
1598
- function handleApiResponse(state, sender, payload) {
1599
- const pending = state.pendingApiCalls.get(payload.requestId);
1600
- if (!pending)
1601
- return;
1602
- const ap = state.appSessions.get(pending.appSessionId);
1603
- if (!ap) {
1604
- // Owning app is gone — drop the stale entry regardless of keep.
1605
- state.pendingApiCalls.delete(payload.requestId);
1606
- clearTimeout(pending.timer);
1607
- return;
1608
- }
1609
- // Only a simulator window bound to the OWNING session may respond. Membership
1610
- // (senderBoundToSession), not sender-resolution equality: during a soft-reload
1611
- // overlap the shared simulator wc still answers the outgoing session's
1612
- // in-flight calls, which a latest-wins comparison would drop. Validate BEFORE
1613
- // mutating pending state so a spoofed/foreign response can't tear down a
1614
- // live subscription.
1615
- if (!senderBoundToSession(state, sender, ap)) {
1616
- console.warn('[bridge-router] API_RESPONSE rejected: sender not bound to app session');
1617
- return;
1618
- }
1619
- // Persistent-subscription fire (keep: true, e.g. audioListen). Re-fire the
1620
- // service-side success callback on EVERY response without tearing the call
1621
- // down: do not delete pending, do not fire `complete`. The 5s no-handler
1622
- // timeout is no longer relevant once the subscription has produced a fire,
1623
- // so clear it but keep the entry alive until page/app teardown (which
1624
- // drains pendingApiCalls in the dispose path).
1625
- // `payload.keep` OR a by-name persistent API: be robust even if the verdict
1626
- // on the wire didn't echo `keep` (the service host strips it), so the router
1627
- // never tears a known subscription (`audioListen`) down as a one-shot.
1628
- if ((payload.keep || isPersistentSimulatorApi(pending.name)) && payload.ok) {
1629
- clearTimeout(pending.timer);
1630
- pending.timer = undefined;
1631
- sendCallback(ap, pending.callbacks.success, payload.result);
1632
- return;
1633
- }
1634
- // One-shot path (default): fire the verdict's callbacks once and clean up.
1635
- state.pendingApiCalls.delete(payload.requestId);
1636
- clearTimeout(pending.timer);
1637
- if (payload.ok) {
1638
- sendCallback(ap, pending.callbacks.success, payload.result);
1639
- sendCallback(ap, pending.callbacks.complete, payload.result ?? { errMsg: `${pending.name}:ok` });
1640
- }
1641
- else {
1642
- // The router is a transport, not an author: the simulator-side fail result
1643
- // (errno etc.) must reach the service callbacks intact, so spread it
1644
- // through instead of rebuilding a bare { errMsg }. errMsg is guaranteed
1645
- // non-empty — `||` (not `??`) so an empty string (e.g. an HTTP/2 response
1646
- // whose statusText is always '') falls through payload.errMsg →
1647
- // result.errMsg → the `${name}:fail` default instead of surfacing blank.
1648
- const result = payload.result && typeof payload.result === 'object' && !Array.isArray(payload.result)
1649
- ? payload.result
1650
- : undefined;
1651
- const resultErrMsg = typeof result?.errMsg === 'string' ? result.errMsg : undefined;
1652
- const fail = {
1653
- ...result,
1654
- errMsg: payload.errMsg || resultErrMsg || `${pending.name}:fail`,
1655
- };
1656
- sendCallback(ap, pending.callbacks.fail, fail);
1657
- sendCallback(ap, pending.callbacks.complete, fail);
1658
- }
1659
- }
1660
- function newRequestId() {
1661
- return `apicall_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
1662
- }
1663
- function extractCallbacks(params) {
1664
- return { success: params.success, fail: params.fail, complete: params.complete };
1665
- }
1666
- function normalizeParams(params) {
1667
- return params && typeof params === 'object' && !Array.isArray(params)
1668
- ? params
1669
- : { value: params };
1670
- }
1671
- function sendCallback(ap, id, args) {
1672
- if (id === undefined || id === null)
1673
- return;
1674
- forwardToService(ap, {
1675
- type: 'triggerCallback',
1676
- target: 'service',
1677
- body: { id, args },
1678
- });
1679
- }
1680
- // ── Resource helpers ────────────────────────────────────────────────────────
1681
- function makeLoadResource(ap, page, target) {
1682
- return {
1683
- type: 'loadResource',
1684
- target,
1685
- body: {
1686
- appId: ap.appId,
1687
- bridgeId: page.bridgeId,
1688
- pagePath: page.pagePath,
1689
- root: ap.root,
1690
- baseUrl: ap.resourceBaseUrl,
1691
- resourceBaseUrl: ap.resourceBaseUrl,
1692
- // dimina's service runtime reads hostEnv as `{ systemInfo, menuRect }`
1693
- // (core/host-env.js init → getSystemInfo/getMenuRect; invokeAPI resolves
1694
- // getSystemInfoSync/getWindowInfo/getDeviceInfo from hostEnv.systemInfo).
1695
- // The snapshot must be nested under `systemInfo`: a FLAT HostEnvSnapshot
1696
- // leaves `systemInfo` null → `wx.getSystemInfoSync()` returns null and
1697
- // pages reading `.screenWidth` throw. (render does NOT read hostEnv, so
1698
- // this is service-only; the devtools sync-api-patch reads the separate
1699
- // __diminaSpawnContext.hostEnvSnapshot and is unaffected.)
1700
- // menuRect stays null — getMenuButtonBoundingClientRect is served by the
1701
- // sync-api-patch / DeviceShell capsule, not this path.
1702
- hostEnv: { systemInfo: ap.hostEnv, menuRect: null },
1703
- },
1704
- };
1705
- }
1706
- function forwardToService(ap, msg) {
1707
- if (!ap.serviceWc.isDestroyed()) {
1708
- ap.serviceWc.send(C.TO_SERVICE, { msg });
1709
- }
1710
- }
1711
- function forwardToRender(ap, msg, targetBridgeId) {
1712
- const renderBridgeId = targetBridgeId || readBridgeId(msg);
1713
- if (!renderBridgeId) {
1714
- throw new Error('[bridge-router] cannot route to render: missing bridgeId in body and no explicit target');
1715
- }
1716
- const page = ap.pages.get(renderBridgeId);
1717
- if (!page)
1718
- return;
1719
- const renderWc = page.renderWc;
1720
- if (renderWc && !renderWc.isDestroyed()) {
1721
- renderWc.send(C.TO_RENDER, { msg });
1722
- }
1723
- }
1724
- function ensureRenderBound(state, sender, bridgeId) {
1725
- const page = state.pageSessions.get(bridgeId);
1726
- if (!page)
1727
- return undefined;
1728
- if (page.renderWc !== sender) {
1729
- if (page.renderWc && page.renderWc !== sender && !page.renderWc.isDestroyed()) {
1730
- console.warn(`[bridge-router] page ${bridgeId} render webview swap (wc ${page.renderWc.id} → ${sender.id})`);
1731
- }
1732
- page.renderWc = sender;
1733
- state.wcIdToBridgeId.set(sender.id, bridgeId);
1734
- // Consolidate the render-guest bookkeeping teardown onto the connection
1735
- // layer (foundation.md §4 / P2): the render guest is its own webContents =
1736
- // its own connection (§4.4); own() ties this cleanup to its lifetime so it
1737
- // fires on destroy, replacing the bespoke `once('destroyed')`. acquire() is
1738
- // idempotent — re-binding the same sender re-uses its connection.
1739
- state.connections.acquire(sender).own(() => {
1740
- const p = state.pageSessions.get(bridgeId);
1741
- if (p && p.renderWc === sender)
1742
- p.renderWc = null;
1743
- if (state.wcIdToBridgeId.get(sender.id) === bridgeId)
1744
- state.wcIdToBridgeId.delete(sender.id);
1745
- });
1746
- }
1747
- return page;
1748
- }
1749
- function pageFromMsg(state, ap, msg) {
1750
- const target = readBridgeId(msg);
1751
- if (!target)
1752
- return undefined;
1753
- const page = ap.pages.get(target);
1754
- return page;
1755
- }
1756
- function appByWc(state, wc) {
1757
- if (wc.isDestroyed())
1758
- return undefined;
1759
- const appSessionId = state.serviceWcIdToAppSessionId.get(wc.id);
1760
- if (appSessionId)
1761
- return state.appSessions.get(appSessionId);
1762
- // A simulator wc hosts several sessions during a soft reload; a message that
1763
- // doesn't name its session belongs to the LATEST still-alive spawn (the Set
1764
- // preserves spawn order).
1765
- const hosted = state.simulatorWcIdToAppSessionIds.get(wc.id);
1766
- if (hosted) {
1767
- let latest;
1768
- for (const id of hosted)
1769
- latest = state.appSessions.get(id) ?? latest;
1770
- if (latest)
1771
- return latest;
1772
- }
1773
- const bridgeId = state.wcIdToBridgeId.get(wc.id);
1774
- if (bridgeId) {
1775
- const page = state.pageSessions.get(bridgeId);
1776
- if (page)
1777
- return state.appSessions.get(page.appSessionId);
1778
- }
1779
- return undefined;
1780
- }
1781
- /**
1782
- * Whether `sender` is authorized to control app session `ap`. A simulator WCV
1783
- * is authorized for EVERY session it hosts (`simulatorWcIdToAppSessionIds` is
1784
- * a membership set, so an older session's own control traffic stays valid
1785
- * while a newer spawn boots on the same wc); any other sender (service host,
1786
- * render guest) resolves one-to-one through `appByWc`.
1787
- */
1788
- function senderBoundToSession(state, sender, ap) {
1789
- if (sender.isDestroyed())
1790
- return false;
1791
- const hosted = state.simulatorWcIdToAppSessionIds.get(sender.id);
1792
- if (hosted)
1793
- return hosted.has(ap.appSessionId);
1794
- return appByWc(state, sender)?.appSessionId === ap.appSessionId;
1795
- }
1796
- function resolveAppByBridgeId(state, bridgeId) {
1797
- if (state.appSessions.has(bridgeId))
1798
- return state.appSessions.get(bridgeId);
1799
- const page = state.pageSessions.get(bridgeId);
1800
- if (page)
1801
- return state.appSessions.get(page.appSessionId);
1802
- return undefined;
1803
- }
1804
- function bindWc(map, wc, id) {
1805
- if (wc.isDestroyed())
1806
- return;
1807
- map.set(wc.id, id);
1808
- }
1809
- /** Add a session to a simulator wc's hosted set (created on first bind). */
1810
- function bindSimulatorWc(map, wc, id) {
1811
- if (wc.isDestroyed())
1812
- return;
1813
- let hosted = map.get(wc.id);
1814
- if (!hosted) {
1815
- hosted = new Set();
1816
- map.set(wc.id, hosted);
1817
- }
1818
- hosted.add(id);
1819
- }
1820
- function readBridgeId(msg) {
1821
- return typeof msg.body?.bridgeId === 'string' ? msg.body.bridgeId : undefined;
1822
- }
1823
- function newBridgeId() {
1824
- return `bridge_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
1825
- }
1826
- function resolveSimulatorWebContents(ctx, simulatorWcId, sender) {
1827
- if (!simulatorWcId)
1828
- return sender ?? ctx.windows.mainWindow.webContents;
1829
- return webContents.fromId(simulatorWcId) ?? ctx.windows.mainWindow.webContents;
1830
- }
1831
- // ── Dispose ─────────────────────────────────────────────────────────────────
1832
- function disposePageSession(state, ap, page) {
1833
- if (page.renderWc && !page.renderWc.isDestroyed()) {
1834
- state.wcIdToBridgeId.delete(page.renderWc.id);
1835
- }
1836
- ap.pages.delete(page.bridgeId);
1837
- state.pageSessions.delete(page.bridgeId);
1838
- }
1839
- // Drain any pending API calls owned by this app session. One-shot calls
1840
- // normally self-clean on response/timeout, but persistent (`keep: true`)
1841
- // subscriptions (e.g. audioListen) live with their timer cleared until
1842
- // teardown — without this they would leak in pendingApiCalls forever.
1843
- function drainPendingApiCallsForSession(state, appSessionId) {
1844
- for (const [requestId, pending] of state.pendingApiCalls) {
1845
- if (pending.appSessionId !== appSessionId)
1846
- continue;
1847
- clearTimeout(pending.timer);
1848
- state.pendingApiCalls.delete(requestId);
1849
- }
1850
- }
1851
- // Closes every page's render guest and drops its wc/pageSessions bindings.
1852
- // Caller clears `ap.pages` itself right after — this only tears each page
1853
- // down, it doesn't touch the map.
1854
- function closeSessionPages(state, ap) {
1855
- for (const page of ap.pages.values()) {
1856
- if (page.renderWc && !page.renderWc.isDestroyed()) {
1857
- state.wcIdToBridgeId.delete(page.renderWc.id);
1858
- // The render-host <webview> guest is otherwise reclaimed only when its
1859
- // host simulator WCV is destroyed, and that cascade is async. Close it
1860
- // here so a project-close / respawn tears the guest down deterministically
1861
- // rather than leaving the outgoing project's guest alive to be
1862
- // screenshotted or re-resolved by the next project. Idempotent with the
1863
- // cascade (guarded on isDestroyed).
1864
- try {
1865
- page.renderWc.close();
1866
- }
1867
- catch { /* guest already gone */ }
1868
- }
1869
- state.pageSessions.delete(page.bridgeId);
1870
- }
1871
- }
1872
- // Three-way release of the session's service-host window: pool release (soft
1873
- // reuse), pool reclaim of an externally-closed window, or a plain close for
1874
- // the unpooled path.
1875
- async function releaseServiceWindow(state, ap, opts) {
1876
- if (ap.poolEntryId !== null && state.pool && !opts.serviceAlreadyClosed) {
1877
- // Soft reuse (foundation.md §4.3): the pooled service-host webContents keeps
1878
- // its wc.id but is about to run a NEW app session. Reset its Connection
1879
- // BEFORE returning it to the pool — dispose this session's owned segment
1880
- // (the serviceWc binding cleanup) and swap a fresh one — so an old in-flight
1881
- // response can't bleed into the next session and the next spawn `acquire`s a
1882
- // clean segment. The connection object stays alive (it's reused), so this is
1883
- // reset (not close); a real destroy still closes it via its 'destroyed' hook.
1884
- state.connections.reset(ap.serviceWc.id);
1885
- // Return the window to the pool instead of closing it. Detach the boot
1886
- // listener first (the 'closed' hook already came off with the listener-bag
1887
- // dispose above) so a stale did-finish-load can't boot this disposed
1888
- // session into the next spawn's recycled window.
1889
- if (ap.onServiceBoot && !ap.serviceWindow.isDestroyed()) {
1890
- ap.serviceWindow.webContents.removeListener('did-finish-load', ap.onServiceBoot);
1891
- }
1892
- await state.pool.release(ap.poolEntryId, ap.serviceWindow).catch((error) => {
1893
- console.warn('[bridge-router] pool release failed:', error);
1894
- });
1895
- }
1896
- else if (ap.poolEntryId !== null && state.pool && opts.serviceAlreadyClosed) {
1897
- // The pooled service window already closed/crashed externally (its 'closed'
1898
- // fired → onServiceClosed → here). `release` is skipped on this path, and the
1899
- // pool only auto-reclaims on render-process-gone — so without this the in-use
1900
- // entry leaks in the pool forever, permanently shrinking capacity. Reclaim
1901
- // the slot explicitly (the window is already gone; releaseDestroyed won't
1902
- // touch it).
1903
- state.pool.releaseDestroyed(ap.poolEntryId);
1904
- }
1905
- else if (!opts.serviceAlreadyClosed && !ap.serviceWindow.isDestroyed()) {
1906
- ap.serviceWindow.close();
1907
- }
1908
- }
1909
- function unbindSessionFromSharedMaps(state, ap, appSessionId) {
1910
- // Value-checked unbind for the service wc: this delete runs after the
1911
- // pool-release await above, so on the pool path the next spawn may have
1912
- // already re-acquired the SAME window and rebound its wc id — only remove
1913
- // an entry that still names THIS session.
1914
- if (state.serviceWcIdToAppSessionId.get(ap.serviceWc.id) === appSessionId) {
1915
- state.serviceWcIdToAppSessionId.delete(ap.serviceWc.id);
1916
- }
1917
- // The simulator wc's hosted set sheds only this session; sessions that are
1918
- // still live on the shared wc (the soft-reload survivor) keep their
1919
- // membership. The map entry goes away with the last session.
1920
- const hosted = state.simulatorWcIdToAppSessionIds.get(ap.simulatorWc.id);
1921
- if (hosted) {
1922
- hosted.delete(appSessionId);
1923
- if (hosted.size === 0)
1924
- state.simulatorWcIdToAppSessionIds.delete(ap.simulatorWc.id);
1925
- }
1926
- }
1927
- async function disposeAppSession(state, appSessionId, opts = {}) {
1928
- const ap = state.appSessions.get(appSessionId);
1929
- if (!ap)
1930
- return;
1931
- state.appSessions.delete(appSessionId);
1932
- // Prevent a leaked/stale watchdog: without this, a session disposed while
1933
- // still 'launching' (e.g. project close mid-launch) would fire its timer
1934
- // `LAUNCH_TIMEOUT_MS` later against an already-torn-down session.
1935
- clearLaunchTimer(ap);
1936
- // Remove this session's shutdown-fallback registry entry so the registry does
1937
- // not accumulate one stale closure per respawn. Ordered AFTER the delete
1938
- // above: dispose() runs the wrapped fn (disposeAppSession again), which
1939
- // early-returns now that the session is gone — no recursion, no double-run.
1940
- // (At whole-app shutdown the registry marks the entry released before calling
1941
- // the fn, so this dispose() is a no-op then.)
1942
- const registryHandle = ap.registryHandle;
1943
- ap.registryHandle = null;
1944
- void registryHandle?.dispose();
1945
- state.appLifecycle.dispose(appSessionId);
1946
- // Evict AppData bridges FIRST — eviction enumerates `ap.pages`, which the
1947
- // page teardown below progressively empties (and finally clears).
1948
- state.evictAppDataBridges(ap);
1949
- drainPendingApiCallsForSession(state, appSessionId);
1950
- closeSessionPages(state, ap);
1951
- ap.pages.clear();
1952
- // Detach every session-scoped hook on emitters that outlive this session —
1953
- // the shared simulator WCV's 'destroyed' and the service window's 'closed' —
1954
- // BEFORE the window is released/closed below: a pool-recycled window must
1955
- // not re-trigger this session's teardown from a stale 'closed', and the
1956
- // simulator wc must not accumulate one dead hook per soft reload until the
1957
- // MaxListeners warning fires. Idempotent with teardown being triggered BY
1958
- // one of these hooks (removing a fired once() is a no-op).
1959
- ap.listenerBag.dispose();
1960
- await releaseServiceWindow(state, ap, opts);
1961
- unbindSessionFromSharedMaps(state, ap, appSessionId);
1962
- // Only the local fallback server needs closing; the dev-server base is owned
1963
- // by the workspace session, not this app session.
1964
- if (ap.resourceServer) {
1965
- await ap.resourceServer.close().catch((error) => {
1966
- console.warn('[bridge-router] resource server close failed:', error);
1967
- });
1968
- }
1969
- }
1970
- // ── App-config / manifest parsing ───────────────────────────────────────────
1971
- /**
1972
- * Fetch+parse `app-config.json`. Pure (no `ctx`, no diagnostics dependency) —
1973
- * a non-2xx response or a parse/network failure reports through `onUnreachable`
1974
- * (the caller routes it into `ctx.diagnostics`) and returns `{}` either way, so
1975
- * the spawn flow always continues with a manifest that just falls back to a
1976
- * single-page/no-tabBar shape (`buildAppManifest`'s fallback).
1977
- */
1978
- async function loadAppConfig(resourceBase, onUnreachable) {
1979
- // `resourceBase` is the dir/URL that directly contains `app-config.json`:
1980
- // the dev server's `<base><appId>/<root>/` (http) or the local fallback
1981
- // server root (also http). Both are HTTP, so a single fetch path covers them.
1982
- const cfgUrl = new URL('app-config.json', resourceBase.endsWith('/') ? resourceBase : `${resourceBase}/`).toString();
1983
- try {
1984
- const res = await fetch(cfgUrl);
1985
- if (!res.ok) {
1986
- const error = new Error(`app-config.json fetch ${res.status} at ${cfgUrl}`);
1987
- console.warn(`[bridge-router] no app-config.json at ${cfgUrl} (${res.status})`);
1988
- onUnreachable?.({ url: cfgUrl, error });
1989
- return {};
1990
- }
1991
- return await res.json();
1992
- }
1993
- catch (error) {
1994
- console.warn('[bridge-router] failed to fetch/parse app-config.json:', error);
1995
- onUnreachable?.({ url: cfgUrl, error });
1996
- return {};
1997
- }
1998
- }
1999
- function buildAppManifest(appConfig, fallbackEntry) {
2000
- const entry = appConfig.app?.entryPagePath || fallbackEntry;
2001
- const hasCompiledPages = !!appConfig.app?.pages?.length;
2002
- const pages = hasCompiledPages ? appConfig.app.pages : [entry];
2003
- const tabBar = appConfig.app?.tabBar && Array.isArray(appConfig.app.tabBar.list) && appConfig.app.tabBar.list.length > 0
2004
- ? appConfig.app.tabBar
2005
- : undefined;
2006
- return {
2007
- entryPagePath: normalizePagePath(entry),
2008
- pages: pages.map(normalizePagePath),
2009
- tabBar,
2010
- // 'app-config' means `pages` above is the real compiled list mount gates
2011
- // can trust; 'fallback' means app-config.json was unreachable and `pages`
2012
- // is just the single requested page — nothing to validate membership
2013
- // against, so every gate keyed on this source lets it through unchanged.
2014
- source: hasCompiledPages ? 'app-config' : 'fallback',
2015
- };
2016
- }
2017
- /**
2018
- * Resolve the root page a spawn actually mounts against the compiled
2019
- * manifest. The request is trusted verbatim unless the manifest is a real
2020
- * compiled one (`source === 'app-config'`) AND the request is absent from its
2021
- * `pages` — in that case the request is unmountable (most commonly a start
2022
- * page removed by a hot reload) and resolution falls back to
2023
- * `manifest.entryPagePath`, or `manifest.pages[0]` when even the entry isn't a
2024
- * member. A `'fallback'` manifest contains only the request itself, so there
2025
- * is trivially nothing to fall back from.
2026
- */
2027
- function resolveRootPagePath(manifest, requestedPagePath) {
2028
- if (manifest.source !== 'app-config' || manifest.pages.includes(requestedPagePath)) {
2029
- return { resolvedPagePath: requestedPagePath, pageFallbackApplied: false };
2030
- }
2031
- const resolvedPagePath = manifest.pages.includes(manifest.entryPagePath)
2032
- ? manifest.entryPagePath
2033
- : manifest.pages[0];
2034
- return { resolvedPagePath, pageFallbackApplied: true };
2035
- }
2036
- function resolvePageWindowConfig(appConfig, pagePath) {
2037
- const normalized = normalizePagePath(pagePath);
2038
- const appWindow = appConfig.app?.window ?? {};
2039
- const pageEntry = appConfig.modules?.[normalized];
2040
- const pageWindow = pageEntry?.window ?? {};
2041
- return {
2042
- navigationBarTitleText: pageWindow.navigationBarTitleText ?? appWindow.navigationBarTitleText ?? '',
2043
- navigationBarBackgroundColor: pageWindow.navigationBarBackgroundColor ?? appWindow.navigationBarBackgroundColor ?? '#ffffff',
2044
- navigationBarTextStyle: pageWindow.navigationBarTextStyle ?? appWindow.navigationBarTextStyle ?? 'black',
2045
- navigationStyle: pageWindow.navigationStyle ?? appWindow.navigationStyle ?? 'default',
2046
- homeButton: pageWindow.homeButton ?? appWindow.homeButton,
2047
- backgroundColor: pageWindow.backgroundColor ?? appWindow.backgroundColor,
2048
- backgroundTextStyle: pageWindow.backgroundTextStyle ?? appWindow.backgroundTextStyle,
2049
- enablePullDownRefresh: pageWindow.enablePullDownRefresh ?? appWindow.enablePullDownRefresh,
2050
- disableScroll: pageWindow.disableScroll ?? appWindow.disableScroll,
2051
- };
2052
- }
2053
- function isTabPage(appConfig, pagePath) {
2054
- const list = appConfig.app?.tabBar?.list ?? [];
2055
- const normalized = normalizePagePath(pagePath);
2056
- return list.some(item => normalizePagePath(item.pagePath) === normalized);
2057
- }
2058
- function normalizePagePath(p) {
2059
- return p ? p.replace(/^\/+/, '') : '';
2060
- }
2061
- // ── Protocol handlers ───────────────────────────────────────────────────────
2062
- function installResourceProtocolHandlers(ctx, state) {
2063
- const handler = async (request) => {
2064
- const url = new URL(request.url);
2065
- const ap = resolveAppByBridgeId(state, url.hostname);
2066
- if (!ap)
2067
- return new Response('Bridge session not found', { status: 404 });
2068
- const target = new URL(url.pathname.replace(/^\/+/, '') + url.search, ap.resourceBaseUrl);
2069
- return fetch(target);
2070
- };
2071
- const simulatorSession = electronSession.fromPartition(SHARED_MINIAPP_PARTITION);
2072
- try {
2073
- protocol.unhandle('dmb-resource');
2074
- }
2075
- catch { }
2076
- try {
2077
- simulatorSession.protocol.unhandle('dmb-resource');
2078
- }
2079
- catch { }
2080
- protocol.handle('dmb-resource', handler);
2081
- simulatorSession.protocol.handle('dmb-resource', handler);
2082
- // Per-project partition sessions need the SAME resource handler so each
2083
- // project's render/service can load `dmb-resource://…`. Install on every
2084
- // miniapp partition (current + future); track installed sessions for teardown.
2085
- const perProjectSessions = new Set();
2086
- const unregisterConfigurator = registerMiniappSessionConfigurator((sess) => {
2087
- if (perProjectSessions.has(sess))
2088
- return;
2089
- perProjectSessions.add(sess);
2090
- try {
2091
- sess.protocol.unhandle('dmb-resource');
2092
- }
2093
- catch { }
2094
- sess.protocol.handle('dmb-resource', handler);
2095
- });
2096
- ctx.registry.add(() => {
2097
- unregisterConfigurator();
2098
- try {
2099
- protocol.unhandle('dmb-resource');
2100
- }
2101
- catch { }
2102
- try {
2103
- simulatorSession.protocol.unhandle('dmb-resource');
2104
- }
2105
- catch { }
2106
- for (const sess of perProjectSessions) {
2107
- try {
2108
- sess.protocol.unhandle('dmb-resource');
2109
- }
2110
- catch { }
2111
- }
2112
- });
2113
- }
2114
- function makeHostEnv(snapshot) {
2115
- return {
2116
- brand: 'devtools',
2117
- model: 'Electron',
2118
- platform: process.platform,
2119
- system: `${process.platform} ${process.versions.electron || ''}`.trim(),
2120
- version: process.versions.electron || '0.0.0',
2121
- SDKVersion: 'native-host-phase-1',
2122
- pixelRatio: 2,
2123
- screenWidth: 390,
2124
- screenHeight: 844,
2125
- windowWidth: 390,
2126
- windowHeight: 844,
2127
- statusBarHeight: 24,
2128
- language: app.getLocale(),
2129
- theme: 'light',
2130
- ...snapshot,
2131
- };
11
+ // The DevTools integration owns a complete copy of the runtime assets. Keep
12
+ // compatibility contexts (including embedders/tests that assemble the
13
+ // context manually) on that copy instead of falling back to the standalone
14
+ // package's optional dist assets.
15
+ ctx.assets ??= runtimeAssetPaths;
16
+ if (!ctx.events) {
17
+ const events = createRuntimeEvents();
18
+ ctx.events = events;
19
+ events.on('session-status', (payload) => ctx.notify?.sessionRuntimeStatus(payload));
20
+ events.on('app-data-evict', ({ appId, bridgeId }) => {
21
+ ctx.appData?.evictBridge(appId, bridgeId);
22
+ });
23
+ events.on('app-data-message', ({ appId, message }) => {
24
+ ctx.appData?.onServiceToRender(appId, message);
25
+ });
26
+ events.on('storage-changed', ({ appId, change }) => {
27
+ ctx.onServiceStorageChanged?.(appId, change);
28
+ });
29
+ ctx.registry.add(() => events.clear());
30
+ }
31
+ installRuntimeBridgeRouter(ctx);
2132
32
  }
2133
33
  //# sourceMappingURL=bridge-router.js.map