saasco-sdk 0.2.4 → 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.4";
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,6 +2039,72 @@ 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
2109
  function getSupportGlobal() {
1993
2110
  return window.SaascoSupport;
@@ -1997,10 +2114,6 @@ 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_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) {
@@ -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.
@@ -2365,52 +2478,20 @@ var Saasco = class {
2365
2478
  );
2366
2479
  }
2367
2480
  this.initAutoPageTracking();
2368
- if (this.config.support && this.config.support.enabled !== false) {
2369
- this.injectSupport();
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 widget when it was constructed with
2386
- * `support: { enabled: false }`. Safe to call multiple times.
2387
- */
2388
- enableSupport() {
2389
- if (!this.config.support) {
2390
- this.logger.warn(
2391
- "Support is not configured; enableSupport() was ignored."
2392
- );
2393
- return;
2394
- }
2395
- this.config.support.enabled = true;
2396
- this.injectSupport();
2397
- this.pushSupportIdentity();
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
  */
@@ -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 **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
- injectSupport() {
2761
- if (!isBrowser5 || typeof document === "undefined") {
2762
- return;
2763
- }
2764
- const chat = this.config.support;
2765
- if (!chat) {
2766
- return;
2767
- }
2768
- if (window.__saascoSupportInjected || document.querySelector('script[src*="saasco-support-loader.js"]')) {
2769
- return;
2770
- }
2771
- const scriptUrl = resolveLoaderScriptUrl(
2772
- "saasco-support-loader.js",
2773
- chat.scriptUrl
2774
- );
2775
- if (!scriptUrl) {
2776
- this.logger.warn(
2777
- "Support is enabled but the loader URL could not be resolved. Pass `support.scriptUrl` pointing at your hosted saasco-support-loader.js (or load the analytics SDK from the CDN)."
2778
- );
2838
+ async injectSupport() {
2839
+ if (!isBrowser5) {
2779
2840
  return;
2780
2841
  }
2781
- window.__saascoSupportInjected = 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_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.pushSupportIdentity();
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
@@ -2818,27 +2873,23 @@ Request Data: ${JSON.stringify(requestData, null, 2)}`
2818
2873
  }
2819
2874
  /**
2820
2875
  * Records the latest CRM identity and forwards it to the support widget.
2821
- * No-op when support isn't enabled.
2822
2876
  */
2823
2877
  updateSupportIdentity(identity) {
2824
- if (!isBrowser5 || !this.config.support) {
2878
+ if (!isBrowser5) {
2825
2879
  return;
2826
2880
  }
2827
2881
  const hasTraits = identity.traits && Object.keys(identity.traits).length > 0;
2828
2882
  this.lastSupportIdentity = identity.distinctId || identity.email || hasTraits ? identity : null;
2829
- if (this.config.support.enabled === false) {
2830
- return;
2831
- }
2832
2883
  this.pushSupportIdentity();
2833
2884
  }
2834
2885
  /**
2835
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
2891
  pushSupportIdentity() {
2841
- if (!isBrowser5 || !this.config.support || this.config.support.enabled === false) {
2892
+ if (!isBrowser5) {
2842
2893
  return;
2843
2894
  }
2844
2895
  const identity = this.lastSupportIdentity ?? {