CodeGym /Corsi /ChatGPT Apps /Lavorare nella sandbox: limiti, sfumature e window.openai...

Lavorare nella sandbox: limiti, sfumature e window.openai

ChatGPT Apps
Livello 3 , Lezione 0
Disponibile

1. Che cos’è la sandbox e perché il tuo widget è in gabbia

Quando ChatGPT mostra il tuo widget, lo rende non come un normale <iframe src="https://il-tuo-sito">. Il widget viene eseguito in una «sandbox» controllata — un iframe isolato con origin separato e impostazioni di sicurezza rigide.

Tecnicamente appare più o meno così:

flowchart TD
    User["Utente in ChatGPT"]
    Chat["ChatGPT UI + modello"]
    Iframe["Il tuo widget
iframe in sandbox"] MCP["Il tuo MCP / backend"] User --> Chat Chat -->|chiamata dello strumento| MCP MCP -->|structuredContent + _meta| Chat Chat -->|window.openai.*| Iframe Iframe -->|callTool / follow-up| Chat Chat --> MCP

Il tuo codice viene eseguito solo dentro questo iframe e l’accesso al resto del mondo passa attraverso un API strettamente controllata fornita dall’host (ChatGPT). Il widget non deve:

  • rompere ChatGPT stesso (DOM, stili, performance);
  • violare la privacy dell’utente;
  • accedere alla rete senza controllo.

Da qui derivano le limitazioni chiave della sandbox.

Isolamento di DOM e origin

Il widget vive su un dominio speciale della sandbox (per esempio, https://sandbox-apps.oaiusercontent.com), con l’attributo sandbox sull’iframe. Questo significa che:

  • non puoi accedere a window.parent o al document di ChatGPT: otterrai un SecurityError;
  • operazioni cross-domain come postMessage sono controllate dall’host;
  • qualunque tentativo di «aggiustare l’interfaccia di ChatGPT con un CSS» è destinato a fallire.

Rete e limitazioni CSP

Il browser e la politica CSP dell’host limitano l’accesso alla rete per il tuo widget:

  • il metodo fetch ha accesso solo ai domini nella whitelist, che devono passare una revisione;
  • i domini accessibili dal widget vanno dichiarati esplicitamente tramite openai/widgetCSP nelle risposte MCP; altrimenti le richieste verranno bloccate;
  • la via consigliata per tutto ciò che è serio è non fare chiamate di rete dal widget, ma passare al backend tramite strumenti MCP e callTool (ne parleremo meglio nel Modulo 4).

In pratica: pensa al widget come a un sottile strato UI. Parla con ChatGPT e con il tuo server tramite canali ben definiti, non come una SPA che vive liberamente su Internet.

Storage e risorse

Gli storage locali (localStorage, sessionStorage) sono disponibili, ma i cookie no. Tienilo presente nello sviluppo dell’applicazione. Memoria e CPU sono limitate: se decidi di calcolare tutti i numeri primi fino a un miliardo dentro il widget, l’host ha pieno diritto di terminare il tuo iframe.

Ne consegue: niente calcoli pesanti e nessuna «cache» di lunga durata nel widget. La logica complessa deve stare lato server, non in un componente React.

2. window.openai: il ponte tra widget e ChatGPT

Perché il widget possa sapere qualcosa (risultati dello strumento, modalità di visualizzazione, locale, stato), ChatGPT durante l’inizializzazione inserisce nella finestra dell’iframe un oggetto globale: window.openai.

Non è codice tuo e non è un pacchetto npm, ma un host object fornito direttamente dalla piattaforma di IA. Sotto il cofano si basa su eventi e messaggi tra host e iframe, ma non serve pensarci troppo. Ricorda però alcune cose.

Chi e quando crea window.openai

window.openai compare solo:

  • all’interno dell’iframe che ChatGPT ha creato per il tuo widget;
  • quando il template HTML viene servito con il mimeType corretto (text/html+skybridge) e ha superato tutti i controlli.

Hai già visto questo tipo nel modulo su HelloWorld App: è quello che la pagina del widget restituisce al posto di text/html.

Se apri semplicemente la pagina del widget direttamente nel browser, allora:

console.log(window.openai); // undefined

ed è normale. Quindi nel codice del widget conviene sempre verificare che l’oggetto esista, se prevedi una modalità «standalone» per lo sviluppo locale o per Storybook.

Esempio semplice (non definitivo, solo illustrazione):

if (typeof window !== "undefined" && (window as any).openai) {
  console.log("We are inside ChatGPT sandbox!");
}

Asincronia dell’inizializzazione

Sotto il cofano, ChatGPT aggiorna window.openai man mano che arrivano nuovi dati (nuovo toolOutput, cambio di displayMode ecc.), usando l’evento interno openai:set_globals.

Quindi i «valori» al suo interno non sono statici: il modello di IA può chiamare uno strumento MCP, il backend restituirà nuovi structuredContent e window.openai.toolOutput cambierà proprio sotto il tuo componente React.

Ne derivano due raccomandazioni:

  1. Non creare snapshot «alla cieca» del tipo const toolOutput = window.openai.toolOutput una volta all’inizio pensando che siano eterni. Lo stesso widget può essere riutilizzato da ChatGPT.
  2. Usa lo strato di hook (tra poco), che sa iscriversi ai cambiamenti.

3. Anatomia di window.openai: dati, API e contesto

La documentazione ufficiale fornisce una tabella compatta con campi e metodi di window.openai. Riorganizziamola in modo più «umano».

Campi e metodi principali

window.openai = {
  // State & data
  toolInput,               // JSON: parametri che l'IA ha passato al tuo MCP tool
  toolOutput,              // JSON: parametri che il tuo MCP tool ha restituito all'IA
  toolResponseMetadata,    // Risposta del MCP tool: parte di _meta: {...}
  widgetState,             // Puoi leggere lo stato salvato del widget
  setWidgetState,          // Puoi salvare lo stato del tuo widget qui

  // Runtime APIs
  callTool,                // Puoi chiamare un MCP tool
  sendFollowUpMessage,     // Inviare di nascosto un messaggio all'IA in chat: inizierà a rispondere.
  requestDisplayMode,      // Passare il widget in un'altra modalità: fullscreen, pip, inline
  requestModal,            // Trasformare il widget in una finestra modale.
  requestClose,            // Chiude il widget. Chiusa la modale, torna a widget.
  requestCheckout,         // Apre una finestra modale per il pagamento. Il server deve implementare ACP
  notifyIntrinsicHeight,   // Notifica il cambiamento dell'altezza del widget
  openExternal,            // Aprire un link in una nuova finestra.

  // Context
  theme,                   // Tema scuro o chiaro
  displayMode,             // Modalità di visualizzazione corrente del widget, può differire da requestDisplayMode
  maxHeight,               // Altezza massima consentita del widget
  safeArea,                // "Area sicura di rendering" - rilevante per telefoni con "notch"
  view,
  userAgent,               // userAgent del browser
  locale                   // locale del browser
}

La stessa cosa in tabella:

Categoria Proprietà / metodo A cosa serve
State & data
toolInput
Argomenti con cui è stato chiamato lo strumento. Sola lettura.
State & data
toolOutput
Il tuo structuredContent dalla risposta MCP. Ciò che vede il widget e il modello.
State & data
toolResponseMetadata
_meta dalla risposta. Visibile solo al widget, il modello non lo legge.
State & data
widgetState
Istanza dello stato UI che ChatGPT conserva tra i render del widget.
State & data
setWidgetState(state)
Salvare sincronicamente una nuova istanza di widgetState.
Function
callTool(name, args)
Chiamare uno strumento MCP dal widget.
Function
sendFollowUpMessage({prompt})
Chiedere a ChatGPT di inviare un messaggio in chat a nome del widget. Inizierà a rispondere.
Function
requestDisplayMode(...)
Richiedere all’host inline / fullscreen / pip.
Function
requestModal({title})
Richiedere l’apertura di una finestra modale.
Function
notifyIntrinsicHeight()
Segnalare che l’altezza del contenuto è cambiata.
Function
requestCheckout(...)
Apre il dialogo di pagamento tramite protocollo ACP.
Function
openExternal({href})
Aprire un link esterno nel browser dell’utente.
Context
theme, displayMode, maxHeight, safeArea, view, userAgent, locale
Segnali d’ambiente: tema, modalità, altezza disponibile, locale ecc.

Non è necessario memorizzare subito tutto ciò che c’è in questa tabella: considerala come una «mappa del territorio». Ora analizziamola non come «manuale», ma come farebbe una persona normale.

toolInput e toolOutput: da dove arrivano i dati

Quando il modello decide di chiamare il tuo strumento, forma degli argomenti JSON. Questi argomenti:

  • arrivano al server MCP come input al relativo handler;
  • arrivano contemporaneamente a window.openai.toolInput nel widget.

Dopo l’esecuzione dello strumento, il server restituisce:

  • structuredContent — dati strutturati per l’UI;
  • _meta — dati privati solo per il widget;
  • content — testo per il modello stesso, così che possa «raccontare» all’utente cosa è successo.

structuredContent diventa window.openai.toolOutput, e _meta diventa window.openai.toolResponseMetadata.

Mini-esempio (vanilla JS, senza React):

const root = document.getElementById("root");

// Si può usare in sicurezza l'operatore nullish
const gifts = window.openai.toolOutput?.gifts ?? [];

root.textContent = `Regali trovati: ${gifts.length}`;

widgetState e setWidgetState: la memoria del widget

widgetState è ciò che la piattaforma è disposta a ricordare del tuo UI tra i render e perfino tra diversi turni di dialogo.

Esempi naturali per widgetState:

  • il regalo selezionato;
  • l’ordinamento corrente (per prezzo / per popolarità);
  • il numero di pagina nell’elenco.

Non naturali:

  • la risposta grezza di un’API esterna;
  • un’immagine in base64;
  • token segreti.

Ricorda due cose:

  1. widgetState viene archiviato e passato al modello insieme al contesto, quindi non metterci nulla di sensibile.
  2. Il volume è limitato (circa 4 mila token), quindi non farne un mini database.

Esempio d’uso semplicissimo (in modo diretto, senza hook, in vanilla JS):

const current = window.openai.widgetState ?? { selectedGiftId: null };

function selectGift(id) {
  window.openai.setWidgetState({ ...current, selectedGiftId: id });
}

Nel codice reale lo incapsuleremo negli hook di React.

Runtime API: callTool, sendFollowUpMessage e simili

Questi metodi permettono al widget non solo di «rendersi», ma anche di interagire con il dialogo e il server.

Alcuni scenari tipici:

  • callTool("search_gifts", { budget: 50 }) — l’utente ha cliccato il pulsante «Modifica budget», chiami il server e aggiorni l’UI;
  • sendFollowUpMessage({ prompt: "Mostra altre idee più costose" }) — invece di chiedere all’utente di scrivere a mano, aggiungi un pulsante follow-up che crea un nuovo messaggio in chat;
  • requestDisplayMode({ mode: "fullscreen" }) — se la modalità inline è stretta, il widget può chiedere gentilmente a ChatGPT di andare a schermo intero;
  • openExternal({ href: "https://myshop.com/checkout?giftId=123" }) — inviare l’utente a un sito esterno (checkout, profilo ecc.) tramite un canale controllato.

Tutto passa «via cavo» attraverso ChatGPT, non direttamente su Internet.

Contesto dell’ambiente: tema, modalità, altezza, locale

Campi come theme, displayMode, maxHeight, locale ti danno il polso dell’ambiente in cui vive il widget.

Per esempio:

const theme = window.openai.theme;          // "light" o "dark"
const mode = window.openai.displayMode;     // "inline" | "fullscreen" | "pip"
const maxH = window.openai.maxHeight;       // altezza disponibile
const locale = window.openai.locale;        // "en-US", "de-DE", ...

Con questi segnali puoi:

  • adattare colori e spaziature al tema;
  • cambiare il layout a seconda della modalità (inline vs fullscreen);
  • localizzare le etichette dell’UI in base alla lingua dell’utente (ci sarà un modulo dedicato).

La piattaforma ti dà segnali su spazio disponibile, tema e locale. È sensato usarli tramite useOpenAIGlobal, useDisplayMode, useMaxHeight e altri hook, così che il widget risulti «nativo» in ChatGPT.

4. Hook sopra window.openai: non toccare l’oggetto globale a mano

L’accesso diretto a window.openai è comodo per un prototipo, ma presto rende il codice caotico: sottoscrizioni a eventi, controlli su undefined, wrapper ripetuti. Per questo nel template Next.js per l’Apps SDK c’è un set di hook React pronti che nascondono i dettagli e rendono tutto reattivo.

Un tipico indice degli hook appare così:

// 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";

I nomi e il percorso esatto possono variare nel tuo template, ma l’idea è sempre la stessa: invece di window.openai.* usi degli hook. Vediamo i principali.

useWidgetProps: input e output dello strumento

useWidgetProps di solito restituisce un oggetto con i dati necessari al widget: toolInput, toolOutput, toolResponseMetadata e talvolta flag aggiuntivi come isLoading.

Esempio:

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>Al momento non ci sono opzioni di regalo.</div>;
  }

  return (
    <ul>
      {gifts.map((g) => (
        <li key={g.id}>{g.title} — ${g.price}</li>
      ))}
    </ul>
  );
}

