Docs do Nodaro
DocumentaçãoReferência de nósModelosAgentes de IA (MCP)DesenvolvedoresSelf-hostingPesquisa
Incorporações

Incorporar o viewport de cena 3D

Incorpore o viewport de cena 3D do Nodaro em um iframe e controle-o com postMessage: handshake, mensagens de estado, eventos de edição, assets e limites.

O viewport de cena 3D é o visualizador 3D de pré-visualização do Nodaro, que você pode incorporar em um iframe dentro do seu próprio app, em /embed/scene3d. É o mesmo visualizador que o editor do Nodaro usa: um viewport three.js, controles de reprodução, uma lista de objetos, editores numéricos de pose e um histórico de revisões. O iframe é controlado inteiramente por postMessage: sua página guarda a cena, e o iframe a desenha e informa o que o usuário fez.

Esta não é a incorporação de miniapp. Um miniapp executa um workflow publicado; o viewport não executa nada. Veja Incorporações para a diferença, e Formato de cena 3D para os dados que ele desenha.

O que o iframe faz e não faz

LêSó o parentOrigin e o channel da própria URL, e as mensagens state dessa origem exata.
DesenhaA cena que você envia: o viewport, a reprodução e a navegação na linha do tempo, a lista de objetos, os editores de pose de cada objeto e da câmera, o histórico de revisões e o aviso de revisão pendente. Para uma cena pré-calculada (versão 2): a câmera pré-calculada, a navegação entre tomadas, as entidades semânticas com as cores de identificação delas e os controles de sobreposição.
Enviaready quando começa a escutar, depois um event por ação do usuário e um asset-request por asset declarado de uma cena da versão 2.
Nunca fazAutenticar-se, ler ou gravar no armazenamento, chamar um endpoint /v1, guardar algo de um carregamento para outro, ou avançar a própria visualização depois de uma edição.

O iframe é sem estado por design. Quando o usuário confirma uma edição, o iframe envia para você uma nova revisão imutável e continua mostrando a antiga até que você envie o resultado de volta. Um iframe que se adiantasse à página pai poderia mostrar uma revisão que nunca chega a ser salva.

O iframe não guarda nenhum segredo e pode ser carregado com segurança em uma página que você não controla totalmente. Os dados de cena que você envia a ele só são tão privados quanto a origem para a qual você os endereça, então sempre envie para uma origem exata; veja o checklist da página pai.

Verificar se o viewport está disponível

Em uma implantação que inclui o viewport, o nó Gerar cena 3D (Generate 3D Scene) anuncia o recurso scene3d-embed-v1 em GET /v1/nodes. Verifique esse recurso antes de oferecer o editor incorporado.

A URL

https://app.nodaro.ai/embed/scene3d?parentOrigin=<origin>&channel=<uuid>
ParâmetroObrigatórioRegra
parentOriginSimA origem exata e normalizada da página que incorpora o iframe: um esquema, um host e uma porta opcional, nada mais. new URL(value).origin precisa ser igual ao valor. Só http: e https:, até 255 caracteres. Uma barra no final, um caminho, uma query, informações de usuário, null ou qualquer outro esquema são recusados.
channelSimUm UUID aleatório novo, de crypto.randomUUID(), um por iframe montado. Ele impede que dois viewports na mesma página, ou um iframe antigo de uma caixa de diálogo fechada, redesenhem um ao outro.

MessageEvent.origin chega normalizado pelo navegador. Exigir o parâmetro com a mesma grafia permite que o iframe compare os dois exatamente, em vez de adivinhar. Se um parâmetro estiver ausente ou malformado, o iframe mostra um erro, não envia nada e não escuta nada.

O handshake

  1. Sua página adiciona o iframe com a URL dele.
  2. O iframe é montado e começa a escutar. Ele envia nodaro:scene3d:ready e repete o envio até você responder.
  3. Sua página responde com uma mensagem nodaro:scene3d:state. O iframe valida a cena e a desenha.
  4. Quando o usuário age, o iframe envia um nodaro:scene3d:event.
  5. Sua página verifica expectedRevisionId, aplica o evento e envia um novo state: a nova verdade.

Envie o seu primeiro state em resposta ao ready, não no evento load do iframe. O load dispara antes de o listener do iframe estar pronto, então uma mensagem enviada nesse momento pode ser descartada. O iframe repete o ready em um intervalo curto e limitado, o que cobre uma página pai que começa a escutar um pouco tarde. Ele para depois da sua primeira mensagem endereçada a ele, mesmo uma que ele recuse: uma recusa ainda prova que você está escutando.

