Integración

Esta página es la guía para consumidores que embeben mas-consents desde otra aplicación. Describe el contrato de URL, mensajería con el host, llamadas backend que realiza el componente y el listener mínimo que debería implementar un host.

Destinos de embebido

mas-consents puede embeberse en:

  • un iframe de navegador
  • un flujo browser opener/popup
  • WebView de React Native
  • WKWebView de iOS
  • WebView de Android

Parámetros de URL

El frontend lee la query string una sola vez durante el arranque. Para llamadas reales al backend, el host debe proporcionar contexto suficiente para la API de consents.

ParámetroComportamiento de runtimeExpectativa para consumidores
tokenSe reenvía como Authorization: Bearer <token> cuando está presenteObligatorio para llamadas reales al backend
customerIdSe usa en las rutas GET/POSTObligatorio para llamadas reales al backend
brandSe usa en las rutas GET/POST. mock activa comportamiento mock localObligatorio para llamadas reales al backend
sectorSe reenvía como header x-sectorObligatorio para llamadas reales al backend
viewLos parámetros repetidos se preservan y se envían como query params repetidosObligatorio en la mayoría de flujos host
langSe normaliza contra la lista de locales soportados. Si falta o es inválido, cae a esRecomendado
localesLista opcional separada por comas. Por defecto usa todos los locales soportadosOpcional
choiceFiltro opcional que se pasa al backendOpcional
actorIdSe incluye en el body POST como actor_idEsperado para flujos reales de submit
sellChannelSe incluye en el body POST como sell_channelEsperado para flujos reales de submit
traceIdSe incluye en el body POST como trace_idEsperado para trazabilidad y submit
enableCustomConfigSolo el valor exacto true tiene efecto. Hace que el componente espere configuración JSON del hostOpcional
hostCommunicationopener activa el modo de comunicación con browser openerOpcional
hostOriginObligatorio con hostCommunication=opener; los orígenes inválidos se ignoranOpcional salvo que se use modo opener

Locales soportados:

  • es
  • eu
  • ca
  • en
  • gl

Valores soportados de view:

  • MANDATORY
  • PORTFOLIO
  • SGDA
  • INTERNAL
  • PRIVATE
  • WHATSAPP
  • OGW_DEVICE_SWAP
  • OGW_SIM-SWAP
  • OGW_NUMBER-VERIFICATION
  • OGW_KYC-MATCH

Varios views se pasan como parámetros repetidos:

text

?view=MANDATORY&view=PORTFOLIO

Valores soportados de choice:

  • PENDING
  • REJECTED
  • ACCEPTED

Limpieza de cadena de consulta

El componente lee los parámetros de URL durante el arranque y después limpia la cadena de consulta de la URL del navegador. La única excepción es brand=mock, que conserva la cadena de consulta visible para facilitar depuración local.

Mensajería con el host

El bridge emite mensajes estructurados y mantiene eventos de texto del navegador por compatibilidad. Las integraciones nuevas deberían soportar mensajes estructurados.

Mensajes estructurados salientes

Los hosts en iframe reciben mensajes estructurados como objetos:

json

{
  "source": "consents",
  "version": "v1",
  "type": "CONSENTS_LOADED",
  "payload": {}
}

React Native, Android WebView, iOS WebView y los flujos browser opener reciben el mismo payload serializado como texto JSON. Los hosts de esos entornos deben parsear el texto antes de leer source o type.

Tipos de mensaje saliente:

  • CONSENTS_CONFIG_REQUIRED: se emite solo cuando enableCustomConfig=true
  • CONSENTS_LOADED: se emite después de cargar consentimientos
  • CONSENTS_COMPLETED: se emite tras un submit correcto o cuando el backend indica que la campaña ya está completada
  • CONSENTS_ERROR: se emite con payload de error backend cuando falla la carga o el submit

Eventos de texto de compatibilidad

Los hosts en iframe también reciben mensajes de texto planos:

  • consents-loaded
  • consents-success
  • consents-error

Estos eventos existen por compatibilidad con integraciones host antiguas. No deberían ser el único contrato para consumidores nuevos.

Mensajes entrantes

El componente acepta actualmente un mensaje enviado por el host:

  • SET_CONFIG

SET_CONFIG solo es obligatorio cuando la URL contiene enableCustomConfig=true. Sin ese flag, el componente renderiza inmediatamente con el tema por defecto e ignora SET_CONFIG inbound.

El mensaje puede enviarse como objeto en integraciones iframe o como texto JSON en integraciones de bridge nativo:

json

{
  "type": "SET_CONFIG",
  "payload": {
    "theme": "light"
  }
}

Escucha recomendada para navegador

ts

const iframe = document.querySelector<HTMLIFrameElement>('#mas-consents')

