sitepong 0.1.13 → 0.2.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.
@@ -2,6 +2,8 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
+ var webVitals = require('web-vitals');
6
+
5
7
  // src/platforms/web/environment.ts
6
8
  function makeLocalStorageAdapter() {
7
9
  try {
@@ -1866,11 +1868,70 @@ var RemoteConfigManager = class {
1866
1868
  }
1867
1869
  }
1868
1870
  };
1869
-
1870
- // src/performance/manager.ts
1871
1871
  var DEFAULT_ENDPOINT3 = "https://ingest.sitepong.com";
1872
1872
  var DEFAULT_FLUSH_INTERVAL2 = 1e4;
1873
1873
  var MAX_FLUSH_FAILURES2 = 3;
1874
+ var MAX_RESOURCES_PER_FLUSH = 200;
1875
+ var PAGE_TIMING_POLL_INTERVAL = 200;
1876
+ var PAGE_TIMING_TIMEOUT = 3e4;
1877
+ function getPaintBlocks(resources) {
1878
+ const paintBlocks = [];
1879
+ const elements = document.getElementsByTagName("*");
1880
+ const styleURL = /url\(("[^"]*"|'[^']*'|[^)]*)\)/i;
1881
+ for (let i = 0; i < elements.length; i++) {
1882
+ const element = elements[i];
1883
+ let src = "";
1884
+ if (element.tagName === "IMG") {
1885
+ src = element.currentSrc || element.src;
1886
+ }
1887
+ if (!src) {
1888
+ const backgroundImage = getComputedStyle(element).getPropertyValue("background-image");
1889
+ if (backgroundImage) {
1890
+ const matches = styleURL.exec(backgroundImage);
1891
+ if (matches !== null) {
1892
+ src = matches[1];
1893
+ if (src.startsWith('"') || src.startsWith("'")) {
1894
+ src = src.substr(1, src.length - 2);
1895
+ }
1896
+ }
1897
+ }
1898
+ }
1899
+ if (!src) continue;
1900
+ const time = src.substr(0, 10) === "data:image" ? 0 : resources[src];
1901
+ if (time === void 0) continue;
1902
+ const rect = element.getBoundingClientRect();
1903
+ const top = Math.max(rect.top, 0);
1904
+ const left = Math.max(rect.left, 0);
1905
+ const bottom = Math.min(
1906
+ rect.bottom,
1907
+ window.innerHeight || document.documentElement && document.documentElement.clientHeight || 0
1908
+ );
1909
+ const right = Math.min(
1910
+ rect.right,
1911
+ window.innerWidth || document.documentElement && document.documentElement.clientWidth || 0
1912
+ );
1913
+ if (bottom <= top || right <= left) continue;
1914
+ const area = (bottom - top) * (right - left);
1915
+ paintBlocks.push({ time, area });
1916
+ }
1917
+ return paintBlocks;
1918
+ }
1919
+ function calculateSpeedIndex(firstContentfulPaint, paintBlocks) {
1920
+ let a = Math.max(
1921
+ document.documentElement && document.documentElement.clientWidth || 0,
1922
+ window.innerWidth || 0
1923
+ ) * Math.max(
1924
+ document.documentElement && document.documentElement.clientHeight || 0,
1925
+ window.innerHeight || 0
1926
+ ) / 10;
1927
+ let s = a * firstContentfulPaint;
1928
+ for (let i = 0; i < paintBlocks.length; i++) {
1929
+ const { time, area } = paintBlocks[i];
1930
+ a += area;
1931
+ s += area * (time > firstContentfulPaint ? time : firstContentfulPaint);
1932
+ }
1933
+ return a === 0 ? 0 : s / a;
1934
+ }
1874
1935
  var PerformanceManager = class {
1875
1936
  constructor(config) {
1876
1937
  this.metrics = [];
@@ -1879,12 +1940,22 @@ var PerformanceManager = class {
1879
1940
  this.disabled = false;
1880
1941
  this.vitals = {};
1881
1942
  this.activeTransactions = /* @__PURE__ */ new Map();
1943
+ // Resource timing state for Speed Index
1944
+ this.resourceObserver = null;
1945
+ this.resourceTimeMap = {};
1946
+ this.resourceCount = 0;
1947
+ // Page timing polling timers
1948
+ this.pageLoadTimer = null;
1949
+ this.pageRenderTimer = null;
1882
1950
  this.config = {
1883
1951
  endpoint: DEFAULT_ENDPOINT3,
1884
1952
  enabled: true,
1885
1953
  webVitals: true,
1886
1954
  navigationTiming: true,
1887
- resourceTiming: false,
1955
+ resourceTiming: true,
1956
+ capturePageLoadTimings: true,
1957
+ capturePageRenderTimings: true,
1958
+ excludedResourceUrls: [],
1888
1959
  flushInterval: DEFAULT_FLUSH_INTERVAL2,
1889
1960
  sampleRate: 1,
1890
1961
  debug: false,
@@ -1895,18 +1966,250 @@ var PerformanceManager = class {
1895
1966
  init() {
1896
1967
  if (!this.config.enabled || !this.sampled) return;
1897
1968
  if (typeof window === "undefined" || typeof window.addEventListener !== "function") return;
1898
- if (this.config.webVitals) {
1899
- this.observeWebVitals();
1969
+ if (this.config.webVitals !== false) {
1970
+ this.initWebVitals();
1900
1971
  }
1901
- if (this.config.navigationTiming) {
1902
- this.collectNavigationTiming();
1972
+ if (this.config.resourceTiming !== false) {
1973
+ this.initResourceTiming();
1974
+ }
1975
+ if (this.config.capturePageLoadTimings !== false) {
1976
+ this.initPageLoadTiming();
1977
+ }
1978
+ if (this.config.capturePageRenderTimings !== false) {
1979
+ this.initPageRenderTiming();
1903
1980
  }
1904
1981
  this.startFlushTimer();
1905
1982
  this.log("Performance monitoring initialized");
1906
1983
  }
1907
- /**
1908
- * Start a custom transaction for measuring operations
1909
- */
1984
+ // ---- Web Vitals (via web-vitals library) ----
1985
+ initWebVitals() {
1986
+ webVitals.onCLS((m) => {
1987
+ this.vitals.cls = m.value;
1988
+ this.reportVital("CLS", m.value, "score");
1989
+ });
1990
+ webVitals.onINP((m) => {
1991
+ this.vitals.inp = m.value;
1992
+ this.reportVital("INP", m.value);
1993
+ });
1994
+ webVitals.onLCP((m) => {
1995
+ this.vitals.lcp = m.value;
1996
+ this.reportVital("LCP", m.value);
1997
+ });
1998
+ webVitals.onTTFB((m) => {
1999
+ this.vitals.ttfb = m.value;
2000
+ this.reportVital("TTFB", m.value);
2001
+ });
2002
+ webVitals.onFCP((m) => {
2003
+ this.vitals.fcp = m.value;
2004
+ this.reportVital("FCP", m.value);
2005
+ });
2006
+ }
2007
+ // ---- Resource Timing (adapted from OpenReplay timing.ts) ----
2008
+ initResourceTiming() {
2009
+ if (typeof PerformanceObserver === "undefined") return;
2010
+ try {
2011
+ this.resourceObserver = new PerformanceObserver((list) => {
2012
+ for (const entry of list.getEntries()) {
2013
+ this.processResourceEntry(entry);
2014
+ }
2015
+ });
2016
+ this.resourceObserver.observe({ type: "resource", buffered: true });
2017
+ } catch {
2018
+ this.log("PerformanceObserver for resource timing not supported");
2019
+ }
2020
+ }
2021
+ isServiceURL(url) {
2022
+ const endpoint = this.config.performanceEndpoint || this.config.endpoint;
2023
+ return url.startsWith(endpoint);
2024
+ }
2025
+ processResourceEntry(entry) {
2026
+ if (entry.duration < 0 || !entry.name.startsWith("http")) return;
2027
+ if (this.isServiceURL(entry.name)) return;
2028
+ if (this.resourceTimeMap !== null) {
2029
+ this.resourceTimeMap[entry.name] = entry.startTime + entry.duration;
2030
+ }
2031
+ for (const excluded of this.config.excludedResourceUrls) {
2032
+ if (entry.name.startsWith(excluded)) return;
2033
+ }
2034
+ if (this.resourceCount >= MAX_RESOURCES_PER_FLUSH) return;
2035
+ this.resourceCount++;
2036
+ let stalled = 0;
2037
+ if (entry.connectEnd && entry.connectEnd > entry.domainLookupEnd) {
2038
+ stalled = Math.max(0, entry.requestStart - entry.connectEnd);
2039
+ } else {
2040
+ stalled = Math.max(0, entry.requestStart - entry.domainLookupEnd);
2041
+ }
2042
+ const cached = entry.responseStatus && entry.responseStatus === 304 || entry.deliveryType && entry.deliveryType === "cache" || entry.transferSize === 0 && entry.decodedBodySize > 0;
2043
+ const responseStatus = entry.responseStatus || 0;
2044
+ const failed = responseStatus >= 400;
2045
+ const breakdown = {
2046
+ queueing: entry.requestStart - entry.fetchStart,
2047
+ dnsLookup: entry.domainLookupEnd - entry.domainLookupStart,
2048
+ initialConnection: entry.connectEnd - entry.connectStart,
2049
+ ssl: entry.secureConnectionStart > 0 ? entry.connectEnd - entry.secureConnectionStart : 0,
2050
+ ttfb: entry.responseStart - entry.requestStart,
2051
+ contentDownload: entry.responseEnd - entry.responseStart,
2052
+ total: entry.duration ?? entry.responseEnd - entry.startTime,
2053
+ stalled,
2054
+ cached,
2055
+ failed,
2056
+ responseStatus,
2057
+ headerSize: entry.transferSize > entry.encodedBodySize ? entry.transferSize - entry.encodedBodySize : 0,
2058
+ encodedBodySize: entry.encodedBodySize || 0,
2059
+ decodedBodySize: failed ? -111 : entry.decodedBodySize || 0,
2060
+ transferSize: entry.transferSize,
2061
+ initiatorType: entry.initiatorType
2062
+ };
2063
+ const entryName = this.config.resourceNameSanitizer ? this.config.resourceNameSanitizer(entry.name) : entry.name;
2064
+ this.addMetric({
2065
+ type: "resource",
2066
+ name: entryName,
2067
+ value: entry.duration,
2068
+ unit: "ms",
2069
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2070
+ url: typeof window !== "undefined" && window.location ? window.location.href : void 0,
2071
+ data: breakdown
2072
+ });
2073
+ }
2074
+ // ---- Page Load Timing (adapted from OpenReplay timing.ts) ----
2075
+ initPageLoadTiming() {
2076
+ if (typeof window === "undefined" || !window.performance) return;
2077
+ let firstPaint = 0;
2078
+ let firstContentfulPaint = 0;
2079
+ let sent = false;
2080
+ const startTime = performance.now();
2081
+ this.pageLoadTimer = setInterval(() => {
2082
+ if (sent) {
2083
+ if (this.pageLoadTimer) clearInterval(this.pageLoadTimer);
2084
+ return;
2085
+ }
2086
+ if (firstPaint === 0 || firstContentfulPaint === 0) {
2087
+ for (const entry of performance.getEntriesByType("paint")) {
2088
+ if (entry.name === "first-paint") firstPaint = entry.startTime;
2089
+ if (entry.name === "first-contentful-paint") firstContentfulPaint = entry.startTime;
2090
+ }
2091
+ }
2092
+ const nav = performance.getEntriesByType("navigation")[0];
2093
+ const timedOut = performance.now() - startTime > PAGE_TIMING_TIMEOUT;
2094
+ if (nav && nav.loadEventEnd > 0 || timedOut) {
2095
+ sent = true;
2096
+ if (this.pageLoadTimer) clearInterval(this.pageLoadTimer);
2097
+ if (nav) {
2098
+ const navigationStart = nav.startTime;
2099
+ this.addMetric({
2100
+ type: "navigation",
2101
+ name: "page_load",
2102
+ value: nav.loadEventEnd - navigationStart,
2103
+ unit: "ms",
2104
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2105
+ url: window.location.href,
2106
+ data: {
2107
+ requestStart: nav.requestStart - navigationStart,
2108
+ responseStart: nav.responseStart - navigationStart,
2109
+ responseEnd: nav.responseEnd - navigationStart,
2110
+ domContentLoadedEventStart: nav.domContentLoadedEventEnd - navigationStart,
2111
+ domContentLoadedEventEnd: nav.domContentLoadedEventEnd - navigationStart,
2112
+ loadEventStart: nav.loadEventStart - navigationStart,
2113
+ loadEventEnd: nav.loadEventEnd - navigationStart,
2114
+ firstPaint,
2115
+ firstContentfulPaint,
2116
+ dns: nav.domainLookupEnd - nav.domainLookupStart,
2117
+ tcp: nav.connectEnd - nav.connectStart,
2118
+ ttfb: nav.responseStart - nav.requestStart,
2119
+ domComplete: nav.domComplete - navigationStart,
2120
+ transferSize: nav.transferSize,
2121
+ encodedBodySize: nav.encodedBodySize,
2122
+ decodedBodySize: nav.decodedBodySize
2123
+ }
2124
+ });
2125
+ }
2126
+ }
2127
+ }, PAGE_TIMING_POLL_INTERVAL);
2128
+ }
2129
+ // ---- Page Render Timing: Speed Index, Visually Complete, TTI (adapted from OpenReplay) ----
2130
+ initPageRenderTiming() {
2131
+ if (typeof window === "undefined" || !window.performance) return;
2132
+ let firstContentfulPaint = 0;
2133
+ let visuallyComplete = 0;
2134
+ let interactiveWindowStartTime = 0;
2135
+ let interactiveWindowTickTime = 0;
2136
+ let paintBlocks = null;
2137
+ let sent = false;
2138
+ const startTime = performance.now();
2139
+ this.pageRenderTimer = setInterval(() => {
2140
+ if (sent) {
2141
+ if (this.pageRenderTimer) clearInterval(this.pageRenderTimer);
2142
+ return;
2143
+ }
2144
+ const time = performance.now();
2145
+ if (firstContentfulPaint === 0) {
2146
+ for (const entry of performance.getEntriesByType("paint")) {
2147
+ if (entry.name === "first-contentful-paint") {
2148
+ firstContentfulPaint = entry.startTime;
2149
+ }
2150
+ }
2151
+ }
2152
+ if (this.resourceTimeMap !== null) {
2153
+ const times = Object.values(this.resourceTimeMap);
2154
+ if (times.length > 0) {
2155
+ visuallyComplete = Math.max(...times);
2156
+ }
2157
+ if (time - visuallyComplete > 1e3) {
2158
+ paintBlocks = getPaintBlocks(this.resourceTimeMap);
2159
+ this.resourceTimeMap = null;
2160
+ }
2161
+ }
2162
+ if (interactiveWindowTickTime !== null) {
2163
+ if (time - interactiveWindowTickTime > 50) {
2164
+ interactiveWindowStartTime = time;
2165
+ }
2166
+ interactiveWindowTickTime = time - interactiveWindowStartTime > 5e3 ? null : time;
2167
+ }
2168
+ const timedOut = time - startTime > PAGE_TIMING_TIMEOUT;
2169
+ if (paintBlocks !== null && interactiveWindowTickTime === null || timedOut) {
2170
+ sent = true;
2171
+ if (this.pageRenderTimer) clearInterval(this.pageRenderTimer);
2172
+ this.resourceTimeMap = null;
2173
+ const speedIndex = paintBlocks === null ? 0 : calculateSpeedIndex(firstContentfulPaint || 0, paintBlocks);
2174
+ const nav = performance.getEntriesByType("navigation")[0];
2175
+ const domContentLoadedEnd = nav ? nav.domContentLoadedEventEnd - nav.startTime : 0;
2176
+ const timeToInteractive = interactiveWindowTickTime === null ? Math.max(interactiveWindowStartTime, firstContentfulPaint, domContentLoadedEnd) : 0;
2177
+ const url = window.location.href;
2178
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2179
+ if (speedIndex > 0) {
2180
+ this.addMetric({
2181
+ type: "page_render",
2182
+ name: "speed_index",
2183
+ value: Math.round(speedIndex),
2184
+ unit: "ms",
2185
+ timestamp,
2186
+ url
2187
+ });
2188
+ }
2189
+ if (visuallyComplete > 0) {
2190
+ this.addMetric({
2191
+ type: "page_render",
2192
+ name: "visually_complete",
2193
+ value: Math.round(visuallyComplete),
2194
+ unit: "ms",
2195
+ timestamp,
2196
+ url
2197
+ });
2198
+ }
2199
+ if (timeToInteractive > 0) {
2200
+ this.addMetric({
2201
+ type: "page_render",
2202
+ name: "tti",
2203
+ value: Math.round(timeToInteractive),
2204
+ unit: "ms",
2205
+ timestamp,
2206
+ url
2207
+ });
2208
+ }
2209
+ }
2210
+ }, PAGE_TIMING_POLL_INTERVAL);
2211
+ }
2212
+ // ---- Transactions / Spans (unchanged) ----
1910
2213
  startTransaction(name, data) {
1911
2214
  const id = `txn_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
1912
2215
  const transaction = {
@@ -1921,9 +2224,6 @@ var PerformanceManager = class {
1921
2224
  this.log("Transaction started:", name, id);
1922
2225
  return id;
1923
2226
  }
1924
- /**
1925
- * End a transaction and report it
1926
- */
1927
2227
  endTransaction(id, status = "ok") {
1928
2228
  const transaction = this.activeTransactions.get(id);
1929
2229
  if (!transaction) {
@@ -1952,9 +2252,6 @@ var PerformanceManager = class {
1952
2252
  });
1953
2253
  this.log("Transaction ended:", transaction.name, `${transaction.duration.toFixed(1)}ms`);
1954
2254
  }
1955
- /**
1956
- * Start a span within a transaction
1957
- */
1958
2255
  startSpan(transactionId, name, data) {
1959
2256
  const transaction = this.activeTransactions.get(transactionId);
1960
2257
  if (!transaction) {
@@ -1971,9 +2268,6 @@ var PerformanceManager = class {
1971
2268
  transaction.spans.push(span);
1972
2269
  return span.id;
1973
2270
  }
1974
- /**
1975
- * End a span
1976
- */
1977
2271
  endSpan(transactionId, spanId, status = "ok") {
1978
2272
  const transaction = this.activeTransactions.get(transactionId);
1979
2273
  if (!transaction) return;
@@ -1983,9 +2277,7 @@ var PerformanceManager = class {
1983
2277
  span.duration = span.endTime - span.startTime;
1984
2278
  span.status = status;
1985
2279
  }
1986
- /**
1987
- * Get current Web Vitals
1988
- */
2280
+ // ---- Public API ----
1989
2281
  getVitals() {
1990
2282
  return { ...this.vitals };
1991
2283
  }
@@ -1994,116 +2286,21 @@ var PerformanceManager = class {
1994
2286
  clearInterval(this.flushTimer);
1995
2287
  this.flushTimer = null;
1996
2288
  }
1997
- this.flush();
1998
- }
1999
- observeWebVitals() {
2000
- if (typeof PerformanceObserver === "undefined") return;
2001
- try {
2002
- const lcpObserver = new PerformanceObserver((list) => {
2003
- const entries = list.getEntries();
2004
- const lastEntry = entries[entries.length - 1];
2005
- if (lastEntry) {
2006
- this.vitals.lcp = lastEntry.startTime;
2007
- this.reportVital("LCP", lastEntry.startTime);
2008
- }
2009
- });
2010
- lcpObserver.observe({ type: "largest-contentful-paint", buffered: true });
2011
- } catch (e) {
2012
- }
2013
- try {
2014
- const fidObserver = new PerformanceObserver((list) => {
2015
- const entry = list.getEntries()[0];
2016
- if (entry) {
2017
- const fid = entry.processingStart - entry.startTime;
2018
- this.vitals.fid = fid;
2019
- this.reportVital("FID", fid);
2020
- }
2021
- });
2022
- fidObserver.observe({ type: "first-input", buffered: true });
2023
- } catch (e) {
2024
- }
2025
- try {
2026
- let clsValue = 0;
2027
- const clsObserver = new PerformanceObserver((list) => {
2028
- for (const entry of list.getEntries()) {
2029
- const layoutShift = entry;
2030
- if (!layoutShift.hadRecentInput) {
2031
- clsValue += layoutShift.value;
2032
- }
2033
- }
2034
- this.vitals.cls = clsValue;
2035
- });
2036
- clsObserver.observe({ type: "layout-shift", buffered: true });
2037
- if (typeof document !== "undefined") {
2038
- document.addEventListener("visibilitychange", () => {
2039
- if (document.visibilityState === "hidden" && clsValue > 0) {
2040
- this.reportVital("CLS", clsValue, "score");
2041
- }
2042
- });
2043
- }
2044
- } catch (e) {
2289
+ if (this.pageLoadTimer) {
2290
+ clearInterval(this.pageLoadTimer);
2291
+ this.pageLoadTimer = null;
2045
2292
  }
2046
- try {
2047
- const fcpObserver = new PerformanceObserver((list) => {
2048
- const entry = list.getEntries().find(
2049
- (e) => e.name === "first-contentful-paint"
2050
- );
2051
- if (entry) {
2052
- this.vitals.fcp = entry.startTime;
2053
- this.reportVital("FCP", entry.startTime);
2054
- }
2055
- });
2056
- fcpObserver.observe({ type: "paint", buffered: true });
2057
- } catch (e) {
2058
- }
2059
- try {
2060
- let maxINP = 0;
2061
- const inpObserver = new PerformanceObserver((list) => {
2062
- for (const entry of list.getEntries()) {
2063
- const duration = entry.duration;
2064
- if (duration > maxINP) {
2065
- maxINP = duration;
2066
- this.vitals.inp = maxINP;
2067
- }
2068
- }
2069
- });
2070
- inpObserver.observe({ type: "event", buffered: true });
2071
- } catch (e) {
2293
+ if (this.pageRenderTimer) {
2294
+ clearInterval(this.pageRenderTimer);
2295
+ this.pageRenderTimer = null;
2072
2296
  }
2073
- }
2074
- collectNavigationTiming() {
2075
- if (typeof window === "undefined" || !window.performance || !window.location) return;
2076
- const collect = () => {
2077
- const nav = performance.getEntriesByType("navigation")[0];
2078
- if (!nav) return;
2079
- const ttfb = nav.responseStart - nav.requestStart;
2080
- this.vitals.ttfb = ttfb;
2081
- this.reportVital("TTFB", ttfb);
2082
- this.addMetric({
2083
- type: "navigation",
2084
- name: "page_load",
2085
- value: nav.loadEventEnd - nav.startTime,
2086
- unit: "ms",
2087
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2088
- url: window.location.href,
2089
- data: {
2090
- dns: nav.domainLookupEnd - nav.domainLookupStart,
2091
- tcp: nav.connectEnd - nav.connectStart,
2092
- ttfb,
2093
- domContentLoaded: nav.domContentLoadedEventEnd - nav.startTime,
2094
- domComplete: nav.domComplete - nav.startTime,
2095
- transferSize: nav.transferSize,
2096
- encodedBodySize: nav.encodedBodySize,
2097
- decodedBodySize: nav.decodedBodySize
2098
- }
2099
- });
2100
- };
2101
- if (document.readyState === "complete") {
2102
- setTimeout(collect, 0);
2103
- } else {
2104
- window.addEventListener("load", () => setTimeout(collect, 0));
2297
+ if (this.resourceObserver) {
2298
+ this.resourceObserver.disconnect();
2299
+ this.resourceObserver = null;
2105
2300
  }
2301
+ this.flush();
2106
2302
  }
2303
+ // ---- Internal ----
2107
2304
  reportVital(name, value, unit = "ms") {
2108
2305
  this.addMetric({
2109
2306
  type: "web_vital",
@@ -2137,6 +2334,7 @@ var PerformanceManager = class {
2137
2334
  }
2138
2335
  const metrics = [...this.metrics];
2139
2336
  this.metrics = [];
2337
+ this.resourceCount = 0;
2140
2338
  const endpoint = this.config.performanceEndpoint || `${this.config.endpoint}/api/performance`;
2141
2339
  try {
2142
2340
  const response = await fetch(endpoint, {
@@ -2314,6 +2512,292 @@ var TracePropagator = class {
2314
2512
  }
2315
2513
  };
2316
2514
 
2515
+ // src/superlink/client.ts
2516
+ var DEFAULT_SUPERLINK_ENDPOINT = "https://pongl.ink";
2517
+ var SuperLinkClient = class {
2518
+ constructor(config = {}) {
2519
+ this.config = {
2520
+ endpoint: stripTrailingSlash(config.endpoint || DEFAULT_SUPERLINK_ENDPOINT),
2521
+ appId: config.appId,
2522
+ installId: config.installId,
2523
+ debug: config.debug ?? false
2524
+ };
2525
+ }
2526
+ /** Replace config in place (used by init() so module-level helpers see updates). */
2527
+ configure(config) {
2528
+ if (config.endpoint) this.config.endpoint = stripTrailingSlash(config.endpoint);
2529
+ if (config.appId !== void 0) this.config.appId = config.appId;
2530
+ if (config.installId !== void 0) this.config.installId = config.installId;
2531
+ if (config.debug !== void 0) this.config.debug = config.debug;
2532
+ }
2533
+ log(...args) {
2534
+ if (this.config.debug) console.warn("[SuperLink]", ...args);
2535
+ }
2536
+ /**
2537
+ * POST /match — ask the redirect engine to resolve a deferred deep link from
2538
+ * a click token (Android referrer / iOS clipboard) and/or a device
2539
+ * fingerprint. Returns `{ matched: false }` on any error.
2540
+ */
2541
+ async match(body) {
2542
+ const payload = {
2543
+ ...body,
2544
+ install_id: body.install_id ?? this.config.installId
2545
+ };
2546
+ try {
2547
+ const res = await fetch(`${this.config.endpoint}/match`, {
2548
+ method: "POST",
2549
+ headers: { "Content-Type": "application/json" },
2550
+ body: JSON.stringify(payload)
2551
+ });
2552
+ if (!res.ok) {
2553
+ this.log("match failed", res.status);
2554
+ return { matched: false };
2555
+ }
2556
+ const data = await res.json();
2557
+ return data ?? { matched: false };
2558
+ } catch (err) {
2559
+ this.log("match error", err);
2560
+ return { matched: false };
2561
+ }
2562
+ }
2563
+ /**
2564
+ * POST /events — report a lifecycle event (opened / converted) back to the
2565
+ * redirect engine, which fans out to analytics + webhooks. Best-effort.
2566
+ */
2567
+ async reportEvent(input) {
2568
+ try {
2569
+ const res = await fetch(`${this.config.endpoint}/events`, {
2570
+ method: "POST",
2571
+ headers: { "Content-Type": "application/json" },
2572
+ body: JSON.stringify({
2573
+ app_id: this.config.appId,
2574
+ install_id: this.config.installId,
2575
+ ...input
2576
+ })
2577
+ });
2578
+ if (!res.ok) this.log("event failed", input.type, res.status);
2579
+ return res.ok;
2580
+ } catch (err) {
2581
+ this.log("event error", err);
2582
+ return false;
2583
+ }
2584
+ }
2585
+ };
2586
+ function stripTrailingSlash(url) {
2587
+ return url.endsWith("/") ? url.slice(0, -1) : url;
2588
+ }
2589
+ var superlinkClient = new SuperLinkClient();
2590
+
2591
+ // src/superlink/parse.ts
2592
+ var UTM_KEYS = [
2593
+ ["source", "utm_source"],
2594
+ ["medium", "utm_medium"],
2595
+ ["campaign", "utm_campaign"],
2596
+ ["term", "utm_term"],
2597
+ ["content", "utm_content"]
2598
+ ];
2599
+ function parseUniversalLink(url) {
2600
+ let parsed;
2601
+ try {
2602
+ parsed = new URL(url);
2603
+ } catch {
2604
+ return null;
2605
+ }
2606
+ const params = parsed.searchParams;
2607
+ const utm = {};
2608
+ for (const [key, queryKey] of UTM_KEYS) {
2609
+ const value = params.get(queryKey);
2610
+ if (value) utm[key] = value;
2611
+ }
2612
+ const referral = {};
2613
+ const deep_link_data = {};
2614
+ params.forEach((value, key) => {
2615
+ if (key.startsWith("r_")) {
2616
+ referral[key.slice(2)] = value;
2617
+ } else if (key.startsWith("~")) {
2618
+ deep_link_data[key.slice(1)] = value;
2619
+ }
2620
+ });
2621
+ const explicitPath = params.get("$deep_link_path") ?? params.get("dlp");
2622
+ const deep_link_path = explicitPath ?? (parsed.pathname && parsed.pathname !== "/" ? parsed.pathname : null);
2623
+ return {
2624
+ deep_link_path,
2625
+ deep_link_data,
2626
+ utm,
2627
+ referral,
2628
+ click_id: params.get("click_id"),
2629
+ match_type: "none",
2630
+ confidence: 1
2631
+ };
2632
+ }
2633
+
2634
+ // src/superlink/deferred.ts
2635
+ var handlers = /* @__PURE__ */ new Set();
2636
+ var lastMatched = null;
2637
+ function toDeepLink(res) {
2638
+ return {
2639
+ deep_link_path: res.deep_link_path ?? null,
2640
+ deep_link_data: res.deep_link_data ?? {},
2641
+ utm: res.utm ?? {},
2642
+ referral: res.referral ?? {},
2643
+ click_id: res.click_id ?? null,
2644
+ match_type: res.match_type ?? "fingerprint",
2645
+ confidence: res.confidence ?? (res.match_type && res.match_type !== "none" ? 1 : 0)
2646
+ };
2647
+ }
2648
+ function emitDeferredDeepLink(link) {
2649
+ lastMatched = link;
2650
+ for (const handler of handlers) {
2651
+ try {
2652
+ handler(link);
2653
+ } catch (err) {
2654
+ if (superlinkClient.config.debug) console.warn("[SuperLink] handler threw", err);
2655
+ }
2656
+ }
2657
+ }
2658
+ function onDeferredDeepLink(handler) {
2659
+ handlers.add(handler);
2660
+ if (lastMatched) {
2661
+ try {
2662
+ handler(lastMatched);
2663
+ } catch {
2664
+ }
2665
+ }
2666
+ return () => {
2667
+ handlers.delete(handler);
2668
+ };
2669
+ }
2670
+ function getMatchedDeepLink() {
2671
+ return lastMatched;
2672
+ }
2673
+
2674
+ // src/superlink/web.ts
2675
+ var STORAGE_KEY3 = "sp_superlink";
2676
+ var captured = null;
2677
+ var didCapture = false;
2678
+ function readStored() {
2679
+ if (typeof sessionStorage === "undefined") return null;
2680
+ try {
2681
+ const raw = sessionStorage.getItem(STORAGE_KEY3);
2682
+ if (!raw) return null;
2683
+ const parsed = JSON.parse(raw);
2684
+ return parsed && typeof parsed === "object" ? parsed : null;
2685
+ } catch {
2686
+ return null;
2687
+ }
2688
+ }
2689
+ function writeStored(link) {
2690
+ if (typeof sessionStorage === "undefined") return;
2691
+ try {
2692
+ sessionStorage.setItem(STORAGE_KEY3, JSON.stringify(link));
2693
+ } catch {
2694
+ }
2695
+ }
2696
+ function hasPayload(link) {
2697
+ return Boolean(
2698
+ link.deep_link_path || link.click_id || Object.keys(link.deep_link_data).length > 0 || Object.keys(link.referral).length > 0 || Object.keys(link.utm).length > 0
2699
+ );
2700
+ }
2701
+ function writeClipboardToken(clickId) {
2702
+ if (typeof navigator === "undefined" || !navigator.clipboard) return;
2703
+ const url = `${superlinkClient.config.endpoint}/c/${clickId}`;
2704
+ navigator.clipboard.writeText(url).catch(() => {
2705
+ });
2706
+ }
2707
+ function extractIdentityMetadata(url) {
2708
+ const href = url ?? (typeof window !== "undefined" && window.location ? window.location.href : null);
2709
+ if (!href) return void 0;
2710
+ let loc;
2711
+ try {
2712
+ loc = new URL(href);
2713
+ } catch {
2714
+ return void 0;
2715
+ }
2716
+ const email = loc.searchParams.get("email");
2717
+ const phone = loc.searchParams.get("phone");
2718
+ const userId = loc.searchParams.get("user_id");
2719
+ const id = loc.searchParams.get("id");
2720
+ const customRaw = loc.searchParams.get("custom");
2721
+ const identity = {};
2722
+ if (email) identity.email = email;
2723
+ if (phone) identity.phone = phone;
2724
+ if (userId) identity.user_id = userId;
2725
+ if (id) identity.id = id;
2726
+ if (customRaw) {
2727
+ try {
2728
+ const custom = JSON.parse(decodeURIComponent(customRaw));
2729
+ if (typeof custom === "object" && custom !== null) {
2730
+ identity.custom = custom;
2731
+ }
2732
+ } catch {
2733
+ }
2734
+ }
2735
+ return Object.keys(identity).length > 0 ? identity : void 0;
2736
+ }
2737
+ function captureWebDeepLink() {
2738
+ if (didCapture) return captured;
2739
+ didCapture = true;
2740
+ const stored = readStored();
2741
+ if (stored) {
2742
+ captured = stored;
2743
+ return captured;
2744
+ }
2745
+ if (typeof window === "undefined" || !window.location) return null;
2746
+ const link = parseUniversalLink(window.location.href);
2747
+ if (link && hasPayload(link)) {
2748
+ captured = link;
2749
+ writeStored(link);
2750
+ void superlinkClient.reportEvent({
2751
+ type: "opened",
2752
+ click_id: link.click_id,
2753
+ platform: "desktop",
2754
+ properties: { surface: "web" }
2755
+ });
2756
+ }
2757
+ return captured;
2758
+ }
2759
+ function getDeepLink() {
2760
+ if (!didCapture) return captureWebDeepLink();
2761
+ return captured;
2762
+ }
2763
+ function webFingerprint() {
2764
+ const fp = { platform: "desktop" };
2765
+ if (typeof navigator !== "undefined") {
2766
+ if (navigator.userAgent) fp.user_agent = navigator.userAgent;
2767
+ const lang = navigator.language || navigator.languages && navigator.languages[0];
2768
+ if (lang) fp.language = lang;
2769
+ }
2770
+ return fp;
2771
+ }
2772
+ async function identify(identity) {
2773
+ if (!identity || Object.keys(identity).length === 0) return null;
2774
+ const res = await superlinkClient.match({
2775
+ identity,
2776
+ fingerprint: webFingerprint()
2777
+ });
2778
+ if (!res.matched) return null;
2779
+ const link = toDeepLink(res);
2780
+ emitDeferredDeepLink(link);
2781
+ return link;
2782
+ }
2783
+ async function completeFromScan(scanned) {
2784
+ if (!scanned) return null;
2785
+ const res = await superlinkClient.match({
2786
+ qr_token: scanned,
2787
+ fingerprint: webFingerprint()
2788
+ });
2789
+ if (!res.matched) return null;
2790
+ const link = toDeepLink(res);
2791
+ emitDeferredDeepLink(link);
2792
+ return link;
2793
+ }
2794
+
2795
+ // src/superlink/index.ts
2796
+ function initSuperLink(config = {}) {
2797
+ superlinkClient.configure(config);
2798
+ captureWebDeepLink();
2799
+ }
2800
+
2317
2801
  // src/index.ts
2318
2802
  function installFallbackEnvironment() {
2319
2803
  if (getEnvironment()) return;
@@ -2523,6 +3007,10 @@ var SitePongClient = class {
2523
3007
  webVitals: config.performance.webVitals,
2524
3008
  navigationTiming: config.performance.navigationTiming,
2525
3009
  resourceTiming: config.performance.resourceTiming,
3010
+ capturePageLoadTimings: config.performance.capturePageLoadTimings,
3011
+ capturePageRenderTimings: config.performance.capturePageRenderTimings,
3012
+ excludedResourceUrls: config.performance.excludedResourceUrls,
3013
+ resourceNameSanitizer: config.performance.resourceNameSanitizer,
2526
3014
  sampleRate: config.performance.sampleRate,
2527
3015
  flushInterval: config.performance.flushInterval,
2528
3016
  performanceEndpoint: config.performance.performanceEndpoint,
@@ -3231,7 +3719,7 @@ var areFlagsReady = sitepong.areFlagsReady.bind(sitepong);
3231
3719
  var refreshFlags = sitepong.refreshFlags.bind(sitepong);
3232
3720
  var track = sitepong.track.bind(sitepong);
3233
3721
  var trackPageView = sitepong.trackPageView.bind(sitepong);
3234
- var identify = sitepong.identify.bind(sitepong);
3722
+ var identify2 = sitepong.identify.bind(sitepong);
3235
3723
  var group = sitepong.group.bind(sitepong);
3236
3724
  var resetAnalytics = sitepong.resetAnalytics.bind(sitepong);
3237
3725
  var getVisitorId = sitepong.getVisitorId.bind(sitepong);
@@ -5167,11 +5655,11 @@ function getNotificationsPermission() {
5167
5655
  return Notification.permission === "granted";
5168
5656
  }
5169
5657
  function getDeviceAge() {
5170
- const STORAGE_KEY3 = "sitepong_device_age";
5658
+ const STORAGE_KEY4 = "sitepong_device_age";
5171
5659
  const result = { visitCount: 1 };
5172
5660
  if (typeof localStorage === "undefined") return result;
5173
5661
  try {
5174
- const stored = localStorage.getItem(STORAGE_KEY3);
5662
+ const stored = localStorage.getItem(STORAGE_KEY4);
5175
5663
  if (stored) {
5176
5664
  const parsed = JSON.parse(stored);
5177
5665
  result.firstSeen = parsed.firstSeen;
@@ -5179,7 +5667,7 @@ function getDeviceAge() {
5179
5667
  } else {
5180
5668
  result.firstSeen = (/* @__PURE__ */ new Date()).toISOString();
5181
5669
  }
5182
- localStorage.setItem(STORAGE_KEY3, JSON.stringify({
5670
+ localStorage.setItem(STORAGE_KEY4, JSON.stringify({
5183
5671
  firstSeen: result.firstSeen,
5184
5672
  visitCount: result.visitCount
5185
5673
  }));
@@ -5961,14 +6449,18 @@ registerWebManagerFactories({
5961
6449
  createPerformance: (cfg) => new PerformanceManager(cfg)
5962
6450
  });
5963
6451
 
6452
+ exports.DEFAULT_SUPERLINK_ENDPOINT = DEFAULT_SUPERLINK_ENDPOINT;
6453
+ exports.SuperLinkClient = SuperLinkClient;
5964
6454
  exports.TracePropagator = TracePropagator;
5965
6455
  exports.addBreadcrumb = addBreadcrumb;
5966
6456
  exports.areFlagsReady = areFlagsReady;
5967
6457
  exports.captureError = captureError;
5968
6458
  exports.captureMessage = captureMessage;
6459
+ exports.captureWebDeepLink = captureWebDeepLink;
5969
6460
  exports.clearAnonymousId = clearAnonymousId;
5970
6461
  exports.clearUser = clearUser;
5971
6462
  exports.client = sitepong;
6463
+ exports.completeFromScan = completeFromScan;
5972
6464
  exports.createTraceContext = createTraceContext;
5973
6465
  exports.cronCheckin = cronCheckin;
5974
6466
  exports.cronStart = cronStart;
@@ -5978,6 +6470,7 @@ exports.dbTrackSync = dbTrackSync;
5978
6470
  exports.default = src_default;
5979
6471
  exports.endSpan = endSpan;
5980
6472
  exports.endTransaction = endTransaction;
6473
+ exports.extractIdentityMetadata = extractIdentityMetadata;
5981
6474
  exports.extractTrace = extractTrace;
5982
6475
  exports.flush = flush;
5983
6476
  exports.flushMetrics = flushMetrics;
@@ -5988,10 +6481,12 @@ exports.getAllFlags = getAllFlags;
5988
6481
  exports.getAnonymousId = getAnonymousId;
5989
6482
  exports.getDbNPlusOnePatterns = getDbNPlusOnePatterns;
5990
6483
  exports.getDbQueryCount = getDbQueryCount;
6484
+ exports.getDeepLink = getDeepLink;
5991
6485
  exports.getDeviceSignals = getDeviceSignals;
5992
6486
  exports.getFlag = getFlag;
5993
6487
  exports.getFraudCheck = getFraudCheck;
5994
6488
  exports.getLatestProfile = getLatestProfile;
6489
+ exports.getMatchedDeepLink = getMatchedDeepLink;
5995
6490
  exports.getProfiles = getProfiles;
5996
6491
  exports.getRemoteConfig = getRemoteConfig;
5997
6492
  exports.getReplaySessionId = getReplaySessionId;
@@ -6000,9 +6495,10 @@ exports.getVariantPayload = getVariantPayload2;
6000
6495
  exports.getVisitorId = getVisitorId;
6001
6496
  exports.getWebVitals = getWebVitals;
6002
6497
  exports.group = group;
6003
- exports.identify = identify;
6498
+ exports.identify = identify2;
6004
6499
  exports.init = init;
6005
6500
  exports.initRN = initRN;
6501
+ exports.initSuperLink = initSuperLink;
6006
6502
  exports.isInitialized = isInitialized;
6007
6503
  exports.isRemoteConfigFeatureEnabled = isRemoteConfigFeatureEnabled;
6008
6504
  exports.isReplayRecording = isReplayRecording;
@@ -6012,7 +6508,9 @@ exports.metricHistogram = metricHistogram;
6012
6508
  exports.metricIncrement = metricIncrement;
6013
6509
  exports.metricStartTimer = metricStartTimer;
6014
6510
  exports.metricTime = metricTime;
6511
+ exports.onDeferredDeepLink = onDeferredDeepLink;
6015
6512
  exports.onRemoteConfigChange = onRemoteConfigChange;
6513
+ exports.parseUniversalLink = parseUniversalLink;
6016
6514
  exports.profile = profile;
6017
6515
  exports.propagateTrace = propagateTrace;
6018
6516
  exports.refreshFlags = refreshFlags;
@@ -6032,8 +6530,11 @@ exports.startReplay = startReplay;
6032
6530
  exports.startSpan = startSpan;
6033
6531
  exports.startTransaction = startTransaction;
6034
6532
  exports.stopReplay = stopReplay;
6533
+ exports.superlinkClient = superlinkClient;
6534
+ exports.superlinkIdentify = identify;
6035
6535
  exports.track = track;
6036
6536
  exports.trackPageView = trackPageView;
6037
6537
  exports.waitForFlags = waitForFlags;
6538
+ exports.writeClipboardToken = writeClipboardToken;
6038
6539
  //# sourceMappingURL=web.js.map
6039
6540
  //# sourceMappingURL=web.js.map