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