openUrl) ✓ Canlıiframe içindeki Custom UI sayfan yeni bir sekme/pencere açamaz — sandbox'ta allow-popups yoktur, window.open ve <a target="_blank"> çalışmaz. Dış bir adrese (ödeme sayfası, rapor, yardım dokümanı) gitmenin tek yolu App Bridge'in openUrl action'ıdır: panel kullanıcıya hedef adresi gösteren bir onay dialogu çıkarır, kullanıcı onaylarsa link açılır.
← Custom UI Sayfaları · ilgili: App Bridge & Gate iframe · iframe Güvenliği.
window.open ve <a target="_blank"> ÇALIŞMAZ. iframe sandbox'ında allow-popups yok — sessizce hiçbir şey olmaz. Dış linke giden tek yol bu action'dır.Her Custom UI iframe yüzeyinde kullanılabilir: menüden açılan sayfalar, kur/ayarlar sayfası ve hook gate iframe'leri. Webhook/action gibi sunucu tarafı yüzeylerde iframe yoktur — orada kullanıcıyı yönlendiremezsin.
const PANEL_ORIGIN = "https://app.restomenum.com"; // panelin gömüldüğü origin
function openUrl(url) {
return new Promise((resolve) => {
const requestId = "openUrl-" + Date.now();
function onMessage(e) {
if (e.origin !== PANEL_ORIGIN) return;
const m = e.data;
if (!m || m.type !== "restomenum-bridge-response" || m.requestId !== requestId) return;
window.removeEventListener("message", onMessage);
resolve(m.result);
}
window.addEventListener("message", onMessage);
window.parent.postMessage(
{ type: "restomenum-bridge", requestId, action: "openUrl", params: { url } },
PANEL_ORIGIN
);
});
}
// kullanım
const res = await openUrl("https://ornek.com/rapor");
// { success: true, data: { opened: true } } → kullanıcı onayladı
// { success: true, data: { opened: false } } → kullanıcı iptal etti
// { success: false, message: "openUrlDenied" } → URL doğrulamayı geçemediPANEL_ORIGIN = panelin gömüldüğü origin. Zaten bir App Bridge köprün varsa (bridgeCall) ayrı fonksiyon yazma — bridgeCall("openUrl", { url }) yeterlidir.
| Cevap | Anlamı | Ne yapmalısın |
|---|---|---|
| { success: true, data: { opened: true } } | Kullanıcı onayladı, adres açıldı. | Akışa devam et (sonucu kendi backend'inden/webhook'undan öğren). |
| { success: true, data: { opened: false } } | Kullanıcı onay dialogunu iptal etti. | Hata gösterme — sessizce geç, buton tekrar denenebilir kalsın. |
| { success: false, message: "openUrlDenied" } | URL doğrulamayı geçemedi (kullanıcıya sorulmadı bile). | Geliştirici hatası → URL'yi düzelt (aşağıdaki kurallar), logla. |
window.open / target="_blank" çalışmaz — iframe sandbox'ında allow-popups yok; dış linke giden tek yol bu action.http(s) ve en fazla 2048 karakter. javascript:, data:, relative yol vb. openUrlDenied döner.PANEL_ORIGIN markaya göre değişir (ayrı markalar = ayrı origin'ler) ve document.referrer'dan öğrenilemez — iframe referrerPolicy="no-referrer" ile yüklenir, referrer boştur. Birden fazla markada çalışacaksan origin'leri dizi tutup gelen e.origin'i o listeye karşı doğrula.DEV panel dahil bilinen panel origin'leri: https://app.restomenum.com · https://test-restomenu.web.app. Aynı sayfa birden fazla origin altında açılacaksa listeyi dizi tut:
// Birden fazla markada/ortamda çalışıyorsan origin'leri DİZİ tut ve gelen e.origin'i
// bu listeye karşı doğrula. Üst pencerenin origin'ini document.referrer'dan öğrenemezsin
// (iframe referrerPolicy="no-referrer" → referrer BOŞ) — hedefi listeden pinle.
const PANEL_ORIGINS = ["https://app.restomenum.com","https://test-restomenu.web.app"];
function openUrl(url) {
return new Promise((resolve) => {
const requestId = "openUrl-" + Date.now();
function onMessage(e) {
if (!PANEL_ORIGINS.includes(e.origin)) return; // gelen origin listeyle doğrulanır
const m = e.data;
if (!m || m.type !== "restomenum-bridge-response" || m.requestId !== requestId) return;
window.removeEventListener("message", onMessage);
resolve(m.result);
}
window.addEventListener("message", onMessage);
const msg = { type: "restomenum-bridge", requestId, action: "openUrl", params: { url } };
// Hedef origin PİNLİ ('*' YASAK). targetOrigin eşleşmeyen gönderim tarayıcı tarafından
// TESLİM EDİLMEZ → mesajı yalnız gerçekten çerçeveleyen panel alır.
PANEL_ORIGINS.forEach((origin) => window.parent.postMessage(msg, origin));
});
}'*' ile postMessage yapma ve gelen e.origin'i doğrulamadan işleme — sürüm incelemesinde reddedilir (iframe Güvenliği). Sayfanın CSP frame-ancestors header'ı da bu origin'leri içermeli.// ✅ Kullanıcı jestine bağla — sayfa açılışında otomatik çağırma (onay dialogu spam olur).
payBtn.addEventListener("click", async () => {
payBtn.disabled = true;
const res = await openUrl(checkoutUrl); // ← TIMEOUT KOYMA: kullanıcı onayı bekleniyor
payBtn.disabled = false;
if (!res.success) { // "openUrlDenied" → URL doğrulamayı geçemedi (senin hatan)
console.error("openUrl reddedildi:", res.message);
return;
}
if (!res.data.opened) return; // kullanıcı iptal etti → hata DEĞİL, sessizce geç
// açıldı: kullanıcı dış sekmede işlemi tamamlayacak → sonucu webhook/backend'inden öğren
});openUrl çağırma — kullanıcı ne onayladığını anlamaz, dialog spam olur.disabled) — dialog beklerken ikinci çağrı ikinci dialog demektir.openUrl'den sonra da resolve/close çağırmayı unutma — openUrl gate'i sonuçlandırmaz, işlem beklemede kalır.| Belirti | Sebep | Çözüm |
|---|---|---|
| Link tıklanınca hiçbir şey olmuyor | window.open / <a target="_blank"> kullanılmış | openUrl action'ına geç |
openUrlDenied | Relative yol · javascript:/data: şeması · 2048 karakter aşımı | Mutlak https://… gönder, URL'yi kısalt (uzun query yerine tek kullanımlık token) |
| Promise hiç dönmüyor | Gelen e.origin senin listende yok (farklı marka/DEV panel) veya postMessage hedefi yanlış origin'e pinlenmiş | Panel origin'lerini diziye ekle; hedefi listeden pinle (referrer'dan tespit etme) |
Kullanıcı onaylamadan timeout aldım | Çağrıya kısa timeout konmuş | openUrl çağrısında timeout kullanma (onay kullanıcıya bağlı) |