@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/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "bugs": {
7
7
  "url": "https://github.com/amageweb/amage-rastro/issues"
8
8
  },
9
- "version": "0.11.2",
9
+ "version": "0.11.4",
10
10
  "license": "Apache-2.0",
11
11
  "publishConfig": {
12
12
  "access": "public"
@@ -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: SHA-256 hex digests, externalId?: a SHA-256 hex digest }",
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 than
87
- * MAX_PHONES phones) drops the whole `meta`, so the lead's own write goes on
88
- * without the Conversions API. The hash of an empty string is left out.
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 { ph?: unknown; externalId?: unknown };
94
- const phones = fields.ph ?? [];
95
- if (!Array.isArray(phones) || phones.length > MAX_PHONES) return ignored();
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
- if (ph.length === 0 && externalId === undefined) return null;
109
- return { ph, ...(externalId ? { externalId } : {}) };
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
- /** The Graph API version every call pins. Bump it here and nowhere else. */
11
- export const META_GRAPH_VERSION = "v25.0";
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: { ph?: string[]; external_id?: string[]; country: string[] };
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. */
@@ -1,5 +1,5 @@
1
1
  // Generated by scripts/build-tracker.mjs. Do not edit.
2
- export const RASTRO_VERSION = "0.11.2";
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;