2018-05-08 16:46:57 -07:00
const nodefetch = require ( 'node-fetch' ) ; // Note, were using node-fetch-npm which had a warning in webpack see https://github.com/bitinn/node-fetch/issues/421 and is intended for clients
const errors = require ( './Errors' ) ; // Standard Dweb Errors
//var fetch,Headers,Request;
//if (typeof(Window) === "undefined") {
if ( typeof ( fetch ) === "undefined" ) {
//var fetch = require('whatwg-fetch').fetch; //Not as good as node-fetch-npm, but might be the polyfill needed for browser.safari
//XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest; // Note this doesnt work if set to a var or const, needed by whatwg-fetch
fetch = nodefetch ;
Headers = fetch . Headers ; // A class
Request = fetch . Request ; // A class
} / * else {
// If on a browser, need to find fetch,Headers,Request in window
console . log ( "Loading browser version of fetch,Headers,Request" ) ;
fetch = window . fetch ;
Headers = window . Headers ;
Request = window . Request ;
} * /
//TODO-HTTP to work on Safari or mobile will require a polyfill, see https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch for comment
httptools = { } ;
2018-06-08 14:39:33 -07:00
async function loopfetch ( req , ms , count , what ) {
let lasterr ;
while ( count -- ) {
try {
return await fetch ( req ) ;
} catch ( err ) {
lasterr = err ;
console . log ( "Delaying" , what , "by" , ms , "because" , err . message ) ;
await new Promise ( resolve => { setTimeout ( ( ) => { resolve ( ) ; } , ms ) } )
ms = ms * ( 1 + Math . random ( ) ) ; // Spread out delays incase all requesting same time
}
}
console . log ( "Looping" , what , "failed" ) ;
throw ( lasterr ) ;
}
2018-05-08 16:46:57 -07:00
httptools . p _httpfetch = async function ( httpurl , init , verbose ) { // Embrace and extend "fetch" to check result etc.
/ *
Fetch a url
url : optional ( depends on command )
resolves to : data as text or json depending on Content - Type header
throws : TransportError if fails to fetch
* /
try {
if ( verbose ) console . log ( "httpurl=%s init=%o" , httpurl , init ) ;
//console.log('CTX=',init["headers"].get('Content-Type'))
// Using window.fetch, because it doesn't appear to be in scope otherwise in the browser.
2018-06-08 14:39:33 -07:00
let req = new Request ( httpurl , init ) ;
//let response = await fetch(new Request(httpurl, init)).catch(err => console.exception(err));
2018-06-11 13:17:31 -07:00
let response = await loopfetch ( req , 500 , 12 , "fetching " + httpurl ) ;
2018-05-08 16:46:57 -07:00
// fetch throws (on Chrome, untested on Firefox or Node) TypeError: Failed to fetch)
// Note response.body gets a stream and response.blob gets a blob and response.arrayBuffer gets a buffer.
if ( response . ok ) {
let contenttype = response . headers . get ( 'Content-Type' ) ;
if ( contenttype === "application/json" ) {
return response . json ( ) ; // promise resolving to JSON
} else if ( contenttype . startsWith ( "text" ) ) { // Note in particular this is used for responses to store
return response . text ( ) ;
} else { // Typically application/octetStream when don't know what fetching
return new Buffer ( await response . arrayBuffer ( ) ) ; // Convert arrayBuffer to Buffer which is much more usable currently
}
}
// noinspection ExceptionCaughtLocallyJS
throw new errors . TransportError ( ` Transport Error ${ response . status } : ${ response . statusText } ` ) ;
} catch ( err ) {
// Error here is particularly unhelpful - if rejected during the COrs process it throws a TypeError
console . log ( "Note error from fetch might be misleading especially TypeError can be Cors issue:" , httpurl ) ;
if ( err instanceof errors . TransportError ) {
throw err ;
} else {
throw new errors . TransportError ( ` Transport error thrown by ${ httpurl } : ${ err . message } ` ) ;
}
}
}
httptools . p _GET = async function ( httpurl , opts = { } ) {
/ * L o c a t e a n d r e t u r n a b l o c k , b a s e d o n i t s u r l
Throws TransportError if fails
opts {
start , end , // Range of bytes wanted - inclusive i.e. 0,1023 is 1024 bytes
verbose }
resolves to : URL that can be used to fetch the resource , of form contenthash : / c o n t e n t h a s h / Q 1 2 3
* /
let headers = new Headers ( ) ;
if ( opts . start || opts . end ) headers . append ( "range" , ` bytes= ${ opts . start || 0 } - ${ opts . end || "" } ` ) ;
let init = { //https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch
method : 'GET' ,
headers : headers ,
mode : 'cors' ,
cache : 'default' ,
redirect : 'follow' , // Chrome defaults to manual
keepalive : true // Keep alive - mostly we'll be going back to same places a lot
} ;
return await httptools . p _httpfetch ( httpurl , init , opts . verbose ) ; // This s a real http url
}
httptools . p _POST = async function ( httpurl , type , data , verbose ) {
// Locate and return a block, based on its url
// Throws TransportError if fails
//let headers = new window.Headers();
//headers.set('content-type',type); Doesn't work, it ignores it
let init = {
//https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch
//https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_header_name for headers tat cant be set
method : 'POST' ,
headers : { } , //headers,
//body: new Buffer(data),
body : data ,
mode : 'cors' ,
cache : 'default' ,
redirect : 'follow' , // Chrome defaults to manual
keepalive : true // Keep alive - mostly we'll be going back to same places a lot
} ;
return await httptools . p _httpfetch ( httpurl , init , verbose ) ;
}
exports = module . exports = httptools ;