Da página pai para o iframe: state

Existe um único tipo de mensagem, e ele carrega um snapshot completo. Não existe atualização parcial: sempre envie o estado completo que você quer desenhado.

{
  type: "nodaro:scene3d:state",
  version: 1 | 2,                     // 2 enables baked (version 2) scenes, see below
  channel: string,                    // must equal the channel in the URL
  scenePlan: Scene3DPlan,             // required, validated
  selectedObjectIds?: string[],       // default []
  lockedObjectIds?: string[],         // default []
  history?: Scene3DRevisionEntry[],   // default []
  pendingPlan?: Scene3DPlan,          // default absent
  isGenerating?: boolean,             // default false
  readOnly?: boolean,                 // default TRUE
}

Uma entrada de histórico:

{
  revisionId: string,                     // UUID
  scenePlan: Scene3DPlan,                 // validated like the active plan
  source: "generate" | "edit" | "manual" | "upstream",
  createdAt: string,                      // your timestamp, shown as it is
  changeSummary?: string,
  context?: { prompt?: string },          // only prompt is kept
}

scenePlan é o Scene3DPlan público do pacote @nodaro/shared: o mesmo formato que um job de Gerar cena 3D ou de Editar cena 3D (Edit 3D Scene) retorna em output_data.scenePlan.

  • readOnly vem como true por padrão. O silêncio significa: olhe, mas não toque. Um iframe editável precisa dizer readOnly: false.
  • context mantém só prompt, que vira a dica (tooltip) do botão de restaurar. Outras chaves dentro de context são descartadas em silêncio. Chaves desconhecidas em qualquer outro lugar são recusadas; veja Versionamento.
  • isGenerating: true só adiciona um aviso de que uma revisão está sendo gerada. A cena continua ativa e, quando não é somente leitura, editável.
  • pendingPlan cobre um job que terminou depois que o usuário editou. O iframe mostra um aviso e, quando editável, dois botões para escolher uma revisão.

O que o iframe ignora e o que ele recusa

O iframe recebeEle
Uma mensagem cujo event.source não é a janela que incorpora o iframeIgnora a mensagem, em silêncio.
Uma mensagem cujo event.origin não é parentOriginIgnora a mensagem, em silêncio.
Outro type, outro channel ou um payload que não é um objetoIgnora a mensagem, em silêncio.
Uma version desconhecidaRecusa a mensagem, de forma visível.
Um envelope malformado, uma chave desconhecida ou uma lista acima do limiteRecusa a mensagem, de forma visível.
Um scenePlan, um pendingPlan ou qualquer history[].scenePlan que falhe na validaçãoRecusa a mensagem, de forma visível.

Ignorar é silencioso, porque o tráfego de outro iframe não é problema do usuário. Recusar é visível, porque foi a sua mensagem que estava errada. Uma recusa nunca destrói dados aceitos: o último snapshot aceito continua na tela, sob um banner que informa o motivo, e o próximo state válido o remove.

Do iframe para a página pai: ready e event

O iframe envia as duas mensagens para o parentOrigin exato da URL dele, nunca para "*".

{
  type: "nodaro:scene3d:ready",
  version: 1,                              // the baseline, always 1
  channel: string,
  protocolVersions: [1, 2],                // every version this frame accepts
  capabilities: {
    assetTransport: true,                  // it can ask you for asset bytes
    sceneSchemaVersions: [1, 2],           // the scene versions it can draw
  },
}

Identifique o ready pelo type e pelo channel, e leia protocolVersions. Não compare a mensagem inteira. version continua 1, então uma página pai escrita para a versão 1 continua funcionando; tudo o que a versão 2 acrescenta é anunciado em campos que essa página pai não lê.

{
  type: "nodaro:scene3d:event",
  version: 1 | 2,                     // 2 ONLY for edit-operations
  channel: string,
  expectedRevisionId: string | null,
  event:
    | { kind: "plan", plan: Scene3DPlan, changeSummary: string }
    | { kind: "selection", objectIds: string[] }
    | { kind: "locks", objectIds: string[] }
    | { kind: "restore", revisionId: string }
    | { kind: "resolve-pending", adopt: boolean }
    // version 2 scenes only, see "Baked scenes" below
    | { kind: "edit-operations", operations: Scene3DV2EditOperation[], expectedContentHash: string },
}

