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