@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.
- package/README.md +315 -0
- package/dist/adapters/angular.d.ts +56 -0
- package/dist/adapters/angular.js +207 -0
- package/dist/adapters/next.d.ts +36 -0
- package/dist/adapters/next.js +120 -0
- package/dist/adapters/nuxt.d.ts +36 -0
- package/dist/adapters/nuxt.js +190 -0
- package/dist/adapters/svelte.d.ts +39 -0
- package/dist/adapters/svelte.js +134 -0
- package/dist/core/auth.d.ts +3 -0
- package/dist/core/auth.js +84 -0
- package/dist/core/client.d.ts +5 -0
- package/dist/core/client.js +37 -0
- package/dist/core/socket.d.ts +2 -0
- package/dist/core/socket.js +62 -0
- package/dist/core/sync.d.ts +3 -0
- package/dist/core/sync.js +340 -0
- package/dist/core/types.d.ts +73 -0
- package/dist/core/types.js +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +14 -0
- package/examples/angular-app/dropbox-sync.service.ts +244 -0
- package/examples/next-app/api-routes.ts +109 -0
- package/examples/next-app/dropbox-client.ts +122 -0
- package/examples/nuxt-app/api-routes.ts +26 -0
- package/examples/nuxt-app/dropbox-plugin.ts +15 -0
- package/examples/nuxt-app/nuxt.config.ts +23 -0
- package/examples/svelte-app/dropbox-store.ts +174 -0
- package/examples/svelte-app/routes.server.ts +120 -0
- package/package.json +66 -0
- package/src/adapters/angular.ts +217 -0
- package/src/adapters/next.ts +155 -0
- package/src/adapters/nuxt.ts +270 -0
- package/src/adapters/svelte.ts +168 -0
- package/src/core/auth.ts +148 -0
- package/src/core/client.ts +52 -0
- package/src/core/socket.ts +73 -0
- package/src/core/sync.ts +476 -0
- package/src/core/types.ts +83 -0
- package/src/index.ts +32 -0
- package/tsconfig.json +16 -0
package/src/core/auth.ts
ADDED
@@ -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
|
+
}
|