/** * 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 { 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 { 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 & Record = {}, ) { const input = requireRecord(params, 'ensure-shared-direct-url') requireDropboxPath(input.path) return ensureSharedDirectUrl(input as unknown as EnsureSharedDirectUrlInput) }