@shware/analytics 9.0.1 → 10.0.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.
Files changed (86) hide show
  1. package/README.md +34 -0
  2. package/dist/click-id/index.cjs +32 -2
  3. package/dist/click-id/index.cjs.map +1 -1
  4. package/dist/click-id/index.d.cts +22 -9
  5. package/dist/click-id/index.d.cts.map +1 -1
  6. package/dist/click-id/index.d.mts +22 -9
  7. package/dist/click-id/index.d.mts.map +1 -1
  8. package/dist/click-id/index.mjs +28 -3
  9. package/dist/click-id/index.mjs.map +1 -1
  10. package/dist/server/google-data-manager.cjs +3 -3
  11. package/dist/server/google-data-manager.cjs.map +1 -1
  12. package/dist/server/google-data-manager.d.cts +15 -2
  13. package/dist/server/google-data-manager.d.cts.map +1 -1
  14. package/dist/server/google-data-manager.d.mts +15 -2
  15. package/dist/server/google-data-manager.d.mts.map +1 -1
  16. package/dist/server/google-data-manager.mjs +3 -3
  17. package/dist/server/google-data-manager.mjs.map +1 -1
  18. package/dist/server/index.d.cts +2 -2
  19. package/dist/server/index.d.mts +2 -2
  20. package/dist/server/linkedin-conversions-api.cjs +6 -1
  21. package/dist/server/linkedin-conversions-api.cjs.map +1 -1
  22. package/dist/server/linkedin-conversions-api.d.cts.map +1 -1
  23. package/dist/server/linkedin-conversions-api.d.mts.map +1 -1
  24. package/dist/server/linkedin-conversions-api.mjs +6 -1
  25. package/dist/server/linkedin-conversions-api.mjs.map +1 -1
  26. package/dist/server/meta-capi.cjs +7 -1
  27. package/dist/server/meta-capi.cjs.map +1 -1
  28. package/dist/server/meta-capi.d.cts.map +1 -1
  29. package/dist/server/meta-capi.d.mts.map +1 -1
  30. package/dist/server/meta-capi.mjs +7 -1
  31. package/dist/server/meta-capi.mjs.map +1 -1
  32. package/dist/server/meta-conversions-api.cjs +3 -1
  33. package/dist/server/meta-conversions-api.cjs.map +1 -1
  34. package/dist/server/meta-conversions-api.d.cts.map +1 -1
  35. package/dist/server/meta-conversions-api.d.mts.map +1 -1
  36. package/dist/server/meta-conversions-api.mjs +3 -1
  37. package/dist/server/meta-conversions-api.mjs.map +1 -1
  38. package/dist/server/microsoft-conversions-api.cjs +3 -1
  39. package/dist/server/microsoft-conversions-api.cjs.map +1 -1
  40. package/dist/server/microsoft-conversions-api.d.cts.map +1 -1
  41. package/dist/server/microsoft-conversions-api.d.mts.map +1 -1
  42. package/dist/server/microsoft-conversions-api.mjs +3 -1
  43. package/dist/server/microsoft-conversions-api.mjs.map +1 -1
  44. package/dist/server/openai-conversions-api.cjs +13 -12
  45. package/dist/server/openai-conversions-api.cjs.map +1 -1
  46. package/dist/server/openai-conversions-api.d.cts.map +1 -1
  47. package/dist/server/openai-conversions-api.d.mts.map +1 -1
  48. package/dist/server/openai-conversions-api.mjs +14 -13
  49. package/dist/server/openai-conversions-api.mjs.map +1 -1
  50. package/dist/server/reddit-conversions-api.cjs +6 -2
  51. package/dist/server/reddit-conversions-api.cjs.map +1 -1
  52. package/dist/server/reddit-conversions-api.d.cts +10 -1
  53. package/dist/server/reddit-conversions-api.d.cts.map +1 -1
  54. package/dist/server/reddit-conversions-api.d.mts +10 -1
  55. package/dist/server/reddit-conversions-api.d.mts.map +1 -1
  56. package/dist/server/reddit-conversions-api.mjs +6 -2
  57. package/dist/server/reddit-conversions-api.mjs.map +1 -1
  58. package/dist/tanstack/index.cjs +0 -1
  59. package/dist/tanstack/index.d.cts +2 -2
  60. package/dist/tanstack/index.d.mts +2 -2
  61. package/dist/tanstack/index.mjs +2 -2
  62. package/dist/tanstack/middleware.cjs +9 -8
  63. package/dist/tanstack/middleware.cjs.map +1 -1
  64. package/dist/tanstack/middleware.d.cts +21 -14
  65. package/dist/tanstack/middleware.d.cts.map +1 -1
  66. package/dist/tanstack/middleware.d.mts +21 -14
  67. package/dist/tanstack/middleware.d.mts.map +1 -1
  68. package/dist/tanstack/middleware.mjs +10 -8
  69. package/dist/tanstack/middleware.mjs.map +1 -1
  70. package/dist/third-parties/openai-pixel.cjs +29 -13
  71. package/dist/third-parties/openai-pixel.cjs.map +1 -1
  72. package/dist/third-parties/openai-pixel.d.cts +4 -3
  73. package/dist/third-parties/openai-pixel.d.cts.map +1 -1
  74. package/dist/third-parties/openai-pixel.d.mts +4 -3
  75. package/dist/third-parties/openai-pixel.d.mts.map +1 -1
  76. package/dist/third-parties/openai-pixel.mjs +30 -14
  77. package/dist/third-parties/openai-pixel.mjs.map +1 -1
  78. package/dist/track/oaiq.cjs +57 -9
  79. package/dist/track/oaiq.cjs.map +1 -1
  80. package/dist/track/oaiq.d.cts +33 -7
  81. package/dist/track/oaiq.d.cts.map +1 -1
  82. package/dist/track/oaiq.d.mts +33 -7
  83. package/dist/track/oaiq.d.mts.map +1 -1
  84. package/dist/track/oaiq.mjs +55 -10
  85. package/dist/track/oaiq.mjs.map +1 -1
  86. package/package.json +1 -1
@@ -2,6 +2,7 @@ import { IGNORED_EVENTS } from "../third-parties/ignored-events.mjs";
2
2
  import { getFirst } from "../utils/field.mjs";
3
3
  import { resolveActionSource } from "./action-source.mjs";
4
4
  import { redditClickId, redditUuid } from "./click-ids.mjs";
5
+ import { pageLocation } from "./page-location.mjs";
5
6
  import { mapRDTEvent, mapServerStandardEvent } from "../track/rdt.mjs";
6
7
  import { fetch } from "@shware/utils";
7
8
  //#region src/server/reddit-conversions-api.ts
