export class Frontend { constructor({apiBaseUrl, appBaseUrl}) { this.apiBaseUrl = apiBaseUrl this.appBaseUrl = appBaseUrl this.files = {} this.tasks = [] } repoUrl(user, repo) { return ( this.apiBaseUrl + '/' + encodeURIComponent(user) + '/' + encodeURIComponent(repo) ) } rawUrl(user, repo, commit, path) { return ( this.appBaseUrl + '/raw/commit' + '/' + encodeURIComponent(commit) + '/' + encodeURI(path) ) } async loadFile(user, repo, commit, src, dest) { const url = this.rawUrl(user, repo) const resp = await fetch(url) const body = await resp.arrayBuffer() this.files[dest] = { user, repo, commit, body, contentType: ( dest.endsWith('.js') ? 'text/javascript' : resp.headers.get('content-type') ) } } contentsUrl(user, repo, path, ref) { const url = new URL( this.repoUrl(user, repo) + '/contents' + path.map(s => ( '/' + encodeURIComponent(s) )).join('') ) 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) const contents = await resp.json() for (const item of contents) { if (item.type === 'dir') { this.tasks.push(this.loadRepo(user, repo, { ...opts, srcPath: [...srcPath, item.name], destPath: [...destPath, item.name], })) } else { this.tasks.push(this.loadFile( user, repo, last_commit_sha, [...srcPath, item.name], [...destPath, item.name], )) } } let prevCount = 0 while (this.tasks.count > prevCount) { prevCount = this.tasks.count await Promise.allSettled(this.tasks) } } 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: 404, headers: { 'Content-Type': file.contentType, }, })) } else { event.respondWith(new Response( 'Not Found', {status: 404} )) } } }