@kundai/angular 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/README.md +332 -0
- package/package.json +30 -0
- package/src/index.d.ts +4 -0
- package/src/index.js +12 -0
- package/src/index.js.map +1 -0
- package/src/lib/kundai-router.service.d.ts +16 -0
- package/src/lib/kundai-router.service.js +45 -0
- package/src/lib/kundai-router.service.js.map +1 -0
- package/src/lib/kundai.config.d.ts +25 -0
- package/src/lib/kundai.config.js +10 -0
- package/src/lib/kundai.config.js.map +1 -0
- package/src/lib/kundai.module.d.ts +34 -0
- package/src/lib/kundai.module.js +59 -0
- package/src/lib/kundai.module.js.map +1 -0
- package/src/lib/kundai.service.d.ts +79 -0
- package/src/lib/kundai.service.js +277 -0
- package/src/lib/kundai.service.js.map +1 -0
package/README.md
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
# @kundai/angular
|
|
2
|
+
|
|
3
|
+
Module Angular officiel pour intégrer le tracking KundAi dans une application Angular.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Sommaire
|
|
8
|
+
|
|
9
|
+
- [Installation](#installation)
|
|
10
|
+
- [Configuration](#configuration)
|
|
11
|
+
- [Démarrage automatique](#démarrage-automatique)
|
|
12
|
+
- [Identifier un utilisateur](#identifier-un-utilisateur)
|
|
13
|
+
- [Événements](#événements)
|
|
14
|
+
- [Conversions](#conversions)
|
|
15
|
+
- [Passer le sessionId au backend](#passer-le-sessionid-au-backend)
|
|
16
|
+
- [Référence API](#référence-api)
|
|
17
|
+
- [Ce qui est collecté automatiquement](#ce-qui-est-collecté-automatiquement)
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install @kundai/angular
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**Versions Angular supportées :** Angular 17, 18, 19+
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Configuration
|
|
32
|
+
|
|
33
|
+
### App standalone (Angular 17+)
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
// app.config.ts
|
|
37
|
+
import { ApplicationConfig } from '@angular/core';
|
|
38
|
+
import { provideRouter } from '@angular/router';
|
|
39
|
+
import { KundaiModule } from '@kundai/angular';
|
|
40
|
+
import { routes } from './app.routes';
|
|
41
|
+
|
|
42
|
+
export const appConfig: ApplicationConfig = {
|
|
43
|
+
providers: [
|
|
44
|
+
provideRouter(routes),
|
|
45
|
+
...KundaiModule.forRoot({
|
|
46
|
+
baseUrl: 'https://api.kundai.io/v1/tracking',
|
|
47
|
+
apiKey: 'kundai_xxxxxxxxxxxx',
|
|
48
|
+
appId: 'mon-app-web',
|
|
49
|
+
}),
|
|
50
|
+
],
|
|
51
|
+
};
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### AppModule (Angular classique)
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
// app.module.ts
|
|
58
|
+
import { NgModule } from '@angular/core';
|
|
59
|
+
import { BrowserModule } from '@angular/platform-browser';
|
|
60
|
+
import { KundaiModule } from '@kundai/angular';
|
|
61
|
+
|
|
62
|
+
@NgModule({
|
|
63
|
+
imports: [BrowserModule],
|
|
64
|
+
providers: [
|
|
65
|
+
...KundaiModule.forRoot({
|
|
66
|
+
baseUrl: 'https://api.kundai.io/v1/tracking',
|
|
67
|
+
apiKey: 'kundai_xxxxxxxxxxxx',
|
|
68
|
+
appId: 'mon-app-web',
|
|
69
|
+
}),
|
|
70
|
+
],
|
|
71
|
+
})
|
|
72
|
+
export class AppModule {}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Options de configuration
|
|
76
|
+
|
|
77
|
+
| Option | Type | Requis | Description |
|
|
78
|
+
|---|---|---|---|
|
|
79
|
+
| `baseUrl` | `string` | ✅ | URL du endpoint tracking KundAi |
|
|
80
|
+
| `apiKey` | `string` | ✅ | Clé API générée dans le dashboard KundAi |
|
|
81
|
+
| `appId` | `string` | — | Identifiant de ton app (défaut : `"web"`) |
|
|
82
|
+
| `timeoutMs` | `number` | — | Timeout des requêtes HTTP en ms (défaut : `5000`) |
|
|
83
|
+
| `autoPageView` | `boolean` | — | Page-view auto à chaque route (défaut : `true`) |
|
|
84
|
+
|
|
85
|
+
### Variables d'environnement recommandées
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
// environments/environment.ts
|
|
89
|
+
export const environment = {
|
|
90
|
+
production: false,
|
|
91
|
+
kundaiTrackingUrl: 'http://localhost:3000/v1/tracking',
|
|
92
|
+
kundaiApiKey: 'kundai_xxxxxxxxxxxx',
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// environments/environment.prod.ts
|
|
96
|
+
export const environment = {
|
|
97
|
+
production: true,
|
|
98
|
+
kundaiTrackingUrl: 'https://api.kundai.io/v1/tracking',
|
|
99
|
+
kundaiApiKey: 'kundai_xxxxxxxxxxxx',
|
|
100
|
+
};
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Démarrage automatique
|
|
106
|
+
|
|
107
|
+
Dès que `KundaiModule.forRoot()` est configuré, le module :
|
|
108
|
+
|
|
109
|
+
1. Génère un `visitorId` persistant (localStorage)
|
|
110
|
+
2. Capture les paramètres UTM et ad click IDs depuis l'URL
|
|
111
|
+
3. Enregistre un clic si des UTM sont présents (touchpoint d'attribution)
|
|
112
|
+
4. Démarre une session
|
|
113
|
+
5. Enregistre la première page-view
|
|
114
|
+
6. Écoute les changements de route Angular et enregistre une page-view à chaque `NavigationEnd`
|
|
115
|
+
7. Clôture la session à la fermeture de l'onglet (`pagehide`)
|
|
116
|
+
|
|
117
|
+
**Aucun code supplémentaire n'est nécessaire pour ces comportements.**
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Identifier un utilisateur
|
|
122
|
+
|
|
123
|
+
À appeler après la connexion de l'utilisateur pour lier son identité à la session.
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
import { inject, Injectable } from '@angular/core';
|
|
127
|
+
import { KundaiService } from '@kundai/angular';
|
|
128
|
+
|
|
129
|
+
@Injectable({ providedIn: 'root' })
|
|
130
|
+
export class AuthService {
|
|
131
|
+
private readonly kundai = inject(KundaiService);
|
|
132
|
+
|
|
133
|
+
async afterLogin(user: { id: string; email: string }) {
|
|
134
|
+
// Lie l'utilisateur à la session navigateur courante
|
|
135
|
+
await this.kundai.identify(user.id, user.email);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
afterLogout() {
|
|
139
|
+
// Efface l'identité locale (ne clôture pas la session)
|
|
140
|
+
this.kundai.clearUser();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
> L'email n'est **jamais stocké en clair** — il est haché SHA-256 côté serveur
|
|
146
|
+
> avant persistance en base de données.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Événements
|
|
151
|
+
|
|
152
|
+
### Événements comportementaux libres
|
|
153
|
+
|
|
154
|
+
`track()` accepte n'importe quel nom d'événement et des propriétés libres.
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
import { Component, inject } from '@angular/core';
|
|
158
|
+
import { KundaiService } from '@kundai/angular';
|
|
159
|
+
|
|
160
|
+
@Component({ ... })
|
|
161
|
+
export class MyComponent {
|
|
162
|
+
private readonly kundai = inject(KundaiService);
|
|
163
|
+
|
|
164
|
+
onButtonClick() {
|
|
165
|
+
this.kundai.track('button_click', { button: 'cta_hero', plan: 'pro' });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
onFormSubmit() {
|
|
169
|
+
this.kundai.track('form_submit', { form: 'contact' });
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
onVideoPlay(videoId: string) {
|
|
173
|
+
this.kundai.track('video_play', { videoId });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Événements recommandés par catégorie
|
|
179
|
+
|
|
180
|
+
Ces noms sont des conventions — KundAi les reconnaît et les affiche dans le dashboard.
|
|
181
|
+
|
|
182
|
+
**Acquisition / Onboarding**
|
|
183
|
+
|
|
184
|
+
| Événement | Propriétés suggérées | Description |
|
|
185
|
+
|---|---|---|
|
|
186
|
+
| `page_view` | `url`, `title` | Vue de page (automatique) |
|
|
187
|
+
| `signup_started` | `plan`, `source` | Début d'inscription |
|
|
188
|
+
| `signup_completed` | `plan`, `method` | Inscription terminée |
|
|
189
|
+
| `account_created` | `plan`, `country` | Compte créé |
|
|
190
|
+
| `login` | `method` | Connexion réussie |
|
|
191
|
+
| `logout` | — | Déconnexion |
|
|
192
|
+
|
|
193
|
+
**Engagement**
|
|
194
|
+
|
|
195
|
+
| Événement | Propriétés suggérées | Description |
|
|
196
|
+
|---|---|---|
|
|
197
|
+
| `button_click` | `button`, `page` | Clic sur un bouton |
|
|
198
|
+
| `form_submit` | `form` | Soumission de formulaire |
|
|
199
|
+
| `search` | `query`, `results_count` | Recherche effectuée |
|
|
200
|
+
| `video_play` | `videoId`, `duration` | Lecture vidéo |
|
|
201
|
+
| `download` | `file`, `type` | Téléchargement |
|
|
202
|
+
| `share` | `platform`, `content` | Partage |
|
|
203
|
+
| `feature_used` | `feature` | Fonctionnalité utilisée |
|
|
204
|
+
|
|
205
|
+
**Funnel commercial**
|
|
206
|
+
|
|
207
|
+
| Événement | Propriétés suggérées | Description |
|
|
208
|
+
|---|---|---|
|
|
209
|
+
| `plan_viewed` | `plan` | Page de tarification vue |
|
|
210
|
+
| `plan_selected` | `plan`, `price`, `currency` | Plan sélectionné |
|
|
211
|
+
| `checkout_started` | `plan`, `amount`, `currency` | Paiement initié |
|
|
212
|
+
| `checkout_abandoned` | `plan`, `step` | Paiement abandonné |
|
|
213
|
+
|
|
214
|
+
**Conversions** (déclenchent l'attribution dans KundAi)
|
|
215
|
+
|
|
216
|
+
| Événement | Propriétés requises | Propriétés optionnelles |
|
|
217
|
+
|---|---|---|
|
|
218
|
+
| `purchase` | `amount`, `currency` | `planId`, `txRef` |
|
|
219
|
+
| `payment` | `amount`, `currency` | `planId`, `provider` |
|
|
220
|
+
| `conversion` | `amount`, `currency` | `planId` |
|
|
221
|
+
| `checkout` | `amount`, `currency` | `planId` |
|
|
222
|
+
|
|
223
|
+
> Les événements `purchase`, `payment`, `conversion` et `checkout` sont traités
|
|
224
|
+
> spécialement par KundAi : ils déclenchent le moteur d'attribution et marquent
|
|
225
|
+
> la session comme convertie.
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## Conversions
|
|
230
|
+
|
|
231
|
+
### Conversion côté frontend (paiement popup)
|
|
232
|
+
|
|
233
|
+
Pour les paiements confirmés directement dans le navigateur (ex. Flutterwave popup) :
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
onPaymentSuccess(result: { amount: number; currency: string; planId: string }) {
|
|
237
|
+
this.kundai.trackConversion({
|
|
238
|
+
amount: result.amount,
|
|
239
|
+
currency: result.currency,
|
|
240
|
+
planId: result.planId,
|
|
241
|
+
event: 'purchase',
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Conversion côté serveur (webhook)
|
|
247
|
+
|
|
248
|
+
Pour les paiements confirmés par webhook (Flutterwave, Stripe, etc.), le frontend
|
|
249
|
+
doit passer son `sessionId` au backend. Voir la section suivante.
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Passer le sessionId au backend
|
|
254
|
+
|
|
255
|
+
Le `sessionId` est le lien entre la session navigateur et les conversions confirmées
|
|
256
|
+
côté serveur. Sans lui, l'attribution est aveugle.
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
// checkout.component.ts
|
|
260
|
+
import { Component, inject } from '@angular/core';
|
|
261
|
+
import { HttpClient } from '@angular/common/http';
|
|
262
|
+
import { KundaiService } from '@kundai/angular';
|
|
263
|
+
|
|
264
|
+
@Component({ ... })
|
|
265
|
+
export class CheckoutComponent {
|
|
266
|
+
private readonly kundai = inject(KundaiService);
|
|
267
|
+
private readonly http = inject(HttpClient);
|
|
268
|
+
|
|
269
|
+
async pay(planId: string, amount: number) {
|
|
270
|
+
this.kundai.track('checkout_started', { planId, amount, currency: 'XOF' });
|
|
271
|
+
|
|
272
|
+
await this.http.post('/api/payments/checkout', {
|
|
273
|
+
planId,
|
|
274
|
+
amount,
|
|
275
|
+
currency: 'XOF',
|
|
276
|
+
sessionId: this.kundai.getSessionId(), // ← transmettre au backend
|
|
277
|
+
}).toPromise();
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Le backend stocke ce `sessionId` et le renvoie à KundAi lors de la confirmation
|
|
283
|
+
du paiement via `@kundai/nestjs`. Voir la documentation de `@kundai/nestjs`.
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## Référence API
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
// Injecter le service
|
|
291
|
+
private readonly kundai = inject(KundaiService);
|
|
292
|
+
|
|
293
|
+
// Identifier l'utilisateur connecté
|
|
294
|
+
await kundai.identify(userId: string, email?: string): Promise<void>
|
|
295
|
+
|
|
296
|
+
// Effacer l'identité locale (après logout)
|
|
297
|
+
kundai.clearUser(): void
|
|
298
|
+
|
|
299
|
+
// Envoyer un événement comportemental
|
|
300
|
+
kundai.track(event: string, properties?: Record<string, unknown>): void
|
|
301
|
+
|
|
302
|
+
// Notifier une conversion avec revenu (retry automatique x3)
|
|
303
|
+
kundai.trackConversion(opts: {
|
|
304
|
+
amount: number
|
|
305
|
+
currency: string // ISO 4217 : 'XOF', 'EUR', 'USD', 'GHS', 'NGN'...
|
|
306
|
+
planId?: string
|
|
307
|
+
event?: 'conversion' | 'purchase' | 'payment' | 'checkout'
|
|
308
|
+
metadata?: Record<string, unknown>
|
|
309
|
+
}): void
|
|
310
|
+
|
|
311
|
+
// Enregistrer manuellement une page-view
|
|
312
|
+
await kundai.recordPageView(): Promise<void>
|
|
313
|
+
|
|
314
|
+
// Récupérer le sessionId courant (à passer au backend)
|
|
315
|
+
kundai.getSessionId(): string | null
|
|
316
|
+
|
|
317
|
+
// Récupérer le visitorId courant
|
|
318
|
+
kundai.getVisitorId(): string
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## Ce qui est collecté automatiquement
|
|
324
|
+
|
|
325
|
+
| Donnée | Stockage | Note |
|
|
326
|
+
|---|---|---|
|
|
327
|
+
| `visitorId` | localStorage | UUID généré à la première visite |
|
|
328
|
+
| UTM (`utm_source`, `utm_medium`, `utm_campaign`, `utm_content`, `utm_term`) | localStorage | Capturés depuis l'URL, persistés pour les conversions futures |
|
|
329
|
+
| Ad click IDs (`fbclid`, `gclid`, `ttclid`, `liclid`) | localStorage | Capturés depuis l'URL |
|
|
330
|
+
| IP address | Serveur | Anonymisée (dernier octet masqué IPv4, 80 bits IPv6) |
|
|
331
|
+
| User-Agent | Serveur | Utilisé pour détecter device/browser/OS |
|
|
332
|
+
| Email | Serveur | Haché SHA-256, jamais stocké en clair |
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kundai/angular",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Module Angular KundAi — tracking sessions, UTM, conversions et identify en 2 lignes.",
|
|
5
|
+
"keywords": ["kundai", "angular", "tracking", "analytics", "attribution", "utm"],
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "KundAi <dev@kundai.io>",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"main": "./src/index.js",
|
|
10
|
+
"types": "./src/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./src/index.d.ts",
|
|
14
|
+
"import": "./src/index.js",
|
|
15
|
+
"require": "./src/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": ["src", "README.md"],
|
|
19
|
+
"publishConfig": { "access": "public" },
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"tslib": "^2.3.0",
|
|
22
|
+
"rxjs": "^7.0.0",
|
|
23
|
+
"@kundai/contracts": "1.0.0"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"@angular/core": ">=17",
|
|
27
|
+
"@angular/common": ">=17",
|
|
28
|
+
"@angular/router": ">=17"
|
|
29
|
+
}
|
|
30
|
+
}
|
package/src/index.d.ts
ADDED
package/src/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.KUNDAI_CONFIG = exports.KundaiRouterService = exports.KundaiService = exports.KundaiModule = void 0;
|
|
4
|
+
var kundai_module_1 = require("./lib/kundai.module");
|
|
5
|
+
Object.defineProperty(exports, "KundaiModule", { enumerable: true, get: function () { return kundai_module_1.KundaiModule; } });
|
|
6
|
+
var kundai_service_1 = require("./lib/kundai.service");
|
|
7
|
+
Object.defineProperty(exports, "KundaiService", { enumerable: true, get: function () { return kundai_service_1.KundaiService; } });
|
|
8
|
+
var kundai_router_service_1 = require("./lib/kundai-router.service");
|
|
9
|
+
Object.defineProperty(exports, "KundaiRouterService", { enumerable: true, get: function () { return kundai_router_service_1.KundaiRouterService; } });
|
|
10
|
+
var kundai_config_1 = require("./lib/kundai.config");
|
|
11
|
+
Object.defineProperty(exports, "KUNDAI_CONFIG", { enumerable: true, get: function () { return kundai_config_1.KUNDAI_CONFIG; } });
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
package/src/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["file:///F:/Projet/Kundai/src/libs/angular/src/index.ts"],"names":[],"mappings":";;;AAAA,qDAAmD;AAA1C,6GAAA,YAAY,OAAA;AACrB,uDAAqD;AAA5C,+GAAA,aAAa,OAAA;AACtB,qEAAkE;AAAzD,4HAAA,mBAAmB,OAAA;AAC5B,qDAAkE;AAA3C,8GAAA,aAAa,OAAA"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service d'écoute du Router Angular pour le tracking automatique des pages.
|
|
3
|
+
* Enregistre une page-view à chaque NavigationEnd si `autoPageView` est activé.
|
|
4
|
+
* Bootstrappé automatiquement par KundaiModule via APP_INITIALIZER.
|
|
5
|
+
*/
|
|
6
|
+
import { OnDestroy } from '@angular/core';
|
|
7
|
+
export declare class KundaiRouterService implements OnDestroy {
|
|
8
|
+
private readonly kundai;
|
|
9
|
+
private readonly router;
|
|
10
|
+
private readonly config;
|
|
11
|
+
private sub;
|
|
12
|
+
private firstNav;
|
|
13
|
+
/** Initialise le tracker et branche l'écoute du router. */
|
|
14
|
+
bootstrap(): Promise<void>;
|
|
15
|
+
ngOnDestroy(): void;
|
|
16
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.KundaiRouterService = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
/**
|
|
6
|
+
* Service d'écoute du Router Angular pour le tracking automatique des pages.
|
|
7
|
+
* Enregistre une page-view à chaque NavigationEnd si `autoPageView` est activé.
|
|
8
|
+
* Bootstrappé automatiquement par KundaiModule via APP_INITIALIZER.
|
|
9
|
+
*/
|
|
10
|
+
const core_1 = require("@angular/core");
|
|
11
|
+
const router_1 = require("@angular/router");
|
|
12
|
+
const rxjs_1 = require("rxjs");
|
|
13
|
+
const kundai_config_1 = require("./kundai.config");
|
|
14
|
+
const kundai_service_1 = require("./kundai.service");
|
|
15
|
+
let KundaiRouterService = class KundaiRouterService {
|
|
16
|
+
kundai = (0, core_1.inject)(kundai_service_1.KundaiService);
|
|
17
|
+
router = (0, core_1.inject)(router_1.Router);
|
|
18
|
+
config = (0, core_1.inject)(kundai_config_1.KUNDAI_CONFIG);
|
|
19
|
+
sub = null;
|
|
20
|
+
firstNav = true;
|
|
21
|
+
/** Initialise le tracker et branche l'écoute du router. */
|
|
22
|
+
async bootstrap() {
|
|
23
|
+
await this.kundai.init();
|
|
24
|
+
if (this.config.autoPageView === false)
|
|
25
|
+
return;
|
|
26
|
+
this.sub = this.router.events
|
|
27
|
+
.pipe((0, rxjs_1.filter)(e => e instanceof router_1.NavigationEnd))
|
|
28
|
+
.subscribe(() => {
|
|
29
|
+
// La première navigation est déjà couverte par init() → recordPageView().
|
|
30
|
+
if (this.firstNav) {
|
|
31
|
+
this.firstNav = false;
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
this.kundai.recordPageView().catch(() => undefined);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
ngOnDestroy() {
|
|
38
|
+
this.sub?.unsubscribe();
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
exports.KundaiRouterService = KundaiRouterService;
|
|
42
|
+
exports.KundaiRouterService = KundaiRouterService = tslib_1.__decorate([
|
|
43
|
+
(0, core_1.Injectable)()
|
|
44
|
+
], KundaiRouterService);
|
|
45
|
+
//# sourceMappingURL=kundai-router.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kundai-router.service.js","sourceRoot":"","sources":["file:///F:/Projet/Kundai/src/libs/angular/src/lib/kundai-router.service.ts"],"names":[],"mappings":";;;;AAAA;;;;GAIG;AACH,wCAA8D;AAC9D,4CAAwD;AACxD,+BAA4C;AAC5C,mDAAgD;AAChD,qDAAiD;AAG1C,IAAM,mBAAmB,GAAzB,MAAM,mBAAmB;IACb,MAAM,GAAG,IAAA,aAAM,EAAC,8BAAa,CAAC,CAAC;IAC/B,MAAM,GAAG,IAAA,aAAM,EAAC,eAAM,CAAC,CAAC;IACxB,MAAM,GAAG,IAAA,aAAM,EAAC,6BAAa,CAAC,CAAC;IACxC,GAAG,GAAwB,IAAI,CAAC;IAChC,QAAQ,GAAG,IAAI,CAAC;IAExB,2DAA2D;IAC3D,KAAK,CAAC,SAAS;QACb,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QAEzB,IAAI,IAAI,CAAC,MAAM,CAAC,YAAY,KAAK,KAAK;YAAE,OAAO;QAE/C,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM;aAC1B,IAAI,CAAC,IAAA,aAAM,EAAC,CAAC,CAAC,EAAE,CAAC,CAAC,YAAY,sBAAa,CAAC,CAAC;aAC7C,SAAS,CAAC,GAAG,EAAE;YACd,0EAA0E;YAC1E,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAAC,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;gBAAC,OAAO;YAAC,CAAC;YACrD,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QACtD,CAAC,CAAC,CAAC;IACP,CAAC;IAED,WAAW;QACT,IAAI,CAAC,GAAG,EAAE,WAAW,EAAE,CAAC;IAC1B,CAAC;CACF,CAAA;AAzBY,kDAAmB;8BAAnB,mBAAmB;IAD/B,IAAA,iBAAU,GAAE;GACA,mBAAmB,CAyB/B"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration du module Angular KundAi (`@kundai/angular`).
|
|
3
|
+
*/
|
|
4
|
+
import { InjectionToken } from '@angular/core';
|
|
5
|
+
/** Configuration passée à `KundaiModule.forRoot()`. */
|
|
6
|
+
export interface KundaiConfig {
|
|
7
|
+
/**
|
|
8
|
+
* URL de base du endpoint de tracking KundAi.
|
|
9
|
+
* Ex. : https://api.votre-domaine.com/v1/tracking
|
|
10
|
+
*/
|
|
11
|
+
baseUrl: string;
|
|
12
|
+
/** Clé API de tracking du tenant (kundai_...). */
|
|
13
|
+
apiKey: string;
|
|
14
|
+
/** Identifiant de l'application source (défaut : "web"). */
|
|
15
|
+
appId?: string;
|
|
16
|
+
/** Timeout des requêtes HTTP en ms (défaut : 5000). */
|
|
17
|
+
timeoutMs?: number;
|
|
18
|
+
/**
|
|
19
|
+
* Si true, enregistre automatiquement une page-view à chaque
|
|
20
|
+
* changement de route Angular (NavigationEnd). Défaut : true.
|
|
21
|
+
*/
|
|
22
|
+
autoPageView?: boolean;
|
|
23
|
+
}
|
|
24
|
+
/** Token d'injection de la configuration. */
|
|
25
|
+
export declare const KUNDAI_CONFIG: InjectionToken<KundaiConfig>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.KUNDAI_CONFIG = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Configuration du module Angular KundAi (`@kundai/angular`).
|
|
6
|
+
*/
|
|
7
|
+
const core_1 = require("@angular/core");
|
|
8
|
+
/** Token d'injection de la configuration. */
|
|
9
|
+
exports.KUNDAI_CONFIG = new core_1.InjectionToken('KUNDAI_CONFIG');
|
|
10
|
+
//# sourceMappingURL=kundai.config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kundai.config.js","sourceRoot":"","sources":["file:///F:/Projet/Kundai/src/libs/angular/src/lib/kundai.config.ts"],"names":[],"mappings":";;;AAAA;;GAEG;AACH,wCAA+C;AAsB/C,6CAA6C;AAChC,QAAA,aAAa,GAAG,IAAI,qBAAc,CAAe,eAAe,CAAC,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module Angular KundAi (`@kundai/angular`).
|
|
3
|
+
*
|
|
4
|
+
* Intégration en 2 lignes dans n'importe quelle app Angular :
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* // app.config.ts (standalone) ou AppModule
|
|
8
|
+
* import { KundaiModule } from '@kundai/angular';
|
|
9
|
+
*
|
|
10
|
+
* providers: [
|
|
11
|
+
* ...KundaiModule.forRoot({
|
|
12
|
+
* baseUrl: 'https://api.votre-domaine.com/v1/tracking',
|
|
13
|
+
* apiKey: 'kundai_xxxxxxxxxxxx',
|
|
14
|
+
* appId: 'mon-app',
|
|
15
|
+
* }),
|
|
16
|
+
* ]
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* Puis dans n'importe quel composant ou service :
|
|
20
|
+
*
|
|
21
|
+
* ```ts
|
|
22
|
+
* private readonly kundai = inject(KundaiService);
|
|
23
|
+
* this.kundai.track('button_click', { plan: 'pro' });
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
import { EnvironmentProviders } from '@angular/core';
|
|
27
|
+
import { KundaiConfig } from './kundai.config';
|
|
28
|
+
export declare class KundaiModule {
|
|
29
|
+
/**
|
|
30
|
+
* Configure et fournit le module KundAi.
|
|
31
|
+
* À appeler dans `providers` de `app.config.ts` (standalone) ou `AppModule`.
|
|
32
|
+
*/
|
|
33
|
+
static forRoot(config: KundaiConfig): EnvironmentProviders;
|
|
34
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.KundaiModule = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Module Angular KundAi (`@kundai/angular`).
|
|
6
|
+
*
|
|
7
|
+
* Intégration en 2 lignes dans n'importe quelle app Angular :
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* // app.config.ts (standalone) ou AppModule
|
|
11
|
+
* import { KundaiModule } from '@kundai/angular';
|
|
12
|
+
*
|
|
13
|
+
* providers: [
|
|
14
|
+
* ...KundaiModule.forRoot({
|
|
15
|
+
* baseUrl: 'https://api.votre-domaine.com/v1/tracking',
|
|
16
|
+
* apiKey: 'kundai_xxxxxxxxxxxx',
|
|
17
|
+
* appId: 'mon-app',
|
|
18
|
+
* }),
|
|
19
|
+
* ]
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* Puis dans n'importe quel composant ou service :
|
|
23
|
+
*
|
|
24
|
+
* ```ts
|
|
25
|
+
* private readonly kundai = inject(KundaiService);
|
|
26
|
+
* this.kundai.track('button_click', { plan: 'pro' });
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
const core_1 = require("@angular/core");
|
|
30
|
+
const http_1 = require("@angular/common/http");
|
|
31
|
+
const kundai_config_1 = require("./kundai.config");
|
|
32
|
+
const kundai_service_1 = require("./kundai.service");
|
|
33
|
+
const kundai_router_service_1 = require("./kundai-router.service");
|
|
34
|
+
/** Factory APP_INITIALIZER : bootstrap du tracker au démarrage de l'app. */
|
|
35
|
+
function kundaiInitFactory(router) {
|
|
36
|
+
return () => router.bootstrap();
|
|
37
|
+
}
|
|
38
|
+
class KundaiModule {
|
|
39
|
+
/**
|
|
40
|
+
* Configure et fournit le module KundAi.
|
|
41
|
+
* À appeler dans `providers` de `app.config.ts` (standalone) ou `AppModule`.
|
|
42
|
+
*/
|
|
43
|
+
static forRoot(config) {
|
|
44
|
+
return (0, core_1.makeEnvironmentProviders)([
|
|
45
|
+
(0, http_1.provideHttpClient)(),
|
|
46
|
+
{ provide: kundai_config_1.KUNDAI_CONFIG, useValue: config },
|
|
47
|
+
kundai_service_1.KundaiService,
|
|
48
|
+
kundai_router_service_1.KundaiRouterService,
|
|
49
|
+
{
|
|
50
|
+
provide: core_1.APP_INITIALIZER,
|
|
51
|
+
useFactory: kundaiInitFactory,
|
|
52
|
+
deps: [kundai_router_service_1.KundaiRouterService],
|
|
53
|
+
multi: true,
|
|
54
|
+
},
|
|
55
|
+
]);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
exports.KundaiModule = KundaiModule;
|
|
59
|
+
//# sourceMappingURL=kundai.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kundai.module.js","sourceRoot":"","sources":["file:///F:/Projet/Kundai/src/libs/angular/src/lib/kundai.module.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wCAAgG;AAChG,+CAAyD;AACzD,mDAA8D;AAC9D,qDAAiD;AACjD,mEAA8D;AAE9D,4EAA4E;AAC5E,SAAS,iBAAiB,CAAC,MAA2B;IACpD,OAAO,GAAG,EAAE,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;AAClC,CAAC;AAED,MAAa,YAAY;IACvB;;;OAGG;IACH,MAAM,CAAC,OAAO,CAAC,MAAoB;QACjC,OAAO,IAAA,+BAAwB,EAAC;YAC9B,IAAA,wBAAiB,GAAE;YACnB,EAAE,OAAO,EAAE,6BAAa,EAAE,QAAQ,EAAE,MAAM,EAAE;YAC5C,8BAAa;YACb,2CAAmB;YACnB;gBACE,OAAO,EAAE,sBAAe;gBACxB,UAAU,EAAE,iBAAiB;gBAC7B,IAAI,EAAE,CAAC,2CAAmB,CAAC;gBAC3B,KAAK,EAAE,IAAI;aACZ;SACF,CAAC,CAAC;IACL,CAAC;CACF;AAnBD,oCAmBC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service principal du module Angular KundAi (`@kundai/angular`).
|
|
3
|
+
*
|
|
4
|
+
* Injectable partout dans l'application. Gère :
|
|
5
|
+
* - l'identifiant visiteur persistant (localStorage)
|
|
6
|
+
* - la capture des UTM depuis l'URL courante
|
|
7
|
+
* - le démarrage de session
|
|
8
|
+
* - les événements comportementaux et conversions
|
|
9
|
+
* - la liaison utilisateur connecté (identify)
|
|
10
|
+
*/
|
|
11
|
+
import { HttpClient } from '@angular/common/http';
|
|
12
|
+
import { OnDestroy } from '@angular/core';
|
|
13
|
+
import { KundaiConfig } from './kundai.config';
|
|
14
|
+
export declare class KundaiService implements OnDestroy {
|
|
15
|
+
private readonly config;
|
|
16
|
+
private readonly http;
|
|
17
|
+
private sessionId;
|
|
18
|
+
private visitorId;
|
|
19
|
+
private userId;
|
|
20
|
+
private utmMeta;
|
|
21
|
+
private initialized;
|
|
22
|
+
constructor(config: KundaiConfig, http: HttpClient);
|
|
23
|
+
/**
|
|
24
|
+
* Démarre la session : capture les UTM, enregistre le click si UTM présents,
|
|
25
|
+
* démarre la session et enregistre la première page-view.
|
|
26
|
+
* Appelé automatiquement par KundaiRouterService au bootstrap.
|
|
27
|
+
*/
|
|
28
|
+
init(): Promise<void>;
|
|
29
|
+
/** Enregistre une vue de page dans la session courante (appelé par le router). */
|
|
30
|
+
recordPageView(): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Enregistre un événement comportemental.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* this.kundai.track('signup_form_view');
|
|
36
|
+
* this.kundai.track('button_click', { button: 'cta_hero', plan: 'pro' });
|
|
37
|
+
*/
|
|
38
|
+
track(event: string, properties?: Record<string, unknown>): void;
|
|
39
|
+
/**
|
|
40
|
+
* Enregistre une conversion avec revenu (retry automatique x3).
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* this.kundai.trackConversion({ amount: 9900, currency: 'XOF', planId: 'pro' });
|
|
44
|
+
*/
|
|
45
|
+
trackConversion(opts: {
|
|
46
|
+
amount: number;
|
|
47
|
+
currency: string;
|
|
48
|
+
planId?: string;
|
|
49
|
+
event?: 'conversion' | 'purchase' | 'payment' | 'checkout';
|
|
50
|
+
metadata?: Record<string, unknown>;
|
|
51
|
+
}): void;
|
|
52
|
+
/**
|
|
53
|
+
* Lie l'utilisateur connecté à la session (à appeler après login).
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* this.kundai.identify('user_123', 'user@example.com');
|
|
57
|
+
*/
|
|
58
|
+
identify(userId: string, email?: string): Promise<void>;
|
|
59
|
+
/** Déconnecte l'identité locale (à appeler après logout). */
|
|
60
|
+
clearUser(): void;
|
|
61
|
+
/** Retourne le sessionId courant (utile pour le passer au backend). */
|
|
62
|
+
getSessionId(): string | null;
|
|
63
|
+
/** Retourne le visitorId courant. */
|
|
64
|
+
getVisitorId(): string;
|
|
65
|
+
private endSession;
|
|
66
|
+
ngOnDestroy(): void;
|
|
67
|
+
private baseUrl;
|
|
68
|
+
private headersObj;
|
|
69
|
+
private headers;
|
|
70
|
+
private post;
|
|
71
|
+
private withRetry;
|
|
72
|
+
private captureUtmFromUrl;
|
|
73
|
+
private campaignFields;
|
|
74
|
+
private readStoredUtm;
|
|
75
|
+
private writeStoredUtm;
|
|
76
|
+
private getOrCreateVisitorId;
|
|
77
|
+
private read;
|
|
78
|
+
private write;
|
|
79
|
+
}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.KundaiService = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
/**
|
|
6
|
+
* Service principal du module Angular KundAi (`@kundai/angular`).
|
|
7
|
+
*
|
|
8
|
+
* Injectable partout dans l'application. Gère :
|
|
9
|
+
* - l'identifiant visiteur persistant (localStorage)
|
|
10
|
+
* - la capture des UTM depuis l'URL courante
|
|
11
|
+
* - le démarrage de session
|
|
12
|
+
* - les événements comportementaux et conversions
|
|
13
|
+
* - la liaison utilisateur connecté (identify)
|
|
14
|
+
*/
|
|
15
|
+
const http_1 = require("@angular/common/http");
|
|
16
|
+
const core_1 = require("@angular/core");
|
|
17
|
+
const rxjs_1 = require("rxjs");
|
|
18
|
+
const kundai_config_1 = require("./kundai.config");
|
|
19
|
+
/** Clés localStorage. */
|
|
20
|
+
const VISITOR_KEY = 'kundai_visitor_id';
|
|
21
|
+
const USER_KEY = 'kundai_user_id';
|
|
22
|
+
const UTM_KEY = 'kundai_utm_meta';
|
|
23
|
+
let KundaiService = class KundaiService {
|
|
24
|
+
config;
|
|
25
|
+
http;
|
|
26
|
+
sessionId = null;
|
|
27
|
+
visitorId;
|
|
28
|
+
userId;
|
|
29
|
+
utmMeta = null;
|
|
30
|
+
initialized = false;
|
|
31
|
+
constructor(config, http) {
|
|
32
|
+
this.config = config;
|
|
33
|
+
this.http = http;
|
|
34
|
+
this.visitorId = this.getOrCreateVisitorId();
|
|
35
|
+
this.userId = this.read(USER_KEY);
|
|
36
|
+
}
|
|
37
|
+
// ── Initialisation ────────────────────────────────────────────────────────
|
|
38
|
+
/**
|
|
39
|
+
* Démarre la session : capture les UTM, enregistre le click si UTM présents,
|
|
40
|
+
* démarre la session et enregistre la première page-view.
|
|
41
|
+
* Appelé automatiquement par KundaiRouterService au bootstrap.
|
|
42
|
+
*/
|
|
43
|
+
async init() {
|
|
44
|
+
if (this.initialized)
|
|
45
|
+
return;
|
|
46
|
+
this.initialized = true;
|
|
47
|
+
this.utmMeta = this.readStoredUtm() ?? this.captureUtmFromUrl();
|
|
48
|
+
if (this.utmMeta && !this.readStoredUtm()) {
|
|
49
|
+
this.writeStoredUtm(this.utmMeta);
|
|
50
|
+
}
|
|
51
|
+
let clickId = null;
|
|
52
|
+
if (this.utmMeta) {
|
|
53
|
+
try {
|
|
54
|
+
const click = await this.post('/click', {
|
|
55
|
+
visitorId: this.visitorId,
|
|
56
|
+
landingPageUrl: window.location.href,
|
|
57
|
+
...this.utmMeta,
|
|
58
|
+
});
|
|
59
|
+
clickId = click.clickId ?? null;
|
|
60
|
+
if (click.visitorId)
|
|
61
|
+
this.visitorId = click.visitorId;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
// Un échec de click ne bloque jamais la session.
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const result = await this.post('/sessions', {
|
|
69
|
+
visitorId: this.visitorId,
|
|
70
|
+
appId: this.config.appId ?? 'web',
|
|
71
|
+
entryPageUrl: window.location.href,
|
|
72
|
+
clickId,
|
|
73
|
+
});
|
|
74
|
+
this.sessionId = result.sessionId;
|
|
75
|
+
if (result.visitorId)
|
|
76
|
+
this.visitorId = result.visitorId;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
await this.recordPageView();
|
|
82
|
+
window.addEventListener('pagehide', () => this.endSession());
|
|
83
|
+
}
|
|
84
|
+
// ── Page view ─────────────────────────────────────────────────────────────
|
|
85
|
+
/** Enregistre une vue de page dans la session courante (appelé par le router). */
|
|
86
|
+
async recordPageView() {
|
|
87
|
+
if (!this.sessionId)
|
|
88
|
+
return;
|
|
89
|
+
const dto = {
|
|
90
|
+
visitorId: this.visitorId,
|
|
91
|
+
url: window.location.href,
|
|
92
|
+
title: document.title,
|
|
93
|
+
referrerUrl: document.referrer || undefined,
|
|
94
|
+
viewportWidth: window.innerWidth,
|
|
95
|
+
viewportHeight: window.innerHeight,
|
|
96
|
+
};
|
|
97
|
+
await this.post(`/sessions/${this.sessionId}/page-view`, dto).catch(() => undefined);
|
|
98
|
+
}
|
|
99
|
+
// ── Événements ────────────────────────────────────────────────────────────
|
|
100
|
+
/**
|
|
101
|
+
* Enregistre un événement comportemental.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* this.kundai.track('signup_form_view');
|
|
105
|
+
* this.kundai.track('button_click', { button: 'cta_hero', plan: 'pro' });
|
|
106
|
+
*/
|
|
107
|
+
track(event, properties) {
|
|
108
|
+
const dto = {
|
|
109
|
+
event,
|
|
110
|
+
visitorId: this.visitorId,
|
|
111
|
+
sessionId: this.sessionId ?? undefined,
|
|
112
|
+
userId: this.userId ?? undefined,
|
|
113
|
+
properties: properties ?? {},
|
|
114
|
+
pageUrl: window.location.href,
|
|
115
|
+
};
|
|
116
|
+
this.post('/events', dto).catch(() => undefined);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Enregistre une conversion avec revenu (retry automatique x3).
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* this.kundai.trackConversion({ amount: 9900, currency: 'XOF', planId: 'pro' });
|
|
123
|
+
*/
|
|
124
|
+
trackConversion(opts) {
|
|
125
|
+
const dto = {
|
|
126
|
+
event: opts.event ?? 'conversion',
|
|
127
|
+
visitorId: this.visitorId,
|
|
128
|
+
sessionId: this.sessionId ?? undefined,
|
|
129
|
+
userId: this.userId ?? undefined,
|
|
130
|
+
properties: {
|
|
131
|
+
amount: opts.amount,
|
|
132
|
+
currency: opts.currency,
|
|
133
|
+
planId: opts.planId ?? null,
|
|
134
|
+
...(opts.metadata ?? {}),
|
|
135
|
+
...this.campaignFields(),
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
this.withRetry(() => this.post('/events', dto)).catch(() => undefined);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Lie l'utilisateur connecté à la session (à appeler après login).
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* this.kundai.identify('user_123', 'user@example.com');
|
|
145
|
+
*/
|
|
146
|
+
async identify(userId, email) {
|
|
147
|
+
this.userId = userId;
|
|
148
|
+
this.write(USER_KEY, userId);
|
|
149
|
+
if (!this.sessionId)
|
|
150
|
+
await this.init();
|
|
151
|
+
if (!this.sessionId)
|
|
152
|
+
return;
|
|
153
|
+
const dto2 = { userId, ...(email ? { email } : {}) };
|
|
154
|
+
await this.post(`/sessions/${this.sessionId}/identify`, dto2).catch(() => undefined);
|
|
155
|
+
}
|
|
156
|
+
/** Déconnecte l'identité locale (à appeler après logout). */
|
|
157
|
+
clearUser() {
|
|
158
|
+
this.userId = null;
|
|
159
|
+
localStorage.removeItem(USER_KEY);
|
|
160
|
+
}
|
|
161
|
+
/** Retourne le sessionId courant (utile pour le passer au backend). */
|
|
162
|
+
getSessionId() {
|
|
163
|
+
return this.sessionId;
|
|
164
|
+
}
|
|
165
|
+
/** Retourne le visitorId courant. */
|
|
166
|
+
getVisitorId() {
|
|
167
|
+
return this.visitorId;
|
|
168
|
+
}
|
|
169
|
+
// ── Fin de session ────────────────────────────────────────────────────────
|
|
170
|
+
endSession() {
|
|
171
|
+
if (!this.sessionId)
|
|
172
|
+
return;
|
|
173
|
+
const sessionId = this.sessionId;
|
|
174
|
+
this.sessionId = null;
|
|
175
|
+
// fetch keepalive : fiable au déchargement de page, supporte les headers
|
|
176
|
+
fetch(`${this.baseUrl()}/sessions/${sessionId}/end`, {
|
|
177
|
+
method: 'POST',
|
|
178
|
+
headers: this.headersObj(),
|
|
179
|
+
body: JSON.stringify({ exitPageUrl: window.location.href }),
|
|
180
|
+
keepalive: true,
|
|
181
|
+
}).catch(() => undefined);
|
|
182
|
+
}
|
|
183
|
+
ngOnDestroy() {
|
|
184
|
+
this.endSession();
|
|
185
|
+
}
|
|
186
|
+
// ── HTTP ──────────────────────────────────────────────────────────────────
|
|
187
|
+
baseUrl() {
|
|
188
|
+
return this.config.baseUrl.replace(/\/+$/, '');
|
|
189
|
+
}
|
|
190
|
+
headersObj() {
|
|
191
|
+
return {
|
|
192
|
+
'Content-Type': 'application/json',
|
|
193
|
+
'X-API-Key': this.config.apiKey,
|
|
194
|
+
'X-KundAi-App': this.config.appId ?? 'web',
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
headers() {
|
|
198
|
+
return new http_1.HttpHeaders(this.headersObj());
|
|
199
|
+
}
|
|
200
|
+
post(path, body) {
|
|
201
|
+
return (0, rxjs_1.firstValueFrom)(this.http.post(`${this.baseUrl()}${path}`, body, {
|
|
202
|
+
headers: this.headers(),
|
|
203
|
+
}));
|
|
204
|
+
}
|
|
205
|
+
// ── Retry ─────────────────────────────────────────────────────────────────
|
|
206
|
+
async withRetry(fn, attempts = 3) {
|
|
207
|
+
for (let i = 0; i < attempts; i++) {
|
|
208
|
+
try {
|
|
209
|
+
await fn();
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
if (i < attempts - 1) {
|
|
214
|
+
await new Promise(r => setTimeout(r, 1000 * 2 ** i));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// ── UTM ───────────────────────────────────────────────────────────────────
|
|
220
|
+
captureUtmFromUrl() {
|
|
221
|
+
const params = new URLSearchParams(window.location.search);
|
|
222
|
+
const utm = {
|
|
223
|
+
utmSource: params.get('utm_source'),
|
|
224
|
+
utmMedium: params.get('utm_medium'),
|
|
225
|
+
utmCampaign: params.get('utm_campaign'),
|
|
226
|
+
utmContent: params.get('utm_content'),
|
|
227
|
+
utmTerm: params.get('utm_term'),
|
|
228
|
+
fbclid: params.get('fbclid'),
|
|
229
|
+
gclid: params.get('gclid'),
|
|
230
|
+
ttclid: params.get('ttclid'),
|
|
231
|
+
liclid: params.get('liclid'),
|
|
232
|
+
};
|
|
233
|
+
const hasData = Object.values(utm).some(v => v !== null);
|
|
234
|
+
return hasData ? utm : null;
|
|
235
|
+
}
|
|
236
|
+
campaignFields() {
|
|
237
|
+
if (!this.utmMeta)
|
|
238
|
+
return {};
|
|
239
|
+
return Object.fromEntries(Object.entries(this.utmMeta).filter(([, v]) => v !== null && v !== undefined));
|
|
240
|
+
}
|
|
241
|
+
readStoredUtm() {
|
|
242
|
+
const raw = localStorage.getItem(UTM_KEY);
|
|
243
|
+
if (!raw)
|
|
244
|
+
return null;
|
|
245
|
+
try {
|
|
246
|
+
return JSON.parse(raw);
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
writeStoredUtm(utm) {
|
|
253
|
+
localStorage.setItem(UTM_KEY, JSON.stringify(utm));
|
|
254
|
+
}
|
|
255
|
+
// ── Visitor ID ────────────────────────────────────────────────────────────
|
|
256
|
+
getOrCreateVisitorId() {
|
|
257
|
+
let id = localStorage.getItem(VISITOR_KEY);
|
|
258
|
+
if (!id) {
|
|
259
|
+
id = crypto.randomUUID();
|
|
260
|
+
localStorage.setItem(VISITOR_KEY, id);
|
|
261
|
+
}
|
|
262
|
+
return id;
|
|
263
|
+
}
|
|
264
|
+
read(key) {
|
|
265
|
+
return localStorage.getItem(key);
|
|
266
|
+
}
|
|
267
|
+
write(key, value) {
|
|
268
|
+
localStorage.setItem(key, value);
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
exports.KundaiService = KundaiService;
|
|
272
|
+
exports.KundaiService = KundaiService = tslib_1.__decorate([
|
|
273
|
+
(0, core_1.Injectable)(),
|
|
274
|
+
tslib_1.__param(0, (0, core_1.Inject)(kundai_config_1.KUNDAI_CONFIG)),
|
|
275
|
+
tslib_1.__metadata("design:paramtypes", [Object, http_1.HttpClient])
|
|
276
|
+
], KundaiService);
|
|
277
|
+
//# sourceMappingURL=kundai.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kundai.service.js","sourceRoot":"","sources":["file:///F:/Projet/Kundai/src/libs/angular/src/lib/kundai.service.ts"],"names":[],"mappings":"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"}
|