@rsweeten/dropbox-sync 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +315 -0
  2. package/dist/adapters/angular.d.ts +56 -0
  3. package/dist/adapters/angular.js +207 -0
  4. package/dist/adapters/next.d.ts +36 -0
  5. package/dist/adapters/next.js +120 -0
  6. package/dist/adapters/nuxt.d.ts +36 -0
  7. package/dist/adapters/nuxt.js +190 -0
  8. package/dist/adapters/svelte.d.ts +39 -0
  9. package/dist/adapters/svelte.js +134 -0
  10. package/dist/core/auth.d.ts +3 -0
  11. package/dist/core/auth.js +84 -0
  12. package/dist/core/client.d.ts +5 -0
  13. package/dist/core/client.js +37 -0
  14. package/dist/core/socket.d.ts +2 -0
  15. package/dist/core/socket.js +62 -0
  16. package/dist/core/sync.d.ts +3 -0
  17. package/dist/core/sync.js +340 -0
  18. package/dist/core/types.d.ts +73 -0
  19. package/dist/core/types.js +1 -0
  20. package/dist/index.d.ts +11 -0
  21. package/dist/index.js +14 -0
  22. package/examples/angular-app/dropbox-sync.service.ts +244 -0
  23. package/examples/next-app/api-routes.ts +109 -0
  24. package/examples/next-app/dropbox-client.ts +122 -0
  25. package/examples/nuxt-app/api-routes.ts +26 -0
  26. package/examples/nuxt-app/dropbox-plugin.ts +15 -0
  27. package/examples/nuxt-app/nuxt.config.ts +23 -0
  28. package/examples/svelte-app/dropbox-store.ts +174 -0
  29. package/examples/svelte-app/routes.server.ts +120 -0
  30. package/package.json +66 -0
  31. package/src/adapters/angular.ts +217 -0
  32. package/src/adapters/next.ts +155 -0
  33. package/src/adapters/nuxt.ts +270 -0
  34. package/src/adapters/svelte.ts +168 -0
  35. package/src/core/auth.ts +148 -0
  36. package/src/core/client.ts +52 -0
  37. package/src/core/socket.ts +73 -0
  38. package/src/core/sync.ts +476 -0
  39. package/src/core/types.ts +83 -0
  40. package/src/index.ts +32 -0
  41. package/tsconfig.json +16 -0
