mkfashion-sdk 2.7.3 → 2.7.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.
@@ -1,311 +1,311 @@
1
- # SDK v3 — Tracking de E-commerce Completo (Core Tracker) — Design Spec
2
-
3
- **Empresa:** MetaKosmos
4
- **Data:** 2026-05-22
5
- **Status:** Rascunho para revisão
6
- **Repos afetados:** `mkfashion-sdk`, `mk-collector-api`
7
-
8
- ---
9
-
10
- ## 1. Contexto e motivação
11
-
12
- ### Situação atual (v2.7.2)
13
-
14
- A `mkfashion-sdk` roda **apenas na PDP** (página de produto). O cliente chama
15
- `mkfashion.init({ projectId, identifier })` ou `isAvailable(...)` na PDP, e a
16
- partir daí a SDK:
17
-
18
- - Auto-captura `page_view`, `page_click`, `page_scroll_depth`, `page_form_submit`,
19
- `page_engagement` da PDP.
20
- - Captura eventos do iframe de try-on (`page_modal_opened`, `page_generation_complete`, etc).
21
- - Detecta `add_to_cart` por heurística de texto de botão.
22
-
23
- **Limitação:** só enxergamos a PDP. Não vemos a jornada completa do visitante —
24
- entrada, navegação por categorias, carrinho, checkout e **compra final**. Sem o
25
- purchase, não conseguimos responder a pergunta de negócio mais importante:
26
-
27
- > **"Visitantes que usaram o try-on convertem mais do que os que não usaram?"**
28
-
29
- ### Objetivo da v3
30
-
31
- Transformar a SDK de "widget de PDP" em **core tracker de e-commerce** que roda
32
- em **todas as páginas** do site, capturando o funil completo:
33
-
34
- ```
35
- entrada → navegação → produto → (try-on) → carrinho → checkout → COMPRA
36
- ```
37
-
38
- **Restrições de design (decididas com o stakeholder):**
39
-
40
- 1. **Zero trabalho extra pro cliente** além de importar o script. Nada de chamar
41
- métodos novos, nada de configurar dataLayer.
42
- 2. **Zero dependência de ferramentas de terceiros** (GA4/GTM podem não existir
43
- na loja). Captura por sinais NATIVOS do e-commerce.
44
- 3. **Try-on continua igual**: a marca chama `open()`/`isAvailable()` só na PDP,
45
- exatamente como hoje.
46
- 4. **Compatibilidade**: clientes na v2 (SDK na PDP) continuam funcionando durante
47
- a migração.
48
-
49
- ---
50
-
51
- ## 2. Princípio central: captura por sinais nativos
52
-
53
- Toda loja — independente de ter GA4, GTM, ou qualquer analytics — possui 3 coisas
54
- que existem porque a loja precisa **funcionar e ser indexada**:
55
-
56
- | Sinal nativo | Sempre existe? | Usado para |
57
- |---|---|---|
58
- | **URLs estruturadas** | ✅ sim | detectar etapa do funil (pdp/cart/checkout/purchase) |
59
- | **Botões/links clicáveis** | ✅ sim | add_to_cart, begin_checkout (heurística de texto) |
60
- | **HTML semântico** (JSON-LD, meta, DOM) | ✅ quase sempre | produto, preço, order_id, revenue |
61
-
62
- **Hierarquia de confiança para cada dado:**
63
-
64
- ```
65
- Conversão (houve compra?) → URL de confirmação → 100% confiável
66
- Produto visto → URL /produto + JSON-LD → 100%
67
- Add to cart → clique em botão (heur.) → ~90%
68
- Revenue da compra → JSON-LD Order > DOM > meta → best-effort
69
- ```
70
-
71
- > **dataLayer é tratado como BÔNUS oportunístico** — se existir, enriquece; se
72
- > não, ignoramos. Nunca é dependência.
73
-
74
- ### A distinção crucial: conversão vs revenue
75
-
76
- - **Conversão (binária)**: "houve uma compra?" → detectável 100% por URL da
77
- página de confirmação. Já responde a pergunta de negócio principal.
78
- - **Revenue (valor exato)**: best-effort via JSON-LD/DOM. Se a loja não expõe,
79
- fica sem valor — mas a conversão continua contada.
80
-
81
- ---
82
-
83
- ## 3. Arquitetura em camadas
84
-
85
- ```
86
- mkfashion-sdk (v3)
87
-
88
- ├── core/ ← roda em TODA página
89
- │ ├── identity.js visitorId + sessionId (localStorage/sessionStorage)
90
- │ ├── transport.js batch + flush + sendBeacon
91
- │ ├── pageContext.js detecta tipo de página por URL/JSON-LD
92
- │ ├── autoTrack.js page_view, click, scroll, form, engagement
93
- │ └── sanitize.js remove PII e campos pesados antes de enviar
94
-
95
- ├── ecommerce/ ← NOVO — funil de compra (sinais nativos)
96
- │ ├── catalog.js view_item_list, select_item, view_item (URL + JSON-LD)
97
- │ ├── cart.js add_to_cart (heurística), view_cart (URL)
98
- │ ├── checkout.js begin_checkout (URL/clique), purchase (URL + extração)
99
- │ └── extractors.js JSON-LD, meta tags, DOM scraping, dataLayer (bônus)
100
-
101
- └── tryon/ ← módulo existente (sem mudança funcional)
102
- └── open(), isAvailable(), getAvailability(), postMessage bridge
103
- ```
104
-
105
- ### Carregamento
106
-
107
- ```html
108
- <!-- Cliente coloca no <head> do TEMA GLOBAL (todas as páginas) -->
109
- <script src="https://unpkg.com/mkfashion-sdk@3/src/mkfashion.js"
110
- data-mk-project="698c7c681d3129430f15dddb"
111
- async></script>
112
- ```
113
-
114
- Single source: a SDK detecta a página, inicia o tracking de core + ecommerce, e
115
- expõe a API de try-on pra ser chamada na PDP.
116
-
117
- ---
118
-
119
- ## 4. Detecção de contexto de página
120
-
121
- `core/pageContext.js` classifica a página em cascata (primeiro match vence):
122
-
123
- ```js
124
- function detectPageType() {
125
- // 1. Override explícito (se o cliente quiser forçar)
126
- if (window.__mkPageType) return window.__mkPageType
127
-
128
- // 2. JSON-LD (@type indica tipo de página)
129
- const ld = readJsonLd()
130
- if (ld?.['@type'] === 'Product') return 'pdp'
131
- if (ld?.['@type'] === 'Order' || ld?.['@type'] === 'Invoice') return 'purchase'
132
- if (ld?.['@type'] === 'CollectionPage' || ld?.['@type'] === 'SearchResultsPage') return 'category'
133
-
134
- // 3. URL patterns (fallback universal)
135
- const p = location.pathname.toLowerCase()
136
- if (p === '/' || p === '') return 'home'
137
- if (/\/(thank[_-]?you|orders?\/|pedido[_-]?(confirmado|realizado)|sucesso|order[_-]confirmation)/.test(p)) return 'purchase'
138
- if (/\/checkout/.test(p)) return 'checkout'
139
- if (/\/(cart|carrinho|sacola|bag)(\/|$|\?)/.test(p)) return 'cart'
140
- if (/\/(products?|produtos?|p)\//.test(p)) return 'pdp'
141
- if (/\/(collections?|categoria|c)\//.test(p)) return 'category'
142
- if (/\/(search|busca|s)(\/|\?)/.test(p)) return 'search'
143
- return 'other'
144
- }
145
- ```
146
-
147
- Cada `page_view` carrega `pageType` no `params`, dando semântica ao funil.
148
-
149
- ---
150
-
151
- ## 5. Mapa de eventos do funil
152
-
153
- | Etapa | Evento (collector) | Como detecta (sem GA) | Dados |
154
- |---|---|---|---|
155
- | Entrada | `page_view` + pageType | sempre | url, referrer, utm, pageType |
156
- | Listagem | `page_view_item_list` | pageType=category + JSON-LD ItemList | SKUs visíveis |
157
- | Seleção | `page_select_item` | clique em card de produto (link `/produto/`) | SKU clicado |
158
- | Produto | `page_view_item` | pageType=pdp + JSON-LD Product | SKU, preço, nome |
159
- | Try-on | `page_modal_opened`, `page_generation_complete`, ... | já existe (postMessage) | já existe |
160
- | Add cart | `page_add_to_cart` | heurística de clique (já temos) + JSON-LD price | SKU, texto botão |
161
- | Carrinho | `page_view_cart` | pageType=cart | — |
162
- | Checkout | `page_begin_checkout` | pageType=checkout OU clique "Finalizar" | — |
163
- | **Compra** | `page_purchase` | **pageType=purchase (URL)** | orderId?, revenue?, items? |
164
-
165
- ---
166
-
167
- ## 6. Detecção de purchase (o ponto crítico)
168
-
169
- ### 6.1 Conversão (binária) — 100% confiável
170
-
171
- Ao detectar `pageType === 'purchase'` (via URL/JSON-LD), emite `page_purchase`
172
- imediatamente. Isso conta a conversão mesmo sem revenue.
173
-
174
- ### 6.2 Revenue (best-effort) — cascata de extractors
175
-
176
- ```js
177
- function extractOrderData() {
178
- // 1. JSON-LD Order (existe pro SEO, independente de GA)
179
- const ld = readJsonLd(['Order', 'Invoice'])
180
- if (ld) return { orderId: ld.orderNumber || ld.confirmationNumber,
181
- revenue: ld.price || ld.totalPaymentDue?.price,
182
- currency: ld.priceCurrency }
183
-
184
- // 2. dataLayer (BÔNUS — só se a loja tiver GA/GTM)
185
- const dl = window.dataLayer?.find(x => x.event === 'purchase' && x.ecommerce)
186
- if (dl) return { orderId: dl.ecommerce.transaction_id,
187
- revenue: dl.ecommerce.value,
188
- currency: dl.ecommerce.currency,
189
- items: dl.ecommerce.items }
190
-
191
- // 3. Meta tags
192
- const metaTotal = document.querySelector('meta[property="order:total"]')?.content
193
- if (metaTotal) return { revenue: parseFloat(metaTotal) }
194
-
195
- // 4. DOM scraping (fallback frágil — best-effort)
196
- // procura elementos com classe/texto de total: .order-total, #total, "Total: R$"
197
- const domTotal = scrapeOrderTotal()
198
- if (domTotal) return { revenue: domTotal, _source: 'dom_scrape' }
199
-
200
- return null // conversão já contada; sem revenue
201
- }
202
- ```
203
-
204
- ### 6.3 Dedup
205
-
206
- Página de confirmação pode recarregar (F5) → mesma compra contada 2x. Dedup por:
207
- - `orderId` quando disponível (collector ignora purchase com orderId já visto)
208
- - fallback: 1 purchase por `sessionId` por janela de 1h
209
-
210
- ### 6.4 Opt-in pra precisão (opcional, não obrigatório)
211
-
212
- Cliente que QUER revenue garantido e não tem JSON-LD pode chamar 1 linha:
213
- ```js
214
- mkfashion.trackPurchase({ orderId: 'X', revenue: 599.80, currency: 'BRL' })
215
- ```
216
- Mas é **opcional** — a captura automática cobre a maioria.
217
-
218
- ---
219
-
220
- ## 7. A pergunta de negócio que isso destrava
221
-
222
- Com `visitorId` consistente em todas as páginas + purchase capturado:
223
-
224
- ```
225
- Coorte A: visitantes que abriram try-on (page_modal_opened)
226
- Coorte B: visitantes que NÃO abriram
227
-
228
- Conversão A = purchases(A) / visitantes(A)
229
- Conversão B = purchases(B) / visitantes(B)
230
-
231
- Uplift = Conversão A / Conversão B
232
- ```
233
-
234
- Exemplo de resultado esperado:
235
- ```
236
- Try-on: 8.2% convertem
237
- Sem try-on: 3.1% convertem
238
- Uplift: 2.6x ← argumento de venda do produto MK
239
- ```
240
-
241
- ---
242
-
243
- ## 8. Fases de implementação
244
-
245
- | Fase | Escopo | Risco |
246
- |---|---|---|
247
- | **1** | Refatorar SDK em `core/ ecommerce/ tryon/` sem mudar comportamento | baixo |
248
- | **2** | `pageContext.js` — detecção de tipo de página | baixo |
249
- | **3** | `extractors.js` — JSON-LD, meta, DOM, dataLayer(bônus) | médio |
250
- | **4** | `ecommerce/*` — eventos de funil (view_item, cart, checkout, purchase) | médio |
251
- | **5** | Backend: novos event types + dedup de purchase + agregação de funil | médio |
252
- | **6** | Cliente migra snippet pra tema global | baixo (config) |
253
- | **7** | Dashboard de funil + atribuição try-on→conversão | médio |
254
-
255
- ---
256
-
257
- ## 9. Mudanças no backend (mk-collector-api)
258
-
259
- - Novos `event_name` aceitos (validação já é livre — `page_*`).
260
- - `sdk-aggregator.service.js`: agregar funil por dia:
261
- ```js
262
- funnel: {
263
- sessions, viewItem, addToCart, beginCheckout, purchase,
264
- purchaseRevenue, conversionRate
265
- }
266
- ```
267
- - Dedup de purchase por `orderId` (nova lógica no controller ou aggregator).
268
- - `sdk_daily_metrics`: campos novos `purchases`, `revenue`, `funnel`.
269
- - Atribuição try-on: cruzar visitors com `page_modal_opened` × `page_purchase`
270
- (no aggregator ou query sob demanda).
271
-
272
- ---
273
-
274
- ## 10. Privacidade / LGPD
275
-
276
- - **NUNCA** capturar campos de formulário de checkout (email, CPF, endereço, cartão).
277
- - `core/sanitize.js` filtra chaves sensíveis (`email`, `cpf`, `phone`, `address`,
278
- `card`, `password`) de qualquer payload antes de enviar.
279
- - `orderId` e `revenue` não são PII.
280
- - IP continua sendo descartado server-side (só GeoIP).
281
-
282
- ---
283
-
284
- ## 11. Riscos e mitigações
285
-
286
- | Risco | Mitigação |
287
- |---|---|
288
- | Loja sem JSON-LD nem dataLayer | Conversão por URL (100%); revenue fica nulo (aceitável) |
289
- | URL de confirmação atípica | Lista de patterns extensível + override `window.__mkPageType` |
290
- | Purchase contado 2x (refresh) | Dedup por orderId / sessionId |
291
- | DOM scraping frágil | Marcado com `_source: 'dom_scrape'` pra saber confiabilidade |
292
- | Volume 5-10x maior (todas páginas) | Batching + sampling de eventos de baixo valor |
293
- | PII vazando | sanitize.js + nunca ler campos de form |
294
- | SPA sem reload entre páginas | history.pushState hook (já temos) re-detecta pageType |
295
-
296
- ---
297
-
298
- ## 12. Decisões em aberto (pra próxima revisão)
299
-
300
- 1. Nome do pacote: manter `mkfashion-sdk` v3 ou renomear? (decidir depois)
301
- 2. Sampling: amostrar `page_scroll_depth`/`page_engagement` em sites de alto volume?
302
- 3. Atribuição try-on→conversão: pré-computar no aggregator ou query sob demanda?
303
- 4. Dashboard de funil: nova aba ou endpoint `/api/export/funnel`?
304
-
305
- ---
306
-
307
- ## 13. Referências
308
-
309
- - SDK atual: `mkfashion-sdk/src/mkfashion.js` (v2.7.2)
310
- - Collector: `mk-collector-api/` (rotas `/v1/track/sdk`, agregadores)
311
- - Spec de eventos GA4 ecommerce (usado só como referência de naming dos eventos)
1
+ # SDK v3 — Tracking de E-commerce Completo (Core Tracker) — Design Spec
2
+
3
+ **Empresa:** MetaKosmos
4
+ **Data:** 2026-05-22
5
+ **Status:** Rascunho para revisão
6
+ **Repos afetados:** `mkfashion-sdk`, `mk-collector-api`
7
+
8
+ ---
9
+
10
+ ## 1. Contexto e motivação
11
+
12
+ ### Situação atual (v2.7.2)
13
+
14
+ A `mkfashion-sdk` roda **apenas na PDP** (página de produto). O cliente chama
15
+ `mkfashion.init({ projectId, identifier })` ou `isAvailable(...)` na PDP, e a
16
+ partir daí a SDK:
17
+
18
+ - Auto-captura `page_view`, `page_click`, `page_scroll_depth`, `page_form_submit`,
19
+ `page_engagement` da PDP.
20
+ - Captura eventos do iframe de try-on (`page_modal_opened`, `page_generation_complete`, etc).
21
+ - Detecta `add_to_cart` por heurística de texto de botão.
22
+
23
+ **Limitação:** só enxergamos a PDP. Não vemos a jornada completa do visitante —
24
+ entrada, navegação por categorias, carrinho, checkout e **compra final**. Sem o
25
+ purchase, não conseguimos responder a pergunta de negócio mais importante:
26
+
27
+ > **"Visitantes que usaram o try-on convertem mais do que os que não usaram?"**
28
+
29
+ ### Objetivo da v3
30
+
31
+ Transformar a SDK de "widget de PDP" em **core tracker de e-commerce** que roda
32
+ em **todas as páginas** do site, capturando o funil completo:
33
+
34
+ ```
35
+ entrada → navegação → produto → (try-on) → carrinho → checkout → COMPRA
36
+ ```
37
+
38
+ **Restrições de design (decididas com o stakeholder):**
39
+
40
+ 1. **Zero trabalho extra pro cliente** além de importar o script. Nada de chamar
41
+ métodos novos, nada de configurar dataLayer.
42
+ 2. **Zero dependência de ferramentas de terceiros** (GA4/GTM podem não existir
43
+ na loja). Captura por sinais NATIVOS do e-commerce.
44
+ 3. **Try-on continua igual**: a marca chama `open()`/`isAvailable()` só na PDP,
45
+ exatamente como hoje.
46
+ 4. **Compatibilidade**: clientes na v2 (SDK na PDP) continuam funcionando durante
47
+ a migração.
48
+
49
+ ---
50
+
51
+ ## 2. Princípio central: captura por sinais nativos
52
+
53
+ Toda loja — independente de ter GA4, GTM, ou qualquer analytics — possui 3 coisas
54
+ que existem porque a loja precisa **funcionar e ser indexada**:
55
+
56
+ | Sinal nativo | Sempre existe? | Usado para |
57
+ |---|---|---|
58
+ | **URLs estruturadas** | ✅ sim | detectar etapa do funil (pdp/cart/checkout/purchase) |
59
+ | **Botões/links clicáveis** | ✅ sim | add_to_cart, begin_checkout (heurística de texto) |
60
+ | **HTML semântico** (JSON-LD, meta, DOM) | ✅ quase sempre | produto, preço, order_id, revenue |
61
+
62
+ **Hierarquia de confiança para cada dado:**
63
+
64
+ ```
65
+ Conversão (houve compra?) → URL de confirmação → 100% confiável
66
+ Produto visto → URL /produto + JSON-LD → 100%
67
+ Add to cart → clique em botão (heur.) → ~90%
68
+ Revenue da compra → JSON-LD Order > DOM > meta → best-effort
69
+ ```
70
+
71
+ > **dataLayer é tratado como BÔNUS oportunístico** — se existir, enriquece; se
72
+ > não, ignoramos. Nunca é dependência.
73
+
74
+ ### A distinção crucial: conversão vs revenue
75
+
76
+ - **Conversão (binária)**: "houve uma compra?" → detectável 100% por URL da
77
+ página de confirmação. Já responde a pergunta de negócio principal.
78
+ - **Revenue (valor exato)**: best-effort via JSON-LD/DOM. Se a loja não expõe,
79
+ fica sem valor — mas a conversão continua contada.
80
+
81
+ ---
82
+
83
+ ## 3. Arquitetura em camadas
84
+
85
+ ```
86
+ mkfashion-sdk (v3)
87
+
88
+ ├── core/ ← roda em TODA página
89
+ │ ├── identity.js visitorId + sessionId (localStorage/sessionStorage)
90
+ │ ├── transport.js batch + flush + sendBeacon
91
+ │ ├── pageContext.js detecta tipo de página por URL/JSON-LD
92
+ │ ├── autoTrack.js page_view, click, scroll, form, engagement
93
+ │ └── sanitize.js remove PII e campos pesados antes de enviar
94
+
95
+ ├── ecommerce/ ← NOVO — funil de compra (sinais nativos)
96
+ │ ├── catalog.js view_item_list, select_item, view_item (URL + JSON-LD)
97
+ │ ├── cart.js add_to_cart (heurística), view_cart (URL)
98
+ │ ├── checkout.js begin_checkout (URL/clique), purchase (URL + extração)
99
+ │ └── extractors.js JSON-LD, meta tags, DOM scraping, dataLayer (bônus)
100
+
101
+ └── tryon/ ← módulo existente (sem mudança funcional)
102
+ └── open(), isAvailable(), getAvailability(), postMessage bridge
103
+ ```
104
+
105
+ ### Carregamento
106
+
107
+ ```html
108
+ <!-- Cliente coloca no <head> do TEMA GLOBAL (todas as páginas) -->
109
+ <script src="https://unpkg.com/mkfashion-sdk@3/src/mkfashion.js"
110
+ data-mk-project="698c7c681d3129430f15dddb"
111
+ async></script>
112
+ ```
113
+
114
+ Single source: a SDK detecta a página, inicia o tracking de core + ecommerce, e
115
+ expõe a API de try-on pra ser chamada na PDP.
116
+
117
+ ---
118
+
119
+ ## 4. Detecção de contexto de página
120
+
121
+ `core/pageContext.js` classifica a página em cascata (primeiro match vence):
122
+
123
+ ```js
124
+ function detectPageType() {
125
+ // 1. Override explícito (se o cliente quiser forçar)
126
+ if (window.__mkPageType) return window.__mkPageType
127
+
128
+ // 2. JSON-LD (@type indica tipo de página)
129
+ const ld = readJsonLd()
130
+ if (ld?.['@type'] === 'Product') return 'pdp'
131
+ if (ld?.['@type'] === 'Order' || ld?.['@type'] === 'Invoice') return 'purchase'
132
+ if (ld?.['@type'] === 'CollectionPage' || ld?.['@type'] === 'SearchResultsPage') return 'category'
133
+
134
+ // 3. URL patterns (fallback universal)
135
+ const p = location.pathname.toLowerCase()
136
+ if (p === '/' || p === '') return 'home'
137
+ if (/\/(thank[_-]?you|orders?\/|pedido[_-]?(confirmado|realizado)|sucesso|order[_-]confirmation)/.test(p)) return 'purchase'
138
+ if (/\/checkout/.test(p)) return 'checkout'
139
+ if (/\/(cart|carrinho|sacola|bag)(\/|$|\?)/.test(p)) return 'cart'
140
+ if (/\/(products?|produtos?|p)\//.test(p)) return 'pdp'
141
+ if (/\/(collections?|categoria|c)\//.test(p)) return 'category'
142
+ if (/\/(search|busca|s)(\/|\?)/.test(p)) return 'search'
143
+ return 'other'
144
+ }
145
+ ```
146
+
147
+ Cada `page_view` carrega `pageType` no `params`, dando semântica ao funil.
148
+
149
+ ---
150
+
151
+ ## 5. Mapa de eventos do funil
152
+
153
+ | Etapa | Evento (collector) | Como detecta (sem GA) | Dados |
154
+ |---|---|---|---|
155
+ | Entrada | `page_view` + pageType | sempre | url, referrer, utm, pageType |
156
+ | Listagem | `page_view_item_list` | pageType=category + JSON-LD ItemList | SKUs visíveis |
157
+ | Seleção | `page_select_item` | clique em card de produto (link `/produto/`) | SKU clicado |
158
+ | Produto | `page_view_item` | pageType=pdp + JSON-LD Product | SKU, preço, nome |
159
+ | Try-on | `page_modal_opened`, `page_generation_complete`, ... | já existe (postMessage) | já existe |
160
+ | Add cart | `page_add_to_cart` | heurística de clique (já temos) + JSON-LD price | SKU, texto botão |
161
+ | Carrinho | `page_view_cart` | pageType=cart | — |
162
+ | Checkout | `page_begin_checkout` | pageType=checkout OU clique "Finalizar" | — |
163
+ | **Compra** | `page_purchase` | **pageType=purchase (URL)** | orderId?, revenue?, items? |
164
+
165
+ ---
166
+
167
+ ## 6. Detecção de purchase (o ponto crítico)
168
+
169
+ ### 6.1 Conversão (binária) — 100% confiável
170
+
171
+ Ao detectar `pageType === 'purchase'` (via URL/JSON-LD), emite `page_purchase`
172
+ imediatamente. Isso conta a conversão mesmo sem revenue.
173
+
174
+ ### 6.2 Revenue (best-effort) — cascata de extractors
175
+
176
+ ```js
177
+ function extractOrderData() {
178
+ // 1. JSON-LD Order (existe pro SEO, independente de GA)
179
+ const ld = readJsonLd(['Order', 'Invoice'])
180
+ if (ld) return { orderId: ld.orderNumber || ld.confirmationNumber,
181
+ revenue: ld.price || ld.totalPaymentDue?.price,
182
+ currency: ld.priceCurrency }
183
+
184
+ // 2. dataLayer (BÔNUS — só se a loja tiver GA/GTM)
185
+ const dl = window.dataLayer?.find(x => x.event === 'purchase' && x.ecommerce)
186
+ if (dl) return { orderId: dl.ecommerce.transaction_id,
187
+ revenue: dl.ecommerce.value,
188
+ currency: dl.ecommerce.currency,
189
+ items: dl.ecommerce.items }
190
+
191
+ // 3. Meta tags
192
+ const metaTotal = document.querySelector('meta[property="order:total"]')?.content
193
+ if (metaTotal) return { revenue: parseFloat(metaTotal) }
194
+
195
+ // 4. DOM scraping (fallback frágil — best-effort)
196
+ // procura elementos com classe/texto de total: .order-total, #total, "Total: R$"
197
+ const domTotal = scrapeOrderTotal()
198
+ if (domTotal) return { revenue: domTotal, _source: 'dom_scrape' }
199
+
200
+ return null // conversão já contada; sem revenue
201
+ }
202
+ ```
203
+
204
+ ### 6.3 Dedup
205
+
206
+ Página de confirmação pode recarregar (F5) → mesma compra contada 2x. Dedup por:
207
+ - `orderId` quando disponível (collector ignora purchase com orderId já visto)
208
+ - fallback: 1 purchase por `sessionId` por janela de 1h
209
+
210
+ ### 6.4 Opt-in pra precisão (opcional, não obrigatório)
211
+
212
+ Cliente que QUER revenue garantido e não tem JSON-LD pode chamar 1 linha:
213
+ ```js
214
+ mkfashion.trackPurchase({ orderId: 'X', revenue: 599.80, currency: 'BRL' })
215
+ ```
216
+ Mas é **opcional** — a captura automática cobre a maioria.
217
+
218
+ ---
219
+
220
+ ## 7. A pergunta de negócio que isso destrava
221
+
222
+ Com `visitorId` consistente em todas as páginas + purchase capturado:
223
+
224
+ ```
225
+ Coorte A: visitantes que abriram try-on (page_modal_opened)
226
+ Coorte B: visitantes que NÃO abriram
227
+
228
+ Conversão A = purchases(A) / visitantes(A)
229
+ Conversão B = purchases(B) / visitantes(B)
230
+
231
+ Uplift = Conversão A / Conversão B
232
+ ```
233
+
234
+ Exemplo de resultado esperado:
235
+ ```
236
+ Try-on: 8.2% convertem
237
+ Sem try-on: 3.1% convertem
238
+ Uplift: 2.6x ← argumento de venda do produto MK
239
+ ```
240
+
241
+ ---
242
+
243
+ ## 8. Fases de implementação
244
+
245
+ | Fase | Escopo | Risco |
246
+ |---|---|---|
247
+ | **1** | Refatorar SDK em `core/ ecommerce/ tryon/` sem mudar comportamento | baixo |
248
+ | **2** | `pageContext.js` — detecção de tipo de página | baixo |
249
+ | **3** | `extractors.js` — JSON-LD, meta, DOM, dataLayer(bônus) | médio |
250
+ | **4** | `ecommerce/*` — eventos de funil (view_item, cart, checkout, purchase) | médio |
251
+ | **5** | Backend: novos event types + dedup de purchase + agregação de funil | médio |
252
+ | **6** | Cliente migra snippet pra tema global | baixo (config) |
253
+ | **7** | Dashboard de funil + atribuição try-on→conversão | médio |
254
+
255
+ ---
256
+
257
+ ## 9. Mudanças no backend (mk-collector-api)
258
+
259
+ - Novos `event_name` aceitos (validação já é livre — `page_*`).
260
+ - `sdk-aggregator.service.js`: agregar funil por dia:
261
+ ```js
262
+ funnel: {
263
+ sessions, viewItem, addToCart, beginCheckout, purchase,
264
+ purchaseRevenue, conversionRate
265
+ }
266
+ ```
267
+ - Dedup de purchase por `orderId` (nova lógica no controller ou aggregator).
268
+ - `sdk_daily_metrics`: campos novos `purchases`, `revenue`, `funnel`.
269
+ - Atribuição try-on: cruzar visitors com `page_modal_opened` × `page_purchase`
270
+ (no aggregator ou query sob demanda).
271
+
272
+ ---
273
+
274
+ ## 10. Privacidade / LGPD
275
+
276
+ - **NUNCA** capturar campos de formulário de checkout (email, CPF, endereço, cartão).
277
+ - `core/sanitize.js` filtra chaves sensíveis (`email`, `cpf`, `phone`, `address`,
278
+ `card`, `password`) de qualquer payload antes de enviar.
279
+ - `orderId` e `revenue` não são PII.
280
+ - IP continua sendo descartado server-side (só GeoIP).
281
+
282
+ ---
283
+
284
+ ## 11. Riscos e mitigações
285
+
286
+ | Risco | Mitigação |
287
+ |---|---|
288
+ | Loja sem JSON-LD nem dataLayer | Conversão por URL (100%); revenue fica nulo (aceitável) |
289
+ | URL de confirmação atípica | Lista de patterns extensível + override `window.__mkPageType` |
290
+ | Purchase contado 2x (refresh) | Dedup por orderId / sessionId |
291
+ | DOM scraping frágil | Marcado com `_source: 'dom_scrape'` pra saber confiabilidade |
292
+ | Volume 5-10x maior (todas páginas) | Batching + sampling de eventos de baixo valor |
293
+ | PII vazando | sanitize.js + nunca ler campos de form |
294
+ | SPA sem reload entre páginas | history.pushState hook (já temos) re-detecta pageType |
295
+
296
+ ---
297
+
298
+ ## 12. Decisões em aberto (pra próxima revisão)
299
+
300
+ 1. Nome do pacote: manter `mkfashion-sdk` v3 ou renomear? (decidir depois)
301
+ 2. Sampling: amostrar `page_scroll_depth`/`page_engagement` em sites de alto volume?
302
+ 3. Atribuição try-on→conversão: pré-computar no aggregator ou query sob demanda?
303
+ 4. Dashboard de funil: nova aba ou endpoint `/api/export/funnel`?
304
+
305
+ ---
306
+
307
+ ## 13. Referências
308
+
309
+ - SDK atual: `mkfashion-sdk/src/mkfashion.js` (v2.7.2)
310
+ - Collector: `mk-collector-api/` (rotas `/v1/track/sdk`, agregadores)
311
+ - Spec de eventos GA4 ecommerce (usado só como referência de naming dos eventos)
package/gtm-snippet.js CHANGED
@@ -1,116 +1,116 @@
1
- /**
2
- * mKFashion SDK — Injection Snippet (versão compacta)
3
- *
4
- * Auto-detecta o SKU do produto na PDP, carrega o SDK e renderiza o botão.
5
- * Quando o cliente adiciona ao carrinho dentro do provador, faz POST
6
- * pra /cart/add.js com tamanho/cor se disponíveis.
7
- *
8
- * USO: cole o conteúdo no GTM (Custom HTML tag), no console DevTools de
9
- * uma PDP, ou converta pra bookmarklet.
10
- *
11
- * CONFIG: troque SEU_PROJECT_ID pelo projectId real do mKFashion.
12
- */
13
- (function () {
14
- // Pode ser sobrescrito setando window.MKFASHION_PROJECT_ID antes deste script
15
- // (útil pra GTM Variables ou ambientes de teste).
16
- var PROJECT_ID = window.MKFASHION_PROJECT_ID || 'SEU_PROJECT_ID';
17
-
18
- if (window.mkfashion) return go();
19
- var s = document.createElement('script');
20
- s.src = 'https://unpkg.com/mkfashion-sdk/src/mkfashion.js';
21
- s.async = true;
22
- s.onload = go;
23
- document.head.appendChild(s);
24
-
25
- // Detecta SKU do produto na PDP, em ordem de prioridade.
26
- function detect() {
27
- // 1. Override manual via ?sku= ou ?identifier=
28
- var qs = new URLSearchParams(location.search);
29
- if (qs.get('sku')) return qs.get('sku');
30
- if (qs.get('identifier')) return qs.get('identifier');
31
-
32
- // 2. Konfidency (Marisa, Renner e outras marcas BR usam esse serviço de reviews)
33
- if (window.konfidencyData && window.konfidencyData.sku) {
34
- return String(window.konfidencyData.sku);
35
- }
36
-
37
- // 3. dataLayer (GA4 / Universal Analytics Enhanced Ecommerce)
38
- if (window.dataLayer) {
39
- for (var i = window.dataLayer.length - 1; i >= 0; i--) {
40
- var x = (window.dataLayer[i] || {}).ecommerce;
41
- if (!x) continue;
42
- var p = (x.detail && x.detail.products && x.detail.products[0]) ||
43
- (x.items && x.items[0]);
44
- if (p) return String(p.id || p.item_id);
45
- }
46
- }
47
-
48
- // 4. JSON-LD Schema.org Product
49
- var lds = document.querySelectorAll('script[type="application/ld+json"]');
50
- for (var j = 0; j < lds.length; j++) {
51
- try {
52
- var items = [].concat(JSON.parse(lds[j].textContent));
53
- for (var k = 0; k < items.length; k++) {
54
- var pr = items[k]['@type'] === 'Product' ? items[k] :
55
- (items[k]['@graph'] || []).find(function (g) { return g['@type'] === 'Product'; });
56
- if (pr && (pr.sku || pr.productID)) return String(pr.sku || pr.productID);
57
- }
58
- } catch (e) {}
59
- }
60
-
61
- return null;
62
- }
63
-
64
- function go() {
65
- var sku = detect();
66
- if (!sku) {
67
- console.error('[mKFashion] SKU não detectado. Tente passar ?sku=SEU_SKU na URL.');
68
- return;
69
- }
70
- console.log('[mKFashion] SKU:', sku);
71
-
72
- // Cria container se ainda não existe (overlay fixo no canto como fallback)
73
- if (!document.getElementById('mkfashion-container')) {
74
- var c = document.createElement('div');
75
- c.id = 'mkfashion-container';
76
- c.style.cssText = 'position:fixed;top:90px;right:20px;z-index:99999;background:#fff;padding:8px;border-radius:8px;box-shadow:0 4px 20px rgba(0,0,0,.12)';
77
- document.body.appendChild(c);
78
- }
79
-
80
- // Adiciona ao carrinho da plataforma com tamanho/cor se disponíveis
81
- mkfashion.addToCart(function (p) {
82
- console.log('[mKFashion] addToCart:', p);
83
- var item = { id: p.selectedIdentifier, quantity: 1 };
84
- var props = {};
85
- if (p.selectedSize) props['Tamanho'] = p.selectedSize;
86
- if (p.selectedColor) props['Cor'] = p.selectedColor;
87
- if (Object.keys(props).length) item.properties = props;
88
-
89
- // Endpoint Shopify-style. Para VTEX/Wake/Magento, ajuste a URL e o body.
90
- fetch('/cart/add.js', {
91
- method: 'POST',
92
- headers: { 'Content-Type': 'application/json' },
93
- body: JSON.stringify({ items: [item] })
94
- })
95
- .then(function (r) { return r.json(); })
96
- .then(function (d) { console.log('[mKFashion] no carrinho:', d); })
97
- .catch(function (e) { console.error('[mKFashion] erro cart:', e); });
98
- });
99
-
100
- mkfashion.init({
101
- projectId: PROJECT_ID,
102
- identifier: sku,
103
- target: '#mkfashion-container'
104
- }).then(function (r) {
105
- // Workaround: depois do init() já validar availability, evita re-fetch no
106
- // click handler. Sem isso, em sites com monitor SPA (New Relic, etc.) o
107
- // click intercepta o documento e quebra o fetch com "global scope shutting
108
- // down". Redundante na SDK 2.5.x+ (cache nativo) — mas inofensivo.
109
- if (r && r.ok) {
110
- mkfashion.getAvailability = function () {
111
- return Promise.resolve({ available: true });
112
- };
113
- }
114
- });
115
- }
116
- })();
1
+ /**
2
+ * mKFashion SDK — Injection Snippet (versão compacta)
3
+ *
4
+ * Auto-detecta o SKU do produto na PDP, carrega o SDK e renderiza o botão.
5
+ * Quando o cliente adiciona ao carrinho dentro do provador, faz POST
6
+ * pra /cart/add.js com tamanho/cor se disponíveis.
7
+ *
8
+ * USO: cole o conteúdo no GTM (Custom HTML tag), no console DevTools de
9
+ * uma PDP, ou converta pra bookmarklet.
10
+ *
11
+ * CONFIG: troque SEU_PROJECT_ID pelo projectId real do mKFashion.
12
+ */
13
+ (function () {
14
+ // Pode ser sobrescrito setando window.MKFASHION_PROJECT_ID antes deste script
15
+ // (útil pra GTM Variables ou ambientes de teste).
16
+ var PROJECT_ID = window.MKFASHION_PROJECT_ID || 'SEU_PROJECT_ID';
17
+
18
+ if (window.mkfashion) return go();
19
+ var s = document.createElement('script');
20
+ s.src = 'https://unpkg.com/mkfashion-sdk/src/mkfashion.js';
21
+ s.async = true;
22
+ s.onload = go;
23
+ document.head.appendChild(s);
24
+
25
+ // Detecta SKU do produto na PDP, em ordem de prioridade.
26
+ function detect() {
27
+ // 1. Override manual via ?sku= ou ?identifier=
28
+ var qs = new URLSearchParams(location.search);
29
+ if (qs.get('sku')) return qs.get('sku');
30
+ if (qs.get('identifier')) return qs.get('identifier');
31
+
32
+ // 2. Konfidency (Marisa, Renner e outras marcas BR usam esse serviço de reviews)
33
+ if (window.konfidencyData && window.konfidencyData.sku) {
34
+ return String(window.konfidencyData.sku);
35
+ }
36
+
37
+ // 3. dataLayer (GA4 / Universal Analytics Enhanced Ecommerce)
38
+ if (window.dataLayer) {
39
+ for (var i = window.dataLayer.length - 1; i >= 0; i--) {
40
+ var x = (window.dataLayer[i] || {}).ecommerce;
41
+ if (!x) continue;
42
+ var p = (x.detail && x.detail.products && x.detail.products[0]) ||
43
+ (x.items && x.items[0]);
44
+ if (p) return String(p.id || p.item_id);
45
+ }
46
+ }
47
+
48
+ // 4. JSON-LD Schema.org Product
49
+ var lds = document.querySelectorAll('script[type="application/ld+json"]');
50
+ for (var j = 0; j < lds.length; j++) {
51
+ try {
52
+ var items = [].concat(JSON.parse(lds[j].textContent));
53
+ for (var k = 0; k < items.length; k++) {
54
+ var pr = items[k]['@type'] === 'Product' ? items[k] :
55
+ (items[k]['@graph'] || []).find(function (g) { return g['@type'] === 'Product'; });
56
+ if (pr && (pr.sku || pr.productID)) return String(pr.sku || pr.productID);
57
+ }
58
+ } catch (e) {}
59
+ }
60
+
61
+ return null;
62
+ }
63
+
64
+ function go() {
65
+ var sku = detect();
66
+ if (!sku) {
67
+ console.error('[mKFashion] SKU não detectado. Tente passar ?sku=SEU_SKU na URL.');
68
+ return;
69
+ }
70
+ console.log('[mKFashion] SKU:', sku);
71
+
72
+ // Cria container se ainda não existe (overlay fixo no canto como fallback)
73
+ if (!document.getElementById('mkfashion-container')) {
74
+ var c = document.createElement('div');
75
+ c.id = 'mkfashion-container';
76
+ c.style.cssText = 'position:fixed;top:90px;right:20px;z-index:99999;background:#fff;padding:8px;border-radius:8px;box-shadow:0 4px 20px rgba(0,0,0,.12)';
77
+ document.body.appendChild(c);
78
+ }
79
+
80
+ // Adiciona ao carrinho da plataforma com tamanho/cor se disponíveis
81
+ mkfashion.addToCart(function (p) {
82
+ console.log('[mKFashion] addToCart:', p);
83
+ var item = { id: p.selectedIdentifier, quantity: 1 };
84
+ var props = {};
85
+ if (p.selectedSize) props['Tamanho'] = p.selectedSize;
86
+ if (p.selectedColor) props['Cor'] = p.selectedColor;
87
+ if (Object.keys(props).length) item.properties = props;
88
+
89
+ // Endpoint Shopify-style. Para VTEX/Wake/Magento, ajuste a URL e o body.
90
+ fetch('/cart/add.js', {
91
+ method: 'POST',
92
+ headers: { 'Content-Type': 'application/json' },
93
+ body: JSON.stringify({ items: [item] })
94
+ })
95
+ .then(function (r) { return r.json(); })
96
+ .then(function (d) { console.log('[mKFashion] no carrinho:', d); })
97
+ .catch(function (e) { console.error('[mKFashion] erro cart:', e); });
98
+ });
99
+
100
+ mkfashion.init({
101
+ projectId: PROJECT_ID,
102
+ identifier: sku,
103
+ target: '#mkfashion-container'
104
+ }).then(function (r) {
105
+ // Workaround: depois do init() já validar availability, evita re-fetch no
106
+ // click handler. Sem isso, em sites com monitor SPA (New Relic, etc.) o
107
+ // click intercepta o documento e quebra o fetch com "global scope shutting
108
+ // down". Redundante na SDK 2.5.x+ (cache nativo) — mas inofensivo.
109
+ if (r && r.ok) {
110
+ mkfashion.getAvailability = function () {
111
+ return Promise.resolve({ available: true });
112
+ };
113
+ }
114
+ });
115
+ }
116
+ })();
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mkfashion-sdk",
3
- "version": "2.7.3",
3
+ "version": "2.7.5",
4
4
  "description": "SDK para integrar o provador virtual mKFashion com suporte a Wake Commerce",
