/ app / protocols / fetch-to-handler.js
fetch-to-handler.js
  1  import fs from 'fs-extra'
  2  import { Readable } from 'stream'
  3  
  4  /*
  5  export const CORS_HEADERS = [
  6   'Access-Control-Allow-Origin',
  7   'Allow-CSP-From',
  8   'Access-Control-Allow-Headers',
  9   'Access-Control-Allow-Methods',
 10   'Access-Control-Request-Headers'
 11  ]
 12  */
 13  
 14  export default function fetchToHandler (getFetch, session) {
 15    let hasFetch = null
 16    let loadingFetch = null
 17  
 18    async function load () {
 19      try {
 20        if (hasFetch) return hasFetch
 21        if (loadingFetch) return loadingFetch
 22  
 23        loadingFetch = Promise.resolve(getFetch()).then((fetch) => {
 24          hasFetch = fetch
 25          return fetch
 26        })
 27  
 28        return loadingFetch
 29      } finally {
 30        loadingFetch = null
 31      }
 32    }
 33  
 34    async function * readBody (body) {
 35      for (const chunk of body) {
 36        if (chunk.bytes) {
 37          yield await Promise.resolve(chunk.bytes)
 38        } else if (chunk.blobUUID) {
 39          yield await session.getBlobData(chunk.blobUUID)
 40        } else if (chunk.file) {
 41          yield * Readable.from(fs.createReadStream(chunk.file))
 42        }
 43      }
 44    }
 45  
 46    const close = async () => {
 47      if (loadingFetch) {
 48        await loadingFetch
 49        await close()
 50      } else if (hasFetch) {
 51        if (hasFetch.close) await hasFetch.close()
 52        else if (hasFetch.destroy) await hasFetch.destroy()
 53      }
 54    }
 55  
 56    return { handler: protocolHandler, close }
 57  
 58    async function protocolHandler (req, sendResponse) {
 59      const headers = {
 60        'Access-Control-Allow-Origin': '*',
 61        'Allow-CSP-From': '*',
 62        'Access-Control-Allow-Headers': '*',
 63        'Access-Control-Allow-Methods': '*',
 64        'Access-Control-Request-Headers': '*'
 65      }
 66  
 67      try {
 68        // Lazy load fetch implementation
 69        const fetch = await load()
 70  
 71        const { url, headers: requestHeaders, method, uploadData } = req
 72  
 73        const body = uploadData ? Readable.from(readBody(uploadData)) : null
 74  
 75        const response = await fetch(url, {
 76          method,
 77          headers: requestHeaders,
 78          body,
 79          session,
 80          duplex: 'half'
 81        })
 82  
 83        const { status: statusCode, body: responseBody, headers: responseHeaders } = response
 84  
 85        for (const [key, value] of responseHeaders) {
 86          if (Array.isArray(value)) {
 87            headers[key] = value[0]
 88          } else {
 89            headers[key] = value
 90          }
 91        }
 92  
 93        const isAsync = responseBody && responseBody[Symbol.asyncIterator]
 94        const data = isAsync ? Readable.from(responseBody, { objectMode: false }) : responseBody
 95  
 96        sendResponse({
 97          statusCode,
 98          headers,
 99          data
100        })
101      } catch (e) {
102        console.log(e)
103        sendResponse({
104          statusCode: 500,
105          headers,
106          data: intoStream(e.stack)
107        })
108      }
109    }
110  }
111  
112  function intoStream (data) {
113    return new Readable({
114      read () {
115        this.push(data)
116        this.push(null)
117      }
118    })
119  }