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} )) } } }