You're Invited:Meet the Socket Team at RSAC and BSidesSF 2026, March 23–26.RSVP
Socket
Book a DemoSign in
Socket

@topsort/analytics.js

Package Overview
Dependencies
Maintainers
2
Versions
15
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@topsort/analytics.js - npm Package Compare versions

Comparing version
2.0.0
to
2.1.0
+1
dist/src/detector.change-attrs.test.d.ts
export {};
export type EventType = "Click" | "Purchase" | "Impression";
interface Placement {
path: string;
}
export interface Entity {
type: "product";
id: string;
}
interface Impression {
resolvedBidId?: string;
entity?: Entity;
placement: Placement;
occurredAt: string;
opaqueUserId: string;
id: string;
}
interface Click {
resolvedBidId?: string;
entity?: Entity;
placement: Placement;
occurredAt: string;
opaqueUserId: string;
id: string;
}
interface Item {
productId: string;
quantity: number;
unitPrice: number;
}
interface Purchase {
occurredAt: string;
opaqueUserId: string;
id: string;
items: Item[];
}
export interface TopsortEvent {
impressions?: Impression[];
clicks?: Click[];
purchases?: Purchase[];
}
export {};
export interface ProcessorResult {
done: Set<string>;
retry: Set<string>;
}
export interface Entry {
id: string;
t: number;
}
export type Processor<T extends Entry> = (data: T[]) => Promise<ProcessorResult>;
export declare class Queue<T extends Entry> {
private _store;
private _processing;
private _processor;
private _scheduled;
constructor(processor: Processor<T>);
append(entry: Entry, opts?: {
highPriority: boolean;
}): void;
private _processNow;
private _setEntries;
private _scheduleProcessing;
}
import type { TopsortEvent } from "./events";
interface Config {
token: string;
url?: string;
}
export declare function reportEvent(e: TopsortEvent, config: Config): Promise<{
ok: boolean;
retry: boolean;
}>;
export {};
export interface Store<T> {
get(): T[];
set(value: T[]): void;
}
export declare class MemoryStore<T> implements Store<T> {
private _data;
constructor();
get(): T[];
set(data: T[]): void;
}
export declare class LocalStorageStore<T> implements Store<T> {
private _key;
private _storage;
constructor(key: string);
get(): T[];
set(data: T[]): void;
}
class I {
constructor() {
this._data = [];
}
get() {
return this._data;
}
set(e) {
this._data = e;
}
}
class g {
constructor(e) {
this._key = e, this._storage = window.localStorage;
}
get() {
const e = this._storage.getItem(this._key);
return e ? JSON.parse(e) : [];
}
set(e) {
this._storage.setItem(this._key, JSON.stringify(e));
}
}
const O = "ts-t", M = "ts-q", N = 250, v = 3, P = 25, A = 250, m = 9, R = 0;
function L(t, e) {
return e > 0 ? t + (Math.random() + Math.pow(2, e)) * 1e3 : 0;
}
function U() {
var e;
const t = new g(O);
try {
const s = "3";
if (t.set([{ x: s }]), ((e = t.get()[0]) == null ? void 0 : e.x) === s)
return new g(M);
} catch {
}
return new I();
}
class C {
constructor(e) {
this._store = U(), this._processing = /* @__PURE__ */ new Set(), this._scheduled = !1, this._processor = e;
}
append(e, s) {
let o = this._store.get();
o.push({
e,
r: 0,
p: s != null && s.highPriority ? m : R
}), o = o.slice(-N), this._setEntries(o);
}
async _processNow(e) {
if (!e.length)
return;
const s = [];
for (let i = e.length - 1; i >= 0 && s.length < P; i--) {
const c = e[i], d = c == null ? void 0 : c.e;
d && !this._processing.has(d.id) && L(d.t, c.r) <= Date.now() && (s.push(d), this._processing.add(d.id));
}
if (!s.length) {
this._scheduleProcessing();
return;
}
let o = { done: /* @__PURE__ */ new Set(), retry: /* @__PURE__ */ new Set() };
try {
o = await this._processor(s);
} catch {
for (const c of s)
o.done.add(c.id);
}
const n = [];
for (const i of this._processing)
s.find((c) => c.id === i) || n.push(i);
this._processing = new Set(n);
const r = this._store.get(), p = [];
for (const i of r)
o.done.has(i.e.id) || (o.retry.has(i.e.id) ? i.r < v && (i.r += 1, p.push(i)) : p.push(i));
this._setEntries(p);
}
_setEntries(e) {
this._store.set(e), e.length && (e.some((s) => s.p === m) || this._store instanceof I ? this._processNow(e) : this._scheduleProcessing());
}
_scheduleProcessing() {
this._scheduled || (this._scheduled = !0, setTimeout(() => {
this._scheduled = !1, this._processNow(this._store.get());
}, A));
}
}
const q = "2.1.0";
async function x(t, e) {
try {
const s = (e.url || "https://api.topsort.com") + "/v2/events", o = await fetch(s, {
method: "POST",
headers: {
"Content-Type": "application/json",
// Can't use User-Agent header because of
// https://bugs.chromium.org/p/chromium/issues/detail?id=571722
"X-UA": `ts.js/${q}`,
Authorization: "Bearer " + e.token
},
body: JSON.stringify(t),
// This parameter ensures in most browsers that the request is performed even in case the browser navigates to another page.
keepalive: !0
});
return { ok: o.ok, retry: o.status === 429 || o.status >= 500 };
} catch {
return { ok: !1, retry: !0 };
}
}
const _ = 2500, H = 0.5;
let a = /* @__PURE__ */ new Set();
function E() {
var t, e;
return ((e = (t = window.URL).createObjectURL) == null ? void 0 : e.call(t, new Blob()).split("/").pop()) || Math.random() + "";
}
let u;
function w() {
if (u)
return u;
const t = B();
if (t)
return u = t, t;
const e = E();
return y(e), e;
}
function y(t) {
const e = window.TS.cookieName || "tsuid";
u = t, document.cookie = e + "=" + t + ";max-age=31536000";
}
function D() {
return u = void 0, document.cookie = "tsuid=", w();
}
window.TS.setUserId = y;
window.TS.getUserId = w;
window.TS.resetUserId = D;
function B() {
var s;
const t = window.TS.cookieName || "tsuid";
return (s = new RegExp("(^|;)\\s*" + t + "\\s*=\\s*([^;]+)").exec(document.cookie)) == null ? void 0 : s.pop();
}
function X(t) {
const e = t.type, s = {
path: t.page
};
let o;
t.product && (o = {
type: "product",
id: t.product
});
const n = new Date(t.t).toISOString();
switch (e) {
case "Click":
return {
clicks: [
{
resolvedBidId: t.bid,
entity: o,
placement: s,
occurredAt: n,
opaqueUserId: t.uid,
id: t.id
}
]
};
case "Impression":
return {
impressions: [
{
resolvedBidId: t.bid,
entity: o,
placement: s,
occurredAt: n,
opaqueUserId: t.uid,
id: t.id
}
]
};
case "Purchase":
return {
purchases: [
{
occurredAt: n,
opaqueUserId: t.uid,
items: (t.items || []).map((r) => ({
productId: r.product,
quantity: r.quantity,
unitPrice: r.price
})),
id: t.id
}
]
};
}
}
async function j(t) {
const e = {
done: /* @__PURE__ */ new Set(),
retry: /* @__PURE__ */ new Set()
}, s = [];
for (const o of t)
s.push(
x(X(o), window.TS).then((n) => {
(n.retry ? e.retry : e.done).add(o.id);
}).catch(() => {
e.done.add(o.id);
})
);
return await Promise.all(s), e;
}
const z = new C(j);
function l(t, e) {
const s = J(t);
if (a.has(s))
return;
if (a.add(s), a.size > _) {
const n = a.values();
for (let r = 0; r < a.size - _; --r)
n.next();
a = new Set(n);
}
z.append(t);
const o = new CustomEvent("topsort", { bubbles: !0, detail: t });
e.dispatchEvent(o);
}
function J(t) {
return [t.page, t.type, t.product, t.bid].join("-");
}
function Y() {
const t = window.location, e = t.hash;
return e[1] === "/" ? e : t.pathname;
}
function h(t, e) {
const s = e.dataset.tsProduct, o = e.dataset.tsResolvedBid, n = {
type: t,
product: s,
bid: o,
t: Date.now(),
page: Y(),
id: E(),
uid: w()
};
return t === "Purchase" && (n.items = JSON.parse(e.dataset.tsItems || "[]")), n;
}
function G(t) {
if (!(t.currentTarget instanceof HTMLElement))
return;
const e = t.currentTarget.closest(T);
e && e instanceof HTMLElement && l(h("Click", e), e);
}
const f = window.IntersectionObserver ? new IntersectionObserver(
(t) => {
for (const e of t)
if (e.isIntersecting) {
const s = e.target;
s instanceof HTMLElement && (l(h("Impression", s), s), f && f.unobserve(s));
}
},
{
threshold: H
}
) : void 0, T = "[data-ts-product],[data-ts-action],[data-ts-items],[data-ts-resolved-bid]";
function K(t) {
const e = t.querySelectorAll("[data-ts-clickable]");
(e.length === 0 ? [t] : e).forEach((o) => o.addEventListener("click", G));
}
function b(t) {
Z(t) ? l(h("Purchase", t), t) : (f ? f.observe(t) : l(h("Impression", t), t), K(t));
}
function k(t) {
const e = t.querySelectorAll(T);
for (let s = 0; s < e.length; s++) {
const o = e[s];
o instanceof HTMLElement && b(o);
}
}
function Z(t) {
return t.dataset.tsAction === "purchase";
}
function W(t) {
for (const e of t)
if (e.type === "childList") {
const s = /* @__PURE__ */ new Set();
for (let o = 0; o < e.addedNodes.length; o++) {
const n = e.addedNodes[o];
if ((n == null ? void 0 : n.nodeType) === Node.ELEMENT_NODE) {
const r = n.parentElement;
r && !s.has(r) && s.add(r);
}
for (const r of s)
k(r);
}
} else if (e.type === "attributes") {
if (!(e.target instanceof HTMLElement))
continue;
b(e.target);
}
}
function S() {
var s, o;
if ((s = window.TS) != null && s.loaded)
return;
if (window.TS.loaded = !0, !((o = window.TS) != null && o.token)) {
console.error("Missing TS token");
return;
}
k(document);
const t = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
new t(W).observe(document, {
attributes: !0,
childList: !0,
subtree: !0,
attributeFilter: [
"data-ts-product",
"data-ts-action",
"data-ts-items",
"data-ts-resolved-bid"
]
});
}
/complete|interactive|loaded/.test(document.readyState) ? S() : window.addEventListener("DOMContentLoaded", S);
+22
-2

