saasco-sdk 0.2.3 → 0.2.5

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/dist/index.cjs CHANGED
@@ -20,9 +20,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ BrowserContextSchema: () => BrowserContextSchema,
23
24
  IntegrationManager: () => IntegrationManager,
24
25
  Saasco: () => Saasco,
25
- browserContextSchema: () => browserContextSchema,
26
26
  createFacebookPixelIntegration: () => createFacebookPixelIntegration,
27
27
  createPinterestTagIntegration: () => createPinterestTagIntegration,
28
28
  createTikTokPixelIntegration: () => createTikTokPixelIntegration,
@@ -38,7 +38,7 @@ module.exports = __toCommonJS(index_exports);
38
38
  var import_psl = require("psl");
39
39
 
40
40
  // package.json
41
- var version = "0.2.3";
41
+ var version = "0.2.5";
42
42
 
43
43
  // src/lib/timezones.ts
44
44
  var timezones = {
@@ -487,6 +487,9 @@ var AnalyticsLogger = class {
487
487
  constructor(config) {
488
488
  this.config = config;
489
489
  }
490
+ setLevel(level) {
491
+ this.config.level = level;
492
+ }
490
493
  debug(...args) {
491
494
  if (this.config.level < 3 /* DEBUG */) {
492
495
  return;
@@ -567,9 +570,34 @@ function getIntegrationLoggerLevel(integrationName, debug) {
567
570
  }
568
571
 
569
572
  // src/lib/utils/uuid.ts
570
- var import_uuid = require("@lukeed/uuid");
573
+ var HEX = [];
574
+ for (let i = 0; i < 256; i++) {
575
+ HEX[i] = (i + 256).toString(16).slice(1);
576
+ }
571
577
  function uuid() {
572
- return (0, import_uuid.v4)();
578
+ const bytes = randomBytes(16);
579
+ bytes[6] = bytes[6] & 15 | 64;
580
+ bytes[8] = bytes[8] & 63 | 128;
581
+ let out = "";
582
+ for (let i = 0; i < 16; i++) {
583
+ out += HEX[bytes[i]];
584
+ if (i === 3 || i === 5 || i === 7 || i === 9) {
585
+ out += "-";
586
+ }
587
+ }
588
+ return out;
589
+ }
590
+ function randomBytes(length) {
591
+ const bytes = new Uint8Array(length);
592
+ const webCrypto = globalThis.crypto;
593
+ if (typeof webCrypto?.getRandomValues === "function") {
594
+ webCrypto.getRandomValues(bytes);
595
+ return bytes;
596
+ }
597
+ for (let i = 0; i < length; i++) {
598
+ bytes[i] = Math.random() * 256 | 0;
599
+ }
600
+ return bytes;
573
601
  }
574
602
 
575
603
  // src/lib/getBrowserContext.ts
@@ -1875,6 +1903,41 @@ function normalizeSaascoUrl(url) {
1875
1903
  return parsed.toString();
1876
1904
  }
1877
1905
 
1906
+ // src/lib/widgetLoader.ts
1907
+ function isEnabledPayload(data2) {
1908
+ return typeof data2 === "object" && data2 !== null && "enabled" in data2 && data2.enabled === true;
1909
+ }
1910
+ function getUrlOrigin(url) {
1911
+ try {
1912
+ return new URL(
1913
+ url,
1914
+ typeof window === "undefined" ? void 0 : window.location.href
1915
+ ).origin;
1916
+ } catch {
1917
+ }
1918
+ }
1919
+ function isLoaderPresent(fileName, injectedFlag) {
1920
+ return Boolean(
1921
+ injectedFlag || document.querySelector(`script[src*="${fileName}"]`)
1922
+ );
1923
+ }
1924
+ function appendLoaderScript({
1925
+ attributes,
1926
+ onLoad,
1927
+ src
1928
+ }) {
1929
+ const script = document.createElement("script");
1930
+ script.async = true;
1931
+ script.src = src;
1932
+ for (const [name, value] of Object.entries(attributes)) {
1933
+ script.setAttribute(name, value);
1934
+ }
1935
+ if (onLoad) {
1936
+ script.addEventListener("load", onLoad);
1937
+ }
1938
+ (document.body ?? document.head ?? document.documentElement).append(script);
1939
+ }
1940
+
1878
1941
  // src/lib/social-proof/storage.ts
1879
1942
  var WIDGET_PAYLOAD_PATH = "/api/social-proof/widget";
1880
1943
  var SOCIAL_PROOF_LOADER_FILE = "saasco-social-proof-loader.js";
@@ -1954,17 +2017,16 @@ async function injectSocialProofLoader({
1954
2017
  return;
1955
2018
  }
1956
2019
  window.__saascoSocialProofInjected = true;
1957
- const script = document.createElement("script");
1958
- script.async = true;
1959
- script.src = normalizeSaascoUrl(scriptUrl);
1960
- script.setAttribute("data-projectId", projectId);
2020
+ const attributes = {
2021
+ "data-projectId": projectId
2022
+ };
1961
2023
  if (baseUrlOverride !== void 0) {
1962
- script.setAttribute(
1963
- "data-base-url",
1964
- normalizeSaascoBaseUrl(baseUrlOverride)
1965
- );
2024
+ attributes["data-base-url"] = normalizeSaascoBaseUrl(baseUrlOverride);
1966
2025
  }
1967
- (document.body ?? document.head ?? document.documentElement).append(script);
2026
+ appendLoaderScript({
2027
+ attributes,
2028
+ src: normalizeSaascoUrl(scriptUrl)
2029
+ });
1968
2030
  }
1969
2031
  async function shouldInjectSocialProof({
1970
2032
  baseUrl,
@@ -1993,23 +2055,12 @@ async function shouldInjectSocialProof({
1993
2055
  writeSession(cacheKey, JSON.stringify({ data: data2, fetchedAt: Date.now() }));
1994
2056
  return isEnabledPayload(data2);
1995
2057
  }
1996
- function isEnabledPayload(data2) {
1997
- return typeof data2 === "object" && data2 !== null && data2.enabled === true;
1998
- }
1999
2058
  function isSocialProofLoaderPresent() {
2000
- return Boolean(
2001
- window.__saascoSocialProofInjected || document.querySelector(`script[src*="${SOCIAL_PROOF_LOADER_FILE}"]`)
2059
+ return isLoaderPresent(
2060
+ SOCIAL_PROOF_LOADER_FILE,
2061
+ window.__saascoSocialProofInjected
2002
2062
  );
2003
2063
  }
2004
- function getUrlOrigin(url) {
2005
- try {
2006
- return new URL(
2007
- url,
2008
- typeof window === "undefined" ? void 0 : window.location.href
2009
- ).origin;
2010
- } catch {
2011
- }
2012
- }
2013
2064
  function isSocialProofQaMode() {
2014
2065
  if (typeof window === "undefined") {
2015
2066
  return false;
@@ -2024,19 +2075,81 @@ function isSocialProofQaMode() {
2024
2075
  }
2025
2076
  }
2026
2077
 
2078
+ // src/lib/support/widgetEnabledCheck.ts
2079
+ var SUPPORT_WIDGET_PATH = "/api/support/widget";
2080
+ var SUPPORT_LOADER_FILE = "saasco-support-loader.js";
2081
+ async function injectSupportLoader({
2082
+ baseUrl: baseUrlOverride,
2083
+ onLoad,
2084
+ placeholder,
2085
+ projectId,
2086
+ scriptUrl
2087
+ }) {
2088
+ if (typeof window === "undefined" || typeof document === "undefined") {
2089
+ return "skipped";
2090
+ }
2091
+ if (isSupportLoaderPresent()) {
2092
+ return "present";
2093
+ }
2094
+ const baseUrl = normalizeSaascoBaseUrl(
2095
+ baseUrlOverride ?? getUrlOrigin(scriptUrl) ?? SAASCO_ORIGIN
2096
+ );
2097
+ const enabled = await shouldInjectSupport({
2098
+ baseUrl,
2099
+ projectId
2100
+ });
2101
+ if (!enabled) {
2102
+ return "skipped";
2103
+ }
2104
+ if (isSupportLoaderPresent()) {
2105
+ return "present";
2106
+ }
2107
+ window.__saascoSupportInjected = true;
2108
+ const attributes = {
2109
+ "data-projectId": projectId
2110
+ };
2111
+ if (baseUrlOverride !== void 0) {
2112
+ attributes["data-base-url"] = normalizeSaascoBaseUrl(baseUrlOverride);
2113
+ }
2114
+ if (placeholder !== void 0) {
2115
+ attributes["data-placeholder"] = placeholder;
2116
+ }
2117
+ appendLoaderScript({
2118
+ attributes,
2119
+ onLoad,
2120
+ src: normalizeSaascoUrl(scriptUrl)
2121
+ });
2122
+ return "injected";
2123
+ }
2124
+ async function shouldInjectSupport({
2125
+ baseUrl,
2126
+ projectId
2127
+ }) {
2128
+ try {
2129
+ const response = await fetch(
2130
+ `${baseUrl}${SUPPORT_WIDGET_PATH}?projectId=${encodeURIComponent(projectId)}`
2131
+ );
2132
+ if (!response.ok) {
2133
+ return false;
2134
+ }
2135
+ return isEnabledPayload(await response.json());
2136
+ } catch {
2137
+ return false;
2138
+ }
2139
+ }
2140
+ function isSupportLoaderPresent() {
2141
+ return isLoaderPresent(SUPPORT_LOADER_FILE, window.__saascoSupportInjected);
2142
+ }
2143
+
2027
2144
  // src/lib/analytics.ts
2028
- function getSupportChatGlobal() {
2029
- return window.SaascoSupportChat;
2145
+ function getSupportGlobal() {
2146
+ return window.SaascoSupport;
2030
2147
  }
2031
2148
  var isBrowser5 = typeof window !== "undefined";
2032
2149
  var isServer5 = !isBrowser5;
2033
2150
  var PREF = "saasco-sdk";
2034
2151
  var SESSION_DURATION = 1e3 * 60 * 30;
2035
2152
  var USER_DURATION = 1e3 * 60 * 60 * 24 * 365;
2036
- var SUPPORT_CHAT_ATTR_MAP = {
2037
- baseUrl: "data-base-url",
2038
- placeholder: "data-placeholder"
2039
- };
2040
2153
  var data = {};
2041
2154
  function getRootDomain(url) {
2042
2155
  if (isServer5) {
@@ -2204,7 +2317,7 @@ function getUserId() {
2204
2317
  function setUserId(userId) {
2205
2318
  storeData(`user-id`, userId ?? void 0, USER_DURATION);
2206
2319
  }
2207
- function buildSupportChatTraits(properties, envelope) {
2320
+ function buildSupportTraits(properties, envelope) {
2208
2321
  const traits = {};
2209
2322
  const add = (key, value) => {
2210
2323
  if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
@@ -2325,7 +2438,7 @@ var Saasco = class {
2325
2438
  * @param config Configuration options.
2326
2439
  * @param config.projectId The unique identifier for the project.
2327
2440
  * @param config.proxy The URL of the proxy server to use, if any.
2328
- * @param config.autoPageTracking Whether to automatically track page views. Default is false.
2441
+ * @param config.autoPageTracking Whether to automatically track page views. Default is true.
2329
2442
  * @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
2330
2443
  * @param config.debug Whether to log debug information. Default is false.
2331
2444
  * @param config.trackUrlParams Whether to track URL parameters. Default is true.
@@ -2374,10 +2487,10 @@ var Saasco = class {
2374
2487
  integrationManager;
2375
2488
  logger;
2376
2489
  // Last identity handed to `identify()`, mirrored into the lazily-loaded
2377
- // support-chat widget so chat conversations attach to a CRM contact. Held
2490
+ // support widget so chat conversations attach to a CRM contact. Held
2378
2491
  // here so an `identify()` that fires before the widget bundle finishes
2379
2492
  // loading is replayed once it does.
2380
- lastSupportChatIdentity = null;
2493
+ lastSupportIdentity = null;
2381
2494
  init() {
2382
2495
  if (this.isInitialized) {
2383
2496
  this.logger.info(
@@ -2401,52 +2514,20 @@ var Saasco = class {
2401
2514
  );
2402
2515
  }
2403
2516
  this.initAutoPageTracking();
2404
- if (this.config.supportChat && this.config.supportChat.enabled !== false) {
2405
- this.injectSupportChat();
2406
- }
2407
- if (this.config.socialProof?.enabled !== false) {
2408
- void this.injectSocialProof();
2409
- }
2517
+ void this.injectSupport();
2518
+ void this.injectSocialProof();
2410
2519
  this.isInitialized = true;
2411
2520
  }
2412
2521
  disableDebug() {
2413
2522
  this.logger.info("Debug mode deactivated.");
2414
2523
  this.config.debug = false;
2524
+ this.logger.setLevel(getLoggerLevel(this.config));
2415
2525
  }
2416
2526
  enableDebug() {
2417
2527
  this.config.debug = true;
2528
+ this.logger.setLevel(getLoggerLevel(this.config));
2418
2529
  this.logger.info("Debug mode activated.");
2419
2530
  }
2420
- /**
2421
- * Enables and injects the support chat widget when it was constructed with
2422
- * `supportChat: { enabled: false }`. Safe to call multiple times.
2423
- */
2424
- enableSupportChat() {
2425
- if (!this.config.supportChat) {
2426
- this.logger.warn(
2427
- "Support chat is not configured; enableSupportChat() was ignored."
2428
- );
2429
- return;
2430
- }
2431
- this.config.supportChat.enabled = true;
2432
- this.injectSupportChat();
2433
- this.pushSupportChatIdentity();
2434
- }
2435
- /**
2436
- * Enables and injects the social-proof widget (e.g. after constructing with
2437
- * `socialProof: { enabled: false }`, or when no `socialProof` block was
2438
- * passed). Injection still runs the server check, so the loader only
2439
- * downloads when the project has the app enabled in the dashboard.
2440
- * Safe to call multiple times — the loader injection is guarded against
2441
- * double-injection.
2442
- */
2443
- enableSocialProof() {
2444
- this.config.socialProof = {
2445
- ...this.config.socialProof,
2446
- enabled: true
2447
- };
2448
- void this.injectSocialProof();
2449
- }
2450
2531
  /**
2451
2532
  * Initialize third-party integrations
2452
2533
  */
@@ -2563,7 +2644,7 @@ var Saasco = class {
2563
2644
  }
2564
2645
  /**
2565
2646
  * The page method lets you record page views on your website
2566
- * This records the page title and path and names the event useing the reserved property "Page Viewed"
2647
+ * This records the page title and path and names the event using the reserved name "Page View"
2567
2648
  *
2568
2649
  * Before implementing this make sure you have disabled the autoPageTracking in the config or you will get duplicate page views
2569
2650
  */
@@ -2609,13 +2690,13 @@ var Saasco = class {
2609
2690
  reset: userIdChangedToNull
2610
2691
  });
2611
2692
  setUserId(distinctId);
2612
- this.updateSupportChatIdentity({
2693
+ this.updateSupportIdentity({
2613
2694
  distinctId: hasId && distinctId !== null ? distinctId : void 0,
2614
2695
  email: typeof properties?.["email"] === "string" ? properties["email"] : void 0,
2615
2696
  // Forward the full scalar payload (browser/track context + identify traits
2616
2697
  // + session envelope) so the widget can bind them via live context without
2617
2698
  // the host mirroring traits into `setStateSnapshot`.
2618
- traits: buildSupportChatTraits(properties, { anonymousId, sessionId })
2699
+ traits: buildSupportTraits(properties, { anonymousId, sessionId })
2619
2700
  });
2620
2701
  this.integrationManager.setContext({
2621
2702
  distinctId,
@@ -2787,51 +2868,25 @@ Request Data: ${JSON.stringify(requestData, null, 2)}`
2787
2868
  return this.integrationManager.getStats();
2788
2869
  }
2789
2870
  /**
2790
- * Lazily injects the support-chat **loader** from the same `/sdk/` origin as
2791
- * the analytics bundle, forwarding the shared `projectId` and the widget
2792
- * config as `data-*` attributes. The loader (no React) injects the
2793
- * cross-origin embed iframe. Deferred (`async`) and guarded against
2794
- * double-injection so re-running `init()` is a no-op.
2871
+ * Lazily injects the support **loader** (server-gated). See
2872
+ * {@link injectSupportLoader}. Replays identity once the script loads.
2795
2873
  */
2796
- injectSupportChat() {
2797
- if (!isBrowser5 || typeof document === "undefined") {
2798
- return;
2799
- }
2800
- const chat = this.config.supportChat;
2801
- if (!chat) {
2802
- return;
2803
- }
2804
- if (window.__saascoSupportChatInjected || document.querySelector('script[src*="saasco-support-chat-loader.js"]')) {
2805
- return;
2806
- }
2807
- const scriptUrl = resolveLoaderScriptUrl(
2808
- "saasco-support-chat-loader.js",
2809
- chat.scriptUrl
2810
- );
2811
- if (!scriptUrl) {
2812
- this.logger.warn(
2813
- "Support chat is enabled but the loader URL could not be resolved. Pass `supportChat.scriptUrl` pointing at your hosted saasco-support-chat-loader.js (or load the analytics SDK from the CDN)."
2814
- );
2874
+ async injectSupport() {
2875
+ if (!isBrowser5) {
2815
2876
  return;
2816
2877
  }
2817
- window.__saascoSupportChatInjected = true;
2818
- const script = document.createElement("script");
2819
- script.async = true;
2820
- script.src = normalizeSaascoUrl(scriptUrl);
2821
- script.setAttribute("data-projectId", this.config.projectId);
2822
- for (const [key, attr] of Object.entries(SUPPORT_CHAT_ATTR_MAP)) {
2823
- const value = chat[key];
2824
- if (value !== void 0) {
2825
- script.setAttribute(
2826
- attr,
2827
- attr === "data-base-url" ? normalizeSaascoBaseUrl(value) : value
2828
- );
2829
- }
2830
- }
2831
- script.addEventListener("load", () => {
2832
- this.pushSupportChatIdentity();
2878
+ const { support } = this.config;
2879
+ const scriptUrl = resolveLoaderScriptUrl("saasco-support-loader.js", support?.scriptUrl) ?? `${SAASCO_ORIGIN}/sdk/saasco-support-loader.js`;
2880
+ const result = await injectSupportLoader({
2881
+ baseUrl: support?.baseUrl,
2882
+ onLoad: () => this.pushSupportIdentity(),
2883
+ placeholder: support?.placeholder,
2884
+ projectId: this.config.projectId,
2885
+ scriptUrl
2833
2886
  });
2834
- (document.body ?? document.head ?? document.documentElement).append(script);
2887
+ if (result === "present") {
2888
+ this.pushSupportIdentity();
2889
+ }
2835
2890
  }
2836
2891
  /**
2837
2892
  * Lazily injects the social-proof loader (server-gated). See
@@ -2853,42 +2908,38 @@ Request Data: ${JSON.stringify(requestData, null, 2)}`
2853
2908
  });
2854
2909
  }
2855
2910
  /**
2856
- * Records the latest CRM identity and forwards it to the support-chat widget.
2857
- * No-op when support chat isn't enabled.
2911
+ * Records the latest CRM identity and forwards it to the support widget.
2858
2912
  */
2859
- updateSupportChatIdentity(identity) {
2860
- if (!isBrowser5 || !this.config.supportChat) {
2913
+ updateSupportIdentity(identity) {
2914
+ if (!isBrowser5) {
2861
2915
  return;
2862
2916
  }
2863
2917
  const hasTraits = identity.traits && Object.keys(identity.traits).length > 0;
2864
- this.lastSupportChatIdentity = identity.distinctId || identity.email || hasTraits ? identity : null;
2865
- if (this.config.supportChat.enabled === false) {
2866
- return;
2867
- }
2868
- this.pushSupportChatIdentity();
2918
+ this.lastSupportIdentity = identity.distinctId || identity.email || hasTraits ? identity : null;
2919
+ this.pushSupportIdentity();
2869
2920
  }
2870
2921
  /**
2871
- * Pushes the current identity onto `window.SaascoSupportChat.identify`. The
2922
+ * Pushes the current identity onto `window.SaascoSupport.identify`. The
2872
2923
  * widget bundle publishes that global asynchronously, so this short-polls for
2873
- * it (same approach as tool registration); the load handler also calls this,
2874
- * so a fresh page load with a stored distinctId still identifies the chat.
2924
+ * it (same approach as tool registration). `injectSupport` calls this on
2925
+ * script load (or immediately when the loader is already present).
2875
2926
  */
2876
- pushSupportChatIdentity() {
2877
- if (!isBrowser5 || !this.config.supportChat || this.config.supportChat.enabled === false) {
2927
+ pushSupportIdentity() {
2928
+ if (!isBrowser5) {
2878
2929
  return;
2879
2930
  }
2880
- const identity = this.lastSupportChatIdentity ?? {
2931
+ const identity = this.lastSupportIdentity ?? {
2881
2932
  distinctId: getUserId() ?? void 0,
2882
2933
  // Anonymous (pre-identify) page load: still forward browser/track context
2883
2934
  // and the session envelope so live context has them from the first turn.
2884
- traits: buildSupportChatTraits(void 0, {
2935
+ traits: buildSupportTraits(void 0, {
2885
2936
  anonymousId: getAnonymousId() ?? void 0,
2886
2937
  sessionId: getSessionId() ?? void 0
2887
2938
  })
2888
2939
  };
2889
2940
  let attempts = 0;
2890
2941
  const tryPush = () => {
2891
- const api = getSupportChatGlobal();
2942
+ const api = getSupportGlobal();
2892
2943
  if (!api) {
2893
2944
  attempts += 1;
2894
2945
  if (attempts > 40) {
@@ -2901,7 +2952,7 @@ Request Data: ${JSON.stringify(requestData, null, 2)}`
2901
2952
  api.identify(identity);
2902
2953
  } catch (error) {
2903
2954
  this.logger.warn(
2904
- "Failed to forward identity to the support chat widget:",
2955
+ "Failed to forward identity to the support widget:",
2905
2956
  error
2906
2957
  );
2907
2958
  }
@@ -3000,9 +3051,9 @@ function getLoggerLevel(config) {
3000
3051
  return effectiveDebugVerbose ? 3 /* DEBUG */ : effectiveDebug ? 2 /* INFO */ : 0 /* ERROR */;
3001
3052
  }
3002
3053
 
3003
- // src/lib/tracking/types.ts
3054
+ // ../shared/src/lib/types/browserContext.ts
3004
3055
  var import_zod = require("zod");
3005
- var browserContextSchema = import_zod.z.object({
3056
+ var BrowserContextSchema = import_zod.z.object({
3006
3057
  $href: import_zod.z.string(),
3007
3058
  $locale: import_zod.z.string(),
3008
3059
  $location: import_zod.z.string(),
@@ -3029,9 +3080,9 @@ var browserContextSchema = import_zod.z.object({
3029
3080
  });
3030
3081
  // Annotate the CommonJS export names for ESM import in node:
3031
3082
  0 && (module.exports = {
3083
+ BrowserContextSchema,
3032
3084
  IntegrationManager,
3033
3085
  Saasco,
3034
- browserContextSchema,
3035
3086
  createFacebookPixelIntegration,
3036
3087
  createPinterestTagIntegration,
3037
3088
  createTikTokPixelIntegration,