@optiqstudio/track 0.1.0 → 0.3.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/browser.d.ts +60 -1
- package/dist/hash.d.ts +10 -0
- package/dist/index.cjs +123 -10
- package/dist/index.d.ts +2 -2
- package/dist/index.js +123 -10
- package/dist/server.cjs +43 -3
- package/dist/server.d.ts +36 -0
- package/dist/server.js +43 -3
- package/dist/t.js +2 -2
- package/package.json +19 -4
package/dist/browser.d.ts
CHANGED
|
@@ -10,6 +10,45 @@ export interface InitOptions {
|
|
|
10
10
|
endpoint?: string;
|
|
11
11
|
/** The visitor's choice, if the site already knows it. */
|
|
12
12
|
consent?: Consent;
|
|
13
|
+
/** Where suggestions come from. Default: the Optiq Studio Enterprise API. */
|
|
14
|
+
api?: string;
|
|
15
|
+
}
|
|
16
|
+
/** A product to show a shopper, and why. */
|
|
17
|
+
export interface Suggestion {
|
|
18
|
+
id: string;
|
|
19
|
+
name: string | null;
|
|
20
|
+
category: string | null;
|
|
21
|
+
brand: string | null;
|
|
22
|
+
price: number | null;
|
|
23
|
+
currency: string | null;
|
|
24
|
+
url: string | null;
|
|
25
|
+
image: string | null;
|
|
26
|
+
/** bought_together, viewed_together, in_category, popular or recently_viewed. */
|
|
27
|
+
reason: string;
|
|
28
|
+
/** The product it goes with (bought_together, viewed_together). */
|
|
29
|
+
because: string | null;
|
|
30
|
+
}
|
|
31
|
+
export interface SuggestOptions {
|
|
32
|
+
/** The product on screen: suggestions go with it, and it is left out. */
|
|
33
|
+
product?: string;
|
|
34
|
+
/** Product ids to leave out (what is already in the basket, for example). */
|
|
35
|
+
exclude?: string[];
|
|
36
|
+
/** How many, 1 to 24. Default 8. */
|
|
37
|
+
limit?: number;
|
|
38
|
+
}
|
|
39
|
+
/** One thing in a basket or an order. */
|
|
40
|
+
export interface Item {
|
|
41
|
+
id: string;
|
|
42
|
+
name?: string;
|
|
43
|
+
category?: string;
|
|
44
|
+
brand?: string;
|
|
45
|
+
variant?: string;
|
|
46
|
+
price?: number;
|
|
47
|
+
quantity?: number;
|
|
48
|
+
/** The product's page. */
|
|
49
|
+
url?: string;
|
|
50
|
+
/** The product's picture. */
|
|
51
|
+
image?: string;
|
|
13
52
|
}
|
|
14
53
|
export interface Properties {
|
|
15
54
|
/** A sale's or deposit's amount. */
|
|
@@ -20,19 +59,24 @@ export interface Properties {
|
|
|
20
59
|
email?: string;
|
|
21
60
|
/** Hashed in the browser before anything is sent (digits only). */
|
|
22
61
|
phone?: string | number;
|
|
23
|
-
|
|
62
|
+
/** What was viewed, put in the basket or bought (view_content, add_to_cart, begin_checkout, purchase). */
|
|
63
|
+
items?: Item[];
|
|
64
|
+
[property: string]: string | number | boolean | Item[] | undefined;
|
|
24
65
|
}
|
|
25
66
|
export declare const COLLECTOR = "https://optiq-collect-951694748196.europe-west2.run.app/e";
|
|
67
|
+
export declare const API = "https://enterpriseapi-951694748196.europe-west2.run.app";
|
|
26
68
|
/** One tracker on one page. Most sites use the module functions below, which share one. */
|
|
27
69
|
export declare class Tracker {
|
|
28
70
|
private key;
|
|
29
71
|
private endpoint;
|
|
72
|
+
private api;
|
|
30
73
|
private consentState;
|
|
31
74
|
private touch;
|
|
32
75
|
private utm;
|
|
33
76
|
private clicks;
|
|
34
77
|
private visit;
|
|
35
78
|
private person;
|
|
79
|
+
private personKind;
|
|
36
80
|
private queue;
|
|
37
81
|
private timer;
|
|
38
82
|
private lastPath;
|
|
@@ -44,6 +88,11 @@ export declare class Tracker {
|
|
|
44
88
|
flush(): void;
|
|
45
89
|
/** Something a visitor did: a standard name (lead, sign_up, purchase, first_deposit, ...) or one of your own. */
|
|
46
90
|
track(name: string, props?: Properties): void;
|
|
91
|
+
/** Products to show this shopper, from what they (and shoppers like them) looked at and bought, and why. */
|
|
92
|
+
suggest(o?: SuggestOptions): Promise<{
|
|
93
|
+
suggestions: Suggestion[];
|
|
94
|
+
recentlyViewed: Suggestion[];
|
|
95
|
+
}>;
|
|
47
96
|
/** Who the visitor is, from now on: their email or phone, hashed here. */
|
|
48
97
|
identify(p: {
|
|
49
98
|
email?: string;
|
|
@@ -53,6 +102,11 @@ export declare class Tracker {
|
|
|
53
102
|
consent(state: Consent): void;
|
|
54
103
|
/** A page view (called by itself in auto mode, on load and on every move of a single-page site). */
|
|
55
104
|
page(): void;
|
|
105
|
+
/**
|
|
106
|
+
* What a shop's page says by itself: a product looked at (the schema.org Product the page publishes for Google, with
|
|
107
|
+
* its id, name, category, brand and price) and a search (?q=, ?s=, ?search=, ?query=, ?keyword=). Once per page.
|
|
108
|
+
*/
|
|
109
|
+
private shopSignals;
|
|
56
110
|
private wire;
|
|
57
111
|
}
|
|
58
112
|
/** Which kind of contact a link begins, or null: tel: is a call, mailto: with an address an email, wa.me with a number WhatsApp. */
|
|
@@ -72,3 +126,8 @@ export declare const consent: (state: Consent) => void;
|
|
|
72
126
|
export declare const page: () => void;
|
|
73
127
|
/** Sends what waits now. */
|
|
74
128
|
export declare const flush: () => void;
|
|
129
|
+
/** Products to show this shopper, and why (an empty list if anything goes wrong, so a page never breaks). */
|
|
130
|
+
export declare function suggest(o?: SuggestOptions): Promise<{
|
|
131
|
+
suggestions: Suggestion[];
|
|
132
|
+
recentlyViewed: Suggestion[];
|
|
133
|
+
}>;
|
package/dist/hash.d.ts
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
/** SHA-256 in hex, by Web Crypto (browsers, Node 20+, Deno, Bun, edge runtimes); null where it is missing. */
|
|
2
2
|
export declare function sha256(s: string): Promise<string | null>;
|
|
3
|
+
/** Which a person hash is of: the ad platforms take emails and phones apart. */
|
|
4
|
+
export type PersonKind = "email" | "phone";
|
|
5
|
+
/** The hash and what it is of, or null. */
|
|
6
|
+
export declare function personOf(p: {
|
|
7
|
+
email?: unknown;
|
|
8
|
+
phone?: unknown;
|
|
9
|
+
} | null | undefined): Promise<{
|
|
10
|
+
hash: string;
|
|
11
|
+
kind: PersonKind;
|
|
12
|
+
} | null>;
|
|
3
13
|
/** An email (trimmed, lower-case) or a phone (digits only), as a SHA-256: the only way a person is ever sent. */
|
|
4
14
|
export declare function hashPerson(p: {
|
|
5
15
|
email?: unknown;
|
package/dist/index.cjs
CHANGED
|
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
API: () => API,
|
|
23
24
|
COLLECTOR: () => COLLECTOR,
|
|
24
25
|
Tracker: () => Tracker,
|
|
25
26
|
consent: () => consent,
|
|
@@ -29,6 +30,7 @@ __export(index_exports, {
|
|
|
29
30
|
identify: () => identify,
|
|
30
31
|
init: () => init,
|
|
31
32
|
page: () => page,
|
|
33
|
+
suggest: () => suggest,
|
|
32
34
|
track: () => track
|
|
33
35
|
});
|
|
34
36
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -42,6 +44,18 @@ function sha256(s) {
|
|
|
42
44
|
() => null
|
|
43
45
|
);
|
|
44
46
|
}
|
|
47
|
+
async function personOf(p) {
|
|
48
|
+
if (p && typeof p.email === "string" && p.email.indexOf("@") > 0) {
|
|
49
|
+
const hash = await sha256(p.email.trim().toLowerCase());
|
|
50
|
+
return hash ? { hash, kind: "email" } : null;
|
|
51
|
+
}
|
|
52
|
+
if (p && (typeof p.phone === "string" || typeof p.phone === "number")) {
|
|
53
|
+
const digits = String(p.phone).replace(/[^0-9]/g, "");
|
|
54
|
+
const hash = digits.length >= 7 ? await sha256(digits) : null;
|
|
55
|
+
return hash ? { hash, kind: "phone" } : null;
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
45
59
|
function hashPerson(p) {
|
|
46
60
|
if (p && typeof p.email === "string" && p.email.indexOf("@") > 0) return sha256(p.email.trim().toLowerCase());
|
|
47
61
|
if (p && (typeof p.phone === "string" || typeof p.phone === "number")) {
|
|
@@ -53,6 +67,7 @@ function hashPerson(p) {
|
|
|
53
67
|
|
|
54
68
|
// src/browser.ts
|
|
55
69
|
var COLLECTOR = "https://optiq-collect-951694748196.europe-west2.run.app/e";
|
|
70
|
+
var API = "https://enterpriseapi-951694748196.europe-west2.run.app";
|
|
56
71
|
var DAY = 864e5;
|
|
57
72
|
var IDLE = 30 * 6e4;
|
|
58
73
|
var hasWindow = () => typeof window !== "undefined" && typeof document !== "undefined";
|
|
@@ -93,6 +108,7 @@ var Tracker = class {
|
|
|
93
108
|
this.wired = false;
|
|
94
109
|
this.key = o.key;
|
|
95
110
|
this.endpoint = o.endpoint || COLLECTOR;
|
|
111
|
+
this.api = (o.api || API).replace(/\/+$/, "");
|
|
96
112
|
this.consentState = o.consent || get("consent") || "unknown";
|
|
97
113
|
const q = new URLSearchParams(window.location.search);
|
|
98
114
|
const now = Date.now();
|
|
@@ -131,6 +147,7 @@ var Tracker = class {
|
|
|
131
147
|
ref: osr && /^[A-Za-z0-9]{4,16}$/.test(osr) ? osr : null
|
|
132
148
|
};
|
|
133
149
|
this.person = get("ph");
|
|
150
|
+
this.personKind = get("pk") || null;
|
|
134
151
|
if (o.auto !== false) this.wire();
|
|
135
152
|
}
|
|
136
153
|
visitor() {
|
|
@@ -186,7 +203,7 @@ var Tracker = class {
|
|
|
186
203
|
/** Something a visitor did: a standard name (lead, sign_up, purchase, first_deposit, ...) or one of your own. */
|
|
187
204
|
track(name, props = {}) {
|
|
188
205
|
const rest = {};
|
|
189
|
-
for (const [k, v] of Object.entries(props)) if (!["value", "currency", "email", "phone"].includes(k) && v !== void 0) rest[k] = v;
|
|
206
|
+
for (const [k, v] of Object.entries(props)) if (!["value", "currency", "email", "phone", "items"].includes(k) && v !== void 0 && !Array.isArray(v)) rest[k] = v;
|
|
190
207
|
const fresh = Date.now();
|
|
191
208
|
this.visit.last = fresh;
|
|
192
209
|
if (this.consentState !== "denied") put("visit", JSON.stringify(this.visit));
|
|
@@ -200,7 +217,7 @@ var Tracker = class {
|
|
|
200
217
|
page_url: window.location.origin + window.location.pathname,
|
|
201
218
|
referrer: document.referrer ? document.referrer.split("?")[0].slice(0, 300) : null,
|
|
202
219
|
utm: this.utm,
|
|
203
|
-
click_ids: denied ? {} : this.clicks,
|
|
220
|
+
click_ids: denied ? {} : { ...this.clicks, ...platformCookies() },
|
|
204
221
|
first_ref: this.touch.first ? this.touch.first.ref : null,
|
|
205
222
|
first_ref_at: this.touch.first ? this.touch.first.at : null,
|
|
206
223
|
last_ref: last ? last.ref : null,
|
|
@@ -208,24 +225,46 @@ var Tracker = class {
|
|
|
208
225
|
value: props.value === void 0 ? null : Number(props.value),
|
|
209
226
|
currency: props.currency || null,
|
|
210
227
|
properties: rest,
|
|
211
|
-
consent: this.consentState
|
|
228
|
+
consent: this.consentState,
|
|
229
|
+
...Array.isArray(props.items) && props.items.length ? { items: props.items.slice(0, 50) } : {}
|
|
212
230
|
};
|
|
213
|
-
void
|
|
231
|
+
void personOf(denied ? null : props).then((h) => {
|
|
214
232
|
if (h) {
|
|
215
|
-
this.person = h;
|
|
216
|
-
|
|
233
|
+
this.person = h.hash;
|
|
234
|
+
this.personKind = h.kind;
|
|
235
|
+
put("ph", h.hash);
|
|
236
|
+
put("pk", h.kind);
|
|
217
237
|
}
|
|
218
238
|
ev.person_hash = denied ? null : this.person;
|
|
239
|
+
ev.person_kind = denied || !this.person ? null : this.personKind;
|
|
219
240
|
this.queue.push(ev);
|
|
220
241
|
if (!this.timer) this.timer = setTimeout(() => this.flush(), 400);
|
|
221
242
|
});
|
|
222
243
|
}
|
|
244
|
+
/** Products to show this shopper, from what they (and shoppers like them) looked at and bought, and why. */
|
|
245
|
+
async suggest(o = {}) {
|
|
246
|
+
const q = new URLSearchParams({ key: this.key });
|
|
247
|
+
const visitor = this.consentState === "denied" ? null : get("vid");
|
|
248
|
+
if (visitor) q.set("visitor", visitor);
|
|
249
|
+
if (o.product) q.set("product", o.product);
|
|
250
|
+
if (o.exclude?.length) q.set("exclude", o.exclude.join(","));
|
|
251
|
+
if (o.limit) q.set("limit", String(o.limit));
|
|
252
|
+
try {
|
|
253
|
+
const r = await fetch(`${this.api}/api/v1/suggest?${q}`, { mode: "cors", credentials: "omit" });
|
|
254
|
+
if (!r.ok) return { suggestions: [], recentlyViewed: [] };
|
|
255
|
+
return await r.json();
|
|
256
|
+
} catch {
|
|
257
|
+
return { suggestions: [], recentlyViewed: [] };
|
|
258
|
+
}
|
|
259
|
+
}
|
|
223
260
|
/** Who the visitor is, from now on: their email or phone, hashed here. */
|
|
224
261
|
identify(p) {
|
|
225
|
-
void
|
|
262
|
+
void personOf(p).then((h) => {
|
|
226
263
|
if (h && this.consentState !== "denied") {
|
|
227
|
-
this.person = h;
|
|
228
|
-
|
|
264
|
+
this.person = h.hash;
|
|
265
|
+
this.personKind = h.kind;
|
|
266
|
+
put("ph", h.hash);
|
|
267
|
+
put("pk", h.kind);
|
|
229
268
|
}
|
|
230
269
|
});
|
|
231
270
|
}
|
|
@@ -234,8 +273,9 @@ var Tracker = class {
|
|
|
234
273
|
this.consentState = state === "granted" || state === "denied" ? state : "unknown";
|
|
235
274
|
put("consent", this.consentState);
|
|
236
275
|
if (this.consentState === "denied") {
|
|
237
|
-
for (const k of ["vid", "ph", "clicks", "visit"]) put(k, null);
|
|
276
|
+
for (const k of ["vid", "ph", "pk", "clicks", "visit"]) put(k, null);
|
|
238
277
|
this.person = null;
|
|
278
|
+
this.personKind = null;
|
|
239
279
|
this.clicks = {};
|
|
240
280
|
}
|
|
241
281
|
}
|
|
@@ -245,6 +285,19 @@ var Tracker = class {
|
|
|
245
285
|
if (path === this.lastPath) return;
|
|
246
286
|
this.lastPath = path;
|
|
247
287
|
this.track("page_view");
|
|
288
|
+
if (this.wired) setTimeout(() => this.shopSignals(path), 1200);
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* What a shop's page says by itself: a product looked at (the schema.org Product the page publishes for Google, with
|
|
292
|
+
* its id, name, category, brand and price) and a search (?q=, ?s=, ?search=, ?query=, ?keyword=). Once per page.
|
|
293
|
+
*/
|
|
294
|
+
shopSignals(path) {
|
|
295
|
+
if (path !== window.location.pathname + window.location.search) return;
|
|
296
|
+
const q = new URLSearchParams(window.location.search);
|
|
297
|
+
const query = ["q", "s", "search", "query", "keyword"].map((k) => q.get(k)).find((v) => v && v.trim());
|
|
298
|
+
if (query) this.track("search", { query: query.trim().slice(0, 200) });
|
|
299
|
+
const product = pageProduct();
|
|
300
|
+
if (product) this.track("view_content", { items: [product.item], value: product.item.price, currency: product.currency });
|
|
248
301
|
}
|
|
249
302
|
wire() {
|
|
250
303
|
if (this.wired) return;
|
|
@@ -297,6 +350,62 @@ var Tracker = class {
|
|
|
297
350
|
window.addEventListener("pagehide", () => this.flush());
|
|
298
351
|
}
|
|
299
352
|
};
|
|
353
|
+
function platformCookies() {
|
|
354
|
+
const out = {};
|
|
355
|
+
try {
|
|
356
|
+
for (const part of document.cookie.split(";")) {
|
|
357
|
+
const [k, ...v] = part.trim().split("=");
|
|
358
|
+
const value = decodeURIComponent(v.join("=") || "").slice(0, 300);
|
|
359
|
+
if (!value) continue;
|
|
360
|
+
if (k === "_fbp") out.fbp = value;
|
|
361
|
+
else if (k === "_fbc") out.fbc = value;
|
|
362
|
+
else if (k === "_ttp") out.ttp = value;
|
|
363
|
+
}
|
|
364
|
+
} catch {
|
|
365
|
+
}
|
|
366
|
+
return out;
|
|
367
|
+
}
|
|
368
|
+
function pageProduct() {
|
|
369
|
+
const scripts = document.querySelectorAll('script[type="application/ld+json"]');
|
|
370
|
+
for (const el of Array.from(scripts)) {
|
|
371
|
+
let data;
|
|
372
|
+
try {
|
|
373
|
+
data = JSON.parse(el.textContent || "null");
|
|
374
|
+
} catch {
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
const queue = [data];
|
|
378
|
+
while (queue.length) {
|
|
379
|
+
const n = queue.shift();
|
|
380
|
+
if (Array.isArray(n)) {
|
|
381
|
+
queue.push(...n);
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
if (!n || typeof n !== "object") continue;
|
|
385
|
+
const o = n;
|
|
386
|
+
if (o["@graph"]) queue.push(o["@graph"]);
|
|
387
|
+
const type = o["@type"];
|
|
388
|
+
if (type !== "Product" && !(Array.isArray(type) && type.includes("Product"))) continue;
|
|
389
|
+
const offers = Array.isArray(o.offers) ? o.offers[0] : o.offers;
|
|
390
|
+
const id = String(o.sku || o.productID || o.gtin13 || o.mpn || o["@id"] || o.url || o.name || "").slice(0, 100);
|
|
391
|
+
if (!id) continue;
|
|
392
|
+
const brand = typeof o.brand === "string" ? o.brand : o.brand?.name;
|
|
393
|
+
const price = Number(offers?.price ?? offers?.lowPrice);
|
|
394
|
+
const item = { id };
|
|
395
|
+
if (typeof o.name === "string") item.name = o.name.slice(0, 200);
|
|
396
|
+
if (typeof o.category === "string") item.category = o.category.slice(0, 120);
|
|
397
|
+
if (brand) item.brand = String(brand).slice(0, 120);
|
|
398
|
+
if (Number.isFinite(price)) item.price = price;
|
|
399
|
+
const image = typeof o.image === "string" ? o.image : Array.isArray(o.image) ? o.image.find((x) => typeof x === "string") : o.image?.url;
|
|
400
|
+
if (typeof image === "string" && /^https?:\/\//.test(image)) item.image = image.slice(0, 500);
|
|
401
|
+
const page2 = typeof o.url === "string" && /^https?:\/\//.test(o.url) ? o.url : window.location.origin + window.location.pathname;
|
|
402
|
+
item.url = page2.slice(0, 500);
|
|
403
|
+
const currency = typeof offers?.priceCurrency === "string" ? offers.priceCurrency.toUpperCase().slice(0, 3) : void 0;
|
|
404
|
+
return { item, currency };
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
300
409
|
function contactChannel(href) {
|
|
301
410
|
if (/^tel:/i.test(href)) return "call";
|
|
302
411
|
if (/^mailto:[^?]/i.test(href)) return "email";
|
|
@@ -326,3 +435,7 @@ var identify = (p) => later((t) => t.identify(p));
|
|
|
326
435
|
var consent = (state) => later((t) => t.consent(state));
|
|
327
436
|
var page = () => later((t) => t.page());
|
|
328
437
|
var flush = () => later((t) => t.flush());
|
|
438
|
+
function suggest(o) {
|
|
439
|
+
if (!hasWindow()) return Promise.resolve({ suggestions: [], recentlyViewed: [] });
|
|
440
|
+
return new Promise((resolve) => later((t) => void t.suggest(o).then(resolve)));
|
|
441
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { COLLECTOR, Tracker, consent, contactChannel, flush, hashPerson, identify, init, page, track } from "./browser";
|
|
2
|
-
export type { Consent, InitOptions, Properties } from "./browser";
|
|
1
|
+
export { API, COLLECTOR, Tracker, consent, contactChannel, flush, hashPerson, identify, init, page, suggest, track } from "./browser";
|
|
2
|
+
export type { Consent, InitOptions, Item, Properties, SuggestOptions, Suggestion } from "./browser";
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,18 @@ function sha256(s) {
|
|
|
7
7
|
() => null
|
|
8
8
|
);
|
|
9
9
|
}
|
|
10
|
+
async function personOf(p) {
|
|
11
|
+
if (p && typeof p.email === "string" && p.email.indexOf("@") > 0) {
|
|
12
|
+
const hash = await sha256(p.email.trim().toLowerCase());
|
|
13
|
+
return hash ? { hash, kind: "email" } : null;
|
|
14
|
+
}
|
|
15
|
+
if (p && (typeof p.phone === "string" || typeof p.phone === "number")) {
|
|
16
|
+
const digits = String(p.phone).replace(/[^0-9]/g, "");
|
|
17
|
+
const hash = digits.length >= 7 ? await sha256(digits) : null;
|
|
18
|
+
return hash ? { hash, kind: "phone" } : null;
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
10
22
|
function hashPerson(p) {
|
|
11
23
|
if (p && typeof p.email === "string" && p.email.indexOf("@") > 0) return sha256(p.email.trim().toLowerCase());
|
|
12
24
|
if (p && (typeof p.phone === "string" || typeof p.phone === "number")) {
|
|
@@ -18,6 +30,7 @@ function hashPerson(p) {
|
|
|
18
30
|
|
|
19
31
|
// src/browser.ts
|
|
20
32
|
var COLLECTOR = "https://optiq-collect-951694748196.europe-west2.run.app/e";
|
|
33
|
+
var API = "https://enterpriseapi-951694748196.europe-west2.run.app";
|
|
21
34
|
var DAY = 864e5;
|
|
22
35
|
var IDLE = 30 * 6e4;
|
|
23
36
|
var hasWindow = () => typeof window !== "undefined" && typeof document !== "undefined";
|
|
@@ -58,6 +71,7 @@ var Tracker = class {
|
|
|
58
71
|
this.wired = false;
|
|
59
72
|
this.key = o.key;
|
|
60
73
|
this.endpoint = o.endpoint || COLLECTOR;
|
|
74
|
+
this.api = (o.api || API).replace(/\/+$/, "");
|
|
61
75
|
this.consentState = o.consent || get("consent") || "unknown";
|
|
62
76
|
const q = new URLSearchParams(window.location.search);
|
|
63
77
|
const now = Date.now();
|
|
@@ -96,6 +110,7 @@ var Tracker = class {
|
|
|
96
110
|
ref: osr && /^[A-Za-z0-9]{4,16}$/.test(osr) ? osr : null
|
|
97
111
|
};
|
|
98
112
|
this.person = get("ph");
|
|
113
|
+
this.personKind = get("pk") || null;
|
|
99
114
|
if (o.auto !== false) this.wire();
|
|
100
115
|
}
|
|
101
116
|
visitor() {
|
|
@@ -151,7 +166,7 @@ var Tracker = class {
|
|
|
151
166
|
/** Something a visitor did: a standard name (lead, sign_up, purchase, first_deposit, ...) or one of your own. */
|
|
152
167
|
track(name, props = {}) {
|
|
153
168
|
const rest = {};
|
|
154
|
-
for (const [k, v] of Object.entries(props)) if (!["value", "currency", "email", "phone"].includes(k) && v !== void 0) rest[k] = v;
|
|
169
|
+
for (const [k, v] of Object.entries(props)) if (!["value", "currency", "email", "phone", "items"].includes(k) && v !== void 0 && !Array.isArray(v)) rest[k] = v;
|
|
155
170
|
const fresh = Date.now();
|
|
156
171
|
this.visit.last = fresh;
|
|
157
172
|
if (this.consentState !== "denied") put("visit", JSON.stringify(this.visit));
|
|
@@ -165,7 +180,7 @@ var Tracker = class {
|
|
|
165
180
|
page_url: window.location.origin + window.location.pathname,
|
|
166
181
|
referrer: document.referrer ? document.referrer.split("?")[0].slice(0, 300) : null,
|
|
167
182
|
utm: this.utm,
|
|
168
|
-
click_ids: denied ? {} : this.clicks,
|
|
183
|
+
click_ids: denied ? {} : { ...this.clicks, ...platformCookies() },
|
|
169
184
|
first_ref: this.touch.first ? this.touch.first.ref : null,
|
|
170
185
|
first_ref_at: this.touch.first ? this.touch.first.at : null,
|
|
171
186
|
last_ref: last ? last.ref : null,
|
|
@@ -173,24 +188,46 @@ var Tracker = class {
|
|
|
173
188
|
value: props.value === void 0 ? null : Number(props.value),
|
|
174
189
|
currency: props.currency || null,
|
|
175
190
|
properties: rest,
|
|
176
|
-
consent: this.consentState
|
|
191
|
+
consent: this.consentState,
|
|
192
|
+
...Array.isArray(props.items) && props.items.length ? { items: props.items.slice(0, 50) } : {}
|
|
177
193
|
};
|
|
178
|
-
void
|
|
194
|
+
void personOf(denied ? null : props).then((h) => {
|
|
179
195
|
if (h) {
|
|
180
|
-
this.person = h;
|
|
181
|
-
|
|
196
|
+
this.person = h.hash;
|
|
197
|
+
this.personKind = h.kind;
|
|
198
|
+
put("ph", h.hash);
|
|
199
|
+
put("pk", h.kind);
|
|
182
200
|
}
|
|
183
201
|
ev.person_hash = denied ? null : this.person;
|
|
202
|
+
ev.person_kind = denied || !this.person ? null : this.personKind;
|
|
184
203
|
this.queue.push(ev);
|
|
185
204
|
if (!this.timer) this.timer = setTimeout(() => this.flush(), 400);
|
|
186
205
|
});
|
|
187
206
|
}
|
|
207
|
+
/** Products to show this shopper, from what they (and shoppers like them) looked at and bought, and why. */
|
|
208
|
+
async suggest(o = {}) {
|
|
209
|
+
const q = new URLSearchParams({ key: this.key });
|
|
210
|
+
const visitor = this.consentState === "denied" ? null : get("vid");
|
|
211
|
+
if (visitor) q.set("visitor", visitor);
|
|
212
|
+
if (o.product) q.set("product", o.product);
|
|
213
|
+
if (o.exclude?.length) q.set("exclude", o.exclude.join(","));
|
|
214
|
+
if (o.limit) q.set("limit", String(o.limit));
|
|
215
|
+
try {
|
|
216
|
+
const r = await fetch(`${this.api}/api/v1/suggest?${q}`, { mode: "cors", credentials: "omit" });
|
|
217
|
+
if (!r.ok) return { suggestions: [], recentlyViewed: [] };
|
|
218
|
+
return await r.json();
|
|
219
|
+
} catch {
|
|
220
|
+
return { suggestions: [], recentlyViewed: [] };
|
|
221
|
+
}
|
|
222
|
+
}
|
|
188
223
|
/** Who the visitor is, from now on: their email or phone, hashed here. */
|
|
189
224
|
identify(p) {
|
|
190
|
-
void
|
|
225
|
+
void personOf(p).then((h) => {
|
|
191
226
|
if (h && this.consentState !== "denied") {
|
|
192
|
-
this.person = h;
|
|
193
|
-
|
|
227
|
+
this.person = h.hash;
|
|
228
|
+
this.personKind = h.kind;
|
|
229
|
+
put("ph", h.hash);
|
|
230
|
+
put("pk", h.kind);
|
|
194
231
|
}
|
|
195
232
|
});
|
|
196
233
|
}
|
|
@@ -199,8 +236,9 @@ var Tracker = class {
|
|
|
199
236
|
this.consentState = state === "granted" || state === "denied" ? state : "unknown";
|
|
200
237
|
put("consent", this.consentState);
|
|
201
238
|
if (this.consentState === "denied") {
|
|
202
|
-
for (const k of ["vid", "ph", "clicks", "visit"]) put(k, null);
|
|
239
|
+
for (const k of ["vid", "ph", "pk", "clicks", "visit"]) put(k, null);
|
|
203
240
|
this.person = null;
|
|
241
|
+
this.personKind = null;
|
|
204
242
|
this.clicks = {};
|
|
205
243
|
}
|
|
206
244
|
}
|
|
@@ -210,6 +248,19 @@ var Tracker = class {
|
|
|
210
248
|
if (path === this.lastPath) return;
|
|
211
249
|
this.lastPath = path;
|
|
212
250
|
this.track("page_view");
|
|
251
|
+
if (this.wired) setTimeout(() => this.shopSignals(path), 1200);
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* What a shop's page says by itself: a product looked at (the schema.org Product the page publishes for Google, with
|
|
255
|
+
* its id, name, category, brand and price) and a search (?q=, ?s=, ?search=, ?query=, ?keyword=). Once per page.
|
|
256
|
+
*/
|
|
257
|
+
shopSignals(path) {
|
|
258
|
+
if (path !== window.location.pathname + window.location.search) return;
|
|
259
|
+
const q = new URLSearchParams(window.location.search);
|
|
260
|
+
const query = ["q", "s", "search", "query", "keyword"].map((k) => q.get(k)).find((v) => v && v.trim());
|
|
261
|
+
if (query) this.track("search", { query: query.trim().slice(0, 200) });
|
|
262
|
+
const product = pageProduct();
|
|
263
|
+
if (product) this.track("view_content", { items: [product.item], value: product.item.price, currency: product.currency });
|
|
213
264
|
}
|
|
214
265
|
wire() {
|
|
215
266
|
if (this.wired) return;
|
|
@@ -262,6 +313,62 @@ var Tracker = class {
|
|
|
262
313
|
window.addEventListener("pagehide", () => this.flush());
|
|
263
314
|
}
|
|
264
315
|
};
|
|
316
|
+
function platformCookies() {
|
|
317
|
+
const out = {};
|
|
318
|
+
try {
|
|
319
|
+
for (const part of document.cookie.split(";")) {
|
|
320
|
+
const [k, ...v] = part.trim().split("=");
|
|
321
|
+
const value = decodeURIComponent(v.join("=") || "").slice(0, 300);
|
|
322
|
+
if (!value) continue;
|
|
323
|
+
if (k === "_fbp") out.fbp = value;
|
|
324
|
+
else if (k === "_fbc") out.fbc = value;
|
|
325
|
+
else if (k === "_ttp") out.ttp = value;
|
|
326
|
+
}
|
|
327
|
+
} catch {
|
|
328
|
+
}
|
|
329
|
+
return out;
|
|
330
|
+
}
|
|
331
|
+
function pageProduct() {
|
|
332
|
+
const scripts = document.querySelectorAll('script[type="application/ld+json"]');
|
|
333
|
+
for (const el of Array.from(scripts)) {
|
|
334
|
+
let data;
|
|
335
|
+
try {
|
|
336
|
+
data = JSON.parse(el.textContent || "null");
|
|
337
|
+
} catch {
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
const queue = [data];
|
|
341
|
+
while (queue.length) {
|
|
342
|
+
const n = queue.shift();
|
|
343
|
+
if (Array.isArray(n)) {
|
|
344
|
+
queue.push(...n);
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
if (!n || typeof n !== "object") continue;
|
|
348
|
+
const o = n;
|
|
349
|
+
if (o["@graph"]) queue.push(o["@graph"]);
|
|
350
|
+
const type = o["@type"];
|
|
351
|
+
if (type !== "Product" && !(Array.isArray(type) && type.includes("Product"))) continue;
|
|
352
|
+
const offers = Array.isArray(o.offers) ? o.offers[0] : o.offers;
|
|
353
|
+
const id = String(o.sku || o.productID || o.gtin13 || o.mpn || o["@id"] || o.url || o.name || "").slice(0, 100);
|
|
354
|
+
if (!id) continue;
|
|
355
|
+
const brand = typeof o.brand === "string" ? o.brand : o.brand?.name;
|
|
356
|
+
const price = Number(offers?.price ?? offers?.lowPrice);
|
|
357
|
+
const item = { id };
|
|
358
|
+
if (typeof o.name === "string") item.name = o.name.slice(0, 200);
|
|
359
|
+
if (typeof o.category === "string") item.category = o.category.slice(0, 120);
|
|
360
|
+
if (brand) item.brand = String(brand).slice(0, 120);
|
|
361
|
+
if (Number.isFinite(price)) item.price = price;
|
|
362
|
+
const image = typeof o.image === "string" ? o.image : Array.isArray(o.image) ? o.image.find((x) => typeof x === "string") : o.image?.url;
|
|
363
|
+
if (typeof image === "string" && /^https?:\/\//.test(image)) item.image = image.slice(0, 500);
|
|
364
|
+
const page2 = typeof o.url === "string" && /^https?:\/\//.test(o.url) ? o.url : window.location.origin + window.location.pathname;
|
|
365
|
+
item.url = page2.slice(0, 500);
|
|
366
|
+
const currency = typeof offers?.priceCurrency === "string" ? offers.priceCurrency.toUpperCase().slice(0, 3) : void 0;
|
|
367
|
+
return { item, currency };
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
265
372
|
function contactChannel(href) {
|
|
266
373
|
if (/^tel:/i.test(href)) return "call";
|
|
267
374
|
if (/^mailto:[^?]/i.test(href)) return "email";
|
|
@@ -291,7 +398,12 @@ var identify = (p) => later((t) => t.identify(p));
|
|
|
291
398
|
var consent = (state) => later((t) => t.consent(state));
|
|
292
399
|
var page = () => later((t) => t.page());
|
|
293
400
|
var flush = () => later((t) => t.flush());
|
|
401
|
+
function suggest(o) {
|
|
402
|
+
if (!hasWindow()) return Promise.resolve({ suggestions: [], recentlyViewed: [] });
|
|
403
|
+
return new Promise((resolve) => later((t) => void t.suggest(o).then(resolve)));
|
|
404
|
+
}
|
|
294
405
|
export {
|
|
406
|
+
API,
|
|
295
407
|
COLLECTOR,
|
|
296
408
|
Tracker,
|
|
297
409
|
consent,
|
|
@@ -301,5 +413,6 @@ export {
|
|
|
301
413
|
identify,
|
|
302
414
|
init,
|
|
303
415
|
page,
|
|
416
|
+
suggest,
|
|
304
417
|
track
|
|
305
418
|
};
|
package/dist/server.cjs
CHANGED
|
@@ -34,6 +34,18 @@ function sha256(s) {
|
|
|
34
34
|
() => null
|
|
35
35
|
);
|
|
36
36
|
}
|
|
37
|
+
async function personOf(p) {
|
|
38
|
+
if (p && typeof p.email === "string" && p.email.indexOf("@") > 0) {
|
|
39
|
+
const hash = await sha256(p.email.trim().toLowerCase());
|
|
40
|
+
return hash ? { hash, kind: "email" } : null;
|
|
41
|
+
}
|
|
42
|
+
if (p && (typeof p.phone === "string" || typeof p.phone === "number")) {
|
|
43
|
+
const digits = String(p.phone).replace(/[^0-9]/g, "");
|
|
44
|
+
const hash = digits.length >= 7 ? await sha256(digits) : null;
|
|
45
|
+
return hash ? { hash, kind: "phone" } : null;
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
37
49
|
function hashPerson(p) {
|
|
38
50
|
if (p && typeof p.email === "string" && p.email.indexOf("@") > 0) return sha256(p.email.trim().toLowerCase());
|
|
39
51
|
if (p && (typeof p.phone === "string" || typeof p.phone === "number")) {
|
|
@@ -58,7 +70,7 @@ var OptiqError = class extends Error {
|
|
|
58
70
|
this.name = "OptiqError";
|
|
59
71
|
}
|
|
60
72
|
};
|
|
61
|
-
var Optiq = class {
|
|
73
|
+
var Optiq = class _Optiq {
|
|
62
74
|
constructor(o) {
|
|
63
75
|
if (!o || typeof o.secretKey !== "string" || !/^sk_[A-Za-z0-9]{30,60}$/.test(o.secretKey)) {
|
|
64
76
|
throw new Error("Optiq needs the source's secret key (sk_...), from the Developer page.");
|
|
@@ -91,14 +103,15 @@ var Optiq = class {
|
|
|
91
103
|
}
|
|
92
104
|
async prepare(e) {
|
|
93
105
|
const { email, phone, ...rest } = e;
|
|
94
|
-
const
|
|
106
|
+
const who = e.person_hash ? { hash: e.person_hash, kind: e.person_kind || "email" } : await personOf({ email, phone });
|
|
95
107
|
const iso = (v) => v instanceof Date ? v.toISOString() : v;
|
|
96
108
|
return {
|
|
97
109
|
...rest,
|
|
98
110
|
event_id: e.event_id || newId(),
|
|
99
111
|
occurred_at: iso(e.occurred_at),
|
|
100
112
|
last_ref_at: iso(e.last_ref_at),
|
|
101
|
-
person_hash: hash || void 0
|
|
113
|
+
person_hash: who?.hash || void 0,
|
|
114
|
+
person_kind: who?.kind || void 0
|
|
102
115
|
};
|
|
103
116
|
}
|
|
104
117
|
/** Sends one event or many (any number: they go 500 at a time). Answers what was kept, repeated or refused. */
|
|
@@ -114,6 +127,33 @@ var Optiq = class {
|
|
|
114
127
|
}
|
|
115
128
|
return out;
|
|
116
129
|
}
|
|
130
|
+
async get(path) {
|
|
131
|
+
const r = await fetch(`${this.baseUrl}${path}`, { headers: { Authorization: `Bearer ${this.secretKey}` } });
|
|
132
|
+
const data = await r.json().catch(() => ({}));
|
|
133
|
+
if (!r.ok) throw new OptiqError(typeof data.error === "string" ? data.error : `Optiq answered ${r.status}.`, r.status);
|
|
134
|
+
return data;
|
|
135
|
+
}
|
|
136
|
+
static who(w) {
|
|
137
|
+
const q = new URLSearchParams();
|
|
138
|
+
if (w.person_hash) q.set("person_hash", w.person_hash);
|
|
139
|
+
else if (w.email) q.set("email", w.email);
|
|
140
|
+
else if (w.phone !== void 0) q.set("phone", String(w.phone));
|
|
141
|
+
if (w.visitor_id) q.set("visitor_id", w.visitor_id);
|
|
142
|
+
return q;
|
|
143
|
+
}
|
|
144
|
+
/** Products to offer one customer (across every device they used), and why: for your pages, emails and messages. */
|
|
145
|
+
async suggest(o) {
|
|
146
|
+
const q = _Optiq.who(o);
|
|
147
|
+
if (o.visitor_id) q.set("visitor", o.visitor_id);
|
|
148
|
+
if (o.product) q.set("product", o.product);
|
|
149
|
+
if (o.exclude?.length) q.set("exclude", o.exclude.join(","));
|
|
150
|
+
if (o.limit) q.set("limit", String(o.limit));
|
|
151
|
+
return this.get(`/api/v1/suggest?${q}`);
|
|
152
|
+
}
|
|
153
|
+
/** Everything your company knows about one customer: visits, sources, products, orders, spend, segment, a timeline. */
|
|
154
|
+
async profile(who) {
|
|
155
|
+
return this.get(`/api/v1/people?${_Optiq.who(who)}`);
|
|
156
|
+
}
|
|
117
157
|
/** Forgets a customer in your company: every event and visit of theirs. Answers how many events went. */
|
|
118
158
|
async forget(who) {
|
|
119
159
|
const hash = who.person_hash || await hashPerson(who);
|
package/dist/server.d.ts
CHANGED
|
@@ -12,6 +12,20 @@ export interface ServerEvent {
|
|
|
12
12
|
phone?: string | number;
|
|
13
13
|
/** Who, if you already hash: SHA-256 of the trimmed, lower-case email, or of the phone's digits. */
|
|
14
14
|
person_hash?: string;
|
|
15
|
+
/** What person_hash is of, so the ad platforms match it as an email or a phone. Default "email". */
|
|
16
|
+
person_kind?: "email" | "phone";
|
|
17
|
+
/** What was bought: each with its id, and its name, category, price and quantity where known. */
|
|
18
|
+
items?: {
|
|
19
|
+
id: string;
|
|
20
|
+
name?: string;
|
|
21
|
+
category?: string;
|
|
22
|
+
brand?: string;
|
|
23
|
+
variant?: string;
|
|
24
|
+
price?: number;
|
|
25
|
+
quantity?: number;
|
|
26
|
+
url?: string;
|
|
27
|
+
image?: string;
|
|
28
|
+
}[];
|
|
15
29
|
/** The browser's visitor id, if your site passes it to your server (localStorage optiq_vid). */
|
|
16
30
|
visitor_id?: string;
|
|
17
31
|
value?: number;
|
|
@@ -58,6 +72,28 @@ export declare class Optiq {
|
|
|
58
72
|
private prepare;
|
|
59
73
|
/** Sends one event or many (any number: they go 500 at a time). Answers what was kept, repeated or refused. */
|
|
60
74
|
track(events: ServerEvent | ServerEvent[]): Promise<TrackResult>;
|
|
75
|
+
private get;
|
|
76
|
+
private static who;
|
|
77
|
+
/** Products to offer one customer (across every device they used), and why: for your pages, emails and messages. */
|
|
78
|
+
suggest(o: {
|
|
79
|
+
email?: string;
|
|
80
|
+
phone?: string | number;
|
|
81
|
+
person_hash?: string;
|
|
82
|
+
visitor_id?: string;
|
|
83
|
+
product?: string;
|
|
84
|
+
exclude?: string[];
|
|
85
|
+
limit?: number;
|
|
86
|
+
}): Promise<{
|
|
87
|
+
suggestions: Record<string, unknown>[];
|
|
88
|
+
recentlyViewed: Record<string, unknown>[];
|
|
89
|
+
}>;
|
|
90
|
+
/** Everything your company knows about one customer: visits, sources, products, orders, spend, segment, a timeline. */
|
|
91
|
+
profile(who: {
|
|
92
|
+
email?: string;
|
|
93
|
+
phone?: string | number;
|
|
94
|
+
person_hash?: string;
|
|
95
|
+
visitor_id?: string;
|
|
96
|
+
}): Promise<Record<string, unknown>>;
|
|
61
97
|
/** Forgets a customer in your company: every event and visit of theirs. Answers how many events went. */
|
|
62
98
|
forget(who: {
|
|
63
99
|
email?: string;
|
package/dist/server.js
CHANGED
|
@@ -7,6 +7,18 @@ function sha256(s) {
|
|
|
7
7
|
() => null
|
|
8
8
|
);
|
|
9
9
|
}
|
|
10
|
+
async function personOf(p) {
|
|
11
|
+
if (p && typeof p.email === "string" && p.email.indexOf("@") > 0) {
|
|
12
|
+
const hash = await sha256(p.email.trim().toLowerCase());
|
|
13
|
+
return hash ? { hash, kind: "email" } : null;
|
|
14
|
+
}
|
|
15
|
+
if (p && (typeof p.phone === "string" || typeof p.phone === "number")) {
|
|
16
|
+
const digits = String(p.phone).replace(/[^0-9]/g, "");
|
|
17
|
+
const hash = digits.length >= 7 ? await sha256(digits) : null;
|
|
18
|
+
return hash ? { hash, kind: "phone" } : null;
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
10
22
|
function hashPerson(p) {
|
|
11
23
|
if (p && typeof p.email === "string" && p.email.indexOf("@") > 0) return sha256(p.email.trim().toLowerCase());
|
|
12
24
|
if (p && (typeof p.phone === "string" || typeof p.phone === "number")) {
|
|
@@ -31,7 +43,7 @@ var OptiqError = class extends Error {
|
|
|
31
43
|
this.name = "OptiqError";
|
|
32
44
|
}
|
|
33
45
|
};
|
|
34
|
-
var Optiq = class {
|
|
46
|
+
var Optiq = class _Optiq {
|
|
35
47
|
constructor(o) {
|
|
36
48
|
if (!o || typeof o.secretKey !== "string" || !/^sk_[A-Za-z0-9]{30,60}$/.test(o.secretKey)) {
|
|
37
49
|
throw new Error("Optiq needs the source's secret key (sk_...), from the Developer page.");
|
|
@@ -64,14 +76,15 @@ var Optiq = class {
|
|
|
64
76
|
}
|
|
65
77
|
async prepare(e) {
|
|
66
78
|
const { email, phone, ...rest } = e;
|
|
67
|
-
const
|
|
79
|
+
const who = e.person_hash ? { hash: e.person_hash, kind: e.person_kind || "email" } : await personOf({ email, phone });
|
|
68
80
|
const iso = (v) => v instanceof Date ? v.toISOString() : v;
|
|
69
81
|
return {
|
|
70
82
|
...rest,
|
|
71
83
|
event_id: e.event_id || newId(),
|
|
72
84
|
occurred_at: iso(e.occurred_at),
|
|
73
85
|
last_ref_at: iso(e.last_ref_at),
|
|
74
|
-
person_hash: hash || void 0
|
|
86
|
+
person_hash: who?.hash || void 0,
|
|
87
|
+
person_kind: who?.kind || void 0
|
|
75
88
|
};
|
|
76
89
|
}
|
|
77
90
|
/** Sends one event or many (any number: they go 500 at a time). Answers what was kept, repeated or refused. */
|
|
@@ -87,6 +100,33 @@ var Optiq = class {
|
|
|
87
100
|
}
|
|
88
101
|
return out;
|
|
89
102
|
}
|
|
103
|
+
async get(path) {
|
|
104
|
+
const r = await fetch(`${this.baseUrl}${path}`, { headers: { Authorization: `Bearer ${this.secretKey}` } });
|
|
105
|
+
const data = await r.json().catch(() => ({}));
|
|
106
|
+
if (!r.ok) throw new OptiqError(typeof data.error === "string" ? data.error : `Optiq answered ${r.status}.`, r.status);
|
|
107
|
+
return data;
|
|
108
|
+
}
|
|
109
|
+
static who(w) {
|
|
110
|
+
const q = new URLSearchParams();
|
|
111
|
+
if (w.person_hash) q.set("person_hash", w.person_hash);
|
|
112
|
+
else if (w.email) q.set("email", w.email);
|
|
113
|
+
else if (w.phone !== void 0) q.set("phone", String(w.phone));
|
|
114
|
+
if (w.visitor_id) q.set("visitor_id", w.visitor_id);
|
|
115
|
+
return q;
|
|
116
|
+
}
|
|
117
|
+
/** Products to offer one customer (across every device they used), and why: for your pages, emails and messages. */
|
|
118
|
+
async suggest(o) {
|
|
119
|
+
const q = _Optiq.who(o);
|
|
120
|
+
if (o.visitor_id) q.set("visitor", o.visitor_id);
|
|
121
|
+
if (o.product) q.set("product", o.product);
|
|
122
|
+
if (o.exclude?.length) q.set("exclude", o.exclude.join(","));
|
|
123
|
+
if (o.limit) q.set("limit", String(o.limit));
|
|
124
|
+
return this.get(`/api/v1/suggest?${q}`);
|
|
125
|
+
}
|
|
126
|
+
/** Everything your company knows about one customer: visits, sources, products, orders, spend, segment, a timeline. */
|
|
127
|
+
async profile(who) {
|
|
128
|
+
return this.get(`/api/v1/people?${_Optiq.who(who)}`);
|
|
129
|
+
}
|
|
90
130
|
/** Forgets a customer in your company: every event and visit of theirs. Answers how many events went. */
|
|
91
131
|
async forget(who) {
|
|
92
132
|
const hash = who.person_hash || await hashPerson(who);
|
package/dist/t.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/* Optiq Studio Enterprise tracking 0.
|
|
2
|
-
"use strict";(()=>{function k(n){let t=typeof globalThis!="undefined"?globalThis.crypto:void 0;return!t||!t.subtle||typeof TextEncoder=="undefined"?Promise.resolve(null):t.subtle.digest("SHA-256",new TextEncoder().encode(n)).then(e=>Array.from(new Uint8Array(e),r=>r.toString(16).padStart(2,"0")).join(""),()=>null)}function m(n){if(n&&typeof n.email=="string"&&n.email.indexOf("@")>0)return k(n.email.trim().toLowerCase());if(n&&(typeof n.phone=="string"||typeof n.phone=="number")){let t=String(n.phone).replace(/[^0-9]/g,"");if(t.length>=7)return k(t)}return Promise.resolve(null)}var A="https://optiq-collect-951694748196.europe-west2.run.app/e",b=864e5,P=30*6e4,S=()=>typeof window!="undefined"&&typeof document!="undefined";function p(n){try{return window.localStorage.getItem(`optiq_${n}`)}catch(t){return null}}function d(n,t){try{t===null?window.localStorage.removeItem(`optiq_${n}`):window.localStorage.setItem(`optiq_${n}`,t)}catch(e){}}function h(n){try{return JSON.parse(p(n)||"{}")||{}}catch(t){return{}}}function w(n){let t="abcdefghijklmnopqrstuvwxyz0123456789",e=new Uint8Array(n);typeof crypto!="undefined"&&crypto.getRandomValues&&crypto.getRandomValues(e);let r="";for(let s=0;s<n;s++)r+=t.charAt((e[s]||Math.floor(Math.random()*256))%36);return r}var y=class{constructor(t){this.queue=[];this.timer=null;this.lastPath=null;this.wired=!1;this.key=t.key,this.endpoint=t.endpoint||A,this.consentState=t.consent||p("consent")||"unknown";let e=new URLSearchParams(window.location.search),r=Date.now();this.touch=h("touch");let s=e.get("osr");if(s&&/^[A-Za-z0-9]{4,16}$/.test(s)){let l=new Date(r).toISOString();(!this.touch.first||r-Date.parse(this.touch.first.at)>90*b)&&(this.touch.first={ref:s,at:l}),this.touch.last={ref:s,at:l},d("touch",JSON.stringify(this.touch))}let i={};for(let l of["source","medium","campaign","content","term"]){let f=e.get(`utm_${l}`);f&&(i[l]=f.slice(0,200))}Object.keys(i).length&&d("utm",JSON.stringify(i)),this.utm=Object.keys(i).length?i:h("utm");let o={};for(let l of["gclid","fbclid","ttclid","wbraid","gbraid"]){let f=e.get(l);f&&(o[l]=f.slice(0,500))}Object.keys(o).length&&d("clicks",JSON.stringify(o)),this.clicks=Object.keys(o).length?o:h("clicks");let u=this.consentState==="denied"?{}:h("visit"),c=`${e.get("utm_campaign")||""}|${s||""}`;this.visit=u.id&&r-(u.last||0)<=P&&(c==="|"||c===u.from)?u:{id:w(20),started:new Date(r).toISOString(),from:c,landing:window.location.pathname,referrer:document.referrer?document.referrer.split("?")[0].slice(0,300):null,utm:i,click:e.get("gclid")?"gclid":e.get("fbclid")?"fbclid":e.get("ttclid")?"ttclid":null,ref:s&&/^[A-Za-z0-9]{4,16}$/.test(s)?s:null},this.person=p("ph"),t.auto!==!1&&this.wire()}visitor(){if(this.consentState==="denied")return null;let t=p("vid");return t||(t=w(20),d("vid",t)),t}context(){let t=null;try{t=Intl.DateTimeFormat().resolvedOptions().timeZone||null}catch(r){t=null}let e=this.visit;return{id:e.id,started_at:e.started,landing:e.landing,referrer:e.referrer,utm:e.utm,click:e.click,ref:e.ref,lang:(navigator.language||"").slice(0,20)||null,tz:t,screen:window.screen?`${window.screen.width}x${window.screen.height}`:null}}flush(){for(this.timer&&clearTimeout(this.timer),this.timer=null;this.queue.length;){let t=JSON.stringify({key:this.key,visit:this.context(),events:this.queue.splice(0,50)}),e=!1;try{e=!!(navigator.sendBeacon&&navigator.sendBeacon(this.endpoint,new Blob([t],{type:"text/plain"})))}catch(r){e=!1}if(!e)try{fetch(this.endpoint,{method:"POST",body:t,keepalive:!0,mode:"cors",headers:{"Content-Type":"text/plain"}}).catch(()=>{})}catch(r){}}}track(t,e={}){let r={};for(let[c,l]of Object.entries(e))!["value","currency","email","phone"].includes(c)&&l!==void 0&&(r[c]=l);let s=Date.now();this.visit.last=s,this.consentState!=="denied"&&d("visit",JSON.stringify(this.visit));let i=this.touch.last&&s-Date.parse(this.touch.last.at)<=7*b?this.touch.last:null,o=this.consentState==="denied",u={event_id:w(18),name:String(t||"").toLowerCase(),occurred_at:new Date(s).toISOString(),visitor_id:this.visitor(),page_url:window.location.origin+window.location.pathname,referrer:document.referrer?document.referrer.split("?")[0].slice(0,300):null,utm:this.utm,click_ids:o?{}:this.clicks,first_ref:this.touch.first?this.touch.first.ref:null,first_ref_at:this.touch.first?this.touch.first.at:null,last_ref:i?i.ref:null,last_ref_at:i?i.at:null,value:e.value===void 0?null:Number(e.value),currency:e.currency||null,properties:r,consent:this.consentState};m(o?null:e).then(c=>{c&&(this.person=c,d("ph",c)),u.person_hash=o?null:this.person,this.queue.push(u),this.timer||(this.timer=setTimeout(()=>this.flush(),400))})}identify(t){m(t).then(e=>{e&&this.consentState!=="denied"&&(this.person=e,d("ph",e))})}consent(t){if(this.consentState=t==="granted"||t==="denied"?t:"unknown",d("consent",this.consentState),this.consentState==="denied"){for(let e of["vid","ph","clicks","visit"])d(e,null);this.person=null,this.clicks={}}}page(){let t=window.location.pathname+window.location.search;t!==this.lastPath&&(this.lastPath=t,this.track("page_view"))}wire(){if(this.wired)return;this.wired=!0,this.page();for(let e of["pushState","replaceState"]){let r=history[e];typeof r=="function"&&(history[e]=function(...s){let i=r.apply(this,s);return setTimeout(()=>a==null?void 0:a.page(),0),i})}window.addEventListener("popstate",()=>this.page());let t=typeof WeakSet=="function"?new WeakSet:null;document.addEventListener("focusin",e=>{var s,i;let r=(i=(s=e.target)==null?void 0:s.closest)==null?void 0:i.call(s,"form");!r||!t||t.has(r)||(t.add(r),this.track("form_start",{form:(r.getAttribute("name")||r.id||"").slice(0,60)||void 0}))}),document.addEventListener("submit",e=>{let r=e.target;if(!r||!r.getAttribute||r.getAttribute("data-optiq")!=="lead")return;let s=r.querySelector('input[type="email"]'),i=r.querySelector('input[type="tel"]');this.track("lead",{form:(r.getAttribute("name")||r.id||"").slice(0,60)||void 0,email:s?s.value:void 0,phone:i?i.value:void 0}),this.flush()}),document.addEventListener("click",e=>{var i,o;let r=(o=(i=e.target)==null?void 0:i.closest)==null?void 0:o.call(i,"a[href]");if(!r)return;let s=L(r.getAttribute("href")||"");s&&(this.track("contact",{channel:s}),this.flush())},!0),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&this.flush()}),window.addEventListener("pagehide",()=>this.flush())}};function L(n){return/^tel:/i.test(n)?"call":/^mailto:[^?]/i.test(n)?"email":/^https?:\/\/(wa\.me\/\+?[0-9]|api\.whatsapp\.com\/send\/?\?(.*&)?phone=)/i.test(n)?"whatsapp":null}var a=null,_=[];function q(n){return S()?a||(!n||typeof n.key!="string"||!/^pk_[A-Za-z0-9]{20,40}$/.test(n.key)?(console.warn("[optiq] init needs the source's publishable key (pk_...), from the Developer page."),null):(a=new y(n),_.splice(0).forEach(t=>t()),a)):null}var g=n=>{S()&&(a?n(a):_.push(()=>a&&n(a)))},v=(n,t)=>g(e=>e.track(n,t)),T=n=>g(t=>t.identify(n)),O=n=>g(t=>t.consent(n)),x=()=>g(n=>n.page());(function(){if(window.__optiqLoaded)return;window.__optiqLoaded=!0;let n=document.currentScript||document.querySelector('script[src*="/t.js"][data-key]');if(!n)return;let t=(window.optiq&&window.optiq.q||[]).map(i=>Array.prototype.slice.call(i));function e(i,o,u){i==="track"?v(String(o),u||{}):i==="identify"?T(o||{}):i==="consent"?O(o):i==="page"&&x()}let r=(...i)=>e(...i);r.track=(i,o)=>v(i,o),window.optiq=r;let s=t.filter(i=>i[0]==="consent").pop();q({key:n.getAttribute("data-key")||"",endpoint:n.getAttribute("data-endpoint")||void 0,consent:s&&(s[1]==="granted"||s[1]==="denied")?s[1]:void 0}),t.forEach(i=>e(...i))})();})();
|
|
1
|
+
/* Optiq Studio Enterprise tracking 0.3.0: <script async src="https://enterprise.optiq.studio/t.js" data-key="pk_..."></script>. Built from @optiqstudio/track (packages/track); docs: https://enterprise.optiq.studio/developers */
|
|
2
|
+
"use strict";(()=>{var N=Object.defineProperty;var x=Object.getOwnPropertySymbols;var H=Object.prototype.hasOwnProperty,U=Object.prototype.propertyIsEnumerable;var O=(n,t,e)=>t in n?N(n,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):n[t]=e,m=(n,t)=>{for(var e in t||(t={}))H.call(t,e)&&O(n,e,t[e]);if(x)for(var e of x(t))U.call(t,e)&&O(n,e,t[e]);return n};function T(n){let t=typeof globalThis!="undefined"?globalThis.crypto:void 0;return!t||!t.subtle||typeof TextEncoder=="undefined"?Promise.resolve(null):t.subtle.digest("SHA-256",new TextEncoder().encode(n)).then(e=>Array.from(new Uint8Array(e),r=>r.toString(16).padStart(2,"0")).join(""),()=>null)}async function k(n){if(n&&typeof n.email=="string"&&n.email.indexOf("@")>0){let t=await T(n.email.trim().toLowerCase());return t?{hash:t,kind:"email"}:null}if(n&&(typeof n.phone=="string"||typeof n.phone=="number")){let t=String(n.phone).replace(/[^0-9]/g,""),e=t.length>=7?await T(t):null;return e?{hash:e,kind:"phone"}:null}return null}var J="https://optiq-collect-951694748196.europe-west2.run.app/e",M="https://enterpriseapi-951694748196.europe-west2.run.app",R=864e5,z=30*6e4,S=()=>typeof window!="undefined"&&typeof document!="undefined";function f(n){try{return window.localStorage.getItem(`optiq_${n}`)}catch(t){return null}}function d(n,t){try{t===null?window.localStorage.removeItem(`optiq_${n}`):window.localStorage.setItem(`optiq_${n}`,t)}catch(e){}}function y(n){try{return JSON.parse(f(n)||"{}")||{}}catch(t){return{}}}function v(n){let t="abcdefghijklmnopqrstuvwxyz0123456789",e=new Uint8Array(n);typeof crypto!="undefined"&&crypto.getRandomValues&&crypto.getRandomValues(e);let r="";for(let s=0;s<n;s++)r+=t.charAt((e[s]||Math.floor(Math.random()*256))%36);return r}var b=class{constructor(t){this.queue=[];this.timer=null;this.lastPath=null;this.wired=!1;this.key=t.key,this.endpoint=t.endpoint||J,this.api=(t.api||M).replace(/\/+$/,""),this.consentState=t.consent||f("consent")||"unknown";let e=new URLSearchParams(window.location.search),r=Date.now();this.touch=y("touch");let s=e.get("osr");if(s&&/^[A-Za-z0-9]{4,16}$/.test(s)){let l=new Date(r).toISOString();(!this.touch.first||r-Date.parse(this.touch.first.at)>90*R)&&(this.touch.first={ref:s,at:l}),this.touch.last={ref:s,at:l},d("touch",JSON.stringify(this.touch))}let i={};for(let l of["source","medium","campaign","content","term"]){let u=e.get(`utm_${l}`);u&&(i[l]=u.slice(0,200))}Object.keys(i).length&&d("utm",JSON.stringify(i)),this.utm=Object.keys(i).length?i:y("utm");let a={};for(let l of["gclid","fbclid","ttclid","wbraid","gbraid"]){let u=e.get(l);u&&(a[l]=u.slice(0,500))}Object.keys(a).length&&d("clicks",JSON.stringify(a)),this.clicks=Object.keys(a).length?a:y("clicks");let c=this.consentState==="denied"?{}:y("visit"),o=`${e.get("utm_campaign")||""}|${s||""}`;this.visit=c.id&&r-(c.last||0)<=z&&(o==="|"||o===c.from)?c:{id:v(20),started:new Date(r).toISOString(),from:o,landing:window.location.pathname,referrer:document.referrer?document.referrer.split("?")[0].slice(0,300):null,utm:i,click:e.get("gclid")?"gclid":e.get("fbclid")?"fbclid":e.get("ttclid")?"ttclid":null,ref:s&&/^[A-Za-z0-9]{4,16}$/.test(s)?s:null},this.person=f("ph"),this.personKind=f("pk")||null,t.auto!==!1&&this.wire()}visitor(){if(this.consentState==="denied")return null;let t=f("vid");return t||(t=v(20),d("vid",t)),t}context(){let t=null;try{t=Intl.DateTimeFormat().resolvedOptions().timeZone||null}catch(r){t=null}let e=this.visit;return{id:e.id,started_at:e.started,landing:e.landing,referrer:e.referrer,utm:e.utm,click:e.click,ref:e.ref,lang:(navigator.language||"").slice(0,20)||null,tz:t,screen:window.screen?`${window.screen.width}x${window.screen.height}`:null}}flush(){for(this.timer&&clearTimeout(this.timer),this.timer=null;this.queue.length;){let t=JSON.stringify({key:this.key,visit:this.context(),events:this.queue.splice(0,50)}),e=!1;try{e=!!(navigator.sendBeacon&&navigator.sendBeacon(this.endpoint,new Blob([t],{type:"text/plain"})))}catch(r){e=!1}if(!e)try{fetch(this.endpoint,{method:"POST",body:t,keepalive:!0,mode:"cors",headers:{"Content-Type":"text/plain"}}).catch(()=>{})}catch(r){}}}track(t,e={}){let r={};for(let[o,l]of Object.entries(e))!["value","currency","email","phone","items"].includes(o)&&l!==void 0&&!Array.isArray(l)&&(r[o]=l);let s=Date.now();this.visit.last=s,this.consentState!=="denied"&&d("visit",JSON.stringify(this.visit));let i=this.touch.last&&s-Date.parse(this.touch.last.at)<=7*R?this.touch.last:null,a=this.consentState==="denied",c=m({event_id:v(18),name:String(t||"").toLowerCase(),occurred_at:new Date(s).toISOString(),visitor_id:this.visitor(),page_url:window.location.origin+window.location.pathname,referrer:document.referrer?document.referrer.split("?")[0].slice(0,300):null,utm:this.utm,click_ids:a?{}:m(m({},this.clicks),W()),first_ref:this.touch.first?this.touch.first.ref:null,first_ref_at:this.touch.first?this.touch.first.at:null,last_ref:i?i.ref:null,last_ref_at:i?i.at:null,value:e.value===void 0?null:Number(e.value),currency:e.currency||null,properties:r,consent:this.consentState},Array.isArray(e.items)&&e.items.length?{items:e.items.slice(0,50)}:{});k(a?null:e).then(o=>{o&&(this.person=o.hash,this.personKind=o.kind,d("ph",o.hash),d("pk",o.kind)),c.person_hash=a?null:this.person,c.person_kind=a||!this.person?null:this.personKind,this.queue.push(c),this.timer||(this.timer=setTimeout(()=>this.flush(),400))})}async suggest(t={}){var s;let e=new URLSearchParams({key:this.key}),r=this.consentState==="denied"?null:f("vid");r&&e.set("visitor",r),t.product&&e.set("product",t.product),(s=t.exclude)!=null&&s.length&&e.set("exclude",t.exclude.join(",")),t.limit&&e.set("limit",String(t.limit));try{let i=await fetch(`${this.api}/api/v1/suggest?${e}`,{mode:"cors",credentials:"omit"});return i.ok?await i.json():{suggestions:[],recentlyViewed:[]}}catch(i){return{suggestions:[],recentlyViewed:[]}}}identify(t){k(t).then(e=>{e&&this.consentState!=="denied"&&(this.person=e.hash,this.personKind=e.kind,d("ph",e.hash),d("pk",e.kind))})}consent(t){if(this.consentState=t==="granted"||t==="denied"?t:"unknown",d("consent",this.consentState),this.consentState==="denied"){for(let e of["vid","ph","pk","clicks","visit"])d(e,null);this.person=null,this.personKind=null,this.clicks={}}}page(){let t=window.location.pathname+window.location.search;t!==this.lastPath&&(this.lastPath=t,this.track("page_view"),this.wired&&setTimeout(()=>this.shopSignals(t),1200))}shopSignals(t){if(t!==window.location.pathname+window.location.search)return;let e=new URLSearchParams(window.location.search),r=["q","s","search","query","keyword"].map(i=>e.get(i)).find(i=>i&&i.trim());r&&this.track("search",{query:r.trim().slice(0,200)});let s=Z();s&&this.track("view_content",{items:[s.item],value:s.item.price,currency:s.currency})}wire(){if(this.wired)return;this.wired=!0,this.page();for(let e of["pushState","replaceState"]){let r=history[e];typeof r=="function"&&(history[e]=function(...s){let i=r.apply(this,s);return setTimeout(()=>g==null?void 0:g.page(),0),i})}window.addEventListener("popstate",()=>this.page());let t=typeof WeakSet=="function"?new WeakSet:null;document.addEventListener("focusin",e=>{var s,i;let r=(i=(s=e.target)==null?void 0:s.closest)==null?void 0:i.call(s,"form");!r||!t||t.has(r)||(t.add(r),this.track("form_start",{form:(r.getAttribute("name")||r.id||"").slice(0,60)||void 0}))}),document.addEventListener("submit",e=>{let r=e.target;if(!r||!r.getAttribute||r.getAttribute("data-optiq")!=="lead")return;let s=r.querySelector('input[type="email"]'),i=r.querySelector('input[type="tel"]');this.track("lead",{form:(r.getAttribute("name")||r.id||"").slice(0,60)||void 0,email:s?s.value:void 0,phone:i?i.value:void 0}),this.flush()}),document.addEventListener("click",e=>{var i,a;let r=(a=(i=e.target)==null?void 0:i.closest)==null?void 0:a.call(i,"a[href]");if(!r)return;let s=B(r.getAttribute("href")||"");s&&(this.track("contact",{channel:s}),this.flush())},!0),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&this.flush()}),window.addEventListener("pagehide",()=>this.flush())}};function W(){let n={};try{for(let t of document.cookie.split(";")){let[e,...r]=t.trim().split("="),s=decodeURIComponent(r.join("=")||"").slice(0,300);s&&(e==="_fbp"?n.fbp=s:e==="_fbc"?n.fbc=s:e==="_ttp"&&(n.ttp=s))}}catch(t){}return n}function Z(){var t,e,r;let n=document.querySelectorAll('script[type="application/ld+json"]');for(let s of Array.from(n)){let i;try{i=JSON.parse(s.textContent||"null")}catch(c){continue}let a=[i];for(;a.length;){let c=a.shift();if(Array.isArray(c)){a.push(...c);continue}if(!c||typeof c!="object")continue;let o=c;o["@graph"]&&a.push(o["@graph"]);let l=o["@type"];if(l!=="Product"&&!(Array.isArray(l)&&l.includes("Product")))continue;let u=Array.isArray(o.offers)?o.offers[0]:o.offers,P=String(o.sku||o.productID||o.gtin13||o.mpn||o["@id"]||o.url||o.name||"").slice(0,100);if(!P)continue;let A=typeof o.brand=="string"?o.brand:(t=o.brand)==null?void 0:t.name,q=Number((e=u==null?void 0:u.price)!=null?e:u==null?void 0:u.lowPrice),p={id:P};typeof o.name=="string"&&(p.name=o.name.slice(0,200)),typeof o.category=="string"&&(p.category=o.category.slice(0,120)),A&&(p.brand=String(A).slice(0,120)),Number.isFinite(q)&&(p.price=q);let w=typeof o.image=="string"?o.image:Array.isArray(o.image)?o.image.find(V=>typeof V=="string"):(r=o.image)==null?void 0:r.url;typeof w=="string"&&/^https?:\/\//.test(w)&&(p.image=w.slice(0,500));let D=typeof o.url=="string"&&/^https?:\/\//.test(o.url)?o.url:window.location.origin+window.location.pathname;p.url=D.slice(0,500);let K=typeof(u==null?void 0:u.priceCurrency)=="string"?u.priceCurrency.toUpperCase().slice(0,3):void 0;return{item:p,currency:K}}}return null}function B(n){return/^tel:/i.test(n)?"call":/^mailto:[^?]/i.test(n)?"email":/^https?:\/\/(wa\.me\/\+?[0-9]|api\.whatsapp\.com\/send\/?\?(.*&)?phone=)/i.test(n)?"whatsapp":null}var g=null,C=[];function L(n){return S()?g||(!n||typeof n.key!="string"||!/^pk_[A-Za-z0-9]{20,40}$/.test(n.key)?(console.warn("[optiq] init needs the source's publishable key (pk_...), from the Developer page."),null):(g=new b(n),C.splice(0).forEach(t=>t()),g)):null}var h=n=>{S()&&(g?n(g):C.push(()=>g&&n(g)))},_=(n,t)=>h(e=>e.track(n,t)),I=n=>h(t=>t.identify(n)),E=n=>h(t=>t.consent(n)),j=()=>h(n=>n.page());function $(n){return S()?new Promise(t=>h(e=>void e.suggest(n).then(t))):Promise.resolve({suggestions:[],recentlyViewed:[]})}(function(){if(window.__optiqLoaded)return;window.__optiqLoaded=!0;let n=document.currentScript||document.querySelector('script[src*="/t.js"][data-key]');if(!n)return;let t=(window.optiq&&window.optiq.q||[]).map(i=>Array.prototype.slice.call(i));function e(i,a,c){i==="track"?_(String(a),c||{}):i==="identify"?I(a||{}):i==="consent"?E(a):i==="page"&&j()}let r=(...i)=>e(...i);r.track=(i,a)=>_(i,a),r.suggest=i=>$(i),window.optiq=r;let s=t.filter(i=>i[0]==="consent").pop();L({key:n.getAttribute("data-key")||"",endpoint:n.getAttribute("data-endpoint")||void 0,api:n.getAttribute("data-api")||void 0,consent:s&&(s[1]==="granted"||s[1]==="denied")?s[1]:void 0}),t.forEach(i=>e(...i))})();})();
|
package/package.json
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@optiqstudio/track",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Optiq Studio Enterprise tracking: join every lead, sale and deposit to the video, post or advert that brought it. Browser and server.",
|
|
5
|
-
"keywords": [
|
|
5
|
+
"keywords": [
|
|
6
|
+
"optiq",
|
|
7
|
+
"optiq studio",
|
|
8
|
+
"attribution",
|
|
9
|
+
"tracking",
|
|
10
|
+
"conversions",
|
|
11
|
+
"analytics",
|
|
12
|
+
"marketing",
|
|
13
|
+
"whatsapp"
|
|
14
|
+
],
|
|
6
15
|
"homepage": "https://enterprise.optiq.studio/developers",
|
|
7
16
|
"license": "MIT",
|
|
8
17
|
"author": "DaveLabs <info@davelabs.co> (https://davelabs.co)",
|
|
@@ -24,8 +33,14 @@
|
|
|
24
33
|
},
|
|
25
34
|
"./package.json": "./package.json"
|
|
26
35
|
},
|
|
27
|
-
"files": [
|
|
28
|
-
|
|
36
|
+
"files": [
|
|
37
|
+
"dist",
|
|
38
|
+
"README.md",
|
|
39
|
+
"LICENSE"
|
|
40
|
+
],
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=20"
|
|
43
|
+
},
|
|
29
44
|
"scripts": {
|
|
30
45
|
"build": "node build.mjs",
|
|
31
46
|
"test": "node --test test/*.test.mjs",
|