@omega.js/desktop 0.52.0 → 0.54.0

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 (345) hide show
  1. package/README.md +53 -48
  2. package/dist/assets/css/core/_initialize.scss +1 -1
  3. package/dist/assets/js/core/app-shell.js +14 -15
  4. package/dist/assets/themes/_template/_theme.js +6 -6
  5. package/dist/assets/themes/base/_includes/global/sections/account.html +4 -4
  6. package/dist/assets/themes/base/_includes/global/sections/app-sidebar.html +5 -5
  7. package/dist/assets/themes/base/_layouts/frontend/pages/account/index.html +3 -3
  8. package/dist/assets/themes/base/_layouts/frontend/pages/alternatives/index.html +1 -1
  9. package/dist/assets/themes/base/_layouts/frontend/pages/blog/tags/tag.html +1 -1
  10. package/dist/assets/themes/base/_layouts/frontend/pages/payment/confirmation.html +1 -1
  11. package/dist/assets/themes/base/_sections/marketing/newsletter-cta/section.js +2 -3
  12. package/dist/assets/themes/base/_sections/verts/unit/section.html +1 -1
  13. package/dist/assets/themes/base/_sections/verts/unit/section.js +3 -4
  14. package/dist/assets/themes/base/_theme.js +4 -3
  15. package/dist/assets/themes/bootstrap/_theme.js +2 -2
  16. package/dist/assets/themes/bootstrap/overrides/_links.scss +1 -1
  17. package/dist/assets/themes/classy/_theme.js +8 -6
  18. package/dist/assets/themes/classy/css/marketing/_sections.scss +1 -2
  19. package/dist/assets/themes/classy/js/hero-demo-form.js +3 -2
  20. package/dist/assets/themes/neobrutalism/_theme.js +5 -5
  21. package/dist/assets/themes/neobrutalism/js/pages/test/libraries/layers/index.js +1 -1
  22. package/dist/assets/themes/newsflash/_theme.js +6 -6
  23. package/dist/assets/themes/newsflash/js/pages/test/libraries/layers/index.js +1 -1
  24. package/dist/build.js +87 -111
  25. package/dist/cli-run.js +4 -1
  26. package/dist/cli.js +3 -3
  27. package/dist/commands/build.js +2 -2
  28. package/dist/commands/cdp/capture.js +2 -2
  29. package/dist/commands/cdp/client.js +1 -1
  30. package/dist/commands/cdp/quit.js +2 -2
  31. package/dist/commands/cdp/relaunch.js +2 -2
  32. package/dist/commands/cdp/theme.js +1 -1
  33. package/dist/commands/cdp.js +1 -1
  34. package/dist/commands/clean.js +4 -5
  35. package/dist/commands/deploy.js +4 -4
  36. package/dist/commands/dev.js +25 -0
  37. package/dist/commands/finalize-release.js +4 -4
  38. package/dist/commands/launch.js +2 -2
  39. package/dist/commands/lib/deploy-precheck.js +3 -3
  40. package/dist/commands/lib/ensure-target.js +18 -23
  41. package/dist/commands/lib/migrate.js +17 -0
  42. package/dist/commands/logs.js +1 -1
  43. package/dist/commands/package.js +2 -2
  44. package/dist/commands/publish.js +2 -2
  45. package/dist/commands/release.js +4 -4
  46. package/dist/commands/runner.js +11 -11
  47. package/dist/commands/sign-windows.js +5 -5
  48. package/dist/commands/test.js +8 -8
  49. package/dist/commands/update.js +7 -6
  50. package/dist/commands/validate-certs.js +4 -4
  51. package/dist/commands/version.js +3 -3
  52. package/dist/defaults/.github/workflows/build.yml +18 -18
  53. package/dist/defaults/_.gitignore +0 -2
  54. package/dist/defaults/_mas/README.md +3 -3
  55. package/dist/defaults/config/certs/README.md +1 -1
  56. package/dist/defaults/config/omega.json5 +43 -43
  57. package/dist/defaults/docs/README.md +3 -3
  58. package/dist/defaults/gulpfile.js +1 -1
  59. package/dist/defaults/hooks/build/post.js +2 -2
  60. package/dist/defaults/hooks/build/pre.js +2 -2
  61. package/dist/defaults/hooks/deploy/pre.js +1 -1
  62. package/dist/defaults/hooks/notarize/post.js +2 -2
  63. package/dist/defaults/hooks/release/post.js +2 -2
  64. package/dist/defaults/hooks/release/pre.js +2 -2
  65. package/dist/defaults/src/assets/js/components/about/index.js +3 -5
  66. package/dist/defaults/src/assets/js/components/main/index.js +3 -5
  67. package/dist/defaults/src/assets/js/components/settings/index.js +3 -5
  68. package/dist/defaults/src/assets/scss/pages/about.scss +1 -1
  69. package/dist/defaults/src/assets/scss/pages/main.scss +1 -1
  70. package/dist/defaults/src/assets/scss/pages/settings.scss +1 -1
  71. package/dist/defaults/src/integrations/context-menu/index.js +13 -13
  72. package/dist/defaults/src/integrations/menu/index.js +8 -8
  73. package/dist/defaults/src/integrations/tray/index.js +12 -12
  74. package/dist/defaults/src/main.js +5 -7
  75. package/dist/defaults/src/preload.js +4 -6
  76. package/dist/defaults/test/README.md +5 -5
  77. package/dist/defaults/test/_init.js +1 -1
  78. package/dist/gulp/main.js +9 -10
  79. package/dist/gulp/tasks/audit.js +11 -14
  80. package/dist/gulp/tasks/build-config.js +8 -8
  81. package/dist/gulp/tasks/bundle.js +16 -16
  82. package/dist/gulp/tasks/defaults.js +3 -3
  83. package/dist/gulp/tasks/distribute.js +2 -2
  84. package/dist/gulp/tasks/html.js +9 -9
  85. package/dist/gulp/tasks/package-quick.js +3 -3
  86. package/dist/gulp/tasks/package.js +3 -3
  87. package/dist/gulp/tasks/release.js +3 -3
  88. package/dist/gulp/tasks/sass.js +6 -6
  89. package/dist/gulp/tasks/serve.js +4 -4
  90. package/dist/hooks/notarize-artifacts.js +1 -1
  91. package/dist/hooks/notarize.js +1 -1
  92. package/dist/index.js +5 -8
  93. package/dist/lib/_environment-mixin.js +50 -0
  94. package/dist/lib/_lifecycle-mixin.js +45 -0
  95. package/dist/lib/analytics.js +33 -35
  96. package/dist/lib/app-state.js +14 -14
  97. package/dist/lib/auth-flow.js +18 -18
  98. package/dist/lib/auth-persistence.js +12 -12
  99. package/dist/lib/auth.js +421 -0
  100. package/dist/lib/auto-updater.js +49 -49
  101. package/dist/lib/context-menu.js +13 -13
  102. package/dist/lib/context.js +19 -19
  103. package/dist/lib/deep-link.js +34 -34
  104. package/dist/lib/fontawesome.js +5 -5
  105. package/dist/lib/ipc.js +4 -4
  106. package/dist/lib/menu.js +25 -25
  107. package/dist/lib/protocol.js +5 -5
  108. package/dist/lib/remote-config.js +22 -22
  109. package/dist/lib/remote-scripts.js +21 -21
  110. package/dist/lib/restart-manager/index.js +29 -29
  111. package/dist/lib/restart-manager/install.js +1 -1
  112. package/dist/lib/restart-manager/protocol.js +1 -1
  113. package/dist/lib/sign-helpers/exec-with-limit.js +1 -1
  114. package/dist/lib/sign-helpers/sign-events.js +1 -1
  115. package/dist/lib/startup.js +18 -13
  116. package/dist/lib/storage.js +10 -10
  117. package/dist/lib/templating.js +16 -16
  118. package/dist/lib/theme.js +10 -10
  119. package/dist/lib/tray.js +27 -27
  120. package/dist/lib/usage.js +11 -11
  121. package/dist/lib/window-manager.js +26 -26
  122. package/dist/main.js +399 -483
  123. package/dist/preload.js +236 -176
  124. package/dist/renderer.js +417 -391
  125. package/dist/test/fixtures/consumer-app/config/omega.json5 +1 -1
  126. package/dist/test/fixtures/consumer-app/src/assets/js/components/main/index.js +8 -11
  127. package/dist/test/fixtures/consumer-app/src/main.js +5 -7
  128. package/dist/test/fixtures/consumer-app/src/preload.js +2 -2
  129. package/dist/test/harness/boot-entry.js +22 -20
  130. package/dist/test/harness/main-entry.js +31 -30
  131. package/dist/test/harness/renderer-entry.js +5 -5
  132. package/dist/test/harness/renderer-preload.js +137 -141
  133. package/dist/test/index.js +10 -10
  134. package/dist/test/runner.js +2 -2
  135. package/dist/test/runners/boot.js +7 -6
  136. package/dist/test/runners/electron.js +3 -2
  137. package/dist/test/runners/render-event.js +2 -2
  138. package/dist/test/suites/boot/consumer-app-boots.test.js +57 -26
  139. package/dist/test/suites/boot/restart-manager.test.js +2 -2
  140. package/dist/test/suites/boot/storage-bundled.test.js +5 -5
  141. package/dist/test/suites/boot/theme.test.js +13 -13
  142. package/dist/test/suites/build/audit.test.js +21 -8
  143. package/dist/test/suites/build/auth-persistence-resolve.test.js +6 -6
  144. package/dist/test/suites/build/boot-fixture.test.js +2 -2
  145. package/dist/test/suites/build/boot-runner-timeout.test.js +6 -5
  146. package/dist/test/suites/build/brand-scss.test.js +1 -1
  147. package/dist/test/suites/build/build-json-bake.test.js +1 -1
  148. package/dist/test/suites/build/build-workflow-jobs.test.js +2 -2
  149. package/dist/test/suites/build/cli.test.js +30 -2
  150. package/dist/test/suites/build/config-schema.test.js +5 -5
  151. package/dist/test/suites/build/defaults-em-dash.test.js +22 -0
  152. package/dist/test/suites/build/defaults-scaffold.test.js +21 -7
  153. package/dist/test/suites/build/deploy-direct.test.js +7 -5
  154. package/dist/test/suites/build/deploy-dispatch.test.js +2 -1
  155. package/dist/test/suites/build/deploy-hook.test.js +7 -5
  156. package/dist/test/suites/build/dev-verb.test.js +67 -0
  157. package/dist/test/suites/build/ensure-target.test.js +13 -5
  158. package/dist/test/suites/build/env-delivery.test.js +2 -2
  159. package/dist/test/suites/build/esm-only-dependency.test.js +2 -2
  160. package/dist/test/suites/build/exports.test.js +9 -8
  161. package/dist/test/suites/build/get-config.test.js +4 -4
  162. package/dist/test/suites/build/manifest-deps.test.js +1 -1
  163. package/dist/test/suites/build/merge-line-files.test.js +7 -7
  164. package/dist/test/suites/build/migrate.test.js +29 -0
  165. package/dist/test/suites/build/omega-shell.test.js +34 -2
  166. package/dist/test/suites/build/omega.test.js +350 -0
  167. package/dist/test/suites/build/project-scripts-deps.test.js +6 -10
  168. package/dist/test/suites/build/renderer-auth-bridge.test.js +211 -78
  169. package/dist/test/suites/build/runner-env-write.test.js +73 -0
  170. package/dist/test/suites/build/runner.test.js +11 -10
  171. package/dist/test/suites/build/sentry.test.js +2 -2
  172. package/dist/test/suites/build/setup-scripts.test.js +27 -0
  173. package/dist/test/suites/build/sign-windows-e2e.test.js +2 -2
  174. package/dist/test/suites/build/templating.test.js +3 -3
  175. package/dist/test/suites/build/test-stealth.test.js +7 -9
  176. package/dist/test/suites/build/url-helpers.test.js +55 -56
  177. package/dist/test/suites/build/validate-config.test.js +15 -4
  178. package/dist/test/suites/build/verb-logs.test.js +20 -0
  179. package/dist/test/suites/build/wave5-pins.test.js +2 -2
  180. package/dist/test/suites/main/analytics.test.js +59 -59
  181. package/dist/test/suites/main/app-state.test.js +66 -66
  182. package/dist/test/suites/main/auth-flow.test.js +41 -41
  183. package/dist/test/suites/main/auth-persistence.test.js +54 -43
  184. package/dist/test/suites/main/{client-bridge.integration.test.js → auth.integration.test.js} +10 -9
  185. package/dist/test/suites/main/auth.test.js +336 -0
  186. package/dist/test/suites/main/auto-updater.test.js +134 -134
  187. package/dist/test/suites/main/boot-sequence.test.js +37 -49
  188. package/dist/test/suites/main/context-menu.test.js +51 -50
  189. package/dist/test/suites/main/context.test.js +25 -25
  190. package/dist/test/suites/main/deep-link.test.js +74 -74
  191. package/dist/test/suites/main/fontawesome.test.js +27 -27
  192. package/dist/test/suites/main/ipc.test.js +35 -35
  193. package/dist/test/suites/main/menu.test.js +101 -100
  194. package/dist/test/suites/main/protocol.test.js +19 -19
  195. package/dist/test/suites/main/remote-config.test.js +63 -63
  196. package/dist/test/suites/main/remote-scripts.test.js +103 -103
  197. package/dist/test/suites/main/request.test.js +71 -0
  198. package/dist/test/suites/main/restart-manager.test.js +33 -33
  199. package/dist/test/suites/main/startup-paths-and-ua.test.js +4 -4
  200. package/dist/test/suites/main/startup.test.js +26 -26
  201. package/dist/test/suites/main/stealth-window.test.js +1 -1
  202. package/dist/test/suites/main/storage.test.js +25 -25
  203. package/dist/test/suites/main/theme.test.js +34 -34
  204. package/dist/test/suites/main/tray.test.js +79 -79
  205. package/dist/test/suites/main/url-helpers.test.js +133 -133
  206. package/dist/test/suites/main/usage.test.js +25 -25
  207. package/dist/test/suites/main/window-bounds.test.js +27 -27
  208. package/dist/test/suites/main/window-manager.test.js +44 -44
  209. package/dist/test/suites/renderer/analytics-bridge.test.js +5 -5
  210. package/dist/test/suites/renderer/cross-context-helpers.test.js +37 -31
  211. package/dist/test/suites/renderer/round-trip.test.js +3 -3
  212. package/dist/test/suites/renderer/tooltips.test.js +15 -15
  213. package/dist/test/suites/renderer/{window-em-surface.test.js → window-desktop-surface.test.js} +28 -8
  214. package/dist/test/utils/extended-mode-warning.js +1 -1
  215. package/dist/utils/boot-harness.js +56 -0
  216. package/dist/utils/build-pipeline.js +4 -4
  217. package/dist/utils/mode-helpers.js +2 -15
  218. package/dist/utils/runner-env.js +13 -28
  219. package/dist/utils/ship-keys.js +3 -3
  220. package/dist/utils/signing-status.js +51 -0
  221. package/dist/utils/test-events.js +7 -0
  222. package/dist/utils/test-stealth.js +6 -6
  223. package/dist/utils/url-helpers.js +52 -42
  224. package/dist/utils/user-agent.js +44 -0
  225. package/dist/vendor/account/engine.js +3 -3
  226. package/dist/vendor/account/index.js +14 -45
  227. package/dist/vendor/account/resolve-account.js +44 -0
  228. package/dist/vendor/account/schema.js +1 -1
  229. package/dist/vendor/account/user.js +99 -0
  230. package/dist/vendor/config/client-config.js +1 -1
  231. package/dist/vendor/config/company.js +46 -14
  232. package/dist/vendor/config/defaults.js +30 -7
  233. package/dist/vendor/config/edit.js +25 -3
  234. package/dist/vendor/config/env-delivery.js +1 -1
  235. package/dist/vendor/config/env-schema.js +3 -6
  236. package/dist/vendor/config/env.js +34 -22
  237. package/dist/vendor/config/environment.js +11 -30
  238. package/dist/vendor/config/index.js +21 -28
  239. package/dist/vendor/config/load.js +16 -9
  240. package/dist/vendor/config/platforms.js +1 -1
  241. package/dist/vendor/config/repo.js +10 -27
  242. package/dist/vendor/config/schema-client.js +64 -0
  243. package/dist/vendor/config/schema-cloud.js +38 -0
  244. package/dist/vendor/config/schema-manager.js +118 -0
  245. package/dist/vendor/config/schema-overrides.js +68 -0
  246. package/dist/vendor/config/schema.js +104 -160
  247. package/dist/vendor/config/site-global.js +2 -3
  248. package/dist/vendor/config/validate.js +97 -78
  249. package/dist/vendor/config/winback.js +1 -1
  250. package/dist/vendor/devkit/actions-secrets.js +1 -1
  251. package/dist/vendor/devkit/agents-md.js +233 -0
  252. package/dist/vendor/devkit/attach-log-file.js +16 -2
  253. package/dist/vendor/devkit/build-json.js +1 -1
  254. package/dist/vendor/devkit/ci-workflows.js +30 -30
  255. package/dist/vendor/devkit/cli-router.js +16 -11
  256. package/dist/vendor/devkit/defaults-engine.js +15 -51
  257. package/dist/vendor/devkit/deploy-snapshot.js +44 -9
  258. package/dist/vendor/devkit/env-lines.js +183 -0
  259. package/dist/vendor/devkit/local.js +64 -10
  260. package/dist/vendor/devkit/lockfile.js +32 -13
  261. package/dist/vendor/devkit/logger.js +7 -2
  262. package/dist/vendor/devkit/merge-line-files.js +219 -177
  263. package/dist/vendor/devkit/omega-bin.js +208 -111
  264. package/dist/vendor/devkit/preludes/docs-sync.js +52 -0
  265. package/dist/vendor/devkit/preludes/index.js +1 -0
  266. package/dist/vendor/devkit/target-picker.js +45 -0
  267. package/dist/vendor/devkit/test/dashed-files.js +37 -0
  268. package/dist/vendor/devkit/test/run-verb-under-tee.js +71 -0
  269. package/dist/vendor/devkit/test/runner-core.js +6 -6
  270. package/dist/vendor/devkit/update.js +15 -15
  271. package/dist/vendor/devkit/verb-scripts.js +40 -0
  272. package/dist/vendor/devkit/verbs.js +170 -0
  273. package/dist/vendor/monitoring/env.js +2 -2
  274. package/dist/vendor/monitoring/index.js +1 -1
  275. package/dist/vendor/monitoring/main.js +1 -1
  276. package/dist/vendor/monitoring/preload.js +1 -1
  277. package/dist/vendor/monitoring/renderer.js +1 -1
  278. package/package.json +19 -26
  279. package/dist/commands/install.js +0 -37
  280. package/dist/defaults/AGENTS.md +0 -110
  281. package/dist/defaults/CLAUDE.md +0 -1
  282. package/dist/lib/client-bridge.js +0 -374
  283. package/dist/lib/logger.js +0 -4
  284. package/dist/test/suites/build/manager.test.js +0 -213
  285. package/dist/test/suites/main/client-bridge.test.js +0 -262
  286. package/dist/vendor/config/env-retired.js +0 -137
  287. package/dist/vendor/config/retired-keys.js +0 -635
  288. package/docs/analytics.md +0 -140
  289. package/docs/app-state.md +0 -92
  290. package/docs/audit.md +0 -69
  291. package/docs/auto-updater.md +0 -243
  292. package/docs/boot-sequence.md +0 -39
  293. package/docs/build-system.md +0 -169
  294. package/docs/cdp-debugging.md +0 -169
  295. package/docs/client-bridge.md +0 -269
  296. package/docs/common-mistakes.md +0 -21
  297. package/docs/config-schema.md +0 -120
  298. package/docs/context-menu.md +0 -112
  299. package/docs/context.md +0 -81
  300. package/docs/css.md +0 -90
  301. package/docs/deep-link.md +0 -186
  302. package/docs/environment-detection.md +0 -112
  303. package/docs/fontawesome.md +0 -107
  304. package/docs/hooks.md +0 -89
  305. package/docs/icons.md +0 -79
  306. package/docs/index.md +0 -317
  307. package/docs/installer-options.md +0 -165
  308. package/docs/ipc.md +0 -61
  309. package/docs/lib-modules.md +0 -53
  310. package/docs/logging.md +0 -229
  311. package/docs/menu.md +0 -160
  312. package/docs/releasing.md +0 -239
  313. package/docs/remote-config.md +0 -118
  314. package/docs/remote-scripts.md +0 -144
  315. package/docs/restart-manager.md +0 -144
  316. package/docs/runner.md +0 -290
  317. package/docs/sentry.md +0 -97
  318. package/docs/shared/agent-docs.md +0 -89
  319. package/docs/shared/analytics.md +0 -612
  320. package/docs/shared/brands.md +0 -57
  321. package/docs/shared/breaking-changes.md +0 -851
  322. package/docs/shared/config.md +0 -1952
  323. package/docs/shared/deploys.md +0 -341
  324. package/docs/shared/icons.md +0 -219
  325. package/docs/shared/local-dev.md +0 -167
  326. package/docs/shared/logging.md +0 -205
  327. package/docs/shared/monitoring.md +0 -167
  328. package/docs/shared/publishing.md +0 -187
  329. package/docs/shared/rulings.md +0 -34
  330. package/docs/shared/testing.md +0 -147
  331. package/docs/shared/theming.md +0 -629
  332. package/docs/shared/translation.md +0 -333
  333. package/docs/shared/updates.md +0 -61
  334. package/docs/signing.md +0 -293
  335. package/docs/startup.md +0 -142
  336. package/docs/storage.md +0 -59
  337. package/docs/templating.md +0 -101
  338. package/docs/test-boot-layer.md +0 -157
  339. package/docs/test-framework.md +0 -362
  340. package/docs/themes.md +0 -149
  341. package/docs/tooltips.md +0 -99
  342. package/docs/tray.md +0 -164
  343. package/docs/usage.md +0 -58
  344. package/docs/verts.md +0 -62
  345. package/docs/windows.md +0 -149
