@bloomscorp/ngx-bloomsight 1.5.1 → 2.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.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) [2024] [Bloomscorp]
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,144 +1,276 @@
1
- # ngx-bloomsight
2
-
3
- A client side angular library to work with bloomsight.io
4
-
5
- ## Badges
6
-
7
- [![ License](https://img.shields.io/badge/License-MIT-green.svg)](./LICENSE.md)
8
-
9
- ## Installation
10
-
11
- Install using `npm` by running
12
-
13
- ```bash
14
- npm install @bloomscorp/ngx-bloomsight
15
- ```
16
-
17
- ## Usage/Examples
18
-
19
-
20
- ### Configuration
21
-
22
- Use the `app.module.ts` to configure the library.
23
-
24
- ```ts
25
- import {NgxBloomsightModule} from '@bloomscorp/ngx-bloomsight';
26
-
27
- @NgModule({
28
- declarations: [AppComponent],
29
- imports: [
30
- BrowserModule,
31
- AppRoutingModule,
32
- NgxBloomsightModule.forRoot({
33
- propertyToken: '65143f755c403e030c222340',
34
- isDevelopmentMode: true,
35
- stopAll: false,
36
- ...
37
- }),
38
- ],
39
- providers: [],
40
- bootstrap: [AppComponent]
41
- })
42
- export class AppModule { }
43
- ```
44
-
45
- Below is the table with all the possible options that can be configured.
46
-
47
- | Option | Type | Description | Mandatory | Default |
48
- |:-----------------------|:----------|:--------------------------------------------------------------------------------------------------------------------------------------------|:----------|:--------|
49
- | `propertyToken` | `string` | [bloomsight.io](https://bloomsight.io) property token | ✅ | NA |
50
- | `isDevelopmentMode` | `boolean` | if `true`, then logs data in the browser console | ✅ | NA |
51
- | `observePageViaWebAPI` | `boolean` | if `true`, will detect page view event based on web APIs. However, if `false`, `pageViewObserver` should be used to trigger page view event | ❌ | `true` |
52
- | `stopSimpleEvent` | `boolean` | stops only simple event tracking | ❌ | `false` |
53
- | `stopDataEvent` | `boolean` | stops only data event tracking | ❌ | `false` |
54
- | `stopPageViewEvent` | `boolean` | stops only page view event tracking | ❌ | `false` |
55
- | `stopAll` | `boolean` | stops all event tracking | ❌ | `false` |
56
- | `logOnly` | `boolean` | tracks event but doesn't save to database. Should be used only when `isDevelopmentMode: true` | ❌ | `false` |
57
-
58
-
59
- ## Page View Event
60
- To tracking website visits for your angular application just initialize `BloomsightEventService` service in `app.component.ts`.
61
-
62
- ```ts
63
- import {BloomsightEventService} from '@bloomscorp/ngx-bloomsight';
64
-
65
- constructor(public bloomsightService: BloomsightEventService) { }
66
- ```
67
-
68
- ### Simple Event
69
-
70
- Use the `resolveSimpleEvent` method to log simple events
71
-
72
- ```ts
73
- import {BloomsightEventService} from '@bloomscorp/ngx-bloomsight';
74
-
75
- constructor(public bloomsightService: BloomsightEventService) { }
76
-
77
- this.bloomsightService
78
- .resolveSimpleEvent('65d735b122354c8ba6a489c2', 'Contact Us CTA button');
79
- ```
80
- | Option | Type | Description | Mandatory | Default |
81
- |:-------------|:---------|:-------------------|:----------|:--------|
82
- | `eventToken` | `string` | Id of simple event | ✅ | NA |
83
- | `label` | `string` | a label/name for the event for future reference | ❌ | '' |
84
-
85
-
86
- ### Data Event
87
-
88
- Use the `resolveDataEvent` method to log data events
89
-
90
- ```ts
91
- import {BloomsightEventService} from '@bloomscorp/ngx-bloomsight';
92
-
93
- constructor(public bloomsightService: BloomsightEventService) { }
94
-
95
- this.bloomsightService.resolveDataEvent(
96
- '66d735b122355c8ba6a456f8',
97
- {
98
- productId: 120,
99
- sku: 'PROD021298'
100
- },
101
- 'Add to Wishlist CTA button'
102
- );
103
- ```
104
- | Option | Type | Description | Mandatory | Default |
105
- |:-------------|:---------|:-----------------------------------------|:----------|:--------|
106
- | `eventToken` | `string` | Id of data event | ✅ | NA |
107
- | `eventData` | `Object` | additional metadata in key, value format | ✅ | NA |
108
- | `label` | `string` | a label/name for the event for future reference | ❌ | '' |
109
-
110
-
111
- ### Send Email
112
-
113
- Use `sendEmail` method to send email
114
-
115
- ```ts
116
- import {BloomsightMailService} from '@bloomscorp/ngx-bloomsight';
117
-
118
- constructor(public bloomsightMailService: BloomsightMailService) { }
119
-
120
- this.bloomsightMailService.sendEmail(
121
- '66d735b122355c8ba6a456f8',
122
- '63d735h822355s6ba6a4556k',
123
- '61d895h922355b6ba6a4587a',
124
- ...
125
- )
126
- ```
127
-
128
- | Option | Type | Description | Mandatory | Default |
129
- |:------------------|:-----------|:-------------------------------------------------------------------------------------------|:----------|:--------|
130
- | `engineId` | `string` | Id of the engine that will be used to send email | ✅ | NA |
131
- | `templateId` | `string` | Id of the email template that will be used as email body | ✅ | NA |
132
- | `templateOwnerId` | `string` | Id of the user who has created the template | ✅ | NA |
133
- | `emailMetaData` | `FormData` | attachment & variables used in the template & their respective values in key, value format | ✅ | NA |
134
- | `onSuccess` | `function` | pass a function to trigger on successfully sending email | ✅ | NA |
135
- | `onError` | `function` | pass a function to trigger when an error occurs | ✅ | NA |
136
-
137
- ## License
138
-
139
- [MIT](./LICENSE.md)
140
-
141
-
142
- ## Support
143
-
144
- Report issues or feature requests [here]()
1
+ # @bloomscorp/ngx-bloomsight
2
+
3
+ Bloomsight analytics for Angular: events, page views, mail and failure
4
+ reporting, through one provider and one service.
5
+
6
+ It wraps [`@bloomscorp/bloomsight.js`](https://www.npmjs.com/package/@bloomscorp/bloomsight.js)
7
+ and works with both backends: the standalone Bloomsight server, and Bloomsight
8
+ inside blooms.ai.
9
+
10
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://github.com/bloomscorp/ngx-bloomsight/blob/main/LICENSE.md)
11
+
12
+ > **Upgrading from 1.x?** 2.0.0 replaces `NgxBloomsightModule.forRoot()` and the
13
+ > two services. See the [migration guide](https://github.com/bloomscorp/ngx-bloomsight/blob/main/MIGRATION.md).
14
+
15
+ ## Requirements
16
+
17
+ | | Supported |
18
+ |---|---|
19
+ | Angular (`@angular/core`, `common`, `router`) | **19, 20, 21, 22**. CI compiles the published package with each of them |
20
+ | `@bloomscorp/bloomsight.js` | `>=0.9.2 <1.0.0` |
21
+ | RxJS | 7 or 8 |
22
+
23
+ ## Install
24
+
25
+ ```bash
26
+ npm install @bloomscorp/ngx-bloomsight @bloomscorp/bloomsight.js
27
+ ```
28
+
29
+ `@bloomscorp/bloomsight.js` is a peer dependency: install it, but never import
30
+ it. Everything your app needs comes from `@bloomscorp/ngx-bloomsight` (see
31
+ [Types](#types)).
32
+
33
+ ## Set up
34
+
35
+ Call `provideBloomsight()` once, in the application's root providers:
36
+
37
+ ```ts
38
+ // app.config.ts
39
+ import {ApplicationConfig} from '@angular/core';
40
+ import {provideRouter} from '@angular/router';
41
+ import {provideBloomsight} from '@bloomscorp/ngx-bloomsight';
42
+
43
+ export const appConfig: ApplicationConfig = {
44
+ providers: [
45
+ provideRouter(routes),
46
+ provideBloomsight({
47
+ propertyToken: '65d72f0b5e990c6028790156',
48
+ isDevelopmentMode: false
49
+ })
50
+ ]
51
+ };
52
+ ```
53
+
54
+ An app that still bootstraps an `AppModule` puts the same call in that module's
55
+ `providers`:
56
+
57
+ ```ts
58
+ @NgModule({
59
+ // …
60
+ providers: [provideBloomsight({propertyToken: '…', isDevelopmentMode: false})]
61
+ })
62
+ export class AppModule {}
63
+ ```
64
+
65
+ That is all the setup. The SDK starts when the app starts, page views are
66
+ recorded from the router, and `BloomsightService` can be injected anywhere,
67
+ including lazy-loaded routes.
68
+
69
+ Call it **once**, in the root providers: not in a lazy route's `providers`, and
70
+ not in a component.
71
+
72
+ ## Configuration
73
+
74
+ | Field | Type | Default | What it does |
75
+ |---|---|---|---|
76
+ | `propertyToken` | `string` | **required** | The property's token. Never regenerate it: the token is how events are attributed |
77
+ | `isDevelopmentMode` | `boolean` | **required** | Logs everything to the console, and skips checking the property against the page's host. Never ship `true` to production |
78
+ | `target` | `'bloomsight'` \| `'blooms-ai'` | `'bloomsight'` | Which backend receives events. See [Moving to blooms.ai](#moving-to-bloomsai) |
79
+ | `apiBase` | `string` | `https://z.bloomscorp.com` | blooms.ai only: the API origin, for pointing a project at staging first |
80
+ | `logOnly` | `boolean` | `false` | Runs everything and sends nothing: each event and mail is logged instead. For demos, tests and dry runs |
81
+ | `debug` | `boolean` | `false` | blooms.ai only: full logging without `isDevelopmentMode` |
82
+ | `onError` | `(error: BloomsightError) => void` | — | Called for every failure the SDK reports. See [Errors](#errors) |
83
+ | `stopAll` | `boolean` | `false` | Records nothing |
84
+ | `stopSimpleEvent` | `boolean` | `false` | Records no simple events |
85
+ | `stopDataEvent` | `boolean` | `false` | Records no data events |
86
+ | `stopPageViewEvent` | `boolean` | `false` | Records no page views |
87
+
88
+ The SDK reads its configuration once, when the app starts.
89
+
90
+ ## Events
91
+
92
+ ```ts
93
+ import {Component, inject} from '@angular/core';
94
+ import {BloomsightService} from '@bloomscorp/ngx-bloomsight';
95
+
96
+ @Component({/* … */})
97
+ export class PricingComponent {
98
+ private readonly bloomsight = inject(BloomsightService);
99
+
100
+ contactUs(): void {
101
+ this.bloomsight.track('65d735b122354c8ba6a489c2');
102
+ }
103
+
104
+ choosePlan(plan: string, seats: number): void {
105
+ this.bloomsight.trackData('66d735b122355c8ba6a456f8', {plan, seats});
106
+ }
107
+ }
108
+ ```
109
+
110
+ | Method | Records |
111
+ |---|---|
112
+ | `track(eventToken)` | a simple event |
113
+ | `trackData(eventToken, data)` | a data event, with `data` as a JSON-serialisable object |
114
+
115
+ The event token comes from the Bloomsight dashboard, where the event is defined.
116
+
117
+ ## Page views
118
+
119
+ Recorded automatically, with no code in your app:
120
+
121
+ - **Once per new destination.** A view is recorded each time a navigation lands
122
+ somewhere new, by the URL the user ends up on after redirects. Following a
123
+ link that redirects to the current page is not a new view
124
+ - **Standalone Bloomsight server only.** blooms.ai has no page-view endpoint, so
125
+ with `target: 'blooms-ai'` the router is not watched at all
126
+ - **After two seconds.** The SDK records a view only once the visitor has spent
127
+ two seconds on the page
128
+
129
+ `stopPageViewEvent: true` turns them off.
130
+
131
+ ## Mail
132
+
133
+ ```ts
134
+ import {BloomsightError, BloomsightService} from '@bloomscorp/ngx-bloomsight';
135
+
136
+ const data = new FormData();
137
+ data.append('name', form.name);
138
+ data.append('email', form.email);
139
+
140
+ this.bloomsight.sendMail({
141
+ engineId: '66d735b122355c8ba6a456f8',
142
+ templateId: '63d735h822355s6ba6a4556k',
143
+ templateOwnerId: '61d895h922355b6ba6a4587a',
144
+ data,
145
+ engine: 'GMAIL' // or 'CUSTOM_SMTP'; defaults to 'GMAIL'
146
+ }).subscribe({
147
+ next: () => this.sent.set(true),
148
+ error: (error: BloomsightError) => this.failure.set(error.message)
149
+ });
150
+ ```
151
+
152
+ | Field | Required | |
153
+ |---|---|---|
154
+ | `engineId` | ✅ | The mail engine, from the dashboard |
155
+ | `templateId` | ✅ | The template to render |
156
+ | `templateOwnerId` | ✅ | The user who owns the template |
157
+ | `data` | ✅ | The template's values; each field arrives as `metadata[<name>]` |
158
+ | `engine` | | `'GMAIL'` or `'CUSTOM_SMTP'`. Defaults to `'GMAIL'` |
159
+
160
+ The result behaves like `HttpClient`:
161
+
162
+ - **Nothing is sent until you subscribe**, and each subscription sends once
163
+ - It emits once and completes when the server accepts the mail
164
+ - It errors with a `BloomsightError` (`code: 'MAIL_FAILED'`) when the server
165
+ rejects it or cannot be reached
166
+ - Under `logOnly` the mail is logged, not sent, and the result still emits and
167
+ completes, so a "sending…" state is always released
168
+
169
+ Mail always goes to the standalone Bloomsight server, whatever `target` is set
170
+ to.
171
+
172
+ ## Errors
173
+
174
+ On blooms.ai, every failed request is reported, with the server's own code
175
+ where there is one (`ORIGIN_REJECTED`, `UNKNOWN_PROPERTY`, `NETWORK_FAILURE`,
176
+ …). There are two ways to receive them:
177
+
178
+ ```ts
179
+ // 1. In the configuration. Called for every failure, including at startup.
180
+ provideBloomsight({
181
+ propertyToken: '…',
182
+ isDevelopmentMode: false,
183
+ target: 'blooms-ai',
184
+ onError: (error) => monitoring.report(error)
185
+ });
186
+
187
+ // 2. As a stream, anywhere.
188
+ inject(BloomsightService).errors$.subscribe((error) => console.warn(error.code, error.message));
189
+
190
+ // …or the latest one as a signal, for a template.
191
+ readonly lastError = toSignal(inject(BloomsightService).errors$);
192
+ ```
193
+
194
+ - **`errors$` only sees what comes after you subscribe.** It emits the failures
195
+ that happen after that point, not earlier ones
196
+ - **A failed start doesn't stop your app.** If the SDK fails to start, the app
197
+ keeps running without it. The failure (`INIT_FAILED`) is reported to `onError`
198
+ and the console, on either target. It is not on `errors$`, because startup
199
+ happens before anything can subscribe
200
+ - **Mail failures stay with the mail.** They are the error of that `sendMail()`
201
+ call, not on `errors$`
202
+ - **The standalone server reports almost nothing.** There the SDK reports no
203
+ request failures; only `INIT_FAILED` reaches `onError`
204
+
205
+ ## Moving to blooms.ai
206
+
207
+ Change the target. The property token and every call site stay the same:
208
+
209
+ ```ts
210
+ provideBloomsight({
211
+ propertyToken: '65d72f0b5e990c6028790156', // unchanged
212
+ isDevelopmentMode: false,
213
+ target: 'blooms-ai',
214
+ apiBase: 'https://staging.example.com', // optional: try staging first
215
+ onError: (error) => monitoring.report(error)
216
+ });
217
+ ```
218
+
219
+ On blooms.ai:
220
+
221
+ - events go to blooms.ai, and never to the standalone server
222
+ - page views are not collected
223
+ - mail is unchanged: it still goes to the standalone server
224
+ - failures are reported through `onError` and `errors$`
225
+
226
+ ## Server-side rendering
227
+
228
+ Nothing runs on the server. The SDK starts in the browser, `track` and
229
+ `trackData` do nothing during server rendering, and `sendMail` completes without
230
+ sending.
231
+
232
+ ## Types
233
+
234
+ Everything is exported from `@bloomscorp/ngx-bloomsight`:
235
+
236
+ | Export | |
237
+ |---|---|
238
+ | `provideBloomsight(config)` | Sets up the SDK |
239
+ | `BloomsightService` | `track`, `trackData`, `sendMail`, `errors$` |
240
+ | `BLOOMSIGHT_CONFIG` | Injection token holding the configuration the app started with |
241
+ | `BloomsightConfig` | The configuration |
242
+ | `BloomsightError` | `{code, message, status?, eventToken?}` |
243
+ | `BloomsightEmailEngine` | `'GMAIL' \| 'CUSTOM_SMTP'` |
244
+ | `BloomsightMailRequest` | What `sendMail` takes |
245
+ | `BloomsightEventData` | What `trackData` takes |
246
+
247
+ **Don't import `@bloomscorp/bloomsight.js` in your app.** If something you need
248
+ is not exported here, that's a gap in this library: please
249
+ [open an issue](https://github.com/bloomscorp/ngx-bloomsight/issues).
250
+
251
+ ## Testing your app
252
+
253
+ Use `logOnly` in tests, so nothing is sent:
254
+
255
+ ```ts
256
+ TestBed.configureTestingModule({
257
+ providers: [provideBloomsight({propertyToken: 'test', isDevelopmentMode: false, logOnly: true})]
258
+ });
259
+ ```
260
+
261
+ In a jsdom test environment (Vitest or Jest), the SDK reads `window.matchMedia`
262
+ at startup, and jsdom doesn't have it. Your tests still pass, but the SDK
263
+ reports `INIT_FAILED` and stays off. To run it for real, stub `matchMedia` in
264
+ your test setup file:
265
+
266
+ ```ts
267
+ window.matchMedia ??= (query: string) => ({
268
+ matches: false, media: query, onchange: null,
269
+ addListener: () => {}, removeListener: () => {},
270
+ addEventListener: () => {}, removeEventListener: () => {}, dispatchEvent: () => false
271
+ }) as MediaQueryList;
272
+ ```
273
+
274
+ ## License
275
+
276
+ [MIT](https://github.com/bloomscorp/ngx-bloomsight/blob/main/LICENSE.md)
@@ -0,0 +1,168 @@
1
+ import * as i0 from "@angular/core";
2
+ import { DestroyRef, Injectable, InjectionToken, PLATFORM_ID, inject, makeEnvironmentProviders, provideAppInitializer } from "@angular/core";
3
+ import { isPlatformBrowser } from "@angular/common";
4
+ import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
5
+ import { NavigationEnd, Router } from "@angular/router";
6
+ import { Observable, Subject, distinctUntilChanged, filter, map } from "rxjs";
7
+ import { init, pageViewObserver, resolveDataEvent, resolveSimpleEvent, sendEmail } from "@bloomscorp/bloomsight.js";
8
+ const BLOOMSIGHT_CONFIG = new InjectionToken("BLOOMSIGHT_CONFIG");
9
+ var BloomsightErrors = class BloomsightErrors {
10
+ constructor() {
11
+ this.failures = new Subject();
12
+ this.errors$ = this.failures.asObservable();
13
+ }
14
+ report(error) {
15
+ this.failures.next(error);
16
+ }
17
+ static {
18
+ this.ɵfac = i0.ɵɵngDeclareFactory({
19
+ minVersion: "12.0.0",
20
+ version: "22.2.2",
21
+ ngImport: i0,
22
+ type: BloomsightErrors,
23
+ deps: [],
24
+ target: i0.ɵɵFactoryTarget.Injectable
25
+ });
26
+ }
27
+ static {
28
+ this.ɵprov = i0.ɵɵngDeclareInjectable({
29
+ minVersion: "12.0.0",
30
+ version: "22.2.2",
31
+ ngImport: i0,
32
+ type: BloomsightErrors,
33
+ providedIn: "root"
34
+ });
35
+ }
36
+ };
37
+ i0.ɵɵngDeclareClassMetadata({
38
+ minVersion: "12.0.0",
39
+ version: "22.2.2",
40
+ ngImport: i0,
41
+ type: BloomsightErrors,
42
+ decorators: [{
43
+ type: Injectable,
44
+ args: [{ providedIn: "root" }]
45
+ }]
46
+ });
47
+ function startBloomsight() {
48
+ if (!isPlatformBrowser(inject(PLATFORM_ID))) return;
49
+ const config = inject(BLOOMSIGHT_CONFIG);
50
+ initialise(config, inject(BloomsightErrors));
51
+ observePageViews(config, inject(Router), inject(DestroyRef));
52
+ }
53
+ function initialise(config, errors) {
54
+ try {
55
+ init({
56
+ ...config,
57
+ onError: (error) => {
58
+ errors.report(error);
59
+ config.onError?.(error);
60
+ }
61
+ });
62
+ } catch (cause) {
63
+ reportInitFailure(config, cause);
64
+ }
65
+ }
66
+ function reportInitFailure(config, cause) {
67
+ const error = {
68
+ code: "INIT_FAILED",
69
+ message: `bloomsight.js failed to initialise: ${cause instanceof Error ? cause.message : String(cause)}`
70
+ };
71
+ console.error("ngx-bloomsight:", error.message);
72
+ try {
73
+ config.onError?.(error);
74
+ } catch {}
75
+ }
76
+ function observePageViews(config, router, destroyRef) {
77
+ if (config.target === "blooms-ai") {
78
+ if (config.debug || config.isDevelopmentMode) console.log("ngx-bloomsight: page views are not collected on blooms.ai, so the router is not observed");
79
+ return;
80
+ }
81
+ router.events.pipe(filter((event) => event instanceof NavigationEnd), map((event) => event.urlAfterRedirects), distinctUntilChanged(), takeUntilDestroyed(destroyRef)).subscribe(() => pageViewObserver());
82
+ }
83
+ function provideBloomsight(config) {
84
+ return makeEnvironmentProviders([{
85
+ provide: BLOOMSIGHT_CONFIG,
86
+ useValue: config
87
+ }, provideAppInitializer(startBloomsight)]);
88
+ }
89
+ var BloomsightService = class BloomsightService {
90
+ constructor() {
91
+ this.config = requireConfig();
92
+ this.isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
93
+ this.errors$ = inject(BloomsightErrors).errors$;
94
+ }
95
+ track(eventToken) {
96
+ if (this.isBrowser) resolveSimpleEvent(eventToken);
97
+ }
98
+ trackData(eventToken, data) {
99
+ if (this.isBrowser) resolveDataEvent(eventToken, data);
100
+ }
101
+ sendMail(request) {
102
+ return new Observable((subscriber) => {
103
+ if (!this.isBrowser) {
104
+ subscriber.complete();
105
+ return;
106
+ }
107
+ const succeed = () => {
108
+ subscriber.next();
109
+ subscriber.complete();
110
+ };
111
+ const fail = (cause) => subscriber.error(mailFailure(cause));
112
+ try {
113
+ sendEmail(request.engine ?? "GMAIL", request.engineId, request.templateId, request.templateOwnerId, request.data, succeed, () => fail());
114
+ } catch (cause) {
115
+ fail(cause);
116
+ return;
117
+ }
118
+ if (this.config.logOnly) succeed();
119
+ });
120
+ }
121
+ static {
122
+ this.ɵfac = i0.ɵɵngDeclareFactory({
123
+ minVersion: "12.0.0",
124
+ version: "22.2.2",
125
+ ngImport: i0,
126
+ type: BloomsightService,
127
+ deps: [],
128
+ target: i0.ɵɵFactoryTarget.Injectable
129
+ });
130
+ }
131
+ static {
132
+ this.ɵprov = i0.ɵɵngDeclareInjectable({
133
+ minVersion: "12.0.0",
134
+ version: "22.2.2",
135
+ ngImport: i0,
136
+ type: BloomsightService,
137
+ providedIn: "root"
138
+ });
139
+ }
140
+ };
141
+ i0.ɵɵngDeclareClassMetadata({
142
+ minVersion: "12.0.0",
143
+ version: "22.2.2",
144
+ ngImport: i0,
145
+ type: BloomsightService,
146
+ decorators: [{
147
+ type: Injectable,
148
+ args: [{ providedIn: "root" }]
149
+ }]
150
+ });
151
+ function requireConfig() {
152
+ const config = inject(BLOOMSIGHT_CONFIG, { optional: true });
153
+ if (!config) throw new Error("ngx-bloomsight: BloomsightService was injected, but the application never called provideBloomsight(). Add provideBloomsight({ propertyToken, isDevelopmentMode }) to the root providers.");
154
+ return config;
155
+ }
156
+ function mailFailure(cause) {
157
+ if (cause === void 0) return {
158
+ code: "MAIL_FAILED",
159
+ message: "The mail was not sent: the server rejected it or could not be reached"
160
+ };
161
+ return {
162
+ code: "MAIL_FAILED",
163
+ message: `The mail was not sent: ${cause instanceof Error ? cause.message : String(cause)}`
164
+ };
165
+ }
166
+ export { BLOOMSIGHT_CONFIG, BloomsightService, provideBloomsight };
167
+
168
+ //# sourceMappingURL=bloomscorp-ngx-bloomsight.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bloomscorp-ngx-bloomsight.mjs","names":[],"sources":["../../../../projects/bloomscorp/ngx-bloomsight/src/lib/bloomsight-config.token.ts","../../../../projects/bloomscorp/ngx-bloomsight/src/lib/internal/bloomsight-errors.ts","../../../../projects/bloomscorp/ngx-bloomsight/src/lib/internal/start-bloomsight.ts","../../../../projects/bloomscorp/ngx-bloomsight/src/lib/provide-bloomsight.ts","../../../../projects/bloomscorp/ngx-bloomsight/src/lib/bloomsight.service.ts"],"sourcesContent":["import {InjectionToken} from '@angular/core';\nimport {BloomsightConfig} from './type/bloomsight-config';\n\n/**\n * The configuration given to `provideBloomsight()`.\n *\n * A real `InjectionToken` (P3). 1.x used the string `'bloomsightConfig'`,\n * which carries no type — whatever was injected under it was trusted to be a\n * config — and which any other library could collide with.\n *\n * Inject it to read the configuration the app started with. Provide it through\n * `provideBloomsight()`, not directly: that is what also starts the SDK.\n *\n * The explicit annotation is deliberate. Inferred, the published declaration\n * comes out as `InjectionToken<import(\"@bloomscorp/bloomsight.js\").IConfig>` —\n * the compiler expands the alias — and the core's name leaks into this\n * package's public types (§2.3).\n */\nexport const BLOOMSIGHT_CONFIG: InjectionToken<BloomsightConfig> =\n\tnew InjectionToken<BloomsightConfig>('BLOOMSIGHT_CONFIG');\n","import {Injectable} from '@angular/core';\nimport {Observable, Subject} from 'rxjs';\nimport {BloomsightError} from '../type/bloomsight-error';\n\n/**\n * Collects the failures the core reports, for `BloomsightService.errors$`.\n *\n * Internal — not exported from the public API. The service hands out the\n * stream read-only, so only the SDK's own `onError` hook can push to it.\n *\n * Hot, like `Router.events`: a subscriber sees the failures that happen after\n * it subscribes. That is why `INIT_FAILED` is not emitted here — initialisation\n * runs while the app starts, before any component exists to subscribe. It goes\n * to the app's `onError` and the console instead.\n */\n@Injectable({providedIn: 'root'})\nexport class BloomsightErrors {\n\n\tprivate readonly failures = new Subject<BloomsightError>();\n\n\treadonly errors$: Observable<BloomsightError> = this.failures.asObservable();\n\n\treport(error: BloomsightError): void {\n\t\tthis.failures.next(error);\n\t}\n}\n","import {DestroyRef, inject, PLATFORM_ID} from '@angular/core';\nimport {isPlatformBrowser} from '@angular/common';\nimport {takeUntilDestroyed} from '@angular/core/rxjs-interop';\nimport {NavigationEnd, Router} from '@angular/router';\nimport {distinctUntilChanged, filter, map} from 'rxjs';\nimport {init, pageViewObserver} from '@bloomscorp/bloomsight.js';\nimport {BLOOMSIGHT_CONFIG} from '../bloomsight-config.token';\nimport {BloomsightConfig} from '../type/bloomsight-config';\nimport {BloomsightErrors} from './bloomsight-errors';\n\n/**\n * The app initializer `provideBloomsight()` registers: starts the SDK once,\n * when the application starts.\n *\n * In 1.x the services did this from their constructors, which is what B1, B7\n * and B8 all came from: the SDK started only if something happened to inject a\n * service, started twice if two did, and started again in a lazy-loaded module\n * that imported `forRoot()`. An initializer runs once per application, whether\n * or not anything injects anything.\n *\n * Nothing runs during server-side rendering (A3): there is no visitor to\n * measure, and the core's browser detection would only report blanks.\n */\nexport function startBloomsight(): void {\n\tif (!isPlatformBrowser(inject(PLATFORM_ID))) return;\n\n\tconst config = inject(BLOOMSIGHT_CONFIG);\n\tinitialise(config, inject(BloomsightErrors));\n\tobservePageViews(config, inject(Router), inject(DestroyRef));\n}\n\n/**\n * Start the core, and never let a failure escape.\n *\n * A throw here would abort the application's bootstrap, so a broken analytics\n * SDK would take the whole app down with it (RV4). The failure is reported\n * instead, and the app carries on without the SDK.\n *\n * The core is given a copy of the config with its own `onError`, which feeds\n * `errors$` and then calls the app's handler. The core copies the config it is\n * given, so the app's object is never touched either way.\n */\nfunction initialise(config: BloomsightConfig, errors: BloomsightErrors): void {\n\ttry {\n\t\tinit({\n\t\t\t...config,\n\t\t\tonError: (error) => {\n\t\t\t\terrors.report(error);\n\t\t\t\tconfig.onError?.(error);\n\t\t\t}\n\t\t});\n\t} catch (cause) {\n\t\treportInitFailure(config, cause);\n\t}\n}\n\nfunction reportInitFailure(config: BloomsightConfig, cause: unknown): void {\n\tconst error = {\n\t\tcode: 'INIT_FAILED',\n\t\tmessage: `bloomsight.js failed to initialise: ${cause instanceof Error ? cause.message : String(cause)}`\n\t};\n\tconsole.error('ngx-bloomsight:', error.message);\n\ttry {\n\t\tconfig.onError?.(error);\n\t} catch {\n\t\t// A throwing handler must not turn a reported failure back into a crash.\n\t}\n}\n\n/**\n * Record a page view each time a navigation lands somewhere new.\n *\n * - `urlAfterRedirects`, not `url` (B3): a redirect or guard means the two\n * differ, and only the first is where the user actually is.\n * - `distinctUntilChanged`: two requested URLs resolving to the same page are\n * one page view, as is a same-URL reload.\n * - `takeUntilDestroyed` (B4): 1.x never unsubscribed. Here the subscription\n * ends with the application's injector.\n *\n * Not observed at all on blooms.ai, which has no page-view endpoint: every call\n * would be a no-op in the core.\n */\nfunction observePageViews(config: BloomsightConfig, router: Router, destroyRef: DestroyRef): void {\n\tif (config.target === 'blooms-ai') {\n\t\tif (config.debug || config.isDevelopmentMode) {\n\t\t\tconsole.log('ngx-bloomsight: page views are not collected on blooms.ai, so the router is not observed');\n\t\t}\n\t\treturn;\n\t}\n\n\trouter.events.pipe(\n\t\tfilter((event): event is NavigationEnd => event instanceof NavigationEnd),\n\t\tmap((event) => event.urlAfterRedirects),\n\t\tdistinctUntilChanged(),\n\t\ttakeUntilDestroyed(destroyRef)\n\t).subscribe(() => pageViewObserver());\n}\n","import {EnvironmentProviders, makeEnvironmentProviders, provideAppInitializer} from '@angular/core';\nimport {BLOOMSIGHT_CONFIG} from './bloomsight-config.token';\nimport {startBloomsight} from './internal/start-bloomsight';\nimport {BloomsightConfig} from './type/bloomsight-config';\n\n/**\n * Set up Bloomsight for an application. Call it once, in the root providers:\n *\n * ```ts\n * bootstrapApplication(AppComponent, {\n * providers: [\n * provideRouter(routes),\n * provideBloomsight({ propertyToken: '…', isDevelopmentMode: false })\n * ]\n * });\n * ```\n *\n * It provides the configuration and starts the SDK when the application starts.\n * Page views are then recorded from the router automatically, and\n * `BloomsightService` can be injected anywhere — including lazy-loaded routes,\n * and components that only send mail.\n *\n * Returns `EnvironmentProviders`, which Angular accepts only in an environment\n * injector — the application or a route — never in a component's `providers`.\n * That replaces 1.x's `NgxBloomsightModule.forRoot()`, whose services existed\n * only where the module was imported with `forRoot()` (B6), and twice if a\n * lazy module imported it again (B7).\n */\nexport function provideBloomsight(config: BloomsightConfig): EnvironmentProviders {\n\treturn makeEnvironmentProviders([\n\t\t{provide: BLOOMSIGHT_CONFIG, useValue: config},\n\t\tprovideAppInitializer(startBloomsight)\n\t]);\n}\n","import {inject, Injectable, PLATFORM_ID} from '@angular/core';\nimport {isPlatformBrowser} from '@angular/common';\nimport {Observable} from 'rxjs';\nimport {resolveDataEvent, resolveSimpleEvent, sendEmail} from '@bloomscorp/bloomsight.js';\nimport {BLOOMSIGHT_CONFIG} from './bloomsight-config.token';\nimport {BloomsightErrors} from './internal/bloomsight-errors';\nimport {BloomsightConfig} from './type/bloomsight-config';\nimport {BloomsightError} from './type/bloomsight-error';\nimport {BloomsightEventData} from './type/bloomsight-event-data';\nimport {BloomsightMailRequest} from './type/bloomsight-mail-request';\n\n/**\n * Everything an app does with Bloomsight: events, mail, and failures.\n *\n * One root service, replacing 1.x's `BloomsightEventService` and\n * `BloomsightMailService`. It does not start the SDK — `provideBloomsight()`\n * does that when the app starts — so injecting it has no side effects.\n *\n * During server-side rendering every method is a no-op (A3).\n */\n@Injectable({providedIn: 'root'})\nexport class BloomsightService {\n\n\tprivate readonly config: BloomsightConfig = requireConfig();\n\n\tprivate readonly isBrowser: boolean = isPlatformBrowser(inject(PLATFORM_ID));\n\n\t/**\n\t * Every failure the SDK reports after you subscribe — rejected or\n\t * unreachable requests, with the server's own code where there is one.\n\t * The configured `onError` is called as well.\n\t *\n\t * Mail failures are not included: they are the error of that `sendMail()`\n\t * call, where a caller is already waiting for them.\n\t */\n\treadonly errors$: Observable<BloomsightError> = inject(BloomsightErrors).errors$;\n\n\t/** Record a simple event. */\n\ttrack(eventToken: string): void {\n\t\tif (this.isBrowser) resolveSimpleEvent(eventToken);\n\t}\n\n\t/** Record a data event, with its payload. */\n\ttrackData(eventToken: string, data: BloomsightEventData): void {\n\t\tif (this.isBrowser) resolveDataEvent(eventToken, data);\n\t}\n\n\t/**\n\t * Send a mail through the standalone Bloomsight server. Mail always goes\n\t * there, whatever `target` is set to.\n\t *\n\t * Nothing is sent until the result is subscribed to, and it is sent once per\n\t * subscription — the same as `HttpClient`. It emits once and completes when\n\t * the server accepts the mail, and errors with a `BloomsightError`\n\t * (`code: 'MAIL_FAILED'`) when it does not.\n\t *\n\t * Under `logOnly` the mail is handed to the core, which logs it and sends\n\t * nothing, and the result still emits and completes, so a UI waiting on it\n\t * is released (F2). During server-side rendering it completes without\n\t * sending or emitting.\n\t */\n\tsendMail(request: BloomsightMailRequest): Observable<void> {\n\t\treturn new Observable<void>((subscriber) => {\n\t\t\tif (!this.isBrowser) {\n\t\t\t\tsubscriber.complete();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst succeed = (): void => {\n\t\t\t\tsubscriber.next();\n\t\t\t\tsubscriber.complete();\n\t\t\t};\n\t\t\tconst fail = (cause?: unknown): void => subscriber.error(mailFailure(cause));\n\n\t\t\ttry {\n\t\t\t\tsendEmail(\n\t\t\t\t\trequest.engine ?? 'GMAIL',\n\t\t\t\t\trequest.engineId,\n\t\t\t\t\trequest.templateId,\n\t\t\t\t\trequest.templateOwnerId,\n\t\t\t\t\trequest.data,\n\t\t\t\t\tsucceed,\n\t\t\t\t\t() => fail()\n\t\t\t\t);\n\t\t\t} catch (cause) {\n\t\t\t\tfail(cause);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// The core returns before sending under logOnly, and calls neither\n\t\t\t// callback, so it is up to this wrapper to finish the call.\n\t\t\tif (this.config.logOnly) succeed();\n\t\t});\n\t}\n}\n\nfunction requireConfig(): BloomsightConfig {\n\tconst config = inject(BLOOMSIGHT_CONFIG, {optional: true});\n\tif (!config) {\n\t\tthrow new Error(\n\t\t\t'ngx-bloomsight: BloomsightService was injected, but the application never called provideBloomsight(). ' +\n\t\t\t'Add provideBloomsight({ propertyToken, isDevelopmentMode }) to the root providers.'\n\t\t);\n\t}\n\treturn config;\n}\n\nfunction mailFailure(cause?: unknown): BloomsightError {\n\tif (cause === undefined) {\n\t\treturn {code: 'MAIL_FAILED', message: 'The mail was not sent: the server rejected it or could not be reached'};\n\t}\n\treturn {\n\t\tcode: 'MAIL_FAILED',\n\t\tmessage: `The mail was not sent: ${cause instanceof Error ? cause.message : String(cause)}`\n\t};\n}\n"],"mappings":"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"}