Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend dropbox provider to support teams folders #5638

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 49 additions & 15 deletions packages/@uppy/companion/src/server/provider/dropbox/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,22 @@ function httpHeaderSafeJson (v) {
})
}

const getClient = async ({ token }) => (await got).extend({
const getClient = async ({ token, rootNamespaceId }) => (await got).extend({
prefixUrl: 'https://api.dropboxapi.com/2',
headers: {
authorization: `Bearer ${token}`,
authorization: `Bearer ${token}`,
...(rootNamespaceId && {
'Dropbox-API-Path-Root': httpHeaderSafeJson({ ".tag": "root", "root": rootNamespaceId })
}),
},
})

const getOauthClient = async () => (await got).extend({
prefixUrl: 'https://api.dropboxapi.com/oauth2',
})

async function list ({ directory, query, token }) {
const client = await getClient({ token })
async function list({ directory, query, token, rootNamespaceId }) {
const client = await getClient({ token, rootNamespaceId })
if (query.cursor) {
return client.post('files/list_folder/continue', { json: { cursor: query.cursor }, responseType: 'json' }).json()
}
Expand All @@ -48,7 +51,7 @@ async function list ({ directory, query, token }) {
}

async function userInfo ({ token }) {
return (await getClient({ token })).post('users/get_current_account', { responseType: 'json' }).json()
return (await getClient({ token, rootNamespaceId: null })).post('users/get_current_account', { responseType: 'json' }).json()
}

/**
Expand All @@ -58,6 +61,7 @@ class DropBox extends Provider {
constructor (options) {
super(options)
this.needsCookieAuth = true
this.rootNamespaceId = null
}

static get oauthProvider () {
Expand All @@ -74,19 +78,31 @@ class DropBox extends Provider {
*/
async list (options) {
return this.#withErrorHandling('provider.dropbox.list.error', async () => {
const responses = await Promise.all([
list(options),
userInfo(options),
])
// @ts-ignore
const [stats, { email }] = responses
const userInfoResponse = await userInfo(options)
const { email, root_info } = userInfoResponse

// Store rootNamespaceId as class member
this.rootNamespaceId = root_info?.root_namespace_id
Copy link
Member

@Murderlon Murderlon Feb 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to double check, do we initialize this class once or per user? Because setting this will only work for the latter.


// Then call list with the directory path and root namespace
const stats = await list({
...options,
rootNamespaceId: this.rootNamespaceId,
})

return adaptData(stats, email, options.companion.buildURL)
})
}

async download ({ id, token }) {
return this.#withErrorHandling('provider.dropbox.download.error', async () => {
const stream = (await getClient({ token })).stream.post('files/download', {
// Fetch rootNamespaceId if not already set
if (!this.rootNamespaceId) {
const userInfoResponse = await userInfo({ token })
this.rootNamespaceId = userInfoResponse.root_info?.root_namespace_id
}

const stream = (await getClient({ token, rootNamespaceId: this.rootNamespaceId })).stream.post('files/download', {
prefixUrl: 'https://content.dropboxapi.com/2',
headers: {
'Dropbox-API-Arg': httpHeaderSafeJson({ path: String(id) }),
Expand All @@ -103,7 +119,13 @@ class DropBox extends Provider {

async thumbnail ({ id, token }) {
return this.#withErrorHandling('provider.dropbox.thumbnail.error', async () => {
const stream = (await getClient({ token })).stream.post('files/get_thumbnail_v2', {
// Fetch rootNamespaceId if not already set
if (!this.rootNamespaceId) {
const userInfoResponse = await userInfo({ token })
this.rootNamespaceId = userInfoResponse.root_info?.root_namespace_id
}

const stream = (await getClient({ token, rootNamespaceId: this.rootNamespaceId })).stream.post('files/get_thumbnail_v2', {
prefixUrl: 'https://content.dropboxapi.com/2',
headers: { 'Dropbox-API-Arg': httpHeaderSafeJson({ resource: { '.tag': 'path', path: `${id}` }, size: 'w256h256', format: 'jpeg' }) },
body: Buffer.alloc(0),
Expand All @@ -117,14 +139,26 @@ class DropBox extends Provider {

async size ({ id, token }) {
return this.#withErrorHandling('provider.dropbox.size.error', async () => {
const { size } = await (await getClient({ token })).post('files/get_metadata', { json: { path: id }, responseType: 'json' }).json()
// Fetch rootNamespaceId if not already set
if (!this.rootNamespaceId) {
const userInfoResponse = await userInfo({ token })
this.rootNamespaceId = userInfoResponse.root_info?.root_namespace_id
}

const { size } = await (await getClient({ token, rootNamespaceId: this.rootNamespaceId })).post('files/get_metadata', { json: { path: id }, responseType: 'json' }).json()
return parseInt(size, 10)
})
}

async logout ({ token }) {
return this.#withErrorHandling('provider.dropbox.logout.error', async () => {
await (await getClient({ token })).post('auth/token/revoke', { responseType: 'json' })
// Fetch rootNamespaceId if not already set
if (!this.rootNamespaceId) {
const userInfoResponse = await userInfo({ token })
this.rootNamespaceId = userInfoResponse.root_info?.root_namespace_id
}
await (await getClient({ token, rootNamespaceId: this.rootNamespaceId })).post('auth/token/revoke', { responseType: 'json' })

return { revoked: true }
})
}
Expand Down
Loading