ngx-boomerangjs 1.0.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 ADDED
@@ -0,0 +1,25 @@
1
+ MIT License
2
+
3
+ <<<<<<< HEAD
4
+ Copyright (c) 2026 Manuel Conde Vendrell
5
+ =======
6
+ Copyright (c) 2026 ngx-boomerangjs contributors
7
+ >>>>>>> 87b5680 (Initial commit)
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all
17
+ copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,230 @@
1
+ # ngx-boomerangjs
2
+
3
+ An Angular 21+ wrapper for [boomerangjs](https://github.com/nicjansma/boomerangjs) that provides automatic script loading and Real User Monitoring (RUM) support via Angular's dependency injection system.
4
+
5
+ ## Features
6
+
7
+ - Automatic boomerang script loading with ordered sequence support
8
+ - Angular signal-based service for RUM metrics
9
+ - `APP_INITIALIZER` integration for zero-boilerplate setup
10
+ - Full TypeScript types for boomerang configuration
11
+ - SPA-friendly with History and SPA plugin support
12
+ - Configurable timeout and script integrity (SRI)
13
+
14
+ ## Requirements
15
+
16
+ | Package | Version |
17
+ | ----------------- | ---------- |
18
+ | `@angular/core` | `^21.0.0` |
19
+ | `@angular/common` | `^21.0.0` |
20
+ | `boomerangjs` | `^1.815.1` |
21
+
22
+ ## Installation
23
+
24
+ As ngx-boomerangjs has a peer dependency on boomerangjs, you need to install both packages:
25
+
26
+ ```bash
27
+ npm install ngx-boomerangjs boomerangjs
28
+ ```
29
+
30
+ ## Quick Start
31
+
32
+ ### 1. Provide the configuration in `app.config.ts`
33
+
34
+ Minimal example with boomerang core config:
35
+
36
+ ```ts
37
+ import { ApplicationConfig } from '@angular/core';
38
+ import { provideBoomerangMetrics } from 'ngx-boomerangjs';
39
+
40
+ export const appConfig: ApplicationConfig = {
41
+ providers: [
42
+ provideBoomerangMetrics({
43
+ enabled: true,
44
+ boomerangConfig: {
45
+ beacon_url: 'https://your-beacon-endpoint.example.com/beacon',
46
+ Errors: { enabled: true, sendInterval: 1000, maxErrors: 20 },
47
+ History: { enabled: true },
48
+ },
49
+ }),
50
+ ],
51
+ };
52
+ ```
53
+
54
+ Extended configuration example with custom script source, timeout, and source IP variable:
55
+
56
+ ```ts
57
+ import { ApplicationConfig } from '@angular/core';
58
+ import { provideBoomerangMetrics } from 'ngx-boomerangjs';
59
+
60
+ export const appConfig: ApplicationConfig = {
61
+ providers: [
62
+ provideBoomerangMetrics({
63
+ enabled: true,
64
+ boomerangConfig: {
65
+ beacon_url: 'https://your-beacon-endpoint.example.com/beacon',
66
+ Errors: { enabled: true, sendInterval: 1000, maxErrors: 20 },
67
+ History: { enabled: true },
68
+ },
69
+ scriptBaseUrl: '/assets/boomerang',
70
+ scriptLoadTimeoutMs: 15000,
71
+ sourceIpVarName: 'my_ip',
72
+ sourceIpFactory: async () => {
73
+ const ipService = inject(IpService);
74
+ const result = await firstValueFrom(ipService.getIpAddress());
75
+ return result.clientHost;
76
+ },
77
+ }),
78
+ ],
79
+ };
80
+ ```
81
+
82
+ ### Set the needed assets for boomerangjs
83
+
84
+ In `angular.json`, in `projects.<app>.architect.build.options.assets`, add the following entries to map the boomerang core and plugin scripts from `node_modules` to your `assets` folder during build:
85
+
86
+ ```json
87
+ {
88
+ "glob": "boomerang.js",
89
+ "input": "node_modules/boomerangjs/",
90
+ "output": "assets/boomerang/"
91
+ },
92
+ {
93
+ "glob": "rt.js",
94
+ "input": "node_modules/boomerangjs/plugins/",
95
+ "output": "assets/boomerang/plugins/"
96
+ },
97
+ {
98
+ "glob": "auto-xhr.js",
99
+ "input": "node_modules/boomerangjs/plugins/",
100
+ "output": "assets/boomerang/plugins/"
101
+ },
102
+ {
103
+ "glob": "errors.js",
104
+ "input": "node_modules/boomerangjs/plugins/",
105
+ "output": "assets/boomerang/plugins/"
106
+ },
107
+ {
108
+ "glob": "memory.js",
109
+ "input": "node_modules/boomerangjs/plugins/",
110
+ "output": "assets/boomerang/plugins/"
111
+ },
112
+ {
113
+ "glob": "painttiming.js",
114
+ "input": "node_modules/boomerangjs/plugins/",
115
+ "output": "assets/boomerang/plugins/"
116
+ },
117
+ {
118
+ "glob": "navtiming.js",
119
+ "input": "node_modules/boomerangjs/plugins/",
120
+ "output": "assets/boomerang/plugins/"
121
+ },
122
+ {
123
+ "glob": "zzz-last-plugin.js",
124
+ "input": "node_modules/boomerangjs/plugins/",
125
+ "output": "assets/boomerang/plugins/"
126
+ }
127
+ ```
128
+
129
+ ## API
130
+
131
+ ### `provideBoomerangMetrics(config: NgxBoomerangjsConfig)`
132
+
133
+ Registers boomerang as an Angular environment provider. Runs script loading and initialization during `APP_INITIALIZER`.
134
+
135
+ ### `NgxBoomerangjsConfig`
136
+
137
+ ```ts
138
+ export interface NgxBoomerangjsConfig {
139
+ enabled: boolean;
140
+ boomerangConfig: BoomrConfig;
141
+ scripts?: ScriptDescriptor[];
142
+ scriptBaseUrl?: string;
143
+ scriptLoadTimeoutMs?: number;
144
+ sourceIpVarName?: string;
145
+ sourceIpFactory?: () => Promise<string>;
146
+ }
147
+ ```
148
+
149
+ | Property | Type | Description |
150
+ | --------------------- | ----------------------- | ------------------------------------------------------------------------------------------- |
151
+ | `enabled` | `boolean` | Enables or disables boomerang initialization. |
152
+ | `boomerangConfig` | `BoomrConfig` | Boomerang `BOOMR.init()` configuration. |
153
+ | `scripts` | `ScriptDescriptor[]` | Optional ordered script list. If omitted, defaults are created from `scriptBaseUrl`. |
154
+ | `scriptBaseUrl` | `string` | Optional Base URL used by `createDefaultBoomerangScripts()` when `scripts` is not provided. |
155
+ | `scriptLoadTimeoutMs` | `number` | Optional Timeout in milliseconds for each script load. |
156
+ | `sourceIpVarName` | `string` | Optional beacon variable name used to store source IP. |
157
+ | `sourceIpFactory` | `() => Promise<string>` | Optional async function that resolves the source IP value. |
158
+
159
+ ### `createDefaultBoomerangScripts(options)`
160
+
161
+ Helper to build a `ScriptDescriptor[]` list for the boomerang core and common plugins. With this option, you can replace the default script loading behavior with your own custom script list while still benefiting from the `scriptBaseUrl` and `scriptLoadTimeoutMs` configuration options.
162
+
163
+ ## Configuration Reference
164
+
165
+ The `boomerangConfig` property maps directly to boomerang's `BOOMR.init()` options. See the [boomerangjs documentation](https://nicj.net/boomerangjs/) for the full list of supported plugins and settings.
166
+
167
+ ## Contributing
168
+
169
+ Contributions are welcome, and pull requests are encouraged.
170
+
171
+ If you want to propose a fix, improvement, or new feature, feel free to open an issue for discussion or submit a PR directly.
172
+
173
+ ## Development notes
174
+
175
+ ### Building the library
176
+
177
+ This library is packaged using [ng-packagr](https://github.com/ng-packagr/ng-packagr) in **Angular Ivy partial compilation mode**, which is the standard format for distributing Angular libraries on npm.
178
+
179
+ Partial compilation produces output that Angular's linker processes at the application build time, making it compatible with AOT and enabling tree-shaking.
180
+
181
+ The build is driven by two config files:
182
+
183
+ - `ng-package.json` — tells ng-packagr where the entry point is (`src/public-api.ts`) and where to write the output (`dist/`).
184
+ - `tsconfig.lib.json` — extends the base `tsconfig.json` and sets `"compilationMode": "partial"` under `angularCompilerOptions`.
185
+
186
+ To build the library:
187
+
188
+ ```bash
189
+ pnpm run build
190
+ ```
191
+
192
+ The output in `dist/` follows the Angular Package Format (APF):
193
+
194
+ - `fesm2022/ngx-boomerangjs.mjs` — flat ES module bundle.
195
+ - `types/ngx-boomerangjs.d.ts` — consolidated type declarations.
196
+ - `package.json` — generated manifest with correct `exports`, `module`, and `typings` fields.
197
+
198
+ ### Testing locally before publishing
199
+
200
+ To install the library in another project without publishing to npm, pack it as a tarball:
201
+
202
+ ```bash
203
+ pnpm run build
204
+ pnpm run pack:local
205
+ ```
206
+
207
+ This generates `ngx-boomerangjs-1.0.0.tgz` in the root. Install it in the consuming project:
208
+
209
+ ```bash
210
+ npm install ../ngx-boomerangjs/ngx-boomerangjs-1.0.0.tgz
211
+ # or with pnpm:
212
+ pnpm add ../ngx-boomerangjs/ngx-boomerangjs-1.0.0.tgz
213
+ ```
214
+
215
+ > Note: after any source change, rebuild and repack before reinstalling in the consumer project.
216
+
217
+ ### Publishing to npm
218
+
219
+ Authentication uses a project-scoped `.npmrc` with an `NPM_TOKEN` environment variable. Set the token before publishing:
220
+
221
+ ```bash
222
+ set NPM_TOKEN=your_token_here # Windows
223
+ npm run publish:dist
224
+ ```
225
+
226
+ The `publish:dist` script runs `npm publish ./dist`, so only the compiled output is uploaded — source files are never included.
227
+
228
+ ## License
229
+
230
+ MIT © 2026
@@ -0,0 +1,170 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, inject, Injectable, makeEnvironmentProviders, provideAppInitializer } from '@angular/core';
3
+
4
+ const DEFAULT_BASE = '/assets/boomerang';
5
+ // These are the original boomerang scripts, but without integrity or crossOrigin attributes since those can vary based on hosting and build setup.
6
+ // Users can customize the script list by providing their own in the config if needed.
7
+ function createDefaultBoomerangScripts(baseUrl = DEFAULT_BASE) {
8
+ return [
9
+ { src: `${baseUrl}/boomerang.js` },
10
+ { src: `${baseUrl}/plugins/rt.js` },
11
+ { src: `${baseUrl}/plugins/auto-xhr.js` },
12
+ { src: `${baseUrl}/plugins/errors.js` },
13
+ { src: `${baseUrl}/plugins/memory.js` },
14
+ { src: `${baseUrl}/plugins/painttiming.js` },
15
+ { src: `${baseUrl}/plugins/navtiming.js` },
16
+ { src: `${baseUrl}/plugins/zzz-last-plugin.js` },
17
+ ];
18
+ }
19
+
20
+ class BoomerangRumManager {
21
+ constructor(runtimeWindow = window) {
22
+ this.runtimeWindow = runtimeWindow;
23
+ // Tracks boomerang runtime lifecycle to simplify service-level decisions.
24
+ this.state = 'idle';
25
+ }
26
+ getState() {
27
+ return this.state;
28
+ }
29
+ getBoomerang() {
30
+ return this.runtimeWindow.BOOMR ?? null;
31
+ }
32
+ markReadyIfAvailable() {
33
+ // Mark as ready only when the BOOMR global has been loaded on window.
34
+ if (!this.runtimeWindow.BOOMR) {
35
+ return false;
36
+ }
37
+ this.state = 'ready';
38
+ return true;
39
+ }
40
+ initialize(options) {
41
+ const boomerang = this.runtimeWindow.BOOMR;
42
+ // Initialization cannot proceed until boomerang exposes its init API.
43
+ if (!boomerang?.init) {
44
+ this.state = 'error';
45
+ return false;
46
+ }
47
+ boomerang.init(options.config);
48
+ if (options.onBeforeBeacon) {
49
+ boomerang.subscribe('before_beacon', options.onBeforeBeacon);
50
+ }
51
+ if (options.onOnBeacon) {
52
+ boomerang.subscribe('onbeacon', options.onOnBeacon);
53
+ }
54
+ if (options.callPageReady ?? true) {
55
+ // Trigger page_ready by default so initial navigation metrics are emitted.
56
+ boomerang.page_ready();
57
+ }
58
+ this.state = 'initialized';
59
+ return true;
60
+ }
61
+ }
62
+
63
+ const DEFAULT_TIMEOUT_MS = 5_000;
64
+ const DEFAULT_SCRIPT_ATTRIBUTE = 'data-boomerang-script';
65
+ function existingScript(selector) {
66
+ return document.querySelector(selector);
67
+ }
68
+ function appendScript(script, scriptAttribute) {
69
+ return new Promise((resolve, reject) => {
70
+ const el = document.createElement('script');
71
+ // Keep sync execution order so plugin dependencies are evaluated in sequence.
72
+ el.src = script.src;
73
+ el.async = false;
74
+ el.defer = false;
75
+ el.setAttribute(scriptAttribute, 'true');
76
+ if (script.integrity) {
77
+ el.integrity = script.integrity;
78
+ el.crossOrigin = script.crossOrigin ?? 'anonymous';
79
+ }
80
+ el.onload = () => resolve();
81
+ el.onerror = () => reject(new Error(`[ngx-boomerangjs] Failed to load script: ${script.src}`));
82
+ document.head.appendChild(el);
83
+ });
84
+ }
85
+ async function loadScriptsInOrder(scripts, options) {
86
+ const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
87
+ const scriptAttribute = options?.scriptAttribute ?? DEFAULT_SCRIPT_ATTRIBUTE;
88
+ for (const script of scripts) {
89
+ // Skip script injection when the same source already exists in the document.
90
+ const selector = `script[src="${script.src}"]`;
91
+ if (existingScript(selector)) {
92
+ continue;
93
+ }
94
+ // Race the load promise against a timeout to avoid hanging forever on blocked networks.
95
+ await Promise.race([
96
+ appendScript(script, scriptAttribute),
97
+ new Promise((_, reject) => {
98
+ window.setTimeout(() => {
99
+ reject(new Error(`[ngx-boomerangjs] Timeout loading script: ${script.src}`));
100
+ }, timeoutMs);
101
+ }),
102
+ ]);
103
+ }
104
+ }
105
+
106
+ const BOOMERANG_CONFIG = new InjectionToken('BOOMERANG_CONFIG');
107
+
108
+ class BoomerangMetricsService {
109
+ constructor() {
110
+ this.config = inject(BOOMERANG_CONFIG);
111
+ this.manager = new BoomerangRumManager();
112
+ this.initialized = false;
113
+ }
114
+ async init() {
115
+ // Avoid duplicate setup and allow feature-level disablement
116
+ if (!this.config.enabled || this.initialized) {
117
+ return;
118
+ }
119
+ // User can use custom scripts providing it in config.scripts; otherwise load the default boomerang bundle
120
+ const scripts = this.config.scripts ?? createDefaultBoomerangScripts(this.config.scriptBaseUrl);
121
+ await loadScriptsInOrder(scripts, {
122
+ timeoutMs: this.config.scriptLoadTimeoutMs,
123
+ });
124
+ // Resolve source IP once and keep a safe fallback for environments where lookup fails.
125
+ // User can also choose to disable this feature by omitting sourceIpFactory and sourceIpVarName from config,
126
+ // in which case the default IP will be used without any lookup attempt.
127
+ const sourceIp = this.config.sourceIpFactory ? await this.config.sourceIpFactory().catch(() => '127.0.0.1') : '127.0.0.1';
128
+ const sourceIpVarName = this.config.sourceIpVarName ?? 'source_ip';
129
+ // Attach source IP variable right before beacon send, and clean them up immediately after
130
+ const initialized = this.manager.initialize({
131
+ config: this.config.boomerangConfig,
132
+ onBeforeBeacon: () => {
133
+ this.manager.getBoomerang()?.addVar(sourceIpVarName, sourceIp);
134
+ },
135
+ onOnBeacon: () => {
136
+ this.manager.getBoomerang()?.removeVar(sourceIpVarName);
137
+ },
138
+ callPageReady: true,
139
+ });
140
+ if (!initialized) {
141
+ throw new Error('[ngx-boomerangjs] BOOMR is unavailable after script load. Verify CSP/AdBlock constraints.');
142
+ }
143
+ this.initialized = true;
144
+ }
145
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: BoomerangMetricsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
146
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: BoomerangMetricsService, providedIn: 'root' }); }
147
+ }
148
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: BoomerangMetricsService, decorators: [{
149
+ type: Injectable,
150
+ args: [{ providedIn: 'root' }]
151
+ }] });
152
+
153
+ function provideBoomerangMetrics(config) {
154
+ return makeEnvironmentProviders([
155
+ {
156
+ provide: BOOMERANG_CONFIG,
157
+ useValue: config,
158
+ },
159
+ provideAppInitializer(() => inject(BoomerangMetricsService).init()),
160
+ ]);
161
+ }
162
+
163
+ // Angular provider & service
164
+
165
+ /**
166
+ * Generated bundle index. Do not edit.
167
+ */
168
+
169
+ export { BOOMERANG_CONFIG, BoomerangMetricsService, BoomerangRumManager, createDefaultBoomerangScripts, loadScriptsInOrder, provideBoomerangMetrics };
170
+ //# sourceMappingURL=ngx-boomerangjs.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ngx-boomerangjs.mjs","sources":["../../src/default-scripts.ts","../../src/manager.ts","../../src/script-loader.ts","../../src/tokens.ts","../../src/boomerang-metrics.service.ts","../../src/providers.ts","../../src/public-api.ts","../../src/ngx-boomerangjs.ts"],"sourcesContent":["import type { ScriptDescriptor } from './types';\r\n\r\nconst DEFAULT_BASE = '/assets/boomerang';\r\n\r\n// These are the original boomerang scripts, but without integrity or crossOrigin attributes since those can vary based on hosting and build setup.\r\n// Users can customize the script list by providing their own in the config if needed.\r\nexport function createDefaultBoomerangScripts(baseUrl = DEFAULT_BASE): ScriptDescriptor[] {\r\n return [\r\n { src: `${baseUrl}/boomerang.js` },\r\n { src: `${baseUrl}/plugins/rt.js` },\r\n { src: `${baseUrl}/plugins/auto-xhr.js` },\r\n { src: `${baseUrl}/plugins/errors.js` },\r\n { src: `${baseUrl}/plugins/memory.js` },\r\n { src: `${baseUrl}/plugins/painttiming.js` },\r\n { src: `${baseUrl}/plugins/navtiming.js` },\r\n { src: `${baseUrl}/plugins/zzz-last-plugin.js` },\r\n ];\r\n}\r\n","import type { BoomerangGlobal, BoomerangRuntimeWindow, BoomrConfig } from './types';\r\n\r\nexport type BoomerangRuntimeState = 'idle' | 'ready' | 'initialized' | 'error';\r\n\r\nexport interface InitializeBoomerangOptions {\r\n config: BoomrConfig;\r\n onBeforeBeacon?: () => void;\r\n onOnBeacon?: () => void;\r\n callPageReady?: boolean;\r\n}\r\n\r\nexport class BoomerangRumManager {\r\n // Tracks boomerang runtime lifecycle to simplify service-level decisions.\r\n private state: BoomerangRuntimeState = 'idle';\r\n\r\n constructor(private readonly runtimeWindow: BoomerangRuntimeWindow = window) {}\r\n\r\n getState(): BoomerangRuntimeState {\r\n return this.state;\r\n }\r\n\r\n getBoomerang(): BoomerangGlobal | null {\r\n return this.runtimeWindow.BOOMR ?? null;\r\n }\r\n\r\n markReadyIfAvailable(): boolean {\r\n // Mark as ready only when the BOOMR global has been loaded on window.\r\n if (!this.runtimeWindow.BOOMR) {\r\n return false;\r\n }\r\n\r\n this.state = 'ready';\r\n return true;\r\n }\r\n\r\n initialize(options: InitializeBoomerangOptions): boolean {\r\n const boomerang = this.runtimeWindow.BOOMR;\r\n // Initialization cannot proceed until boomerang exposes its init API.\r\n if (!boomerang?.init) {\r\n this.state = 'error';\r\n return false;\r\n }\r\n\r\n boomerang.init(options.config);\r\n\r\n if (options.onBeforeBeacon) {\r\n boomerang.subscribe('before_beacon', options.onBeforeBeacon);\r\n }\r\n\r\n if (options.onOnBeacon) {\r\n boomerang.subscribe('onbeacon', options.onOnBeacon);\r\n }\r\n\r\n if (options.callPageReady ?? true) {\r\n // Trigger page_ready by default so initial navigation metrics are emitted.\r\n boomerang.page_ready();\r\n }\r\n\r\n this.state = 'initialized';\r\n return true;\r\n }\r\n}\r\n","import type { LoadScriptsOptions, ScriptDescriptor } from './types';\r\n\r\nconst DEFAULT_TIMEOUT_MS = 5_000;\r\nconst DEFAULT_SCRIPT_ATTRIBUTE = 'data-boomerang-script';\r\n\r\nfunction existingScript(selector: string): HTMLScriptElement | null {\r\n return document.querySelector<HTMLScriptElement>(selector);\r\n}\r\n\r\nfunction appendScript(script: ScriptDescriptor, scriptAttribute: string): Promise<void> {\r\n return new Promise((resolve, reject) => {\r\n const el = document.createElement('script');\r\n // Keep sync execution order so plugin dependencies are evaluated in sequence.\r\n el.src = script.src;\r\n el.async = false;\r\n el.defer = false;\r\n el.setAttribute(scriptAttribute, 'true');\r\n\r\n if (script.integrity) {\r\n el.integrity = script.integrity;\r\n el.crossOrigin = script.crossOrigin ?? 'anonymous';\r\n }\r\n\r\n el.onload = () => resolve();\r\n el.onerror = () => reject(new Error(`[ngx-boomerangjs] Failed to load script: ${script.src}`));\r\n\r\n document.head.appendChild(el);\r\n });\r\n}\r\n\r\nexport async function loadScriptsInOrder(scripts: ScriptDescriptor[], options?: LoadScriptsOptions): Promise<void> {\r\n const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;\r\n const scriptAttribute = options?.scriptAttribute ?? DEFAULT_SCRIPT_ATTRIBUTE;\r\n\r\n for (const script of scripts) {\r\n // Skip script injection when the same source already exists in the document.\r\n const selector = `script[src=\"${script.src}\"]`;\r\n if (existingScript(selector)) {\r\n continue;\r\n }\r\n\r\n // Race the load promise against a timeout to avoid hanging forever on blocked networks.\r\n await Promise.race([\r\n appendScript(script, scriptAttribute),\r\n new Promise<never>((_, reject) => {\r\n window.setTimeout(() => {\r\n reject(new Error(`[ngx-boomerangjs] Timeout loading script: ${script.src}`));\r\n }, timeoutMs);\r\n }),\r\n ]);\r\n }\r\n}\r\n","import { InjectionToken } from '@angular/core';\r\nimport type { BoomrConfig, ScriptDescriptor } from './types';\r\n\r\nexport interface NgxBoomerangjsConfig {\r\n enabled: boolean;\r\n boomerangConfig: BoomrConfig;\r\n scripts?: ScriptDescriptor[];\r\n scriptBaseUrl?: string;\r\n scriptLoadTimeoutMs?: number;\r\n sourceIpVarName?: string;\r\n sourceIpFactory?: () => Promise<string>;\r\n}\r\n\r\nexport const BOOMERANG_CONFIG = new InjectionToken<NgxBoomerangjsConfig>('BOOMERANG_CONFIG');\r\n","import { inject, Injectable } from '@angular/core';\r\nimport { createDefaultBoomerangScripts } from './default-scripts';\r\nimport { BoomerangRumManager } from './manager';\r\nimport { loadScriptsInOrder } from './script-loader';\r\nimport { BOOMERANG_CONFIG } from './tokens';\r\n\r\n@Injectable({ providedIn: 'root' })\r\nexport class BoomerangMetricsService {\r\n private readonly config = inject(BOOMERANG_CONFIG);\r\n \r\n private readonly manager = new BoomerangRumManager();\r\n private initialized = false;\r\n\r\n async init(): Promise<void> {\r\n // Avoid duplicate setup and allow feature-level disablement\r\n if (!this.config.enabled || this.initialized) {\r\n return;\r\n }\r\n\r\n // User can use custom scripts providing it in config.scripts; otherwise load the default boomerang bundle\r\n const scripts = this.config.scripts ?? createDefaultBoomerangScripts(this.config.scriptBaseUrl);\r\n\r\n await loadScriptsInOrder(scripts, {\r\n timeoutMs: this.config.scriptLoadTimeoutMs,\r\n });\r\n\r\n // Resolve source IP once and keep a safe fallback for environments where lookup fails.\r\n // User can also choose to disable this feature by omitting sourceIpFactory and sourceIpVarName from config,\r\n // in which case the default IP will be used without any lookup attempt.\r\n const sourceIp = this.config.sourceIpFactory ? await this.config.sourceIpFactory().catch(() => '127.0.0.1') : '127.0.0.1';\r\n const sourceIpVarName = this.config.sourceIpVarName ?? 'source_ip';\r\n\r\n // Attach source IP variable right before beacon send, and clean them up immediately after\r\n const initialized = this.manager.initialize({\r\n config: this.config.boomerangConfig,\r\n onBeforeBeacon: () => {\r\n this.manager.getBoomerang()?.addVar(sourceIpVarName, sourceIp);\r\n },\r\n onOnBeacon: () => {\r\n this.manager.getBoomerang()?.removeVar(sourceIpVarName);\r\n },\r\n callPageReady: true,\r\n });\r\n\r\n if (!initialized) {\r\n throw new Error('[ngx-boomerangjs] BOOMR is unavailable after script load. Verify CSP/AdBlock constraints.');\r\n }\r\n\r\n this.initialized = true;\r\n }\r\n}\r\n","import { inject, makeEnvironmentProviders, provideAppInitializer, type EnvironmentProviders } from '@angular/core';\r\nimport { BoomerangMetricsService } from './boomerang-metrics.service';\r\nimport { BOOMERANG_CONFIG, type NgxBoomerangjsConfig } from './tokens';\r\n\r\nexport function provideBoomerangMetrics(config: NgxBoomerangjsConfig): EnvironmentProviders {\r\n return makeEnvironmentProviders([\r\n {\r\n provide: BOOMERANG_CONFIG,\r\n useValue: config,\r\n },\r\n provideAppInitializer(() => inject(BoomerangMetricsService).init()),\r\n ]);\r\n}\r\n","// Angular provider & service\r\nexport { BoomerangMetricsService } from './boomerang-metrics.service';\r\nexport { createDefaultBoomerangScripts } from './default-scripts';\r\nexport { provideBoomerangMetrics } from './providers';\r\nexport { BOOMERANG_CONFIG } from './tokens';\r\nexport type { NgxBoomerangjsConfig } from './tokens';\r\n\r\n// Core types — useful for advanced usage and custom script config\r\nexport { BoomerangRumManager } from './manager';\r\nexport type { BoomerangRuntimeState, InitializeBoomerangOptions } from './manager';\r\nexport { loadScriptsInOrder } from './script-loader';\r\nexport type {\r\n BoomerangGlobal, BoomerangRuntimeWindow, BoomrAutoXhrConfig, BoomrConfig, BoomrErrorsConfig, BoomrHistoryConfig, BoomrSpaConfig, LoadScriptsOptions, ScriptDescriptor\r\n} from './types';\r\n\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "ngx-boomerangjs",
3
+ "version": "1.0.0",
4
+ "description": "Angular 21+ wrapper for boomerangjs with automatic script loading and RUM support",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "publishConfig": {
8
+ "access": "public"
9
+ },
10
+ "keywords": [
11
+ "angular",
12
+ "boomerang",
13
+ "boomerangjs",
14
+ "rum",
15
+ "real-user-monitoring",
16
+ "ngx",
17
+ "performance"
18
+ ],
19
+ "peerDependencies": {
20
+ "@angular/common": "^21.0.0",
21
+ "@angular/core": "^21.0.0",
22
+ "boomerangjs": "^1.815.1"
23
+ },
24
+ "dependencies": {
25
+ "tslib": "^2.8.1"
26
+ },
27
+ "module": "fesm2022/ngx-boomerangjs.mjs",
28
+ "typings": "types/ngx-boomerangjs.d.ts",
29
+ "exports": {
30
+ "./package.json": {
31
+ "default": "./package.json"
32
+ },
33
+ ".": {
34
+ "types": "./types/ngx-boomerangjs.d.ts",
35
+ "default": "./fesm2022/ngx-boomerangjs.mjs"
36
+ }
37
+ },
38
+ "sideEffects": false
39
+ }
@@ -0,0 +1,112 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, EnvironmentProviders } from '@angular/core';
3
+
4
+ declare class BoomerangMetricsService {
5
+ private readonly config;
6
+ private readonly manager;
7
+ private initialized;
8
+ init(): Promise<void>;
9
+ static ɵfac: i0.ɵɵFactoryDeclaration<BoomerangMetricsService, never>;
10
+ static ɵprov: i0.ɵɵInjectableDeclaration<BoomerangMetricsService>;
11
+ }
12
+
13
+ interface BoomrAutoXhrConfig {
14
+ enabled?: boolean;
15
+ monitorFetch?: boolean;
16
+ alwaysSendXhr?: boolean;
17
+ xhrRequireChanges?: boolean;
18
+ }
19
+ interface BoomrErrorsConfig {
20
+ enabled?: boolean;
21
+ monitorRejections?: boolean;
22
+ monitorConsole?: boolean;
23
+ sendAfterOnload?: boolean;
24
+ sendInterval?: number;
25
+ maxErrors?: number;
26
+ }
27
+ interface BoomrSpaConfig {
28
+ enabled?: boolean;
29
+ singlePageApp?: boolean;
30
+ }
31
+ interface BoomrHistoryConfig {
32
+ enabled?: boolean;
33
+ }
34
+ interface BoomrConfig {
35
+ beacon_url: string;
36
+ beacon_type?: 'GET' | 'POST' | 'AUTO';
37
+ autorun?: boolean;
38
+ log?: ((msg: unknown, level: string, source: string) => void) | null;
39
+ instrument_xhr?: boolean;
40
+ ResourceTiming?: {
41
+ enabled?: boolean;
42
+ clearOnBeacon?: boolean;
43
+ };
44
+ NavigationTiming?: Record<string, unknown>;
45
+ PaintTiming?: Record<string, unknown>;
46
+ Memory?: Record<string, unknown>;
47
+ AutoXHR?: BoomrAutoXhrConfig;
48
+ Errors?: BoomrErrorsConfig;
49
+ SPA?: BoomrSpaConfig;
50
+ History?: BoomrHistoryConfig;
51
+ [key: string]: unknown;
52
+ }
53
+ interface BoomerangGlobal {
54
+ t_end: number;
55
+ debug: (...args: unknown[]) => void;
56
+ init(config: BoomrConfig): BoomerangGlobal;
57
+ page_ready(): void;
58
+ subscribe(event: string, callback: (...args: unknown[]) => void): BoomerangGlobal;
59
+ addVar(name: string, value: unknown): BoomerangGlobal;
60
+ removeVar(...names: string[]): BoomerangGlobal;
61
+ sendBeacon(): void;
62
+ }
63
+ interface ScriptDescriptor {
64
+ src: string;
65
+ integrity?: string;
66
+ crossOrigin?: 'anonymous' | 'use-credentials';
67
+ }
68
+ interface LoadScriptsOptions {
69
+ timeoutMs?: number;
70
+ scriptAttribute?: string;
71
+ }
72
+ type BoomerangRuntimeWindow = Window & {
73
+ BOOMR?: BoomerangGlobal;
74
+ };
75
+
76
+ declare function createDefaultBoomerangScripts(baseUrl?: string): ScriptDescriptor[];
77
+
78
+ interface NgxBoomerangjsConfig {
79
+ enabled: boolean;
80
+ boomerangConfig: BoomrConfig;
81
+ scripts?: ScriptDescriptor[];
82
+ scriptBaseUrl?: string;
83
+ scriptLoadTimeoutMs?: number;
84
+ sourceIpVarName?: string;
85
+ sourceIpFactory?: () => Promise<string>;
86
+ }
87
+ declare const BOOMERANG_CONFIG: InjectionToken<NgxBoomerangjsConfig>;
88
+
89
+ declare function provideBoomerangMetrics(config: NgxBoomerangjsConfig): EnvironmentProviders;
90
+
91
+ type BoomerangRuntimeState = 'idle' | 'ready' | 'initialized' | 'error';
92
+ interface InitializeBoomerangOptions {
93
+ config: BoomrConfig;
94
+ onBeforeBeacon?: () => void;
95
+ onOnBeacon?: () => void;
96
+ callPageReady?: boolean;
97
+ }
98
+ declare class BoomerangRumManager {
99
+ private readonly runtimeWindow;
100
+ private state;
101
+ constructor(runtimeWindow?: BoomerangRuntimeWindow);
102
+ getState(): BoomerangRuntimeState;
103
+ getBoomerang(): BoomerangGlobal | null;
104
+ markReadyIfAvailable(): boolean;
105
+ initialize(options: InitializeBoomerangOptions): boolean;
106
+ }
107
+
108
+ declare function loadScriptsInOrder(scripts: ScriptDescriptor[], options?: LoadScriptsOptions): Promise<void>;
109
+
110
+ export { BOOMERANG_CONFIG, BoomerangMetricsService, BoomerangRumManager, createDefaultBoomerangScripts, loadScriptsInOrder, provideBoomerangMetrics };
111
+ export type { BoomerangGlobal, BoomerangRuntimeState, BoomerangRuntimeWindow, BoomrAutoXhrConfig, BoomrConfig, BoomrErrorsConfig, BoomrHistoryConfig, BoomrSpaConfig, InitializeBoomerangOptions, LoadScriptsOptions, NgxBoomerangjsConfig, ScriptDescriptor };
112
+ //# sourceMappingURL=ngx-boomerangjs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ngx-boomerangjs.d.ts","sources":["../../src/boomerang-metrics.service.ts","../../src/types.ts","../../src/default-scripts.ts","../../src/tokens.ts","../../src/providers.ts","../../src/manager.ts","../../src/script-loader.ts"],"mappings":";;;AAMA,cACa,uBAAuB;AAClC;AAEA;;AAGM,YAAQ,OAAO;yCANV,uBAAuB;6CAAvB,uBAAuB;AA2CnC;;UClDgB,kBAAkB;;;;;AAKlC;UAEgB,iBAAiB;;;;;;;AAOjC;UAEgB,cAAc;;;AAG9B;UAEgB,kBAAkB;;AAElC;UAEgB,WAAW;;AAE1B;;;;AAIA;;;;uBACmB,MAAM;kBACX,MAAM;aACX,MAAM;cACL,kBAAkB;aACnB,iBAAiB;UACpB,cAAc;cACV,kBAAkB;AAC5B;AACD;UAEgB,eAAe;;;AAG9B,iBAAa,WAAW,GAAG,eAAe;;AAE1C,sEAAkE,eAAe;0CAC3C,eAAe;mCACtB,eAAe;;AAE/C;UAEgB,gBAAgB;;;AAG/B;AACD;UAEgB,kBAAkB;;;AAGlC;AAEK,KAAM,sBAAsB,GAAG,MAAM;YACjC,eAAe;;;AC3DzB,iBAAgB,6BAA6B,oBAA0B,gBAAgB;;UCHtE,oBAAoB;;qBAElB,WAAW;AAC5B,cAAU,gBAAgB;;;;AAI1B,4BAAwB,OAAO;AAChC;AAED,cAAa,gBAAgB,EAAA,cAAA,CAAA,oBAAA;;ACT7B,iBAAgB,uBAAuB,SAAS,oBAAoB,GAAG,oBAAoB;;ACFrF,KAAM,qBAAqB;UAEhB,0BAA0B;YACjC,WAAW;AACnB;AACA;;AAED;AAED,cAAa,mBAAmB;;;AAID,gCAAe,sBAA+B;AAE3E,gBAAY,qBAAqB;oBAIjB,eAAe;AAI/B;AAUA,wBAAoB,0BAA0B;AA0B/C;;AC/BD,iBAAsB,kBAAkB,UAAU,gBAAgB,cAAc,kBAAkB,GAAG,OAAO;;;;","names":[]}