@omega.js/desktop 0.52.0 → 0.53.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 (273) hide show
  1. package/README.md +17 -12
  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.js +1 -1
  26. package/dist/commands/build.js +2 -2
  27. package/dist/commands/cdp/capture.js +2 -2
  28. package/dist/commands/cdp/quit.js +2 -2
  29. package/dist/commands/cdp/relaunch.js +2 -2
  30. package/dist/commands/cdp/theme.js +1 -1
  31. package/dist/commands/clean.js +2 -2
  32. package/dist/commands/deploy.js +4 -4
  33. package/dist/commands/finalize-release.js +4 -4
  34. package/dist/commands/install.js +3 -3
  35. package/dist/commands/launch.js +2 -2
  36. package/dist/commands/lib/deploy-precheck.js +3 -3
  37. package/dist/commands/lib/ensure-target.js +6 -6
  38. package/dist/commands/package.js +2 -2
  39. package/dist/commands/publish.js +2 -2
  40. package/dist/commands/release.js +3 -3
  41. package/dist/commands/runner.js +11 -11
  42. package/dist/commands/sign-windows.js +5 -5
  43. package/dist/commands/test.js +4 -4
  44. package/dist/commands/update.js +2 -2
  45. package/dist/commands/validate-certs.js +4 -4
  46. package/dist/commands/version.js +3 -3
  47. package/dist/defaults/AGENTS.md +17 -8
  48. package/dist/defaults/config/omega.json5 +7 -7
  49. package/dist/defaults/hooks/build/post.js +1 -1
  50. package/dist/defaults/hooks/build/pre.js +1 -1
  51. package/dist/defaults/hooks/deploy/pre.js +1 -1
  52. package/dist/defaults/hooks/release/post.js +1 -1
  53. package/dist/defaults/hooks/release/pre.js +1 -1
  54. package/dist/defaults/src/assets/js/components/about/index.js +3 -5
  55. package/dist/defaults/src/assets/js/components/main/index.js +3 -5
  56. package/dist/defaults/src/assets/js/components/settings/index.js +3 -5
  57. package/dist/defaults/src/integrations/context-menu/index.js +2 -2
  58. package/dist/defaults/src/integrations/menu/index.js +3 -3
  59. package/dist/defaults/src/integrations/tray/index.js +3 -3
  60. package/dist/defaults/src/main.js +3 -5
  61. package/dist/defaults/src/preload.js +3 -5
  62. package/dist/defaults/test/README.md +2 -2
  63. package/dist/gulp/main.js +9 -10
  64. package/dist/gulp/tasks/audit.js +7 -7
  65. package/dist/gulp/tasks/build-config.js +8 -8
  66. package/dist/gulp/tasks/bundle.js +16 -16
  67. package/dist/gulp/tasks/defaults.js +3 -3
  68. package/dist/gulp/tasks/distribute.js +2 -2
  69. package/dist/gulp/tasks/html.js +9 -9
  70. package/dist/gulp/tasks/package-quick.js +3 -3
  71. package/dist/gulp/tasks/package.js +3 -3
  72. package/dist/gulp/tasks/release.js +3 -3
  73. package/dist/gulp/tasks/sass.js +6 -6
  74. package/dist/gulp/tasks/serve.js +4 -4
  75. package/dist/hooks/notarize-artifacts.js +1 -1
  76. package/dist/hooks/notarize.js +1 -1
  77. package/dist/index.js +5 -8
  78. package/dist/lib/_environment-mixin.js +50 -0
  79. package/dist/lib/_lifecycle-mixin.js +45 -0
  80. package/dist/lib/analytics.js +33 -35
  81. package/dist/lib/app-state.js +14 -14
  82. package/dist/lib/auth-flow.js +18 -18
  83. package/dist/lib/auth-persistence.js +12 -12
  84. package/dist/lib/auth.js +421 -0
  85. package/dist/lib/auto-updater.js +49 -49
  86. package/dist/lib/context-menu.js +13 -13
  87. package/dist/lib/context.js +19 -19
  88. package/dist/lib/deep-link.js +34 -34
  89. package/dist/lib/fontawesome.js +5 -5
  90. package/dist/lib/ipc.js +4 -4
  91. package/dist/lib/menu.js +25 -25
  92. package/dist/lib/protocol.js +5 -5
  93. package/dist/lib/remote-config.js +22 -22
  94. package/dist/lib/remote-scripts.js +21 -21
  95. package/dist/lib/restart-manager/index.js +28 -28
  96. package/dist/lib/sign-helpers/exec-with-limit.js +1 -1
  97. package/dist/lib/sign-helpers/sign-events.js +1 -1
  98. package/dist/lib/startup.js +18 -13
  99. package/dist/lib/storage.js +10 -10
  100. package/dist/lib/templating.js +16 -16
  101. package/dist/lib/theme.js +10 -10
  102. package/dist/lib/tray.js +27 -27
  103. package/dist/lib/usage.js +11 -11
  104. package/dist/lib/window-manager.js +26 -26
  105. package/dist/main.js +398 -483
  106. package/dist/preload.js +236 -176
  107. package/dist/renderer.js +417 -391
  108. package/dist/test/fixtures/consumer-app/config/omega.json5 +1 -1
  109. package/dist/test/fixtures/consumer-app/src/assets/js/components/main/index.js +8 -11
  110. package/dist/test/fixtures/consumer-app/src/main.js +5 -7
  111. package/dist/test/fixtures/consumer-app/src/preload.js +2 -2
  112. package/dist/test/harness/boot-entry.js +22 -20
  113. package/dist/test/harness/main-entry.js +31 -30
  114. package/dist/test/harness/renderer-entry.js +5 -5
  115. package/dist/test/harness/renderer-preload.js +137 -141
  116. package/dist/test/index.js +10 -10
  117. package/dist/test/runner.js +2 -2
  118. package/dist/test/runners/boot.js +7 -6
  119. package/dist/test/runners/electron.js +3 -2
  120. package/dist/test/runners/render-event.js +2 -2
  121. package/dist/test/suites/boot/consumer-app-boots.test.js +57 -26
  122. package/dist/test/suites/boot/restart-manager.test.js +2 -2
  123. package/dist/test/suites/boot/storage-bundled.test.js +5 -5
  124. package/dist/test/suites/boot/theme.test.js +13 -13
  125. package/dist/test/suites/build/audit.test.js +1 -1
  126. package/dist/test/suites/build/auth-persistence-resolve.test.js +6 -6
  127. package/dist/test/suites/build/boot-fixture.test.js +2 -2
  128. package/dist/test/suites/build/boot-runner-timeout.test.js +6 -5
  129. package/dist/test/suites/build/brand-scss.test.js +1 -1
  130. package/dist/test/suites/build/build-json-bake.test.js +1 -1
  131. package/dist/test/suites/build/cli.test.js +2 -2
  132. package/dist/test/suites/build/config-schema.test.js +5 -5
  133. package/dist/test/suites/build/defaults-scaffold.test.js +2 -2
  134. package/dist/test/suites/build/deploy-hook.test.js +3 -3
  135. package/dist/test/suites/build/ensure-target.test.js +2 -2
  136. package/dist/test/suites/build/env-delivery.test.js +2 -2
  137. package/dist/test/suites/build/esm-only-dependency.test.js +2 -2
  138. package/dist/test/suites/build/exports.test.js +9 -8
  139. package/dist/test/suites/build/get-config.test.js +4 -4
  140. package/dist/test/suites/build/manifest-deps.test.js +1 -1
  141. package/dist/test/suites/build/merge-line-files.test.js +1 -1
  142. package/dist/test/suites/build/omega-shell.test.js +34 -2
  143. package/dist/test/suites/build/omega.test.js +350 -0
  144. package/dist/test/suites/build/renderer-auth-bridge.test.js +211 -78
  145. package/dist/test/suites/build/runner.test.js +2 -2
  146. package/dist/test/suites/build/sentry.test.js +2 -2
  147. package/dist/test/suites/build/sign-windows-e2e.test.js +2 -2
  148. package/dist/test/suites/build/templating.test.js +3 -3
  149. package/dist/test/suites/build/test-stealth.test.js +7 -9
  150. package/dist/test/suites/build/url-helpers.test.js +55 -56
  151. package/dist/test/suites/build/validate-config.test.js +2 -2
  152. package/dist/test/suites/build/wave5-pins.test.js +2 -2
  153. package/dist/test/suites/main/analytics.test.js +59 -59
  154. package/dist/test/suites/main/app-state.test.js +66 -66
  155. package/dist/test/suites/main/auth-flow.test.js +41 -41
  156. package/dist/test/suites/main/auth-persistence.test.js +54 -43
  157. package/dist/test/suites/main/{client-bridge.integration.test.js → auth.integration.test.js} +10 -9
  158. package/dist/test/suites/main/auth.test.js +336 -0
  159. package/dist/test/suites/main/auto-updater.test.js +134 -134
  160. package/dist/test/suites/main/boot-sequence.test.js +37 -49
  161. package/dist/test/suites/main/context-menu.test.js +51 -50
  162. package/dist/test/suites/main/context.test.js +25 -25
  163. package/dist/test/suites/main/deep-link.test.js +74 -74
  164. package/dist/test/suites/main/fontawesome.test.js +27 -27
  165. package/dist/test/suites/main/ipc.test.js +35 -35
  166. package/dist/test/suites/main/menu.test.js +101 -100
  167. package/dist/test/suites/main/protocol.test.js +19 -19
  168. package/dist/test/suites/main/remote-config.test.js +63 -63
  169. package/dist/test/suites/main/remote-scripts.test.js +103 -103
  170. package/dist/test/suites/main/request.test.js +71 -0
  171. package/dist/test/suites/main/restart-manager.test.js +33 -33
  172. package/dist/test/suites/main/startup-paths-and-ua.test.js +4 -4
  173. package/dist/test/suites/main/startup.test.js +26 -26
  174. package/dist/test/suites/main/stealth-window.test.js +1 -1
  175. package/dist/test/suites/main/storage.test.js +25 -25
  176. package/dist/test/suites/main/theme.test.js +34 -34
  177. package/dist/test/suites/main/tray.test.js +79 -79
  178. package/dist/test/suites/main/url-helpers.test.js +133 -133
  179. package/dist/test/suites/main/usage.test.js +25 -25
  180. package/dist/test/suites/main/window-bounds.test.js +27 -27
  181. package/dist/test/suites/main/window-manager.test.js +44 -44
  182. package/dist/test/suites/renderer/analytics-bridge.test.js +5 -5
  183. package/dist/test/suites/renderer/cross-context-helpers.test.js +37 -31
  184. package/dist/test/suites/renderer/round-trip.test.js +3 -3
  185. package/dist/test/suites/renderer/tooltips.test.js +15 -15
  186. package/dist/test/suites/renderer/{window-em-surface.test.js → window-desktop-surface.test.js} +27 -7
  187. package/dist/test/utils/extended-mode-warning.js +1 -1
  188. package/dist/utils/boot-harness.js +56 -0
  189. package/dist/utils/mode-helpers.js +2 -15
  190. package/dist/utils/ship-keys.js +3 -3
  191. package/dist/utils/signing-status.js +51 -0
  192. package/dist/utils/test-events.js +7 -0
  193. package/dist/utils/test-stealth.js +6 -6
  194. package/dist/utils/url-helpers.js +52 -42
  195. package/dist/utils/user-agent.js +44 -0
  196. package/dist/vendor/account/engine.js +3 -3
  197. package/dist/vendor/account/index.js +14 -45
  198. package/dist/vendor/account/resolve-account.js +44 -0
  199. package/dist/vendor/account/schema.js +1 -1
  200. package/dist/vendor/account/user.js +99 -0
  201. package/dist/vendor/config/client-config.js +1 -1
  202. package/dist/vendor/config/environment.js +11 -30
  203. package/dist/vendor/config/index.js +8 -11
  204. package/dist/vendor/config/load.js +1 -2
  205. package/dist/vendor/config/platforms.js +1 -1
  206. package/dist/vendor/config/retired-keys.js +2 -2
  207. package/dist/vendor/config/schema.js +5 -8
  208. package/dist/vendor/config/site-global.js +2 -3
  209. package/dist/vendor/config/validate.js +1 -2
  210. package/dist/vendor/config/winback.js +1 -1
  211. package/dist/vendor/devkit/actions-secrets.js +1 -1
  212. package/dist/vendor/devkit/attach-log-file.js +1 -1
  213. package/dist/vendor/devkit/build-json.js +1 -1
  214. package/dist/vendor/devkit/cli-router.js +3 -4
  215. package/dist/vendor/devkit/defaults-engine.js +9 -11
  216. package/dist/vendor/devkit/local.js +2 -0
  217. package/dist/vendor/devkit/merge-line-files.js +2 -3
  218. package/dist/vendor/devkit/test/runner-core.js +6 -6
  219. package/dist/vendor/monitoring/env.js +2 -2
  220. package/dist/vendor/monitoring/index.js +1 -1
  221. package/dist/vendor/monitoring/main.js +1 -1
  222. package/dist/vendor/monitoring/preload.js +1 -1
  223. package/dist/vendor/monitoring/renderer.js +1 -1
  224. package/docs/analytics.md +8 -8
  225. package/docs/app-state.md +19 -19
  226. package/docs/audit.md +4 -4
  227. package/docs/{client-bridge.md → auth.md} +88 -73
  228. package/docs/auto-updater.md +8 -8
  229. package/docs/boot-sequence.md +11 -6
  230. package/docs/build-system.md +1 -1
  231. package/docs/cdp-debugging.md +1 -1
  232. package/docs/common-mistakes.md +7 -7
  233. package/docs/config-schema.md +1 -1
  234. package/docs/context-menu.md +13 -13
  235. package/docs/context.md +11 -11
  236. package/docs/css.md +3 -9
  237. package/docs/deep-link.md +25 -25
  238. package/docs/environment-detection.md +16 -16
  239. package/docs/fontawesome.md +7 -5
  240. package/docs/hooks.md +8 -8
  241. package/docs/index.md +38 -27
  242. package/docs/ipc.md +10 -10
  243. package/docs/lib-modules.md +9 -9
  244. package/docs/logging.md +12 -14
  245. package/docs/menu.md +18 -18
  246. package/docs/remote-config.md +9 -9
  247. package/docs/remote-scripts.md +12 -12
  248. package/docs/restart-manager.md +8 -8
  249. package/docs/sentry.md +4 -4
  250. package/docs/shared/analytics.md +1 -1
  251. package/docs/shared/breaking-changes.md +79 -13
  252. package/docs/shared/config.md +17 -21
  253. package/docs/shared/logging.md +1 -1
  254. package/docs/shared/monitoring.md +5 -5
  255. package/docs/shared/testing.md +2 -2
  256. package/docs/shared/theming.md +1 -1
  257. package/docs/shared/translation.md +19 -10
  258. package/docs/startup.md +16 -16
  259. package/docs/storage.md +11 -11
  260. package/docs/templating.md +3 -3
  261. package/docs/test-boot-layer.md +12 -12
  262. package/docs/test-framework.md +17 -17
  263. package/docs/themes.md +7 -7
  264. package/docs/tooltips.md +2 -2
  265. package/docs/tray.md +31 -31
  266. package/docs/usage.md +7 -7
  267. package/docs/verts.md +1 -1
  268. package/docs/windows.md +22 -22
  269. package/package.json +3 -4
  270. package/dist/lib/client-bridge.js +0 -374
  271. package/dist/lib/logger.js +0 -4
  272. package/dist/test/suites/build/manager.test.js +0 -213
  273. package/dist/test/suites/main/client-bridge.test.js +0 -262
