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