@rsweeten/dropbox-sync
Version:
Reusable Dropbox synchronization module with framework adapters
271 lines (236 loc) • 8.91 kB
text/typescript
import { createDropboxSyncClient } from '../core/client'
import type { DropboxCredentials, DropboxSyncClient } from '../core/types'
import { useCookie, useRuntimeConfig } from 'nuxt/app'
import type { H3Event } from 'h3'
/**
* Helper to safely access runtime config properties
*/
function getConfigValue<T>(obj: any, path: string, defaultValue: T): T {
const parts = path.split('.')
let current = obj
for (const part of parts) {
if (current === undefined || current === null) {
return defaultValue
}
current = current[part]
}
return (current as T) || defaultValue
}
/**
* Nuxt-specific helper for creating a Dropbox sync client
* Can be used in both client and server components
*/
export function useNuxtDropboxSync(
credentials?: Partial<DropboxCredentials>
): DropboxSyncClient {
// Get Nuxt runtime config (for client ID and secret)
const config = useRuntimeConfig()
// Create base credentials with defaults from runtime config
const baseCredentials: DropboxCredentials = {
clientId: getConfigValue<string>(config, 'public.dropboxAppKey', ''),
clientSecret: getConfigValue<string | undefined>(
config,
'dropboxAppSecret',
''
),
...credentials,
}
// On client-side, attempt to get tokens from cookies
if (process.client) {
const accessToken = useCookie('dropbox_access_token')
const refreshToken = useCookie('dropbox_refresh_token')
if (accessToken.value && !baseCredentials.accessToken) {
baseCredentials.accessToken = accessToken.value as string
}
if (refreshToken.value && !baseCredentials.refreshToken) {
baseCredentials.refreshToken = refreshToken.value as string
}
}
return createDropboxSyncClient(baseCredentials)
}
/**
* Server-side helper to get credentials from Nuxt server event
*/
export function getCredentialsFromCookies(event: H3Event): DropboxCredentials {
const config = useRuntimeConfig()
// Get cookies from Nuxt server event
const accessToken = getCookie(event, 'dropbox_access_token')
const refreshToken = getCookie(event, 'dropbox_refresh_token')
return {
clientId: getConfigValue<string>(config, 'public.dropboxAppKey', ''),
clientSecret: getConfigValue<string | undefined>(
config,
'dropboxAppSecret',
''
),
accessToken,
refreshToken,
}
}
/**
* Create API event handlers for a Nuxt app
*/
export function createNuxtApiHandlers() {
return {
/**
* Handler for status check endpoint
*/
async status(event: H3Event) {
const accessToken = getCookie(event, 'dropbox_access_token')
const isConnected = !!accessToken
return { connected: isConnected }
},
/**
* Handler for OAuth start endpoint
*/
async oauthStart(event: H3Event) {
const config = useRuntimeConfig()
const dropboxSync = createDropboxSyncClient({
clientId: getConfigValue<string>(
config,
'public.dropboxAppKey',
''
),
})
const redirectUri =
getConfigValue<string>(config, 'dropboxRedirectUri', '') ||
`${getConfigValue<string>(
config,
'public.appUrl',
'http://localhost:3000'
)}/api/dropbox/auth/callback`
const authUrl = await dropboxSync.auth.getAuthUrl(redirectUri)
return sendRedirect(event, authUrl)
},
/**
* Handler for OAuth callback endpoint
*/
async oauthCallback(event: H3Event) {
const config = useRuntimeConfig()
// Get the authorization code from query parameters
const query = getQuery(event)
const code = query.code as string
if (!code) {
return sendRedirect(event, '/auth/error')
}
const redirectUri =
getConfigValue<string>(config, 'dropboxRedirectUri', '') ||
`${getConfigValue<string>(
config,
'public.appUrl',
'http://localhost:3000'
)}/api/dropbox/auth/callback`
const dropboxSync = createDropboxSyncClient({
clientId: getConfigValue<string>(
config,
'public.dropboxAppKey',
''
),
clientSecret: getConfigValue<string | undefined>(
config,
'dropboxAppSecret',
''
),
})
try {
const tokens = await dropboxSync.auth.exchangeCodeForToken(
code,
redirectUri
)
// Set cookies with the tokens
setCookie(event, 'dropbox_access_token', tokens.accessToken, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
maxAge: tokens.expiresAt
? Math.floor((tokens.expiresAt - Date.now()) / 1000)
: 14 * 24 * 60 * 60, // 14 days default
sameSite: 'lax',
path: '/',
})
if (tokens.refreshToken) {
setCookie(
event,
'dropbox_refresh_token',
tokens.refreshToken,
{
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
maxAge: 365 * 24 * 60 * 60, // 1 year
sameSite: 'lax',
path: '/',
}
)
}
// Set a non-httpOnly cookie to indicate connection status to the client
setCookie(event, 'dropbox_connected', 'true', {
secure: process.env.NODE_ENV === 'production',
maxAge: tokens.expiresAt
? Math.floor((tokens.expiresAt - Date.now()) / 1000)
: 14 * 24 * 60 * 60,
sameSite: 'lax',
path: '/',
})
return sendRedirect(event, '/')
} catch (error) {
console.error('Error completing Dropbox OAuth flow:', error)
return sendRedirect(event, '/auth/error')
}
},
/**
* Handler for logout endpoint
*/
async logout(event: H3Event) {
// Clear all Dropbox-related cookies
deleteCookie(event, 'dropbox_access_token')
deleteCookie(event, 'dropbox_refresh_token')
deleteCookie(event, 'dropbox_connected')
return { success: true }
},
}
}
/**
* Helper functions to work with Nuxt's H3Event
* These import statements need to be added to avoid reference errors
*/
const { getCookie, setCookie, deleteCookie } = useNuxtCookies()
const { getQuery, sendRedirect } = useNuxtServer()
/**
* Helper to access h3 cookie methods
*/
function useNuxtCookies() {
return {
getCookie: (event: H3Event, name: string) => {
// Import inside function to avoid module loading issues
const { getCookie } = require('h3')
return getCookie(event, name)
},
setCookie: (
event: H3Event,
name: string,
value: string,
options?: any
) => {
const { setCookie } = require('h3')
return setCookie(event, name, value, options)
},
deleteCookie: (event: H3Event, name: string, options?: any) => {
const { deleteCookie } = require('h3')
return deleteCookie(event, name, { ...options, path: '/' })
},
}
}
/**
* Helper to access h3 server methods
*/
function useNuxtServer() {
return {
getQuery: (event: H3Event) => {
const { getQuery } = require('h3')
return getQuery(event)
},
sendRedirect: (event: H3Event, location: string) => {
const { sendRedirect } = require('h3')
return sendRedirect(event, location)
},
}
}