/ app / protocols / browser-protocol.js
browser-protocol.js
  1  import path from 'node:path'
  2  import { Readable } from 'node:stream'
  3  import { fileURLToPath } from 'node:url'
  4  import mime from 'mime'
  5  import ScopedFS from 'scoped-fs'
  6  
  7  import { version, dependencies as packageDependencies } from '../version.js'
  8  import Config from '../config.js'
  9  const { theme } = Config
 10  
 11  const CHECK_PATHS = [
 12    (path) => path,
 13    (path) => path + 'index.html',
 14    (path) => path + 'index.md',
 15    (path) => path + '/index.html',
 16    (path) => path + '/index.md',
 17    (path) => path + '.html',
 18    (path) => path + '.md'
 19  ]
 20  
 21  const pagesURL = new URL('../pages', import.meta.url)
 22  const pagesPath = fileURLToPath(pagesURL)
 23  
 24  const fs = new ScopedFS(pagesPath)
 25  
 26  export default async function createHandler () {
 27    return { handler: protocolHandler, close }
 28  
 29    function close () {}
 30  
 31    async function protocolHandler (req, sendResponse) {
 32      const { url } = req
 33  
 34      const parsed = new URL(url)
 35      const { pathname, hostname } = parsed
 36      const toResolve = path.join(hostname, pathname)
 37  
 38      if (hostname === 'about') {
 39        const statusCode = 200
 40  
 41        const packagesToRender = [
 42          'hypercore-fetch',
 43          'hyper-sdk',
 44          'js-ipfs-fetch',
 45          'ipfs-core',
 46          'bt-fetch',
 47          'gun-fetch',
 48          'gemini-fetch'
 49        ]
 50  
 51        const dependencies = {}
 52        for (const name of packagesToRender) {
 53          dependencies[name] = packageDependencies[name]
 54        }
 55  
 56        const aboutInfo = {
 57          version,
 58          dependencies
 59        }
 60  
 61        const data = intoStream(JSON.stringify(aboutInfo, null, '\t'))
 62  
 63        const headers = {
 64          'Access-Control-Allow-Origin': '*',
 65          'Allow-CSP-From': '*',
 66          'Content-Type': 'application/json'
 67        }
 68  
 69        sendResponse({
 70          statusCode,
 71          headers,
 72          data
 73        })
 74  
 75        return
 76      } else if ((hostname === 'theme') && (pathname === '/vars.css')) {
 77        const statusCode = 200
 78  
 79        const themes = Object
 80          .keys(theme)
 81          .map((name) => `  --ag-theme-${name}: ${theme[name]};`)
 82          .join('\n')
 83  
 84        const data = intoStream(`
 85  :root {
 86    --ag-color-purple: #6e2de5;
 87    --ag-color-black: #111;
 88    --ag-color-white: #F2F2F2;
 89    --ag-color-green: #2de56e;
 90  }
 91  
 92  :root {
 93  ${themes}
 94  }
 95        `)
 96  
 97        const headers = {
 98          'Access-Control-Allow-Origin': '*',
 99          'Allow-CSP-From': '*',
100          'Cache-Control': 'no-cache',
101          'Content-Type': 'text/css'
102        }
103  
104        sendResponse({
105          statusCode,
106          headers,
107          data
108        })
109  
110        return
111      }
112  
113      try {
114        const resolvedPath = await resolveFile(toResolve)
115        const statusCode = 200
116  
117        const contentType = mime.getType(resolvedPath) || 'text/plain'
118  
119        const data = fs.createReadStream(resolvedPath)
120  
121        const headers = {
122          'Access-Control-Allow-Origin': '*',
123          'Allow-CSP-From': 'agregore://welcome',
124          'Cache-Control': 'no-cache',
125          'Content-Type': contentType
126        }
127  
128        sendResponse({
129          statusCode,
130          headers,
131          data
132        })
133      } catch (e) {
134        const statusCode = 404
135  
136        const data = fs.createReadStream('404.html')
137  
138        const headers = {
139          'Access-Control-Allow-Origin': '*',
140          'Allow-CSP-From': '*',
141          'Cache-Control': 'no-cache',
142          'Content-Type': 'text/html'
143        }
144  
145        sendResponse({
146          statusCode,
147          headers,
148          data
149        })
150      }
151    }
152  }
153  
154  async function resolveFile (path) {
155    for (const toTry of CHECK_PATHS) {
156      const tryPath = toTry(path)
157      if (await exists(tryPath)) return tryPath
158    }
159    throw new Error('Not Found')
160  }
161  
162  function exists (path) {
163    return new Promise((resolve, reject) => {
164      fs.stat(path, (err, stat) => {
165        if (err) {
166          if (err.code === 'ENOENT') resolve(false)
167          else reject(err)
168        } else resolve(stat.isFile())
169      })
170    })
171  }
172  
173  function intoStream (data) {
174    return new Readable({
175      read () {
176        this.push(data)
177        this.push(null)
178      }
179    })
180  }