diff options
author | Hailey <me@haileyok.com> | 2024-06-27 11:31:24 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-06-27 11:31:24 -0700 |
commit | d5ca95233e3f8dd545fddb54a1f182d5a2e354f8 (patch) | |
tree | 5412dbdd5997cd88ad6b2ece3659398a208bc4c4 /bskylink | |
parent | fff3ae8f359f496de3165d9d15c7135fc4269916 (diff) | |
download | voidsky-d5ca95233e3f8dd545fddb54a1f182d5a2e354f8.tar.zst |
offer a json response for grabbing short links (#4671)
Diffstat (limited to 'bskylink')
-rw-r--r-- | bskylink/src/routes/redirect.ts | 20 | ||||
-rw-r--r-- | bskylink/tests/index.ts | 39 |
2 files changed, 56 insertions, 3 deletions
diff --git a/bskylink/src/routes/redirect.ts b/bskylink/src/routes/redirect.ts index 7791ea815..276aae1ca 100644 --- a/bskylink/src/routes/redirect.ts +++ b/bskylink/src/routes/redirect.ts @@ -11,6 +11,7 @@ export default function (ctx: AppContext, app: Express) { '/:linkId', handler(async (req, res) => { const linkId = req.params.linkId + const contentType = req.accepts(['html', 'json']) assert( typeof linkId === 'string', 'express guarantees id parameter is a string', @@ -21,9 +22,19 @@ export default function (ctx: AppContext, app: Express) { .where('id', '=', linkId) .executeTakeFirst() if (!found) { - // potentially broken or mistyped link— send user to the app - res.setHeader('Location', `https://${ctx.cfg.service.appHostname}`) + // potentially broken or mistyped link res.setHeader('Cache-Control', 'no-store') + if (contentType === 'json') { + return res + .status(404) + .json({ + error: 'NotFound', + message: 'Link not found', + }) + .end() + } + // send the user to the app + res.setHeader('Location', `https://${ctx.cfg.service.appHostname}`) return res.status(302).end() } // build url from original url in order to preserve query params @@ -32,8 +43,11 @@ export default function (ctx: AppContext, app: Express) { `https://${ctx.cfg.service.appHostname}`, ) url.pathname = found.path - res.setHeader('Location', url.href) res.setHeader('Cache-Control', `max-age=${(7 * DAY) / SECOND}`) + if (contentType === 'json') { + return res.json({url: url.href}).end() + } + res.setHeader('Location', url.href) return res.status(301).end() }), ) diff --git a/bskylink/tests/index.ts b/bskylink/tests/index.ts index 51449c21b..c5604c7a1 100644 --- a/bskylink/tests/index.ts +++ b/bskylink/tests/index.ts @@ -56,6 +56,26 @@ describe('link service', async () => { ) }) + it('returns json object with url when requested', async () => { + const link = await getLink('/start/did:example:carol/zzz/') + const [status, json] = await getJsonRedirect(link) + assert.strictEqual(status, 200) + assert(json.url) + const url = new URL(json.url) + assert.strictEqual(url.pathname, '/start/did:example:carol/zzz') + }) + + it('returns 404 for unknown link when requesting json', async () => { + const [status, json] = await getJsonRedirect( + 'https://test.bsky.link/unknown', + ) + assert(json.error) + assert(json.message) + assert.strictEqual(status, 404) + assert.strictEqual(json.error, 'NotFound') + assert.strictEqual(json.message, 'Link not found') + }) + async function getRedirect(link: string): Promise<[number, string]> { const url = new URL(link) const base = new URL(baseUrl) @@ -70,6 +90,25 @@ describe('link service', async () => { return [res.status, res.headers.get('location') ?? ''] } + async function getJsonRedirect( + link: string, + ): Promise<[number, {url?: string; error?: string; message?: string}]> { + const url = new URL(link) + const base = new URL(baseUrl) + url.protocol = base.protocol + url.host = base.host + const res = await fetch(url, { + redirect: 'manual', + headers: {accept: 'application/json,text/html'}, + }) + assert( + res.headers.get('content-type')?.startsWith('application/json'), + 'content type was not json', + ) + const json = await res.json() + return [res.status, json] + } + async function getLink(path: string): Promise<string> { const res = await fetch(new URL('/link', baseUrl), { method: 'post', |