@iann29/rastro 0.11.2 → 0.11.4
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/dist/client/index.d.ts +3 -9
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/metaHashes.d.ts +50 -0
- package/dist/client/metaHashes.d.ts.map +1 -0
- package/dist/client/metaHashes.js +116 -0
- package/dist/client/metaHashes.js.map +1 -0
- package/dist/component/metaCapi.d.ts +10 -3
- package/dist/component/metaCapi.d.ts.map +1 -1
- package/dist/component/metaCapi.js +45 -16
- package/dist/component/metaCapi.js.map +1 -1
- package/dist/component/metaGraph.d.ts +12 -2
- package/dist/component/metaGraph.d.ts.map +1 -1
- package/dist/component/metaGraph.js +6 -2
- package/dist/component/metaGraph.js.map +1 -1
- package/dist/component/schema.d.ts +20 -2
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/validators.d.ts +18 -1
- package/dist/component/validators.d.ts.map +1 -1
- package/dist/component/validators.js +11 -0
- package/dist/component/validators.js.map +1 -1
- package/dist/tracker/generated.d.ts +1 -1
- package/dist/tracker/generated.js +1 -1
- package/docs/meta-ads.md +29 -15
- package/package.json +1 -1
- package/src/component/metaCapi.ts +48 -15
- package/src/component/metaGraph.ts +17 -3
- package/src/component/validators.ts +11 -0
- package/src/tracker/generated.ts +1 -1
package/package.json
CHANGED
|
@@ -75,38 +75,65 @@ const digestOf = (value: unknown): string | null => {
|
|
|
75
75
|
function ignored(): null {
|
|
76
76
|
// Never the value itself: it may be a plain phone number.
|
|
77
77
|
console.warn(
|
|
78
|
-
"[rastro] meta ignored: expected { ph
|
|
78
|
+
"[rastro] meta ignored: expected { ph, em?, fn?, ln?, ct?, st?, zp?: SHA-256 hex digests, externalId?: a SHA-256 hex digest }",
|
|
79
79
|
);
|
|
80
80
|
return null;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
/** Meta's other customer information fields, each a list of digests. */
|
|
84
|
+
const USER_FIELDS = ["em", "fn", "ln", "ct", "st", "zp"] as const;
|
|
85
|
+
type UserField = (typeof USER_FIELDS)[number];
|
|
86
|
+
/** Values a field may carry (variants: with and without accents…). */
|
|
87
|
+
const MAX_FIELD_VALUES = 4;
|
|
88
|
+
|
|
89
|
+
/** A list of digests, deduplicated, without the hash of nothing; or null. */
|
|
90
|
+
function digestList(value: unknown, max: number): string[] | null {
|
|
91
|
+
const items = value ?? [];
|
|
92
|
+
if (!Array.isArray(items) || items.length > max) return null;
|
|
93
|
+
const out: string[] = [];
|
|
94
|
+
for (const item of items) {
|
|
95
|
+
const hash = digestOf(item);
|
|
96
|
+
if (hash === null) return null;
|
|
97
|
+
if (hash !== EMPTY_SHA256 && !out.includes(hash)) out.push(hash);
|
|
98
|
+
}
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
|
|
83
102
|
/**
|
|
84
103
|
* The host's hashes, checked, or null when there is nothing to send with.
|
|
85
104
|
* Never throws and never echoes what it got: anything but SHA-256 hex digests
|
|
86
|
-
* in the documented shape (a plain phone number, a wrong type, more
|
|
87
|
-
* MAX_PHONES phones
|
|
88
|
-
*
|
|
105
|
+
* in the documented shape (a plain phone number or e-mail, a wrong type, more
|
|
106
|
+
* than MAX_PHONES phones or MAX_FIELD_VALUES values of another field) drops
|
|
107
|
+
* the whole `meta`, so the lead's own write goes on without the Conversions
|
|
108
|
+
* API. The hash of an empty string is left out.
|
|
89
109
|
*/
|
|
90
110
|
export function parseMetaUserData(value: unknown): MetaUserData | null {
|
|
91
111
|
if (value === undefined || value === null) return null;
|
|
92
112
|
if (typeof value !== "object" || Array.isArray(value)) return ignored();
|
|
93
|
-
const fields = value as
|
|
94
|
-
const
|
|
95
|
-
if (
|
|
96
|
-
const ph: string[] = [];
|
|
97
|
-
for (const item of phones) {
|
|
98
|
-
const hash = digestOf(item);
|
|
99
|
-
if (hash === null) return ignored();
|
|
100
|
-
if (hash !== EMPTY_SHA256 && !ph.includes(hash)) ph.push(hash);
|
|
101
|
-
}
|
|
113
|
+
const fields = value as Record<string, unknown>;
|
|
114
|
+
const ph = digestList(fields.ph, MAX_PHONES);
|
|
115
|
+
if (ph === null) return ignored();
|
|
102
116
|
let externalId: string | undefined;
|
|
103
117
|
if (fields.externalId !== undefined) {
|
|
104
118
|
const hash = digestOf(fields.externalId);
|
|
105
119
|
if (hash === null) return ignored();
|
|
106
120
|
if (hash !== EMPTY_SHA256) externalId = hash;
|
|
107
121
|
}
|
|
108
|
-
|
|
109
|
-
|
|
122
|
+
const more: Partial<Record<UserField, string[]>> = {};
|
|
123
|
+
for (const field of USER_FIELDS) {
|
|
124
|
+
if (fields[field] === undefined) continue;
|
|
125
|
+
const hashes = digestList(fields[field], MAX_FIELD_VALUES);
|
|
126
|
+
if (hashes === null) return ignored();
|
|
127
|
+
if (hashes.length > 0) more[field] = hashes;
|
|
128
|
+
}
|
|
129
|
+
if (
|
|
130
|
+
ph.length === 0 &&
|
|
131
|
+
externalId === undefined &&
|
|
132
|
+
Object.keys(more).length === 0
|
|
133
|
+
) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
return { ph, ...(externalId ? { externalId } : {}), ...more };
|
|
110
137
|
}
|
|
111
138
|
|
|
112
139
|
// ---------------------------------------------------------------------------
|
|
@@ -542,6 +569,12 @@ export async function conversionEvent(
|
|
|
542
569
|
user_data: {
|
|
543
570
|
...(userData.ph.length > 0 ? { ph: userData.ph } : {}),
|
|
544
571
|
...(userData.externalId ? { external_id: [userData.externalId] } : {}),
|
|
572
|
+
...Object.fromEntries(
|
|
573
|
+
USER_FIELDS.flatMap((field) => {
|
|
574
|
+
const hashes = userData[field];
|
|
575
|
+
return hashes && hashes.length > 0 ? [[field, hashes]] : [];
|
|
576
|
+
}),
|
|
577
|
+
),
|
|
545
578
|
// Every site Rastro serves today sells in Brazil.
|
|
546
579
|
country: [await sha256Hex("br")],
|
|
547
580
|
},
|
|
@@ -7,8 +7,12 @@ import { fakeMetaGraph } from "./metaFake.js";
|
|
|
7
7
|
* Tests of the real client stub `fetch`.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
/**
|
|
11
|
-
|
|
10
|
+
/**
|
|
11
|
+
* The Graph API version every call pins. The dashboard's login dialog pins
|
|
12
|
+
* its own copy (control-plane/src/meta/paginaMeta.ts); a test keeps the two
|
|
13
|
+
* equal.
|
|
14
|
+
*/
|
|
15
|
+
export const META_GRAPH_VERSION = "v26.0";
|
|
12
16
|
export const META_GRAPH_ORIGIN = "https://graph.facebook.com";
|
|
13
17
|
|
|
14
18
|
const PAGE_LIMIT = 500;
|
|
@@ -267,7 +271,17 @@ export interface ConversionEvent {
|
|
|
267
271
|
event_time: number;
|
|
268
272
|
event_id: string;
|
|
269
273
|
action_source: "chat";
|
|
270
|
-
user_data: {
|
|
274
|
+
user_data: {
|
|
275
|
+
ph?: string[];
|
|
276
|
+
external_id?: string[];
|
|
277
|
+
em?: string[];
|
|
278
|
+
fn?: string[];
|
|
279
|
+
ln?: string[];
|
|
280
|
+
ct?: string[];
|
|
281
|
+
st?: string[];
|
|
282
|
+
zp?: string[];
|
|
283
|
+
country: string[];
|
|
284
|
+
};
|
|
271
285
|
custom_data?: { value: number; currency: string };
|
|
272
286
|
}
|
|
273
287
|
|
|
@@ -805,6 +805,17 @@ export const metaConversionEventNameValidator = v.union(
|
|
|
805
805
|
export const metaUserDataValidator = v.object({
|
|
806
806
|
ph: v.array(v.string()),
|
|
807
807
|
externalId: v.optional(v.string()),
|
|
808
|
+
/**
|
|
809
|
+
* The rest of Meta's customer information, each a list of SHA-256 hex
|
|
810
|
+
* digests of values normalized as Meta asks (`metaUserHashes` in the
|
|
811
|
+
* client does it): e-mail, first and last name, city, state, zip.
|
|
812
|
+
*/
|
|
813
|
+
em: v.optional(v.array(v.string())),
|
|
814
|
+
fn: v.optional(v.array(v.string())),
|
|
815
|
+
ln: v.optional(v.array(v.string())),
|
|
816
|
+
ct: v.optional(v.array(v.string())),
|
|
817
|
+
st: v.optional(v.array(v.string())),
|
|
818
|
+
zp: v.optional(v.array(v.string())),
|
|
808
819
|
});
|
|
809
820
|
|
|
810
821
|
/** A conversion sent (or queued) to the site's Meta pixel. No user data. */
|
package/src/tracker/generated.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Generated by scripts/build-tracker.mjs. Do not edit.
|
|
2
|
-
export const RASTRO_VERSION = "0.11.
|
|
2
|
+
export const RASTRO_VERSION = "0.11.4";
|
|
3
3
|
export const TRACKER_SOURCE = "(()=>{const t=document.currentScript,e=t?.dataset.site,i=t?.dataset.endpoint||t?.src&&new URL(\"events\",t.src).href;if(!e||!i)return;const n=()=>crypto.randomUUID?.()||Math.random()+\"\",r=new URLSearchParams(location.search),s=r.get(\"ref\")?.slice(0,64);let o;for(const t of[\"source\",\"medium\",\"campaign\",\"term\",\"content\"]){const e=r.get(\"utm_\"+t)?.slice(0,64);e&&((o??={})[\"utm_\"+t]=e)}const a=\"gclid gbraid wbraid msclkid ttclid twclid li_fat_id ScCid rdt_cid dclid epik srsltid mc_cid mc_eid _hsenc igsh igshid fbclid\".split(\" \").filter(t=>r.has(t)).slice(0,8);let c,d,l,h,f=t.dataset.visitor||\"\",p=Boolean(f);try{f||=localStorage._rv||=n()}catch{}try{d=sessionStorage,l=d._r,c=d._ri==f&&d._rt,h=s||d._a,h&&(d._a=h),o||=d._u&&JSON.parse(d._u),o&&(d._u=JSON.stringify(o))}catch{h=s}let v,g=0;const u=[];let m=document.referrer.split(\"/\",3).join(\"/\")||void 0;const _=t=>{const n=u.splice(0,50);if(!n.length)return;const r=()=>new Blob([JSON.stringify({siteId:e,events:n,sentAt:Date.now()})]);let s=r();for(;s.size>64e3&&n.length>1;)u.unshift(n.pop()),s=r();t&&navigator.sendBeacon(i,s)||fetch(i,{method:\"POST\",body:s,keepalive:1}).catch(()=>{}),u.length&&_(t)},y=(t,e,i)=>{const r=Date.now(),s=r-c;c=r,s<18e5||(l=n(),\"pageview\"!=t&&(S=\"\",w()));try{d._r=l,d._ri=f,d._rt=r}catch{}u.push({eventId:`${l}.${r.toString(36)}.${g}`,sessionId:l,visitorId:f||l,type:t,name:e,path:location.pathname.slice(0,256),referrer:m,timestamp:r,sequence:g++,affiliateSlug:h,...i}),clearTimeout(v),v=setTimeout(_,1200)};let S;const w=()=>{const t=location.pathname;t!=S&&(S=t,y(\"pageview\",void 0,{properties:o,clid:a.length?a:void 0}))};window.rastro=(e,i,r)=>{if(\"context\"==e)return{sessionId:l,visitorId:f||l,identified:p};if(\"identify\"==e||\"reset\"==e){if(\"identify\"==e&&(!i||i.length>128||!/^[A-Za-z0-9][A-Za-z0-9_.:-]*$/.test(i)))return;if(\"identify\"==e&&i==f)return;_();const r=\"reset\"==e||p;if(p=\"identify\"==e,f=\"identify\"==e?i:n(),t.dataset.visitor=\"identify\"==e?f:\"\",r){h=void 0,o=void 0,m=void 0,a.length=0;try{\"reset\"==e&&(localStorage._rv=f),d._a=\"\",d._u=\"\"}catch{}}c=0,g=0,S=\"\",w()}\"event\"==e&&y(\"custom\",`${i||\"event\"}`.slice(0,80),r&&JSON.stringify(r).length>2e4?void 0:{properties:r})},document.addEventListener(\"click\",t=>{const e=t.target?.closest(\"a,button,[data-rastro-event]\");if(!e)return;const i=e.dataset.rastroEvent,n=e.href,r=n&&new URL(n),s=/^https?:$/.test(r?.protocol),o=s&&r.origin!=location.origin;y(i?\"custom\":o?\"outbound\":\"click\",i,{target:(e.dataset.rastroLabel||e.innerText||e.tagName).slice(0,64),href:s?((o?r.origin:\"\")+r.pathname).slice(0,256):void 0})},1);for(const t of[\"pushState\",\"replaceState\"]){const e=history[t];history[t]=(...t)=>{e.apply(history,t),w()}}addEventListener(\"popstate\",w);let b=0;const I=()=>{y(\"heartbeat\"),_()},L=()=>{b&&(clearInterval(b),b=0,y(\"leave\"),_(1))},E=()=>{document.hidden?L():b||(I(),b=setInterval(I,2e4))};addEventListener(\"pagehide\",L),addEventListener(\"pageshow\",E),document.addEventListener(\"visibilitychange\",E),b=setInterval(I,2e4),w(),E()})();";
|
|
4
4
|
export const TRACKER_GZIP_BASE64 = "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";
|
|
5
5
|
export const TRACKER_RAW_BYTES = 2999;
|