package/dist/preload.js CHANGED
@@ -1,195 +1,255 @@
1
- // Preload Manager singleton.
2
- // Consumer entry: `new (require('@omega.js/desktop/preload'))().initialize()`.
1
+ // The preload runtime: ONE ready-made `omega` instance.
2
+ // Consumer entry: `const omega = require('@omega.js/desktop/preload'); omega.initialize()`.
3
3
  // Wires contextBridge so renderer code can call `window.desktop.ipc.invoke(...)` without nodeIntegration.
4
4
 
5
5
  const LoggerLite = require('./lib/logger-lite.js');
6
6
  const { ENVIRONMENT_VAR } = require('./vendor/config/environment.js');
7
+ const { getEnvironment, isDevelopment, isProduction, isTesting, getVersion } = require('./utils/mode-helpers.js');
8
+ const { getFunctionsUrl, getApiUrl, getWebsiteUrl, getAuthUrl } = require('./utils/url-helpers.js');
7
9
 
8
- function Manager() {
9
- const self = this;
10
+ /**
11
+ * The preload runtime: exposes `window.desktop` to the page. This module
12
+ * exports ONE instance of it; a consumer never writes `new`, and awaits
13
+ * `initialize()` or `ready`.
14
+ */
15
+ class Omega {
16
+ constructor() {
17
+ this.logger = new LoggerLite('preload');
10
18
 
11
- self.logger = new LoggerLite('preload');
12
-
13
- return self;
14
- }
19
+ // Settled by initialize() with the instance: a module that did not call
20
+ // initialize() can still await it
21
+ this._readyResolve = null;
22
+ this.ready = new Promise((resolve) => {
23
+ this._readyResolve = resolve;
24
+ });
25
+ }
15
26
 
16
- // Async even though there's nothing to await — keeps the API uniform with main + renderer
17
- // so consumers can do `manager.initialize().then(...)` regardless of which entry they're in.
18
- Manager.prototype.initialize = async function () {
19
- const self = this;
27
+ /**
28
+ * Expose `window.desktop`, wire the activity pings and the theme applier,
29
+ * and settle `ready`. Async even though there's nothing to await: the API
30
+ * stays uniform with main + renderer, so `omega.initialize().then(...)`
31
+ * reads the same in every entry.
32
+ * @returns {Promise<Omega>} the instance.
33
+ */
34
+ async initialize() {
35
+ this._expose();
20
36
 
21
- const { contextBridge, ipcRenderer } = require('electron');
37
+ this._readyResolve(this);
22
38
 
23
- if (!contextBridge || !ipcRenderer) {
24
- self.logger.warn('contextBridge / ipcRenderer not available — preload running in test mode.');
25
- return self;
39
+ return this;
26
40
  }
27
41
 
28
- // Expose a stable, namespaced surface to the renderer.
29
- // Real impl in pass 2 will type the channel list and proxy storage through here.
30
- contextBridge.exposeInMainWorld('desktop', {
31
- // The RUNNING environment, for the one context that cannot read it: a page has
32
- // no `process`, so its Manager answers `OMEGA_BUILD_JSON.config.environment`,
33
- // the word the BUILD was for. A test lane boots a production artifact with
34
- // `OMEGA_ENVIRONMENT=testing`, so the two differ there, and a preload is Node
35
- // ([#925](https://github.com/Omega-JS-Stack/omega/issues/925)). The renderer
36
- // bootstrap applies this over the baked word, so it answers what main answers.
37
- environment: process.env[ENVIRONMENT_VAR] || null,
38
- ipc: {
39
- invoke: (channel, payload) => ipcRenderer.invoke(channel, payload),
40
- // Returns an unsubscribe fn (docs/ipc.md contract — same shape as the
41
- // sibling onChange subscriptions below).
42
- on: (channel, handler) => {
43
- const wrapped = (_, payload) => handler(payload);
44
- ipcRenderer.on(channel, wrapped);
45
- return () => ipcRenderer.removeListener(channel, wrapped);
42
+ _expose() {
43
+ const { contextBridge, ipcRenderer } = require('electron');
44
+
45
+ // A preload loaded outside a BrowserWindow (the build-layer tests) has no bridge to expose on
46
+ if (!contextBridge || !ipcRenderer) {
47
+ this.logger.warn('contextBridge / ipcRenderer not available: preload running in test mode.');
48
+ return;
49
+ }
50
+
51
+ // Expose a stable, namespaced surface to the renderer.
52
+ // Real impl in pass 2 will type the channel list and proxy storage through here.
53
+ contextBridge.exposeInMainWorld('desktop', {
54
+ // The RUNNING environment, for the one context that cannot read it: a page has
55
+ // no `process`, so its instance answers `OMEGA_BUILD_JSON.config.environment`,
56
+ // the word the BUILD was for. A test lane boots a production artifact with
57
+ // `OMEGA_ENVIRONMENT=testing`, so the two differ there, and a preload is Node
58
+ // ([#925](https://github.com/Omega-JS-Stack/omega/issues/925)). The renderer
59
+ // bootstrap applies this over the baked word, so it answers what main answers.
60
+ environment: process.env[ENVIRONMENT_VAR] || null,
61
+ ipc: {
62
+ invoke: (channel, payload) => ipcRenderer.invoke(channel, payload),
63
+ // Returns an unsubscribe fn (docs/ipc.md contract, same shape as the
64
+ // sibling onChange subscriptions below).
65
+ on: (channel, handler) => {
66
+ const wrapped = (_, payload) => handler(payload);
67
+ ipcRenderer.on(channel, wrapped);
68
+ return () => ipcRenderer.removeListener(channel, wrapped);
69
+ },
70
+ send: (channel, payload) => ipcRenderer.send(channel, payload),
46
71
  },
47
- send: (channel, payload) => ipcRenderer.send(channel, payload),
48
- },
49
- storage: {
50
- get: (key, def) => ipcRenderer.invoke('desktop:storage:get', { key, def }),
51
- set: (key, val) => ipcRenderer.invoke('desktop:storage:set', { key, val }),
52
- delete: (key) => ipcRenderer.invoke('desktop:storage:delete', { key }),
53
- has: (key) => ipcRenderer.invoke('desktop:storage:has', { key }),
54
- clear: () => ipcRenderer.invoke('desktop:storage:clear'),
55
- // Subscribe to changes broadcast from main. Returns an unsubscribe fn.
56
- // Pass '*' as key to receive all changes.
57
- onChange: (key, handler) => {
58
- const wrapped = (_, payload) => {
59
- if (key === '*' || payload?.key === key) {
60
- handler(payload);
61
- }
62
- };
63
- ipcRenderer.on('desktop:storage:change', wrapped);
64
- return () => ipcRenderer.removeListener('desktop:storage:change', wrapped);
72
+ storage: {
73
+ get: (key, def) => ipcRenderer.invoke('desktop:storage:get', { key, def }),
74
+ set: (key, val) => ipcRenderer.invoke('desktop:storage:set', { key, val }),
75
+ delete: (key) => ipcRenderer.invoke('desktop:storage:delete', { key }),
76
+ has: (key) => ipcRenderer.invoke('desktop:storage:has', { key }),
77
+ clear: () => ipcRenderer.invoke('desktop:storage:clear'),
78
+ // Subscribe to changes broadcast from main. Returns an unsubscribe fn.
79
+ // Pass '*' as key to receive all changes.
80
+ onChange: (key, handler) => {
81
+ const wrapped = (_, payload) => {
82
+ if (key === '*' || payload?.key === key) {
83
+ handler(payload);
84
+ }
85
+ };
86
+ ipcRenderer.on('desktop:storage:change', wrapped);
87
+ return () => ipcRenderer.removeListener('desktop:storage:change', wrapped);
88
+ },
65
89
  },
66
- },
67
- // Theme — system-aware appearance. get/set proxy to main (lib/theme.js owns
68
- // nativeTheme.themeSource + persistence). onChange is MATCHMEDIA-powered, not an
69
- // IPC broadcast: setting themeSource flips `prefers-color-scheme` in every
70
- // renderer of the app (including embedded WebContentsViews, which ipc.broadcast
71
- // can never reach), so each renderer self-resolves. Returns an unsubscribe fn.
72
- theme: {
73
- get: () => ipcRenderer.invoke('desktop:theme:get'),
74
- set: (source) => ipcRenderer.invoke('desktop:theme:set', { source }),
75
- onChange: (handler) => {
76
- const media = window.matchMedia('(prefers-color-scheme: dark)');
77
- const wrapped = (e) => handler({ resolved: e.matches ? 'dark' : 'light' });
78
- media.addEventListener('change', wrapped);
79
- return () => media.removeEventListener('change', wrapped);
90
+ // Theme: system-aware appearance. get/set proxy to main (lib/theme.js owns
91
+ // nativeTheme.themeSource + persistence). onChange is MATCHMEDIA-powered, not an
92
+ // IPC broadcast: setting themeSource flips `prefers-color-scheme` in every
93
+ // renderer of the app (including embedded WebContentsViews, which ipc.broadcast
94
+ // can never reach), so each renderer self-resolves. Returns an unsubscribe fn.
95
+ theme: {
96
+ get: () => ipcRenderer.invoke('desktop:theme:get'),
97
+ set: (source) => ipcRenderer.invoke('desktop:theme:set', { source }),
98
+ onChange: (handler) => {
99
+ const media = window.matchMedia('(prefers-color-scheme: dark)');
100
+ const wrapped = (e) => handler({ resolved: e.matches ? 'dark' : 'light' });
101
+ media.addEventListener('change', wrapped);
102
+ return () => media.removeEventListener('change', wrapped);
103
+ },
80
104
  },
81
- },
82
- // FontAwesome — resolve a bundled icon to its inline-SVG string (or null).
83
- // Renderer code rarely needs this directly: @omega.js/desktop's renderer bootstrap
84
- // auto-renders `<i class="fa-solid fa-*">` elements (see src/renderer.js).
85
- fontawesome: {
86
- get: (name, style) => ipcRenderer.invoke('desktop:fontawesome:get', { name, style }).then((r) => r?.svg ?? null),
87
- },
88
- // Renderer logger — writes to console (visible in DevTools) AND forwards each
89
- // call to main where it's written through electron-log's file transport. Same
90
- // file (logs/runtime.log in dev, OS logs/<AppName>/runtime.log in prod) where
91
- // main + preload logs land too. Each entry is scoped 'renderer' so you can
92
- // grep for renderer-only output.
93
- logger: makeForwardingLogger('renderer', ipcRenderer),
94
- autoUpdater: {
95
- getStatus: () => ipcRenderer.invoke('desktop:auto-updater:status'),
96
- checkNow: () => ipcRenderer.invoke('desktop:auto-updater:check-now'),
97
- installNow: () => ipcRenderer.invoke('desktop:auto-updater:install-now'),
98
- // Subscribe to status broadcasts. Returns an unsubscribe fn.
99
- onStatus: (handler) => {
100
- const wrapped = (_, payload) => handler(payload);
101
- ipcRenderer.on('desktop:auto-updater:status', wrapped);
102
- return () => ipcRenderer.removeListener('desktop:auto-updater:status', wrapped);
105
+ // FontAwesome: resolve a bundled icon to its inline-SVG string (or null).
106
+ // Renderer code rarely needs this directly: @omega.js/desktop's renderer bootstrap
107
+ // auto-renders `<i class="fa-solid fa-*">` elements (see src/renderer.js).
108
+ fontawesome: {
109
+ get: (name, style) => ipcRenderer.invoke('desktop:fontawesome:get', { name, style }).then((r) => r?.svg ?? null),
103
110
  },
104
- },
105
- // GA4 analytics — fire-and-forget event sender. Same shape as on main, just
106
- // routes through IPC so renderer code is identical.
107
- analytics: {
108
- event: (name, params) => ipcRenderer.send('desktop:analytics:event', { name, params }),
109
- pageview: (path) => ipcRenderer.send('desktop:analytics:event', { name: 'page_view', params: path ? { page_path: path } : {} }),
110
- screenview: (screenName) => ipcRenderer.send('desktop:analytics:event', { name: 'screen_view', params: screenName ? { screen_name: screenName } : {} }),
111
- setUserProperties: (props) => ipcRenderer.send('desktop:analytics:set-user-properties', props),
112
- getStatus: () => ipcRenderer.invoke('desktop:analytics:status'),
113
- },
114
- // Runtime context (geolocation / client / session / app). Read once at
115
- // renderer init or whenever fresh values are needed.
116
- context: {
117
- get: () => ipcRenderer.invoke('desktop:context:get'),
118
- },
119
- // Usage stats (opens / hoursTotal / hoursThisSession).
120
- usage: {
121
- get: () => ipcRenderer.invoke('desktop:usage:get'),
122
- },
123
- // Hot config — same get/refreshNow surface as main, plus an onUpdate
124
- // subscription that fires whenever main re-fetches successfully.
125
- remoteConfig: {
126
- get: (path) => ipcRenderer.invoke('desktop:remote-config:get', path),
127
- refreshNow: () => ipcRenderer.invoke('desktop:remote-config:refresh-now'),
128
- onUpdate: (handler) => {
129
- const wrapped = (_, payload) => handler(payload);
130
- ipcRenderer.on('desktop:remote-config:update', wrapped);
131
- return () => ipcRenderer.removeListener('desktop:remote-config:update', wrapped);
111
+ // Renderer logger: writes to console (visible in DevTools) AND forwards each
112
+ // call to main where it's written through electron-log's file transport. Same
113
+ // file (logs/runtime.log in dev, OS logs/<AppName>/runtime.log in prod) where
114
+ // main + preload logs land too. Each entry is scoped 'renderer' so you can
115
+ // grep for renderer-only output.
116
+ logger: makeForwardingLogger('renderer', ipcRenderer),
117
+ autoUpdater: {
118
+ getStatus: () => ipcRenderer.invoke('desktop:auto-updater:status'),
119
+ checkNow: () => ipcRenderer.invoke('desktop:auto-updater:check-now'),
120
+ installNow: () => ipcRenderer.invoke('desktop:auto-updater:install-now'),
121
+ // Subscribe to status broadcasts. Returns an unsubscribe fn.
122
+ onStatus: (handler) => {
123
+ const wrapped = (_, payload) => handler(payload);
124
+ ipcRenderer.on('desktop:auto-updater:status', wrapped);
125
+ return () => ipcRenderer.removeListener('desktop:auto-updater:status', wrapped);
126
+ },
132
127
  },
133
- },
134
- });
128
+ // GA4 analytics: fire-and-forget event sender. Same shape as on main, just
129
+ // routes through IPC so renderer code is identical.
130
+ analytics: {
131
+ event: (name, params) => ipcRenderer.send('desktop:analytics:event', { name, params }),
132
+ pageview: (path) => ipcRenderer.send('desktop:analytics:event', { name: 'page_view', params: path ? { page_path: path } : {} }),
133
+ screenview: (screenName) => ipcRenderer.send('desktop:analytics:event', { name: 'screen_view', params: screenName ? { screen_name: screenName } : {} }),
134
+ setUserProperties: (props) => ipcRenderer.send('desktop:analytics:set-user-properties', props),
135
+ getStatus: () => ipcRenderer.invoke('desktop:analytics:status'),
136
+ },
137
+ // Runtime context (geolocation / client / session / app). Read once at
138
+ // renderer init or whenever fresh values are needed.
139
+ context: {
140
+ get: () => ipcRenderer.invoke('desktop:context:get'),
141
+ },
142
+ // Usage stats (opens / hoursTotal / hoursThisSession).
143
+ usage: {
144
+ get: () => ipcRenderer.invoke('desktop:usage:get'),
145
+ },
146
+ // Hot config: same get/refreshNow surface as main, plus an onUpdate
147
+ // subscription that fires whenever main re-fetches successfully.
148
+ remoteConfig: {
149
+ get: (path) => ipcRenderer.invoke('desktop:remote-config:get', path),
150
+ refreshNow: () => ipcRenderer.invoke('desktop:remote-config:refresh-now'),
151
+ onUpdate: (handler) => {
152
+ const wrapped = (_, payload) => handler(payload);
153
+ ipcRenderer.on('desktop:remote-config:update', wrapped);
154
+ return () => ipcRenderer.removeListener('desktop:remote-config:update', wrapped);
155
+ },
156
+ },
157
+ });
135
158
 
136
- // Auto-updater activity tracking — listen for renderer-side mouse / keyboard / wheel
137
- // / focus events and debounce-fire an `desktop:auto-updater:activity` IPC ping to main.
138
- // Main uses these pings to keep `lastActivityAt` fresh so a downloaded update only
139
- // auto-installs when the user has been idle for 15+ minutes.
140
- //
141
- // Debounced to once per 5s — anything more granular is just noise (the threshold is
142
- // 15 min; sub-second precision doesn't matter). Listeners attach in `capture` phase
143
- // on `window` so renderer code can't accidentally suppress them via stopPropagation.
144
- try {
145
- let lastPing = 0;
146
- const ACTIVITY_DEBOUNCE_MS = 5000;
147
- const ping = () => {
148
- const now = Date.now();
149
- if (now - lastPing < ACTIVITY_DEBOUNCE_MS) return;
150
- lastPing = now;
151
- try { ipcRenderer.send('desktop:auto-updater:activity'); } catch (e) { /* ignore */ }
152
- };
153
- const events = ['mousedown', 'keydown', 'wheel', 'touchstart', 'focus'];
154
- for (const ev of events) {
155
- // `passive: true` so we never accidentally block scrolling; `capture: true` so we
156
- // see the event before any renderer-side handler can stopPropagation it.
157
- window.addEventListener(ev, ping, { passive: true, capture: true });
158
- }
159
- } catch (e) { /* DOM not available (test mode) — skip */ }
160
-
161
- // Theme applier — keeps `<html data-bs-theme>` matched to the RESOLVED appearance
162
- // ('light'/'dark'), live, on every framework-templated page. Opt-in by presence: only pages
163
- // that already carry the attribute (stamped by the page template at build) are
164
- // managed — pages without it (e.g. external sites loaded in a consumer's embedded
165
- // web views, which get this same preload) are never touched.
166
- //
167
- // This is the live-update path for ALL renderers: main flips nativeTheme.themeSource
168
- // → `prefers-color-scheme` flips here → matchMedia 'change' fires → re-apply. No IPC.
169
- try {
170
- const media = window.matchMedia('(prefers-color-scheme: dark)');
171
- const apply = () => {
172
- const root = document.documentElement;
173
- if (!root || !root.hasAttribute('data-bs-theme')) {
174
- return;
159
+ // Auto-updater activity tracking: listen for renderer-side mouse / keyboard / wheel
160
+ // / focus events and debounce-fire an `desktop:auto-updater:activity` IPC ping to main.
161
+ // Main uses these pings to keep `lastActivityAt` fresh so a downloaded update only
162
+ // auto-installs when the user has been idle for 15+ minutes.
163
+ //
164
+ // Debounced to once per 5s, anything more granular is just noise (the threshold is
165
+ // 15 min; sub-second precision doesn't matter). Listeners attach in `capture` phase
166
+ // on `window` so renderer code can't accidentally suppress them via stopPropagation.
167
+ try {
168
+ let lastPing = 0;
169
+ const ACTIVITY_DEBOUNCE_MS = 5000;
170
+ const ping = () => {
171
+ const now = Date.now();
172
+ if (now - lastPing < ACTIVITY_DEBOUNCE_MS) return;
173
+ lastPing = now;
174
+ try { ipcRenderer.send('desktop:auto-updater:activity'); } catch (e) { /* ignore */ }
175
+ };
176
+ const events = ['mousedown', 'keydown', 'wheel', 'touchstart', 'focus'];
177
+ for (const ev of events) {
178
+ // `passive: true` so we never accidentally block scrolling; `capture: true` so we
179
+ // see the event before any renderer-side handler can stopPropagation it.
180
+ window.addEventListener(ev, ping, { passive: true, capture: true });
175
181
  }
176
- root.setAttribute('data-bs-theme', media.matches ? 'dark' : 'light');
177
- };
182
+ } catch (e) { /* DOM not available (test mode), skip */ }
178
183
 
179
- // The stamped attribute only exists once the HTML has parsed — apply at
180
- // DOMContentLoaded (or immediately when the document is already past it).
181
- if (document.readyState === 'loading') {
182
- document.addEventListener('DOMContentLoaded', apply, { once: true });
183
- } else {
184
- apply();
185
- }
186
- media.addEventListener('change', apply);
187
- } catch (e) { /* DOM not available (test mode) — skip */ }
184
+ // Theme applier: keeps `<html data-bs-theme>` matched to the RESOLVED appearance
185
+ // ('light'/'dark'), live, on every framework-templated page. Opt-in by presence: only pages
186
+ // that already carry the attribute (stamped by the page template at build) are
187
+ // managed: pages without it (e.g. external sites loaded in a consumer's embedded
188
+ // web views, which get this same preload) are never touched.
189
+ //
190
+ // This is the live-update path for ALL renderers: main flips nativeTheme.themeSource
191
+ // → `prefers-color-scheme` flips here → matchMedia 'change' fires → re-apply. No IPC.
192
+ try {
193
+ const media = window.matchMedia('(prefers-color-scheme: dark)');
194
+ const apply = () => {
195
+ const root = document.documentElement;
196
+ if (!root || !root.hasAttribute('data-bs-theme')) {
197
+ return;
198
+ }
199
+ root.setAttribute('data-bs-theme', media.matches ? 'dark' : 'light');
200
+ };
201
+
202
+ // The stamped attribute only exists once the HTML has parsed: apply at
203
+ // DOMContentLoaded (or immediately when the document is already past it).
204
+ if (document.readyState === 'loading') {
205
+ document.addEventListener('DOMContentLoaded', apply, { once: true });
206
+ } else {
207
+ apply();
208
+ }
209
+ media.addEventListener('change', apply);
210
+ } catch (e) { /* DOM not available (test mode), skip */ }
188
211
 
189
- self.logger.log('@omega.js/desktop (preload) initialized.');
212
+ this.logger.log('@omega.js/desktop (preload) initialized.');
213
+ }
214
+
215
+ // The environment surface (#817) and the backend URL helpers: the same plain
216
+ // functions main and the build module call
217
+ getEnvironment() {
218
+ return getEnvironment.call(this);
219
+ }
220
+
221
+ isDevelopment() {
222
+ return isDevelopment.call(this);
223
+ }
190
224
 
191
- return self;
192
- };
225
+ isProduction() {
226
+ return isProduction.call(this);
227
+ }
228
+
229
+ isTesting() {
230
+ return isTesting.call(this);
231
+ }
232
+
233
+ getVersion() {
234
+ return getVersion();
235
+ }
236
+
237
+ getFunctionsUrl(environment) {
238
+ return getFunctionsUrl(this, environment);
239
+ }
240
+
241
+ getApiUrl(environment) {
242
+ return getApiUrl(this, environment);
243
+ }
244
+
245
+ getWebsiteUrl(environment) {
246
+ return getWebsiteUrl(this, environment);
247
+ }
248
+
249
+ getAuthUrl(environment, returnUrl) {
250
+ return getAuthUrl(this, environment, returnUrl);
251
+ }
252
+ }
193
253
 
194
254
  // Build a console+forward logger object suitable for exposing through contextBridge
195
255
  // to the renderer. Each method:
@@ -209,7 +269,7 @@ function makeForwardingLogger(scope, ipcRenderer) {
209
269
  const fileLevel = m === 'log' ? 'info' : m;
210
270
  out[m] = function () {
211
271
  // Console first.
212
- const args = ['[em]', ...Array.from(arguments)];
272
+ const args = ['[omega]', ...Array.from(arguments)];
213
273
  const fn = console[m] || console.log;
214
274
  try { fn.apply(console, args); } catch (e) { /* ignore */ }
215
275
  // Forward to main.
@@ -242,8 +302,8 @@ function serializeForIpc(args) {
242
302
  });
243
303
  }
244
304
 
245
- // Mix in shared cross-context helpers — same code path used in main, renderer, build.
246
- require('./utils/mode-helpers.js').attachTo(Manager);
247
- require('./utils/url-helpers.js').attachTo(Manager);
305
+ // The ONE instance, initialized by the consumer's src/preload.js
306
+ const omega = new Omega();
248
307
 
249
- module.exports = Manager;
308
+ module.exports = omega;
309
+ module.exports.Omega = Omega;