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.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { isValid, parse } from "psl";
3
3
 
4
4
  // package.json
5
- var version = "0.2.3";
5
+ var version = "0.2.5";
6
6
 
7
7
  // src/lib/timezones.ts
8
8
  var timezones = {
@@ -451,6 +451,9 @@ var AnalyticsLogger = class {
451
451
  constructor(config) {
452
452
  this.config = config;
453
453
  }
454
+ setLevel(level) {
455
+ this.config.level = level;
456
+ }
454
457
  debug(...args) {
455
458
  if (this.config.level < 3 /* DEBUG */) {
456
459
  return;
@@ -531,9 +534,34 @@ function getIntegrationLoggerLevel(integrationName, debug) {
531
534
  }
532
535
 
533
536
  // src/lib/utils/uuid.ts
534
- import { v4 } from "@lukeed/uuid";
537
+ var HEX = [];
538
+ for (let i = 0; i < 256; i++) {
539
+ HEX[i] = (i + 256).toString(16).slice(1);
540
+ }
535
541
  function uuid() {
536
- return v4();
542
+ const bytes = randomBytes(16);
543
+ bytes[6] = bytes[6] & 15 | 64;
544
+ bytes[8] = bytes[8] & 63 | 128;
545
+ let out = "";
546
+ for (let i = 0; i < 16; i++) {
547
+ out += HEX[bytes[i]];
548
+ if (i === 3 || i === 5 || i === 7 || i === 9) {
549
+ out += "-";
550
+ }
551
+ }
552
+ return out;
553
+ }
554
+ function randomBytes(length) {
555
+ const bytes = new Uint8Array(length);
556
+ const webCrypto = globalThis.crypto;
557
+ if (typeof webCrypto?.getRandomValues === "function") {
558
+ webCrypto.getRandomValues(bytes);
559
+ return bytes;
560
+ }
561
+ for (let i = 0; i < length; i++) {
562
+ bytes[i] = Math.random() * 256 | 0;
563
+ }
564
+ return bytes;
537
565
  }
538
566
 
539
567
  // src/lib/getBrowserContext.ts
@@ -1839,6 +1867,41 @@ function normalizeSaascoUrl(url) {
1839
1867
  return parsed.toString();
1840
1868
  }
1841
1869
 
1870
+ // src/lib/widgetLoader.ts
1871
+ function isEnabledPayload(data2) {
1872
+ return typeof data2 === "object" && data2 !== null && "enabled" in data2 && data2.enabled === true;
1873
+ }
1874
+ function getUrlOrigin(url) {
1875
+ try {
1876
+ return new URL(
1877
+ url,
1878
+ typeof window === "undefined" ? void 0 : window.location.href
1879
+ ).origin;
1880
+ } catch {
1881
+ }
1882
+ }
1883
+ function isLoaderPresent(fileName, injectedFlag) {
1884
+ return Boolean(
1885
+ injectedFlag || document.querySelector(`script[src*="${fileName}"]`)
1886
+ );
1887
+ }
1888
+ function appendLoaderScript({
1889
+ attributes,
1890
+ onLoad,
1891
+ src
1892
+ }) {
1893
+ const script = document.createElement("script");
1894
+ script.async = true;
1895
+ script.src = src;
1896
+ for (const [name, value] of Object.entries(attributes)) {
1897
+ script.setAttribute(name, value);
1898
+ }
1899
+ if (onLoad) {
1900
+ script.addEventListener("load", onLoad);
1901
+ }
1902
+ (document.body ?? document.head ?? document.documentElement).append(script);
1903
+ }
1904
+
1842
1905
  // src/lib/social-proof/storage.ts
1843
1906
  var WIDGET_PAYLOAD_PATH = "/api/social-proof/widget";
1844
1907
  var SOCIAL_PROOF_LOADER_FILE = "saasco-social-proof-loader.js";
@@ -1918,17 +1981,16 @@ async function injectSocialProofLoader({
1918
1981
  return;
1919
1982
  }
1920
1983
  window.__saascoSocialProofInjected = true;
1921
- const script = document.createElement("script");
1922
- script.async = true;
1923
- script.src = normalizeSaascoUrl(scriptUrl);
1924
- script.setAttribute("data-projectId", projectId);
1984
+ const attributes = {
1985
+ "data-projectId": projectId
1986
+ };
1925
1987
  if (baseUrlOverride !== void 0) {
1926
- script.setAttribute(
1927
- "data-base-url",
1928
- normalizeSaascoBaseUrl(baseUrlOverride)
1929
- );
1988
+ attributes["data-base-url"] = normalizeSaascoBaseUrl(baseUrlOverride);
1930
1989
  }
1931
- (document.body ?? document.head ?? document.documentElement).append(script);
1990
+ appendLoaderScript({
1991
+ attributes,
1992
+ src: normalizeSaascoUrl(scriptUrl)
1993
+ });
1932
1994
  }
1933
1995
  async function shouldInjectSocialProof({
1934
1996
  baseUrl,
@@ -1957,23 +2019,12 @@ async function shouldInjectSocialProof({
1957
2019
  writeSession(cacheKey, JSON.stringify({ data: data2, fetchedAt: Date.now() }));
1958
2020
  return isEnabledPayload(data2);
1959
2021
  }
1960
- function isEnabledPayload(data2) {
1961
- return typeof data2 === "object" && data2 !== null && data2.enabled === true;
1962
- }
1963
2022
  function isSocialProofLoaderPresent() {
1964
- return Boolean(
1965
- window.__saascoSocialProofInjected || document.querySelector(`script[src*="${SOCIAL_PROOF_LOADER_FILE}"]`)
2023
+ return isLoaderPresent(
2024
+ SOCIAL_PROOF_LOADER_FILE,
2025
+ window.__saascoSocialProofInjected
1966
2026
  );
1967
2027
  }
1968
- function getUrlOrigin(url) {
1969
- try {
1970
- return new URL(
1971
- url,
1972
- typeof window === "undefined" ? void 0 : window.location.href
1973
- ).origin;
1974
- } catch {
1975
- }
1976
- }
1977
2028
  function isSocialProofQaMode() {
1978
2029
  if (typeof window === "undefined") {
1979
2030
  return false;
@@ -1988,19 +2039,81 @@ function isSocialProofQaMode() {
1988
2039
  }
1989
2040
  }
1990
2041
 
2042
+ // src/lib/support/widgetEnabledCheck.ts
2043
+ var SUPPORT_WIDGET_PATH = "/api/support/widget";
2044
+ var SUPPORT_LOADER_FILE = "saasco-support-loader.js";
2045
+ async function injectSupportLoader({
2046
+ baseUrl: baseUrlOverride,
2047
+ onLoad,
2048
+ placeholder,
2049
+ projectId,
2050
+ scriptUrl
2051
+ }) {
2052
+ if (typeof window === "undefined" || typeof document === "undefined") {
2053
+ return "skipped";
2054
+ }
2055
+ if (isSupportLoaderPresent()) {
2056
+ return "present";
2057
+ }
2058
+ const baseUrl = normalizeSaascoBaseUrl(
2059
+ baseUrlOverride ?? getUrlOrigin(scriptUrl) ?? SAASCO_ORIGIN
2060
+ );
2061
+ const enabled = await shouldInjectSupport({
2062
+ baseUrl,
2063
+ projectId
2064
+ });
2065
+ if (!enabled) {
2066
+ return "skipped";
2067
+ }
2068
+ if (isSupportLoaderPresent()) {
2069
+ return "present";
2070
+ }
2071
+ window.__saascoSupportInjected = true;
2072
+ const attributes = {
2073
+ "data-projectId": projectId
2074
+ };
2075
+ if (baseUrlOverride !== void 0) {
2076
+ attributes["data-base-url"] = normalizeSaascoBaseUrl(baseUrlOverride);
2077
+ }
2078
+ if (placeholder !== void 0) {
2079
+ attributes["data-placeholder"] = placeholder;
2080
+ }
2081
+ appendLoaderScript({
2082
+ attributes,
2083
+ onLoad,
2084
+ src: normalizeSaascoUrl(scriptUrl)
2085
+ });
2086
+ return "injected";
2087
+ }
2088
+ async function shouldInjectSupport({
2089
+ baseUrl,
2090
+ projectId
2091
+ }) {
2092
+ try {
2093
+ const response = await fetch(
2094
+ `${baseUrl}${SUPPORT_WIDGET_PATH}?projectId=${encodeURIComponent(projectId)}`
2095
+ );
2096
+ if (!response.ok) {
2097
+ return false;
2098
+ }
2099
+ return isEnabledPayload(await response.json());
2100
+ } catch {
2101
+ return false;
2102
+ }
2103
+ }
2104
+ function isSupportLoaderPresent() {
2105
+ return isLoaderPresent(SUPPORT_LOADER_FILE, window.__saascoSupportInjected);
2106
+ }
2107
+
1991
2108
  // src/lib/analytics.ts
1992
- function getSupportChatGlobal() {
1993
- return window.SaascoSupportChat;
2109
+ function getSupportGlobal() {
2110
+ return window.SaascoSupport;
1994
2111
  }
1995
2112
  var isBrowser5 = typeof window !== "undefined";
1996
2113
  var isServer5 = !isBrowser5;
1997
2114
  var PREF = "saasco-sdk";
1998
2115
  var SESSION_DURATION = 1e3 * 60 * 30;
1999
2116
  var USER_DURATION = 1e3 * 60 * 60 * 24 * 365;
2000
- var SUPPORT_CHAT_ATTR_MAP = {
2001
- baseUrl: "data-base-url",
2002
- placeholder: "data-placeholder"
2003
- };
2004
2117
  var data = {};
2005
2118
  function getRootDomain(url) {
2006
2119
  if (isServer5) {
@@ -2168,7 +2281,7 @@ function getUserId() {
2168
2281
  function setUserId(userId) {
2169
2282
  storeData(`user-id`, userId ?? void 0, USER_DURATION);
2170
2283
  }
2171
- function buildSupportChatTraits(properties, envelope) {
2284
+ function buildSupportTraits(properties, envelope) {
2172
2285
  const traits = {};
2173
2286
  const add = (key, value) => {
2174
2287
  if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
@@ -2289,7 +2402,7 @@ var Saasco = class {
2289
2402
  * @param config Configuration options.
2290
2403
  * @param config.projectId The unique identifier for the project.
2291
2404
  * @param config.proxy The URL of the proxy server to use, if any.
2292
- * @param config.autoPageTracking Whether to automatically track page views. Default is false.
2405
+ * @param config.autoPageTracking Whether to automatically track page views. Default is true.
2293
2406
  * @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
2294
2407
  * @param config.debug Whether to log debug information. Default is false.
2295
2408
  * @param config.trackUrlParams Whether to track URL parameters. Default is true.
@@ -2338,10 +2451,10 @@ var Saasco = class {
2338
2451
  integrationManager;
2339
2452
  logger;
2340
2453
  // Last identity handed to `identify()`, mirrored into the lazily-loaded
2341
- // support-chat widget so chat conversations attach to a CRM contact. Held
2454
+ // support widget so chat conversations attach to a CRM contact. Held
2342
2455
  // here so an `identify()` that fires before the widget bundle finishes
2343
2456
  // loading is replayed once it does.
2344
- lastSupportChatIdentity = null;
2457
+ lastSupportIdentity = null;
2345
2458
  init() {
2346
2459
  if (this.isInitialized) {
2347
2460
  this.logger.info(
@@ -2365,52 +2478,20 @@ var Saasco = class {
2365
2478
  );
2366
2479
  }
2367
2480
  this.initAutoPageTracking();
2368
- if (this.config.supportChat && this.config.supportChat.enabled !== false) {
2369
- this.injectSupportChat();
2370
- }
2371
- if (this.config.socialProof?.enabled !== false) {
2372
- void this.injectSocialProof();
2373
- }
2481
+ void this.injectSupport();
2482
+ void this.injectSocialProof();
2374
2483
  this.isInitialized = true;
2375
2484
  }
2376
2485
  disableDebug() {
2377
2486
  this.logger.info("Debug mode deactivated.");
2378
2487
  this.config.debug = false;
2488
+ this.logger.setLevel(getLoggerLevel(this.config));
2379
2489
  }
2380
2490
  enableDebug() {
2381
2491
  this.config.debug = true;
2492
+ this.logger.setLevel(getLoggerLevel(this.config));
2382
2493
  this.logger.info("Debug mode activated.");
2383
2494
  }
2384
- /**
2385
- * Enables and injects the support chat widget when it was constructed with
2386
- * `supportChat: { enabled: false }`. Safe to call multiple times.
2387
- */
2388
- enableSupportChat() {
2389
- if (!this.config.supportChat) {
2390
- this.logger.warn(
2391
- "Support chat is not configured; enableSupportChat() was ignored."
2392
- );
2393
- return;
2394
- }
2395
- this.config.supportChat.enabled = true;
2396
- this.injectSupportChat();
2397
- this.pushSupportChatIdentity();
2398
- }
2399
- /**
2400
- * Enables and injects the social-proof widget (e.g. after constructing with
2401
- * `socialProof: { enabled: false }`, or when no `socialProof` block was
2402
- * passed). Injection still runs the server check, so the loader only
2403
- * downloads when the project has the app enabled in the dashboard.
2404
- * Safe to call multiple times — the loader injection is guarded against
2405
- * double-injection.
2406
- */
2407
- enableSocialProof() {
2408
- this.config.socialProof = {
2409
- ...this.config.socialProof,
2410
- enabled: true
2411
- };
2412
- void this.injectSocialProof();
2413
- }
2414
2495
  /**
2415
2496
  * Initialize third-party integrations
2416
2497
  */
@@ -2527,7 +2608,7 @@ var Saasco = class {
2527
2608
  }
2528
2609
  /**
2529
2610
  * The page method lets you record page views on your website
2530
- * This records the page title and path and names the event useing the reserved property "Page Viewed"
2611
+ * This records the page title and path and names the event using the reserved name "Page View"
2531
2612
  *
2532
2613
  * Before implementing this make sure you have disabled the autoPageTracking in the config or you will get duplicate page views
2533
2614
  */
@@ -2573,13 +2654,13 @@ var Saasco = class {
2573
2654
  reset: userIdChangedToNull
2574
2655
  });
2575
2656
  setUserId(distinctId);
2576
- this.updateSupportChatIdentity({
2657
+ this.updateSupportIdentity({
2577
2658
  distinctId: hasId && distinctId !== null ? distinctId : void 0,
2578
2659
  email: typeof properties?.["email"] === "string" ? properties["email"] : void 0,
2579
2660
  // Forward the full scalar payload (browser/track context + identify traits
2580
2661
  // + session envelope) so the widget can bind them via live context without
2581
2662
  // the host mirroring traits into `setStateSnapshot`.
2582
- traits: buildSupportChatTraits(properties, { anonymousId, sessionId })
2663
+ traits: buildSupportTraits(properties, { anonymousId, sessionId })
2583
2664
  });
2584
2665
  this.integrationManager.setContext({
2585
2666
  distinctId,
@@ -2751,51 +2832,25 @@ Request Data: ${JSON.stringify(requestData, null, 2)}`
2751
2832
  return this.integrationManager.getStats();
2752
2833
  }
2753
2834
  /**
2754
- * Lazily injects the support-chat **loader** from the same `/sdk/` origin as
2755
- * the analytics bundle, forwarding the shared `projectId` and the widget
2756
- * config as `data-*` attributes. The loader (no React) injects the
2757
- * cross-origin embed iframe. Deferred (`async`) and guarded against
2758
- * double-injection so re-running `init()` is a no-op.
2835
+ * Lazily injects the support **loader** (server-gated). See
2836
+ * {@link injectSupportLoader}. Replays identity once the script loads.
2759
2837
  */
2760
- injectSupportChat() {
2761
- if (!isBrowser5 || typeof document === "undefined") {
2762
- return;
2763
- }
2764
- const chat = this.config.supportChat;
2765
- if (!chat) {
2766
- return;
2767
- }
2768
- if (window.__saascoSupportChatInjected || document.querySelector('script[src*="saasco-support-chat-loader.js"]')) {
2769
- return;
2770
- }
2771
- const scriptUrl = resolveLoaderScriptUrl(
2772
- "saasco-support-chat-loader.js",
2773
- chat.scriptUrl
2774
- );
2775
- if (!scriptUrl) {
2776
- this.logger.warn(
2777
- "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)."
2778
- );
2838
+ async injectSupport() {
2839
+ if (!isBrowser5) {
2779
2840
  return;
2780
2841
  }
2781
- window.__saascoSupportChatInjected = true;
2782
- const script = document.createElement("script");
2783
- script.async = true;
2784
- script.src = normalizeSaascoUrl(scriptUrl);
2785
- script.setAttribute("data-projectId", this.config.projectId);
2786
- for (const [key, attr] of Object.entries(SUPPORT_CHAT_ATTR_MAP)) {
2787
- const value = chat[key];
2788
- if (value !== void 0) {
2789
- script.setAttribute(
2790
- attr,
2791
- attr === "data-base-url" ? normalizeSaascoBaseUrl(value) : value
2792
- );
2793
- }
2794
- }
2795
- script.addEventListener("load", () => {
2796
- this.pushSupportChatIdentity();
2842
+ const { support } = this.config;
2843
+ const scriptUrl = resolveLoaderScriptUrl("saasco-support-loader.js", support?.scriptUrl) ?? `${SAASCO_ORIGIN}/sdk/saasco-support-loader.js`;
2844
+ const result = await injectSupportLoader({
2845
+ baseUrl: support?.baseUrl,
2846
+ onLoad: () => this.pushSupportIdentity(),
2847
+ placeholder: support?.placeholder,
2848
+ projectId: this.config.projectId,
2849
+ scriptUrl
2797
2850
  });
2798
- (document.body ?? document.head ?? document.documentElement).append(script);
2851
+ if (result === "present") {
2852
+ this.pushSupportIdentity();
2853
+ }
2799
2854
  }
2800
2855
  /**
2801
2856
  * Lazily injects the social-proof loader (server-gated). See
@@ -2817,42 +2872,38 @@ Request Data: ${JSON.stringify(requestData, null, 2)}`
2817
2872
  });
2818
2873
  }
2819
2874
  /**
2820
- * Records the latest CRM identity and forwards it to the support-chat widget.
2821
- * No-op when support chat isn't enabled.
2875
+ * Records the latest CRM identity and forwards it to the support widget.
2822
2876
  */
2823
- updateSupportChatIdentity(identity) {
2824
- if (!isBrowser5 || !this.config.supportChat) {
2877
+ updateSupportIdentity(identity) {
2878
+ if (!isBrowser5) {
2825
2879
  return;
2826
2880
  }
2827
2881
  const hasTraits = identity.traits && Object.keys(identity.traits).length > 0;
2828
- this.lastSupportChatIdentity = identity.distinctId || identity.email || hasTraits ? identity : null;
2829
- if (this.config.supportChat.enabled === false) {
2830
- return;
2831
- }
2832
- this.pushSupportChatIdentity();
2882
+ this.lastSupportIdentity = identity.distinctId || identity.email || hasTraits ? identity : null;
2883
+ this.pushSupportIdentity();
2833
2884
  }
2834
2885
  /**
2835
- * Pushes the current identity onto `window.SaascoSupportChat.identify`. The
2886
+ * Pushes the current identity onto `window.SaascoSupport.identify`. The
2836
2887
  * widget bundle publishes that global asynchronously, so this short-polls for
2837
- * it (same approach as tool registration); the load handler also calls this,
2838
- * so a fresh page load with a stored distinctId still identifies the chat.
2888
+ * it (same approach as tool registration). `injectSupport` calls this on
2889
+ * script load (or immediately when the loader is already present).
2839
2890
  */
2840
- pushSupportChatIdentity() {
2841
- if (!isBrowser5 || !this.config.supportChat || this.config.supportChat.enabled === false) {
2891
+ pushSupportIdentity() {
2892
+ if (!isBrowser5) {
2842
2893
  return;
2843
2894
  }
2844
- const identity = this.lastSupportChatIdentity ?? {
2895
+ const identity = this.lastSupportIdentity ?? {
2845
2896
  distinctId: getUserId() ?? void 0,
2846
2897
  // Anonymous (pre-identify) page load: still forward browser/track context
2847
2898
  // and the session envelope so live context has them from the first turn.
2848
- traits: buildSupportChatTraits(void 0, {
2899
+ traits: buildSupportTraits(void 0, {
2849
2900
  anonymousId: getAnonymousId() ?? void 0,
2850
2901
  sessionId: getSessionId() ?? void 0
2851
2902
  })
2852
2903
  };
2853
2904
  let attempts = 0;
2854
2905
  const tryPush = () => {
2855
- const api = getSupportChatGlobal();
2906
+ const api = getSupportGlobal();
2856
2907
  if (!api) {
2857
2908
  attempts += 1;
2858
2909
  if (attempts > 40) {
@@ -2865,7 +2916,7 @@ Request Data: ${JSON.stringify(requestData, null, 2)}`
2865
2916
  api.identify(identity);
2866
2917
  } catch (error) {
2867
2918
  this.logger.warn(
2868
- "Failed to forward identity to the support chat widget:",
2919
+ "Failed to forward identity to the support widget:",
2869
2920
  error
2870
2921
  );
2871
2922
  }
@@ -2964,9 +3015,9 @@ function getLoggerLevel(config) {
2964
3015
  return effectiveDebugVerbose ? 3 /* DEBUG */ : effectiveDebug ? 2 /* INFO */ : 0 /* ERROR */;
2965
3016
  }
2966
3017
 
2967
- // src/lib/tracking/types.ts
3018
+ // ../shared/src/lib/types/browserContext.ts
2968
3019
  import { z } from "zod";
2969
- var browserContextSchema = z.object({
3020
+ var BrowserContextSchema = z.object({
2970
3021
  $href: z.string(),
2971
3022
  $locale: z.string(),
2972
3023
  $location: z.string(),
@@ -2992,9 +3043,9 @@ var browserContextSchema = z.object({
2992
3043
  $utmTerm: z.string().nullable()
2993
3044
  });
2994
3045
  export {
3046
+ BrowserContextSchema,
2995
3047
  IntegrationManager,
2996
3048
  Saasco,
2997
- browserContextSchema,
2998
3049
  createFacebookPixelIntegration,
2999
3050
  createPinterestTagIntegration,
3000
3051
  createTikTokPixelIntegration,