diff --git a/app/api/nextcloud/status/route.ts b/app/api/nextcloud/status/route.ts index 94bc8c0d..a8df89af 100644 --- a/app/api/nextcloud/status/route.ts +++ b/app/api/nextcloud/status/route.ts @@ -3,31 +3,73 @@ import { getServerSession } from 'next-auth'; import { authOptions } from '@/app/api/auth/[...nextauth]/route'; import { DOMParser } from '@xmldom/xmldom'; -async function establishNextcloudSession(nextcloudUrl: string, keycloakToken: string) { - // First, try to establish a session with Nextcloud using the Keycloak token - const sessionResponse = await fetch(`${nextcloudUrl}/index.php/apps/oauth2/api/v1/token`, { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'Authorization': `Bearer ${keycloakToken}`, - }, - body: new URLSearchParams({ - grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange', - subject_token: keycloakToken, - subject_token_type: 'urn:ietf:params:oauth:token-type:access_token', - client_id: process.env.NEXTCLOUD_CLIENT_ID || '', - client_secret: process.env.NEXTCLOUD_CLIENT_SECRET || '', - }).toString(), - }); +// Simple in-memory cache for Nextcloud tokens +const tokenCache = new Map(); - if (!sessionResponse.ok) { - const errorText = await sessionResponse.text(); - console.error('Failed to establish Nextcloud session:', errorText); - throw new Error('Failed to establish Nextcloud session'); +async function sleep(ms: number) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +async function establishNextcloudSession(nextcloudUrl: string, keycloakToken: string, maxRetries = 3) { + // Check cache first + const cacheKey = `${nextcloudUrl}:${keycloakToken}`; + const cached = tokenCache.get(cacheKey); + if (cached && cached.expires > Date.now()) { + return cached.token; } - const sessionData = await sessionResponse.json(); - return sessionData.access_token; + let lastError; + for (let attempt = 0; attempt < maxRetries; attempt++) { + try { + // Add exponential backoff + if (attempt > 0) { + await sleep(Math.pow(2, attempt) * 1000); + } + + const sessionResponse = await fetch(`${nextcloudUrl}/index.php/apps/oauth2/api/v1/token`, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Authorization': `Bearer ${keycloakToken}`, + }, + body: new URLSearchParams({ + grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange', + subject_token: keycloakToken, + subject_token_type: 'urn:ietf:params:oauth:token-type:access_token', + client_id: process.env.NEXTCLOUD_CLIENT_ID || '', + client_secret: process.env.NEXTCLOUD_CLIENT_SECRET || '', + }).toString(), + }); + + if (sessionResponse.status === 429) { + // Rate limited, wait and retry + const retryAfter = sessionResponse.headers.get('Retry-After'); + await sleep((retryAfter ? parseInt(retryAfter) : 5) * 1000); + continue; + } + + if (!sessionResponse.ok) { + const errorText = await sessionResponse.text(); + console.error('Failed to establish Nextcloud session:', errorText); + throw new Error('Failed to establish Nextcloud session'); + } + + const sessionData = await sessionResponse.json(); + + // Cache the token for 5 minutes + tokenCache.set(cacheKey, { + token: sessionData.access_token, + expires: Date.now() + 5 * 60 * 1000 + }); + + return sessionData.access_token; + } catch (error) { + lastError = error; + console.error(`Attempt ${attempt + 1} failed:`, error); + } + } + + throw lastError || new Error('Failed to establish Nextcloud session after retries'); } export async function GET() { @@ -61,7 +103,7 @@ export async function GET() { } try { - // Establish Nextcloud session + // Establish Nextcloud session with retries const nextcloudToken = await establishNextcloudSession(nextcloudUrl, session.accessToken); // Get user's folders using WebDAV with Nextcloud token