UNPKG

@rsweeten/dropbox-sync

Version:

Reusable Dropbox synchronization module with framework adapters

149 lines (128 loc) 5.12 kB
import { Dropbox } from 'dropbox' import type { AuthMethods, DropboxCredentials, TokenResponse } from './types' // Define missing types for Dropbox SDK interface DropboxAuthExtended { getAuthenticationUrl( redirectUri: string, state: string, authType?: string, tokenAccessType?: string, scope?: string | string[], includeGrantedScopes?: string, usePKCE?: boolean ): string getAccessTokenFromCode( redirectUri: string, code: string ): Promise<{ result: { access_token: string refresh_token?: string expires_in?: number } }> } export function createAuthMethods( getClient: () => Dropbox, credentials: DropboxCredentials, setAccessToken: (token: string) => void ): AuthMethods { return { /** * Generate an authentication URL for Dropbox OAuth flow */ async getAuthUrl(redirectUri: string, state?: string): Promise<string> { const dropbox: any = getClient() const randomState = state || Math.random().toString(36).substring(2, 15) // Cast to extended auth type to access missing methods const auth = dropbox.auth as unknown as DropboxAuthExtended // Generate the authentication URL const authUrl = auth.getAuthenticationUrl( redirectUri, randomState, 'code', // Use authorization code flow 'offline', // Request a refresh token for long-lived access undefined, // No scope specified, request full access undefined, // No include_granted_scopes true // Force reapproval to ensure we get fresh tokens ) return authUrl }, /** * Exchange authorization code for access token */ async exchangeCodeForToken( code: string, redirectUri: string ): Promise<TokenResponse> { const dropbox: any = getClient() // Cast to extended auth type to access missing methods const auth = dropbox.auth as unknown as DropboxAuthExtended // Exchange the code for an access token const response = await auth.getAccessTokenFromCode( redirectUri, code ) const tokenResponse = { accessToken: response.result.access_token, refreshToken: response.result.refresh_token, expiresAt: response.result.expires_in ? Date.now() + response.result.expires_in * 1000 : undefined, } // Update the client with the new token setAccessToken(tokenResponse.accessToken) return tokenResponse }, /** * Refresh an expired access token using the refresh token */ async refreshAccessToken(): Promise<TokenResponse> { if ( !credentials.refreshToken || !credentials.clientId || !credentials.clientSecret ) { throw new Error( 'Refresh token, client ID, and client secret are required to refresh access token' ) } // Dropbox API endpoint for token refresh const tokenUrl = 'https://api.dropboxapi.com/oauth2/token' // Prepare the form data for the token request const formData = new URLSearchParams({ grant_type: 'refresh_token', refresh_token: credentials.refreshToken, client_id: credentials.clientId, client_secret: credentials.clientSecret, }) // Make the request to refresh the token const response = await fetch(tokenUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: formData.toString(), }) if (!response.ok) { await response.text() // Consume the response body throw new Error( `Failed to refresh token: ${response.status} ${response.statusText}` ) } // Parse the response const data = await response.json() const tokenResponse = { accessToken: data.access_token, refreshToken: data.refresh_token || credentials.refreshToken, expiresAt: data.expires_in ? Date.now() + data.expires_in * 1000 : undefined, } // Update the client with the new token setAccessToken(tokenResponse.accessToken) return tokenResponse }, } }