window.addEventListener('message', (event) => {
  const data = event.data

  if (typeof data === 'string') {
    if (data === 'consents-loaded') {
      // Señal de ready de compatibilidad
    }
    if (data === 'consents-success') {
      // Señal de finalización de compatibilidad
    }
    if (data === 'consents-error') {
      // Señal de error de compatibilidad
    }
    return
  }

  if (data?.source !== 'consents') return

  switch (data.type) {
    case 'CONSENTS_CONFIG_REQUIRED':
      iframe?.contentWindow?.postMessage(
        {
          type: 'SET_CONFIG',
          payload: {
            theme: 'light',
          },
        },
        '*',
      )
      break
    case 'CONSENTS_LOADED':
      // Señal ready
      break
    case 'CONSENTS_COMPLETED':
      // Señal de finalización
      break
    case 'CONSENTS_ERROR':
      // El payload de error backend está disponible en data.payload
      break
  }
})

Las aplicaciones host deben validar event.origin según su propio modelo de seguridad.

Llamadas HTTP que realiza el componente

El host no llama directamente al backend de consents para el flujo embebido. El componente realiza estas llamadas usando el contexto de URL.

GET consents

text

GET /v2/orgs/{brand}/customers/{customerId}/consents

Headers reenviados:

  • Authorization: Bearer <token> cuando existe token
  • Accept-Language desde el locale normalizado
  • x-sector desde la URL
  • x-key si fue devuelto por una respuesta anterior

Query params reenviados:

  • view repetido
  • choice opcional
  • consent_ids opcional cuando se proporciona CustomConfig.consentIds
  • display_format=SHORT opcional en móvil xs

POST consents

text

POST /v2/orgs/{brand}/customers/{customerId}/consents

Headers reenviados:

  • Authorization: Bearer <token> cuando existe token
  • Accept-Language
  • x-sector
  • x-key capturado de la respuesta GET previa

El body POST incluye actor_id, sell_channel, trace_id, selecciones del cliente, IP y fecha de captura.

Configuración de plataforma de runtime

El frontend también carga /config.json al arrancar. Este archivo lo aporta el despliegue y normalmente no lo suministra el host consumidor.

Campos soportados:

  • ENV
  • API_GATEWAY
  • FARO_COLLECTOR_URL
  • IS_OBSERVABILITY_ENABLED
  • TRACING_WHITELIST_URLS

Si /config.json no puede cargarse y el runtime sigue en LOCAL, se mantiene la configuración local por defecto.

Ejemplos

Los ejemplos siguientes usan enableCustomConfig=true para mostrar el handshake completo. Si el host no necesita personalizar el componente, elimina enableCustomConfig=true de la URL y no envíes SET_CONFIG.

Iframe de navegador

html

<iframe
  id="mas-consents"
  src="https://consents.masstack.com/?token=JWT&customerId=123&brand=masmovil&sector=telco&view=MANDATORY&lang=es&actorId=agent-123&sellChannel=ecare&traceId=trace-123&enableCustomConfig=true"
  style="width: 100%; height: 900px; border: 0;"
></iframe>

<script>
  const iframe = document.getElementById('mas-consents')

  window.addEventListener('message', (event) => {
    const data = event.data

    if (typeof data === 'string') {
      if (data === 'consents-success') {
        window.location.href = '/next-step'
      }
      if (data === 'consents-error') {
        console.error('Consents failed')
      }
      return
    }

    if (data?.source !== 'consents') return

    if (data.type === 'CONSENTS_CONFIG_REQUIRED') {
      iframe.contentWindow?.postMessage(
        {
          type: 'SET_CONFIG',
          payload: {
            theme: 'light',
            uiOptions: {
              groupDisplay: 'flat',
              collapsible: true,
            },
          },
        },
        '*',
      )
    }

    if (data.type === 'CONSENTS_COMPLETED') {
      window.location.href = '/next-step'
    }

    if (data.type === 'CONSENTS_ERROR') {
      console.error(data.payload)
    }
  })
</script>

Popup/opener de navegador

ts

const consentsUrl = new URL('https://consents.masstack.com/')

consentsUrl.search = new URLSearchParams({
  token: 'JWT',
  customerId: '123',
  brand: 'masmovil',
  sector: 'telco',
  view: 'MANDATORY',
  lang: 'es',
  actorId: 'agent-123',
  sellChannel: 'ecare',
  traceId: 'trace-123',
  enableCustomConfig: 'true',
  hostCommunication: 'opener',
  hostOrigin: window.location.origin,
}).toString()

const popup = window.open(consentsUrl.toString(), 'mas-consents', 'width=480,height=900')

window.addEventListener('message', (event) => {
  if (event.origin !== 'https://consents.masstack.com') return

  const data = typeof event.data === 'string' ? JSON.parse(event.data) : event.data
  if (data?.source !== 'consents') return

  if (data.type === 'CONSENTS_CONFIG_REQUIRED') {
    popup?.postMessage(
      JSON.stringify({
        type: 'SET_CONFIG',
        payload: {
          theme: 'light',
        },
      }),
      'https://consents.masstack.com',
    )
  }

  if (data.type === 'CONSENTS_COMPLETED') {
    popup?.close()
  }
})

