@omega.js/client 0.1.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.
- package/LICENSE +98 -0
- package/README.md +874 -0
- package/dist/index.js +999 -0
- package/dist/modules/analytics.js +584 -0
- package/dist/modules/auth.js +469 -0
- package/dist/modules/bindings.js +319 -0
- package/dist/modules/device.js +282 -0
- package/dist/modules/dom.js +96 -0
- package/dist/modules/features.js +30 -0
- package/dist/modules/firestore.js +313 -0
- package/dist/modules/form-manager.js +1577 -0
- package/dist/modules/icon-core.js +226 -0
- package/dist/modules/icon-renderer.js +149 -0
- package/dist/modules/live-page.js +235 -0
- package/dist/modules/logger.js +36 -0
- package/dist/modules/motion.js +853 -0
- package/dist/modules/notifications.js +433 -0
- package/dist/modules/path-prefix.js +22 -0
- package/dist/modules/request.js +223 -0
- package/dist/modules/sentry.js +108 -0
- package/dist/modules/service-worker.js +237 -0
- package/dist/modules/storage.js +133 -0
- package/dist/modules/triggers.js +117 -0
- package/dist/modules/utilities.js +479 -0
- package/dist/modules/vert-document.js +354 -0
- package/dist/modules/verts.js +1133 -0
- package/dist/vendor/account/engine.js +182 -0
- package/dist/vendor/account/features.js +220 -0
- package/dist/vendor/account/index.js +53 -0
- package/dist/vendor/account/schema.js +272 -0
- package/dist/vendor/account/subscription.js +38 -0
- package/dist/vendor/analytics/adapters/ga4.js +26 -0
- package/dist/vendor/analytics/adapters/meta.js +26 -0
- package/dist/vendor/analytics/adapters/resolve.js +130 -0
- package/dist/vendor/analytics/adapters/tiktok.js +27 -0
- package/dist/vendor/analytics/catalog.js +908 -0
- package/dist/vendor/analytics/consent.js +49 -0
- package/dist/vendor/analytics/core.js +141 -0
- package/dist/vendor/analytics/identity.js +136 -0
- package/dist/vendor/analytics/index.js +170 -0
- package/dist/vendor/analytics/logger.js +40 -0
- package/dist/vendor/analytics/transports/browser.js +110 -0
- package/dist/vendor/monitoring/browser.js +207 -0
- package/dist/vendor/monitoring/core.js +180 -0
- package/dist/vendor/monitoring/logger.js +39 -0
- package/docs/architecture.md +59 -0
- package/docs/bindings.md +235 -0
- package/docs/build-system.md +32 -0
- package/docs/cdp-debugging.md +29 -0
- package/docs/code-patterns.md +96 -0
- package/docs/common-tasks.md +36 -0
- package/docs/dependencies.md +19 -0
- package/docs/index.md +159 -0
- package/docs/modules.md +180 -0
- package/docs/shared/agent-docs.md +89 -0
- package/docs/shared/analytics.md +612 -0
- package/docs/shared/brands.md +51 -0
- package/docs/shared/breaking-changes.md +497 -0
- package/docs/shared/config.md +1387 -0
- package/docs/shared/deploys.md +215 -0
- package/docs/shared/icons.md +201 -0
- package/docs/shared/local-dev.md +147 -0
- package/docs/shared/logging.md +202 -0
- package/docs/shared/monitoring.md +153 -0
- package/docs/shared/publishing.md +183 -0
- package/docs/shared/rulings.md +34 -0
- package/docs/shared/testing.md +147 -0
- package/docs/shared/theming.md +604 -0
- package/docs/shared/translation.md +291 -0
- package/docs/shared/updates.md +61 -0
- package/docs/testing.md +9 -0
- package/package.json +65 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* browser — the web-bundle entry (#380). @omega.js/client's sentry module is
|
|
3
|
+
* its only host, which puts one policy on every browser surface the client
|
|
4
|
+
* ships into (@omega.js/web pages and @omega.js/extension views alike).
|
|
5
|
+
*
|
|
6
|
+
* The doctrine this file enforces (Ian, 2026-08-20): client-side, ONLY errors
|
|
7
|
+
* from our own framework code report. A web page shares its global with
|
|
8
|
+
* everything — user-land inline scripts, ad and chat widgets, browser
|
|
9
|
+
* extensions injecting into the DOM — and none of that is ours to answer for.
|
|
10
|
+
* So the send gate is a stack-frame check against our bundle URLs, on top of
|
|
11
|
+
* the environment gates (development, Lighthouse, automated browsers) the
|
|
12
|
+
* client already ran.
|
|
13
|
+
*
|
|
14
|
+
* No `process` and no `require` of an SDK: this file is BUNDLED into a page.
|
|
15
|
+
* The host imports @sentry/browser itself (dynamically, to keep it out of the
|
|
16
|
+
* initial chunk) and passes the module in.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const { DEFAULTS, normalizeUser, createBundleFilter } = require('./core.js');
|
|
20
|
+
const { createLogger } = require('./logger.js');
|
|
21
|
+
|
|
22
|
+
const logger = createLogger('browser');
|
|
23
|
+
|
|
24
|
+
/** Lighthouse audits every error it can provoke — none of it is a real user's. */
|
|
25
|
+
function isLighthouse() {
|
|
26
|
+
try {
|
|
27
|
+
return typeof navigator !== 'undefined' && navigator.userAgent?.includes('Lighthouse');
|
|
28
|
+
} catch (e) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Selenium/Puppeteer/Playwright — an e2e run's errors are the run's, not production's. */
|
|
34
|
+
function isAutomatedBrowser() {
|
|
35
|
+
try {
|
|
36
|
+
return typeof navigator !== 'undefined' && navigator.webdriver === true;
|
|
37
|
+
} catch (e) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The url params that ARE a credential (#661): `?authPrivateKey` is a durable
|
|
44
|
+
* key and `?authCustomToken` signs in whoever holds it. A module constant, not
|
|
45
|
+
* config — a page cannot be allowed to opt its own credentials back into an
|
|
46
|
+
* event. Not exhaustive by design: only params the framework's own auth lanes
|
|
47
|
+
* put in an address bar.
|
|
48
|
+
*/
|
|
49
|
+
const SENSITIVE_AUTH_PARAMS = ['authPrivateKey', 'authCustomToken'];
|
|
50
|
+
|
|
51
|
+
// Any absolute url parses against a base too, so ONE parse serves both shapes;
|
|
52
|
+
// the host below only ever names it back out of a relative input.
|
|
53
|
+
const SCRUB_BASE = 'https://scrub.invalid';
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Drop the credential params from a url string. The SDK reads the address bar
|
|
57
|
+
* in two places we cannot reach from a catch block — the navigation breadcrumb
|
|
58
|
+
* it records around `history.replaceState`, and the request url `httpContext`
|
|
59
|
+
* attaches at capture time — so the scrub belongs on this seam, once, rather
|
|
60
|
+
* than in every lane that handles a key.
|
|
61
|
+
*
|
|
62
|
+
* A url carrying none of them comes back untouched (never re-serialized), and a
|
|
63
|
+
* same-origin breadcrumb url stays relative, the way the SDK recorded it.
|
|
64
|
+
*
|
|
65
|
+
* @param {string} url - an absolute or root-relative url
|
|
66
|
+
* @returns {string} the url without the credential params
|
|
67
|
+
*/
|
|
68
|
+
function scrubAuthParams(url) {
|
|
69
|
+
if (typeof url !== 'string' || !SENSITIVE_AUTH_PARAMS.some((param) => url.includes(param))) {
|
|
70
|
+
return url;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let parsed;
|
|
74
|
+
let relative = false;
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
parsed = new URL(url);
|
|
78
|
+
} catch (e) {
|
|
79
|
+
relative = true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (relative) {
|
|
83
|
+
try {
|
|
84
|
+
parsed = new URL(url, SCRUB_BASE);
|
|
85
|
+
} catch (e) {
|
|
86
|
+
// Not a url at all — better a param name in a breadcrumb than a mangled one.
|
|
87
|
+
return url;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
SENSITIVE_AUTH_PARAMS.forEach((param) => parsed.searchParams.delete(param));
|
|
92
|
+
|
|
93
|
+
return relative ? `${parsed.pathname}${parsed.search}${parsed.hash}` : parsed.toString();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Build the @sentry/browser init options: integrations plus the one beforeSend
|
|
98
|
+
* that decides what leaves the page.
|
|
99
|
+
*
|
|
100
|
+
* @param {object} params
|
|
101
|
+
* @param {object} params.Sentry - the imported @sentry/browser module
|
|
102
|
+
* @param {object} [params.config] - the resolved Sentry settings for this surface
|
|
103
|
+
* (the build maps `monitoring.providers.sentry` into the client's `sentry.config`)
|
|
104
|
+
* @param {string} [params.release] - the host's release tag (core.releaseTag)
|
|
105
|
+
* @param {string} [params.environment]
|
|
106
|
+
* @param {() => boolean} [params.isDevelopment] - the host's dev signal, read per event
|
|
107
|
+
* @param {() => object} [params.getUser] - the signed-in user, read per event
|
|
108
|
+
* @param {() => object} [params.getTags] - extra tags, read per event
|
|
109
|
+
* @returns {object} the options object to hand Sentry.init()
|
|
110
|
+
*/
|
|
111
|
+
function buildInitOptions(params) {
|
|
112
|
+
const { Sentry, release, environment, isDevelopment, getUser, getTags } = params || {};
|
|
113
|
+
|
|
114
|
+
const options = { ...DEFAULTS, ...((params && params.config) || {}) };
|
|
115
|
+
|
|
116
|
+
const isFrameworkEvent = createBundleFilter(options.bundlePatterns);
|
|
117
|
+
|
|
118
|
+
// Session-hours baseline: the page's own clock starts when reporting boots.
|
|
119
|
+
const startTime = Date.now();
|
|
120
|
+
|
|
121
|
+
const integrations = [];
|
|
122
|
+
if (typeof Sentry.browserTracingIntegration === 'function') {
|
|
123
|
+
integrations.push(Sentry.browserTracingIntegration());
|
|
124
|
+
}
|
|
125
|
+
// Replay costs bandwidth and captures the DOM — strictly opt-in via sample rates.
|
|
126
|
+
const hasReplays = (options.replaysSessionSampleRate > 0) || (options.replaysOnErrorSampleRate > 0);
|
|
127
|
+
if (hasReplays && typeof Sentry.replayIntegration === 'function') {
|
|
128
|
+
integrations.push(Sentry.replayIntegration({ maskAllText: false, blockAllMedia: false }));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
dsn: options.dsn,
|
|
133
|
+
release,
|
|
134
|
+
environment: environment || options.environment || 'production',
|
|
135
|
+
sampleRate: options.sampleRate,
|
|
136
|
+
tracesSampleRate: options.tracesSampleRate,
|
|
137
|
+
...(options.replaysSessionSampleRate > 0 ? { replaysSessionSampleRate: options.replaysSessionSampleRate } : {}),
|
|
138
|
+
...(options.replaysOnErrorSampleRate > 0 ? { replaysOnErrorSampleRate: options.replaysOnErrorSampleRate } : {}),
|
|
139
|
+
integrations,
|
|
140
|
+
|
|
141
|
+
beforeBreadcrumb(breadcrumb) {
|
|
142
|
+
// The SDK patches history.replaceState, so the very call that STRIPS a key
|
|
143
|
+
// from the address bar records the pre-strip url as `data.from` — and the
|
|
144
|
+
// breadcrumb then rides with every later event on that page load (#661).
|
|
145
|
+
if (breadcrumb && breadcrumb.category === 'navigation' && breadcrumb.data) {
|
|
146
|
+
['from', 'to'].forEach((key) => {
|
|
147
|
+
if (typeof breadcrumb.data[key] === 'string') {
|
|
148
|
+
breadcrumb.data[key] = scrubAuthParams(breadcrumb.data[key]);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return breadcrumb;
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
beforeSend(event, hint) {
|
|
157
|
+
// The console line lands FIRST, before any gate: a dropped event is still
|
|
158
|
+
// an error the developer wants to see in devtools.
|
|
159
|
+
logger.error('Caught error:', {
|
|
160
|
+
message: event.message || (event.exception?.values?.[0]?.value) || 'Unknown error',
|
|
161
|
+
level: event.level,
|
|
162
|
+
hint,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
if (isDevelopment && isDevelopment()) {
|
|
166
|
+
logger.log('Development mode — not sending');
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
if (isLighthouse()) {
|
|
170
|
+
logger.log('Lighthouse detected — not sending');
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
if (isAutomatedBrowser()) {
|
|
174
|
+
logger.log('Automated browser detected — not sending');
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
if (!isFrameworkEvent(event)) {
|
|
178
|
+
logger.log('Not from an @omega.js bundle — not sending');
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
event.tags = {
|
|
183
|
+
...event.tags,
|
|
184
|
+
'process.type': 'browser',
|
|
185
|
+
'usage.session.hours': ((Date.now() - startTime) / (1000 * 3600)).toFixed(2),
|
|
186
|
+
...(getTags ? getTags() : {}),
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
// The other half of the #661 scrub: httpContext attaches the address bar
|
|
190
|
+
// as the request url when the event is CAPTURED, key and all.
|
|
191
|
+
if (typeof event.request?.url === 'string') {
|
|
192
|
+
event.request.url = scrubAuthParams(event.request.url);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// PII: the uid is the join key to the account and rides; the email is
|
|
196
|
+
// scrubbed unless `monitoring.providers.sentry.scrubEmail: false` opts in.
|
|
197
|
+
const user = normalizeUser(getUser ? getUser() : null, options);
|
|
198
|
+
if (user) {
|
|
199
|
+
event.user = { ...event.user, ...user };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return event;
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
module.exports = { buildInitOptions, isLighthouse, isAutomatedBrowser, scrubAuthParams };
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core — the ONE place error-reporting policy lives (#380).
|
|
3
|
+
*
|
|
4
|
+
* Pure functions, zero runtime assumptions: no SDK, no `process`, no DOM. The
|
|
5
|
+
* browser bundle imports this file, so an env read here would be a
|
|
6
|
+
* ReferenceError on a web page (esbuild defines only the NODE_ENV key) — every
|
|
7
|
+
* environment signal is passed IN as a gate. `env.js` is the Node/Electron side
|
|
8
|
+
* of that seam.
|
|
9
|
+
*
|
|
10
|
+
* Policy it owns, once for every target:
|
|
11
|
+
* - config resolution from omega.json5's `monitoring` role section — the
|
|
12
|
+
* settings live under `monitoring.providers.sentry` (#425) and DSN presence
|
|
13
|
+
* is the enable signal (no separate `enabled` flag — matches
|
|
14
|
+
* @omega.js/backend convention: a config block's credentials are its switch)
|
|
15
|
+
* - the release tag, from the host's own version identity
|
|
16
|
+
* - user normalization with the email SCRUBBED BY DEFAULT (the uid stays —
|
|
17
|
+
* it is the join key to the account, and it is not PII on its own)
|
|
18
|
+
* - the client-side framework-bundle filter: browser events report ONLY when
|
|
19
|
+
* they come from OUR bundles. User-land page scripts, browser extensions
|
|
20
|
+
* injecting into the page, and cross-origin "Script error." noise never do.
|
|
21
|
+
*
|
|
22
|
+
* The SDK is never loaded from here. Nothing in this file can turn reporting
|
|
23
|
+
* ON either: it can only say whether a host SHOULD boot one.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const DEFAULTS = {
|
|
27
|
+
dsn: '',
|
|
28
|
+
environment: null, // null = the host's gate decides (production / development)
|
|
29
|
+
sampleRate: 1, // error events kept, 0..1 — the sampling knob (Sentry-native)
|
|
30
|
+
tracesSampleRate: 0.1,
|
|
31
|
+
attachScreenshot: false,
|
|
32
|
+
scrubEmail: true, // PII off by default — set false to opt IN to emails
|
|
33
|
+
// Browser only: the URL fragments that identify OUR bundles. Both @omega.js/web
|
|
34
|
+
// and @omega.js/extension serve every framework bundle (the client runtime
|
|
35
|
+
// included) out of `/assets/js/`, so one default covers both surfaces.
|
|
36
|
+
bundlePatterns: ['/assets/js/'],
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// A resolved-but-off result. `options` still comes back so a caller can log what
|
|
40
|
+
// it read.
|
|
41
|
+
function disabled(options, reason) {
|
|
42
|
+
return { shouldEnable: false, options, reason };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The Sentry settings inside a `monitoring` role section (#425). The role level
|
|
47
|
+
* carries only `enabled`; every SDK-facing key hangs off the provider, so this
|
|
48
|
+
* is the one place that knows the nesting. `{ sentry: false }` (deliberately
|
|
49
|
+
* disabled) and a missing block both resolve to nothing — no dsn, no boot.
|
|
50
|
+
*
|
|
51
|
+
* @param {object} [section] - the `monitoring` config block (target-resolved)
|
|
52
|
+
* @returns {object} the provider's settings, or {} when there are none
|
|
53
|
+
*/
|
|
54
|
+
function providerOptions(section) {
|
|
55
|
+
const settings = section && section.providers && section.providers.sentry;
|
|
56
|
+
return settings && typeof settings === 'object' ? settings : {};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Resolve the runtime config and decide whether reporting should boot.
|
|
61
|
+
*
|
|
62
|
+
* Reads omega.json5's `monitoring` role section: the SDK-facing keys come from
|
|
63
|
+
* `monitoring.providers.sentry`, so the resolved options feed init directly.
|
|
64
|
+
*
|
|
65
|
+
* @param {object} section - the `monitoring` config block (target-resolved)
|
|
66
|
+
* @param {object} [gates] - the host's environment signals
|
|
67
|
+
* @param {boolean} [gates.killed] - a kill switch tripped (env var, test runner)
|
|
68
|
+
* @param {string} [gates.killedReason] - what tripped it, for the log line
|
|
69
|
+
* @param {boolean} [gates.isProduction] - this run ships telemetry
|
|
70
|
+
* @param {boolean} [gates.allowInDev] - report anyway outside production
|
|
71
|
+
* @returns {{ shouldEnable: boolean, options: object, reason: string|null }}
|
|
72
|
+
*/
|
|
73
|
+
function resolveConfig(section, gates) {
|
|
74
|
+
gates = gates || {};
|
|
75
|
+
|
|
76
|
+
const options = { ...DEFAULTS, ...providerOptions(section) };
|
|
77
|
+
|
|
78
|
+
if (gates.killed) {
|
|
79
|
+
return disabled(options, gates.killedReason || 'disabled by the host');
|
|
80
|
+
}
|
|
81
|
+
if (!options.dsn) {
|
|
82
|
+
return disabled(options, 'no dsn set');
|
|
83
|
+
}
|
|
84
|
+
if (!gates.isProduction && !gates.allowInDev) {
|
|
85
|
+
return disabled(options, 'not a production run (set OMEGA_SENTRY_FORCE=true to override)');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!options.environment) {
|
|
89
|
+
options.environment = gates.isProduction ? 'production' : 'development';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return { shouldEnable: true, options, reason: null };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Normalize a @omega.js/client / firebase / admin user into the minimal shape
|
|
97
|
+
* the SDK wants. The email is PII: it rides ONLY when the host explicitly opts
|
|
98
|
+
* in with `monitoring.providers.sentry.scrubEmail: false`.
|
|
99
|
+
*
|
|
100
|
+
* @param {object} user - a user-ish object carrying uid/id and maybe email
|
|
101
|
+
* @param {object} [options] - the resolved monitoring options
|
|
102
|
+
* @returns {object|null} { id, email? } — null when there is nothing safe to send
|
|
103
|
+
*/
|
|
104
|
+
function normalizeUser(user, options) {
|
|
105
|
+
if (!user) return null;
|
|
106
|
+
|
|
107
|
+
const scrubEmail = (options || {}).scrubEmail !== false;
|
|
108
|
+
const out = {};
|
|
109
|
+
|
|
110
|
+
if (user.uid) out.id = user.uid;
|
|
111
|
+
else if (user.id) out.id = user.id;
|
|
112
|
+
if (user.email && !scrubEmail) out.email = user.email;
|
|
113
|
+
|
|
114
|
+
return Object.keys(out).length === 0 ? null : out;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Build the release tag from the host's own version identity. ONE format on
|
|
119
|
+
* every target (Ian, 2026-08-20): `<brand.id>@<version>` — a Sentry release is
|
|
120
|
+
* comparable across the backend, the desktop app and the browser bundles only
|
|
121
|
+
* when they spell it the same way. The brand id is required config, so a tag
|
|
122
|
+
* missing one is a config hole, not a second format: no id, no tag.
|
|
123
|
+
*
|
|
124
|
+
* The version is the host's own — @omega.js/backend's functions package
|
|
125
|
+
* version, @omega.js/desktop's app version, the browser blob's app version.
|
|
126
|
+
*
|
|
127
|
+
* @param {object} identity - { id, version }
|
|
128
|
+
* @returns {string|undefined} the tag, or undefined when either half is missing
|
|
129
|
+
*/
|
|
130
|
+
function releaseTag(identity) {
|
|
131
|
+
const { id, version } = identity || {};
|
|
132
|
+
if (!id || !version) return undefined;
|
|
133
|
+
return `${id}@${version}`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Every stack-frame filename an event carries (exception frames first, then the
|
|
138
|
+
* threads a hung-renderer report uses).
|
|
139
|
+
*/
|
|
140
|
+
function eventFrameFilenames(event) {
|
|
141
|
+
const values = [
|
|
142
|
+
...((event && event.exception && event.exception.values) || []),
|
|
143
|
+
...((event && event.threads && event.threads.values) || []),
|
|
144
|
+
];
|
|
145
|
+
|
|
146
|
+
return values.flatMap((value) => ((value.stacktrace && value.stacktrace.frames) || [])
|
|
147
|
+
.map((frame) => frame.filename || frame.abs_path || '')
|
|
148
|
+
.filter(Boolean));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* The client-side doctrine gate (#380): a browser event reports ONLY when our
|
|
153
|
+
* own framework code is on its stack.
|
|
154
|
+
*
|
|
155
|
+
* An event with no matching frame is DROPPED, and that includes an event with no
|
|
156
|
+
* frames at all — a cross-origin `Script error.` is exactly the third-party noise
|
|
157
|
+
* this exists to kill. Deliberate `omega.sentry().captureException(...)` calls
|
|
158
|
+
* are unaffected: they are thrown from page bundles, which ARE our bundles.
|
|
159
|
+
*
|
|
160
|
+
* @param {string[]} [patterns] - URL fragments identifying our bundles
|
|
161
|
+
* @returns {(event: object) => boolean} true when the event may report
|
|
162
|
+
*/
|
|
163
|
+
function createBundleFilter(patterns) {
|
|
164
|
+
const fragments = (patterns || DEFAULTS.bundlePatterns).filter(Boolean);
|
|
165
|
+
|
|
166
|
+
return function isFrameworkEvent(event) {
|
|
167
|
+
const filenames = eventFrameFilenames(event);
|
|
168
|
+
return filenames.some((filename) => fragments.some((fragment) => filename.includes(fragment)));
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
module.exports = {
|
|
173
|
+
DEFAULTS,
|
|
174
|
+
providerOptions,
|
|
175
|
+
resolveConfig,
|
|
176
|
+
normalizeUser,
|
|
177
|
+
releaseTag,
|
|
178
|
+
eventFrameFilenames,
|
|
179
|
+
createBundleFilter,
|
|
180
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The monitoring package's log tag — the ONE identity tag every OMEGA surface
|
|
3
|
+
* prints: `[@omega.js/monitoring:<module>]` ([#12](https://github.com/Omega-JS-Stack/omega/issues/12)).
|
|
4
|
+
*
|
|
5
|
+
* The twin of @omega.js/analytics' createLogger, emitting this package's
|
|
6
|
+
* segment: no timestamp, because devtools, Cloud Logging and electron-log all
|
|
7
|
+
* stamp their own lines.
|
|
8
|
+
*
|
|
9
|
+
* CJS like the rest of the package: the backend and the electron main process
|
|
10
|
+
* require() it, the browser bundles import it with standard interop.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// The package segment — this file IS @omega.js/monitoring, so it is a literal.
|
|
14
|
+
const PACKAGE = '@omega.js/monitoring';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Create a tagged console for one module, e.g. createLogger('core').
|
|
18
|
+
* @param {string} module - The module identity segment.
|
|
19
|
+
* @returns {object} A console-shaped logger whose calls carry the tag.
|
|
20
|
+
*/
|
|
21
|
+
function createLogger(module) {
|
|
22
|
+
const tag = `[${PACKAGE}:${module}]`;
|
|
23
|
+
|
|
24
|
+
// GETTERS returning a BOUND console method, not wrapper arrows: devtools
|
|
25
|
+
// attributes a line to the frame that called console, so a wrapper would make
|
|
26
|
+
// every line read as coming from this file. Resolution stays at ACCESS time,
|
|
27
|
+
// so a test (or a consumer) that swaps console[method] still sees its own stub.
|
|
28
|
+
const logger = { tag };
|
|
29
|
+
for (const method of ['log', 'info', 'warn', 'error', 'debug']) {
|
|
30
|
+
Object.defineProperty(logger, method, {
|
|
31
|
+
get: () => console[method].bind(console, tag),
|
|
32
|
+
enumerable: true,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return logger;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = { createLogger };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
## Singleton Pattern
|
|
4
|
+
|
|
5
|
+
The library exports a singleton `Manager` instance. Import it directly from any file — it's always the same initialized instance:
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
import omega from '@omega.js/client';
|
|
9
|
+
|
|
10
|
+
// Same instance everywhere — config, auth, firestore, all ready
|
|
11
|
+
omega.auth().listen((state) => { ... });
|
|
12
|
+
omega.utilities().escapeHTML(untrustedText);
|
|
13
|
+
omega.config.environment; // 'development' or 'production'
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
**Do NOT create new instances** (`new Manager()`). @omega.js/web, @omega.js/extension, and @omega.js/desktop initialize the singleton — every import gets that same object. Do NOT pass `omega` through function params or store it in module-level variables — just import it.
|
|
17
|
+
|
|
18
|
+
## Directory Structure
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
@omega.js/client/
|
|
22
|
+
├── src/ # Source code (ES6+)
|
|
23
|
+
│ ├── index.js # Manager class, initialization, Firebase setup
|
|
24
|
+
│ └── modules/ # Feature modules
|
|
25
|
+
│ ├── auth.js # Firebase Auth wrapper
|
|
26
|
+
│ ├── bindings.js # Reactive DOM data binding
|
|
27
|
+
│ ├── dom.js # loadScript, ready utilities
|
|
28
|
+
│ ├── firestore.js # Firestore wrapper with chainable queries
|
|
29
|
+
│ ├── notifications.js # FCM push notifications
|
|
30
|
+
│ ├── sentry.js # Error tracking integration
|
|
31
|
+
│ ├── service-worker.js # SW registration and messaging
|
|
32
|
+
│ ├── storage.js # localStorage/sessionStorage wrapper
|
|
33
|
+
│ └── utilities.js # Helper functions (clipboard, escape, etc.)
|
|
34
|
+
├── dist/ # Transpiled ES5 output (generated)
|
|
35
|
+
├── _legacy/ # Old implementation (reference only, DO NOT MODIFY)
|
|
36
|
+
└── test/ # Mocha tests
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Module Dependencies
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
Manager (index.js)
|
|
43
|
+
├── Storage (standalone, no deps)
|
|
44
|
+
├── Auth → Manager, Bindings, Storage, Firestore
|
|
45
|
+
├── Bindings → Manager
|
|
46
|
+
├── Firestore → Manager (lazy Firebase import)
|
|
47
|
+
├── Notifications → Manager, Storage, Firestore
|
|
48
|
+
├── ServiceWorker → Manager
|
|
49
|
+
├── Sentry → Manager (dynamic import)
|
|
50
|
+
├── DOM utilities (standalone)
|
|
51
|
+
└── Utilities (standalone)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Firebase Initialization
|
|
55
|
+
|
|
56
|
+
`initialize(config)` boots Firebase only when a usable web SDK config resolves:
|
|
57
|
+
|
|
58
|
+
- `_resolveFirebaseConfig()` checks `cloud.config` first (the omega.json5 role shape — desktop passes its resolved config through), then the nested `firebase.app.config` (the web/extension bridge contract shape). A blob only counts when **at least one value is non-empty** — framework config merges (e.g. UJM's Jekyll chain) inject all-empty-string blobs into Firebase-less sites, and those resolve to `null` (no init, no URL derivation).
|
|
59
|
+
- Initialization additionally requires a **non-empty `apiKey`** — the SDK cannot boot without one (it crashes the page with `auth/invalid-api-key`). Configs carrying only `projectId` still resolve so `getFunctionsUrl()` can derive its URL (`getApiUrl()` derives from `brand.url`, not the Firebase blob), but Firebase itself stays uninitialized and the console logs `[Firebase] Skipped: config has no apiKey ...` (same idiom as `[Analytics] Skipped:`).
|