@@ -9,12 +9,31 @@ # Changelog

## Unreleased
## Version 2.1.0 (2023-08-30)
### Added
- Use vite/esbuild to compile libraries
- Enabled coverage for tests
- Increase test coverage for reporter.ts
- Export ESM version of the library
- Export types
### Changed
- Use React 18 features in end to end tests
### Refactor
- Replaced `npm` with `pnpm`
- Replaced `jest` ecosystem with `vitest`
- Replace `nock`/`isomorphic-fetch` with `msw`
## Version 2.0.0 (2023-07-13)
### Changed
- Remove usages of `data-ts-auction` as that only works when using API v1
- When specifying purchases, the price must now be specified as a float in the marketplace currency (i.e. USD) instead of cents.
### Refactor
### Refactor
- Use [Events API v2](https://docs.topsort.com/reference/reportevents-2)

@@ -25,3 +44,4 @@

### Fixed
- Fix parsing of user cookie
([#182](https://github.com/Topsort/analytics.js/pull/182))
+1
-1

@@ -1,1 +0,1 @@

(function(window,document,Math,JSON,undefined,HTMLElement){(()=>{"use strict";var __webpack_modules__={800:()=>{eval('\n;// CONCATENATED MODULE: ./node_modules/tslib/tslib.es6.js\n/*! *****************************************************************************\r\nCopyright (c) Microsoft Corporation.\r\n\r\nPermission to use, copy, modify, and/or distribute this software for any\r\npurpose with or without fee is hereby granted.\r\n\r\nTHE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\r\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\r\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\r\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\r\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\r\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\r\nPERFORMANCE OF THIS SOFTWARE.\r\n***************************************************************************** */\r\n/* global Reflect, Promise */\r\n\r\nvar extendStatics = function(d, b) {\r\n extendStatics = Object.setPrototypeOf ||\r\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\r\n function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };\r\n return extendStatics(d, b);\r\n};\r\n\r\nfunction __extends(d, b) {\r\n extendStatics(d, b);\r\n function __() { this.constructor = d; }\r\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\r\n}\r\n\r\nvar __assign = function() {\r\n __assign = Object.assign || function __assign(t) {\r\n for (var s, i = 1, n = arguments.length; i < n; i++) {\r\n s = arguments[i];\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\r\n }\r\n return t;\r\n }\r\n return __assign.apply(this, arguments);\r\n}\r\n\r\nfunction __rest(s, e) {\r\n var t = {};\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\r\n t[p] = s[p];\r\n if (s != null && typeof Object.getOwnPropertySymbols === "function")\r\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\r\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\r\n t[p[i]] = s[p[i]];\r\n }\r\n return t;\r\n}\r\n\r\nfunction __decorate(decorators, target, key, desc) {\r\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\r\n if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);\r\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\r\n return c > 3 && r && Object.defineProperty(target, key, r), r;\r\n}\r\n\r\nfunction __param(paramIndex, decorator) {\r\n return function (target, key) { decorator(target, key, paramIndex); }\r\n}\r\n\r\nfunction __metadata(metadataKey, metadataValue) {\r\n if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue);\r\n}\r\n\r\nfunction __awaiter(thisArg, _arguments, P, generator) {\r\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\r\n return new (P || (P = Promise))(function (resolve, reject) {\r\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\r\n function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }\r\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\r\n step((generator = generator.apply(thisArg, _arguments || [])).next());\r\n });\r\n}\r\n\r\nfunction __generator(thisArg, body) {\r\n var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;\r\n return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;\r\n function verb(n) { return function (v) { return step([n, v]); }; }\r\n function step(op) {\r\n if (f) throw new TypeError("Generator is already executing.");\r\n while (_) try {\r\n if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\r\n if (y = 0, t) op = [op[0] & 2, t.value];\r\n switch (op[0]) {\r\n case 0: case 1: t = op; break;\r\n case 4: _.label++; return { value: op[1], done: false };\r\n case 5: _.label++; y = op[1]; op = [0]; continue;\r\n case 7: op = _.ops.pop(); _.trys.pop(); continue;\r\n default:\r\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\r\n if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\r\n if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\r\n if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\r\n if (t[2]) _.ops.pop();\r\n _.trys.pop(); continue;\r\n }\r\n op = body.call(thisArg, _);\r\n } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\r\n if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\r\n }\r\n}\r\n\r\nfunction __createBinding(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n o[k2] = m[k];\r\n}\r\n\r\nfunction __exportStar(m, exports) {\r\n for (var p in m) if (p !== "default" && !exports.hasOwnProperty(p)) exports[p] = m[p];\r\n}\r\n\r\nfunction __values(o) {\r\n var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;\r\n if (m) return m.call(o);\r\n if (o && typeof o.length === "number") return {\r\n next: function () {\r\n if (o && i >= o.length) o = void 0;\r\n return { value: o && o[i++], done: !o };\r\n }\r\n };\r\n throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");\r\n}\r\n\r\nfunction __read(o, n) {\r\n var m = typeof Symbol === "function" && o[Symbol.iterator];\r\n if (!m) return o;\r\n var i = m.call(o), r, ar = [], e;\r\n try {\r\n while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\r\n }\r\n catch (error) { e = { error: error }; }\r\n finally {\r\n try {\r\n if (r && !r.done && (m = i["return"])) m.call(i);\r\n }\r\n finally { if (e) throw e.error; }\r\n }\r\n return ar;\r\n}\r\n\r\nfunction __spread() {\r\n for (var ar = [], i = 0; i < arguments.length; i++)\r\n ar = ar.concat(__read(arguments[i]));\r\n return ar;\r\n}\r\n\r\nfunction __spreadArrays() {\r\n for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;\r\n for (var r = Array(s), k = 0, i = 0; i < il; i++)\r\n for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)\r\n r[k] = a[j];\r\n return r;\r\n};\r\n\r\nfunction __await(v) {\r\n return this instanceof __await ? (this.v = v, this) : new __await(v);\r\n}\r\n\r\nfunction __asyncGenerator(thisArg, _arguments, generator) {\r\n if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");\r\n var g = generator.apply(thisArg, _arguments || []), i, q = [];\r\n return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i;\r\n function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }\r\n function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\r\n function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }\r\n function fulfill(value) { resume("next", value); }\r\n function reject(value) { resume("throw", value); }\r\n function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\r\n}\r\n\r\nfunction __asyncDelegator(o) {\r\n var i, p;\r\n return i = {}, verb("next"), verb("throw", function (e) { throw e; }), verb("return"), i[Symbol.iterator] = function () { return this; }, i;\r\n function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === "return" } : f ? f(v) : v; } : f; }\r\n}\r\n\r\nfunction __asyncValues(o) {\r\n if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");\r\n var m = o[Symbol.asyncIterator], i;\r\n return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);\r\n function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }\r\n function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\r\n}\r\n\r\nfunction __makeTemplateObject(cooked, raw) {\r\n if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }\r\n return cooked;\r\n};\r\n\r\nfunction __importStar(mod) {\r\n if (mod && mod.__esModule) return mod;\r\n var result = {};\r\n if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];\r\n result.default = mod;\r\n return result;\r\n}\r\n\r\nfunction __importDefault(mod) {\r\n return (mod && mod.__esModule) ? mod : { default: mod };\r\n}\r\n\r\nfunction __classPrivateFieldGet(receiver, privateMap) {\r\n if (!privateMap.has(receiver)) {\r\n throw new TypeError("attempted to get private field on non-instance");\r\n }\r\n return privateMap.get(receiver);\r\n}\r\n\r\nfunction __classPrivateFieldSet(receiver, privateMap, value) {\r\n if (!privateMap.has(receiver)) {\r\n throw new TypeError("attempted to set private field on non-instance");\r\n }\r\n privateMap.set(receiver, value);\r\n return value;\r\n}\r\n\n;// CONCATENATED MODULE: ./src/store.ts\nclass MemoryStore {\n constructor() {\n this._data = [];\n }\n get() {\n return this._data;\n }\n set(data) {\n this._data = data;\n }\n}\nclass LocalStorageStore {\n constructor(key) {\n this._key = key;\n this._storage = window.localStorage;\n }\n get() {\n const data = this._storage.getItem(this._key);\n return data ? JSON.parse(data) : [];\n }\n set(data) {\n this._storage.setItem(this._key, JSON.stringify(data));\n }\n}\n\n;// CONCATENATED MODULE: ./src/queue.ts\n\n\nconst STORAGE_TEST_KEY = "ts-t";\nconst STORAGE_KEY = "ts-q";\nconst MAX_SIZE = 250;\nconst MAX_RETRIES = 3;\nconst MAX_PROCESSING_SIZE = 25;\nconst WAIT_TIME_MS = 250;\nconst PRIORITY_MAX = 9;\nconst PRIORITY_MIN = 0;\nfunction expBackoff(startTime, retries) {\n if (retries > 0) {\n return startTime + (Math.random() + Math.pow(2, retries)) * 1000;\n }\n // This could be just startTime, but we chose to return 0, just to ensure\n // that the event is always added the first time. Even if the clock changed.\n return 0;\n}\n/**\n * Probes the different store implementations looking for one that works in the current browser.\n *\n * To do so, it checks {@link LocalStorageStore} is actually functional by adding a dummy object and\n * checking the retrived result is the same.\n *\n * Note: there\'s nothing special about the id, but it need to be ideally a length one string, in\n * order to minimize the JS library size.\n */\nfunction getStore() {\n const localStore = new LocalStorageStore(STORAGE_TEST_KEY);\n try {\n const id = "3";\n localStore.set([{ x: id }]);\n const r = localStore.get();\n if (r[0].x === id) {\n return new LocalStorageStore(STORAGE_KEY);\n }\n }\n catch (error) { }\n return new MemoryStore();\n}\nclass Queue {\n constructor(processor) {\n this._store = getStore();\n this._processing = new Set();\n this._scheduled = false;\n this._processor = processor;\n }\n append(entry, opts) {\n let entries = this._store.get();\n entries.push({\n e: entry,\n r: 0,\n p: (opts === null || opts === void 0 ? void 0 : opts.highPriority) ? PRIORITY_MAX : PRIORITY_MIN,\n });\n // TODO: allow custom trim, so that clients can control which entries get dropped\n entries = entries.slice(-MAX_SIZE);\n this._setEntries(entries);\n }\n _processNow(entries) {\n return __awaiter(this, void 0, void 0, function* () {\n if (!entries.length) {\n return;\n }\n const chunk = [];\n for (let i = entries.length - 1; i >= 0 && chunk.length < MAX_PROCESSING_SIZE; i--) {\n const retryableEntry = entries[i];\n const entry = retryableEntry.e;\n if (!this._processing.has(entry.id) &&\n expBackoff(entry.t, retryableEntry.r) <= Date.now()) {\n chunk.push(entry);\n this._processing.add(entry.id);\n }\n }\n if (!chunk.length) {\n this._scheduleProcessing();\n return;\n }\n let r = { done: new Set(), retry: new Set() };\n try {\n r = yield this._processor(chunk);\n }\n catch (error) {\n // Mark all as failed\n for (const entry of chunk) {\n r.done.add(entry.id);\n }\n }\n // Remove entries from processing\n const newProcessing = [];\n for (const entryId of this._processing) {\n if (!chunk.find((e) => e.id === entryId)) {\n newProcessing.push(entryId);\n }\n }\n this._processing = new Set(newProcessing);\n // Modify entries\n const oldEntries = this._store.get();\n const newEntries = [];\n for (const entry of oldEntries) {\n if (r.done.has(entry.e.id)) {\n // do nothing\n }\n else if (r.retry.has(entry.e.id)) {\n if (entry.r < MAX_RETRIES) {\n entry.r += 1;\n newEntries.push(entry);\n }\n }\n else {\n newEntries.push(entry);\n }\n }\n this._setEntries(newEntries);\n });\n }\n _setEntries(entries) {\n this._store.set(entries);\n if (!entries.length) {\n return;\n }\n if (entries.some((e) => e.p === PRIORITY_MAX) ||\n this._store instanceof MemoryStore) {\n this._processNow(entries);\n }\n else {\n this._scheduleProcessing();\n }\n }\n _scheduleProcessing() {\n if (this._scheduled)\n return;\n this._scheduled = true;\n setTimeout(() => {\n this._scheduled = false;\n this._processNow(this._store.get());\n }, WAIT_TIME_MS);\n }\n}\n\n;// CONCATENATED MODULE: ./src/version.ts\nconst version = "2.0.0";\n\n;// CONCATENATED MODULE: ./src/reporter.ts\n\n\nfunction reportEvent(e, config) {\n return __awaiter(this, void 0, void 0, function* () {\n try {\n const url = (config.url || "https://api.topsort.com") + "/v2/events";\n const r = yield fetch(url, {\n method: "POST",\n headers: {\n "Content-Type": "application/json",\n // Can\'t use User-Agent header because of\n // https://bugs.chromium.org/p/chromium/issues/detail?id=571722\n "X-UA": `ts.js/${version}`,\n Authorization: "Bearer " + config.token,\n },\n body: JSON.stringify(e),\n // This parameter ensures in most browsers that the request is performed even in case the browser navigates to another page.\n keepalive: true,\n });\n return { ok: r.ok, retry: r.status === 429 || r.status >= 500 };\n }\n catch (error) {\n return { ok: false, retry: true };\n }\n });\n}\n\n;// CONCATENATED MODULE: ./src/detector.ts\n\n\n\nconst MAX_EVENTS_SIZE = 2500;\n// See https://support.google.com/admanager/answer/4524488?hl=en\nconst INTERSECTION_THRESHOLD = 0.5;\nlet seenEvents = new Set();\n/**\n * Generate an id.\n *\n * In modern browsers this will be a UUID, otherwise if createObjectUrl is not available it will\n * just be a random number;\n */\nfunction generateId() {\n var _a, _b;\n return (((_b = (_a = window.URL).createObjectURL) === null || _b === void 0 ? void 0 : _b.call(_a, new Blob()).split("/").pop()) ||\n Math.random() + "");\n}\nlet globalUserId;\nfunction getUserId() {\n if (globalUserId) {\n return globalUserId;\n }\n const userId = getUserIdCookie();\n if (userId) {\n globalUserId = userId;\n return userId;\n }\n const newUserId = generateId();\n setUserIdCookie(newUserId);\n return newUserId;\n}\nfunction setUserIdCookie(id) {\n const cookieName = window.TS.cookieName || "tsuid";\n globalUserId = id;\n document.cookie = cookieName + "=" + id + ";max-age=31536000";\n}\nfunction resetUserId() {\n globalUserId = undefined;\n document.cookie = "tsuid=";\n return getUserId();\n}\nwindow.TS.setUserId = setUserIdCookie;\nwindow.TS.getUserId = getUserId;\nwindow.TS.resetUserId = resetUserId;\n// Based on https://stackoverflow.com/a/25490531/1413687\nfunction getUserIdCookie() {\n var _a;\n const cookieName = window.TS.cookieName || "tsuid";\n const regex = new RegExp("(^|;)\\\\s*" + cookieName + "\\\\s*=\\\\s*([^;]+)");\n return (_a = regex.exec(document.cookie)) === null || _a === void 0 ? void 0 : _a.pop();\n}\nfunction getApiPayload(event) {\n const eventType = event.type;\n const placement = {\n path: event.page,\n };\n let entity = undefined;\n if (event.product) {\n entity = {\n type: "product",\n id: event.product,\n };\n }\n const t = new Date(event.t).toISOString();\n switch (eventType) {\n case "Click":\n return {\n clicks: [\n {\n resolvedBidId: event.bid,\n entity,\n placement,\n occurredAt: t,\n opaqueUserId: event.uid,\n id: event.id,\n },\n ],\n };\n case "Impression":\n return {\n impressions: [\n {\n resolvedBidId: event.bid,\n entity,\n placement,\n occurredAt: t,\n opaqueUserId: event.uid,\n id: event.id,\n },\n ],\n };\n case "Purchase":\n return {\n purchases: [\n {\n occurredAt: t,\n opaqueUserId: event.uid,\n items: (event.items || []).map((e) => ({\n productId: e.product,\n quantity: e.quantity,\n unitPrice: e.price,\n })),\n id: event.id,\n },\n ],\n };\n }\n}\n// TODO: batch requests. Unfortunately at the moment only the impressions are batchable.\nfunction processor(data) {\n return __awaiter(this, void 0, void 0, function* () {\n const r = {\n done: new Set(),\n retry: new Set(),\n };\n const promises = [];\n for (const entry of data) {\n promises.push(reportEvent(getApiPayload(entry), window.TS)\n .then((result) => {\n const q = result.retry ? r.retry : r.done;\n q.add(entry.id);\n })\n .catch(() => {\n r.done.add(entry.id);\n }));\n }\n yield Promise.all(promises);\n return r;\n });\n}\nconst queue = new Queue(processor);\nfunction logEvent(info, node) {\n const id = getId(info);\n if (seenEvents.has(id)) {\n return;\n }\n seenEvents.add(id);\n if (seenEvents.size > MAX_EVENTS_SIZE) {\n const iterator = seenEvents.values();\n for (let i = 0; i < seenEvents.size - MAX_EVENTS_SIZE; --i) {\n iterator.next();\n }\n seenEvents = new Set(iterator);\n }\n queue.append(info);\n // Raise a custom event, so that clients can trigger their own logic.\n // One concrete use of this is for testing purposes.\n const event = new CustomEvent("topsort", { bubbles: true, detail: info });\n node.dispatchEvent(event);\n}\nfunction getId(event) {\n return [event.page, event.type, event.product, event.bid].join("-");\n}\nfunction getPage() {\n const location = window.location;\n const hash = location.hash;\n // This is to support HashRouter from either ReactRouter or VueRouter.\n if (hash[1] === "/")\n return hash;\n return location.pathname;\n}\nfunction getEvent(type, node) {\n const product = node.dataset.tsProduct;\n const bid = node.dataset.tsResolvedBid;\n const event = {\n type,\n product,\n bid,\n t: Date.now(),\n page: getPage(),\n id: generateId(),\n uid: getUserId(),\n };\n if (type === "Purchase") {\n event.items = JSON.parse(node.dataset.tsItems || "[]");\n }\n return event;\n}\nfunction interactionHandler(event) {\n if (!(event.currentTarget instanceof HTMLElement)) {\n return;\n }\n const container = event.currentTarget.closest(PRODUCT_SELECTOR);\n if (container && container instanceof HTMLElement) {\n logEvent(getEvent("Click", container), container);\n }\n}\nconst intersectionObserver = !!window.IntersectionObserver\n ? new IntersectionObserver((entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting) {\n const node = entry.target;\n if (node instanceof HTMLElement) {\n logEvent(getEvent("Impression", node), node);\n if (intersectionObserver) {\n intersectionObserver.unobserve(node);\n }\n }\n }\n }\n }, {\n threshold: INTERSECTION_THRESHOLD,\n })\n : undefined;\nconst PRODUCT_SELECTOR = "[data-ts-product],[data-ts-action],[data-ts-items],[data-ts-resolved-bid]";\nfunction addClickHandler(node) {\n const clickables = node.querySelectorAll("[data-ts-clickable]");\n const elements = clickables.length === 0 ? [node] : clickables;\n elements.forEach((e) => e.addEventListener("click", interactionHandler));\n}\nfunction processChild(node) {\n if (!isPurchase(node)) {\n if (intersectionObserver) {\n intersectionObserver.observe(node);\n }\n else {\n logEvent(getEvent("Impression", node), node);\n }\n addClickHandler(node);\n }\n else {\n logEvent(getEvent("Purchase", node), node);\n }\n}\nfunction checkChildren(node) {\n const matchedNodes = node.querySelectorAll(PRODUCT_SELECTOR);\n for (let i = 0; i < matchedNodes.length; i++) {\n const node = matchedNodes[i];\n if (!(node instanceof HTMLElement)) {\n continue;\n }\n processChild(node);\n }\n}\nfunction isPurchase(node) {\n return node.dataset.tsAction === "purchase";\n}\nfunction mutationCallback(mutationsList) {\n for (const mutation of mutationsList) {\n if (mutation.type === "childList") {\n // Here we could just do a mutation.target.querySelectorAll, but that could also bring the\n // nodes we have already seen before. Instead we just check the added nodes.\n const newParents = new Set();\n for (let i = 0; i < mutation.addedNodes.length; i++) {\n const newNode = mutation.addedNodes[i];\n if (newNode.nodeType === 1) {\n const parent = newNode.parentElement;\n if (parent && !newParents.has(parent)) {\n newParents.add(parent);\n }\n }\n for (const node of newParents) {\n checkChildren(node);\n }\n }\n }\n else if (mutation.type === "attributes") {\n if (!(mutation.target instanceof HTMLElement)) {\n continue;\n }\n processChild(mutation.target);\n }\n }\n}\nfunction start() {\n var _a, _b;\n if ((_a = window.TS) === null || _a === void 0 ? void 0 : _a.loaded) {\n return;\n }\n window.TS.loaded = true;\n if (!((_b = window.TS) === null || _b === void 0 ? void 0 : _b.token)) {\n console.error("Missing TS token");\n return;\n }\n checkChildren(document);\n const MutationObserverImpl = window.MutationObserver ||\n window.WebKitMutationObserver ||\n window.MozMutationObserver;\n const mutationObserver = new MutationObserverImpl(mutationCallback);\n mutationObserver.observe(document, {\n attributes: true,\n childList: true,\n subtree: true,\n attributeFilter: [\n "data-ts-product",\n "data-ts-action",\n "data-ts-items",\n "data-ts-resolved-bid",\n ],\n });\n}\nif (/complete|interactive|loaded/.test(document.readyState)) {\n start();\n}\nelse {\n window.addEventListener("DOMContentLoaded", start);\n}\n\n\n//# sourceURL=webpack://@topsort/analytics.js/./src/detector.ts_+_5_modules?')}},__webpack_exports__={};__webpack_modules__[800]()})()})(window,document,Math,JSON,void 0,HTMLElement);
(function(d){typeof define=="function"&&define.amd?define(d):d()})(function(){"use strict";class d{constructor(){this._data=[]}get(){return this._data}set(e){this._data=e}}class m{constructor(e){this._key=e,this._storage=window.localStorage}get(){const e=this._storage.getItem(this._key);return e?JSON.parse(e):[]}set(e){this._storage.setItem(this._key,JSON.stringify(e))}}const O="ts-t",M="ts-q",N=250,v=3,P=25,A=250,_=9,R=0;function L(t,e){return e>0?t+(Math.random()+Math.pow(2,e))*1e3:0}function U(){var e;const t=new m(O);try{const s="3";if(t.set([{x:s}]),((e=t.get()[0])==null?void 0:e.x)===s)return new m(M)}catch{}return new d}class C{constructor(e){this._store=U(),this._processing=new Set,this._scheduled=!1,this._processor=e}append(e,s){let n=this._store.get();n.push({e,r:0,p:s!=null&&s.highPriority?_:R}),n=n.slice(-N),this._setEntries(n)}async _processNow(e){if(!e.length)return;const s=[];for(let i=e.length-1;i>=0&&s.length<P;i--){const c=e[i],l=c==null?void 0:c.e;l&&!this._processing.has(l.id)&&L(l.t,c.r)<=Date.now()&&(s.push(l),this._processing.add(l.id))}if(!s.length){this._scheduleProcessing();return}let n={done:new Set,retry:new Set};try{n=await this._processor(s)}catch{for(const c of s)n.done.add(c.id)}const o=[];for(const i of this._processing)s.find(c=>c.id===i)||o.push(i);this._processing=new Set(o);const r=this._store.get(),g=[];for(const i of r)n.done.has(i.e.id)||(n.retry.has(i.e.id)?i.r<v&&(i.r+=1,g.push(i)):g.push(i));this._setEntries(g)}_setEntries(e){this._store.set(e),e.length&&(e.some(s=>s.p===_)||this._store instanceof d?this._processNow(e):this._scheduleProcessing())}_scheduleProcessing(){this._scheduled||(this._scheduled=!0,setTimeout(()=>{this._scheduled=!1,this._processNow(this._store.get())},A))}}const q="2.1.0";async function x(t,e){try{const s=(e.url||"https://api.topsort.com")+"/v2/events",n=await fetch(s,{method:"POST",headers:{"Content-Type":"application/json","X-UA":`ts.js/${q}`,Authorization:"Bearer "+e.token},body:JSON.stringify(t),keepalive:!0});return{ok:n.ok,retry:n.status===429||n.status>=500}}catch{return{ok:!1,retry:!0}}}const S=2500,H=.5;let a=new Set;function I(){var t,e;return((e=(t=window.URL).createObjectURL)==null?void 0:e.call(t,new Blob).split("/").pop())||Math.random()+""}let u;function w(){if(u)return u;const t=B();if(t)return u=t,t;const e=I();return E(e),e}function E(t){const e=window.TS.cookieName||"tsuid";u=t,document.cookie=e+"="+t+";max-age=31536000"}function D(){return u=void 0,document.cookie="tsuid=",w()}window.TS.setUserId=E,window.TS.getUserId=w,window.TS.resetUserId=D;function B(){var s;const t=window.TS.cookieName||"tsuid";return(s=new RegExp("(^|;)\\s*"+t+"\\s*=\\s*([^;]+)").exec(document.cookie))==null?void 0:s.pop()}function X(t){const e=t.type,s={path:t.page};let n;t.product&&(n={type:"product",id:t.product});const o=new Date(t.t).toISOString();switch(e){case"Click":return{clicks:[{resolvedBidId:t.bid,entity:n,placement:s,occurredAt:o,opaqueUserId:t.uid,id:t.id}]};case"Impression":return{impressions:[{resolvedBidId:t.bid,entity:n,placement:s,occurredAt:o,opaqueUserId:t.uid,id:t.id}]};case"Purchase":return{purchases:[{occurredAt:o,opaqueUserId:t.uid,items:(t.items||[]).map(r=>({productId:r.product,quantity:r.quantity,unitPrice:r.price})),id:t.id}]}}}async function j(t){const e={done:new Set,retry:new Set},s=[];for(const n of t)s.push(x(X(n),window.TS).then(o=>{(o.retry?e.retry:e.done).add(n.id)}).catch(()=>{e.done.add(n.id)}));return await Promise.all(s),e}const z=new C(j);function h(t,e){const s=J(t);if(a.has(s))return;if(a.add(s),a.size>S){const o=a.values();for(let r=0;r<a.size-S;--r)o.next();a=new Set(o)}z.append(t);const n=new CustomEvent("topsort",{bubbles:!0,detail:t});e.dispatchEvent(n)}function J(t){return[t.page,t.type,t.product,t.bid].join("-")}function Y(){const t=window.location,e=t.hash;return e[1]==="/"?e:t.pathname}function f(t,e){const s=e.dataset.tsProduct,n=e.dataset.tsResolvedBid,o={type:t,product:s,bid:n,t:Date.now(),page:Y(),id:I(),uid:w()};return t==="Purchase"&&(o.items=JSON.parse(e.dataset.tsItems||"[]")),o}function G(t){if(!(t.currentTarget instanceof HTMLElement))return;const e=t.currentTarget.closest(y);e&&e instanceof HTMLElement&&h(f("Click",e),e)}const p=window.IntersectionObserver?new IntersectionObserver(t=>{for(const e of t)if(e.isIntersecting){const s=e.target;s instanceof HTMLElement&&(h(f("Impression",s),s),p&&p.unobserve(s))}},{threshold:H}):void 0,y="[data-ts-product],[data-ts-action],[data-ts-items],[data-ts-resolved-bid]";function K(t){const e=t.querySelectorAll("[data-ts-clickable]");(e.length===0?[t]:e).forEach(n=>n.addEventListener("click",G))}function T(t){Z(t)?h(f("Purchase",t),t):(p?p.observe(t):h(f("Impression",t),t),K(t))}function b(t){const e=t.querySelectorAll(y);for(let s=0;s<e.length;s++){const n=e[s];n instanceof HTMLElement&&T(n)}}function Z(t){return t.dataset.tsAction==="purchase"}function W(t){for(const e of t)if(e.type==="childList"){const s=new Set;for(let n=0;n<e.addedNodes.length;n++){const o=e.addedNodes[n];if((o==null?void 0:o.nodeType)===Node.ELEMENT_NODE){const r=o.parentElement;r&&!s.has(r)&&s.add(r)}for(const r of s)b(r)}}else if(e.type==="attributes"){if(!(e.target instanceof HTMLElement))continue;T(e.target)}}function k(){var s,n;if((s=window.TS)!=null&&s.loaded)return;if(window.TS.loaded=!0,!((n=window.TS)!=null&&n.token)){console.error("Missing TS token");return}b(document);const t=window.MutationObserver||window.WebKitMutationObserver||window.MozMutationObserver;new t(W).observe(document,{attributes:!0,childList:!0,subtree:!0,attributeFilter:["data-ts-product","data-ts-action","data-ts-items","data-ts-resolved-bid"]})}/complete|interactive|loaded/.test(document.readyState)?k():window.addEventListener("DOMContentLoaded",k)});
{
"name": "@topsort/analytics.js",
"version": "2.0.0",
"version": "2.1.0",
"description": "JS library to automatically report events to Topsort's Analytics",
"main": "dist/ts.js",
"type": "module",
"packageManager": "pnpm@8.6.0",
"engines": {
"node": ">=18",
"pnpm": ">=8"
},
"keywords": [

@@ -13,41 +19,56 @@ "ads",

],
"exports": {
".": {
"import": "./dist/ts.mjs",
"require": "./dist/ts.js",
"types": "./dist/src/events.d.ts"
}
},
"module": "./dist/ts.mjs",
"files": [
"dist/src",
"dist/ts.js",
"dist/ts.mjs",
"dist/*.d.ts",
"package.json",
"README.md",
"CHANGELOG.md"
],
"homepage": "https://github.com/Topsort/analytics.js#readme",
"scripts": {
"prepublishOnly": "npm run build && node update-version.js",
"build": "webpack",
"test": "jest",
"test:e2e": "webpack && webpack -c webpack.config.browser-test.js && tsc && cp dist/mocks/api-server.{,m}js && node dist/mocks/api-server.mjs",
"lint": "eslint . --max-warnings 0 --ignore-path .gitignore",
"lint:fix": "npm run lint -- --fix",
"lint:ci": "npm run lint -- --format=compact",
"types:check": "tsc"
},
"author": "Topsort",
"license": "MIT",
"devDependencies": {
"@types/express": "^4.17.14",
"@types/jest": "^27.4.1",
"@types/react": "^18.2.14",
"@types/react-dom": "^18.0.10",
"@typescript-eslint/eslint-plugin": "^5.59.8",
"@typescript-eslint/parser": "^5.54.0",
"eslint": "^8.25.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-jest": "^27.2.2",
"eslint-plugin-prettier": "^4.2.1",
"@types/express": "^4.17.17",
"@types/node": "^20.5.7",
"@types/react": "^18.2.21",
"@types/react-dom": "^18.2.7",
"@typescript-eslint/eslint-plugin": "^6.5.0",
"@typescript-eslint/parser": "^6.5.0",
"@vitest/coverage-v8": "^0.34.3",
"eslint": "^8.48.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-vitest": "^0.2.8",
"express": "^4.18.2",
"isomorphic-fetch": "^3.0.0",
"jest": "^27.5.1",
"nock": "^13.2.9",
"prettier": "^2.8.7",
"react": "^18.1.0",
"jsdom": "^22.1.0",
"msw": "^1.2.5",
"prettier": "^3.0.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.6.1",
"terser-webpack-plugin": "^5.3.9",
"ts-jest": "^27.1.4",
"ts-loader": "^9.4.2",
"typescript": "^4.8.4",
"webpack": "^5.86.0",
"webpack-cli": "^5.0.1"
"react-router-dom": "^6.15.0",
"tslib": "^2.6.2",
"typescript": "^5.2.2",
"vite": "^4.4.9",
"vite-plugin-dts": "^3.5.3",
"vitest": "^0.33.0"
},
"scripts": {
"build": "vite build",
"lint": "eslint . --max-warnings 0 --ignore-path .gitignore",
"lint:fix": "eslint . --max-warnings 0 --ignore-path .gitignore --fix",
"lint:ci": "eslint . --max-warnings 0 --ignore-path .gitignore",
"test": "vitest run --coverage",
"test:e2e": "vite build && vite build -c vite.config.browser-test.js && tsc && node dist/mocks/api-server.js",
"types:check": "tsc"
}
}
}

@@ -6,4 +6,4 @@ ![version](https://img.shields.io/npm/v/@topsort/analytics.js)

# Topsort analytics.js
# Topsort analytics.js
Topsort's JS analytics event library

@@ -37,4 +37,18 @@

Either mix quotes (single/double) or escape certain characters inside your values. In javascript:
```js
const newvalue = currentvalue.replace('"', "&quot;").replace("'", "&apos;"); // etc.
```
Pass said values to your html:
```html
<div class="product" data-ts-product="<productId>" data-ts-resolved-bid="<resolvedBidId>">...</div>
<div
class="product"
data-ts-product="<productId>"
data-ts-resolved-bid="<resolvedBidId>"
>
...
</div>
```

@@ -54,5 +68,17 @@

Finally, adding further information to purchases can be made by passing the `ts-data-items` JSON array:
```html
<div
data-ts-action="purchase"
data-ts-items='[{"product": "product-id-purchase-1", "quantity":1, "price": 2399}, {"product": "product-id-purchase-2", "quantity": 2, "price": 399}]'
>
My purchase
</div>
```
# E2E tests
Execute `npm run test:e2e`, at the end it will show you the url you need to visit to test the library.
Ideally you would check the library both in desktop and mobile browsers. For that you need to be connected to the same network.
version: 0.2
phases:
install:
commands:
- npm ci
pre_build:
commands:
# - echo Logging in to Amazon ECR...
# - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin 160485138118.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
- echo Running tests
- npm run test
build:
commands:
- echo Building project...
- npm run build
post_build:
commands:
- echo Uploading files to S3...
- aws s3 cp dist/ts.js s3://$BUCKET_NAME
- echo Invalidating Cloudfront cache
- aws cloudfront create-invalidation --distribution-id $DISTRIBUTION_ID --paths "/ts.js"