Skip to content

Commit

Permalink
feat: sentry to gateway
Browse files Browse the repository at this point in the history
  • Loading branch information
vasco-santos committed Dec 8, 2021
1 parent 30e7ad1 commit 5a2b187
Show file tree
Hide file tree
Showing 12 changed files with 200 additions and 23 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/gateway.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ jobs:
- uses: bahmutov/npm-install@v1
- name: Publish app
uses: cloudflare/[email protected]
env:
SENTRY_TOKEN: ${{secrets.SENTRY_TOKEN}}
with:
apiToken: ${{secrets.CF_API_TOKEN }}
workingDirectory: 'packages/gateway'
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@ jobs:
- name: Gateway - Deploy
if: ${{ steps.tag-release.outputs.release_created && matrix.package == 'gateway' }}
uses: cloudflare/[email protected]
env:
SENTRY_TOKEN: ${{ secrets.SENTRY_TOKEN }}
SENTRY_UPLOAD: ${{ secrets.SENTRY_UPLOAD }}
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
workingDirectory: 'packages/gateway'
Expand Down
6 changes: 6 additions & 0 deletions packages/gateway/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ One time set up of your cloudflare worker subdomain for dev:
account_id = "<what does the `wrangler whoami` say>"
```

- Add secrets

```sh
wrangler secret put SENTRY_DSN --env $(whoami) # Get from Sentry (not required for dev)
```

- `npm run publish` - Publish the worker under your env. An alias for `wrangler publish --env $(whoami)`
- `npm start` - Run the worker in dev mode. An alias for `wrangler dev --env $(whoami)

Expand Down
8 changes: 8 additions & 0 deletions packages/gateway/ava.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default {
nonSemVerExperiments: {
configurableModuleFormat: true,
},
files: ['test/*.spec.js'],
timeout: '5m',
nodeArguments: ['--experimental-vm-modules'],
}
13 changes: 9 additions & 4 deletions packages/gateway/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
"description": "IPFS gateway for nft.storage",
"private": true,
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"scripts": {
"build": "esbuild --bundle --sourcemap --outdir=dist ./src/index.js",
"build": "node scripts/cli.js build",
"dev": "miniflare --watch --debug",
"deploy": "wrangler publish --env production",
"pretest": "npm run build ",
Expand All @@ -15,14 +15,19 @@
"mock:ipfs.io": "smoke -p 9081 test/mocks/ipfs.io"
},
"dependencies": {
"multiformats": "^9.5.2"
"multiformats": "^9.5.2",
"toucan-js": "^2.4.1"
},
"devDependencies": {
"@sentry/cli": "^1.71.0",
"ava": "^3.15.0",
"esbuild": "^0.14.2",
"git-rev-sync": "^3.0.1",
"miniflare": "^2.0.0-rc.2",
"npm-run-all": "^4.1.5",
"smoke": "^3.1.1"
"sade": "^1.7.4",
"smoke": "^3.1.1",
"toucan-js": "^2.5.0"
},
"author": "Vasco Santos <[email protected]>",
"license": "(Apache-2.0 AND MIT)"
Expand Down
74 changes: 74 additions & 0 deletions packages/gateway/scripts/cli.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/usr/bin/env node
import fs from 'fs'
import path from 'path'
import { fileURLToPath } from 'url'
import sade from 'sade'
import { build } from 'esbuild'
import git from 'git-rev-sync'
import Sentry from '@sentry/cli'

const __dirname = path.dirname(fileURLToPath(import.meta.url))
const pkg = JSON.parse(
fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8')
)

const prog = sade('gateway')

prog
.command('build')
.describe('Build the worker.')
.option('--env', 'Environment', 'dev')
.action(async (opts) => {
try {
const version = `${pkg.name}@${pkg.version}-${opts.env}+${git.short(
__dirname
)}`

await build({
entryPoints: [path.join(__dirname, '../src/index.js')],
bundle: true,
format: 'esm',
outfile: 'dist/index.mjs',
legalComments: 'external',
define: {
VERSION: JSON.stringify(version),
COMMITHASH: JSON.stringify(git.long(__dirname)),
BRANCH: JSON.stringify(git.branch(__dirname)),
ENV: opts.env || 'dev',
global: 'globalThis',
},
minify: opts.env === 'dev' ? false : true,
sourcemap: true,
})

// Sentry release and sourcemap upload
if (process.env.SENTRY_UPLOAD === 'true') {
const cli = new Sentry(undefined, {
authToken: process.env.SENTRY_TOKEN,
org: 'protocol-labs-it',
project: 'nft-gateway',
dist: git.short(__dirname),
})

await cli.releases.new(version)
await cli.releases.setCommits(version, {
auto: true,
ignoreEmpty: true,
ignoreMissing: true,
})
await cli.releases.uploadSourceMaps(version, {
include: ['./dist'],
urlPrefix: '/',
})
await cli.releases.finalize(version)
await cli.releases.newDeploy(version, {
env: opts.env,
})
}
} catch (err) {
console.error(err)
process.exit(1)
}
})

prog.parse(process.argv)
39 changes: 37 additions & 2 deletions packages/gateway/src/error-handler.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import Toucan from 'toucan-js'

import pkg from '../package.json'
import { JSONResponse } from './utils/json-response.js'

