@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.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Clianta SDK v1.5.1
2
+ * Clianta SDK v1.6.0
3
3
  * (c) 2026 Clianta
4
4
  * Released under the MIT License.
5
5
  */
@@ -14,7 +14,7 @@
14
14
  * @see SDK_VERSION in core/config.ts
15
15
  */
16
16
  /** SDK Version */
17
- const SDK_VERSION = '1.4.0';
17
+ const SDK_VERSION = '1.6.0';
18
18
  /** Default API endpoint — reads from env or falls back to localhost */
19
19
  const getDefaultApiEndpoint = () => {
20
20
  // Build-time env var (works with Next.js, Vite, CRA, etc.)
@@ -3696,6 +3696,85 @@
3696
3696
  }
3697
3697
  }
3698
3698
 
3699
+ /**
3700
+ * Privacy-safe visitor API client.
3701
+ * All methods return data for the current visitor only (no cross-visitor access).
3702
+ */
3703
+ class VisitorClient {
3704
+ constructor(transport, workspaceId, visitorId) {
3705
+ this.transport = transport;
3706
+ this.workspaceId = workspaceId;
3707
+ this.visitorId = visitorId;
3708
+ }
3709
+ /** Update visitorId (e.g. after reset) */
3710
+ setVisitorId(id) {
3711
+ this.visitorId = id;
3712
+ }
3713
+ basePath() {
3714
+ return `/api/public/track/visitor/${this.workspaceId}/${this.visitorId}`;
3715
+ }
3716
+ /**
3717
+ * Get the current visitor's profile from the CRM.
3718
+ * Returns visitor data and linked contact info if identified.
3719
+ */
3720
+ async getProfile() {
3721
+ const result = await this.transport.fetchData(`${this.basePath()}/profile`);
3722
+ if (result.success && result.data) {
3723
+ logger.debug('Visitor profile fetched:', result.data);
3724
+ return result.data;
3725
+ }
3726
+ logger.warn('Failed to fetch visitor profile:', result.error);
3727
+ return null;
3728
+ }
3729
+ /**
3730
+ * Get the current visitor's recent activity/events.
3731
+ * Returns paginated list of tracking events.
3732
+ */
3733
+ async getActivity(options) {
3734
+ const params = {};
3735
+ if (options?.page)
3736
+ params.page = options.page.toString();
3737
+ if (options?.limit)
3738
+ params.limit = options.limit.toString();
3739
+ if (options?.eventType)
3740
+ params.eventType = options.eventType;
3741
+ if (options?.startDate)
3742
+ params.startDate = options.startDate;
3743
+ if (options?.endDate)
3744
+ params.endDate = options.endDate;
3745
+ const result = await this.transport.fetchData(`${this.basePath()}/activity`, params);
3746
+ if (result.success && result.data) {
3747
+ return result.data;
3748
+ }
3749
+ logger.warn('Failed to fetch visitor activity:', result.error);
3750
+ return null;
3751
+ }
3752
+ /**
3753
+ * Get a summarized journey timeline for the current visitor.
3754
+ * Includes top pages, sessions, time spent, and recent activities.
3755
+ */
3756
+ async getTimeline() {
3757
+ const result = await this.transport.fetchData(`${this.basePath()}/timeline`);
3758
+ if (result.success && result.data) {
3759
+ return result.data;
3760
+ }
3761
+ logger.warn('Failed to fetch visitor timeline:', result.error);
3762
+ return null;
3763
+ }
3764
+ /**
3765
+ * Get engagement metrics for the current visitor.
3766
+ * Includes time on site, page views, bounce rate, and engagement score.
3767
+ */
3768
+ async getEngagement() {
3769
+ const result = await this.transport.fetchData(`${this.basePath()}/engagement`);
3770
+ if (result.success && result.data) {
3771
+ return result.data;
3772
+ }
3773
+ logger.warn('Failed to fetch visitor engagement:', result.error);
3774
+ return null;
3775
+ }
3776
+ }
3777
+
3699
3778
  /**
3700
3779
  * Clianta SDK - Main Tracker Class
3701
3780
  * @see SDK_VERSION in core/config.ts
@@ -3709,10 +3788,16 @@
3709
3788
  this.isInitialized = false;
3710
3789
  /** contactId after a successful identify() call */
