webmention-receiver
Version:
webmention receiver with webmention-handler
127 lines (108 loc) • 4.09 kB
JavaScript
import { WebMentionHandler } from 'webmention-handler'
import BlobStorage from 'webmention-handler-netlify-blobs'
import HTTP from '../lib/HTTPResponse.js'
import { translate } from '../lib/convert.js'
import fetchWebmentions from '../lib/webmentionio.js'
import sendWebhook from '../lib/webhook.js'
export default class WebmentionReceiver {
#store // Defaults to BlobStorage
#token // (Optional) Generate a random string (https://generate-random.org/string-generator)
#webhook // (Optional) webhook to send POST request to
#handler
constructor({ urls, store, token, webhook }) {
this.#store = store ? store : new BlobStorage()
this.#token = token
this.#webhook = webhook
this.#handler = new WebMentionHandler({
supportedHosts: urls, // ['example.com', 'www.example.com', 'example.net']
storageHandler: this.#store
})
}
#validateRequest = token => {
if (!this.#token) return HTTP.INTERNAL_SERVER_ERROR('Missing token')
if (!token) return HTTP.UNAUTHORIZED()
if (this.#token != token) return HTTP.FORBIDDEN()
}
cleanupHandler = async (req) => {
if ('GET' !== req.method) return HTTP.METHOD_NOT_ALLOWED()
const params = new URL(req.url).searchParams
const token = params.get('token')
const error = this.#validateRequest(token)
if (error) return error
const total = await this.#store.clearAll()
return HTTP.OK(`Deleted ${total} items`)
}
importHandler = async (req) => {
if ('GET' !== req.method) return HTTP.METHOD_NOT_ALLOWED()
const params = new URL(req.url).searchParams
const token = params.get('token')
const webmentionio = params.get('webmentionio')
const error = this.#validateRequest(token)
if (error) return error
if (!webmentionio) return HTTP.BAD_REQUEST('Missing "webmentionio" token')
try {
const webmentions = await fetchWebmentions(webmentionio)
console.log(`[INFO] Importing ${webmentions.length} items`)
const targets = {}
for (const wm of webmentions) {
const mention = translate(wm)
targets[mention.target] = targets[mention.target] || []
targets[mention.target].push(mention)
}
for (const [target, mentions] of Object.entries(targets)) {
await this.#store.storeMentionsForPage(target, mentions)
}
return HTTP.OK(`Imported ${webmentions.length} webmentions for ${Object.entries(targets).length} targets`)
} catch (error) {
console.error('[ERROR]', error.message)
return HTTP.BAD_REQUEST(error.message)
}
}
processHandler = async () => {
await this.#handler.processPendingMentions()
return HTTP.OK()
}
webmentionHandler = async (req) => {
const contentType = req.headers.get('content-type')
let body
if ('application/x-www-form-urlencoded' === contentType) {
body = await req.formData()
} else {
body = new URL(req.url).searchParams
}
if (!body) return HTTP.BAD_REQUEST('Missing "source" and "target"')
const source = body.get('source')
if (!source) return HTTP.BAD_REQUEST('Missing "source"')
const target = body.get('target')
if (!target) return HTTP.BAD_REQUEST('Missing "target"')
const recommendedResponse = await this.#handler.addPendingMention(source, target)
if ([200, 201, 202].includes(recommendedResponse.code)) {
await sendWebhook(this.#webhook, { source, target })
}
return new Response('accepted', { status: recommendedResponse.code })
}
webmentionsHandler = async (req) => {
if ('GET' !== req.method) return HTTP.METHOD_NOT_ALLOWED()
const params = new URL(req.url).searchParams
const url = params.get('url')
const token = params.get('token')
if (url) {
try {
const type = params.get('type')
const mentions = await this.#handler.getMentionsForPage(url, type)
return HTTP.OK({
[url]: mentions
})
} catch (error) {
console.error('[ERROR]', error.message)
return HTTP.BAD_REQUEST(`${url} is not valid`)
}
} else if (token) {
const error = this.#validateRequest(token)
if (error) return error
const mentions = await this.#store.getAllMentions()
return HTTP.OK(mentions)
}
return HTTP.BAD_REQUEST('Missing "url"')
}
}