Skip to content

Commit

Permalink
offer a json response for grabbing short links (#4671)
Browse files Browse the repository at this point in the history
  • Loading branch information
haileyok authored Jun 27, 2024
1 parent fff3ae8 commit d5ca952
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 3 deletions.
20 changes: 17 additions & 3 deletions bskylink/src/routes/redirect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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
Expand All @@ -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()
}),
)
Expand Down
39 changes: 39 additions & 0 deletions bskylink/tests/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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',
Expand Down

0 comments on commit d5ca952

Please sign in to comment.