You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
server/frontend.js

127 lines
2.9 KiB
JavaScript

export class Frontend {
contentTypes = {
html: 'text/html; charset=utf-8',
js: 'text/javascript',
css: 'text/css',
md: 'text/plain',
txt: 'text/plain',
png: 'image/png',
jpg: 'image/jpg',
webm: 'image/webm',
svg: 'image/svg',
}
constructor({apiBaseUrl, appBaseUrl}) {
this.apiBaseUrl = apiBaseUrl
this.appBaseUrl = appBaseUrl
this.files = {}
}
repoUrl(user, repo) {
return (
this.apiBaseUrl +
'/repos' +
'/' + encodeURIComponent(user) +
'/' + encodeURIComponent(repo)
)
}
pathString(path) {
return path.map(s => (
'/' + encodeURIComponent(s)
)).join('')
}
rawUrl(user, repo, commit, path) {
return (
this.appBaseUrl +
'/' + encodeURIComponent(user) +
'/' + encodeURIComponent(repo) +
'/raw/commit' +
'/' + encodeURIComponent(commit) +
this.pathString(path)
)
}
checkOk(resp) {
if (!resp.ok) {
throw new Error('not ok - ' + JSON.stringify(
{status: resp.status, url: resp.url}
))
}
}
async loadFile(user, repo, commit, src, dest) {
const url = this.rawUrl(user, repo, commit, src)
const resp = await fetch(url)
this.checkOk(resp)
const body = await resp.arrayBuffer()
const ext = dest.at('-1')?.match?.(/\.(\w+)$/)?.[1]
const contentType = this.contentTypes[ext]
this.files[this.pathString(dest)] = {
user,
repo,
commit,
body,
contentType: contentType ?? 'application/octet-stream',
}
}
contentsUrl(user, repo, path, ref) {
const url = new URL(
this.repoUrl(user, repo) +
'/contents' +
this.pathString(path)
)
if (ref !== undefined) {
const search = new URLSearchParams()
search.set('ref', ref)
url.search = search.toString()
}
return url.toString()
}
async loadRepo(user, repo, opts) {
const {
srcPath = [], destPath = [], ref
} = opts
const url = this.contentsUrl(
user, repo, srcPath, ref
)
const resp = await fetch(url)
this.checkOk(resp)
const contents = await resp.json()
await Promise.all(contents.map(item => (
item.type === 'dir' ?
this.loadRepo(user, repo, {
...opts,
srcPath: [...srcPath, item.name],
destPath: [...destPath, item.name],
}) :
this.loadFile(
user,
repo,
item.last_commit_sha,
[...srcPath, item.name],
[...destPath, item.name],
)
)))
}
async serve(event) {
const {pathname} = new URL(event.request.url)
if (pathname in this.files) {
const file = this.files[pathname]
event.respondWith(new Response(file.body, {
status: 200,
headers: {
'Content-Type': file.contentType,
},
}))
} else {
event.respondWith(new Response(
'Not Found', {status: 404}
))
}
}
}