@@ -0,0 +1,174 @@
1
+ // SvelteKit store for Dropbox sync
2
+ import { writable, derived } from 'svelte/store'
3
+ // For a private module, you would import from your private registry or local path
4
+ // import { useSvelteDropboxSync } from '@yourcompany/dropbox-sync';
5
+ import { useSvelteDropboxSync } from 'dropbox-sync'
6
+ import { browser } from '$app/environment'
7
+ import { goto } from '$app/navigation'
8
+
9
+ // Create a persistent store
10
+ export function createDropboxStore() {
11
+ const credentials = {
12
+ clientId: browser
13
+ ? import.meta.env.VITE_DROPBOX_APP_KEY
14
+ : process.env.DROPBOX_APP_KEY,
15
+ // The access token will be added from cookies on the server side
16
+ }
17
+
18
+ // Initialize the Dropbox client
19
+ const dropboxSync = useSvelteDropboxSync(credentials)
20
+
21
+ // Create stores for state management
22
+ const connected = writable(false)
23
+ const syncing = writable(false)
24
+ const progress = writable(0)
25
+ const message = writable('')
26
+ const error = writable<string | null>(null)
27
+ const syncStats = writable({
28
+ total: 0,
29
+ uploads: 0,
30
+ downloads: 0,
31
+ completed: 0,
32
+ })
33
+
34
+ // Check connection status on initialization
35
+ async function checkConnection() {
36
+ if (!browser) return
37
+
38
+ try {
39
+ const response = await fetch('/api/dropbox/status')
40
+ const data = await response.json()
41
+ connected.set(data.connected)
42
+ } catch (err) {
43
+ console.error('Error checking Dropbox connection:', err)
44
+ connected.set(false)
45
+ }
46
+ }
47
+
48
+ if (browser) {
49
+ checkConnection()
50
+
51
+ // Set up socket connection for real-time updates
52
+ dropboxSync.socket.connect()
53
+
54
+ // Listen for progress updates
55
+ dropboxSync.socket.on('sync:progress', (data) => {
56
+ progress.set(data.progress)
57
+ message.set(data.message)
58
+ })
59
+
60
+ // Listen for queue information
61
+ dropboxSync.socket.on('sync:queue', (data) => {
62
+ syncStats.set({
63
+ total: data.total,
64
+ uploads: data.totalUploads,
65
+ downloads: data.totalDownloads,
66
+ completed: 0,
67
+ })
68
+ })
69
+
70
+ // Listen for completion
71
+ dropboxSync.socket.on('sync:complete', (data) => {
72
+ progress.set(100)
73
+ message.set(data.message)
74
+ syncing.set(false)
75
+
76
+ if (data.stats) {
77
+ syncStats.update((stats) => ({
78
+ ...stats,
79
+ completed: stats.total,
80
+ }))
81
+ }
82
+ })
83
+
84
+ // Listen for errors
85
+ dropboxSync.socket.on('sync:error', (data) => {
86
+ error.set(data.message)
87
+ syncing.set(false)
88
+ })
89
+ }
90
+
91
+ return {
92
+ connected: { subscribe: connected.subscribe },
93
+ syncing: { subscribe: syncing.subscribe },
94
+ progress: { subscribe: progress.subscribe },
95
+ message: { subscribe: message.subscribe },
96
+ error: { subscribe: error.subscribe },
97
+ syncStats: { subscribe: syncStats.subscribe },
98
+
99
+ // Connect to Dropbox
100
+ connect() {
101
+ if (!browser) return
102
+ goto('/api/dropbox/auth/start')
103
+ },
104
+
105
+ // Disconnect from Dropbox
106
+ async disconnect() {
107
+ if (!browser) return
108
+
109
+ try {
110
+ await fetch('/api/dropbox/logout', { method: 'POST' })
111
+ connected.set(false)
112
+ } catch (err) {
113
+ console.error('Error disconnecting from Dropbox:', err)
114
+ }
115
+ },
116
+
117
+ // Start synchronization
118
+ async startSync() {
119
+ if (!browser) return
120
+
121
+ try {
122
+ syncing.set(true)
123
+ progress.set(0)
124
+ message.set('Initializing Dropbox sync...')
125
+ error.set(null)
126
+
127
+ // Trigger sync via socket
128
+ dropboxSync.socket.emit('dropbox:sync')
129
+ } catch (err: any) {
130
+ console.error('Error starting Dropbox sync:', err)
131
+ error.set(
132
+ err.message || 'An error occurred while starting sync'
133
+ )
134
+ syncing.set(false)
135
+ }
136
+ },
137
+
138
+ // Cancel synchronization
139
+ cancelSync() {
140
+ if (!browser) return
141
+
142
+ dropboxSync.sync.cancelSync()
143
+ syncing.set(false)
144
+ message.set('Sync cancelled')
145
+ },
146
+
147
+ // Check files that need syncing without starting the sync
148
+ async checkSyncNeeded(options?: {
149
+ localDir?: string
150
+ dropboxDir?: string
151
+ }) {
152
+ if (!browser) return { upload: 0, download: 0 }
153
+
154
+ try {
155
+ const { uploadQueue, downloadQueue } =
156
+ await dropboxSync.sync.createSyncQueue({
157
+ localDir: options?.localDir,
158
+ dropboxDir: options?.dropboxDir,
159
+ })
160
+
161
+ return {
162
+ upload: uploadQueue.length,
163
+ download: downloadQueue.length,
164
+ }
165
+ } catch (err) {
166
+ console.error('Error checking sync status:', err)
167
+ return { upload: 0, download: 0 }
168
+ }
169
+ },
170
+ }
171
+ }
172
+
173
+ // Export singleton instance
174
+ export const dropbox = createDropboxStore()
@@ -0,0 +1,120 @@
1
+ // SvelteKit server routes for Dropbox integration
2
+ // For a private module, you would import from your private registry or local path
3
+ // import { createSvelteKitHandlers } from '@yourcompany/dropbox-sync';
4
+ import { createSvelteKitHandlers } from 'dropbox-sync'
5
+ import { Server } from 'socket.io'
6
+ import type { RequestEvent } from '@sveltejs/kit'
7
+
8
+ // Create all the route handlers
9
+ export const handlers = createSvelteKitHandlers()
10
+
11
+ // Status endpoint handler
12
+ export async function handleStatus(event: RequestEvent) {
13
+ return handlers.status({ cookies: event.cookies })
14
+ }
15
+
16
+ // OAuth start endpoint handler
17
+ export async function handleOAuthStart() {
18
+ return handlers.oauthStart()
19
+ }
20
+
21
+ // OAuth callback endpoint handler
22
+ export async function handleOAuthCallback(event: RequestEvent) {
23
+ return handlers.oauthCallback({
24
+ url: new URL(event.request.url),
25
+ cookies: event.cookies,
26
+ })
27
+ }
28
+
29
+ // Logout endpoint handler
30
+ export async function handleLogout(event: RequestEvent) {
31
+ return handlers.logout({ cookies: event.cookies })
32
+ }
33
+
34
+ // Setup Socket.IO for SvelteKit
35
+ let io: Server
36
+
37
+ export function getSocketIO(server: any) {
38
+ if (!io) {
39
+ io = new Server(server)
40
+
41
+ io.on('connection', (socket) => {
42
+ console.log('Client connected:', socket.id)
43
+
44
+ // Handle sync start request
45
+ socket.on('dropbox:sync', async () => {
46
+ try {
47
+ // In a real implementation, this would use the server-side Dropbox client
48
+ // to perform the actual sync
49
+
50
+ // Emit queue information
51
+ socket.emit('sync:queue', {
52
+ total: 8,
53
+ totalUploads: 3,
54
+ totalDownloads: 5,
55
+ })
56
+
57
+ // Simulate sync process with progress updates
58
+ let progress = 0
59
+ const interval = setInterval(() => {
60
+ progress += 12.5 // 8 total files, so each is 12.5%
61
+
62
+ if (progress <= 100) {
63
+ socket.emit('sync:progress', {
64
+ progress,
65
+ message: `Processing files... ${Math.round(
66
+ progress
67
+ )}%`,
68
+ type: progress < 50 ? 'upload' : 'download',
69
+ })
70
+ }
71
+
72
+ if (progress >= 100) {
73
+ clearInterval(interval)
74
+ socket.emit('sync:complete', {
75
+ message: 'Sync completed successfully',
76
+ stats: {
77
+ uploaded: 3,
78
+ downloaded: 5,
79
+ },
80
+ })
81
+ }
82
+ }, 600)
83
+
84
+ // Store the interval for cancellation
85
+ socket.data.syncInterval = interval
86
+ } catch (error: any) {
87
+ console.error('Error syncing with Dropbox:', error)
88
+ socket.emit('sync:error', {
89
+ message:
90
+ error.message || 'An error occurred during sync',
91
+ })
92
+ }
93
+ })
94
+
95
+ // Handle sync cancel request
96
+ socket.on('sync:cancel', () => {
97
+ if (socket.data.syncInterval) {
98
+ clearInterval(socket.data.syncInterval)
99
+ delete socket.data.syncInterval
100
+ }
101
+
102
+ socket.emit('sync:complete', {
103
+ message: 'Sync cancelled by user',
104
+ })
105
+ })
106
+
107
+ // Handle disconnect
108
+ socket.on('disconnect', () => {
109
+ console.log('Client disconnected:', socket.id)
110
+
111
+ // Clean up any ongoing operations
112
+ if (socket.data.syncInterval) {
113
+ clearInterval(socket.data.syncInterval)
114
+ }
115
+ })
116
+ })
117
+ }
118
+
119
+ return io
120
+ }
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@rsweeten/dropbox-sync",
3
+ "version": "0.1.0",
4
+ "description": "Reusable Dropbox synchronization module with framework adapters",
5
+ "private": false,
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "main": "dist/index.js",
10
+ "types": "dist/index.d.ts",
11
+ "scripts": {
12
+ "build": "tsc",
13
+ "test": "jest",
14
+ "prepublishOnly": "npm run build"
15
+ },
16
+ "keywords": [
17
+ "dropbox",
18
+ "sync",
19
+ "upload",
20
+ "download",
21
+ "file-sync"
22
+ ],
23
+ "author": "",
24
+ "license": "MIT",
25
+ "dependencies": {
26
+ "dropbox": "^10.34.0",
27
+ "socket.io": "^4.7.2",
28
+ "socket.io-client": "^4.7.2"
29
+ },
30
+ "devDependencies": {
31
+ "@angular/common": "^19.2.11",
32
+ "@angular/core": "^19.2.11",
33
+ "@sveltejs/kit": "^2.21.1",
34
+ "@types/angular": "^1.8.9",
35
+ "@types/jest": "^29.5.1",
36
+ "@types/node": "^20.1.0",
37
+ "h3": "^1.11.1",
38
+ "jest": "^29.5.0",
39
+ "next": "^15.3.2",
40
+ "nuxt": "^3.10.3",
41
+ "rxjs": "^7.8.2",
42
+ "svelte": "^5.31.1",
43
+ "ts-jest": "^29.1.0",
44
+ "typescript": "^5.0.4"
45
+ },
46
+ "peerDependencies": {
47
+ "@angular/core": ">=14.0.0",
48
+ "next": ">=13.0.0",
49
+ "nuxt": ">=3.0.0",
50
+ "svelte": ">=3.0.0"
51
+ },
52
+ "peerDependenciesMeta": {
53
+ "next": {
54
+ "optional": true
55
+ },
56
+ "svelte": {
57
+ "optional": true
58
+ },
59
+ "@angular/core": {
60
+ "optional": true
61
+ },
62
+ "nuxt": {
63
+ "optional": true
64
+ }
65
+ }
66
+ }
@@ -0,0 +1,217 @@
1
+ import { Injectable } from '@angular/core'
2
+ import { HttpClient, HttpHeaders } from '@angular/common/http'
3
+ import { Observable, from, of } from 'rxjs'
4
+ import { catchError, map, switchMap, tap } from 'rxjs/operators'
5
+ import { createDropboxSyncClient } from '../core/client'
6
+ import type {
7
+ DropboxCredentials,
8
+ DropboxSyncClient,
9
+ SyncOptions,
10
+ SyncResult,
11
+ TokenResponse,
12
+ } from '../core/types'
13
+
14
+ @Injectable({
15
+ providedIn: 'root',
16
+ })
17
+ export class DropboxSyncService {
18
+ private client: DropboxSyncClient | null = null
19
+
20
+ constructor(private http: HttpClient) {}
21
+
22
+ /**
23
+ * Initialize the Dropbox sync client with credentials
24
+ */
25
+ initialize(credentials: DropboxCredentials): DropboxSyncClient {
26
+ this.client = createDropboxSyncClient(credentials)
27
+ return this.client
28
+ }
29
+
30
+ /**
31
+ * Get the current Dropbox sync client instance, or initialize with credentials if not exists
32
+ */
33
+ getClient(credentials?: DropboxCredentials): DropboxSyncClient {
34
+ if (!this.client && credentials) {
35
+ return this.initialize(credentials)
36
+ }
37
+
38
+ if (!this.client) {
39
+ throw new Error(
40
+ 'DropboxSyncService not initialized. Call initialize() first.'
41
+ )
42
+ }
43
+
44
+ return this.client
45
+ }
46
+
47
+ /**
48
+ * Check the connection status with Dropbox
49
+ */
50
+ checkConnection(): Observable<boolean> {
51
+ return this.http
52
+ .get<{ connected: boolean }>('/api/dropbox/status')
53
+ .pipe(
54
+ map((response: any) => response.connected),
55
+ catchError(() => of(false))
56
+ )
57
+ }
58
+
59
+ /**
60
+ * Start the OAuth flow to connect to Dropbox
61
+ */
62
+ connectDropbox(): void {
63
+ window.location.href = '/api/dropbox/auth/start'
64
+ }
65
+
66
+ /**
67
+ * Disconnect from Dropbox
68
+ */
69
+ disconnectDropbox(): Observable<boolean> {
70
+ return this.http
71
+ .post<{ success: boolean }>('/api/dropbox/logout', {})
72
+ .pipe(
73
+ map((response: any) => response.success),
74
+ catchError(() => of(false))
75
+ )
76
+ }
77
+
78
+ /**
79
+ * Start the sync process
80
+ */
81
+ startSync(options?: Partial<SyncOptions>): Observable<SyncResult> {
82
+ if (!this.client) {
83
+ throw new Error(
84
+ 'DropboxSyncService not initialized. Call initialize() first.'
85
+ )
86
+ }
87
+
88
+ // Connect to socket before starting sync
89
+ this.client.socket.connect()
90
+
91
+ // Setup socket listeners for sync events
92
+ this.setupSocketListeners()
93
+
94
+ return from(this.client.sync.syncFiles(options))
95
+ }
96
+
97
+ /**
98
+ * Cancel an ongoing sync process
99
+ */
100
+ cancelSync(): void {
101
+ if (!this.client) {
102
+ return
103
+ }
104
+
105
+ this.client.sync.cancelSync()
106
+ }
107
+
108
+ /**
109
+ * Set up socket listeners for sync events
110
+ * Returns an RxJS Observable that emits sync progress events
111
+ */
112
+ setupSocketListeners(): Observable<any> {
113
+ if (!this.client) {
114
+ throw new Error(
115
+ 'DropboxSyncService not initialized. Call initialize() first.'
116
+ )
117
+ }
118
+
119
+ // Create observable for sync progress events
120
+ return new Observable((observer: any) => {
121
+ // Listen for progress updates
122
+ this.client!.socket.on('sync:progress', (data: any) => {
123
+ observer.next({ type: 'progress', data })
124
+ })
125
+
126
+ // Listen for queue information
127
+ this.client!.socket.on('sync:queue', (data: any) => {
128
+ observer.next({ type: 'queue', data })
129
+ })
130
+
131
+ // Listen for sync completion
132
+ this.client!.socket.on('sync:complete', (data: any) => {
133
+ observer.next({ type: 'complete', data })
134
+ observer.complete()
135
+ })
136
+
137
+ // Listen for sync errors
138
+ this.client!.socket.on('sync:error', (data: any) => {
139
+ if (!data.continue) {
140
+ observer.error(data.message)
141
+ } else {
142
+ observer.next({ type: 'error', data })
143
+ }
144
+ })
145
+
146
+ // Cleanup function - remove event listeners when subscription is disposed
147
+ return () => {
148
+ this.client!.socket.off('sync:progress')
149
+ this.client!.socket.off('sync:queue')
150
+ this.client!.socket.off('sync:complete')
151
+ this.client!.socket.off('sync:error')
152
+ }
153
+ })
154
+ }
155
+
156
+ /**
157
+ * Handle OAuth callback on the client side
158
+ */
159
+ handleOAuthCallback(code: string): Observable<TokenResponse> {
160
+ if (!this.client) {
161
+ throw new Error(
162
+ 'DropboxSyncService not initialized. Call initialize() first.'
163
+ )
164
+ }
165
+
166
+ const redirectUri =
167
+ window.location.origin + '/api/dropbox/auth/callback'
168
+
169
+ return from(
170
+ this.client.auth.exchangeCodeForToken(code, redirectUri)
171
+ ).pipe(
172
+ tap((tokens: any) => {
173
+ // Store tokens in local storage for client-side access
174
+ localStorage.setItem('dropbox_access_token', tokens.accessToken)
175
+
176
+ if (tokens.refreshToken) {
177
+ localStorage.setItem(
178
+ 'dropbox_refresh_token',
179
+ tokens.refreshToken
180
+ )
181
+ }
182
+
183
+ localStorage.setItem('dropbox_connected', 'true')
184
+ })
185
+ )
186
+ }
187
+
188
+ /**
189
+ * Create a sync queue to determine which files need to be uploaded/downloaded
190
+ */
191
+ createSyncQueue(
192
+ options?: Partial<SyncOptions>
193
+ ): Observable<{ uploadQueue: string[]; downloadQueue: string[] }> {
194
+ if (!this.client) {
195
+ throw new Error(
196
+ 'DropboxSyncService not initialized. Call initialize() first.'
197
+ )
198
+ }
199
+
200
+ return from(this.client.sync.createSyncQueue(options))
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Function to extract credentials from Angular environment
206
+ */
207
+ export function getCredentialsFromEnvironment(
208
+ environment: any
209
+ ): DropboxCredentials {
210
+ return {
211
+ clientId: environment.dropboxAppKey || '',
212
+ clientSecret: environment.dropboxAppSecret,
213
+ accessToken: localStorage.getItem('dropbox_access_token') || undefined,
214
+ refreshToken:
215
+ localStorage.getItem('dropbox_refresh_token') || undefined,
216
+ }
217
+ }