3711
3790
  this.contactId = null;
3791
+ /** groupId after a successful group() call */
3792
+ this.groupId = null;
3712
3793
  /** Pending identify retry on next flush */
3713
3794
  this.pendingIdentify = null;
3714
3795
  /** Registered event schemas for validation */
3715
3796
  this.eventSchemas = new Map();
3797
+ /** Event middleware pipeline */
3798
+ this.middlewares = [];
3799
+ /** Ready callbacks */
3800
+ this.readyCallbacks = [];
3716
3801
  if (!workspaceId) {
3717
3802
  throw new Error('[Clianta] Workspace ID is required');
3718
3803
  }
@@ -3738,6 +3823,8 @@
3738
3823
  this.visitorId = this.createVisitorId();
3739
3824
  this.sessionId = this.createSessionId();
3740
3825
  logger.debug('IDs created', { visitorId: this.visitorId, sessionId: this.sessionId });
3826
+ // Initialize visitor API client
3827
+ this.visitor = new VisitorClient(this.transport, this.workspaceId, this.visitorId);
3741
3828
  // Security warnings
3742
3829
  if (this.config.apiEndpoint.startsWith('http://') &&
3743
3830
  typeof window !== 'undefined' &&
@@ -3752,6 +3839,16 @@
3752
3839
  this.initPlugins();
3753
3840
  this.isInitialized = true;
3754
3841
  logger.info('SDK initialized successfully');
3842
+ // Fire ready callbacks
3843
+ for (const cb of this.readyCallbacks) {
3844
+ try {
3845
+ cb();
3846
+ }
3847
+ catch (e) {
3848
+ logger.error('onReady callback error:', e);
3849
+ }
3850
+ }
3851
+ this.readyCallbacks = [];
3755
3852
  }
3756
3853
  /**
3757
3854
  * Create visitor ID based on storage mode
@@ -3864,6 +3961,10 @@
3864
3961
  if (this.contactId) {
3865
3962
  event.contactId = this.contactId;
3866
3963
  }
3964
+ // Attach groupId if known (from a prior group() call)
3965
+ if (this.groupId) {
3966
+ event.groupId = this.groupId;
3967
+ }
3867
3968
  // Validate event against registered schema (debug mode only)
3868
3969
  this.validateEventSchema(eventType, properties);
3869
3970
  // Check consent before tracking
@@ -3877,8 +3978,11 @@
3877
3978
  logger.debug('Event dropped (no consent):', eventName);
3878
3979
  return;
3879
3980
  }
3880
- this.queue.push(event);
3881
- logger.debug('Event tracked:', eventName, properties);
3981
+ // Run event through middleware pipeline
3982
+ this.runMiddleware(event, () => {
3983
+ this.queue.push(event);
3984
+ logger.debug('Event tracked:', eventName, properties);
3985
+ });
3882
3986
  }
3883
3987
  /**
3884
3988
  * Track a page view
@@ -3940,80 +4044,47 @@
3940
4044
  }
3941
4045
  /**
3942
4046
  * Get the current visitor's profile from the CRM.
3943
- * Returns visitor data and linked contact info if identified.
3944
- * Only returns data for the current visitor (privacy-safe for frontend).
4047
+ * @deprecated Use `tracker.visitor.getProfile()` instead.
3945
4048
  */
3946
4049
  async getVisitorProfile() {
3947
4050
  if (!this.isInitialized) {
3948
4051
  logger.warn('SDK not initialized');
3949
4052
  return null;
3950
4053
  }
3951
- const result = await this.transport.fetchData(`/api/public/track/visitor/${this.workspaceId}/${this.visitorId}/profile`);
3952
- if (result.success && result.data) {
3953
- logger.debug('Visitor profile fetched:', result.data);
3954
- return result.data;
3955
- }
3956
- logger.warn('Failed to fetch visitor profile:', result.error);
3957
- return null;
4054
+ return this.visitor.getProfile();
3958
4055
  }