Nessun window.openai nel codice del componente — ed è un bene.

useWidgetState: «wrapper reattivo» sopra widgetState

useWidgetState permette di lavorare con widgetState come con un normale stato React: ottieni [state, setState], e l’hook sincronizza sotto il cofano con window.openai.widgetState e setWidgetState.

Esempio:

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>Nessun regalo selezionato.</div>;
  }

  return (
    <div>
      Hai selezionato il regalo con id={uiState.selectedGiftId}
      <button onClick={() => setUiState({ selectedGiftId: null })}>
        Reimposta
      </button>
    </div>
  );
}

Dopo il click, setUiState non solo aggiorna lo stato React, ma salva anche il nuovo stato lato ChatGPT.

useOpenAIGlobal: accesso a qualsiasi campo di window.openai

Se serve accedere a un singolo campo globale (per esempio tema o modalità), c’è l’hook universale useOpenAIGlobal(key). Si sottoscrive all’evento openai:set_globals e restituisce sempre il valore aggiornato.

Esempio:

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 }}>Rispetto il tema di ChatGPT</div>;
}

useCallTool, useSendMessage, useOpenExternal e altri

  • useCallTool(name) — restituisce una funzione che chiama lo strumento MCP con quel nome. È un wrapper sopra callTool.
  • useSendMessage() — incapsula sendFollowUpMessage, così che il widget possa iniziare messaggi.
  • useOpenExternal() — helper comodo attorno a openExternal({ href }).
  • useRequestDisplayMode() e useRequestModal() — wrapper per richiedere cambio modalità / apertura modale.

