@astroscope/relay 0.0.0-stage → 1.0.1

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.
Files changed (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +236 -2
  3. package/dist/awin/index.d.ts +57 -0
  4. package/dist/awin/index.d.ts.map +1 -0
  5. package/dist/awin/index.js +54 -0
  6. package/dist/awin/index.js.map +1 -0
  7. package/dist/bus-Z7l8oJtS.d.ts +48 -0
  8. package/dist/bus-Z7l8oJtS.d.ts.map +1 -0
  9. package/dist/ccm19/index.d.ts +33 -0
  10. package/dist/ccm19/index.d.ts.map +1 -0
  11. package/dist/ccm19/index.js +29 -0
  12. package/dist/ccm19/index.js.map +1 -0
  13. package/dist/client/index.d.ts +16 -0
  14. package/dist/client/index.d.ts.map +1 -0
  15. package/dist/client/index.js +24 -0
  16. package/dist/client/index.js.map +1 -0
  17. package/dist/client/track.d.ts +4 -0
  18. package/dist/client/track.d.ts.map +1 -0
  19. package/dist/client/track.js +7 -0
  20. package/dist/client/track.js.map +1 -0
  21. package/dist/collect-DRl177oR.d.ts +31 -0
  22. package/dist/collect-DRl177oR.d.ts.map +1 -0
  23. package/dist/collect-DWdHyn-u.js +45 -0
  24. package/dist/collect-DWdHyn-u.js.map +1 -0
  25. package/dist/commerce-DCGO8yag.d.ts +106 -0
  26. package/dist/commerce-DCGO8yag.d.ts.map +1 -0
  27. package/dist/commerce-DgNeBWhO.js +46 -0
  28. package/dist/commerce-DgNeBWhO.js.map +1 -0
  29. package/dist/context-D11BBDcb.js +27 -0
  30. package/dist/context-D11BBDcb.js.map +1 -0
  31. package/dist/conversion-user-CV8xIWDK.js +49 -0
  32. package/dist/conversion-user-CV8xIWDK.js.map +1 -0
  33. package/dist/conversion-user-DCfM5EuZ.d.ts +35 -0
  34. package/dist/conversion-user-DCfM5EuZ.d.ts.map +1 -0
  35. package/dist/ga4/index.d.ts +50 -0
  36. package/dist/ga4/index.d.ts.map +1 -0
  37. package/dist/ga4/index.js +45 -0
  38. package/dist/ga4/index.js.map +1 -0
  39. package/dist/gtm/index.d.ts +51 -0
  40. package/dist/gtm/index.d.ts.map +1 -0
  41. package/dist/gtm/index.js +49 -0
  42. package/dist/gtm/index.js.map +1 -0
  43. package/dist/index.browser.d.ts +8 -0
  44. package/dist/index.browser.d.ts.map +1 -0
  45. package/dist/index.browser.js +11 -0
  46. package/dist/index.browser.js.map +1 -0
  47. package/dist/index.d.ts +14 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +73 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/instance-Bu0EMJ_-.js +268 -0
  52. package/dist/instance-Bu0EMJ_-.js.map +1 -0
  53. package/dist/matomo/index.d.ts +46 -0
  54. package/dist/matomo/index.d.ts.map +1 -0
  55. package/dist/matomo/index.js +61 -0
  56. package/dist/matomo/index.js.map +1 -0
  57. package/dist/openai/index.d.ts +97 -0
  58. package/dist/openai/index.d.ts.map +1 -0
  59. package/dist/openai/index.js +79 -0
  60. package/dist/openai/index.js.map +1 -0
  61. package/dist/openai/server.d.ts +17 -0
  62. package/dist/openai/server.d.ts.map +1 -0
  63. package/dist/openai/server.js +55 -0
  64. package/dist/openai/server.js.map +1 -0
  65. package/dist/react/index.d.ts +7 -0
  66. package/dist/react/index.d.ts.map +1 -0
  67. package/dist/react/index.js +13 -0
  68. package/dist/react/index.js.map +1 -0
  69. package/dist/runtime-J8VplGyY.d.ts +48 -0
  70. package/dist/runtime-J8VplGyY.d.ts.map +1 -0
  71. package/dist/server/index.d.ts +18 -0
  72. package/dist/server/index.d.ts.map +1 -0
  73. package/dist/server/index.js +10 -0
  74. package/dist/server/index.js.map +1 -0
  75. package/dist/server/middleware-entrypoint.d.ts +2 -0
  76. package/dist/server/middleware-entrypoint.d.ts.map +1 -0
  77. package/dist/server/middleware-entrypoint.js +25 -0
  78. package/dist/server/middleware-entrypoint.js.map +1 -0
  79. package/dist/server/registry.d.ts +9 -0
  80. package/dist/server/registry.d.ts.map +1 -0
  81. package/dist/server/registry.js +10 -0
  82. package/dist/server/registry.js.map +1 -0
  83. package/dist/server/track.d.ts +10 -0
  84. package/dist/server/track.d.ts.map +1 -0
  85. package/dist/server/track.js +90 -0
  86. package/dist/server/track.js.map +1 -0
  87. package/dist/shared-CSwqcxPI.d.ts +77 -0
  88. package/dist/shared-CSwqcxPI.d.ts.map +1 -0
  89. package/dist/shared-D281USsp.js +106 -0
  90. package/dist/shared-D281USsp.js.map +1 -0
  91. package/package.json +133 -3
  92. package/src/astro/Track.astro +16 -0
  93. package/src/astro/index.ts +1 -0
@@ -0,0 +1,79 @@
1
+ import { t as hashUser } from "../conversion-user-CV8xIWDK.js";
2
+ import { a as isBrowser } from "../shared-D281USsp.js";
3
+ // src/openai/shared.ts
4
+ /** the openai builders: the events openai has a name for. What an event definition puts under `openai` */
5
+ const openai = {
6
+ contentsViewed: (contents) => contentsEvent("contents_viewed", contents),
7
+ itemsAdded: (contents) => contentsEvent("items_added", contents),
8
+ checkoutStarted: (contents) => contentsEvent("checkout_started", contents),
9
+ orderCreated: (contents) => contentsEvent("order_created", contents),
10
+ leadCreated: (user) => ({
11
+ name: "lead_created",
12
+ data: { type: "customer_action" },
13
+ user
14
+ })
15
+ };
16
+ function contentsEvent(name, { items, currency, value, contentType = "product", user }) {
17
+ return {
18
+ name,
19
+ data: {
20
+ type: "contents",
21
+ amount: toMinorUnits(value ?? items.reduce((sum, item) => sum + item.price * item.quantity, 0)),
22
+ currency,
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),
29
+ currency
30
+ }))
31
+ },
32
+ user
33
+ };
34
+ }
35
+ function toMinorUnits(value) {
36
+ return Math.round(value * 100);
37
+ }
38
+ // src/openai/index.ts
39
+ let initializedUserKey;
40
+ /**
41
+ * The openai ads pixel as a destination: measures the `openai` payload of an event under the event's
42
+ * id, so a conversion the server also reported through the conversions api counts once. The pixel
43
+ * is injected by the cmp after consent, so `window.oaiq` shows up late: events wait for it. The
44
+ * identifiers an event carries are hashed and sent through `init` right before it.
45
+ */
46
+ function openAiPixel(options = {}) {
47
+ return {
48
+ name: "openai",
49
+ ready: () => isBrowser() && typeof globalThis.window.oaiq === "function",
50
+ send: (event, meta) => {
51
+ const user = event.user ? hashUser(event.user, { countryCallingCode: options.countryCallingCode }) : void 0;
52
+ Promise.resolve(user).then((hashed) => measure(event, meta, hashed)).catch((error) => console.error("relay: destination openai failed", error));
53
+ }
54
+ };
55
+ }
56
+ function measure(event, meta, hashed) {
57
+ const oaiq = window.oaiq;
58
+ if (!oaiq) return;
59
+ if (hashed) identify(oaiq, hashed);
60
+ oaiq("measure", event.name, event.data, { event_id: meta.id });
61
+ }
62
+ function identify(oaiq, hashed) {
63
+ const user = withoutEmpty({
64
+ email_sha256: hashed.email,
65
+ phone_number_sha256: hashed.phone,
66
+ first_name_sha256: hashed.firstName,
67
+ last_name_sha256: hashed.lastName
68
+ });
69
+ const key = JSON.stringify(user);
70
+ if (key === initializedUserKey || Object.keys(user).length === 0) return;
71
+ initializedUserKey = key;
72
+ oaiq("init", { user });
73
+ }
74
+ function withoutEmpty(user) {
75
+ return Object.fromEntries(Object.entries(user).filter(([, value]) => value));
76
+ }
77
+ export { openAiPixel, openai, toMinorUnits };
78
+
79
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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"}
@@ -0,0 +1,17 @@
1
+ import { a as ServerDestination } from "../runtime-J8VplGyY.js";
2
+ export type OpenAiConversionsOptions = {
3
+ /** the conversions api key from the ads manager */
4
+ apiKey: string;
5
+ pixelId: string;
6
+ /** names the sending system in the request, e.g. `my-site-de` */
7
+ integrationSource: string;
8
+ /** the country calling code national phone numbers get before hashing, e.g. `49` */
9
+ countryCallingCode?: string | undefined;
10
+ fetch?: typeof fetch | undefined;
11
+ };
12
+ /**
13
+ * The openai ads conversions api as a server destination: reports the `openai` payload of an event
14
+ * under the id the pixel fires it with.
15
+ */
16
+ export declare function openAiConversions(options: OpenAiConversionsOptions): ServerDestination<'openai'>;
17
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","names":[],"sources":["../../src/openai/server.ts"],"mappings":";YAUY;;EAEV;EACA;;EAEA;;EAEA;EACA,eAAe;;;;;;wBAOD,kBAAkB,SAAS,2BAA2B"}
@@ -0,0 +1,55 @@
1
+ import { t as hashUser } from "../conversion-user-CV8xIWDK.js";
2
+ // src/openai/server.ts
3
+ const CONVERSIONS_API_URL = "https://bzr.openai.com/v1/events";
4
+ const CONVERSIONS_API_TIMEOUT_MS = 1e4;
5
+ /** the click reference the pixel stores for the visitor who came from an openai ad */
6
+ const CLICK_REFERENCE_COOKIE = "__obref";
7
+ /**
8
+ * The openai ads conversions api as a server destination: reports the `openai` payload of an event
9
+ * under the id the pixel fires it with.
10
+ */
11
+ function openAiConversions(options) {
12
+ const doFetch = options.fetch ?? fetch;
13
+ return {
14
+ name: "openai",
15
+ async send(event, { id, request }) {
16
+ const hashed = await hashUser(event.user ?? {}, { countryCallingCode: options.countryCallingCode });
17
+ const body = {
18
+ integration_source: options.integrationSource,
19
+ events: [{
20
+ id,
21
+ type: event.name,
22
+ timestamp_ms: Date.now(),
23
+ source_url: request.sourceUrl,
24
+ action_source: "web",
25
+ user: {
26
+ emails_sha256: toList(hashed.email),
27
+ phone_numbers_sha256: toList(hashed.phone),
28
+ first_names_sha256: toList(hashed.firstName),
29
+ last_names_sha256: toList(hashed.lastName),
30
+ ip_address: request.clientAddress,
31
+ user_agent: request.headers.get("user-agent") ?? void 0,
32
+ obref: request.cookie(CLICK_REFERENCE_COOKIE)
33
+ },
34
+ data: event.data
35
+ }]
36
+ };
37
+ const response = await doFetch(`${CONVERSIONS_API_URL}?pid=${encodeURIComponent(options.pixelId)}`, {
38
+ method: "POST",
39
+ headers: {
40
+ authorization: `Bearer ${options.apiKey}`,
41
+ "content-type": "application/json"
42
+ },
43
+ body: JSON.stringify(body),
44
+ signal: AbortSignal.timeout(CONVERSIONS_API_TIMEOUT_MS)
45
+ });
46
+ if (!response.ok) throw new Error(`conversions api responded ${response.status}: ${await response.text()}`);
47
+ }
48
+ };
49
+ }
50
+ function toList(value) {
51
+ return value ? [value] : void 0;
52
+ }
53
+ export { openAiConversions };
54
+
55
+ //# sourceMappingURL=server.js.map
@@ -0,0 +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":";;AAIA,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,cAAc,KAAK,IAAI;KACvB,YAAY,QAAQ;KACpB,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"}
@@ -0,0 +1,7 @@
1
+ import { p as TrackInput } from "../shared-CSwqcxPI.js";
2
+ export type TrackProps = {
3
+ event: TrackInput;
4
+ };
5
+ /** tracks the event once, when the island mounts; renders nothing */
6
+ export declare function Track({ event }: TrackProps): null;
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/react/index.tsx"],"mappings":";YAIY;EACV,OAAO;;;wBAIO,QAAQ,SAAS"}
@@ -0,0 +1,13 @@
1
+ import { track } from "../client/track.js";
2
+ import { useEffectOnce } from "@entwico/dash/react";
3
+ // src/react/index.tsx
4
+ /** tracks the event once, when the island mounts; renders nothing */
5
+ function Track({ event }) {
6
+ useEffectOnce(() => {
7
+ track(event);
8
+ });
9
+ return null;
10
+ }
11
+ export { Track };
12
+
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/react/index.tsx"],"sourcesContent":["import { useEffectOnce } from '@entwico/dash/react';\nimport { track } from '../client/track.js';\nimport type { TrackInput } from '../shared.js';\n\nexport type TrackProps = {\n event: TrackInput;\n};\n\n/** tracks the event once, when the island mounts; renders nothing */\nexport function Track({ event }: TrackProps) {\n useEffectOnce(() => {\n track(event);\n });\n\n return null;\n}\n"],"mappings":";;;;AASA,SAAgB,MAAM,EAAE,SAAqB;CAC3C,oBAAoB;EAClB,MAAM,KAAK;CACb,CAAC;CAED,OAAO;AACT"}
@@ -0,0 +1,48 @@
1
+ import { i as DestinationName, l as RelayDestinations, n as ConsentRecord, s as EventMeta } from "./shared-CSwqcxPI.js";
2
+ import { ExcludePattern } from "@astroscope/node/excludes";
3
+ import { APIContext } from "astro";
4
+ /** what a server destination gets from the request the conversion arrived with */
5
+ type RelayRequest = {
6
+ clientAddress: string;
7
+ headers: Headers;
8
+ /** a cookie's value, for the click ids the pixels set */
9
+ cookie: (name: string) => string | undefined;
10
+ /** the page the conversion happened on: the referer, else the site's base url */
11
+ sourceUrl: string;
12
+ };
13
+ /** what a server destination knows about an event: the browser's meta and the request */
14
+ type ServerEventMeta = EventMeta & {
15
+ request: RelayRequest;
16
+ };
17
+ /**
18
+ * The server half of a destination: reports a conversion through its conversions api, or records it
19
+ * first-party. The same shape as a browser destination — a name, its consent, a `send` of its payload.
20
+ */
21
+ type ServerDestination<N extends DestinationName = DestinationName> = {
22
+ /** the destination's name: its key in every event; by default also the browser destination whose consent it shares */
23
+ name: N;
24
+ send: (payload: RelayDestinations[N], meta: ServerEventMeta) => void | Promise<void>;
25
+ };
26
+ /** a server destination of any registered name */
27
+ type AnyServerDestination = { [N in DestinationName]: ServerDestination<N>; }[DestinationName];
28
+ /** the server destinations by name: the key an event's payload sits under, which is also the destination's own name */
29
+ type ServerDestinations = { [N in DestinationName]?: ServerDestination<N> | undefined; };
30
+ /**
31
+ * The consent per server destination, where the default — the flag of its name in the record the
32
+ * request carried — does not fit: `false` for a first-party record that needs none, a function
33
+ * answering from the record (another destination's flag).
34
+ */
35
+ type ServerConsent<N extends string> = { [K in N]?: false | ((consent: ConsentRecord) => boolean) | undefined; };
36
+ type RelayServerConfig<P extends ServerDestinations = ServerDestinations> = {
37
+ destinations: P;
38
+ consent?: ServerConsent<NoInfer<keyof P & string>> | undefined;
39
+ /**
40
+ * Requests whose conversions are not reported — test sessions, crawlers — as the path patterns or
41
+ * the predicate every astroscope middleware takes.
42
+ */
43
+ exclude?: ExcludePattern[] | ((ctx: APIContext) => boolean) | undefined;
44
+ };
45
+ /** types the server registry: the default export of `src/relay/server.ts` */
46
+ declare function defineRelayServer<P extends ServerDestinations>(config: RelayServerConfig<P>): RelayServerConfig<P>;
47
+ export { ServerDestination as a, defineRelayServer as c, ServerConsent as i, RelayRequest as n, ServerDestinations as o, RelayServerConfig as r, ServerEventMeta as s, AnyServerDestination as t };
48
+ //# sourceMappingURL=runtime-J8VplGyY.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-J8VplGyY.d.ts","names":[],"sources":["../src/runtime.ts"],"mappings":";;;;KAKY;EACV;EACA,SAAS;;EAET,SAAS;;EAET;;;KAIU,kBAAkB;EAC5B,SAAS;;;;;;KAOC,kBAAkB,UAAU,kBAAkB;;EAExD,MAAM;EACN,OAAO,SAAS,kBAAkB,IAAI,MAAM,2BAA2B;;;KAI7D,0BAA0B,KAAK,kBAAkB,kBAAkB,MAAK;;KAGxE,wBAAwB,KAAK,mBAAmB,kBAAkB;;;;;;KAOlE,cAAc,uBAAuB,KAAK,eAAe,SAAS;KAElE,kBAAkB,UAAU,qBAAqB;EAC3D,cAAc;EACd,UAAU,cAAc,cAAc;;;;;EAKtC,UAAU,qBAAqB,KAAK;;;iBAItB,kBAAkB,UAAU,oBAAoB,QAAQ,kBAAkB,KAAK,kBAAkB"}
@@ -0,0 +1,18 @@
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-DCfM5EuZ.js";
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
+ import { APIContext } from "astro";
5
+ /** what the middleware knows about the request `track()` is called in */
6
+ type RelayRequestContext = {
7
+ /** the consent the browser sent, by destination name */
8
+ consent: ConsentRecord;
9
+ context: APIContext;
10
+ };
11
+ /**
12
+ * The consent the current request carries, by destination name: what a server destination's default
13
+ * `consent` reads, and what a custom one reads to share another destination's flag. Empty outside a
14
+ * request and for a request the browser sent nothing with.
15
+ */
16
+ export declare function relayConsent(): ConsentRecord;
17
+ export { type AnyServerDestination, type HashOptions, type HashedUser, type RelayRequest, type RelayRequestContext, type RelayServerConfig, type ServerConsent, type ServerDestination, type ServerDestinations, type ServerEventMeta, defineRelayServer, hashUser, normalizeEmail, normalizeName, normalizePhone, sha256 };
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/server/context.ts"],"mappings":";;;;;KAKY;;EAEV,SAAS;EACT,SAAS;;;;;;;wBAgCK,gBAAgB"}
@@ -0,0 +1,10 @@
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-CV8xIWDK.js";
3
+ // src/runtime.ts
4
+ /** types the server registry: the default export of `src/relay/server.ts` */
5
+ function defineRelayServer(config) {
6
+ return config;
7
+ }
8
+ export { defineRelayServer, hashUser, normalizeEmail, normalizeName, normalizePhone, relayConsent, sha256 };
9
+
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/runtime.ts"],"sourcesContent":["import type { ExcludePattern } from '@astroscope/node/excludes';\nimport type { APIContext } from 'astro';\nimport type { ConsentRecord, DestinationName, EventMeta, RelayDestinations } from './shared.js';\n\n/** what a server destination gets from the request the conversion arrived with */\nexport type RelayRequest = {\n clientAddress: string;\n headers: Headers;\n /** a cookie's value, for the click ids the pixels set */\n cookie: (name: string) => string | undefined;\n /** the page the conversion happened on: the referer, else the site's base url */\n sourceUrl: string;\n};\n\n/** what a server destination knows about an event: the browser's meta and the request */\nexport type ServerEventMeta = EventMeta & {\n request: RelayRequest;\n};\n\n/**\n * The server half of a destination: reports a conversion through its conversions api, or records it\n * first-party. The same shape as a browser destination — a name, its consent, a `send` of its payload.\n */\nexport type ServerDestination<N extends DestinationName = DestinationName> = {\n /** the destination's name: its key in every event; by default also the browser destination whose consent it shares */\n name: N;\n send: (payload: RelayDestinations[N], meta: ServerEventMeta) => void | Promise<void>;\n};\n\n/** a server destination of any registered name */\nexport type AnyServerDestination = { [N in DestinationName]: ServerDestination<N> }[DestinationName];\n\n/** the server destinations by name: the key an event's payload sits under, which is also the destination's own name */\nexport type ServerDestinations = { [N in DestinationName]?: ServerDestination<N> | undefined };\n\n/**\n * The consent per server destination, where the default — the flag of its name in the record the\n * request carried — does not fit: `false` for a first-party record that needs none, a function\n * answering from the record (another destination's flag).\n */\nexport type ServerConsent<N extends string> = { [K in N]?: false | ((consent: ConsentRecord) => boolean) | undefined };\n\nexport type RelayServerConfig<P extends ServerDestinations = ServerDestinations> = {\n destinations: P;\n consent?: ServerConsent<NoInfer<keyof P & string>> | undefined;\n /**\n * Requests whose conversions are not reported — test sessions, crawlers — as the path patterns or\n * the predicate every astroscope middleware takes.\n */\n exclude?: ExcludePattern[] | ((ctx: APIContext) => boolean) | undefined;\n};\n\n/** types the server registry: the default export of `src/relay/server.ts` */\nexport function defineRelayServer<P extends ServerDestinations>(config: RelayServerConfig<P>): RelayServerConfig<P> {\n return config;\n}\n"],"mappings":";;;;AAqDA,SAAgB,kBAAgD,QAAoD;CAClH,OAAO;AACT"}
@@ -0,0 +1,2 @@
1
+ export declare const onRequest: import("astro").MiddlewareHandler;
2
+ //# sourceMappingURL=middleware-entrypoint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware-entrypoint.d.ts","names":[],"sources":["../../src/server/middleware-entrypoint.ts"],"mappings":"A,qBAEa,2BAAS"}
@@ -0,0 +1,25 @@
1
+ import { s as parseConsent, t as RELAY_CONSENT_HEADER } from "../shared-D281USsp.js";
2
+ import { r as runWithRelayContext } from "../context-D11BBDcb.js";
3
+ // src/server/middleware.ts
4
+ /**
5
+ * Reads the consent header the browser attached and runs the rest of the chain inside the relay
6
+ * context, so `track()` in an action, an endpoint or a page reports to the consented destinations.
7
+ * Injected by the integration as `pre` middleware, before the project's own — not part of the
8
+ * public API.
9
+ *
10
+ * @internal
11
+ */
12
+ function createRelayMiddleware() {
13
+ return (ctx, next) => {
14
+ const consent = parseConsent(ctx.request.headers.get(RELAY_CONSENT_HEADER));
15
+ return runWithRelayContext({
16
+ consent,
17
+ context: ctx
18
+ }, next);
19
+ };
20
+ }
21
+ // src/server/middleware-entrypoint.ts
22
+ const onRequest = createRelayMiddleware();
23
+ export { onRequest };
24
+
25
+ //# sourceMappingURL=middleware-entrypoint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware-entrypoint.js","names":[],"sources":["../../src/server/middleware.ts","../../src/server/middleware-entrypoint.ts"],"sourcesContent":["import type { MiddlewareHandler } from 'astro';\nimport { RELAY_CONSENT_HEADER, parseConsent } from '../shared.js';\nimport { runWithRelayContext } from './context.js';\n\n/**\n * Reads the consent header the browser attached and runs the rest of the chain inside the relay\n * context, so `track()` in an action, an endpoint or a page reports to the consented destinations.\n * Injected by the integration as `pre` middleware, before the project's own — not part of the\n * public API.\n *\n * @internal\n */\nexport function createRelayMiddleware(): MiddlewareHandler {\n return (ctx, next) => {\n const consent = parseConsent(ctx.request.headers.get(RELAY_CONSENT_HEADER));\n\n return runWithRelayContext({ consent, context: ctx }, next);\n };\n}\n","import { createRelayMiddleware } from './middleware.js';\n\nexport const onRequest = createRelayMiddleware();\n"],"mappings":";;;;;;;;;;;AAYA,SAAgB,wBAA2C;CACzD,QAAQ,KAAK,SAAS;EACpB,MAAM,UAAU,aAAa,IAAI,QAAQ,QAAQ,IAAI,oBAAoB,CAAC;EAE1E,OAAO,oBAAoB;GAAE;GAAS,SAAS;EAAI,GAAG,IAAI;CAC5D;AACF;;AChBA,MAAa,YAAY,sBAAsB"}
@@ -0,0 +1,9 @@
1
+ import { r as RelayServerConfig } from "../runtime-J8VplGyY.js";
2
+ /**
3
+ * The empty server registry. In a site, the integration resolves `@astroscope/relay/registry` to
4
+ * `src/relay/server.ts` when the file exists; everywhere else — a site without the file, unit tests
5
+ * outside vite — this one answers and the server-side `track()` reports nothing.
6
+ */
7
+ declare const registry: RelayServerConfig;
8
+ export { registry as default };
9
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","names":[],"sources":["../../src/server/registry.ts"],"mappings":";;;;;;cAOM,UAAU"}
@@ -0,0 +1,10 @@
1
+ // src/server/registry.ts
2
+ /**
3
+ * The empty server registry. In a site, the integration resolves `@astroscope/relay/registry` to
4
+ * `src/relay/server.ts` when the file exists; everywhere else — a site without the file, unit tests
5
+ * outside vite — this one answers and the server-side `track()` reports nothing.
6
+ */
7
+ const registry = { destinations: {} };
8
+ export { registry as default };
9
+
10
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","names":[],"sources":["../../src/server/registry.ts"],"sourcesContent":["import type { RelayServerConfig } from '../runtime.js';\n\n/**\n * The empty server registry. In a site, the integration resolves `@astroscope/relay/registry` to\n * `src/relay/server.ts` when the file exists; everywhere else — a site without the file, unit tests\n * outside vite — this one answers and the server-side `track()` reports nothing.\n */\nconst registry: RelayServerConfig = { destinations: {} };\n\nexport default registry;\n"],"mappings":";;;;;;AAOA,MAAM,WAA8B,EAAE,cAAc,CAAC,EAAE"}
@@ -0,0 +1,10 @@
1
+ import { f as TrackFunction } from "../shared-CSwqcxPI.js";
2
+ /**
3
+ * Reports an event to every consented server destination it carries a payload for, in the
4
+ * background: a failure is logged and never fails the request. Returns the event id the browser
5
+ * fires its pixels under: the event's own (an order number, so a resubmitted order never counts
6
+ * twice) or a fresh one. Outside a request, for an excluded one, or without the browser's consent it
7
+ * reports nothing and still returns the id.
8
+ */
9
+ export declare const track: TrackFunction;
10
+ //# sourceMappingURL=track.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"track.d.ts","names":[],"sources":["../../src/server/track.ts"],"mappings":";;;;;;;;qBAoBa,OAAO"}
@@ -0,0 +1,90 @@
1
+ import { c as payloadOf, f as unusableEventId, i as eventIdOf, u as toTracked } from "../shared-D281USsp.js";
2
+ import { t as getRelayContext } from "../context-D11BBDcb.js";
3
+ import { shouldExclude } from "@astroscope/node/excludes";
4
+ import { log } from "@astroscope/node/log";
5
+ import registry from "@astroscope/relay/registry";
6
+ // src/server/config.ts
7
+ /** the server registry: `src/relay/server.ts` of the site, or the empty one without the file */
8
+ function getServerConfig() {
9
+ return registry;
10
+ }
11
+ // src/server/track.ts
12
+ /**
13
+ * Reports an event to every consented server destination it carries a payload for, in the
14
+ * background: a failure is logged and never fails the request. Returns the event id the browser
15
+ * fires its pixels under: the event's own (an order number, so a resubmitted order never counts
16
+ * twice) or a fresh one. Outside a request, for an excluded one, or without the browser's consent it
17
+ * reports nothing and still returns the id.
18
+ */
19
+ const track = (input) => {
20
+ const { event, key, id: own } = toTracked(input);
21
+ const id = eventIdOf(own);
22
+ const unusable = unusableEventId(own, id);
23
+ if (unusable) log.error({
24
+ key,
25
+ id
26
+ }, unusable);
27
+ const context = getRelayContext();
28
+ if (!context) {
29
+ log.debug({
30
+ key,
31
+ id
32
+ }, "tracked outside a request, nothing reported");
33
+ return id;
34
+ }
35
+ const config = getServerConfig();
36
+ if (shouldExclude(context.context, config.exclude)) return id;
37
+ const meta = {
38
+ id,
39
+ at: Date.now(),
40
+ key,
41
+ request: requestOf(context)
42
+ };
43
+ const destinations = Object.values(config.destinations).filter((destination) => destination !== void 0).filter((destination) => consentOf(destination.name, config.consent, context.consent));
44
+ if (destinations.length === 0) {
45
+ log.debug({
46
+ key,
47
+ id
48
+ }, "no consented server destination, nothing reported");
49
+ return id;
50
+ }
51
+ for (const destination of destinations) {
52
+ const payload = payloadOf(event, destination.name);
53
+ if (payload !== void 0) report(destination, payload, meta);
54
+ }
55
+ return id;
56
+ };
57
+ function consentOf(name, rules, consent) {
58
+ const rule = rules?.[name];
59
+ if (rule === false) return true;
60
+ if (typeof rule === "function") return rule(consent);
61
+ return consent[name] === true;
62
+ }
63
+ function requestOf({ context }) {
64
+ return {
65
+ clientAddress: context.clientAddress,
66
+ headers: context.request.headers,
67
+ cookie: (name) => context.cookies.get(name)?.value,
68
+ sourceUrl: context.request.headers.get("referer") ?? context.url.origin
69
+ };
70
+ }
71
+ async function report(destination, payload, meta) {
72
+ try {
73
+ await destination.send(payload, meta);
74
+ log.debug({
75
+ destination: destination.name,
76
+ key: meta.key,
77
+ id: meta.id
78
+ }, "conversion reported");
79
+ } catch (error) {
80
+ log.error({
81
+ error,
82
+ destination: destination.name,
83
+ key: meta.key,
84
+ id: meta.id
85
+ }, "failed to report conversion");
86
+ }
87
+ }
88
+ export { track };
89
+
90
+ //# sourceMappingURL=track.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"track.js","names":[],"sources":["../../src/server/config.ts","../../src/server/track.ts"],"sourcesContent":["import registry from '@astroscope/relay/registry';\nimport type { RelayServerConfig } from '../runtime.js';\n\n/** the server registry: `src/relay/server.ts` of the site, or the empty one without the file */\nexport function getServerConfig(): RelayServerConfig {\n return registry;\n}\n","import { shouldExclude } from '@astroscope/node/excludes';\nimport { log } from '@astroscope/node/log';\nimport type { ServerConsent, ServerEventMeta } from '../runtime.js';\nimport { type ConsentRecord, type TrackFunction, eventIdOf, payloadOf, toTracked, unusableEventId } from '../shared.js';\nimport { getServerConfig } from './config.js';\nimport { type RelayRequestContext, getRelayContext } from './context.js';\n\n// the destinations as the reporter holds them: the payload type is the destination's business\ntype Registered = {\n name: string;\n send(payload: unknown, meta: ServerEventMeta): void | Promise<void>;\n};\n\n/**\n * Reports an event to every consented server destination it carries a payload for, in the\n * background: a failure is logged and never fails the request. Returns the event id the browser\n * fires its pixels under: the event's own (an order number, so a resubmitted order never counts\n * twice) or a fresh one. Outside a request, for an excluded one, or without the browser's consent it\n * reports nothing and still returns the id.\n */\nexport const track: TrackFunction = (input) => {\n const { event, key, id: own } = toTracked(input);\n const id = eventIdOf(own);\n const unusable = unusableEventId(own, id);\n\n if (unusable) {\n log.error({ key, id }, unusable);\n }\n\n const context = getRelayContext();\n\n if (!context) {\n log.debug({ key, id }, 'tracked outside a request, nothing reported');\n\n return id;\n }\n\n const config = getServerConfig();\n\n if (shouldExclude(context.context, config.exclude)) {\n return id;\n }\n\n const meta: ServerEventMeta = { id, at: Date.now(), key, request: requestOf(context) };\n const registered = Object.values(config.destinations).filter((destination) => destination !== undefined) as\n Registered[];\n const destinations = registered.filter((destination) => consentOf(destination.name, config.consent, context.consent));\n\n if (destinations.length === 0) {\n log.debug({ key, id }, 'no consented server destination, nothing reported');\n\n return id;\n }\n\n for (const destination of destinations) {\n const payload = payloadOf(event, destination.name);\n\n if (payload !== undefined) {\n void report(destination, payload, meta);\n }\n }\n\n return id;\n};\n\nfunction consentOf(name: string, rules: ServerConsent<string> | undefined, consent: ConsentRecord): boolean {\n const rule = rules?.[name];\n\n if (rule === false) return true;\n if (typeof rule === 'function') return rule(consent);\n\n return consent[name] === true;\n}\n\nfunction requestOf({ context }: RelayRequestContext): ServerEventMeta['request'] {\n return {\n clientAddress: context.clientAddress,\n headers: context.request.headers,\n cookie: (name) => context.cookies.get(name)?.value,\n sourceUrl: context.request.headers.get('referer') ?? context.url.origin,\n };\n}\n\nasync function report(destination: Registered, payload: unknown, meta: ServerEventMeta): Promise<void> {\n try {\n await destination.send(payload, meta);\n log.debug({ destination: destination.name, key: meta.key, id: meta.id }, 'conversion reported');\n } catch (error) {\n log.error({ error, destination: destination.name, key: meta.key, id: meta.id }, 'failed to report conversion');\n }\n}\n"],"mappings":";;;;;;;AAIA,SAAgB,kBAAqC;CACnD,OAAO;AACT;;;;;;;;;ACcA,MAAa,SAAwB,UAAU;CAC7C,MAAM,EAAE,OAAO,KAAK,IAAI,QAAQ,UAAU,KAAK;CAC/C,MAAM,KAAK,UAAU,GAAG;CACxB,MAAM,WAAW,gBAAgB,KAAK,EAAE;CAExC,IAAI,UACF,IAAI,MAAM;EAAE;EAAK;CAAG,GAAG,QAAQ;CAGjC,MAAM,UAAU,gBAAgB;CAEhC,IAAI,CAAC,SAAS;EACZ,IAAI,MAAM;GAAE;GAAK;EAAG,GAAG,6CAA6C;EAEpE,OAAO;CACT;CAEA,MAAM,SAAS,gBAAgB;CAE/B,IAAI,cAAc,QAAQ,SAAS,OAAO,OAAO,GAC/C,OAAO;CAGT,MAAM,OAAwB;EAAE;EAAI,IAAI,KAAK,IAAI;EAAG;EAAK,SAAS,UAAU,OAAO;CAAE;CAGrF,MAAM,eAFa,OAAO,OAAO,OAAO,YAAY,CAAC,CAAC,QAAQ,gBAAgB,gBAAgB,KAAA,CAEhE,CAAC,CAAC,QAAQ,gBAAgB,UAAU,YAAY,MAAM,OAAO,SAAS,QAAQ,OAAO,CAAC;CAEpH,IAAI,aAAa,WAAW,GAAG;EAC7B,IAAI,MAAM;GAAE;GAAK;EAAG,GAAG,mDAAmD;EAE1E,OAAO;CACT;CAEA,KAAK,MAAM,eAAe,cAAc;EACtC,MAAM,UAAU,UAAU,OAAO,YAAY,IAAI;EAEjD,IAAI,YAAY,KAAA,GACd,OAAY,aAAa,SAAS,IAAI;CAE1C;CAEA,OAAO;AACT;AAEA,SAAS,UAAU,MAAc,OAA0C,SAAiC;CAC1G,MAAM,OAAO,QAAQ;CAErB,IAAI,SAAS,OAAO,OAAO;CAC3B,IAAI,OAAO,SAAS,YAAY,OAAO,KAAK,OAAO;CAEnD,OAAO,QAAQ,UAAU;AAC3B;AAEA,SAAS,UAAU,EAAE,WAA4D;CAC/E,OAAO;EACL,eAAe,QAAQ;EACvB,SAAS,QAAQ,QAAQ;EACzB,SAAS,SAAS,QAAQ,QAAQ,IAAI,IAAI,CAAC,EAAE;EAC7C,WAAW,QAAQ,QAAQ,QAAQ,IAAI,SAAS,KAAK,QAAQ,IAAI;CACnE;AACF;AAEA,eAAe,OAAO,aAAyB,SAAkB,MAAsC;CACrG,IAAI;EACF,MAAM,YAAY,KAAK,SAAS,IAAI;EACpC,IAAI,MAAM;GAAE,aAAa,YAAY;GAAM,KAAK,KAAK;GAAK,IAAI,KAAK;EAAG,GAAG,qBAAqB;CAChG,SAAS,OAAO;EACd,IAAI,MAAM;GAAE;GAAO,aAAa,YAAY;GAAM,KAAK,KAAK;GAAK,IAAI,KAAK;EAAG,GAAG,6BAA6B;CAC/G;AACF"}
@@ -0,0 +1,77 @@
1
+ /** the attribute of an element whose click tracks the event serialized into it */
2
+ declare const RELAY_TRACK_ATTRIBUTE = "data-track";
3
+ /** what the visitor allowed, by destination name */
4
+ type ConsentRecord = Readonly<Record<string, boolean>>;
5
+ /**
6
+ * The answer for one destination: `undefined` as long as the cmp has not read the stored choice yet
7
+ * — not "no", but "not known", so events wait in memory instead of being dropped.
8
+ */
9
+ type ConsentAnswer = boolean | undefined;
10
+ /**
11
+ * Where the bus asks whether the visitor allowed a destination: a cmp adapter (`ccm19()`), or three
12
+ * members of your own. It covers exactly the registered names — `names` and `of` make the type
13
+ * invariant in them — so a destination without an answer is a type error.
14
+ */
15
+ type ConsentSource<N extends string = string> = {
16
+ /** the registered destination names */
17
+ names: readonly N[];
18
+ /** whether the visitor allowed the destination */
19
+ of: (name: N) => ConsentAnswer;
20
+ /** destinations needing no consent: taken before the cmp has answered, not in the record */
21
+ free?: readonly N[] | undefined;
22
+ /** called once with the re-check: hook it to the cmp's change event, so a choice on this page delivers at once */
23
+ watch?: ((notify: () => void) => void) | undefined;
24
+ };
25
+ /**
26
+ * The destinations an event can carry a payload for, by name: the package's destination entries and a
27
+ * site's own add theirs through module augmentation of `@astroscope/relay`, so every event
28
+ * definition is checked against the payload the destination takes. `id`, `key` and `event` are
29
+ * reserved names.
30
+ */
31
+ interface RelayDestinations {}
32
+ type DestinationName = keyof RelayDestinations & string;
33
+ /** the parameters of an event as a destination's builder takes them */
34
+ type RelayParams = Record<string, unknown>;
35
+ /** an event on the bus: the payload for each destination it is meant for, built by the destination's own builders */
36
+ type RelayEvent = { [N in DestinationName]?: RelayDestinations[N] | undefined; };
37
+ /**
38
+ * An event a site defined: the definition's key, optionally the id it fires under (an order
39
+ * number, so every destination counts a conversion once, on both sides), and the payloads it built.
40
+ */
41
+ type DomainEvent<E extends RelayEvent = RelayEvent> = {
42
+ key: string;
43
+ id?: string | undefined;
44
+ event: E;
45
+ };
46
+ /** what a definition returns: the payloads, and the id where the site's data has one */
47
+ type DefinedPayloads = RelayEvent & {
48
+ id?: string | undefined;
49
+ };
50
+ /** what `track()`, `<Track>` and `data-track` take: a defined event or a plain one */
51
+ type TrackInput = RelayEvent | DomainEvent;
52
+ /** a defined event: builds the event from the site's own arguments and carries its key */
53
+ type EventDefinition<Args extends unknown[] = unknown[], E extends RelayEvent = RelayEvent, K extends string = string> = ((...args: Args) => DomainEvent<E>) & {
54
+ readonly key: K;
55
+ };
56
+ /**
57
+ * One of the site's events: a function that turns the site's own objects into the payloads of the
58
+ * destinations the event is meant for, under the key that identifies it in a `data-track`
59
+ * attribute and in logs. One export per event, so an island bundles only the events
60
+ * it tracks and an event only an action imports never reaches the browser; the key is the export's
61
+ * name.
62
+ */
63
+ declare function defineEvent<const K extends string, Args extends unknown[], E extends DefinedPayloads>(key: K, create: (...args: Args) => E): EventDefinition<Args, Omit<E, 'id'>, K>;
64
+ /** the value of the `data-track` attribute of an element whose click tracks the event */
65
+ declare function trackAttribute(input: TrackInput): string;
66
+ /** what a destination knows about an event besides its payload */
67
+ type EventMeta = {
68
+ id: string;
69
+ /** epoch milliseconds of the track call */
70
+ at: number;
71
+ /** the key of the definition the event came from, `undefined` for a plain event */
72
+ key?: string | undefined;
73
+ };
74
+ /** `track()`, the same in the browser and on the server: puts the event on the bus, returns its id */
75
+ type TrackFunction = (input: TrackInput) => string;
76
+ export { DomainEvent as a, RELAY_TRACK_ATTRIBUTE as c, RelayParams as d, TrackFunction as f, trackAttribute as h, DestinationName as i, RelayDestinations as l, defineEvent as m, ConsentRecord as n, EventDefinition as o, TrackInput as p, ConsentSource as r, EventMeta as s, ConsentAnswer as t, RelayEvent as u };
77
+ //# sourceMappingURL=shared-CSwqcxPI.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared-CSwqcxPI.d.ts","names":[],"sources":["../src/shared.ts"],"mappings":";cASa;;KAGD,gBAAgB,SAAS;;;;;KA6BzB;;;;;;KAOA,cAAc;;EAExB,gBAAgB;;EAEhB,KAAK,MAAM,MAAM;;EAEjB,gBAAgB;;EAEhB,UAAU;;;;;;;;UAUK;KAEL,wBAAwB;;KAGxB,cAAc;;KAGd,gBAAgB,KAAK,mBAAmB,kBAAkB;;;;;KAM1D,YAAY,UAAU,aAAa;EAC7C;EACA;EACA,OAAO;;;KAIG,kBAAkB;EAAe;;;KAGjC,aAAa,aAAa;;KAG1B,gBACV,oCACA,UAAU,aAAa,YACvB,kCACO,MAAM,SAAS,YAAY;WACzB,KAAK;;;;;;;;;iBAUA,kBAAkB,kBAAkB,wBAAwB,UAAU,iBACpF,KAAK,GACL,YAAY,MAAM,SAAS,IAC1B,gBAAgB,MAAM,KAAK,UAAU;;iBAmCxB,eAAe,OAAO;;KAU1B;EACV;;EAEA;;EAEA;;;KAIU,iBAAiB,OAAO"}