180 lines
6.7 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Facebook OAuth2 Callback</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: #18191a;
color: #e4e6eb;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
text-align: center;
}
.card {
background: #242526;
border-radius: 12px;
padding: 36px 44px;
max-width: 440px;
width: 90%;
box-shadow: 0 8px 32px rgba(0,0,0,0.5);
}
.icon { font-size: 3.2rem; margin-bottom: 16px; }
h2 { font-size: 1.25rem; margin-bottom: 10px; font-weight: 600; }
p { font-size: 0.9rem; color: #aaa; line-height: 1.5; }
.error { color: #f44336; }
.success { color: #4caf50; }
.warn { color: #ff9800; }
.spinner {
width: 36px; height: 36px;
border: 3px solid rgba(45,136,255,0.2);
border-top-color: #2D88FF;
border-radius: 50%;
animation: spin 0.8s linear infinite;
margin: 0 auto 16px;
}
@keyframes spin { to { transform: rotate(360deg); } }
.debug {
margin-top: 16px;
background: #1a1a1a;
border-radius: 6px;
padding: 10px 14px;
font-size: 0.75rem;
color: #666;
text-align: left;
word-break: break-all;
white-space: pre-wrap;
display: none;
}
</style>
</head>
<body>
<div class="card">
<div class="spinner" id="spinner"></div>
<div class="icon" id="icon" style="display:none"></div>
<h2 id="title">Procesando autorización…</h2>
<p id="msg">Por favor espera</p>
<div class="debug" id="debug"></div>
</div>
<script>
(function () {
'use strict';
var TARGET_ORIGIN = '*';
function show(icon, title, msg, cls) {
document.getElementById('spinner').style.display = 'none';
var iconEl = document.getElementById('icon');
iconEl.style.display = 'block';
iconEl.textContent = icon;
document.getElementById('title').textContent = title;
var msgEl = document.getElementById('msg');
msgEl.textContent = msg;
msgEl.className = cls || '';
}
function showDebug(text) {
var el = document.getElementById('debug');
el.style.display = 'block';
el.textContent = text;
}
function parseParams(str) {
var params = {};
if (!str) return params;
str.replace(/^[?#]/, '').split('&').forEach(function (part) {
var idx = part.indexOf('=');
if (idx > -1) {
params[decodeURIComponent(part.slice(0, idx))] =
decodeURIComponent(part.slice(idx + 1).replace(/\+/g, ' '));
}
});
return params;
}
var hashParams = parseParams(window.location.hash);
var queryParams = parseParams(window.location.search);
// ── ERROR de Facebook ─────────────────────────────────────────────────
var errorCode = hashParams.error || queryParams.error;
var errorDesc = hashParams.error_description || queryParams.error_description
|| hashParams.error_reason || queryParams.error_reason
|| 'Autorización cancelada o denegada';
if (errorCode) {
show('❌', 'Autorización fallida', errorCode + ': ' + errorDesc, 'error');
showDebug('error: ' + errorCode + '\ndescription: ' + errorDesc);
if (window.opener) {
window.opener.postMessage({ type: 'fb_oauth_result', error: errorCode + ': ' + errorDesc }, TARGET_ORIGIN);
}
setTimeout(function () { window.close(); }, 4000);
return;
}
// ── AUTH CODE (response_type=code) — flujo preferido con backend ──────
// El backend (server/index.js) intercambia: code → short-lived → long-lived (60 días)
var code = queryParams.code;
var state = queryParams.state || '';
if (code) {
show('🔄', 'Intercambiando código…',
'Obteniendo token de larga duración (60 días)…', 'success');
// Enviar el code a la ventana principal; ella llamará al backend
if (window.opener) {
window.opener.postMessage(
{ type: 'fb_oauth_result', flow: 'code', code: code, state: state,
redirect_uri: window.location.origin + '/oauth/facebook/callback.html' },
TARGET_ORIGIN
);
show('✅', '¡Código enviado!', 'Procesando token de larga duración…', 'success');
setTimeout(function () { window.close(); }, 2500);
} else {
show('⚠️', 'Ventana sin opener',
'Esta página debe abrirse como popup desde Restreamer.', 'warn');
showDebug('code recibido pero window.opener es null.\ncode: ' + code.slice(0, 20) + '…');
}
return;
}
// ── TOKEN IMPLÍCITO (response_type=token) — fallback 2h ──────────────
// Se usa cuando el App Secret no está configurado en el backend.
var accessToken = hashParams.access_token || queryParams.access_token;
var expiresIn = parseInt(hashParams.expires_in || queryParams.expires_in || '0', 10);
if (accessToken) {
show('✅', '¡Token recibido!', 'Enviando a Restreamer para upgrade…', 'success');
if (window.opener) {
window.opener.postMessage(
{ type: 'fb_oauth_result', flow: 'token',
access_token: accessToken, expires_in: expiresIn },
TARGET_ORIGIN
);
setTimeout(function () { window.close(); }, 1800);
} else {
show('⚠️', 'Ventana sin opener',
'Copia el token manualmente.', 'warn');
showDebug('access_token: ' + accessToken.slice(0, 30) + '…\nexpires_in: ' + expiresIn);
}
return;
}
// ── Sin datos útiles ──────────────────────────────────────────────────
show('⚠️', 'Sin datos de autorización',
'No se recibió code ni token. Revisa la configuración de tu App de Facebook.', 'warn');
showDebug('hash: ' + window.location.hash + '\nsearch: ' + window.location.search);
if (window.opener) {
window.opener.postMessage({ type: 'fb_oauth_result', error: 'no_data_received' }, TARGET_ORIGIN);
}
setTimeout(function () { window.close(); }, 6000);
})();
</script>
</body>
</html>