Esempio base del mini-widget GiftGenius, che usa quasi tutto insieme:

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>Non ci sono ancora idee. Prova a chiedere a GPT di aggiornare i risultati.</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: "Mostra regali più costosi degli attuali." })
          }
        >
          Chiedi altre idee
        </button>

        <button
          onClick={async () => {
            await callSearch({ budget: 200 });
          }}
        >
          Aggiorna con budget di $200
        </button>

        {ui?.selectedId && (
          <button
            onClick={() =>
              openExternal({
                href: `https://giftgenius.example.com/checkout?id=${ui.selectedId}`,
              })
            }
          >
            Vai all'acquisto
          </button>
        )}
      </div>
    </div>
  );
}

Questa pagina è ancora grezza (nei moduli successivi miglioreremo UX, gestione degli errori ecc.), ma illustra già l’approccio: nessun accesso diretto a window.openai, solo hook.

5. Pratica: esploriamo la sandbox e window.openai

Per capire che cos’è «un widget non come un normale sito», è utile fare un paio di esercizi.

Esercizio: «Esplora l’ambiente»

Prendi il tuo attuale app/page.tsx nel widget e aggiungi al primo render un semplice effetto:

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>Eseguito dentro ChatGPT: {String(isChatGpt)}</p>
    </main>
  );
}

