@active-reach/web-sdk 1.3.1 → 1.5.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.
- package/dist/aegis.min.js +1 -1
- package/dist/aegis.min.js.map +1 -1
- package/dist/{analytics-B0JfoAJs.mjs → analytics-C00PJUSy.mjs} +269 -2
- package/dist/analytics-C00PJUSy.mjs.map +1 -0
- package/dist/cdn.d.ts +7 -0
- package/dist/cdn.d.ts.map +1 -1
- package/dist/core/analytics.d.ts +20 -0
- package/dist/core/analytics.d.ts.map +1 -1
- package/dist/core/bootstrap.d.ts +71 -0
- package/dist/core/bootstrap.d.ts.map +1 -0
- package/dist/core/prefetch-bundle-client.d.ts +150 -0
- package/dist/core/prefetch-bundle-client.d.ts.map +1 -0
- package/dist/governance/bloom-filter.d.ts +47 -0
- package/dist/governance/bloom-filter.d.ts.map +1 -0
- package/dist/governance/index.d.ts +6 -0
- package/dist/governance/index.d.ts.map +1 -0
- package/dist/governance/murmur3.d.ts +43 -0
- package/dist/governance/murmur3.d.ts.map +1 -0
- package/dist/governance/name-governor.d.ts +98 -0
- package/dist/governance/name-governor.d.ts.map +1 -0
- package/dist/inapp/AegisInAppManager.d.ts +28 -1
- package/dist/inapp/AegisInAppManager.d.ts.map +1 -1
- package/dist/inapp/renderers/carousel-cards.d.ts +15 -0
- package/dist/inapp/renderers/carousel-cards.d.ts.map +1 -0
- package/dist/inapp/renderers/coachmark-tour.d.ts +24 -0
- package/dist/inapp/renderers/coachmark-tour.d.ts.map +1 -0
- package/dist/inapp/renderers/index.d.ts +12 -0
- package/dist/inapp/renderers/index.d.ts.map +1 -0
- package/dist/inapp/renderers/product-recommendation.d.ts +23 -0
- package/dist/inapp/renderers/product-recommendation.d.ts.map +1 -0
- package/dist/inapp/renderers/progress-bar.d.ts +24 -0
- package/dist/inapp/renderers/progress-bar.d.ts.map +1 -0
- package/dist/inapp/renderers/sticky-bar.d.ts +14 -0
- package/dist/inapp/renderers/sticky-bar.d.ts.map +1 -0
- package/dist/inapp/renderers/types.d.ts +27 -0
- package/dist/inapp/renderers/types.d.ts.map +1 -0
- package/dist/inbox/AegisInbox.d.ts +103 -0
- package/dist/inbox/AegisInbox.d.ts.map +1 -0
- package/dist/inbox/index.d.ts +3 -0
- package/dist/inbox/index.d.ts.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1396 -10
- package/dist/index.js.map +1 -1
- package/dist/push/AegisWebPush.d.ts +17 -2
- package/dist/push/AegisWebPush.d.ts.map +1 -1
- package/dist/push/AegisWebPush.js +95 -29
- package/dist/push/AegisWebPush.js.map +1 -1
- package/dist/react.js +1 -1
- package/package.json +1 -1
- package/dist/analytics-B0JfoAJs.mjs.map +0 -1
|
@@ -1761,6 +1761,196 @@ class RateLimiter {
|
|
|
1761
1761
|
this.firstDroppedName = null;
|
|
1762
1762
|
}
|
|
1763
1763
|
}
|
|
1764
|
+
const C1 = 3432918353;
|
|
1765
|
+
const C2 = 461845907;
|
|
1766
|
+
function murmurhash3_x86_32(input, seed = 0) {
|
|
1767
|
+
const bytes = new TextEncoder().encode(input);
|
|
1768
|
+
return murmurhash3_bytes(bytes, seed);
|
|
1769
|
+
}
|
|
1770
|
+
function murmurhash3_bytes(bytes, seed = 0) {
|
|
1771
|
+
const len = bytes.length;
|
|
1772
|
+
const nBlocks = Math.floor(len / 4);
|
|
1773
|
+
let h1 = seed >>> 0;
|
|
1774
|
+
for (let i = 0; i < nBlocks; i++) {
|
|
1775
|
+
const offset = i * 4;
|
|
1776
|
+
let k12 = bytes[offset] | bytes[offset + 1] << 8 | bytes[offset + 2] << 16 | bytes[offset + 3] << 24;
|
|
1777
|
+
k12 = Math.imul(k12, C1);
|
|
1778
|
+
k12 = k12 << 15 | k12 >>> 17;
|
|
1779
|
+
k12 = Math.imul(k12, C2);
|
|
1780
|
+
h1 ^= k12;
|
|
1781
|
+
h1 = h1 << 13 | h1 >>> 19;
|
|
1782
|
+
h1 = Math.imul(h1, 5) + 3864292196 >>> 0;
|
|
1783
|
+
}
|
|
1784
|
+
const tailStart = nBlocks * 4;
|
|
1785
|
+
let k1 = 0;
|
|
1786
|
+
const tailLen = len - tailStart;
|
|
1787
|
+
if (tailLen === 3) k1 ^= bytes[tailStart + 2] << 16;
|
|
1788
|
+
if (tailLen >= 2) k1 ^= bytes[tailStart + 1] << 8;
|
|
1789
|
+
if (tailLen >= 1) {
|
|
1790
|
+
k1 ^= bytes[tailStart];
|
|
1791
|
+
k1 = Math.imul(k1, C1);
|
|
1792
|
+
k1 = k1 << 15 | k1 >>> 17;
|
|
1793
|
+
k1 = Math.imul(k1, C2);
|
|
1794
|
+
h1 ^= k1;
|
|
1795
|
+
}
|
|
1796
|
+
h1 ^= len;
|
|
1797
|
+
h1 ^= h1 >>> 16;
|
|
1798
|
+
h1 = Math.imul(h1, 2246822507);
|
|
1799
|
+
h1 ^= h1 >>> 13;
|
|
1800
|
+
h1 = Math.imul(h1, 3266489909);
|
|
1801
|
+
h1 ^= h1 >>> 16;
|
|
1802
|
+
return h1 >>> 0;
|
|
1803
|
+
}
|
|
1804
|
+
function base64ToBytes(b64) {
|
|
1805
|
+
if (typeof atob !== "undefined") {
|
|
1806
|
+
const bin = atob(b64);
|
|
1807
|
+
const out = new Uint8Array(bin.length);
|
|
1808
|
+
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
1809
|
+
return out;
|
|
1810
|
+
}
|
|
1811
|
+
const B = globalThis.Buffer;
|
|
1812
|
+
if (B && typeof B.from === "function") {
|
|
1813
|
+
const buf = B.from(b64, "base64");
|
|
1814
|
+
return new Uint8Array(buf);
|
|
1815
|
+
}
|
|
1816
|
+
throw new Error("No base64 decoder available (neither atob nor Buffer)");
|
|
1817
|
+
}
|
|
1818
|
+
class BloomFilter {
|
|
1819
|
+
constructor(buf, params) {
|
|
1820
|
+
this.params = params;
|
|
1821
|
+
if ((params.m & params.m - 1) !== 0) {
|
|
1822
|
+
throw new Error(`Bloom filter m must be a power of 2, got ${params.m}`);
|
|
1823
|
+
}
|
|
1824
|
+
if (buf.length !== params.m >> 3) {
|
|
1825
|
+
throw new Error(
|
|
1826
|
+
`Bloom filter buffer size mismatch: expected ${params.m >> 3} bytes, got ${buf.length}`
|
|
1827
|
+
);
|
|
1828
|
+
}
|
|
1829
|
+
this.buf = buf;
|
|
1830
|
+
this.mask = params.m - 1;
|
|
1831
|
+
}
|
|
1832
|
+
/** Build from the wire format (base64 bytes + explicit params). */
|
|
1833
|
+
static fromBase64(bloomB64, params) {
|
|
1834
|
+
const bytes = base64ToBytes(bloomB64);
|
|
1835
|
+
return new BloomFilter(bytes, params);
|
|
1836
|
+
}
|
|
1837
|
+
/**
|
|
1838
|
+
* Returns true if `name` is probably in the set — possibly with the
|
|
1839
|
+
* filter's configured false-positive rate. FALSE is always authoritative.
|
|
1840
|
+
*
|
|
1841
|
+
* FP here means: SDK thinks a name is already registered when it isn't.
|
|
1842
|
+
* That costs one wasted server round-trip (gateway does the exact check
|
|
1843
|
+
* and catches it) — strictly safer than a false-negative, which could
|
|
1844
|
+
* leak a novel name past the SDK.
|
|
1845
|
+
*/
|
|
1846
|
+
has(name) {
|
|
1847
|
+
const h1 = murmurhash3_x86_32(name, this.params.seedA);
|
|
1848
|
+
const h2 = murmurhash3_x86_32(name, this.params.seedB);
|
|
1849
|
+
for (let i = 0; i < this.params.k; i++) {
|
|
1850
|
+
const combined = h1 + Math.imul(i, h2) >>> 0;
|
|
1851
|
+
const idx = combined & this.mask;
|
|
1852
|
+
const bit = this.buf[idx >> 3] & 1 << (idx & 7);
|
|
1853
|
+
if (bit === 0) return false;
|
|
1854
|
+
}
|
|
1855
|
+
return true;
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
const SUPPORTED_ALGO = "mmh3_x86_32_km";
|
|
1859
|
+
function warnOncePerSession(message) {
|
|
1860
|
+
if (typeof console === "undefined" || typeof console.warn !== "function") return;
|
|
1861
|
+
console.warn(message);
|
|
1862
|
+
}
|
|
1863
|
+
class NameGovernor {
|
|
1864
|
+
constructor() {
|
|
1865
|
+
this.bloom = null;
|
|
1866
|
+
this.remainingNewNames = Infinity;
|
|
1867
|
+
this.graceActive = false;
|
|
1868
|
+
this.localNovelNames = /* @__PURE__ */ new Set();
|
|
1869
|
+
this.droppedSinceLastReport = /* @__PURE__ */ new Map();
|
|
1870
|
+
this.reportWindowStart = Date.now();
|
|
1871
|
+
this.hasWarnedThisSession = false;
|
|
1872
|
+
}
|
|
1873
|
+
/**
|
|
1874
|
+
* Ingest a freshly-bootstrapped hint. Call on every successful bootstrap.
|
|
1875
|
+
* Passing `null` disables governance (fail-open).
|
|
1876
|
+
*/
|
|
1877
|
+
ingestHint(hint) {
|
|
1878
|
+
if (!hint || hint.bloom_algo !== SUPPORTED_ALGO) {
|
|
1879
|
+
this.bloom = null;
|
|
1880
|
+
this.remainingNewNames = Infinity;
|
|
1881
|
+
this.graceActive = false;
|
|
1882
|
+
this.localNovelNames.clear();
|
|
1883
|
+
return;
|
|
1884
|
+
}
|
|
1885
|
+
try {
|
|
1886
|
+
this.bloom = BloomFilter.fromBase64(hint.bloom_b64, {
|
|
1887
|
+
m: hint.m,
|
|
1888
|
+
k: hint.k,
|
|
1889
|
+
seedA: hint.seed_a,
|
|
1890
|
+
seedB: hint.seed_b
|
|
1891
|
+
});
|
|
1892
|
+
} catch {
|
|
1893
|
+
this.bloom = null;
|
|
1894
|
+
}
|
|
1895
|
+
this.remainingNewNames = hint.remaining_new_names ?? Infinity;
|
|
1896
|
+
this.graceActive = hint.grace_active === true;
|
|
1897
|
+
this.localNovelNames.clear();
|
|
1898
|
+
}
|
|
1899
|
+
/**
|
|
1900
|
+
* Decide whether a `track()` call should proceed.
|
|
1901
|
+
*
|
|
1902
|
+
* Returns true = send to network (rate-limiter still runs after).
|
|
1903
|
+
* Returns false = drop locally; caller should return early.
|
|
1904
|
+
*/
|
|
1905
|
+
shouldSend(eventName) {
|
|
1906
|
+
if (!this.bloom) return true;
|
|
1907
|
+
if (this.graceActive) return true;
|
|
1908
|
+
if (this.bloom.has(eventName)) return true;
|
|
1909
|
+
if (this.localNovelNames.has(eventName)) return true;
|
|
1910
|
+
if (this.remainingNewNames > 0) {
|
|
1911
|
+
this.localNovelNames.add(eventName);
|
|
1912
|
+
this.remainingNewNames -= 1;
|
|
1913
|
+
return true;
|
|
1914
|
+
}
|
|
1915
|
+
const prev = this.droppedSinceLastReport.get(eventName) ?? 0;
|
|
1916
|
+
this.droppedSinceLastReport.set(eventName, prev + 1);
|
|
1917
|
+
if (!this.hasWarnedThisSession) {
|
|
1918
|
+
this.hasWarnedThisSession = true;
|
|
1919
|
+
warnOncePerSession(
|
|
1920
|
+
`[aegis] Event-name cap reached — "${eventName}" dropped locally. Upgrade your plan or remove dynamically-generated event names.`
|
|
1921
|
+
);
|
|
1922
|
+
}
|
|
1923
|
+
return false;
|
|
1924
|
+
}
|
|
1925
|
+
/**
|
|
1926
|
+
* Snapshot + reset the dropped-names counter. Called by the telemetry
|
|
1927
|
+
* beacon on batch flush so the gateway gets visibility into client-side
|
|
1928
|
+
* drops for ops dashboards.
|
|
1929
|
+
*/
|
|
1930
|
+
drainDropReport() {
|
|
1931
|
+
if (this.droppedSinceLastReport.size === 0) return null;
|
|
1932
|
+
const events = {};
|
|
1933
|
+
let total = 0;
|
|
1934
|
+
for (const [name, count] of this.droppedSinceLastReport) {
|
|
1935
|
+
events[name] = count;
|
|
1936
|
+
total += count;
|
|
1937
|
+
}
|
|
1938
|
+
const since = this.reportWindowStart;
|
|
1939
|
+
this.droppedSinceLastReport.clear();
|
|
1940
|
+
this.reportWindowStart = Date.now();
|
|
1941
|
+
return { events, total, since };
|
|
1942
|
+
}
|
|
1943
|
+
// Test-only accessors — not part of public API but easier than reflection.
|
|
1944
|
+
/** @internal */
|
|
1945
|
+
_debugState() {
|
|
1946
|
+
return {
|
|
1947
|
+
hasBloom: this.bloom !== null,
|
|
1948
|
+
remaining: this.remainingNewNames,
|
|
1949
|
+
localNovel: this.localNovelNames.size,
|
|
1950
|
+
graceActive: this.graceActive
|
|
1951
|
+
};
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1764
1954
|
class Aegis {
|
|
1765
1955
|
constructor() {
|
|
1766
1956
|
this.config = null;
|
|
@@ -1772,6 +1962,7 @@ class Aegis {
|
|
|
1772
1962
|
this.consent = null;
|
|
1773
1963
|
this._ecommerce = null;
|
|
1774
1964
|
this.rateLimiter = null;
|
|
1965
|
+
this.nameGovernor = new NameGovernor();
|
|
1775
1966
|
this._lastPageUrl = null;
|
|
1776
1967
|
this._popstateHandler = null;
|
|
1777
1968
|
this._originalPushState = null;
|
|
@@ -2031,6 +2222,13 @@ class Aegis {
|
|
|
2031
2222
|
}
|
|
2032
2223
|
}
|
|
2033
2224
|
}
|
|
2225
|
+
if (event.type === "track") {
|
|
2226
|
+
const trackEvent = event;
|
|
2227
|
+
const isMetaEvent = typeof trackEvent.event === "string" && trackEvent.event.startsWith("aegis.client.");
|
|
2228
|
+
if (!isMetaEvent && !this.nameGovernor.shouldSend(trackEvent.event)) {
|
|
2229
|
+
return;
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2034
2232
|
if (this.rateLimiter) {
|
|
2035
2233
|
const eventLabel = event.type === "track" && event.event ? event.event : event.type;
|
|
2036
2234
|
const isMetaEvent = event.type === "track" && typeof event.event === "string" && event.event.startsWith("aegis.client.");
|
|
@@ -2038,6 +2236,21 @@ class Aegis {
|
|
|
2038
2236
|
return;
|
|
2039
2237
|
}
|
|
2040
2238
|
}
|
|
2239
|
+
const ctx = event.context;
|
|
2240
|
+
const props = event.properties;
|
|
2241
|
+
if ((ctx == null ? void 0 : ctx.campaign) && props && !props.campaign_id) {
|
|
2242
|
+
const utm = ctx.campaign;
|
|
2243
|
+
if (utm.campaign) props.campaign_id = utm.campaign;
|
|
2244
|
+
if (utm.source) props.campaign_source = utm.source;
|
|
2245
|
+
if (utm.medium) props.campaign_medium = utm.medium;
|
|
2246
|
+
if (utm.content) props.campaign_content = utm.content;
|
|
2247
|
+
if (utm.term) props.campaign_term = utm.term;
|
|
2248
|
+
}
|
|
2249
|
+
if ((ctx == null ? void 0 : ctx.adClickIDs) && props) {
|
|
2250
|
+
const clicks = ctx.adClickIDs;
|
|
2251
|
+
const clickId = clicks.gclid || clicks.fbclid || clicks.ctwa_clid || clicks.msclkid || clicks.ttclid;
|
|
2252
|
+
if (clickId && !props.campaign_click_id) props.campaign_click_id = clickId;
|
|
2253
|
+
}
|
|
2041
2254
|
logger.debug("Capturing event:", event);
|
|
2042
2255
|
const transformedEvent = await this.plugins.executeHookChain(
|
|
2043
2256
|
"beforeEventCapture",
|
|
@@ -2081,8 +2294,59 @@ class Aegis {
|
|
|
2081
2294
|
this.identity.reset();
|
|
2082
2295
|
logger.info("User identity reset");
|
|
2083
2296
|
}
|
|
2297
|
+
/**
|
|
2298
|
+
* Ingest the event-governance hint returned from `/v1/sdk/bootstrap`.
|
|
2299
|
+
*
|
|
2300
|
+
* Callers (Shopify pixel, snippet, react integration) run `bootstrap()`
|
|
2301
|
+
* themselves and pass the resulting `eventGovernance` field here so the
|
|
2302
|
+
* SDK can self-throttle novel event names before they hit the gateway.
|
|
2303
|
+
*
|
|
2304
|
+
* Passing null/undefined disables governance (Enterprise plan / outage
|
|
2305
|
+
* fail-open). Safe to call before `init()` — the hint is stored and
|
|
2306
|
+
* applied as soon as init completes.
|
|
2307
|
+
*/
|
|
2308
|
+
ingestGovernanceHint(hint) {
|
|
2309
|
+
this.nameGovernor.ingestHint(hint ?? null);
|
|
2310
|
+
logger.debug("Governance hint ingested", hint ? {
|
|
2311
|
+
k: hint.k,
|
|
2312
|
+
m: hint.m,
|
|
2313
|
+
remaining: hint.remaining_new_names
|
|
2314
|
+
} : "disabled");
|
|
2315
|
+
}
|
|
2316
|
+
/**
|
|
2317
|
+
* Drain the client-side drop counter and emit a meta-event. Called
|
|
2318
|
+
* periodically by the queue flush path so ops dashboards see novel-name
|
|
2319
|
+
* amplification patterns in near-real-time.
|
|
2320
|
+
*/
|
|
2321
|
+
emitGovernanceDropMeta() {
|
|
2322
|
+
var _a, _b;
|
|
2323
|
+
if (!((_a = this.config) == null ? void 0 : _a.initialized) || !this.session || !this.identity) return;
|
|
2324
|
+
const report = this.nameGovernor.drainDropReport();
|
|
2325
|
+
if (!report) return;
|
|
2326
|
+
const event = {
|
|
2327
|
+
type: "track",
|
|
2328
|
+
event: "aegis.client.name_governor_dropped",
|
|
2329
|
+
properties: {
|
|
2330
|
+
dropped_count: report.total,
|
|
2331
|
+
distinct_names: Object.keys(report.events).length,
|
|
2332
|
+
// Cap the payload — a runaway loop could produce thousands of names;
|
|
2333
|
+
// we ship the top 10 for diagnostics and the total for counting.
|
|
2334
|
+
sample_names: Object.entries(report.events).sort((a, b) => b[1] - a[1]).slice(0, 10).map(([name, count]) => ({ name, count })),
|
|
2335
|
+
window_start: report.since,
|
|
2336
|
+
window_end: Date.now()
|
|
2337
|
+
},
|
|
2338
|
+
messageId: generateMessageId(),
|
|
2339
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2340
|
+
anonymousId: this.identity.getAnonymousId(),
|
|
2341
|
+
userId: this.identity.getUserId() || void 0,
|
|
2342
|
+
sessionId: this.session.getSessionId(),
|
|
2343
|
+
context: buildContext(this.config, this.session)
|
|
2344
|
+
};
|
|
2345
|
+
(_b = this.queue) == null ? void 0 : _b.push(event);
|
|
2346
|
+
}
|
|
2084
2347
|
async flush() {
|
|
2085
2348
|
if (!this.assertInitialized()) return;
|
|
2349
|
+
this.emitGovernanceDropMeta();
|
|
2086
2350
|
await this.queue.flush();
|
|
2087
2351
|
}
|
|
2088
2352
|
getAnonymousId() {
|
|
@@ -2214,8 +2478,11 @@ class Aegis {
|
|
|
2214
2478
|
}
|
|
2215
2479
|
export {
|
|
2216
2480
|
Aegis as A,
|
|
2481
|
+
BloomFilter as B,
|
|
2217
2482
|
EcommerceTracker as E,
|
|
2483
|
+
NameGovernor as N,
|
|
2218
2484
|
RateLimiter as R,
|
|
2219
|
-
logger as l
|
|
2485
|
+
logger as l,
|
|
2486
|
+
murmurhash3_x86_32 as m
|
|
2220
2487
|
};
|
|
2221
|
-
//# sourceMappingURL=analytics-
|
|
2488
|
+
//# sourceMappingURL=analytics-C00PJUSy.mjs.map
|