O envelope diz version: 2 só para edit-operations. Todos os outros tipos de evento continuam dizendo 1, então uma página pai da versão 1 nunca encontra uma versão desconhecida em uma mensagem que ela entende.

expectedRevisionId é a revisão que o iframe mostrava quando o usuário agiu, a base a partir da qual a ação foi calculada. Aplique o evento só se ele ainda for igual ao seu scenePlan.revisionId ativo; caso contrário, descarte-o. Essa única verificação faz com que um clique em um snapshot que você já substituiu não tenha efeito, em vez de causar uma reversão silenciosa. Ele só é null antes de o iframe aceitar algum estado.

kindSignificadoO que você faz
planUma edição local, como uma pose numérica ou uma mudança de cor, produziu uma nova revisão imutável. plan.parentRevisionId é expectedRevisionId. Nenhum modelo foi executado e nada foi cobrado.Torne plan a revisão ativa, adicione-a ao histórico e envie um novo state.
selectionO usuário selecionou ou desmarcou objetos.Reproduza a seleção e envie state, ou apenas guarde-a.
locksO usuário bloqueou ou desbloqueou objetos. Um objeto bloqueado é um objeto que um job de edição precisa deixar idêntico byte a byte.Salve a alteração e envie state.
restoreO usuário pediu uma revisão anterior do histórico que você enviou.Torne essa revisão ativa e envie state.
resolve-pendingadopt: true usa o pendingPlan; false mantém o plano atual.Resolva e envie state sem pendingPlan.
edit-operationsUma edição de sobreposição da versão 2, na forma de operações. O iframe não a aplica.Aplique a edição com o aplicador compartilhado, salve o resultado e envie de volta o plano salvo. Veja Editar uma cena pré-calculada.

Modo somente leitura

readOnly, que é true por padrão, mantém o visualizador inteiro e bloqueia todas as gravações:

Continua funcionandoBloqueado
Reproduzir, pausar e percorrer a linha do tempoConfirmações numéricas de pose, de objetos e da câmera
Selecionar no viewport e na listaCores de objetos e do fundo
Ler a lista de objetos, os selos de bloqueio e o histórico de revisõesBotões de bloqueio
O aviso de revisão pendenteRestaurar e os botões de revisão pendente

O único evento que um iframe somente leitura emite é selection. A regra vale nos dois sentidos: os controles que alterariam a cena ficam desativados ou ocultos, e o iframe se recusa a emitir uma alteração mesmo que um controle seja acionado diretamente. Somente leitura é uma propriedade do iframe, não um estilo.

Cenas pré-calculadas e o transporte de assets

Uma cena da versão 1 é autossuficiente: primitivas, quadros-chave e uma câmera, tudo dentro do plano que você envia. Uma cena da versão 2 é geometria pré-calculada (baked). O manifesto dela traz entidades semânticas, tomadas, uma trilha de câmera pré-calculada e uma lista de ids de assets com hashes SHA-256, enquanto os bytes em si ficam atrás da API autenticada do Nodaro.

O iframe continua sem sessão e não faz nenhuma chamada de rede. Em vez disso, ele pede a você exatamente os assets que o manifesto declara, e você os busca com a sua própria sessão.

Ativar

Envie version: 2 na sua mensagem state. Uma cena da versão 2 em uma mensagem version: 1 é recusada com scenePlan — this scene uses schema version 2, which needs embed protocol version 2 (asset transport): uma página pai da versão 1 não tem tratamento para pedidos de assets, e o iframe ficaria esperando para sempre. As cenas da versão 1 funcionam com qualquer uma das versões.

As mensagens

// frame to parent
{
  type: "nodaro:scene3d:asset-request",
  version: 2,
  channel: string,
  requestId: string,                 // new for each request; send it back unchanged
  revisionId: string,                // plan.revisionId
  assetId: string,                   // an opaque id from plan.assets
  kind: "glb" | "camera-track-json",
  byteLength: number,                // what the manifest declares
  sha256: string,                    // 64 lower-case hex characters
}

// parent to frame, success
{
  type: "nodaro:scene3d:asset-response",
  version: 2,
  channel: string,
  requestId: string,                 // echoed
  revisionId: string,                // echoed
  assetId: string,                   // echoed
  ok: true,
  bytes: ArrayBuffer,                // a structured clone: NOT a URL, NOT base64
}

