@getvitops/utils 8.3.0-alpha.3 → 8.3.0-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/dist/ads/index.d.mts +10 -91
- package/dist/ads/index.d.mts.map +1 -1
- package/dist/ads/index.mjs +1 -314
- package/dist/ads/index.mjs.map +1 -1
- package/dist/audit/index.mjs +1 -0
- package/dist/audit/index.mjs.map +1 -1
- package/dist/crm/index.d.mts +1 -1
- package/dist/notify/index.d.mts +1 -1
- package/dist/notify/index.d.mts.map +1 -1
- package/dist/notify/index.mjs +1 -0
- package/dist/notify/index.mjs.map +1 -1
- package/dist/providers-CEHw-j4v.mjs +351 -0
- package/dist/providers-CEHw-j4v.mjs.map +1 -0
- package/dist/tracking/index.d.mts +53 -2
- package/dist/tracking/index.d.mts.map +1 -1
- package/dist/tracking/index.mjs +111 -1
- package/dist/tracking/index.mjs.map +1 -1
- package/dist/{types-oac2P5or.d.mts → types-ByK6OuXQ.d.mts} +6 -1
- package/dist/{types-oac2P5or.d.mts.map → types-ByK6OuXQ.d.mts.map} +1 -1
- package/dist/types-C0hQKdsM.d.mts +92 -0
- package/dist/types-C0hQKdsM.d.mts.map +1 -0
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/tracking/cookie.ts"],"sourcesContent":["/**\n * Reading and writing the `_ac` cookie, as functions over a cookie **string**.\n *\n * String in, string out, so one implementation serves both ends: the browser\n * passes `document.cookie`, the conversion handler passes the request's `Cookie`\n * header. A separate server-side parser would be a second thing to keep in step\n * with the writer, and the failure — attribution silently absent from a\n * notification — looks identical to a visitor who arrived organically.\n *\n * The same shape, for the same reason, as `@getvitops/core`'s consent store.\n */\nimport { TRACKING_COOKIE, TRACKING_COOKIE_DAYS } from './params.ts';\nimport type { TrackingData } from './types.ts';\n\n/** Read one cookie out of a `document.cookie`-shaped string. */\nexport function readCookie(cookieString: string | null | undefined, name: string): string | null {\n if (!cookieString) return null;\n for (const part of cookieString.split(';')) {\n const raw = part.trim();\n if (raw.slice(0, name.length + 1) === `${name}=`) return raw.slice(name.length + 1);\n }\n return null;\n}\n\n/**\n * Cookie header → attribution, or `null`.\n *\n * Anything unparseable reads as `null` — no attribution — rather than as a\n * partial record. A half-read cookie would attribute a conversion to whichever\n * fields happened to survive, which is worse than reporting it unattributed:\n * \"we don't know\" is actionable, a wrong campaign is not.\n */\nexport function parseTrackingCookie(cookieHeader: string | null | undefined): TrackingData | null {\n const raw = readCookie(cookieHeader, TRACKING_COOKIE);\n if (!raw) return null;\n try {\n const data: unknown = JSON.parse(decodeURIComponent(raw));\n if (typeof data !== 'object' || data === null || Array.isArray(data)) return null;\n return data as TrackingData;\n } catch {\n return null;\n }\n}\n\n/** Attribution → cookie value (not the full `Set-Cookie` — see `cookieAttributes`). */\nexport function serializeTrackingCookie(data: TrackingData): string {\n // Undefined values would serialize as absent keys anyway; dropping them first\n // keeps the cookie small and the round-trip exact.\n const clean = Object.fromEntries(Object.entries(data).filter(([, v]) => v !== undefined));\n return encodeURIComponent(JSON.stringify(clean));\n}\n\n/**\n * Attributes for the attribution cookie.\n *\n * `SameSite=Lax` rather than `Strict`: the visitor arrives here by following a\n * link from the ad platform, and `Strict` withholds the cookie on exactly that\n * cross-site navigation — so a returning click would read as a fresh one. No\n * `HttpOnly`, because the capture script has to write it from the page.\n */\nexport function cookieAttributes(now: number, secure: boolean): string {\n const expires = new Date(now + TRACKING_COOKIE_DAYS * 864e5).toUTCString();\n return `;expires=${expires};path=/;SameSite=Lax${secure ? ';Secure' : ''}`;\n}\n\n/**\n * Fold a fresh capture into whatever was already stored.\n *\n * New values win, but `ts` is the **first** capture's — it dates the visitor's\n * original arrival, which is what a 90-day attribution window is measured from.\n * Taking the latest would restart the window on every visit and make the cookie\n * outlive the click it represents.\n */\nexport function mergeTracking(\n existing: TrackingData | null,\n captured: TrackingData,\n now: number,\n): TrackingData {\n return { ...existing, ...captured, ts: existing?.ts ?? now };\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAeA,SAAgB,WAAW,cAAyC,MAA6B;CAC/F,IAAI,CAAC,cAAc,OAAO;CAC1B,KAAK,MAAM,QAAQ,aAAa,MAAM,GAAG,GAAG;EAC1C,MAAM,MAAM,KAAK,KAAK;EACtB,IAAI,IAAI,MAAM,GAAG,KAAK,SAAS,CAAC,MAAM,GAAG,KAAK,IAAI,OAAO,IAAI,MAAM,KAAK,SAAS,CAAC;CACpF;CACA,OAAO;AACT;;;;;;;;;AAUA,SAAgB,oBAAoB,cAA8D;CAChG,MAAM,MAAM,WAAW,mBAA6B;CACpD,IAAI,CAAC,KAAK,OAAO;CACjB,IAAI;EACF,MAAM,OAAgB,KAAK,MAAM,mBAAmB,GAAG,CAAC;EACxD,IAAI,OAAO,SAAS,YAAY,SAAS,QAAQ,MAAM,QAAQ,IAAI,GAAG,OAAO;EAC7E,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;;AAGA,SAAgB,wBAAwB,MAA4B;CAGlE,MAAM,QAAQ,OAAO,YAAY,OAAO,QAAQ,IAAI,CAAC,CAAC,QAAQ,GAAG,OAAO,MAAM,MAAS,CAAC;CACxF,OAAO,mBAAmB,KAAK,UAAU,KAAK,CAAC;AACjD;;;;;;;;;AAUA,SAAgB,iBAAiB,KAAa,QAAyB;CAErE,OAAO,YADS,IAAI,KAAK,WAA6B,KAAK,CAAC,CAAC,YACpC,EAAE,sBAAsB,SAAS,YAAY;AACxE;;;;;;;;;AAUA,SAAgB,cACd,UACA,UACA,KACc;CACd,OAAO;EAAE,GAAG;EAAU,GAAG;EAAU,IAAI,UAAU,MAAM;CAAI;AAC7D"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/tracking/cookie.ts","../../src/tracking/phone.ts"],"sourcesContent":["/**\n * Reading and writing the `_ac` cookie, as functions over a cookie **string**.\n *\n * String in, string out, so one implementation serves both ends: the browser\n * passes `document.cookie`, the conversion handler passes the request's `Cookie`\n * header. A separate server-side parser would be a second thing to keep in step\n * with the writer, and the failure — attribution silently absent from a\n * notification — looks identical to a visitor who arrived organically.\n *\n * The same shape, for the same reason, as `@getvitops/core`'s consent store.\n */\nimport { TRACKING_COOKIE, TRACKING_COOKIE_DAYS } from './params.ts';\nimport type { TrackingData } from './types.ts';\n\n/** Read one cookie out of a `document.cookie`-shaped string. */\nexport function readCookie(cookieString: string | null | undefined, name: string): string | null {\n if (!cookieString) return null;\n for (const part of cookieString.split(';')) {\n const raw = part.trim();\n if (raw.slice(0, name.length + 1) === `${name}=`) return raw.slice(name.length + 1);\n }\n return null;\n}\n\n/**\n * Cookie header → attribution, or `null`.\n *\n * Anything unparseable reads as `null` — no attribution — rather than as a\n * partial record. A half-read cookie would attribute a conversion to whichever\n * fields happened to survive, which is worse than reporting it unattributed:\n * \"we don't know\" is actionable, a wrong campaign is not.\n */\nexport function parseTrackingCookie(cookieHeader: string | null | undefined): TrackingData | null {\n const raw = readCookie(cookieHeader, TRACKING_COOKIE);\n if (!raw) return null;\n try {\n const data: unknown = JSON.parse(decodeURIComponent(raw));\n if (typeof data !== 'object' || data === null || Array.isArray(data)) return null;\n return data as TrackingData;\n } catch {\n return null;\n }\n}\n\n/** Attribution → cookie value (not the full `Set-Cookie` — see `cookieAttributes`). */\nexport function serializeTrackingCookie(data: TrackingData): string {\n // Undefined values would serialize as absent keys anyway; dropping them first\n // keeps the cookie small and the round-trip exact.\n const clean = Object.fromEntries(Object.entries(data).filter(([, v]) => v !== undefined));\n return encodeURIComponent(JSON.stringify(clean));\n}\n\n/**\n * Attributes for the attribution cookie.\n *\n * `SameSite=Lax` rather than `Strict`: the visitor arrives here by following a\n * link from the ad platform, and `Strict` withholds the cookie on exactly that\n * cross-site navigation — so a returning click would read as a fresh one. No\n * `HttpOnly`, because the capture script has to write it from the page.\n */\nexport function cookieAttributes(now: number, secure: boolean): string {\n const expires = new Date(now + TRACKING_COOKIE_DAYS * 864e5).toUTCString();\n return `;expires=${expires};path=/;SameSite=Lax${secure ? ';Secure' : ''}`;\n}\n\n/**\n * Fold a fresh capture into whatever was already stored.\n *\n * New values win, but `ts` is the **first** capture's — it dates the visitor's\n * original arrival, which is what a 90-day attribution window is measured from.\n * Taking the latest would restart the window on every visit and make the cookie\n * outlive the click it represents.\n */\nexport function mergeTracking(\n existing: TrackingData | null,\n captured: TrackingData,\n now: number,\n): TrackingData {\n return { ...existing, ...captured, ts: existing?.ts ?? now };\n}\n","/**\n * Which phone number a visitor should be shown — the pure decision behind call tracking.\n *\n * `site.tracking.phoneNumbers` records facts: the real number, and one number per ad\n * platform the client has provisioned with a call-tracking provider (Twilio, CallRail,\n * …), each forwarding to the real line. This module decides which one a given arrival\n * gets. It touches no DOM, cookie or clock, so it runs in a Worker and in a test, and\n * `scripts/tracking.ts` in `@getvitops/astro` mirrors it (that file is inlined into the\n * document and cannot import) — `tracking.test.ts` there pins the two.\n *\n * Google is deliberately absent from the swap when `site.ads.google.callTracking` is\n * set: Google's own tag fetches and inserts its forwarding number, and swapping a second\n * number on top would show a visitor one number and attribute the call to another.\n */\nimport { AD_PLATFORMS } from '../ads/providers.ts';\nimport type { AdProvider } from '../ads/types.ts';\nimport type { TrackingData } from './types.ts';\n\n/** The fallback key: any paid visit with no platform-specific number. */\nexport const DEFAULT_PAID = 'default-paid';\n\nexport interface PhoneNumbersConfig {\n /** The real number, exactly as it is displayed on the page. */\n number: string;\n /** One tracking number per platform, plus an optional `default-paid` fallback. */\n sources: Readonly<Record<string, string | undefined>>;\n}\n\nexport interface SelectedNumber {\n /** The number to display, in the format it was configured in. */\n number: string;\n /** The `sources` key that matched, for the conversion notification. */\n source: string;\n}\n\n/** Click-ID parameter → the `site.ads` provider key it belongs to. */\nexport const PROVIDER_BY_PARAM: Readonly<Record<string, AdProvider>> = Object.fromEntries(\n (Object.keys(AD_PLATFORMS) as AdProvider[]).flatMap((provider) =>\n AD_PLATFORMS[provider].clickIdParams.map((param) => [param, provider] as const),\n ),\n);\n\n/**\n * `utm_source` values people actually write → provider key. A UTM is self-declared, so\n * this is matched loosely; a click ID needs no such help.\n */\nexport const UTM_SOURCE_ALIASES: Readonly<Record<string, AdProvider>> = {\n google: 'google',\n facebook: 'meta',\n instagram: 'meta',\n meta: 'meta',\n fb: 'meta',\n ig: 'meta',\n bing: 'microsoft',\n microsoft: 'microsoft',\n linkedin: 'linkedin',\n reddit: 'reddit',\n tiktok: 'tiktok',\n pinterest: 'pinterest',\n snapchat: 'snapchat',\n snap: 'snapchat',\n};\n\n/** `utm_medium` values that mean \"paid\" and so qualify for `default-paid`. */\nexport const PAID_MEDIUM = /^(cpc|ppc|paid.*|cpm|display|paidsocial|paid_social)$/i;\n\n/**\n * The digits of a phone number with a leading North American `1` removed, so\n * `+1 (613) 555-0100`, `613-555-0100` and `tel:+16135550100` all compare equal.\n */\nexport function phoneDigits(value: string): string {\n const digits = value.replace(/\\D/g, '');\n return digits.length === 11 && digits.startsWith('1') ? digits.slice(1) : digits;\n}\n\n/** Do two displayed numbers name the same line? Empty never matches. */\nexport function samePhone(a: string, b: string): boolean {\n const da = phoneDigits(a);\n return da.length > 0 && da === phoneDigits(b);\n}\n\n/**\n * The `tel:` URI for a displayed number. A bare ten-digit number is North American\n * (this toolchain's market) and gets `+1`; anything already carrying a country code\n * keeps it.\n */\nexport function telUri(display: string): string {\n const digits = display.replace(/\\D/g, '');\n if (digits.length === 10) return `tel:+1${digits}`;\n return `tel:+${digits}`;\n}\n\n/** Which provider a set of tracking data points at, by click ID first and `utm_source` second. */\nfunction providerOf(data: TrackingData): AdProvider | null {\n for (const [param, provider] of Object.entries(PROVIDER_BY_PARAM)) {\n if (data[param]) return provider;\n }\n const source = data['utm_source'];\n return typeof source === 'string'\n ? (UTM_SOURCE_ALIASES[source.trim().toLowerCase()] ?? null)\n : null;\n}\n\nfunction isPaid(data: TrackingData): boolean {\n for (const param of Object.keys(PROVIDER_BY_PARAM)) if (data[param]) return true;\n const medium = data['utm_medium'];\n return typeof medium === 'string' && PAID_MEDIUM.test(medium.trim());\n}\n\n/**\n * Pick the number for this visit, or null to leave the real one alone.\n *\n * `current` is what this page's URL carried and `stored` is the `_ac` cookie. The URL\n * wins: a visitor arriving from a Meta ad today with a stale Google click ID stored\n * from last month is a Meta visitor now. An organic visit (neither carries anything)\n * always returns null — nobody is shown a tracking number they were not sent to.\n *\n * `googleNative` is true when `site.ads.google.callTracking` is configured; Google's\n * tag owns that swap, so a Google visit returns null here rather than competing.\n */\nexport function selectPhoneNumber(\n current: TrackingData | null,\n stored: TrackingData | null,\n config: PhoneNumbersConfig | undefined,\n opts: { googleNative?: boolean } = {},\n): SelectedNumber | null {\n if (!config) return null;\n for (const data of [current, stored]) {\n if (!data) continue;\n const provider = providerOf(data);\n if (provider === 'google' && opts.googleNative) return null;\n const specific = provider ? config.sources[provider] : undefined;\n if (specific && !samePhone(specific, config.number)) {\n return { number: specific, source: provider! };\n }\n if (isPaid(data) || provider) {\n const fallback = config.sources[DEFAULT_PAID];\n if (\n fallback &&\n !samePhone(fallback, config.number) &&\n !(provider === 'google' && opts.googleNative)\n ) {\n return { number: fallback, source: DEFAULT_PAID };\n }\n return null;\n }\n }\n return null;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAeA,SAAgB,WAAW,cAAyC,MAA6B;CAC/F,IAAI,CAAC,cAAc,OAAO;CAC1B,KAAK,MAAM,QAAQ,aAAa,MAAM,GAAG,GAAG;EAC1C,MAAM,MAAM,KAAK,KAAK;EACtB,IAAI,IAAI,MAAM,GAAG,KAAK,SAAS,CAAC,MAAM,GAAG,KAAK,IAAI,OAAO,IAAI,MAAM,KAAK,SAAS,CAAC;CACpF;CACA,OAAO;AACT;;;;;;;;;AAUA,SAAgB,oBAAoB,cAA8D;CAChG,MAAM,MAAM,WAAW,mBAA6B;CACpD,IAAI,CAAC,KAAK,OAAO;CACjB,IAAI;EACF,MAAM,OAAgB,KAAK,MAAM,mBAAmB,GAAG,CAAC;EACxD,IAAI,OAAO,SAAS,YAAY,SAAS,QAAQ,MAAM,QAAQ,IAAI,GAAG,OAAO;EAC7E,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;;AAGA,SAAgB,wBAAwB,MAA4B;CAGlE,MAAM,QAAQ,OAAO,YAAY,OAAO,QAAQ,IAAI,CAAC,CAAC,QAAQ,GAAG,OAAO,MAAM,MAAS,CAAC;CACxF,OAAO,mBAAmB,KAAK,UAAU,KAAK,CAAC;AACjD;;;;;;;;;AAUA,SAAgB,iBAAiB,KAAa,QAAyB;CAErE,OAAO,YADS,IAAI,KAAK,WAA6B,KAAK,CAAC,CAAC,YACpC,EAAE,sBAAsB,SAAS,YAAY;AACxE;;;;;;;;;AAUA,SAAgB,cACd,UACA,UACA,KACc;CACd,OAAO;EAAE,GAAG;EAAU,GAAG;EAAU,IAAI,UAAU,MAAM;CAAI;AAC7D;;;;;;;;;;;;;;;;;;AC5DA,MAAa,eAAe;;AAiB5B,MAAa,oBAA0D,OAAO,YAC3E,OAAO,KAAK,YAAY,CAAC,CAAkB,SAAS,aACnD,aAAa,SAAS,CAAC,cAAc,KAAK,UAAU,CAAC,OAAO,QAAQ,CAAU,CAChF,CACF;;;;;AAMA,MAAa,qBAA2D;CACtE,QAAQ;CACR,UAAU;CACV,WAAW;CACX,MAAM;CACN,IAAI;CACJ,IAAI;CACJ,MAAM;CACN,WAAW;CACX,UAAU;CACV,QAAQ;CACR,QAAQ;CACR,WAAW;CACX,UAAU;CACV,MAAM;AACR;;AAGA,MAAa,cAAc;;;;;AAM3B,SAAgB,YAAY,OAAuB;CACjD,MAAM,SAAS,MAAM,QAAQ,OAAO,EAAE;CACtC,OAAO,OAAO,WAAW,MAAM,OAAO,WAAW,GAAG,IAAI,OAAO,MAAM,CAAC,IAAI;AAC5E;;AAGA,SAAgB,UAAU,GAAW,GAAoB;CACvD,MAAM,KAAK,YAAY,CAAC;CACxB,OAAO,GAAG,SAAS,KAAK,OAAO,YAAY,CAAC;AAC9C;;;;;;AAOA,SAAgB,OAAO,SAAyB;CAC9C,MAAM,SAAS,QAAQ,QAAQ,OAAO,EAAE;CACxC,IAAI,OAAO,WAAW,IAAI,OAAO,SAAS;CAC1C,OAAO,QAAQ;AACjB;;AAGA,SAAS,WAAW,MAAuC;CACzD,KAAK,MAAM,CAAC,OAAO,aAAa,OAAO,QAAQ,iBAAiB,GAC9D,IAAI,KAAK,QAAQ,OAAO;CAE1B,MAAM,SAAS,KAAK;CACpB,OAAO,OAAO,WAAW,WACpB,mBAAmB,OAAO,KAAK,CAAC,CAAC,YAAY,MAAM,OACpD;AACN;AAEA,SAAS,OAAO,MAA6B;CAC3C,KAAK,MAAM,SAAS,OAAO,KAAK,iBAAiB,GAAG,IAAI,KAAK,QAAQ,OAAO;CAC5E,MAAM,SAAS,KAAK;CACpB,OAAO,OAAO,WAAW,YAAY,YAAY,KAAK,OAAO,KAAK,CAAC;AACrE;;;;;;;;;;;;AAaA,SAAgB,kBACd,SACA,QACA,QACA,OAAmC,CAAC,GACb;CACvB,IAAI,CAAC,QAAQ,OAAO;CACpB,KAAK,MAAM,QAAQ,CAAC,SAAS,MAAM,GAAG;EACpC,IAAI,CAAC,MAAM;EACX,MAAM,WAAW,WAAW,IAAI;EAChC,IAAI,aAAa,YAAY,KAAK,cAAc,OAAO;EACvD,MAAM,WAAW,WAAW,OAAO,QAAQ,YAAY;EACvD,IAAI,YAAY,CAAC,UAAU,UAAU,OAAO,MAAM,GAChD,OAAO;GAAE,QAAQ;GAAU,QAAQ;EAAU;EAE/C,IAAI,OAAO,IAAI,KAAK,UAAU;GAC5B,MAAM,WAAW,OAAO,QAAQ;GAChC,IACE,YACA,CAAC,UAAU,UAAU,OAAO,MAAM,KAClC,EAAE,aAAa,YAAY,KAAK,eAEhC,OAAO;IAAE,QAAQ;IAAU,QAAQ;GAAa;GAElD,OAAO;EACT;CACF;CACA,OAAO;AACT"}
|
|
@@ -37,6 +37,11 @@ interface ConversionEvent {
|
|
|
37
37
|
/** Submitted fields, for `type: 'form'`. Honeypots are stripped before this. */
|
|
38
38
|
formData?: Record<string, string> | undefined;
|
|
39
39
|
phone?: string | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* For `type: 'call'` on a swapped number: which `site.tracking.phoneNumbers` source (or
|
|
42
|
+
* `google`) the visitor was shown. Lets the notification say "called the Meta number".
|
|
43
|
+
*/
|
|
44
|
+
phoneSource?: string | undefined;
|
|
40
45
|
/** The `mailto:` address, for `type: 'email'`. */
|
|
41
46
|
email?: string | undefined;
|
|
42
47
|
/** Attribution from the `_ac` cookie, or null when the visitor arrived cold. */
|
|
@@ -47,4 +52,4 @@ interface ConversionEvent {
|
|
|
47
52
|
at: number;
|
|
48
53
|
}
|
|
49
54
|
export { TrackingData as i, TrackingCategory as n, TrackingConfig as r, ConversionEvent as t };
|
|
50
|
-
//# sourceMappingURL=types-
|
|
55
|
+
//# sourceMappingURL=types-ByK6OuXQ.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types-
|
|
1
|
+
{"version":3,"file":"types-ByK6OuXQ.d.mts","names":[],"sources":["../src/tracking/types.ts"],"mappings":";;KAsBY,mBAAmB,YAAY;;;;;;;;;UAU1B;GACd;;EAED;;EAEA;;EAEA;;EAEA;;;;;;;;;;;;;;UAee;EACf;;EAEA,WAAW;EACX;;;;;EAKA;;EAEA;;EAEA,UAAU;EACV;EACA;;EAEA"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { AdPropertyConfig } from "@getvitops/config";
|
|
2
|
+
import { AdProvider } from "@getvitops/config/providers";
|
|
3
|
+
/**
|
|
4
|
+
* The ad platforms this toolchain knows how to link a site to.
|
|
5
|
+
*
|
|
6
|
+
* A closed set, and `AD_PROVIDERS` in `@getvitops/config` is its single source: the capability
|
|
7
|
+
* table in `providers.ts` is a `Record<AdProvider, …>`, so a provider added there without an entry
|
|
8
|
+
* here is a compile error. (This was a hand-copied union held to the schema's enum by a test in the
|
|
9
|
+
* generator; the schema now lives below this package, so the copy and the test are gone.) An open
|
|
10
|
+
* string would mean a typo'd provider silently getting no verification step and no tag.
|
|
11
|
+
*/
|
|
12
|
+
type AdProvider$1 = AdProvider;
|
|
13
|
+
/** Which consent category gates a provider's tag. */
|
|
14
|
+
type AdConsentCategory = 'marketing' | 'analytics';
|
|
15
|
+
/**
|
|
16
|
+
* The three interactions Google Ads can report a distinct conversion action
|
|
17
|
+
* for. `form` is fired client-side via `vitopsFormConversion()`
|
|
18
|
+
* (`@getvitops/astro`'s tracking script), the other two from the tel:/mailto:
|
|
19
|
+
* click handler.
|
|
20
|
+
*/
|
|
21
|
+
type ConversionAction = 'call' | 'email' | 'form';
|
|
22
|
+
/**
|
|
23
|
+
* One label per action this site wants Google Ads to tell apart.
|
|
24
|
+
*
|
|
25
|
+
* Written out field by field, `?: string | undefined` — not
|
|
26
|
+
* `Partial<Record<ConversionAction, string>>` — matching this file's own
|
|
27
|
+
* convention above: `exactOptionalPropertyTypes` treats `{ email?: string }`
|
|
28
|
+
* (present-or-absent) and `{ email?: string | undefined }` (present,
|
|
29
|
+
* absent, or explicitly `undefined`) as different types, and a parsed JSON
|
|
30
|
+
* config's optional field is the latter. `Partial<Record<...>>` produces the
|
|
31
|
+
* former, which rejected the zod-inferred shape config.ts actually produces.
|
|
32
|
+
*/
|
|
33
|
+
interface ConversionLabelMap {
|
|
34
|
+
call?: string | undefined;
|
|
35
|
+
email?: string | undefined;
|
|
36
|
+
form?: string | undefined;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* A bare string is back-compat shorthand for `{ call: label }` — the only
|
|
40
|
+
* action that existed before this was a map. Absent input normalises to `{}`.
|
|
41
|
+
*/
|
|
42
|
+
declare function normalizeConversionLabels(label: string | ConversionLabelMap | undefined): ConversionLabelMap;
|
|
43
|
+
/**
|
|
44
|
+
* One Google Ads conversion action **resource id** per action this site
|
|
45
|
+
* uploads server-side — a numeric id (`"123456789"`) or a full resource name
|
|
46
|
+
* (`"customers/123/conversionActions/456"`), never the `conversionLabel`
|
|
47
|
+
* string. Written out field by field for the same `exactOptionalPropertyTypes`
|
|
48
|
+
* reason as `ConversionLabelMap` above.
|
|
49
|
+
*
|
|
50
|
+
* **This is a genuinely different identifier from `conversionLabel`, not an
|
|
51
|
+
* alternate spelling of it.** `conversionLabel` is the opaque label the web
|
|
52
|
+
* tag embeds in `send_to: "AW-…/label"` — it identifies the conversion action
|
|
53
|
+
* to `gtag.js`, not to the Ads API. The API's click-conversion upload
|
|
54
|
+
* (`ConversionUploadService.UploadClickConversions`) addresses the action by
|
|
55
|
+
* `conversion_action` **resource name**, a value the label cannot be turned
|
|
56
|
+
* into locally — Google returns both when a conversion action is created, but
|
|
57
|
+
* only the label ships to the browser today. Until something in this
|
|
58
|
+
* toolchain calls `ConversionActionService` to create/list actions and print
|
|
59
|
+
* both back, this field has to be copied in by hand from the Ads UI/API.
|
|
60
|
+
*/
|
|
61
|
+
interface ConversionActionIdMap {
|
|
62
|
+
call?: string | undefined;
|
|
63
|
+
email?: string | undefined;
|
|
64
|
+
form?: string | undefined;
|
|
65
|
+
}
|
|
66
|
+
/** Same back-compat shorthand as `normalizeConversionLabels`, for ids. */
|
|
67
|
+
declare function normalizeConversionActionIds(id: string | ConversionActionIdMap | undefined): ConversionActionIdMap;
|
|
68
|
+
/** One ad property to link: the config block, plus the platform it is keyed by. */
|
|
69
|
+
type AdPropertySetup = AdPropertyConfig & {
|
|
70
|
+
provider: AdProvider$1;
|
|
71
|
+
};
|
|
72
|
+
/** Everything `plan()` needs, once the CLI has read the config. */
|
|
73
|
+
interface AdsConfig {
|
|
74
|
+
/** The properties to link. Already filtered to `--provider` if that was passed. */
|
|
75
|
+
properties: AdPropertySetup[];
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* One property's observed live state, gathered by the executor before planning.
|
|
79
|
+
*
|
|
80
|
+
* Same shape of arrangement as `DomainState`: the decision is pure, the
|
|
81
|
+
* observation is not. `txtContents` is every apex TXT record on the domain, so
|
|
82
|
+
* the planner can decide both "our record is present" and "a *different* record
|
|
83
|
+
* from this provider is present" — the second is a token rotation, which needs
|
|
84
|
+
* saying rather than silently adding a second record.
|
|
85
|
+
*/
|
|
86
|
+
interface AdDomainState {
|
|
87
|
+
zoneId?: string | undefined;
|
|
88
|
+
/** Every apex TXT record content, unquoted. */
|
|
89
|
+
txtContents: string[];
|
|
90
|
+
}
|
|
91
|
+
export { AdsConfig as a, ConversionLabelMap as c, AdProvider$1 as i, normalizeConversionActionIds as l, AdDomainState as n, ConversionAction as o, AdPropertySetup as r, ConversionActionIdMap as s, AdConsentCategory as t, normalizeConversionLabels as u };
|
|
92
|
+
//# sourceMappingURL=types-C0hQKdsM.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types-C0hQKdsM.d.mts","names":["AdProvider","AdProviderConfig"],"sources":["../src/ads/types.ts"],"mappings":";;;;;;;;;;;KAmBYA,eAAaC;;KAGb;;;;;;;KAQA;;;;;;;;;;;;UAaK;EACf;EACA;EACA;;;;;;iBAOc,0BACd,gBAAgB,iCACf;;;;;;;;;;;;;;;;;;;UAuBc;EACf;EACA;EACA;;;iBAIc,6BACd,aAAa,oCACZ;;KAMS,kBAAkB;EAC5B,UAAUD;;;UAIK;;EAEf,YAAY;;;;;;;;;;;UAYG;EACf;;EAEA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getvitops/utils",
|
|
3
|
-
"version": "8.3.0-alpha.
|
|
3
|
+
"version": "8.3.0-alpha.5",
|
|
4
4
|
"description": "Shared build-time utilities for the Vitops toolchain — favicon generation and friends.",
|
|
5
5
|
"homepage": "https://docs.vitops.ca",
|
|
6
6
|
"bugs": "https://github.com/getvitops/design-system/issues",
|
|
@@ -107,7 +107,7 @@
|
|
|
107
107
|
"access": "public"
|
|
108
108
|
},
|
|
109
109
|
"dependencies": {
|
|
110
|
-
"@getvitops/config": "8.3.0-alpha.
|
|
110
|
+
"@getvitops/config": "8.3.0-alpha.5",
|
|
111
111
|
"colorjs.io": "^0.5.2",
|
|
112
112
|
"parse5": "^7.3.0",
|
|
113
113
|
"png-to-ico": "^3.0.2",
|