Apri DevTools: o direttamente nella finestra di ChatGPT (tramite il viewer integrato del tunnel, se lo consente), oppure nel browser locale aprendo la pagina direttamente. In entrambi i casi confronta:

  • in un browser normale, isChatGptApp sarà false e window.openai molto probabilmente undefined;
  • quando avviato tramite ChatGPT vedrai un oggetto con campi come toolInput, toolOutput, theme ecc.

È una buona intuizione: lo stesso codice React si comporta in modo diverso a seconda dell’ambiente, ed è proprio per questo che esistono gli hook.

Esercizio: «Mostra tutto ciò che offre la piattaforma»

Aggiungi un componente temporaneo per il debug:

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 inserisci temporaneamente <DebugPanel /> sotto l’UI principale. Vedrai chiaramente:

  • quali campi arrivano da MCP in toolOutput;
  • cosa c’è in _meta (per esempio, locale, userLocation e altro);
  • come cambia displayMode quando espandi il widget.

Poi puoi rimuovere questo componente o lasciarlo attivabile tramite un flag tipo DEBUG_WIDGET.

6. Relazioni: ChatGPT ↔ widget ↔ MCP/server

Per non considerare il widget come l’«attore principale» del sistema, è utile fissare di nuovo i ruoli.

  1. L’utente scrive un messaggio: «Scegli un regalo per la ragazza, budget 50$».
  2. Il modello di ChatGPT decide di chiamare il tuo strumento MCP search_gifts con gli argomenti { recipient: "girlfriend", budget: 50 }.
  3. Il server MCP esegue la logica di business e restituisce:
    • content con una breve descrizione per il modello;
    • structuredContent con l’array di regali;
    • _meta con dettagli tecnici (per esempio, source e valuta).
  4. ChatGPT:
    • mostra all’utente un messaggio testuale («Ho trovato alcune opzioni…»);
    • crea l’iframe del widget e vi passa structuredContent e _meta tramite window.openai.toolOutput e toolResponseMetadata.
  5. Il tuo widget:
    • renderizza l’UI in base a toolOutput;
    • durante le interazioni chiama callTool o invia follow-up;
  6. Il modello decide poi come usare i risultati di queste azioni.