// parent to frame, failure
{ /* same envelope */ ok: false, error: "short reason" }

revisionId é a revisão armazenada atual do plano. Cada revisão armazenada fixa todos os seus assets, incluindo os bytes que reaproveita de uma revisão anterior.

O que o iframe verifica

O iframe ignora em silêncio o tráfego normal que não é para ele:

  • Uma resposta de outra janela, de outra origem ou de outro channel.
  • Um requestId que não está pendente, como uma resposta atrasada depois de um tempo limite, uma duplicata ou bytes que ninguém pediu.

Ele faz aquele asset falhar, de forma visível, nestes casos:

  • version não é 2, ou um campo é desconhecido ou está ausente.
  • revisionId ou assetId não é o que foi pedido.
  • A resposta é ok: false. O iframe mostra o seu error, encurtado.
  • bytes não é um ArrayBuffer.
  • O tamanho não é o byteLength do manifesto.
  • O hash SHA-256 não é o hash do manifesto.

A verificação do hash é a que importa: ela separa “o host entregou alguns bytes” de “estes são os bytes de que esta revisão é feita”. O renderizador verifica o hash uma segunda vez antes do parsing, então nenhum caminho desenha geometria não verificada.

O iframe também impõe limites a si mesmo:

  • No máximo 4 pedidos ficam em andamento, e o resto espera em uma fila.
  • No máximo 64 pedidos e 64 MiB são permitidos por revisão.
  • Cada pedido tem 20 segundos para ser respondido.
  • Todo pedido pendente é rejeitado quando chega uma nova revisão ou quando o iframe é desmontado, então uma resposta para uma cena que o usuário deixou nunca é desenhada.
  • Assets idênticos, com o mesmo id e o mesmo hash, são pedidos uma vez, então uma revisão de sobreposição que reaproveita um GLB não o baixa de novo.

Regras para a sua página

  1. Autorize com base no que você enviou. Só responda quando request.revisionId for a revisão que você enviou (plan.revisionId) e request.assetId estiver nos assets desse plano, com o mesmo byteLength e o mesmo sha256. Caso contrário, responda ok: false. Se você pular essa verificação, a sua sessão vira um oráculo: quem controlar a página dentro do iframe pode pedir qualquer revisão ou asset e ler a resposta.
  2. Nunca envie uma credencial. Nem token, nem cookie, nem URL assinada: uma URL que concede acesso é um token bearer escrito de outro jeito. Envie bytes.
  3. Envie para a origem exata do iframe, nunca para "*".
  4. Não transfira um buffer que você mantém. Um structured clone o copia; uma lista de transferência o tiraria de você.
  5. Descarte pedidos de uma revisão que você já substituiu, e mantenha no máximo uma busca em andamento por revisão e asset.
import { createClient } from "@nodaro/sdk"
import type { Scene3DPlanV2 } from "@nodaro/shared"

const client = createClient({ baseUrl: "https://app.nodaro.ai", auth: myAuth })

/** The plan you sent most recently. */
let active: Scene3DPlanV2

window.addEventListener("message", async (event) => {
  if (event.source !== iframe.contentWindow) return
  if (event.origin !== NODARO_ORIGIN) return
  const data = event.data
  if (data?.type !== "nodaro:scene3d:asset-request") return
  if (data.channel !== channel || data.version !== 2) return

  const reply = (body: Record<string, unknown>) =>
    iframe.contentWindow?.postMessage(
      {
        type: "nodaro:scene3d:asset-response",
        version: 2,
        channel,
        requestId: data.requestId,
        revisionId: data.revisionId,
        assetId: data.assetId,
        ...body,
      },
      NODARO_ORIGIN,
    )

  // 1. Authorize: the asset must belong to the plan we sent, and the request
  //    must name the revision that plan pins the bytes to.
  const asset = active.assets.find((a) => a.assetId === data.assetId)
  const pinnedTo = active.revisionId
  if (
    !asset ||
    data.revisionId !== pinnedTo ||
    asset.byteLength !== data.byteLength ||
    asset.sha256 !== data.sha256
  ) {
    reply({ ok: false, error: "unknown asset" })
    return
  }

  // 2. Fetch with OUR session, and send the bytes, never the URL or the token.
  try {
    const bytes = await client.scene3d.assetBytes(pinnedTo, asset)
    reply({ ok: true, bytes })
  } catch (error) {
    reply({ ok: false, error: error instanceof Error ? error.message : "fetch failed" })
  }
})

