Add support for Plausible Analytics (#4)

* Added a dirty way to POST Plausible

* Added some documentation

* Made the documentation slightly better

* Wrapped fetch in waitUntil

* Apply suggestions from code review
This commit is contained in:
aura 2022-12-20 17:21:42 -05:00 committed by GitHub
parent 15adcb9bd7
commit 45472515e0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 57 additions and 30 deletions

View file

@ -12,13 +12,14 @@ Wrangler 2 is required now, but should be handled automatically for `yarn` comma
To run manual wrangler commands, try `npx wrangler`. To run manual wrangler commands, try `npx wrangler`.
Simple steps: Simple steps:
- `yarn install` - `yarn install`
- `yarn addsecret` - `yarn addsecret`
- `yarn deploy` - `yarn deploy`
## Deploy (More involved) ## Deploy (More involved)
To deploy to your Cloudflare Workers account, edit the relevant entries in `wrangler.toml`, add a secret with `wrangler secret put WORKERLINKS_SECRET` and use `wrangler publish`. To deploy to your Cloudflare Workers account, edit the relevant entries in `wrangler.toml`, add a secret with `wrangler secret put WORKERLINKS_SECRET` and use `wrangler publish`.
## Debugging ## Debugging
@ -83,6 +84,10 @@ It is a planned feature to be able to list all URLs via a `GET` on `/` with `Aut
For the time being you can view them from your Cloudflare Dashboard: For the time being you can view them from your Cloudflare Dashboard:
Cloudflare Dashboard -> Workers -> KV -> View on the namespace. Cloudflare Dashboard -> Workers -> KV -> View on the namespace.
## Plausible Analytics
To get statistics for your short URLs with Plausible Analytics, define a `PLAUSIBLE_HOST` secret set to the URL of your Plausible instance. For example, `https://plausible.io/`.
## Security ## Security
This code is relatively simple but still, if you find any security issues that can be exploited publicly, please reach out to me via email: `erisa (at) erisa.uk` with any relevant details. This code is relatively simple but still, if you find any security issues that can be exploited publicly, please reach out to me via email: `erisa (at) erisa.uk` with any relevant details.

View file

@ -1,14 +1,15 @@
let secret let secret
addEventListener('fetch', (event) => { addEventListener('fetch', (event) => {
event.respondWith(handleRequest(event.request)) event.respondWith(handleRequest(event))
}) })
/** /**
* Respond to the request * Respond to the request
* @param {Request} request * @param {Event} event
*/ */
async function handleRequest(request) { async function handleRequest(event) {
const { request } = event
// Set this in your worker's environment. wrangler.toml or cloudflare dashboard // Set this in your worker's environment. wrangler.toml or cloudflare dashboard
if (WORKERLINKS_SECRET === undefined) { if (WORKERLINKS_SECRET === undefined) {
return new Response('Secret is not defined. Please add WORKERLINKS_SECRET.') return new Response('Secret is not defined. Please add WORKERLINKS_SECRET.')
@ -31,11 +32,11 @@ async function handleRequest(request) {
return Response.json( return Response.json(
{ {
code: '405 Method Not Allowed', code: '405 Method Not Allowed',
message: 'POST not valid for individual keys. Did you mean PUT?' message: 'POST not valid for individual keys. Did you mean PUT?',
}, },
{ {
status: 405 status: 405,
} },
) )
} }
key = '/' + Math.random().toString(36).slice(5) key = '/' + Math.random().toString(36).slice(5)
@ -52,13 +53,34 @@ async function handleRequest(request) {
return Response.json( return Response.json(
{ {
code: '404 Not Found', code: '404 Not Found',
message: 'Key does not exist or has not propagated.' message: 'Key does not exist or has not propagated.',
}, },
{ {
status: 404 status: 404,
} },
) )
} else { } else {
// PLAUSIBLE_HOST should be the full URL to your Plausible Analytics instance
// e.g. https://plausible.io/
if (PLAUSIBLE_HOST !== undefined) {
const url = PLAUSIBLE_HOST + 'api/event'
const headers = new Headers()
headers.append('User-Agent', request.headers.get('User-Agent'))
headers.append(
'X-Forwarded-For',
request.headers.get('X-Forwarded-For'),
)
headers.append('Content-Type', 'application/json')
const data = {
name: 'pageview',
url: request.url,
domain: new URL(request.url).hostname,
}
event.waitUntil(
fetch(url, { method: 'POST', headers, body: JSON.stringify(data) }),
)
}
return new Response(null, { status: 302, headers: { Location: url } }) return new Response(null, { status: 302, headers: { Location: url } })
} }
} else if (request.method == 'DELETE') { } else if (request.method == 'DELETE') {
@ -66,11 +88,11 @@ async function handleRequest(request) {
return Response.json( return Response.json(
{ {
code: '401 Unauthorized', code: '401 Unauthorized',
message: 'Unauthorized.' message: 'Unauthorized.',
}, },
{ {
status: 401 status: 401,
} },
) )
} }
@ -80,11 +102,11 @@ async function handleRequest(request) {
return Response.json( return Response.json(
{ {
code: '404 Not Found', code: '404 Not Found',
message: 'Key does not exist or has not propagated.' message: 'Key does not exist or has not propagated.',
}, },
{ {
status: 404, status: 404,
} },
) )
} else { } else {
await kv.delete(key) await kv.delete(key)
@ -93,11 +115,11 @@ async function handleRequest(request) {
message: 'Short URL deleted succesfully.', message: 'Short URL deleted succesfully.',
key: key.substr(1), key: key.substr(1),
shorturl: shorturl, shorturl: shorturl,
longurl: url longurl: url,
}, },
{ {
status: 200 status: 200,
} },
) )
} }
} }
@ -106,11 +128,11 @@ async function handleRequest(request) {
{ {
code: '405 Method Not Allowed', code: '405 Method Not Allowed',
message: message:
'Unsupported method. Please use one of GET, PUT, POST, DELETE, HEAD.' 'Unsupported method. Please use one of GET, PUT, POST, DELETE, HEAD.',
}, },
{ {
status: 405 status: 405,
} },
) )
} }
@ -132,11 +154,11 @@ async function putLink(givenSecret, shorturl, key, url) {
return Response.json( return Response.json(
{ {
code: '401 Unauthorized', code: '401 Unauthorized',
message: 'Unauthorized.' message: 'Unauthorized.',
}, },
{ {
status: 401 status: 401,
} },
) )
} }
@ -144,11 +166,11 @@ async function putLink(givenSecret, shorturl, key, url) {
return Response.json( return Response.json(
{ {
code: '400 Bad Request', code: '400 Bad Request',
message: "No valid URL given. Please set a 'URL' header." message: "No valid URL given. Please set a 'URL' header.",
}, },
{ {
status: 400 status: 400,
} },
) )
} }
@ -158,10 +180,10 @@ async function putLink(givenSecret, shorturl, key, url) {
message: 'URL created succesfully.', message: 'URL created succesfully.',
key: key.substr(1), key: key.substr(1),
shorturl: shorturl, shorturl: shorturl,
longurl: url longurl: url,
}, },
{ {
status: 200 status: 200,
} },
) )
} }