Questo porta a una conclusione importante: il widget non è mai l’unico padrone del processo. È lo strato UI che vive nell’ecosistema formato dal modello e dal server MCP. Le cose complesse (autorizzazione, accesso a dati privati, logica di business seria) devono restare lato server. Il widget è responsabile di un’interfaccia comoda e di un’interazione accurata con l’utente.

7. Policy e regole del gioco nella sandbox

Tutta questa costruzione con iframe isolato e window.openai esiste per motivi di sicurezza e privacy. Le guide ufficiali di OpenAI sottolineano alcuni principi.

Primo, minimizzazione dei dati. Non devi cercare di estrarre quante più PII (personally identifiable information) possibile dall’utente tramite il widget e portarle da te. Tutto ciò che è davvero necessario deve essere descritto chiaramente negli strumenti, e il modello e il livello di sicurezza valuteranno attentamente tali chiamate.

Secondo, divieto di tracciamento nascosto e fingerprinting. Non costruire sistemi per «spiare» il dispositivo dell’utente, raccogliere impronte del browser o aggirare le limitazioni. Parametri come userAgent, userLocation ecc. sono suggerimenti per l’UX, non per l’autenticazione o l’identificazione.

Terzo, tutto ciò che inserisci in structuredContent, _meta, widgetState in qualche forma è visibile all’utente o può esserlo a un revisore dello Store. Pertanto:

  • non mettere lì API key, token, password o segreti amministrativi;
  • progetta lo stato del widget in modo che l’utente non si sorprenda a vederlo nei log o nel debug.