Editar uma cena pré-calculada

As edições da versão 2 são sobreposições, e o iframe nunca aplica uma sozinho. Uma revisão da versão 2 que o seu servidor ainda não armazenou pareceria salva enquanto os assets dela ainda pertencem à revisão de origem. Por isso, o iframe envia as operações e continua mostrando a revisão que você enviou:

// event.event
{
  kind: "edit-operations",
  operations: [
    { op: "set-override", override: { kind: "entity-transform", entityId: "hero", space: "local", position: [3, 0.5, 0] } },
  ],
  expectedContentHash: "<64 hex characters>",   // plan.provenance.contentHash
}

Passe os três valores de verificação de revisão desatualizada para o aplicador compartilhado, o mesmo que a API usa, e depois salve o resultado e envie-o de volta:

import { applyScene3DV2EditOperations } from "@nodaro/shared"

const result = await applyScene3DV2EditOperations(active, message.event.operations, {
  expectedRevisionId: message.expectedRevisionId,          // from the envelope
  expectedContentHash: message.event.expectedContentHash,
  lockedObjectIds,
})
if (!result.ok) return showError(result.message)           // stale_revision, locked, ...

Uma substituição nomeia só os canais que altera, e o iframe a monta assim de propósito. Em uma entidade de asset, a transformação do nó no arquivo GLB é a referência, então uma edição que moveu algo não deve também repetir uma rotação que nunca tocou. Para salvar uma edição sem um job de geração, chame POST /v1/3d-scene/revisions/:revisionId/edits, ou client.scene3d.applyEdits() no SDK; veja a API de cenas 3D.

Validação e limites

scenePlan, pendingPlan e cada history[].scenePlan são analisados com o scene3DPlanSchema público de @nodaro/shared. A verificação cobre a estrutura e as regras entre campos: ciclos de pais, pais ausentes, quadros-chave depois do último quadro e o teto de duração. Um plano que falha é recusado por inteiro.

LimiteValor
Entradas de history12
Entradas de selectedObjectIds ou de lockedObjectIds100
Tamanho do id de um objeto64 caracteres
Tamanho de changeSummary e de context.prompt2.000 caracteres
Tamanho de createdAt64 caracteres
Tamanho de parentOrigin255 caracteres
Largura e altura do quadro100 a 2560 px em cada eixo
Objetos por cena100
Quadros-chave por objeto ou por trilha de câmera240
Entidades em uma cena da versão 2100
Assets, tomadas e substituições em uma cena da versão 264, 32 e 200
Bytes de assets que uma cena da versão 2 pode declarar64 MiB

O iframe verifica o tamanho das listas antes de analisar qualquer coisa, então um payload grande demais é recusado sem ser lido. Um manifesto da versão 2 é uma promessa sobre bytes, então os totais declarados são verificados antes de qualquer asset ser pedido. Um quadro com mais de 1920 px no lado mais longo é renderizado em MP4 por um preço maior; veja Renderizar vídeo (Render Video).

Checklist da página pai

O iframe protege a própria caixa de entrada. Só a sua página pode proteger a sua.

  1. Crie um channel novo por iframe com crypto.randomUUID(), e guarde-o.
  2. Monte a URL com a sua própria origem exata, a partir de window.location.origin, não de uma string que você montou.
  3. Verifique quatro coisas em toda mensagem que você recebe.
    • event.source === iframe.contentWindow.
    • event.origin é exatamente a origem do Nodaro que você incorporou.
    • data.channel é o seu channel.
    • data.version é uma versão que você implementa: 1, ou 2 se você fornece assets.
  4. Compare expectedRevisionId com a sua revisão ativa antes de aplicar um evento plan, restore ou resolve-pending, e descarte o evento se não corresponder.
  5. Valide plan de novo com scene3DPlanSchema antes de armazená-lo. O iframe valida o que desenha; você é responsável pelo que salva.
  6. Envie state ao receber ready, e de novo depois de cada alteração que você aceitar.
  7. Nunca envie para "*". Enderece a origem do Nodaro de forma explícita.
  8. Não coloque nada secreto em uma mensagem. O protocolo transporta geometria de cena e ids de revisão: nada de tokens, de identificadores de usuário ou de URLs que você não colocaria em uma referência de cena.
  9. Remova o listener quando a caixa de diálogo fechar. Um listener antigo somado a um channel reutilizado é como duas caixas de diálogo começam a responder uma à outra.
  10. Se você fornece cenas da versão 2, autorize cada pedido de asset com base no plano que você enviou, como descrito em Regras para a sua página.

