@clianta/sdk 1.5.0 → 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 +36 -0
- package/dist/angular.cjs.js +358 -57
- package/dist/angular.cjs.js.map +1 -1
- package/dist/angular.d.ts +136 -1
- package/dist/angular.esm.js +358 -57
- package/dist/angular.esm.js.map +1 -1
- package/dist/clianta.cjs.js +357 -56
- package/dist/clianta.cjs.js.map +1 -1
- package/dist/clianta.esm.js +357 -56
- package/dist/clianta.esm.js.map +1 -1
- package/dist/clianta.umd.js +357 -56
- 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 +242 -8
- package/dist/react.cjs.js +425 -69
- package/dist/react.cjs.js.map +1 -1
- package/dist/react.d.ts +156 -4
- package/dist/react.esm.js +426 -71
- package/dist/react.esm.js.map +1 -1
- package/dist/svelte.cjs.js +358 -57
- package/dist/svelte.cjs.js.map +1 -1
- package/dist/svelte.d.ts +136 -1
- package/dist/svelte.esm.js +358 -57
- package/dist/svelte.esm.js.map +1 -1
- package/dist/vue.cjs.js +358 -57
- package/dist/vue.cjs.js.map +1 -1
- package/dist/vue.d.ts +136 -1
- package/dist/vue.esm.js +358 -57
- package/dist/vue.esm.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,42 @@ All notable changes to the Clianta SDK will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.6.0] - 2026-03-01
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **`group()` method** — Associate visitors with a company/account. The `groupId` is attached to all subsequent `track()` calls, enabling ABM (Account-Based Marketing) use cases
|
|
12
|
+
- **`alias()` method** — Merge two visitor identities (e.g., anonymous visitor → logged-in user). Supports cross-device identity resolution
|
|
13
|
+
- **`screen()` method** — Track screen views for mobile-first PWAs and SPAs. Semantic equivalent of `page()` for app screens
|
|
14
|
+
- **Event middleware API** — `use((event, next) => { ... })` to intercept, transform, or drop events before they're sent. Supports chaining multiple middleware functions
|
|
15
|
+
- **`onReady()` callback** — Register callbacks that fire when the SDK is fully initialized. If already ready, fires immediately
|
|
16
|
+
- **`isReady()` method** — Check initialization state synchronously
|
|
17
|
+
- **React `ErrorBoundary`** — `CliantaProvider` now wraps children in an ErrorBoundary to prevent SDK errors from crashing the host application
|
|
18
|
+
- **React `useCliantaReady()` hook** — Returns `{ isReady, tracker }` for components that need to wait for initialization
|
|
19
|
+
- **React `onError` prop** — `CliantaProvider` accepts an `onError` callback for custom error handling
|
|
20
|
+
- **New types** — `GroupTraits`, `MiddlewareFn` exported from main SDK entry
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
- `TrackerCore` interface expanded with `group()`, `alias()`, `screen()`, `use()`, `onReady()`, `isReady()` methods
|
|
24
|
+
- React `CliantaContext` now provides `{ tracker, isReady }` instead of just `tracker`
|
|
25
|
+
- `track()` now runs events through the middleware pipeline before queueing
|
|
26
|
+
- Events include `groupId` field when visitor is associated with a group
|
|
27
|
+
|
|
28
|
+
## [1.5.1] - 2026-02-28
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
- **Public CRM API** — Frontend-safe CRM methods that don't require an API key (secured by domain whitelist):
|
|
32
|
+
- `createContact()` — Create or upsert a contact by email
|
|
33
|
+
- `updateContact()` — Update an existing contact by ID
|
|
34
|
+
- `submitForm()` — Submit a form and auto-create/update contact
|
|
35
|
+
- `logActivity()` — Append an activity to a contact
|
|
36
|
+
- `createOpportunity()` — Create an opportunity (e.g., from "Request Demo" forms)
|
|
37
|
+
- **Public CRM types** — `PublicContactData`, `PublicContactUpdate`, `PublicFormSubmission`, `PublicActivityData`, `PublicOpportunityData`, `PublicCrmResult`
|
|
38
|
+
- **Updated docs** — API Reference and Getting Started guides updated with public CRM usage
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
- Config: improved `getDefaultApiEndpoint()` with env variable support
|
|
42
|
+
- Framework integrations: minor fixes for Angular, Svelte, Vue, React
|
|
43
|
+
|
|
8
44
|
## [1.5.0] - 2026-02-28
|
|
9
45
|
|
|
10
46
|
### Security
|
package/dist/angular.cjs.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
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
|
*/
|
|
@@ -10,16 +10,23 @@
|
|
|
10
10
|
* @see SDK_VERSION in core/config.ts
|
|
11
11
|
*/
|
|
12
12
|
/** SDK Version */
|
|
13
|
-
const SDK_VERSION = '1.
|
|
14
|
-
/** Default API endpoint
|
|
13
|
+
const SDK_VERSION = '1.6.0';
|
|
14
|
+
/** Default API endpoint — reads from env or falls back to localhost */
|
|
15
15
|
const getDefaultApiEndpoint = () => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
// Build-time env var (works with Next.js, Vite, CRA, etc.)
|
|
17
|
+
if (typeof process !== 'undefined' && process.env?.NEXT_PUBLIC_CLIANTA_API_ENDPOINT) {
|
|
18
|
+
return process.env.NEXT_PUBLIC_CLIANTA_API_ENDPOINT;
|
|
19
|
+
}
|
|
20
|
+
if (typeof process !== 'undefined' && process.env?.VITE_CLIANTA_API_ENDPOINT) {
|
|
21
|
+
return process.env.VITE_CLIANTA_API_ENDPOINT;
|
|
22
|
+
}
|
|
23
|
+
if (typeof process !== 'undefined' && process.env?.REACT_APP_CLIANTA_API_ENDPOINT) {
|
|
24
|
+
return process.env.REACT_APP_CLIANTA_API_ENDPOINT;
|
|
25
|
+
}
|
|
26
|
+
if (typeof process !== 'undefined' && process.env?.CLIANTA_API_ENDPOINT) {
|
|
27
|
+
return process.env.CLIANTA_API_ENDPOINT;
|
|
21
28
|
}
|
|
22
|
-
return '
|
|
29
|
+
return 'http://localhost:5000';
|
|
23
30
|
};
|
|
24
31
|
/** Core plugins enabled by default */
|
|
25
32
|
const DEFAULT_PLUGINS = [
|
|
@@ -1781,7 +1788,7 @@ class PopupFormsPlugin extends BasePlugin {
|
|
|
1781
1788
|
return;
|
|
1782
1789
|
const config = this.tracker.getConfig();
|
|
1783
1790
|
const workspaceId = this.tracker.getWorkspaceId();
|
|
1784
|
-
const apiEndpoint = config.apiEndpoint || '
|
|
1791
|
+
const apiEndpoint = config.apiEndpoint || 'http://localhost:5000';
|
|
1785
1792
|
try {
|
|
1786
1793
|
const url = encodeURIComponent(window.location.href);
|
|
1787
1794
|
const response = await fetch(`${apiEndpoint}/api/public/lead-forms/${workspaceId}?url=${url}`);
|
|
@@ -1886,7 +1893,7 @@ class PopupFormsPlugin extends BasePlugin {
|
|
|
1886
1893
|
if (!this.tracker)
|
|
1887
1894
|
return;
|
|
1888
1895
|
const config = this.tracker.getConfig();
|
|
1889
|
-
const apiEndpoint = config.apiEndpoint || '
|
|
1896
|
+
const apiEndpoint = config.apiEndpoint || 'http://localhost:5000';
|
|
1890
1897
|
try {
|
|
1891
1898
|
await fetch(`${apiEndpoint}/api/public/lead-forms/${formId}/view`, {
|
|
1892
1899
|
method: 'POST',
|
|
@@ -2133,7 +2140,7 @@ class PopupFormsPlugin extends BasePlugin {
|
|
|
2133
2140
|
if (!this.tracker)
|
|
2134
2141
|
return;
|
|
2135
2142
|
const config = this.tracker.getConfig();
|
|
2136
|
-
const apiEndpoint = config.apiEndpoint || '
|
|
2143
|
+
const apiEndpoint = config.apiEndpoint || 'http://localhost:5000';
|
|
2137
2144
|
const visitorId = this.tracker.getVisitorId();
|
|
2138
2145
|
// Collect form data
|
|
2139
2146
|
const formData = new FormData(formElement);
|
|
@@ -3028,7 +3035,7 @@ class CRMClient {
|
|
|
3028
3035
|
* The contact is upserted in the CRM and matching workflow automations fire automatically.
|
|
3029
3036
|
*
|
|
3030
3037
|
* @example
|
|
3031
|
-
* const crm = new CRMClient('
|
|
3038
|
+
* const crm = new CRMClient('http://localhost:5000', 'WORKSPACE_ID');
|
|
3032
3039
|
* crm.setApiKey('mm_live_...');
|
|
3033
3040
|
*
|
|
3034
3041
|
* await crm.sendEvent({
|
|
@@ -3685,6 +3692,85 @@ class CRMClient {
|
|
|
3685
3692
|
}
|
|
3686
3693
|
}
|
|
3687
3694
|
|
|
3695
|
+
/**
|
|
3696
|
+
* Privacy-safe visitor API client.
|
|
3697
|
+
* All methods return data for the current visitor only (no cross-visitor access).
|
|
3698
|
+
*/
|
|
3699
|
+
class VisitorClient {
|
|
3700
|
+
constructor(transport, workspaceId, visitorId) {
|
|
3701
|
+
this.transport = transport;
|
|
3702
|
+
this.workspaceId = workspaceId;
|
|
3703
|
+
this.visitorId = visitorId;
|
|
3704
|
+
}
|
|
3705
|
+
/** Update visitorId (e.g. after reset) */
|
|
3706
|
+
setVisitorId(id) {
|
|
3707
|
+
this.visitorId = id;
|
|
3708
|
+
}
|
|
3709
|
+
basePath() {
|
|
3710
|
+
return `/api/public/track/visitor/${this.workspaceId}/${this.visitorId}`;
|
|
3711
|
+
}
|
|
3712
|
+
/**
|
|
3713
|
+
* Get the current visitor's profile from the CRM.
|
|
3714
|
+
* Returns visitor data and linked contact info if identified.
|
|
3715
|
+
*/
|
|
3716
|
+
async getProfile() {
|
|
3717
|
+
const result = await this.transport.fetchData(`${this.basePath()}/profile`);
|
|
3718
|
+
if (result.success && result.data) {
|
|
3719
|
+
logger.debug('Visitor profile fetched:', result.data);
|
|
3720
|
+
return result.data;
|
|
3721
|
+
}
|
|
3722
|
+
logger.warn('Failed to fetch visitor profile:', result.error);
|
|
3723
|
+
return null;
|
|
3724
|
+
}
|
|
3725
|
+
/**
|
|
3726
|
+
* Get the current visitor's recent activity/events.
|
|
3727
|
+
* Returns paginated list of tracking events.
|
|
3728
|
+
*/
|
|
3729
|
+
async getActivity(options) {
|
|
3730
|
+
const params = {};
|
|
3731
|
+
if (options?.page)
|
|
3732
|
+
params.page = options.page.toString();
|
|
3733
|
+
if (options?.limit)
|
|
3734
|
+
params.limit = options.limit.toString();
|
|
3735
|
+
if (options?.eventType)
|
|
3736
|
+
params.eventType = options.eventType;
|
|
3737
|
+
if (options?.startDate)
|
|
3738
|
+
params.startDate = options.startDate;
|
|
3739
|
+
if (options?.endDate)
|
|
3740
|
+
params.endDate = options.endDate;
|
|
3741
|
+
const result = await this.transport.fetchData(`${this.basePath()}/activity`, params);
|
|
3742
|
+
if (result.success && result.data) {
|
|
3743
|
+
return result.data;
|
|
3744
|
+
}
|
|
3745
|
+
logger.warn('Failed to fetch visitor activity:', result.error);
|
|
3746
|
+
return null;
|
|
3747
|
+
}
|
|
3748
|
+
/**
|
|
3749
|
+
* Get a summarized journey timeline for the current visitor.
|
|
3750
|
+
* Includes top pages, sessions, time spent, and recent activities.
|
|
3751
|
+
*/
|
|
3752
|
+
async getTimeline() {
|
|
3753
|
+
const result = await this.transport.fetchData(`${this.basePath()}/timeline`);
|
|
3754
|
+
if (result.success && result.data) {
|
|
3755
|
+
return result.data;
|
|
3756
|
+
}
|
|
3757
|
+
logger.warn('Failed to fetch visitor timeline:', result.error);
|
|
3758
|
+
return null;
|
|
3759
|
+
}
|
|
3760
|
+
/**
|
|
3761
|
+
* Get engagement metrics for the current visitor.
|
|
3762
|
+
* Includes time on site, page views, bounce rate, and engagement score.
|
|
3763
|
+
*/
|
|
3764
|
+
async getEngagement() {
|
|
3765
|
+
const result = await this.transport.fetchData(`${this.basePath()}/engagement`);
|
|
3766
|
+
if (result.success && result.data) {
|
|
3767
|
+
return result.data;
|
|
3768
|
+
}
|
|
3769
|
+
logger.warn('Failed to fetch visitor engagement:', result.error);
|
|
3770
|
+
return null;
|
|
3771
|
+
}
|
|
3772
|
+
}
|
|
3773
|
+
|
|
3688
3774
|
/**
|
|
3689
3775
|
* Clianta SDK - Main Tracker Class
|
|
3690
3776
|
* @see SDK_VERSION in core/config.ts
|
|
@@ -3698,10 +3784,16 @@ class Tracker {
|
|
|
3698
3784
|
this.isInitialized = false;
|
|
3699
3785
|
/** contactId after a successful identify() call */
|
|
3700
3786
|
this.contactId = null;
|
|
3787
|
+
/** groupId after a successful group() call */
|
|
3788
|
+
this.groupId = null;
|
|
3701
3789
|
/** Pending identify retry on next flush */
|
|
3702
3790
|
this.pendingIdentify = null;
|
|
3703
3791
|
/** Registered event schemas for validation */
|
|
3704
3792
|
this.eventSchemas = new Map();
|
|
3793
|
+
/** Event middleware pipeline */
|
|
3794
|
+
this.middlewares = [];
|
|
3795
|
+
/** Ready callbacks */
|
|
3796
|
+
this.readyCallbacks = [];
|
|
3705
3797
|
if (!workspaceId) {
|
|
3706
3798
|
throw new Error('[Clianta] Workspace ID is required');
|
|
3707
3799
|
}
|
|
@@ -3727,6 +3819,8 @@ class Tracker {
|
|
|
3727
3819
|
this.visitorId = this.createVisitorId();
|
|
3728
3820
|
this.sessionId = this.createSessionId();
|
|
3729
3821
|
logger.debug('IDs created', { visitorId: this.visitorId, sessionId: this.sessionId });
|
|
3822
|
+
// Initialize visitor API client
|
|
3823
|
+
this.visitor = new VisitorClient(this.transport, this.workspaceId, this.visitorId);
|
|
3730
3824
|
// Security warnings
|
|
3731
3825
|
if (this.config.apiEndpoint.startsWith('http://') &&
|
|
3732
3826
|
typeof window !== 'undefined' &&
|
|
@@ -3741,6 +3835,16 @@ class Tracker {
|
|
|
3741
3835
|
this.initPlugins();
|
|
3742
3836
|
this.isInitialized = true;
|
|
3743
3837
|
logger.info('SDK initialized successfully');
|
|
3838
|
+
// Fire ready callbacks
|
|
3839
|
+
for (const cb of this.readyCallbacks) {
|
|
3840
|
+
try {
|
|
3841
|
+
cb();
|
|
3842
|
+
}
|
|
3843
|
+
catch (e) {
|
|
3844
|
+
logger.error('onReady callback error:', e);
|
|
3845
|
+
}
|
|
3846
|
+
}
|
|
3847
|
+
this.readyCallbacks = [];
|
|
3744
3848
|
}
|
|
3745
3849
|
/**
|
|
3746
3850
|
* Create visitor ID based on storage mode
|
|
@@ -3853,6 +3957,10 @@ class Tracker {
|
|
|
3853
3957
|
if (this.contactId) {
|
|
3854
3958
|
event.contactId = this.contactId;
|
|
3855
3959
|
}
|
|
3960
|
+
// Attach groupId if known (from a prior group() call)
|
|
3961
|
+
if (this.groupId) {
|
|
3962
|
+
event.groupId = this.groupId;
|
|
3963
|
+
}
|
|
3856
3964
|
// Validate event against registered schema (debug mode only)
|
|
3857
3965
|
this.validateEventSchema(eventType, properties);
|
|
3858
3966
|
// Check consent before tracking
|
|
@@ -3866,8 +3974,11 @@ class Tracker {
|
|
|
3866
3974
|
logger.debug('Event dropped (no consent):', eventName);
|
|
3867
3975
|
return;
|
|
3868
3976
|
}
|
|
3869
|
-
|
|
3870
|
-
|
|
3977
|
+
// Run event through middleware pipeline
|
|
3978
|
+
this.runMiddleware(event, () => {
|
|
3979
|
+
this.queue.push(event);
|
|
3980
|
+
logger.debug('Event tracked:', eventName, properties);
|
|
3981
|
+
});
|
|
3871
3982
|
}
|
|
3872
3983
|
/**
|
|
3873
3984
|
* Track a page view
|
|
@@ -3929,80 +4040,47 @@ class Tracker {
|
|
|
3929
4040
|
}
|
|
3930
4041
|
/**
|
|
3931
4042
|
* Get the current visitor's profile from the CRM.
|
|
3932
|
-
*
|
|
3933
|
-
* Only returns data for the current visitor (privacy-safe for frontend).
|
|
4043
|
+
* @deprecated Use `tracker.visitor.getProfile()` instead.
|
|
3934
4044
|
*/
|
|
3935
4045
|
async getVisitorProfile() {
|
|
3936
4046
|
if (!this.isInitialized) {
|
|
3937
4047
|
logger.warn('SDK not initialized');
|
|
3938
4048
|
return null;
|
|
3939
4049
|
}
|
|
3940
|
-
|
|
3941
|
-
if (result.success && result.data) {
|
|
3942
|
-
logger.debug('Visitor profile fetched:', result.data);
|
|
3943
|
-
return result.data;
|
|
3944
|
-
}
|
|
3945
|
-
logger.warn('Failed to fetch visitor profile:', result.error);
|
|
3946
|
-
return null;
|
|
4050
|
+
return this.visitor.getProfile();
|
|
3947
4051
|
}
|
|
3948
4052
|
/**
|
|
3949
4053
|
* Get the current visitor's recent activity/events.
|
|
3950
|
-
*
|
|
4054
|
+
* @deprecated Use `tracker.visitor.getActivity()` instead.
|
|
3951
4055
|
*/
|
|
3952
4056
|
async getVisitorActivity(options) {
|
|
3953
4057
|
if (!this.isInitialized) {
|
|
3954
4058
|
logger.warn('SDK not initialized');
|
|
3955
4059
|
return null;
|
|
3956
4060
|
}
|
|
3957
|
-
|
|
3958
|
-
if (options?.page)
|
|
3959
|
-
params.page = options.page.toString();
|
|
3960
|
-
if (options?.limit)
|
|
3961
|
-
params.limit = options.limit.toString();
|
|
3962
|
-
if (options?.eventType)
|
|
3963
|
-
params.eventType = options.eventType;
|
|
3964
|
-
if (options?.startDate)
|
|
3965
|
-
params.startDate = options.startDate;
|
|
3966
|
-
if (options?.endDate)
|
|
3967
|
-
params.endDate = options.endDate;
|
|
3968
|
-
const result = await this.transport.fetchData(`/api/public/track/visitor/${this.workspaceId}/${this.visitorId}/activity`, params);
|
|
3969
|
-
if (result.success && result.data) {
|
|
3970
|
-
return result.data;
|
|
3971
|
-
}
|
|
3972
|
-
logger.warn('Failed to fetch visitor activity:', result.error);
|
|
3973
|
-
return null;
|
|
4061
|
+
return this.visitor.getActivity(options);
|
|
3974
4062
|
}
|
|
3975
4063
|
/**
|
|
3976
4064
|
* Get a summarized journey timeline for the current visitor.
|
|
3977
|
-
*
|
|
4065
|
+
* @deprecated Use `tracker.visitor.getTimeline()` instead.
|
|
3978
4066
|
*/
|
|
3979
4067
|
async getVisitorTimeline() {
|
|
3980
4068
|
if (!this.isInitialized) {
|
|
3981
4069
|
logger.warn('SDK not initialized');
|
|
3982
4070
|
return null;
|
|
3983
4071
|
}
|
|
3984
|
-
|
|
3985
|
-
if (result.success && result.data) {
|
|
3986
|
-
return result.data;
|
|
3987
|
-
}
|
|
3988
|
-
logger.warn('Failed to fetch visitor timeline:', result.error);
|
|
3989
|
-
return null;
|
|
4072
|
+
return this.visitor.getTimeline();
|
|
3990
4073
|
}
|
|
3991
4074
|
/**
|
|
3992
4075
|
* Get engagement metrics for the current visitor.
|
|
3993
|
-
*
|
|
4076
|
+
* @deprecated Use `tracker.visitor.getEngagement()` instead.
|
|
3994
4077
|
*/
|
|
3995
4078
|
async getVisitorEngagement() {
|
|
3996
4079
|
if (!this.isInitialized) {
|
|
3997
4080
|
logger.warn('SDK not initialized');
|
|
3998
4081
|
return null;
|
|
3999
4082
|
}
|
|
4000
|
-
|
|
4001
|
-
if (result.success && result.data) {
|
|
4002
|
-
return result.data;
|
|
4003
|
-
}
|
|
4004
|
-
logger.warn('Failed to fetch visitor engagement:', result.error);
|
|
4005
|
-
return null;
|
|
4083
|
+
return this.visitor.getEngagement();
|
|
4006
4084
|
}
|
|
4007
4085
|
/**
|
|
4008
4086
|
* Retry pending identify call
|
|
@@ -4033,6 +4111,149 @@ class Tracker {
|
|
|
4033
4111
|
logger.enabled = enabled;
|
|
4034
4112
|
logger.info(`Debug mode ${enabled ? 'enabled' : 'disabled'}`);
|
|
4035
4113
|
}
|
|
4114
|
+
// ============================================
|
|
4115
|
+
// GROUP, ALIAS, SCREEN
|
|
4116
|
+
// ============================================
|
|
4117
|
+
/**
|
|
4118
|
+
* Associate the current visitor with a group (company/account).
|
|
4119
|
+
* The groupId will be attached to all subsequent track() calls.
|
|
4120
|
+
*/
|
|
4121
|
+
group(groupId, traits = {}) {
|
|
4122
|
+
if (!groupId) {
|
|
4123
|
+
logger.warn('groupId is required for group()');
|
|
4124
|
+
return;
|
|
4125
|
+
}
|
|
4126
|
+
this.groupId = groupId;
|
|
4127
|
+
logger.info('Visitor grouped:', groupId);
|
|
4128
|
+
this.track('group', 'Group Identified', {
|
|
4129
|
+
groupId,
|
|
4130
|
+
...traits,
|
|
4131
|
+
});
|
|
4132
|
+
}
|
|
4133
|
+
/**
|
|
4134
|
+
* Merge two visitor identities.
|
|
4135
|
+
* Links `previousId` (typically the anonymous visitor) to `newId` (the known user).
|
|
4136
|
+
* If `previousId` is omitted, the current visitorId is used.
|
|
4137
|
+
*/
|
|
4138
|
+
async alias(newId, previousId) {
|
|
4139
|
+
if (!newId) {
|
|
4140
|
+
logger.warn('newId is required for alias()');
|
|
4141
|
+
return false;
|
|
4142
|
+
}
|
|
4143
|
+
const prevId = previousId || this.visitorId;
|
|
4144
|
+
logger.info('Aliasing visitor:', { from: prevId, to: newId });
|
|
4145
|
+
try {
|
|
4146
|
+
const url = `${this.config.apiEndpoint}/api/public/track/alias`;
|
|
4147
|
+
const response = await fetch(url, {
|
|
4148
|
+
method: 'POST',
|
|
4149
|
+
headers: { 'Content-Type': 'application/json' },
|
|
4150
|
+
body: JSON.stringify({
|
|
4151
|
+
workspaceId: this.workspaceId,
|
|
4152
|
+
previousId: prevId,
|
|
4153
|
+
newId,
|
|
4154
|
+
}),
|
|
4155
|
+
});
|
|
4156
|
+
if (response.ok) {
|
|
4157
|
+
logger.info('Alias successful');
|
|
4158
|
+
return true;
|
|
4159
|
+
}
|
|
4160
|
+
logger.error('Alias failed:', response.status);
|
|
4161
|
+
return false;
|
|
4162
|
+
}
|
|
4163
|
+
catch (error) {
|
|
4164
|
+
logger.error('Alias request failed:', error);
|
|
4165
|
+
return false;
|
|
4166
|
+
}
|
|
4167
|
+
}
|
|
4168
|
+
/**
|
|
4169
|
+
* Track a screen view (for mobile-first PWAs and SPAs).
|
|
4170
|
+
* Similar to page() but semantically for app screens.
|
|
4171
|
+
*/
|
|
4172
|
+
screen(name, properties = {}) {
|
|
4173
|
+
this.track('screen_view', name, {
|
|
4174
|
+
...properties,
|
|
4175
|
+
screenName: name,
|
|
4176
|
+
});
|
|
4177
|
+
}
|
|
4178
|
+
// ============================================
|
|
4179
|
+
// MIDDLEWARE
|
|
4180
|
+
// ============================================
|
|
4181
|
+
/**
|
|
4182
|
+
* Register event middleware.
|
|
4183
|
+
* Middleware functions receive the event and a `next` callback.
|
|
4184
|
+
* Call `next()` to pass the event through, or don't call it to drop the event.
|
|
4185
|
+
*
|
|
4186
|
+
* @example
|
|
4187
|
+
* tracker.use((event, next) => {
|
|
4188
|
+
* // Strip PII from events
|
|
4189
|
+
* delete event.properties.email;
|
|
4190
|
+
* next(); // pass it through
|
|
4191
|
+
* });
|
|
4192
|
+
*/
|
|
4193
|
+
use(middleware) {
|
|
4194
|
+
this.middlewares.push(middleware);
|
|
4195
|
+
logger.debug('Middleware registered');
|
|
4196
|
+
}
|
|
4197
|
+
/**
|
|
4198
|
+
* Run event through the middleware pipeline.
|
|
4199
|
+
* Executes each middleware in order; if any skips `next()`, the event is dropped.
|
|
4200
|
+
*/
|
|
4201
|
+
runMiddleware(event, finalCallback) {
|
|
4202
|
+
if (this.middlewares.length === 0) {
|
|
4203
|
+
finalCallback();
|
|
4204
|
+
return;
|
|
4205
|
+
}
|
|
4206
|
+
let index = 0;
|
|
4207
|
+
const middlewares = this.middlewares;
|
|
4208
|
+
const next = () => {
|
|
4209
|
+
index++;
|
|
4210
|
+
if (index < middlewares.length) {
|
|
4211
|
+
try {
|
|
4212
|
+
middlewares[index](event, next);
|
|
4213
|
+
}
|
|
4214
|
+
catch (e) {
|
|
4215
|
+
logger.error('Middleware error:', e);
|
|
4216
|
+
finalCallback();
|
|
4217
|
+
}
|
|
4218
|
+
}
|
|
4219
|
+
else {
|
|
4220
|
+
finalCallback();
|
|
4221
|
+
}
|
|
4222
|
+
};
|
|
4223
|
+
try {
|
|
4224
|
+
middlewares[0](event, next);
|
|
4225
|
+
}
|
|
4226
|
+
catch (e) {
|
|
4227
|
+
logger.error('Middleware error:', e);
|
|
4228
|
+
finalCallback();
|
|
4229
|
+
}
|
|
4230
|
+
}
|
|
4231
|
+
// ============================================
|
|
4232
|
+
// LIFECYCLE
|
|
4233
|
+
// ============================================
|
|
4234
|
+
/**
|
|
4235
|
+
* Register a callback to be invoked when the SDK is fully initialized.
|
|
4236
|
+
* If already initialized, the callback fires immediately.
|
|
4237
|
+
*/
|
|
4238
|
+
onReady(callback) {
|
|
4239
|
+
if (this.isInitialized) {
|
|
4240
|
+
try {
|
|
4241
|
+
callback();
|
|
4242
|
+
}
|
|
4243
|
+
catch (e) {
|
|
4244
|
+
logger.error('onReady callback error:', e);
|
|
4245
|
+
}
|
|
4246
|
+
}
|
|
4247
|
+
else {
|
|
4248
|
+
this.readyCallbacks.push(callback);
|
|
4249
|
+
}
|
|
4250
|
+
}
|
|
4251
|
+
/**
|
|
4252
|
+
* Check if the SDK is fully initialized and ready.
|
|
4253
|
+
*/
|
|
4254
|
+
isReady() {
|
|
4255
|
+
return this.isInitialized;
|
|
4256
|
+
}
|
|
4036
4257
|
/**
|
|
4037
4258
|
* Register a schema for event validation.
|
|
4038
4259
|
* When debug mode is enabled, events will be validated against registered schemas.
|
|
@@ -4168,6 +4389,86 @@ class Tracker {
|
|
|
4168
4389
|
this.sessionId = this.createSessionId();
|
|
4169
4390
|
logger.info('All user data deleted');
|
|
4170
4391
|
}
|
|
4392
|
+
// ============================================
|
|
4393
|
+
// PUBLIC CRM METHODS (no API key required)
|
|
4394
|
+
// ============================================
|
|
4395
|
+
/**
|
|
4396
|
+
* Create or update a contact by email (upsert).
|
|
4397
|
+
* Secured by domain whitelist — no API key needed.
|
|
4398
|
+
*/
|
|
4399
|
+
async createContact(data) {
|
|
4400
|
+
return this.publicCrmRequest('/api/public/crm/contacts', 'POST', {
|
|
4401
|
+
workspaceId: this.workspaceId,
|
|
4402
|
+
...data,
|
|
4403
|
+
});
|
|
4404
|
+
}
|
|
4405
|
+
/**
|
|
4406
|
+
* Update an existing contact by ID (limited fields only).
|
|
4407
|
+
*/
|
|
4408
|
+
async updateContact(contactId, data) {
|
|
4409
|
+
return this.publicCrmRequest(`/api/public/crm/contacts/${contactId}`, 'PUT', {
|
|
4410
|
+
workspaceId: this.workspaceId,
|
|
4411
|
+
...data,
|
|
4412
|
+
});
|
|
4413
|
+
}
|
|
4414
|
+
/**
|
|
4415
|
+
* Submit a form — creates/updates contact from form data.
|
|
4416
|
+
*/
|
|
4417
|
+
async submitForm(formId, data) {
|
|
4418
|
+
const payload = {
|
|
4419
|
+
...data,
|
|
4420
|
+
metadata: {
|
|
4421
|
+
...data.metadata,
|
|
4422
|
+
visitorId: this.visitorId,
|
|
4423
|
+
sessionId: this.sessionId,
|
|
4424
|
+
pageUrl: typeof window !== 'undefined' ? window.location.href : undefined,
|
|
4425
|
+
referrer: typeof document !== 'undefined' ? document.referrer || undefined : undefined,
|
|
4426
|
+
},
|
|
4427
|
+
};
|
|
4428
|
+
return this.publicCrmRequest(`/api/public/crm/forms/${formId}/submit`, 'POST', payload);
|
|
4429
|
+
}
|
|
4430
|
+
/**
|
|
4431
|
+
* Log an activity linked to a contact (append-only).
|
|
4432
|
+
*/
|
|
4433
|
+
async logActivity(data) {
|
|
4434
|
+
return this.publicCrmRequest('/api/public/crm/activities', 'POST', {
|
|
4435
|
+
workspaceId: this.workspaceId,
|
|
4436
|
+
...data,
|
|
4437
|
+
});
|
|
4438
|
+
}
|
|
4439
|
+
/**
|
|
4440
|
+
* Create an opportunity (e.g., from "Request Demo" forms).
|
|
4441
|
+
*/
|
|
4442
|
+
async createOpportunity(data) {
|
|
4443
|
+
return this.publicCrmRequest('/api/public/crm/opportunities', 'POST', {
|
|
4444
|
+
workspaceId: this.workspaceId,
|
|
4445
|
+
...data,
|
|
4446
|
+
});
|
|
4447
|
+
}
|
|
4448
|
+
/**
|
|
4449
|
+
* Internal helper for public CRM API calls.
|
|
4450
|
+
*/
|
|
4451
|
+
async publicCrmRequest(path, method, body) {
|
|
4452
|
+
const url = `${this.config.apiEndpoint}${path}`;
|
|
4453
|
+
try {
|
|
4454
|
+
const response = await fetch(url, {
|
|
4455
|
+
method,
|
|
4456
|
+
headers: { 'Content-Type': 'application/json' },
|
|
4457
|
+
body: JSON.stringify(body),
|
|
4458
|
+
});
|
|
4459
|
+
const data = await response.json().catch(() => ({}));
|
|
4460
|
+
if (response.ok) {
|
|
4461
|
+
logger.debug(`Public CRM ${method} ${path} succeeded`);
|
|
4462
|
+
return { success: true, data: data.data ?? data, status: response.status };
|
|
4463
|
+
}
|
|
4464
|
+
logger.error(`Public CRM ${method} ${path} failed (${response.status}):`, data.message);
|
|
4465
|
+
return { success: false, error: data.message, status: response.status };
|
|
4466
|
+
}
|
|
4467
|
+
catch (error) {
|
|
4468
|
+
logger.error(`Public CRM ${method} ${path} error:`, error);
|
|
4469
|
+
return { success: false, error: error.message };
|
|
4470
|
+
}
|
|
4471
|
+
}
|
|
4171
4472
|
/**
|
|
4172
4473
|
* Destroy tracker and cleanup
|
|
4173
4474
|
*/
|
|
@@ -4292,7 +4593,7 @@ if (typeof window !== 'undefined') {
|
|
|
4292
4593
|
* @example
|
|
4293
4594
|
* const instance = createCliantaTracker({
|
|
4294
4595
|
* projectId: 'your-project-id',
|
|
4295
|
-
* apiEndpoint: '
|
|
4596
|
+
* apiEndpoint: environment.cliantaApiEndpoint || 'http://localhost:5000',
|
|
4296
4597
|
* });
|
|
4297
4598
|
*
|
|
4298
4599
|
* instance.tracker?.track('page_view', 'Home Page');
|