saasco-sdk 0.1.13 → 0.1.15

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/index.esm.js CHANGED
@@ -2,6 +2,8 @@ import { __awaiter } from 'tslib';
2
2
  import { v4 } from '@lukeed/uuid';
3
3
  import { z } from 'zod';
4
4
 
5
+ var version = "0.1.15";
6
+
5
7
  const timezones = {
6
8
  'Asia/Barnaul': 'RU',
7
9
  'Africa/Nouakchott': 'MR',
@@ -430,10 +432,39 @@ const timezones = {
430
432
  'Asia/Calcutta': 'IN'
431
433
  };
432
434
 
433
- var version = "0.1.13";
435
+ function getBrowserContext() {
436
+ var _a;
437
+ const isBrowser = typeof window !== 'undefined';
438
+ if (!isBrowser) return;
439
+ const customNavigator = navigator;
440
+ const $locale = (customNavigator === null || customNavigator === void 0 ? void 0 : customNavigator.languages) && (customNavigator === null || customNavigator === void 0 ? void 0 : customNavigator.languages.length) ? customNavigator === null || customNavigator === void 0 ? void 0 : customNavigator.languages[0] : (customNavigator === null || customNavigator === void 0 ? void 0 : customNavigator.userLanguage) || (customNavigator === null || customNavigator === void 0 ? void 0 : customNavigator.language) || (customNavigator === null || customNavigator === void 0 ? void 0 : customNavigator.browserLanguage) || 'en';
441
+ // https://caniuse.com/?search=Intl.DateTimeFormat().resolvedOptions().timeZone
442
+ // Only has 96.63% global support, so we need to check for undefined
443
+ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
444
+ const $location = timezones[timezone] || ((_a = $locale === null || $locale === void 0 ? void 0 : $locale.split('-')) === null || _a === void 0 ? void 0 : _a[1]) || 'undefined';
445
+ const $href = window.location.href;
446
+ const $pathname = window.location.pathname;
447
+ const $referrer = document.referrer;
448
+ const $screenDPI = window.devicePixelRatio;
449
+ const $screenHeight = screen.height;
450
+ const $screenWidth = screen.width;
451
+ const $title = document.title;
452
+ const $userAgent = window.navigator.userAgent;
453
+ return {
454
+ $locale,
455
+ $location,
456
+ $href,
457
+ $pathname,
458
+ $referrer,
459
+ $screenDPI,
460
+ $screenHeight,
461
+ $screenWidth,
462
+ $title,
463
+ $userAgent
464
+ };
465
+ }
434
466
 
435
467
  const isBrowser = typeof window !== 'undefined';
436
- let userId = null;
437
468
  const PREF = 'saasco-sdk';
438
469
  const data = {};
439
470
  function storeData(key, value, ttl) {
@@ -499,35 +530,11 @@ function setAnonmousId({
499
530
  storeData(`anonymous-id`, anonymousId, 1000 * 60 * 60 * 24 * 365);
500
531
  return anonymousId;
501
532
  }
502
- function getBrowserContext() {
503
- var _a;
504
- if (!isBrowser) return {};
505
- const customNavigator = navigator;
506
- const $locale = (customNavigator === null || customNavigator === void 0 ? void 0 : customNavigator.languages) && (customNavigator === null || customNavigator === void 0 ? void 0 : customNavigator.languages.length) ? customNavigator === null || customNavigator === void 0 ? void 0 : customNavigator.languages[0] : (customNavigator === null || customNavigator === void 0 ? void 0 : customNavigator.userLanguage) || (customNavigator === null || customNavigator === void 0 ? void 0 : customNavigator.language) || (customNavigator === null || customNavigator === void 0 ? void 0 : customNavigator.browserLanguage) || 'en';
507
- // https://caniuse.com/?search=Intl.DateTimeFormat().resolvedOptions().timeZone
508
- // Only has 96.63% global support, so we need to check for undefined
509
- const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
510
- const $location = timezones[timezone] || ((_a = $locale === null || $locale === void 0 ? void 0 : $locale.split('-')) === null || _a === void 0 ? void 0 : _a[1]) || 'undefined';
511
- const $href = window.location.href;
512
- const $pathname = window.location.pathname;
513
- const $referrer = document.referrer;
514
- const $screenDPI = window.devicePixelRatio;
515
- const $screenHeight = screen.height;
516
- const $screenWidth = screen.width;
517
- const $title = document.title;
518
- const $userAgent = window.navigator.userAgent;
519
- return {
520
- $locale,
521
- $location,
522
- $href,
523
- $pathname,
524
- $referrer,
525
- $screenDPI,
526
- $screenHeight,
527
- $screenWidth,
528
- $title,
529
- $userAgent
530
- };
533
+ function getUserId() {
534
+ return retrieveData(`user-id`);
535
+ }
536
+ function setUserId(userId) {
537
+ storeData(`user-id`, userId, 1000 * 60 * 60 * 24 * 365);
531
538
  }
532
539
  class Saasco {
533
540
  /**
@@ -538,10 +545,12 @@ class Saasco {
538
545
  * @param config.autoPageTracking Whether to automatically track page views. Default is false.
539
546
  * @param config.enabled Whether analytics is enabled. Default is true. Set to false for development and staging envioronments. Will still allow debug mode to be true, just no events will be sent
540
547
  * @param config.debug Whether to log debug information. Default is false.
548
+ * @param config.trackUrlParams Whether to track URL parameters. Default is true.
549
+ * @param config.trackHashChanges Whether to track hash changes. Default is true.
541
550
  */
542
551
  constructor(config) {
543
552
  this.config = config;
544
- this.lastPageViewHref = '';
553
+ this.lastPageViewPath = '';
545
554
  this.isInitialized = false;
546
555
  if (!config.projectId) {
547
556
  this.error("Project ID is required but has not been provided. If you are using an env variable make sure it's set correctly.");
@@ -549,6 +558,9 @@ class Saasco {
549
558
  }
550
559
  // default enabled to true
551
560
  this.config.enabled = this.config.enabled === undefined ? true : this.config.enabled;
561
+ // default URL tracking options
562
+ this.config.trackUrlParams = this.config.trackUrlParams === undefined ? true : this.config.trackUrlParams;
563
+ this.config.trackHashChanges = this.config.trackHashChanges === undefined ? true : this.config.trackHashChanges;
552
564
  }
553
565
  init() {
554
566
  if (this.isInitialized) {
@@ -586,16 +598,6 @@ class Saasco {
586
598
  if (!this.config.projectId) {
587
599
  return this.error("Unable to track event. Project ID is required but has not been provided. If you are using an env variable make sure it's set correctly.");
588
600
  }
589
- // Prevent duplicate Page View tracking
590
- if (name === 'Page View') {
591
- if (!isBrowser) {
592
- console.warn('Saasco page tracking is only available in the browser');
593
- return;
594
- }
595
- const pageViewHref = window.location.href;
596
- if (pageViewHref === this.lastPageViewHref) return;
597
- this.lastPageViewHref = pageViewHref;
598
- }
599
601
  setSessionId();
600
602
  setAnonmousId();
601
603
  const browserContext = getBrowserContext();
@@ -606,9 +608,9 @@ class Saasco {
606
608
  version,
607
609
  sessionId: getSessionId(),
608
610
  anonymousId: getAnonymousId(),
609
- distinctId: userId,
611
+ distinctId: getUserId(),
610
612
  projectId: this.config.projectId,
611
- payload: JSON.stringify(Object.assign(Object.assign({}, browserContext), {
613
+ payload: JSON.stringify(Object.assign(Object.assign({}, browserContext || {}), {
612
614
  properties: properties || {}
613
615
  })),
614
616
  context: JSON.stringify(context || {})
@@ -622,6 +624,23 @@ class Saasco {
622
624
  * Before implementing this make sure you have disabled the autoPageTracking in the config or you will get duplicate page views
623
625
  */
624
626
  page() {
627
+ if (!isBrowser) {
628
+ console.warn('Saasco page tracking is only available in the browser');
629
+ return;
630
+ }
631
+ const url = new URL(window.location.href);
632
+ let pathToCompare = url.pathname;
633
+ // Include search params if configured
634
+ if (this.config.trackUrlParams) {
635
+ pathToCompare += url.search;
636
+ }
637
+ // Include hash if configured
638
+ if (this.config.trackHashChanges) {
639
+ pathToCompare += url.hash;
640
+ }
641
+ // Only track if the path has changed
642
+ if (pathToCompare === this.lastPageViewPath) return;
643
+ this.lastPageViewPath = pathToCompare;
625
644
  return this.track('Page View');
626
645
  }
627
646
  identify(distinctIdOrProperties, propertiesOrContext, contextOrNothing) {
@@ -635,7 +654,7 @@ class Saasco {
635
654
  // When the user gets identified as null this will reset the users session and anonymous id
636
655
  // This should only be called when distinctId is null and there is an existing userId.
637
656
  // This means the user has logged out and we should reset the session and anonymous IDs
638
- const userIdChangedToNull = distinctId === null && !!userId;
657
+ const userIdChangedToNull = distinctId === null && !!getUserId();
639
658
  if (userIdChangedToNull) this.log('User ID change to null. Session reset');
640
659
  const reset = userIdChangedToNull;
641
660
  setSessionId({
@@ -645,7 +664,7 @@ class Saasco {
645
664
  reset
646
665
  });
647
666
  // set the distinct Id to the userId
648
- userId = distinctId;
667
+ setUserId(distinctId);
649
668
  // No distinctId provided so we don't track the user
650
669
  if (!distinctId) return;
651
670
  const data = {
@@ -805,6 +824,9 @@ function coerceReservedProperties(properties) {
805
824
  }, {
806
825
  key: 'displayName',
807
826
  coerceFrom: ['display_name', 'displayName', 'user_display_name', 'userDisplayName', 'full_name', 'fullName', 'user_full_name', 'userFullName', 'complete_name', 'completeName', 'user_complete_name', 'userCompleteName', 'name', 'username']
827
+ }, {
828
+ key: '$stripeCustomerId',
829
+ coerceFrom: ['stripe_customer_id', 'stripeCustomerId', 'stripe_customer_id', 'stripeCustomerId', 'stripe_customer_id', 'stripeCustomerId', 'stripe_customer_id', 'stripeCustomerId', 'stripe_customer_id', 'stripeCustomerId']
808
830
  }];
809
831
  return coerce.reduce((acc, {
810
832
  key,
@@ -852,6 +874,61 @@ function coerceValue(properties, keys, schema) {
852
874
  return coercedValue;
853
875
  }
854
876
 
877
+ const browserContextSchema = z.object({
878
+ $locale: z.string(),
879
+ $location: z.string(),
880
+ $href: z.string(),
881
+ $pathname: z.string(),
882
+ $referrer: z.string(),
883
+ $screenDPI: z.number(),
884
+ $screenHeight: z.number(),
885
+ $screenWidth: z.number(),
886
+ $title: z.string(),
887
+ $userAgent: z.string()
888
+ });
889
+ const serverContextSchema = z.object({
890
+ $city: z.string().optional(),
891
+ $country: z.string().optional(),
892
+ $continent: z.string().optional(),
893
+ $latitude: z.number().optional(),
894
+ $longitude: z.number().optional(),
895
+ $timezone: z.string().optional(),
896
+ $userAgent: z.string().optional(),
897
+ $referrer: z.string().optional(),
898
+ $ip: z.string().optional(),
899
+ $processedAt: z.string().datetime().optional(),
900
+ $identityHash: z.string().optional()
901
+ });
902
+ const trackedEventTypesSchema = z.enum(['SHORTLINK_REDIRECT']);
903
+ const trackedEventSchema = z.object({
904
+ id: z.string().uuid(),
905
+ timestamp: z.string().datetime(),
906
+ projectId: z.string(),
907
+ version: z.string(),
908
+ type: trackedEventTypesSchema,
909
+ payload: z.object({
910
+ browserContext: browserContextSchema.optional(),
911
+ serverContext: serverContextSchema.optional(),
912
+ properties: z.record(z.string(), z.any()).optional()
913
+ })
914
+ });
915
+ /**
916
+ * Creates a zod for tracking any type of event.
917
+ * Includes the base tracking schema and extends with any new schema
918
+ * @param type
919
+ * @param payloadSchema
920
+ * @returns
921
+ */
922
+ function createTrackedEventTypeSchema(type, payloadSchema) {
923
+ const typeSchema = z.literal(type);
924
+ return trackedEventSchema.extend({
925
+ type: typeSchema,
926
+ payload: z.object(Object.assign(Object.assign({}, trackedEventSchema.shape.payload.shape), {
927
+ properties: payloadSchema
928
+ }))
929
+ });
930
+ }
931
+
855
932
  const tbAny = z.union([z.string(), z.number(), z.array(z.union([z.string(), z.number()]))]);
856
933
  const tinyBirdBaseSchema = z.object({
857
934
  meta: z.array(z.object({
@@ -891,7 +968,9 @@ const TinybirdJobResponseSchema = z.object({
891
968
  const latestEventsRequestSchema = z.object({
892
969
  projectId: z.string(),
893
970
  limit: z.number().optional(),
894
- payload: z.boolean().optional()
971
+ payload: z.boolean().optional(),
972
+ distinctId: z.string().optional(),
973
+ action: z.string().optional()
895
974
  });
896
975
  const latestEventItemSchema = z.object({
897
976
  id: z.string(),
@@ -902,7 +981,9 @@ const latestEventItemSchema = z.object({
902
981
  href: z.string(),
903
982
  device: z.string(),
904
983
  browser: z.string(),
905
- payload: z.string().optional()
984
+ payload: z.string().optional(),
985
+ distinctId: z.string().optional(),
986
+ unique: z.boolean().optional()
906
987
  });
907
988
  const latestEventsResponseSchema = tinyBirdBaseSchema.extend({
908
989
  data: z.array(latestEventItemSchema)
@@ -1014,7 +1095,7 @@ const currentUsersResponseSchema = tinyBirdBaseSchema.extend({
1014
1095
  }))
1015
1096
  });
1016
1097
  const reservedPropertiesSchema = z.object({
1017
- age: z.number().or(z.string()).nullish(),
1098
+ age: z.number().nullish(),
1018
1099
  avatar: z.string().url().nullish(),
1019
1100
  birthday: z.string().nullish(),
1020
1101
  createdAt: z.string().nullish(),
@@ -1029,8 +1110,17 @@ const reservedPropertiesSchema = z.object({
1029
1110
  title: z.string().nullish(),
1030
1111
  username: z.string().nullish(),
1031
1112
  website: z.string().nullish(),
1113
+ // Default Properties
1032
1114
  $id: z.string().nullish(),
1033
- $lastSeen: z.string().nullish()
1115
+ $lastSeen: z.string().nullish(),
1116
+ $unsubscribed: z.boolean().nullish(),
1117
+ $unsubscribeReason: z.enum(['manual', 'complained', 'bounced']).nullish(),
1118
+ // Stripe Properties
1119
+ $stripeCustomerId: z.string().nullish(),
1120
+ $stripeTotalPayments: z.number().or(z.string()).nullish(),
1121
+ $stripeTotalSpent: z.number().or(z.string()).nullish(),
1122
+ // Internal properties
1123
+ $_toDelete: z.boolean().nullish()
1034
1124
  });
1035
1125
  const contactPropertiesSchema = reservedPropertiesSchema.optional().and(z.record(z.union([z.string(), z.number(), z.boolean(), z.date(), z.null(), z.array(z.union([z.string(), z.number(), z.boolean(), z.date(), z.null()]))])));
1036
1126
  const analyticsUserRawSchema = z.object({
@@ -1075,4 +1165,4 @@ const latestIdentifiesResponseSchema = tinyBirdBaseSchema.extend({
1075
1165
  }))
1076
1166
  });
1077
1167
 
1078
- export { Saasco, TinybirdJobResponseSchema, analyticsEventSchema, analyticsUserRawSchema, analyticsUserSchema, baseTopRequestSchema, coerceReservedProperties, contactPropertiesSchema, currentUsersRequestSchema, currentUsersResponseSchema, getAnalyticsUserParamsSchema, idenfitySchema, kpiSchema, kpisRequestSchema, kpisResponseSchema, latestEventItemSchema, latestEventsRequestSchema, latestEventsResponseSchema, latestIdentifiesRequestSchema, latestIdentifiesResponseSchema, listUsersParamsSchema, reservedPropertiesSchema, tbAny, timezones, tinyBirdBaseSchema, topActionsResponseSchema, topBrowsersResponseSchema, topDevicesResponseSchema, topLocationsResponseSchema, topPagesResponseSchema, topSourcesResponseSchema, topSourcesSchema, userEventsResponseSchema };
1168
+ export { Saasco, TinybirdJobResponseSchema, analyticsEventSchema, analyticsUserRawSchema, analyticsUserSchema, baseTopRequestSchema, browserContextSchema, coerceReservedProperties, contactPropertiesSchema, createTrackedEventTypeSchema, currentUsersRequestSchema, currentUsersResponseSchema, getAnalyticsUserParamsSchema, getBrowserContext, idenfitySchema, kpiSchema, kpisRequestSchema, kpisResponseSchema, latestEventItemSchema, latestEventsRequestSchema, latestEventsResponseSchema, latestIdentifiesRequestSchema, latestIdentifiesResponseSchema, listUsersParamsSchema, reservedPropertiesSchema, serverContextSchema, tbAny, timezones, tinyBirdBaseSchema, topActionsResponseSchema, topBrowsersResponseSchema, topDevicesResponseSchema, topLocationsResponseSchema, topPagesResponseSchema, topSourcesResponseSchema, topSourcesSchema, trackedEventSchema, trackedEventTypesSchema, userEventsResponseSchema };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "saasco-sdk",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "dependencies": {
5
5
  "tslib": "^2.3.0",
6
6
  "@lukeed/uuid": "^2.0.1",
package/src/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  export * from './lib/analytics';
2
2
  export * from './lib/coerceReservedProperties';
3
+ export * from './lib/getBrowserContext';
3
4
  export * from './lib/timezones';
5
+ export * from './lib/tracking';
4
6
  export * from './lib/types';
@@ -1,11 +1,12 @@
1
1
  declare global {
2
2
  interface Window {
3
3
  saascoAutoPageTrackingActive?: boolean;
4
+ saasco: Saasco;
4
5
  }
5
6
  }
6
7
  export declare class Saasco {
7
8
  private config;
8
- private lastPageViewHref;
9
+ private lastPageViewPath;
9
10
  private isInitialized;
10
11
  /**
11
12
  * Creates an instance of Saasco SDK.
@@ -15,6 +16,8 @@ export declare class Saasco {
15
16
  * @param config.autoPageTracking Whether to automatically track page views. Default is false.
16
17
  * @param config.enabled Whether analytics is enabled. Default is true. Set to false for development and staging envioronments. Will still allow debug mode to be true, just no events will be sent
17
18
  * @param config.debug Whether to log debug information. Default is false.
19
+ * @param config.trackUrlParams Whether to track URL parameters. Default is true.
20
+ * @param config.trackHashChanges Whether to track hash changes. Default is true.
18
21
  */
19
22
  constructor(config: {
20
23
  projectId: string;
@@ -22,6 +25,8 @@ export declare class Saasco {
22
25
  autoPageTracking?: boolean;
23
26
  enabled?: boolean;
24
27
  debug?: boolean;
28
+ trackUrlParams?: boolean;
29
+ trackHashChanges?: boolean;
25
30
  });
26
31
  init(): void;
27
32
  disableDebug(): void;
@@ -0,0 +1,2 @@
1
+ import { BrowserContext } from './tracking/types';
2
+ export declare function getBrowserContext(): BrowserContext | undefined;
@@ -0,0 +1 @@
1
+ export * from './types';