package/dist/renderer.js CHANGED
@@ -1,468 +1,494 @@
1
- // Renderer-process Manager singleton.
2
- // Consumer entry (per view): `new (require('@omega.js/desktop/renderer'))().initialize()`.
3
- // Reads window.OMEGA_BUILD_JSON.config (the one dist/build.js the view's shell loads, #743), bootstraps @omega.js/client + auth.
1
+ // The renderer runtime: ONE ready-made `omega` instance per view, built on @omega.js/client.
2
+ // Consumer entry (per view): `import omega from '@omega.js/desktop/renderer'; omega.initialize()`.
3
+ // Reads window.OMEGA_BUILD_JSON.config (the one dist/build.js the view's shell loads, #743),
4
+ // boots the client it extends, then wires the desktop surfaces.
4
5
  //
5
6
  // Auth bridge:
6
7
  // - On init, asks main "I'm at UID X (or null), are we in sync?" via desktop:auth:sync-request.
7
- // If main returns a custom token, this renderer calls omega.auth().signInWithCustomToken(token).
8
+ // If main returns a custom token, this renderer signs in with it (auth.signInWithCustomToken).
8
9
  // If main says "sign out," this renderer signs out.
9
10
  // - Listens for desktop:auth:sign-in-with-token broadcasts (fired when ANY renderer or main signs in)