/**
* @param {Error & {status?: number;code?: string;}} err
* @param {Request} request
* @param {import('./index').Env} env
*/
export function errorHandler(err) {
// TODO: setup sentry
export function errorHandler(err, request, env) {
console.error(err.stack)

let error = {
Expand All @@ -13,5 +17,36 @@ export function errorHandler(err) {
}
let status = err.status || 500

const sentry = getSentry(request, env)
if (sentry && status >= 500) {
sentry.captureException(err)
}

return new JSONResponse(error, { status })
}

/**
* Get sentry instance if configured
*
* @param {Request} request
* @param {import('./index').Env} env
*/
function getSentry(request, env) {
if (!env.SENTRY_DSN) {
return
}

return new Toucan({
request,
dsn: env.SENTRY_DSN,
allowedHeaders: ['user-agent', 'x-client'],
allowedSearchParams: /(.*)/,
debug: false,
environment: env.ENV || 'dev',
rewriteFrames: {
root: '/',
},
release: env.VERSION,
pkg,
})
}
25 changes: 25 additions & 0 deletions packages/gateway/src/errors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export class InvalidIpfsPathError extends Error {
/**
* @param {string} cid
*/
constructor(cid) {
super(`invalid ipfs path: invalid path "/ipfs/${cid}/"`)
this.name = 'InvalidIpfsPath'
this.status = 400
this.code = InvalidIpfsPathError.CODE
}
}
InvalidIpfsPathError.CODE = 'ERROR_INVALID_IPFS_PATH'

export class InvalidUrlError extends Error {
/**
* @param {string} url
*/
constructor(url) {
super(`invalid url: ${url}`)
this.name = 'InvalidUrl'
this.status = 400
this.code = InvalidUrlError.CODE
}
}
InvalidUrlError.CODE = 'ERROR_INVALID_URL'
34 changes: 23 additions & 11 deletions packages/gateway/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,21 @@ import { addCorsHeaders } from './cors.js'
import { errorHandler } from './error-handler.js'
import { getCidFromSubdomainUrl } from './utils/cid.js'

/**
* @typedef {Object} Env
* @property {string} IPFS_GATEWAY
* @property {string} VERSION
* @property {string} ENV
* @property {string} [SENTRY_DSN]
*/

/**
* Handle gateway request
* @param {Request} request
* @param {Env} env
*/
async function handleRequest(request) {
const publicGatewayUrl = new URL('ipfs', IPFS_GATEWAY)
async function handleRequest(request, env) {
const publicGatewayUrl = new URL('ipfs', env.IPFS_GATEWAY)
const cid = getCidFromSubdomainUrl(request.url)
const response = await fetch(`${publicGatewayUrl.toString()}/${cid}`)

Expand All @@ -18,15 +27,18 @@ async function handleRequest(request) {
/**
* @param {Error} error
* @param {Request} request
* @param {Env} env
*/
function serverError(error, request) {
return addCorsHeaders(request, errorHandler(error))
function serverError(error, request, env) {
return addCorsHeaders(request, errorHandler(error, request, env))
}

addEventListener('fetch', (event) => {
event.respondWith(
handleRequest(event.request).catch((error) =>
serverError(error, event.request)
)
)
})
export default {
async fetch(request, env) {
try {
return await handleRequest(request, env)
} catch (error) {
return serverError(error, request, env)
}
},
}
6 changes: 4 additions & 2 deletions packages/gateway/src/utils/cid.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { CID } from 'multiformats/cid'

import { InvalidIpfsPathError, InvalidUrlError } from '../errors.js'

/**
* Parse subdomain URL and return cid
*
Expand All @@ -10,7 +12,7 @@ export function getCidFromSubdomainUrl(url) {
const splitUrl = nUrl.split('.ipfs.')

if (!splitUrl.length) {
throw new Error(`invalid url: ${url}`)
throw new InvalidUrlError(url)
}

return normalizeCid(splitUrl[0])
Expand All @@ -26,6 +28,6 @@ export function normalizeCid(cid) {
const c = CID.parse(cid)
return c.toV1().toString()
} catch (err) {
throw new Error(`invalid ipfs path: invalid path "/ipfs/${cid}/": ${err}`)
throw new InvalidIpfsPathError(cid)
}
}
9 changes: 6 additions & 3 deletions packages/gateway/test/index.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import test from 'ava'
import { Miniflare } from 'miniflare'

import { InvalidIpfsPathError } from '../src/errors.js'

test.beforeEach((t) => {
// Create a new Miniflare environment for each test
const mf = new Miniflare({
Expand All @@ -13,6 +15,7 @@ test.beforeEach((t) => {
// This will override the option in wrangler.toml.
buildCommand: undefined,
wranglerConfigEnv: 'test',
modules: true,
})

t.context = {
Expand All @@ -25,13 +28,13 @@ test('Fails when invalid cid is provided', async (t) => {

const invalidCid = 'bafy'
const response = await mf.dispatchFetch(`${invalidCid}.ipfs.localhost:8787`)
t.is(response.status, 500)
t.is(response.status, 400)

const jsonResponse = await response.json()
t.is(jsonResponse.code, 'HTTP_ERROR')
t.is(jsonResponse.code, InvalidIpfsPathError.CODE)
t.is(
jsonResponse.message,
`invalid ipfs path: invalid path "/ipfs/${invalidCid}/": SyntaxError: Unexpected end of data`
`invalid ipfs path: invalid path "/ipfs/${invalidCid}/"`
)
})

Expand Down
4 changes: 3 additions & 1 deletion packages/gateway/wrangler.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ compatibility_date = "2021-12-03"
[build]
command = "npm run build"
[build.upload]
format = "service-worker"
format = "modules"
dir = "dist"
main = "index.mjs"

# PROD!
[env.production]
Expand Down

0 comments on commit 5a2b187

Please sign in to comment.