@astroscope/relay 1.0.0 → 1.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 +3 -1
- package/dist/{conversion-user-CV8xIWDK.js → conversion-user-BgpXmIdJ.js} +21 -4
- package/dist/conversion-user-BgpXmIdJ.js.map +1 -0
- package/dist/{conversion-user-DCfM5EuZ.d.ts → conversion-user-fHh2xXBc.d.ts} +11 -1
- package/dist/conversion-user-fHh2xXBc.d.ts.map +1 -0
- package/dist/index.browser.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/meta/index.d.ts +95 -0
- package/dist/meta/index.d.ts.map +1 -0
- package/dist/meta/index.js +84 -0
- package/dist/meta/index.js.map +1 -0
- package/dist/meta/server.d.ts +20 -0
- package/dist/meta/server.d.ts.map +1 -0
- package/dist/meta/server.js +77 -0
- package/dist/meta/server.js.map +1 -0
- package/dist/openai/index.d.ts +78 -17
- package/dist/openai/index.d.ts.map +1 -1
- package/dist/openai/index.js +79 -15
- package/dist/openai/index.js.map +1 -1
- package/dist/openai/server.d.ts.map +1 -1
- package/dist/openai/server.js +4 -2
- package/dist/openai/server.js.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +1 -1
- package/dist/source-url-Cqfh3UjO.js +13 -0
- package/dist/source-url-Cqfh3UjO.js.map +1 -0
- package/package.json +10 -1
- package/dist/conversion-user-CV8xIWDK.js.map +0 -1
- package/dist/conversion-user-DCfM5EuZ.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -227,8 +227,10 @@ A server destination is a `name` and a `send(payload, meta)`, where `meta` adds
|
|
|
227
227
|
| `@astroscope/relay/gtm` | `gtm.purchase(commerce, params?)`, … and `gtm.event(name, params)` | `gtmContainer()`: `dataLayer` pushes with `event_id`; consent is the container's own embedding |
|
|
228
228
|
| `@astroscope/relay/matomo` | `matomo.event()`, `matomo.siteSearch()`, `matomo.order()`, `matomo.calls(…)` | `matomoTracker()`: the `_paq` queue; a cookieless setup is `false` in the consent source |
|
|
229
229
|
| `@astroscope/relay/awin` | `awin.sale({ amount, orderRef, currency, voucher?, parts? })` | `awinMastertag({ advertiserId, testMode? })`: declares `AWIN.Tracking.Sale` and fires the fall-back pixel |
|
|
230
|
-
| `@astroscope/relay/openai` | `openai.contentsViewed()`, `itemsAdded()`, `checkoutStarted()`, `orderCreated()`, `leadCreated()` | `openAiPixel({ countryCallingCode? })`: the openai ads pixel, identifiers hashed and sent through `init` |
|
|
230
|
+
| `@astroscope/relay/openai` | every event openai names: `openai.pageViewed()`, `contentsViewed()`, `itemsAdded()`, `checkoutStarted()`, `orderCreated()`, `leadCreated()`, `registrationCompleted()`, `appointmentScheduled()`, `subscriptionCreated()`, `trialStarted()`, and `custom(name)` | `openAiPixel({ countryCallingCode? })`: the openai ads pixel, identifiers hashed and sent through `init` |
|
|
231
231
|
| `@astroscope/relay/openai/server` | the same builders | `openAiConversions({ apiKey, pixelId, integrationSource, countryCallingCode? })`: the conversions api |
|
|
232
|
+
| `@astroscope/relay/meta` | `meta.purchase()`, `meta.lead()`, `meta.subscribe()`, … the standard events, and `meta.event(name, data)` | `metaPixel({ pixelId, countryCallingCode? })`: the meta pixel the cmp's base code initialized, waited for, events fired on it alone, identifiers hashed into advanced matching; track `meta.pageView()` from the layout |
|
|
233
|
+
| `@astroscope/relay/meta/server` | the same builders | `metaConversions({ accessToken, pixelId, testEventCode?, countryCallingCode? })`: the conversions api, with `fbp`/`fbc` off the pixel's cookies |
|
|
232
234
|
|
|
233
235
|
A destination of your own follows the same shape: builders for the payload, a browser destination, a server destination, all under one name. `wellKnownEvents(create)` from the root entry makes a builder set of the well-known events.
|
|
234
236
|
|
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
// src/conversion-user.ts
|
|
2
2
|
/** normalizes and hashes the identifiers the way the pixels and the conversions apis expect them */
|
|
3
3
|
async function hashUser(user, options = {}) {
|
|
4
|
-
const [email, phone, firstName, lastName] = await Promise.all([
|
|
4
|
+
const [email, phone, firstName, lastName, city, postalCode, country, externalId] = await Promise.all([
|
|
5
5
|
hashOptional(normalizeEmail(user.email)),
|
|
6
6
|
hashOptional(normalizePhone(user.phone, options.countryCallingCode)),
|
|
7
7
|
hashOptional(normalizeName(user.firstName)),
|
|
8
|
-
hashOptional(normalizeName(user.lastName))
|
|
8
|
+
hashOptional(normalizeName(user.lastName)),
|
|
9
|
+
hashOptional(normalizeName(user.city)),
|
|
10
|
+
hashOptional(normalizePostalCode(user.postalCode)),
|
|
11
|
+
hashOptional(normalizeCountry(user.country)),
|
|
12
|
+
hashOptional(user.externalId?.trim())
|
|
9
13
|
]);
|
|
10
14
|
return withoutEmpty({
|
|
11
15
|
email,
|
|
12
16
|
phone,
|
|
13
17
|
firstName,
|
|
14
|
-
lastName
|
|
18
|
+
lastName,
|
|
19
|
+
city,
|
|
20
|
+
postalCode,
|
|
21
|
+
country,
|
|
22
|
+
externalId
|
|
15
23
|
});
|
|
16
24
|
}
|
|
17
25
|
function normalizeEmail(email) {
|
|
@@ -34,6 +42,15 @@ function normalizePhone(phone, countryCallingCode) {
|
|
|
34
42
|
function normalizeName(name) {
|
|
35
43
|
return name?.toLowerCase().replaceAll(/[\s!-/:-@[-`{-~]/g, "") || void 0;
|
|
36
44
|
}
|
|
45
|
+
/** lowercase without whitespace */
|
|
46
|
+
function normalizePostalCode(postalCode) {
|
|
47
|
+
return postalCode?.toLowerCase().replaceAll(/\s/g, "") || void 0;
|
|
48
|
+
}
|
|
49
|
+
/** the lowercase two-letter code, anything else is dropped */
|
|
50
|
+
function normalizeCountry(country) {
|
|
51
|
+
const code = country?.trim().toLowerCase();
|
|
52
|
+
return code && /^[a-z]{2}$/.test(code) ? code : void 0;
|
|
53
|
+
}
|
|
37
54
|
async function sha256(value) {
|
|
38
55
|
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value));
|
|
39
56
|
return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
@@ -46,4 +63,4 @@ function withoutEmpty(user) {
|
|
|
46
63
|
}
|
|
47
64
|
export { sha256 as a, normalizePhone as i, normalizeEmail as n, normalizeName as r, hashUser as t };
|
|
48
65
|
|
|
49
|
-
//# sourceMappingURL=conversion-user-
|
|
66
|
+
//# sourceMappingURL=conversion-user-BgpXmIdJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversion-user-BgpXmIdJ.js","names":[],"sources":["../src/conversion-user.ts"],"sourcesContent":["/** what the visitor entered for a conversion; a destination that identifies hashes it as it requires */\nexport type ConversionUser = {\n email?: string | undefined;\n phone?: string | undefined;\n firstName?: string | undefined;\n lastName?: string | undefined;\n city?: string | undefined;\n postalCode?: string | undefined;\n /** the two-letter country code */\n country?: string | undefined;\n /** the site's own id of the visitor: a customer or subscriber id */\n externalId?: string | undefined;\n};\n\n/** the identifiers of a visitor as the ad platforms match them: normalized, then sha256 as lowercase hex */\nexport type HashedUser = {\n email?: string | undefined;\n phone?: string | undefined;\n firstName?: string | undefined;\n lastName?: string | undefined;\n city?: string | undefined;\n postalCode?: string | undefined;\n country?: string | undefined;\n externalId?: string | undefined;\n};\n\nexport type HashOptions = {\n /**\n * The country calling code a national phone number gets (`49` for a German site): a number\n * with one leading zero is taken as national, `+` or `00` prefixes are international.\n */\n countryCallingCode?: string | undefined;\n};\n\n/** normalizes and hashes the identifiers the way the pixels and the conversions apis expect them */\nexport async function hashUser(user: ConversionUser, options: HashOptions = {}): Promise<HashedUser> {\n const [email, phone, firstName, lastName, city, postalCode, country, externalId] = await Promise.all([\n hashOptional(normalizeEmail(user.email)),\n hashOptional(normalizePhone(user.phone, options.countryCallingCode)),\n hashOptional(normalizeName(user.firstName)),\n hashOptional(normalizeName(user.lastName)),\n hashOptional(normalizeName(user.city)),\n hashOptional(normalizePostalCode(user.postalCode)),\n hashOptional(normalizeCountry(user.country)),\n hashOptional(user.externalId?.trim()),\n ]);\n\n return withoutEmpty({ email, phone, firstName, lastName, city, postalCode, country, externalId });\n}\n\nexport function normalizeEmail(email: string | undefined): string | undefined {\n return email?.trim().toLowerCase() || undefined;\n}\n\n/**\n * Digits only, with the country calling code: a national number (one leading zero) gets the\n * site's code, `+` and `00` prefixes are dropped. Returns `undefined` for anything that is not\n * 8 to 15 digits afterwards.\n */\nexport function normalizePhone(phone: string | undefined, countryCallingCode: string | undefined): string | undefined {\n const compact = phone?.replaceAll(/[\\s().-]/g, '');\n\n if (!compact) {\n return undefined;\n }\n\n const international = compact.startsWith('+') || compact.startsWith('00');\n const national = compact.replace(/^\\+?0*/, '');\n const digits = international || !compact.startsWith('0') ? national : `${countryCallingCode ?? ''}${national}`;\n\n return /^\\d{8,15}$/.test(digits) ? digits : undefined;\n}\n\n/** lowercase, without whitespace and punctuation */\nexport function normalizeName(name: string | undefined): string | undefined {\n return name?.toLowerCase().replaceAll(/[\\s!-/:-@[-`{-~]/g, '') || undefined;\n}\n\n/** lowercase without whitespace */\nexport function normalizePostalCode(postalCode: string | undefined): string | undefined {\n return postalCode?.toLowerCase().replaceAll(/\\s/g, '') || undefined;\n}\n\n/** the lowercase two-letter code, anything else is dropped */\nexport function normalizeCountry(country: string | undefined): string | undefined {\n const code = country?.trim().toLowerCase();\n\n return code && /^[a-z]{2}$/.test(code) ? code : undefined;\n}\n\nexport async function sha256(value: string): Promise<string> {\n const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(value));\n\n return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, '0')).join('');\n}\n\nasync function hashOptional(value: string | undefined): Promise<string | undefined> {\n return value ? sha256(value) : undefined;\n}\n\nfunction withoutEmpty(user: HashedUser): HashedUser {\n return Object.fromEntries(Object.entries(user).filter(([, value]) => value)) as HashedUser;\n}\n"],"mappings":";;AAmCA,eAAsB,SAAS,MAAsB,UAAuB,CAAC,GAAwB;CACnG,MAAM,CAAC,OAAO,OAAO,WAAW,UAAU,MAAM,YAAY,SAAS,cAAc,MAAM,QAAQ,IAAI;EACnG,aAAa,eAAe,KAAK,KAAK,CAAC;EACvC,aAAa,eAAe,KAAK,OAAO,QAAQ,kBAAkB,CAAC;EACnE,aAAa,cAAc,KAAK,SAAS,CAAC;EAC1C,aAAa,cAAc,KAAK,QAAQ,CAAC;EACzC,aAAa,cAAc,KAAK,IAAI,CAAC;EACrC,aAAa,oBAAoB,KAAK,UAAU,CAAC;EACjD,aAAa,iBAAiB,KAAK,OAAO,CAAC;EAC3C,aAAa,KAAK,YAAY,KAAK,CAAC;CACtC,CAAC;CAED,OAAO,aAAa;EAAE;EAAO;EAAO;EAAW;EAAU;EAAM;EAAY;EAAS;CAAW,CAAC;AAClG;AAEA,SAAgB,eAAe,OAA+C;CAC5E,OAAO,OAAO,KAAK,CAAC,CAAC,YAAY,KAAK,KAAA;AACxC;;;;;;AAOA,SAAgB,eAAe,OAA2B,oBAA4D;CACpH,MAAM,UAAU,OAAO,WAAW,aAAa,EAAE;CAEjD,IAAI,CAAC,SACH;CAGF,MAAM,gBAAgB,QAAQ,WAAW,GAAG,KAAK,QAAQ,WAAW,IAAI;CACxE,MAAM,WAAW,QAAQ,QAAQ,UAAU,EAAE;CAC7C,MAAM,SAAS,iBAAiB,CAAC,QAAQ,WAAW,GAAG,IAAI,WAAW,GAAG,sBAAsB,KAAK;CAEpG,OAAO,aAAa,KAAK,MAAM,IAAI,SAAS,KAAA;AAC9C;;AAGA,SAAgB,cAAc,MAA8C;CAC1E,OAAO,MAAM,YAAY,CAAC,CAAC,WAAW,qBAAqB,EAAE,KAAK,KAAA;AACpE;;AAGA,SAAgB,oBAAoB,YAAoD;CACtF,OAAO,YAAY,YAAY,CAAC,CAAC,WAAW,OAAO,EAAE,KAAK,KAAA;AAC5D;;AAGA,SAAgB,iBAAiB,SAAiD;CAChF,MAAM,OAAO,SAAS,KAAK,CAAC,CAAC,YAAY;CAEzC,OAAO,QAAQ,aAAa,KAAK,IAAI,IAAI,OAAO,KAAA;AAClD;AAEA,eAAsB,OAAO,OAAgC;CAC3D,MAAM,SAAS,MAAM,OAAO,OAAO,OAAO,WAAW,IAAI,YAAY,CAAC,CAAC,OAAO,KAAK,CAAC;CAEpF,OAAO,MAAM,KAAK,IAAI,WAAW,MAAM,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE;AACjG;AAEA,eAAe,aAAa,OAAwD;CAClF,OAAO,QAAQ,OAAO,KAAK,IAAI,KAAA;AACjC;AAEA,SAAS,aAAa,MAA8B;CAClD,OAAO,OAAO,YAAY,OAAO,QAAQ,IAAI,CAAC,CAAC,QAAQ,GAAG,WAAW,KAAK,CAAC;AAC7E"}
|
|
@@ -4,6 +4,12 @@ type ConversionUser = {
|
|
|
4
4
|
phone?: string | undefined;
|
|
5
5
|
firstName?: string | undefined;
|
|
6
6
|
lastName?: string | undefined;
|
|
7
|
+
city?: string | undefined;
|
|
8
|
+
postalCode?: string | undefined;
|
|
9
|
+
/** the two-letter country code */
|
|
10
|
+
country?: string | undefined;
|
|
11
|
+
/** the site's own id of the visitor: a customer or subscriber id */
|
|
12
|
+
externalId?: string | undefined;
|
|
7
13
|
};
|
|
8
14
|
/** the identifiers of a visitor as the ad platforms match them: normalized, then sha256 as lowercase hex */
|
|
9
15
|
type HashedUser = {
|
|
@@ -11,6 +17,10 @@ type HashedUser = {
|
|
|
11
17
|
phone?: string | undefined;
|
|
12
18
|
firstName?: string | undefined;
|
|
13
19
|
lastName?: string | undefined;
|
|
20
|
+
city?: string | undefined;
|
|
21
|
+
postalCode?: string | undefined;
|
|
22
|
+
country?: string | undefined;
|
|
23
|
+
externalId?: string | undefined;
|
|
14
24
|
};
|
|
15
25
|
type HashOptions = {
|
|
16
26
|
/**
|
|
@@ -32,4 +42,4 @@ declare function normalizePhone(phone: string | undefined, countryCallingCode: s
|
|
|
32
42
|
declare function normalizeName(name: string | undefined): string | undefined;
|
|
33
43
|
declare function sha256(value: string): Promise<string>;
|
|
34
44
|
export { normalizeEmail as a, sha256 as c, hashUser as i, HashOptions as n, normalizeName as o, HashedUser as r, normalizePhone as s, ConversionUser as t };
|
|
35
|
-
//# sourceMappingURL=conversion-user-
|
|
45
|
+
//# sourceMappingURL=conversion-user-fHh2xXBc.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversion-user-fHh2xXBc.d.ts","names":[],"sources":["../src/conversion-user.ts"],"mappings":";KACY;EACV;EACA;EACA;EACA;EACA;EACA;;EAEA;;EAEA;;;KAIU;EACV;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;KAGU;;;;;EAKV;;;iBAIoB,SAAS,MAAM,gBAAgB,UAAS,cAAmB,QAAQ;iBAezE,eAAe;;;;;;iBASf,eAAe,2BAA2B;;iBAe1C,cAAc;iBAgBR,OAAO,gBAAgB"}
|
package/dist/index.browser.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as ConversionUser } from "./conversion-user-
|
|
1
|
+
import { t as ConversionUser } from "./conversion-user-fHh2xXBc.js";
|
|
2
2
|
import { n as collect, r as inlineTrack, t as CollectOptions } from "./collect-DRl177oR.js";
|
|
3
3
|
import { _ as WellKnownCreate, a as CommerceItem, b as wellKnownEvents, c as PurchaseParams, d as SearchParams, f as SelectItemParams, g as ViewItemParams, h as ViewItemListParams, i as BeginCheckoutParams, l as RefundParams, m as ViewCartParams, n as AddShippingInfoParams, o as GenerateLeadParams, p as SignUpParams, r as AddToCartParams, s as LoginParams, t as AddPaymentInfoParams, u as RemoveFromCartParams, v as WellKnownName, y as isCommerceEvent } from "./commerce-DCGO8yag.js";
|
|
4
4
|
import { a as DomainEvent, c as RELAY_TRACK_ATTRIBUTE, d as RelayParams, h as trackAttribute, i as DestinationName, l as RelayDestinations, m as defineEvent, n as ConsentRecord, o as EventDefinition, p as TrackInput, r as ConsentSource, s as EventMeta, t as ConsentAnswer, u as RelayEvent } from "./shared-CSwqcxPI.js";
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as ConversionUser } from "./conversion-user-
|
|
1
|
+
import { t as ConversionUser } from "./conversion-user-fHh2xXBc.js";
|
|
2
2
|
import { n as collect, r as inlineTrack, t as CollectOptions } from "./collect-DRl177oR.js";
|
|
3
3
|
import { _ as WellKnownCreate, a as CommerceItem, b as wellKnownEvents, c as PurchaseParams, d as SearchParams, f as SelectItemParams, g as ViewItemParams, h as ViewItemListParams, i as BeginCheckoutParams, l as RefundParams, m as ViewCartParams, n as AddShippingInfoParams, o as GenerateLeadParams, p as SignUpParams, r as AddToCartParams, s as LoginParams, t as AddPaymentInfoParams, u as RemoveFromCartParams, v as WellKnownName, y as isCommerceEvent } from "./commerce-DCGO8yag.js";
|
|
4
4
|
import { a as DomainEvent, c as RELAY_TRACK_ATTRIBUTE, d as RelayParams, h as trackAttribute, i as DestinationName, l as RelayDestinations, m as defineEvent, n as ConsentRecord, o as EventDefinition, p as TrackInput, r as ConsentSource, s as EventMeta, t as ConsentAnswer, u as RelayEvent } from "./shared-CSwqcxPI.js";
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { a as Destination } from "../bus-Z7l8oJtS.js";
|
|
2
|
+
import { t as ConversionUser } from "../conversion-user-fHh2xXBc.js";
|
|
3
|
+
/** the standard events meta optimizes on; anything else is a custom event */
|
|
4
|
+
type MetaStandardEventName = 'AddPaymentInfo' | 'AddToCart' | 'AddToWishlist' | 'CompleteRegistration' | 'Contact' | 'CustomizeProduct' | 'Donate' | 'FindLocation' | 'InitiateCheckout' | 'Lead' | 'PageView' | 'Purchase' | 'Schedule' | 'Search' | 'StartTrial' | 'SubmitApplication' | 'Subscribe' | 'ViewContent';
|
|
5
|
+
/** an item of `contents` */
|
|
6
|
+
type MetaContent = {
|
|
7
|
+
id: string;
|
|
8
|
+
quantity: number;
|
|
9
|
+
item_price?: number | undefined;
|
|
10
|
+
title?: string | undefined;
|
|
11
|
+
category?: string | undefined;
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
};
|
|
14
|
+
/** the parameters of an event, as the pixel and the conversions api take them under `custom_data` */
|
|
15
|
+
type MetaCustomData = {
|
|
16
|
+
value?: number | undefined;
|
|
17
|
+
currency?: string | undefined;
|
|
18
|
+
content_name?: string | undefined;
|
|
19
|
+
content_category?: string | undefined;
|
|
20
|
+
content_ids?: readonly string[] | undefined;
|
|
21
|
+
content_type?: 'product' | 'product_group' | undefined;
|
|
22
|
+
contents?: readonly MetaContent[] | undefined;
|
|
23
|
+
num_items?: number | undefined;
|
|
24
|
+
order_id?: string | undefined;
|
|
25
|
+
search_string?: string | undefined;
|
|
26
|
+
status?: string | undefined;
|
|
27
|
+
predicted_ltv?: number | undefined;
|
|
28
|
+
[key: string]: unknown;
|
|
29
|
+
};
|
|
30
|
+
type MetaPurchaseData = MetaCustomData & {
|
|
31
|
+
value: number;
|
|
32
|
+
currency: string;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* What meta takes, from the pixel and from the conversions api alike: the event, its parameters
|
|
36
|
+
* and, for a conversion the visitor entered identifiers for, the identifiers — hashed by whichever
|
|
37
|
+
* half sends them.
|
|
38
|
+
*/
|
|
39
|
+
type MetaEvent = {
|
|
40
|
+
name: string;
|
|
41
|
+
/** a custom event goes through `trackCustom`; a standard one through `track` */
|
|
42
|
+
custom: boolean;
|
|
43
|
+
data: MetaCustomData;
|
|
44
|
+
user?: ConversionUser | undefined;
|
|
45
|
+
};
|
|
46
|
+
declare module '@astroscope/relay' {
|
|
47
|
+
interface RelayDestinations {
|
|
48
|
+
meta: MetaEvent;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The meta builders: the standard events, and `event(name, data)` for a custom one. What an event
|
|
53
|
+
* definition puts under `meta`.
|
|
54
|
+
*/
|
|
55
|
+
export declare const meta: {
|
|
56
|
+
/** a standard event by name, for one without its own builder */
|
|
57
|
+
standard: (name: MetaStandardEventName, data?: MetaCustomData, user?: ConversionUser) => MetaEvent;
|
|
58
|
+
/** a custom event: any name meta has no standard event for */
|
|
59
|
+
event: (name: string, data?: MetaCustomData, user?: ConversionUser) => MetaEvent;
|
|
60
|
+
pageView: () => MetaEvent;
|
|
61
|
+
viewContent: (data: MetaCustomData, user?: ConversionUser) => MetaEvent;
|
|
62
|
+
search: (data: MetaCustomData, user?: ConversionUser) => MetaEvent;
|
|
63
|
+
addToCart: (data: MetaCustomData, user?: ConversionUser) => MetaEvent;
|
|
64
|
+
initiateCheckout: (data: MetaCustomData, user?: ConversionUser) => MetaEvent;
|
|
65
|
+
addPaymentInfo: (data: MetaCustomData, user?: ConversionUser) => MetaEvent;
|
|
66
|
+
purchase: (data: MetaPurchaseData, user?: ConversionUser) => MetaEvent;
|
|
67
|
+
lead: (data?: MetaCustomData, user?: ConversionUser) => MetaEvent;
|
|
68
|
+
completeRegistration: (data?: MetaCustomData, user?: ConversionUser) => MetaEvent;
|
|
69
|
+
subscribe: (data?: MetaCustomData, user?: ConversionUser) => MetaEvent;
|
|
70
|
+
schedule: (data?: MetaCustomData, user?: ConversionUser) => MetaEvent;
|
|
71
|
+
contact: (data?: MetaCustomData, user?: ConversionUser) => MetaEvent;
|
|
72
|
+
};
|
|
73
|
+
type Fbq = (...args: unknown[]) => void;
|
|
74
|
+
declare global {
|
|
75
|
+
interface Window {
|
|
76
|
+
fbq?: Fbq | undefined;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
export type MetaPixelOptions = {
|
|
80
|
+
/** the pixel the base code initialized: the events fire on it alone, the identifiers go to it */
|
|
81
|
+
pixelId: string;
|
|
82
|
+
/** the country calling code national phone numbers get before hashing, e.g. `49` */
|
|
83
|
+
countryCallingCode?: string | undefined;
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* The meta pixel as a destination: fires the `meta` payload of an event on this pixel under the
|
|
87
|
+
* event's id, so a conversion the server also reported through the conversions api counts once.
|
|
88
|
+
* The pixel's base code is injected by the cmp after consent, so `window.fbq` shows up late:
|
|
89
|
+
* events wait for it. The identifiers an event carries are hashed and handed to the pixel's
|
|
90
|
+
* advanced matching right before it. `PageView` is an event like any other: track
|
|
91
|
+
* `meta.pageView()` from the layout.
|
|
92
|
+
*/
|
|
93
|
+
export declare function metaPixel(options: MetaPixelOptions): Destination<'meta'>;
|
|
94
|
+
export type { MetaContent, MetaCustomData, MetaEvent, MetaPurchaseData, MetaStandardEventName };
|
|
95
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":["_0"],"sources":["../../src/meta/shared.ts","../../src/meta/index.ts"],"mappings":";;;KAKY;;KAqBA;EACV;EACA;EACA;EACA;EACA;GACC;;;KAIS;EACV;EACA;EACA;EACA;EACA;EACA;EACA,oBAAoB;EACpB;EACA;EACA;EACA;EACA;GACC;;KAGS,mBAAmB;EAAmB;EAAe;;;;;;;KAOrD;EACV;;EAEA;EACA,MAAM;EACN,OAAO;;;YAIG;IACR,MAAM;;;;;;;qBAeG;;EAXW,WAAA,MAAA,uBAAqB,OAAQ,gBAAc,OAAc,mBAAiB;;EAelF,QAAA,cAAM,OAAQ,gBAAc,OAAc,mBAAiB;EAM3D,gBAAA;EACM,cAAA,MAAA,gBAAc,OAAS,mBAAiB;EAC7C,SAAA,MAAA,gBAAc,OAAS,mBAAiB;EACrC,YAAA,MAAA,gBAAc,OAAS,mBAAiB;EACjC,mBAAA,MAAA,gBAAc,OAAS,mBAAiB;EAC1C,iBAAA,MAAA,gBAAc,OAAS,mBAAiB;EAC9C,WAAA,MAAA,kBAAgB,OAAS,mBAAiB;EAC7C,OAAA,OAAA,gBAAc,OAAS,mBAAiB;EACxB,uBAAA,OAAA,gBAAc,OAAS,mBAAiB;EACnD,YAAA,OAAA,gBAAc,OAAS,mBAAiB;EACzC,WAAA,OAAA,gBAAc,OAAS,mBAAiB;EACzC,UAAA,OAAA,gBAAc,OAAS,mBAAiB;;KCrFtD,UAAU;QAEP;YACI;IACR,MAAM;;;YAIE;;EAEV;;EAEA;;;;;;;;;;wBAWc,UAAU,SAAS,mBAAmB"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { t as hashUser } from "../conversion-user-BgpXmIdJ.js";
|
|
2
|
+
import { a as isBrowser } from "../shared-D281USsp.js";
|
|
3
|
+
// src/meta/shared.ts
|
|
4
|
+
const standard = (name, data = {}, user) => ({
|
|
5
|
+
name,
|
|
6
|
+
custom: false,
|
|
7
|
+
data,
|
|
8
|
+
user
|
|
9
|
+
});
|
|
10
|
+
/**
|
|
11
|
+
* The meta builders: the standard events, and `event(name, data)` for a custom one. What an event
|
|
12
|
+
* definition puts under `meta`.
|
|
13
|
+
*/
|
|
14
|
+
const meta = {
|
|
15
|
+
/** a standard event by name, for one without its own builder */
|
|
16
|
+
standard,
|
|
17
|
+
/** a custom event: any name meta has no standard event for */
|
|
18
|
+
event: (name, data = {}, user) => ({
|
|
19
|
+
name,
|
|
20
|
+
custom: true,
|
|
21
|
+
data,
|
|
22
|
+
user
|
|
23
|
+
}),
|
|
24
|
+
pageView: () => standard("PageView"),
|
|
25
|
+
viewContent: (data, user) => standard("ViewContent", data, user),
|
|
26
|
+
search: (data, user) => standard("Search", data, user),
|
|
27
|
+
addToCart: (data, user) => standard("AddToCart", data, user),
|
|
28
|
+
initiateCheckout: (data, user) => standard("InitiateCheckout", data, user),
|
|
29
|
+
addPaymentInfo: (data, user) => standard("AddPaymentInfo", data, user),
|
|
30
|
+
purchase: (data, user) => standard("Purchase", data, user),
|
|
31
|
+
lead: (data, user) => standard("Lead", data, user),
|
|
32
|
+
completeRegistration: (data, user) => standard("CompleteRegistration", data, user),
|
|
33
|
+
subscribe: (data, user) => standard("Subscribe", data, user),
|
|
34
|
+
schedule: (data, user) => standard("Schedule", data, user),
|
|
35
|
+
contact: (data, user) => standard("Contact", data, user)
|
|
36
|
+
};
|
|
37
|
+
// src/meta/index.ts
|
|
38
|
+
/**
|
|
39
|
+
* The meta pixel as a destination: fires the `meta` payload of an event on this pixel under the
|
|
40
|
+
* event's id, so a conversion the server also reported through the conversions api counts once.
|
|
41
|
+
* The pixel's base code is injected by the cmp after consent, so `window.fbq` shows up late:
|
|
42
|
+
* events wait for it. The identifiers an event carries are hashed and handed to the pixel's
|
|
43
|
+
* advanced matching right before it. `PageView` is an event like any other: track
|
|
44
|
+
* `meta.pageView()` from the layout.
|
|
45
|
+
*/
|
|
46
|
+
function metaPixel(options) {
|
|
47
|
+
const { pixelId, countryCallingCode } = options;
|
|
48
|
+
let initializedUserKey;
|
|
49
|
+
return {
|
|
50
|
+
name: "meta",
|
|
51
|
+
ready: () => isBrowser() && typeof globalThis.window.fbq === "function",
|
|
52
|
+
send: (event, meta) => {
|
|
53
|
+
Promise.resolve(event.user ? hashUser(event.user, { countryCallingCode }) : void 0).then((hashed) => fire(event, meta, hashed)).catch((error) => console.error("relay: destination meta failed", error));
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
function fire(event, meta, hashed) {
|
|
57
|
+
const fbq = globalThis.window.fbq;
|
|
58
|
+
if (!fbq) return;
|
|
59
|
+
identify(fbq, hashed);
|
|
60
|
+
fbq(event.custom ? "trackSingleCustom" : "trackSingle", pixelId, event.name, event.data, { eventID: meta.id });
|
|
61
|
+
}
|
|
62
|
+
function identify(fbq, hashed) {
|
|
63
|
+
const user = withoutEmpty({
|
|
64
|
+
em: hashed?.email,
|
|
65
|
+
ph: hashed?.phone,
|
|
66
|
+
fn: hashed?.firstName,
|
|
67
|
+
ln: hashed?.lastName,
|
|
68
|
+
ct: hashed?.city,
|
|
69
|
+
zp: hashed?.postalCode,
|
|
70
|
+
country: hashed?.country,
|
|
71
|
+
external_id: hashed?.externalId
|
|
72
|
+
});
|
|
73
|
+
const key = JSON.stringify(user);
|
|
74
|
+
if (key === initializedUserKey || Object.keys(user).length === 0) return;
|
|
75
|
+
initializedUserKey = key;
|
|
76
|
+
fbq("init", pixelId, user);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function withoutEmpty(user) {
|
|
80
|
+
return Object.fromEntries(Object.entries(user).filter(([, value]) => value));
|
|
81
|
+
}
|
|
82
|
+
export { meta, metaPixel };
|
|
83
|
+
|
|
84
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/meta/shared.ts","../../src/meta/index.ts"],"sourcesContent":["// imported by the pixel and the conversions api — no node or browser imports here\n\nimport type { ConversionUser } from '../conversion-user.js';\n\n/** the standard events meta optimizes on; anything else is a custom event */\nexport type MetaStandardEventName =\n | 'AddPaymentInfo'\n | 'AddToCart'\n | 'AddToWishlist'\n | 'CompleteRegistration'\n | 'Contact'\n | 'CustomizeProduct'\n | 'Donate'\n | 'FindLocation'\n | 'InitiateCheckout'\n | 'Lead'\n | 'PageView'\n | 'Purchase'\n | 'Schedule'\n | 'Search'\n | 'StartTrial'\n | 'SubmitApplication'\n | 'Subscribe'\n | 'ViewContent';\n\n/** an item of `contents` */\nexport type MetaContent = {\n id: string;\n quantity: number;\n item_price?: number | undefined;\n title?: string | undefined;\n category?: string | undefined;\n [key: string]: unknown;\n};\n\n/** the parameters of an event, as the pixel and the conversions api take them under `custom_data` */\nexport type MetaCustomData = {\n value?: number | undefined;\n currency?: string | undefined;\n content_name?: string | undefined;\n content_category?: string | undefined;\n content_ids?: readonly string[] | undefined;\n content_type?: 'product' | 'product_group' | undefined;\n contents?: readonly MetaContent[] | undefined;\n num_items?: number | undefined;\n order_id?: string | undefined;\n search_string?: string | undefined;\n status?: string | undefined;\n predicted_ltv?: number | undefined;\n [key: string]: unknown;\n};\n\nexport type MetaPurchaseData = MetaCustomData & { value: number; currency: string };\n\n/**\n * What meta takes, from the pixel and from the conversions api alike: the event, its parameters\n * and, for a conversion the visitor entered identifiers for, the identifiers — hashed by whichever\n * half sends them.\n */\nexport type MetaEvent = {\n name: string;\n /** a custom event goes through `trackCustom`; a standard one through `track` */\n custom: boolean;\n data: MetaCustomData;\n user?: ConversionUser | undefined;\n};\n\ndeclare module '@astroscope/relay' {\n interface RelayDestinations {\n meta: MetaEvent;\n }\n}\n\nconst standard = (name: MetaStandardEventName, data: MetaCustomData = {}, user?: ConversionUser): MetaEvent => ({\n name,\n custom: false,\n data,\n user,\n});\n\n/**\n * The meta builders: the standard events, and `event(name, data)` for a custom one. What an event\n * definition puts under `meta`.\n */\nexport const meta = {\n /** a standard event by name, for one without its own builder */\n standard,\n /** a custom event: any name meta has no standard event for */\n event: (name: string, data: MetaCustomData = {}, user?: ConversionUser): MetaEvent => ({\n name,\n custom: true,\n data,\n user,\n }),\n pageView: (): MetaEvent => standard('PageView'),\n viewContent: (data: MetaCustomData, user?: ConversionUser): MetaEvent => standard('ViewContent', data, user),\n search: (data: MetaCustomData, user?: ConversionUser): MetaEvent => standard('Search', data, user),\n addToCart: (data: MetaCustomData, user?: ConversionUser): MetaEvent => standard('AddToCart', data, user),\n initiateCheckout: (data: MetaCustomData, user?: ConversionUser): MetaEvent => standard('InitiateCheckout', data, user),\n addPaymentInfo: (data: MetaCustomData, user?: ConversionUser): MetaEvent => standard('AddPaymentInfo', data, user),\n purchase: (data: MetaPurchaseData, user?: ConversionUser): MetaEvent => standard('Purchase', data, user),\n lead: (data?: MetaCustomData, user?: ConversionUser): MetaEvent => standard('Lead', data, user),\n completeRegistration: (data?: MetaCustomData, user?: ConversionUser): MetaEvent => standard('CompleteRegistration', data, user),\n subscribe: (data?: MetaCustomData, user?: ConversionUser): MetaEvent => standard('Subscribe', data, user),\n schedule: (data?: MetaCustomData, user?: ConversionUser): MetaEvent => standard('Schedule', data, user),\n contact: (data?: MetaCustomData, user?: ConversionUser): MetaEvent => standard('Contact', data, user),\n};\n","import type { Destination } from '../client/bus.js';\nimport { type HashedUser, hashUser } from '../conversion-user.js';\nimport { type EventMeta, isBrowser } from '../shared.js';\nimport type { MetaEvent } from './shared.js';\n\nexport type { MetaContent, MetaCustomData, MetaEvent, MetaPurchaseData, MetaStandardEventName } from './shared.js';\nexport { meta } from './shared.js';\n\n/** the advanced matching data of the pixel: normalized identifiers as lowercase sha256 hex */\ntype MetaPixelUser = {\n em?: string | undefined;\n ph?: string | undefined;\n fn?: string | undefined;\n ln?: string | undefined;\n ct?: string | undefined;\n zp?: string | undefined;\n country?: string | undefined;\n external_id?: string | undefined;\n};\n\ntype Fbq = (...args: unknown[]) => void;\n\ndeclare global {\n interface Window {\n fbq?: Fbq | undefined;\n }\n}\n\nexport type MetaPixelOptions = {\n /** the pixel the base code initialized: the events fire on it alone, the identifiers go to it */\n pixelId: string;\n /** the country calling code national phone numbers get before hashing, e.g. `49` */\n countryCallingCode?: string | undefined;\n};\n\n/**\n * The meta pixel as a destination: fires the `meta` payload of an event on this pixel under the\n * event's id, so a conversion the server also reported through the conversions api counts once.\n * The pixel's base code is injected by the cmp after consent, so `window.fbq` shows up late:\n * events wait for it. The identifiers an event carries are hashed and handed to the pixel's\n * advanced matching right before it. `PageView` is an event like any other: track\n * `meta.pageView()` from the layout.\n */\nexport function metaPixel(options: MetaPixelOptions): Destination<'meta'> {\n const { pixelId, countryCallingCode } = options;\n let initializedUserKey: string | undefined;\n\n return {\n name: 'meta',\n ready: () => isBrowser() && typeof globalThis.window.fbq === 'function',\n send: (event, meta) => {\n void Promise.resolve(event.user ? hashUser(event.user, { countryCallingCode }) : undefined)\n .then((hashed) => fire(event, meta, hashed))\n .catch((error: unknown) => console.error('relay: destination meta failed', error));\n },\n };\n\n function fire(event: MetaEvent, meta: EventMeta, hashed: HashedUser | undefined): void {\n const fbq = globalThis.window.fbq;\n\n if (!fbq) return;\n\n identify(fbq, hashed);\n fbq(event.custom ? 'trackSingleCustom' : 'trackSingle', pixelId, event.name, event.data, { eventID: meta.id });\n }\n\n // the base code initialized the pixel already; a conversion with identifiers initializes it again with them\n function identify(fbq: Fbq, hashed: HashedUser | undefined): void {\n const user = withoutEmpty({\n em: hashed?.email,\n ph: hashed?.phone,\n fn: hashed?.firstName,\n ln: hashed?.lastName,\n ct: hashed?.city,\n zp: hashed?.postalCode,\n country: hashed?.country,\n external_id: hashed?.externalId,\n });\n const key = JSON.stringify(user);\n\n if (key === initializedUserKey || Object.keys(user).length === 0) {\n return;\n }\n\n initializedUserKey = key;\n fbq('init', pixelId, user);\n }\n}\n\nfunction withoutEmpty(user: MetaPixelUser): MetaPixelUser {\n return Object.fromEntries(Object.entries(user).filter(([, value]) => value)) as MetaPixelUser;\n}\n"],"mappings":";;;AAyEA,MAAM,YAAY,MAA6B,OAAuB,CAAC,GAAG,UAAsC;CAC9G;CACA,QAAQ;CACR;CACA;AACF;;;;;AAMA,MAAa,OAAO;;CAElB;;CAEA,QAAQ,MAAc,OAAuB,CAAC,GAAG,UAAsC;EACrF;EACA,QAAQ;EACR;EACA;CACF;CACA,gBAA2B,SAAS,UAAU;CAC9C,cAAc,MAAsB,SAAqC,SAAS,eAAe,MAAM,IAAI;CAC3G,SAAS,MAAsB,SAAqC,SAAS,UAAU,MAAM,IAAI;CACjG,YAAY,MAAsB,SAAqC,SAAS,aAAa,MAAM,IAAI;CACvG,mBAAmB,MAAsB,SAAqC,SAAS,oBAAoB,MAAM,IAAI;CACrH,iBAAiB,MAAsB,SAAqC,SAAS,kBAAkB,MAAM,IAAI;CACjH,WAAW,MAAwB,SAAqC,SAAS,YAAY,MAAM,IAAI;CACvG,OAAO,MAAuB,SAAqC,SAAS,QAAQ,MAAM,IAAI;CAC9F,uBAAuB,MAAuB,SAAqC,SAAS,wBAAwB,MAAM,IAAI;CAC9H,YAAY,MAAuB,SAAqC,SAAS,aAAa,MAAM,IAAI;CACxG,WAAW,MAAuB,SAAqC,SAAS,YAAY,MAAM,IAAI;CACtG,UAAU,MAAuB,SAAqC,SAAS,WAAW,MAAM,IAAI;AACtG;;;;;;;;;;AC/DA,SAAgB,UAAU,SAAgD;CACxE,MAAM,EAAE,SAAS,uBAAuB;CACxC,IAAI;CAEJ,OAAO;EACL,MAAM;EACN,aAAa,UAAU,KAAK,OAAO,WAAW,OAAO,QAAQ;EAC7D,OAAO,OAAO,SAAS;GACrB,QAAa,QAAQ,MAAM,OAAO,SAAS,MAAM,MAAM,EAAE,mBAAmB,CAAC,IAAI,KAAA,CAAS,CAAC,CACxF,MAAM,WAAW,KAAK,OAAO,MAAM,MAAM,CAAC,CAAC,CAC3C,OAAO,UAAmB,QAAQ,MAAM,kCAAkC,KAAK,CAAC;EACrF;CACF;CAEA,SAAS,KAAK,OAAkB,MAAiB,QAAsC;EACrF,MAAM,MAAM,WAAW,OAAO;EAE9B,IAAI,CAAC,KAAK;EAEV,SAAS,KAAK,MAAM;EACpB,IAAI,MAAM,SAAS,sBAAsB,eAAe,SAAS,MAAM,MAAM,MAAM,MAAM,EAAE,SAAS,KAAK,GAAG,CAAC;CAC/G;CAGA,SAAS,SAAS,KAAU,QAAsC;EAChE,MAAM,OAAO,aAAa;GACxB,IAAI,QAAQ;GACZ,IAAI,QAAQ;GACZ,IAAI,QAAQ;GACZ,IAAI,QAAQ;GACZ,IAAI,QAAQ;GACZ,IAAI,QAAQ;GACZ,SAAS,QAAQ;GACjB,aAAa,QAAQ;EACvB,CAAC;EACD,MAAM,MAAM,KAAK,UAAU,IAAI;EAE/B,IAAI,QAAQ,sBAAsB,OAAO,KAAK,IAAI,CAAC,CAAC,WAAW,GAC7D;EAGF,qBAAqB;EACrB,IAAI,QAAQ,SAAS,IAAI;CAC3B;AACF;AAEA,SAAS,aAAa,MAAoC;CACxD,OAAO,OAAO,YAAY,OAAO,QAAQ,IAAI,CAAC,CAAC,QAAQ,GAAG,WAAW,KAAK,CAAC;AAC7E"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { a as ServerDestination } from "../runtime-J8VplGyY.js";
|
|
2
|
+
export type MetaConversionsOptions = {
|
|
3
|
+
/** the system user access token from the events manager */
|
|
4
|
+
accessToken: string;
|
|
5
|
+
pixelId: string;
|
|
6
|
+
/** the code from the events manager's test events tab: the events show up there instead of counting */
|
|
7
|
+
testEventCode?: string | undefined;
|
|
8
|
+
/** the country calling code national phone numbers get before hashing, e.g. `49` */
|
|
9
|
+
countryCallingCode?: string | undefined;
|
|
10
|
+
/** the graph api version; default the one this package was written against */
|
|
11
|
+
apiVersion?: string | undefined;
|
|
12
|
+
fetch?: typeof fetch | undefined;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* The meta conversions api as a server destination: reports the `meta` payload of an event under
|
|
16
|
+
* the id the pixel fires it with, with the identifiers hashed and the browser and click ids off
|
|
17
|
+
* the pixel's cookies; a click id the pixel has not stored yet is taken from the page's `fbclid`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function metaConversions(options: MetaConversionsOptions): ServerDestination<'meta'>;
|
|
20
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","names":[],"sources":["../../src/meta/server.ts"],"mappings":";YAcY;;EAEV;EACA;;EAEA;;EAEA;;EAEA;EACA,eAAe;;;;;;;wBAQD,gBAAgB,SAAS,yBAAyB"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { t as hashUser } from "../conversion-user-BgpXmIdJ.js";
|
|
2
|
+
import { t as withoutQuery } from "../source-url-Cqfh3UjO.js";
|
|
3
|
+
// src/meta/server.ts
|
|
4
|
+
const CONVERSIONS_API_URL = "https://graph.facebook.com";
|
|
5
|
+
const CONVERSIONS_API_VERSION = "v24.0";
|
|
6
|
+
const CONVERSIONS_API_TIMEOUT_MS = 1e4;
|
|
7
|
+
/** the cookies the pixel sets: the browser id, and the click id of the visitor who came from a meta ad */
|
|
8
|
+
const BROWSER_ID_COOKIE = "_fbp";
|
|
9
|
+
const CLICK_ID_COOKIE = "_fbc";
|
|
10
|
+
const CLICK_ID_PARAM = "fbclid";
|
|
11
|
+
/**
|
|
12
|
+
* The meta conversions api as a server destination: reports the `meta` payload of an event under
|
|
13
|
+
* the id the pixel fires it with, with the identifiers hashed and the browser and click ids off
|
|
14
|
+
* the pixel's cookies; a click id the pixel has not stored yet is taken from the page's `fbclid`.
|
|
15
|
+
*/
|
|
16
|
+
function metaConversions(options) {
|
|
17
|
+
const doFetch = options.fetch ?? fetch;
|
|
18
|
+
const url = `${CONVERSIONS_API_URL}/${options.apiVersion ?? CONVERSIONS_API_VERSION}/${encodeURIComponent(options.pixelId)}/events`;
|
|
19
|
+
return {
|
|
20
|
+
name: "meta",
|
|
21
|
+
async send(event, { id, at, request }) {
|
|
22
|
+
const hashed = await hashUser(event.user ?? {}, { countryCallingCode: options.countryCallingCode });
|
|
23
|
+
const body = {
|
|
24
|
+
access_token: options.accessToken,
|
|
25
|
+
...options.testEventCode && { test_event_code: options.testEventCode },
|
|
26
|
+
data: [{
|
|
27
|
+
event_name: event.name,
|
|
28
|
+
event_time: Math.floor(at / 1e3),
|
|
29
|
+
event_id: id,
|
|
30
|
+
event_source_url: withoutQuery(request.sourceUrl),
|
|
31
|
+
action_source: "website",
|
|
32
|
+
user_data: withoutEmpty({
|
|
33
|
+
em: toList(hashed.email),
|
|
34
|
+
ph: toList(hashed.phone),
|
|
35
|
+
fn: toList(hashed.firstName),
|
|
36
|
+
ln: toList(hashed.lastName),
|
|
37
|
+
ct: toList(hashed.city),
|
|
38
|
+
zp: toList(hashed.postalCode),
|
|
39
|
+
country: toList(hashed.country),
|
|
40
|
+
external_id: toList(hashed.externalId),
|
|
41
|
+
client_ip_address: request.clientAddress,
|
|
42
|
+
client_user_agent: request.headers.get("user-agent") ?? void 0,
|
|
43
|
+
fbp: request.cookie(BROWSER_ID_COOKIE),
|
|
44
|
+
fbc: request.cookie(CLICK_ID_COOKIE) ?? clickIdOf(request, at)
|
|
45
|
+
}),
|
|
46
|
+
custom_data: event.data
|
|
47
|
+
}]
|
|
48
|
+
};
|
|
49
|
+
const response = await doFetch(url, {
|
|
50
|
+
method: "POST",
|
|
51
|
+
headers: { "content-type": "application/json" },
|
|
52
|
+
body: JSON.stringify(body),
|
|
53
|
+
signal: AbortSignal.timeout(CONVERSIONS_API_TIMEOUT_MS)
|
|
54
|
+
});
|
|
55
|
+
if (!response.ok) throw new Error(`conversions api responded ${response.status}: ${await response.text()}`);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/** the click id in the pixel's cookie format, from the `fbclid` of the page the conversion happened on */
|
|
60
|
+
function clickIdOf(request, at) {
|
|
61
|
+
let fbclid;
|
|
62
|
+
try {
|
|
63
|
+
fbclid = new URL(request.sourceUrl).searchParams.get(CLICK_ID_PARAM);
|
|
64
|
+
} catch {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
return fbclid ? `fb.1.${at}.${fbclid}` : void 0;
|
|
68
|
+
}
|
|
69
|
+
function toList(value) {
|
|
70
|
+
return value ? [value] : void 0;
|
|
71
|
+
}
|
|
72
|
+
function withoutEmpty(data) {
|
|
73
|
+
return Object.fromEntries(Object.entries(data).filter(([, value]) => value !== void 0));
|
|
74
|
+
}
|
|
75
|
+
export { metaConversions };
|
|
76
|
+
|
|
77
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","names":[],"sources":["../../src/meta/server.ts"],"sourcesContent":["import { hashUser } from '../conversion-user.js';\nimport type { RelayRequest, ServerDestination } from '../runtime.js';\nimport { withoutQuery } from '../source-url.js';\nimport type { MetaEvent } from './shared.js';\n\nconst CONVERSIONS_API_URL = 'https://graph.facebook.com';\nconst CONVERSIONS_API_VERSION = 'v24.0';\nconst CONVERSIONS_API_TIMEOUT_MS = 10_000;\n\n/** the cookies the pixel sets: the browser id, and the click id of the visitor who came from a meta ad */\nconst BROWSER_ID_COOKIE = '_fbp';\nconst CLICK_ID_COOKIE = '_fbc';\nconst CLICK_ID_PARAM = 'fbclid';\n\nexport type MetaConversionsOptions = {\n /** the system user access token from the events manager */\n accessToken: string;\n pixelId: string;\n /** the code from the events manager's test events tab: the events show up there instead of counting */\n testEventCode?: string | undefined;\n /** the country calling code national phone numbers get before hashing, e.g. `49` */\n countryCallingCode?: string | undefined;\n /** the graph api version; default the one this package was written against */\n apiVersion?: string | undefined;\n fetch?: typeof fetch | undefined;\n};\n\n/**\n * The meta conversions api as a server destination: reports the `meta` payload of an event under\n * the id the pixel fires it with, with the identifiers hashed and the browser and click ids off\n * the pixel's cookies; a click id the pixel has not stored yet is taken from the page's `fbclid`.\n */\nexport function metaConversions(options: MetaConversionsOptions): ServerDestination<'meta'> {\n const doFetch = options.fetch ?? fetch;\n const url = `${CONVERSIONS_API_URL}/${options.apiVersion ?? CONVERSIONS_API_VERSION}/${encodeURIComponent(options.pixelId)}/events`;\n\n return {\n name: 'meta',\n async send(event: MetaEvent, { id, at, request }) {\n const hashed = await hashUser(event.user ?? {}, { countryCallingCode: options.countryCallingCode });\n const body = {\n access_token: options.accessToken,\n ...(options.testEventCode && { test_event_code: options.testEventCode }),\n data: [\n {\n event_name: event.name,\n event_time: Math.floor(at / 1000),\n event_id: id,\n event_source_url: withoutQuery(request.sourceUrl),\n action_source: 'website',\n user_data: withoutEmpty({\n em: toList(hashed.email),\n ph: toList(hashed.phone),\n fn: toList(hashed.firstName),\n ln: toList(hashed.lastName),\n ct: toList(hashed.city),\n zp: toList(hashed.postalCode),\n country: toList(hashed.country),\n external_id: toList(hashed.externalId),\n client_ip_address: request.clientAddress,\n client_user_agent: request.headers.get('user-agent') ?? undefined,\n fbp: request.cookie(BROWSER_ID_COOKIE),\n fbc: request.cookie(CLICK_ID_COOKIE) ?? clickIdOf(request, at),\n }),\n custom_data: event.data,\n },\n ],\n };\n\n const response = await doFetch(url, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(CONVERSIONS_API_TIMEOUT_MS),\n });\n\n if (!response.ok) {\n throw new Error(`conversions api responded ${response.status}: ${await response.text()}`);\n }\n },\n };\n}\n\n/** the click id in the pixel's cookie format, from the `fbclid` of the page the conversion happened on */\nfunction clickIdOf(request: RelayRequest, at: number): string | undefined {\n let fbclid: string | null;\n\n try {\n fbclid = new URL(request.sourceUrl).searchParams.get(CLICK_ID_PARAM);\n } catch {\n return undefined;\n }\n\n return fbclid ? `fb.1.${at}.${fbclid}` : undefined;\n}\n\nfunction toList(value: string | undefined): string[] | undefined {\n return value ? [value] : undefined;\n}\n\nfunction withoutEmpty<T extends Record<string, unknown>>(data: T): Partial<T> {\n return Object.fromEntries(Object.entries(data).filter(([, value]) => value !== undefined)) as Partial<T>;\n}\n"],"mappings":";;;AAKA,MAAM,sBAAsB;AAC5B,MAAM,0BAA0B;AAChC,MAAM,6BAA6B;;AAGnC,MAAM,oBAAoB;AAC1B,MAAM,kBAAkB;AACxB,MAAM,iBAAiB;;;;;;AAoBvB,SAAgB,gBAAgB,SAA4D;CAC1F,MAAM,UAAU,QAAQ,SAAS;CACjC,MAAM,MAAM,GAAG,oBAAoB,GAAG,QAAQ,cAAc,wBAAwB,GAAG,mBAAmB,QAAQ,OAAO,EAAE;CAE3H,OAAO;EACL,MAAM;EACN,MAAM,KAAK,OAAkB,EAAE,IAAI,IAAI,WAAW;GAChD,MAAM,SAAS,MAAM,SAAS,MAAM,QAAQ,CAAC,GAAG,EAAE,oBAAoB,QAAQ,mBAAmB,CAAC;GAClG,MAAM,OAAO;IACX,cAAc,QAAQ;IACtB,GAAI,QAAQ,iBAAiB,EAAE,iBAAiB,QAAQ,cAAc;IACtE,MAAM,CACJ;KACE,YAAY,MAAM;KAClB,YAAY,KAAK,MAAM,KAAK,GAAI;KAChC,UAAU;KACV,kBAAkB,aAAa,QAAQ,SAAS;KAChD,eAAe;KACf,WAAW,aAAa;MACtB,IAAI,OAAO,OAAO,KAAK;MACvB,IAAI,OAAO,OAAO,KAAK;MACvB,IAAI,OAAO,OAAO,SAAS;MAC3B,IAAI,OAAO,OAAO,QAAQ;MAC1B,IAAI,OAAO,OAAO,IAAI;MACtB,IAAI,OAAO,OAAO,UAAU;MAC5B,SAAS,OAAO,OAAO,OAAO;MAC9B,aAAa,OAAO,OAAO,UAAU;MACrC,mBAAmB,QAAQ;MAC3B,mBAAmB,QAAQ,QAAQ,IAAI,YAAY,KAAK,KAAA;MACxD,KAAK,QAAQ,OAAO,iBAAiB;MACrC,KAAK,QAAQ,OAAO,eAAe,KAAK,UAAU,SAAS,EAAE;KAC/D,CAAC;KACD,aAAa,MAAM;IACrB,CACF;GACF;GAEA,MAAM,WAAW,MAAM,QAAQ,KAAK;IAClC,QAAQ;IACR,SAAS,EAAE,gBAAgB,mBAAmB;IAC9C,MAAM,KAAK,UAAU,IAAI;IACzB,QAAQ,YAAY,QAAQ,0BAA0B;GACxD,CAAC;GAED,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,MAAM,6BAA6B,SAAS,OAAO,IAAI,MAAM,SAAS,KAAK,GAAG;EAE5F;CACF;AACF;;AAGA,SAAS,UAAU,SAAuB,IAAgC;CACxE,IAAI;CAEJ,IAAI;EACF,SAAS,IAAI,IAAI,QAAQ,SAAS,CAAC,CAAC,aAAa,IAAI,cAAc;CACrE,QAAQ;EACN;CACF;CAEA,OAAO,SAAS,QAAQ,GAAG,GAAG,WAAW,KAAA;AAC3C;AAEA,SAAS,OAAO,OAAiD;CAC/D,OAAO,QAAQ,CAAC,KAAK,IAAI,KAAA;AAC3B;AAEA,SAAS,aAAgD,MAAqB;CAC5E,OAAO,OAAO,YAAY,OAAO,QAAQ,IAAI,CAAC,CAAC,QAAQ,GAAG,WAAW,UAAU,KAAA,CAAS,CAAC;AAC3F"}
|
package/dist/openai/index.d.ts
CHANGED
|
@@ -1,33 +1,59 @@
|
|
|
1
1
|
import { a as Destination } from "../bus-Z7l8oJtS.js";
|
|
2
|
-
import { t as ConversionUser } from "../conversion-user-
|
|
2
|
+
import { t as ConversionUser } from "../conversion-user-fHh2xXBc.js";
|
|
3
3
|
/** monetary values are integers in the minor unit of the currency: 12999 for 129.99 EUR */
|
|
4
4
|
type OpenAiContent = {
|
|
5
|
-
id
|
|
6
|
-
name
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
id?: string | undefined;
|
|
6
|
+
name?: string | undefined;
|
|
7
|
+
/** e.g. `product`, `plan`, `page` */
|
|
8
|
+
content_type?: string | undefined;
|
|
9
|
+
quantity?: number | undefined;
|
|
10
|
+
/** per unit */
|
|
11
|
+
amount?: number | undefined;
|
|
12
|
+
currency?: string | undefined;
|
|
13
|
+
};
|
|
14
|
+
/** what every data shape may carry: the money, and the items, where the event has any */
|
|
15
|
+
type OpenAiBaseData = {
|
|
16
|
+
amount?: number | undefined;
|
|
17
|
+
/** required with `amount` */
|
|
18
|
+
currency?: string | undefined;
|
|
19
|
+
contents?: readonly OpenAiContent[] | undefined;
|
|
11
20
|
};
|
|
12
|
-
type OpenAiContentsData = {
|
|
21
|
+
type OpenAiContentsData = OpenAiBaseData & {
|
|
13
22
|
type: 'contents';
|
|
14
|
-
amount: number;
|
|
15
|
-
currency: string;
|
|
16
|
-
contents: readonly OpenAiContent[];
|
|
17
23
|
};
|
|
18
|
-
type OpenAiCustomerActionData = {
|
|
24
|
+
type OpenAiCustomerActionData = Omit<OpenAiBaseData, 'contents'> & {
|
|
19
25
|
type: 'customer_action';
|
|
20
26
|
};
|
|
27
|
+
type OpenAiPlanEnrollmentData = OpenAiBaseData & {
|
|
28
|
+
type: 'plan_enrollment';
|
|
29
|
+
plan_id?: string | undefined;
|
|
30
|
+
};
|
|
31
|
+
type OpenAiCustomData = OpenAiBaseData & {
|
|
32
|
+
type: 'custom';
|
|
33
|
+
plan_id?: string | undefined;
|
|
34
|
+
};
|
|
35
|
+
type OpenAiContentsEventName = 'contents_viewed' | 'items_added' | 'checkout_started' | 'order_created' | 'page_viewed';
|
|
36
|
+
type OpenAiCustomerActionEventName = 'lead_created' | 'registration_completed' | 'appointment_scheduled';
|
|
37
|
+
type OpenAiPlanEnrollmentEventName = 'subscription_created' | 'trial_started';
|
|
21
38
|
/**
|
|
22
39
|
* What openai takes, from the pixel and from the conversions api alike: the event and, for a
|
|
23
40
|
* conversion the visitor entered identifiers for, the identifiers — hashed by whichever half sends.
|
|
41
|
+
* The app events (`app_installed`, `app_opened`) are a mobile app's, not a website's, and have no builder.
|
|
24
42
|
*/
|
|
25
43
|
type OpenAiEvent = ({
|
|
26
|
-
name:
|
|
44
|
+
name: OpenAiContentsEventName;
|
|
27
45
|
data: OpenAiContentsData;
|
|
28
46
|
} | {
|
|
29
|
-
name:
|
|
47
|
+
name: OpenAiCustomerActionEventName;
|
|
30
48
|
data: OpenAiCustomerActionData;
|
|
49
|
+
} | {
|
|
50
|
+
name: OpenAiPlanEnrollmentEventName;
|
|
51
|
+
data: OpenAiPlanEnrollmentData;
|
|
52
|
+
} | {
|
|
53
|
+
name: 'custom';
|
|
54
|
+
/** 1 to 64 lowercase letters, digits, underscores or dashes, no standard event's name */
|
|
55
|
+
customName: string;
|
|
56
|
+
data: OpenAiCustomData;
|
|
31
57
|
}) & {
|
|
32
58
|
user?: ConversionUser | undefined;
|
|
33
59
|
};
|
|
@@ -36,12 +62,18 @@ declare module '@astroscope/relay' {
|
|
|
36
62
|
openai: OpenAiEvent;
|
|
37
63
|
}
|
|
38
64
|
}
|
|
65
|
+
/** an item as the site has it: the price in the major unit, 129.99 for 129.99 EUR */
|
|
39
66
|
type OpenAiContentInput = {
|
|
40
67
|
id: string;
|
|
41
68
|
name: string;
|
|
42
69
|
price: number;
|
|
43
70
|
quantity: number;
|
|
44
71
|
};
|
|
72
|
+
/** the money of an event: the total in the major unit, and its currency */
|
|
73
|
+
type OpenAiMoney = {
|
|
74
|
+
value: number;
|
|
75
|
+
currency: string;
|
|
76
|
+
};
|
|
45
77
|
type OpenAiContents = {
|
|
46
78
|
items: readonly OpenAiContentInput[];
|
|
47
79
|
currency: string;
|
|
@@ -51,13 +83,40 @@ type OpenAiContents = {
|
|
|
51
83
|
contentType?: string | undefined;
|
|
52
84
|
user?: ConversionUser | undefined;
|
|
53
85
|
};
|
|
54
|
-
|
|
86
|
+
type OpenAiPlan = {
|
|
87
|
+
/** the site's plan identifier */
|
|
88
|
+
id?: string | undefined;
|
|
89
|
+
money?: OpenAiMoney | undefined;
|
|
90
|
+
/** the plan's items, if any; `content_type` `plan` by default */
|
|
91
|
+
items?: readonly OpenAiContentInput[] | undefined;
|
|
92
|
+
contentType?: string | undefined;
|
|
93
|
+
user?: ConversionUser | undefined;
|
|
94
|
+
};
|
|
95
|
+
type OpenAiCustom = OpenAiPlan & {
|
|
96
|
+
/** the `content_type` of the items; default `product` */
|
|
97
|
+
contentType?: string | undefined;
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* The openai builders: every event openai has a name for, and `custom(name)`. What an event
|
|
101
|
+
* definition puts under `openai`.
|
|
102
|
+
*/
|
|
55
103
|
export declare const openai: {
|
|
56
104
|
contentsViewed: (contents: OpenAiContents) => OpenAiEvent;
|
|
57
105
|
itemsAdded: (contents: OpenAiContents) => OpenAiEvent;
|
|
58
106
|
checkoutStarted: (contents: OpenAiContents) => OpenAiEvent;
|
|
59
107
|
orderCreated: (contents: OpenAiContents) => OpenAiEvent;
|
|
60
|
-
|
|
108
|
+
/** the page as a content: its id is what the reports group by, e.g. the path */
|
|
109
|
+
pageViewed: (page: {
|
|
110
|
+
id: string;
|
|
111
|
+
name: string;
|
|
112
|
+
}) => OpenAiEvent;
|
|
113
|
+
leadCreated: (user?: ConversionUser, money?: OpenAiMoney) => OpenAiEvent;
|
|
114
|
+
registrationCompleted: (user?: ConversionUser, money?: OpenAiMoney) => OpenAiEvent;
|
|
115
|
+
appointmentScheduled: (user?: ConversionUser, money?: OpenAiMoney) => OpenAiEvent;
|
|
116
|
+
subscriptionCreated: (plan?: OpenAiPlan) => OpenAiEvent;
|
|
117
|
+
trialStarted: (plan?: OpenAiPlan) => OpenAiEvent;
|
|
118
|
+
/** a custom event: any name openai has no standard event for */
|
|
119
|
+
custom: (name: string, { id, money, items, contentType, user }?: OpenAiCustom) => OpenAiEvent;
|
|
61
120
|
};
|
|
62
121
|
export declare function toMinorUnits(value: number): number;
|
|
63
122
|
/** advanced matching data of the pixel: normalized identifiers as lowercase sha256 hex */
|
|
@@ -70,6 +129,8 @@ type OpenAiUser = {
|
|
|
70
129
|
type OpenAiOptions = {
|
|
71
130
|
/** deduplicates against the same event sent through the conversions api */
|
|
72
131
|
event_id?: string | undefined;
|
|
132
|
+
/** the name of a `custom` event; no standard event takes one */
|
|
133
|
+
custom_event_name?: string | undefined;
|
|
73
134
|
};
|
|
74
135
|
type Oaiq = {
|
|
75
136
|
(command: 'init', options: {
|
|
@@ -93,5 +154,5 @@ export type OpenAiPixelOptions = {
|
|
|
93
154
|
* identifiers an event carries are hashed and sent through `init` right before it.
|
|
94
155
|
*/
|
|
95
156
|
export declare function openAiPixel(options?: OpenAiPixelOptions): Destination<'openai'>;
|
|
96
|
-
export type { OpenAiContent, OpenAiContentInput, OpenAiContents, OpenAiContentsData, OpenAiCustomerActionData, OpenAiEvent };
|
|
157
|
+
export type { OpenAiContent, OpenAiContentInput, OpenAiContents, OpenAiContentsData, OpenAiCustom, OpenAiCustomData, OpenAiCustomerActionData, OpenAiEvent, OpenAiMoney, OpenAiPlan, OpenAiPlanEnrollmentData };
|
|
97
158
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":["_0"],"sources":["../../src/openai/shared.ts","../../src/openai/index.ts"],"mappings":";;;KAKY;EACV;EACA;EACA;EACA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":["_0"],"sources":["../../src/openai/shared.ts","../../src/openai/index.ts"],"mappings":";;;KAKY;EACV;EACA;;EAEA;EACA;;EAEA;EACA;;;KAIG;EACH;;EAEA;EACA,oBAAoB;;KAGV,qBAAqB;EAC/B;;KAGU,2BAA2B,KAAK;EAC1C;;KAGU,2BAA2B;EACrC;EACA;;KAGU,mBAAmB;EAC7B;EACA;;KAGU;KACA;KACA;;;;;;KAOA;EAER,MAAM;EACN,MAAM;;EAGN,MAAM;EACN,MAAM;;EAGN,MAAM;EACN,MAAM;;EAGN;;EAEA;EACA,MAAM;;EAGR,OAAO;;;YAIG;IACR,QAAQ;;;;KAKA;EAAuB;EAAY;EAAc;EAAe;;;KAGhE;EAAgB;EAAe;;KAE/B;EACV,gBAAgB;EAChB;;EAEA;;EAEA;EACA,OAAO;;KAGG;;EAEV;EACA,QAAQ;;EAER,iBAAiB;EACjB;EACA,OAAO;;KAGG,eAAe;;EAEzB;;;;;;qBAOW;EACgB,iBAAA,UAAA,mBAAiB;EACrB,aAAA,UAAA,mBAAiB;EACZ,kBAAA,UAAA,mBAAiB;EACpB,eAAA,UAAA,mBAAiB;;EAEvB,aAAA;IAAE;IAAY;QAAiB;EAI7B,cAAA,OAAA,gBAAc,QAAU,gBAAc;EAC5B,wBAAA,OAAA,gBAAc,QAAU,gBAAc;EAEvC,uBAAA,OAAA,gBAAc,QAAU,gBAAc;EAExC,sBAAA,OAAA,eAAkB;EACzB,eAAA,OAAA,eAAkB;;EAExB,SAAA,gBAAM,IAAA,OAAA,OAAA,aAAA,SAAuD,iBAAoB;;wBAqDlF,aAAa;;KCrKxB;EACH;EACA;EACA;EACA;;KAGG;;EAEH;;EAEA;;KAGG;GACF,iBAAiB;IAAW,MAAM;;GAClC,oBAAoB,MAAM,qBAAqB,MAAM,qBAAqB,UAAU;;QAG/E;YACI;IACR,OAAO;;;YAIC;;EAEV;;;;;;;;wBAWc,YAAY,UAAS,qBAA0B"}
|
package/dist/openai/index.js
CHANGED
|
@@ -1,15 +1,42 @@
|
|
|
1
|
-
import { t as hashUser } from "../conversion-user-
|
|
1
|
+
import { t as hashUser } from "../conversion-user-BgpXmIdJ.js";
|
|
2
2
|
import { a as isBrowser } from "../shared-D281USsp.js";
|
|
3
3
|
// src/openai/shared.ts
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* The openai builders: every event openai has a name for, and `custom(name)`. What an event
|
|
6
|
+
* definition puts under `openai`.
|
|
7
|
+
*/
|
|
5
8
|
const openai = {
|
|
6
9
|
contentsViewed: (contents) => contentsEvent("contents_viewed", contents),
|
|
7
10
|
itemsAdded: (contents) => contentsEvent("items_added", contents),
|
|
8
11
|
checkoutStarted: (contents) => contentsEvent("checkout_started", contents),
|
|
9
12
|
orderCreated: (contents) => contentsEvent("order_created", contents),
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
/** the page as a content: its id is what the reports group by, e.g. the path */
|
|
14
|
+
pageViewed: (page) => ({
|
|
15
|
+
name: "page_viewed",
|
|
16
|
+
data: {
|
|
17
|
+
type: "contents",
|
|
18
|
+
contents: [{
|
|
19
|
+
id: page.id,
|
|
20
|
+
name: page.name,
|
|
21
|
+
content_type: "page"
|
|
22
|
+
}]
|
|
23
|
+
}
|
|
24
|
+
}),
|
|
25
|
+
leadCreated: (user, money) => actionEvent("lead_created", user, money),
|
|
26
|
+
registrationCompleted: (user, money) => actionEvent("registration_completed", user, money),
|
|
27
|
+
appointmentScheduled: (user, money) => actionEvent("appointment_scheduled", user, money),
|
|
28
|
+
subscriptionCreated: (plan = {}) => planEvent("subscription_created", plan),
|
|
29
|
+
trialStarted: (plan = {}) => planEvent("trial_started", plan),
|
|
30
|
+
/** a custom event: any name openai has no standard event for */
|
|
31
|
+
custom: (name, { id, money, items, contentType = "product", user } = {}) => ({
|
|
32
|
+
name: "custom",
|
|
33
|
+
customName: name,
|
|
34
|
+
data: {
|
|
35
|
+
type: "custom",
|
|
36
|
+
plan_id: id,
|
|
37
|
+
...toMoneyData(money),
|
|
38
|
+
...toContentsData(items, contentType, money?.currency)
|
|
39
|
+
},
|
|
13
40
|
user
|
|
14
41
|
})
|
|
15
42
|
};
|
|
@@ -18,20 +45,54 @@ function contentsEvent(name, { items, currency, value, contentType = "product",
|
|
|
18
45
|
name,
|
|
19
46
|
data: {
|
|
20
47
|
type: "contents",
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
contents: items.map((item) => ({
|
|
24
|
-
id: item.id,
|
|
25
|
-
name: item.name,
|
|
26
|
-
content_type: contentType,
|
|
27
|
-
quantity: item.quantity,
|
|
28
|
-
amount: toMinorUnits(item.price),
|
|
48
|
+
...toMoneyData({
|
|
49
|
+
value: value ?? items.reduce((sum, item) => sum + item.price * item.quantity, 0),
|
|
29
50
|
currency
|
|
30
|
-
})
|
|
51
|
+
}),
|
|
52
|
+
...toContentsData(items, contentType, currency)
|
|
53
|
+
},
|
|
54
|
+
user
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function actionEvent(name, user, money) {
|
|
58
|
+
return {
|
|
59
|
+
name,
|
|
60
|
+
data: {
|
|
61
|
+
type: "customer_action",
|
|
62
|
+
...toMoneyData(money)
|
|
63
|
+
},
|
|
64
|
+
user
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function planEvent(name, { id, money, items, contentType = "plan", user }) {
|
|
68
|
+
return {
|
|
69
|
+
name,
|
|
70
|
+
data: {
|
|
71
|
+
type: "plan_enrollment",
|
|
72
|
+
plan_id: id,
|
|
73
|
+
...toMoneyData(money),
|
|
74
|
+
...toContentsData(items, contentType, money?.currency)
|
|
31
75
|
},
|
|
32
76
|
user
|
|
33
77
|
};
|
|
34
78
|
}
|
|
79
|
+
function toMoneyData(money) {
|
|
80
|
+
return money ? {
|
|
81
|
+
amount: toMinorUnits(money.value),
|
|
82
|
+
currency: money.currency
|
|
83
|
+
} : {};
|
|
84
|
+
}
|
|
85
|
+
function toContentsData(items, contentType, currency) {
|
|
86
|
+
if (!items) return {};
|
|
87
|
+
return { contents: items.map((item) => ({
|
|
88
|
+
id: item.id,
|
|
89
|
+
name: item.name,
|
|
90
|
+
content_type: contentType,
|
|
91
|
+
quantity: item.quantity,
|
|
92
|
+
amount: toMinorUnits(item.price),
|
|
93
|
+
currency
|
|
94
|
+
})) };
|
|
95
|
+
}
|
|
35
96
|
function toMinorUnits(value) {
|
|
36
97
|
return Math.round(value * 100);
|
|
37
98
|
}
|
|
@@ -57,7 +118,10 @@ function measure(event, meta, hashed) {
|
|
|
57
118
|
const oaiq = window.oaiq;
|
|
58
119
|
if (!oaiq) return;
|
|
59
120
|
if (hashed) identify(oaiq, hashed);
|
|
60
|
-
oaiq("measure", event.name, event.data, {
|
|
121
|
+
oaiq("measure", event.name, event.data, {
|
|
122
|
+
event_id: meta.id,
|
|
123
|
+
...event.name === "custom" && { custom_event_name: event.customName }
|
|
124
|
+
});
|
|
61
125
|
}
|
|
62
126
|
function identify(oaiq, hashed) {
|
|
63
127
|
const user = withoutEmpty({
|
package/dist/openai/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/openai/shared.ts","../../src/openai/index.ts"],"sourcesContent":["// imported by the pixel and the conversions api — no node or browser imports here\n\nimport type { ConversionUser } from '../conversion-user.js';\n\n/** monetary values are integers in the minor unit of the currency: 12999 for 129.99 EUR */\nexport type OpenAiContent = {\n id: string;\n name: string;\n content_type: string;\n quantity: number;\n amount: number;\n currency: string;\n};\n\nexport type OpenAiContentsData = {\n type: 'contents';\n amount: number;\n currency: string;\n contents: readonly OpenAiContent[];\n};\n\nexport type OpenAiCustomerActionData = {\n type: 'customer_action';\n};\n\n/**\n * What openai takes, from the pixel and from the conversions api alike: the event and, for a\n * conversion the visitor entered identifiers for, the identifiers — hashed by whichever half sends.\n */\nexport type OpenAiEvent = (\n | {\n name: 'contents_viewed' | 'items_added' | 'checkout_started' | 'order_created';\n data: OpenAiContentsData;\n }\n | {\n name: 'lead_created';\n data: OpenAiCustomerActionData;\n }\n) & {\n user?: ConversionUser | undefined;\n};\n\ndeclare module '@astroscope/relay' {\n interface RelayDestinations {\n openai: OpenAiEvent;\n }\n}\n\nexport type OpenAiContentInput = { id: string; name: string; price: number; quantity: number };\n\nexport type OpenAiContents = {\n items: readonly OpenAiContentInput[];\n currency: string;\n /** the total; default: the items summed */\n value?: number | undefined;\n /** the `content_type` of the contents; default `product` */\n contentType?: string | undefined;\n user?: ConversionUser | undefined;\n};\n\n/** the openai builders: the events openai has a name for. What an event definition puts under `openai` */\nexport const openai = {\n contentsViewed: (contents: OpenAiContents): OpenAiEvent => contentsEvent('contents_viewed', contents),\n itemsAdded: (contents: OpenAiContents): OpenAiEvent => contentsEvent('items_added', contents),\n checkoutStarted: (contents: OpenAiContents): OpenAiEvent => contentsEvent('checkout_started', contents),\n orderCreated: (contents: OpenAiContents): OpenAiEvent => contentsEvent('order_created', contents),\n leadCreated: (user?: ConversionUser): OpenAiEvent => ({ name: 'lead_created', data: { type: 'customer_action' }, user }),\n};\n\nfunction contentsEvent(\n name: 'contents_viewed' | 'items_added' | 'checkout_started' | 'order_created',\n { items, currency, value, contentType = 'product', user }: OpenAiContents,\n): OpenAiEvent {\n const total = value ?? items.reduce((sum, item) => sum + item.price * item.quantity, 0);\n\n return {\n name,\n data: {\n type: 'contents',\n amount: toMinorUnits(total),\n currency,\n contents: items.map((item) => ({\n id: item.id,\n name: item.name,\n content_type: contentType,\n quantity: item.quantity,\n amount: toMinorUnits(item.price),\n currency,\n })),\n },\n user,\n };\n}\n\nexport function toMinorUnits(value: number): number {\n return Math.round(value * 100);\n}\n","import type { Destination } from '../client/bus.js';\nimport { type HashedUser, hashUser } from '../conversion-user.js';\nimport { type EventMeta, isBrowser } from '../shared.js';\nimport type { OpenAiEvent } from './shared.js';\n\nexport type { OpenAiContent, OpenAiContentInput, OpenAiContents, OpenAiContentsData, OpenAiCustomerActionData, OpenAiEvent } from './shared.js';\nexport { openai, toMinorUnits } from './shared.js';\n\n/** advanced matching data of the pixel: normalized identifiers as lowercase sha256 hex */\ntype OpenAiUser = {\n email_sha256?: string | undefined;\n phone_number_sha256?: string | undefined;\n first_name_sha256?: string | undefined;\n last_name_sha256?: string | undefined;\n};\n\ntype OpenAiOptions = {\n /** deduplicates against the same event sent through the conversions api */\n event_id?: string | undefined;\n};\n\ntype Oaiq = {\n (command: 'init', options: { user: OpenAiUser }): void;\n (command: 'measure', name: OpenAiEvent['name'], data: OpenAiEvent['data'], options?: OpenAiOptions): void;\n};\n\ndeclare global {\n interface Window {\n oaiq?: Oaiq | undefined;\n }\n}\n\nexport type OpenAiPixelOptions = {\n /** the country calling code national phone numbers get before hashing, e.g. `49` */\n countryCallingCode?: string | undefined;\n};\n\nlet initializedUserKey: string | undefined;\n\n/**\n * The openai ads pixel as a destination: measures the `openai` payload of an event under the event's\n * id, so a conversion the server also reported through the conversions api counts once. The pixel\n * is injected by the cmp after consent, so `window.oaiq` shows up late: events wait for it. The\n * identifiers an event carries are hashed and sent through `init` right before it.\n */\nexport function openAiPixel(options: OpenAiPixelOptions = {}): Destination<'openai'> {\n return {\n name: 'openai',\n ready: () => isBrowser() && typeof globalThis.window.oaiq === 'function',\n send: (event, meta) => {\n const user = event.user ? hashUser(event.user, { countryCallingCode: options.countryCallingCode }) : undefined;\n\n void Promise.resolve(user)\n .then((hashed) => measure(event, meta, hashed))\n .catch((error: unknown) => console.error('relay: destination openai failed', error));\n },\n };\n}\n\nfunction measure(event: OpenAiEvent, meta: EventMeta, hashed: HashedUser | undefined): void {\n const oaiq = window.oaiq;\n\n if (!oaiq) return;\n\n if (hashed) {\n identify(oaiq, hashed);\n }\n\n oaiq('measure', event.name, event.data as OpenAiEvent['data'], { event_id: meta.id });\n}\n\nfunction identify(oaiq: Oaiq, hashed: HashedUser): void {\n const user = withoutEmpty({\n email_sha256: hashed.email,\n phone_number_sha256: hashed.phone,\n first_name_sha256: hashed.firstName,\n last_name_sha256: hashed.lastName,\n });\n const key = JSON.stringify(user);\n\n if (key === initializedUserKey || Object.keys(user).length === 0) {\n return;\n }\n\n initializedUserKey = key;\n oaiq('init', { user });\n}\n\nfunction withoutEmpty(user: OpenAiUser): OpenAiUser {\n return Object.fromEntries(Object.entries(user).filter(([, value]) => value)) as OpenAiUser;\n}\n"],"mappings":";;;;AA6DA,MAAa,SAAS;CACpB,iBAAiB,aAA0C,cAAc,mBAAmB,QAAQ;CACpG,aAAa,aAA0C,cAAc,eAAe,QAAQ;CAC5F,kBAAkB,aAA0C,cAAc,oBAAoB,QAAQ;CACtG,eAAe,aAA0C,cAAc,iBAAiB,QAAQ;CAChG,cAAc,UAAwC;EAAE,MAAM;EAAgB,MAAM,EAAE,MAAM,kBAAkB;EAAG;CAAK;AACxH;AAEA,SAAS,cACP,MACA,EAAE,OAAO,UAAU,OAAO,cAAc,WAAW,QACtC;CAGb,OAAO;EACL;EACA,MAAM;GACJ,MAAM;GACN,QAAQ,aANE,SAAS,MAAM,QAAQ,KAAK,SAAS,MAAM,KAAK,QAAQ,KAAK,UAAU,CAAC,CAMxD;GAC1B;GACA,UAAU,MAAM,KAAK,UAAU;IAC7B,IAAI,KAAK;IACT,MAAM,KAAK;IACX,cAAc;IACd,UAAU,KAAK;IACf,QAAQ,aAAa,KAAK,KAAK;IAC/B;GACF,EAAE;EACJ;EACA;CACF;AACF;AAEA,SAAgB,aAAa,OAAuB;CAClD,OAAO,KAAK,MAAM,QAAQ,GAAG;AAC/B;;AC3DA,IAAI;;;;;;;AAQJ,SAAgB,YAAY,UAA8B,CAAC,GAA0B;CACnF,OAAO;EACL,MAAM;EACN,aAAa,UAAU,KAAK,OAAO,WAAW,OAAO,SAAS;EAC9D,OAAO,OAAO,SAAS;GACrB,MAAM,OAAO,MAAM,OAAO,SAAS,MAAM,MAAM,EAAE,oBAAoB,QAAQ,mBAAmB,CAAC,IAAI,KAAA;GAErG,QAAa,QAAQ,IAAI,CAAC,CACvB,MAAM,WAAW,QAAQ,OAAO,MAAM,MAAM,CAAC,CAAC,CAC9C,OAAO,UAAmB,QAAQ,MAAM,oCAAoC,KAAK,CAAC;EACvF;CACF;AACF;AAEA,SAAS,QAAQ,OAAoB,MAAiB,QAAsC;CAC1F,MAAM,OAAO,OAAO;CAEpB,IAAI,CAAC,MAAM;CAEX,IAAI,QACF,SAAS,MAAM,MAAM;CAGvB,KAAK,WAAW,MAAM,MAAM,MAAM,MAA6B,EAAE,UAAU,KAAK,GAAG,CAAC;AACtF;AAEA,SAAS,SAAS,MAAY,QAA0B;CACtD,MAAM,OAAO,aAAa;EACxB,cAAc,OAAO;EACrB,qBAAqB,OAAO;EAC5B,mBAAmB,OAAO;EAC1B,kBAAkB,OAAO;CAC3B,CAAC;CACD,MAAM,MAAM,KAAK,UAAU,IAAI;CAE/B,IAAI,QAAQ,sBAAsB,OAAO,KAAK,IAAI,CAAC,CAAC,WAAW,GAC7D;CAGF,qBAAqB;CACrB,KAAK,QAAQ,EAAE,KAAK,CAAC;AACvB;AAEA,SAAS,aAAa,MAA8B;CAClD,OAAO,OAAO,YAAY,OAAO,QAAQ,IAAI,CAAC,CAAC,QAAQ,GAAG,WAAW,KAAK,CAAC;AAC7E"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/openai/shared.ts","../../src/openai/index.ts"],"sourcesContent":["// imported by the pixel and the conversions api — no node or browser imports here\n\nimport type { ConversionUser } from '../conversion-user.js';\n\n/** monetary values are integers in the minor unit of the currency: 12999 for 129.99 EUR */\nexport type OpenAiContent = {\n id?: string | undefined;\n name?: string | undefined;\n /** e.g. `product`, `plan`, `page` */\n content_type?: string | undefined;\n quantity?: number | undefined;\n /** per unit */\n amount?: number | undefined;\n currency?: string | undefined;\n};\n\n/** what every data shape may carry: the money, and the items, where the event has any */\ntype OpenAiBaseData = {\n amount?: number | undefined;\n /** required with `amount` */\n currency?: string | undefined;\n contents?: readonly OpenAiContent[] | undefined;\n};\n\nexport type OpenAiContentsData = OpenAiBaseData & {\n type: 'contents';\n};\n\nexport type OpenAiCustomerActionData = Omit<OpenAiBaseData, 'contents'> & {\n type: 'customer_action';\n};\n\nexport type OpenAiPlanEnrollmentData = OpenAiBaseData & {\n type: 'plan_enrollment';\n plan_id?: string | undefined;\n};\n\nexport type OpenAiCustomData = OpenAiBaseData & {\n type: 'custom';\n plan_id?: string | undefined;\n};\n\nexport type OpenAiContentsEventName = 'contents_viewed' | 'items_added' | 'checkout_started' | 'order_created' | 'page_viewed';\nexport type OpenAiCustomerActionEventName = 'lead_created' | 'registration_completed' | 'appointment_scheduled';\nexport type OpenAiPlanEnrollmentEventName = 'subscription_created' | 'trial_started';\n\n/**\n * What openai takes, from the pixel and from the conversions api alike: the event and, for a\n * conversion the visitor entered identifiers for, the identifiers — hashed by whichever half sends.\n * The app events (`app_installed`, `app_opened`) are a mobile app's, not a website's, and have no builder.\n */\nexport type OpenAiEvent = (\n | {\n name: OpenAiContentsEventName;\n data: OpenAiContentsData;\n }\n | {\n name: OpenAiCustomerActionEventName;\n data: OpenAiCustomerActionData;\n }\n | {\n name: OpenAiPlanEnrollmentEventName;\n data: OpenAiPlanEnrollmentData;\n }\n | {\n name: 'custom';\n /** 1 to 64 lowercase letters, digits, underscores or dashes, no standard event's name */\n customName: string;\n data: OpenAiCustomData;\n }\n) & {\n user?: ConversionUser | undefined;\n};\n\ndeclare module '@astroscope/relay' {\n interface RelayDestinations {\n openai: OpenAiEvent;\n }\n}\n\n/** an item as the site has it: the price in the major unit, 129.99 for 129.99 EUR */\nexport type OpenAiContentInput = { id: string; name: string; price: number; quantity: number };\n\n/** the money of an event: the total in the major unit, and its currency */\nexport type OpenAiMoney = { value: number; currency: string };\n\nexport type OpenAiContents = {\n items: readonly OpenAiContentInput[];\n currency: string;\n /** the total; default: the items summed */\n value?: number | undefined;\n /** the `content_type` of the contents; default `product` */\n contentType?: string | undefined;\n user?: ConversionUser | undefined;\n};\n\nexport type OpenAiPlan = {\n /** the site's plan identifier */\n id?: string | undefined;\n money?: OpenAiMoney | undefined;\n /** the plan's items, if any; `content_type` `plan` by default */\n items?: readonly OpenAiContentInput[] | undefined;\n contentType?: string | undefined;\n user?: ConversionUser | undefined;\n};\n\nexport type OpenAiCustom = OpenAiPlan & {\n /** the `content_type` of the items; default `product` */\n contentType?: string | undefined;\n};\n\n/**\n * The openai builders: every event openai has a name for, and `custom(name)`. What an event\n * definition puts under `openai`.\n */\nexport const openai = {\n contentsViewed: (contents: OpenAiContents): OpenAiEvent => contentsEvent('contents_viewed', contents),\n itemsAdded: (contents: OpenAiContents): OpenAiEvent => contentsEvent('items_added', contents),\n checkoutStarted: (contents: OpenAiContents): OpenAiEvent => contentsEvent('checkout_started', contents),\n orderCreated: (contents: OpenAiContents): OpenAiEvent => contentsEvent('order_created', contents),\n /** the page as a content: its id is what the reports group by, e.g. the path */\n pageViewed: (page: { id: string; name: string }): OpenAiEvent => ({\n name: 'page_viewed',\n data: { type: 'contents', contents: [{ id: page.id, name: page.name, content_type: 'page' }] },\n }),\n leadCreated: (user?: ConversionUser, money?: OpenAiMoney): OpenAiEvent => actionEvent('lead_created', user, money),\n registrationCompleted: (user?: ConversionUser, money?: OpenAiMoney): OpenAiEvent =>\n actionEvent('registration_completed', user, money),\n appointmentScheduled: (user?: ConversionUser, money?: OpenAiMoney): OpenAiEvent =>\n actionEvent('appointment_scheduled', user, money),\n subscriptionCreated: (plan: OpenAiPlan = {}): OpenAiEvent => planEvent('subscription_created', plan),\n trialStarted: (plan: OpenAiPlan = {}): OpenAiEvent => planEvent('trial_started', plan),\n /** a custom event: any name openai has no standard event for */\n custom: (name: string, { id, money, items, contentType = 'product', user }: OpenAiCustom = {}): OpenAiEvent => ({\n name: 'custom',\n customName: name,\n data: { type: 'custom', plan_id: id, ...toMoneyData(money), ...toContentsData(items, contentType, money?.currency) },\n user,\n }),\n};\n\nfunction contentsEvent(name: OpenAiContentsEventName, { items, currency, value, contentType = 'product', user }: OpenAiContents): OpenAiEvent {\n const total = value ?? items.reduce((sum, item) => sum + item.price * item.quantity, 0);\n\n return {\n name,\n data: { type: 'contents', ...toMoneyData({ value: total, currency }), ...toContentsData(items, contentType, currency) },\n user,\n };\n}\n\nfunction actionEvent(name: OpenAiCustomerActionEventName, user?: ConversionUser, money?: OpenAiMoney): OpenAiEvent {\n return { name, data: { type: 'customer_action', ...toMoneyData(money) }, user };\n}\n\nfunction planEvent(name: OpenAiPlanEnrollmentEventName, { id, money, items, contentType = 'plan', user }: OpenAiPlan): OpenAiEvent {\n return {\n name,\n data: { type: 'plan_enrollment', plan_id: id, ...toMoneyData(money), ...toContentsData(items, contentType, money?.currency) },\n user,\n };\n}\n\nfunction toMoneyData(money: OpenAiMoney | undefined): Pick<OpenAiBaseData, 'amount' | 'currency'> {\n return money ? { amount: toMinorUnits(money.value), currency: money.currency } : {};\n}\n\nfunction toContentsData(\n items: readonly OpenAiContentInput[] | undefined,\n contentType: string,\n currency: string | undefined,\n): Pick<OpenAiBaseData, 'contents'> {\n if (!items) return {};\n\n return {\n contents: items.map((item) => ({\n id: item.id,\n name: item.name,\n content_type: contentType,\n quantity: item.quantity,\n amount: toMinorUnits(item.price),\n currency,\n })),\n };\n}\n\nexport function toMinorUnits(value: number): number {\n return Math.round(value * 100);\n}\n","import type { Destination } from '../client/bus.js';\nimport { type HashedUser, hashUser } from '../conversion-user.js';\nimport { type EventMeta, isBrowser } from '../shared.js';\nimport type { OpenAiEvent } from './shared.js';\n\nexport type {\n OpenAiContent,\n OpenAiContentInput,\n OpenAiContents,\n OpenAiContentsData,\n OpenAiCustom,\n OpenAiCustomData,\n OpenAiCustomerActionData,\n OpenAiEvent,\n OpenAiMoney,\n OpenAiPlan,\n OpenAiPlanEnrollmentData,\n} from './shared.js';\nexport { openai, toMinorUnits } from './shared.js';\n\n/** advanced matching data of the pixel: normalized identifiers as lowercase sha256 hex */\ntype OpenAiUser = {\n email_sha256?: string | undefined;\n phone_number_sha256?: string | undefined;\n first_name_sha256?: string | undefined;\n last_name_sha256?: string | undefined;\n};\n\ntype OpenAiOptions = {\n /** deduplicates against the same event sent through the conversions api */\n event_id?: string | undefined;\n /** the name of a `custom` event; no standard event takes one */\n custom_event_name?: string | undefined;\n};\n\ntype Oaiq = {\n (command: 'init', options: { user: OpenAiUser }): void;\n (command: 'measure', name: OpenAiEvent['name'], data: OpenAiEvent['data'], options?: OpenAiOptions): void;\n};\n\ndeclare global {\n interface Window {\n oaiq?: Oaiq | undefined;\n }\n}\n\nexport type OpenAiPixelOptions = {\n /** the country calling code national phone numbers get before hashing, e.g. `49` */\n countryCallingCode?: string | undefined;\n};\n\nlet initializedUserKey: string | undefined;\n\n/**\n * The openai ads pixel as a destination: measures the `openai` payload of an event under the event's\n * id, so a conversion the server also reported through the conversions api counts once. The pixel\n * is injected by the cmp after consent, so `window.oaiq` shows up late: events wait for it. The\n * identifiers an event carries are hashed and sent through `init` right before it.\n */\nexport function openAiPixel(options: OpenAiPixelOptions = {}): Destination<'openai'> {\n return {\n name: 'openai',\n ready: () => isBrowser() && typeof globalThis.window.oaiq === 'function',\n send: (event, meta) => {\n const user = event.user ? hashUser(event.user, { countryCallingCode: options.countryCallingCode }) : undefined;\n\n void Promise.resolve(user)\n .then((hashed) => measure(event, meta, hashed))\n .catch((error: unknown) => console.error('relay: destination openai failed', error));\n },\n };\n}\n\nfunction measure(event: OpenAiEvent, meta: EventMeta, hashed: HashedUser | undefined): void {\n const oaiq = window.oaiq;\n\n if (!oaiq) return;\n\n if (hashed) {\n identify(oaiq, hashed);\n }\n\n oaiq('measure', event.name, event.data as OpenAiEvent['data'], {\n event_id: meta.id,\n ...(event.name === 'custom' && { custom_event_name: event.customName }),\n });\n}\n\nfunction identify(oaiq: Oaiq, hashed: HashedUser): void {\n const user = withoutEmpty({\n email_sha256: hashed.email,\n phone_number_sha256: hashed.phone,\n first_name_sha256: hashed.firstName,\n last_name_sha256: hashed.lastName,\n });\n const key = JSON.stringify(user);\n\n if (key === initializedUserKey || Object.keys(user).length === 0) {\n return;\n }\n\n initializedUserKey = key;\n oaiq('init', { user });\n}\n\nfunction withoutEmpty(user: OpenAiUser): OpenAiUser {\n return Object.fromEntries(Object.entries(user).filter(([, value]) => value)) as OpenAiUser;\n}\n"],"mappings":";;;;;;;AAmHA,MAAa,SAAS;CACpB,iBAAiB,aAA0C,cAAc,mBAAmB,QAAQ;CACpG,aAAa,aAA0C,cAAc,eAAe,QAAQ;CAC5F,kBAAkB,aAA0C,cAAc,oBAAoB,QAAQ;CACtG,eAAe,aAA0C,cAAc,iBAAiB,QAAQ;;CAEhG,aAAa,UAAqD;EAChE,MAAM;EACN,MAAM;GAAE,MAAM;GAAY,UAAU,CAAC;IAAE,IAAI,KAAK;IAAI,MAAM,KAAK;IAAM,cAAc;GAAO,CAAC;EAAE;CAC/F;CACA,cAAc,MAAuB,UAAqC,YAAY,gBAAgB,MAAM,KAAK;CACjH,wBAAwB,MAAuB,UAC7C,YAAY,0BAA0B,MAAM,KAAK;CACnD,uBAAuB,MAAuB,UAC5C,YAAY,yBAAyB,MAAM,KAAK;CAClD,sBAAsB,OAAmB,CAAC,MAAmB,UAAU,wBAAwB,IAAI;CACnG,eAAe,OAAmB,CAAC,MAAmB,UAAU,iBAAiB,IAAI;;CAErF,SAAS,MAAc,EAAE,IAAI,OAAO,OAAO,cAAc,WAAW,SAAuB,CAAC,OAAoB;EAC9G,MAAM;EACN,YAAY;EACZ,MAAM;GAAE,MAAM;GAAU,SAAS;GAAI,GAAG,YAAY,KAAK;GAAG,GAAG,eAAe,OAAO,aAAa,OAAO,QAAQ;EAAE;EACnH;CACF;AACF;AAEA,SAAS,cAAc,MAA+B,EAAE,OAAO,UAAU,OAAO,cAAc,WAAW,QAAqC;CAG5I,OAAO;EACL;EACA,MAAM;GAAE,MAAM;GAAY,GAAG,YAAY;IAAE,OAJ/B,SAAS,MAAM,QAAQ,KAAK,SAAS,MAAM,KAAK,QAAQ,KAAK,UAAU,CAAC;IAI3B;GAAS,CAAC;GAAG,GAAG,eAAe,OAAO,aAAa,QAAQ;EAAE;EACtH;CACF;AACF;AAEA,SAAS,YAAY,MAAqC,MAAuB,OAAkC;CACjH,OAAO;EAAE;EAAM,MAAM;GAAE,MAAM;GAAmB,GAAG,YAAY,KAAK;EAAE;EAAG;CAAK;AAChF;AAEA,SAAS,UAAU,MAAqC,EAAE,IAAI,OAAO,OAAO,cAAc,QAAQ,QAAiC;CACjI,OAAO;EACL;EACA,MAAM;GAAE,MAAM;GAAmB,SAAS;GAAI,GAAG,YAAY,KAAK;GAAG,GAAG,eAAe,OAAO,aAAa,OAAO,QAAQ;EAAE;EAC5H;CACF;AACF;AAEA,SAAS,YAAY,OAA6E;CAChG,OAAO,QAAQ;EAAE,QAAQ,aAAa,MAAM,KAAK;EAAG,UAAU,MAAM;CAAS,IAAI,CAAC;AACpF;AAEA,SAAS,eACP,OACA,aACA,UACkC;CAClC,IAAI,CAAC,OAAO,OAAO,CAAC;CAEpB,OAAO,EACL,UAAU,MAAM,KAAK,UAAU;EAC7B,IAAI,KAAK;EACT,MAAM,KAAK;EACX,cAAc;EACd,UAAU,KAAK;EACf,QAAQ,aAAa,KAAK,KAAK;EAC/B;CACF,EAAE,EACJ;AACF;AAEA,SAAgB,aAAa,OAAuB;CAClD,OAAO,KAAK,MAAM,QAAQ,GAAG;AAC/B;;ACzIA,IAAI;;;;;;;AAQJ,SAAgB,YAAY,UAA8B,CAAC,GAA0B;CACnF,OAAO;EACL,MAAM;EACN,aAAa,UAAU,KAAK,OAAO,WAAW,OAAO,SAAS;EAC9D,OAAO,OAAO,SAAS;GACrB,MAAM,OAAO,MAAM,OAAO,SAAS,MAAM,MAAM,EAAE,oBAAoB,QAAQ,mBAAmB,CAAC,IAAI,KAAA;GAErG,QAAa,QAAQ,IAAI,CAAC,CACvB,MAAM,WAAW,QAAQ,OAAO,MAAM,MAAM,CAAC,CAAC,CAC9C,OAAO,UAAmB,QAAQ,MAAM,oCAAoC,KAAK,CAAC;EACvF;CACF;AACF;AAEA,SAAS,QAAQ,OAAoB,MAAiB,QAAsC;CAC1F,MAAM,OAAO,OAAO;CAEpB,IAAI,CAAC,MAAM;CAEX,IAAI,QACF,SAAS,MAAM,MAAM;CAGvB,KAAK,WAAW,MAAM,MAAM,MAAM,MAA6B;EAC7D,UAAU,KAAK;EACf,GAAI,MAAM,SAAS,YAAY,EAAE,mBAAmB,MAAM,WAAW;CACvE,CAAC;AACH;AAEA,SAAS,SAAS,MAAY,QAA0B;CACtD,MAAM,OAAO,aAAa;EACxB,cAAc,OAAO;EACrB,qBAAqB,OAAO;EAC5B,mBAAmB,OAAO;EAC1B,kBAAkB,OAAO;CAC3B,CAAC;CACD,MAAM,MAAM,KAAK,UAAU,IAAI;CAE/B,IAAI,QAAQ,sBAAsB,OAAO,KAAK,IAAI,CAAC,CAAC,WAAW,GAC7D;CAGF,qBAAqB;CACrB,KAAK,QAAQ,EAAE,KAAK,CAAC;AACvB;AAEA,SAAS,aAAa,MAA8B;CAClD,OAAO,OAAO,YAAY,OAAO,QAAQ,IAAI,CAAC,CAAC,QAAQ,GAAG,WAAW,KAAK,CAAC;AAC7E"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","names":[],"sources":["../../src/openai/server.ts"],"mappings":";
|
|
1
|
+
{"version":3,"file":"server.d.ts","names":[],"sources":["../../src/openai/server.ts"],"mappings":";YAWY;;EAEV;EACA;;EAEA;;EAEA;EACA,eAAe;;;;;;wBAOD,kBAAkB,SAAS,2BAA2B"}
|
package/dist/openai/server.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { t as hashUser } from "../conversion-user-
|
|
1
|
+
import { t as hashUser } from "../conversion-user-BgpXmIdJ.js";
|
|
2
|
+
import { t as withoutQuery } from "../source-url-Cqfh3UjO.js";
|
|
2
3
|
// src/openai/server.ts
|
|
3
4
|
const CONVERSIONS_API_URL = "https://bzr.openai.com/v1/events";
|
|
4
5
|
const CONVERSIONS_API_TIMEOUT_MS = 1e4;
|
|
@@ -19,8 +20,9 @@ function openAiConversions(options) {
|
|
|
19
20
|
events: [{
|
|
20
21
|
id,
|
|
21
22
|
type: event.name,
|
|
23
|
+
...event.name === "custom" && { custom_event_name: event.customName },
|
|
22
24
|
timestamp_ms: Date.now(),
|
|
23
|
-
source_url: request.sourceUrl,
|
|
25
|
+
source_url: withoutQuery(request.sourceUrl),
|
|
24
26
|
action_source: "web",
|
|
25
27
|
user: {
|
|
26
28
|
emails_sha256: toList(hashed.email),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","names":[],"sources":["../../src/openai/server.ts"],"sourcesContent":["import { hashUser } from '../conversion-user.js';\nimport type { ServerDestination } from '../runtime.js';\nimport type { OpenAiEvent } from './shared.js';\n\nconst CONVERSIONS_API_URL = 'https://bzr.openai.com/v1/events';\nconst CONVERSIONS_API_TIMEOUT_MS = 10_000;\n\n/** the click reference the pixel stores for the visitor who came from an openai ad */\nconst CLICK_REFERENCE_COOKIE = '__obref';\n\nexport type OpenAiConversionsOptions = {\n /** the conversions api key from the ads manager */\n apiKey: string;\n pixelId: string;\n /** names the sending system in the request, e.g. `my-site-de` */\n integrationSource: string;\n /** the country calling code national phone numbers get before hashing, e.g. `49` */\n countryCallingCode?: string | undefined;\n fetch?: typeof fetch | undefined;\n};\n\n/**\n * The openai ads conversions api as a server destination: reports the `openai` payload of an event\n * under the id the pixel fires it with.\n */\nexport function openAiConversions(options: OpenAiConversionsOptions): ServerDestination<'openai'> {\n const doFetch = options.fetch ?? fetch;\n\n return {\n name: 'openai',\n async send(event: OpenAiEvent, { id, request }) {\n const hashed = await hashUser(event.user ?? {}, { countryCallingCode: options.countryCallingCode });\n const body = {\n integration_source: options.integrationSource,\n events: [\n {\n id,\n type: event.name,\n timestamp_ms: Date.now(),\n source_url: request.sourceUrl,\n action_source: 'web',\n user: {\n emails_sha256: toList(hashed.email),\n phone_numbers_sha256: toList(hashed.phone),\n first_names_sha256: toList(hashed.firstName),\n last_names_sha256: toList(hashed.lastName),\n ip_address: request.clientAddress,\n user_agent: request.headers.get('user-agent') ?? undefined,\n obref: request.cookie(CLICK_REFERENCE_COOKIE),\n },\n data: event.data,\n },\n ],\n };\n\n const response = await doFetch(`${CONVERSIONS_API_URL}?pid=${encodeURIComponent(options.pixelId)}`, {\n method: 'POST',\n headers: { authorization: `Bearer ${options.apiKey}`, 'content-type': 'application/json' },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(CONVERSIONS_API_TIMEOUT_MS),\n });\n\n if (!response.ok) {\n throw new Error(`conversions api responded ${response.status}: ${await response.text()}`);\n }\n },\n };\n}\n\nfunction toList(value: string | undefined): string[] | undefined {\n return value ? [value] : undefined;\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"server.js","names":[],"sources":["../../src/openai/server.ts"],"sourcesContent":["import { hashUser } from '../conversion-user.js';\nimport type { ServerDestination } from '../runtime.js';\nimport { withoutQuery } from '../source-url.js';\nimport type { OpenAiEvent } from './shared.js';\n\nconst CONVERSIONS_API_URL = 'https://bzr.openai.com/v1/events';\nconst CONVERSIONS_API_TIMEOUT_MS = 10_000;\n\n/** the click reference the pixel stores for the visitor who came from an openai ad */\nconst CLICK_REFERENCE_COOKIE = '__obref';\n\nexport type OpenAiConversionsOptions = {\n /** the conversions api key from the ads manager */\n apiKey: string;\n pixelId: string;\n /** names the sending system in the request, e.g. `my-site-de` */\n integrationSource: string;\n /** the country calling code national phone numbers get before hashing, e.g. `49` */\n countryCallingCode?: string | undefined;\n fetch?: typeof fetch | undefined;\n};\n\n/**\n * The openai ads conversions api as a server destination: reports the `openai` payload of an event\n * under the id the pixel fires it with.\n */\nexport function openAiConversions(options: OpenAiConversionsOptions): ServerDestination<'openai'> {\n const doFetch = options.fetch ?? fetch;\n\n return {\n name: 'openai',\n async send(event: OpenAiEvent, { id, request }) {\n const hashed = await hashUser(event.user ?? {}, { countryCallingCode: options.countryCallingCode });\n const body = {\n integration_source: options.integrationSource,\n events: [\n {\n id,\n type: event.name,\n ...(event.name === 'custom' && { custom_event_name: event.customName }),\n timestamp_ms: Date.now(),\n source_url: withoutQuery(request.sourceUrl),\n action_source: 'web',\n user: {\n emails_sha256: toList(hashed.email),\n phone_numbers_sha256: toList(hashed.phone),\n first_names_sha256: toList(hashed.firstName),\n last_names_sha256: toList(hashed.lastName),\n ip_address: request.clientAddress,\n user_agent: request.headers.get('user-agent') ?? undefined,\n obref: request.cookie(CLICK_REFERENCE_COOKIE),\n },\n data: event.data,\n },\n ],\n };\n\n const response = await doFetch(`${CONVERSIONS_API_URL}?pid=${encodeURIComponent(options.pixelId)}`, {\n method: 'POST',\n headers: { authorization: `Bearer ${options.apiKey}`, 'content-type': 'application/json' },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(CONVERSIONS_API_TIMEOUT_MS),\n });\n\n if (!response.ok) {\n throw new Error(`conversions api responded ${response.status}: ${await response.text()}`);\n }\n },\n };\n}\n\nfunction toList(value: string | undefined): string[] | undefined {\n return value ? [value] : undefined;\n}\n"],"mappings":";;;AAKA,MAAM,sBAAsB;AAC5B,MAAM,6BAA6B;;AAGnC,MAAM,yBAAyB;;;;;AAiB/B,SAAgB,kBAAkB,SAAgE;CAChG,MAAM,UAAU,QAAQ,SAAS;CAEjC,OAAO;EACL,MAAM;EACN,MAAM,KAAK,OAAoB,EAAE,IAAI,WAAW;GAC9C,MAAM,SAAS,MAAM,SAAS,MAAM,QAAQ,CAAC,GAAG,EAAE,oBAAoB,QAAQ,mBAAmB,CAAC;GAClG,MAAM,OAAO;IACX,oBAAoB,QAAQ;IAC5B,QAAQ,CACN;KACE;KACA,MAAM,MAAM;KACZ,GAAI,MAAM,SAAS,YAAY,EAAE,mBAAmB,MAAM,WAAW;KACrE,cAAc,KAAK,IAAI;KACvB,YAAY,aAAa,QAAQ,SAAS;KAC1C,eAAe;KACf,MAAM;MACJ,eAAe,OAAO,OAAO,KAAK;MAClC,sBAAsB,OAAO,OAAO,KAAK;MACzC,oBAAoB,OAAO,OAAO,SAAS;MAC3C,mBAAmB,OAAO,OAAO,QAAQ;MACzC,YAAY,QAAQ;MACpB,YAAY,QAAQ,QAAQ,IAAI,YAAY,KAAK,KAAA;MACjD,OAAO,QAAQ,OAAO,sBAAsB;KAC9C;KACA,MAAM,MAAM;IACd,CACF;GACF;GAEA,MAAM,WAAW,MAAM,QAAQ,GAAG,oBAAoB,OAAO,mBAAmB,QAAQ,OAAO,KAAK;IAClG,QAAQ;IACR,SAAS;KAAE,eAAe,UAAU,QAAQ;KAAU,gBAAgB;IAAmB;IACzF,MAAM,KAAK,UAAU,IAAI;IACzB,QAAQ,YAAY,QAAQ,0BAA0B;GACxD,CAAC;GAED,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,MAAM,6BAA6B,SAAS,OAAO,IAAI,MAAM,SAAS,KAAK,GAAG;EAE5F;CACF;AACF;AAEA,SAAS,OAAO,OAAiD;CAC/D,OAAO,QAAQ,CAAC,KAAK,IAAI,KAAA;AAC3B"}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as ConsentRecord } from "../shared-CSwqcxPI.js";
|
|
2
|
-
import { a as normalizeEmail, c as sha256, i as hashUser, n as HashOptions, o as normalizeName, r as HashedUser, s as normalizePhone } from "../conversion-user-
|
|
2
|
+
import { a as normalizeEmail, c as sha256, i as hashUser, n as HashOptions, o as normalizeName, r as HashedUser, s as normalizePhone } from "../conversion-user-fHh2xXBc.js";
|
|
3
3
|
import { a as ServerDestination, c as defineRelayServer, i as ServerConsent, n as RelayRequest, o as ServerDestinations, r as RelayServerConfig, s as ServerEventMeta, t as AnyServerDestination } from "../runtime-J8VplGyY.js";
|
|
4
4
|
import { APIContext } from "astro";
|
|
5
5
|
/** what the middleware knows about the request `track()` is called in */
|
package/dist/server/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as relayConsent } from "../context-D11BBDcb.js";
|
|
2
|
-
import { a as sha256, i as normalizePhone, n as normalizeEmail, r as normalizeName, t as hashUser } from "../conversion-user-
|
|
2
|
+
import { a as sha256, i as normalizePhone, n as normalizeEmail, r as normalizeName, t as hashUser } from "../conversion-user-BgpXmIdJ.js";
|
|
3
3
|
// src/runtime.ts
|
|
4
4
|
/** types the server registry: the default export of `src/relay/server.ts` */
|
|
5
5
|
function defineRelayServer(config) {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// src/source-url.ts
|
|
2
|
+
/** the page without its query and fragment: a token or a click id in the url is no conversion api's business */
|
|
3
|
+
function withoutQuery(url) {
|
|
4
|
+
try {
|
|
5
|
+
const parsed = new URL(url);
|
|
6
|
+
return `${parsed.origin}${parsed.pathname}`;
|
|
7
|
+
} catch {
|
|
8
|
+
return url;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export { withoutQuery as t };
|
|
12
|
+
|
|
13
|
+
//# sourceMappingURL=source-url-Cqfh3UjO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source-url-Cqfh3UjO.js","names":[],"sources":["../src/source-url.ts"],"sourcesContent":["/** the page without its query and fragment: a token or a click id in the url is no conversion api's business */\nexport function withoutQuery(url: string): string {\n try {\n const parsed = new URL(url);\n\n return `${parsed.origin}${parsed.pathname}`;\n } catch {\n return url;\n }\n}\n"],"mappings":";;AACA,SAAgB,aAAa,KAAqB;CAChD,IAAI;EACF,MAAM,SAAS,IAAI,IAAI,GAAG;EAE1B,OAAO,GAAG,OAAO,SAAS,OAAO;CACnC,QAAQ;EACN,OAAO;CACT;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astroscope/relay",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Consent-aware event relay for Astro",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -66,6 +66,14 @@
|
|
|
66
66
|
"./openai/server": {
|
|
67
67
|
"types": "./dist/openai/server.d.ts",
|
|
68
68
|
"import": "./dist/openai/server.js"
|
|
69
|
+
},
|
|
70
|
+
"./meta": {
|
|
71
|
+
"types": "./dist/meta/index.d.ts",
|
|
72
|
+
"import": "./dist/meta/index.js"
|
|
73
|
+
},
|
|
74
|
+
"./meta/server": {
|
|
75
|
+
"types": "./dist/meta/server.d.ts",
|
|
76
|
+
"import": "./dist/meta/server.js"
|
|
69
77
|
}
|
|
70
78
|
},
|
|
71
79
|
"files": [
|
|
@@ -89,6 +97,7 @@
|
|
|
89
97
|
"tracking",
|
|
90
98
|
"ga4",
|
|
91
99
|
"awin",
|
|
100
|
+
"meta",
|
|
92
101
|
"ccm19",
|
|
93
102
|
"matomo",
|
|
94
103
|
"conversions",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"conversion-user-CV8xIWDK.js","names":[],"sources":["../src/conversion-user.ts"],"sourcesContent":["/** what the visitor entered for a conversion; a destination that identifies hashes it as it requires */\nexport type ConversionUser = {\n email?: string | undefined;\n phone?: string | undefined;\n firstName?: string | undefined;\n lastName?: string | undefined;\n};\n\n/** the identifiers of a visitor as the ad platforms match them: normalized, then sha256 as lowercase hex */\nexport type HashedUser = {\n email?: string | undefined;\n phone?: string | undefined;\n firstName?: string | undefined;\n lastName?: string | undefined;\n};\n\nexport type HashOptions = {\n /**\n * The country calling code a national phone number gets (`49` for a German site): a number\n * with one leading zero is taken as national, `+` or `00` prefixes are international.\n */\n countryCallingCode?: string | undefined;\n};\n\n/** normalizes and hashes the identifiers the way the pixels and the conversions apis expect them */\nexport async function hashUser(user: ConversionUser, options: HashOptions = {}): Promise<HashedUser> {\n const [email, phone, firstName, lastName] = await Promise.all([\n hashOptional(normalizeEmail(user.email)),\n hashOptional(normalizePhone(user.phone, options.countryCallingCode)),\n hashOptional(normalizeName(user.firstName)),\n hashOptional(normalizeName(user.lastName)),\n ]);\n\n return withoutEmpty({ email, phone, firstName, lastName });\n}\n\nexport function normalizeEmail(email: string | undefined): string | undefined {\n return email?.trim().toLowerCase() || undefined;\n}\n\n/**\n * Digits only, with the country calling code: a national number (one leading zero) gets the\n * site's code, `+` and `00` prefixes are dropped. Returns `undefined` for anything that is not\n * 8 to 15 digits afterwards.\n */\nexport function normalizePhone(phone: string | undefined, countryCallingCode: string | undefined): string | undefined {\n const compact = phone?.replaceAll(/[\\s().-]/g, '');\n\n if (!compact) {\n return undefined;\n }\n\n const international = compact.startsWith('+') || compact.startsWith('00');\n const national = compact.replace(/^\\+?0*/, '');\n const digits = international || !compact.startsWith('0') ? national : `${countryCallingCode ?? ''}${national}`;\n\n return /^\\d{8,15}$/.test(digits) ? digits : undefined;\n}\n\n/** lowercase, without whitespace and punctuation */\nexport function normalizeName(name: string | undefined): string | undefined {\n return name?.toLowerCase().replaceAll(/[\\s!-/:-@[-`{-~]/g, '') || undefined;\n}\n\nexport async function sha256(value: string): Promise<string> {\n const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(value));\n\n return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, '0')).join('');\n}\n\nasync function hashOptional(value: string | undefined): Promise<string | undefined> {\n return value ? sha256(value) : undefined;\n}\n\nfunction withoutEmpty(user: HashedUser): HashedUser {\n return Object.fromEntries(Object.entries(user).filter(([, value]) => value)) as HashedUser;\n}\n"],"mappings":";;AAyBA,eAAsB,SAAS,MAAsB,UAAuB,CAAC,GAAwB;CACnG,MAAM,CAAC,OAAO,OAAO,WAAW,YAAY,MAAM,QAAQ,IAAI;EAC5D,aAAa,eAAe,KAAK,KAAK,CAAC;EACvC,aAAa,eAAe,KAAK,OAAO,QAAQ,kBAAkB,CAAC;EACnE,aAAa,cAAc,KAAK,SAAS,CAAC;EAC1C,aAAa,cAAc,KAAK,QAAQ,CAAC;CAC3C,CAAC;CAED,OAAO,aAAa;EAAE;EAAO;EAAO;EAAW;CAAS,CAAC;AAC3D;AAEA,SAAgB,eAAe,OAA+C;CAC5E,OAAO,OAAO,KAAK,CAAC,CAAC,YAAY,KAAK,KAAA;AACxC;;;;;;AAOA,SAAgB,eAAe,OAA2B,oBAA4D;CACpH,MAAM,UAAU,OAAO,WAAW,aAAa,EAAE;CAEjD,IAAI,CAAC,SACH;CAGF,MAAM,gBAAgB,QAAQ,WAAW,GAAG,KAAK,QAAQ,WAAW,IAAI;CACxE,MAAM,WAAW,QAAQ,QAAQ,UAAU,EAAE;CAC7C,MAAM,SAAS,iBAAiB,CAAC,QAAQ,WAAW,GAAG,IAAI,WAAW,GAAG,sBAAsB,KAAK;CAEpG,OAAO,aAAa,KAAK,MAAM,IAAI,SAAS,KAAA;AAC9C;;AAGA,SAAgB,cAAc,MAA8C;CAC1E,OAAO,MAAM,YAAY,CAAC,CAAC,WAAW,qBAAqB,EAAE,KAAK,KAAA;AACpE;AAEA,eAAsB,OAAO,OAAgC;CAC3D,MAAM,SAAS,MAAM,OAAO,OAAO,OAAO,WAAW,IAAI,YAAY,CAAC,CAAC,OAAO,KAAK,CAAC;CAEpF,OAAO,MAAM,KAAK,IAAI,WAAW,MAAM,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE;AACjG;AAEA,eAAe,aAAa,OAAwD;CAClF,OAAO,QAAQ,OAAO,KAAK,IAAI,KAAA;AACjC;AAEA,SAAS,aAAa,MAA8B;CAClD,OAAO,OAAO,YAAY,OAAO,QAAQ,IAAI,CAAC,CAAC,QAAQ,GAAG,WAAW,KAAK,CAAC;AAC7E"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"conversion-user-DCfM5EuZ.d.ts","names":[],"sources":["../src/conversion-user.ts"],"mappings":";KACY;EACV;EACA;EACA;EACA;;;KAIU;EACV;EACA;EACA;EACA;;KAGU;;;;;EAKV;;;iBAIoB,SAAS,MAAM,gBAAgB,UAAS,cAAmB,QAAQ;iBAWzE,eAAe;;;;;;iBASf,eAAe,2BAA2B;;iBAe1C,cAAc;iBAIR,OAAO,gBAAgB"}
|