1. O que é a sandbox e por que seu widget está em uma jaula
Quando o ChatGPT exibe seu widget, ele o renderiza não como um <iframe src="https://seu-site"> comum. O widget é executado em uma “sandbox” controlada — um iframe isolado com origin separado e configurações de segurança rígidas.
Tecnicamente, isso se parece com o seguinte:
flowchart TD
User["Usuário no ChatGPT"]
Chat["ChatGPT UI + modelo"]
Iframe["Seu widget
iframe em sandbox"]
MCP["Seu MCP / backend"]
User --> Chat
Chat -->|chamada da ferramenta| MCP
MCP -->|structuredContent + _meta| Chat
Chat -->|window.openai.*| Iframe
Iframe -->|callTool / follow-up| Chat
Chat --> MCP
Seu código é executado somente dentro desse iframe, e o acesso ao restante do mundo ocorre por meio de uma API estritamente controlada fornecida pelo host (ChatGPT). O widget não deve:
- quebrar o próprio ChatGPT (DOM, estilos, performance);
- violar a privacidade do usuário;
- acessar a rede sem controle.
Daí decorrem as principais limitações da sandbox.
Isolamento de DOM e origin
O widget vive em um domínio especial de sandbox (por exemplo, https://sandbox-apps.oaiusercontent.com), com o atributo sandbox no iframe. Isso significa que:
- você não pode acessar window.parent ou o document do ChatGPT — resultará em SecurityError;
- coisas cross-domain como postMessage são controladas pelo host;
- quaisquer tentativas de “consertar a interface do ChatGPT com CSS” estão condenadas.
Rede e restrições de CSP
O navegador e a política CSP do host limitam o acesso à rede para o seu widget:
- o método fetch tem acesso apenas a domínios da lista de permissões, que devem passar por revisão;
- quais domínios podem ser acessados a partir do widget você declara explicitamente via openai/widgetCSP nas respostas do MCP; caso contrário, as requisições simplesmente não passam;
- o caminho recomendado para tudo o que é sério — é nem chamar a rede a partir do widget, mas ir ao backend por meio de ferramentas MCP e callTool (mais detalhes no Módulo 4).
Na prática: pense no widget como uma camada de UI fina. Ele conversa com o ChatGPT e com seu servidor por canais estritamente definidos, e não como um SPA comum, livre na internet.
Armazenamentos e recursos
Armazenamentos locais (localStorage, sessionStorage) estão disponíveis, mas cookie — não. Leve isso em conta ao desenvolver o aplicativo. Memória e CPU são limitados: se você decidir calcular todos os números primos até um bilhão dentro do widget, o host tem todo o direito de simplesmente encerrar seu iframe.
Daí uma conclusão importante: nada de cálculos pesados e “caches” de longa duração no widget. A lógica complexa fica no servidor, não no componente React.
2. window.openai: a ponte entre o widget e o ChatGPT
Para que o widget possa saber de algo (resultados da ferramenta, modo de exibição, localidade, estado), o ChatGPT, ao inicializar, injeta na janela do iframe um objeto global — window.openai.
Isso não é seu código nem um pacote npm, mas um host object, fornecido pela própria plataforma de IA. Por baixo dos panos, ele se baseia em eventos e mensagens entre o host e o iframe, mas você quase não precisa pensar nisso. É importante lembrar alguns pontos.
Quem e quando cria o window.openai
window.openai aparece somente:
- dentro do iframe que o ChatGPT criou para o seu widget;
- quando o template HTML é entregue com o mimeType correto (text/html+skybridge) e passou em todas as verificações.
Você já viu esse type no módulo sobre o HelloWorld App — é exatamente o que a página do widget retorna no lugar de text/html comum.
Se você simplesmente abre a página do widget diretamente no navegador, então:
console.log(window.openai); // undefined
e está tudo bem. Por isso, no código do widget, sempre vale a pena verificar se o objeto existe, caso você conte com o modo “standalone” para desenvolvimento local ou Storybook.
Exemplo primitivo (não final, apenas ilustração):
if (typeof window !== "undefined" && (window as any).openai) {
console.log("We are inside ChatGPT sandbox!");
}
Assincronismo da inicialização
Por baixo dos panos, o ChatGPT atualiza window.openai à medida que chegam novos dados (novo toolOutput, troca de displayMode etc.), usando o evento interno openai:set_globals.
Ou seja, os “valores” nele não são estáticos: o modelo de IA pode chamar a ferramenta MCP, o backend retorna novo structuredContent, e window.openai.toolOutput muda bem debaixo do seu componente React.
Daí duas recomendações:
- Não faça snapshots “às cegas” do tipo const toolOutput = window.openai.toolOutput uma vez no início e ache que ele é eterno. O mesmo widget pode ser reutilizado pelo ChatGPT.
- Use a camada de hooks (já chegamos nela), que sabe se inscrever para mudanças.
3. Anatomia do window.openai: dados, APIs e contexto
A documentação oficial traz uma tabela compacta sobre os campos e métodos de window.openai. Vamos condensá-la em um formato mais “humano”.
Campos e métodos principais
window.openai = {
// State & data
toolInput, // JSON: parâmetros que a IA passou para sua MCP-tool
toolOutput, // JSON: parâmetros que sua MCP-tool retornou para a IA
toolResponseMetadata, // Resposta da MCP-tool: parte de _meta: {...}
widgetState, // É possível ler o estado salvo do widget
setWidgetState, // É possível salvar o estado do seu widget aqui
// Runtime APIs
callTool, // Pode chamar a MCP-tool
sendFollowUpMessage, // Enviar discretamente uma mensagem para a IA no chat: ela começará a responder.
requestDisplayMode, // Alternar o widget para outro modo: fullscreen, pip, inline
requestModal, // Transformar o widget em uma janela modal.
requestClose, // Fecha o widget. Fecha a janela modal — volta a ser widget.
requestCheckout, // Abre a janela modal de pagamento. O servidor deve implementar ACP
notifyIntrinsicHeight, // Notifica a mudança da altura do widget
openExternal, // Abrir um link em uma nova janela.
// Context
theme, // Tema escuro ou claro
displayMode, // Modo de exibição atual do widget; pode diferir de requestDisplayMode
maxHeight, // Altura máxima permitida do widget
safeArea, // "Área segura de renderização" — relevante para celulares com notch
view,
userAgent, // userAgent do navegador
locale // locale do navegador
}
O mesmo em forma de tabela:
| Categoria | Propriedade / método | Para que serve |
|---|---|---|
| State & data | |
Argumentos com que a ferramenta foi chamada. Somente leitura. |
| State & data | |
Seu structuredContent da resposta MCP. O que o widget e o modelo veem. |
| State & data | |
_meta da resposta. Visível apenas para o widget; o modelo não lê isso. |
| State & data | |
Snapshot do estado de UI que o ChatGPT armazena entre renderizações do widget. |
| State & data | |
Salvar um novo snapshot de widgetState de forma síncrona. |
| Function | |
Chamar uma ferramenta MCP a partir do widget. |
| Function | |
Pedir ao ChatGPT para enviar uma mensagem no chat em nome do widget. Ele começará a responder. |
| Function | |
Solicitar ao host inline / fullscreen / pip. |
| Function | |
Solicitar a abertura de uma janela modal. |
| Function | |
Informar que a altura do conteúdo mudou. |
| Function | |
Abre o diálogo de pagamento pelo protocolo ACP. |
| Function | |
Abrir um link externo no navegador do usuário. |
| Context | |
Sinais de ambiente: tema, modo, altura disponível, localidade etc. |
Não é necessário memorizar tudo dessa tabela de imediato — use-a como um “mapa do território”. Agora vamos tratar disso não como “manual”, mas como gente normal.
toolInput e toolOutput: de onde vêm os dados
Quando o modelo decide chamar sua ferramenta, ele forma argumentos JSON. Esses argumentos:
- chegam ao servidor MCP como input no handler;
- ao mesmo tempo caem em window.openai.toolInput no widget.
Após a execução da ferramenta, o servidor retorna:
- structuredContent — dados estruturados para a UI;
- _meta — dados privados apenas para o widget;
- content — texto para o próprio modelo, para que ele possa “contar” ao usuário o que aconteceu.
structuredContent se torna window.openai.toolOutput, e _meta se torna window.openai.toolResponseMetadata.
Mini-exemplo (JS “vanilla”, sem React):
const root = document.getElementById("root");
// Você pode usar com segurança o operador nullish
const gifts = window.openai.toolOutput?.gifts ?? [];
root.textContent = `Presentes encontrados: ${gifts.length}`;
widgetState e setWidgetState: a memória do widget
widgetState é aquilo que a plataforma está disposta a lembrar sobre sua UI entre renderizações e até entre turnos separados do diálogo.
Exemplos naturais para o widgetState:
- presente selecionado;
- ordenação atual (por preço / por popularidade);
- número da página na lista.
Não naturais:
- resposta bruta de uma API de terceiros;
- imagem em base64;
- tokens secretos.
É importante lembrar duas coisas:
- widgetState é armazenado e passado ao modelo junto com o contexto, portanto não coloque nada sensível ali.
- O volume é limitado (aproximadamente 4 mil tokens), então não o transforme em um mini-banco de dados.
Exemplo mais simples de uso (direto, sem hooks, em JS “vanilla”):
const current = window.openai.widgetState ?? { selectedGiftId: null };
function selectGift(id) {
window.openai.setWidgetState({ ...current, selectedGiftId: id });
}
No código real, envolveremos isso em hooks do React.
Runtime API: callTool, sendFollowUpMessage e afins
Esses métodos permitem que o widget não só “se desenhe”, mas também interaja com o diálogo e com o servidor.
Alguns cenários típicos:
- callTool("search_gifts", { budget: 50 }) — o usuário clicou no botão “Alterar orçamento”, você chamou o servidor e atualizou a UI;
- sendFollowUpMessage({ prompt: "Mostre mais ideias mais caras" }) — em vez de pedir ao usuário para digitar o texto, você adiciona um botão de follow-up que cria uma nova mensagem no chat;
- requestDisplayMode({ mode: "fullscreen" }) — se o modo inline ficou apertado, o widget pode educadamente pedir ao ChatGPT para ocupar a tela toda;
- openExternal({ href: "https://myshop.com/checkout?giftId=123" }) — enviar o usuário para um site externo (checkout, perfil etc.) por um canal controlado.
Todos eles passam “pelos fios” via ChatGPT, e não diretamente para a internet.
Contexto de ambiente: tema, modo, altura, localidade
Campos como theme, displayMode, maxHeight, locale dão noção do ambiente em que o widget vive.
Por exemplo:
const theme = window.openai.theme; // "light" ou "dark"
const mode = window.openai.displayMode; // "inline" | "fullscreen" | "pip"
const maxH = window.openai.maxHeight; // altura disponível
const locale = window.openai.locale; // "en-US", "de-DE", ...
Com esses sinais, você pode:
- ajustar cores e espaçamentos ao tema;
- mudar o layout dependendo do modo (inline vs fullscreen);
- localizar rótulos da UI para o idioma do usuário (haverá um módulo inteiro sobre isso).
A plataforma fornece sinais sobre quanto espaço existe, qual tema e localidade. Faz sentido usá-los via useOpenAIGlobal, useDisplayMode, useMaxHeight e outros hooks, para que o widget pareça “nativo” no ChatGPT.
4. Hooks sobre o window.openai: não mexa no objeto global na unha
O acesso direto a window.openai é conveniente para protótipo, mas rapidamente transforma o código em bagunça: inscrições em eventos, checagens de undefined, wrappers repetitivos. Por isso, no template de Next.js para Apps SDK há um conjunto pronto de hooks do React que escondem os detalhes e tornam tudo reativo.
Um índice típico de hooks se parece com isto:
// app/hooks/openai/index.ts
export { useCallTool } from "./use-call-tool";
export { useSendMessage } from "./use-send-message";
export { useOpenExternal } from "./use-open-external";
export { useRequestDisplayMode, useRequestModal, useRequestClose } from "./use-request-display-mode";
export { useRequestCheckout } from "./use-request-checkout";
// State hooks
export { useDisplayMode } from "./use-display-mode";
export { useWidgetProps } from "./use-widget-props";
export { useWidgetState } from "./use-widget-state";
export { useOpenAIGlobal } from "./use-openai-global";
export { useMaxHeight } from "./use-max-height";
export { useIsChatGptApp } from "./use-is-chatgpt-app";
Os nomes e o caminho exato podem variar um pouco no seu template, mas a ideia é a mesma: em vez de window.openai.*, você usa hooks. Vamos ver os principais.
useWidgetProps: entrada e saída da ferramenta
useWidgetProps normalmente retorna um objeto com os dados de que o widget precisa: toolInput, toolOutput, toolResponseMetadata e às vezes flags adicionais como isLoading.
Exemplo:
import { useWidgetProps } from "../hooks/openai";
type Gift = { id: string; title: string; price: number };
export function GiftList() {
const { toolOutput } = useWidgetProps<{ gifts: Gift[] }>();
const gifts = toolOutput?.gifts ?? [];
if (!gifts.length) {
return <div>Ainda não há opções de presentes.</div>;
}
return (
<ul>
{gifts.map((g) => (
<li key={g.id}>{g.title} — ${g.price}</li>
))}
</ul>
);
}
Nada de window.openai no código do componente — e isso é bom.
useWidgetState: “wrapper reativo” sobre o widgetState
useWidgetState permite trabalhar com widgetState como com um estado React comum: você recebe [state, setState], e o hook, por baixo dos panos, o sincroniza com window.openai.widgetState e setWidgetState.
Exemplo:
import { useWidgetState } from "../hooks/openai";
type UiState = { selectedGiftId: string | null };
export function SelectedGiftIndicator() {
const [uiState, setUiState] = useWidgetState<UiState>(() => ({
selectedGiftId: null,
}));
if (!uiState?.selectedGiftId) {
return <div>O presente ainda não foi selecionado.</div>;
}
return (
<div>
Você selecionou o presente com id={uiState.selectedGiftId}
<button onClick={() => setUiState({ selectedGiftId: null })}>
Limpar
</button>
</div>
);
}
Após o clique, setUiState não apenas atualizará o estado do React, mas também salvará o novo estado no lado do ChatGPT.
useOpenAIGlobal: acesso a qualquer campo do window.openai
Se você precisa acessar um único campo global (por exemplo, tema ou modo), há o hook universal useOpenAIGlobal(key). Ele se inscreve no evento openai:set_globals e sempre retorna o valor atual.
Exemplo:
import { useOpenAIGlobal } from "../hooks/openai";
export function ThemeAwareBlock() {
const theme = useOpenAIGlobal<"light" | "dark">("theme");
const background = theme === "dark" ? "#222" : "#fff";
const color = theme === "dark" ? "#fff" : "#000";
return <div style={{ background, color }}>Eu respeito o tema do ChatGPT</div>;
}
useCallTool, useSendMessage, useOpenExternal e outros
- useCallTool(name) — retorna uma função que chama a ferramenta MCP com o nome indicado. É um wrapper sobre callTool.
- useSendMessage() — encapsula sendFollowUpMessage, para que o widget possa iniciar mensagens.
- useOpenExternal() — helper conveniente em torno de openExternal({ href }).
- useRequestDisplayMode() e useRequestModal() — wrappers para solicitar troca de modo / abertura de modal.
Exemplo básico do mini-widget GiftGenius, que usa quase tudo de uma vez:
import {
useWidgetProps,
useWidgetState,
useCallTool,
useSendMessage,
useOpenExternal,
} from "../hooks/openai";
type Gift = { id: string; title: string; url: string; price: number };
export function GiftWidget() {
const { toolOutput } = useWidgetProps<{ gifts: Gift[] }>();
const gifts = toolOutput?.gifts ?? [];
const [ui, setUi] = useWidgetState<{ selectedId: string | null }>(() => ({
selectedId: null,
}));
const callSearch = useCallTool("search_gifts");
const sendMessage = useSendMessage();
const openExternal = useOpenExternal();
if (!gifts.length) {
return <div>Ainda não há ideias. Tente pedir ao GPT para atualizar os resultados.</div>;
}
return (
<div>
{gifts.map((g) => (
<button
key={g.id}
style={{
display: "block",
fontWeight: ui?.selectedId === g.id ? "bold" : "normal",
}}
onClick={() => setUi({ selectedId: g.id })}
>
{g.title} — ${g.price}
</button>
))}
<div style={{ marginTop: 12 }}>
<button
onClick={() =>
sendMessage({ prompt: "Mostre presentes mais caros do que os atuais." })
}
>
Pedir mais ideias
</button>
<button
onClick={async () => {
await callSearch({ budget: 200 });
}}
>
Atualizar com orçamento de $200
</button>
{ui?.selectedId && (
<button
onClick={() =>
openExternal({
href: `https://giftgenius.example.com/checkout?id=${ui.selectedId}`,
})
}
>
Ir para a compra
</button>
)}
</div>
</div>
);
}
Esta página ainda está crua (nos próximos módulos melhoraremos UX, tratamento de erros etc.), mas já ilustra a abordagem: nada de acessos diretos a window.openai, somente hooks.
5. Prática: explorando a sandbox e o window.openai
Para sentir o que é “um widget que não é um site comum”, vale fazer alguns exercícios.
Exercício: “Explore o ambiente”
Pegue seu app/page.tsx atual no widget e adicione, no primeiro render, um efeito simples:
import { useEffect } from "react";
import { useIsChatGptApp } from "../hooks/openai";
export default function Root() {
const isChatGpt = useIsChatGptApp();
useEffect(() => {
if (typeof window !== "undefined") {
console.log("window.origin =", window.origin);
console.log("window.openai =", (window as any).openai);
}
}, []);
return (
<main>
<h1>GiftGenius widget</h1>
<p>Executado dentro do ChatGPT: {String(isChatGpt)}</p>
</main>
);
}
Abra o DevTools: ou diretamente na janela do ChatGPT (via viewer embutido do túnel, se ele permitir), ou no navegador local ao abrir a página diretamente. Em ambos os casos, compare:
- ao rodar no navegador comum, isChatGptApp será false, e window.openai provavelmente undefined;
- ao rodar pelo ChatGPT, você verá um objeto com campos toolInput, toolOutput, theme etc.
É uma boa intuição: o mesmo código React se comporta de maneira diferente dependendo do ambiente, e é para isso que existem os hooks.
Exercício: “Exiba tudo o que a plataforma fornece”
Adicione um componente temporário para depuração:
import { useWidgetProps, useOpenAIGlobal } from "../hooks/openai";
export function DebugPanel() {
const { toolInput, toolOutput, toolResponseMetadata } = useWidgetProps();
const theme = useOpenAIGlobal("theme");
const displayMode = useOpenAIGlobal("displayMode");
return (
<pre style={{ fontSize: 10, maxHeight: 200, overflow: "auto" }}>
{JSON.stringify(
{ toolInput, toolOutput, toolResponseMetadata, theme, displayMode },
null,
2
)}
</pre>
);
}
E insira temporariamente <DebugPanel /> abaixo da UI principal. Assim você verá claramente:
- quais campos exatamente chegam do MCP em toolOutput;
- o que vive em _meta (por exemplo, locale, userLocation e afins);
- como displayMode muda quando você expande o widget.
Depois, esse componente pode ser removido ou deixado opcional por meio de alguma flag como DEBUG_WIDGET.
6. Relações: ChatGPT ↔ widget ↔ MCP/servidor
Para não tratar o widget como o “principal” da história, vale reforçar os papéis.
- O usuário escreve uma mensagem: “Escolha um presente para a namorada, orçamento de 50$”.
- O modelo do ChatGPT decide chamar sua ferramenta MCP search_gifts com argumentos { recipient: "girlfriend", budget: 50 }.
- O servidor MCP executa a lógica de negócio e retorna:
- content com uma breve descrição para o modelo;
- structuredContent com um array de presentes;
- _meta com detalhes técnicos (por exemplo, source e moeda).
- O ChatGPT:
- mostra ao usuário a mensagem de texto (“Encontrei algumas opções...”);
- cria um iframe do widget e passa para ele structuredContent e _meta por meio de window.openai.toolOutput e toolResponseMetadata.
- Seu widget:
- renderiza a UI conforme o toolOutput;
- ao interagir, chama callTool ou envia follow-up;
- O modelo então decide o que fazer com os resultados dessas ações.
Tudo isso leva a um ponto importante: o widget nunca é o único dono do processo. Ele é a camada de UI que vive em um ecossistema composto pelo modelo e pelo servidor MCP. Coisas complexas (autorização, acesso a dados privados, lógica de negócio séria) devem permanecer no servidor. O widget é responsável por uma interface conveniente e por uma comunicação cuidadosa com o usuário.
7. Políticas e regras do jogo na sandbox
Toda essa construção com iframe isolado e window.openai existe não por acaso, mas por requisitos de segurança e privacidade. As diretrizes oficiais da OpenAI enfatizam alguns princípios.
Primeiro, minimização de dados. Você não deve, por meio do widget, tentar extrair do usuário o máximo possível de PII (personally identifiable information) e levá-la para si. Tudo o que realmente for necessário deve estar claramente descrito nas ferramentas, e o modelo e a camada de segurança observarão atentamente tais chamadas.
Segundo, proibição de rastreamento oculto e fingerprinting. Não é permitido construir um sistema de “espionagem” do dispositivo do usuário, coletar impressões digitais do navegador e buscar contornar as restrições. Parâmetros como userAgent, userLocation e afins — são dicas para UX, não para autorização ou identificação.
Terceiro, tudo o que você coloca em structuredContent, _meta, widgetState, de algum modo é visto pelo usuário ou pode ser visto pelo revisor da Store. Portanto:
- nenhumas chaves de API, tokens, senhas ou segredos administrativos devem ir ali;
- o estado do widget deve ser projetado de modo que o usuário não se surpreenda ao vê-lo em logs ou na depuração.
Quarto, chamadas de rede. Requisições diretas do widget a APIs de terceiros são permitidas apenas a uma lista estritamente limitada de domínios e em cenários não sensíveis. Assim que o assunto envolve dinheiro, contas, dados privados — tudo isso deve passar por MCP/backend.
8. Erros comuns ao trabalhar na sandbox e com o window.openai
Erro nº 1: achar que o widget é “um site comum em iframe”.
Por hábito, iniciantes tentam acessar window.parent, alterar estilos do ChatGPT ou usar localStorage como sempre. Na sandbox, isso não funciona ou funciona de modo instável: o origin é outro, o storage é isolado, o acesso ao DOM é bloqueado. É preciso aceitar que você vive em um ambiente controlado e se comunica com o host apenas via window.openai e hooks.
Erro nº 2: mexer no window.openai diretamente por toda parte.
Código do tipo window.openai.toolOutput em dez componentes — caminho para um aplicativo difícil de depurar. Além disso, você mesmo terá que lidar com eventos, assincronismo e checagens de undefined. É muito mais confiável usar desde o início useWidgetProps, useWidgetState, useOpenAIGlobal e outros hooks, que já encapsulam openai:set_globals e sincronizam o estado.
Erro nº 3: guardar no widgetState tudo (especialmente segredos).
Às vezes dá vontade de “por via das dúvidas” colocar ali um objeto enorme com resultados de API ou até um token de acesso. Como resultado, o contexto cresce, o desempenho do modelo piora e você viola requisitos básicos de segurança. widgetState deve ser pequeno, conter apenas sinais de UI, e nunca — dados confidenciais.
Erro nº 4: tentar acessar a internet diretamente a partir do widget.
Chamadas fetch("https://api.superbank.com/...") a partir da sandbox quase certamente esbarrarão em CORS, e mesmo que você configure tudo perfeitamente, isso será inseguro e pouco gerenciável. Tudo o que envolve contas reais, dinheiro e dados pessoais deve ser implementado como ferramentas MCP e chamado via callTool ou pela parte de servidor.
Erro nº 5: depender da estabilidade do window.openai fora do ChatGPT.
Alguns desenvolvedores tentam rodar o widget como um SPA separado e não adicionam checagens de que window.openai pode ser undefined. No ambiente de desenvolvimento, isso termina com um crash “Cannot read properties of undefined”. Use useIsChatGptApp, checagens de typeof window !== "undefined" e um fallback de UI para casos em que o widget não exista de fato.
Erro nº 6: ignorar o contexto do ambiente (theme, displayMode, maxHeight, locale).
Claro que você pode fixar a altura em 2000px, usar sempre o tema escuro e compor pensando apenas em desktop — mas isso deixará a experiência do usuário bem estranha. A plataforma fornece sinais sobre quanto espaço está disponível, qual tema e localidade — é sensato usá-los via useOpenAIGlobal, useDisplayMode, useMaxHeight e outros, para que o widget pareça “nativo” no ChatGPT.
Erro nº 7: tentar “contornar” a política com scripts de terceiros.
Às vezes surge a tentação de puxar algum tracker, um bundle JS de terceiros ou executar código de outro domínio “discretamente”. A sandbox e as políticas CSP existem justamente para impedir isso: scripts de terceiros são bloqueados, e tentativas de burlar o sistema são caminho direto para a rejeição do seu App na Store.
GO TO FULL VERSION