@@ -12,7 +13,8 @@ function getServerEvent(event, data, actionSource) {
12
13
  return {
13
14
  click_id: redditClickId(tags),
14
15
  event_at: new Date(created_at).getTime(),
15
- action_source: source === "web" ? "WEBSITE" : source === "app" ? "APP" : "UNKNOWN",
16
+ action_source: source === "web" ? "WEBSITE" : source === "app" ? "APP" : "OTHER",
17
+ event_source_url: source === "web" ? pageLocation(tags) : void 0,
16
18
  type: {
17
19
  tracking_type: type === "Custom" ? "CUSTOM" : mapServerStandardEvent(type),
18
20
  custom_event_name: type === "Custom" ? params.customEventName : void 0
@@ -40,10 +42,12 @@ function getServerEvent(event, data, actionSource) {
40
42
  }
41
43
  };
42
44
  }
45
+ /** `event_at` "can't be older than seven days"; a minute's margin for the trip. */
46
+ const MAX_EVENT_AGE_MS = 60474e4;
43
47
  async function sendEvents(accessToken, pixelId, events, data = {}, testId, actionSource) {
44
48
  const dto = { data: {
45
49
  test_id: testId,
46
- events: events.filter((event) => !IGNORED_EVENTS.includes(event.name)).map((event) => getServerEvent(event, data, actionSource))
50
+ events: events.filter((event) => !IGNORED_EVENTS.includes(event.name)).filter((event) => Date.now() - Date.parse(event.created_at) <= MAX_EVENT_AGE_MS).map((event) => getServerEvent(event, data, actionSource))
47
51
  } };
48
52
  if (dto.data.events.length === 0) return;
49
53
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"reddit-conversions-api.mjs","names":[],"sources":["../../src/server/reddit-conversions-api.ts"],"sourcesContent":["import { fetch } from '@shware/utils';\nimport { IGNORED_EVENTS } from '../third-parties/ignored-events';\nimport { type ServerStandardEvent, mapRDTEvent, mapServerStandardEvent } from '../track/rdt';\nimport type { TrackEvent, UserProvidedData } from '../track/types';\nimport { getFirst } from '../utils/field';\nimport { type EventActionSource, resolveActionSource } from './action-source';\nimport { redditClickId, redditUuid } from './click-ids';\n\n/**\n * https://ads-api.reddit.com/docs/v3/operations/Post%20Conversion%20Events\n * https://business.reddithelp.com/s/article/map-a-catalog-to-a-signal-source\n */\nexport interface RedditEvent {\n /** Match keys: Share user identifiers to match conversions to a Reddit ad engagement. */\n click_id?: string;\n\n /** Unix epoch timestamp in milliseconds, event_at can't be older than seven days. */\n event_at: number;\n\n action_source: 'WEBSITE' | 'APP' | (string & {});\n\n type: {\n tracking_type: ServerStandardEvent | 'CUSTOM';\n custom_event_name?: string;\n };\n\n /**\n * Event metadata\n * Share as much additional information about your conversion event as you'd like. If you're\n * using the Conversions API with the pixel, conversion_id is required for deduplication.\n */\n metadata?: {\n conversion_id?: string;\n currency?: string; // ISO 4217 3-letter currency code\n item_count?: number;\n value?: number;\n products?: { id: string; name?: string; category?: string }[];\n };\n\n user?: {\n email?: string;\n external_id?: string;\n ip_address?: string;\n phone_number?: string;\n user_agent?: string;\n\n /** The Identifier for Advertisers (IDFA) of the user's Apple device. */\n idfa?: string;\n\n /** The Android Advertising ID (AAID) of the user's Android device. */\n aaid?: string;\n /**\n * The value from the first-party Pixel _rdt_uuid cookie on your domain. Note that it is in\n * the {timestamp}.{uuid} format. You may use the full value or just the UUID portion.\n * Example: 1684189007728.7c73f2ae-a433-4d7b-9838-f467da98f48e\n */\n uuid?: string;\n\n screen_dimensions?: { width: number; height: number };\n\n /**\n * A structure of data processing options to specify the processing type for the event\n * https://business.reddithelp.com/s/article/Limited-Data-Use\n */\n data_processing_options?: {\n country: string;\n region: string;\n modes: string[] | ['LDU'];\n };\n };\n}\n\nexport interface CreateRedditEventDTO {\n data: { test_id?: string; events: RedditEvent[] };\n}\n\nexport function getServerEvent(\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n event: TrackEvent<any>,\n data: UserProvidedData,\n actionSource?: EventActionSource\n): RedditEvent {\n const { id, name, properties, tags, platform, created_at } = event;\n const [type, params] = mapRDTEvent(name, properties, id);\n // Reddit documents WEBSITE and APP only, so an offline conversion shares the fallback with an\n // undeterminable platform rather than inventing an enum value the API may reject.\n const source = resolveActionSource(platform, actionSource);\n\n return {\n click_id: redditClickId(tags),\n event_at: new Date(created_at).getTime(),\n action_source: source === 'web' ? 'WEBSITE' : source === 'app' ? 'APP' : 'UNKNOWN',\n type: {\n tracking_type: type === 'Custom' ? 'CUSTOM' : mapServerStandardEvent(type),\n custom_event_name: type === 'Custom' ? params.customEventName : undefined,\n },\n metadata: {\n conversion_id: id,\n currency:\n 'currency' in params && typeof params.currency === 'string'\n ? params.currency.toUpperCase()\n : undefined,\n item_count:\n 'itemCount' in params && typeof params.itemCount === 'number'\n ? params.itemCount\n : undefined,\n value: 'value' in params && typeof params.value === 'number' ? params.value : undefined,\n products:\n 'products' in params && Array.isArray(params.products) && params.products.length > 0\n ? params.products\n : undefined,\n },\n user: {\n email: getFirst(data.email),\n external_id: data.user_id,\n ip_address: data.ip_address,\n phone_number: getFirst(data.phone_number),\n user_agent: data.user_agent,\n idfa: platform === 'ios' ? tags.advertising_id : undefined,\n aaid: platform === 'android' ? tags.advertising_id : undefined,\n uuid: redditUuid(tags),\n screen_dimensions:\n tags.screen_width && tags.screen_height\n ? { width: tags.screen_width, height: tags.screen_height }\n : undefined,\n },\n };\n}\n\nexport async function sendEvents(\n accessToken: string,\n pixelId: string,\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n events: TrackEvent<any>[],\n data: UserProvidedData = {},\n testId?: string,\n actionSource?: EventActionSource\n) {\n const dto: CreateRedditEventDTO = {\n data: {\n test_id: testId,\n events: events\n .filter((event) => !IGNORED_EVENTS.includes(event.name))\n .map((event) => getServerEvent(event, data, actionSource)),\n },\n };\n\n if (dto.data.events.length === 0) return;\n\n try {\n const response = await fetch(\n `https://ads-api.reddit.com/api/v3/pixels/${pixelId}/conversion_events`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n Authorization: `Bearer ${accessToken}`,\n },\n body: JSON.stringify(dto),\n }\n );\n if (response.ok) return;\n const { status } = response;\n const message = await response.text();\n console.error(`Failed to send Reddit conversion, status: ${status}, body: ${message}`);\n } catch (error) {\n console.error('Failed to send Reddit conversion, network error:', error);\n }\n}\n"],"mappings":";;;;;;;AA4EA,SAAgB,eAEd,OACA,MACA,cACa;CACb,MAAM,EAAE,IAAI,MAAM,YAAY,MAAM,UAAU,eAAe;CAC7D,MAAM,CAAC,MAAM,UAAU,YAAY,MAAM,YAAY,EAAE;CAGvD,MAAM,SAAS,oBAAoB,UAAU,YAAY;CAEzD,OAAO;EACL,UAAU,cAAc,IAAI;EAC5B,UAAU,IAAI,KAAK,UAAU,CAAC,CAAC,QAAQ;EACvC,eAAe,WAAW,QAAQ,YAAY,WAAW,QAAQ,QAAQ;EACzE,MAAM;GACJ,eAAe,SAAS,WAAW,WAAW,uBAAuB,IAAI;GACzE,mBAAmB,SAAS,WAAW,OAAO,kBAAkB,KAAA;EAClE;EACA,UAAU;GACR,eAAe;GACf,UACE,cAAc,UAAU,OAAO,OAAO,aAAa,WAC/C,OAAO,SAAS,YAAY,IAC5B,KAAA;GACN,YACE,eAAe,UAAU,OAAO,OAAO,cAAc,WACjD,OAAO,YACP,KAAA;GACN,OAAO,WAAW,UAAU,OAAO,OAAO,UAAU,WAAW,OAAO,QAAQ,KAAA;GAC9E,UACE,cAAc,UAAU,MAAM,QAAQ,OAAO,QAAQ,KAAK,OAAO,SAAS,SAAS,IAC/E,OAAO,WACP,KAAA;EACR;EACA,MAAM;GACJ,OAAO,SAAS,KAAK,KAAK;GAC1B,aAAa,KAAK;GAClB,YAAY,KAAK;GACjB,cAAc,SAAS,KAAK,YAAY;GACxC,YAAY,KAAK;GACjB,MAAM,aAAa,QAAQ,KAAK,iBAAiB,KAAA;GACjD,MAAM,aAAa,YAAY,KAAK,iBAAiB,KAAA;GACrD,MAAM,WAAW,IAAI;GACrB,mBACE,KAAK,gBAAgB,KAAK,gBACtB;IAAE,OAAO,KAAK;IAAc,QAAQ,KAAK;GAAc,IACvD,KAAA;EACR;CACF;AACF;AAEA,eAAsB,WACpB,aACA,SAEA,QACA,OAAyB,CAAC,GAC1B,QACA,cACA;CACA,MAAM,MAA4B,EAChC,MAAM;EACJ,SAAS;EACT,QAAQ,OACL,QAAQ,UAAU,CAAC,eAAe,SAAS,MAAM,IAAI,CAAC,CAAC,CACvD,KAAK,UAAU,eAAe,OAAO,MAAM,YAAY,CAAC;CAC7D,EACF;CAEA,IAAI,IAAI,KAAK,OAAO,WAAW,GAAG;CAElC,IAAI;EACF,MAAM,WAAW,MAAM,MACrB,4CAA4C,QAAQ,qBACpD;GACE,QAAQ;GACR,SAAS;IACP,gBAAgB;IAChB,QAAQ;IACR,eAAe,UAAU;GAC3B;GACA,MAAM,KAAK,UAAU,GAAG;EAC1B,CACF;EACA,IAAI,SAAS,IAAI;EACjB,MAAM,EAAE,WAAW;EACnB,MAAM,UAAU,MAAM,SAAS,KAAK;EACpC,QAAQ,MAAM,6CAA6C,OAAO,UAAU,SAAS;CACvF,SAAS,OAAO;EACd,QAAQ,MAAM,oDAAoD,KAAK;CACzE;AACF"}
1
+ {"version":3,"file":"reddit-conversions-api.mjs","names":[],"sources":["../../src/server/reddit-conversions-api.ts"],"sourcesContent":["import { fetch } from '@shware/utils';\nimport { IGNORED_EVENTS } from '../third-parties/ignored-events';\nimport { type ServerStandardEvent, mapRDTEvent, mapServerStandardEvent } from '../track/rdt';\nimport type { TrackEvent, UserProvidedData } from '../track/types';\nimport { getFirst } from '../utils/field';\nimport { type EventActionSource, resolveActionSource } from './action-source';\nimport { redditClickId, redditUuid } from './click-ids';\nimport { pageLocation } from './page-location';\n\n/**\n * https://ads-api.reddit.com/docs/v3/operations/Post%20Conversion%20Events\n * https://business.reddithelp.com/s/article/map-a-catalog-to-a-signal-source\n */\nexport interface RedditEvent {\n /** Match keys: Share user identifiers to match conversions to a Reddit ad engagement. */\n click_id?: string;\n\n /** Unix epoch timestamp in milliseconds, event_at can't be older than seven days. */\n event_at: number;\n\n /**\n * Where the conversion happened. Pixel events are deduplicated only against `WEBSITE` ones.\n * https://business.reddithelp.com/s/article/Conversions-API#measure-impact-across-channels\n */\n action_source: 'WEBSITE' | 'APP' | 'PHYSICAL_STORE' | 'OTHER';\n\n /**\n * The page the event happened on, for `WEBSITE` events: Reddit reads the domain from it, and the\n * click id when `click_id` is missing.\n */\n event_source_url?: string;\n\n type: {\n tracking_type: ServerStandardEvent | 'CUSTOM';\n custom_event_name?: string;\n };\n\n /**\n * Event metadata\n * Share as much additional information about your conversion event as you'd like. If you're\n * using the Conversions API with the pixel, conversion_id is required for deduplication.\n */\n metadata?: {\n conversion_id?: string;\n currency?: string; // ISO 4217 3-letter currency code\n item_count?: number;\n value?: number;\n products?: { id: string; name?: string; category?: string }[];\n };\n\n user?: {\n email?: string;\n external_id?: string;\n ip_address?: string;\n phone_number?: string;\n user_agent?: string;\n\n /** The Identifier for Advertisers (IDFA) of the user's Apple device. */\n idfa?: string;\n\n /** The Android Advertising ID (AAID) of the user's Android device. */\n aaid?: string;\n /**\n * The value from the first-party Pixel _rdt_uuid cookie on your domain. Note that it is in\n * the {timestamp}.{uuid} format. You may use the full value or just the UUID portion.\n * Example: 1684189007728.7c73f2ae-a433-4d7b-9838-f467da98f48e\n */\n uuid?: string;\n\n screen_dimensions?: { width: number; height: number };\n\n /**\n * A structure of data processing options to specify the processing type for the event\n * https://business.reddithelp.com/s/article/Limited-Data-Use\n */\n data_processing_options?: {\n country: string;\n region: string;\n modes: string[] | ['LDU'];\n };\n };\n}\n\nexport interface CreateRedditEventDTO {\n data: { test_id?: string; events: RedditEvent[] };\n}\n\nexport function getServerEvent(\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n event: TrackEvent<any>,\n data: UserProvidedData,\n actionSource?: EventActionSource\n): RedditEvent {\n const { id, name, properties, tags, platform, created_at } = event;\n const [type, params] = mapRDTEvent(name, properties, id);\n // An offline conversion — in store, from a CRM, over the phone — is `OTHER` rather than\n // `PHYSICAL_STORE`, which only one of those is; so is an undeterminable platform.\n const source = resolveActionSource(platform, actionSource);\n\n return {\n click_id: redditClickId(tags),\n event_at: new Date(created_at).getTime(),\n action_source: source === 'web' ? 'WEBSITE' : source === 'app' ? 'APP' : 'OTHER',\n event_source_url: source === 'web' ? pageLocation(tags) : undefined,\n type: {\n tracking_type: type === 'Custom' ? 'CUSTOM' : mapServerStandardEvent(type),\n custom_event_name: type === 'Custom' ? params.customEventName : undefined,\n },\n metadata: {\n conversion_id: id,\n currency:\n 'currency' in params && typeof params.currency === 'string'\n ? params.currency.toUpperCase()\n : undefined,\n item_count:\n 'itemCount' in params && typeof params.itemCount === 'number'\n ? params.itemCount\n : undefined,\n value: 'value' in params && typeof params.value === 'number' ? params.value : undefined,\n products:\n 'products' in params && Array.isArray(params.products) && params.products.length > 0\n ? params.products\n : undefined,\n },\n user: {\n email: getFirst(data.email),\n external_id: data.user_id,\n ip_address: data.ip_address,\n phone_number: getFirst(data.phone_number),\n user_agent: data.user_agent,\n idfa: platform === 'ios' ? tags.advertising_id : undefined,\n aaid: platform === 'android' ? tags.advertising_id : undefined,\n uuid: redditUuid(tags),\n screen_dimensions:\n tags.screen_width && tags.screen_height\n ? { width: tags.screen_width, height: tags.screen_height }\n : undefined,\n },\n };\n}\n\n/** `event_at` \"can't be older than seven days\"; a minute's margin for the trip. */\nconst MAX_EVENT_AGE_MS = 7 * 24 * 60 * 60 * 1000 - 60 * 1000;\n\nexport async function sendEvents(\n accessToken: string,\n pixelId: string,\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n events: TrackEvent<any>[],\n data: UserProvidedData = {},\n testId?: string,\n actionSource?: EventActionSource\n) {\n const dto: CreateRedditEventDTO = {\n data: {\n test_id: testId,\n events: events\n .filter((event) => !IGNORED_EVENTS.includes(event.name))\n .filter((event) => Date.now() - Date.parse(event.created_at) <= MAX_EVENT_AGE_MS)\n .map((event) => getServerEvent(event, data, actionSource)),\n },\n };\n\n if (dto.data.events.length === 0) return;\n\n try {\n const response = await fetch(\n `https://ads-api.reddit.com/api/v3/pixels/${pixelId}/conversion_events`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n Authorization: `Bearer ${accessToken}`,\n },\n body: JSON.stringify(dto),\n }\n );\n if (response.ok) return;\n const { status } = response;\n const message = await response.text();\n console.error(`Failed to send Reddit conversion, status: ${status}, body: ${message}`);\n } catch (error) {\n console.error('Failed to send Reddit conversion, network error:', error);\n }\n}\n"],"mappings":";;;;;;;;AAuFA,SAAgB,eAEd,OACA,MACA,cACa;CACb,MAAM,EAAE,IAAI,MAAM,YAAY,MAAM,UAAU,eAAe;CAC7D,MAAM,CAAC,MAAM,UAAU,YAAY,MAAM,YAAY,EAAE;CAGvD,MAAM,SAAS,oBAAoB,UAAU,YAAY;CAEzD,OAAO;EACL,UAAU,cAAc,IAAI;EAC5B,UAAU,IAAI,KAAK,UAAU,CAAC,CAAC,QAAQ;EACvC,eAAe,WAAW,QAAQ,YAAY,WAAW,QAAQ,QAAQ;EACzE,kBAAkB,WAAW,QAAQ,aAAa,IAAI,IAAI,KAAA;EAC1D,MAAM;GACJ,eAAe,SAAS,WAAW,WAAW,uBAAuB,IAAI;GACzE,mBAAmB,SAAS,WAAW,OAAO,kBAAkB,KAAA;EAClE;EACA,UAAU;GACR,eAAe;GACf,UACE,cAAc,UAAU,OAAO,OAAO,aAAa,WAC/C,OAAO,SAAS,YAAY,IAC5B,KAAA;GACN,YACE,eAAe,UAAU,OAAO,OAAO,cAAc,WACjD,OAAO,YACP,KAAA;GACN,OAAO,WAAW,UAAU,OAAO,OAAO,UAAU,WAAW,OAAO,QAAQ,KAAA;GAC9E,UACE,cAAc,UAAU,MAAM,QAAQ,OAAO,QAAQ,KAAK,OAAO,SAAS,SAAS,IAC/E,OAAO,WACP,KAAA;EACR;EACA,MAAM;GACJ,OAAO,SAAS,KAAK,KAAK;GAC1B,aAAa,KAAK;GAClB,YAAY,KAAK;GACjB,cAAc,SAAS,KAAK,YAAY;GACxC,YAAY,KAAK;GACjB,MAAM,aAAa,QAAQ,KAAK,iBAAiB,KAAA;GACjD,MAAM,aAAa,YAAY,KAAK,iBAAiB,KAAA;GACrD,MAAM,WAAW,IAAI;GACrB,mBACE,KAAK,gBAAgB,KAAK,gBACtB;IAAE,OAAO,KAAK;IAAc,QAAQ,KAAK;GAAc,IACvD,KAAA;EACR;CACF;AACF;;AAGA,MAAM,mBAAmB;AAEzB,eAAsB,WACpB,aACA,SAEA,QACA,OAAyB,CAAC,GAC1B,QACA,cACA;CACA,MAAM,MAA4B,EAChC,MAAM;EACJ,SAAS;EACT,QAAQ,OACL,QAAQ,UAAU,CAAC,eAAe,SAAS,MAAM,IAAI,CAAC,CAAC,CACvD,QAAQ,UAAU,KAAK,IAAI,IAAI,KAAK,MAAM,MAAM,UAAU,KAAK,gBAAgB,CAAC,CAChF,KAAK,UAAU,eAAe,OAAO,MAAM,YAAY,CAAC;CAC7D,EACF;CAEA,IAAI,IAAI,KAAK,OAAO,WAAW,GAAG;CAElC,IAAI;EACF,MAAM,WAAW,MAAM,MACrB,4CAA4C,QAAQ,qBACpD;GACE,QAAQ;GACR,SAAS;IACP,gBAAgB;IAChB,QAAQ;IACR,eAAe,UAAU;GAC3B;GACA,MAAM,KAAK,UAAU,GAAG;EAC1B,CACF;EACA,IAAI,SAAS,IAAI;EACjB,MAAM,EAAE,WAAW;EACnB,MAAM,UAAU,MAAM,SAAS,KAAK;EACpC,QAAQ,MAAM,6CAA6C,OAAO,UAAU,SAAS;CACvF,SAAS,OAAO;EACd,QAAQ,MAAM,oDAAoD,KAAK;CACzE;AACF"}
@@ -2,5 +2,4 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_tanstack_analytics = require("./analytics.cjs");
3
3
  const require_tanstack_middleware = require("./middleware.cjs");
4
4
  exports.Analytics = require_tanstack_analytics.Analytics;
5
- exports.clickIdMiddleware = require_tanstack_middleware.clickIdMiddleware;
6
5
  exports.createClickIdMiddleware = require_tanstack_middleware.createClickIdMiddleware;
@@ -1,3 +1,3 @@
1
1
  import { Analytics } from "./analytics.cjs";
2
- import { ClickIdMiddlewareOptions, clickIdMiddleware, createClickIdMiddleware } from "./middleware.cjs";
3
- export { Analytics, type ClickIdMiddlewareOptions, clickIdMiddleware, createClickIdMiddleware };
2
+ import { ClickIdMiddlewareOptions, createClickIdMiddleware } from "./middleware.cjs";
3
+ export { Analytics, type ClickIdMiddlewareOptions, createClickIdMiddleware };
@@ -1,3 +1,3 @@
1
1
  import { Analytics } from "./analytics.mjs";
2
- import { ClickIdMiddlewareOptions, clickIdMiddleware, createClickIdMiddleware } from "./middleware.mjs";
3
- export { Analytics, type ClickIdMiddlewareOptions, clickIdMiddleware, createClickIdMiddleware };
2
+ import { ClickIdMiddlewareOptions, createClickIdMiddleware } from "./middleware.mjs";
3
+ export { Analytics, type ClickIdMiddlewareOptions, createClickIdMiddleware };
@@ -1,3 +1,3 @@
1
1
  import { Analytics } from "./analytics.mjs";
2
- import { clickIdMiddleware, createClickIdMiddleware } from "./middleware.mjs";
3
- export { Analytics, clickIdMiddleware, createClickIdMiddleware };
2
+ import { createClickIdMiddleware } from "./middleware.mjs";
3
+ export { Analytics, createClickIdMiddleware };
@@ -3,9 +3,9 @@ const require_click_id_index = require("../click-id/index.cjs");
3
3
  let _tanstack_react_start = require("@tanstack/react-start");
4
4
  //#region src/tanstack/middleware.ts
5
5
  /**
6
- * TanStack Start request middleware that persists ad click-id cookies (`_fbc`, `_gcl_aw`/`_gcl_gb`,
7
- * `_rdt_cid`, `_uetmsclkid`) on the
8
- * document response.
6
+ * TanStack Start request middleware that persists the ad platforms' first-party cookies on the
7
+ * document response: `_fbc`, `_fbp`, `_gcl_aw` / `_gcl_gb`, `_rdt_cid`, `_rdt_uuid`,
8
+ * `_uetmsclkid`, `__oppref` and `__obref`.
9
9
  *
10
10
  * Setting `_fbc` here — on the top document via an HTTP `Set-Cookie` header, before any client JS
11
11
  * runs — is what Meta officially recommends and the only reliable way to keep the cookie alive for
@@ -17,13 +17,17 @@ let _tanstack_react_start = require("@tanstack/react-start");
17
17
  * ```ts
18
18
  * // start.ts
19
19
  * import { createStart } from '@tanstack/react-start'
20
- * import { clickIdMiddleware } from '@shware/analytics/tanstack'
20
+ * import { createClickIdMiddleware } from '@shware/analytics/tanstack'
21
+ * const clickIdMiddleware = createClickIdMiddleware({
22
+ * domain: import.meta.env.DEV ? null : '.example.com',
23
+ * secure: !import.meta.env.DEV,
24
+ * })
21
25
  * export const startInstance = createStart(() => ({ requestMiddleware: [clickIdMiddleware] }))
22
26
  * ```
23
27
  *
24
28
  * reference: https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/fbp-and-fbc
25
29
  */
26
- function createClickIdMiddleware(options = {}) {
30
+ function createClickIdMiddleware(options) {
27
31
  const { cacheControl = "private, no-store" } = options;
28
32
  return (0, _tanstack_react_start.createMiddleware)({ type: "request" }).server(async ({ request, next, handlerType }) => {
29
33
  const result = await next();
@@ -44,10 +48,7 @@ function createClickIdMiddleware(options = {}) {
44
48
  return result;
45
49
  });
46
50
  }
47
- /** Ready-to-register middleware with default options. */
48
- const clickIdMiddleware = createClickIdMiddleware();
49
51
  //#endregion
50
- exports.clickIdMiddleware = clickIdMiddleware;
51
52
  exports.createClickIdMiddleware = createClickIdMiddleware;
52
53
 
53
54
  //# sourceMappingURL=middleware.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.cjs","names":["createMiddleware","resolveClickIdCookies","toSetCookieHeaders"],"sources":["../../src/tanstack/middleware.ts"],"sourcesContent":["import { createMiddleware } from '@tanstack/react-start';\nimport { resolveClickIdCookies, toSetCookieHeaders } from '../click-id/index';\n\nexport interface ClickIdMiddlewareOptions {\n /** `Domain` attribute for the cookies, e.g. `.shware.io`. Omit for a host-only cookie. */\n domain?: string;\n /** `Secure` attribute, default true. Set false only for local http testing. */\n secure?: boolean;\n /** subdomainIndex for a freshly built `_fbc` (com=0, example.com=1, www.example.com=2). Default 1. */\n subdomainIndex?: number;\n /**\n * Re-issue a still-valid `_fbc` on every request as an ITP self-heal (restores the long-lived\n * HTTP cookie if the Meta Pixel's `document.cookie` write re-capped it to 24h in Safari). On by\n * default. Note it attaches a per-user `Set-Cookie` — and thus `no-store` — to every page\n * response carrying an `_fbc`, defeating CDN caching of those pages; set false to strictly follow\n * Meta's conditional-write rule and keep them cacheable. See {@link resolveClickIdCookies}.\n */\n refresh?: boolean;\n /**\n * Override the `Cache-Control` of a response we attach cookies to (default `private, no-store`).\n * A per-user `Set-Cookie` must never end up on a shared-cache entry, or one visitor's `_fbc` would\n * be served to everyone. Only set this false if you guarantee these responses are never cached.\n */\n cacheControl?: string | false;\n /**\n * Consent gate. Return false to skip setting cookies for this request (e.g. before the visitor has\n * granted consent where required). Runs per request with the incoming `Request`.\n */\n shouldPersist?: (request: Request) => boolean;\n}\n\n/**\n * TanStack Start request middleware that persists ad click-id cookies (`_fbc`, `_gcl_aw`/`_gcl_gb`,\n * `_rdt_cid`, `_uetmsclkid`) on the\n * document response.\n *\n * Setting `_fbc` here — on the top document via an HTTP `Set-Cookie` header, before any client JS\n * runs — is what Meta officially recommends and the only reliable way to keep the cookie alive for\n * 90 days in Safari: ITP caps JavaScript-set cookies on a fbclid-decorated landing page to 24\n * hours, and a document response is never classified as CNAME/IP cloaking (it is the reference the\n * browser measures cloaking against).\n *\n * Register it as a global request middleware:\n * ```ts\n * // start.ts\n * import { createStart } from '@tanstack/react-start'\n * import { clickIdMiddleware } from '@shware/analytics/tanstack'\n * export const startInstance = createStart(() => ({ requestMiddleware: [clickIdMiddleware] }))\n * ```\n *\n * reference: https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/fbp-and-fbc\n */\nexport function createClickIdMiddleware(options: ClickIdMiddlewareOptions = {}) {\n const { cacheControl = 'private, no-store' } = options;\n\n return createMiddleware({ type: 'request' }).server(async ({ request, next, handlerType }) => {\n const result = await next();\n\n // Skip serverFn RPC responses. 'router' covers SSR document requests *and* custom server\n // routes (API endpoints) — those also get cookies when the URL carries a click id.\n if (handlerType !== 'router') return result;\n if (options.shouldPersist && !options.shouldPersist(request)) return result;\n\n const { cookies } = resolveClickIdCookies({\n url: request.url,\n cookieHeader: request.headers.get('cookie'),\n domain: options.domain,\n secure: options.secure,\n subdomainIndex: options.subdomainIndex,\n refresh: options.refresh ?? true,\n });\n\n if (cookies.length > 0) {\n for (const header of toSetCookieHeaders(cookies)) {\n result.response.headers.append('set-cookie', header);\n }\n if (cacheControl !== false) {\n result.response.headers.set('cache-control', cacheControl);\n }\n }\n\n return result;\n });\n}\n\n/** Ready-to-register middleware with default options. */\nexport const clickIdMiddleware = createClickIdMiddleware();\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAoDA,SAAgB,wBAAwB,UAAoC,CAAC,GAAG;CAC9E,MAAM,EAAE,eAAe,wBAAwB;CAE/C,QAAA,GAAOA,sBAAAA,iBAAAA,CAAiB,EAAE,MAAM,UAAU,CAAC,CAAC,CAAC,OAAO,OAAO,EAAE,SAAS,MAAM,kBAAkB;EAC5F,MAAM,SAAS,MAAM,KAAK;EAI1B,IAAI,gBAAgB,UAAU,OAAO;EACrC,IAAI,QAAQ,iBAAiB,CAAC,QAAQ,cAAc,OAAO,GAAG,OAAO;EAErE,MAAM,EAAE,YAAYC,uBAAAA,sBAAsB;GACxC,KAAK,QAAQ;GACb,cAAc,QAAQ,QAAQ,IAAI,QAAQ;GAC1C,QAAQ,QAAQ;GAChB,QAAQ,QAAQ;GAChB,gBAAgB,QAAQ;GACxB,SAAS,QAAQ,WAAW;EAC9B,CAAC;EAED,IAAI,QAAQ,SAAS,GAAG;GACtB,KAAK,MAAM,UAAUC,uBAAAA,mBAAmB,OAAO,GAC7C,OAAO,SAAS,QAAQ,OAAO,cAAc,MAAM;GAErD,IAAI,iBAAiB,OACnB,OAAO,SAAS,QAAQ,IAAI,iBAAiB,YAAY;EAE7D;EAEA,OAAO;CACT,CAAC;AACH;;AAGA,MAAa,oBAAoB,wBAAwB"}
1
+ {"version":3,"file":"middleware.cjs","names":["createMiddleware","resolveClickIdCookies","toSetCookieHeaders"],"sources":["../../src/tanstack/middleware.ts"],"sourcesContent":["import { createMiddleware } from '@tanstack/react-start';\nimport { resolveClickIdCookies, toSetCookieHeaders } from '../click-id/index';\n\nexport interface ClickIdMiddlewareOptions {\n /**\n * `Domain` attribute for the cookies: the site's registrable domain, e.g. `.shware.io`, where the\n * pixels write theirs. Required — set it in production; `null` (host-only) is for `localhost`.\n * See `ResolveClickIdCookiesInput.domain`.\n */\n domain: string | null;\n /** `Secure` attribute, default true. Set false only for local http testing. */\n secure?: boolean;\n /** subdomainIndex for a freshly built `_fbc` (com=0, example.com=1, www.example.com=2). Default 1. */\n subdomainIndex?: number;\n /**\n * Re-issue the stored click and browser ids on every request as an ITP self-heal: the pixels\n * rewrite them through `document.cookie`, which Safari caps at 7 days (24 hours on an\n * ad-decorated landing), and the re-issue restores the long-lived HTTP cookie. On by default.\n * Note it attaches a per-user `Set-Cookie` — and thus `no-store` — to every page response of a\n * visitor carrying one; set false to keep those pages CDN-cacheable. See\n * {@link resolveClickIdCookies}.\n */\n refresh?: boolean;\n /**\n * Override the `Cache-Control` of a response we attach cookies to (default `private, no-store`).\n * A per-user `Set-Cookie` must never end up on a shared-cache entry, or one visitor's `_fbc` would\n * be served to everyone. Only set this false if you guarantee these responses are never cached.\n */\n cacheControl?: string | false;\n /**\n * Consent gate. Return false to skip setting cookies for this request (e.g. before the visitor has\n * granted consent where required). Runs per request with the incoming `Request`.\n */\n shouldPersist?: (request: Request) => boolean;\n}\n\n/**\n * TanStack Start request middleware that persists the ad platforms' first-party cookies on the\n * document response: `_fbc`, `_fbp`, `_gcl_aw` / `_gcl_gb`, `_rdt_cid`, `_rdt_uuid`,\n * `_uetmsclkid`, `__oppref` and `__obref`.\n *\n * Setting `_fbc` here — on the top document via an HTTP `Set-Cookie` header, before any client JS\n * runs — is what Meta officially recommends and the only reliable way to keep the cookie alive for\n * 90 days in Safari: ITP caps JavaScript-set cookies on a fbclid-decorated landing page to 24\n * hours, and a document response is never classified as CNAME/IP cloaking (it is the reference the\n * browser measures cloaking against).\n *\n * Register it as a global request middleware:\n * ```ts\n * // start.ts\n * import { createStart } from '@tanstack/react-start'\n * import { createClickIdMiddleware } from '@shware/analytics/tanstack'\n * const clickIdMiddleware = createClickIdMiddleware({\n * domain: import.meta.env.DEV ? null : '.example.com',\n * secure: !import.meta.env.DEV,\n * })\n * export const startInstance = createStart(() => ({ requestMiddleware: [clickIdMiddleware] }))\n * ```\n *\n * reference: https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/fbp-and-fbc\n */\nexport function createClickIdMiddleware(options: ClickIdMiddlewareOptions) {\n const { cacheControl = 'private, no-store' } = options;\n\n return createMiddleware({ type: 'request' }).server(async ({ request, next, handlerType }) => {\n const result = await next();\n\n // Skip serverFn RPC responses. 'router' covers SSR document requests *and* custom server\n // routes (API endpoints) — those also get cookies when the URL carries a click id.\n if (handlerType !== 'router') return result;\n if (options.shouldPersist && !options.shouldPersist(request)) return result;\n\n const { cookies } = resolveClickIdCookies({\n url: request.url,\n cookieHeader: request.headers.get('cookie'),\n domain: options.domain,\n secure: options.secure,\n subdomainIndex: options.subdomainIndex,\n refresh: options.refresh ?? true,\n });\n\n if (cookies.length > 0) {\n for (const header of toSetCookieHeaders(cookies)) {\n result.response.headers.append('set-cookie', header);\n }\n if (cacheControl !== false) {\n result.response.headers.set('cache-control', cacheControl);\n }\n }\n\n return result;\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6DA,SAAgB,wBAAwB,SAAmC;CACzE,MAAM,EAAE,eAAe,wBAAwB;CAE/C,QAAA,GAAOA,sBAAAA,iBAAAA,CAAiB,EAAE,MAAM,UAAU,CAAC,CAAC,CAAC,OAAO,OAAO,EAAE,SAAS,MAAM,kBAAkB;EAC5F,MAAM,SAAS,MAAM,KAAK;EAI1B,IAAI,gBAAgB,UAAU,OAAO;EACrC,IAAI,QAAQ,iBAAiB,CAAC,QAAQ,cAAc,OAAO,GAAG,OAAO;EAErE,MAAM,EAAE,YAAYC,uBAAAA,sBAAsB;GACxC,KAAK,QAAQ;GACb,cAAc,QAAQ,QAAQ,IAAI,QAAQ;GAC1C,QAAQ,QAAQ;GAChB,QAAQ,QAAQ;GAChB,gBAAgB,QAAQ;GACxB,SAAS,QAAQ,WAAW;EAC9B,CAAC;EAED,IAAI,QAAQ,SAAS,GAAG;GACtB,KAAK,MAAM,UAAUC,uBAAAA,mBAAmB,OAAO,GAC7C,OAAO,SAAS,QAAQ,OAAO,cAAc,MAAM;GAErD,IAAI,iBAAiB,OACnB,OAAO,SAAS,QAAQ,IAAI,iBAAiB,YAAY;EAE7D;EAEA,OAAO;CACT,CAAC;AACH"}
@@ -1,17 +1,22 @@
1
1
  //#region src/tanstack/middleware.d.ts
2
2
  export interface ClickIdMiddlewareOptions {
3
- /** `Domain` attribute for the cookies, e.g. `.shware.io`. Omit for a host-only cookie. */
4
- domain?: string;
3
+ /**
4
+ * `Domain` attribute for the cookies: the site's registrable domain, e.g. `.shware.io`, where the
5
+ * pixels write theirs. Required — set it in production; `null` (host-only) is for `localhost`.
6
+ * See `ResolveClickIdCookiesInput.domain`.
7
+ */
8
+ domain: string | null;
5
9
  /** `Secure` attribute, default true. Set false only for local http testing. */
6
10
  secure?: boolean;
7
11
  /** subdomainIndex for a freshly built `_fbc` (com=0, example.com=1, www.example.com=2). Default 1. */
8
12
  subdomainIndex?: number;
9
13
  /**
10
- * Re-issue a still-valid `_fbc` on every request as an ITP self-heal (restores the long-lived
11
- * HTTP cookie if the Meta Pixel's `document.cookie` write re-capped it to 24h in Safari). On by
12
- * default. Note it attaches a per-user `Set-Cookie` — and thus `no-store` — to every page
13
- * response carrying an `_fbc`, defeating CDN caching of those pages; set false to strictly follow
14
- * Meta's conditional-write rule and keep them cacheable. See {@link resolveClickIdCookies}.
14
+ * Re-issue the stored click and browser ids on every request as an ITP self-heal: the pixels
15
+ * rewrite them through `document.cookie`, which Safari caps at 7 days (24 hours on an
16
+ * ad-decorated landing), and the re-issue restores the long-lived HTTP cookie. On by default.
17
+ * Note it attaches a per-user `Set-Cookie` — and thus `no-store` — to every page response of a
18
+ * visitor carrying one; set false to keep those pages CDN-cacheable. See
19
+ * {@link resolveClickIdCookies}.
15
20
  */
16
21
  refresh?: boolean;
17
22
  /**
@@ -27,9 +32,9 @@ export interface ClickIdMiddlewareOptions {
27
32
  shouldPersist?: (request: Request) => boolean;
28
33
  }
29
34
  /**
30
- * TanStack Start request middleware that persists ad click-id cookies (`_fbc`, `_gcl_aw`/`_gcl_gb`,
31
- * `_rdt_cid`, `_uetmsclkid`) on the
32
- * document response.
35
+ * TanStack Start request middleware that persists the ad platforms' first-party cookies on the
36
+ * document response: `_fbc`, `_fbp`, `_gcl_aw` / `_gcl_gb`, `_rdt_cid`, `_rdt_uuid`,
37
+ * `_uetmsclkid`, `__oppref` and `__obref`.
33
38
  *
34
39
  * Setting `_fbc` here — on the top document via an HTTP `Set-Cookie` header, before any client JS
35
40
  * runs — is what Meta officially recommends and the only reliable way to keep the cookie alive for
@@ -41,14 +46,16 @@ export interface ClickIdMiddlewareOptions {
41
46
  * ```ts
42
47
  * // start.ts
43
48
  * import { createStart } from '@tanstack/react-start'
44
- * import { clickIdMiddleware } from '@shware/analytics/tanstack'
49
+ * import { createClickIdMiddleware } from '@shware/analytics/tanstack'
50
+ * const clickIdMiddleware = createClickIdMiddleware({
51
+ * domain: import.meta.env.DEV ? null : '.example.com',
52
+ * secure: !import.meta.env.DEV,
53
+ * })
45
54
  * export const startInstance = createStart(() => ({ requestMiddleware: [clickIdMiddleware] }))
46
55
  * ```
47
56
  *
48
57
  * reference: https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/fbp-and-fbc
49
58
  */
50
- export declare function createClickIdMiddleware(options?: ClickIdMiddlewareOptions): import("@tanstack/react-start").RequestMiddlewareAfterServer<{}, undefined, undefined>;
51
- /** Ready-to-register middleware with default options. */
52
- export declare const clickIdMiddleware: import("@tanstack/react-start").RequestMiddlewareAfterServer<{}, undefined, undefined>;
59
+ export declare function createClickIdMiddleware(options: ClickIdMiddlewareOptions): import("@tanstack/react-start").RequestMiddlewareAfterServer<{}, undefined, undefined>;
53
60
  //#endregion
54
61
  //# sourceMappingURL=middleware.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.d.cts","names":[],"sources":["../../src/tanstack/middleware.ts"],"mappings":";iBAGiB;;EAEf;;EAEA;;EAEA;;;;;;;;EAQA;;;;;;EAMA;;;;;EAKA,iBAAiB,SAAS;;;;;;;;;;;;;;;;;;;;;;;wBAwBZ,wBAAwB,UAAS,2DAA6B;;qBAkCjE,mDAAiB"}
1
+ {"version":3,"file":"middleware.d.cts","names":[],"sources":["../../src/tanstack/middleware.ts"],"mappings":";iBAGiB;;;;;;EAMf;;EAEA;;EAEA;;;;;;;;;EASA;;;;;;EAMA;;;;;EAKA,iBAAiB,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;wBA4BZ,wBAAwB,SAAS,2DAAwB"}
@@ -1,17 +1,22 @@
1
1
  //#region src/tanstack/middleware.d.ts
2
2
  export interface ClickIdMiddlewareOptions {
3
- /** `Domain` attribute for the cookies, e.g. `.shware.io`. Omit for a host-only cookie. */
4
- domain?: string;
3
+ /**
4
+ * `Domain` attribute for the cookies: the site's registrable domain, e.g. `.shware.io`, where the
5
+ * pixels write theirs. Required — set it in production; `null` (host-only) is for `localhost`.
6
+ * See `ResolveClickIdCookiesInput.domain`.
7
+ */
8
+ domain: string | null;
5
9
  /** `Secure` attribute, default true. Set false only for local http testing. */
6
10
  secure?: boolean;
7
11
  /** subdomainIndex for a freshly built `_fbc` (com=0, example.com=1, www.example.com=2). Default 1. */
8
12
  subdomainIndex?: number;
9
13
  /**
10
- * Re-issue a still-valid `_fbc` on every request as an ITP self-heal (restores the long-lived
11
- * HTTP cookie if the Meta Pixel's `document.cookie` write re-capped it to 24h in Safari). On by
12
- * default. Note it attaches a per-user `Set-Cookie` — and thus `no-store` — to every page
13
- * response carrying an `_fbc`, defeating CDN caching of those pages; set false to strictly follow
14
- * Meta's conditional-write rule and keep them cacheable. See {@link resolveClickIdCookies}.
14
+ * Re-issue the stored click and browser ids on every request as an ITP self-heal: the pixels
15
+ * rewrite them through `document.cookie`, which Safari caps at 7 days (24 hours on an
16
+ * ad-decorated landing), and the re-issue restores the long-lived HTTP cookie. On by default.
17
+ * Note it attaches a per-user `Set-Cookie` — and thus `no-store` — to every page response of a
18
+ * visitor carrying one; set false to keep those pages CDN-cacheable. See
19
+ * {@link resolveClickIdCookies}.
15
20
  */
16
21
  refresh?: boolean;
17
22
  /**
@@ -27,9 +32,9 @@ export interface ClickIdMiddlewareOptions {
27
32
  shouldPersist?: (request: Request) => boolean;
28
33
  }
29
34
  /**
30
- * TanStack Start request middleware that persists ad click-id cookies (`_fbc`, `_gcl_aw`/`_gcl_gb`,
31
- * `_rdt_cid`, `_uetmsclkid`) on the
32
- * document response.
35
+ * TanStack Start request middleware that persists the ad platforms' first-party cookies on the
36
+ * document response: `_fbc`, `_fbp`, `_gcl_aw` / `_gcl_gb`, `_rdt_cid`, `_rdt_uuid`,
37
+ * `_uetmsclkid`, `__oppref` and `__obref`.
33
38
  *
34
39
  * Setting `_fbc` here — on the top document via an HTTP `Set-Cookie` header, before any client JS
35
40
  * runs — is what Meta officially recommends and the only reliable way to keep the cookie alive for
@@ -41,14 +46,16 @@ export interface ClickIdMiddlewareOptions {
41
46
  * ```ts
42
47
  * // start.ts
43
48
  * import { createStart } from '@tanstack/react-start'
44
- * import { clickIdMiddleware } from '@shware/analytics/tanstack'
49
+ * import { createClickIdMiddleware } from '@shware/analytics/tanstack'
50
+ * const clickIdMiddleware = createClickIdMiddleware({
51
+ * domain: import.meta.env.DEV ? null : '.example.com',
52
+ * secure: !import.meta.env.DEV,
53
+ * })
45
54
  * export const startInstance = createStart(() => ({ requestMiddleware: [clickIdMiddleware] }))
46
55
  * ```
47
56
  *
48
57
  * reference: https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/fbp-and-fbc
49
58
  */
50
- export declare function createClickIdMiddleware(options?: ClickIdMiddlewareOptions): import("@tanstack/react-start").RequestMiddlewareAfterServer<{}, undefined, undefined>;
51
- /** Ready-to-register middleware with default options. */
52
- export declare const clickIdMiddleware: import("@tanstack/react-start").RequestMiddlewareAfterServer<{}, undefined, undefined>;
59
+ export declare function createClickIdMiddleware(options: ClickIdMiddlewareOptions): import("@tanstack/react-start").RequestMiddlewareAfterServer<{}, undefined, undefined>;
53
60
  //#endregion
54
61
  //# sourceMappingURL=middleware.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.d.mts","names":[],"sources":["../../src/tanstack/middleware.ts"],"mappings":";iBAGiB;;EAEf;;EAEA;;EAEA;;;;;;;;EAQA;;;;;;EAMA;;;;;EAKA,iBAAiB,SAAS;;;;;;;;;;;;;;;;;;;;;;;wBAwBZ,wBAAwB,UAAS,2DAA6B;;qBAkCjE,mDAAiB"}
1
+ {"version":3,"file":"middleware.d.mts","names":[],"sources":["../../src/tanstack/middleware.ts"],"mappings":";iBAGiB;;;;;;EAMf;;EAEA;;EAEA;;;;;;;;;EASA;;;;;;EAMA;;;;;EAKA,iBAAiB,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;wBA4BZ,wBAAwB,SAAS,2DAAwB"}
@@ -2,9 +2,9 @@ import { resolveClickIdCookies, toSetCookieHeaders } from "../click-id/index.mjs
2
2
  import { createMiddleware } from "@tanstack/react-start";
3
3
  //#region src/tanstack/middleware.ts
4
4
  /**
5
- * TanStack Start request middleware that persists ad click-id cookies (`_fbc`, `_gcl_aw`/`_gcl_gb`,
6
- * `_rdt_cid`, `_uetmsclkid`) on the
7
- * document response.
5
+ * TanStack Start request middleware that persists the ad platforms' first-party cookies on the
6
+ * document response: `_fbc`, `_fbp`, `_gcl_aw` / `_gcl_gb`, `_rdt_cid`, `_rdt_uuid`,
7
+ * `_uetmsclkid`, `__oppref` and `__obref`.
8
8
  *
9
9
  * Setting `_fbc` here — on the top document via an HTTP `Set-Cookie` header, before any client JS
10
10
  * runs — is what Meta officially recommends and the only reliable way to keep the cookie alive for
@@ -16,13 +16,17 @@ import { createMiddleware } from "@tanstack/react-start";
16
16
  * ```ts
17
17
  * // start.ts
18
18
  * import { createStart } from '@tanstack/react-start'
19
- * import { clickIdMiddleware } from '@shware/analytics/tanstack'
19
+ * import { createClickIdMiddleware } from '@shware/analytics/tanstack'
20
+ * const clickIdMiddleware = createClickIdMiddleware({
21
+ * domain: import.meta.env.DEV ? null : '.example.com',
22
+ * secure: !import.meta.env.DEV,
23
+ * })
20
24
  * export const startInstance = createStart(() => ({ requestMiddleware: [clickIdMiddleware] }))
21
25
  * ```
22
26
  *
23
27
  * reference: https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/fbp-and-fbc
24
28
  */
25
- function createClickIdMiddleware(options = {}) {
29
+ function createClickIdMiddleware(options) {
26
30
  const { cacheControl = "private, no-store" } = options;
27
31
  return createMiddleware({ type: "request" }).server(async ({ request, next, handlerType }) => {
28
32
  const result = await next();
@@ -43,9 +47,7 @@ function createClickIdMiddleware(options = {}) {
43
47
  return result;
44
48
  });
45
49
  }
46
- /** Ready-to-register middleware with default options. */
47
- const clickIdMiddleware = createClickIdMiddleware();
48
50
  //#endregion
49
- export { clickIdMiddleware, createClickIdMiddleware };
51
+ export { createClickIdMiddleware };
50
52
 
51
53
  //# sourceMappingURL=middleware.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.mjs","names":[],"sources":["../../src/tanstack/middleware.ts"],"sourcesContent":["import { createMiddleware } from '@tanstack/react-start';\nimport { resolveClickIdCookies, toSetCookieHeaders } from '../click-id/index';\n\nexport interface ClickIdMiddlewareOptions {\n /** `Domain` attribute for the cookies, e.g. `.shware.io`. Omit for a host-only cookie. */\n domain?: string;\n /** `Secure` attribute, default true. Set false only for local http testing. */\n secure?: boolean;\n /** subdomainIndex for a freshly built `_fbc` (com=0, example.com=1, www.example.com=2). Default 1. */\n subdomainIndex?: number;\n /**\n * Re-issue a still-valid `_fbc` on every request as an ITP self-heal (restores the long-lived\n * HTTP cookie if the Meta Pixel's `document.cookie` write re-capped it to 24h in Safari). On by\n * default. Note it attaches a per-user `Set-Cookie` — and thus `no-store` — to every page\n * response carrying an `_fbc`, defeating CDN caching of those pages; set false to strictly follow\n * Meta's conditional-write rule and keep them cacheable. See {@link resolveClickIdCookies}.\n */\n refresh?: boolean;\n /**\n * Override the `Cache-Control` of a response we attach cookies to (default `private, no-store`).\n * A per-user `Set-Cookie` must never end up on a shared-cache entry, or one visitor's `_fbc` would\n * be served to everyone. Only set this false if you guarantee these responses are never cached.\n */\n cacheControl?: string | false;\n /**\n * Consent gate. Return false to skip setting cookies for this request (e.g. before the visitor has\n * granted consent where required). Runs per request with the incoming `Request`.\n */\n shouldPersist?: (request: Request) => boolean;\n}\n\n/**\n * TanStack Start request middleware that persists ad click-id cookies (`_fbc`, `_gcl_aw`/`_gcl_gb`,\n * `_rdt_cid`, `_uetmsclkid`) on the\n * document response.\n *\n * Setting `_fbc` here — on the top document via an HTTP `Set-Cookie` header, before any client JS\n * runs — is what Meta officially recommends and the only reliable way to keep the cookie alive for\n * 90 days in Safari: ITP caps JavaScript-set cookies on a fbclid-decorated landing page to 24\n * hours, and a document response is never classified as CNAME/IP cloaking (it is the reference the\n * browser measures cloaking against).\n *\n * Register it as a global request middleware:\n * ```ts\n * // start.ts\n * import { createStart } from '@tanstack/react-start'\n * import { clickIdMiddleware } from '@shware/analytics/tanstack'\n * export const startInstance = createStart(() => ({ requestMiddleware: [clickIdMiddleware] }))\n * ```\n *\n * reference: https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/fbp-and-fbc\n */\nexport function createClickIdMiddleware(options: ClickIdMiddlewareOptions = {}) {\n const { cacheControl = 'private, no-store' } = options;\n\n return createMiddleware({ type: 'request' }).server(async ({ request, next, handlerType }) => {\n const result = await next();\n\n // Skip serverFn RPC responses. 'router' covers SSR document requests *and* custom server\n // routes (API endpoints) — those also get cookies when the URL carries a click id.\n if (handlerType !== 'router') return result;\n if (options.shouldPersist && !options.shouldPersist(request)) return result;\n\n const { cookies } = resolveClickIdCookies({\n url: request.url,\n cookieHeader: request.headers.get('cookie'),\n domain: options.domain,\n secure: options.secure,\n subdomainIndex: options.subdomainIndex,\n refresh: options.refresh ?? true,\n });\n\n if (cookies.length > 0) {\n for (const header of toSetCookieHeaders(cookies)) {\n result.response.headers.append('set-cookie', header);\n }\n if (cacheControl !== false) {\n result.response.headers.set('cache-control', cacheControl);\n }\n }\n\n return result;\n });\n}\n\n/** Ready-to-register middleware with default options. */\nexport const clickIdMiddleware = createClickIdMiddleware();\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAoDA,SAAgB,wBAAwB,UAAoC,CAAC,GAAG;CAC9E,MAAM,EAAE,eAAe,wBAAwB;CAE/C,OAAO,iBAAiB,EAAE,MAAM,UAAU,CAAC,CAAC,CAAC,OAAO,OAAO,EAAE,SAAS,MAAM,kBAAkB;EAC5F,MAAM,SAAS,MAAM,KAAK;EAI1B,IAAI,gBAAgB,UAAU,OAAO;EACrC,IAAI,QAAQ,iBAAiB,CAAC,QAAQ,cAAc,OAAO,GAAG,OAAO;EAErE,MAAM,EAAE,YAAY,sBAAsB;GACxC,KAAK,QAAQ;GACb,cAAc,QAAQ,QAAQ,IAAI,QAAQ;GAC1C,QAAQ,QAAQ;GAChB,QAAQ,QAAQ;GAChB,gBAAgB,QAAQ;GACxB,SAAS,QAAQ,WAAW;EAC9B,CAAC;EAED,IAAI,QAAQ,SAAS,GAAG;GACtB,KAAK,MAAM,UAAU,mBAAmB,OAAO,GAC7C,OAAO,SAAS,QAAQ,OAAO,cAAc,MAAM;GAErD,IAAI,iBAAiB,OACnB,OAAO,SAAS,QAAQ,IAAI,iBAAiB,YAAY;EAE7D;EAEA,OAAO;CACT,CAAC;AACH;;AAGA,MAAa,oBAAoB,wBAAwB"}
1
+ {"version":3,"file":"middleware.mjs","names":[],"sources":["../../src/tanstack/middleware.ts"],"sourcesContent":["import { createMiddleware } from '@tanstack/react-start';\nimport { resolveClickIdCookies, toSetCookieHeaders } from '../click-id/index';\n\nexport interface ClickIdMiddlewareOptions {\n /**\n * `Domain` attribute for the cookies: the site's registrable domain, e.g. `.shware.io`, where the\n * pixels write theirs. Required — set it in production; `null` (host-only) is for `localhost`.\n * See `ResolveClickIdCookiesInput.domain`.\n */\n domain: string | null;\n /** `Secure` attribute, default true. Set false only for local http testing. */\n secure?: boolean;\n /** subdomainIndex for a freshly built `_fbc` (com=0, example.com=1, www.example.com=2). Default 1. */\n subdomainIndex?: number;\n /**\n * Re-issue the stored click and browser ids on every request as an ITP self-heal: the pixels\n * rewrite them through `document.cookie`, which Safari caps at 7 days (24 hours on an\n * ad-decorated landing), and the re-issue restores the long-lived HTTP cookie. On by default.\n * Note it attaches a per-user `Set-Cookie` — and thus `no-store` — to every page response of a\n * visitor carrying one; set false to keep those pages CDN-cacheable. See\n * {@link resolveClickIdCookies}.\n */\n refresh?: boolean;\n /**\n * Override the `Cache-Control` of a response we attach cookies to (default `private, no-store`).\n * A per-user `Set-Cookie` must never end up on a shared-cache entry, or one visitor's `_fbc` would\n * be served to everyone. Only set this false if you guarantee these responses are never cached.\n */\n cacheControl?: string | false;\n /**\n * Consent gate. Return false to skip setting cookies for this request (e.g. before the visitor has\n * granted consent where required). Runs per request with the incoming `Request`.\n */\n shouldPersist?: (request: Request) => boolean;\n}\n\n/**\n * TanStack Start request middleware that persists the ad platforms' first-party cookies on the\n * document response: `_fbc`, `_fbp`, `_gcl_aw` / `_gcl_gb`, `_rdt_cid`, `_rdt_uuid`,\n * `_uetmsclkid`, `__oppref` and `__obref`.\n *\n * Setting `_fbc` here — on the top document via an HTTP `Set-Cookie` header, before any client JS\n * runs — is what Meta officially recommends and the only reliable way to keep the cookie alive for\n * 90 days in Safari: ITP caps JavaScript-set cookies on a fbclid-decorated landing page to 24\n * hours, and a document response is never classified as CNAME/IP cloaking (it is the reference the\n * browser measures cloaking against).\n *\n * Register it as a global request middleware:\n * ```ts\n * // start.ts\n * import { createStart } from '@tanstack/react-start'\n * import { createClickIdMiddleware } from '@shware/analytics/tanstack'\n * const clickIdMiddleware = createClickIdMiddleware({\n * domain: import.meta.env.DEV ? null : '.example.com',\n * secure: !import.meta.env.DEV,\n * })\n * export const startInstance = createStart(() => ({ requestMiddleware: [clickIdMiddleware] }))\n * ```\n *\n * reference: https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/fbp-and-fbc\n */\nexport function createClickIdMiddleware(options: ClickIdMiddlewareOptions) {\n const { cacheControl = 'private, no-store' } = options;\n\n return createMiddleware({ type: 'request' }).server(async ({ request, next, handlerType }) => {\n const result = await next();\n\n // Skip serverFn RPC responses. 'router' covers SSR document requests *and* custom server\n // routes (API endpoints) — those also get cookies when the URL carries a click id.\n if (handlerType !== 'router') return result;\n if (options.shouldPersist && !options.shouldPersist(request)) return result;\n\n const { cookies } = resolveClickIdCookies({\n url: request.url,\n cookieHeader: request.headers.get('cookie'),\n domain: options.domain,\n secure: options.secure,\n subdomainIndex: options.subdomainIndex,\n refresh: options.refresh ?? true,\n });\n\n if (cookies.length > 0) {\n for (const header of toSetCookieHeaders(cookies)) {\n result.response.headers.append('set-cookie', header);\n }\n if (cacheControl !== false) {\n result.response.headers.set('cache-control', cacheControl);\n }\n }\n\n return result;\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6DA,SAAgB,wBAAwB,SAAmC;CACzE,MAAM,EAAE,eAAe,wBAAwB;CAE/C,OAAO,iBAAiB,EAAE,MAAM,UAAU,CAAC,CAAC,CAAC,OAAO,OAAO,EAAE,SAAS,MAAM,kBAAkB;EAC5F,MAAM,SAAS,MAAM,KAAK;EAI1B,IAAI,gBAAgB,UAAU,OAAO;EACrC,IAAI,QAAQ,iBAAiB,CAAC,QAAQ,cAAc,OAAO,GAAG,OAAO;EAErE,MAAM,EAAE,YAAY,sBAAsB;GACxC,KAAK,QAAQ;GACb,cAAc,QAAQ,QAAQ,IAAI,QAAQ;GAC1C,QAAQ,QAAQ;GAChB,QAAQ,QAAQ;GAChB,gBAAgB,QAAQ;GACxB,SAAS,QAAQ,WAAW;EAC9B,CAAC;EAED,IAAI,QAAQ,SAAS,GAAG;GACtB,KAAK,MAAM,UAAU,mBAAmB,OAAO,GAC7C,OAAO,SAAS,QAAQ,OAAO,cAAc,MAAM;GAErD,IAAI,iBAAiB,OACnB,OAAO,SAAS,QAAQ,IAAI,iBAAiB,YAAY;EAE7D;EAEA,OAAO;CACT,CAAC;AACH"}
@@ -21,16 +21,20 @@ function sendOpenAIEvent(name, properties, eventId) {
21
21
  if (window.location.host.includes("127.0.0.1")) return;
22
22
  if (window.location.host.includes("localhost")) return;
23
23
  const { type, data } = require_track_oaiq.mapOAIEvent(name, properties);
24
- if (type === "custom") window.oaiq("measure", "custom", clean(data), {
25
- event_id: eventId,
26
- custom_event_name: name
27
- });
28
- else window.oaiq("measure", type, clean(data), { event_id: eventId });
24
+ if (type === "custom") {
25
+ const custom_event_name = require_track_oaiq.oaiCustomEventName(name);
26
+ if (!custom_event_name) return;
27
+ window.oaiq("measure", "custom", clean(data), {
28
+ event_id: eventId,
29
+ custom_event_name
30
+ });
31
+ } else window.oaiq("measure", type, clean(data), { event_id: eventId });
29
32
  }
30
33
  /**
31
- * Re-initialize the pixel with hashed user identity for better conversion matching. Email and
32
- * external id are hashed client-side; geographic fields are sent raw. Hashing is asynchronous,
33
- * so the `init` call is deferred until the digests resolve.
34
+ * Re-initialize the pixel with hashed user identity for better conversion matching. Email, phone,
35
+ * external id and names are normalized as OpenAI documents and hashed client-side; geographic
36
+ * fields are sent raw. Hashing is asynchronous, so the `init` call is deferred until the digests
37
+ * resolve.
34
38
  */
35
39
  function setOpenAIUser(pixelId) {
36
40
  return ({ user_id, user_data }) => {
@@ -39,12 +43,14 @@ function setOpenAIUser(pixelId) {
39
43
  return;
40
44
  }
41
45
  const oaiq = window.oaiq;
42
- const email = require_utils_field.getFirst(user_data?.email)?.trim().toLowerCase();
46
+ const email = require_utils_field.getFirst(user_data?.email)?.trim().toLowerCase() || void 0;
47
+ const phone = require_utils_field.getFirst(user_data?.phone_number);
43
48
  const address = require_utils_field.getFirst(user_data?.address);
44
49
  const base = {
45
50
  country: address?.country?.trim().toUpperCase(),
46
- city: address?.city?.trim().toLowerCase(),
47
- zip_code: address?.postal_code
51
+ city: address?.city?.trim(),
52
+ region: address?.region?.trim(),
53
+ postal_code: address?.postal_code?.trim()
48
54
  };
49
55
  const init = (hashed) => {
50
56
  oaiq("init", {
@@ -55,9 +61,19 @@ function setOpenAIUser(pixelId) {
55
61
  })
56
62
  });
57
63
  };
58
- Promise.all([email ? require_utils_sha256.sha256(email) : void 0, user_id ? require_utils_sha256.sha256(user_id) : void 0]).then(([email_sha256, external_id_sha256]) => init({
64
+ const hash = (value) => value ? require_utils_sha256.sha256(value) : void 0;
65
+ Promise.all([
66
+ hash(email),
67
+ hash(phone ? require_track_oaiq.normalizeOAIPhone(phone) : void 0),
68
+ hash(user_id?.trim()),
69
+ hash(require_track_oaiq.normalizeOAIName(address?.first_name)),
70
+ hash(require_track_oaiq.normalizeOAIName(address?.last_name))
71
+ ]).then(([email_sha256, phone_number_sha256, external_id_sha256, first, last]) => init({
59
72
  email_sha256,
60
- external_id_sha256
73
+ phone_number_sha256,
74
+ external_id_sha256,
75
+ first_name_sha256: first,
76
+ last_name_sha256: last
61
77
  })).catch(() => init({}));
62
78
  };
63
79
  }
@@ -1 +1 @@
1
- {"version":3,"file":"openai-pixel.cjs","names":["NON_AD_EVENTS","mapOAIEvent","getFirst","sha256"],"sources":["../../src/third-parties/openai-pixel.ts"],"sourcesContent":["import type { UpdateVisitorDTO } from '../schema/index';\nimport { NON_AD_EVENTS, type OAIQ, type OAIQUser, mapOAIEvent } from '../track/oaiq';\nimport type { EventName, TrackName, TrackProperties } from '../track/types';\nimport { getFirst } from '../utils/field';\nimport { sha256 } from '../utils/sha256';\n\ndeclare global {\n interface Window {\n /** Undefined until the OpenAI pixel script has loaded. */\n oaiq?: OAIQ['oaiq'];\n }\n}\n\n/** Drop `undefined` fields so the SDK only receives populated values. */\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any\nfunction clean(value: unknown): any {\n return JSON.parse(JSON.stringify(value));\n}\n\n/**\n * Forward an internal track event to the OpenAI measurement pixel.\n * `eventId` is reused as the OpenAI `event_id` so browser events deduplicate against the\n * Conversions API. https://developers.openai.com/ads/measurement-pixel\n */\nexport function sendOpenAIEvent<T extends EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n eventId?: string\n) {\n if (typeof window === 'undefined' || !window.oaiq) {\n console.warn('oaiq has not been initialized');\n return;\n }\n if (NON_AD_EVENTS.includes(name)) return;\n if (window.location.host.includes('127.0.0.1')) return;\n if (window.location.host.includes('localhost')) return;\n\n const { type, data } = mapOAIEvent(name, properties);\n if (type === 'custom') {\n window.oaiq('measure', 'custom', clean(data), { event_id: eventId, custom_event_name: name });\n } else {\n window.oaiq('measure', type, clean(data), { event_id: eventId });\n }\n}\n\n/**\n * Re-initialize the pixel with hashed user identity for better conversion matching. Email and\n * external id are hashed client-side; geographic fields are sent raw. Hashing is asynchronous,\n * so the `init` call is deferred until the digests resolve.\n */\nexport function setOpenAIUser(pixelId: string) {\n return ({ user_id, user_data }: UpdateVisitorDTO) => {\n if (typeof window === 'undefined' || !window.oaiq) {\n console.warn('oaiq has not been initialized');\n return;\n }\n // Capture the narrowed reference so the deferred `init` closure keeps it non-optional.\n const oaiq = window.oaiq;\n\n const email = getFirst(user_data?.email)?.trim().toLowerCase();\n const address = getFirst(user_data?.address);\n\n const base: OAIQUser = {\n country: address?.country?.trim().toUpperCase(),\n city: address?.city?.trim().toLowerCase(),\n zip_code: address?.postal_code,\n };\n\n const init = (hashed: Partial<OAIQUser>) => {\n oaiq('init', { pixelId, user: clean({ ...base, ...hashed }) });\n };\n\n Promise.all([email ? sha256(email) : undefined, user_id ? sha256(user_id) : undefined])\n .then(([email_sha256, external_id_sha256]) => init({ email_sha256, external_id_sha256 }))\n .catch(() => init({}));\n };\n}\n"],"mappings":";;;;;;AAeA,SAAS,MAAM,OAAqB;CAClC,OAAO,KAAK,MAAM,KAAK,UAAU,KAAK,CAAC;AACzC;;;;;;AAOA,SAAgB,gBACd,MACA,YACA,SACA;CACA,IAAI,OAAO,WAAW,eAAe,CAAC,OAAO,MAAM;EACjD,QAAQ,KAAK,+BAA+B;EAC5C;CACF;CACA,IAAIA,mBAAAA,cAAc,SAAS,IAAI,GAAG;CAClC,IAAI,OAAO,SAAS,KAAK,SAAS,WAAW,GAAG;CAChD,IAAI,OAAO,SAAS,KAAK,SAAS,WAAW,GAAG;CAEhD,MAAM,EAAE,MAAM,SAASC,mBAAAA,YAAY,MAAM,UAAU;CACnD,IAAI,SAAS,UACX,OAAO,KAAK,WAAW,UAAU,MAAM,IAAI,GAAG;EAAE,UAAU;EAAS,mBAAmB;CAAK,CAAC;MAE5F,OAAO,KAAK,WAAW,MAAM,MAAM,IAAI,GAAG,EAAE,UAAU,QAAQ,CAAC;AAEnE;;;;;;AAOA,SAAgB,cAAc,SAAiB;CAC7C,QAAQ,EAAE,SAAS,gBAAkC;EACnD,IAAI,OAAO,WAAW,eAAe,CAAC,OAAO,MAAM;GACjD,QAAQ,KAAK,+BAA+B;GAC5C;EACF;EAEA,MAAM,OAAO,OAAO;EAEpB,MAAM,QAAQC,oBAAAA,SAAS,WAAW,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC,YAAY;EAC7D,MAAM,UAAUA,oBAAAA,SAAS,WAAW,OAAO;EAE3C,MAAM,OAAiB;GACrB,SAAS,SAAS,SAAS,KAAK,CAAC,CAAC,YAAY;GAC9C,MAAM,SAAS,MAAM,KAAK,CAAC,CAAC,YAAY;GACxC,UAAU,SAAS;EACrB;EAEA,MAAM,QAAQ,WAA8B;GAC1C,KAAK,QAAQ;IAAE;IAAS,MAAM,MAAM;KAAE,GAAG;KAAM,GAAG;IAAO,CAAC;GAAE,CAAC;EAC/D;EAEA,QAAQ,IAAI,CAAC,QAAQC,qBAAAA,OAAO,KAAK,IAAI,KAAA,GAAW,UAAUA,qBAAAA,OAAO,OAAO,IAAI,KAAA,CAAS,CAAC,CAAC,CACpF,MAAM,CAAC,cAAc,wBAAwB,KAAK;GAAE;GAAc;EAAmB,CAAC,CAAC,CAAC,CACxF,YAAY,KAAK,CAAC,CAAC,CAAC;CACzB;AACF"}
1
+ {"version":3,"file":"openai-pixel.cjs","names":["NON_AD_EVENTS","mapOAIEvent","oaiCustomEventName","getFirst","sha256","normalizeOAIPhone","normalizeOAIName"],"sources":["../../src/third-parties/openai-pixel.ts"],"sourcesContent":["import type { UpdateVisitorDTO } from '../schema/index';\nimport {\n NON_AD_EVENTS,\n type OAIQ,\n type OAIQUser,\n mapOAIEvent,\n normalizeOAIName,\n normalizeOAIPhone,\n oaiCustomEventName,\n} from '../track/oaiq';\nimport type { EventName, TrackName, TrackProperties } from '../track/types';\nimport { getFirst } from '../utils/field';\nimport { sha256 } from '../utils/sha256';\n\ndeclare global {\n interface Window {\n /** Undefined until the OpenAI pixel script has loaded. */\n oaiq?: OAIQ['oaiq'];\n }\n}\n\n/** Drop `undefined` fields so the SDK only receives populated values. */\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any\nfunction clean(value: unknown): any {\n return JSON.parse(JSON.stringify(value));\n}\n\n/**\n * Forward an internal track event to the OpenAI measurement pixel.\n * `eventId` is reused as the OpenAI `event_id` so browser events deduplicate against the\n * Conversions API. https://developers.openai.com/ads/measurement-pixel\n */\nexport function sendOpenAIEvent<T extends EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n eventId?: string\n) {\n if (typeof window === 'undefined' || !window.oaiq) {\n console.warn('oaiq has not been initialized');\n return;\n }\n if (NON_AD_EVENTS.includes(name)) return;\n if (window.location.host.includes('127.0.0.1')) return;\n if (window.location.host.includes('localhost')) return;\n\n const { type, data } = mapOAIEvent(name, properties);\n if (type === 'custom') {\n // Named as the Conversions API names it, so the two deduplicate; skipped when it cannot be.\n const custom_event_name = oaiCustomEventName(name);\n if (!custom_event_name) return;\n window.oaiq('measure', 'custom', clean(data), { event_id: eventId, custom_event_name });\n } else {\n window.oaiq('measure', type, clean(data), { event_id: eventId });\n }\n}\n\n/**\n * Re-initialize the pixel with hashed user identity for better conversion matching. Email, phone,\n * external id and names are normalized as OpenAI documents and hashed client-side; geographic\n * fields are sent raw. Hashing is asynchronous, so the `init` call is deferred until the digests\n * resolve.\n */\nexport function setOpenAIUser(pixelId: string) {\n return ({ user_id, user_data }: UpdateVisitorDTO) => {\n if (typeof window === 'undefined' || !window.oaiq) {\n console.warn('oaiq has not been initialized');\n return;\n }\n // Capture the narrowed reference so the deferred `init` closure keeps it non-optional.\n const oaiq = window.oaiq;\n\n const email = getFirst(user_data?.email)?.trim().toLowerCase() || undefined;\n const phone = getFirst(user_data?.phone_number);\n const address = getFirst(user_data?.address);\n\n const base: OAIQUser = {\n country: address?.country?.trim().toUpperCase(),\n city: address?.city?.trim(),\n region: address?.region?.trim(),\n postal_code: address?.postal_code?.trim(),\n };\n\n const init = (hashed: Partial<OAIQUser>) => {\n oaiq('init', { pixelId, user: clean({ ...base, ...hashed }) });\n };\n\n const hash = (value: string | undefined) => (value ? sha256(value) : undefined);\n Promise.all([\n hash(email),\n hash(phone ? normalizeOAIPhone(phone) : undefined),\n hash(user_id?.trim()),\n hash(normalizeOAIName(address?.first_name)),\n hash(normalizeOAIName(address?.last_name)),\n ])\n .then(([email_sha256, phone_number_sha256, external_id_sha256, first, last]) =>\n init({\n email_sha256,\n phone_number_sha256,\n external_id_sha256,\n first_name_sha256: first,\n last_name_sha256: last,\n })\n )\n .catch(() => init({}));\n };\n}\n"],"mappings":";;;;;;AAuBA,SAAS,MAAM,OAAqB;CAClC,OAAO,KAAK,MAAM,KAAK,UAAU,KAAK,CAAC;AACzC;;;;;;AAOA,SAAgB,gBACd,MACA,YACA,SACA;CACA,IAAI,OAAO,WAAW,eAAe,CAAC,OAAO,MAAM;EACjD,QAAQ,KAAK,+BAA+B;EAC5C;CACF;CACA,IAAIA,mBAAAA,cAAc,SAAS,IAAI,GAAG;CAClC,IAAI,OAAO,SAAS,KAAK,SAAS,WAAW,GAAG;CAChD,IAAI,OAAO,SAAS,KAAK,SAAS,WAAW,GAAG;CAEhD,MAAM,EAAE,MAAM,SAASC,mBAAAA,YAAY,MAAM,UAAU;CACnD,IAAI,SAAS,UAAU;EAErB,MAAM,oBAAoBC,mBAAAA,mBAAmB,IAAI;EACjD,IAAI,CAAC,mBAAmB;EACxB,OAAO,KAAK,WAAW,UAAU,MAAM,IAAI,GAAG;GAAE,UAAU;GAAS;EAAkB,CAAC;CACxF,OACE,OAAO,KAAK,WAAW,MAAM,MAAM,IAAI,GAAG,EAAE,UAAU,QAAQ,CAAC;AAEnE;;;;;;;AAQA,SAAgB,cAAc,SAAiB;CAC7C,QAAQ,EAAE,SAAS,gBAAkC;EACnD,IAAI,OAAO,WAAW,eAAe,CAAC,OAAO,MAAM;GACjD,QAAQ,KAAK,+BAA+B;GAC5C;EACF;EAEA,MAAM,OAAO,OAAO;EAEpB,MAAM,QAAQC,oBAAAA,SAAS,WAAW,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC,YAAY,KAAK,KAAA;EAClE,MAAM,QAAQA,oBAAAA,SAAS,WAAW,YAAY;EAC9C,MAAM,UAAUA,oBAAAA,SAAS,WAAW,OAAO;EAE3C,MAAM,OAAiB;GACrB,SAAS,SAAS,SAAS,KAAK,CAAC,CAAC,YAAY;GAC9C,MAAM,SAAS,MAAM,KAAK;GAC1B,QAAQ,SAAS,QAAQ,KAAK;GAC9B,aAAa,SAAS,aAAa,KAAK;EAC1C;EAEA,MAAM,QAAQ,WAA8B;GAC1C,KAAK,QAAQ;IAAE;IAAS,MAAM,MAAM;KAAE,GAAG;KAAM,GAAG;IAAO,CAAC;GAAE,CAAC;EAC/D;EAEA,MAAM,QAAQ,UAA+B,QAAQC,qBAAAA,OAAO,KAAK,IAAI,KAAA;EACrE,QAAQ,IAAI;GACV,KAAK,KAAK;GACV,KAAK,QAAQC,mBAAAA,kBAAkB,KAAK,IAAI,KAAA,CAAS;GACjD,KAAK,SAAS,KAAK,CAAC;GACpB,KAAKC,mBAAAA,iBAAiB,SAAS,UAAU,CAAC;GAC1C,KAAKA,mBAAAA,iBAAiB,SAAS,SAAS,CAAC;EAC3C,CAAC,CAAC,CACC,MAAM,CAAC,cAAc,qBAAqB,oBAAoB,OAAO,UACpE,KAAK;GACH;GACA;GACA;GACA,mBAAmB;GACnB,kBAAkB;EACpB,CAAC,CACH,CAAC,CACA,YAAY,KAAK,CAAC,CAAC,CAAC;CACzB;AACF"}
@@ -15,9 +15,10 @@ declare global {
15
15
  */
16
16
  export declare function sendOpenAIEvent<T extends EventName>(name: TrackName<T>, properties?: TrackProperties<T>, eventId?: string): void;
17
17
  /**
18
- * Re-initialize the pixel with hashed user identity for better conversion matching. Email and
19
- * external id are hashed client-side; geographic fields are sent raw. Hashing is asynchronous,
20
- * so the `init` call is deferred until the digests resolve.
18
+ * Re-initialize the pixel with hashed user identity for better conversion matching. Email, phone,
19
+ * external id and names are normalized as OpenAI documents and hashed client-side; geographic
20
+ * fields are sent raw. Hashing is asynchronous, so the `init` call is deferred until the digests
21
+ * resolve.
21
22
  */
22
23
  export declare function setOpenAIUser(pixelId: string): ({ user_id, user_data }: UpdateVisitorDTO) => void;
23
24
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"openai-pixel.d.cts","names":[],"sources":["../../src/third-parties/openai-pixel.ts"],"mappings":";;;;QAMQ;YACI;;IAER,OAAO;;;;;;;;wBAeK,gBAAgB,UAAU,WACxC,MAAM,UAAU,IAChB,aAAa,gBAAgB,IAC7B;;;;;;wBAuBc,cAAc,qBAAe,SAAA,aACX"}
1
+ {"version":3,"file":"openai-pixel.d.cts","names":[],"sources":["../../src/third-parties/openai-pixel.ts"],"mappings":";;;;QAcQ;YACI;;IAER,OAAO;;;;;;;;wBAeK,gBAAgB,UAAU,WACxC,MAAM,UAAU,IAChB,aAAa,gBAAgB,IAC7B;;;;;;;wBA2Bc,cAAc,qBAAe,SAAA,aACX"}
@@ -15,9 +15,10 @@ declare global {
15
15
  */
16
16
  export declare function sendOpenAIEvent<T extends EventName>(name: TrackName<T>, properties?: TrackProperties<T>, eventId?: string): void;
17
17
  /**
18
- * Re-initialize the pixel with hashed user identity for better conversion matching. Email and
19
- * external id are hashed client-side; geographic fields are sent raw. Hashing is asynchronous,
20
- * so the `init` call is deferred until the digests resolve.
18
+ * Re-initialize the pixel with hashed user identity for better conversion matching. Email, phone,
19
+ * external id and names are normalized as OpenAI documents and hashed client-side; geographic
20
+ * fields are sent raw. Hashing is asynchronous, so the `init` call is deferred until the digests
21
+ * resolve.
21
22
  */
22
23
  export declare function setOpenAIUser(pixelId: string): ({ user_id, user_data }: UpdateVisitorDTO) => void;
23
24
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"openai-pixel.d.mts","names":[],"sources":["../../src/third-parties/openai-pixel.ts"],"mappings":";;;;QAMQ;YACI;;IAER,OAAO;;;;;;;;wBAeK,gBAAgB,UAAU,WACxC,MAAM,UAAU,IAChB,aAAa,gBAAgB,IAC7B;;;;;;wBAuBc,cAAc,qBAAe,SAAA,aACX"}
1
+ {"version":3,"file":"openai-pixel.d.mts","names":[],"sources":["../../src/third-parties/openai-pixel.ts"],"mappings":";;;;QAcQ;YACI;;IAER,OAAO;;;;;;;;wBAeK,gBAAgB,UAAU,WACxC,MAAM,UAAU,IAChB,aAAa,gBAAgB,IAC7B;;;;;;;wBA2Bc,cAAc,qBAAe,SAAA,aACX"}
@@ -1,6 +1,6 @@
1
1
  import { getFirst } from "../utils/field.mjs";
2
2
  import { sha256 } from "../utils/sha256.mjs";
3
- import { NON_AD_EVENTS, mapOAIEvent } from "../track/oaiq.mjs";
3
+ import { NON_AD_EVENTS, mapOAIEvent, normalizeOAIName, normalizeOAIPhone, oaiCustomEventName } from "../track/oaiq.mjs";
4
4
  //#region src/third-parties/openai-pixel.ts
5
5
  /** Drop `undefined` fields so the SDK only receives populated values. */
6
6
  function clean(value) {
@@ -20,16 +20,20 @@ function sendOpenAIEvent(name, properties, eventId) {
20
20
  if (window.location.host.includes("127.0.0.1")) return;
21
21
  if (window.location.host.includes("localhost")) return;
22
22
  const { type, data } = mapOAIEvent(name, properties);
23
- if (type === "custom") window.oaiq("measure", "custom", clean(data), {
24
- event_id: eventId,
25
- custom_event_name: name
26
- });
27
- else window.oaiq("measure", type, clean(data), { event_id: eventId });
23
+ if (type === "custom") {
24
+ const custom_event_name = oaiCustomEventName(name);
25
+ if (!custom_event_name) return;
26
+ window.oaiq("measure", "custom", clean(data), {
27
+ event_id: eventId,
28
+ custom_event_name
29
+ });
30
+ } else window.oaiq("measure", type, clean(data), { event_id: eventId });
28
31
  }
29
32
  /**
30
- * Re-initialize the pixel with hashed user identity for better conversion matching. Email and
31
- * external id are hashed client-side; geographic fields are sent raw. Hashing is asynchronous,
32
- * so the `init` call is deferred until the digests resolve.
33
+ * Re-initialize the pixel with hashed user identity for better conversion matching. Email, phone,
34
+ * external id and names are normalized as OpenAI documents and hashed client-side; geographic
35
+ * fields are sent raw. Hashing is asynchronous, so the `init` call is deferred until the digests
36
+ * resolve.
33
37
  */
34
38
  function setOpenAIUser(pixelId) {
35
39
  return ({ user_id, user_data }) => {
@@ -38,12 +42,14 @@ function setOpenAIUser(pixelId) {
38
42
  return;
39
43
  }
40
44
  const oaiq = window.oaiq;
41
- const email = getFirst(user_data?.email)?.trim().toLowerCase();
45
+ const email = getFirst(user_data?.email)?.trim().toLowerCase() || void 0;
46
+ const phone = getFirst(user_data?.phone_number);
42
47
  const address = getFirst(user_data?.address);
43
48
  const base = {
44
49
  country: address?.country?.trim().toUpperCase(),
45
- city: address?.city?.trim().toLowerCase(),
46
- zip_code: address?.postal_code
50
+ city: address?.city?.trim(),
51
+ region: address?.region?.trim(),
52
+ postal_code: address?.postal_code?.trim()
47
53
  };
48
54
  const init = (hashed) => {
49
55
  oaiq("init", {
@@ -54,9 +60,19 @@ function setOpenAIUser(pixelId) {
54
60
  })
55
61
  });
56
62
  };
57
- Promise.all([email ? sha256(email) : void 0, user_id ? sha256(user_id) : void 0]).then(([email_sha256, external_id_sha256]) => init({
63
+ const hash = (value) => value ? sha256(value) : void 0;
64
+ Promise.all([
65
+ hash(email),
66
+ hash(phone ? normalizeOAIPhone(phone) : void 0),
67
+ hash(user_id?.trim()),
68
+ hash(normalizeOAIName(address?.first_name)),
69
+ hash(normalizeOAIName(address?.last_name))
70
+ ]).then(([email_sha256, phone_number_sha256, external_id_sha256, first, last]) => init({
58
71
  email_sha256,
59
- external_id_sha256
72
+ phone_number_sha256,
73
+ external_id_sha256,
74
+ first_name_sha256: first,
75
+ last_name_sha256: last
60
76
  })).catch(() => init({}));
61
77
  };
62
78
  }