3959
4056
  /**
3960
4057
  * Get the current visitor's recent activity/events.
3961
- * Returns paginated list of tracking events for this visitor.
4058
+ * @deprecated Use `tracker.visitor.getActivity()` instead.
3962
4059
  */
3963
4060
  async getVisitorActivity(options) {
3964
4061
  if (!this.isInitialized) {
3965
4062
  logger.warn('SDK not initialized');
3966
4063
  return null;
3967
4064
  }
3968
- const params = {};
3969
- if (options?.page)
3970
- params.page = options.page.toString();
3971
- if (options?.limit)
3972
- params.limit = options.limit.toString();
3973
- if (options?.eventType)
3974
- params.eventType = options.eventType;
3975
- if (options?.startDate)
3976
- params.startDate = options.startDate;
3977
- if (options?.endDate)
3978
- params.endDate = options.endDate;
3979
- const result = await this.transport.fetchData(`/api/public/track/visitor/${this.workspaceId}/${this.visitorId}/activity`, params);
3980
- if (result.success && result.data) {
3981
- return result.data;
3982
- }
3983
- logger.warn('Failed to fetch visitor activity:', result.error);
3984
- return null;
4065
+ return this.visitor.getActivity(options);
3985
4066
  }
3986
4067
  /**
3987
4068
  * Get a summarized journey timeline for the current visitor.
3988
- * Includes top pages, sessions, time spent, and recent activities.
4069
+ * @deprecated Use `tracker.visitor.getTimeline()` instead.
3989
4070
  */
3990
4071
  async getVisitorTimeline() {
3991
4072
  if (!this.isInitialized) {
3992
4073
  logger.warn('SDK not initialized');
3993
4074
  return null;
3994
4075
  }
3995
- const result = await this.transport.fetchData(`/api/public/track/visitor/${this.workspaceId}/${this.visitorId}/timeline`);
3996
- if (result.success && result.data) {
3997
- return result.data;
3998
- }
3999
- logger.warn('Failed to fetch visitor timeline:', result.error);
4000
- return null;
4076
+ return this.visitor.getTimeline();
4001
4077
  }
4002
4078
  /**
4003
4079
  * Get engagement metrics for the current visitor.
4004
- * Includes time on site, page views, bounce rate, and engagement score.
4080
+ * @deprecated Use `tracker.visitor.getEngagement()` instead.
4005
4081
  */
4006
4082
  async getVisitorEngagement() {
4007
4083
  if (!this.isInitialized) {
4008
4084
  logger.warn('SDK not initialized');
4009
4085
  return null;
4010
4086
  }
4011
- const result = await this.transport.fetchData(`/api/public/track/visitor/${this.workspaceId}/${this.visitorId}/engagement`);
4012
- if (result.success && result.data) {
4013
- return result.data;
4014
- }
4015
- logger.warn('Failed to fetch visitor engagement:', result.error);
4016
- return null;
4087
+ return this.visitor.getEngagement();
4017
4088
  }
