@funnel10x/tracking 1.3.0 → 2.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.
- package/README.md +241 -74
- package/dist/THIRD_PARTY_LICENSES.txt +0 -850
- package/dist/gtm.html +3 -2
- package/dist/index.d.ts +5 -4
- package/dist/index.js +10 -12
- package/dist/tag.js +10 -12
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -5,22 +5,29 @@ atribuição](https://github.com/funnel10x/f10x-admin/blob/main/docs/plano-atrib
|
|
|
5
5
|
do admin. Vive em `packages/tracking/` do `funnel10x/f10x-admin`, e publica-se
|
|
6
6
|
no npm como pacote público, pela versão.
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
8
|
+
**Desde a 2.0.0 (Fase A2b, 2026-10-14) o snippet é o PostHog oficial com os
|
|
9
|
+
nossos extras por cima**: o mesmo `array.js` que a tag oficial carrega, do mesmo
|
|
10
|
+
CDN, no canal 1.x, e o SDK não vem neste ficheiro. O que faz numa página:
|
|
11
|
+
|
|
12
|
+
- **o PostHog oficial**, por um loader: o stub (o `window.posthog` com o
|
|
13
|
+
`__SV`, como o do snippet oficial) posto antes de tudo, e o `array.js` do CDN
|
|
14
|
+
do PostHog no canal 1.x (`/static/1/array.js`, pelo host dos assets do
|
|
15
|
+
`apiHost`, ou pelo `/_t` de um repo `direct`). As funcionalidades ficam como
|
|
16
|
+
na instalação oficial (feature flags, surveys, conversations, product tours;
|
|
17
|
+
os web experiments desligados, como lá), e um site desliga uma pelas
|
|
18
|
+
`posthog` das opções. **O PostHog recebe tudo**: o snippet não limpa nada, e o
|
|
19
|
+
URL chega inteiro, com o que a query string traga (o email, as respostas de
|
|
20
|
+
um quiz, um `token`). Quem apara é o admin, na ingestão (abaixo);
|
|
21
|
+
- **o FingerprintJS v5** (MIT, embutido), sem o pedido de monitorização que ele
|
|
22
|
+
faz por omissão a um servidor deles. O primeiro `$pageview` espera por ele
|
|
23
|
+
até 1 s, contado com o carregamento dele, e cada evento leva o `fingerprint`
|
|
24
|
+
e o `fingerprint_ms` (o tempo que levou, ou o tecto, 1000);
|
|
25
|
+
- **a lista de atribuição nos forms, no CRM e no checkout, e só ela**: os ids
|
|
26
|
+
e os 22 parâmetros do anúncio (`src/params.js`) nos campos escondidos de cada
|
|
27
|
+
form de lead da página (os que já existem com o nome, e os que faltam,
|
|
22
28
|
acrescentados), no URL do iframe de um form, inquérito ou calendário do GHL,
|
|
23
|
-
e nos links do checkout
|
|
29
|
+
e nos links do checkout. O email, o `token` e o resto do URL não seguem: iam
|
|
30
|
+
para o URL do Stripe e para os campos do GHL, que é entregá-los a terceiros;
|
|
24
31
|
- **o identify pelo id do contacto** num link de email ou de SMS
|
|
25
32
|
(`?contact_id={{contact.id}}`);
|
|
26
33
|
- **o email e o telefone que a pessoa envia** (desde a 1.1.0, como a HYROS;
|
|
@@ -28,7 +35,7 @@ O que faz numa página:
|
|
|
28
35
|
(`email`, `phone`, `phoneNumber`, `phone_number`, `tel`, `telefone`), e dos
|
|
29
36
|
campos de email e de telefone do form que ela envia (o `submit` de um
|
|
30
37
|
`<form>`, ou o botão de enviar do form nativo do GHL). O de um form que ela
|
|
31
|
-
não enviou não
|
|
38
|
+
não enviou não vai. O telefone
|
|
32
39
|
ganha o indicativo do seletor de país (o intl-tel-input), que é o que um
|
|
33
40
|
seletor costuma esconder. Vão num evento `contact_captured` (com
|
|
34
41
|
`contact_source`: `url` ou `form`), cada valor uma vez por página, e
|
|
@@ -40,19 +47,89 @@ O que faz numa página:
|
|
|
40
47
|
**interruptor de consentimento** do `site.json`, e a guarda das
|
|
41
48
|
pré-visualizações.
|
|
42
49
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
o
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
50
|
+
**Quem lê isto do lado do admin** (Fase A3 do plano da atribuição): a fonte
|
|
51
|
+
PostHog da ingestão guarda de cada toque o `tracking_version`, a
|
|
52
|
+
`page_version`, o `ab_test`, o `fingerprint`, e desde a A2b a versão do SDK e a
|
|
53
|
+
data dos `defaults` (`$lib_version` e `$config_defaults`), e o email e o
|
|
54
|
+
telefone do `contact_captured` só em hash. **Apara cada URL pela mesma lista**
|
|
55
|
+
de `src/params.js`: um parâmetro que entre na `ALLOWED_PARAMS` entra também na
|
|
56
|
+
entrada `posthog` da `ingest/pii.json` e na `supabase/verify/atr3.sql` do
|
|
57
|
+
admin, no mesmo PR (um teste do admin prova que as três listas são a mesma).
|
|
58
|
+
|
|
59
|
+
### Uma de duas instalações por site
|
|
60
|
+
|
|
61
|
+
Cada site, de qualquer account, escolhe **uma** de duas instalações, e nunca as
|
|
62
|
+
duas na mesma página (decisão 1 da A2b): a **oficial do PostHog** (a tag no
|
|
63
|
+
GTM, o snippet oficial no código do site, ou o posthog-js no código de uma app)
|
|
64
|
+
ou **este snippet** (embutido num repo `direct`, ou pela tag do
|
|
65
|
+
`dist/gtm.html` em qualquer outro site). Quem escolhe este não perde nada da
|
|
66
|
+
oficial: é o mesmo SDK, com os extras. Com as duas, a segunda ou toma o
|
|
67
|
+
arranque da primeira ou duplica cada evento:
|
|
68
|
+
|
|
69
|
+
- **a oficial antes deste**: o primeiro `init` da fila manda, e este não
|
|
70
|
+
arranca; avisa na consola (*another PostHog is already on this page*) e o
|
|
71
|
+
`window.f10xTracking.mode` fica `conflict`;
|
|
72
|
+
- **a oficial depois deste**: o stub deste é o `window.posthog`, e o `init`
|
|
73
|
+
dela entra na fila dele, sem efeito (o stub nunca insere um script, por isso
|
|
74
|
+
não há outro `array.js`); avisa na consola, e cada evento leva
|
|
75
|
+
`tracking_conflict: true`, que é o sinal nos dados. A
|
|
76
|
+
`supabase/verify/atr2b.sql` do admin guarda o resto: num host com este
|
|
77
|
+
snippet, nenhum `$pageview` de outra instalação no mesmo minuto.
|
|
78
|
+
|
|
79
|
+
Num contentor GTM partilhado por vários sites, a tag oficial dispara só nos
|
|
80
|
+
hosts que a escolheram (uma excepção por hostname no trigger). **Dentro de um
|
|
81
|
+
domínio registável, todas as instalações levam a mesma data de `defaults`**: o
|
|
82
|
+
cookie do PostHog é partilhado pelos subdomínios, e a data muda a regra entre o
|
|
83
|
+
cookie e o `localStorage` (`cookieWinsOnConflict`). A deste pacote é
|
|
84
|
+
`2026-08-30`; uma tag oficial no mesmo domínio passa a ela.
|
|
85
|
+
|
|
86
|
+
### O que se actualiza sozinho, e o que não
|
|
87
|
+
|
|
88
|
+
| camada | quem a dá | actualiza-se |
|
|
89
|
+
| --- | --- | --- |
|
|
90
|
+
| o SDK (`array.js`) | o CDN do PostHog, no canal 1.x | **sozinho**, como na tag oficial: o canal tem 4 horas de cache, e uma versão nova chega a todos os sites nesse prazo |
|
|
91
|
+
| as extensões (o gravador, os surveys) | o mesmo CDN | seguem a versão do `array.js` (`strict_script_versioning: true`) |
|
|
92
|
+
| o comportamento por omissão do SDK | a data dos `defaults`, fixa neste pacote | só com uma versão nova do pacote |
|
|
93
|
+
| os extras (este ficheiro) | o jsDelivr pela versão, com o `integrity`, numa tag; o `package.json`, num `direct` | **nunca sozinhos**: decidem o que vai para os forms, o CRM e o checkout, e uma mudança não anunciada chegaria a todos os sites ao mesmo tempo. Num `direct`, uma versão nova do pacote é uma release da base |
|
|
94
|
+
|
|
95
|
+
Os testes A/B de um site `direct` correm sobre um SDK externo: uma versão nova
|
|
96
|
+
chega a todas as versões de um teste ao mesmo tempo, e a comparação entre elas
|
|
97
|
+
continua válida. O admin guarda a versão do SDK e a data dos `defaults` de cada
|
|
98
|
+
toque, para uma análise se poder partir por elas.
|
|
99
|
+
|
|
100
|
+
**O `sdkVersion`, numa emergência.** Uma versão exacta do posthog-js
|
|
101
|
+
(`'1.438.9'`) fixa o caminho exacto do `array.js`, que é imutável no CDN: numa
|
|
102
|
+
tag, na configuração dela; num repo `direct`, pela variável de ambiente da
|
|
103
|
+
Vercel (`VITE_POSTHOG_SDK_VERSION` num `static`, `NEXT_PUBLIC_POSTHOG_SDK_VERSION`
|
|
104
|
+
num `nextjs`), que muda com um redeploy, sem PR. É a resposta a uma versão do
|
|
105
|
+
SDK que parta um site, até uma versão nova do pacote; o workflow diário
|
|
106
|
+
`tracking-sdk.yml` do admin corre os testes contra o `array.js` que o CDN serve
|
|
107
|
+
nesse dia, e abre uma issue quando falham.
|
|
108
|
+
|
|
109
|
+
### O que muda por o PostHog receber tudo
|
|
110
|
+
|
|
111
|
+
O que a 1.x limpava no browser vai inteiro ao PostHog (decisão 2 da A2b), e
|
|
112
|
+
isso tem três consequências que se sabem:
|
|
113
|
+
|
|
114
|
+
- **o cookie do PostHog leva o URL de entrada em bruto** (o
|
|
115
|
+
`$initial_person_info`), e o cookie é partilhado por todos os subdomínios do
|
|
116
|
+
domínio registável;
|
|
117
|
+
- **as gravações seguem as definições do projeto**, com os corpos dos pedidos
|
|
118
|
+
de rede se o projeto os ligar (um form de lead manda o email);
|
|
119
|
+
- **o `Referer` de um pedido ao `/_t`** (do mesmo domínio) leva o URL inteiro,
|
|
120
|
+
e o site volta à política do browser (`strict-origin-when-cross-origin`): o
|
|
121
|
+
`strict-origin` existia por isso, e a razão caiu.
|
|
122
|
+
|
|
123
|
+
**Um site com CSP** tem de deixar passar o host dos assets do PostHog no
|
|
124
|
+
`script-src` (o `us-assets.i.posthog.com`, ou o proxy da account): o SDK deixou
|
|
125
|
+
de vir no ficheiro. É uma das razões de a 2.0.0 ser major.
|
|
49
126
|
|
|
50
127
|
## Num repo `direct`: embutido no build
|
|
51
128
|
|
|
52
129
|
O `package.json` do repo pina a versão exacta (sem `^`), e o build embute-o:
|
|
53
130
|
|
|
54
131
|
```json
|
|
55
|
-
{ "dependencies": { "@funnel10x/tracking": "
|
|
132
|
+
{ "dependencies": { "@funnel10x/tracking": "2.0.0" } }
|
|
56
133
|
```
|
|
57
134
|
|
|
58
135
|
```js
|
|
@@ -61,18 +138,20 @@ import { init } from '@funnel10x/tracking'
|
|
|
61
138
|
init({
|
|
62
139
|
key: import.meta.env.VITE_POSTHOG_KEY,
|
|
63
140
|
apiHost: '/_t',
|
|
141
|
+
sdkVersion: import.meta.env.VITE_POSTHOG_SDK_VERSION || undefined,
|
|
64
142
|
consent: site.consent,
|
|
65
143
|
pageVersion: 3,
|
|
66
|
-
test: '/pt/
|
|
144
|
+
test: '/pt/acq_f1/webinar/lp#2',
|
|
67
145
|
})
|
|
68
146
|
```
|
|
69
147
|
|
|
70
148
|
- `key`: a chave do projeto PostHog da account (`phc_…`), pela variável de
|
|
71
149
|
ambiente da Vercel (`VITE_POSTHOG_KEY` num `static`, `NEXT_PUBLIC_POSTHOG_KEY`
|
|
72
150
|
num `nextjs`). É pública: vai no HTML de qualquer maneira.
|
|
73
|
-
- `apiHost: '/_t'`: os eventos vão pelo **proxy no domínio do site**,
|
|
74
|
-
os
|
|
75
|
-
`vercel.json` do repo leva
|
|
151
|
+
- `apiHost: '/_t'`: o SDK e os eventos vão pelo **proxy no domínio do site**,
|
|
152
|
+
que os livra das listas de bloqueio (a EasyPrivacy bloqueia o `posthog.com`):
|
|
153
|
+
o `array.js` pede-se a `/_t/static/1/array.js`. O `vercel.json` do repo leva
|
|
154
|
+
os três rewrites, por esta ordem:
|
|
76
155
|
|
|
77
156
|
```json
|
|
78
157
|
{
|
|
@@ -87,16 +166,25 @@ init({
|
|
|
87
166
|
O padrão é `:path(.*)` e não `:path*`, que não apanha a barra final dos
|
|
88
167
|
envios (`/e/`) e lhes responde 404 (medido em produção). O `_` à frente
|
|
89
168
|
garante que não colide com um funil (a base não deixa um nome
|
|
90
|
-
de funil começar por `_`).
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
169
|
+
de funil começar por `_`). Até à 1.x o site levava `Referrer-Policy:
|
|
170
|
+
strict-origin`, para o `Referer` de um pedido ao proxy não levar o URL
|
|
171
|
+
inteiro; desde a 2.0.0 o PostHog recebe-o de qualquer maneira, e a base volta
|
|
172
|
+
à política do browser.
|
|
173
|
+
- `sdkVersion`: vazio, o canal 1.x do CDN; uma versão exacta só numa
|
|
174
|
+
emergência (acima), pela variável de ambiente da Vercel
|
|
175
|
+
(`VITE_POSTHOG_SDK_VERSION` num `static`, `NEXT_PUBLIC_POSTHOG_SDK_VERSION` num
|
|
176
|
+
`nextjs`), que o `_scripts/tracking.mjs` e o `Tracking.tsx` da base lêem.
|
|
94
177
|
- `consent`: o do `site.json`. Com `true`, o snippet não mede nem guarda nada
|
|
95
178
|
até à escolha: os forms levam só os parâmetros do anúncio tirados do URL, e o
|
|
96
179
|
banner do site chama `window.f10xTracking.grant()` no sim e `.deny()` no não
|
|
97
|
-
(ou para retirar o sim, que pára o PostHog e apaga o que ele guardou).
|
|
98
|
-
|
|
99
|
-
|
|
180
|
+
(ou para retirar o sim, que pára o PostHog e apaga o que ele guardou). **Antes
|
|
181
|
+
do sim nada se pede, nem o `array.js`**: o padrão que o PostHog recomenda,
|
|
182
|
+
carregar com a recolha desligada, pede a configuração remota antes da
|
|
183
|
+
escolha. Um não antes de o SDK chegar cancela o arranque (o `init` sai da
|
|
184
|
+
fila, e o `array.js` não se pede). A escolha guarda-se em `localStorage`
|
|
185
|
+
(`f10x_consent`), e a página seguinte arranca sozinha. Com `false`, mede
|
|
186
|
+
logo. **O GTM do site arranca depois do stub do snippet**, para uma tag que
|
|
187
|
+
use o `window.posthog` o encontrar.
|
|
100
188
|
- `pageVersion` e `test`: o `version` da página principal no `tests.json` (ou o
|
|
101
189
|
`N` de uma variante) e o nome do teste activo, que o build entrega.
|
|
102
190
|
|
|
@@ -105,33 +193,39 @@ de uma versão) não mede nada nem mexe nos forms.
|
|
|
105
193
|
|
|
106
194
|
## Num site que não é repo nosso: a tag do GTM
|
|
107
195
|
|
|
108
|
-
Num funil do GHL ou num site em Webflow, onde não há
|
|
109
|
-
carrega-se por uma tag Custom HTML do GTM (em All Pages),
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
196
|
+
Num funil do GHL, num site institucional ou num site em Webflow, onde não há
|
|
197
|
+
build, o mesmo pacote carrega-se por uma tag Custom HTML do GTM (em All Pages),
|
|
198
|
+
**em vez da tag oficial do PostHog, e nunca ao lado dela** (*Uma de duas
|
|
199
|
+
instalações por site*, acima), **pela versão fixa** no URL do jsDelivr e com o
|
|
200
|
+
hash do ficheiro no `integrity`. A tag de cada versão publicada está no próprio
|
|
201
|
+
pacote, em `dist/gtm.html`, e cola-se tal como está: só se trocam a chave, o
|
|
202
|
+
`apiHost` (se a account tiver o proxy gerido, abaixo), os hosts do checkout e,
|
|
203
|
+
numa emergência, o `sdkVersion`.
|
|
114
204
|
|
|
115
205
|
```html
|
|
116
206
|
<script>
|
|
117
207
|
window.f10xTracking = {
|
|
118
208
|
key: '{{PostHog project key}}',
|
|
119
209
|
apiHost: '',
|
|
210
|
+
sdkVersion: '',
|
|
120
211
|
checkoutHosts: []
|
|
121
212
|
}
|
|
122
213
|
</script>
|
|
123
|
-
<script src="https://cdn.jsdelivr.net/npm/@funnel10x/tracking@
|
|
214
|
+
<script src="https://cdn.jsdelivr.net/npm/@funnel10x/tracking@2.0.0/dist/tag.js" integrity="sha384-…" crossorigin="anonymous"></script>
|
|
124
215
|
```
|
|
125
216
|
|
|
126
|
-
Uma versão nova é uma tag nova, nunca `@
|
|
127
|
-
diz que versão corre, e o `tracking_version` de cada evento
|
|
217
|
+
Uma versão nova é uma tag nova, nunca `@2` nem `@latest`: o GTM de cada site
|
|
218
|
+
diz que versão dos extras corre, e o `tracking_version` de cada evento
|
|
219
|
+
confirma-o. O SDK, esse, actualiza-se sozinho, pelo CDN do PostHog.
|
|
128
220
|
|
|
129
|
-
**O proxy gerido do PostHog.** Sem build não há o `/_t`, e com o
|
|
130
|
-
vazio os envios vão para o `us.i.posthog.com
|
|
131
|
-
visita de quem tem um bloqueador não chega ao
|
|
221
|
+
**O proxy gerido do PostHog, opcional.** Sem build não há o `/_t`, e com o
|
|
222
|
+
`apiHost` vazio os envios vão para o `us.i.posthog.com`: funciona, mas a
|
|
223
|
+
EasyPrivacy bloqueia-o, e a visita de quem tem um bloqueador não chega ao
|
|
224
|
+
PostHog. O PostHog tem um proxy
|
|
132
225
|
gerido, um subdomínio da account (um CNAME para o PostHog, nas definições da
|
|
133
226
|
organização, *Managed reverse proxy*) que recebe os envios e os passa ao
|
|
134
|
-
PostHog. Com ele, o `apiHost` da tag é esse
|
|
227
|
+
PostHog, e serve também o `array.js`. Com ele, o `apiHost` da tag é esse
|
|
228
|
+
subdomínio: na IR,
|
|
135
229
|
`https://p.infinityresponse.com`, e na IMO Circle, `https://p.imocircle.com`
|
|
136
230
|
(os dois válidos, lidos no PostHog a 2026-10-08; o Piko ainda não tem). As listas que bloqueiam pelo nome do domínio deixam de o apanhar;
|
|
137
231
|
o uBlock Origin no Firefox, que lê o CNAME, apanha na mesma, e quanto se
|
|
@@ -178,6 +272,7 @@ O que os forms, o iframe e os links do checkout levam:
|
|
|
178
272
|
| os click ids | `fbclid`, `gclid`, `gbraid`, `wbraid`, `gad_source`, `gad_campaignid`, `msclkid`, `ctwa_clid` | sim, do URL | não |
|
|
179
273
|
| os ids de anúncio | `a_campaign_id`, `a_adset_id`, `a_ad_id` (a convenção, abaixo), e os da HYROS `h_ad_id`, `fbc_id` | sim, do URL | só `a_ad_id`, como as UTMs |
|
|
180
274
|
| `lp` | a página de entrada que o URL declara | sim, do URL | não |
|
|
275
|
+
| `narrative` | o slug da narrativa declarado pela página (desde a 1.4.0) | sim, do URL | `narrative`, com o query key igual ao nome, se a location o tiver |
|
|
181
276
|
|
|
182
277
|
- **Num form da página**, um campo que já existe com o nome recebe o valor se
|
|
183
278
|
for escondido ou estiver vazio; os que faltam acrescentam-se escondidos, só
|
|
@@ -197,19 +292,27 @@ O que os forms, o iframe e os links do checkout levam:
|
|
|
197
292
|
checkout.
|
|
198
293
|
- **Num checkout criado no servidor** (uma sessão do Stripe em código),
|
|
199
294
|
`window.f10xTracking.fields()` dá os mesmos valores, para a metadata.
|
|
295
|
+
Os `ph_distinct_id`, `ph_session_id`, `ph_anon_id` e `contact_id` chegam ao
|
|
296
|
+
admin pela A4, com forma de id. A `sale_checkout_keys` expõe-os, e a
|
|
297
|
+
`sale_posthog_links` liga a venda pelo `cs_…` observado no PostHog primeiro,
|
|
298
|
+
senão pelo primeiro id da metadata que o projeto da account tenha visto.
|
|
200
299
|
|
|
201
300
|
## A API
|
|
202
301
|
|
|
203
302
|
`init(options)` (ou a tag) devolve, e põe em `window.f10xTracking`:
|
|
204
303
|
|
|
205
304
|
- `mode`: `tag` ou `direct`; `frame` num iframe, `preview` numa
|
|
206
|
-
pré-visualização, e `conflict` quando a página já
|
|
305
|
+
pré-visualização, e `conflict` quando a página já tinha outro PostHog antes
|
|
306
|
+
deste (um que chegue depois não muda o `mode`: deixa `tracking_conflict` nos
|
|
307
|
+
eventos);
|
|
207
308
|
- `fields()`: os valores de cima, agora;
|
|
208
309
|
- `grant()`, `deny()`, `consent()`: o interruptor de um site `direct`;
|
|
209
310
|
- `onReady(fn)`: quando o PostHog estiver a medir;
|
|
210
311
|
- `capture(event, properties)`: um evento pelo mesmo PostHog (o `lead` de um
|
|
211
312
|
form em código), que sem consentimento não faz nada;
|
|
212
|
-
- `posthog`: a instância,
|
|
313
|
+
- `posthog`: a instância do PostHog depois de o SDK arrancar, e o stub até lá
|
|
314
|
+
(o que se lhe chamar fica na fila, como no stub oficial). O
|
|
315
|
+
`window.posthog` é o mesmo, e é por ele que as tags do GTM o usam.
|
|
213
316
|
|
|
214
317
|
## Os modelos de parâmetros de URL
|
|
215
318
|
|
|
@@ -234,26 +337,45 @@ nomes sem prefixo da 1.0.0 à 1.2.0 (`ad_id`, `adset_id`, `campaign_id`,
|
|
|
234
337
|
`adgroup_id`) saíram: nenhum anúncio os usou. Nenhum URL das três accounts
|
|
235
338
|
trazia já um parâmetro `a_` (medido no mesmo dia).
|
|
236
339
|
|
|
340
|
+
**O texto de cada anúncio sai do gerador de links** da Marketing Ops (o módulo
|
|
341
|
+
URL Parameters, Fase M48 do plano da Marketing), que o compõe a partir da regra
|
|
342
|
+
da fonte na Traffic Sources, do funil e do stage: é aí que se gera, e os dois
|
|
343
|
+
modelos de baixo são a forma do que ele dá. O `utm_campaign` deixou de ser o nome
|
|
344
|
+
da campanha (que chega pelo `a_campaign_id`) e é o funil e o stage,
|
|
345
|
+
`<funil>_<stage>[_<temperatura>]`; o nome do ad set e o `utm_id` saíram, porque
|
|
346
|
+
os ids `a_` já os dizem.
|
|
347
|
+
|
|
237
348
|
**Meta**, nos *URL parameters* de cada anúncio:
|
|
238
349
|
|
|
239
350
|
```
|
|
240
|
-
utm_source=meta&utm_medium=paid_social&utm_campaign={{campaign.name}}
|
|
351
|
+
utm_source=meta&utm_medium=paid_social&utm_campaign=acq_f1_webinar_cold&utm_term={{campaign.name}} >> {{adset.name}}&utm_content={{ad.name}}&utm_placement={{placement}}&a_campaign_id={{campaign.id}}&a_adset_id={{adset.id}}&a_ad_id={{ad.id}}
|
|
241
352
|
```
|
|
242
353
|
|
|
243
354
|
O nome do anúncio acaba no código do asset (decisão 10 do plano), e é por isso
|
|
244
|
-
que vai no `utm_content
|
|
355
|
+
que vai no `utm_content`; o `utm_term` leva a campanha e o ad set, separados por
|
|
356
|
+
` >> ` (Fase M60 do plano da Marketing), só para se ler no PostHog: nenhum
|
|
357
|
+
código o lê, e quem liga o clique ao anúncio são os ids `a_`. O `utm_source` é `meta`, como na Traffic Sources e nos
|
|
245
358
|
anúncios das três accounts (45.819 pageviews com `fbclid` em 90 dias, medidos a
|
|
246
359
|
2026-10-08); até à 1.2.0 este modelo dizia `facebook`, que nenhum usava.
|
|
247
360
|
|
|
248
|
-
**Google**, no *Final URL suffix* da
|
|
249
|
-
`
|
|
250
|
-
|
|
251
|
-
|
|
361
|
+
**Google**, no *Final URL suffix* da campanha (ou do ad group, ou do anúncio:
|
|
362
|
+
o `utm_campaign` é o do funil e do stage, e por isso não vai ao nível da conta),
|
|
363
|
+
com o auto-tagging ligado (o `gclid`). A Google não tem macro para nomes, só
|
|
364
|
+
para ids, e por isso os nomes vão por três parâmetros personalizados, escritos à
|
|
365
|
+
mão, com o nome só de letras e dígitos e a referência com o `_` à frente:
|
|
366
|
+
`campaign` (`{_campaign}`) na campanha, `adgroup` (`{_adgroup}`) em cada ad
|
|
367
|
+
group e `ad` (`{_ad}`) em cada anúncio (o nome dele, que acaba no código do
|
|
368
|
+
asset; num anúncio de pesquisa, que não tem campo de nome, é o único nome que
|
|
369
|
+
leva). Sem eles chegam os ids `a_`, que
|
|
370
|
+
são a garantia. Numa campanha de pesquisa o `utm_term` leva a keyword no fim, e
|
|
371
|
+
o ` >> ` vai codificado:
|
|
252
372
|
|
|
253
373
|
```
|
|
254
|
-
utm_source=google&utm_medium=cpc&utm_campaign=
|
|
374
|
+
utm_source=google&utm_medium=cpc&utm_campaign=acq_f1_webinar&utm_term={_campaign}%20%3E%3E%20{_adgroup}%20%3E%3E%20{keyword}&utm_content={_ad}&a_campaign_id={campaignid}&a_adset_id={adgroupid}&a_ad_id={creative}
|
|
255
375
|
```
|
|
256
376
|
|
|
377
|
+
Fora da pesquisa, o mesmo sem o `%20%3E%3E%20{keyword}`.
|
|
378
|
+
|
|
257
379
|
## Os links orgânicos
|
|
258
380
|
|
|
259
381
|
Um link que não é anúncio (a bio do Instagram, a descrição de um vídeo, um
|
|
@@ -262,22 +384,27 @@ tabela **Traffic Sources** do admin, que é nomenclatura partilhada por todas as
|
|
|
262
384
|
accounts. Sem elas a visita chega sem origem, e o referrer de um browser in-app
|
|
263
385
|
ou de um cliente de email vem quase sempre vazio, o que a conta como *direct*.
|
|
264
386
|
Este README não copia a tabela, que é a fonte; o gerador de links da Marketing
|
|
265
|
-
Ops
|
|
387
|
+
Ops (o módulo URL Parameters, Fase M48 do plano da Marketing) monta-os a partir
|
|
388
|
+
dela, junta-os a um URL que já tenha parâmetros sem duplicar nenhum, e guarda
|
|
389
|
+
cada link com o sítio onde foi posto.
|
|
266
390
|
|
|
267
391
|
## O checklist de um funil
|
|
268
392
|
|
|
269
393
|
Antes de ligar o tráfego:
|
|
270
394
|
|
|
271
395
|
1. **O snippet carrega pela versão**: num repo `direct`, a versão exacta no
|
|
272
|
-
`package.json
|
|
273
|
-
`vercel.json`; num site de fora, a tag do `dist/gtm.html` da
|
|
274
|
-
chave da account e, se ela tiver o proxy gerido, o `apiHost`.
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
396
|
+
`package.json` (a da base), a chave na variável de ambiente e os rewrites
|
|
397
|
+
do `/_t` no `vercel.json`; num site de fora, a tag do `dist/gtm.html` da
|
|
398
|
+
versão, com a chave da account e, se ela tiver o proxy gerido, o `apiHost`.
|
|
399
|
+
O `sdkVersion` fica vazio.
|
|
400
|
+
2. **Uma só instalação na página**: este snippet ou a oficial do PostHog,
|
|
401
|
+
nunca as duas. Ao escolher este, a tag ou o snippet do PostHog que o site já
|
|
402
|
+
tenha (no GTM, no head, no código do site) sai quando este entra, e num
|
|
403
|
+
contentor partilhado a tag oficial ganha a excepção do hostname. Com a mesma
|
|
404
|
+
chave não se perde nada: quem volta mantém o id anónimo. **O `apiHost` da
|
|
405
|
+
tag nova é o `api_host` que a antiga tinha** (o proxy, se ela o usava):
|
|
406
|
+
vazio, perdia quem hoje chega por ele. **A data dos `defaults` é a mesma em
|
|
407
|
+
todas as instalações do domínio registável** (a deste pacote).
|
|
281
408
|
3. **O consentimento**: o `consent` do `site.json` escrito, e o banner a chamar
|
|
282
409
|
`grant()` e `deny()`; num site de fora, a tag no GTM com a condição do
|
|
283
410
|
consentimento se o site tiver banner, e no head só se não tiver.
|
|
@@ -289,11 +416,14 @@ Antes de ligar o tráfego:
|
|
|
289
416
|
5. **Os links de email e de SMS** levam `contact_id={{contact.id}}`.
|
|
290
417
|
6. **O checkout**: o host em `checkoutHosts` (o Stripe já lá está), e num
|
|
291
418
|
checkout em código os `fields()` na metadata.
|
|
292
|
-
7. **Os anúncios**:
|
|
293
|
-
orgânicos com as UTMs da Traffic Sources.
|
|
294
|
-
8. **A prova**:
|
|
295
|
-
|
|
296
|
-
|
|
419
|
+
7. **Os anúncios**: o texto do gerador de links nos anúncios da Meta e da
|
|
420
|
+
Google, e os links orgânicos gerados por ele, com as UTMs da Traffic Sources.
|
|
421
|
+
8. **A prova**: na consola, `window.f10xTracking.mode` não é `conflict`; um
|
|
422
|
+
pageview aberto com `?email=teste@example.com&utm_source=teste` chega ao
|
|
423
|
+
PostHog (Activity, no projeto da account) uma vez só, inteiro, com o
|
|
424
|
+
`tracking_version` da versão, o `$lib_version` que o CDN serve e nenhum
|
|
425
|
+
`tracking_conflict`; e os campos do form e o link do checkout levam a
|
|
426
|
+
`utm_source` e não o `email`.
|
|
297
427
|
|
|
298
428
|
## Desenvolver e publicar
|
|
299
429
|
|
|
@@ -304,15 +434,52 @@ npm run build
|
|
|
304
434
|
|
|
305
435
|
O `dist/` commita-se, como o build das apps do admin, e a CI refaz-o e falha se
|
|
306
436
|
não der os mesmos bytes: o que se publica é o que se testou. Os testes estão no
|
|
307
|
-
admin, em `tests/api/tracking.test.mjs` (
|
|
308
|
-
`tests/tracking.spec.mjs` (o snippet numa página, contra o `dist
|
|
437
|
+
admin, em `tests/api/tracking.test.mjs` (as listas e o pacote, sem browser) e
|
|
438
|
+
`tests/tracking.spec.mjs` (o snippet numa página, contra o `dist/`, com o
|
|
439
|
+
`array.js` da devDependency servido sem rede). O workflow `tracking-sdk.yml`
|
|
440
|
+
corre o mesmo spec uma vez por dia contra o `array.js` que o CDN do PostHog
|
|
441
|
+
serve nesse dia, e abre uma issue quando falha.
|
|
309
442
|
|
|
310
443
|
Uma versão nova: subir a `version` no `package.json` (semver: uma mudança no
|
|
311
444
|
que os forms ou os eventos levam é minor, uma que parte uma integração é
|
|
312
|
-
major), `npm run build`, PR, e depois do merge, da `main
|
|
445
|
+
major), `npm run build`, PR, e depois do merge, da `main`, nesta pasta:
|
|
446
|
+
|
|
447
|
+
```bash
|
|
448
|
+
npm publish --dry-run
|
|
449
|
+
```
|
|
313
450
|
|
|
314
451
|
```bash
|
|
315
452
|
npm publish
|
|
316
453
|
```
|
|
317
454
|
|
|
455
|
+
O primeiro confere a versão e os ficheiros sem publicar nada. **O segundo é do
|
|
456
|
+
dono**: a conta do npm (`funnel10x-org`) tem 2FA, e o `npm publish` pede uma
|
|
457
|
+
autenticação no browser (*EOTP: This operation requires a one-time
|
|
458
|
+
password*) mesmo com a sessão iniciada (medido a 2026-10-08, na 1.3.0). Um
|
|
459
|
+
agente prepara a pasta na `main` e corre o primeiro, e o dono corre o segundo
|
|
460
|
+
no terminal dele. A alternativa sem OTP é o *trusted publishing* do npm (a
|
|
461
|
+
publicação pela CI do GitHub, sem token), que é configuração do dono no
|
|
462
|
+
npmjs.com.
|
|
463
|
+
|
|
464
|
+
Depois de publicar, o npm avisa que a versão *may take a few minutes to become
|
|
465
|
+
available*: espera-se, e só então se confirma. O `npm view` lê de uma cache e
|
|
466
|
+
mostrou a versão anterior durante esses minutos; o registo, lido directamente,
|
|
467
|
+
diz a verdade:
|
|
468
|
+
|
|
469
|
+
```bash
|
|
470
|
+
curl -s https://registry.npmjs.org/@funnel10x%2ftracking | node -e "process.stdin.on('data',d=>b+=d);let b='';process.stdin.on('end',()=>console.log(JSON.parse(b)['dist-tags']))"
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
E o `tag.js` que o jsDelivr serve tem de ter o hash do `integrity` da tag do
|
|
474
|
+
GTM, senão a tag não carrega em site nenhum. As duas linhas têm de dar o mesmo
|
|
475
|
+
`sha384-…`:
|
|
476
|
+
|
|
477
|
+
```bash
|
|
478
|
+
echo "sha384-$(curl -sL "https://cdn.jsdelivr.net/npm/@funnel10x/tracking@$(node -p "require('./package.json').version")/dist/tag.js" | openssl dgst -sha384 -binary | openssl base64 -A)"
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
```bash
|
|
482
|
+
grep -o 'sha384-[^"]*' dist/gtm.html
|
|
483
|
+
```
|
|
484
|
+
|
|
318
485
|
O `dist/THIRD_PARTY_LICENSES.txt` leva as licenças do que vai embutido.
|