← Public packages
@kentcdodds/dropbox
Upload, share, and manage Dropbox files via OAuth — small or large uploads and direct download URLs.
src/ensure-shared-direct-url.ts
227 lines · 7.8 KB · TypeScript/**
* Ensure a Dropbox shared link exists for a path and return both the share URL
* and a browser/curl-friendly direct content URL on `dl.dropboxusercontent.com` (`raw=1`).
* Reuses an existing link via `sharing/list_shared_links` when create returns
* “link already exists”. Prefer this over hand-rolling share + URL rewriting.
* Requires `confirm: true` (create may mutate); `dryRun: true` previews create.
*
* @param params.path - Dropbox path starting with `/` (prefer `/Apps/Kody/...`).
* @param params.settings - Optional shared-link settings for create.
* @param params.confirm - Required `true` to create or list/reuse a link.
* @param params.dryRun - When true, return the planned create request without calling Dropbox.
* @returns `{ path, url, directUrl, reused, link }` — `directUrl` is curl-friendly content.
*
* @example
* import ensureSharedDirectUrl from 'kody:@kentcdodds/dropbox/ensure-shared-direct-url'
* const result = await ensureSharedDirectUrl({
* path: '/Apps/Kody/notes/guide.pdf',
* confirm: true,
* })
* // => { path, url, directUrl, reused, link }
*/
import { DropboxApiError, dropboxRpc, request } from './request.ts'
import { APP_FOLDER_ROOT, suggestAppFolderPath } from './paths.ts'
import type { JsonRecord } from './types.ts'
import { requireDropboxPath, requireRecord } from './types.ts'
export type EnsureSharedDirectUrlInput = {
path: string
settings?: JsonRecord
confirm?: boolean
dryRun?: boolean
}
export type EnsureSharedDirectUrlResult = {
path: string
url: string
directUrl: string
reused: boolean
link: JsonRecord
}
/**
* Ensure a Dropbox shared link exists for a path and return both the share URL
* and a browser/curl-friendly direct content URL on `dl.dropboxusercontent.com` (`raw=1`).
* Reuses an existing link via `sharing/list_shared_links` when create returns
* “link already exists”. Prefer this over hand-rolling share + URL rewriting.
* Requires `confirm: true` (create may mutate); `dryRun: true` previews create.
*
* @param params.path - Dropbox path starting with `/` (prefer `/Apps/Kody/...`).
* @param params.settings - Optional shared-link settings for create.
* @param params.confirm - Required `true` to create or list/reuse a link.
* @param params.dryRun - When true, return the planned create request without calling Dropbox.
* @returns `{ path, url, directUrl, reused, link }` — `directUrl` is curl-friendly content.
*
* @example
* import ensureSharedDirectUrl from 'kody:@kentcdodds/dropbox/ensure-shared-direct-url'
* const result = await ensureSharedDirectUrl({
* path: '/Apps/Kody/notes/guide.pdf',
* confirm: true,
* })
* // => { path, url, directUrl, reused, link }
*/
export async function ensureSharedDirectUrl(
input: EnsureSharedDirectUrlInput,
): Promise<EnsureSharedDirectUrlResult | JsonRecord> {
const path = requireDropboxPath(input.path)
if (
input.settings !== undefined &&
(!input.settings || typeof input.settings !== 'object' || Array.isArray(input.settings))
) {
throw new Error('settings must be an object when provided.')
}
if (input.dryRun) {
return {
dryRun: true,
endpoint: 'sharing/create_shared_link_with_settings',
path,
body: {
path,
...(input.settings ? { settings: input.settings } : {}),
},
onAlreadyExists: 'sharing/list_shared_links { path, direct_only: true }',
directUrlHint: 'Rewrite the share host to dl.dropboxusercontent.com and set query param raw=1.',
authorization: '[managed dropbox OAuth integration]',
}
}
if (input.confirm !== true) {
throw new Error(
'ensure-shared-direct-url may create a shared link. Pass confirm: true only after explicit user approval, or use dryRun: true.',
)
}
try {
const created = await request({
endpoint: 'sharing/create_shared_link_with_settings',
body: {
path,
...(input.settings ? { settings: input.settings } : {}),
},
confirm: true,
})
const link = unwrapLink(created)
const url = requireUrl(link)
return {
path,
url,
directUrl: toDirectContentUrl(url),
reused: false,
link,
}
} catch (error) {
if (!isSharedLinkAlreadyExists(error)) {
throw enhancePathError(error, path)
}
const link = await findExistingDirectLink(path)
const url = requireUrl(link)
return {
path,
url,
directUrl: toDirectContentUrl(url),
reused: true,
link,
}
}
}
async function findExistingDirectLink(path: string): Promise<JsonRecord> {
const listed = await dropboxRpc<{ links?: JsonRecord[]; has_more?: boolean; cursor?: string }>(
'sharing/list_shared_links',
{ path, direct_only: true },
)
const links = Array.isArray(listed.links) ? listed.links : []
const match =
links.find((link) => typeof link?.url === 'string' && link.url.length > 0) ?? links[0]
if (!match || typeof match.url !== 'string') {
throw new Error(
`Shared link already exists for ${path}, but list_shared_links returned no usable url.`,
)
}
return match
}
/** Convert a Dropbox share URL into a direct content URL (raw=1; follows redirects to content). */
export function toDirectContentUrl(shareUrl: string): string {
let parsed: URL
try {
parsed = new URL(shareUrl)
} catch {
throw new Error('Shared link url is not a valid URL.')
}
// Prefer the content host for programmatic/curl access; remove conflicting dl.
parsed.hostname = 'dl.dropboxusercontent.com'
parsed.searchParams.delete('dl')
parsed.searchParams.set('raw', '1')
return parsed.toString()
}
function unwrapLink(result: unknown): JsonRecord {
if (result && typeof result === 'object' && 'body' in result && 'ok' in result) {
const body = (result as { body: unknown }).body
if (!body || typeof body !== 'object' || Array.isArray(body)) {
throw new Error('Unexpected create_shared_link response body.')
}
return body as JsonRecord
}
if (!result || typeof result !== 'object' || Array.isArray(result)) {
throw new Error('Unexpected create_shared_link response.')
}
return result as JsonRecord
}
function requireUrl(link: JsonRecord): string {
if (typeof link.url !== 'string' || !link.url) {
throw new Error('Shared link response did not include url.')
}
return link.url
}
function isSharedLinkAlreadyExists(error: unknown): boolean {
if (!(error instanceof DropboxApiError)) return false
const blob = `${error.message} ${JSON.stringify(error.details ?? '')}`
return /shared_link_already_exists/i.test(blob)
}
function enhancePathError(error: unknown, path: string): Error {
if (!(error instanceof DropboxApiError)) {
return error instanceof Error ? error : new Error(String(error))
}
const details = JSON.stringify(error.details ?? '')
const looksLikePath =
error.status === 409 ||
/path\/not_found|path\/malformed|not_found/i.test(details) ||
/path\/not_found|path\/malformed|not_found/i.test(error.message)
if (!looksLikePath) return error
const suggestion = suggestAppFolderPath(path)
const hint =
suggestion !== path
? ` Path may need the Kody app-folder prefix. Try "${suggestion}" (APP_FOLDER_ROOT is ${APP_FOLDER_ROOT}).`
: ` Writable Kody files usually live under ${APP_FOLDER_ROOT}/….`
return new DropboxApiError(`${error.message}.${hint}`, {
status: error.status,
statusText: error.statusText,
requestId: error.requestId,
details: error.details,
})
}
/**
* Ensure a shared link and return `{ path, url, directUrl }`.
* Requires `confirm: true`; use `dryRun: true` to preview.
* @example
* import ensureSharedDirectUrl from 'kody:@kentcdodds/dropbox/ensure-shared-direct-url'
* const result = await ensureSharedDirectUrl({
* path: '/Apps/Kody/notes/guide.pdf',
* confirm: true,
* })
*/
export default async function ensureSharedDirectUrlEntrypoint(
params: Partial<EnsureSharedDirectUrlInput> & Record<string, unknown> = {},
) {
const input = requireRecord(params, 'ensure-shared-direct-url')
requireDropboxPath(input.path)
return ensureSharedDirectUrl(input as unknown as EnsureSharedDirectUrlInput)
}