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