@rsweeten/dropbox-sync
Version:
Reusable Dropbox synchronization module with framework adapters
218 lines (188 loc) • 6.67 kB
text/typescript
import { Injectable } from '@angular/core'
import { HttpClient, HttpHeaders } from '@angular/common/http'
import { Observable, from, of } from 'rxjs'
import { catchError, map, switchMap, tap } from 'rxjs/operators'
import { createDropboxSyncClient } from '../core/client'
import type {
DropboxCredentials,
DropboxSyncClient,
SyncOptions,
SyncResult,
TokenResponse,
} from '../core/types'
export class DropboxSyncService {
private client: DropboxSyncClient | null = null
constructor(private http: HttpClient) {}
/**
* Initialize the Dropbox sync client with credentials
*/
initialize(credentials: DropboxCredentials): DropboxSyncClient {
this.client = createDropboxSyncClient(credentials)
return this.client
}
/**
* Get the current Dropbox sync client instance, or initialize with credentials if not exists
*/
getClient(credentials?: DropboxCredentials): DropboxSyncClient {
if (!this.client && credentials) {
return this.initialize(credentials)
}
if (!this.client) {
throw new Error(
'DropboxSyncService not initialized. Call initialize() first.'
)
}
return this.client
}
/**
* Check the connection status with Dropbox
*/
checkConnection(): Observable<boolean> {
return this.http
.get<{ connected: boolean }>('/api/dropbox/status')
.pipe(
map((response: any) => response.connected),
catchError(() => of(false))
)
}
/**
* Start the OAuth flow to connect to Dropbox
*/
connectDropbox(): void {
window.location.href = '/api/dropbox/auth/start'
}
/**
* Disconnect from Dropbox
*/
disconnectDropbox(): Observable<boolean> {
return this.http
.post<{ success: boolean }>('/api/dropbox/logout', {})
.pipe(
map((response: any) => response.success),
catchError(() => of(false))
)
}
/**
* Start the sync process
*/
startSync(options?: Partial<SyncOptions>): Observable<SyncResult> {
if (!this.client) {
throw new Error(
'DropboxSyncService not initialized. Call initialize() first.'
)
}
// Connect to socket before starting sync
this.client.socket.connect()
// Setup socket listeners for sync events
this.setupSocketListeners()
return from(this.client.sync.syncFiles(options))
}
/**
* Cancel an ongoing sync process
*/
cancelSync(): void {
if (!this.client) {
return
}
this.client.sync.cancelSync()
}
/**
* Set up socket listeners for sync events
* Returns an RxJS Observable that emits sync progress events
*/
setupSocketListeners(): Observable<any> {
if (!this.client) {
throw new Error(
'DropboxSyncService not initialized. Call initialize() first.'
)
}
// Create observable for sync progress events
return new Observable((observer: any) => {
// Listen for progress updates
this.client!.socket.on('sync:progress', (data: any) => {
observer.next({ type: 'progress', data })
})
// Listen for queue information
this.client!.socket.on('sync:queue', (data: any) => {
observer.next({ type: 'queue', data })
})
// Listen for sync completion
this.client!.socket.on('sync:complete', (data: any) => {
observer.next({ type: 'complete', data })
observer.complete()
})
// Listen for sync errors
this.client!.socket.on('sync:error', (data: any) => {
if (!data.continue) {
observer.error(data.message)
} else {
observer.next({ type: 'error', data })
}
})
// Cleanup function - remove event listeners when subscription is disposed
return () => {
this.client!.socket.off('sync:progress')
this.client!.socket.off('sync:queue')
this.client!.socket.off('sync:complete')
this.client!.socket.off('sync:error')
}
})
}
/**
* Handle OAuth callback on the client side
*/
handleOAuthCallback(code: string): Observable<TokenResponse> {
if (!this.client) {
throw new Error(
'DropboxSyncService not initialized. Call initialize() first.'
)
}
const redirectUri =
window.location.origin + '/api/dropbox/auth/callback'
return from(
this.client.auth.exchangeCodeForToken(code, redirectUri)
).pipe(
tap((tokens: any) => {
// Store tokens in local storage for client-side access
localStorage.setItem('dropbox_access_token', tokens.accessToken)
if (tokens.refreshToken) {
localStorage.setItem(
'dropbox_refresh_token',
tokens.refreshToken
)
}
localStorage.setItem('dropbox_connected', 'true')
})
)
}
/**
* Create a sync queue to determine which files need to be uploaded/downloaded
*/
createSyncQueue(
options?: Partial<SyncOptions>
): Observable<{ uploadQueue: string[]; downloadQueue: string[] }> {
if (!this.client) {
throw new Error(
'DropboxSyncService not initialized. Call initialize() first.'
)
}
return from(this.client.sync.createSyncQueue(options))
}
}
/**
* Function to extract credentials from Angular environment
*/
export function getCredentialsFromEnvironment(
environment: any
): DropboxCredentials {
return {
clientId: environment.dropboxAppKey || '',
clientSecret: environment.dropboxAppSecret,
accessToken: localStorage.getItem('dropbox_access_token') || undefined,
refreshToken:
localStorage.getItem('dropbox_refresh_token') || undefined,
}
}