@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,148 @@
1
+ import { Dropbox } from 'dropbox'
2
+ import type { AuthMethods, DropboxCredentials, TokenResponse } from './types'
3
+
4
+ // Define missing types for Dropbox SDK
5
+ interface DropboxAuthExtended {
6
+ getAuthenticationUrl(
7
+ redirectUri: string,
8
+ state: string,
9
+ authType?: string,
10
+ tokenAccessType?: string,
11
+ scope?: string | string[],
12
+ includeGrantedScopes?: string,
13
+ usePKCE?: boolean
14
+ ): string
15
+
16
+ getAccessTokenFromCode(
17
+ redirectUri: string,
18
+ code: string
19
+ ): Promise<{
20
+ result: {
21
+ access_token: string
22
+ refresh_token?: string
23
+ expires_in?: number
24
+ }
25
+ }>
26
+ }
27
+
28
+ export function createAuthMethods(
29
+ getClient: () => Dropbox,
30
+ credentials: DropboxCredentials,
31
+ setAccessToken: (token: string) => void
32
+ ): AuthMethods {
33
+ return {
34
+ /**
35
+ * Generate an authentication URL for Dropbox OAuth flow
36
+ */
37
+ async getAuthUrl(redirectUri: string, state?: string): Promise<string> {
38
+ const dropbox: any = getClient()
39
+ const randomState =
40
+ state || Math.random().toString(36).substring(2, 15)
41
+
42
+ // Cast to extended auth type to access missing methods
43
+ const auth = dropbox.auth as unknown as DropboxAuthExtended
44
+
45
+ // Generate the authentication URL
46
+ const authUrl = auth.getAuthenticationUrl(
47
+ redirectUri,
48
+ randomState,
49
+ 'code', // Use authorization code flow
50
+ 'offline', // Request a refresh token for long-lived access
51
+ undefined, // No scope specified, request full access
52
+ undefined, // No include_granted_scopes
53
+ true // Force reapproval to ensure we get fresh tokens
54
+ )
55
+
56
+ return authUrl
57
+ },
58
+
59
+ /**
60
+ * Exchange authorization code for access token
61
+ */
62
+ async exchangeCodeForToken(
63
+ code: string,
64
+ redirectUri: string
65
+ ): Promise<TokenResponse> {
66
+ const dropbox: any = getClient()
67
+ // Cast to extended auth type to access missing methods
68
+ const auth = dropbox.auth as unknown as DropboxAuthExtended
69
+
70
+ // Exchange the code for an access token
71
+ const response = await auth.getAccessTokenFromCode(
72
+ redirectUri,
73
+ code
74
+ )
75
+
76
+ const tokenResponse = {
77
+ accessToken: response.result.access_token,
78
+ refreshToken: response.result.refresh_token,
79
+ expiresAt: response.result.expires_in
80
+ ? Date.now() + response.result.expires_in * 1000
81
+ : undefined,
82
+ }
83
+
84
+ // Update the client with the new token
85
+ setAccessToken(tokenResponse.accessToken)
86
+
87
+ return tokenResponse
88
+ },
89
+
90
+ /**
91
+ * Refresh an expired access token using the refresh token
92
+ */
93
+ async refreshAccessToken(): Promise<TokenResponse> {
94
+ if (
95
+ !credentials.refreshToken ||
96
+ !credentials.clientId ||
97
+ !credentials.clientSecret
98
+ ) {
99
+ throw new Error(
100
+ 'Refresh token, client ID, and client secret are required to refresh access token'
101
+ )
102
+ }
103
+
104
+ // Dropbox API endpoint for token refresh
105
+ const tokenUrl = 'https://api.dropboxapi.com/oauth2/token'
106
+
107
+ // Prepare the form data for the token request
108
+ const formData = new URLSearchParams({
109
+ grant_type: 'refresh_token',
110
+ refresh_token: credentials.refreshToken,
111
+ client_id: credentials.clientId,
112
+ client_secret: credentials.clientSecret,
113
+ })
114
+
115
+ // Make the request to refresh the token
116
+ const response = await fetch(tokenUrl, {
117
+ method: 'POST',
118
+ headers: {
119
+ 'Content-Type': 'application/x-www-form-urlencoded',
120
+ },
121
+ body: formData.toString(),
122
+ })
123
+
124
+ if (!response.ok) {
125
+ await response.text() // Consume the response body
126
+ throw new Error(
127
+ `Failed to refresh token: ${response.status} ${response.statusText}`
128
+ )
129
+ }
130
+
131
+ // Parse the response
132
+ const data = await response.json()
133
+
134
+ const tokenResponse = {
135
+ accessToken: data.access_token,
136
+ refreshToken: data.refresh_token || credentials.refreshToken,
137
+ expiresAt: data.expires_in
138
+ ? Date.now() + data.expires_in * 1000
139
+ : undefined,
140
+ }
141
+
142
+ // Update the client with the new token
143
+ setAccessToken(tokenResponse.accessToken)
144
+
145
+ return tokenResponse
146
+ },
147
+ }
148
+ }
@@ -0,0 +1,52 @@
1
+ import { Dropbox } from 'dropbox'
2
+ import type {
3
+ DropboxCredentials,
4
+ DropboxSyncClient,
5
+ AuthMethods,
6
+ SyncMethods,
7
+ TokenResponse,
8
+ SocketMethods,
9
+ } from './types'
10
+ import { createAuthMethods } from './auth'
11
+ import { createSyncMethods } from './sync'
12
+ import { createSocketMethods } from './socket'
13
+
14
+ /**
15
+ * Creates a Dropbox sync client with auth, sync, and socket methods
16
+ */
17
+ export function createDropboxSyncClient(
18
+ credentials: DropboxCredentials
19
+ ): DropboxSyncClient {
20
+ let dropboxClient: Dropbox | null = null
21
+
22
+ function getClient(): Dropbox {
23
+ if (!dropboxClient) {
24
+ if (credentials.accessToken) {
25
+ dropboxClient = new Dropbox({
26
+ accessToken: credentials.accessToken,
27
+ })
28
+ } else if (credentials.clientId) {
29
+ dropboxClient = new Dropbox({ clientId: credentials.clientId })
30
+ } else {
31
+ throw new Error(
32
+ 'Either clientId or accessToken must be provided'
33
+ )
34
+ }
35
+ }
36
+ return dropboxClient
37
+ }
38
+
39
+ function setAccessToken(token: string): void {
40
+ dropboxClient = new Dropbox({ accessToken: token })
41
+ }
42
+
43
+ const auth = createAuthMethods(getClient, credentials, setAccessToken)
44
+ const socket = createSocketMethods()
45
+ const sync = createSyncMethods(getClient, credentials, socket)
46
+
47
+ return {
48
+ auth,
49
+ sync,
50
+ socket,
51
+ }
52
+ }
@@ -0,0 +1,73 @@
1
+ import { io, Socket as IOSocket } from 'socket.io-client'
2
+ import type { SocketMethods } from './types'
3
+
4
+ export function createSocketMethods(): SocketMethods {
5
+ let socket: IOSocket | null = null
6
+ const handlers: Record<string, ((...args: any[]) => void)[]> = {}
7
+
8
+ return {
9
+ /**
10
+ * Establish a Socket.IO connection
11
+ */
12
+ connect() {
13
+ if (!socket) {
14
+ // Default connection to the server's base URL
15
+ const url =
16
+ globalThis?.window?.location?.origin ||
17
+ 'http://localhost:3000'
18
+ socket = io(url)
19
+ }
20
+
21
+ if (socket && !socket.connected) {
22
+ socket.connect()
23
+ }
24
+ },
25
+
26
+ /**
27
+ * Disconnect the Socket.IO connection
28
+ */
29
+ disconnect() {
30
+ if (socket && socket.connected) {
31
+ socket.disconnect()
32
+ }
33
+ },
34
+
35
+ /**
36
+ * Listen for an event on the Socket.IO connection
37
+ */
38
+ on(event: string, handler: (...args: any[]) => void) {
39
+ if (!handlers[event]) {
40
+ handlers[event] = []
41
+ }
42
+ handlers[event].push(handler)
43
+
44
+ if (socket) {
45
+ socket.on(event, handler)
46
+ }
47
+ },
48
+
49
+ /**
50
+ * Remove listeners for an event on the Socket.IO connection
51
+ */
52
+ off(event: string) {
53
+ if (socket && handlers[event]) {
54
+ for (const handler of handlers[event]) {
55
+ socket.off(event, handler)
56
+ }
57
+ delete handlers[event]
58
+ }
59
+ },
60
+
61
+ /**
62
+ * Emit an event on the Socket.IO connection
63
+ */
64
+ emit(event: string, ...args: any[]) {
65
+ if (socket) {
66
+ socket.emit(event, ...args)
67
+ return true
68
+ }
69
+
70
+ return false
71
+ },
72
+ }
73
+ }