Quarto, chiamate di rete. Richieste dirette dal widget verso API di terze parti sono ammesse solo verso una lista stretta di domini e in scenari non sensibili. Quando si parla di denaro, account, dati personali — tutto deve passare attraverso MCP/backend.

8. Errori tipici nel lavoro in sandbox e con window.openai

Errore n. 1: pensare che il widget sia «un normale sito in iframe».
Per abitudine i neofiti provano ad accedere a window.parent, a modificare gli stili di ChatGPT o a usare localStorage come sempre. Nella sandbox questo o non funziona, o è instabile: l’origin è diversa, lo storage è isolato, l’accesso al DOM è bloccato. Accetta di vivere in un ambiente controllato e comunica con l’host solo tramite window.openai e gli hook.

Errore n. 2: toccare window.openai direttamente dappertutto.
Codice del tipo window.openai.toolOutput in dieci componenti porta a un’app difficile da debuggare. Inoltre devi gestire da solo eventi, asincronia e controlli di undefined. È molto più affidabile usare subito useWidgetProps, useWidgetState, useOpenAIGlobal e altri hook, che già incapsulano openai:set_globals e sincronizzano lo stato.

Errore n. 3: archiviare in widgetState di tutto (soprattutto segreti).
Talvolta viene voglia di infilarci per sicurezza un enorme oggetto con risultati di API o perfino un access token. Il risultato è un contesto che cresce, peggiora il lavoro del modello e viola requisiti basilari di sicurezza. widgetState deve essere piccolo, contenere solo segnali UI e mai dati confidenziali.

Errore n. 4: provare ad andare su Internet direttamente dal widget.
Le chiamate fetch("https://api.superbank.com/...") dalla sandbox quasi sicuramente incontreranno CORS e, anche se configuri tutto alla perfezione, sarà insicuro e difficile da gestire. Tutto ciò che riguarda account reali, denaro e dati personali va implementato come strumenti MCP e chiamato tramite callTool o attraverso la parte server.

Errore n. 5: contare sulla stabilità di window.openai fuori da ChatGPT.
A volte gli sviluppatori provano ad avviare il widget come SPA separata e non aggiungono controlli sul fatto che window.openai possa essere undefined. In dev questo si traduce in crash «Cannot read properties of undefined». Usa useIsChatGptApp, i controlli su typeof window !== "undefined" e un fallback UI per i casi in cui il widget non esista.

Errore n. 6: ignorare il contesto dell’ambiente (theme, displayMode, maxHeight, locale).
Puoi certo impostare un’altezza fissa di 2000px, tema scuro sempre e progettare per desktop — ma l’esperienza per l’utente sarà strana. La piattaforma ti fornisce segnali su spazio disponibile, tema e locale: usali tramite useOpenAIGlobal, useDisplayMode, useMaxHeight ecc., così che il widget sembri «di casa» in ChatGPT.

Errore n. 7: provare ad «aggirare» la policy tramite script di terze parti.
A volte viene la tentazione di caricare un tracker, un bundle JS esterno o eseguire codice da un dominio terzo «in silenzio». La sandbox e le policy CSP esistono proprio per evitarlo: gli script di terze parti vengono bloccati e i tentativi di aggirare il sistema portano direttamente al rifiuto della tua App nello Store.

Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION