Static site hosting via tangled
1import { getContentTypeForExtension, trimLeadingSlash } from "./helpers.js"; 2import path from "node:path"; 3 4// Helpers 5 6function getContentTypeForFilename(filename) { 7 const extension = path.extname(filename).toLowerCase(); 8 return getContentTypeForExtension(extension); 9} 10 11class KnotClient { 12 constructor({ domain, ownerDid, repoName, branch }) { 13 this.domain = domain; 14 this.ownerDid = ownerDid; 15 this.repoName = repoName; 16 this.branch = branch; 17 } 18 19 async getBlob(filename) { 20 const url = `https://${this.domain}/${this.ownerDid}/${ 21 this.repoName 22 }/blob/${this.branch}/${trimLeadingSlash(filename)}`; 23 const res = await fetch(url); 24 return await res.json(); 25 } 26 27 async getRaw(filename) { 28 const url = `https://${this.domain}/${this.ownerDid}/${this.repoName}/raw/${ 29 this.branch 30 }/${trimLeadingSlash(filename)}`; 31 const res = await fetch(url, { 32 responseType: "arraybuffer", 33 }); 34 const arrayBuffer = await res.arrayBuffer(); 35 return Buffer.from(arrayBuffer); 36 } 37} 38 39class PagesService { 40 constructor({ 41 domain, 42 ownerDid, 43 repoName, 44 branch, 45 baseDir = "/", 46 notFoundFilepath = null, 47 }) { 48 this.domain = domain; 49 this.ownerDid = ownerDid; 50 this.repoName = repoName; 51 this.branch = branch; 52 this.baseDir = baseDir; 53 this.notFoundFilepath = notFoundFilepath; 54 this.client = new KnotClient({ 55 domain: domain, 56 ownerDid: ownerDid, 57 repoName: repoName, 58 branch: branch, 59 }); 60 } 61 62 async getFileContent(filename) { 63 let content = null; 64 const blob = await this.client.getBlob(filename); 65 if (blob.is_binary) { 66 content = await this.client.getRaw(filename); 67 } else { 68 content = blob.contents; 69 } 70 return content; 71 } 72 73 async getPage(route) { 74 let filePath = route; 75 const extension = path.extname(filePath); 76 if (extension === "") { 77 filePath = path.join(filePath, "index.html"); 78 } 79 const fullPath = path.join(this.baseDir, trimLeadingSlash(filePath)); 80 const content = await this.getFileContent(fullPath); 81 if (!content) { 82 return this.get404(); 83 } 84 return { 85 status: 200, 86 content, 87 contentType: getContentTypeForFilename(fullPath), 88 }; 89 } 90 91 async get404() { 92 if (this.notFoundFilepath) { 93 const content = await this.getFileContent(this.notFoundFilepath); 94 return { 95 status: 404, 96 content, 97 contentType: getContentTypeForFilename(this.notFoundFilepath), 98 }; 99 } 100 return { status: 404, content: "Not Found", contentType: "text/plain" }; 101 } 102} 103 104export default PagesService;