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 }