1. Çölə çıxan iki yol: naviqasiya və məlumatlar
Adi bir Next.js proqramçısı “serverə getmək lazımdır” deyəndə, avtomatik olaraq fetch-ə və ya sevdiyi HTTP‑klientə əl atır. ChatGPT Apps dünyasında belə refleks ağrıya gətirib çıxarır.
ChatGPT Apps-də vidcetlərin təhlükəsizliyinə həsr olunmuş hissədə biz həmin köhnə refleksi lap əvvəlcədən sındırmağı təklif edirik. Vidcet açıq internetdə yaşamır: o, sərt izolyasiyadadır və şəbəkə girişi hostun siyasətləri ilə filtrdən keçirilir və məhdudlaşdırılır.
Vidzcet üçün çölə cəmi üç əsas “pəncərə” var:
- Naviqasiya: istifadəçini harasa xaricə aparmaq. Bunun üçün openExternal var.
- Məlumat mübadiləsi: JSON almaq/göndərmək, backend ilə danışmaq. Bu, fetch vasitəsilə edilir, amma ciddi məhdudiyyətlər ola bilər.
- MCP tool call: heç bir məhdudiyyəti olmayan alətlərin (MCP / backend) çağırışı.
Bu mühazirədə biz birinci və ən təhlükəsiz yolun (naviqasiya) üzərində fokuslanırıq və nəzarət olunan fetch ilə ehtiyatla tanış oluruq. Növbəti modullarda əsas serverlə ciddi ünsiyyət vasitəsi kimi MCP və alətləri müzakirə edəcəyik.
2. openExternal: istifadəçini təhlükəsiz “teleport” etmək
Niyə sadəcə window.open etmək olmaz
Adi veb‑tətbiqdə siz təxminən belə edərdiniz:
window.open("https://example.com", "_blank");
ChatGPT sandbox‑unda bu ya ümumiyyətlə işləməyəcək, ya da çox qəribə davranacaq. Vidcet — sərt sandbox ilə izolyasiya olunmuş iframe-dir və brauzer vərəqəsinin eyni hüquqlarına malik deyil.
Bundan əlavə, ChatGPT host siz istifadəçini hara və nə zaman apardığınızı nəzarətdə saxlamaq istəyir ki:
- gizli trekinqə yol verilməsin;
- istifadəçiyə anlaşılan təsdiq UI‑ı göstərilsin (xüsusilə mobil/desktop müştərilərdə);
- fərqli mühitlərdə (web, desktop, mobil tətbiq) linklərin eyni davranışı təmin olunsun.
Ona görə də xüsusi openExternal API‑si fikirləşilib və window.openai vasitəsilə və ya daha rahat React hook-u useOpenExternal vasitəsilə əlçatandır.
useOpenExternal necə görünür
Rəsmi Apps SDK nümunələrində useOpenExternal hook-u təxminən belə reallaşdırılıb:
export function useOpenExternal() {
const openExternal = useCallback((href: string) => {
if (typeof window === "undefined") return;
if (window?.openai?.openExternal) {
try {
window.openai.openExternal({ href });
return;
} catch (error) {
console.warn("openExternal failed, falling back to window.open", error);
}
}
window.open(href, "_blank", "noopener,noreferrer");
}, []);
return openExternal;
}
Əsas fikir sadədir. Əvvəlcə ChatGPT‑nin natıv mexanizmindən istifadə etməyə çalışırıq (window.openai.openExternal). Əgər vidcet birdən ChatGPT‑də render olunmursa (məsələn, işləmə zamanı onu sadəcə brauzerdə açmısınızsa), zərifcə window.open-ə geri düşürük.
Sizin tətbiqinizdə bu hook artıq şablonda mövcuddur (əgər OpenAI‑nin standart repozitorisini götürmüsünüzsə) və ondan məhz belə istifadə etmək lazımdır — birbaşa window.openai-a girməyin.
Nümunə: GiftGenius-də “Mağazada bax” düyməsi
Tutaq ki, GiftGenius‑un toolOutput-unda productUrl sahəsi olan tövsiyələr gəlir. Hər karta bir düymə əlavə edək ki, məhsulu sizin saytda açsın:
import { useWidgetProps } from "../hooks/use-widget-props";
import { useOpenExternal } from "../hooks/use-open-external";
export function GiftListWidget() {
const { toolOutput } = useWidgetProps<{
recommendations: { id: string; title: string; price: string; url: string }[];
}>();
const openExternal = useOpenExternal();
if (!toolOutput) return <p>Hələ tövsiyə yoxdur…</p>;
return (
<div>
{toolOutput.recommendations.map((gift) => (
<div key={gift.id} className="flex justify-between gap-2">
<div>
<div>{gift.title}</div>
<div className="text-sm text-muted-foreground">{gift.price}</div>
</div>
<button onClick={() => openExternal(gift.url)}>
Aç
</button>
</div>
))}
</div>
);
}
İstifadəçi baxımından: o, düyməni sıxır, ChatGPT “Xarici saytı açım?” tipli sistem pəncərəsi göstərə bilər, sonra isə səhifənizi yeni vərəqdə və ya standart brauzerdə açacaq. Siz ora heç bir sirr, token və s. daşımırsınız, sadəcə insanı “çatdan sayta” aparırsınız.
3. Sandbox-da window.fetch: öyrəşdiyiniz fetch deyil
Frontender adətən nə gözləyir
Adətən məntiq belədir: “Bu brauzerdirsə, deməli CORS qurulmuş istənilən URL‑ə rahatlıqla müraciət edə bilərəm. Ən pis halda səhv alaram, amma cəhd etmək olar.”
ChatGPT Apps ekosistemində bu təhlükəli yanılmadır. Vidcet ətrafındakı sandbox sadəcə “xırda irad” deyil, təhlükəsizliyin fundamental tələbi̇dir: vidcet istifadəçini trekləyə bilməsin, istənilən domenə çıxmasın, lokal şəbəkəni skan etməsin və ümumiyyətlə brauzerin içində mini‑brauzer kimi davranmasın.
Eyni zamanda vurğulanır ki, Apps SDK vidcetində ixtiyari şəbəkə girişi ya yoxdur, ya da ciddi şəkildə məhdudlaşdırılıb — və bu, xəta deyil, şüurlu memarlıq qərarıdır.
Bu praktikada necə görünür
Tipik ChatGPT mühitində:
- fetch əlçatan ola bilər, amma yalnız məhdud domen siyahısına (adətən App‑inizin çalışdığı domen və bəlkə açıq-aşkar icazə verilmiş bir‑iki API);
- sorğular hostun xüsusi proksisi vasitəsilə gedə bilər, hansı ki, başlıqları və URL‑ləri filtrlayır;
- bəzi metodlar (PUT, DELETE) və ya qeyri‑standart başlıqlar təhlükəsizlik siyasətləri tərəfindən bloklana bilər.
Bununla belə, rahat yolunuz hələ də var: əgər vidcetiniz və backendiniz eyni domendə yaşayırsa (Next.js şablonunda olduğu kimi, həm MCP‑server, həm də UI eyni tətbiq tərəfindən servis olunur), daxili sorğular fetch("/api/...") adətən icazəli olur.
Əsas — ondan asılı qalmamaqdır ki, vidcet internette istənilən API‑yə gedə biləcək. Bütün “qalın” ünsiyyət (Stripe, Notion, CRM və s.) MCP/backend tərəfində baş verməlidir; ChatGPT isə ora etibarlı resurs kimi müraciət edir.
Insight
ChatGPT vidcetində nisbi yollardan dərhal imtina etmək və tam absolut URL‑lərlə yaşamaq lazımdır. Səbəb sadədir: HTML‑iniz backend ilə eyni domendə işləmir. ChatGPT HTML‑inizi oxuyur, öz hostuna qoyur və izolyasiya olunmuş iframe‑də render edir. Hər hansı "/api/..." və ya "/static/logo.png" birdən‑birə sizin tətbiqinizin deyil, ChatGPT domeninə görə həll olunur — və hər şey dağılır.
<base> burada demək olar ki, kömək etmir. Təcrübə göstərir ki, əgər vidcet üçün heç bir widgetCSP verilməyibsə, siz <base href="https://my-app.dev/"> yaza bilərsiniz: resurslar sizin domendən gələcək, amma skriptlər sandbox qaydalarına görə yenə də işləməyəcək. Və bu yalnız Dev Mode‑da işləyir.
Amma normal openai/widgetCSP təyin edən kimi (və prod‑da review üçün onsuz da lazım olacaq), platforma <base> etiketi̇ni sıfırlayır və oyun bitir: resurslar və skriptlər yalnız CSP‑də icazə verilmiş domenlərdən, absolut linklərlə yüklənir.
Tövsiyə: ChatGPT vidcetində çölə çıxan hər şey — fetch, şəkillər, CSS, openExternal üçün səhifələr — həmişə tətbiqinizin əsas domenindən tam URL kimi qurulmalıdır; bunu konfiq/ENV vasitəsilə idarə edin, nisbi yollar və <base> vasitəsilə yox.
4. Memarlıq: nazik UI, qalın backend
fetch məhdudiyyətləri və ümumi sandbox daha geniş memarlıq prinsipinə gətirir ki, bu da bütün kurs üçün vacibdir. Biz artıq bir neçə dəfə bu mantranı demişik, indi isə möhkəmləndirməyin vaxtıdır: vidcet — nazik UI qatıdır. O, backendin (MCP/tools vasitəsilə) artıq hazırladığı şeyi render edir, istifadəçi hərəkətlərinə reaksiyalar göstərir və ən sonunda bir‑iki kiçik publik sorğu edir.
Avtorizasiya, şəxsi məlumatlara çıxış, sirlər və qeyri‑sadə biznes məntiqi ilə bağlı olan hər şey server tərəfində yaşamalıdır. Kursun təhlükəsizlik sənədləri ayrıca vurğulayır: frontend (React vidceti) — “public place”, sıfır etibar zonasıdır və orada sirlər saxlanmamalıdır.
Mövzu üzrə araşdırmalarım hədəfi sərt formalaşdırır: ChatGPT Apps üçün “qalın klient” ideyasının tabutuna “son mismarı” vurmaq. Vidcet — yalnız başdır, bədən və beyin isə MCP/backenddədir.
Ona görə də:
- openExternal — istifadəçini “normal” saytınıza aparmaq üçün; orada artıq alışdığınız SPA, şəxsi kabinet və s. işləyə bilər;
- callTool (növbəti modul) — modeli̇n sizin backendin yerinə yetirəcəyi tapşırığı alması üçün əsas üsul;
- vidcetdən fetch — öz tətbiqinizə kiçik, təhlükəsiz və məqsədəuyğun olaraq publik sorğular üçün nadir qəhrəman.
5. Praktika: openExternal bizim GiftGenius‑da
Gəlin openExternal-ı tədris App‑imizə bir az daha səliqəli yerləşdirək və eyni zamanda UX barədə düşünək.
Mini UX qaydası
İstifadəçini xaricə aparırsınızsa, faydalıdır:
- dəqiq göstərmək ki, o hara düşəcək;
- mətndə izah etmədən gözlənilməz “atlamalar” etməmək (ya GPT bildirir: “Mağazanın saytını açacağam…”, ya da siz düyməni aydın imzalayırsınız).
Başlıq və yazı nümunəsi:
<button onClick={() => openExternal(gift.url)}>
Mağazanın saytında aç
</button>
İstifadəçi anlayır ki, indi onu isti çatdan real dünyaya — səbət və ödəniş olan yerə aparacaqlar.
Siyahı komponentinin kiçik refaktorinqi
Əvvəllər biz sadə GiftListWidget hazırlamışdıq. Tutaq ki, əvvəlki mühazirələrdə artıq toolOutput-a görə hədiyyə siyahısını göstərən vidcet hazırlamısınız. İndi bir qədər səliqəli versiya edəcəyik: url sahəsi olan Gift tipini və openExternal düyməsini əlavə edəcəyik.
type Gift = {
id: string;
title: string;
priceLabel: string;
url: string;
};
export function GiftListWidget() {
const { toolOutput } = useWidgetProps<{ gifts: Gift[] }>();
const openExternal = useOpenExternal();
if (!toolOutput || toolOutput.gifts.length === 0) {
return <p>Hələ heç nə tapılmadı. Sorğunu dəyişməyə çalışın.</p>;
}
return (
<div>
{toolOutput.gifts.map((gift) => (
<div key={gift.id} className="flex justify-between gap-2">
<div>
<div>{gift.title}</div>
<div className="text-sm text-muted-foreground">
{gift.priceLabel}
</div>
</div>
<button onClick={() => openExternal(gift.url)}>
Bax
</button>
</div>
))}
</div>
);
}
Biz hələ də birbaşa window.openai ilə işləmirik, rahat hook‑dan istifadə edirik — o, ChatGPT mühiti olmayanda window.open-ə geri düşməyi artıq bacarır. Gift strukturu burada təxmini̇dir — öz App‑inizdə backendinizə uyğunlaşdıracaqsınız.
6. Praktika: backendimizə səliqəli fetch
İndi isə fetch-ə baxaq. Bir daha xatırladım: mürəkkəb və ya həssas əməliyyatları alətlər/MCP vasitəsilə etmək daha yaxşıdır. Amma bəzən vidcetdən öz serverinizdən yüngül və publik nəsə çəkmək istəyirsiniz, məsələn, populyar hədiyyə kateqoriyalarının siyahısını.
Next.js‑də sadə publik API marşrutu
Next.js layihəmizə belə bir handler əlavə edək:
// app/api/public/popular-tags/route.ts
import { NextResponse } from "next/server";
const tags = ["Uşaqlar üçün", "Səyahətçilər üçün", "Oyunçular üçün"];
export async function GET() {
return NextResponse.json({ tags });
}
Bu marşrut istifadəçi barədə heç nə bilmir, token tələb etmir, xarici servislərə getmir — sadəcə statik massiv qaytarır. Belə kodu az risklə həm prodakşnda, həm də sandbox‑da işlətmək olar.
Bu marşrutu vidcetdən fetch vasitəsilə çağırmaq
İndi vidcet komponentində bu teqlərin yüklənməsini əlavə edək. Sandbox məhdudiyyətlərini nəzərə alaraq sorğunu absolut URL‑ə etmək daha rahatdır: App‑inizin işlətdiyi eyni domenə — Dev Mode ChatGPT‑də qeydiyyatdan keçirdiyiniz və tunel vasitəsilə ötürdüyünüz domenə (bunu Dev Mode və tunel modulunda qurmuşduq).
Vacibdir: vidcetinizin domeni https://genius.web-sandbox.oaiusercontent.com tipində olacaq, buna görə də məlumat yükləmək üçün nisbi yolları istifadə etməyin, yalnız absolut yollar. Nümunə:
import { useEffect, useState } from "react";
export function PopularTags() {
const [tags, setTags] = useState<string[] | null>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
let cancelled = false;
async function loadTags() {
try {
const res = await fetch("https://giftgenius.app/api/public/popular-tags");
if (!res.ok) throw new Error("Bad status");
const data: { tags: string[] } = await res.json();
if (!cancelled) setTags(data.tags);
} catch (e) {
if (!cancelled) setError("Məşhur kateqoriyaları yükləmək mümkün olmadı");
}
}
loadTags();
return () => {
cancelled = true;
};
}, []);
if (error) return <p>{error}</p>;
if (!tags) return <p>Məşhur kateqoriyalar yüklənir…</p>;
return (
<div className="flex flex-wrap gap-2 text-sm">
{tags.map((tag) => (
<span key={tag} className="rounded border px-2 py-1">
{tag}
</span>
))}
</div>
);
}
Vacib məqamlar:
- səhvləri səliqəli emal edirik və istifadəçiyə anlaşılan mesaj göstəririk;
- fetch-in “mütləq işləyəcəyinə” bel bağlamırıq — domeni dəyişsəniz və ya qəribə sorğular etsəniz, sandbox siyasətləri girişləri istənilən an kəsə bilər;
- burada heç bir token/sirri ötürmürük; autentifikasiya lazım olsa — bu artıq MCP və Auth modullarının işidir.
7. openExternal vs fetch vs alətlər (callTool): kim nəyə cavabdehdir
Qarışdırmamaq üçün belə bir “öhdəliklər matrisi”ni yadda saxlamaq rahatdır:
| Ssenari | Nədən istifadə edirik | Niyə məhz belə |
|---|---|---|
| Lendinq/məhsul/kabinet açmaq | openExternal | Host tərəfindən idarə olunan, istifadəçinin açıq keçidi |
| App‑dən publik məlumat almaq | fetch("my.com/api/...") | Yüngül JSON, eyni domen, sirr yoxdur |
| İstifadəçi məlumatı, DB‑dən məlumat çıxarmaq | callTool/MCP | Avtorizasiya, məntiq, təhlükəsiz backend lazımdır |
| Xarici API‑lərə getmək (Stripe…) | MCP/server | Front sirləri görmür, siyasətlərə əməl olunur |
Bu modulda bizə şüurlu alət seçməyi öyrənmək vacibdir. “Vidzcet = frontend, deməli hər şeyi fetch ilə edə bilərəm” düşüncəsindən “vidcet = LLM+MCP backendin üzərində idarə olunan UI qatı” memarlığına keçmək lazımdır.
Insight
ChatGPT App‑də serverlə qarşılıqlı əlaqəni iki səviyyəyə bölmək məntiqlidir:
- ChatGPT ↔ MCP‑server: model MCP alətlərini çağırır. Hər alət çağırışı — biznes ssenarisinin işə salınması və ya dəyişdirilməsidir (hədiyyə seçimi, sifarişin yaradılması, məbləğin hesablanması və s.). Burada “ağır” məntiq, məlumatlarla iş, xarici API‑lər və avtorizasiya yaşayır.
- Vidzcet ↔ server: vidcet öz backendinə yüngül fetch() sorğuları edir və/və ya eyni MCP alətlərini callTool() vasitəsilə artıq aktiv ssenarinin daxilində çağırır. Bunlar lokal addımlardır: köməkçi məlumatları yükləmək, UI‑ın bir hissəsini yeniləmək, vəziyyəti dəqiqləşdirmək.
Yəni MCP‑aləti = biznes prosesinin işə salınması/idarə olunması, fetch()/callTool() isə vidcetdən artıq seçilmiş ssenarinin daxilindəki kiçik əməliyyatlardır, dialoqun ümumi “hekayəsini” dəyişdirmək iddiasında deyillər.
8. Kiçik praktiki tapşırıq
Mövzunu praktika ilə möhkəmləndirmək üçün GiftGenius‑da kiçik bir fıça yazmaq olar.
Təklif olunan ssenari:
- Hədiyyə siyahısında “Rəsmiləşdirməyə keç” düyməsi əlavə edin; bu düymə openExternal vasitəsilə dev saytınızda sifariş rəsmiləşdirmə səhifəsini açsın.
- Hədiyyə siyahısının üzərində yuxarıdakı nümunədən PopularTags komponentini render edin ki, populyar kateqoriyaları göstərsin. Yükləmə xətasında fallback mətni göstərin və bütün vidceti sındırmayın.
- UX‑ə fikir verin: GPT cavab mətnində və ya vidcet UI‑ında istifadəçiyə izah edin ki, “düyməni kliklədikdə mağazanın səhifəsini yeni vərəqdə açacağam”.
Bu fıça miniatürdə iki kanalı göstərir:
- aydın naviqasiya üçün openExternal;
- App‑inizin yanında yaşayan kiçik publik API üçün fetch.
9. window.fetch və openExternal ilə işləyərkən tipik səhvlər
Səhv №1: vidceti bütün API‑lərinizə tamhüquqlu SPA‑klient kimi istifadə etməyə cəhd etmək.
Köhnə vərdişlər “gəlin REST/GraphQL‑i birbaşa React‑dən vuraq” istiqamətinə güclü dartır. ChatGPT Apps dünyasında bu, sandbox ilə birbaşa toqquşmaya gətirir: sorğuların bir hissəsi keçməyəcək, bir hissəsi siyasətlər tərəfindən bloklanacaq, layihənin təhlükəsizliyi isə sual altında qalacaq. Mürəkkəb məntiq və istifadəçi məlumatlarına çıxış MCP/alətlər vasitəsilə getməlidir, birbaşa vidcetdən yox.
Səhv №2: sirləri və tokenləri vidcet kodunda saxlamaq.
Bəzən “tez prototipləşdirək” deyib front‑koduna hər hansı servisin API açarını yazmaq istəyirlər (“axı mən sadəcə test edirəm”). Bu, adi SPA üçün belə pis fikirdir, ChatGPT Apps üçün isə qətiyyən olmaz. Vidcet — publik mühitdir; sirlər server konfiqində və ya sirr idarəetmə sistemlərində (məs., Vercel env, KMS və s.) yaşamalıdır.
Səhv №3: istənilən domenə fetch‑in “sadəcə işləyəcəyini” zənn etmək.
Hətta Dev Mode‑da hansısa sorğu keçsə belə (məsələn, tunel standart olmayan şəkildə ötürüldüyünə görə), prod mühitində o, demək olar ki, mütləq sınacaq: ChatGPT çıxan sorğuları məhdudlaşdırır və ixtiyari xarici domen vidcet üçün əlçatan deyil. Vidcet yalnız öz domeninə və açıq‑aşkar çox kiçik ağ siyahıya daxil edilmiş resurslara etibarlı şəkildə gedə bilər — buna uyğunlaşın.
Səhv №4: window.open‑dən openExternal əvəzinə istifadə etmək.
Texniki baxımdan bəzən window.open işləyə bilər, xüsusilə brauzer önbaxışında, və “hər şey qaydasındadır” illüziyası yaranır. Amma real ChatGPT mühitində, xüsusən natıv müştərilərdə, davranış qeyri‑proqnozlaşdırılandır. İstifadəçi keçidi ümumiyyətlə görməyə bilər və ya qəribə səhv alar. Düzgün yol — cari mühitdə linki düzgün açmağı bilən openExternal-dan (hook useOpenExternal vasitəsilə) istifadə etməkdir.
Səhv №5: fetch xətalarını emal etməmək və istifadəçiyə yüklənmə vəziyyətini göstərməmək.
Sandbox‑da şəbəkə xətaları istisna deyil, normadır: tunel düşə bilər, domen dəyişə bilər, siyasətlər nəyisə kəsə bilər. Əgər sadəcə await fetch(...) edirsiniz və sonra məlumatın var olduğunu güman edib UI render edirsinizsə — “bəzən işləyən, bəzən yox” qəribə, yarım‑sınıq interfeys alacaqsınız. Həmişə try/catch qoyun, res.ok-u yoxlayın, “Yüklənir…” və səliqəli xəta mesajı göstərin.
Səhv №6: openExternal‑ı gizli yönləndirməyə çevirmək.
Bəzən istənilən düymədə istifadəçini dərhal, kontekstsiz olaraq, xarici sayta — xüsusən checkout‑a — aparmaq istəyirlər. Bu, həm istifadəçi, həm də Store reviewerləri üçün qəribə görünür. Yaxşı ton — nə baş verəcəyini açıq şəkildə yazmaqdır: ya GPT model bildirir “Mağazanın səhifəsini açacağam…”, ya da düymənin özü kifayət qədər şəffaf imzalanır (“Ödənişə mağazanın saytında keçin”).
Səhv №7: vidcetin dialoqun yeganə “sahibi” olduğunu unutmaq.
Əgər UI bir çox link və şəbəkə sorğusu ilə mürəkkəb ssenarini zorla təklif etməyə çalışırsa, çatın özünü və follow‑up‑ları görməzlikdən gələrək, həm UX pisləşir, həm də modelin işi. Memarlığı xatırlayın: GPT App‑i nə vaxt göstərəcəyinə, nəticələrdən necə istifadə edəcəyinə qərar verir, vidcet isə sadəcə məsləhət verir və vizuallaşdırır. Naviqasiyanı və şəbəkə çağırışlarını elə layihələndirin ki, ümumi dialoqa uyğun gəlsin, hər şeyi özünə çəkməsin.
GO TO FULL VERSION