@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/main.js CHANGED
@@ -1,14 +1,17 @@
1
- // Main-process Manager singleton.
2
- // Consumer entry: `new (require('@omega.js/desktop/main'))().initialize()` — config auto-loads (config/omega.json5).
3
- // Boot sequence below — each step delegates to a `lib/*.js` module. Stubs today, real impls land in pass 2.
1
+ // The main-process runtime: ONE ready-made `omega` instance.
2
+ // Consumer entry: `const omega = require('@omega.js/desktop/main'); omega.initialize()`; config
3
+ // auto-loads (config/omega.json5). The boot sequence below delegates each step to a `lib/*.js` module.
4
4
 
5
5
  const LoggerLite = require('./lib/logger-lite.js');
6
6
  const { setEnvironment, ENVIRONMENT_VAR } = require('./vendor/config/environment.js');
7
+ const { createRequest } = require('@omega.js/client/modules/request.js');
8
+ const environment = require('./lib/_environment-mixin.js');
9
+ const lifecycle = require('./lib/_lifecycle-mixin.js');
7
10
 
8
11
  const storage = require('./lib/storage.js');
9
12
  const theme = require('./lib/theme.js');
10
13
  const fontawesome = require('./lib/fontawesome.js');
11
- // Error reporting is the shared @omega.js/monitoring contract (#380) — the
14
+ // Error reporting is the shared @omega.js/monitoring contract (#380): the
12
15
  // package's entry detects main vs renderer and forwards, exactly as the
13
16
  // lib/sentry/ split it replaced did.
14
17
  const sentry = require('./vendor/monitoring/index.js');
@@ -22,7 +25,7 @@ const tray = require('./lib/tray.js');
22
25
  const menu = require('./lib/menu.js');
23
26
  const ctxMenu = require('./lib/context-menu.js');
24
27
  const startup = require('./lib/startup.js');
25
- const wmBridge = require('./lib/client-bridge.js');
28
+ const auth = require('./lib/auth.js');
26
29
  const windows = require('./lib/window-manager.js');
27
30
  const context = require('./lib/context.js');
28
31
  const usage = require('./lib/usage.js');
@@ -31,499 +34,428 @@ const remoteScripts = require('./lib/remote-scripts.js');
31
34
  const analytics = require('./lib/analytics.js');
32
35
  const restartManager = require('./lib/restart-manager/index.js');
33
36
 
34
- function Manager() {
35
- const self = this;
36
-
37
- self.config = null;
38
- self.logger = new LoggerLite('main');
39
-
40
- // Quit-vs-hide gating. The window-manager `close` handler checks `_allowQuit` /
41
- // `_isQuitting` before deciding whether to actually close (=quit) or to swallow
42
- // the event and just hide the window. Set true via `manager.quit({ force: true })`,
43
- // by `app.on('before-quit')` (any user-initiated quit), and by the auto-updater
44
- // when it's about to call `quitAndInstall()`.
45
- self._allowQuit = false;
46
- self._isQuitting = false;
47
-
48
- // Public lib references (consumer code can call them by name)
49
- self.storage = storage;
50
- self.theme = theme;
51
- self.fontawesome = fontawesome;
52
- self.sentry = sentry;
53
- self.protocol = protocol;
54
- self.deepLink = deepLink;
55
- self.authFlow = authFlow;
56
- self.appState = appState;
57
- self.ipc = ipc;
58
- self.autoUpdater = autoUpdater;
59
- self.tray = tray;
60
- self.menu = menu;
61
- self.contextMenu = ctxMenu;
62
- self.startup = startup;
63
- self.omega = wmBridge;
64
- self.windows = windows;
65
- self.context = context;
66
- self.usage = usage;
67
- self.remoteConfig = remoteConfig;
68
- self.remoteScripts = remoteScripts;
69
- self.analytics = analytics;
70
- self.restartManager = restartManager;
71
-
72
- return self;
73
- }
74
-
75
- // Open the sign-in round trip in the user's default browser (lib/auth-flow.js).
76
- // Resolves once launched; completion arrives via the auth/token deep-link route →
77
- // omega.handleAuthToken → the desktop:auth:sign-in-with-token broadcast.
78
- Manager.prototype.openAuthFlow = function (options) {
79
- return authFlow.open(options);
80
- };
81
-
82
- // Force a real quit, bypassing per-window `hideOnClose`. Use this anywhere the
83
- // app legitimately wants to exit (tray Quit, Cmd+Q from menu role:'quit',
84
- // auto-updater install). Without `{ force: true }`, the close events still get
85
- // trapped by hide-on-close handlers and the app stays running.
86
- Manager.prototype.quit = function (options) {
87
- const self = this;
88
- options = options || {};
89
-
90
- if (options.force) {
91
- self._allowQuit = true;
92
- }
93
-
94
- try {
95
- require('electron').app.quit();
96
- } catch (e) { /* electron not available — no-op in test/headless */ }
97
- };
37
+ /**
38
+ * The main-process runtime. This module exports ONE instance of it; a consumer
39
+ * never writes `new`, and awaits `initialize()` or `ready`. Every library is a
40
+ * plain property (`omega.windows`, `omega.tray`, `omega.auth`, ...).
41
+ */
42
+ class Omega {
43
+ constructor() {
44
+ this.config = null;
45
+ this.logger = new LoggerLite('main');
46
+
47
+ // Quit-vs-hide gating. The window-manager `close` handler checks `_allowQuit` /
48
+ // `_isQuitting` before deciding whether to actually close (=quit) or to swallow
49
+ // the event and just hide the window. Set true via `omega.quit({ force: true })`,
50
+ // by `app.on('before-quit')` (any user-initiated quit), and by the auto-updater
51
+ // when it's about to call `quitAndInstall()`.
52
+ this._allowQuit = false;
53
+ this._isQuitting = false;
54
+
55
+ // Public lib references (consumer code can call them by name)
56
+ this.storage = storage;
57
+ this.theme = theme;
58
+ this.fontawesome = fontawesome;
59
+ this.sentry = sentry;
60
+ this.protocol = protocol;
61
+ this.deepLink = deepLink;
62
+ this.authFlow = authFlow;
63
+ this.appState = appState;
64
+ this.ipc = ipc;
65
+ this.autoUpdater = autoUpdater;
66
+ this.tray = tray;
67
+ this.menu = menu;
68
+ this.contextMenu = ctxMenu;
69
+ this.startup = startup;
70
+ this.auth = auth;
71
+ this.windows = windows;
72
+ this.context = context;
73
+ this.usage = usage;
74
+ this.remoteConfig = remoteConfig;
75
+ this.remoteScripts = remoteScripts;
76
+ this.analytics = analytics;
77
+ this.restartManager = restartManager;
78
+
79
+ // The harmonized API fetch (omega.request), the client base's shape: a
80
+ // fresh Bearer token from main's session when signed in
81
+ this._request = createRequest({
82
+ getApiUrl: () => this.getApiUrl(),
83
+ getIdToken: (force) => this.auth.getIdToken(force),
84
+ });
98
85
 
99
- // Force a relaunch — same gating as quit, but tells electron to start back up
100
- // after exit. If an update has been downloaded, prefers `quitAndInstall()` so
101
- // the user lands on the new version instead of the old one.
102
- Manager.prototype.relaunch = function (options) {
103
- const self = this;
104
- options = options || {};
86
+ // Settled by initialize(): resolves with the instance, rejects with the
87
+ // error initialize() rethrows. A module that did not call initialize() can
88
+ // still await it.
89
+ this._readyResolve = null;
90
+ this._readyReject = null;
91
+ this.ready = new Promise((resolve, reject) => {
92
+ this._readyResolve = resolve;
93
+ this._readyReject = reject;
94
+ });
105
95
 
106
- if (options.force) {
107
- self._allowQuit = true;
96
+ // A rejected boot nobody awaited must not surface as an unhandled
97
+ // rejection; a consumer awaiting `ready` still sees the error, because this
98
+ // catch hangs off a separate branch of the same promise.
99
+ this.ready.catch(() => {});
108
100
  }
109
101
 
110
- const electron = require('electron');
102
+ // Open the sign-in round trip in the user's default browser (lib/auth-flow.js).
103
+ // Resolves once launched; completion arrives via the auth/token deep-link route →
104
+ // auth.handleToken → the desktop:auth:sign-in-with-token broadcast.
105
+ openAuthFlow(options) {
106
+ return authFlow.open(options);
107
+ }
111
108
 
112
- // If updater downloaded a fresh build, install + relaunch via electron-updater
113
- // (which calls `app.quit()` internally with the right post-quit script). Falls
114
- // back to plain relaunch if updater hasn't downloaded anything.
115
- const updaterStatus = self.autoUpdater.getStatus();
116
- if (updaterStatus.code === 'downloaded') {
117
- return self.autoUpdater.installNow();
109
+ // Make an API request: `omega.request('/omega/user/token', { method: 'POST', body: {} })`,
110
+ // with @omega.js/client's options (`auth: false`, `output: 'complete'`, `wakeup: true`)
111
+ request(url, options) {
112
+ return this._request(url, options);
118
113
  }
119
114
 
120
- electron.app.relaunch();
121
- electron.app.quit();
122
- };
123
-
124
- Manager.prototype.initialize = async function (consumerConfig, options) {
125
- const self = this;
126
-
127
- // Accept either an already-RESOLVED config object, a string path to a consumer
128
- // project dir, or nothing. Default resolution order (when called with no arg):
129
- // 1. OMEGA_BUILD_JSON.config — injected at build time by the bundle task's esbuild `define`. This is
130
- // authoritative in packaged apps because config/omega.json5 is inside the asar —
131
- // not loadable from disk. It's the RESOLVED config (Manager.getConfig() output —
132
- // shared sections + targets.desktop overlaid) snapshotted at build time.
133
- // 2. <appRoot>/config/omega.json5 — resolved via @omega.js/config for dev mode where
134
- // @omega.js/desktop is loaded directly (unbundled). appRoot = the consumer project dir.
135
- if (typeof consumerConfig === 'string') {
136
- consumerConfig = loadResolvedConfig(consumerConfig);
137
- } else if (!consumerConfig) {
138
- // Try OMEGA_BUILD_JSON (set by DefinePlugin in packaged builds) first.
139
- if (typeof OMEGA_BUILD_JSON !== 'undefined' && OMEGA_BUILD_JSON?.config) {
140
- consumerConfig = OMEGA_BUILD_JSON.config;
141
- } else {
142
- const appRoot = require('./utils/app-root.js')();
143
- consumerConfig = loadResolvedConfig(appRoot);
115
+ /**
116
+ * Boot the main process in the fixed order (docs/desktop/boot-sequence.md), and
117
+ * settle `ready`.
118
+ * @param {object|string} [consumerConfig] - a RESOLVED config, a project dir to resolve one from, or nothing.
119
+ * @param {object} [options] - boot options (the test harness passes `skipWindowCreation`).
120
+ * @returns {Promise<Omega>} the instance.
121
+ */
122
+ async initialize(consumerConfig, options) {
123
+ try {
124
+ await this._boot(consumerConfig, options);
125
+ } catch (error) {
126
+ this._readyReject(error);
127
+ throw error;
144
128
  }
145
- }
146
129
 
147
- self.config = consumerConfig || {};
148
- self._options = options || {};
149
-
150
- // The environment's ONE input is the `OMEGA_ENVIRONMENT` variable
151
- // ([#817](https://github.com/Omega-JS-Stack/omega/issues/817)), and a lane
152
- // that named one is NEVER overridden here
153
- // ([#925](https://github.com/Omega-JS-Stack/omega/issues/925)). The baked word
154
- // is the FALLBACK, for the one case with no parent lane to inherit from: a
155
- // packaged app, where this is the input every later read answers (the
156
- // context-free ones, like test-stealth's, included). A dev boot inherits the
157
- // word from the gulp lane that spawned this electron, and the test lanes spawn
158
- // their child with `testing` while booting a PRODUCTION artifact, so writing
159
- // the baked word over theirs turned every isTesting() gate in a booted app
160
- // silently off.
161
- if (!process.env[ENVIRONMENT_VAR] && self.config.environment) {
162
- setEnvironment(self.config.environment);
163
- }
130
+ this._readyResolve(this);
164
131
 
165
- {
166
- const fs = require('fs');
167
- const _path = require('path');
168
- const _app = require('electron').app;
132
+ return this;
133
+ }
169
134
 
170
- const _productName = self.config.app?.productName || self.config.brand?.name;
171
- if (_productName) {
172
- _app.setName(_productName);
135
+ async _boot(consumerConfig, options) {
136
+ // Accept either an already-RESOLVED config object, a string path to a consumer
137
+ // project dir, or nothing. Default resolution order (when called with no arg):
138
+ // 1. OMEGA_BUILD_JSON.config: injected at build time by the bundle task's esbuild `define`. This is
139
+ // authoritative in packaged apps because config/omega.json5 is inside the asar and
140
+ // not loadable from disk. It's the RESOLVED config (build.getConfig() output:
141
+ // shared sections + targets.desktop overlaid) snapshotted at build time.
142
+ // 2. <appRoot>/config/omega.json5: resolved via @omega.js/config for dev mode where
143
+ // @omega.js/desktop is loaded directly (unbundled). appRoot = the consumer project dir.
144
+ if (typeof consumerConfig === 'string') {
145
+ consumerConfig = loadResolvedConfig(consumerConfig);
146
+ } else if (!consumerConfig) {
147
+ // Try OMEGA_BUILD_JSON (set by DefinePlugin in packaged builds) first.
148
+ if (typeof OMEGA_BUILD_JSON !== 'undefined' && OMEGA_BUILD_JSON?.config) {
149
+ consumerConfig = OMEGA_BUILD_JSON.config;
150
+ } else {
151
+ const appRoot = require('./utils/app-root.js')();
152
+ consumerConfig = loadResolvedConfig(appRoot);
153
+ }
173
154
  }
174
- const _logPath = _app.isPackaged
175
- ? _path.join(_app.getPath('logs'), 'runtime.log')
176
- : _path.join(process.cwd(), 'logs', 'runtime.log');
177
- try { fs.writeFileSync(_logPath, ''); } catch (e) {}
178
- }
179
155
 
180
- self.logger.log(`Initializing @omega.js/desktop (main)... pid=${process.pid} platform=${process.platform} arch=${process.arch} packaged=${require('electron').app.isPackaged} argv=${JSON.stringify(process.argv.slice(1))}`);
181
-
182
- try { if (require('electron').app.isPackaged) {
183
- const { execFile } = require('child_process');
184
- if (process.platform === 'darwin') {
185
- const _appIdx = process.execPath.indexOf('.app');
186
- if (_appIdx === -1) throw new Error('no .app in execPath');
187
- const _appPath = process.execPath.substring(0, _appIdx + 4);
188
- execFile('codesign', ['--verify', '--deep', '--strict', _appPath], (err) => {
189
- if (err) {
190
- self.logger.warn(`signing: NOT signed (${err.message.split('\n')[0]})`);
191
- } else {
192
- execFile('codesign', ['-dv', _appPath], { encoding: 'utf8' }, (e2, stdout, stderr) => {
193
- const authority = (stderr || '').match(/Authority=(.+)/);
194
- const adhoc = (stderr || '').includes('Signature=adhoc');
195
- const label = adhoc ? 'ad-hoc' : (authority ? authority[1] : 'unknown');
196
- self.logger.log(`signing: signed (${label})`);
197
- });
198
- }
199
- });
200
- } else if (process.platform === 'win32') {
201
- execFile('powershell', ['-NoProfile', '-Command', `(Get-AuthenticodeSignature '${process.execPath}').Status`], { encoding: 'utf8' }, (err, stdout) => {
202
- const status = (stdout || '').trim();
203
- if (err || status === 'NotSigned') {
204
- self.logger.warn(`signing: NOT signed`);
205
- } else {
206
- self.logger.log(`signing: signed (${status})`);
207
- }
208
- });
209
- } else {
210
- self.logger.log('signing: n/a (Linux — no OS-level code signing)');
156
+ this.config = consumerConfig || {};
157
+ this._options = options || {};
158
+
159
+ // The environment's ONE input is the `OMEGA_ENVIRONMENT` variable
160
+ // ([#817](https://github.com/Omega-JS-Stack/omega/issues/817)), and a lane
161
+ // that named one is NEVER overridden here
162
+ // ([#925](https://github.com/Omega-JS-Stack/omega/issues/925)). The baked word
163
+ // is the FALLBACK, for the one case with no parent lane to inherit from: a
164
+ // packaged app, where this is the input every later read answers (the
165
+ // context-free ones, like test-stealth's, included). A dev boot inherits the
166
+ // word from the gulp lane that spawned this electron, and the test lanes spawn
167
+ // their child with `testing` while booting a PRODUCTION artifact, so writing
168
+ // the baked word over theirs turned every isTesting() gate in a booted app
169
+ // silently off.
170
+ if (!process.env[ENVIRONMENT_VAR] && this.config.environment) {
171
+ setEnvironment(this.config.environment);
211
172
  }
212
- } } catch (e) { self.logger.warn(`signing: check failed (${e.message})`); }
213
-
214
- // Schema validation. Hard-fail boot if required fields are missing — same rules as
215
- // gulp/audit (single source of truth in @omega.js/config: shared schema + the desktop
216
- // target refinements). We do this before any lib initializes so a misconfigured app
217
- // fails loud + early instead of partway through boot with a confusing stack trace.
218
- {
219
- const { validateConfig, formatErrors } = require('./vendor/config/index.js');
220
- const { errors } = validateConfig(self.config, { target: 'desktop' });
221
- if (errors.length > 0) {
222
- throw new Error(`@omega.js/desktop: config validation failed — fix the following in config/omega.json5:\n${formatErrors(errors)}`);
173
+
174
+ {
175
+ const fs = require('fs');
176
+ const _path = require('path');
177
+ const _app = require('electron').app;
178
+
179
+ const _productName = this.config.app?.productName || this.config.brand?.name;
180
+ if (_productName) {
181
+ _app.setName(_productName);
182
+ }
183
+ const _logPath = _app.isPackaged
184
+ ? _path.join(_app.getPath('logs'), 'runtime.log')
185
+ : _path.join(process.cwd(), 'logs', 'runtime.log');
186
+ try { fs.writeFileSync(_logPath, ''); } catch (e) {}
223
187
  }
224
- }
225
188
 
226
- // electron is a peer dep — main process only (we're in main.js, always defined).
227
- const electron = require('electron');
228
- const app = electron.app;
229
-
230
- // Lifecycle event logging. These are the high-signal app-level events worth tracing
231
- // when something goes wrong — quit reasons, window-all-closed, will-finish-launching,
232
- // ready, render-process-gone, child-process-gone. All cheap to log; one line each.
233
- app.on('before-quit', () => {
234
- self._isQuitting = true;
235
- self.logger.log('app event: before-quit (entering quit sequence — close events bypass hide-on-close)');
236
- });
237
- app.on('will-quit', () => self.logger.log('app event: will-quit'));
238
- app.on('quit', (_e, exitCode) => self.logger.log(`app event: quit code=${exitCode}`));
239
- app.on('window-all-closed', () => self.logger.log('app event: window-all-closed'));
240
- app.on('render-process-gone', (_e, webContents, details) => self.logger.warn(`app event: render-process-gone reason=${details.reason} exitCode=${details.exitCode}`));
241
- app.on('child-process-gone', (_e, details) => self.logger.warn(`app event: child-process-gone type=${details.type} reason=${details.reason} exitCode=${details.exitCode}`));
242
- app.on('activate', () => self.logger.log('app event: activate (macOS — dock click or app re-launch)'));
243
- app.on('open-url', (_e, url) => self.logger.log(`app event: open-url url=${url}`));
244
- app.on('open-file', (_e, p) => self.logger.log(`app event: open-file path=${p}`));
245
-
246
- // Process-level signals that bypass Electron's app events. Catches uncaught exceptions
247
- // so we never crash silently — combined with electron-log file transport, this means
248
- // ANY unhandled throw lands in runtime.log rather than disappearing into stderr.
249
- process.on('uncaughtException', (e) => {
250
- // EPIPE = stdout/stderr pipe closed (CLI consumer hung up). Logging it would
251
- // write to console, which triggers another EPIPE, cascading into thousands of
252
- // identical log entries. Exit cleanly — standard Unix behavior.
253
- if (e?.code === 'EPIPE') {
254
- process.exit(0);
255
- return;
189
+ this.logger.log(`Initializing @omega.js/desktop (main)... pid=${process.pid} platform=${process.platform} arch=${process.arch} packaged=${require('electron').app.isPackaged} argv=${JSON.stringify(process.argv.slice(1))}`);
190
+
191
+ require('./utils/signing-status.js')(this.logger);
192
+
193
+ // Schema validation. Hard-fail boot if required fields are missing: same rules as
194
+ // gulp/audit (single source of truth in @omega.js/config: shared schema + the desktop
195
+ // target refinements). We do this before any lib initializes so a misconfigured app
196
+ // fails loud + early instead of partway through boot with a confusing stack trace.
197
+ {
198
+ const { validateConfig, formatErrors } = require('./vendor/config/index.js');
199
+ // A packaged app's config is baked, build facts included: validate it as decorated
200
+ const { errors } = validateConfig(this.config, { target: 'desktop', decorated: true });
201
+ if (errors.length > 0) {
202
+ throw new Error(`@omega.js/desktop: config validation failed. Fix the following in config/omega.json5:\n${formatErrors(errors)}`);
203
+ }
256
204
  }
257
- self.logger.error(`uncaughtException: ${e?.stack || e?.message || String(e)}`);
258
- });
259
- process.on('unhandledRejection', (reason) => {
260
- self.logger.error(`unhandledRejection: ${reason?.stack || reason?.message || String(reason)}`);
261
- });
262
- process.on('exit', (code) => {
263
- // electron-log's file transport flushes synchronously, so this last line lands.
264
- self.logger.log(`process exit code=${code}`);
265
- });
266
-
267
- // 1. Apply early startup-mode hide. For `mode: 'hidden'` we call app.dock.hide() *before*
268
- // anything else so macOS spends as little time animating the dock entry as possible.
269
- // In packaged builds the Info.plist's LSUIElement (injected by gulp/build-config when
270
- // startup.mode === 'hidden') prevents the bounce entirely.
271
- self.startup._manager = self;
272
- self.startup._electron = electron || null;
273
- self.startup.applyEarly();
274
-
275
- // 1a. Test stealth: suppress app-level activation on macOS. Launching a regular-
276
- // policy app activates it — menu bar + keyboard focus switch away from whatever
277
- // the developer is typing in — even though stealth windows surface via
278
- // showInactive() (see lib/window-manager.js). The accessory policy (the same
279
- // switch app.dock.hide() flips, and what LSUIElement bakes for packaged
280
- // hidden-mode apps) keeps the test process from ever activating; windows still
281
- // render normally. Must run before app ready — activation happens when the app
282
- // finishes launching. OMEGA_TEST_SHOW=1 restores normal activation along with
283
- // visible windows.
284
- if (process.platform === 'darwin' && require('./utils/test-stealth.js')(self)) {
285
- app.dock.hide();
286
- self.logger.log('test stealth: app activation suppressed (dock hidden / accessory policy) — launch will not steal focus');
287
- }
288
205
 
289
- // 1a-ii. Test stealth for EVERY BrowserWindow — including RAW ones created with
290
- // `new BrowserWindow()` that never pass through lib/window-manager (e.g.
291
- // a consumer's automation popup). Window-manager stealths only its own
292
- // named windows via _surface(); this hook closes the gap so no window
293
- // can flash or steal focus during a test run. The predicate is evaluated
294
- // PER WINDOW so OMEGA_TEST_SHOW=1 keeps working even when flipped mid-run
295
- // (the window-manager suite does exactly that).
296
- if (self.isTesting()) {
297
- app.on('browser-window-created', (_event, win) => {
298
- if (!require('./utils/test-stealth.js')(self)) return;
299
- require('./utils/stealth-window.js').applyStealth(win);
206
+ // electron is a peer dep, main process only (we're in main.js, always defined).
207
+ const electron = require('electron');
208
+ const app = electron.app;
209
+
210
+ // Lifecycle event logging. These are the high-signal app-level events worth tracing
211
+ // when something goes wrong: quit reasons, window-all-closed, will-finish-launching,
212
+ // ready, render-process-gone, child-process-gone. All cheap to log; one line each.
213
+ app.on('before-quit', () => {
214
+ this._isQuitting = true;
215
+ this.logger.log('app event: before-quit (entering quit sequence: close events bypass hide-on-close)');
300
216
  });
301
- // webContents.focus() is NOT covered by the window stealth above — it's a
302
- // different object whose focus() reaches the native window directly,
303
- // making the invisible window KEY: the test app grabs the keyboard from
304
- // whatever the developer is typing in (the accessory policy from 1a does
305
- // not prevent key-window steals, only launch activation). Consumers call
306
- // it legitimately (address-bar focus on tab select, overlay dismiss
307
- // hand-back), so no-op it per-contents under the same predicate —
308
- // synthetic input (executeJavaScript, sendInputEvent, CDP) targets a
309
- // specific webContents and never depends on OS key status.
310
- app.on('web-contents-created', (_event, wc) => {
311
- if (!require('./utils/test-stealth.js')(self)) return;
312
- wc.focus = () => {};
217
+ app.on('will-quit', () => this.logger.log('app event: will-quit'));
218
+ app.on('quit', (_e, exitCode) => this.logger.log(`app event: quit code=${exitCode}`));
219
+ app.on('window-all-closed', () => this.logger.log('app event: window-all-closed'));
220
+ app.on('render-process-gone', (_e, webContents, details) => this.logger.warn(`app event: render-process-gone reason=${details.reason} exitCode=${details.exitCode}`));
221
+ app.on('child-process-gone', (_e, details) => this.logger.warn(`app event: child-process-gone type=${details.type} reason=${details.reason} exitCode=${details.exitCode}`));
222
+ app.on('activate', () => this.logger.log('app event: activate (macOS: dock click or app re-launch)'));
223
+ app.on('open-url', (_e, url) => this.logger.log(`app event: open-url url=${url}`));
224
+ app.on('open-file', (_e, p) => this.logger.log(`app event: open-file path=${p}`));
225
+
226
+ // Process-level signals that bypass Electron's app events. Catches uncaught exceptions
227
+ // so we never crash silently: combined with electron-log file transport, this means
228
+ // ANY unhandled throw lands in runtime.log rather than disappearing into stderr.
229
+ process.on('uncaughtException', (e) => {
230
+ // EPIPE = stdout/stderr pipe closed (CLI consumer hung up). Logging it would
231
+ // write to console, which triggers another EPIPE, cascading into thousands of
232
+ // identical log entries. Exit cleanly: standard Unix behavior.
233
+ if (e?.code === 'EPIPE') {
234
+ process.exit(0);
235
+ return;
236
+ }
237
+ this.logger.error(`uncaughtException: ${e?.stack || e?.message || String(e)}`);
238
+ });
239
+ process.on('unhandledRejection', (reason) => {
240
+ this.logger.error(`unhandledRejection: ${reason?.stack || reason?.message || String(reason)}`);
241
+ });
242
+ process.on('exit', (code) => {
243
+ // electron-log's file transport flushes synchronously, so this last line lands.
244
+ this.logger.log(`process exit code=${code}`);
313
245
  });
314
- self.logger.log('test stealth: every BrowserWindow (raw ones included) surfaces invisible + unfocusable, webContents.focus() suppressed (OMEGA_TEST_SHOW=1 to watch)');
315
- }
316
246
 
317
- // 1b. Isolate the userData path per environment. MUST run before
318
- // storage.initialize() because electron-store reads `app.getPath('userData')`
319
- // at construction time.
320
- // production → <name> (untouched)
321
- // development → <name> (Development) (dev runs never touch installed-app data)
322
- // testing → <name> (Testing) (wiped at boot — every test run starts
323
- // from a clean slate; post-run state stays
324
- // on disk for inspection until the next run.
325
- // Set OMEGA_TEST_KEEP_USERDATA=1 to skip the wipe.)
326
- if (self.isTesting()) {
327
- const before = app.getPath('userData');
328
- const after = `${before} (Testing)`;
329
- const keep = process.env.OMEGA_TEST_KEEP_USERDATA === '1';
330
- if (!keep) {
331
- require('fs').rmSync(after, { recursive: true, force: true });
247
+ // 1. Apply early startup-mode hide. For `mode: 'hidden'` we call app.dock.hide() *before*
248
+ // anything else so macOS spends as little time animating the dock entry as possible.
249
+ // In packaged builds the Info.plist's LSUIElement (injected by gulp/build-config when
250
+ // startup.mode === 'hidden') prevents the bounce entirely.
251
+ this.startup._omega = this;
252
+ this.startup._electron = electron || null;
253
+ this.startup.applyEarly();
254
+
255
+ // 1a. Test stealth: suppress app-level activation on macOS. Launching a regular-
256
+ // policy app activates it (menu bar + keyboard focus switch away from whatever
257
+ // the developer is typing in) even though stealth windows surface via
258
+ // showInactive() (see lib/window-manager.js). The accessory policy (the same
259
+ // switch app.dock.hide() flips, and what LSUIElement bakes for packaged
260
+ // hidden-mode apps) keeps the test process from ever activating; windows still
261
+ // render normally. Must run before app ready: activation happens when the app
262
+ // finishes launching. OMEGA_TEST_SHOW=1 restores normal activation along with
263
+ // visible windows.
264
+ if (process.platform === 'darwin' && require('./utils/test-stealth.js')(this)) {
265
+ app.dock.hide();
266
+ this.logger.log('test stealth: app activation suppressed (dock hidden / accessory policy), launch will not steal focus');
332
267
  }
333
- app.setPath('userData', after);
334
- self.logger.log(`userData path: ${before} -> ${after} (testing mode, ${keep ? 'kept' : 'wiped at boot'})`);
335
- } else if (!self.isProduction()) {
336
- const before = app.getPath('userData');
337
- const after = `${before} (Development)`;
338
- app.setPath('userData', after);
339
- self.logger.log(`userData path: ${before} -> ${after} (dev mode)`);
340
- } else {
341
- self.logger.log(`userData path: ${app.getPath('userData')} (production)`);
342
- }
343
268
 
344
- // 1c. Set the global user agent fallback. Default template applied to every @omega.js/desktop app so
345
- // web requests (BrowserWindow loads, fetch, electron-updater downloads) carry a
346
- // branded UA — Mozilla parsers see a normal Chrome UA + we tag with the app's
347
- // name/version for our own server-side telemetry. Legacy @omega.js/desktop did the
348
- // same; merge tags now use node-powertools.template (single-curly syntax).
349
- {
350
- const { template } = require('node-powertools');
351
- const ctx = {
352
- brand: {
353
- name: self.config.brand.name,
354
- id: self.config.brand.id,
355
- },
356
- app: {
357
- version: self.getVersion(),
358
- },
359
- chrome: process.versions.chrome,
360
- electron: process.versions.electron,
361
- node: process.versions.node,
362
- platform: process.platform,
363
- arch: process.arch,
364
- };
365
- const templates = {
366
- darwin: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) {brand.name}/{app.version} Chrome/{chrome} Safari/537.36',
367
- win32: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) {brand.name}/{app.version} Chrome/{chrome} Safari/537.36',
368
- linux: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) {brand.name}/{app.version} Chrome/{chrome} Safari/537.36',
369
- };
370
- const tmpl = templates[process.platform] || templates.linux;
371
- const ua = template(tmpl, ctx);
372
- app.userAgentFallback = ua;
373
- self.logger.log(`userAgent: ${ua}`);
374
- }
269
+ // 1a-ii. Test stealth for EVERY BrowserWindow, including RAW ones created with
270
+ // `new BrowserWindow()` that never pass through lib/window-manager (e.g.
271
+ // a consumer's automation popup). Window-manager stealths only its own
272
+ // named windows via _surface(); this hook closes the gap so no window
273
+ // can flash or steal focus during a test run. The predicate is evaluated
274
+ // PER WINDOW so OMEGA_TEST_SHOW=1 keeps working even when flipped mid-run
275
+ // (the window-manager suite does exactly that).
276
+ if (this.isTesting()) {
277
+ app.on('browser-window-created', (_event, win) => {
278
+ if (!require('./utils/test-stealth.js')(this)) return;
279
+ require('./utils/stealth-window.js').applyStealth(win);
280
+ });
281
+ // webContents.focus() is NOT covered by the window stealth above: it's a
282
+ // different object whose focus() reaches the native window directly,
283
+ // making the invisible window KEY, so the test app grabs the keyboard from
284
+ // whatever the developer is typing in (the accessory policy from 1a does
285
+ // not prevent key-window steals, only launch activation). Consumers call
286
+ // it legitimately (address-bar focus on tab select, overlay dismiss
287
+ // hand-back), so no-op it per-contents under the same predicate.
288
+ // Synthetic input (executeJavaScript, sendInputEvent, CDP) targets a
289
+ // specific webContents and never depends on OS key status.
290
+ app.on('web-contents-created', (_event, wc) => {
291
+ if (!require('./utils/test-stealth.js')(this)) return;
292
+ wc.focus = () => {};
293
+ });
294
+ this.logger.log('test stealth: every BrowserWindow (raw ones included) surfaces invisible + unfocusable, webContents.focus() suppressed (OMEGA_TEST_SHOW=1 to watch)');
295
+ }
375
296
 
376
- // 2. IPC bus online first — storage and other libs register handlers on it
377
- self.ipc.initialize(self);
297
+ // 1b. Isolate the userData path per environment. MUST run before
298
+ // storage.initialize() because electron-store reads `app.getPath('userData')`
299
+ // at construction time.
300
+ // production → <name> (untouched)
301
+ // development → <name> (Development) (dev runs never touch installed-app data)
302
+ // testing → <name> (Testing) (wiped at boot: every test run starts
303
+ // from a clean slate; post-run state stays
304
+ // on disk for inspection until the next run.
305
+ // Set OMEGA_TEST_KEEP_USERDATA=1 to skip the wipe.)
306
+ if (this.isTesting()) {
307
+ const before = app.getPath('userData');
308
+ const after = `${before} (Testing)`;
309
+ const keep = process.env.OMEGA_TEST_KEEP_USERDATA === '1';
310
+ if (!keep) {
311
+ require('fs').rmSync(after, { recursive: true, force: true });
312
+ }
313
+ app.setPath('userData', after);
314
+ this.logger.log(`userData path: ${before} -> ${after} (testing mode, ${keep ? 'kept' : 'wiped at boot'})`);
315
+ } else if (!this.isProduction()) {
316
+ const before = app.getPath('userData');
317
+ const after = `${before} (Development)`;
318
+ app.setPath('userData', after);
319
+ this.logger.log(`userData path: ${before} -> ${after} (dev mode)`);
320
+ } else {
321
+ this.logger.log(`userData path: ${app.getPath('userData')} (production)`);
322
+ }
378
323
 
379
- // 3. Storage (precedes sentry + auth so opt-out + persisted session are honored)
380
- await self.storage.initialize(self);
324
+ // 1c. Set the global user agent fallback (utils/user-agent.js): a branded UA on
325
+ // every BrowserWindow load, fetch and electron-updater download.
326
+ require('./utils/user-agent.js')(this);
381
327
 
382
- // 3b. Theme — sets nativeTheme.themeSource from the persisted override / config
383
- // default, so every renderer (and native UI) resolves the right appearance
384
- // from its very first paint. Needs storage (override) + ipc (handlers) only.
385
- self.theme.initialize(self);
328
+ // 2. IPC bus online first: storage and other libs register handlers on it
329
+ this.ipc.initialize(this);
386
330
 
387
- // 3c. FontAwesome — serves the bundled icon SVGs to renderers over IPC
388
- // (desktop:fontawesome:get). Needs ipc only.
389
- self.fontawesome.initialize(self);
331
+ // 3. Storage (precedes sentry + auth so opt-out + persisted session are honored)
332
+ await this.storage.initialize(this);
390
333
 
391
- // 4. Sentry (earliest catchable global handler)
392
- self.sentry.initialize(self);
334
+ // 3b. Theme: sets nativeTheme.themeSource from the persisted override / config
335
+ // default, so every renderer (and native UI) resolves the right appearance
336
+ // from its very first paint. Needs storage (override) + ipc (handlers) only.
337
+ this.theme.initialize(this);
393
338
 
394
- // 5. Protocol (single-instance lock + custom URL scheme)
395
- self.protocol.initialize(self);
339
+ // 3c. FontAwesome: serves the bundled icon SVGs to renderers over IPC
340
+ // (desktop:fontawesome:get). Needs ipc only.
341
+ this.fontawesome.initialize(this);
396
342
 
397
- if (!self.protocol.hasSingleInstanceLock()) {
398
- // Quit the duplicate AND halt the boot: returning normally would resolve
399
- // initialize(), letting the consumer's .then() run its whole main.js (servers,
400
- // shared files, IPC) against a dying Electron — the duplicate must go quietly.
401
- // The returned promise intentionally never settles; the process exits first.
402
- self.logger.warn('Single-instance lock lost. Quitting this duplicate instance.');
403
- app.quit();
404
- return new Promise(() => {});
405
- }
343
+ // 4. Sentry (earliest catchable global handler)
344
+ this.sentry.initialize(this);
406
345
 
407
- // 6. Deep links (parse cold-start argv, install second-instance handler)
408
- self.deepLink.initialize(self);
409
-
410
- // 6b. Auth flow (the getAuthUrl sign-in round trip — external browser always;
411
- // dev/test return via a loopback listener since the scheme isn't OS-registered)
412
- self.authFlow.initialize(self);
413
-
414
- // 7. App state (first-launch / crash / startup-context flags)
415
- await self.appState.initialize(self);
416
-
417
- // 7b. Runtime context — session id, deviceId, OS info, async geolocation fetch.
418
- // Must run AFTER storage (writes deviceId + cached geolocation to storage) and
419
- // BEFORE analytics (which reads context.session.deviceId). Async but the geolocation
420
- // fetch is fire-and-forget so this returns quickly.
421
- await self.context.initialize(self);
422
-
423
- // 7c. Usage tracking — opens / hours-total / hours-this-session. Reads/writes
424
- // storage.usage and registers a before-quit handler to record session duration.
425
- self.usage.initialize(self);
426
-
427
- // 8. Wait for app readiness before any UI
428
- await app.whenReady();
429
-
430
- // 9. Auto-updater (queues check, never blocks UI)
431
- self.autoUpdater.initialize(self);
432
-
433
- // 10. Tray + menu + context menu
434
- self.tray.initialize(self);
435
- self.menu.initialize(self);
436
- self.contextMenu.initialize(self);
437
-
438
- // 11. Open-at-login + hide-on-startup state sync
439
- self.startup.initialize(self);
440
-
441
- // 12. Web-manager bridge (main-side Firebase Auth source of truth, IPC handlers for renderers)
442
- await self.omega.initialize(self);
443
-
444
- // 12b. Remote config — fetches `<brand.url>/data/resources/main.json` for hot
445
- // config flips (force-update gate, default user agents, etc.). Polls hourly.
446
- // Wired AFTER auto-updater so it can inherit feedCheckIntervalMs from there.
447
- self.remoteConfig.initialize(self);
448
-
449
- // 12c. Remote scripts — fetches `<brand.url>/data/scripts/main.json` for
450
- // emergency hotfixes (force-update, storage patches, etc.) when the normal
451
- // update pipeline is broken. Same polling cadence as remote-config.
452
- self.remoteScripts.initialize(self);
453
-
454
- // 12d. Analytics — GA4 via Measurement Protocol. Wired AFTER client-bridge
455
- // so it can subscribe to onAuthChange and flip user_id automatically.
456
- self.analytics.initialize(self);
457
-
458
- // 12e. Restart Manager — external guardian app that relaunches us if we crash.
459
- // Registers over RM's loopback HTTP protocol (runtime.json advertises the port),
460
- // heartbeats every 60s, deregisters on graceful quit, and silently installs RM
461
- // when missing (mac zip / win silent NSIS / linux AppImage — RM then self-updates
462
- // via its own @omega.js/desktop autoUpdater). Skips itself when this app IS restart-manager, in
463
- // dev (unless OMEGA_RESTART_MANAGER_DEV=1), or when restartManager.enabled=false.
464
- // See docs/restart-manager.md.
465
- self.restartManager.initialize(self);
466
-
467
- // 13. Initialize the windows lib — registers app-level handlers (window-all-closed, etc.)
468
- // but does NOT create any windows. Consumers are responsible for calling
469
- // `manager.windows.create('main')` (or any other named window) from their main.js
470
- // when they want to surface UI. This makes hidden / agent-app patterns trivial:
471
- // just don't call create() until something (tray click, deep link, IPC) warrants it.
472
- self.windows.initialize(self);
473
-
474
- self._initialized = true;
475
-
476
- // 14. Release deep-link dispatch — cold-start URLs (and any early open-url)
477
- // were queued so handlers like auth/token never fire before client-bridge
478
- // has Firebase up. Everything they touch exists now.
479
- self.deepLink.markManagerReady();
480
-
481
- self.logger.log('@omega.js/desktop (main) initialized.');
482
-
483
- // Boot test harness — runs against the live manager AFTER all libs are up. Test runner
484
- // sets OMEGA_TEST_BOOT=1 + OMEGA_TEST_BOOT_HARNESS=<absolute path> + OMEGA_TEST_BOOT_SPEC=<path>
485
- // before spawning electron. The harness emits __EM_TEST__ JSON lines on stdout (parsed
486
- // by runners/boot.js) then app.exit()s.
487
- //
488
- // We use a runtime env-var path (not a static `require('./test/harness/...')`) because
489
- // @omega.js/desktop is bundled into the consumer's bundle, and a static require would either get
490
- // inlined (bundling test code into production) or dead-code-eliminated. An env-var
491
- // path stays external and can only resolve when the runner sets it.
492
- if (process.env.OMEGA_TEST_BOOT === '1') {
493
- global.__em_manager = self;
494
- const harnessPath = process.env.OMEGA_TEST_BOOT_HARNESS;
495
- if (harnessPath) {
496
- // Defer the harness so the consumer's `manager.initialize().then(() => { ... })`
497
- // callback gets a chance to run first. @omega.js/desktop doesn't auto-create any windows; the
498
- // consumer's main.js does it inside .then(). If we ran the harness synchronously
499
- // here, that callback wouldn't have fired yet and `manager.windows.get('main')`
500
- // would be null. setImmediate flushes the microtask queue (where promise callbacks
501
- // live) before our boot harness inspects state.
502
- setImmediate(() => {
503
- try {
504
- // A variable specifier, so the bundler leaves the call alone and it
505
- // resolves at RUNTIME against the path the runner set — which is the
506
- // whole point (see the env-var note above).
507
- const harness = require(harnessPath);
508
- harness.run(self);
509
- } catch (e) {
510
- const { app } = require('electron');
511
- process.stdout.write(`__EM_TEST__${JSON.stringify({ event: 'fatal', message: `boot harness failed to load: ${e.message}` })}\n`);
512
- app.exit(1);
513
- }
514
- });
515
- } else {
516
- const { app } = require('electron');
517
- process.stdout.write(`__EM_TEST__${JSON.stringify({ event: 'fatal', message: 'OMEGA_TEST_BOOT=1 but OMEGA_TEST_BOOT_HARNESS not set' })}\n`);
518
- app.exit(1);
346
+ // 5. Protocol (single-instance lock + custom URL scheme)
347
+ this.protocol.initialize(this);
348
+
349
+ if (!this.protocol.hasSingleInstanceLock()) {
350
+ // Quit the duplicate AND halt the boot: returning normally would resolve
351
+ // initialize(), letting the consumer's .then() run its whole main.js (servers,
352
+ // shared files, IPC) against a dying Electron. The duplicate must go quietly.
353
+ // The returned promise intentionally never settles; the process exits first.
354
+ this.logger.warn('Single-instance lock lost. Quitting this duplicate instance.');
355
+ app.quit();
356
+ return new Promise(() => {});
519
357
  }
358
+
359
+ // 6. Deep links (parse cold-start argv, install second-instance handler)
360
+ this.deepLink.initialize(this);
361
+
362
+ // 6b. Auth flow (the getAuthUrl sign-in round trip: external browser always;
363
+ // dev/test return via a loopback listener since the scheme isn't OS-registered)
364
+ this.authFlow.initialize(this);
365
+
366
+ // 7. App state (first-launch / crash / startup-context flags)
367
+ await this.appState.initialize(this);
368
+
369
+ // 7b. Runtime context: session id, deviceId, OS info, async geolocation fetch.
370
+ // Must run AFTER storage (writes deviceId + cached geolocation to storage) and
371
+ // BEFORE analytics (which reads context.session.deviceId). Async but the geolocation
372
+ // fetch is fire-and-forget so this returns quickly.
373
+ await this.context.initialize(this);
374
+
375
+ // 7c. Usage tracking: opens / hours-total / hours-this-session. Reads/writes
376
+ // storage.usage and registers a before-quit handler to record session duration.
377
+ this.usage.initialize(this);
378
+
379
+ // 8. Wait for app readiness before any UI
380
+ await app.whenReady();
381
+
382
+ // 9. Auto-updater (queues check, never blocks UI)
383
+ this.autoUpdater.initialize(this);
384
+
385
+ // 10. Tray + menu + context menu
386
+ this.tray.initialize(this);
387
+ this.menu.initialize(this);
388
+ this.contextMenu.initialize(this);
389
+
390
+ // 11. Open-at-login + hide-on-startup state sync
391
+ this.startup.initialize(this);
392
+
393
+ // 12. Auth (main-side Firebase Auth source of truth, IPC handlers for renderers)
394
+ await this.auth.initialize(this);
395
+
396
+ // 12b. Remote config: fetches `<brand.url>/data/resources/main.json` for hot
397
+ // config flips (force-update gate, default user agents, etc.). Polls hourly.
398
+ // Wired AFTER auto-updater so it can inherit feedCheckIntervalMs from there.
399
+ this.remoteConfig.initialize(this);
400
+
401
+ // 12c. Remote scripts: fetches `<brand.url>/data/scripts/main.json` for
402
+ // emergency hotfixes (force-update, storage patches, etc.) when the normal
403
+ // update pipeline is broken. Same polling cadence as remote-config.
404
+ this.remoteScripts.initialize(this);
405
+
406
+ // 12d. Analytics: GA4 via Measurement Protocol. Wired AFTER auth so it can
407
+ // listen to it and flip user_id automatically.
408
+ this.analytics.initialize(this);
409
+
410
+ // 12e. Restart Manager: external guardian app that relaunches us if we crash.
411
+ // Registers over RM's loopback HTTP protocol (runtime.json advertises the port),
412
+ // heartbeats every 60s, deregisters on graceful quit, and silently installs RM
413
+ // when missing (mac zip / win silent NSIS / linux AppImage; RM then self-updates
414
+ // via its own @omega.js/desktop autoUpdater). Skips itself when this app IS restart-manager, in
415
+ // dev (unless OMEGA_RESTART_MANAGER_DEV=1), or when restartManager.enabled=false.
416
+ // See docs/desktop/restart-manager.md.
417
+ this.restartManager.initialize(this);
418
+
419
+ // 13. Initialize the windows lib: registers app-level handlers (window-all-closed, etc.)
420
+ // but does NOT create any windows. Consumers are responsible for calling
421
+ // `omega.windows.create('main')` (or any other named window) from their main.js
422
+ // when they want to surface UI. This makes hidden / agent-app patterns trivial:
423
+ // just don't call create() until something (tray click, deep link, IPC) warrants it.
424
+ this.windows.initialize(this);
425
+
426
+ this._initialized = true;
427
+
428
+ // 14. Release deep-link dispatch: cold-start URLs (and any early open-url)
429
+ // were queued so handlers like auth/token never fire before auth
430
+ // has Firebase up. Everything they touch exists now.
431
+ this.deepLink.markOmegaReady();
432
+
433
+ this.logger.log('@omega.js/desktop (main) initialized.');
434
+
435
+ // Boot test harness (utils/boot-harness.js): runs against the live instance AFTER
436
+ // all libs are up, only when the boot test lane spawned this process.
437
+ require('./utils/boot-harness.js')(this);
438
+ }
439
+
440
+ // Require: lets consumer main-process code load @omega.js/desktop's bundled dependencies at runtime
441
+ // (e.g. `omega.require('fs-jetpack')`). Resolves from @omega.js/desktop's module context, not the
442
+ // consumer's. Mirrors @omega.js/backend's omega.require(). For build-time resolution, the
443
+ // bundle task's framework-deps resolve hook handles this automatically.
444
+ require(name) {
445
+ return require(name);
520
446
  }
521
447
 
522
- return self;
523
- };
448
+ static require(name) {
449
+ return require(name);
450
+ }
451
+ }
452
+
453
+ // The environment and URL helpers, and quit/relaunch: methods on the instance,
454
+ // each concern in its own lib file
455
+ Object.assign(Omega.prototype, environment, lifecycle);
524
456
 
525
457
  function loadResolvedConfig(projectDir) {
526
- // @omega.js/config is vendored into dist (and bundled from there) — it
458
+ // @omega.js/config is vendored into dist (and bundled from there): it
527
459
  // finds config/omega.json5 under the project dir and resolves the desktop target
528
460
  // (shared sections + targets.desktop overlaid, brand-monorepo walk-up included).
529
461
  const { hasOmegaConfig, loadConfig } = require('./vendor/config/index.js');
@@ -535,24 +467,8 @@ function loadResolvedConfig(projectDir) {
535
467
  return loadConfig(projectDir, 'desktop').config;
536
468
  }
537
469
 
538
- // Cross-context helpers (isDevelopment/isProduction/isTesting + getFunctionsUrl/getApiUrl
539
- // + getEnvironment) live in src/utils/mode-helpers.js + src/utils/url-helpers.js +
540
- // src/build.js. All four Manager constructors mix them in via their respective
541
- // `attachTo(Manager)` calls — see the bottom of this file.
542
-
543
- // Require — lets consumer main-process code load @omega.js/desktop's bundled dependencies at runtime
544
- // (e.g. `manager.require('fs-jetpack')`). Resolves from @omega.js/desktop's module context, not the
545
- // consumer's. Mirrors @omega.js/backend's Manager.require(). For build-time resolution, the
546
- // bundle task's framework-deps resolve hook handles this automatically.
547
- Manager.prototype.require = function (name) {
548
- return require(name);
549
- };
550
- Manager.require = function (name) {
551
- return require(name);
552
- };
553
-
554
- // Mix in shared cross-context helpers — same code path used in renderer, preload, build.
555
- require('./utils/mode-helpers.js').attachTo(Manager);
556
- require('./utils/url-helpers.js').attachTo(Manager);
557
-
558
- module.exports = Manager;
470
+ // The ONE instance, initialized by the consumer's src/main.js
471
+ const omega = new Omega();
472
+
473
+ module.exports = omega;
474
+ module.exports.Omega = Omega;