@omega.js/desktop 0.53.0 → 0.54.1
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.
- package/README.md +38 -38
- package/dist/cli-run.js +4 -1
- package/dist/cli.js +2 -2
- package/dist/commands/cdp/client.js +1 -1
- package/dist/commands/cdp.js +1 -1
- package/dist/commands/clean.js +2 -3
- package/dist/commands/dev.js +25 -0
- package/dist/commands/lib/ensure-target.js +12 -17
- package/dist/commands/lib/migrate.js +17 -0
- package/dist/commands/logs.js +1 -1
- package/dist/commands/release.js +1 -1
- package/dist/commands/test.js +4 -4
- package/dist/commands/update.js +5 -4
- package/dist/defaults/.github/workflows/build.yml +18 -18
- package/dist/defaults/_.gitignore +0 -2
- package/dist/defaults/_mas/README.md +3 -3
- package/dist/defaults/config/certs/README.md +1 -1
- package/dist/defaults/config/omega.json5 +36 -36
- package/dist/defaults/docs/README.md +3 -3
- package/dist/defaults/gulpfile.js +1 -1
- package/dist/defaults/hooks/build/post.js +1 -1
- package/dist/defaults/hooks/build/pre.js +1 -1
- package/dist/defaults/hooks/notarize/post.js +2 -2
- package/dist/defaults/hooks/release/post.js +1 -1
- package/dist/defaults/hooks/release/pre.js +1 -1
- package/dist/defaults/src/assets/scss/pages/about.scss +1 -1
- package/dist/defaults/src/assets/scss/pages/main.scss +1 -1
- package/dist/defaults/src/assets/scss/pages/settings.scss +1 -1
- package/dist/defaults/src/integrations/context-menu/index.js +11 -11
- package/dist/defaults/src/integrations/menu/index.js +5 -5
- package/dist/defaults/src/integrations/tray/index.js +9 -9
- package/dist/defaults/src/main.js +2 -2
- package/dist/defaults/src/preload.js +1 -1
- package/dist/defaults/test/README.md +3 -3
- package/dist/defaults/test/_init.js +1 -1
- package/dist/gulp/tasks/audit.js +5 -8
- package/dist/lib/restart-manager/index.js +1 -1
- package/dist/lib/restart-manager/install.js +1 -1
- package/dist/lib/restart-manager/protocol.js +1 -1
- package/dist/main.js +4 -3
- package/dist/preload.js +1 -1
- package/dist/test/suites/build/audit.test.js +20 -7
- package/dist/test/suites/build/build-workflow-jobs.test.js +2 -2
- package/dist/test/suites/build/cli.test.js +28 -0
- package/dist/test/suites/build/defaults-em-dash.test.js +22 -0
- package/dist/test/suites/build/defaults-scaffold.test.js +19 -5
- package/dist/test/suites/build/deploy-direct.test.js +7 -5
- package/dist/test/suites/build/deploy-dispatch.test.js +2 -1
- package/dist/test/suites/build/deploy-hook.test.js +4 -2
- package/dist/test/suites/build/dev-verb.test.js +67 -0
- package/dist/test/suites/build/ensure-target.test.js +11 -3
- package/dist/test/suites/build/merge-line-files.test.js +6 -6
- package/dist/test/suites/build/migrate.test.js +29 -0
- package/dist/test/suites/build/project-scripts-deps.test.js +6 -10
- package/dist/test/suites/build/runner-env-write.test.js +73 -0
- package/dist/test/suites/build/runner.test.js +9 -8
- package/dist/test/suites/build/setup-scripts.test.js +27 -0
- package/dist/test/suites/build/validate-config.test.js +13 -2
- package/dist/test/suites/build/verb-logs.test.js +20 -0
- package/dist/test/suites/renderer/window-desktop-surface.test.js +1 -1
- package/dist/utils/build-pipeline.js +4 -4
- package/dist/utils/runner-env.js +13 -28
- package/dist/vendor/config/company.js +46 -14
- package/dist/vendor/config/defaults.js +30 -7
- package/dist/vendor/config/edit.js +25 -3
- package/dist/vendor/config/env-delivery.js +1 -1
- package/dist/vendor/config/env-schema.js +3 -6
- package/dist/vendor/config/env.js +34 -22
- package/dist/vendor/config/index.js +13 -17
- package/dist/vendor/config/load.js +15 -7
- package/dist/vendor/config/repo.js +10 -27
- package/dist/vendor/config/schema-client.js +64 -0
- package/dist/vendor/config/schema-cloud.js +38 -0
- package/dist/vendor/config/schema-manager.js +118 -0
- package/dist/vendor/config/schema-overrides.js +68 -0
- package/dist/vendor/config/schema.js +99 -152
- package/dist/vendor/config/validate.js +97 -77
- package/dist/vendor/devkit/agents-md.js +233 -0
- package/dist/vendor/devkit/attach-log-file.js +15 -1
- package/dist/vendor/devkit/ci-workflows.js +30 -30
- package/dist/vendor/devkit/cli-router.js +13 -7
- package/dist/vendor/devkit/defaults-engine.js +9 -43
- package/dist/vendor/devkit/deploy-snapshot.js +44 -9
- package/dist/vendor/devkit/env-lines.js +183 -0
- package/dist/vendor/devkit/local.js +62 -10
- package/dist/vendor/devkit/lockfile.js +32 -13
- package/dist/vendor/devkit/logger.js +7 -2
- package/dist/vendor/devkit/merge-line-files.js +219 -176
- package/dist/vendor/devkit/omega-bin.js +208 -111
- package/dist/vendor/devkit/preludes/docs-sync.js +52 -0
- package/dist/vendor/devkit/preludes/index.js +1 -0
- package/dist/vendor/devkit/target-picker.js +45 -0
- package/dist/vendor/devkit/test/dashed-files.js +37 -0
- package/dist/vendor/devkit/test/run-verb-under-tee.js +71 -0
- package/dist/vendor/devkit/update.js +15 -15
- package/dist/vendor/devkit/verb-scripts.js +40 -0
- package/dist/vendor/devkit/verbs.js +170 -0
- package/package.json +18 -24
- package/dist/commands/install.js +0 -37
- package/dist/defaults/AGENTS.md +0 -119
- package/dist/defaults/CLAUDE.md +0 -1
- package/dist/vendor/config/env-retired.js +0 -137
- package/dist/vendor/config/retired-keys.js +0 -635
- package/docs/analytics.md +0 -140
- package/docs/app-state.md +0 -92
- package/docs/audit.md +0 -69
- package/docs/auth.md +0 -284
- package/docs/auto-updater.md +0 -243
- package/docs/boot-sequence.md +0 -44
- package/docs/build-system.md +0 -169
- package/docs/cdp-debugging.md +0 -169
- package/docs/common-mistakes.md +0 -21
- package/docs/config-schema.md +0 -120
- package/docs/context-menu.md +0 -112
- package/docs/context.md +0 -81
- package/docs/css.md +0 -84
- package/docs/deep-link.md +0 -186
- package/docs/environment-detection.md +0 -112
- package/docs/fontawesome.md +0 -109
- package/docs/hooks.md +0 -89
- package/docs/icons.md +0 -79
- package/docs/index.md +0 -328
- package/docs/installer-options.md +0 -165
- package/docs/ipc.md +0 -61
- package/docs/lib-modules.md +0 -53
- package/docs/logging.md +0 -227
- package/docs/menu.md +0 -160
- package/docs/releasing.md +0 -239
- package/docs/remote-config.md +0 -118
- package/docs/remote-scripts.md +0 -144
- package/docs/restart-manager.md +0 -144
- package/docs/runner.md +0 -290
- package/docs/sentry.md +0 -97
- package/docs/shared/agent-docs.md +0 -89
- package/docs/shared/analytics.md +0 -612
- package/docs/shared/brands.md +0 -57
- package/docs/shared/breaking-changes.md +0 -917
- package/docs/shared/config.md +0 -1948
- package/docs/shared/deploys.md +0 -341
- package/docs/shared/icons.md +0 -219
- package/docs/shared/local-dev.md +0 -167
- package/docs/shared/logging.md +0 -205
- package/docs/shared/monitoring.md +0 -167
- package/docs/shared/publishing.md +0 -187
- package/docs/shared/rulings.md +0 -34
- package/docs/shared/testing.md +0 -147
- package/docs/shared/theming.md +0 -629
- package/docs/shared/translation.md +0 -342
- package/docs/shared/updates.md +0 -61
- package/docs/signing.md +0 -293
- package/docs/startup.md +0 -142
- package/docs/storage.md +0 -59
- package/docs/templating.md +0 -101
- package/docs/test-boot-layer.md +0 -157
- package/docs/test-framework.md +0 -362
- package/docs/themes.md +0 -149
- package/docs/tooltips.md +0 -99
- package/docs/tray.md +0 -164
- package/docs/usage.md +0 -58
- package/docs/verts.md +0 -62
- package/docs/windows.md +0 -149
package/docs/common-mistakes.md
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# Common Mistakes to Avoid
|
|
2
|
-
|
|
3
|
-
1. **Auto-creating windows in main.js**: @omega.js/desktop does NOT auto-create windows. The consumer's main.js must call `omega.windows.create('main', { show: !startup.isLaunchHidden() })` inside `omega.initialize().then()`. Always create `main`: even in hidden launches, with `show: false`, so the activate/second-instance handlers can surface UI on user re-launch.
|
|
4
|
-
2. **Putting JS logic in config** — Trays / menus / context-menus are file-based (`src/integrations/<name>/index.js`). Click handlers, dynamic labels, conditional visibility — all live in the JS file. Don't try to express them in `omega.json5`.
|
|
5
|
-
3. **Shipping `<slot>@2x.png` files** — Ship ONE file at the native (@2x) size; @omega.js/desktop downscales the @1x sibling. Bundled defaults work the same way. See [icons.md](icons.md).
|
|
6
|
-
4. **Naming the macOS tray icon source `trayTemplate.png`** — The input filename is `tray.png` (matches Windows/Linux). @omega.js/desktop owns the `Template` magic when writing to dist.
|
|
7
|
-
5. **Reading `process.cwd()` from packaged-app runtime code** — It's `/` in packaged apps. Use `require('./utils/app-root.js')()` (tries `app.getAppPath()` first, falls back for tests/non-Electron contexts).
|
|
8
|
-
6. **Setting `enabled: true` to turn on sentry/analytics** — Wrong convention. Set the credentials (`monitoring.providers.sentry.dsn = '...'`, `analytics.providers.google.id = '...'`); presence enables. Same for `cloud.config`.
|
|
9
|
-
7. **Defining a cross-context helper on one process's class alone**: write it as a plain function in `src/utils/<topic>-helpers.js` and call it from each process class (main, preload, renderer) and the build module, so they all share the same code path.
|
|
10
|
-
8. **Trying to share an `omega` instance across processes**: each process has its own. They communicate via IPC (`omega.ipc.invoke/handle` in main, `omega.desktop.ipc` in a renderer).
|
|
11
|
-
9. **Calling `shell.openExternal(url)` directly with a dynamic URL** — Gate through `require('./utils/sanitize-url.js')` first (returns `''` for non-http(s) protocols). Any dynamic URL must have its protocol filtered before navigation.
|
|
12
|
-
10. **Hand-editing `dist/electron-builder.yml` or `dist/config/entitlements.mac.plist`** — Both are generated by `gulp/build-config` from `config/omega.json5` + @omega.js/desktop defaults. Edit the source config; the YAML/plist regenerate every build.
|
|
13
|
-
11. **Forgetting that "build" failed but the .app launched anyway** — `ELECTRON_RUN_AS_NODE=1` makes Electron silently run as Node: `app` is undefined, no BrowserWindow, no window appears. The CLI boundary strips this var; if you see weird "nothing happens" launches in dev, check whether your shell has it set.
|
|
14
|
-
12. **Reading env vars ad-hoc in source**: use `omega.isDevelopment()`, `omega.isProduction()`, `omega.isTesting()`, `omega.getEnvironment()` instead. See [environment-detection.md](environment-detection.md).
|
|
15
|
-
13. **Installing @omega.js/desktop's dependencies as direct consumer deps**: consumer projects must NOT `npm install firebase`, `fs-jetpack`, `@omega.js/client`, or any other @omega.js/desktop/@omega.js/client transitive dep. The bundler re-resolves every name @omega.js/desktop DECLARES from the framework's own installation (`@omega.js/devkit/bundle`'s framework-deps hook). If a dependency isn't resolving, the fix is in @omega.js/desktop's `package.json` or its bundle task, not the consumer's `package.json`. Same rule on every OMEGA framework.
|
|
16
|
-
14. **Touching Firebase directly in consumer code**: Firebase is owned by @omega.js/client. Consumer code NEVER does `require('firebase')` or `import('firebase/app')`. In renderers use `omega.auth` and `omega.firestore` on the renderer instance. In the main process, use `omega.auth` (the @omega.js/desktop bridge). Same rule on every OMEGA browser surface.
|
|
17
|
-
15. **Forgetting `await` on `windows.create()`** — It's async and returns a Promise, not a BrowserWindow. Passing the Promise to code that calls `win.on(...)` silently fails with "is not a function".
|
|
18
|
-
16. **Using raw `import()` for ESM-only deps** — Use `importESM(specifier)` from `utils/import-esm.js`. It tries the consumer's `node_modules/` first, then falls back to @omega.js/desktop's own copy. This means consumers don't need to install @omega.js/desktop's transitive ESM deps (they're resolved from @omega.js/desktop's `node_modules/` automatically). A dep import the bundler cannot inline (a variable specifier) only resolves from the consumer — if the dep isn't installed there, it fails silently.
|
|
19
|
-
17. **Touching `process` in code shared with renderers** — With `contextIsolation: true` and `nodeIntegration: false` (the defaults), `process` does not exist in renderers — `process.platform` in a shared util throws a `ReferenceError`, it does not return `undefined`. Branch platform/env logic in main (or the preload) and hand the RESULT to the renderer (IPC, preload-exposed value, or a data attribute) — never share a util that dereferences `process` across contexts.
|
|
20
|
-
18. **Expecting `target="_blank"` to work in a `file://` renderer** — Anchors with `target="_blank"` silently do nothing; there is no browser to open. External links go through `shell.openExternal` (gated per mistake 9) — wire a click handler or use the framework's external-link binding rather than a bare anchor.
|
|
21
|
-
19. **Registering the brand-scheme handler on the default session only**: `protocol.handle('<brand.id>', handler)` covers ONLY the default session. Sessions from `session.fromPartition(...)` do NOT inherit it, and Electron auto-opens external protocols: a `brand://` load in a partitioned webContents is classified by Chromium as an EXTERNAL protocol and handed to the OS: macOS Launch Services then launches whatever installed app owns the scheme (an old production copy of YOUR app, mid-run). Silent in dev until it isn't. If your app uses partitions, register the same handler on every partition you vend, idempotently: e.g. a session-manager whose `getElectronSession()` does `ses.protocol.handle(scheme, handler)` guarded by a `WeakSet`. There is no Electron API to read a registered handler back, so this cannot be automated after the fact; register through one owned code path. (Candidate future @omega.js/desktop API: opt-in `omega.protocol.handle(handler)` that applies the handler to the default session + every `session-created`, see TODO.md.) The TEST harness already contains this class of escape during `mgr test` (stub brand handler + `openExternal` denied, see [test-framework.md](test-framework.md)), but that protects tests only, not your packaged app.
|
package/docs/config-schema.md
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
# Config schema
|
|
2
|
-
|
|
3
|
-
@omega.js/desktop validates `config/omega.json5` against the canonical OMEGA schema in **`@omega.js/config`** (vendored into `dist/vendor/config/` at prepare time; also exposed to consumers as `require('@omega.js/desktop/config')`). The shared schema covers the cross-framework sections (brand, cloud, analytics, payment, monitoring, connections, theme, targets); the desktop-specific refinements (app.category, the `platforms` shipping declaration, platforms.windows.signing.strategy, startup.mode, restartManager.*, …) live in the same package's `TARGET_SCHEMAS.desktop` and apply when validating with `{ target: 'desktop' }`. Validation always runs against the RESOLVED config: `targets.desktop` contents land at the top level (see the monorepo's `docs/shared/config.md` for the format).
|
|
4
|
-
|
|
5
|
-
Validation runs in two places:
|
|
6
|
-
|
|
7
|
-
1. **`omega.initialize()` (boot, main)**: hard-fails the app at boot if any required field is missing or any present field is invalid. So a misconfigured app never reaches the "white window of confusion" phase: it tells you exactly which field is broken.
|
|
8
|
-
2. **`gulp audit` (build)**: same schema, plus build-pipeline-specific extras (file-existence for icons, an addressable releases repo in publish mode, etc.).
|
|
9
|
-
|
|
10
|
-
## Schema entry shape
|
|
11
|
-
|
|
12
|
-
```js
|
|
13
|
-
{
|
|
14
|
-
path: 'brand.id', // dot-path into the config
|
|
15
|
-
type: 'string' | 'boolean' | 'number' | 'array' | 'object',
|
|
16
|
-
required: true | false | (config) => bool,
|
|
17
|
-
match: /^[a-z][a-z0-9+\-.]*$/, // string-value regex
|
|
18
|
-
enum: ['normal', 'hidden'], // value-must-be-in-this-list
|
|
19
|
-
description: 'Used for the deep-link scheme + default appId.',
|
|
20
|
-
}
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
## The `required` flag
|
|
24
|
-
|
|
25
|
-
@omega.js/desktop keeps validation simple: **`required` is either `true`, `false`, or a function**.
|
|
26
|
-
|
|
27
|
-
```js
|
|
28
|
-
required: true // hard-fail if missing
|
|
29
|
-
required: false // OK to omit (but if present, match/enum/type still run)
|
|
30
|
-
required: (cfg) => bool // conditional — predicate gets the full config
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
The function form is for "this field is mandatory only when another part of config is set." Illustrative shape (no current entry uses it — every present-day field is `true` or `false`):
|
|
34
|
-
|
|
35
|
-
```js
|
|
36
|
-
{
|
|
37
|
-
path: 'analytics.providers.google.id',
|
|
38
|
-
required: (cfg) => Boolean(cfg?.analytics?.providers?.google?.secret), // id mandatory only when a secret is configured
|
|
39
|
-
match: /^G-[A-Z0-9]+$/,
|
|
40
|
-
}
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
This is identical strictness in dev and production. There's no separate `'publish-only'` tier — if a field truly matters only for builds, validate it inside `gulp/audit.js` (next to `fileMustExist` calls for icons, etc.) rather than the schema.
|
|
44
|
-
|
|
45
|
-
## How `match` / `enum` / `type` interact with absence
|
|
46
|
-
|
|
47
|
-
They **only run when the value is present**. A missing field with `required: false` is silent. A missing field with `required: true` fires the "missing" error and nothing else — so consumers don't see a confusing flood of "missing AND wrong type AND doesn't match" for the same field.
|
|
48
|
-
|
|
49
|
-
## Presence-driven feature flags (@omega.js/backend convention)
|
|
50
|
-
|
|
51
|
-
A non-empty credential value enables a feature — there is no separate `enabled: true/false` flag for credential-gated features:
|
|
52
|
-
|
|
53
|
-
| Feature | Enable signal | Disable signal |
|
|
54
|
-
|---|---|---|
|
|
55
|
-
| Sentry | `monitoring.providers.sentry.dsn = 'https://...'` | `monitoring.providers.sentry.dsn = ''` |
|
|
56
|
-
| GA4 analytics | `analytics.providers.google.id = 'G-XXXXX'` | `analytics.providers.google.id = ''` |
|
|
57
|
-
| Firebase Auth (renderer) | `cloud.config.projectId = '...'` (etc.) | empty `cloud.config` |
|
|
58
|
-
|
|
59
|
-
**Exceptions where an explicit `enabled` flag exists:** `remoteConfig.enabled`, `autoUpdate.enabled`, `releases.enabled`, `restartManager.enabled`, `startup.openAtLogin.enabled`. (`platforms.linux.snap.enabled` was one until [#867](https://github.com/Omega-JS-Stack/omega/issues/867): the snap is a declared FORMAT now, so its presence is the switch and `platforms.linux.formats.snap: false` is the off.) These toggle BEHAVIOR, not credentials: a fork can keep the brand's `repo.org` and still want releases off, for example.
|
|
60
|
-
|
|
61
|
-
## Adding a new field
|
|
62
|
-
|
|
63
|
-
When you add a new config knob anywhere in @omega.js/desktop:
|
|
64
|
-
|
|
65
|
-
1. Add an entry to `TARGET_SCHEMAS.desktop` in `@omega.js/config` (`packages/config/src/schema.js` in the Omega monorepo) — or to `SHARED_SCHEMA` if the field is genuinely cross-framework.
|
|
66
|
-
2. If it has a default, set it in [`src/defaults/config/omega.json5`](../src/defaults/config/omega.json5) (under `targets.desktop` for desktop-scoped fields).
|
|
67
|
-
3. That's it. No separate validation logic to add elsewhere — the schema entry is the validation.
|
|
68
|
-
|
|
69
|
-
## What's NOT in the schema
|
|
70
|
-
|
|
71
|
-
These checks live in [`gulp/tasks/audit.js`](../src/gulp/tasks/audit.js) instead, because they depend on build-pipeline state rather than the config shape:
|
|
72
|
-
|
|
73
|
-
- **`src/main.js` / `src/preload.js` existence** — the bundle task skips them with a warning but the schema doesn't know about consumer entry points.
|
|
74
|
-
- **`brand.images.icon` file existence** — only enforced when packaging (`isBuildMode()` / `isPublishMode()`); dev runs with the default Electron icon.
|
|
75
|
-
- **An addressable releases repo** (`repo.org` + `brand.id`), only enforced in publish mode.
|
|
76
|
-
|
|
77
|
-
These are kept in `audit.js` so the schema stays a pure description of the config shape, callable from any context without dragging in build state.
|
|
78
|
-
|
|
79
|
-
## Examples
|
|
80
|
-
|
|
81
|
-
Required field missing:
|
|
82
|
-
|
|
83
|
-
```
|
|
84
|
-
@omega.js/desktop: config validation failed — fix the following in config/omega.json5:
|
|
85
|
-
1. config.brand.id is required — URL-scheme-safe slug. Used as deep-link scheme + default appId. Must be lowercase, start with a letter, alnum/+/-/.
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
Field present but invalid:
|
|
89
|
-
|
|
90
|
-
```
|
|
91
|
-
1. config.startup.mode "tray-only" is not allowed — must be one of [normal, hidden]
|
|
92
|
-
2. config.brand.id "My App!" does not match expected pattern /^[a-z][a-z0-9+\-.]*$/ — URL-scheme-safe slug. Used as deep-link scheme + default appId. Must be lowercase, start with a letter, alnum/+/-/.
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
Errors are numbered so you can fix everything in one pass instead of fix-rebuild-fix-rebuild.
|
|
96
|
-
|
|
97
|
-
## Adding payment fields (@omega.js/backend-shaped)
|
|
98
|
-
|
|
99
|
-
@omega.js/desktop's schema mirrors [@omega.js/backend's `manager-config.example.json`](https://github.com/itw-creative-works/backend-manager) shape for payment so the same product catalog reads identically on backend, web, and desktop:
|
|
100
|
-
|
|
101
|
-
```js
|
|
102
|
-
{
|
|
103
|
-
payment: {
|
|
104
|
-
providers: {
|
|
105
|
-
stripe: { publishableKey: 'pk_live_...' }, // schema: match /^pk_(test|live)_/
|
|
106
|
-
paypal: { clientId: '...' },
|
|
107
|
-
},
|
|
108
|
-
products: [
|
|
109
|
-
{ id: 'basic', name: 'Basic', type: 'subscription', limits: { credits: 100 } },
|
|
110
|
-
],
|
|
111
|
-
},
|
|
112
|
-
}
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
The schema only enforces shape for the few well-defined publishable keys — the product catalog itself is freeform so @omega.js/backend can extend it without @omega.js/desktop caring.
|
|
116
|
-
|
|
117
|
-
## Source
|
|
118
|
-
|
|
119
|
-
- Schema definitions + validator engine: `@omega.js/config` (`packages/config/src/{schema,validate}.js` in the Omega monorepo; vendored copy at `dist/vendor/config/`)
|
|
120
|
-
- @omega.js/desktop integration tests: [`src/test/suites/build/validate-config.test.js`](../src/test/suites/build/validate-config.test.js)
|
package/docs/context-menu.md
DELETED
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
# Context Menu (Right-Click)
|
|
2
|
-
|
|
3
|
-
File-based context menu. Unlike tray and application menu (called once at boot), the context-menu definition is called **every time the user right-clicks** — so it gets fresh `params` each time and can vary the menu by selection.
|
|
4
|
-
|
|
5
|
-
## Config
|
|
6
|
-
|
|
7
|
-
No config block. Path is conventional: `src/integrations/context-menu/index.js`. To opt out, call `omega.contextMenu.disable()` from your main entry: after that, right-click events are silently swallowed.
|
|
8
|
-
|
|
9
|
-
## Definition file
|
|
10
|
-
|
|
11
|
-
```js
|
|
12
|
-
// src/integrations/context-menu/index.js
|
|
13
|
-
module.exports = ({ omega, menu, params, webContents }) => {
|
|
14
|
-
// Easiest: start from @omega.js/desktop's defaults, then customize per event.
|
|
15
|
-
menu.useDefaults();
|
|
16
|
-
|
|
17
|
-
// Add a "Search Google" entry when text is selected:
|
|
18
|
-
if (params.selectionText) {
|
|
19
|
-
menu.insertAfter('copy', {
|
|
20
|
-
id: 'search-google',
|
|
21
|
-
label: `Search "${params.selectionText.slice(0, 20)}"`,
|
|
22
|
-
click: () => require('electron').shell.openExternal(
|
|
23
|
-
`https://google.com/search?q=${encodeURIComponent(params.selectionText)}`,
|
|
24
|
-
),
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Hide the dev-tools entries even in development:
|
|
29
|
-
menu.remove('toggle-devtools');
|
|
30
|
-
};
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
Calling no `menu.*` methods (or `menu.clear()` after `useDefaults()` with nothing added) **suppresses the popup** entirely.
|
|
34
|
-
|
|
35
|
-
## Builder API (per event)
|
|
36
|
-
|
|
37
|
-
```js
|
|
38
|
-
menu.item(descriptor)
|
|
39
|
-
menu.separator()
|
|
40
|
-
menu.submenu(label, items)
|
|
41
|
-
menu.useDefaults() // populate with @omega.js/desktop's defaults based on params
|
|
42
|
-
menu.clear() // wipe items added so far this event
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
## Id-path API (per event)
|
|
46
|
-
|
|
47
|
-
Same shape across menu / tray / context-menu. Available **inside the definition fn** on the `menu` builder. Operates on the items being built for the current right-click event:
|
|
48
|
-
|
|
49
|
-
```js
|
|
50
|
-
.find(idPath)
|
|
51
|
-
.has(idPath)
|
|
52
|
-
.update(idPath, patch)
|
|
53
|
-
.remove(idPath)
|
|
54
|
-
.enable(idPath, bool = true)
|
|
55
|
-
.show(idPath, bool = true)
|
|
56
|
-
.hide(idPath)
|
|
57
|
-
.insertBefore(idPath, item)
|
|
58
|
-
.insertAfter(idPath, item)
|
|
59
|
-
.appendTo(idPath, item)
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
Context-menu ids are **flat** — no `context/` prefix needed (the lib namespace is implicit). Submenus you build with `menu.submenu(...)` are addressable as `parent/child` paths via the resolver.
|
|
63
|
-
|
|
64
|
-
(Runtime-on-`omega.contextMenu` mutators don't apply here: items are rebuilt every event. Mutate inside the definition fn instead.)
|
|
65
|
-
|
|
66
|
-
## Default template ids
|
|
67
|
-
|
|
68
|
-
@omega.js/desktop's `useDefaults()` populates items based on `params`. Every default item carries an id you can target:
|
|
69
|
-
|
|
70
|
-
| ID | When it appears |
|
|
71
|
-
|---|---|
|
|
72
|
-
| `undo`, `redo` | `params.editFlags.canUndo` / `canRedo` |
|
|
73
|
-
| `cut`, `copy`, `paste`, `paste-and-match-style`, `select-all` | `params.isEditable` |
|
|
74
|
-
| `copy` | `params.selectionText` (read-only) |
|
|
75
|
-
| `open-link`, `copy-link` | `params.linkURL` |
|
|
76
|
-
| `reload` | always |
|
|
77
|
-
| `inspect`, `toggle-devtools` | `omega.isDevelopment()` only |
|
|
78
|
-
|
|
79
|
-
## Definition fn arguments
|
|
80
|
-
|
|
81
|
-
| Arg | Description |
|
|
82
|
-
|---|---|
|
|
83
|
-
| `omega` | The running @omega.js/desktop main-process instance |
|
|
84
|
-
| `menu` | Per-event builder + id-path API |
|
|
85
|
-
| `params` | Electron's [`ContextMenuParams`](https://www.electronjs.org/docs/latest/api/web-contents#event-context-menu) — `selectionText`, `isEditable`, `linkURL`, `srcURL`, `mediaType`, `editFlags`, `x`, `y`, etc. |
|
|
86
|
-
| `webContents` | The `webContents` that fired the event |
|
|
87
|
-
|
|
88
|
-
## Auto-attach
|
|
89
|
-
|
|
90
|
-
Every window created via `omega.windows.createNamed()` is automatically wired up with the context-menu listener. Idempotent per `webContents` (uses a `WeakSet`). For windows you create directly with `new BrowserWindow()`, call:
|
|
91
|
-
|
|
92
|
-
```js
|
|
93
|
-
omega.contextMenu.attach(win.webContents);
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
## Runtime API on `omega.contextMenu`
|
|
97
|
-
|
|
98
|
-
```js
|
|
99
|
-
omega.contextMenu.define(fn) // replace the definition at runtime
|
|
100
|
-
omega.contextMenu.disable() // ignore future right-click events (idempotent)
|
|
101
|
-
omega.contextMenu.attach(webContents) // manual attach
|
|
102
|
-
omega.contextMenu.buildItems(params, wc) // run the definition without popping a menu (useful for tests)
|
|
103
|
-
omega.contextMenu.hasCustomDefinition() // false → using the built-in default fn
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
## Default fn
|
|
107
|
-
|
|
108
|
-
Without a consumer file, @omega.js/desktop uses a built-in fallback that just calls `useDefaults()` — sensible undo/redo/cut/copy/paste/link/reload/inspect baseline. Same behavior as the default scaffold.
|
|
109
|
-
|
|
110
|
-
## Default scaffold
|
|
111
|
-
|
|
112
|
-
The scaffold every verb runs ships `src/integrations/context-menu/index.js` calling `menu.useDefaults()` plus commented-out examples covering insertAfter, remove, hide, enable, and building from scratch.
|
package/docs/context.md
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
# Context
|
|
2
|
-
|
|
3
|
-
Runtime info block. Mirrors @omega.js/backend's `assistant.request.{geolocation,client}` shape so @omega.js/desktop apps + sister projects (@omega.js/backend, UJM, @omega.js/client) all reference the same property paths when reading user info.
|
|
4
|
-
|
|
5
|
-
Populated asynchronously during `omega.initialize()`.
|
|
6
|
-
|
|
7
|
-
## Shape
|
|
8
|
-
|
|
9
|
-
```js
|
|
10
|
-
omega.context.geolocation = {
|
|
11
|
-
ip: '203.0.113.42', // async-fetched via ipify
|
|
12
|
-
country: null, // future enhancement
|
|
13
|
-
region: null,
|
|
14
|
-
city: null,
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
omega.context.client = {
|
|
18
|
-
userAgent: 'Mozilla/5.0 ...', // app.userAgentFallback
|
|
19
|
-
locale: 'en-US', // app.getLocale()
|
|
20
|
-
platform: 'darwin', // os.platform()
|
|
21
|
-
arch: 'arm64', // os.arch()
|
|
22
|
-
mobile: false, // always false on @omega.js/desktop (desktop framework)
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
omega.context.session = {
|
|
26
|
-
id: '<uuid>', // fresh per launch (crypto.randomUUID)
|
|
27
|
-
startTime: '2026-05-08T...', // ISO at boot
|
|
28
|
-
deviceId: '<uuid or MAC>', // stable per-machine
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
omega.context.app = {
|
|
32
|
-
version: '1.2.3', // omega.getVersion()
|
|
33
|
-
environment: 'production', // omega.getEnvironment()
|
|
34
|
-
isPackaged: true, // app.isPackaged
|
|
35
|
-
};
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
## Device ID resolution
|
|
39
|
-
|
|
40
|
-
The walk itself is the shared one — `@omega.js/analytics`' `deriveDeviceId({ get, set, seed })`, the same call `@omega.js/client` makes on a page ([#396](https://github.com/Omega-JS-Stack/omega/issues/396)). What this module supplies is desktop's own world: electron-store, and the MAC as the seed. Order:
|
|
41
|
-
|
|
42
|
-
1. **Storage** — already persisted from a prior boot. Wins so we're stable across NIC swaps / VPN changes.
|
|
43
|
-
2. **First non-internal MAC** from `os.networkInterfaces()`, the injected seed. Stable on a stable rig, and it hands a reinstalled app the id it had before its storage was wiped.
|
|
44
|
-
3. **A generated UUID** — the shared derivation's floor. Persisted on first launch.
|
|
45
|
-
|
|
46
|
-
Once resolved on first launch it never changes. This is the input to `analytics._clientId = uuidv5(deviceId, projectIdNamespace)`, and it is desktop's alone: a browser on the same machine derives its own id from its own localStorage.
|
|
47
|
-
|
|
48
|
-
## Geolocation
|
|
49
|
-
|
|
50
|
-
`geolocation.ip` is fetched in the background via `https://api.ipify.org?format=json`. Cached to `storage.context.geolocation` so the next launch has last-known-good values even if offline. The `country/region/city` fields are reserved for a future enrichment provider.
|
|
51
|
-
|
|
52
|
-
Failure mode: a failed ipify fetch leaves the previous cached value untouched. The app keeps working with last-known-good.
|
|
53
|
-
|
|
54
|
-
## API
|
|
55
|
-
|
|
56
|
-
```js
|
|
57
|
-
omega.context.geolocation.ip // direct read
|
|
58
|
-
omega.context.session.deviceId // direct read
|
|
59
|
-
const snap = omega.context.toJSON(); // structured-cloneable snapshot
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
Renderer:
|
|
63
|
-
|
|
64
|
-
```js
|
|
65
|
-
const snap = await window.desktop.context.get();
|
|
66
|
-
console.log(snap.session.deviceId);
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
## Why the @omega.js/backend shape
|
|
70
|
-
|
|
71
|
-
Sister projects (@omega.js/backend, @omega.js/client, UJM) all reference paths like `assistant.request.geolocation.country` and `assistant.request.client.userAgent`. @omega.js/desktop matches the leaf names so consumer code can write logic that works across all four runtimes:
|
|
72
|
-
|
|
73
|
-
```js
|
|
74
|
-
const country = omega.context.geolocation.country
|
|
75
|
-
|| assistant.request.geolocation.country // @omega.js/backend
|
|
76
|
-
|| omega.context.geolocation.country;
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
## Tests
|
|
80
|
-
|
|
81
|
-
- `src/test/suites/main/context.test.js` — session shape, deviceId stability across re-init, the injected seed + persistence of the shared derivation, client info, IPC handler, JSON-roundtrippability.
|
package/docs/css.md
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
# CSS Architecture
|
|
2
|
-
|
|
3
|
-
@omega.js/desktop styles are SCSS, compiled by the pipeline's `sass` task into per-window bundles on top of a shared base. Bootstrap 5 (via @omega.js/desktop's classy theme) is the foundation — consumers restyle Bootstrap, they don't replace it.
|
|
4
|
-
|
|
5
|
-
## Main entry
|
|
6
|
-
|
|
7
|
-
`<consumer>/src/assets/scss/main.scss` — loaded by EVERY window. It configures the theme via `@use ... with (...)`:
|
|
8
|
-
|
|
9
|
-
```scss
|
|
10
|
-
// Generated from `brand.color` by the sass task (#912).
|
|
11
|
-
@use 'brand';
|
|
12
|
-
|
|
13
|
-
@use 'omega-desktop' as * with (
|
|
14
|
-
$primary: brand.$primary,
|
|
15
|
-
$dark: #1a1a2e,
|
|
16
|
-
$classy-bg-dark: #0f0f1a,
|
|
17
|
-
$classy-bg-dark-secondary: #161628,
|
|
18
|
-
$classy-bg-dark-tertiary: #1e1e38,
|
|
19
|
-
);
|
|
20
|
-
|
|
21
|
-
// The runtime --omega-accent ramp, after the framework import.
|
|
22
|
-
@include brand.ramp;
|
|
23
|
-
|
|
24
|
-
// Custom global styles below
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
Compiles to `dist/assets/css/main.bundle.css` (Bootstrap + classy theme + your globals).
|
|
28
|
-
|
|
29
|
-
## Per-window styles
|
|
30
|
-
|
|
31
|
-
`src/assets/scss/pages/<window>.scss` → `dist/assets/css/components/<window>.bundle.css`, loaded ONLY on that window's page. One file per window (`main.scss`, `settings.scss`, …) — page-specific chrome lives here, shared styles live in the main entry.
|
|
32
|
-
|
|
33
|
-
## Theme integration
|
|
34
|
-
|
|
35
|
-
The `@use 'omega-desktop'` entry pulls in Bootstrap 5 + @omega.js/desktop's classy theme. Appearance (`system`/`light`/`dark`) defaults from `config.theme.appearance` and is applied + kept live on `<html data-bs-theme>` by `omega.theme` (OS-following, runtime-switchable, persisted override, see [themes.md](themes.md)). Theme variables (`$primary`, `$dark`, `$classy-bg-*`, typography, borders) are overridable via the `with (...)` block, and `$primary` arrives from `brand.color` through the generated `dist/assets/scss/_brand.scss` unless a literal replaces `brand.$primary` ([#912](https://github.com/Omega-JS-Stack/omega/issues/912)). See [themes.md](themes.md) for the full variable reference.
|
|
36
|
-
|
|
37
|
-
## Icon presentation
|
|
38
|
-
|
|
39
|
-
Icon CSS is ONE sheet for every omega target, vendored from @omega.js/web at prepare (package.json `omega.vendorAssets` → `dist/assets/css/core/_fontawesome.scss`) and loaded by the `omega-desktop` entry. It ships the square glyph-centered box every rendered `<i>` gets, the `fa-2xs`…`fa-6xl` size scale, and the `fa-spin` / `fa-bounce` / `fa-beat` utilities (each parked under `prefers-reduced-motion`). Nothing to import and nothing to hand-fix. See [shared/icons.md](shared/icons.md).
|
|
40
|
-
|
|
41
|
-
## App shell
|
|
42
|
-
|
|
43
|
-
Dashboard/admin windows use the `.omega-shell` layout — a sidebar + topbar + main grid with a collapsible desktop rail and a mobile drawer. Two layers ship, both vendored from @omega.js/web at prepare: the MECHANICS (`dist/assets/css/shell/_index.scss` — the grid, region geometry, states, and the `--omega-shell-*` tokens, loaded by the `omega-desktop` entry before the theme) and the theme's SKIN (`dist/assets/themes/<theme-id>/css/layout/_shell.scss`, layered over it). Nothing to import — `@use 'omega-desktop'` gets both.
|
|
44
|
-
|
|
45
|
-
Emit this markup in the window's HTML:
|
|
46
|
-
|
|
47
|
-
```html
|
|
48
|
-
<div class="omega-shell" data-omega-shell>
|
|
49
|
-
<aside class="omega-shell__sidebar" id="app-sidebar">
|
|
50
|
-
<!-- pinned head (brand, selector) sits here, outside the scroll region -->
|
|
51
|
-
<div class="omega-shell__sidebar-scroll">
|
|
52
|
-
<!-- nav scrolls HERE (the rail itself clips nothing, so popovers can
|
|
53
|
-
escape); text that should hide in the collapsed rail wears
|
|
54
|
-
.omega-shell__label -->
|
|
55
|
-
</div>
|
|
56
|
-
</aside>
|
|
57
|
-
|
|
58
|
-
<header class="omega-shell__topbar">
|
|
59
|
-
<div class="omega-shell__topbar-start">
|
|
60
|
-
<button data-shell-toggle="drawer" aria-expanded="false" aria-controls="app-sidebar">☰</button>
|
|
61
|
-
<button data-shell-toggle="collapse" aria-expanded="true" aria-controls="app-sidebar">⇤</button>
|
|
62
|
-
</div>
|
|
63
|
-
<div class="omega-shell__topbar-end"><!-- account menu, actions --></div>
|
|
64
|
-
</header>
|
|
65
|
-
|
|
66
|
-
<main class="omega-shell__main"><!-- page content --></main>
|
|
67
|
-
|
|
68
|
-
<div class="omega-shell__scrim" data-shell-dismiss></div>
|
|
69
|
-
</div>
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
The renderer instance wires the behavior during `omega.initialize()` (the vendored `__main_assets__/js/core/app-shell.js`, the same module @omega.js/web runs), so a view that renders the markup needs no script of its own. The API is `omega.shell` (`isCollapsed`, `isOpen`, `setCollapsed`, `setOpen`, `toggleCollapsed`, `toggleOpen`).
|
|
73
|
-
|
|
74
|
-
The module is delegated and declarative: `[data-shell-toggle="collapse"]` toggles the rail, `[data-shell-toggle="drawer"]` toggles the mobile drawer, `[data-shell-dismiss]` (and Escape) closes it. It stamps the state on the container: `data-shell-collapsed="true"` (persisted under the `shell.collapsed` storage key) and `data-shell-open="true"`, which is what the CSS keys off. Add `.omega-shell--locked` when `main` should never scroll (the page manages its own interior scroll).
|
|
75
|
-
|
|
76
|
-
## Bootstrap-first convention
|
|
77
|
-
|
|
78
|
-
NEVER create custom classes for things Bootstrap already provides — use `btn`, `card`, `form-*`, `d-flex`, `gap-*`, `rounded-*`, `bg-body-*`, `text-*` natively, and use `bg-body` variants (not `bg-light`/`bg-dark`) so dark mode adapts. Theme SCSS overrides how Bootstrap components LOOK; custom CSS is only for genuinely novel components with no Bootstrap equivalent. Same rule in BXM and UJM.
|
|
79
|
-
|
|
80
|
-
## See also
|
|
81
|
-
|
|
82
|
-
- [themes.md](themes.md) — theme variables, appearance modes
|
|
83
|
-
- [build-system.md](build-system.md) — where the `sass` task runs in the pipeline
|
|
84
|
-
- [templating.md](templating.md) — the page template that loads the bundles
|
package/docs/deep-link.md
DELETED
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
# Deep Links
|
|
2
|
-
|
|
3
|
-
Cross-platform deep-link handling that's simple to use and hard to get wrong. @omega.js/desktop owns all the OS plumbing (single-instance lock, scheme registration, argv parsing, second-instance routing, focus-on-warm-start) and gives you one unified event API regardless of how the link arrived.
|
|
4
|
-
|
|
5
|
-
## Config
|
|
6
|
-
|
|
7
|
-
```jsonc
|
|
8
|
-
"deepLinks": {
|
|
9
|
-
"schemes": ["myapp"] // urls like myapp://...
|
|
10
|
-
}
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
@omega.js/desktop registers each scheme with the OS via `app.setAsDefaultProtocolClient` so the system routes matching URLs to your app. Registration is **production-only**: dev/test runs never claim OS-wide protocol handlers for unpackaged Electron binaries. In dev, exercise your handlers with `omega.deepLink.dispatch(url)` instead.
|
|
14
|
-
|
|
15
|
-
## How it works (so you don't have to think about it)
|
|
16
|
-
|
|
17
|
-
| Platform | Cold-start (app not running) | Warm-start (app already running) |
|
|
18
|
-
|---|---|---|
|
|
19
|
-
| **macOS** | `app.on('open-url')` — queued before `whenReady`, drained after | `app.on('open-url')` |
|
|
20
|
-
| **Windows** | URL appended to `process.argv`; @omega.js/desktop extracts it | OS forwards argv to the existing instance via `app.on('second-instance')`; @omega.js/desktop reads the duplicate's real argv from that event's `additionalData` |
|
|
21
|
-
| **Linux** | Same as Windows | Same as Windows |
|
|
22
|
-
|
|
23
|
-
@omega.js/desktop handles all of these and dispatches them through the same `omega.deepLink.on()` event registry. Your code looks identical regardless of platform or cold/warm start. Single-instance lock is acquired automatically (via `lib/protocol.js`); duplicate launches exit cleanly and forward their argv to the original instance.
|
|
24
|
-
|
|
25
|
-
## Public API
|
|
26
|
-
|
|
27
|
-
```js
|
|
28
|
-
omega.deepLink.on(pattern, handler) // register a handler. Returns unsubscribe fn.
|
|
29
|
-
omega.deepLink.off(pattern, handler)
|
|
30
|
-
omega.deepLink.dispatch(url) // manually fire (testing, custom triggers)
|
|
31
|
-
omega.deepLink.getColdStartUrl() // the URL the app was launched with, or null
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
## Patterns
|
|
35
|
-
|
|
36
|
-
```
|
|
37
|
-
'auth/token' // exact match
|
|
38
|
-
'user/profile/:id' // named param → ctx.params.id
|
|
39
|
-
'org/:slug/repo/:repo' // multiple params
|
|
40
|
-
'*' // wildcard catch-all (only fires when no concrete handler matched)
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
## Handler signature
|
|
44
|
-
|
|
45
|
-
```js
|
|
46
|
-
omega.deepLink.on('user/profile/:id', (ctx) => {
|
|
47
|
-
ctx.url // 'myapp://user/profile/42?ref=tray'
|
|
48
|
-
ctx.scheme // 'myapp'
|
|
49
|
-
ctx.route // 'user/profile/42'
|
|
50
|
-
ctx.pattern // 'user/profile/:id'
|
|
51
|
-
ctx.params // { id: '42' }
|
|
52
|
-
ctx.query // { ref: 'tray' }
|
|
53
|
-
ctx.source // 'cold-start' | 'warm-start' | 'manual'
|
|
54
|
-
ctx.argv // process.argv (cold) or the duplicate's real argv (warm, from additionalData)
|
|
55
|
-
ctx.cwd // working directory (the duplicate's on warm-start)
|
|
56
|
-
ctx.handled // mutable: set true to suppress remaining handlers (including built-ins)
|
|
57
|
-
});
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
## Built-in routes
|
|
61
|
-
|
|
62
|
-
@omega.js/desktop ships with handlers for common patterns. They run AFTER consumer handlers, so you can shadow any of them by registering your own handler at the same pattern.
|
|
63
|
-
|
|
64
|
-
| Route | Default behavior |
|
|
65
|
-
|---|---|
|
|
66
|
-
| `auth/token` | Calls `omega.auth.handleToken(query.authToken)`: the receiving end of the `omega.openAuthFlow()` sign-in round-trip. MODERN shape only (`?authToken=`, what the website's token page sends); other formats (`?token=`, `?payload=`) are ignored. In production the URL arrives via the OS scheme; in dev/test `lib/auth-flow.js`'s loopback listener dispatches the same URL manually (the scheme isn't OS-registered in dev: protocol.js registers only in production, and macOS can't runtime-register unlisted schemes at all) |
|
|
67
|
-
| `app/show` | `omega.windows.show(query.window || 'main')` |
|
|
68
|
-
| `app/quit` | `app.quit()` |
|
|
69
|
-
|
|
70
|
-
### Overriding a built-in
|
|
71
|
-
|
|
72
|
-
```js
|
|
73
|
-
// Replace the built-in app/show with custom logic.
|
|
74
|
-
omega.deepLink.on('app/show', (ctx) => {
|
|
75
|
-
if (ctx.query.window === 'admin' && !omega.appState.isAdminUser()) {
|
|
76
|
-
showError('not authorized');
|
|
77
|
-
ctx.handled = true; // suppress built-in
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
// Otherwise let the built-in run normally.
|
|
81
|
-
});
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
## Resolution order
|
|
85
|
-
|
|
86
|
-
For each incoming URL, @omega.js/desktop walks handlers in this order:
|
|
87
|
-
|
|
88
|
-
1. **Consumer concrete handlers** (any non-wildcard pattern you registered with `.on()`)
|
|
89
|
-
2. **Built-in concrete handlers** (`auth/token`, `app/show`, `app/quit`)
|
|
90
|
-
3. **Wildcard handlers** (`'*'`) — only if NO concrete handler matched
|
|
91
|
-
|
|
92
|
-
Setting `ctx.handled = true` in any handler stops the cascade. Within a single tier, handlers fire in registration order. Errors in a handler are caught and logged — they don't stop subsequent handlers.
|
|
93
|
-
|
|
94
|
-
## Common patterns
|
|
95
|
-
|
|
96
|
-
### Route to a window + send IPC
|
|
97
|
-
|
|
98
|
-
```js
|
|
99
|
-
omega.deepLink.on('user/profile/:id', (ctx) => {
|
|
100
|
-
omega.windows.show('main');
|
|
101
|
-
omega.windows.get('main').webContents.send('navigate', {
|
|
102
|
-
to: `/profile/${ctx.params.id}`,
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
### Catch-all logger
|
|
108
|
-
|
|
109
|
-
```js
|
|
110
|
-
omega.deepLink.on('*', (ctx) => {
|
|
111
|
-
omega.logger.warn(`Unrouted deep link: ${ctx.url}`);
|
|
112
|
-
});
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
### Cold-start branching
|
|
116
|
-
|
|
117
|
-
```js
|
|
118
|
-
const coldUrl = omega.deepLink.getColdStartUrl();
|
|
119
|
-
if (coldUrl) {
|
|
120
|
-
omega.logger.log(`Launched from deep link: ${coldUrl}`);
|
|
121
|
-
// appState.launchedFromDeepLink() is also set automatically
|
|
122
|
-
}
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
### Manually dispatching (e.g. from a tray click)
|
|
126
|
-
|
|
127
|
-
```js
|
|
128
|
-
tray.item({
|
|
129
|
-
label: 'Open Profile',
|
|
130
|
-
click: () => omega.deepLink.dispatch('myapp://user/profile/me'),
|
|
131
|
-
});
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
## Boot queueing
|
|
135
|
-
|
|
136
|
-
Every dispatch is held until `omega.initialize()` completes (main.js calls `deepLink.markOmegaReady()` as its last step). A cold-start `auth/token` link (the OS launching the app from the sign-in round trip) therefore never fires before `omega.auth` has Firebase up; it queues and drains the moment the instance is ready. Warm-start dispatches on a running app pass straight through.
|
|
137
|
-
|
|
138
|
-
## Single-instance behavior
|
|
139
|
-
|
|
140
|
-
@omega.js/desktop acquires the OS-level single-instance lock during `protocol.initialize()` (boot step 5, before deep-link inits). If another copy of the app is already running:
|
|
141
|
-
|
|
142
|
-
1. The new instance loses the lock.
|
|
143
|
-
2. The OS forwards its argv to the original instance.
|
|
144
|
-
3. The new instance's `omega.initialize()` halts (after `protocol.hasSingleInstanceLock() === false`): the duplicate quits and its promise never settles.
|
|
145
|
-
4. The original instance's `app.on('second-instance')` fires with the Chromium-processed argv as its second argument AND the duplicate's real argv as its fourth, `additionalData` (@omega.js/desktop passes `{ argv, cwd }` to `app.requestSingleInstanceLock()` for you).
|
|
146
|
-
5. @omega.js/desktop extracts the deep-link URL from that argv and dispatches normally — but as `source: 'warm-start'`.
|
|
147
|
-
6. @omega.js/desktop also focuses the existing main window automatically (consumer can override by registering a route handler that does its own thing).
|
|
148
|
-
|
|
149
|
-
Reading the duplicate's own flags (a CLI-shaped app, a `--open <file>` handler) means reading that fourth argument:
|
|
150
|
-
|
|
151
|
-
```js
|
|
152
|
-
app.on('second-instance', (event, argv, cwd, additionalData) => additionalData.argv);
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
Never parse the event's own `argv` for flags: Chromium re-serializes it (switches first, Chromium's own switches spliced in, the values detached at the end), so a `--message two` launch arrives with the value detached from the flag.
|
|
156
|
-
|
|
157
|
-
## Linking with `appState`
|
|
158
|
-
|
|
159
|
-
When a deep link is detected at cold-start, @omega.js/desktop calls `omega.appState.setLaunchedFromDeepLink(true)`. This means:
|
|
160
|
-
|
|
161
|
-
```js
|
|
162
|
-
if (omega.appState.launchedFromDeepLink()) {
|
|
163
|
-
// user clicked a link to launch the app — handle differently than a tray click or login launch
|
|
164
|
-
}
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
Combine with `appState.isFirstLaunch()` to detect "first launch via deep link" (e.g. from an onboarding flow on your website).
|
|
168
|
-
|
|
169
|
-
## Testing
|
|
170
|
-
|
|
171
|
-
The dispatch pipeline is unit-testable without actually triggering an OS event:
|
|
172
|
-
|
|
173
|
-
```js
|
|
174
|
-
omega.deepLink.dispatch('myapp://auth/token?token=test');
|
|
175
|
-
// Fires source='manual'. Handlers run synchronously.
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
See `src/test/suites/main/deep-link.test.js` for the full coverage.
|
|
179
|
-
|
|
180
|
-
## Implementation notes
|
|
181
|
-
|
|
182
|
-
- `lib/protocol.js` owns the single-instance lock + scheme registration; `lib/deep-link.js` owns the dispatch pipeline. They're separate modules but tightly coupled.
|
|
183
|
-
- OS scheme registration is gated on `omega.isProduction()` (in `lib/protocol.js`): unpackaged dev/test binaries are never registered as system protocol handlers (unconditional registration also intermittently triggered macOS Launch Services `-600` dialogs during test runs).
|
|
184
|
-
- On Windows/Linux, scheme registration uses `app.setAsDefaultProtocolClient(scheme, process.execPath, [process.cwd()])` so `app.exe scheme://...` style invocations route argv correctly.
|
|
185
|
-
- macOS open-url events that arrive before `whenReady` are queued internally and drained on `deepLink.initialize()`.
|
|
186
|
-
- Argv extraction walks backward from the end of argv (where the URL typically sits) and matches against registered schemes.
|