Dış Link Açma (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.

Nerede geçerli

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.

Kod

openUrl — App Bridge postMessage
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çemedi

PANEL_ORIGIN = panelin gömüldüğü origin. Zaten bir App Bridge köprün varsa (bridgeCall) ayrı fonksiyon yazma — bridgeCall("openUrl", { url }) yeterlidir.

Dönen cevap

CevapAnlamı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.

Bilmen gereken dört şey

  1. window.open / target="_blank" çalışmaz — iframe sandbox'ında allow-popups yok; dış linke giden tek yol bu action.
  2. Panel önce onay dialogu gösterir. Kullanıcıya hedef adres gösterilir; promise o cevaplanana kadar bekler → çağrıya kısa bir timeout koyma (koyarsan kullanıcı onaylamadan "timeout" alırsın).
  3. URL kuralları: mutlak http(s) ve en fazla 2048 karakter. javascript:, data:, relative yol vb. openUrlDenied döner.
  4. 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.

Birden fazla marka / DEV panel

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:

çoklu origin — dizi + e.origin doğrulaması
// 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));
  });
}
Wildcard '*' 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.

Doğru kullanım

kullanıcı jesti + cevap ayrımı
// ✅ 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
});
  • Kullanıcı jestine bağla: sayfa yüklenirken otomatik openUrl çağırma — kullanıcı ne onayladığını anlamaz, dialog spam olur.
  • URL'yi kendi backend'inde üret. Kullanıcı girdisini/tenant'tan gelen serbest metni doğrudan geçirme (open-redirect); allowlist'lediğin kendi domain'lerine veya ödeme sağlayıcına yönlendir.
  • Butonu çift tıklamaya karşı kilitle (disabled) — dialog beklerken ikinci çağrı ikinci dialog demektir.
  • Kısa ömürlü linklerde süreye dikkat: checkout/imzalı URL'ler kullanıcı onaylayana kadar geçerli kalmalı; TTL'i dar tutma.
  • Sonucu linkten bekleme: dış sekmede ne olduğunu iframe göremez — ödeme/işlem sonucunu webhook veya kendi backend'inden doğrula.
  • Gate iframe'indeysen openUrl'den sonra da resolve/close çağırmayı unutma — openUrl gate'i sonuçlandırmaz, işlem beklemede kalır.

Hata → çözüm

BelirtiSebepÇözüm
Link tıklanınca hiçbir şey olmuyorwindow.open / <a target="_blank"> kullanılmışopenUrl action'ına geç
openUrlDeniedRelative 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üyorGelen 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ı)