@vibiz/analytics 0.1.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/README.md +49 -0
- package/dist/index.cjs +66 -0
- package/dist/index.d.cts +103 -0
- package/dist/index.d.ts +103 -0
- package/dist/index.js +55 -0
- package/dist/node.cjs +55 -0
- package/dist/node.d.cts +92 -0
- package/dist/node.d.ts +92 -0
- package/dist/node.js +52 -0
- package/package.json +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# @vibiz/analytics
|
|
2
|
+
|
|
3
|
+
Typed client for the Vibiz analytics tag.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm i @vibiz/analytics
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { init, track, identify } from '@vibiz/analytics';
|
|
11
|
+
|
|
12
|
+
init({ key: 'pk_live_xxx' });
|
|
13
|
+
|
|
14
|
+
identify('cust_8842', { email: 'mario.rossi@example.com' });
|
|
15
|
+
track('purchase', { value: 4200, currency: 'EUR', order_id: '84213' });
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Full documentation: <https://www.vibiz.ai/docs/analytics>
|
|
19
|
+
|
|
20
|
+
## What this package is
|
|
21
|
+
|
|
22
|
+
A typed façade, ~1.6 KB, containing **no tracking logic**. It loads the same
|
|
23
|
+
`v.js` a pasted `<script>` tag loads and adds types on top.
|
|
24
|
+
|
|
25
|
+
Shipping a second implementation for bundled apps is the obvious design and the
|
|
26
|
+
wrong one: two copies of cookie handling, consent reading and queue flushing
|
|
27
|
+
drift apart within a release or two, and the drift surfaces as *"it works on the
|
|
28
|
+
Shopify store and is broken in the React app"* — two behaviours under one name,
|
|
29
|
+
with no way to tell from a dashboard which produced a row.
|
|
30
|
+
|
|
31
|
+
One implementation, two ways to reach it. If you have no bundler, skip this
|
|
32
|
+
package entirely:
|
|
33
|
+
|
|
34
|
+
```html
|
|
35
|
+
<script async src="https://www.vibiz.ai/sdk/v1/v.js" data-vibiz-key="pk_live_xxx"></script>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Notes
|
|
39
|
+
|
|
40
|
+
- **Safe during server rendering.** Every call is a no-op when there is no
|
|
41
|
+
`window`, so importing this in a Next.js server component does nothing rather
|
|
42
|
+
than throwing.
|
|
43
|
+
- **Safe before the tag loads.** Calls are queued and replayed once it arrives,
|
|
44
|
+
so you can track from the first line of your app.
|
|
45
|
+
- **The key is public.** `pk_live_…` ships in the page source either way, like a
|
|
46
|
+
Meta pixel id. Traffic is bound to your workspace by allowed domains.
|
|
47
|
+
- **Contact details are never hashed here.** They are hashed by the collector,
|
|
48
|
+
because a digest computed in a browser cannot be re-normalised — and
|
|
49
|
+
normalisation is exactly where matching fails silently.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
// src/index.ts
|
|
6
|
+
var DEFAULT_SCRIPT = "https://www.vibiz.ai/sdk/v1/v.js";
|
|
7
|
+
var SCRIPT_ID = "vibiz-analytics";
|
|
8
|
+
function globalTarget() {
|
|
9
|
+
if (typeof window === "undefined") return null;
|
|
10
|
+
const w = window;
|
|
11
|
+
if (!w.vibiz) w.vibiz = { q: [] };
|
|
12
|
+
if (!w.vibiz.q) w.vibiz.q = [];
|
|
13
|
+
return w.vibiz;
|
|
14
|
+
}
|
|
15
|
+
function call(method, ...args) {
|
|
16
|
+
const target = globalTarget();
|
|
17
|
+
if (!target) return;
|
|
18
|
+
const fn = target[method];
|
|
19
|
+
if (typeof fn === "function") {
|
|
20
|
+
fn(...args);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
target.q?.push([method, ...args]);
|
|
24
|
+
}
|
|
25
|
+
function init(config) {
|
|
26
|
+
if (typeof document === "undefined") return;
|
|
27
|
+
call("init", config);
|
|
28
|
+
if (document.getElementById(SCRIPT_ID)) return;
|
|
29
|
+
const script = document.createElement("script");
|
|
30
|
+
script.id = SCRIPT_ID;
|
|
31
|
+
script.async = true;
|
|
32
|
+
script.src = config.scriptUrl ?? DEFAULT_SCRIPT;
|
|
33
|
+
script.setAttribute("data-vibiz-key", config.key);
|
|
34
|
+
if (config.endpoint)
|
|
35
|
+
script.setAttribute("data-vibiz-endpoint", config.endpoint);
|
|
36
|
+
if (config.autocapture === false)
|
|
37
|
+
script.setAttribute("data-vibiz-autocapture", "false");
|
|
38
|
+
if (config.debug) script.setAttribute("data-vibiz-debug", "true");
|
|
39
|
+
document.head.appendChild(script);
|
|
40
|
+
}
|
|
41
|
+
function track(name, properties) {
|
|
42
|
+
call("track", name, properties);
|
|
43
|
+
}
|
|
44
|
+
function page(properties) {
|
|
45
|
+
call("page", properties);
|
|
46
|
+
}
|
|
47
|
+
function identify(userId, contact) {
|
|
48
|
+
call("identify", userId, contact);
|
|
49
|
+
}
|
|
50
|
+
function setConsent(state) {
|
|
51
|
+
call("setConsent", state);
|
|
52
|
+
}
|
|
53
|
+
function flush() {
|
|
54
|
+
call("flush");
|
|
55
|
+
}
|
|
56
|
+
var vibiz = { init, track, page, identify, setConsent, flush };
|
|
57
|
+
var index_default = vibiz;
|
|
58
|
+
|
|
59
|
+
exports.default = index_default;
|
|
60
|
+
exports.flush = flush;
|
|
61
|
+
exports.identify = identify;
|
|
62
|
+
exports.init = init;
|
|
63
|
+
exports.page = page;
|
|
64
|
+
exports.setConsent = setConsent;
|
|
65
|
+
exports.track = track;
|
|
66
|
+
exports.vibiz = vibiz;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed client for the Vibiz analytics tag.
|
|
3
|
+
*
|
|
4
|
+
* This package deliberately contains NO tracking logic. It loads the same
|
|
5
|
+
* `v.js` that a pasted `<script>` tag loads, and adds types on top.
|
|
6
|
+
*
|
|
7
|
+
* The alternative — shipping a second implementation for bundled apps — is the
|
|
8
|
+
* obvious design and the wrong one. Two copies of cookie handling, consent
|
|
9
|
+
* reading and queue flushing drift apart within a release or two, and the drift
|
|
10
|
+
* surfaces as "it works on the Shopify store and is broken in the React app",
|
|
11
|
+
* which is the worst possible bug to hold: two behaviours, one name, and no way
|
|
12
|
+
* to tell from a dashboard which one produced a row.
|
|
13
|
+
*
|
|
14
|
+
* One implementation, two ways to reach it.
|
|
15
|
+
*
|
|
16
|
+
* Calls made before the tag finishes loading are queued and replayed by the tag
|
|
17
|
+
* itself — no new mechanism here, just the `window.vibiz.q` stub it already
|
|
18
|
+
* drains on startup.
|
|
19
|
+
*/
|
|
20
|
+
/** Events the collector maps onto ad-platform standards. Any other string is kept as your own custom event. */
|
|
21
|
+
type StandardEvent = 'page_view' | 'view_content' | 'search' | 'lead' | 'contact' | 'sign_up' | 'login' | 'add_to_cart' | 'remove_from_cart' | 'add_to_wishlist' | 'begin_checkout' | 'add_payment_info' | 'schedule' | 'purchase' | 'subscribe' | 'start_trial' | 'refund';
|
|
22
|
+
/**
|
|
23
|
+
* `StandardEvent | (string & {})` keeps autocomplete for the known names while
|
|
24
|
+
* still accepting your own — a bare `string` would silently discard the
|
|
25
|
+
* suggestions, and a bare union would reject events we have no business
|
|
26
|
+
* rejecting.
|
|
27
|
+
*/
|
|
28
|
+
type EventName = StandardEvent | (string & {});
|
|
29
|
+
interface EventProperties {
|
|
30
|
+
/** Revenue, in major units. `4200.5`, never cents. */
|
|
31
|
+
value?: number;
|
|
32
|
+
/** ISO 4217, e.g. `EUR`. Meta ignores a value that arrives without one. */
|
|
33
|
+
currency?: string;
|
|
34
|
+
/** Your order id. Doubles as the deduplication key against a server-side copy. */
|
|
35
|
+
order_id?: string;
|
|
36
|
+
[key: string]: unknown;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Contact details, sent only with advertising consent and hashed by the
|
|
40
|
+
* collector before storage. Never hashed here: a digest computed in the browser
|
|
41
|
+
* cannot be re-normalised, and normalisation is where matching silently fails.
|
|
42
|
+
*/
|
|
43
|
+
interface Contact {
|
|
44
|
+
email?: string;
|
|
45
|
+
phone?: string;
|
|
46
|
+
first_name?: string;
|
|
47
|
+
last_name?: string;
|
|
48
|
+
city?: string;
|
|
49
|
+
region?: string;
|
|
50
|
+
postal_code?: string;
|
|
51
|
+
country?: string;
|
|
52
|
+
}
|
|
53
|
+
interface VibizConfig {
|
|
54
|
+
/** Public write key, `pk_live_…`. Safe in client code: it ships in the page source either way. */
|
|
55
|
+
key: string;
|
|
56
|
+
/** Point this at a first-party subdomain to survive ad blockers. */
|
|
57
|
+
endpoint?: string;
|
|
58
|
+
/** Page views, clicks, form submits and time on page without any code. Default true. */
|
|
59
|
+
autocapture?: boolean;
|
|
60
|
+
/** Log every event to the console as it is sent. */
|
|
61
|
+
debug?: boolean;
|
|
62
|
+
/** Override where the tag is loaded from. Only useful when self-hosting it. */
|
|
63
|
+
scriptUrl?: string;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Load the tag and start tracking. Safe to call more than once — the tag itself
|
|
67
|
+
* ignores a second init, because a duplicated install must not double-count
|
|
68
|
+
* every event.
|
|
69
|
+
*/
|
|
70
|
+
declare function init(config: VibizConfig): void;
|
|
71
|
+
/** Record an event. Unknown names are kept as your own custom event, not renamed. */
|
|
72
|
+
declare function track(name: EventName, properties?: EventProperties): void;
|
|
73
|
+
/** Record a page or route view. Only needed when autocapture is off. */
|
|
74
|
+
declare function page(properties?: EventProperties): void;
|
|
75
|
+
/**
|
|
76
|
+
* Attach a person to this browser, and to everything it did before.
|
|
77
|
+
*
|
|
78
|
+
* The moment someone logs in is usually the only moment they say who they are,
|
|
79
|
+
* and it is rarely the moment that matters commercially. Details accumulate
|
|
80
|
+
* across calls rather than replacing each other.
|
|
81
|
+
*/
|
|
82
|
+
declare function identify(userId: string, contact?: Contact): void;
|
|
83
|
+
/**
|
|
84
|
+
* Report consent as your consent tool determined it.
|
|
85
|
+
*
|
|
86
|
+
* Without advertising consent, contact details are never sent and never stored;
|
|
87
|
+
* traffic analytics keep working.
|
|
88
|
+
*/
|
|
89
|
+
declare function setConsent(state: {
|
|
90
|
+
advertising?: boolean;
|
|
91
|
+
}): void;
|
|
92
|
+
/** Send anything queued right now. Rarely needed — the tag flushes on its own. */
|
|
93
|
+
declare function flush(): void;
|
|
94
|
+
declare const vibiz: {
|
|
95
|
+
init: typeof init;
|
|
96
|
+
track: typeof track;
|
|
97
|
+
page: typeof page;
|
|
98
|
+
identify: typeof identify;
|
|
99
|
+
setConsent: typeof setConsent;
|
|
100
|
+
flush: typeof flush;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export { type Contact, type EventName, type EventProperties, type StandardEvent, type VibizConfig, vibiz as default, flush, identify, init, page, setConsent, track, vibiz };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed client for the Vibiz analytics tag.
|
|
3
|
+
*
|
|
4
|
+
* This package deliberately contains NO tracking logic. It loads the same
|
|
5
|
+
* `v.js` that a pasted `<script>` tag loads, and adds types on top.
|
|
6
|
+
*
|
|
7
|
+
* The alternative — shipping a second implementation for bundled apps — is the
|
|
8
|
+
* obvious design and the wrong one. Two copies of cookie handling, consent
|
|
9
|
+
* reading and queue flushing drift apart within a release or two, and the drift
|
|
10
|
+
* surfaces as "it works on the Shopify store and is broken in the React app",
|
|
11
|
+
* which is the worst possible bug to hold: two behaviours, one name, and no way
|
|
12
|
+
* to tell from a dashboard which one produced a row.
|
|
13
|
+
*
|
|
14
|
+
* One implementation, two ways to reach it.
|
|
15
|
+
*
|
|
16
|
+
* Calls made before the tag finishes loading are queued and replayed by the tag
|
|
17
|
+
* itself — no new mechanism here, just the `window.vibiz.q` stub it already
|
|
18
|
+
* drains on startup.
|
|
19
|
+
*/
|
|
20
|
+
/** Events the collector maps onto ad-platform standards. Any other string is kept as your own custom event. */
|
|
21
|
+
type StandardEvent = 'page_view' | 'view_content' | 'search' | 'lead' | 'contact' | 'sign_up' | 'login' | 'add_to_cart' | 'remove_from_cart' | 'add_to_wishlist' | 'begin_checkout' | 'add_payment_info' | 'schedule' | 'purchase' | 'subscribe' | 'start_trial' | 'refund';
|
|
22
|
+
/**
|
|
23
|
+
* `StandardEvent | (string & {})` keeps autocomplete for the known names while
|
|
24
|
+
* still accepting your own — a bare `string` would silently discard the
|
|
25
|
+
* suggestions, and a bare union would reject events we have no business
|
|
26
|
+
* rejecting.
|
|
27
|
+
*/
|
|
28
|
+
type EventName = StandardEvent | (string & {});
|
|
29
|
+
interface EventProperties {
|
|
30
|
+
/** Revenue, in major units. `4200.5`, never cents. */
|
|
31
|
+
value?: number;
|
|
32
|
+
/** ISO 4217, e.g. `EUR`. Meta ignores a value that arrives without one. */
|
|
33
|
+
currency?: string;
|
|
34
|
+
/** Your order id. Doubles as the deduplication key against a server-side copy. */
|
|
35
|
+
order_id?: string;
|
|
36
|
+
[key: string]: unknown;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Contact details, sent only with advertising consent and hashed by the
|
|
40
|
+
* collector before storage. Never hashed here: a digest computed in the browser
|
|
41
|
+
* cannot be re-normalised, and normalisation is where matching silently fails.
|
|
42
|
+
*/
|
|
43
|
+
interface Contact {
|
|
44
|
+
email?: string;
|
|
45
|
+
phone?: string;
|
|
46
|
+
first_name?: string;
|
|
47
|
+
last_name?: string;
|
|
48
|
+
city?: string;
|
|
49
|
+
region?: string;
|
|
50
|
+
postal_code?: string;
|
|
51
|
+
country?: string;
|
|
52
|
+
}
|
|
53
|
+
interface VibizConfig {
|
|
54
|
+
/** Public write key, `pk_live_…`. Safe in client code: it ships in the page source either way. */
|
|
55
|
+
key: string;
|
|
56
|
+
/** Point this at a first-party subdomain to survive ad blockers. */
|
|
57
|
+
endpoint?: string;
|
|
58
|
+
/** Page views, clicks, form submits and time on page without any code. Default true. */
|
|
59
|
+
autocapture?: boolean;
|
|
60
|
+
/** Log every event to the console as it is sent. */
|
|
61
|
+
debug?: boolean;
|
|
62
|
+
/** Override where the tag is loaded from. Only useful when self-hosting it. */
|
|
63
|
+
scriptUrl?: string;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Load the tag and start tracking. Safe to call more than once — the tag itself
|
|
67
|
+
* ignores a second init, because a duplicated install must not double-count
|
|
68
|
+
* every event.
|
|
69
|
+
*/
|
|
70
|
+
declare function init(config: VibizConfig): void;
|
|
71
|
+
/** Record an event. Unknown names are kept as your own custom event, not renamed. */
|
|
72
|
+
declare function track(name: EventName, properties?: EventProperties): void;
|
|
73
|
+
/** Record a page or route view. Only needed when autocapture is off. */
|
|
74
|
+
declare function page(properties?: EventProperties): void;
|
|
75
|
+
/**
|
|
76
|
+
* Attach a person to this browser, and to everything it did before.
|
|
77
|
+
*
|
|
78
|
+
* The moment someone logs in is usually the only moment they say who they are,
|
|
79
|
+
* and it is rarely the moment that matters commercially. Details accumulate
|
|
80
|
+
* across calls rather than replacing each other.
|
|
81
|
+
*/
|
|
82
|
+
declare function identify(userId: string, contact?: Contact): void;
|
|
83
|
+
/**
|
|
84
|
+
* Report consent as your consent tool determined it.
|
|
85
|
+
*
|
|
86
|
+
* Without advertising consent, contact details are never sent and never stored;
|
|
87
|
+
* traffic analytics keep working.
|
|
88
|
+
*/
|
|
89
|
+
declare function setConsent(state: {
|
|
90
|
+
advertising?: boolean;
|
|
91
|
+
}): void;
|
|
92
|
+
/** Send anything queued right now. Rarely needed — the tag flushes on its own. */
|
|
93
|
+
declare function flush(): void;
|
|
94
|
+
declare const vibiz: {
|
|
95
|
+
init: typeof init;
|
|
96
|
+
track: typeof track;
|
|
97
|
+
page: typeof page;
|
|
98
|
+
identify: typeof identify;
|
|
99
|
+
setConsent: typeof setConsent;
|
|
100
|
+
flush: typeof flush;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export { type Contact, type EventName, type EventProperties, type StandardEvent, type VibizConfig, vibiz as default, flush, identify, init, page, setConsent, track, vibiz };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
var DEFAULT_SCRIPT = "https://www.vibiz.ai/sdk/v1/v.js";
|
|
3
|
+
var SCRIPT_ID = "vibiz-analytics";
|
|
4
|
+
function globalTarget() {
|
|
5
|
+
if (typeof window === "undefined") return null;
|
|
6
|
+
const w = window;
|
|
7
|
+
if (!w.vibiz) w.vibiz = { q: [] };
|
|
8
|
+
if (!w.vibiz.q) w.vibiz.q = [];
|
|
9
|
+
return w.vibiz;
|
|
10
|
+
}
|
|
11
|
+
function call(method, ...args) {
|
|
12
|
+
const target = globalTarget();
|
|
13
|
+
if (!target) return;
|
|
14
|
+
const fn = target[method];
|
|
15
|
+
if (typeof fn === "function") {
|
|
16
|
+
fn(...args);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
target.q?.push([method, ...args]);
|
|
20
|
+
}
|
|
21
|
+
function init(config) {
|
|
22
|
+
if (typeof document === "undefined") return;
|
|
23
|
+
call("init", config);
|
|
24
|
+
if (document.getElementById(SCRIPT_ID)) return;
|
|
25
|
+
const script = document.createElement("script");
|
|
26
|
+
script.id = SCRIPT_ID;
|
|
27
|
+
script.async = true;
|
|
28
|
+
script.src = config.scriptUrl ?? DEFAULT_SCRIPT;
|
|
29
|
+
script.setAttribute("data-vibiz-key", config.key);
|
|
30
|
+
if (config.endpoint)
|
|
31
|
+
script.setAttribute("data-vibiz-endpoint", config.endpoint);
|
|
32
|
+
if (config.autocapture === false)
|
|
33
|
+
script.setAttribute("data-vibiz-autocapture", "false");
|
|
34
|
+
if (config.debug) script.setAttribute("data-vibiz-debug", "true");
|
|
35
|
+
document.head.appendChild(script);
|
|
36
|
+
}
|
|
37
|
+
function track(name, properties) {
|
|
38
|
+
call("track", name, properties);
|
|
39
|
+
}
|
|
40
|
+
function page(properties) {
|
|
41
|
+
call("page", properties);
|
|
42
|
+
}
|
|
43
|
+
function identify(userId, contact) {
|
|
44
|
+
call("identify", userId, contact);
|
|
45
|
+
}
|
|
46
|
+
function setConsent(state) {
|
|
47
|
+
call("setConsent", state);
|
|
48
|
+
}
|
|
49
|
+
function flush() {
|
|
50
|
+
call("flush");
|
|
51
|
+
}
|
|
52
|
+
var vibiz = { init, track, page, identify, setConsent, flush };
|
|
53
|
+
var index_default = vibiz;
|
|
54
|
+
|
|
55
|
+
export { index_default as default, flush, identify, init, page, setConsent, track, vibiz };
|
package/dist/node.cjs
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/node.ts
|
|
4
|
+
var DEFAULT_ENDPOINT = "https://www.vibiz.ai/api/ingest";
|
|
5
|
+
var PROTOCOL_VERSION = 1;
|
|
6
|
+
async function track(input) {
|
|
7
|
+
if (!input.key) return { ok: false, reason: "missing key" };
|
|
8
|
+
if (!input.event) return { ok: false, reason: "missing event" };
|
|
9
|
+
if (!input.visitorId && !input.userId && !input.contact) {
|
|
10
|
+
return {
|
|
11
|
+
ok: false,
|
|
12
|
+
reason: "no visitorId, userId or contact: nothing to attribute to"
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
const body = {
|
|
16
|
+
v: PROTOCOL_VERSION,
|
|
17
|
+
k: input.key,
|
|
18
|
+
n: input.event,
|
|
19
|
+
vid: input.visitorId ?? `srv-${input.userId ?? "anon"}`
|
|
20
|
+
};
|
|
21
|
+
if (input.userId) body.uid = input.userId;
|
|
22
|
+
if (input.properties) body.p = input.properties;
|
|
23
|
+
if (input.timestamp) body.ts = input.timestamp;
|
|
24
|
+
if (input.contact && input.advertisingConsent === true) {
|
|
25
|
+
body.pii = input.contact;
|
|
26
|
+
body.cad = true;
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
const headers = {
|
|
30
|
+
// text/plain matches what the tag sends, so both halves hit the same
|
|
31
|
+
// parsing path on the collector rather than two that can diverge.
|
|
32
|
+
"Content-Type": "text/plain;charset=UTF-8"
|
|
33
|
+
};
|
|
34
|
+
if (input.serverKey) headers["x-vibiz-server-key"] = input.serverKey;
|
|
35
|
+
const response = await fetch(input.endpoint ?? DEFAULT_ENDPOINT, {
|
|
36
|
+
method: "POST",
|
|
37
|
+
headers,
|
|
38
|
+
body: JSON.stringify(body)
|
|
39
|
+
});
|
|
40
|
+
return response.ok ? { ok: true } : { ok: false, reason: `HTTP ${response.status}` };
|
|
41
|
+
} catch (error) {
|
|
42
|
+
return {
|
|
43
|
+
ok: false,
|
|
44
|
+
reason: error instanceof Error ? error.message : "request failed"
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async function trackBatch(events) {
|
|
49
|
+
const results = [];
|
|
50
|
+
for (const event of events) results.push(await track(event));
|
|
51
|
+
return results;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
exports.track = track;
|
|
55
|
+
exports.trackBatch = trackBatch;
|
package/dist/node.d.cts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server side tracking.
|
|
3
|
+
*
|
|
4
|
+
* The browser cannot see a webhook, a background job, or a payment confirmed
|
|
5
|
+
* by a provider three seconds after the customer closed the tab. Those are the
|
|
6
|
+
* events worth the most, because they carry the revenue that actually settled
|
|
7
|
+
* rather than the total that was in the cart.
|
|
8
|
+
*
|
|
9
|
+
* Deliberately dependency free and built on `fetch`: this runs in whatever
|
|
10
|
+
* runtime a customer's backend happens to be, and a tracking call is not worth
|
|
11
|
+
* a dependency anyone has to resolve.
|
|
12
|
+
*/
|
|
13
|
+
interface Contact {
|
|
14
|
+
email?: string;
|
|
15
|
+
phone?: string;
|
|
16
|
+
first_name?: string;
|
|
17
|
+
last_name?: string;
|
|
18
|
+
city?: string;
|
|
19
|
+
region?: string;
|
|
20
|
+
postal_code?: string;
|
|
21
|
+
country?: string;
|
|
22
|
+
}
|
|
23
|
+
interface ServerEventProperties {
|
|
24
|
+
/** Revenue in major units. `4200.5`, never cents. */
|
|
25
|
+
value?: number;
|
|
26
|
+
/** ISO 4217. A value without one is discarded by Meta. */
|
|
27
|
+
currency?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Your order id, and the deduplication key.
|
|
30
|
+
*
|
|
31
|
+
* Send the SAME id the browser sent. Without it a purchase tracked in both
|
|
32
|
+
* places counts twice, which is worse than tracking it in neither: the
|
|
33
|
+
* numbers look better and the optimisation is trained on revenue that does
|
|
34
|
+
* not exist.
|
|
35
|
+
*/
|
|
36
|
+
order_id?: string;
|
|
37
|
+
[key: string]: unknown;
|
|
38
|
+
}
|
|
39
|
+
interface ServerTrackInput {
|
|
40
|
+
/** Public write key. Says which workspace the event belongs to. */
|
|
41
|
+
key: string;
|
|
42
|
+
/**
|
|
43
|
+
* Server key, from your Vibiz workspace. Unlike `key` this IS a credential:
|
|
44
|
+
* keep it in your environment and never send it to a browser.
|
|
45
|
+
*
|
|
46
|
+
* Required once the site has an origin allowlist, because a server call
|
|
47
|
+
* carries no `Origin` for that allowlist to check. Without it the allowlist
|
|
48
|
+
* would be bypassable by anyone willing to use curl instead of a browser.
|
|
49
|
+
*/
|
|
50
|
+
serverKey?: string;
|
|
51
|
+
event: string;
|
|
52
|
+
/**
|
|
53
|
+
* The visitor id the browser minted, when you have it.
|
|
54
|
+
*
|
|
55
|
+
* This is what joins a server side conversion to the visit that produced it,
|
|
56
|
+
* and therefore to the ad click. Persist it with the order at checkout: it
|
|
57
|
+
* is readable from the `vbz_vid` cookie, and it is the difference between a
|
|
58
|
+
* purchase attributed to a campaign and one attributed to nobody.
|
|
59
|
+
*/
|
|
60
|
+
visitorId?: string;
|
|
61
|
+
/** Your own user id. Becomes Meta's `external_id`. */
|
|
62
|
+
userId?: string;
|
|
63
|
+
properties?: ServerEventProperties;
|
|
64
|
+
/** Hashed by the collector, never here: a digest computed early cannot be re-normalised. */
|
|
65
|
+
contact?: Contact;
|
|
66
|
+
/** Advertising consent. Without `true`, contact details are neither sent nor stored. */
|
|
67
|
+
advertisingConsent?: boolean;
|
|
68
|
+
/** When it happened, if not now. Milliseconds. */
|
|
69
|
+
timestamp?: number;
|
|
70
|
+
endpoint?: string;
|
|
71
|
+
}
|
|
72
|
+
interface ServerTrackResult {
|
|
73
|
+
ok: boolean;
|
|
74
|
+
reason?: string;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Send one event. NEVER THROWS.
|
|
78
|
+
*
|
|
79
|
+
* A tracking call that takes down a checkout handler is a far worse outcome
|
|
80
|
+
* than a missing event, and this runs inside payment webhooks and order
|
|
81
|
+
* confirmations. Failures come back as values so a caller can log them and
|
|
82
|
+
* carry on.
|
|
83
|
+
*
|
|
84
|
+
* Not awaited internally either: there is no retry here on purpose. A caller
|
|
85
|
+
* inside a webhook already has a retry policy imposed by the provider, and a
|
|
86
|
+
* second one underneath it produces duplicate events on every redelivery.
|
|
87
|
+
*/
|
|
88
|
+
declare function track(input: ServerTrackInput): Promise<ServerTrackResult>;
|
|
89
|
+
/** Send several events. Sequential on purpose: order is preserved and no burst is created. */
|
|
90
|
+
declare function trackBatch(events: ServerTrackInput[]): Promise<ServerTrackResult[]>;
|
|
91
|
+
|
|
92
|
+
export { type Contact, type ServerEventProperties, type ServerTrackInput, type ServerTrackResult, track, trackBatch };
|
package/dist/node.d.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server side tracking.
|
|
3
|
+
*
|
|
4
|
+
* The browser cannot see a webhook, a background job, or a payment confirmed
|
|
5
|
+
* by a provider three seconds after the customer closed the tab. Those are the
|
|
6
|
+
* events worth the most, because they carry the revenue that actually settled
|
|
7
|
+
* rather than the total that was in the cart.
|
|
8
|
+
*
|
|
9
|
+
* Deliberately dependency free and built on `fetch`: this runs in whatever
|
|
10
|
+
* runtime a customer's backend happens to be, and a tracking call is not worth
|
|
11
|
+
* a dependency anyone has to resolve.
|
|
12
|
+
*/
|
|
13
|
+
interface Contact {
|
|
14
|
+
email?: string;
|
|
15
|
+
phone?: string;
|
|
16
|
+
first_name?: string;
|
|
17
|
+
last_name?: string;
|
|
18
|
+
city?: string;
|
|
19
|
+
region?: string;
|
|
20
|
+
postal_code?: string;
|
|
21
|
+
country?: string;
|
|
22
|
+
}
|
|
23
|
+
interface ServerEventProperties {
|
|
24
|
+
/** Revenue in major units. `4200.5`, never cents. */
|
|
25
|
+
value?: number;
|
|
26
|
+
/** ISO 4217. A value without one is discarded by Meta. */
|
|
27
|
+
currency?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Your order id, and the deduplication key.
|
|
30
|
+
*
|
|
31
|
+
* Send the SAME id the browser sent. Without it a purchase tracked in both
|
|
32
|
+
* places counts twice, which is worse than tracking it in neither: the
|
|
33
|
+
* numbers look better and the optimisation is trained on revenue that does
|
|
34
|
+
* not exist.
|
|
35
|
+
*/
|
|
36
|
+
order_id?: string;
|
|
37
|
+
[key: string]: unknown;
|
|
38
|
+
}
|
|
39
|
+
interface ServerTrackInput {
|
|
40
|
+
/** Public write key. Says which workspace the event belongs to. */
|
|
41
|
+
key: string;
|
|
42
|
+
/**
|
|
43
|
+
* Server key, from your Vibiz workspace. Unlike `key` this IS a credential:
|
|
44
|
+
* keep it in your environment and never send it to a browser.
|
|
45
|
+
*
|
|
46
|
+
* Required once the site has an origin allowlist, because a server call
|
|
47
|
+
* carries no `Origin` for that allowlist to check. Without it the allowlist
|
|
48
|
+
* would be bypassable by anyone willing to use curl instead of a browser.
|
|
49
|
+
*/
|
|
50
|
+
serverKey?: string;
|
|
51
|
+
event: string;
|
|
52
|
+
/**
|
|
53
|
+
* The visitor id the browser minted, when you have it.
|
|
54
|
+
*
|
|
55
|
+
* This is what joins a server side conversion to the visit that produced it,
|
|
56
|
+
* and therefore to the ad click. Persist it with the order at checkout: it
|
|
57
|
+
* is readable from the `vbz_vid` cookie, and it is the difference between a
|
|
58
|
+
* purchase attributed to a campaign and one attributed to nobody.
|
|
59
|
+
*/
|
|
60
|
+
visitorId?: string;
|
|
61
|
+
/** Your own user id. Becomes Meta's `external_id`. */
|
|
62
|
+
userId?: string;
|
|
63
|
+
properties?: ServerEventProperties;
|
|
64
|
+
/** Hashed by the collector, never here: a digest computed early cannot be re-normalised. */
|
|
65
|
+
contact?: Contact;
|
|
66
|
+
/** Advertising consent. Without `true`, contact details are neither sent nor stored. */
|
|
67
|
+
advertisingConsent?: boolean;
|
|
68
|
+
/** When it happened, if not now. Milliseconds. */
|
|
69
|
+
timestamp?: number;
|
|
70
|
+
endpoint?: string;
|
|
71
|
+
}
|
|
72
|
+
interface ServerTrackResult {
|
|
73
|
+
ok: boolean;
|
|
74
|
+
reason?: string;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Send one event. NEVER THROWS.
|
|
78
|
+
*
|
|
79
|
+
* A tracking call that takes down a checkout handler is a far worse outcome
|
|
80
|
+
* than a missing event, and this runs inside payment webhooks and order
|
|
81
|
+
* confirmations. Failures come back as values so a caller can log them and
|
|
82
|
+
* carry on.
|
|
83
|
+
*
|
|
84
|
+
* Not awaited internally either: there is no retry here on purpose. A caller
|
|
85
|
+
* inside a webhook already has a retry policy imposed by the provider, and a
|
|
86
|
+
* second one underneath it produces duplicate events on every redelivery.
|
|
87
|
+
*/
|
|
88
|
+
declare function track(input: ServerTrackInput): Promise<ServerTrackResult>;
|
|
89
|
+
/** Send several events. Sequential on purpose: order is preserved and no burst is created. */
|
|
90
|
+
declare function trackBatch(events: ServerTrackInput[]): Promise<ServerTrackResult[]>;
|
|
91
|
+
|
|
92
|
+
export { type Contact, type ServerEventProperties, type ServerTrackInput, type ServerTrackResult, track, trackBatch };
|
package/dist/node.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// src/node.ts
|
|
2
|
+
var DEFAULT_ENDPOINT = "https://www.vibiz.ai/api/ingest";
|
|
3
|
+
var PROTOCOL_VERSION = 1;
|
|
4
|
+
async function track(input) {
|
|
5
|
+
if (!input.key) return { ok: false, reason: "missing key" };
|
|
6
|
+
if (!input.event) return { ok: false, reason: "missing event" };
|
|
7
|
+
if (!input.visitorId && !input.userId && !input.contact) {
|
|
8
|
+
return {
|
|
9
|
+
ok: false,
|
|
10
|
+
reason: "no visitorId, userId or contact: nothing to attribute to"
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
const body = {
|
|
14
|
+
v: PROTOCOL_VERSION,
|
|
15
|
+
k: input.key,
|
|
16
|
+
n: input.event,
|
|
17
|
+
vid: input.visitorId ?? `srv-${input.userId ?? "anon"}`
|
|
18
|
+
};
|
|
19
|
+
if (input.userId) body.uid = input.userId;
|
|
20
|
+
if (input.properties) body.p = input.properties;
|
|
21
|
+
if (input.timestamp) body.ts = input.timestamp;
|
|
22
|
+
if (input.contact && input.advertisingConsent === true) {
|
|
23
|
+
body.pii = input.contact;
|
|
24
|
+
body.cad = true;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const headers = {
|
|
28
|
+
// text/plain matches what the tag sends, so both halves hit the same
|
|
29
|
+
// parsing path on the collector rather than two that can diverge.
|
|
30
|
+
"Content-Type": "text/plain;charset=UTF-8"
|
|
31
|
+
};
|
|
32
|
+
if (input.serverKey) headers["x-vibiz-server-key"] = input.serverKey;
|
|
33
|
+
const response = await fetch(input.endpoint ?? DEFAULT_ENDPOINT, {
|
|
34
|
+
method: "POST",
|
|
35
|
+
headers,
|
|
36
|
+
body: JSON.stringify(body)
|
|
37
|
+
});
|
|
38
|
+
return response.ok ? { ok: true } : { ok: false, reason: `HTTP ${response.status}` };
|
|
39
|
+
} catch (error) {
|
|
40
|
+
return {
|
|
41
|
+
ok: false,
|
|
42
|
+
reason: error instanceof Error ? error.message : "request failed"
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async function trackBatch(events) {
|
|
47
|
+
const results = [];
|
|
48
|
+
for (const event of events) results.push(await track(event));
|
|
49
|
+
return results;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export { track, trackBatch };
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vibiz/analytics",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Typed client for the Vibiz analytics tag.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"main": "./dist/index.cjs",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"require": {
|
|
17
|
+
"types": "./dist/index.d.cts",
|
|
18
|
+
"default": "./dist/index.cjs"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"./node": {
|
|
22
|
+
"import": {
|
|
23
|
+
"types": "./dist/node.d.ts",
|
|
24
|
+
"default": "./dist/node.js"
|
|
25
|
+
},
|
|
26
|
+
"require": {
|
|
27
|
+
"types": "./dist/node.d.cts",
|
|
28
|
+
"default": "./dist/node.cjs"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"./package.json": "./package.json"
|
|
32
|
+
},
|
|
33
|
+
"files": ["dist", "README.md"],
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsup src/index.ts src/node.ts --format esm,cjs --dts --clean --treeshake",
|
|
36
|
+
"typecheck": "tsc --noEmit"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"tsup": "^8.5.0",
|
|
40
|
+
"typescript": "^5.7.2"
|
|
41
|
+
},
|
|
42
|
+
"keywords": ["vibiz", "analytics", "attribution", "conversions"],
|
|
43
|
+
"homepage": "https://www.vibiz.ai/docs/analytics",
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public"
|
|
46
|
+
},
|
|
47
|
+
"license": "UNLICENSED"
|
|
48
|
+
}
|