@clianta/sdk 1.3.0 → 1.4.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/CHANGELOG.md +16 -0
- package/dist/clianta.cjs.js +759 -631
- package/dist/clianta.cjs.js.map +1 -1
- package/dist/clianta.esm.js +759 -631
- package/dist/clianta.esm.js.map +1 -1
- package/dist/clianta.umd.js +759 -631
- package/dist/clianta.umd.js.map +1 -1
- package/dist/clianta.umd.min.js +2 -2
- package/dist/clianta.umd.min.js.map +1 -1
- package/dist/index.d.ts +863 -803
- package/dist/react.cjs.js +759 -631
- package/dist/react.cjs.js.map +1 -1
- package/dist/react.d.ts +37 -3
- package/dist/react.esm.js +759 -631
- package/dist/react.esm.js.map +1 -1
- package/dist/vue.cjs.js +759 -631
- package/dist/vue.cjs.js.map +1 -1
- package/dist/vue.d.ts +37 -3
- package/dist/vue.esm.js +759 -631
- package/dist/vue.esm.js.map +1 -1
- package/package.json +1 -1
package/dist/clianta.umd.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* Clianta SDK v1.
|
|
2
|
+
* Clianta SDK v1.4.0
|
|
3
3
|
* (c) 2026 Clianta
|
|
4
4
|
* Released under the MIT License.
|
|
5
5
|
*/
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* @see SDK_VERSION in core/config.ts
|
|
15
15
|
*/
|
|
16
16
|
/** SDK Version */
|
|
17
|
-
const SDK_VERSION = '1.
|
|
17
|
+
const SDK_VERSION = '1.4.0';
|
|
18
18
|
/** Default API endpoint based on environment */
|
|
19
19
|
const getDefaultApiEndpoint = () => {
|
|
20
20
|
if (typeof window === 'undefined')
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
projectId: '',
|
|
41
41
|
apiEndpoint: getDefaultApiEndpoint(),
|
|
42
42
|
authToken: '',
|
|
43
|
+
apiKey: '',
|
|
43
44
|
debug: false,
|
|
44
45
|
autoPageView: true,
|
|
45
46
|
plugins: DEFAULT_PLUGINS,
|
|
@@ -187,12 +188,39 @@
|
|
|
187
188
|
return this.send(url, payload);
|
|
188
189
|
}
|
|
189
190
|
/**
|
|
190
|
-
* Send identify request
|
|
191
|
+
* Send identify request.
|
|
192
|
+
* Returns contactId from the server response so the Tracker can store it.
|
|
191
193
|
*/
|
|
192
194
|
async sendIdentify(data) {
|
|
193
195
|
const url = `${this.config.apiEndpoint}/api/public/track/identify`;
|
|
194
|
-
|
|
195
|
-
|
|
196
|
+
try {
|
|
197
|
+
const response = await this.fetchWithTimeout(url, {
|
|
198
|
+
method: 'POST',
|
|
199
|
+
headers: { 'Content-Type': 'application/json' },
|
|
200
|
+
body: JSON.stringify(data),
|
|
201
|
+
keepalive: true,
|
|
202
|
+
});
|
|
203
|
+
const body = await response.json().catch(() => ({}));
|
|
204
|
+
if (response.ok) {
|
|
205
|
+
logger.debug('Identify successful, contactId:', body.contactId);
|
|
206
|
+
return {
|
|
207
|
+
success: true,
|
|
208
|
+
status: response.status,
|
|
209
|
+
contactId: body.contactId ?? undefined,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
if (response.status >= 500) {
|
|
213
|
+
logger.warn(`Identify server error (${response.status})`);
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
logger.error(`Identify failed with status ${response.status}:`, body.message);
|
|
217
|
+
}
|
|
218
|
+
return { success: false, status: response.status };
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
logger.error('Identify request failed:', error);
|
|
222
|
+
return { success: false, error: error };
|
|
223
|
+
}
|
|
196
224
|
}
|
|
197
225
|
/**
|
|
198
226
|
* Send events synchronously (for page unload)
|
|
@@ -2367,453 +2395,129 @@
|
|
|
2367
2395
|
}
|
|
2368
2396
|
|
|
2369
2397
|
/**
|
|
2370
|
-
* Clianta SDK -
|
|
2371
|
-
*
|
|
2398
|
+
* Clianta SDK - Event Triggers Manager
|
|
2399
|
+
* Manages event-driven automation and email notifications
|
|
2372
2400
|
*/
|
|
2373
2401
|
/**
|
|
2374
|
-
*
|
|
2402
|
+
* Event Triggers Manager
|
|
2403
|
+
* Handles event-driven automation based on CRM actions
|
|
2404
|
+
*
|
|
2405
|
+
* Similar to:
|
|
2406
|
+
* - Salesforce: Process Builder, Flow Automation
|
|
2407
|
+
* - HubSpot: Workflows, Email Sequences
|
|
2408
|
+
* - Pipedrive: Workflow Automation
|
|
2375
2409
|
*/
|
|
2376
|
-
class
|
|
2377
|
-
constructor(workspaceId,
|
|
2378
|
-
this.
|
|
2379
|
-
this.
|
|
2380
|
-
|
|
2381
|
-
this.pendingIdentify = null;
|
|
2382
|
-
if (!workspaceId) {
|
|
2383
|
-
throw new Error('[Clianta] Workspace ID is required');
|
|
2384
|
-
}
|
|
2410
|
+
class EventTriggersManager {
|
|
2411
|
+
constructor(apiEndpoint, workspaceId, authToken) {
|
|
2412
|
+
this.triggers = new Map();
|
|
2413
|
+
this.listeners = new Map();
|
|
2414
|
+
this.apiEndpoint = apiEndpoint;
|
|
2385
2415
|
this.workspaceId = workspaceId;
|
|
2386
|
-
this.
|
|
2387
|
-
// Setup debug mode
|
|
2388
|
-
logger.enabled = this.config.debug;
|
|
2389
|
-
logger.info(`Initializing SDK v${SDK_VERSION}`, { workspaceId });
|
|
2390
|
-
// Initialize consent manager
|
|
2391
|
-
this.consentManager = new ConsentManager({
|
|
2392
|
-
...this.config.consent,
|
|
2393
|
-
onConsentChange: (state, previous) => {
|
|
2394
|
-
this.onConsentChange(state, previous);
|
|
2395
|
-
},
|
|
2396
|
-
});
|
|
2397
|
-
// Initialize transport and queue
|
|
2398
|
-
this.transport = new Transport({ apiEndpoint: this.config.apiEndpoint });
|
|
2399
|
-
this.queue = new EventQueue(this.transport, {
|
|
2400
|
-
batchSize: this.config.batchSize,
|
|
2401
|
-
flushInterval: this.config.flushInterval,
|
|
2402
|
-
});
|
|
2403
|
-
// Get or create visitor and session IDs based on mode
|
|
2404
|
-
this.visitorId = this.createVisitorId();
|
|
2405
|
-
this.sessionId = this.createSessionId();
|
|
2406
|
-
logger.debug('IDs created', { visitorId: this.visitorId, sessionId: this.sessionId });
|
|
2407
|
-
// Initialize plugins
|
|
2408
|
-
this.initPlugins();
|
|
2409
|
-
this.isInitialized = true;
|
|
2410
|
-
logger.info('SDK initialized successfully');
|
|
2416
|
+
this.authToken = authToken;
|
|
2411
2417
|
}
|
|
2412
2418
|
/**
|
|
2413
|
-
*
|
|
2419
|
+
* Set authentication token
|
|
2414
2420
|
*/
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2421
|
+
setAuthToken(token) {
|
|
2422
|
+
this.authToken = token;
|
|
2423
|
+
}
|
|
2424
|
+
/**
|
|
2425
|
+
* Make authenticated API request
|
|
2426
|
+
*/
|
|
2427
|
+
async request(endpoint, options = {}) {
|
|
2428
|
+
const url = `${this.apiEndpoint}${endpoint}`;
|
|
2429
|
+
const headers = {
|
|
2430
|
+
'Content-Type': 'application/json',
|
|
2431
|
+
...(options.headers || {}),
|
|
2432
|
+
};
|
|
2433
|
+
if (this.authToken) {
|
|
2434
|
+
headers['Authorization'] = `Bearer ${this.authToken}`;
|
|
2425
2435
|
}
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2436
|
+
try {
|
|
2437
|
+
const response = await fetch(url, {
|
|
2438
|
+
...options,
|
|
2439
|
+
headers,
|
|
2440
|
+
});
|
|
2441
|
+
const data = await response.json();
|
|
2442
|
+
if (!response.ok) {
|
|
2443
|
+
return {
|
|
2444
|
+
success: false,
|
|
2445
|
+
error: data.message || 'Request failed',
|
|
2446
|
+
status: response.status,
|
|
2447
|
+
};
|
|
2432
2448
|
}
|
|
2433
|
-
return
|
|
2449
|
+
return {
|
|
2450
|
+
success: true,
|
|
2451
|
+
data: data.data || data,
|
|
2452
|
+
status: response.status,
|
|
2453
|
+
};
|
|
2454
|
+
}
|
|
2455
|
+
catch (error) {
|
|
2456
|
+
return {
|
|
2457
|
+
success: false,
|
|
2458
|
+
error: error instanceof Error ? error.message : 'Network error',
|
|
2459
|
+
status: 0,
|
|
2460
|
+
};
|
|
2434
2461
|
}
|
|
2435
|
-
// Normal mode
|
|
2436
|
-
return getOrCreateVisitorId(this.config.useCookies);
|
|
2437
2462
|
}
|
|
2463
|
+
// ============================================
|
|
2464
|
+
// TRIGGER MANAGEMENT
|
|
2465
|
+
// ============================================
|
|
2438
2466
|
/**
|
|
2439
|
-
*
|
|
2467
|
+
* Get all event triggers
|
|
2440
2468
|
*/
|
|
2441
|
-
|
|
2442
|
-
return
|
|
2469
|
+
async getTriggers() {
|
|
2470
|
+
return this.request(`/api/workspaces/${this.workspaceId}/triggers`);
|
|
2443
2471
|
}
|
|
2444
2472
|
/**
|
|
2445
|
-
*
|
|
2473
|
+
* Get a single trigger by ID
|
|
2446
2474
|
*/
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
// If analytics consent was just granted
|
|
2450
|
-
if (state.analytics && !previous.analytics) {
|
|
2451
|
-
// Upgrade from anonymous ID to persistent ID
|
|
2452
|
-
if (this.config.consent.anonymousMode) {
|
|
2453
|
-
this.visitorId = getOrCreateVisitorId(this.config.useCookies);
|
|
2454
|
-
logger.info('Upgraded from anonymous to persistent visitor ID');
|
|
2455
|
-
}
|
|
2456
|
-
// Flush buffered events
|
|
2457
|
-
const buffered = this.consentManager.flushBuffer();
|
|
2458
|
-
for (const event of buffered) {
|
|
2459
|
-
// Update event with new visitor ID
|
|
2460
|
-
event.visitorId = this.visitorId;
|
|
2461
|
-
this.queue.push(event);
|
|
2462
|
-
}
|
|
2463
|
-
}
|
|
2475
|
+
async getTrigger(triggerId) {
|
|
2476
|
+
return this.request(`/api/workspaces/${this.workspaceId}/triggers/${triggerId}`);
|
|
2464
2477
|
}
|
|
2465
2478
|
/**
|
|
2466
|
-
*
|
|
2467
|
-
* Handles both sync and async plugin init methods
|
|
2479
|
+
* Create a new event trigger
|
|
2468
2480
|
*/
|
|
2469
|
-
|
|
2470
|
-
const
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
const plugin = getPlugin(pluginName);
|
|
2478
|
-
// Handle both sync and async init (fire-and-forget for async)
|
|
2479
|
-
const result = plugin.init(this);
|
|
2480
|
-
if (result instanceof Promise) {
|
|
2481
|
-
result.catch((error) => {
|
|
2482
|
-
logger.error(`Async plugin init failed: ${pluginName}`, error);
|
|
2483
|
-
});
|
|
2484
|
-
}
|
|
2485
|
-
this.plugins.push(plugin);
|
|
2486
|
-
logger.debug(`Plugin loaded: ${pluginName}`);
|
|
2487
|
-
}
|
|
2488
|
-
catch (error) {
|
|
2489
|
-
logger.error(`Failed to load plugin: ${pluginName}`, error);
|
|
2490
|
-
}
|
|
2481
|
+
async createTrigger(trigger) {
|
|
2482
|
+
const result = await this.request(`/api/workspaces/${this.workspaceId}/triggers`, {
|
|
2483
|
+
method: 'POST',
|
|
2484
|
+
body: JSON.stringify(trigger),
|
|
2485
|
+
});
|
|
2486
|
+
// Cache the trigger locally if successful
|
|
2487
|
+
if (result.success && result.data?._id) {
|
|
2488
|
+
this.triggers.set(result.data._id, result.data);
|
|
2491
2489
|
}
|
|
2490
|
+
return result;
|
|
2492
2491
|
}
|
|
2493
2492
|
/**
|
|
2494
|
-
*
|
|
2493
|
+
* Update an existing trigger
|
|
2495
2494
|
*/
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
}
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
sessionId: this.sessionId,
|
|
2505
|
-
eventType: eventType,
|
|
2506
|
-
eventName,
|
|
2507
|
-
url: typeof window !== 'undefined' ? window.location.href : '',
|
|
2508
|
-
referrer: typeof document !== 'undefined' ? document.referrer || undefined : undefined,
|
|
2509
|
-
properties,
|
|
2510
|
-
device: getDeviceInfo(),
|
|
2511
|
-
...getUTMParams(),
|
|
2512
|
-
timestamp: new Date().toISOString(),
|
|
2513
|
-
sdkVersion: SDK_VERSION,
|
|
2514
|
-
};
|
|
2515
|
-
// Check consent before tracking
|
|
2516
|
-
if (!this.consentManager.canTrack()) {
|
|
2517
|
-
// Buffer event for later if waitForConsent is enabled
|
|
2518
|
-
if (this.config.consent.waitForConsent) {
|
|
2519
|
-
this.consentManager.bufferEvent(event);
|
|
2520
|
-
return;
|
|
2521
|
-
}
|
|
2522
|
-
// Otherwise drop the event
|
|
2523
|
-
logger.debug('Event dropped (no consent):', eventName);
|
|
2524
|
-
return;
|
|
2495
|
+
async updateTrigger(triggerId, updates) {
|
|
2496
|
+
const result = await this.request(`/api/workspaces/${this.workspaceId}/triggers/${triggerId}`, {
|
|
2497
|
+
method: 'PUT',
|
|
2498
|
+
body: JSON.stringify(updates),
|
|
2499
|
+
});
|
|
2500
|
+
// Update cache if successful
|
|
2501
|
+
if (result.success && result.data?._id) {
|
|
2502
|
+
this.triggers.set(result.data._id, result.data);
|
|
2525
2503
|
}
|
|
2526
|
-
|
|
2527
|
-
logger.debug('Event tracked:', eventName, properties);
|
|
2504
|
+
return result;
|
|
2528
2505
|
}
|
|
2529
2506
|
/**
|
|
2530
|
-
*
|
|
2507
|
+
* Delete a trigger
|
|
2531
2508
|
*/
|
|
2532
|
-
|
|
2533
|
-
const
|
|
2534
|
-
|
|
2535
|
-
...properties,
|
|
2536
|
-
path: typeof window !== 'undefined' ? window.location.pathname : '',
|
|
2509
|
+
async deleteTrigger(triggerId) {
|
|
2510
|
+
const result = await this.request(`/api/workspaces/${this.workspaceId}/triggers/${triggerId}`, {
|
|
2511
|
+
method: 'DELETE',
|
|
2537
2512
|
});
|
|
2513
|
+
// Remove from cache if successful
|
|
2514
|
+
if (result.success) {
|
|
2515
|
+
this.triggers.delete(triggerId);
|
|
2516
|
+
}
|
|
2517
|
+
return result;
|
|
2538
2518
|
}
|
|
2539
2519
|
/**
|
|
2540
|
-
*
|
|
2541
|
-
*/
|
|
2542
|
-
async identify(email, traits = {}) {
|
|
2543
|
-
if (!email) {
|
|
2544
|
-
logger.warn('Email is required for identification');
|
|
2545
|
-
return;
|
|
2546
|
-
}
|
|
2547
|
-
logger.info('Identifying visitor:', email);
|
|
2548
|
-
const result = await this.transport.sendIdentify({
|
|
2549
|
-
workspaceId: this.workspaceId,
|
|
2550
|
-
visitorId: this.visitorId,
|
|
2551
|
-
email,
|
|
2552
|
-
properties: traits,
|
|
2553
|
-
});
|
|
2554
|
-
if (result.success) {
|
|
2555
|
-
logger.info('Visitor identified successfully');
|
|
2556
|
-
this.pendingIdentify = null;
|
|
2557
|
-
}
|
|
2558
|
-
else {
|
|
2559
|
-
logger.error('Failed to identify visitor:', result.error);
|
|
2560
|
-
// Store for retry on next flush
|
|
2561
|
-
this.pendingIdentify = { email, traits };
|
|
2562
|
-
}
|
|
2563
|
-
}
|
|
2564
|
-
/**
|
|
2565
|
-
* Retry pending identify call
|
|
2566
|
-
*/
|
|
2567
|
-
async retryPendingIdentify() {
|
|
2568
|
-
if (!this.pendingIdentify)
|
|
2569
|
-
return;
|
|
2570
|
-
const { email, traits } = this.pendingIdentify;
|
|
2571
|
-
this.pendingIdentify = null;
|
|
2572
|
-
await this.identify(email, traits);
|
|
2573
|
-
}
|
|
2574
|
-
/**
|
|
2575
|
-
* Update consent state
|
|
2576
|
-
*/
|
|
2577
|
-
consent(state) {
|
|
2578
|
-
this.consentManager.update(state);
|
|
2579
|
-
}
|
|
2580
|
-
/**
|
|
2581
|
-
* Get current consent state
|
|
2582
|
-
*/
|
|
2583
|
-
getConsentState() {
|
|
2584
|
-
return this.consentManager.getState();
|
|
2585
|
-
}
|
|
2586
|
-
/**
|
|
2587
|
-
* Toggle debug mode
|
|
2588
|
-
*/
|
|
2589
|
-
debug(enabled) {
|
|
2590
|
-
logger.enabled = enabled;
|
|
2591
|
-
logger.info(`Debug mode ${enabled ? 'enabled' : 'disabled'}`);
|
|
2592
|
-
}
|
|
2593
|
-
/**
|
|
2594
|
-
* Get visitor ID
|
|
2595
|
-
*/
|
|
2596
|
-
getVisitorId() {
|
|
2597
|
-
return this.visitorId;
|
|
2598
|
-
}
|
|
2599
|
-
/**
|
|
2600
|
-
* Get session ID
|
|
2601
|
-
*/
|
|
2602
|
-
getSessionId() {
|
|
2603
|
-
return this.sessionId;
|
|
2604
|
-
}
|
|
2605
|
-
/**
|
|
2606
|
-
* Get workspace ID
|
|
2607
|
-
*/
|
|
2608
|
-
getWorkspaceId() {
|
|
2609
|
-
return this.workspaceId;
|
|
2610
|
-
}
|
|
2611
|
-
/**
|
|
2612
|
-
* Get current configuration
|
|
2613
|
-
*/
|
|
2614
|
-
getConfig() {
|
|
2615
|
-
return { ...this.config };
|
|
2616
|
-
}
|
|
2617
|
-
/**
|
|
2618
|
-
* Force flush event queue
|
|
2619
|
-
*/
|
|
2620
|
-
async flush() {
|
|
2621
|
-
await this.retryPendingIdentify();
|
|
2622
|
-
await this.queue.flush();
|
|
2623
|
-
}
|
|
2624
|
-
/**
|
|
2625
|
-
* Reset visitor and session (for logout)
|
|
2626
|
-
*/
|
|
2627
|
-
reset() {
|
|
2628
|
-
logger.info('Resetting visitor data');
|
|
2629
|
-
resetIds(this.config.useCookies);
|
|
2630
|
-
this.visitorId = this.createVisitorId();
|
|
2631
|
-
this.sessionId = this.createSessionId();
|
|
2632
|
-
this.queue.clear();
|
|
2633
|
-
}
|
|
2634
|
-
/**
|
|
2635
|
-
* Delete all stored user data (GDPR right-to-erasure)
|
|
2636
|
-
*/
|
|
2637
|
-
deleteData() {
|
|
2638
|
-
logger.info('Deleting all user data (GDPR request)');
|
|
2639
|
-
// Clear queue
|
|
2640
|
-
this.queue.clear();
|
|
2641
|
-
// Reset consent
|
|
2642
|
-
this.consentManager.reset();
|
|
2643
|
-
// Clear all stored IDs
|
|
2644
|
-
resetIds(this.config.useCookies);
|
|
2645
|
-
// Clear session storage items
|
|
2646
|
-
if (typeof sessionStorage !== 'undefined') {
|
|
2647
|
-
try {
|
|
2648
|
-
sessionStorage.removeItem(STORAGE_KEYS.VISITOR_ID);
|
|
2649
|
-
sessionStorage.removeItem(STORAGE_KEYS.VISITOR_ID + '_anon');
|
|
2650
|
-
sessionStorage.removeItem(STORAGE_KEYS.SESSION_ID);
|
|
2651
|
-
sessionStorage.removeItem(STORAGE_KEYS.SESSION_TIMESTAMP);
|
|
2652
|
-
}
|
|
2653
|
-
catch {
|
|
2654
|
-
// Ignore errors
|
|
2655
|
-
}
|
|
2656
|
-
}
|
|
2657
|
-
// Clear localStorage items
|
|
2658
|
-
if (typeof localStorage !== 'undefined') {
|
|
2659
|
-
try {
|
|
2660
|
-
localStorage.removeItem(STORAGE_KEYS.VISITOR_ID);
|
|
2661
|
-
localStorage.removeItem(STORAGE_KEYS.CONSENT);
|
|
2662
|
-
localStorage.removeItem(STORAGE_KEYS.EVENT_QUEUE);
|
|
2663
|
-
}
|
|
2664
|
-
catch {
|
|
2665
|
-
// Ignore errors
|
|
2666
|
-
}
|
|
2667
|
-
}
|
|
2668
|
-
// Generate new IDs
|
|
2669
|
-
this.visitorId = this.createVisitorId();
|
|
2670
|
-
this.sessionId = this.createSessionId();
|
|
2671
|
-
logger.info('All user data deleted');
|
|
2672
|
-
}
|
|
2673
|
-
/**
|
|
2674
|
-
* Destroy tracker and cleanup
|
|
2675
|
-
*/
|
|
2676
|
-
async destroy() {
|
|
2677
|
-
logger.info('Destroying tracker');
|
|
2678
|
-
// Flush any remaining events (await to ensure completion)
|
|
2679
|
-
await this.queue.flush();
|
|
2680
|
-
// Destroy plugins
|
|
2681
|
-
for (const plugin of this.plugins) {
|
|
2682
|
-
if (plugin.destroy) {
|
|
2683
|
-
plugin.destroy();
|
|
2684
|
-
}
|
|
2685
|
-
}
|
|
2686
|
-
this.plugins = [];
|
|
2687
|
-
// Destroy queue
|
|
2688
|
-
this.queue.destroy();
|
|
2689
|
-
this.isInitialized = false;
|
|
2690
|
-
}
|
|
2691
|
-
}
|
|
2692
|
-
|
|
2693
|
-
/**
|
|
2694
|
-
* Clianta SDK - Event Triggers Manager
|
|
2695
|
-
* Manages event-driven automation and email notifications
|
|
2696
|
-
*/
|
|
2697
|
-
/**
|
|
2698
|
-
* Event Triggers Manager
|
|
2699
|
-
* Handles event-driven automation based on CRM actions
|
|
2700
|
-
*
|
|
2701
|
-
* Similar to:
|
|
2702
|
-
* - Salesforce: Process Builder, Flow Automation
|
|
2703
|
-
* - HubSpot: Workflows, Email Sequences
|
|
2704
|
-
* - Pipedrive: Workflow Automation
|
|
2705
|
-
*/
|
|
2706
|
-
class EventTriggersManager {
|
|
2707
|
-
constructor(apiEndpoint, workspaceId, authToken) {
|
|
2708
|
-
this.triggers = new Map();
|
|
2709
|
-
this.listeners = new Map();
|
|
2710
|
-
this.apiEndpoint = apiEndpoint;
|
|
2711
|
-
this.workspaceId = workspaceId;
|
|
2712
|
-
this.authToken = authToken;
|
|
2713
|
-
}
|
|
2714
|
-
/**
|
|
2715
|
-
* Set authentication token
|
|
2716
|
-
*/
|
|
2717
|
-
setAuthToken(token) {
|
|
2718
|
-
this.authToken = token;
|
|
2719
|
-
}
|
|
2720
|
-
/**
|
|
2721
|
-
* Make authenticated API request
|
|
2722
|
-
*/
|
|
2723
|
-
async request(endpoint, options = {}) {
|
|
2724
|
-
const url = `${this.apiEndpoint}${endpoint}`;
|
|
2725
|
-
const headers = {
|
|
2726
|
-
'Content-Type': 'application/json',
|
|
2727
|
-
...(options.headers || {}),
|
|
2728
|
-
};
|
|
2729
|
-
if (this.authToken) {
|
|
2730
|
-
headers['Authorization'] = `Bearer ${this.authToken}`;
|
|
2731
|
-
}
|
|
2732
|
-
try {
|
|
2733
|
-
const response = await fetch(url, {
|
|
2734
|
-
...options,
|
|
2735
|
-
headers,
|
|
2736
|
-
});
|
|
2737
|
-
const data = await response.json();
|
|
2738
|
-
if (!response.ok) {
|
|
2739
|
-
return {
|
|
2740
|
-
success: false,
|
|
2741
|
-
error: data.message || 'Request failed',
|
|
2742
|
-
status: response.status,
|
|
2743
|
-
};
|
|
2744
|
-
}
|
|
2745
|
-
return {
|
|
2746
|
-
success: true,
|
|
2747
|
-
data: data.data || data,
|
|
2748
|
-
status: response.status,
|
|
2749
|
-
};
|
|
2750
|
-
}
|
|
2751
|
-
catch (error) {
|
|
2752
|
-
return {
|
|
2753
|
-
success: false,
|
|
2754
|
-
error: error instanceof Error ? error.message : 'Network error',
|
|
2755
|
-
status: 0,
|
|
2756
|
-
};
|
|
2757
|
-
}
|
|
2758
|
-
}
|
|
2759
|
-
// ============================================
|
|
2760
|
-
// TRIGGER MANAGEMENT
|
|
2761
|
-
// ============================================
|
|
2762
|
-
/**
|
|
2763
|
-
* Get all event triggers
|
|
2764
|
-
*/
|
|
2765
|
-
async getTriggers() {
|
|
2766
|
-
return this.request(`/api/workspaces/${this.workspaceId}/triggers`);
|
|
2767
|
-
}
|
|
2768
|
-
/**
|
|
2769
|
-
* Get a single trigger by ID
|
|
2770
|
-
*/
|
|
2771
|
-
async getTrigger(triggerId) {
|
|
2772
|
-
return this.request(`/api/workspaces/${this.workspaceId}/triggers/${triggerId}`);
|
|
2773
|
-
}
|
|
2774
|
-
/**
|
|
2775
|
-
* Create a new event trigger
|
|
2776
|
-
*/
|
|
2777
|
-
async createTrigger(trigger) {
|
|
2778
|
-
const result = await this.request(`/api/workspaces/${this.workspaceId}/triggers`, {
|
|
2779
|
-
method: 'POST',
|
|
2780
|
-
body: JSON.stringify(trigger),
|
|
2781
|
-
});
|
|
2782
|
-
// Cache the trigger locally if successful
|
|
2783
|
-
if (result.success && result.data?._id) {
|
|
2784
|
-
this.triggers.set(result.data._id, result.data);
|
|
2785
|
-
}
|
|
2786
|
-
return result;
|
|
2787
|
-
}
|
|
2788
|
-
/**
|
|
2789
|
-
* Update an existing trigger
|
|
2790
|
-
*/
|
|
2791
|
-
async updateTrigger(triggerId, updates) {
|
|
2792
|
-
const result = await this.request(`/api/workspaces/${this.workspaceId}/triggers/${triggerId}`, {
|
|
2793
|
-
method: 'PUT',
|
|
2794
|
-
body: JSON.stringify(updates),
|
|
2795
|
-
});
|
|
2796
|
-
// Update cache if successful
|
|
2797
|
-
if (result.success && result.data?._id) {
|
|
2798
|
-
this.triggers.set(result.data._id, result.data);
|
|
2799
|
-
}
|
|
2800
|
-
return result;
|
|
2801
|
-
}
|
|
2802
|
-
/**
|
|
2803
|
-
* Delete a trigger
|
|
2804
|
-
*/
|
|
2805
|
-
async deleteTrigger(triggerId) {
|
|
2806
|
-
const result = await this.request(`/api/workspaces/${this.workspaceId}/triggers/${triggerId}`, {
|
|
2807
|
-
method: 'DELETE',
|
|
2808
|
-
});
|
|
2809
|
-
// Remove from cache if successful
|
|
2810
|
-
if (result.success) {
|
|
2811
|
-
this.triggers.delete(triggerId);
|
|
2812
|
-
}
|
|
2813
|
-
return result;
|
|
2814
|
-
}
|
|
2815
|
-
/**
|
|
2816
|
-
* Activate a trigger
|
|
2520
|
+
* Activate a trigger
|
|
2817
2521
|
*/
|
|
2818
2522
|
async activateTrigger(triggerId) {
|
|
2819
2523
|
return this.updateTrigger(triggerId, { isActive: true });
|
|
@@ -3132,19 +2836,29 @@
|
|
|
3132
2836
|
* CRM API Client for managing contacts and opportunities
|
|
3133
2837
|
*/
|
|
3134
2838
|
class CRMClient {
|
|
3135
|
-
constructor(apiEndpoint, workspaceId, authToken) {
|
|
2839
|
+
constructor(apiEndpoint, workspaceId, authToken, apiKey) {
|
|
3136
2840
|
this.apiEndpoint = apiEndpoint;
|
|
3137
2841
|
this.workspaceId = workspaceId;
|
|
3138
2842
|
this.authToken = authToken;
|
|
2843
|
+
this.apiKey = apiKey;
|
|
3139
2844
|
this.triggers = new EventTriggersManager(apiEndpoint, workspaceId, authToken);
|
|
3140
2845
|
}
|
|
3141
2846
|
/**
|
|
3142
|
-
* Set authentication token for API requests
|
|
2847
|
+
* Set authentication token for API requests (user JWT)
|
|
3143
2848
|
*/
|
|
3144
2849
|
setAuthToken(token) {
|
|
3145
2850
|
this.authToken = token;
|
|
2851
|
+
this.apiKey = undefined;
|
|
3146
2852
|
this.triggers.setAuthToken(token);
|
|
3147
2853
|
}
|
|
2854
|
+
/**
|
|
2855
|
+
* Set workspace API key for server-to-server requests.
|
|
2856
|
+
* Use this instead of setAuthToken when integrating from an external app.
|
|
2857
|
+
*/
|
|
2858
|
+
setApiKey(key) {
|
|
2859
|
+
this.apiKey = key;
|
|
2860
|
+
this.authToken = undefined;
|
|
2861
|
+
}
|
|
3148
2862
|
/**
|
|
3149
2863
|
* Validate required parameter exists
|
|
3150
2864
|
* @throws {Error} if value is null/undefined or empty string
|
|
@@ -3163,7 +2877,10 @@
|
|
|
3163
2877
|
'Content-Type': 'application/json',
|
|
3164
2878
|
...(options.headers || {}),
|
|
3165
2879
|
};
|
|
3166
|
-
if (this.
|
|
2880
|
+
if (this.apiKey) {
|
|
2881
|
+
headers['X-Api-Key'] = this.apiKey;
|
|
2882
|
+
}
|
|
2883
|
+
else if (this.authToken) {
|
|
3167
2884
|
headers['Authorization'] = `Bearer ${this.authToken}`;
|
|
3168
2885
|
}
|
|
3169
2886
|
try {
|
|
@@ -3194,6 +2911,65 @@
|
|
|
3194
2911
|
}
|
|
3195
2912
|
}
|
|
3196
2913
|
// ============================================
|
|
2914
|
+
// INBOUND EVENTS API (API-key authenticated)
|
|
2915
|
+
// ============================================
|
|
2916
|
+
/**
|
|
2917
|
+
* Send an inbound event from an external app (e.g. user signup on client website).
|
|
2918
|
+
* Requires the client to be initialized with an API key via setApiKey() or the constructor.
|
|
2919
|
+
*
|
|
2920
|
+
* The contact is upserted in the CRM and matching workflow automations fire automatically.
|
|
2921
|
+
*
|
|
2922
|
+
* @example
|
|
2923
|
+
* const crm = new CRMClient('https://api.clianta.online', 'WORKSPACE_ID');
|
|
2924
|
+
* crm.setApiKey('mm_live_...');
|
|
2925
|
+
*
|
|
2926
|
+
* await crm.sendEvent({
|
|
2927
|
+
* event: 'user.registered',
|
|
2928
|
+
* contact: { email: 'alice@example.com', firstName: 'Alice' },
|
|
2929
|
+
* data: { plan: 'free', signupSource: 'homepage' },
|
|
2930
|
+
* });
|
|
2931
|
+
*/
|
|
2932
|
+
async sendEvent(payload) {
|
|
2933
|
+
const url = `${this.apiEndpoint}/api/public/events`;
|
|
2934
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
2935
|
+
if (this.apiKey) {
|
|
2936
|
+
headers['X-Api-Key'] = this.apiKey;
|
|
2937
|
+
}
|
|
2938
|
+
else if (this.authToken) {
|
|
2939
|
+
headers['Authorization'] = `Bearer ${this.authToken}`;
|
|
2940
|
+
}
|
|
2941
|
+
try {
|
|
2942
|
+
const response = await fetch(url, {
|
|
2943
|
+
method: 'POST',
|
|
2944
|
+
headers,
|
|
2945
|
+
body: JSON.stringify(payload),
|
|
2946
|
+
});
|
|
2947
|
+
const data = await response.json();
|
|
2948
|
+
if (!response.ok) {
|
|
2949
|
+
return {
|
|
2950
|
+
success: false,
|
|
2951
|
+
contactCreated: false,
|
|
2952
|
+
event: payload.event,
|
|
2953
|
+
error: data.error || 'Request failed',
|
|
2954
|
+
};
|
|
2955
|
+
}
|
|
2956
|
+
return {
|
|
2957
|
+
success: data.success,
|
|
2958
|
+
contactCreated: data.contactCreated,
|
|
2959
|
+
contactId: data.contactId,
|
|
2960
|
+
event: data.event,
|
|
2961
|
+
};
|
|
2962
|
+
}
|
|
2963
|
+
catch (error) {
|
|
2964
|
+
return {
|
|
2965
|
+
success: false,
|
|
2966
|
+
contactCreated: false,
|
|
2967
|
+
event: payload.event,
|
|
2968
|
+
error: error instanceof Error ? error.message : 'Network error',
|
|
2969
|
+
};
|
|
2970
|
+
}
|
|
2971
|
+
}
|
|
2972
|
+
// ============================================
|
|
3197
2973
|
// CONTACTS API
|
|
3198
2974
|
// ============================================
|
|
3199
2975
|
/**
|
|
@@ -3377,319 +3153,671 @@
|
|
|
3377
3153
|
return this.request(endpoint);
|
|
3378
3154
|
}
|
|
3379
3155
|
/**
|
|
3380
|
-
* Get deals/opportunities belonging to a company
|
|
3156
|
+
* Get deals/opportunities belonging to a company
|
|
3157
|
+
*/
|
|
3158
|
+
async getCompanyDeals(companyId, params) {
|
|
3159
|
+
const queryParams = new URLSearchParams();
|
|
3160
|
+
if (params?.page)
|
|
3161
|
+
queryParams.set('page', params.page.toString());
|
|
3162
|
+
if (params?.limit)
|
|
3163
|
+
queryParams.set('limit', params.limit.toString());
|
|
3164
|
+
const query = queryParams.toString();
|
|
3165
|
+
const endpoint = `/api/workspaces/${this.workspaceId}/companies/${companyId}/deals${query ? `?${query}` : ''}`;
|
|
3166
|
+
return this.request(endpoint);
|
|
3167
|
+
}
|
|
3168
|
+
// ============================================
|
|
3169
|
+
// PIPELINES API
|
|
3170
|
+
// ============================================
|
|
3171
|
+
/**
|
|
3172
|
+
* Get all pipelines
|
|
3173
|
+
*/
|
|
3174
|
+
async getPipelines() {
|
|
3175
|
+
return this.request(`/api/workspaces/${this.workspaceId}/pipelines`);
|
|
3176
|
+
}
|
|
3177
|
+
/**
|
|
3178
|
+
* Get a single pipeline by ID
|
|
3179
|
+
*/
|
|
3180
|
+
async getPipeline(pipelineId) {
|
|
3181
|
+
return this.request(`/api/workspaces/${this.workspaceId}/pipelines/${pipelineId}`);
|
|
3182
|
+
}
|
|
3183
|
+
/**
|
|
3184
|
+
* Create a new pipeline
|
|
3185
|
+
*/
|
|
3186
|
+
async createPipeline(pipeline) {
|
|
3187
|
+
return this.request(`/api/workspaces/${this.workspaceId}/pipelines`, {
|
|
3188
|
+
method: 'POST',
|
|
3189
|
+
body: JSON.stringify(pipeline),
|
|
3190
|
+
});
|
|
3191
|
+
}
|
|
3192
|
+
/**
|
|
3193
|
+
* Update an existing pipeline
|
|
3194
|
+
*/
|
|
3195
|
+
async updatePipeline(pipelineId, updates) {
|
|
3196
|
+
return this.request(`/api/workspaces/${this.workspaceId}/pipelines/${pipelineId}`, {
|
|
3197
|
+
method: 'PUT',
|
|
3198
|
+
body: JSON.stringify(updates),
|
|
3199
|
+
});
|
|
3200
|
+
}
|
|
3201
|
+
/**
|
|
3202
|
+
* Delete a pipeline
|
|
3203
|
+
*/
|
|
3204
|
+
async deletePipeline(pipelineId) {
|
|
3205
|
+
return this.request(`/api/workspaces/${this.workspaceId}/pipelines/${pipelineId}`, {
|
|
3206
|
+
method: 'DELETE',
|
|
3207
|
+
});
|
|
3208
|
+
}
|
|
3209
|
+
// ============================================
|
|
3210
|
+
// TASKS API
|
|
3211
|
+
// ============================================
|
|
3212
|
+
/**
|
|
3213
|
+
* Get all tasks with pagination
|
|
3214
|
+
*/
|
|
3215
|
+
async getTasks(params) {
|
|
3216
|
+
const queryParams = new URLSearchParams();
|
|
3217
|
+
if (params?.page)
|
|
3218
|
+
queryParams.set('page', params.page.toString());
|
|
3219
|
+
if (params?.limit)
|
|
3220
|
+
queryParams.set('limit', params.limit.toString());
|
|
3221
|
+
if (params?.status)
|
|
3222
|
+
queryParams.set('status', params.status);
|
|
3223
|
+
if (params?.priority)
|
|
3224
|
+
queryParams.set('priority', params.priority);
|
|
3225
|
+
if (params?.contactId)
|
|
3226
|
+
queryParams.set('contactId', params.contactId);
|
|
3227
|
+
if (params?.companyId)
|
|
3228
|
+
queryParams.set('companyId', params.companyId);
|
|
3229
|
+
if (params?.opportunityId)
|
|
3230
|
+
queryParams.set('opportunityId', params.opportunityId);
|
|
3231
|
+
const query = queryParams.toString();
|
|
3232
|
+
const endpoint = `/api/workspaces/${this.workspaceId}/tasks${query ? `?${query}` : ''}`;
|
|
3233
|
+
return this.request(endpoint);
|
|
3234
|
+
}
|
|
3235
|
+
/**
|
|
3236
|
+
* Get a single task by ID
|
|
3237
|
+
*/
|
|
3238
|
+
async getTask(taskId) {
|
|
3239
|
+
return this.request(`/api/workspaces/${this.workspaceId}/tasks/${taskId}`);
|
|
3240
|
+
}
|
|
3241
|
+
/**
|
|
3242
|
+
* Create a new task
|
|
3243
|
+
*/
|
|
3244
|
+
async createTask(task) {
|
|
3245
|
+
return this.request(`/api/workspaces/${this.workspaceId}/tasks`, {
|
|
3246
|
+
method: 'POST',
|
|
3247
|
+
body: JSON.stringify(task),
|
|
3248
|
+
});
|
|
3249
|
+
}
|
|
3250
|
+
/**
|
|
3251
|
+
* Update an existing task
|
|
3252
|
+
*/
|
|
3253
|
+
async updateTask(taskId, updates) {
|
|
3254
|
+
return this.request(`/api/workspaces/${this.workspaceId}/tasks/${taskId}`, {
|
|
3255
|
+
method: 'PUT',
|
|
3256
|
+
body: JSON.stringify(updates),
|
|
3257
|
+
});
|
|
3258
|
+
}
|
|
3259
|
+
/**
|
|
3260
|
+
* Mark a task as completed
|
|
3261
|
+
*/
|
|
3262
|
+
async completeTask(taskId) {
|
|
3263
|
+
return this.request(`/api/workspaces/${this.workspaceId}/tasks/${taskId}/complete`, {
|
|
3264
|
+
method: 'PATCH',
|
|
3265
|
+
});
|
|
3266
|
+
}
|
|
3267
|
+
/**
|
|
3268
|
+
* Delete a task
|
|
3269
|
+
*/
|
|
3270
|
+
async deleteTask(taskId) {
|
|
3271
|
+
return this.request(`/api/workspaces/${this.workspaceId}/tasks/${taskId}`, {
|
|
3272
|
+
method: 'DELETE',
|
|
3273
|
+
});
|
|
3274
|
+
}
|
|
3275
|
+
// ============================================
|
|
3276
|
+
// ACTIVITIES API
|
|
3277
|
+
// ============================================
|
|
3278
|
+
/**
|
|
3279
|
+
* Get activities for a contact
|
|
3280
|
+
*/
|
|
3281
|
+
async getContactActivities(contactId, params) {
|
|
3282
|
+
const queryParams = new URLSearchParams();
|
|
3283
|
+
if (params?.page)
|
|
3284
|
+
queryParams.set('page', params.page.toString());
|
|
3285
|
+
if (params?.limit)
|
|
3286
|
+
queryParams.set('limit', params.limit.toString());
|
|
3287
|
+
if (params?.type)
|
|
3288
|
+
queryParams.set('type', params.type);
|
|
3289
|
+
const query = queryParams.toString();
|
|
3290
|
+
const endpoint = `/api/workspaces/${this.workspaceId}/contacts/${contactId}/activities${query ? `?${query}` : ''}`;
|
|
3291
|
+
return this.request(endpoint);
|
|
3292
|
+
}
|
|
3293
|
+
/**
|
|
3294
|
+
* Get activities for an opportunity/deal
|
|
3295
|
+
*/
|
|
3296
|
+
async getOpportunityActivities(opportunityId, params) {
|
|
3297
|
+
const queryParams = new URLSearchParams();
|
|
3298
|
+
if (params?.page)
|
|
3299
|
+
queryParams.set('page', params.page.toString());
|
|
3300
|
+
if (params?.limit)
|
|
3301
|
+
queryParams.set('limit', params.limit.toString());
|
|
3302
|
+
if (params?.type)
|
|
3303
|
+
queryParams.set('type', params.type);
|
|
3304
|
+
const query = queryParams.toString();
|
|
3305
|
+
const endpoint = `/api/workspaces/${this.workspaceId}/opportunities/${opportunityId}/activities${query ? `?${query}` : ''}`;
|
|
3306
|
+
return this.request(endpoint);
|
|
3307
|
+
}
|
|
3308
|
+
/**
|
|
3309
|
+
* Create a new activity
|
|
3310
|
+
*/
|
|
3311
|
+
async createActivity(activity) {
|
|
3312
|
+
// Determine the correct endpoint based on related entity
|
|
3313
|
+
let endpoint;
|
|
3314
|
+
if (activity.opportunityId) {
|
|
3315
|
+
endpoint = `/api/workspaces/${this.workspaceId}/opportunities/${activity.opportunityId}/activities`;
|
|
3316
|
+
}
|
|
3317
|
+
else if (activity.contactId) {
|
|
3318
|
+
endpoint = `/api/workspaces/${this.workspaceId}/contacts/${activity.contactId}/activities`;
|
|
3319
|
+
}
|
|
3320
|
+
else {
|
|
3321
|
+
endpoint = `/api/workspaces/${this.workspaceId}/activities`;
|
|
3322
|
+
}
|
|
3323
|
+
return this.request(endpoint, {
|
|
3324
|
+
method: 'POST',
|
|
3325
|
+
body: JSON.stringify(activity),
|
|
3326
|
+
});
|
|
3327
|
+
}
|
|
3328
|
+
/**
|
|
3329
|
+
* Update an existing activity
|
|
3330
|
+
*/
|
|
3331
|
+
async updateActivity(activityId, updates) {
|
|
3332
|
+
return this.request(`/api/workspaces/${this.workspaceId}/activities/${activityId}`, {
|
|
3333
|
+
method: 'PATCH',
|
|
3334
|
+
body: JSON.stringify(updates),
|
|
3335
|
+
});
|
|
3336
|
+
}
|
|
3337
|
+
/**
|
|
3338
|
+
* Delete an activity
|
|
3339
|
+
*/
|
|
3340
|
+
async deleteActivity(activityId) {
|
|
3341
|
+
return this.request(`/api/workspaces/${this.workspaceId}/activities/${activityId}`, {
|
|
3342
|
+
method: 'DELETE',
|
|
3343
|
+
});
|
|
3344
|
+
}
|
|
3345
|
+
/**
|
|
3346
|
+
* Log a call activity
|
|
3347
|
+
*/
|
|
3348
|
+
async logCall(data) {
|
|
3349
|
+
return this.createActivity({
|
|
3350
|
+
type: 'call',
|
|
3351
|
+
title: `${data.direction === 'inbound' ? 'Inbound' : 'Outbound'} Call`,
|
|
3352
|
+
direction: data.direction,
|
|
3353
|
+
duration: data.duration,
|
|
3354
|
+
outcome: data.outcome,
|
|
3355
|
+
description: data.notes,
|
|
3356
|
+
contactId: data.contactId,
|
|
3357
|
+
opportunityId: data.opportunityId,
|
|
3358
|
+
});
|
|
3359
|
+
}
|
|
3360
|
+
/**
|
|
3361
|
+
* Log a meeting activity
|
|
3362
|
+
*/
|
|
3363
|
+
async logMeeting(data) {
|
|
3364
|
+
return this.createActivity({
|
|
3365
|
+
type: 'meeting',
|
|
3366
|
+
title: data.title,
|
|
3367
|
+
duration: data.duration,
|
|
3368
|
+
outcome: data.outcome,
|
|
3369
|
+
description: data.notes,
|
|
3370
|
+
contactId: data.contactId,
|
|
3371
|
+
opportunityId: data.opportunityId,
|
|
3372
|
+
});
|
|
3373
|
+
}
|
|
3374
|
+
/**
|
|
3375
|
+
* Add a note to a contact or opportunity
|
|
3376
|
+
*/
|
|
3377
|
+
async addNote(data) {
|
|
3378
|
+
return this.createActivity({
|
|
3379
|
+
type: 'note',
|
|
3380
|
+
title: 'Note',
|
|
3381
|
+
description: data.content,
|
|
3382
|
+
contactId: data.contactId,
|
|
3383
|
+
opportunityId: data.opportunityId,
|
|
3384
|
+
});
|
|
3385
|
+
}
|
|
3386
|
+
// ============================================
|
|
3387
|
+
// EMAIL TEMPLATES API
|
|
3388
|
+
// ============================================
|
|
3389
|
+
/**
|
|
3390
|
+
* Get all email templates
|
|
3381
3391
|
*/
|
|
3382
|
-
async
|
|
3392
|
+
async getEmailTemplates(params) {
|
|
3383
3393
|
const queryParams = new URLSearchParams();
|
|
3384
3394
|
if (params?.page)
|
|
3385
3395
|
queryParams.set('page', params.page.toString());
|
|
3386
3396
|
if (params?.limit)
|
|
3387
3397
|
queryParams.set('limit', params.limit.toString());
|
|
3388
3398
|
const query = queryParams.toString();
|
|
3389
|
-
const endpoint = `/api/workspaces/${this.workspaceId}/
|
|
3399
|
+
const endpoint = `/api/workspaces/${this.workspaceId}/email-templates${query ? `?${query}` : ''}`;
|
|
3390
3400
|
return this.request(endpoint);
|
|
3391
3401
|
}
|
|
3392
|
-
// ============================================
|
|
3393
|
-
// PIPELINES API
|
|
3394
|
-
// ============================================
|
|
3395
|
-
/**
|
|
3396
|
-
* Get all pipelines
|
|
3397
|
-
*/
|
|
3398
|
-
async getPipelines() {
|
|
3399
|
-
return this.request(`/api/workspaces/${this.workspaceId}/pipelines`);
|
|
3400
|
-
}
|
|
3401
3402
|
/**
|
|
3402
|
-
* Get a single
|
|
3403
|
+
* Get a single email template by ID
|
|
3403
3404
|
*/
|
|
3404
|
-
async
|
|
3405
|
-
return this.request(`/api/workspaces/${this.workspaceId}/
|
|
3405
|
+
async getEmailTemplate(templateId) {
|
|
3406
|
+
return this.request(`/api/workspaces/${this.workspaceId}/email-templates/${templateId}`);
|
|
3406
3407
|
}
|
|
3407
3408
|
/**
|
|
3408
|
-
* Create a new
|
|
3409
|
+
* Create a new email template
|
|
3409
3410
|
*/
|
|
3410
|
-
async
|
|
3411
|
-
return this.request(`/api/workspaces/${this.workspaceId}/
|
|
3411
|
+
async createEmailTemplate(template) {
|
|
3412
|
+
return this.request(`/api/workspaces/${this.workspaceId}/email-templates`, {
|
|
3412
3413
|
method: 'POST',
|
|
3413
|
-
body: JSON.stringify(
|
|
3414
|
+
body: JSON.stringify(template),
|
|
3414
3415
|
});
|
|
3415
3416
|
}
|
|
3416
3417
|
/**
|
|
3417
|
-
* Update an
|
|
3418
|
+
* Update an email template
|
|
3418
3419
|
*/
|
|
3419
|
-
async
|
|
3420
|
-
return this.request(`/api/workspaces/${this.workspaceId}/
|
|
3420
|
+
async updateEmailTemplate(templateId, updates) {
|
|
3421
|
+
return this.request(`/api/workspaces/${this.workspaceId}/email-templates/${templateId}`, {
|
|
3421
3422
|
method: 'PUT',
|
|
3422
3423
|
body: JSON.stringify(updates),
|
|
3423
3424
|
});
|
|
3424
3425
|
}
|
|
3425
3426
|
/**
|
|
3426
|
-
* Delete
|
|
3427
|
+
* Delete an email template
|
|
3427
3428
|
*/
|
|
3428
|
-
async
|
|
3429
|
-
return this.request(`/api/workspaces/${this.workspaceId}/
|
|
3429
|
+
async deleteEmailTemplate(templateId) {
|
|
3430
|
+
return this.request(`/api/workspaces/${this.workspaceId}/email-templates/${templateId}`, {
|
|
3430
3431
|
method: 'DELETE',
|
|
3431
3432
|
});
|
|
3432
3433
|
}
|
|
3434
|
+
/**
|
|
3435
|
+
* Send an email using a template
|
|
3436
|
+
*/
|
|
3437
|
+
async sendEmail(data) {
|
|
3438
|
+
return this.request(`/api/workspaces/${this.workspaceId}/emails/send`, {
|
|
3439
|
+
method: 'POST',
|
|
3440
|
+
body: JSON.stringify(data),
|
|
3441
|
+
});
|
|
3442
|
+
}
|
|
3433
3443
|
// ============================================
|
|
3434
|
-
//
|
|
3444
|
+
// EVENT TRIGGERS API (delegated to triggers manager)
|
|
3435
3445
|
// ============================================
|
|
3436
3446
|
/**
|
|
3437
|
-
* Get all
|
|
3447
|
+
* Get all event triggers
|
|
3438
3448
|
*/
|
|
3439
|
-
async
|
|
3440
|
-
|
|
3441
|
-
if (params?.page)
|
|
3442
|
-
queryParams.set('page', params.page.toString());
|
|
3443
|
-
if (params?.limit)
|
|
3444
|
-
queryParams.set('limit', params.limit.toString());
|
|
3445
|
-
if (params?.status)
|
|
3446
|
-
queryParams.set('status', params.status);
|
|
3447
|
-
if (params?.priority)
|
|
3448
|
-
queryParams.set('priority', params.priority);
|
|
3449
|
-
if (params?.contactId)
|
|
3450
|
-
queryParams.set('contactId', params.contactId);
|
|
3451
|
-
if (params?.companyId)
|
|
3452
|
-
queryParams.set('companyId', params.companyId);
|
|
3453
|
-
if (params?.opportunityId)
|
|
3454
|
-
queryParams.set('opportunityId', params.opportunityId);
|
|
3455
|
-
const query = queryParams.toString();
|
|
3456
|
-
const endpoint = `/api/workspaces/${this.workspaceId}/tasks${query ? `?${query}` : ''}`;
|
|
3457
|
-
return this.request(endpoint);
|
|
3449
|
+
async getEventTriggers() {
|
|
3450
|
+
return this.triggers.getTriggers();
|
|
3458
3451
|
}
|
|
3459
3452
|
/**
|
|
3460
|
-
*
|
|
3453
|
+
* Create a new event trigger
|
|
3461
3454
|
*/
|
|
3462
|
-
async
|
|
3463
|
-
return this.
|
|
3455
|
+
async createEventTrigger(trigger) {
|
|
3456
|
+
return this.triggers.createTrigger(trigger);
|
|
3464
3457
|
}
|
|
3465
3458
|
/**
|
|
3466
|
-
*
|
|
3459
|
+
* Update an event trigger
|
|
3467
3460
|
*/
|
|
3468
|
-
async
|
|
3469
|
-
return this.
|
|
3470
|
-
method: 'POST',
|
|
3471
|
-
body: JSON.stringify(task),
|
|
3472
|
-
});
|
|
3461
|
+
async updateEventTrigger(triggerId, updates) {
|
|
3462
|
+
return this.triggers.updateTrigger(triggerId, updates);
|
|
3473
3463
|
}
|
|
3474
3464
|
/**
|
|
3475
|
-
*
|
|
3465
|
+
* Delete an event trigger
|
|
3476
3466
|
*/
|
|
3477
|
-
async
|
|
3478
|
-
return this.
|
|
3479
|
-
|
|
3480
|
-
|
|
3467
|
+
async deleteEventTrigger(triggerId) {
|
|
3468
|
+
return this.triggers.deleteTrigger(triggerId);
|
|
3469
|
+
}
|
|
3470
|
+
}
|
|
3471
|
+
|
|
3472
|
+
/**
|
|
3473
|
+
* Clianta SDK - Main Tracker Class
|
|
3474
|
+
* @see SDK_VERSION in core/config.ts
|
|
3475
|
+
*/
|
|
3476
|
+
/**
|
|
3477
|
+
* Main Clianta Tracker Class
|
|
3478
|
+
*/
|
|
3479
|
+
class Tracker {
|
|
3480
|
+
constructor(workspaceId, userConfig = {}) {
|
|
3481
|
+
this.plugins = [];
|
|
3482
|
+
this.isInitialized = false;
|
|
3483
|
+
/** contactId after a successful identify() call */
|
|
3484
|
+
this.contactId = null;
|
|
3485
|
+
/** Pending identify retry on next flush */
|
|
3486
|
+
this.pendingIdentify = null;
|
|
3487
|
+
if (!workspaceId) {
|
|
3488
|
+
throw new Error('[Clianta] Workspace ID is required');
|
|
3489
|
+
}
|
|
3490
|
+
this.workspaceId = workspaceId;
|
|
3491
|
+
this.config = mergeConfig(userConfig);
|
|
3492
|
+
// Setup debug mode
|
|
3493
|
+
logger.enabled = this.config.debug;
|
|
3494
|
+
logger.info(`Initializing SDK v${SDK_VERSION}`, { workspaceId });
|
|
3495
|
+
// Initialize consent manager
|
|
3496
|
+
this.consentManager = new ConsentManager({
|
|
3497
|
+
...this.config.consent,
|
|
3498
|
+
onConsentChange: (state, previous) => {
|
|
3499
|
+
this.onConsentChange(state, previous);
|
|
3500
|
+
},
|
|
3501
|
+
});
|
|
3502
|
+
// Initialize transport and queue
|
|
3503
|
+
this.transport = new Transport({ apiEndpoint: this.config.apiEndpoint });
|
|
3504
|
+
this.queue = new EventQueue(this.transport, {
|
|
3505
|
+
batchSize: this.config.batchSize,
|
|
3506
|
+
flushInterval: this.config.flushInterval,
|
|
3481
3507
|
});
|
|
3508
|
+
// Get or create visitor and session IDs based on mode
|
|
3509
|
+
this.visitorId = this.createVisitorId();
|
|
3510
|
+
this.sessionId = this.createSessionId();
|
|
3511
|
+
logger.debug('IDs created', { visitorId: this.visitorId, sessionId: this.sessionId });
|
|
3512
|
+
// Initialize plugins
|
|
3513
|
+
this.initPlugins();
|
|
3514
|
+
this.isInitialized = true;
|
|
3515
|
+
logger.info('SDK initialized successfully');
|
|
3482
3516
|
}
|
|
3483
3517
|
/**
|
|
3484
|
-
*
|
|
3518
|
+
* Create visitor ID based on storage mode
|
|
3485
3519
|
*/
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3520
|
+
createVisitorId() {
|
|
3521
|
+
// Anonymous mode: use temporary ID until consent
|
|
3522
|
+
if (this.config.consent.anonymousMode && !this.consentManager.hasExplicit()) {
|
|
3523
|
+
const key = STORAGE_KEYS.VISITOR_ID + '_anon';
|
|
3524
|
+
let anonId = getSessionStorage(key);
|
|
3525
|
+
if (!anonId) {
|
|
3526
|
+
anonId = 'anon_' + generateUUID();
|
|
3527
|
+
setSessionStorage(key, anonId);
|
|
3528
|
+
}
|
|
3529
|
+
return anonId;
|
|
3530
|
+
}
|
|
3531
|
+
// Cookie-less mode: use sessionStorage only
|
|
3532
|
+
if (this.config.cookielessMode) {
|
|
3533
|
+
let visitorId = getSessionStorage(STORAGE_KEYS.VISITOR_ID);
|
|
3534
|
+
if (!visitorId) {
|
|
3535
|
+
visitorId = generateUUID();
|
|
3536
|
+
setSessionStorage(STORAGE_KEYS.VISITOR_ID, visitorId);
|
|
3537
|
+
}
|
|
3538
|
+
return visitorId;
|
|
3539
|
+
}
|
|
3540
|
+
// Normal mode
|
|
3541
|
+
return getOrCreateVisitorId(this.config.useCookies);
|
|
3490
3542
|
}
|
|
3491
3543
|
/**
|
|
3492
|
-
*
|
|
3544
|
+
* Create session ID
|
|
3493
3545
|
*/
|
|
3494
|
-
|
|
3495
|
-
return
|
|
3496
|
-
method: 'DELETE',
|
|
3497
|
-
});
|
|
3546
|
+
createSessionId() {
|
|
3547
|
+
return getOrCreateSessionId(this.config.sessionTimeout);
|
|
3498
3548
|
}
|
|
3499
|
-
// ============================================
|
|
3500
|
-
// ACTIVITIES API
|
|
3501
|
-
// ============================================
|
|
3502
3549
|
/**
|
|
3503
|
-
*
|
|
3550
|
+
* Handle consent state changes
|
|
3504
3551
|
*/
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3552
|
+
onConsentChange(state, previous) {
|
|
3553
|
+
logger.debug('Consent changed:', { from: previous, to: state });
|
|
3554
|
+
// If analytics consent was just granted
|
|
3555
|
+
if (state.analytics && !previous.analytics) {
|
|
3556
|
+
// Upgrade from anonymous ID to persistent ID
|
|
3557
|
+
if (this.config.consent.anonymousMode) {
|
|
3558
|
+
this.visitorId = getOrCreateVisitorId(this.config.useCookies);
|
|
3559
|
+
logger.info('Upgraded from anonymous to persistent visitor ID');
|
|
3560
|
+
}
|
|
3561
|
+
// Flush buffered events
|
|
3562
|
+
const buffered = this.consentManager.flushBuffer();
|
|
3563
|
+
for (const event of buffered) {
|
|
3564
|
+
// Update event with new visitor ID
|
|
3565
|
+
event.visitorId = this.visitorId;
|
|
3566
|
+
this.queue.push(event);
|
|
3567
|
+
}
|
|
3568
|
+
}
|
|
3516
3569
|
}
|
|
3517
3570
|
/**
|
|
3518
|
-
*
|
|
3571
|
+
* Initialize enabled plugins
|
|
3572
|
+
* Handles both sync and async plugin init methods
|
|
3519
3573
|
*/
|
|
3520
|
-
|
|
3521
|
-
const
|
|
3522
|
-
if
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3574
|
+
initPlugins() {
|
|
3575
|
+
const pluginsToLoad = this.config.plugins;
|
|
3576
|
+
// Skip pageView plugin if autoPageView is disabled
|
|
3577
|
+
const filteredPlugins = this.config.autoPageView
|
|
3578
|
+
? pluginsToLoad
|
|
3579
|
+
: pluginsToLoad.filter((p) => p !== 'pageView');
|
|
3580
|
+
for (const pluginName of filteredPlugins) {
|
|
3581
|
+
try {
|
|
3582
|
+
const plugin = getPlugin(pluginName);
|
|
3583
|
+
// Handle both sync and async init (fire-and-forget for async)
|
|
3584
|
+
const result = plugin.init(this);
|
|
3585
|
+
if (result instanceof Promise) {
|
|
3586
|
+
result.catch((error) => {
|
|
3587
|
+
logger.error(`Async plugin init failed: ${pluginName}`, error);
|
|
3588
|
+
});
|
|
3589
|
+
}
|
|
3590
|
+
this.plugins.push(plugin);
|
|
3591
|
+
logger.debug(`Plugin loaded: ${pluginName}`);
|
|
3592
|
+
}
|
|
3593
|
+
catch (error) {
|
|
3594
|
+
logger.error(`Failed to load plugin: ${pluginName}`, error);
|
|
3595
|
+
}
|
|
3596
|
+
}
|
|
3531
3597
|
}
|
|
3532
3598
|
/**
|
|
3533
|
-
*
|
|
3599
|
+
* Track a custom event
|
|
3534
3600
|
*/
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
endpoint = `/api/workspaces/${this.workspaceId}/opportunities/${activity.opportunityId}/activities`;
|
|
3601
|
+
track(eventType, eventName, properties = {}) {
|
|
3602
|
+
if (!this.isInitialized) {
|
|
3603
|
+
logger.warn('SDK not initialized, event dropped');
|
|
3604
|
+
return;
|
|
3540
3605
|
}
|
|
3541
|
-
|
|
3542
|
-
|
|
3606
|
+
const event = {
|
|
3607
|
+
workspaceId: this.workspaceId,
|
|
3608
|
+
visitorId: this.visitorId,
|
|
3609
|
+
sessionId: this.sessionId,
|
|
3610
|
+
eventType: eventType,
|
|
3611
|
+
eventName,
|
|
3612
|
+
url: typeof window !== 'undefined' ? window.location.href : '',
|
|
3613
|
+
referrer: typeof document !== 'undefined' ? document.referrer || undefined : undefined,
|
|
3614
|
+
properties: {
|
|
3615
|
+
...properties,
|
|
3616
|
+
eventId: generateUUID(), // Unique ID for deduplication on retry
|
|
3617
|
+
},
|
|
3618
|
+
device: getDeviceInfo(),
|
|
3619
|
+
...getUTMParams(),
|
|
3620
|
+
timestamp: new Date().toISOString(),
|
|
3621
|
+
sdkVersion: SDK_VERSION,
|
|
3622
|
+
};
|
|
3623
|
+
// Attach contactId if known (from a prior identify() call)
|
|
3624
|
+
if (this.contactId) {
|
|
3625
|
+
event.contactId = this.contactId;
|
|
3543
3626
|
}
|
|
3544
|
-
|
|
3545
|
-
|
|
3627
|
+
// Check consent before tracking
|
|
3628
|
+
if (!this.consentManager.canTrack()) {
|
|
3629
|
+
// Buffer event for later if waitForConsent is enabled
|
|
3630
|
+
if (this.config.consent.waitForConsent) {
|
|
3631
|
+
this.consentManager.bufferEvent(event);
|
|
3632
|
+
return;
|
|
3633
|
+
}
|
|
3634
|
+
// Otherwise drop the event
|
|
3635
|
+
logger.debug('Event dropped (no consent):', eventName);
|
|
3636
|
+
return;
|
|
3546
3637
|
}
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
body: JSON.stringify(activity),
|
|
3550
|
-
});
|
|
3638
|
+
this.queue.push(event);
|
|
3639
|
+
logger.debug('Event tracked:', eventName, properties);
|
|
3551
3640
|
}
|
|
3552
3641
|
/**
|
|
3553
|
-
*
|
|
3642
|
+
* Track a page view
|
|
3554
3643
|
*/
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3644
|
+
page(name, properties = {}) {
|
|
3645
|
+
const pageName = name || (typeof document !== 'undefined' ? document.title : 'Page View');
|
|
3646
|
+
this.track('page_view', pageName, {
|
|
3647
|
+
...properties,
|
|
3648
|
+
path: typeof window !== 'undefined' ? window.location.pathname : '',
|
|
3559
3649
|
});
|
|
3560
3650
|
}
|
|
3561
3651
|
/**
|
|
3562
|
-
*
|
|
3652
|
+
* Identify a visitor.
|
|
3653
|
+
* Links the anonymous visitorId to a CRM contact and returns the contactId.
|
|
3654
|
+
* All subsequent track() calls will include the contactId automatically.
|
|
3563
3655
|
*/
|
|
3564
|
-
async
|
|
3565
|
-
|
|
3566
|
-
|
|
3656
|
+
async identify(email, traits = {}) {
|
|
3657
|
+
if (!email) {
|
|
3658
|
+
logger.warn('Email is required for identification');
|
|
3659
|
+
return null;
|
|
3660
|
+
}
|
|
3661
|
+
logger.info('Identifying visitor:', email);
|
|
3662
|
+
const result = await this.transport.sendIdentify({
|
|
3663
|
+
workspaceId: this.workspaceId,
|
|
3664
|
+
visitorId: this.visitorId,
|
|
3665
|
+
email,
|
|
3666
|
+
properties: traits,
|
|
3567
3667
|
});
|
|
3668
|
+
if (result.success) {
|
|
3669
|
+
logger.info('Visitor identified successfully, contactId:', result.contactId);
|
|
3670
|
+
// Store contactId so all future track() calls include it
|
|
3671
|
+
this.contactId = result.contactId ?? null;
|
|
3672
|
+
this.pendingIdentify = null;
|
|
3673
|
+
return this.contactId;
|
|
3674
|
+
}
|
|
3675
|
+
else {
|
|
3676
|
+
logger.error('Failed to identify visitor:', result.error);
|
|
3677
|
+
// Store for retry on next flush
|
|
3678
|
+
this.pendingIdentify = { email, traits };
|
|
3679
|
+
return null;
|
|
3680
|
+
}
|
|
3568
3681
|
}
|
|
3569
3682
|
/**
|
|
3570
|
-
*
|
|
3683
|
+
* Send a server-side inbound event via the API key endpoint.
|
|
3684
|
+
* Convenience proxy to CRMClient.sendEvent() — requires apiKey in config.
|
|
3571
3685
|
*/
|
|
3572
|
-
async
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
contactId: data.contactId,
|
|
3581
|
-
opportunityId: data.opportunityId,
|
|
3582
|
-
});
|
|
3686
|
+
async sendEvent(payload) {
|
|
3687
|
+
const apiKey = this.config.apiKey;
|
|
3688
|
+
if (!apiKey) {
|
|
3689
|
+
logger.error('sendEvent() requires an apiKey in the SDK config');
|
|
3690
|
+
return { success: false, contactCreated: false, event: payload.event, error: 'No API key configured' };
|
|
3691
|
+
}
|
|
3692
|
+
const client = new CRMClient(this.config.apiEndpoint, this.workspaceId, undefined, apiKey);
|
|
3693
|
+
return client.sendEvent(payload);
|
|
3583
3694
|
}
|
|
3584
3695
|
/**
|
|
3585
|
-
*
|
|
3696
|
+
* Retry pending identify call
|
|
3586
3697
|
*/
|
|
3587
|
-
async
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
description: data.notes,
|
|
3594
|
-
contactId: data.contactId,
|
|
3595
|
-
opportunityId: data.opportunityId,
|
|
3596
|
-
});
|
|
3698
|
+
async retryPendingIdentify() {
|
|
3699
|
+
if (!this.pendingIdentify)
|
|
3700
|
+
return;
|
|
3701
|
+
const { email, traits } = this.pendingIdentify;
|
|
3702
|
+
this.pendingIdentify = null;
|
|
3703
|
+
await this.identify(email, traits);
|
|
3597
3704
|
}
|
|
3598
3705
|
/**
|
|
3599
|
-
*
|
|
3706
|
+
* Update consent state
|
|
3600
3707
|
*/
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
type: 'note',
|
|
3604
|
-
title: 'Note',
|
|
3605
|
-
description: data.content,
|
|
3606
|
-
contactId: data.contactId,
|
|
3607
|
-
opportunityId: data.opportunityId,
|
|
3608
|
-
});
|
|
3708
|
+
consent(state) {
|
|
3709
|
+
this.consentManager.update(state);
|
|
3609
3710
|
}
|
|
3610
|
-
// ============================================
|
|
3611
|
-
// EMAIL TEMPLATES API
|
|
3612
|
-
// ============================================
|
|
3613
3711
|
/**
|
|
3614
|
-
* Get
|
|
3712
|
+
* Get current consent state
|
|
3615
3713
|
*/
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
if (params?.page)
|
|
3619
|
-
queryParams.set('page', params.page.toString());
|
|
3620
|
-
if (params?.limit)
|
|
3621
|
-
queryParams.set('limit', params.limit.toString());
|
|
3622
|
-
const query = queryParams.toString();
|
|
3623
|
-
const endpoint = `/api/workspaces/${this.workspaceId}/email-templates${query ? `?${query}` : ''}`;
|
|
3624
|
-
return this.request(endpoint);
|
|
3714
|
+
getConsentState() {
|
|
3715
|
+
return this.consentManager.getState();
|
|
3625
3716
|
}
|
|
3626
3717
|
/**
|
|
3627
|
-
*
|
|
3718
|
+
* Toggle debug mode
|
|
3628
3719
|
*/
|
|
3629
|
-
|
|
3630
|
-
|
|
3720
|
+
debug(enabled) {
|
|
3721
|
+
logger.enabled = enabled;
|
|
3722
|
+
logger.info(`Debug mode ${enabled ? 'enabled' : 'disabled'}`);
|
|
3631
3723
|
}
|
|
3632
3724
|
/**
|
|
3633
|
-
*
|
|
3725
|
+
* Get visitor ID
|
|
3634
3726
|
*/
|
|
3635
|
-
|
|
3636
|
-
return this.
|
|
3637
|
-
method: 'POST',
|
|
3638
|
-
body: JSON.stringify(template),
|
|
3639
|
-
});
|
|
3727
|
+
getVisitorId() {
|
|
3728
|
+
return this.visitorId;
|
|
3640
3729
|
}
|
|
3641
3730
|
/**
|
|
3642
|
-
*
|
|
3731
|
+
* Get session ID
|
|
3643
3732
|
*/
|
|
3644
|
-
|
|
3645
|
-
return this.
|
|
3646
|
-
method: 'PUT',
|
|
3647
|
-
body: JSON.stringify(updates),
|
|
3648
|
-
});
|
|
3733
|
+
getSessionId() {
|
|
3734
|
+
return this.sessionId;
|
|
3649
3735
|
}
|
|
3650
3736
|
/**
|
|
3651
|
-
*
|
|
3737
|
+
* Get workspace ID
|
|
3652
3738
|
*/
|
|
3653
|
-
|
|
3654
|
-
return this.
|
|
3655
|
-
method: 'DELETE',
|
|
3656
|
-
});
|
|
3739
|
+
getWorkspaceId() {
|
|
3740
|
+
return this.workspaceId;
|
|
3657
3741
|
}
|
|
3658
3742
|
/**
|
|
3659
|
-
*
|
|
3743
|
+
* Get current configuration
|
|
3660
3744
|
*/
|
|
3661
|
-
|
|
3662
|
-
return
|
|
3663
|
-
method: 'POST',
|
|
3664
|
-
body: JSON.stringify(data),
|
|
3665
|
-
});
|
|
3745
|
+
getConfig() {
|
|
3746
|
+
return { ...this.config };
|
|
3666
3747
|
}
|
|
3667
|
-
// ============================================
|
|
3668
|
-
// EVENT TRIGGERS API (delegated to triggers manager)
|
|
3669
|
-
// ============================================
|
|
3670
3748
|
/**
|
|
3671
|
-
*
|
|
3749
|
+
* Force flush event queue
|
|
3672
3750
|
*/
|
|
3673
|
-
async
|
|
3674
|
-
|
|
3751
|
+
async flush() {
|
|
3752
|
+
await this.retryPendingIdentify();
|
|
3753
|
+
await this.queue.flush();
|
|
3675
3754
|
}
|
|
3676
3755
|
/**
|
|
3677
|
-
*
|
|
3756
|
+
* Reset visitor and session (for logout)
|
|
3678
3757
|
*/
|
|
3679
|
-
|
|
3680
|
-
|
|
3758
|
+
reset() {
|
|
3759
|
+
logger.info('Resetting visitor data');
|
|
3760
|
+
resetIds(this.config.useCookies);
|
|
3761
|
+
this.visitorId = this.createVisitorId();
|
|
3762
|
+
this.sessionId = this.createSessionId();
|
|
3763
|
+
this.queue.clear();
|
|
3681
3764
|
}
|
|
3682
3765
|
/**
|
|
3683
|
-
*
|
|
3766
|
+
* Delete all stored user data (GDPR right-to-erasure)
|
|
3684
3767
|
*/
|
|
3685
|
-
|
|
3686
|
-
|
|
3768
|
+
deleteData() {
|
|
3769
|
+
logger.info('Deleting all user data (GDPR request)');
|
|
3770
|
+
// Clear queue
|
|
3771
|
+
this.queue.clear();
|
|
3772
|
+
// Reset consent
|
|
3773
|
+
this.consentManager.reset();
|
|
3774
|
+
// Clear all stored IDs
|
|
3775
|
+
resetIds(this.config.useCookies);
|
|
3776
|
+
// Clear session storage items
|
|
3777
|
+
if (typeof sessionStorage !== 'undefined') {
|
|
3778
|
+
try {
|
|
3779
|
+
sessionStorage.removeItem(STORAGE_KEYS.VISITOR_ID);
|
|
3780
|
+
sessionStorage.removeItem(STORAGE_KEYS.VISITOR_ID + '_anon');
|
|
3781
|
+
sessionStorage.removeItem(STORAGE_KEYS.SESSION_ID);
|
|
3782
|
+
sessionStorage.removeItem(STORAGE_KEYS.SESSION_TIMESTAMP);
|
|
3783
|
+
}
|
|
3784
|
+
catch {
|
|
3785
|
+
// Ignore errors
|
|
3786
|
+
}
|
|
3787
|
+
}
|
|
3788
|
+
// Clear localStorage items
|
|
3789
|
+
if (typeof localStorage !== 'undefined') {
|
|
3790
|
+
try {
|
|
3791
|
+
localStorage.removeItem(STORAGE_KEYS.VISITOR_ID);
|
|
3792
|
+
localStorage.removeItem(STORAGE_KEYS.CONSENT);
|
|
3793
|
+
localStorage.removeItem(STORAGE_KEYS.EVENT_QUEUE);
|
|
3794
|
+
}
|
|
3795
|
+
catch {
|
|
3796
|
+
// Ignore errors
|
|
3797
|
+
}
|
|
3798
|
+
}
|
|
3799
|
+
// Generate new IDs
|
|
3800
|
+
this.visitorId = this.createVisitorId();
|
|
3801
|
+
this.sessionId = this.createSessionId();
|
|
3802
|
+
logger.info('All user data deleted');
|
|
3687
3803
|
}
|
|
3688
3804
|
/**
|
|
3689
|
-
*
|
|
3805
|
+
* Destroy tracker and cleanup
|
|
3690
3806
|
*/
|
|
3691
|
-
async
|
|
3692
|
-
|
|
3807
|
+
async destroy() {
|
|
3808
|
+
logger.info('Destroying tracker');
|
|
3809
|
+
// Flush any remaining events (await to ensure completion)
|
|
3810
|
+
await this.queue.flush();
|
|
3811
|
+
// Destroy plugins
|
|
3812
|
+
for (const plugin of this.plugins) {
|
|
3813
|
+
if (plugin.destroy) {
|
|
3814
|
+
plugin.destroy();
|
|
3815
|
+
}
|
|
3816
|
+
}
|
|
3817
|
+
this.plugins = [];
|
|
3818
|
+
// Destroy queue
|
|
3819
|
+
this.queue.destroy();
|
|
3820
|
+
this.isInitialized = false;
|
|
3693
3821
|
}
|
|
3694
3822
|
}
|
|
3695
3823
|
|