React Native WebView

tsx

import { useRef } from 'react'
import { WebView } from 'react-native-webview'

const consentsUrl =
  'https://consents.masstack.com/?token=JWT&customerId=123&brand=masmovil&sector=telco&view=MANDATORY&lang=es&actorId=agent-123&sellChannel=app&traceId=trace-123&enableCustomConfig=true'

export function ConsentsScreen() {
  const webViewRef = useRef<WebView>(null)

  const sendConfig = () => {
    const message = JSON.stringify({
      type: 'SET_CONFIG',
      payload: {
        theme: 'light',
        uiOptions: {
          groupDisplay: 'flat',
        },
      },
    })

    webViewRef.current?.injectJavaScript(`
      window.dispatchEvent(new MessageEvent('message', { data: ${JSON.stringify(message)} }));
      true;
    `)
  }

  return (
    <WebView
      ref={webViewRef}
      source={{ uri: consentsUrl }}
      onMessage={(event) => {
        const data = JSON.parse(event.nativeEvent.data)

        if (data.type === 'CONSENTS_CONFIG_REQUIRED') {
          sendConfig()
        }

        if (data.type === 'CONSENTS_COMPLETED') {
          // Continuar el flujo host
        }

        if (data.type === 'CONSENTS_ERROR') {
          // Gestionar data.payload
        }
      }}
    />
  )
}

iOS WKWebView

swift

import UIKit
import WebKit

final class ConsentsViewController: UIViewController, WKScriptMessageHandler {
  private lazy var webView: WKWebView = {
    let contentController = WKUserContentController()
    contentController.add(self, name: "consentsBridge")

    let config = WKWebViewConfiguration()
    config.userContentController = contentController
    return WKWebView(frame: .zero, configuration: config)
  }()

  override func viewDidLoad() {
    super.viewDidLoad()
    view = webView

    var components = URLComponents(string: "https://consents.masstack.com/")!
    components.queryItems = [
      URLQueryItem(name: "token", value: "JWT"),
      URLQueryItem(name: "customerId", value: "123"),
      URLQueryItem(name: "brand", value: "masmovil"),
      URLQueryItem(name: "sector", value: "telco"),
      URLQueryItem(name: "view", value: "MANDATORY"),
      URLQueryItem(name: "lang", value: "es"),
      URLQueryItem(name: "actorId", value: "agent-123"),
      URLQueryItem(name: "sellChannel", value: "ios"),
      URLQueryItem(name: "traceId", value: "trace-123"),
      URLQueryItem(name: "enableCustomConfig", value: "true")
    ]

    webView.load(URLRequest(url: components.url!))
  }

  func userContentController(
    _ userContentController: WKUserContentController,
    didReceive message: WKScriptMessage
  ) {
    guard let raw = message.body as? String,
          let data = raw.data(using: .utf8),
          let payload = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
          payload["source"] as? String == "consents"
    else { return }

    if payload["type"] as? String == "CONSENTS_CONFIG_REQUIRED" {
      let config = #"{"type":"SET_CONFIG","payload":{"theme":"light"}}"#
      webView.evaluateJavaScript(
        "window.dispatchEvent(new MessageEvent('message', { data: '\(config)' }));"
      )
    }
  }
}

Android WebView

kotlin

class ConsentsActivity : AppCompatActivity() {
  private lateinit var webView: WebView

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    webView = WebView(this)
    setContentView(webView)

    webView.settings.javaScriptEnabled = true
    webView.addJavascriptInterface(ConsentsBridge(), "NativeBridge")

    val url = Uri.parse("https://consents.masstack.com/").buildUpon()
      .appendQueryParameter("token", "JWT")
      .appendQueryParameter("customerId", "123")
      .appendQueryParameter("brand", "masmovil")
      .appendQueryParameter("sector", "telco")
      .appendQueryParameter("view", "MANDATORY")
      .appendQueryParameter("lang", "es")
      .appendQueryParameter("actorId", "agent-123")
      .appendQueryParameter("sellChannel", "android")
      .appendQueryParameter("traceId", "trace-123")
      .appendQueryParameter("enableCustomConfig", "true")
      .build()

    webView.loadUrl(url.toString())
  }

  inner class ConsentsBridge {
    @JavascriptInterface
    fun postMessage(raw: String) {
      val message = JSONObject(raw)
      if (message.optString("source") != "consents") return

      if (message.optString("type") == "CONSENTS_CONFIG_REQUIRED") {
        val config = JSONObject()
          .put("type", "SET_CONFIG")
          .put("payload", JSONObject().put("theme", "light"))
          .toString()

        runOnUiThread {
          webView.evaluateJavascript(
            "window.dispatchEvent(new MessageEvent('message', { data: ${JSONObject.quote(config)} }));",
            null,
          )
        }
      }
    }
  }
}