@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,155 @@
1
+ import { createDropboxSyncClient } from '../core/client'
2
+ import type { DropboxCredentials, DropboxSyncClient } from '../core/types'
3
+ import { cookies } from 'next/headers'
4
+ import { NextRequest, NextResponse } from 'next/server'
5
+
6
+ /**
7
+ * Next.js-specific helper to create a Dropbox sync client
8
+ * Can be used in both client and server components
9
+ */
10
+ export function useNextDropboxSync(
11
+ credentials: DropboxCredentials
12
+ ): DropboxSyncClient {
13
+ return createDropboxSyncClient(credentials)
14
+ }
15
+
16
+ /**
17
+ * Server-side helper to get credentials from Next.js cookies
18
+ */
19
+ export async function getCredentialsFromCookies(): Promise<DropboxCredentials> {
20
+ const cookieStore = await cookies()
21
+
22
+ return {
23
+ clientId: process.env.DROPBOX_APP_KEY || '',
24
+ clientSecret: process.env.DROPBOX_APP_SECRET,
25
+ accessToken: cookieStore.get('dropbox_access_token')?.value,
26
+ refreshToken: cookieStore.get('dropbox_refresh_token')?.value,
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Server action to handle Dropbox OAuth callback
32
+ */
33
+ export async function handleOAuthCallback(
34
+ request: NextRequest
35
+ ): Promise<NextResponse> {
36
+ const url = new URL(request.url)
37
+ const code = url.searchParams.get('code')
38
+
39
+ if (!code) {
40
+ return NextResponse.redirect(new URL('/auth/error', request.url))
41
+ }
42
+
43
+ const redirectUri =
44
+ process.env.DROPBOX_REDIRECT_URI ||
45
+ `${
46
+ process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'
47
+ }/api/dropbox/auth/callback`
48
+
49
+ const dropboxSync = createDropboxSyncClient({
50
+ clientId: process.env.DROPBOX_APP_KEY || '',
51
+ clientSecret: process.env.DROPBOX_APP_SECRET,
52
+ })
53
+
54
+ try {
55
+ const tokens = await dropboxSync.auth.exchangeCodeForToken(
56
+ code,
57
+ redirectUri
58
+ )
59
+
60
+ // Create response with redirect
61
+ const response = NextResponse.redirect(new URL('/', request.url))
62
+
63
+ // Set cookies with the tokens
64
+ response.cookies.set({
65
+ name: 'dropbox_access_token',
66
+ value: tokens.accessToken,
67
+ httpOnly: true,
68
+ secure: process.env.NODE_ENV === 'production',
69
+ maxAge: tokens.expiresAt
70
+ ? (tokens.expiresAt - Date.now()) / 1000
71
+ : 14 * 24 * 60 * 60, // 14 days default
72
+ sameSite: 'lax',
73
+ path: '/',
74
+ })
75
+
76
+ if (tokens.refreshToken) {
77
+ response.cookies.set({
78
+ name: 'dropbox_refresh_token',
79
+ value: tokens.refreshToken,
80
+ httpOnly: true,
81
+ secure: process.env.NODE_ENV === 'production',
82
+ maxAge: 365 * 24 * 60 * 60, // 1 year
83
+ sameSite: 'lax',
84
+ path: '/',
85
+ })
86
+ }
87
+
88
+ // Set a non-httpOnly cookie to indicate connection status to the client
89
+ response.cookies.set({
90
+ name: 'dropbox_connected',
91
+ value: 'true',
92
+ secure: process.env.NODE_ENV === 'production',
93
+ maxAge: tokens.expiresAt
94
+ ? (tokens.expiresAt - Date.now()) / 1000
95
+ : 14 * 24 * 60 * 60,
96
+ sameSite: 'lax',
97
+ path: '/',
98
+ })
99
+
100
+ return response
101
+ } catch (error) {
102
+ console.error('Error completing Dropbox OAuth flow:', error)
103
+ return NextResponse.redirect(new URL('/auth/error', request.url))
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Create API route handlers for a Next.js app
109
+ */
110
+ export function createNextDropboxApiHandlers() {
111
+ return {
112
+ /**
113
+ * Handler for status check route
114
+ */
115
+ async status() {
116
+ const cookieStore = await cookies()
117
+ const isConnected = !!cookieStore.get('dropbox_access_token')?.value
118
+
119
+ return NextResponse.json({ connected: isConnected })
120
+ },
121
+
122
+ /**
123
+ * Handler for OAuth start route
124
+ */
125
+ async oauthStart() {
126
+ const dropboxSync = createDropboxSyncClient({
127
+ clientId: process.env.DROPBOX_APP_KEY || '',
128
+ })
129
+
130
+ const redirectUri =
131
+ process.env.DROPBOX_REDIRECT_URI ||
132
+ `${
133
+ process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'
134
+ }/api/dropbox/auth/callback`
135
+
136
+ const authUrl = await dropboxSync.auth.getAuthUrl(redirectUri)
137
+
138
+ return NextResponse.redirect(authUrl)
139
+ },
140
+
141
+ /**
142
+ * Handler for logout route
143
+ */
144
+ async logout() {
145
+ const response = NextResponse.json({ success: true })
146
+
147
+ // Clear all Dropbox-related cookies
148
+ response.cookies.delete('dropbox_access_token')
149
+ response.cookies.delete('dropbox_refresh_token')
150
+ response.cookies.delete('dropbox_connected')
151
+
152
+ return response
153
+ },
154
+ }
155
+ }
@@ -0,0 +1,270 @@
1
+ import { createDropboxSyncClient } from '../core/client'
2
+ import type { DropboxCredentials, DropboxSyncClient } from '../core/types'
3
+ import { useCookie, useRuntimeConfig } from 'nuxt/app'
4
+ import type { H3Event } from 'h3'
5
+
6
+ /**
7
+ * Helper to safely access runtime config properties
8
+ */
9
+ function getConfigValue<T>(obj: any, path: string, defaultValue: T): T {
10
+ const parts = path.split('.')
11
+ let current = obj
12
+
13
+ for (const part of parts) {
14
+ if (current === undefined || current === null) {
15
+ return defaultValue
16
+ }
17
+ current = current[part]
18
+ }
19
+
20
+ return (current as T) || defaultValue
21
+ }
22
+
23
+ /**
24
+ * Nuxt-specific helper for creating a Dropbox sync client
25
+ * Can be used in both client and server components
26
+ */
27
+ export function useNuxtDropboxSync(
28
+ credentials?: Partial<DropboxCredentials>
29
+ ): DropboxSyncClient {
30
+ // Get Nuxt runtime config (for client ID and secret)
31
+ const config = useRuntimeConfig()
32
+
33
+ // Create base credentials with defaults from runtime config
34
+ const baseCredentials: DropboxCredentials = {
35
+ clientId: getConfigValue<string>(config, 'public.dropboxAppKey', ''),
36
+ clientSecret: getConfigValue<string | undefined>(
37
+ config,
38
+ 'dropboxAppSecret',
39
+ ''
40
+ ),
41
+ ...credentials,
42
+ }
43
+
44
+ // On client-side, attempt to get tokens from cookies
45
+ if (process.client) {
46
+ const accessToken = useCookie('dropbox_access_token')
47
+ const refreshToken = useCookie('dropbox_refresh_token')
48
+
49
+ if (accessToken.value && !baseCredentials.accessToken) {
50
+ baseCredentials.accessToken = accessToken.value as string
51
+ }
52
+
53
+ if (refreshToken.value && !baseCredentials.refreshToken) {
54
+ baseCredentials.refreshToken = refreshToken.value as string
55
+ }
56
+ }
57
+
58
+ return createDropboxSyncClient(baseCredentials)
59
+ }
60
+
61
+ /**
62
+ * Server-side helper to get credentials from Nuxt server event
63
+ */
64
+ export function getCredentialsFromCookies(event: H3Event): DropboxCredentials {
65
+ const config = useRuntimeConfig()
66
+
67
+ // Get cookies from Nuxt server event
68
+ const accessToken = getCookie(event, 'dropbox_access_token')
69
+ const refreshToken = getCookie(event, 'dropbox_refresh_token')
70
+
71
+ return {
72
+ clientId: getConfigValue<string>(config, 'public.dropboxAppKey', ''),
73
+ clientSecret: getConfigValue<string | undefined>(
74
+ config,
75
+ 'dropboxAppSecret',
76
+ ''
77
+ ),
78
+ accessToken,
79
+ refreshToken,
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Create API event handlers for a Nuxt app
85
+ */
86
+ export function createNuxtApiHandlers() {
87
+ return {
88
+ /**
89
+ * Handler for status check endpoint
90
+ */
91
+ async status(event: H3Event) {
92
+ const accessToken = getCookie(event, 'dropbox_access_token')
93
+ const isConnected = !!accessToken
94
+
95
+ return { connected: isConnected }
96
+ },
97
+
98
+ /**
99
+ * Handler for OAuth start endpoint
100
+ */
101
+ async oauthStart(event: H3Event) {
102
+ const config = useRuntimeConfig()
103
+
104
+ const dropboxSync = createDropboxSyncClient({
105
+ clientId: getConfigValue<string>(
106
+ config,
107
+ 'public.dropboxAppKey',
108
+ ''
109
+ ),
110
+ })
111
+
112
+ const redirectUri =
113
+ getConfigValue<string>(config, 'dropboxRedirectUri', '') ||
114
+ `${getConfigValue<string>(
115
+ config,
116
+ 'public.appUrl',
117
+ 'http://localhost:3000'
118
+ )}/api/dropbox/auth/callback`
119
+
120
+ const authUrl = await dropboxSync.auth.getAuthUrl(redirectUri)
121
+
122
+ return sendRedirect(event, authUrl)
123
+ },
124
+
125
+ /**
126
+ * Handler for OAuth callback endpoint
127
+ */
128
+ async oauthCallback(event: H3Event) {
129
+ const config = useRuntimeConfig()
130
+
131
+ // Get the authorization code from query parameters
132
+ const query = getQuery(event)
133
+ const code = query.code as string
134
+
135
+ if (!code) {
136
+ return sendRedirect(event, '/auth/error')
137
+ }
138
+
139
+ const redirectUri =
140
+ getConfigValue<string>(config, 'dropboxRedirectUri', '') ||
141
+ `${getConfigValue<string>(
142
+ config,
143
+ 'public.appUrl',
144
+ 'http://localhost:3000'
145
+ )}/api/dropbox/auth/callback`
146
+
147
+ const dropboxSync = createDropboxSyncClient({
148
+ clientId: getConfigValue<string>(
149
+ config,
150
+ 'public.dropboxAppKey',
151
+ ''
152
+ ),
153
+ clientSecret: getConfigValue<string | undefined>(
154
+ config,
155
+ 'dropboxAppSecret',
156
+ ''
157
+ ),
158
+ })
159
+
160
+ try {
161
+ const tokens = await dropboxSync.auth.exchangeCodeForToken(
162
+ code,
163
+ redirectUri
164
+ )
165
+
166
+ // Set cookies with the tokens
167
+ setCookie(event, 'dropbox_access_token', tokens.accessToken, {
168
+ httpOnly: true,
169
+ secure: process.env.NODE_ENV === 'production',
170
+ maxAge: tokens.expiresAt
171
+ ? Math.floor((tokens.expiresAt - Date.now()) / 1000)
172
+ : 14 * 24 * 60 * 60, // 14 days default
173
+ sameSite: 'lax',
174
+ path: '/',
175
+ })
176
+
177
+ if (tokens.refreshToken) {
178
+ setCookie(
179
+ event,
180
+ 'dropbox_refresh_token',
181
+ tokens.refreshToken,
182
+ {
183
+ httpOnly: true,
184
+ secure: process.env.NODE_ENV === 'production',
185
+ maxAge: 365 * 24 * 60 * 60, // 1 year
186
+ sameSite: 'lax',
187
+ path: '/',
188
+ }
189
+ )
190
+ }
191
+
192
+ // Set a non-httpOnly cookie to indicate connection status to the client
193
+ setCookie(event, 'dropbox_connected', 'true', {
194
+ secure: process.env.NODE_ENV === 'production',
195
+ maxAge: tokens.expiresAt
196
+ ? Math.floor((tokens.expiresAt - Date.now()) / 1000)
197
+ : 14 * 24 * 60 * 60,
198
+ sameSite: 'lax',
199
+ path: '/',
200
+ })
201
+
202
+ return sendRedirect(event, '/')
203
+ } catch (error) {
204
+ console.error('Error completing Dropbox OAuth flow:', error)
205
+ return sendRedirect(event, '/auth/error')
206
+ }
207
+ },
208
+
209
+ /**
210
+ * Handler for logout endpoint
211
+ */
212
+ async logout(event: H3Event) {
213
+ // Clear all Dropbox-related cookies
214
+ deleteCookie(event, 'dropbox_access_token')
215
+ deleteCookie(event, 'dropbox_refresh_token')
216
+ deleteCookie(event, 'dropbox_connected')
217
+
218
+ return { success: true }
219
+ },
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Helper functions to work with Nuxt's H3Event
225
+ * These import statements need to be added to avoid reference errors
226
+ */
227
+ const { getCookie, setCookie, deleteCookie } = useNuxtCookies()
228
+ const { getQuery, sendRedirect } = useNuxtServer()
229
+
230
+ /**
231
+ * Helper to access h3 cookie methods
232
+ */
233
+ function useNuxtCookies() {
234
+ return {
235
+ getCookie: (event: H3Event, name: string) => {
236
+ // Import inside function to avoid module loading issues
237
+ const { getCookie } = require('h3')
238
+ return getCookie(event, name)
239
+ },
240
+ setCookie: (
241
+ event: H3Event,
242
+ name: string,
243
+ value: string,
244
+ options?: any
245
+ ) => {
246
+ const { setCookie } = require('h3')
247
+ return setCookie(event, name, value, options)
248
+ },
249
+ deleteCookie: (event: H3Event, name: string, options?: any) => {
250
+ const { deleteCookie } = require('h3')
251
+ return deleteCookie(event, name, { ...options, path: '/' })
252
+ },
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Helper to access h3 server methods
258
+ */
259
+ function useNuxtServer() {
260
+ return {
261
+ getQuery: (event: H3Event) => {
262
+ const { getQuery } = require('h3')
263
+ return getQuery(event)
264
+ },
265
+ sendRedirect: (event: H3Event, location: string) => {
266
+ const { sendRedirect } = require('h3')
267
+ return sendRedirect(event, location)
268
+ },
269
+ }
270
+ }
@@ -0,0 +1,168 @@
1
+ import { createDropboxSyncClient } from '../core/client'
2
+ import type { DropboxCredentials, DropboxSyncClient } from '../core/types'
3
+ import type { Cookies } from '@sveltejs/kit'
4
+
5
+ /**
6
+ * SvelteKit-specific helper for creating a Dropbox sync client
7
+ * Can be used in both client and server contexts
8
+ */
9
+ export function useSvelteDropboxSync(
10
+ credentials: DropboxCredentials
11
+ ): DropboxSyncClient {
12
+ return createDropboxSyncClient(credentials)
13
+ }
14
+
15
+ /**
16
+ * Helper to get credentials from SvelteKit cookies
17
+ */
18
+ export function getCredentialsFromCookies(
19
+ cookies: Cookies
20
+ ): DropboxCredentials {
21
+ return {
22
+ clientId: process.env.DROPBOX_APP_KEY || '',
23
+ clientSecret: process.env.DROPBOX_APP_SECRET,
24
+ accessToken: cookies.get('dropbox_access_token'),
25
+ refreshToken: cookies.get('dropbox_refresh_token'),
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Create server-side handlers for SvelteKit
31
+ */
32
+ export function createSvelteKitHandlers() {
33
+ return {
34
+ /**
35
+ * Handler for status check endpoint
36
+ */
37
+ async status({ cookies }: { cookies: Cookies }) {
38
+ const isConnected = !!cookies.get('dropbox_access_token')
39
+
40
+ return new Response(JSON.stringify({ connected: isConnected }), {
41
+ status: 200,
42
+ headers: {
43
+ 'Content-Type': 'application/json',
44
+ },
45
+ })
46
+ },
47
+
48
+ /**
49
+ * Handler for OAuth start endpoint
50
+ */
51
+ async oauthStart() {
52
+ const dropboxSync = createDropboxSyncClient({
53
+ clientId: process.env.DROPBOX_APP_KEY || '',
54
+ })
55
+
56
+ const redirectUri =
57
+ process.env.DROPBOX_REDIRECT_URI ||
58
+ `${
59
+ process.env.PUBLIC_APP_URL || 'http://localhost:5173'
60
+ }/api/dropbox/auth/callback`
61
+
62
+ const authUrl = await dropboxSync.auth.getAuthUrl(redirectUri)
63
+
64
+ return new Response(null, {
65
+ status: 302,
66
+ headers: {
67
+ Location: authUrl,
68
+ },
69
+ })
70
+ },
71
+
72
+ /**
73
+ * Handler for OAuth callback endpoint
74
+ */
75
+ async oauthCallback({ url, cookies }: { url: URL; cookies: Cookies }) {
76
+ const code = url.searchParams.get('code')
77
+
78
+ if (!code) {
79
+ return new Response(null, {
80
+ status: 302,
81
+ headers: {
82
+ Location: '/auth/error',
83
+ },
84
+ })
85
+ }
86
+
87
+ const redirectUri =
88
+ process.env.DROPBOX_REDIRECT_URI ||
89
+ `${
90
+ process.env.PUBLIC_APP_URL || 'http://localhost:5173'
91
+ }/api/dropbox/auth/callback`
92
+
93
+ const dropboxSync = createDropboxSyncClient({
94
+ clientId: process.env.DROPBOX_APP_KEY || '',
95
+ clientSecret: process.env.DROPBOX_APP_SECRET,
96
+ })
97
+
98
+ try {
99
+ const tokens = await dropboxSync.auth.exchangeCodeForToken(
100
+ code,
101
+ redirectUri
102
+ )
103
+
104
+ // Set cookies with the tokens
105
+ cookies.set('dropbox_access_token', tokens.accessToken, {
106
+ path: '/',
107
+ httpOnly: true,
108
+ secure: process.env.NODE_ENV === 'production',
109
+ maxAge: tokens.expiresAt
110
+ ? Math.floor((tokens.expiresAt - Date.now()) / 1000)
111
+ : 14 * 24 * 60 * 60, // 14 days default
112
+ sameSite: 'lax',
113
+ })
114
+
115
+ if (tokens.refreshToken) {
116
+ cookies.set('dropbox_refresh_token', tokens.refreshToken, {
117
+ path: '/',
118
+ httpOnly: true,
119
+ secure: process.env.NODE_ENV === 'production',
120
+ maxAge: 365 * 24 * 60 * 60, // 1 year
121
+ sameSite: 'lax',
122
+ })
123
+ }
124
+
125
+ cookies.set('dropbox_connected', 'true', {
126
+ path: '/',
127
+ secure: process.env.NODE_ENV === 'production',
128
+ maxAge: tokens.expiresAt
129
+ ? Math.floor((tokens.expiresAt - Date.now()) / 1000)
130
+ : 14 * 24 * 60 * 60,
131
+ sameSite: 'lax',
132
+ })
133
+
134
+ return new Response(null, {
135
+ status: 302,
136
+ headers: {
137
+ Location: '/',
138
+ },
139
+ })
140
+ } catch (error) {
141
+ console.error('Error completing Dropbox OAuth flow:', error)
142
+
143
+ return new Response(null, {
144
+ status: 302,
145
+ headers: {
146
+ Location: '/auth/error',
147
+ },
148
+ })
149
+ }
150
+ },
151
+
152
+ /**
153
+ * Handler for logout endpoint
154
+ */
155
+ async logout({ cookies }: { cookies: Cookies }) {
156
+ cookies.delete('dropbox_access_token', { path: '/' })
157
+ cookies.delete('dropbox_refresh_token', { path: '/' })
158
+ cookies.delete('dropbox_connected', { path: '/' })
159
+
160
+ return new Response(JSON.stringify({ success: true }), {
161
+ status: 200,
162
+ headers: {
163
+ 'Content-Type': 'application/json',
164
+ },
165
+ })
166
+ },
167
+ }
168
+ }