5
5
  "main": "src/mkfashion.js",
6
6
  "scripts": {
package/src/mkfashion.js CHANGED
@@ -21,6 +21,12 @@ const mkfashion = {
21
21
  apiUrl: 'https://mkfashion-new-api.mk3dlabs.com',
22
22
  debug: false,
23
23
 
24
+ // GA4 do mkfashion (mesmo ID do visualizer/index.html). Usado pra disparar
25
+ // experience_clicked, que ocorre ANTES do iframe carregar — sem isto, esse evento
26
+ // cairia apenas no GA da loja (se houver), nao no nosso.
27
+ _MK_GA_ID: 'G-LPV8HY4JPQ',
28
+ _gaInitialized: false,
29
+
24
30
  // STAGING - ativado via ?mkStaging=true na URL da página (ver bloco no final)
25
31
  stagingAppUrl: 'https://mkfashion.metakosmoslab.com/visualizer',
26
32
  stagingApiUrl: 'https://mkfashion-api.metakosmoslab.com',
@@ -43,6 +49,8 @@ const mkfashion = {
43
49
  _messageHandler: null,
44
50
  _confirmingExit: false,
45
51
  _projectIdOverride: null, // sobrescreve projectId via ?mkProjectId= (QA/teste)
52
+ _orientation: 'portrait', // 'portrait' | 'landscape' (apenas desktop usa resize via SDK)
53
+ _portraitDimensions: null, // { width, height } salvos pra restaurar ao voltar de landscape
46
54
 
47
55
  _callbacks: {
48
56
  onReady: null,
@@ -72,7 +80,7 @@ const mkfashion = {
72
80
  return
73
81
  }
74
82
 
75
- const raw = this._projectIdOverride || options.projectId || options.projectid || options.store
83
+ const raw = options.projectId || options.projectid || options.store
76
84
  const projectId = this._resolveProjectId(raw)
77
85
  const identifier = (options.identifier || options.sku || '').trim()
78
86
 
@@ -85,6 +93,11 @@ const mkfashion = {
85
93
  return
86
94
  }
87
95
 
96
+ // Track de intencao ANTES do availability check: captura todo clique no botao
97
+ // (inclusive em produtos indisponiveis). Complementa o experience_started, que so
98
+ // dispara quando o iframe carrega — a diferenca entre os dois mostra o drop-off.
99
+ this._trackExperienceClicked(projectId, identifier)
100
+
88
101
  try {
89
102
  const availability = await this.getAvailability(projectId, identifier)
90
103
  if (!availability.available) {
@@ -399,6 +412,52 @@ const mkfashion = {
399
412
 
400
413
  // ============ METODOS PRIVADOS ============
401
414
 
415
+ /**
416
+ * Garante o gtag.js do mkfashion no contexto da loja (idempotente).
417
+ * Se a loja ja tem gtag proprio, reusa window.gtag e so adiciona NOSSO id via config
418
+ * com send_page_view:false (nao infla pageviews da loja). Eventos usam send_to pra
419
+ * nao vazar pro GA da loja.
420
+ */
421
+ _ensureGa() {
422
+ if (this._gaInitialized || typeof window === 'undefined') return
423
+
424
+ if (typeof window.gtag !== 'function') {
425
+ const script = document.createElement('script')
426
+ script.async = true
427
+ script.src = `https://www.googletagmanager.com/gtag/js?id=${this._MK_GA_ID}`
428
+ document.head.appendChild(script)
429
+ window.dataLayer = window.dataLayer || []
430
+ window.gtag = function () { window.dataLayer.push(arguments) }
431
+ window.gtag('js', new Date())
432
+ }
433
+ window.gtag('config', this._MK_GA_ID, { send_page_view: false })
434
+ this._gaInitialized = true
435
+ },
436
+
437
+ /** Dispara experience_clicked no GA4 do mkfashion + callback onInteraction da loja. */
438
+ _trackExperienceClicked(projectId, identifier) {
439
+ try {
440
+ this._ensureGa()
441
+ if (typeof window !== 'undefined' && typeof window.gtag === 'function') {
442
+ window.gtag('event', 'experience_clicked', {
443
+ send_to: this._MK_GA_ID,
444
+ project_id: projectId || 'unknown',
445
+ product_id: identifier || 'unknown'
446
+ })
447
+ }
448
+ } catch (error) {
449
+ console.error('[mKFashion] Erro ao registrar experience_clicked:', error)
450
+ }
451
+ this._triggerCallback('onInteraction', {
452
+ category: 'experience',
453
+ action: 'click',
454
+ projectId,
455
+ identifier,
456
+ product_id: identifier || 'unknown',
457
+ project_id: projectId || 'unknown'
458
+ })
459
+ },
460
+
402
461
  _log(message, data = null) {
403
462
  if (this.debug) {
404
463
  if (data) {
@@ -414,6 +473,9 @@ const mkfashion = {
414
473
  },
415
474
 
416
475
  _resolveProjectId(projectId) {
476
+ // Override de QA via ?mkProjectId= — força esse projeto em TODOS os caminhos
477
+ // (open, init, getAvailability, getProduct), ignorando o que a página passa.
478
+ if (this._projectIdOverride) return this._projectIdOverride
417
479
  if (!projectId) return '698c7e791d3129430f15dddd'
418
480
  const key = String(projectId).toLowerCase()
419
481
  return this._storeAliases[key] || projectId
@@ -581,6 +643,46 @@ const mkfashion = {
581
643
  )
582
644
  },
583
645
 
646
+ /**
647
+ * Desktop: alterna o iframe/modal entre portrait e um formato widescreen (landscape).
648
+ * Em landscape usa ~90vw x 9/16, limitado, centralizado. Idempotente.
649
+ * Mobile NAO usa isto — la a rotacao eh fisica (fullscreen + orientation.lock no iframe).
650
+ */
651
+ _setIframeOrientation(orientation) {
652
+ if (this._orientation === orientation) return
653
+ if (!this._container || !this._iframe) return
654
+
655
+ if (orientation === 'landscape') {
656
+ if (!this._portraitDimensions) {
657
+ this._portraitDimensions = {
658
+ width: this._container.style.width || `${this._config?.width || 430}px`,
659
+ height: this._container.style.height || `${this._config?.height || 800}px`
660
+ }
661
+ }
662
+ // Widescreen: ocupa boa parte da viewport mantendo 16:9, sem estourar a tela.
663
+ const w = Math.min(window.innerWidth * 0.92, 960)
664
+ const h = Math.min(w * 9 / 16, window.innerHeight * 0.92)
665
+ this._applyContainerSize(`${Math.round(w)}px`, `${Math.round(h)}px`)
666
+ } else {
667
+ const dims = this._portraitDimensions || { width: '430px', height: '800px' }
668
+ this._applyContainerSize(dims.width, dims.height)
669
+ }
670
+
671
+ this._orientation = orientation
672
+ this._log(`Orientacao do iframe: ${orientation}`)
673
+ },
674
+
675
+ _applyContainerSize(width, height) {
676
+ if (this._container) {
677
+ this._container.style.width = width
678
+ this._container.style.height = height
679
+ }
680
+ if (this._iframe) {
681
+ this._iframe.style.width = width
682
+ this._iframe.style.height = height
683
+ }
684
+ },
685
+
584
686
  /** Esconde o modal sem remover do DOM (preserva sessao do iframe) */
585
687
  _hideModal() {
586
688
  if (!this._modal) return
@@ -634,6 +736,8 @@ const mkfashion = {
634
736
  this._container = null
635
737
  this._lastConfig = null
636
738
  this._confirmingExit = false
739
+ this._orientation = 'portrait'
740
+ this._portraitDimensions = null
637
741
 
638
742
  if (this._escHandler) {
639
743
  document.removeEventListener('keydown', this._escHandler)
@@ -647,7 +751,10 @@ const mkfashion = {
647
751
  iframe.style.width = typeof width === 'number' ? `${width}px` : width
648
752
  iframe.style.height = typeof height === 'number' ? `${height}px` : height
649
753
  iframe.style.border = 'none'
650
- iframe.setAttribute('allow', 'camera *; microphone *')
754
+ // fullscreen necessario pro "Ver na horizontal" do fluxo de moveis em mobile:
755
+ // o visualizer chama requestFullscreen + screen.orientation.lock de dentro do iframe
756
+ // (aproveitando o gesto do clique local) pra girar a tela no Android.
757
+ iframe.setAttribute('allow', 'camera *; microphone *; web-share *; fullscreen *')
651
758
  iframe.setAttribute('allowfullscreen', 'true')
652
759
  return iframe
653
760
  },
@@ -676,6 +783,14 @@ const mkfashion = {
676
783
  this.restart()
677
784
  break
678
785
 
786
+ case 'request_orientation':
787
+ // Usado pelo fluxo de moveis (furniture) no DESKTOP, onde girar a tela nao faz
788
+ // sentido: o SDK so redimensiona o iframe/modal pra um formato widescreen.
789
+ // Em mobile a rotacao acontece dentro do iframe (fullscreen + orientation.lock),
790
+ // entao mobile nao manda esta mensagem.
791
+ this._setIframeOrientation(data === 'landscape' ? 'landscape' : 'portrait')
792
+ break
793
+
679
794
  case 'ready':
680
795
  this._log('App pronta', data)
681
796
  this._triggerCallback('onReady', data)
@@ -824,7 +939,7 @@ const mkfashion = {
824
939
  async init(options = {}) {
825
940
  const fail = reason => ({ ok: false, reason, element: null })
826
941
 
827
- const raw = this._projectIdOverride || options.projectId || options.projectid || options.store
942
+ const raw = options.projectId || options.projectid || options.store
828
943
  const projectId = this._resolveProjectId(raw)
829
944
  const identifier = (options.identifier || options.sku || '').trim()
830
945
  const target = options.target