@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
@@ -0,0 +1,120 @@
|
|
1
|
+
import { createDropboxSyncClient } from '../core/client';
|
2
|
+
import { cookies } from 'next/headers';
|
3
|
+
import { NextResponse } from 'next/server';
|
4
|
+
/**
|
5
|
+
* Next.js-specific helper to create a Dropbox sync client
|
6
|
+
* Can be used in both client and server components
|
7
|
+
*/
|
8
|
+
export function useNextDropboxSync(credentials) {
|
9
|
+
return createDropboxSyncClient(credentials);
|
10
|
+
}
|
11
|
+
/**
|
12
|
+
* Server-side helper to get credentials from Next.js cookies
|
13
|
+
*/
|
14
|
+
export async function getCredentialsFromCookies() {
|
15
|
+
const cookieStore = await cookies();
|
16
|
+
return {
|
17
|
+
clientId: process.env.DROPBOX_APP_KEY || '',
|
18
|
+
clientSecret: process.env.DROPBOX_APP_SECRET,
|
19
|
+
accessToken: cookieStore.get('dropbox_access_token')?.value,
|
20
|
+
refreshToken: cookieStore.get('dropbox_refresh_token')?.value,
|
21
|
+
};
|
22
|
+
}
|
23
|
+
/**
|
24
|
+
* Server action to handle Dropbox OAuth callback
|
25
|
+
*/
|
26
|
+
export async function handleOAuthCallback(request) {
|
27
|
+
const url = new URL(request.url);
|
28
|
+
const code = url.searchParams.get('code');
|
29
|
+
if (!code) {
|
30
|
+
return NextResponse.redirect(new URL('/auth/error', request.url));
|
31
|
+
}
|
32
|
+
const redirectUri = process.env.DROPBOX_REDIRECT_URI ||
|
33
|
+
`${process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'}/api/dropbox/auth/callback`;
|
34
|
+
const dropboxSync = createDropboxSyncClient({
|
35
|
+
clientId: process.env.DROPBOX_APP_KEY || '',
|
36
|
+
clientSecret: process.env.DROPBOX_APP_SECRET,
|
37
|
+
});
|
38
|
+
try {
|
39
|
+
const tokens = await dropboxSync.auth.exchangeCodeForToken(code, redirectUri);
|
40
|
+
// Create response with redirect
|
41
|
+
const response = NextResponse.redirect(new URL('/', request.url));
|
42
|
+
// Set cookies with the tokens
|
43
|
+
response.cookies.set({
|
44
|
+
name: 'dropbox_access_token',
|
45
|
+
value: tokens.accessToken,
|
46
|
+
httpOnly: true,
|
47
|
+
secure: process.env.NODE_ENV === 'production',
|
48
|
+
maxAge: tokens.expiresAt
|
49
|
+
? (tokens.expiresAt - Date.now()) / 1000
|
50
|
+
: 14 * 24 * 60 * 60, // 14 days default
|
51
|
+
sameSite: 'lax',
|
52
|
+
path: '/',
|
53
|
+
});
|
54
|
+
if (tokens.refreshToken) {
|
55
|
+
response.cookies.set({
|
56
|
+
name: 'dropbox_refresh_token',
|
57
|
+
value: tokens.refreshToken,
|
58
|
+
httpOnly: true,
|
59
|
+
secure: process.env.NODE_ENV === 'production',
|
60
|
+
maxAge: 365 * 24 * 60 * 60, // 1 year
|
61
|
+
sameSite: 'lax',
|
62
|
+
path: '/',
|
63
|
+
});
|
64
|
+
}
|
65
|
+
// Set a non-httpOnly cookie to indicate connection status to the client
|
66
|
+
response.cookies.set({
|
67
|
+
name: 'dropbox_connected',
|
68
|
+
value: 'true',
|
69
|
+
secure: process.env.NODE_ENV === 'production',
|
70
|
+
maxAge: tokens.expiresAt
|
71
|
+
? (tokens.expiresAt - Date.now()) / 1000
|
72
|
+
: 14 * 24 * 60 * 60,
|
73
|
+
sameSite: 'lax',
|
74
|
+
path: '/',
|
75
|
+
});
|
76
|
+
return response;
|
77
|
+
}
|
78
|
+
catch (error) {
|
79
|
+
console.error('Error completing Dropbox OAuth flow:', error);
|
80
|
+
return NextResponse.redirect(new URL('/auth/error', request.url));
|
81
|
+
}
|
82
|
+
}
|
83
|
+
/**
|
84
|
+
* Create API route handlers for a Next.js app
|
85
|
+
*/
|
86
|
+
export function createNextDropboxApiHandlers() {
|
87
|
+
return {
|
88
|
+
/**
|
89
|
+
* Handler for status check route
|
90
|
+
*/
|
91
|
+
async status() {
|
92
|
+
const cookieStore = await cookies();
|
93
|
+
const isConnected = !!cookieStore.get('dropbox_access_token')?.value;
|
94
|
+
return NextResponse.json({ connected: isConnected });
|
95
|
+
},
|
96
|
+
/**
|
97
|
+
* Handler for OAuth start route
|
98
|
+
*/
|
99
|
+
async oauthStart() {
|
100
|
+
const dropboxSync = createDropboxSyncClient({
|
101
|
+
clientId: process.env.DROPBOX_APP_KEY || '',
|
102
|
+
});
|
103
|
+
const redirectUri = process.env.DROPBOX_REDIRECT_URI ||
|
104
|
+
`${process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'}/api/dropbox/auth/callback`;
|
105
|
+
const authUrl = await dropboxSync.auth.getAuthUrl(redirectUri);
|
106
|
+
return NextResponse.redirect(authUrl);
|
107
|
+
},
|
108
|
+
/**
|
109
|
+
* Handler for logout route
|
110
|
+
*/
|
111
|
+
async logout() {
|
112
|
+
const response = NextResponse.json({ success: true });
|
113
|
+
// Clear all Dropbox-related cookies
|
114
|
+
response.cookies.delete('dropbox_access_token');
|
115
|
+
response.cookies.delete('dropbox_refresh_token');
|
116
|
+
response.cookies.delete('dropbox_connected');
|
117
|
+
return response;
|
118
|
+
},
|
119
|
+
};
|
120
|
+
}
|
@@ -0,0 +1,36 @@
|
|
1
|
+
import type { DropboxCredentials, DropboxSyncClient } from '../core/types';
|
2
|
+
import type { H3Event } from 'h3';
|
3
|
+
/**
|
4
|
+
* Nuxt-specific helper for creating a Dropbox sync client
|
5
|
+
* Can be used in both client and server components
|
6
|
+
*/
|
7
|
+
export declare function useNuxtDropboxSync(credentials?: Partial<DropboxCredentials>): DropboxSyncClient;
|
8
|
+
/**
|
9
|
+
* Server-side helper to get credentials from Nuxt server event
|
10
|
+
*/
|
11
|
+
export declare function getCredentialsFromCookies(event: H3Event): DropboxCredentials;
|
12
|
+
/**
|
13
|
+
* Create API event handlers for a Nuxt app
|
14
|
+
*/
|
15
|
+
export declare function createNuxtApiHandlers(): {
|
16
|
+
/**
|
17
|
+
* Handler for status check endpoint
|
18
|
+
*/
|
19
|
+
status(event: H3Event): Promise<{
|
20
|
+
connected: boolean;
|
21
|
+
}>;
|
22
|
+
/**
|
23
|
+
* Handler for OAuth start endpoint
|
24
|
+
*/
|
25
|
+
oauthStart(event: H3Event): Promise<any>;
|
26
|
+
/**
|
27
|
+
* Handler for OAuth callback endpoint
|
28
|
+
*/
|
29
|
+
oauthCallback(event: H3Event): Promise<any>;
|
30
|
+
/**
|
31
|
+
* Handler for logout endpoint
|
32
|
+
*/
|
33
|
+
logout(event: H3Event): Promise<{
|
34
|
+
success: boolean;
|
35
|
+
}>;
|
36
|
+
};
|
@@ -0,0 +1,190 @@
|
|
1
|
+
import { createDropboxSyncClient } from '../core/client';
|
2
|
+
import { useCookie, useRuntimeConfig } from 'nuxt/app';
|
3
|
+
/**
|
4
|
+
* Helper to safely access runtime config properties
|
5
|
+
*/
|
6
|
+
function getConfigValue(obj, path, defaultValue) {
|
7
|
+
const parts = path.split('.');
|
8
|
+
let current = obj;
|
9
|
+
for (const part of parts) {
|
10
|
+
if (current === undefined || current === null) {
|
11
|
+
return defaultValue;
|
12
|
+
}
|
13
|
+
current = current[part];
|
14
|
+
}
|
15
|
+
return current || defaultValue;
|
16
|
+
}
|
17
|
+
/**
|
18
|
+
* Nuxt-specific helper for creating a Dropbox sync client
|
19
|
+
* Can be used in both client and server components
|
20
|
+
*/
|
21
|
+
export function useNuxtDropboxSync(credentials) {
|
22
|
+
// Get Nuxt runtime config (for client ID and secret)
|
23
|
+
const config = useRuntimeConfig();
|
24
|
+
// Create base credentials with defaults from runtime config
|
25
|
+
const baseCredentials = {
|
26
|
+
clientId: getConfigValue(config, 'public.dropboxAppKey', ''),
|
27
|
+
clientSecret: getConfigValue(config, 'dropboxAppSecret', ''),
|
28
|
+
...credentials,
|
29
|
+
};
|
30
|
+
// On client-side, attempt to get tokens from cookies
|
31
|
+
if (process.client) {
|
32
|
+
const accessToken = useCookie('dropbox_access_token');
|
33
|
+
const refreshToken = useCookie('dropbox_refresh_token');
|
34
|
+
if (accessToken.value && !baseCredentials.accessToken) {
|
35
|
+
baseCredentials.accessToken = accessToken.value;
|
36
|
+
}
|
37
|
+
if (refreshToken.value && !baseCredentials.refreshToken) {
|
38
|
+
baseCredentials.refreshToken = refreshToken.value;
|
39
|
+
}
|
40
|
+
}
|
41
|
+
return createDropboxSyncClient(baseCredentials);
|
42
|
+
}
|
43
|
+
/**
|
44
|
+
* Server-side helper to get credentials from Nuxt server event
|
45
|
+
*/
|
46
|
+
export function getCredentialsFromCookies(event) {
|
47
|
+
const config = useRuntimeConfig();
|
48
|
+
// Get cookies from Nuxt server event
|
49
|
+
const accessToken = getCookie(event, 'dropbox_access_token');
|
50
|
+
const refreshToken = getCookie(event, 'dropbox_refresh_token');
|
51
|
+
return {
|
52
|
+
clientId: getConfigValue(config, 'public.dropboxAppKey', ''),
|
53
|
+
clientSecret: getConfigValue(config, 'dropboxAppSecret', ''),
|
54
|
+
accessToken,
|
55
|
+
refreshToken,
|
56
|
+
};
|
57
|
+
}
|
58
|
+
/**
|
59
|
+
* Create API event handlers for a Nuxt app
|
60
|
+
*/
|
61
|
+
export function createNuxtApiHandlers() {
|
62
|
+
return {
|
63
|
+
/**
|
64
|
+
* Handler for status check endpoint
|
65
|
+
*/
|
66
|
+
async status(event) {
|
67
|
+
const accessToken = getCookie(event, 'dropbox_access_token');
|
68
|
+
const isConnected = !!accessToken;
|
69
|
+
return { connected: isConnected };
|
70
|
+
},
|
71
|
+
/**
|
72
|
+
* Handler for OAuth start endpoint
|
73
|
+
*/
|
74
|
+
async oauthStart(event) {
|
75
|
+
const config = useRuntimeConfig();
|
76
|
+
const dropboxSync = createDropboxSyncClient({
|
77
|
+
clientId: getConfigValue(config, 'public.dropboxAppKey', ''),
|
78
|
+
});
|
79
|
+
const redirectUri = getConfigValue(config, 'dropboxRedirectUri', '') ||
|
80
|
+
`${getConfigValue(config, 'public.appUrl', 'http://localhost:3000')}/api/dropbox/auth/callback`;
|
81
|
+
const authUrl = await dropboxSync.auth.getAuthUrl(redirectUri);
|
82
|
+
return sendRedirect(event, authUrl);
|
83
|
+
},
|
84
|
+
/**
|
85
|
+
* Handler for OAuth callback endpoint
|
86
|
+
*/
|
87
|
+
async oauthCallback(event) {
|
88
|
+
const config = useRuntimeConfig();
|
89
|
+
// Get the authorization code from query parameters
|
90
|
+
const query = getQuery(event);
|
91
|
+
const code = query.code;
|
92
|
+
if (!code) {
|
93
|
+
return sendRedirect(event, '/auth/error');
|
94
|
+
}
|
95
|
+
const redirectUri = getConfigValue(config, 'dropboxRedirectUri', '') ||
|
96
|
+
`${getConfigValue(config, 'public.appUrl', 'http://localhost:3000')}/api/dropbox/auth/callback`;
|
97
|
+
const dropboxSync = createDropboxSyncClient({
|
98
|
+
clientId: getConfigValue(config, 'public.dropboxAppKey', ''),
|
99
|
+
clientSecret: getConfigValue(config, 'dropboxAppSecret', ''),
|
100
|
+
});
|
101
|
+
try {
|
102
|
+
const tokens = await dropboxSync.auth.exchangeCodeForToken(code, redirectUri);
|
103
|
+
// Set cookies with the tokens
|
104
|
+
setCookie(event, 'dropbox_access_token', tokens.accessToken, {
|
105
|
+
httpOnly: true,
|
106
|
+
secure: process.env.NODE_ENV === 'production',
|
107
|
+
maxAge: tokens.expiresAt
|
108
|
+
? Math.floor((tokens.expiresAt - Date.now()) / 1000)
|
109
|
+
: 14 * 24 * 60 * 60, // 14 days default
|
110
|
+
sameSite: 'lax',
|
111
|
+
path: '/',
|
112
|
+
});
|
113
|
+
if (tokens.refreshToken) {
|
114
|
+
setCookie(event, 'dropbox_refresh_token', tokens.refreshToken, {
|
115
|
+
httpOnly: true,
|
116
|
+
secure: process.env.NODE_ENV === 'production',
|
117
|
+
maxAge: 365 * 24 * 60 * 60, // 1 year
|
118
|
+
sameSite: 'lax',
|
119
|
+
path: '/',
|
120
|
+
});
|
121
|
+
}
|
122
|
+
// Set a non-httpOnly cookie to indicate connection status to the client
|
123
|
+
setCookie(event, 'dropbox_connected', 'true', {
|
124
|
+
secure: process.env.NODE_ENV === 'production',
|
125
|
+
maxAge: tokens.expiresAt
|
126
|
+
? Math.floor((tokens.expiresAt - Date.now()) / 1000)
|
127
|
+
: 14 * 24 * 60 * 60,
|
128
|
+
sameSite: 'lax',
|
129
|
+
path: '/',
|
130
|
+
});
|
131
|
+
return sendRedirect(event, '/');
|
132
|
+
}
|
133
|
+
catch (error) {
|
134
|
+
console.error('Error completing Dropbox OAuth flow:', error);
|
135
|
+
return sendRedirect(event, '/auth/error');
|
136
|
+
}
|
137
|
+
},
|
138
|
+
/**
|
139
|
+
* Handler for logout endpoint
|
140
|
+
*/
|
141
|
+
async logout(event) {
|
142
|
+
// Clear all Dropbox-related cookies
|
143
|
+
deleteCookie(event, 'dropbox_access_token');
|
144
|
+
deleteCookie(event, 'dropbox_refresh_token');
|
145
|
+
deleteCookie(event, 'dropbox_connected');
|
146
|
+
return { success: true };
|
147
|
+
},
|
148
|
+
};
|
149
|
+
}
|
150
|
+
/**
|
151
|
+
* Helper functions to work with Nuxt's H3Event
|
152
|
+
* These import statements need to be added to avoid reference errors
|
153
|
+
*/
|
154
|
+
const { getCookie, setCookie, deleteCookie } = useNuxtCookies();
|
155
|
+
const { getQuery, sendRedirect } = useNuxtServer();
|
156
|
+
/**
|
157
|
+
* Helper to access h3 cookie methods
|
158
|
+
*/
|
159
|
+
function useNuxtCookies() {
|
160
|
+
return {
|
161
|
+
getCookie: (event, name) => {
|
162
|
+
// Import inside function to avoid module loading issues
|
163
|
+
const { getCookie } = require('h3');
|
164
|
+
return getCookie(event, name);
|
165
|
+
},
|
166
|
+
setCookie: (event, name, value, options) => {
|
167
|
+
const { setCookie } = require('h3');
|
168
|
+
return setCookie(event, name, value, options);
|
169
|
+
},
|
170
|
+
deleteCookie: (event, name, options) => {
|
171
|
+
const { deleteCookie } = require('h3');
|
172
|
+
return deleteCookie(event, name, { ...options, path: '/' });
|
173
|
+
},
|
174
|
+
};
|
175
|
+
}
|
176
|
+
/**
|
177
|
+
* Helper to access h3 server methods
|
178
|
+
*/
|
179
|
+
function useNuxtServer() {
|
180
|
+
return {
|
181
|
+
getQuery: (event) => {
|
182
|
+
const { getQuery } = require('h3');
|
183
|
+
return getQuery(event);
|
184
|
+
},
|
185
|
+
sendRedirect: (event, location) => {
|
186
|
+
const { sendRedirect } = require('h3');
|
187
|
+
return sendRedirect(event, location);
|
188
|
+
},
|
189
|
+
};
|
190
|
+
}
|
@@ -0,0 +1,39 @@
|
|
1
|
+
import type { DropboxCredentials, DropboxSyncClient } from '../core/types';
|
2
|
+
import type { Cookies } from '@sveltejs/kit';
|
3
|
+
/**
|
4
|
+
* SvelteKit-specific helper for creating a Dropbox sync client
|
5
|
+
* Can be used in both client and server contexts
|
6
|
+
*/
|
7
|
+
export declare function useSvelteDropboxSync(credentials: DropboxCredentials): DropboxSyncClient;
|
8
|
+
/**
|
9
|
+
* Helper to get credentials from SvelteKit cookies
|
10
|
+
*/
|
11
|
+
export declare function getCredentialsFromCookies(cookies: Cookies): DropboxCredentials;
|
12
|
+
/**
|
13
|
+
* Create server-side handlers for SvelteKit
|
14
|
+
*/
|
15
|
+
export declare function createSvelteKitHandlers(): {
|
16
|
+
/**
|
17
|
+
* Handler for status check endpoint
|
18
|
+
*/
|
19
|
+
status({ cookies }: {
|
20
|
+
cookies: Cookies;
|
21
|
+
}): Promise<Response>;
|
22
|
+
/**
|
23
|
+
* Handler for OAuth start endpoint
|
24
|
+
*/
|
25
|
+
oauthStart(): Promise<Response>;
|
26
|
+
/**
|
27
|
+
* Handler for OAuth callback endpoint
|
28
|
+
*/
|
29
|
+
oauthCallback({ url, cookies }: {
|
30
|
+
url: URL;
|
31
|
+
cookies: Cookies;
|
32
|
+
}): Promise<Response>;
|
33
|
+
/**
|
34
|
+
* Handler for logout endpoint
|
35
|
+
*/
|
36
|
+
logout({ cookies }: {
|
37
|
+
cookies: Cookies;
|
38
|
+
}): Promise<Response>;
|
39
|
+
};
|
@@ -0,0 +1,134 @@
|
|
1
|
+
import { createDropboxSyncClient } from '../core/client';
|
2
|
+
/**
|
3
|
+
* SvelteKit-specific helper for creating a Dropbox sync client
|
4
|
+
* Can be used in both client and server contexts
|
5
|
+
*/
|
6
|
+
export function useSvelteDropboxSync(credentials) {
|
7
|
+
return createDropboxSyncClient(credentials);
|
8
|
+
}
|
9
|
+
/**
|
10
|
+
* Helper to get credentials from SvelteKit cookies
|
11
|
+
*/
|
12
|
+
export function getCredentialsFromCookies(cookies) {
|
13
|
+
return {
|
14
|
+
clientId: process.env.DROPBOX_APP_KEY || '',
|
15
|
+
clientSecret: process.env.DROPBOX_APP_SECRET,
|
16
|
+
accessToken: cookies.get('dropbox_access_token'),
|
17
|
+
refreshToken: cookies.get('dropbox_refresh_token'),
|
18
|
+
};
|
19
|
+
}
|
20
|
+
/**
|
21
|
+
* Create server-side handlers for SvelteKit
|
22
|
+
*/
|
23
|
+
export function createSvelteKitHandlers() {
|
24
|
+
return {
|
25
|
+
/**
|
26
|
+
* Handler for status check endpoint
|
27
|
+
*/
|
28
|
+
async status({ cookies }) {
|
29
|
+
const isConnected = !!cookies.get('dropbox_access_token');
|
30
|
+
return new Response(JSON.stringify({ connected: isConnected }), {
|
31
|
+
status: 200,
|
32
|
+
headers: {
|
33
|
+
'Content-Type': 'application/json',
|
34
|
+
},
|
35
|
+
});
|
36
|
+
},
|
37
|
+
/**
|
38
|
+
* Handler for OAuth start endpoint
|
39
|
+
*/
|
40
|
+
async oauthStart() {
|
41
|
+
const dropboxSync = createDropboxSyncClient({
|
42
|
+
clientId: process.env.DROPBOX_APP_KEY || '',
|
43
|
+
});
|
44
|
+
const redirectUri = process.env.DROPBOX_REDIRECT_URI ||
|
45
|
+
`${process.env.PUBLIC_APP_URL || 'http://localhost:5173'}/api/dropbox/auth/callback`;
|
46
|
+
const authUrl = await dropboxSync.auth.getAuthUrl(redirectUri);
|
47
|
+
return new Response(null, {
|
48
|
+
status: 302,
|
49
|
+
headers: {
|
50
|
+
Location: authUrl,
|
51
|
+
},
|
52
|
+
});
|
53
|
+
},
|
54
|
+
/**
|
55
|
+
* Handler for OAuth callback endpoint
|
56
|
+
*/
|
57
|
+
async oauthCallback({ url, cookies }) {
|
58
|
+
const code = url.searchParams.get('code');
|
59
|
+
if (!code) {
|
60
|
+
return new Response(null, {
|
61
|
+
status: 302,
|
62
|
+
headers: {
|
63
|
+
Location: '/auth/error',
|
64
|
+
},
|
65
|
+
});
|
66
|
+
}
|
67
|
+
const redirectUri = process.env.DROPBOX_REDIRECT_URI ||
|
68
|
+
`${process.env.PUBLIC_APP_URL || 'http://localhost:5173'}/api/dropbox/auth/callback`;
|
69
|
+
const dropboxSync = createDropboxSyncClient({
|
70
|
+
clientId: process.env.DROPBOX_APP_KEY || '',
|
71
|
+
clientSecret: process.env.DROPBOX_APP_SECRET,
|
72
|
+
});
|
73
|
+
try {
|
74
|
+
const tokens = await dropboxSync.auth.exchangeCodeForToken(code, redirectUri);
|
75
|
+
// Set cookies with the tokens
|
76
|
+
cookies.set('dropbox_access_token', tokens.accessToken, {
|
77
|
+
path: '/',
|
78
|
+
httpOnly: true,
|
79
|
+
secure: process.env.NODE_ENV === 'production',
|
80
|
+
maxAge: tokens.expiresAt
|
81
|
+
? Math.floor((tokens.expiresAt - Date.now()) / 1000)
|
82
|
+
: 14 * 24 * 60 * 60, // 14 days default
|
83
|
+
sameSite: 'lax',
|
84
|
+
});
|
85
|
+
if (tokens.refreshToken) {
|
86
|
+
cookies.set('dropbox_refresh_token', tokens.refreshToken, {
|
87
|
+
path: '/',
|
88
|
+
httpOnly: true,
|
89
|
+
secure: process.env.NODE_ENV === 'production',
|
90
|
+
maxAge: 365 * 24 * 60 * 60, // 1 year
|
91
|
+
sameSite: 'lax',
|
92
|
+
});
|
93
|
+
}
|
94
|
+
cookies.set('dropbox_connected', 'true', {
|
95
|
+
path: '/',
|
96
|
+
secure: process.env.NODE_ENV === 'production',
|
97
|
+
maxAge: tokens.expiresAt
|
98
|
+
? Math.floor((tokens.expiresAt - Date.now()) / 1000)
|
99
|
+
: 14 * 24 * 60 * 60,
|
100
|
+
sameSite: 'lax',
|
101
|
+
});
|
102
|
+
return new Response(null, {
|
103
|
+
status: 302,
|
104
|
+
headers: {
|
105
|
+
Location: '/',
|
106
|
+
},
|
107
|
+
});
|
108
|
+
}
|
109
|
+
catch (error) {
|
110
|
+
console.error('Error completing Dropbox OAuth flow:', error);
|
111
|
+
return new Response(null, {
|
112
|
+
status: 302,
|
113
|
+
headers: {
|
114
|
+
Location: '/auth/error',
|
115
|
+
},
|
116
|
+
});
|
117
|
+
}
|
118
|
+
},
|
119
|
+
/**
|
120
|
+
* Handler for logout endpoint
|
121
|
+
*/
|
122
|
+
async logout({ cookies }) {
|
123
|
+
cookies.delete('dropbox_access_token', { path: '/' });
|
124
|
+
cookies.delete('dropbox_refresh_token', { path: '/' });
|
125
|
+
cookies.delete('dropbox_connected', { path: '/' });
|
126
|
+
return new Response(JSON.stringify({ success: true }), {
|
127
|
+
status: 200,
|
128
|
+
headers: {
|
129
|
+
'Content-Type': 'application/json',
|
130
|
+
},
|
131
|
+
});
|
132
|
+
},
|
133
|
+
};
|
134
|
+
}
|
@@ -0,0 +1,84 @@
|
|
1
|
+
export function createAuthMethods(getClient, credentials, setAccessToken) {
|
2
|
+
return {
|
3
|
+
/**
|
4
|
+
* Generate an authentication URL for Dropbox OAuth flow
|
5
|
+
*/
|
6
|
+
async getAuthUrl(redirectUri, state) {
|
7
|
+
const dropbox = getClient();
|
8
|
+
const randomState = state || Math.random().toString(36).substring(2, 15);
|
9
|
+
// Cast to extended auth type to access missing methods
|
10
|
+
const auth = dropbox.auth;
|
11
|
+
// Generate the authentication URL
|
12
|
+
const authUrl = auth.getAuthenticationUrl(redirectUri, randomState, 'code', // Use authorization code flow
|
13
|
+
'offline', // Request a refresh token for long-lived access
|
14
|
+
undefined, // No scope specified, request full access
|
15
|
+
undefined, // No include_granted_scopes
|
16
|
+
true // Force reapproval to ensure we get fresh tokens
|
17
|
+
);
|
18
|
+
return authUrl;
|
19
|
+
},
|
20
|
+
/**
|
21
|
+
* Exchange authorization code for access token
|
22
|
+
*/
|
23
|
+
async exchangeCodeForToken(code, redirectUri) {
|
24
|
+
const dropbox = getClient();
|
25
|
+
// Cast to extended auth type to access missing methods
|
26
|
+
const auth = dropbox.auth;
|
27
|
+
// Exchange the code for an access token
|
28
|
+
const response = await auth.getAccessTokenFromCode(redirectUri, code);
|
29
|
+
const tokenResponse = {
|
30
|
+
accessToken: response.result.access_token,
|
31
|
+
refreshToken: response.result.refresh_token,
|
32
|
+
expiresAt: response.result.expires_in
|
33
|
+
? Date.now() + response.result.expires_in * 1000
|
34
|
+
: undefined,
|
35
|
+
};
|
36
|
+
// Update the client with the new token
|
37
|
+
setAccessToken(tokenResponse.accessToken);
|
38
|
+
return tokenResponse;
|
39
|
+
},
|
40
|
+
/**
|
41
|
+
* Refresh an expired access token using the refresh token
|
42
|
+
*/
|
43
|
+
async refreshAccessToken() {
|
44
|
+
if (!credentials.refreshToken ||
|
45
|
+
!credentials.clientId ||
|
46
|
+
!credentials.clientSecret) {
|
47
|
+
throw new Error('Refresh token, client ID, and client secret are required to refresh access token');
|
48
|
+
}
|
49
|
+
// Dropbox API endpoint for token refresh
|
50
|
+
const tokenUrl = 'https://api.dropboxapi.com/oauth2/token';
|
51
|
+
// Prepare the form data for the token request
|
52
|
+
const formData = new URLSearchParams({
|
53
|
+
grant_type: 'refresh_token',
|
54
|
+
refresh_token: credentials.refreshToken,
|
55
|
+
client_id: credentials.clientId,
|
56
|
+
client_secret: credentials.clientSecret,
|
57
|
+
});
|
58
|
+
// Make the request to refresh the token
|
59
|
+
const response = await fetch(tokenUrl, {
|
60
|
+
method: 'POST',
|
61
|
+
headers: {
|
62
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
63
|
+
},
|
64
|
+
body: formData.toString(),
|
65
|
+
});
|
66
|
+
if (!response.ok) {
|
67
|
+
await response.text(); // Consume the response body
|
68
|
+
throw new Error(`Failed to refresh token: ${response.status} ${response.statusText}`);
|
69
|
+
}
|
70
|
+
// Parse the response
|
71
|
+
const data = await response.json();
|
72
|
+
const tokenResponse = {
|
73
|
+
accessToken: data.access_token,
|
74
|
+
refreshToken: data.refresh_token || credentials.refreshToken,
|
75
|
+
expiresAt: data.expires_in
|
76
|
+
? Date.now() + data.expires_in * 1000
|
77
|
+
: undefined,
|
78
|
+
};
|
79
|
+
// Update the client with the new token
|
80
|
+
setAccessToken(tokenResponse.accessToken);
|
81
|
+
return tokenResponse;
|
82
|
+
},
|
83
|
+
};
|
84
|
+
}
|
@@ -0,0 +1,37 @@
|
|
1
|
+
import { Dropbox } from 'dropbox';
|
2
|
+
import { createAuthMethods } from './auth';
|
3
|
+
import { createSyncMethods } from './sync';
|
4
|
+
import { createSocketMethods } from './socket';
|
5
|
+
/**
|
6
|
+
* Creates a Dropbox sync client with auth, sync, and socket methods
|
7
|
+
*/
|
8
|
+
export function createDropboxSyncClient(credentials) {
|
9
|
+
let dropboxClient = null;
|
10
|
+
function getClient() {
|
11
|
+
if (!dropboxClient) {
|
12
|
+
if (credentials.accessToken) {
|
13
|
+
dropboxClient = new Dropbox({
|
14
|
+
accessToken: credentials.accessToken,
|
15
|
+
});
|
16
|
+
}
|
17
|
+
else if (credentials.clientId) {
|
18
|
+
dropboxClient = new Dropbox({ clientId: credentials.clientId });
|
19
|
+
}
|
20
|
+
else {
|
21
|
+
throw new Error('Either clientId or accessToken must be provided');
|
22
|
+
}
|
23
|
+
}
|
24
|
+
return dropboxClient;
|
25
|
+
}
|
26
|
+
function setAccessToken(token) {
|
27
|
+
dropboxClient = new Dropbox({ accessToken: token });
|
28
|
+
}
|
29
|
+
const auth = createAuthMethods(getClient, credentials, setAccessToken);
|
30
|
+
const socket = createSocketMethods();
|
31
|
+
const sync = createSyncMethods(getClient, credentials, socket);
|
32
|
+
return {
|
33
|
+
auth,
|
34
|
+
sync,
|
35
|
+
socket,
|
36
|
+
};
|
37
|
+
}
|