Add YouTube OAuth callback URL configuration and enhance token handling
This commit is contained in:
parent
f866802864
commit
3a2edb1e30
@ -31,6 +31,10 @@ services:
|
||||
# Debe coincidir con lo que tienes en developers.facebook.com
|
||||
FB_OAUTH_CALLBACK_URL: "https://djmaster.nextream.sytes.net/oauth/facebook/callback.htm"
|
||||
|
||||
# URL EXACTA registrada en Google Console como "Authorized redirect URI"
|
||||
# Debe coincidir con lo que tienes en console.cloud.google.com
|
||||
YT_OAUTH_CALLBACK_URL: "https://djmaster.nextream.sytes.net/oauth2callback"
|
||||
|
||||
# Clave de cifrado para tokens almacenados (cámbiala en producción)
|
||||
FB_ENCRYPTION_SECRET: "restreamer-ui-fb-secret-key-32x!"
|
||||
|
||||
|
||||
@ -13,6 +13,7 @@ window.__RESTREAMER_CONFIG__ = {
|
||||
YTDLP_URL: "${YTDLP_URL:-}",
|
||||
FB_SERVER_URL: "${FB_SERVER_URL:-}",
|
||||
FB_OAUTH_CALLBACK_URL: "${FB_OAUTH_CALLBACK_URL:-}",
|
||||
YT_OAUTH_CALLBACK_URL: "${YT_OAUTH_CALLBACK_URL:-}",
|
||||
};
|
||||
EOF
|
||||
|
||||
|
||||
@ -135,14 +135,13 @@
|
||||
function sendCode() {
|
||||
if (ackReceived || attempts >= maxAttempts) return;
|
||||
attempts++;
|
||||
// redirect_uri = esta misma página (origin + pathname), exactamente como está registrada en Google
|
||||
var thisUrl = window.location.origin + window.location.pathname;
|
||||
var msg = { service: 'youtube', code: code, state: state, redirect_uri: thisUrl };
|
||||
try {
|
||||
window.opener.postMessage(
|
||||
{ service: 'youtube', code: code, state: state },
|
||||
window.location.origin // mismo origen
|
||||
);
|
||||
window.opener.postMessage(msg, window.location.origin);
|
||||
} catch (e) {
|
||||
// Si falla por política de origen, intentar con '*'
|
||||
try { window.opener.postMessage({ service: 'youtube', code: code, state: state }, '*'); } catch (e2) {}
|
||||
try { window.opener.postMessage(msg, '*'); } catch (e2) {}
|
||||
}
|
||||
setTimeout(sendCode, 600);
|
||||
}
|
||||
|
||||
@ -266,10 +266,29 @@ const refreshAccount = async (accountKey) => {
|
||||
}
|
||||
};
|
||||
|
||||
/** Obtener token válido (refresca automáticamente si está expirado) */
|
||||
/** Obtener token válido (refresca automáticamente si está expirado).
|
||||
* Si el access_token no está en caché local (porque viene del servidor cifrado),
|
||||
* lo obtiene del endpoint /yt/accounts/:key/token.
|
||||
*/
|
||||
const getValidToken = async (accountKey) => {
|
||||
const account = getAccount(accountKey);
|
||||
if (!account || !account.access_token) throw new Error('Account not connected: ' + accountKey);
|
||||
let account = getAccount(accountKey);
|
||||
if (!account) throw new Error('Account not connected: ' + accountKey);
|
||||
|
||||
// Si no hay access_token en caché local, obtenerlo del servidor
|
||||
if (!account.access_token) {
|
||||
const serverAccount = await getAccountToken(accountKey);
|
||||
if (serverAccount && serverAccount.access_token) {
|
||||
// Guardar en caché para futuras llamadas
|
||||
const cache = _readCache();
|
||||
if (!cache.accounts) cache.accounts = {};
|
||||
cache.accounts[accountKey] = { ...(cache.accounts[accountKey] || {}), ...serverAccount };
|
||||
_writeCache(cache);
|
||||
account = cache.accounts[accountKey];
|
||||
} else {
|
||||
throw new Error('Could not retrieve token for account: ' + accountKey);
|
||||
}
|
||||
}
|
||||
|
||||
const isExpired = account.token_expiry && Date.now() > account.token_expiry - 60000;
|
||||
if (isExpired) return await refreshAccount(accountKey);
|
||||
return account.access_token;
|
||||
|
||||
@ -116,8 +116,9 @@ function Service(props) {
|
||||
|
||||
// Cuenta conectada a ESTA publication (guardada en ytOAuth store)
|
||||
const connectedAccount = settings.account_key ? ytOAuth.getAccount(settings.account_key) : null;
|
||||
const isConnected = !!(connectedAccount && connectedAccount.access_token);
|
||||
const isTokenExpired = isConnected && connectedAccount.token_expiry
|
||||
// isConnected: tiene token local O tiene token en servidor (has_access_token)
|
||||
const isConnected = !!(connectedAccount && (connectedAccount.access_token || connectedAccount.has_access_token));
|
||||
const isTokenExpired = isConnected && connectedAccount && connectedAccount.token_expiry
|
||||
? Date.now() > connectedAccount.token_expiry - 60000
|
||||
: false;
|
||||
|
||||
@ -174,7 +175,12 @@ function Service(props) {
|
||||
return;
|
||||
}
|
||||
|
||||
const redirectUri = window.location.origin + '/oauth2callback';
|
||||
// Usar YT_OAUTH_CALLBACK_URL si está definida (producción con dominio real)
|
||||
// De lo contrario construir desde window.location.origin (desarrollo)
|
||||
const _rtCfg = (typeof window !== 'undefined' && window.__RESTREAMER_CONFIG__) || {};
|
||||
const redirectUri = _rtCfg.YT_OAUTH_CALLBACK_URL
|
||||
? _rtCfg.YT_OAUTH_CALLBACK_URL
|
||||
: window.location.origin + '/oauth2callback';
|
||||
const oauthState = btoa(JSON.stringify({ service: 'youtube', ts: Date.now() }));
|
||||
const authUrl = 'https://accounts.google.com/o/oauth2/v2/auth?' + new URLSearchParams({
|
||||
client_id: globalCreds.client_id,
|
||||
@ -237,13 +243,17 @@ function Service(props) {
|
||||
|
||||
if (event.data.code) {
|
||||
try {
|
||||
// Usar el redirect_uri que envió el callback (URL exacta de la página de callback)
|
||||
// Esto garantiza que coincida exactamente con el registrado en Google Console
|
||||
const callbackRedirectUri = event.data.redirect_uri || redirectUri;
|
||||
|
||||
// Intercambiar code por tokens vía servidor (incluye IDs de Restreamer)
|
||||
const exchangeResp = await fetch('/fb-server/yt/exchange', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
code: event.data.code,
|
||||
redirect_uri: redirectUri,
|
||||
redirect_uri: callbackRedirectUri,
|
||||
restreamer_channel_id: props.channelId || '',
|
||||
restreamer_publication_id: props.publicationId || '',
|
||||
}),
|
||||
@ -263,7 +273,7 @@ function Service(props) {
|
||||
code: event.data.code,
|
||||
client_id: localCreds.client_id,
|
||||
client_secret: localCreds.client_secret,
|
||||
redirect_uri: redirectUri,
|
||||
redirect_uri: callbackRedirectUri,
|
||||
grant_type: 'authorization_code',
|
||||
}).toString(),
|
||||
});
|
||||
@ -411,8 +421,8 @@ function Service(props) {
|
||||
if (!bResp.ok) throw new Error('Broadcast: ' + (bData.error ? bData.error.message : `HTTP ${bResp.status}`));
|
||||
const broadcastId = bData.id;
|
||||
|
||||
// 2) LiveStream
|
||||
const sResp = await fetch(`${YT_API}/liveStreams?part=snippet,cdn,status`, {
|
||||
// 2) LiveStream — contentDetails debe estar en ?part= para poder enviarlo en el body
|
||||
const sResp = await fetch(`${YT_API}/liveStreams?part=snippet,cdn,status,contentDetails`, {
|
||||
method: 'POST', headers,
|
||||
body: JSON.stringify({
|
||||
snippet: { title: settings.title },
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user