10
11
  // and signs in with the provided token.
11
12
  // - Listens for desktop:auth:sign-out broadcasts and signs out.
13
+ // - Pushes every signed-in state's account to main (desktop:auth:account-resolved), which builds
14
+ // main's `auth.user` from it.
12
15
  //
13
- // Pattern mirrors BXM: main is the source of truth, renderers reflect.
16
+ // Main is the source of truth, renderers reflect.
14
17
 
18
+ const { Omega: ClientOmega } = require('@omega.js/client');
19
+ const { WAKEUP_ROUTE } = require('@omega.js/client/modules/request.js');
20
+ const { createShell } = require('./assets/js/core/app-shell.js');
15
21
  const LoggerLite = require('./lib/logger-lite.js');
22
+ const { getVersion } = require('./utils/mode-helpers.js');
23
+ const { getWebsiteUrl, getAuthUrl } = require('./utils/url-helpers.js');
24
+
25
+ /**
26
+ * The renderer runtime: @omega.js/client's base class (`auth`, `storage`,
27
+ * `analytics`, `bindings`, ...) plus `desktop`, the preload's bridge to the main
28
+ * process. This module exports ONE instance of it; a consumer never writes
29
+ * `new`, and awaits `initialize()` or `ready`.
30
+ */
31
+ class Omega extends ClientOmega {
32
+ constructor() {
33
+ // The preload exposes the bridge before any page script runs, so its absence
34
+ // is a window built without @omega.js/desktop/preload: a programmer error
35
+ if (typeof window === 'undefined' || !window.desktop) {
36
+ throw new Error('@omega.js/desktop/renderer: window.desktop is missing, so this window did not load the @omega.js/desktop preload (src/preload.js).');
37
+ }
16
38
 
17
- function Manager() {
18
- const self = this;
19
-
20
- self.config = null;
21
- self.logger = new LoggerLite('renderer');
22
- self.omega = null;
39
+ super();
23
40
 
24
- // Bridges exposed by preload contextBridge (window.desktop.*)
25
- self.ipc = (typeof window !== 'undefined' && window.desktop?.ipc) || null;
26
- self.storage = (typeof window !== 'undefined' && window.desktop?.storage) || null;
41
+ // Everything that crosses to the main process, under main's names: ipc,
42
+ // storage (the APP store; `omega.storage` stays the client's page store),
43
+ // theme, fontawesome, autoUpdater, analytics, context, usage, remoteConfig
44
+ this.desktop = window.desktop;
45
+ this.logger = new LoggerLite('renderer');
46
+ }
27
47
 
28
- return self;
29
- }
48
+ /**
49
+ * Boot the client from the view's build snapshot plus `overrides`, then wire
50
+ * the desktop surfaces: the auth bridge, theme controls, the app shell,
51
+ * icons, tooltips and verts.
52
+ * @param {object} [overrides] - runtime config merged over the build snapshot.
53
+ * @returns {Promise<Omega>} the instance.
54
+ */
55
+ async initialize(overrides) {
56
+ // Merge runtime overrides on top of build-time config.
57
+ // OMEGA_BUILD_JSON comes off the window: the view's shell loads the ONE
58
+ // `dist/build.js` ahead of this bundle (#743), which is also what makes it
59
+ // readable from DevTools.
60
+ const config = Object.assign({}, window.OMEGA_BUILD_JSON?.config || {}, overrides || {});
61
+
62
+ // The RUNNING environment beats the baked one, exactly as it does in main
63
+ // ([#925](https://github.com/Omega-JS-Stack/omega/issues/925)). A page has no
64
+ // `process`, so `isTesting()` here reads `config.environment`, the word the
65
+ // BUILD was for; the preload does have it and hands it over on the bridge, so a
66
+ // test lane booting a production artifact gets the same answer in this renderer
67
+ // that main gives. No second signal: it is the one input, one context removed.
68
+ if (this.desktop.environment) {
69
+ config.environment = this.desktop.environment;
70
+ }
30
71
 
31
- Manager.prototype.initialize = async function (overrides) {
32
- const self = this;
33
-
34
- // Merge runtime overrides on top of build-time config.
35
- // OMEGA_BUILD_JSON comes off the window: the view's shell loads the ONE
36
- // `dist/build.js` ahead of this bundle (#743), which is also what makes it
37
- // readable from DevTools.
38
- const buildJson = (typeof window !== 'undefined' && window.OMEGA_BUILD_JSON) || {};
39
- self.config = Object.assign({}, buildJson.config || {}, overrides || {});
40
-
41
- // The RUNNING environment beats the baked one, exactly as it does in main
42
- // ([#925](https://github.com/Omega-JS-Stack/omega/issues/925)). A page has no
43
- // `process`, so `isTesting()` here reads `config.environment`, the word the
44
- // BUILD was for; the preload does have it and hands it over on the bridge, so a
45
- // test lane booting a production artifact gets the same answer in this renderer
46
- // that main gives. No second signal: it is the one input, one context removed.
47
- const runningEnvironment = (typeof window !== 'undefined' && window.desktop?.environment) || null;
48
- if (runningEnvironment) {
49
- self.config.environment = runningEnvironment;
72
+ this.logger.log('Initializing @omega.js/desktop (renderer)...');
73
+
74
+ // `analyticsBridge` is the preload's analytics surface, handed over as config:
75
+ // a renderer NEVER sends analytics itself. The client forwards every event
76
+ // through this bridge to main, whose sender owns the one device id
77
+ // (electron-store, MAC-seeded), the one session id minted per launch, and the
78
+ // real engagement time ([#411](https://github.com/Omega-JS-Stack/omega/issues/411)).
79
+ // The client reads this key alone, so nothing but THIS line can bridge it.
80
+ //
81
+ // 🚫 The GA Measurement Protocol secret must NEVER be injected into this
82
+ // config. A second sender in a renderer would split one install into two GA
83
+ // clients (the #396 class), which is why desktop's config carries the
84
+ // measurement id alone, and why the client drops any secret handed to a
85
+ // bridged renderer.
86
+ await super.initialize(Object.assign({}, config, {
87
+ analyticsBridge: this.desktop.analytics,
88
+ }));
89
+
90
+ // Wire the auth bridge: sync with main, listen for broadcasts.
91
+ await this._wireAuthBridge();
92
+
93
+ // Wire declarative theme controls ([data-omega-theme-set]).
94
+ this._wireThemeControls();
95
+
96
+ // The vendored app shell (@omega.js/web's core/js/core/app-shell.js), built
97
+ // from this instance exactly as web builds it
98
+ this.shell = createShell(this);
99
+
100
+ // Auto-render FontAwesome icons (<i class="fa-solid fa-*">) and Bootstrap
101
+ // tooltips ([data-bs-toggle="tooltip"]): presence-driven, live via
102
+ // MutationObserver, zero consumer setup.
103
+ this.enableFontAwesome();
104
+ this._wireTooltips();
105
+
106
+ // Auto-bind [data-omega-vert] elements to the client verts module
107
+ // (house/company lane only, no AdSense in desktop surfaces).
108
+ this._wireAds();
109
+
110
+ this.logger.log('@omega.js/desktop (renderer) initialized.');
111
+
112
+ return this;
50
113
  }
51
114
 
52
- self.logger.log('Initializing @omega.js/desktop (renderer)...');
53
-
54
- // Boot @omega.js/client so Firebase Auth is available in this renderer.
115
+ // Declarative theme switching: any element with `data-omega-theme-set="system|light|dark"`
116
+ // becomes a theme control: clicking it calls main's theme setter (persisted, applied to
117
+ // every renderer live). The `<html data-bs-theme>` attribute itself is maintained by the
118
+ // preload's theme applier (src/preload.js); consumers just drop plain buttons:
55
119
  //
56
- // `analyticsBridge` is the preload's analytics surface, handed over as config:
57
- // a renderer NEVER sends analytics itself — the client forwards every event
58
- // through this bridge to main, whose sender owns the one device id
59
- // (electron-store, MAC-seeded), the one session id minted per launch, and the
60
- // real engagement time ([#411](https://github.com/Omega-JS-Stack/omega/issues/411)).
61
- // The client reads this key alone, so nothing but THIS line can bridge it.
120
+ // <button data-omega-theme-set="light">Day</button>
121
+ // <button data-omega-theme-set="dark">Dusk</button>
122
+ // <button data-omega-theme-set="system">Auto</button>
62
123
  //
63
- // 🚫 The GA Measurement Protocol secret must NEVER be injected into this
64
- // config. A second sender in a renderer would split one install into two GA
65
- // clients (the #396 class) — which is why desktop's config carries the
66
- // measurement id alone, and why the client drops any secret handed to a
67
- // bridged renderer.
68
- try {
69
- const wmMod = require('@omega.js/client');
70
- self.omega = wmMod.default || wmMod;
71
- if (self.omega?.initialize) {
72
- await self.omega.initialize(Object.assign({}, self.config, {
73
- analyticsBridge: (typeof window !== 'undefined' && window.desktop?.analytics) || null,
74
- }));
124
+ // Delegated on document so controls rendered after initialize still work.
125
+ _wireThemeControls() {
126
+ if (typeof document === 'undefined') {
127
+ return;
75
128
  }
76
- } catch (e) {
77
- self.logger.warn('@omega.js/client not available — auth bridge running in no-op mode.', e?.message);
129
+
130
+ document.addEventListener('click', (event) => {
131
+ const control = event.target.closest('[data-omega-theme-set]');
132
+ if (!control) {
133
+ return;
134
+ }
135
+ const source = control.getAttribute('data-omega-theme-set');
136
+ this.desktop.theme.set(source).catch((e) => {
137
+ this.logger.warn(`theme set '${source}' failed:`, e?.message);
138
+ });
139
+ });
78
140
  }
79
141
 
80
- // Wire the auth bridge: sync with main, listen for broadcasts.
81
- await self._wireAuthBridge();
142
+ // FontAwesome auto-render: any `<i>` element carrying `fa-*` classes gets the
143
+ // Font Awesome SVG injected inline, whether present at init, inserted later,
144
+ // or re-classed via JS at any time (`el.className = 'fa-solid fa-stop'`
145
+ // re-renders in place). The whole DOM mechanism (class parsing on FA's
146
+ // family × weight model, MutationObserver, caching, re-render/clear) is
147
+ // @omega.js/client's icon renderer, started through the instance's `icons`
148
+ // module, the SAME one web pages run; desktop only supplies the transport
149
+ // (IPC to main's icon server). Unknown names leave the element empty (marked
150
+ // data-omega-fa); consumers that want a fallback check `omega.desktop.fontawesome.get()`.
151
+ //
152
+ // <i class="fa-solid fa-rocket me-2"></i> → <i …><svg …>…</svg></i>
153
+ //
154
+ // Public because a minimal surface that skips initialize() can still enable
155
+ // it: `omega.enableFontAwesome()` on the imported instance.
156
+ enableFontAwesome() {
157
+ if (typeof document === 'undefined') {
158
+ return;
159
+ }
82
160
 
83
- // Wire declarative theme controls ([data-omega-theme-set]).
84
- self._wireThemeControls();
161
+ this.icons.start({
162
+ resolve: (name, style) => this.desktop.fontawesome.get(name, style),
163
+ }, document);
164
+ }
85
165
 
86
- // Auto-render FontAwesome icons (<i class="fa-solid fa-*">) and Bootstrap
87
- // tooltips ([data-bs-toggle="tooltip"]) — presence-driven, live via
88
- // MutationObserver, zero consumer setup.
89
- self._wireFontAwesome();
90
- self._wireTooltips();
166
+ // Bootstrap tooltips: auto-initialize every `[data-bs-toggle="tooltip"]`
167
+ // element (Bootstrap's JS + Popper ship inside @omega.js/desktop as a prebuilt bundle,
168
+ // assets/js/bootstrap.bundle.js; consumers add ZERO setup).
169
+ // Live-managed via MutationObserver:
170
+ // - elements inserted later get their tooltip on arrival
171
+ // - `data-bs-title` / `title` changes update the live instance (emptying it
172
+ // disposes: no tooltip is a valid state)
173
+ // - removed elements have their instance disposed (no orphaned tips)
174
+ // The full Bootstrap namespace is exposed at `window.bootstrap` (and
175
+ // `omega.bootstrap`) for manual control: Tooltip, Collapse, Dropdown, etc.
176
+ // Only tooltips are auto-initialized (Bootstrap 5 makes them opt-in); the other
177
+ // components' standard data-api works out of the box on Bootstrap markup.
178
+ //
179
+ // Gotcha (Bootstrap's own): disabled elements don't fire hover events, so wrap a
180
+ // disabled control in a <span data-bs-toggle="tooltip"> to tooltip it.
181
+ _wireTooltips() {
182
+ if (typeof document === 'undefined') {
183
+ return;
184
+ }
91
185
 
92
- // Auto-bind [data-omega-vert] elements to the shared client verts module
93
- // (house/company lane only — no AdSense in desktop surfaces).
94
- self._wireAds();
186
+ // Everything, including the require, is deferred until the document
187
+ // exists: the Bootstrap bundle reads document.documentElement at import
188
+ // time, which is null when this runs from a preload (the @omega.js/desktop test harness
189
+ // does) before the DOM is built.
190
+ const start = () => {
191
+ let Tooltip;
192
+ try {
193
+ // Prebuilt UMD (Popper inlined): loads through the bundle task AND plain require().
194
+ const bootstrap = require('./assets/js/bootstrap.bundle.js');
195
+ Tooltip = bootstrap.Tooltip;
196
+ window.bootstrap = Object.assign(window.bootstrap || {}, bootstrap);
197
+ } catch (e) {
198
+ this.logger.warn('bootstrap Tooltip unavailable: tooltips disabled.', e?.message);
199
+ return;
200
+ }
95
201
 
96
- self.logger.log('@omega.js/desktop (renderer) initialized.');
202
+ this.bootstrap = window.bootstrap;
97
203
 
98
- return self;
99
- };
204
+ const SELECTOR = '[data-bs-toggle="tooltip"]';
100
205
 
101
- // Declarative theme switching — any element with `data-omega-theme-set="system|light|dark"`
102
- // becomes a theme control: clicking it calls main's theme setter (persisted, applied to
103
- // every renderer live). The `<html data-bs-theme>` attribute itself is maintained by the
104
- // preload's theme applier (src/preload.js) — consumers just drop plain buttons:
105
- //
106
- // <button data-omega-theme-set="light">Day</button>
107
- // <button data-omega-theme-set="dark">Dusk</button>
108
- // <button data-omega-theme-set="system">Auto</button>
109
- //
110
- // Delegated on document so controls rendered after initialize still work.
111
- Manager.prototype._wireThemeControls = function () {
112
- const self = this;
206
+ const init = (el) => {
207
+ Tooltip.getOrCreateInstance(el);
208
+ };
113
209
 
114
- if (typeof document === 'undefined' || !window.desktop?.theme) {
115
- return;
116
- }
210
+ const scan = (root) => {
211
+ if (root.matches?.(SELECTOR)) {
212
+ init(root);
213
+ }
214
+ root.querySelectorAll?.(SELECTOR).forEach(init);
215
+ };
117
216
 
118
- document.addEventListener('click', (event) => {
119
- const control = event.target.closest('[data-omega-theme-set]');
120
- if (!control) {
121
- return;
122
- }
123
- const source = control.getAttribute('data-omega-theme-set');
124
- window.desktop.theme.set(source).catch((e) => {
125
- self.logger.warn(`theme set '${source}' failed:`, e?.message);
126
- });
127
- });
128
- };
129
-
130
- // FontAwesome auto-render — any `<i>` element carrying `fa-*` classes gets the
131
- // Font Awesome SVG injected inline, whether present at init, inserted later,
132
- // or re-classed via JS at any time (`el.className = 'fa-solid fa-stop'`
133
- // re-renders in place). The whole DOM mechanism — class parsing (FA's
134
- // family × weight model), MutationObserver, caching, re-render/clear — is
135
- // @omega.js/client's shared icon-renderer (C4 cp112), the SAME module web
136
- // pages run; desktop only supplies the transport (IPC to main's icon
137
- // server). Unknown names leave the element empty (marked data-omega-fa) —
138
- // consumers that want a fallback check `window.desktop.fontawesome.get()`.
139
- //
140
- // <i class="fa-solid fa-rocket me-2"></i> → <i …><svg …>…</svg></i>
141
- Manager.prototype._wireFontAwesome = function () {
142
- const self = this;
217
+ const dispose = (root) => {
218
+ if (root.nodeType !== 1) {
219
+ return;
220
+ }
221
+ const candidates = [root, ...(root.querySelectorAll?.(SELECTOR) || [])];
222
+ for (const el of candidates) {
223
+ Tooltip.getInstance(el)?.dispose();
224
+ }
225
+ };
226
+
227
+ new MutationObserver((mutations) => {
228
+ for (const mutation of mutations) {
229
+ if (mutation.type === 'childList') {
230
+ mutation.addedNodes.forEach((node) => {
231
+ if (node.nodeType === 1) {
232
+ scan(node);
233
+ }
234
+ });
235
+ mutation.removedNodes.forEach(dispose);
236
+ continue;
237
+ }
143
238
 
144
- if (typeof document === 'undefined' || !self.ipc) {
145
- return;
146
- }
239
+ // Attribute change on a (potential) tooltip host.
240
+ const el = mutation.target;
241
+ const instance = Tooltip.getInstance(el);
242
+ if (!el.matches(SELECTOR)) {
243
+ instance?.dispose();
244
+ continue;
245
+ }
246
+ if (!instance) {
247
+ init(el);
248
+ continue;
249
+ }
250
+ if (mutation.attributeName !== 'data-bs-toggle') {
251
+ // data-bs-original-title is Bootstrap's OWN bookkeeping: the
252
+ // Tooltip constructor MOVES a plain `title` attribute there.
253
+ // Without reading it, a title-only host infinite-loops the
254
+ // renderer: init removes `title` → this handler sees no title →
255
+ // dispose → dispose RESTORES `title` → re-init → …, a pure
256
+ // MutationObserver microtask storm that starves the main thread
257
+ // (found by Somiibo's session-limits boot suite: the settings
258
+ // page froze solid on one badge).
259
+ const title = el.getAttribute('data-bs-title') || el.getAttribute('title') || el.getAttribute('data-bs-original-title');
260
+ if (title) {
261
+ instance.setContent({ '.tooltip-inner': title });
262
+ } else {
263
+ instance.dispose();
264
+ }
265
+ }
266
+ }
267
+ }).observe(document.documentElement, {
268
+ childList: true,
269
+ subtree: true,
270
+ attributes: true,
271
+ attributeFilter: ['data-bs-toggle', 'data-bs-title', 'title'],
272
+ });
147
273
 
148
- const { createIconRenderer } = require('@omega.js/client/modules/icon-renderer.js');
149
- createIconRenderer({
150
- resolve: (name, style) => self.ipc.invoke('desktop:fontawesome:get', { name, style })
151
- .then((r) => r?.svg ?? null),
152
- }).start(document);
153
- };
154
-
155
- // Public alias — minimal surfaces that skip the full initialize() (no
156
- // @omega.js/client / auth bridge) can still enable the FontAwesome auto-render:
157
- // new (require('@omega.js/desktop/renderer'))().enableFontAwesome();
158
- Manager.prototype.enableFontAwesome = Manager.prototype._wireFontAwesome;
159
-
160
- // Bootstrap tooltips — auto-initialize every `[data-bs-toggle="tooltip"]`
161
- // element (Bootstrap's JS + Popper ship inside @omega.js/desktop as a prebuilt bundle —
162
- // assets/js/bootstrap.bundle.js; consumers add ZERO setup).
163
- // Live-managed via MutationObserver:
164
- // - elements inserted later get their tooltip on arrival
165
- // - `data-bs-title` / `title` changes update the live instance (emptying it
166
- // disposes — no tooltip is a valid state)
167
- // - removed elements have their instance disposed (no orphaned tips)
168
- // The full Bootstrap namespace is exposed at `window.bootstrap` (and
169
- // `manager.bootstrap`) for manual control — Tooltip, Collapse, Dropdown, etc.
170
- // Only tooltips are auto-initialized (Bootstrap 5 makes them opt-in); the other
171
- // components' standard data-api works out of the box on Bootstrap markup.
172
- //
173
- // Gotcha (Bootstrap's own): disabled elements don't fire hover events — wrap a
174
- // disabled control in a <span data-bs-toggle="tooltip"> to tooltip it.
175
- Manager.prototype._wireTooltips = function () {
176
- const self = this;
274
+ scan(document.documentElement);
275
+ };
177
276
 
178
- if (typeof document === 'undefined') {
179
- return;
277
+ if (document.documentElement && document.readyState !== 'loading') {
278
+ start();
279
+ } else {
280
+ document.addEventListener('DOMContentLoaded', start, { once: true });
281
+ }
180
282
  }
181
283
 
182
- // Everything — including the require — is deferred until the document
183
- // exists: the Bootstrap bundle reads document.documentElement at import
184
- // time, which is null when this runs from a preload (the @omega.js/desktop test harness
185
- // does) before the DOM is built.
186
- const start = () => {
187
- let Tooltip;
188
- try {
189
- // Prebuilt UMD (Popper inlined) — loads through the bundle task AND plain require().
190
- const bootstrap = require('./assets/js/bootstrap.bundle.js');
191
- Tooltip = bootstrap.Tooltip;
192
- window.bootstrap = Object.assign(window.bootstrap || {}, bootstrap);
193
- } catch (e) {
194
- self.logger.warn('bootstrap Tooltip unavailable — tooltips disabled.', e?.message);
284
+ // Vert auto-bind (ads-system phase 4): every `[data-omega-vert]` element,
285
+ // present at init or inserted later (MutationObserver, same liveness as the
286
+ // FontAwesome/tooltip wiring), is handed to the client's verts module,
287
+ // which owns the WHOLE lifecycle: lazy arming near the viewport, sandboxed
288
+ // iframe to the resolved in-house source's /omega/verts/serve, origin-validated
289
+ // postMessage, host-owned rotation + staleness recovery, no-fill collapse.
290
+ // The type is PINNED to 'house': desktop surfaces never run the AdSense
291
+ // provider lane (policy: no web context), so even a shared omega.json5 that
292
+ // carries `advertising.providers.adsense` can only ever take the
293
+ // house/company inventory here. mount() merges passed options OVER the
294
+ // element attributes, so the pin is absolute; bound hosts are marked
295
+ // `data-omega-vert-bound="house"` for observability.
296
+ //
297
+ // <div data-omega-vert data-omega-vert-size="banner"></div>
298
+ _wireAds() {
299
+ if (this._adsWired || typeof document === 'undefined' || typeof MutationObserver === 'undefined') {
195
300
  return;
196
301
  }
302
+ this._adsWired = true;
197
303
 
198
- self.bootstrap = window.bootstrap;
304
+ const SELECTOR = '[data-omega-vert]';
199
305
 
200
- const SELECTOR = '[data-bs-toggle="tooltip"]';
201
-
202
- const init = (el) => {
203
- Tooltip.getOrCreateInstance(el);
306
+ const mount = (el) => {
307
+ this.verts.mount(el, { type: 'house' });
308
+ el.setAttribute('data-omega-vert-bound', 'house');
204
309
  };
205
310
 
206
311
  const scan = (root) => {
207
312
  if (root.matches?.(SELECTOR)) {
208
- init(root);
209
- }
210
- root.querySelectorAll?.(SELECTOR).forEach(init);
211
- };
212
-
213
- const dispose = (root) => {
214
- if (root.nodeType !== 1) {
215
- return;
216
- }
217
- const candidates = [root, ...(root.querySelectorAll?.(SELECTOR) || [])];
218
- for (const el of candidates) {
219
- Tooltip.getInstance(el)?.dispose();
313
+ mount(root);
220
314
  }
315
+ root.querySelectorAll?.(SELECTOR).forEach(mount);
221
316
  };
222
317
 
223
- new MutationObserver((mutations) => {
224
- for (const mutation of mutations) {
225
- if (mutation.type === 'childList') {
318
+ const start = () => {
319
+ new MutationObserver((mutations) => {
320
+ for (const mutation of mutations) {
226
321
  mutation.addedNodes.forEach((node) => {
227
322
  if (node.nodeType === 1) {
228
323
  scan(node);
229
324
  }
230
325
  });
231
- mutation.removedNodes.forEach(dispose);
232
- continue;
233
326
  }
327
+ }).observe(document.documentElement, { childList: true, subtree: true });
234
328
 
235
- // Attribute change on a (potential) tooltip host.
236
- const el = mutation.target;
237
- const instance = Tooltip.getInstance(el);
238
- if (!el.matches(SELECTOR)) {
239
- instance?.dispose();
240
- continue;
241
- }
242
- if (!instance) {
243
- init(el);
244
- continue;
245
- }
246
- if (mutation.attributeName !== 'data-bs-toggle') {
247
- // data-bs-original-title is Bootstrap's OWN bookkeeping: the
248
- // Tooltip constructor MOVES a plain `title` attribute there.
249
- // Without reading it, a title-only host infinite-loops the
250
- // renderer: init removes `title` → this handler sees no title →
251
- // dispose → dispose RESTORES `title` → re-init → … — a pure
252
- // MutationObserver microtask storm that starves the main thread
253
- // (found by Somiibo's session-limits boot suite: the settings
254
- // page froze solid on one badge).
255
- const title = el.getAttribute('data-bs-title') || el.getAttribute('title') || el.getAttribute('data-bs-original-title');
256
- if (title) {
257
- instance.setContent({ '.tooltip-inner': title });
258
- } else {
259
- instance.dispose();
260
- }
261
- }
262
- }
263
- }).observe(document.documentElement, {
264
- childList: true,
265
- subtree: true,
266
- attributes: true,
267
- attributeFilter: ['data-bs-toggle', 'data-bs-title', 'title'],
268
- });
269
-
270
- scan(document.documentElement);
271
- };
272
-
273
- if (document.documentElement && document.readyState !== 'loading') {
274
- start();
275
- } else {
276
- document.addEventListener('DOMContentLoaded', start, { once: true });
277
- }
278
- };
279
-
280
- // Vert auto-bind (ads-system phase 4) — every `[data-omega-vert]` element,
281
- // present at init or inserted later (MutationObserver, same liveness as the
282
- // FontAwesome/tooltip wiring), is handed to @omega.js/client's verts module,
283
- // which owns the WHOLE lifecycle: lazy arming near the viewport, sandboxed
284
- // iframe to the resolved in-house source's /omega/verts/serve, origin-validated
285
- // postMessage, host-owned rotation + staleness recovery, no-fill collapse.
286
- // The type is PINNED to 'house': desktop surfaces never run the AdSense
287
- // provider lane (policy: no web context), so even a shared omega.json5 that
288
- // carries `advertising.providers.adsense` can only ever take the
289
- // house/company inventory here. mount() merges passed options OVER the
290
- // element attributes, so the pin is absolute; bound hosts are marked
291
- // `data-omega-vert-bound="house"` for observability.
292
- //
293
- // <div data-omega-vert data-omega-vert-size="banner"></div>
294
- Manager.prototype._wireAds = function () {
295
- const self = this;
296
-
297
- if (self._adsWired || typeof document === 'undefined' || typeof MutationObserver === 'undefined' || !self.omega?.verts) {
298
- return;
299
- }
300
- self._adsWired = true;
301
-
302
- const SELECTOR = '[data-omega-vert]';
303
-
304
- const mount = (el) => {
305
- self.omega.verts().mount(el, { type: 'house' });
306
- el.setAttribute('data-omega-vert-bound', 'house');
307
- };
329
+ scan(document.documentElement);
330
+ };
308
331
 
309
- const scan = (root) => {
310
- if (root.matches?.(SELECTOR)) {
311
- mount(root);
332
+ if (document.documentElement && document.readyState !== 'loading') {
333
+ start();
334
+ } else {
335
+ document.addEventListener('DOMContentLoaded', start, { once: true });
312
336
  }
313
- root.querySelectorAll?.(SELECTOR).forEach(mount);
314
- };
315
-
316
- const start = () => {
317
- new MutationObserver((mutations) => {
318
- for (const mutation of mutations) {
319
- mutation.addedNodes.forEach((node) => {
320
- if (node.nodeType === 1) {
321
- scan(node);
322
- }
323
- });
324
- }
325
- }).observe(document.documentElement, { childList: true, subtree: true });
326
-
327
- scan(document.documentElement);
328
- };
329
-
330
- if (document.documentElement && document.readyState !== 'loading') {
331
- start();
332
- } else {
333
- document.addEventListener('DOMContentLoaded', start, { once: true });
334
- }
335
- };
336
-
337
- // Bridge between renderer's @omega.js/client and main's client-bridge.
338
- // Mirrors BXM's foreground sync logic.
339
- Manager.prototype._wireAuthBridge = async function () {
340
- const self = this;
341
- if (!self.ipc) return;
342
-
343
- // Warm the backend before any of the waits below. This bridge ends in a
344
- // sync-request, and main answers it by POSTing `/omega/user/token` — the
345
- // app's first backend call, on a function that is cold every time the app
346
- // launches. Fire-and-forget and unauthenticated: the backend answers a wakeup
347
- // before it loads a route or authenticates
348
- // ([#644](https://github.com/Omega-JS-Stack/omega/issues/644)).
349
- // The route is required where it is used, the way client-bridge requires the
350
- // request layer it shares with this bridge.
351
- try {
352
- const { WAKEUP_ROUTE } = require('@omega.js/client/modules/request.js');
353
- self.omega?.request?.(WAKEUP_ROUTE, { wakeup: true });
354
- } catch (e) {
355
- self.logger.warn('wakeup ping failed (non-fatal):', e?.message);
356
337
  }
357
338
 
358
- const auth = self.omega?.auth?.();
359
- const getCurrentUid = () => {
360
- try { return auth ? (auth.getUser()?.uid || null) : null; }
361
- catch (e) { return null; }
362
- };
363
-
364
- // Listen for sign-in-with-token broadcasts (main signed in via deep link, or another renderer signed in).
365
- self.ipc.on('desktop:auth:sign-in-with-token', async ({ token }) => {
366
- if (!token || !auth?.signInWithCustomToken) return;
339
+ // Bridge between this renderer's auth and main's lib/auth.js.
340
+ async _wireAuthBridge() {
341
+ const { ipc } = this.desktop;
342
+
343
+ // The auth click triggers the extension's pages carry, routed to main:
344
+ // `omega-signin` opens the sign-in flow, `omega-account` the brand's
345
+ // /account page, both in the user's browser. `omega-signout` is the
346
+ // client's, and it runs this instance's signOut(): main signs out, and this
347
+ // window's own sign-out arrives on the broadcast below.
348
+ this.triggers.register('signin', () => ipc.invoke('desktop:auth:open-flow'));
349
+ this.triggers.register('account', () => ipc.invoke('desktop:auth:open-account'));
350
+
351
+ // Warm the backend before any of the waits below. This bridge ends in a
352
+ // sync-request, and main answers it by POSTing `/omega/user/token`, the
353
+ // app's first backend call, on a function that is cold every time the app
354
+ // launches. Fire-and-forget and unauthenticated: the backend answers a wakeup
355
+ // before it loads a route or authenticates
356
+ // ([#644](https://github.com/Omega-JS-Stack/omega/issues/644)). A context
357
+ // with no resolvable API base (a dev build with no stack) skips it.
367
358
  try {
368
- await auth.signInWithCustomToken(token);
369
- self.logger.log('signed in via broadcast token.');
359
+ this.request(WAKEUP_ROUTE, { wakeup: true });
370
360
  } catch (e) {
371
- self.logger.error('signInWithCustomToken (broadcast) failed:', e?.message);
361
+ this.logger.warn('wakeup ping failed (non-fatal):', e?.message);
372
362
  }
373
- });
374
363
 
375
- // Listen for sign-out broadcasts.
376
- self.ipc.on('desktop:auth:sign-out', async () => {
377
- if (!auth?.signOut) return;
378
- try {
379
- await auth.signOut();
380
- self.logger.log('signed out via broadcast.');
381
- } catch (e) {
382
- self.logger.error('signOut (broadcast) failed:', e?.message);
383
- }
384
- });
364
+ // Listen for sign-in-with-token broadcasts (main signed in via deep link, or another renderer signed in).
365
+ ipc.on('desktop:auth:sign-in-with-token', async ({ token }) => {
366
+ if (!token) return;
367
+ try {
368
+ await this.auth.signInWithCustomToken(token);
369
+ this.logger.log('signed in via broadcast token.');
370
+ } catch (e) {
371
+ this.logger.error('signInWithCustomToken (broadcast) failed:', e?.message);
372
+ }
373
+ });
385
374
 
386
- // Run @omega.js/client's FULL auth cycle (UJM/BXM parity): listen() waits for auth to
387
- // settle, fetches the Firestore account, resolves the subscription, and auto-populates
388
- // the data-omega-bind bindings — so any @omega.js/desktop app can write UJM-style reactive HTML
389
- // (`@show auth.user`, `@text auth.account.plan.id`, ...). Persistent listener: fires
390
- // again on every subsequent sign-in/out (broadcast tokens included).
391
- //
392
- // Each settle pushes the resolution to main (desktop:auth:account-resolved) — main can't
393
- // run Firestore, so this is how main-side plan gates learn the REAL plan (BXM's
394
- // "browser contexts resolve, the authority caches" split). Main uid-guards the push.
395
- if (auth?.listen) {
396
- let lastState = null;
397
-
398
- const pushResolved = async (state) => {
399
- const uid = state?.user?.uid || null;
400
- if (!uid) return;
375
+ // Listen for sign-out broadcasts.
376
+ ipc.on('desktop:auth:sign-out', async () => {
377
+ try {
378
+ await this.auth.signOut();
379
+ this.logger.log('signed out via broadcast.');
380
+ } catch (e) {
381
+ this.logger.error('signOut (broadcast) failed:', e?.message);
382
+ }
383
+ });
384
+
385
+ // The client's FULL auth cycle already runs: each state
386
+ // change fetches the Firestore account and lands one `User`, which the
387
+ // data-omega-bind bindings read. Every signed-in state's account is pushed
388
+ // to main (desktop:auth:account-resolved): main can't run Firestore, so this
389
+ // is how its `auth.user` learns the REAL account (browser contexts
390
+ // resolve, the authority caches). Main uid-guards the push.
391
+ let lastUser = null;
392
+
393
+ const push = async (user) => {
394
+ if (!user.authenticated) return;
401
395
  try {
402
- await self.ipc.invoke('desktop:auth:account-resolved', {
403
- uid,
404
- resolved: state?.resolved || null,
405
- roles: state?.account?.roles || null,
396
+ await ipc.invoke('desktop:auth:account-resolved', {
397
+ uid: user.uid,
398
+ document: user.toJSON(),
399
+ identity: identityOf(user),
406
400
  });
407
401
  } catch (e) {
408
- self.logger.warn('account-resolved push failed (non-fatal):', e?.message);
402
+ this.logger.warn('account-resolved push failed (non-fatal):', e?.message);
409
403
  }
410
404
  };
411
405
 
406
+ this.auth.listen((state) => {
407
+ lastUser = state.user;
408
+ push(state.user);
409
+ });
410
+
411
+ // Re-push when MAIN's auth state changes: a renderer that resolved BEFORE main
412
+ // signed in had its push uid-rejected (correctly: main was signed out). When
413
+ // main comes up on the same user, offer the account again.
414
+ ipc.on('desktop:auth:state-changed', (identity) => {
415
+ if (identity && lastUser && lastUser.uid === identity.uid) {
416
+ push(lastUser);
417
+ }
418
+ });
419
+
420
+ // Sync with main on load. If main has a different state, it'll send back instructions.
412
421
  try {
413
- auth.listen({}, (state) => {
414
- lastState = state;
415
- pushResolved(state);
422
+ const current = this.firebaseAuth ? this.firebaseAuth.currentUser : null;
423
+ const result = await ipc.invoke('desktop:auth:sync-request', {
424
+ contextUid: current ? current.uid : null,
416
425
  });
426
+
427
+ if (!result?.needsSync) return;
428
+
429
+ if (result.signOut) {
430
+ await this.auth.signOut();
431
+ this.logger.log('synced: signed out (main was signed out).');
432
+ } else if (result.customToken) {
433
+ await this.auth.signInWithCustomToken(result.customToken);
434
+ this.logger.log(`synced: signed in as ${result.user?.email || result.user?.uid}.`);
435
+ }
417
436
  } catch (e) {
418
- self.logger.warn('auth listen failed (non-fatal):', e?.message);
437
+ this.logger.warn('auth sync failed (non-fatal):', e?.message);
419
438
  }
439
+ }
420
440
 
421
- // Re-push when MAIN's auth state changes: a renderer that resolved BEFORE main
422
- // signed in had its push uid-rejected (correctly — main was signed out). When
423
- // main comes up on the same user, offer the resolution again.
424
- self.ipc.on('desktop:auth:state-changed', (snap) => {
425
- if (snap?.uid && lastState?.user?.uid === snap.uid) {
426
- pushResolved(lastState);
427
- }
428
- });
441
+ /**
442
+ * Sign out through main, so main and every renderer sign out together (the
443
+ * desktop:auth:sign-out broadcast). The `.omega-signout` trigger runs this;
444
+ * `omega.auth.signOut()` signs out this renderer alone.
445
+ * @returns {Promise<{ success: boolean, error?: string }>}
446
+ */
447
+ signOut() {
448
+ return this.desktop.ipc.invoke('desktop:auth:sign-out');
429
449
  }
430
450
 
431
- // Sync with main on load. If main has a different state, it'll send back instructions.
432
- try {
433
- const result = await self.ipc.invoke('desktop:auth:sync-request', {
434
- contextUid: getCurrentUid(),
435
- });
451
+ /**
452
+ * Main's account: `{ uid, document, identity }`, main's authoritative answer
453
+ * rather than this renderer's own Firebase session.
454
+ * @returns {Promise<{ uid: string|null, document: object, identity: object|null }>}
455
+ */
456
+ getMainUser() {
457
+ return this.desktop.ipc.invoke('desktop:auth:get-user');
458
+ }
436
459
 
437
- if (!result?.needsSync) return;
460
+ // Desktop's own helpers beside the client's environment four and
461
+ // getFunctionsUrl/getApiUrl: the same plain functions main and preload call
462
+ getVersion() {
463
+ return getVersion();
464
+ }
438
465
 
439
- if (result.signOut && auth?.signOut) {
440
- await auth.signOut();
441
- self.logger.log('synced: signed out (main was signed out).');
442
- } else if (result.customToken && auth?.signInWithCustomToken) {
443
- await auth.signInWithCustomToken(result.customToken);
444
- self.logger.log(`synced: signed in as ${result.user?.email || result.user?.uid}.`);
445
- }
446
- } catch (e) {
447
- self.logger.warn('auth sync failed (non-fatal):', e?.message);
466
+ getWebsiteUrl(environment) {
467
+ return getWebsiteUrl(this, environment);
448
468
  }
449
- };
450
-
451
- // Sign-out helper for renderer code (UI button etc.). Goes through main so all
452
- // renderers + main stay in sync via the broadcast.
453
- Manager.prototype.signOut = async function () {
454
- if (!this.ipc) return { success: false, error: 'no-ipc' };
455
- return this.ipc.invoke('desktop:auth:sign-out');
456
- };
457
-
458
- // Read main's current user (sync answer from main, not the renderer's local Firebase).
459
- Manager.prototype.getMainUser = async function () {
460
- if (!this.ipc) return null;
461
- return this.ipc.invoke('desktop:auth:get-user');
462
- };
463
-
464
- // Mix in shared cross-context helpers — same code path used in main, preload, build.
465
- require('./utils/mode-helpers.js').attachTo(Manager);
466
- require('./utils/url-helpers.js').attachTo(Manager);
467
-
468
- module.exports = Manager;
469
+
470
+ getAuthUrl(environment, returnUrl) {
471
+ return getAuthUrl(this, environment, returnUrl);
472
+ }
473
+ }
474
+
475
+ /**
476
+ * The identity a `User` was built from, the shape main builds its own from.
477
+ * @param {object} user - a signed-in `User`.
478
+ * @returns {{ uid: string, email: string|null, displayName: string|null, photoURL: string|null, emailVerified: boolean }}
479
+ */
480
+ function identityOf(user) {
481
+ return {
482
+ uid: user.uid,
483
+ email: user.email,
484
+ displayName: user.profile.displayName,
485
+ photoURL: user.profile.photoURL,
486
+ emailVerified: user.profile.emailVerified,
487
+ };
488
+ }
489
+
490
+ // The ONE instance, initialized by the view's entry
491
+ const omega = new Omega();
492
+
493
+ module.exports = omega;
494
+ module.exports.Omega = Omega;