@rsweeten/dropbox-sync v0.1.3
Dropbox Sync Module
A reusable TypeScript module for syncing files between your application and Dropbox. Features framework-specific adapters for Next.js, SvelteKit, Nuxt, and Angular.
N.B. This NPM package is neither tested nor supported
Table of Contents
- Features
- Installation
- Quick Start
- Development Setup
- Architecture
- Testing
- Framework-Specific Usage
- API Reference
- Socket Events
- Configuration Options
- License
Features
- File Synchronization: Upload local files to Dropbox and download Dropbox files to your local filesystem
- Real-time Progress: Integrated Socket.IO support for real-time sync progress updates
- Path Normalization: Robust path handling to ensure consistent comparison between local and remote paths
- OAuth Authentication: Built-in OAuth flow handling for Dropbox API authentication
- Framework Adapters: Ready-to-use integrations for Next.js, SvelteKit, and Angular
- TypeScript Support: Fully typed API for improved developer experience
Installation
# Install the package in your project
npm install dropbox-sync
# or
yarn add dropbox-syncYou may also need to install peer dependencies:
npm install socket.io socket.io-client
# or
yarn add socket.io socket.io-clientQuick Start
Core Usage
import createDropboxSyncClient from 'dropbox-sync'
// Create a Dropbox sync client
const dropboxSync = createDropboxSyncClient({
clientId: 'YOUR_DROPBOX_APP_KEY',
clientSecret: 'YOUR_DROPBOX_APP_SECRET',
accessToken: 'OPTIONAL_EXISTING_TOKEN',
refreshToken: 'OPTIONAL_REFRESH_TOKEN',
})
// Start OAuth flow
const authUrl = await dropboxSync.auth.getAuthUrl(
'http://localhost:3000/callback'
)
// Redirect the user to authUrl
// Exchange authorization code for tokens
const tokens = await dropboxSync.auth.exchangeCodeForToken(code, redirectUri)
// Save tokens.accessToken and tokens.refreshToken
// Sync files
const syncResult = await dropboxSync.sync.syncFiles({
localDir: './public/img',
dropboxDir: '/app-images',
})
console.log(`Uploaded ${syncResult.uploaded.length} files`)
console.log(`Downloaded ${syncResult.downloaded.length} files`)Development Setup
To set up this project for local development:
# Clone the repository
git clone https://github.com/yourusername/dropbox-sync-service.git
cd dropbox-sync-service
# Install dependencies
npm install
# Run tests
npm test
# Build the package
npm run buildArchitecture
The Dropbox Sync Module architecture is designed with modularity, extensibility, and framework-agnosticism in mind.
graph TD
A[Application] --> B[Framework Adapter]
B --> C[Core Client]
C --> D[Auth Module]
C --> E[Sync Module]
C --> F[Socket Module]
D --> G[Dropbox API]
E --> G
F --> H[Socket.IO]
H --> E
H --> A
%% File Flow
I[Local Files] --> E
E --> I
G --> E
E --> GData Flow
Authentication Flow:
sequenceDiagram participant User participant App participant DropboxSync participant Dropbox User->>App: Initiate auth App->>DropboxSync: getAuthUrl() DropboxSync->>Dropbox: Request auth URL Dropbox-->>DropboxSync: Auth URL DropboxSync-->>App: Auth URL App-->>User: Redirect to auth URL User->>Dropbox: Authorize app Dropbox-->>App: Auth code (via redirect) App->>DropboxSync: exchangeCodeForToken(code) DropboxSync->>Dropbox: Exchange code for token Dropbox-->>DropboxSync: Access & refresh tokens DropboxSync-->>App: TokensSync Flow:
sequenceDiagram participant App participant DropboxSync participant Socket participant Dropbox participant LocalFS App->>DropboxSync: syncFiles(options) DropboxSync->>LocalFS: Scan local files LocalFS-->>DropboxSync: Local file list DropboxSync->>Dropbox: List files Dropbox-->>DropboxSync: Dropbox file list DropboxSync->>DropboxSync: Create sync queue loop For each file to upload DropboxSync->>Dropbox: Upload file DropboxSync->>Socket: Emit progress Socket-->>App: Progress update end loop For each file to download DropboxSync->>Dropbox: Download file DropboxSync->>LocalFS: Write file DropboxSync->>Socket: Emit progress Socket-->>App: Progress update end DropboxSync-->>App: Sync results
Testing
The Dropbox Sync Module uses Jest for unit testing. Tests are organized to match the structure of the source code.
Test Structure
__tests__/ # Test utilities and mocks
src/
core/
__tests__/ # Core module tests
auth.spec.ts # Authentication tests
client.spec.ts # Client tests
socket.spec.ts # Socket tests
sync.spec.ts # Sync tests
adapters/
__tests__/ # Framework adapter tests
angular.spec.ts # Angular adapter tests
next.spec.ts # Next.js adapter tests
nuxt.spec.ts # Nuxt adapter tests
svelte.spec.ts # SvelteKit adapter testsRunning Tests
# Run all tests
npm test
# Run tests with coverage
npm test -- --coverage
# Run specific test file
npm test -- src/core/__tests__/auth.spec.ts
# Run tests in watch mode during development
npm test -- --watchTesting Approach
- Unit Tests: Each module is tested in isolation with mocked dependencies
- Integration Tests: Tests for interactions between modules
- Adapter Tests: Tests for framework-specific adapters
Mock Strategy
- Dropbox API calls are mocked using Jest mock functions
- File system operations are mocked to avoid touching real files
- Socket.IO is mocked to test event emission and handling
Test Coverage
We aim for high test coverage, especially for the core functionality:
| Module | Coverage |
|---|---|
| Core | >90% |
| Auth | >95% |
| Sync | >90% |
| Socket | >90% |
| Adapters | >85% |
Framework-Specific Usage
Next.js
// In your component
import { useNextDropboxSync } from 'dropbox-sync'
export function DropboxComponent() {
// Initialize client
const dropboxSync = useNextDropboxSync({
clientId: process.env.NEXT_PUBLIC_DROPBOX_APP_KEY,
})
// Connect to socket for real-time updates
useEffect(() => {
dropboxSync.socket.connect()
dropboxSync.socket.on('sync:progress', handleProgress)
return () => {
dropboxSync.socket.disconnect()
}
}, [])
// Start sync
const handleSync = () => {
// This will trigger the server-side sync through Socket.IO
dropboxSync.socket.emit('dropbox:sync')
}
return <button onClick={handleSync}>Sync with Dropbox</button>
}
// In your API route
import { createNextDropboxApiHandlers } from 'dropbox-sync'
const handlers = createNextDropboxApiHandlers()
export async function GET(request) {
return handlers.status()
}Nuxt.js
The Nuxt adapter leverages Nuxt's runtime config system for app settings and uses H3 (Nitro's HTTP server) utilities for handling requests and cookies. It's designed to work seamlessly with Nuxt 3's composition API and server routes.
To use this adapter in a Nuxt project, developers would:
- Add runtime config in their nuxt.config.ts:
export default defineNuxtConfig({
runtimeConfig: {
// Private keys
dropboxAppSecret: process.env.DROPBOX_APP_SECRET,
dropboxRedirectUri: process.env.DROPBOX_REDIRECT_URI,
// Public keys
public: {
dropboxAppKey: process.env.DROPBOX_APP_KEY,
appUrl: process.env.APP_URL || 'http://localhost:3000',
},
},
})- Create a plugin using the example provided
- Set up API routes using the handlers from
createNuxtApiHandlers()
// In your plugins/dropbox.ts
import { useNuxtDropboxSync } from 'dropbox-sync'
import { defineNuxtPlugin } from 'nuxt/app'
export default defineNuxtPlugin((nuxtApp) => {
// Create the Dropbox client
const dropboxSync = useNuxtDropboxSync()
// Provide the client to the app
return {
provide: {
dropbox: dropboxSync
}
}
})
// In your Vue component
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'
const { $dropbox } = useNuxtApp()
const syncProgress = ref(0)
// Handle progress updates
const handleProgress = (data) => {
syncProgress.value = data.progress
}
// Connect to socket
onMounted(() => {
$dropbox.socket.connect()
$dropbox.socket.on('sync:progress', handleProgress)
})
// Clean up on unmount
onBeforeUnmount(() => {
$dropbox.socket.disconnect()
})
// Start sync
const startSync = () => {
$dropbox.socket.emit('dropbox:sync')
}
</script>
<template>
<div>
<button @click="startSync">Sync with Dropbox</button>
<progress :value="syncProgress" max="100"></progress>
</div>
</template>
// In your server/api/dropbox/[...].ts
import { createNuxtApiHandlers } from 'dropbox-sync'
import { defineEventHandler } from 'h3'
// Create handlers
const handlers = createNuxtApiHandlers()
export default defineEventHandler(async (event) => {
// Handle different endpoints
const path = event.path || ''
if (path.endsWith('/status')) {
return await handlers.status(event)
}
if (path.endsWith('/auth')) {
return await handlers.oauthStart(event)
}
if (path.endsWith('/auth/callback')) {
return await handlers.oauthCallback(event)
}
if (path.endsWith('/logout')) {
return await handlers.logout(event)
}
return { error: 'Not found' }
})SvelteKit
// In your store
import { useSvelteDropboxSync } from 'dropbox-sync'
import { writable } from 'svelte/store'
// Create store
export function createDropboxStore() {
const dropboxSync = useSvelteDropboxSync({
clientId: import.meta.env.VITE_DROPBOX_APP_KEY,
})
const progress = writable(0)
// Set up socket listeners
dropboxSync.socket.connect()
dropboxSync.socket.on('sync:progress', (data) => {
progress.set(data.progress)
})
return {
progress: { subscribe: progress.subscribe },
startSync: () => {
dropboxSync.socket.emit('dropbox:sync')
},
}
}Angular
// In your service
import { DropboxSyncService, getCredentialsFromEnvironment } from 'dropbox-sync'
import { environment } from '../environments/environment'
@Injectable({ providedIn: 'root' })
export class DropboxService {
constructor(private dropboxSyncService: DropboxSyncService) {
// Initialize the service
this.dropboxSyncService.initialize(
getCredentialsFromEnvironment(environment)
)
// Listen for socket events
const socketEvents = this.dropboxSyncService.setupSocketListeners()
socketEvents.subscribe((event) => console.log(event))
}
startSync() {
return this.dropboxSyncService.startSync()
}
}API Reference
Core Client
createDropboxSyncClient(credentials)- Creates a new client instance
Auth Methods
getAuthUrl(redirectUri, state?)- Generate an OAuth authentication URLexchangeCodeForToken(code, redirectUri)- Exchange authorization code for access tokenrefreshAccessToken()- Refresh an expired access token
Sync Methods
scanLocalFiles(dir?)- Scan local directory for filesscanDropboxFiles(dir?)- Scan Dropbox folder for filescreateSyncQueue(options?)- Create upload/download queuessyncFiles(options?)- Synchronize files between local and DropboxcancelSync()- Cancel an ongoing synchronization
Socket Methods
connect()- Connect to Socket.IOdisconnect()- Disconnect from Socket.IOon(event, handler)- Listen for an eventoff(event)- Remove event listeneremit(event, ...args)- Emit an event
Socket Events
sync:progress- Emitted during sync with progress informationsync:queue- Emitted at the start of sync with queue informationsync:complete- Emitted when sync is completesync:error- Emitted when an error occurs during sync
Configuration Options
interface SyncOptions {
localDir: string // Local directory path
dropboxDir?: string // Dropbox folder path
fileTypes?: RegExp // File types to sync (default: images and JSON)
progressCallback?: (progress: SyncProgress) => void
}License
MIT