Exemplo completo

import { scene3DPlanSchema, type Scene3DPlan } from "@nodaro/shared"

const NODARO_ORIGIN = "https://app.nodaro.ai"
const channel = crypto.randomUUID()

const iframe = document.createElement("iframe")
iframe.src =
  `${NODARO_ORIGIN}/embed/scene3d` +
  `?parentOrigin=${encodeURIComponent(window.location.origin)}` +
  `&channel=${channel}`
iframe.allow = "" // the frame needs no permissions

let active: Scene3DPlan = initialPlan                // your current revision
let history: RevisionEntry[] = []                    // newest last, at most 12

function pushState() {
  iframe.contentWindow?.postMessage(
    {
      type: "nodaro:scene3d:state",
      version: 1,
      channel,
      scenePlan: active,
      selectedObjectIds: selection,
      lockedObjectIds: locks,
      history: history.slice(-12),
      readOnly: false,          // leave it out and the frame is view-only
    },
    NODARO_ORIGIN,              // never "*"
  )
}

function onMessage(e: MessageEvent) {
  if (e.source !== iframe.contentWindow) return
  if (e.origin !== NODARO_ORIGIN) return
  const data = e.data
  if (!data || typeof data !== "object") return
  if (data.channel !== channel || data.version !== 1) return

  if (data.type === "nodaro:scene3d:ready") {
    pushState()                 // the frame listens: send the truth
    return
  }
  if (data.type !== "nodaro:scene3d:event") return

  // The action was computed from a revision we may have replaced already.
  const mutates = data.event.kind !== "selection"
  if (mutates && data.expectedRevisionId !== active.revisionId) return

  switch (data.event.kind) {
    case "selection":
      selection = data.event.objectIds
      return                    // no new state needed; the frame shows it already
    case "locks":
      locks = data.event.objectIds
      break
    case "plan": {
      // Trust nothing you save.
      const parsed = scene3DPlanSchema.safeParse(data.event.plan)
      if (!parsed.success) return
      active = parsed.data
      history = [...history, {
        revisionId: active.revisionId,
        scenePlan: active,
        source: "manual",
        changeSummary: data.event.changeSummary,
        createdAt: new Date().toISOString(),
      }].slice(-12)
      break
    }
    case "restore": {
      const entry = history.find((h) => h.revisionId === data.event.revisionId)
      if (!entry) return
      active = entry.scenePlan
      break
    }
    case "resolve-pending":
      active = data.event.adopt ? pending! : active
      pending = undefined
      break
  }
  pushState()
}

window.addEventListener("message", onMessage)
document.body.appendChild(iframe)

// On teardown:
//   window.removeEventListener("message", onMessage)
//   iframe.remove()

Gerar e editar cenas com um modelo, e renderizar uma cena em MP4, são jobs comuns da API: veja Gerar cena 3D, Editar cena 3D e Renderizar vídeo. O viewport é só o visualizador: ele nunca inicia um job e nunca gasta nada.

Versionamento

version: 1 é o primeiro contrato, congelado. O envelope state é estrito: uma chave desconhecida no nível superior, ou dentro de uma entrada de histórico, é recusada em vez de ignorada. Assim, uma página pai e um iframe nunca concordam só pela metade sobre o significado de uma mensagem. O protocolo cresce aumentando a version, e um iframe que não implementa uma versão recusa a mensagem de forma visível, em vez de adivinhar.

version: 2 acrescenta à versão 1 e não substitui nada. Ela traz as cenas pré-calculadas, o transporte de assets e o evento edit-operations, e uma página pai que só fala a versão 1 continua funcionando sem mudanças. O iframe anuncia o que consegue fazer em ready.protocolVersions e ready.capabilities: leia esses campos em vez de supor, o que mantém a próxima versão também aditiva. Se você precisar de um campo que não existe, abra uma issue no repositório público em vez de enviá-lo mesmo assim: uma mensagem recusada é o contrato funcionando.

Perguntas frequentes

Última atualização

Nesta página