4018
4089
  /**
4019
4090
  * Retry pending identify call
@@ -4044,6 +4115,149 @@
4044
4115
  logger.enabled = enabled;
4045
4116
  logger.info(`Debug mode ${enabled ? 'enabled' : 'disabled'}`);
4046
4117
  }
4118
+ // ============================================
4119
+ // GROUP, ALIAS, SCREEN
4120
+ // ============================================
4121
+ /**
4122
+ * Associate the current visitor with a group (company/account).
4123
+ * The groupId will be attached to all subsequent track() calls.
4124
+ */
4125
+ group(groupId, traits = {}) {
4126
+ if (!groupId) {
4127
+ logger.warn('groupId is required for group()');
4128
+ return;
4129
+ }
4130
+ this.groupId = groupId;
4131
+ logger.info('Visitor grouped:', groupId);
4132
+ this.track('group', 'Group Identified', {
4133
+ groupId,
4134
+ ...traits,
4135
+ });
4136
+ }
4137
+ /**
4138
+ * Merge two visitor identities.
4139
+ * Links `previousId` (typically the anonymous visitor) to `newId` (the known user).
4140
+ * If `previousId` is omitted, the current visitorId is used.
4141
+ */
4142
+ async alias(newId, previousId) {
4143
+ if (!newId) {
4144
+ logger.warn('newId is required for alias()');
4145
+ return false;
4146
+ }
4147
+ const prevId = previousId || this.visitorId;
4148
+ logger.info('Aliasing visitor:', { from: prevId, to: newId });
4149
+ try {
4150
+ const url = `${this.config.apiEndpoint}/api/public/track/alias`;
4151
+ const response = await fetch(url, {
4152
+ method: 'POST',
4153
+ headers: { 'Content-Type': 'application/json' },
4154
+ body: JSON.stringify({
4155
+ workspaceId: this.workspaceId,
4156
+ previousId: prevId,
4157
+ newId,
4158
+ }),
4159
+ });
4160
+ if (response.ok) {
4161
+ logger.info('Alias successful');
4162
+ return true;
4163
+ }
4164
+ logger.error('Alias failed:', response.status);
4165
+ return false;
4166
+ }
4167
+ catch (error) {
4168
+ logger.error('Alias request failed:', error);
4169
+ return false;
4170
+ }
4171
+ }
4172
+ /**
4173
+ * Track a screen view (for mobile-first PWAs and SPAs).
4174
+ * Similar to page() but semantically for app screens.
4175
+ */
4176
+ screen(name, properties = {}) {
4177
+ this.track('screen_view', name, {
4178
+ ...properties,
4179
+ screenName: name,
4180
+ });
4181
+ }
4182
+ // ============================================
4183
+ // MIDDLEWARE
4184
+ // ============================================
4185
+ /**
4186
+ * Register event middleware.
4187
+ * Middleware functions receive the event and a `next` callback.
4188
+ * Call `next()` to pass the event through, or don't call it to drop the event.
4189
+ *
4190
+ * @example
4191
+ * tracker.use((event, next) => {
4192
+ * // Strip PII from events
4193
+ * delete event.properties.email;
4194
+ * next(); // pass it through
4195
+ * });
4196
+ */
4197
+ use(middleware) {
4198
+ this.middlewares.push(middleware);
4199
+ logger.debug('Middleware registered');
4200
+ }
4201
+ /**
4202
+ * Run event through the middleware pipeline.
4203
+ * Executes each middleware in order; if any skips `next()`, the event is dropped.
4204
+ */
4205
+ runMiddleware(event, finalCallback) {
4206
+ if (this.middlewares.length === 0) {
4207
+ finalCallback();
4208
+ return;
4209
+ }
4210
+ let index = 0;
4211
+ const middlewares = this.middlewares;
4212
+ const next = () => {
4213
+ index++;
4214
+ if (index < middlewares.length) {
4215
+ try {
4216
+ middlewares[index](event, next);
4217
+ }
4218
+ catch (e) {
4219
+ logger.error('Middleware error:', e);
4220
+ finalCallback();
4221
+ }
4222
+ }
4223
+ else {
4224
+ finalCallback();
4225
+ }
4226
+ };
4227
+ try {
4228
+ middlewares[0](event, next);
4229
+ }
4230
+ catch (e) {
4231
+ logger.error('Middleware error:', e);
4232
+ finalCallback();
4233
+ }
4234
+ }
4235
+ // ============================================
4236
+ // LIFECYCLE
4237
+ // ============================================
4238
+ /**
4239
+ * Register a callback to be invoked when the SDK is fully initialized.
4240
+ * If already initialized, the callback fires immediately.
4241
+ */
4242
+ onReady(callback) {
4243
+ if (this.isInitialized) {
4244
+ try {
4245
+ callback();
4246
+ }
4247
+ catch (e) {
4248
+ logger.error('onReady callback error:', e);
4249
+ }
4250
+ }
4251
+ else {
4252
+ this.readyCallbacks.push(callback);
4253
+ }
4254
+ }
4255
+ /**
4256
+ * Check if the SDK is fully initialized and ready.
4257
+ */
4258
+ isReady() {
4259
+ return this.isInitialized;
4260
+ }
4047
4261
  /**
4048
4262
  * Register a schema for event validation.
4049
4263
  * When debug mode is enabled, events will be validated against registered schemas.