@clianta/sdk 1.5.1 → 1.6.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 +20 -0
- package/dist/angular.cjs.js +259 -45
- package/dist/angular.cjs.js.map +1 -1
- package/dist/angular.d.ts +69 -0
- package/dist/angular.esm.js +259 -45
- package/dist/angular.esm.js.map +1 -1
- package/dist/clianta.cjs.js +259 -45
- package/dist/clianta.cjs.js.map +1 -1
- package/dist/clianta.esm.js +259 -45
- package/dist/clianta.esm.js.map +1 -1
- package/dist/clianta.umd.js +259 -45
- 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 +150 -7
- package/dist/react.cjs.js +326 -57
- package/dist/react.cjs.js.map +1 -1
- package/dist/react.d.ts +89 -3
- package/dist/react.esm.js +327 -59
- package/dist/react.esm.js.map +1 -1
- package/dist/svelte.cjs.js +259 -45
- package/dist/svelte.cjs.js.map +1 -1
- package/dist/svelte.d.ts +69 -0
- package/dist/svelte.esm.js +259 -45
- package/dist/svelte.esm.js.map +1 -1
- package/dist/vue.cjs.js +259 -45
- package/dist/vue.cjs.js.map +1 -1
- package/dist/vue.d.ts +69 -0
- package/dist/vue.esm.js +259 -45
- package/dist/vue.esm.js.map +1 -1
- package/package.json +1 -1
package/dist/react.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { ReactNode } from 'react';
|
|
2
|
+
import { ReactNode, ErrorInfo } from 'react';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Clianta SDK - CRM API Client
|
|
@@ -87,6 +87,63 @@ interface ConsentState {
|
|
|
87
87
|
personalization?: boolean;
|
|
88
88
|
}
|
|
89
89
|
type EventType = 'page_view' | 'button_click' | 'form_view' | 'form_submit' | 'form_interaction' | 'scroll_depth' | 'engagement' | 'download' | 'exit_intent' | 'error' | 'performance' | 'time_on_page' | 'custom';
|
|
90
|
+
interface TrackingEvent {
|
|
91
|
+
/** Workspace/project ID */
|
|
92
|
+
workspaceId: string;
|
|
93
|
+
/** Anonymous visitor identifier */
|
|
94
|
+
visitorId: string;
|
|
95
|
+
/** Session identifier */
|
|
96
|
+
sessionId: string;
|
|
97
|
+
/** Event type category */
|
|
98
|
+
eventType: EventType;
|
|
99
|
+
/** Human-readable event name */
|
|
100
|
+
eventName: string;
|
|
101
|
+
/** Current page URL */
|
|
102
|
+
url: string;
|
|
103
|
+
/** Referrer URL */
|
|
104
|
+
referrer?: string;
|
|
105
|
+
/** Event properties/metadata */
|
|
106
|
+
properties: Record<string, unknown>;
|
|
107
|
+
/** Device information */
|
|
108
|
+
device: DeviceInfo;
|
|
109
|
+
/** UTM parameters */
|
|
110
|
+
utm?: UTMParams;
|
|
111
|
+
/** ISO timestamp */
|
|
112
|
+
timestamp: string;
|
|
113
|
+
/** SDK version */
|
|
114
|
+
sdkVersion: string;
|
|
115
|
+
}
|
|
116
|
+
interface DeviceInfo {
|
|
117
|
+
userAgent: string;
|
|
118
|
+
screen: string;
|
|
119
|
+
language: string;
|
|
120
|
+
timezone?: string;
|
|
121
|
+
}
|
|
122
|
+
interface UTMParams {
|
|
123
|
+
utmSource?: string;
|
|
124
|
+
utmMedium?: string;
|
|
125
|
+
utmCampaign?: string;
|
|
126
|
+
utmTerm?: string;
|
|
127
|
+
utmContent?: string;
|
|
128
|
+
}
|
|
129
|
+
interface GroupTraits {
|
|
130
|
+
/** Company/account name */
|
|
131
|
+
name?: string;
|
|
132
|
+
/** Industry */
|
|
133
|
+
industry?: string;
|
|
134
|
+
/** Company size */
|
|
135
|
+
employees?: number;
|
|
136
|
+
/** Annual revenue */
|
|
137
|
+
revenue?: number;
|
|
138
|
+
/** Company website */
|
|
139
|
+
website?: string;
|
|
140
|
+
/** Company plan/tier */
|
|
141
|
+
plan?: string;
|
|
142
|
+
/** Additional custom properties */
|
|
143
|
+
[key: string]: unknown;
|
|
144
|
+
}
|
|
145
|
+
/** Event middleware function — intercept or transform events before they are sent */
|
|
146
|
+
type MiddlewareFn = (event: TrackingEvent, next: () => void) => void;
|
|
90
147
|
interface UserTraits {
|
|
91
148
|
firstName?: string;
|
|
92
149
|
lastName?: string;
|
|
@@ -122,6 +179,18 @@ interface TrackerCore {
|
|
|
122
179
|
deleteData(): void;
|
|
123
180
|
/** Get current consent state */
|
|
124
181
|
getConsentState(): ConsentState;
|
|
182
|
+
/** Associate the current visitor with a group (company/account) */
|
|
183
|
+
group(groupId: string, traits?: GroupTraits): void;
|
|
184
|
+
/** Merge two visitor identities (e.g., anonymous → logged-in) */
|
|
185
|
+
alias(newId: string, previousId?: string): Promise<boolean>;
|
|
186
|
+
/** Track a screen view (for mobile-first PWAs and SPAs) */
|
|
187
|
+
screen(name: string, properties?: Record<string, unknown>): void;
|
|
188
|
+
/** Register event middleware to intercept/transform events before sending */
|
|
189
|
+
use(middleware: MiddlewareFn): void;
|
|
190
|
+
/** Register a callback to be invoked when the SDK is fully initialized */
|
|
191
|
+
onReady(callback: () => void): void;
|
|
192
|
+
/** Check if the SDK is fully initialized and ready */
|
|
193
|
+
isReady(): boolean;
|
|
125
194
|
/** Get the current visitor's profile from the CRM */
|
|
126
195
|
getVisitorProfile(): Promise<VisitorProfile | null>;
|
|
127
196
|
/** Get the current visitor's recent activity */
|
|
@@ -283,10 +352,14 @@ interface CliantaProviderProps {
|
|
|
283
352
|
config: CliantaConfig;
|
|
284
353
|
/** React children */
|
|
285
354
|
children: ReactNode;
|
|
355
|
+
/** Optional error handler when the SDK encounters errors */
|
|
356
|
+
onError?: (error: Error, errorInfo: ErrorInfo) => void;
|
|
286
357
|
}
|
|
287
358
|
/**
|
|
288
359
|
* CliantaProvider - Wrap your app to enable tracking
|
|
289
360
|
*
|
|
361
|
+
* Includes an ErrorBoundary so SDK failures never crash the host app.
|
|
362
|
+
*
|
|
290
363
|
* @example
|
|
291
364
|
* // In clianta.config.ts:
|
|
292
365
|
* import { CliantaConfig } from '@clianta/sdk';
|
|
@@ -307,7 +380,7 @@ interface CliantaProviderProps {
|
|
|
307
380
|
* {children}
|
|
308
381
|
* </CliantaProvider>
|
|
309
382
|
*/
|
|
310
|
-
declare function CliantaProvider({ config, children }: CliantaProviderProps): react_jsx_runtime.JSX.Element;
|
|
383
|
+
declare function CliantaProvider({ config, children, onError }: CliantaProviderProps): react_jsx_runtime.JSX.Element;
|
|
311
384
|
/**
|
|
312
385
|
* useClianta - Hook to access tracker in any component
|
|
313
386
|
*
|
|
@@ -316,6 +389,19 @@ declare function CliantaProvider({ config, children }: CliantaProviderProps): re
|
|
|
316
389
|
* tracker?.track('button_click', 'CTA Button');
|
|
317
390
|
*/
|
|
318
391
|
declare function useClianta(): TrackerCore | null;
|
|
392
|
+
/**
|
|
393
|
+
* useCliantaReady - Hook to check if SDK is initialized
|
|
394
|
+
*
|
|
395
|
+
* @example
|
|
396
|
+
* const { isReady, tracker } = useCliantaReady();
|
|
397
|
+
* if (isReady) {
|
|
398
|
+
* tracker.track('purchase', 'Order', { value: 99 });
|
|
399
|
+
* }
|
|
400
|
+
*/
|
|
401
|
+
declare function useCliantaReady(): {
|
|
402
|
+
isReady: boolean;
|
|
403
|
+
tracker: TrackerCore | null;
|
|
404
|
+
};
|
|
319
405
|
/**
|
|
320
406
|
* useCliantaTrack - Convenience hook for tracking events
|
|
321
407
|
*
|
|
@@ -325,5 +411,5 @@ declare function useClianta(): TrackerCore | null;
|
|
|
325
411
|
*/
|
|
326
412
|
declare function useCliantaTrack(): (eventType: string, eventName: string, properties?: Record<string, unknown>) => void;
|
|
327
413
|
|
|
328
|
-
export { CliantaProvider, useClianta, useCliantaTrack };
|
|
414
|
+
export { CliantaProvider, useClianta, useCliantaReady, useCliantaTrack };
|
|
329
415
|
export type { CliantaConfig, CliantaProviderProps, TrackerCore };
|
package/dist/react.esm.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* Clianta SDK v1.
|
|
2
|
+
* Clianta SDK v1.6.0
|
|
3
3
|
* (c) 2026 Clianta
|
|
4
4
|
* Released under the MIT License.
|
|
5
5
|
*/
|
|
6
6
|
import { jsx } from 'react/jsx-runtime';
|
|
7
|
-
import { createContext, useState, useRef, useEffect, useContext } from 'react';
|
|
7
|
+
import { createContext, useState, useRef, useEffect, useContext, Component } from 'react';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Clianta SDK - Configuration
|
|
11
11
|
* @see SDK_VERSION in core/config.ts
|
|
12
12
|
*/
|
|
13
13
|
/** SDK Version */
|
|
14
|
-
const SDK_VERSION = '1.
|
|
14
|
+
const SDK_VERSION = '1.6.0';
|
|
15
15
|
/** Default API endpoint — reads from env or falls back to localhost */
|
|
16
16
|
const getDefaultApiEndpoint = () => {
|
|
17
17
|
// Build-time env var (works with Next.js, Vite, CRA, etc.)
|
|
@@ -3693,6 +3693,85 @@ class CRMClient {
|
|
|
3693
3693
|
}
|
|
3694
3694
|
}
|
|
3695
3695
|
|
|
3696
|
+
/**
|
|
3697
|
+
* Privacy-safe visitor API client.
|
|
3698
|
+
* All methods return data for the current visitor only (no cross-visitor access).
|
|
3699
|
+
*/
|
|
3700
|
+
class VisitorClient {
|
|
3701
|
+
constructor(transport, workspaceId, visitorId) {
|
|
3702
|
+
this.transport = transport;
|
|
3703
|
+
this.workspaceId = workspaceId;
|
|
3704
|
+
this.visitorId = visitorId;
|
|
3705
|
+
}
|
|
3706
|
+
/** Update visitorId (e.g. after reset) */
|
|
3707
|
+
setVisitorId(id) {
|
|
3708
|
+
this.visitorId = id;
|
|
3709
|
+
}
|
|
3710
|
+
basePath() {
|
|
3711
|
+
return `/api/public/track/visitor/${this.workspaceId}/${this.visitorId}`;
|
|
3712
|
+
}
|
|
3713
|
+
/**
|
|
3714
|
+
* Get the current visitor's profile from the CRM.
|
|
3715
|
+
* Returns visitor data and linked contact info if identified.
|
|
3716
|
+
*/
|
|
3717
|
+
async getProfile() {
|
|
3718
|
+
const result = await this.transport.fetchData(`${this.basePath()}/profile`);
|
|
3719
|
+
if (result.success && result.data) {
|
|
3720
|
+
logger.debug('Visitor profile fetched:', result.data);
|
|
3721
|
+
return result.data;
|
|
3722
|
+
}
|
|
3723
|
+
logger.warn('Failed to fetch visitor profile:', result.error);
|
|
3724
|
+
return null;
|
|
3725
|
+
}
|
|
3726
|
+
/**
|
|
3727
|
+
* Get the current visitor's recent activity/events.
|
|
3728
|
+
* Returns paginated list of tracking events.
|
|
3729
|
+
*/
|
|
3730
|
+
async getActivity(options) {
|
|
3731
|
+
const params = {};
|
|
3732
|
+
if (options?.page)
|
|
3733
|
+
params.page = options.page.toString();
|
|
3734
|
+
if (options?.limit)
|
|
3735
|
+
params.limit = options.limit.toString();
|
|
3736
|
+
if (options?.eventType)
|
|
3737
|
+
params.eventType = options.eventType;
|
|
3738
|
+
if (options?.startDate)
|
|
3739
|
+
params.startDate = options.startDate;
|
|
3740
|
+
if (options?.endDate)
|
|
3741
|
+
params.endDate = options.endDate;
|
|
3742
|
+
const result = await this.transport.fetchData(`${this.basePath()}/activity`, params);
|
|
3743
|
+
if (result.success && result.data) {
|
|
3744
|
+
return result.data;
|
|
3745
|
+
}
|
|
3746
|
+
logger.warn('Failed to fetch visitor activity:', result.error);
|
|
3747
|
+
return null;
|
|
3748
|
+
}
|
|
3749
|
+
/**
|
|
3750
|
+
* Get a summarized journey timeline for the current visitor.
|
|
3751
|
+
* Includes top pages, sessions, time spent, and recent activities.
|
|
3752
|
+
*/
|
|
3753
|
+
async getTimeline() {
|
|
3754
|
+
const result = await this.transport.fetchData(`${this.basePath()}/timeline`);
|
|
3755
|
+
if (result.success && result.data) {
|
|
3756
|
+
return result.data;
|
|
3757
|
+
}
|
|
3758
|
+
logger.warn('Failed to fetch visitor timeline:', result.error);
|
|
3759
|
+
return null;
|
|
3760
|
+
}
|
|
3761
|
+
/**
|
|
3762
|
+
* Get engagement metrics for the current visitor.
|
|
3763
|
+
* Includes time on site, page views, bounce rate, and engagement score.
|
|
3764
|
+
*/
|
|
3765
|
+
async getEngagement() {
|
|
3766
|
+
const result = await this.transport.fetchData(`${this.basePath()}/engagement`);
|
|
3767
|
+
if (result.success && result.data) {
|
|
3768
|
+
return result.data;
|
|
3769
|
+
}
|
|
3770
|
+
logger.warn('Failed to fetch visitor engagement:', result.error);
|
|
3771
|
+
return null;
|
|
3772
|
+
}
|
|
3773
|
+
}
|
|
3774
|
+
|
|
3696
3775
|
/**
|
|
3697
3776
|
* Clianta SDK - Main Tracker Class
|
|
3698
3777
|
* @see SDK_VERSION in core/config.ts
|
|
@@ -3706,10 +3785,16 @@ class Tracker {
|
|
|
3706
3785
|
this.isInitialized = false;
|
|
3707
3786
|
/** contactId after a successful identify() call */
|
|
3708
3787
|
this.contactId = null;
|
|
3788
|
+
/** groupId after a successful group() call */
|
|
3789
|
+
this.groupId = null;
|
|
3709
3790
|
/** Pending identify retry on next flush */
|
|
3710
3791
|
this.pendingIdentify = null;
|
|
3711
3792
|
/** Registered event schemas for validation */
|
|
3712
3793
|
this.eventSchemas = new Map();
|
|
3794
|
+
/** Event middleware pipeline */
|
|
3795
|
+
this.middlewares = [];
|
|
3796
|
+
/** Ready callbacks */
|
|
3797
|
+
this.readyCallbacks = [];
|
|
3713
3798
|
if (!workspaceId) {
|
|
3714
3799
|
throw new Error('[Clianta] Workspace ID is required');
|
|
3715
3800
|
}
|
|
@@ -3735,6 +3820,8 @@ class Tracker {
|
|
|
3735
3820
|
this.visitorId = this.createVisitorId();
|
|
3736
3821
|
this.sessionId = this.createSessionId();
|
|
3737
3822
|
logger.debug('IDs created', { visitorId: this.visitorId, sessionId: this.sessionId });
|
|
3823
|
+
// Initialize visitor API client
|
|
3824
|
+
this.visitor = new VisitorClient(this.transport, this.workspaceId, this.visitorId);
|
|
3738
3825
|
// Security warnings
|
|
3739
3826
|
if (this.config.apiEndpoint.startsWith('http://') &&
|
|
3740
3827
|
typeof window !== 'undefined' &&
|
|
@@ -3749,6 +3836,16 @@ class Tracker {
|
|
|
3749
3836
|
this.initPlugins();
|
|
3750
3837
|
this.isInitialized = true;
|
|
3751
3838
|
logger.info('SDK initialized successfully');
|
|
3839
|
+
// Fire ready callbacks
|
|
3840
|
+
for (const cb of this.readyCallbacks) {
|
|
3841
|
+
try {
|
|
3842
|
+
cb();
|
|
3843
|
+
}
|
|
3844
|
+
catch (e) {
|
|
3845
|
+
logger.error('onReady callback error:', e);
|
|
3846
|
+
}
|
|
3847
|
+
}
|
|
3848
|
+
this.readyCallbacks = [];
|
|
3752
3849
|
}
|
|
3753
3850
|
/**
|
|
3754
3851
|
* Create visitor ID based on storage mode
|
|
@@ -3861,6 +3958,10 @@ class Tracker {
|
|
|
3861
3958
|
if (this.contactId) {
|
|
3862
3959
|
event.contactId = this.contactId;
|
|
3863
3960
|
}
|
|
3961
|
+
// Attach groupId if known (from a prior group() call)
|
|
3962
|
+
if (this.groupId) {
|
|
3963
|
+
event.groupId = this.groupId;
|
|
3964
|
+
}
|
|
3864
3965
|
// Validate event against registered schema (debug mode only)
|
|
3865
3966
|
this.validateEventSchema(eventType, properties);
|
|
3866
3967
|
// Check consent before tracking
|
|
@@ -3874,8 +3975,11 @@ class Tracker {
|
|
|
3874
3975
|
logger.debug('Event dropped (no consent):', eventName);
|
|
3875
3976
|
return;
|
|
3876
3977
|
}
|
|
3877
|
-
|
|
3878
|
-
|
|
3978
|
+
// Run event through middleware pipeline
|
|
3979
|
+
this.runMiddleware(event, () => {
|
|
3980
|
+
this.queue.push(event);
|
|
3981
|
+
logger.debug('Event tracked:', eventName, properties);
|
|
3982
|
+
});
|
|
3879
3983
|
}
|
|
3880
3984
|
/**
|
|
3881
3985
|
* Track a page view
|
|
@@ -3937,80 +4041,47 @@ class Tracker {
|
|
|
3937
4041
|
}
|
|
3938
4042
|
/**
|
|
3939
4043
|
* Get the current visitor's profile from the CRM.
|
|
3940
|
-
*
|
|
3941
|
-
* Only returns data for the current visitor (privacy-safe for frontend).
|
|
4044
|
+
* @deprecated Use `tracker.visitor.getProfile()` instead.
|
|
3942
4045
|
*/
|
|
3943
4046
|
async getVisitorProfile() {
|
|
3944
4047
|
if (!this.isInitialized) {
|
|
3945
4048
|
logger.warn('SDK not initialized');
|
|
3946
4049
|
return null;
|
|
3947
4050
|
}
|
|
3948
|
-
|
|
3949
|
-
if (result.success && result.data) {
|
|
3950
|
-
logger.debug('Visitor profile fetched:', result.data);
|
|
3951
|
-
return result.data;
|
|
3952
|
-
}
|
|
3953
|
-
logger.warn('Failed to fetch visitor profile:', result.error);
|
|
3954
|
-
return null;
|
|
4051
|
+
return this.visitor.getProfile();
|
|
3955
4052
|
}
|
|
3956
4053
|
/**
|
|
3957
4054
|
* Get the current visitor's recent activity/events.
|
|
3958
|
-
*
|
|
4055
|
+
* @deprecated Use `tracker.visitor.getActivity()` instead.
|
|
3959
4056
|
*/
|
|
3960
4057
|
async getVisitorActivity(options) {
|
|
3961
4058
|
if (!this.isInitialized) {
|
|
3962
4059
|
logger.warn('SDK not initialized');
|
|
3963
4060
|
return null;
|
|
3964
4061
|
}
|
|
3965
|
-
|
|
3966
|
-
if (options?.page)
|
|
3967
|
-
params.page = options.page.toString();
|
|
3968
|
-
if (options?.limit)
|
|
3969
|
-
params.limit = options.limit.toString();
|
|
3970
|
-
if (options?.eventType)
|
|
3971
|
-
params.eventType = options.eventType;
|
|
3972
|
-
if (options?.startDate)
|
|
3973
|
-
params.startDate = options.startDate;
|
|
3974
|
-
if (options?.endDate)
|
|
3975
|
-
params.endDate = options.endDate;
|
|
3976
|
-
const result = await this.transport.fetchData(`/api/public/track/visitor/${this.workspaceId}/${this.visitorId}/activity`, params);
|
|
3977
|
-
if (result.success && result.data) {
|
|
3978
|
-
return result.data;
|
|
3979
|
-
}
|
|
3980
|
-
logger.warn('Failed to fetch visitor activity:', result.error);
|
|
3981
|
-
return null;
|
|
4062
|
+
return this.visitor.getActivity(options);
|
|
3982
4063
|
}
|
|
3983
4064
|
/**
|
|
3984
4065
|
* Get a summarized journey timeline for the current visitor.
|
|
3985
|
-
*
|
|
4066
|
+
* @deprecated Use `tracker.visitor.getTimeline()` instead.
|
|
3986
4067
|
*/
|
|
3987
4068
|
async getVisitorTimeline() {
|
|
3988
4069
|
if (!this.isInitialized) {
|
|
3989
4070
|
logger.warn('SDK not initialized');
|
|
3990
4071
|
return null;
|
|
3991
4072
|
}
|
|
3992
|
-
|
|
3993
|
-
if (result.success && result.data) {
|
|
3994
|
-
return result.data;
|
|
3995
|
-
}
|
|
3996
|
-
logger.warn('Failed to fetch visitor timeline:', result.error);
|
|
3997
|
-
return null;
|
|
4073
|
+
return this.visitor.getTimeline();
|
|
3998
4074
|
}
|
|
3999
4075
|
/**
|
|
4000
4076
|
* Get engagement metrics for the current visitor.
|
|
4001
|
-
*
|
|
4077
|
+
* @deprecated Use `tracker.visitor.getEngagement()` instead.
|
|
4002
4078
|
*/
|
|
4003
4079
|
async getVisitorEngagement() {
|
|
4004
4080
|
if (!this.isInitialized) {
|
|
4005
4081
|
logger.warn('SDK not initialized');
|
|
4006
4082
|
return null;
|
|
4007
4083
|
}
|
|
4008
|
-
|
|
4009
|
-
if (result.success && result.data) {
|
|
4010
|
-
return result.data;
|
|
4011
|
-
}
|
|
4012
|
-
logger.warn('Failed to fetch visitor engagement:', result.error);
|
|
4013
|
-
return null;
|
|
4084
|
+
return this.visitor.getEngagement();
|
|
4014
4085
|
}
|
|
4015
4086
|
/**
|
|
4016
4087
|
* Retry pending identify call
|
|
@@ -4041,6 +4112,149 @@ class Tracker {
|
|
|
4041
4112
|
logger.enabled = enabled;
|
|
4042
4113
|
logger.info(`Debug mode ${enabled ? 'enabled' : 'disabled'}`);
|
|
4043
4114
|
}
|
|
4115
|
+
// ============================================
|
|
4116
|
+
// GROUP, ALIAS, SCREEN
|
|
4117
|
+
// ============================================
|
|
4118
|
+
/**
|
|
4119
|
+
* Associate the current visitor with a group (company/account).
|
|
4120
|
+
* The groupId will be attached to all subsequent track() calls.
|
|
4121
|
+
*/
|
|
4122
|
+
group(groupId, traits = {}) {
|
|
4123
|
+
if (!groupId) {
|
|
4124
|
+
logger.warn('groupId is required for group()');
|
|
4125
|
+
return;
|
|
4126
|
+
}
|
|
4127
|
+
this.groupId = groupId;
|
|
4128
|
+
logger.info('Visitor grouped:', groupId);
|
|
4129
|
+
this.track('group', 'Group Identified', {
|
|
4130
|
+
groupId,
|
|
4131
|
+
...traits,
|
|
4132
|
+
});
|
|
4133
|
+
}
|
|
4134
|
+
/**
|
|
4135
|
+
* Merge two visitor identities.
|
|
4136
|
+
* Links `previousId` (typically the anonymous visitor) to `newId` (the known user).
|
|
4137
|
+
* If `previousId` is omitted, the current visitorId is used.
|
|
4138
|
+
*/
|
|
4139
|
+
async alias(newId, previousId) {
|
|
4140
|
+
if (!newId) {
|
|
4141
|
+
logger.warn('newId is required for alias()');
|
|
4142
|
+
return false;
|
|
4143
|
+
}
|
|
4144
|
+
const prevId = previousId || this.visitorId;
|
|
4145
|
+
logger.info('Aliasing visitor:', { from: prevId, to: newId });
|
|
4146
|
+
try {
|
|
4147
|
+
const url = `${this.config.apiEndpoint}/api/public/track/alias`;
|
|
4148
|
+
const response = await fetch(url, {
|
|
4149
|
+
method: 'POST',
|
|
4150
|
+
headers: { 'Content-Type': 'application/json' },
|
|
4151
|
+
body: JSON.stringify({
|
|
4152
|
+
workspaceId: this.workspaceId,
|
|
4153
|
+
previousId: prevId,
|
|
4154
|
+
newId,
|
|
4155
|
+
}),
|
|
4156
|
+
});
|
|
4157
|
+
if (response.ok) {
|
|
4158
|
+
logger.info('Alias successful');
|
|
4159
|
+
return true;
|
|
4160
|
+
}
|
|
4161
|
+
logger.error('Alias failed:', response.status);
|
|
4162
|
+
return false;
|
|
4163
|
+
}
|
|
4164
|
+
catch (error) {
|
|
4165
|
+
logger.error('Alias request failed:', error);
|
|
4166
|
+
return false;
|
|
4167
|
+
}
|
|
4168
|
+
}
|
|
4169
|
+
/**
|
|
4170
|
+
* Track a screen view (for mobile-first PWAs and SPAs).
|
|
4171
|
+
* Similar to page() but semantically for app screens.
|
|
4172
|
+
*/
|
|
4173
|
+
screen(name, properties = {}) {
|
|
4174
|
+
this.track('screen_view', name, {
|
|
4175
|
+
...properties,
|
|
4176
|
+
screenName: name,
|
|
4177
|
+
});
|
|
4178
|
+
}
|
|
4179
|
+
// ============================================
|
|
4180
|
+
// MIDDLEWARE
|
|
4181
|
+
// ============================================
|
|
4182
|
+
/**
|
|
4183
|
+
* Register event middleware.
|
|
4184
|
+
* Middleware functions receive the event and a `next` callback.
|
|
4185
|
+
* Call `next()` to pass the event through, or don't call it to drop the event.
|
|
4186
|
+
*
|
|
4187
|
+
* @example
|
|
4188
|
+
* tracker.use((event, next) => {
|
|
4189
|
+
* // Strip PII from events
|
|
4190
|
+
* delete event.properties.email;
|
|
4191
|
+
* next(); // pass it through
|
|
4192
|
+
* });
|
|
4193
|
+
*/
|
|
4194
|
+
use(middleware) {
|
|
4195
|
+
this.middlewares.push(middleware);
|
|
4196
|
+
logger.debug('Middleware registered');
|
|
4197
|
+
}
|
|
4198
|
+
/**
|
|
4199
|
+
* Run event through the middleware pipeline.
|
|
4200
|
+
* Executes each middleware in order; if any skips `next()`, the event is dropped.
|
|
4201
|
+
*/
|
|
4202
|
+
runMiddleware(event, finalCallback) {
|
|
4203
|
+
if (this.middlewares.length === 0) {
|
|
4204
|
+
finalCallback();
|
|
4205
|
+
return;
|
|
4206
|
+
}
|
|
4207
|
+
let index = 0;
|
|
4208
|
+
const middlewares = this.middlewares;
|
|
4209
|
+
const next = () => {
|
|
4210
|
+
index++;
|
|
4211
|
+
if (index < middlewares.length) {
|
|
4212
|
+
try {
|
|
4213
|
+
middlewares[index](event, next);
|
|
4214
|
+
}
|
|
4215
|
+
catch (e) {
|
|
4216
|
+
logger.error('Middleware error:', e);
|
|
4217
|
+
finalCallback();
|
|
4218
|
+
}
|
|
4219
|
+
}
|
|
4220
|
+
else {
|
|
4221
|
+
finalCallback();
|
|
4222
|
+
}
|
|
4223
|
+
};
|
|
4224
|
+
try {
|
|
4225
|
+
middlewares[0](event, next);
|
|
4226
|
+
}
|
|
4227
|
+
catch (e) {
|
|
4228
|
+
logger.error('Middleware error:', e);
|
|
4229
|
+
finalCallback();
|
|
4230
|
+
}
|
|
4231
|
+
}
|
|
4232
|
+
// ============================================
|
|
4233
|
+
// LIFECYCLE
|
|
4234
|
+
// ============================================
|
|
4235
|
+
/**
|
|
4236
|
+
* Register a callback to be invoked when the SDK is fully initialized.
|
|
4237
|
+
* If already initialized, the callback fires immediately.
|
|
4238
|
+
*/
|
|
4239
|
+
onReady(callback) {
|
|
4240
|
+
if (this.isInitialized) {
|
|
4241
|
+
try {
|
|
4242
|
+
callback();
|
|
4243
|
+
}
|
|
4244
|
+
catch (e) {
|
|
4245
|
+
logger.error('onReady callback error:', e);
|
|
4246
|
+
}
|
|
4247
|
+
}
|
|
4248
|
+
else {
|
|
4249
|
+
this.readyCallbacks.push(callback);
|
|
4250
|
+
}
|
|
4251
|
+
}
|
|
4252
|
+
/**
|
|
4253
|
+
* Check if the SDK is fully initialized and ready.
|
|
4254
|
+
*/
|
|
4255
|
+
isReady() {
|
|
4256
|
+
return this.isInitialized;
|
|
4257
|
+
}
|
|
4044
4258
|
/**
|
|
4045
4259
|
* Register a schema for event validation.
|
|
4046
4260
|
* When debug mode is enabled, events will be validated against registered schemas.
|
|
@@ -4335,14 +4549,42 @@ if (typeof window !== 'undefined') {
|
|
|
4335
4549
|
/**
|
|
4336
4550
|
* Clianta SDK - React Integration
|
|
4337
4551
|
*
|
|
4338
|
-
* Provides CliantaProvider component for easy
|
|
4339
|
-
* using the clianta.config.ts pattern.
|
|
4552
|
+
* Provides CliantaProvider component (with ErrorBoundary) for easy
|
|
4553
|
+
* React/Next.js integration using the clianta.config.ts pattern.
|
|
4554
|
+
*/
|
|
4555
|
+
const CliantaContext = createContext({
|
|
4556
|
+
tracker: null,
|
|
4557
|
+
isReady: false,
|
|
4558
|
+
});
|
|
4559
|
+
/**
|
|
4560
|
+
* Internal ErrorBoundary to prevent SDK errors from crashing the host app.
|
|
4561
|
+
* Catches render-time errors in the provider tree.
|
|
4340
4562
|
*/
|
|
4341
|
-
|
|
4342
|
-
|
|
4563
|
+
class CliantaErrorBoundary extends Component {
|
|
4564
|
+
constructor(props) {
|
|
4565
|
+
super(props);
|
|
4566
|
+
this.state = { hasError: false };
|
|
4567
|
+
}
|
|
4568
|
+
static getDerivedStateFromError() {
|
|
4569
|
+
return { hasError: true };
|
|
4570
|
+
}
|
|
4571
|
+
componentDidCatch(error, errorInfo) {
|
|
4572
|
+
console.error('[Clianta] SDK error caught by ErrorBoundary:', error);
|
|
4573
|
+
this.props.onError?.(error, errorInfo);
|
|
4574
|
+
}
|
|
4575
|
+
render() {
|
|
4576
|
+
if (this.state.hasError) {
|
|
4577
|
+
// Render children anyway — SDK failure shouldn't break the host UI
|
|
4578
|
+
return this.props.fallback ?? this.props.children;
|
|
4579
|
+
}
|
|
4580
|
+
return this.props.children;
|
|
4581
|
+
}
|
|
4582
|
+
}
|
|
4343
4583
|
/**
|
|
4344
4584
|
* CliantaProvider - Wrap your app to enable tracking
|
|
4345
4585
|
*
|
|
4586
|
+
* Includes an ErrorBoundary so SDK failures never crash the host app.
|
|
4587
|
+
*
|
|
4346
4588
|
* @example
|
|
4347
4589
|
* // In clianta.config.ts:
|
|
4348
4590
|
* import { CliantaConfig } from '@clianta/sdk';
|
|
@@ -4363,8 +4605,9 @@ const CliantaContext = createContext(null);
|
|
|
4363
4605
|
* {children}
|
|
4364
4606
|
* </CliantaProvider>
|
|
4365
4607
|
*/
|
|
4366
|
-
function CliantaProvider({ config, children }) {
|
|
4608
|
+
function CliantaProvider({ config, children, onError }) {
|
|
4367
4609
|
const [tracker, setTracker] = useState(null);
|
|
4610
|
+
const [isReady, setIsReady] = useState(false);
|
|
4368
4611
|
// Stable ref to projectId — the only value that truly identifies the tracker
|
|
4369
4612
|
const projectIdRef = useRef(config.projectId);
|
|
4370
4613
|
useEffect(() => {
|
|
@@ -4378,18 +4621,29 @@ function CliantaProvider({ config, children }) {
|
|
|
4378
4621
|
if (projectIdRef.current !== projectId) {
|
|
4379
4622
|
projectIdRef.current = projectId;
|
|
4380
4623
|
}
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4624
|
+
try {
|
|
4625
|
+
// Extract projectId (handled separately) and pass rest as options
|
|
4626
|
+
const { projectId: _, ...options } = config;
|
|
4627
|
+
const instance = clianta(projectId, options);
|
|
4628
|
+
setTracker(instance);
|
|
4629
|
+
setIsReady(true);
|
|
4630
|
+
}
|
|
4631
|
+
catch (error) {
|
|
4632
|
+
console.error('[Clianta] Failed to initialize SDK:', error);
|
|
4633
|
+
onError?.(error, { componentStack: '' });
|
|
4634
|
+
}
|
|
4385
4635
|
// Cleanup: flush pending events on unmount
|
|
4386
4636
|
return () => {
|
|
4387
|
-
|
|
4637
|
+
tracker?.flush();
|
|
4638
|
+
setIsReady(false);
|
|
4388
4639
|
};
|
|
4389
4640
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
4390
4641
|
}, [config.projectId]);
|
|
4391
|
-
return (jsx(CliantaContext.Provider, { value: tracker, children: children }));
|
|
4642
|
+
return (jsx(CliantaErrorBoundary, { onError: onError, children: jsx(CliantaContext.Provider, { value: { tracker, isReady }, children: children }) }));
|
|
4392
4643
|
}
|
|
4644
|
+
// ============================================
|
|
4645
|
+
// HOOKS
|
|
4646
|
+
// ============================================
|
|
4393
4647
|
/**
|
|
4394
4648
|
* useClianta - Hook to access tracker in any component
|
|
4395
4649
|
*
|
|
@@ -4398,7 +4652,21 @@ function CliantaProvider({ config, children }) {
|
|
|
4398
4652
|
* tracker?.track('button_click', 'CTA Button');
|
|
4399
4653
|
*/
|
|
4400
4654
|
function useClianta() {
|
|
4401
|
-
|
|
4655
|
+
const { tracker } = useContext(CliantaContext);
|
|
4656
|
+
return tracker;
|
|
4657
|
+
}
|
|
4658
|
+
/**
|
|
4659
|
+
* useCliantaReady - Hook to check if SDK is initialized
|
|
4660
|
+
*
|
|
4661
|
+
* @example
|
|
4662
|
+
* const { isReady, tracker } = useCliantaReady();
|
|
4663
|
+
* if (isReady) {
|
|
4664
|
+
* tracker.track('purchase', 'Order', { value: 99 });
|
|
4665
|
+
* }
|
|
4666
|
+
*/
|
|
4667
|
+
function useCliantaReady() {
|
|
4668
|
+
const { tracker, isReady } = useContext(CliantaContext);
|
|
4669
|
+
return { isReady, tracker };
|
|
4402
4670
|
}
|
|
4403
4671
|
/**
|
|
4404
4672
|
* useCliantaTrack - Convenience hook for tracking events
|
|
@@ -4414,5 +4682,5 @@ function useCliantaTrack() {
|
|
|
4414
4682
|
};
|
|
4415
4683
|
}
|
|
4416
4684
|
|
|
4417
|
-
export { CliantaProvider, useClianta, useCliantaTrack };
|
|
4685
|
+
export { CliantaProvider, useClianta, useCliantaReady, useCliantaTrack };
|
|
4418
4686
|
//# sourceMappingURL=react.esm.js.map
|