2018-04-06 19:28:04 +10:00
const Transport = require ( './Transport' ) ; // Base class for TransportXyz
const Transports = require ( './Transports' ) ; // Manage all Transports that are loaded
2018-05-08 16:46:57 -07:00
const httptools = require ( './httptools' ) ; // Expose some of the httptools so that IPFS can use it as a backup
2018-04-06 19:28:04 +10:00
const Url = require ( 'url' ) ;
defaulthttpoptions = {
2018-06-12 11:36:10 -07:00
urlbase : 'https://dweb.me:443'
2018-04-06 19:28:04 +10:00
} ;
servercommands = { // What the server wants to see to return each of these
2018-06-11 14:56:38 -07:00
rawfetch : "contenthash" , // was content/rawfetch which should still work.
2018-04-06 19:28:04 +10:00
rawstore : "contenturl/rawstore" ,
rawadd : "void/rawadd" ,
rawlist : "metadata/rawlist" ,
get : "get/table" ,
set : "set/table" ,
delete : "delete/table" ,
keys : "keys/table" ,
getall : "getall/table"
} ;
class TransportHTTP extends Transport {
constructor ( options , verbose ) {
super ( options , verbose ) ;
this . options = options ;
this . urlbase = options . http . urlbase ;
this . supportURLs = [ 'contenthash' , 'http' , 'https' ] ;
this . supportFunctions = [ 'fetch' , 'store' , 'add' , 'list' , 'reverse' , 'newlisturls' , "get" , "set" , "keys" , "getall" , "delete" , "newtable" , "newdatabase" ] ; //Does not support: listmonitor - reverse is disabled somewhere not sure if here or caller
this . supportFeatures = [ 'fetch.range' ]
this . name = "HTTP" ; // For console log etc
this . status = Transport . STATUS _LOADED ;
}
static setup0 ( options , verbose ) {
let combinedoptions = Transport . mergeoptions ( { http : defaulthttpoptions } , options ) ;
try {
let t = new TransportHTTP ( combinedoptions , verbose ) ;
Transports . addtransport ( t ) ;
return t ;
} catch ( err ) {
console . log ( "Exception thrown in TransportHTTP.p_setup" , err . message ) ;
throw err ;
}
}
2018-04-08 14:53:19 +10:00
async p _setup1 ( verbose , cb ) {
this . status = Transport . STATUS _STARTING ;
if ( cb ) cb ( this ) ;
await this . p _status ( verbose ) ;
if ( cb ) cb ( this ) ;
2018-04-06 19:28:04 +10:00
return this ;
}
2018-04-09 12:12:20 +10:00
async p _status ( verbose ) {
2018-04-06 19:28:04 +10:00
/ *
2018-04-09 12:12:20 +10:00
Return a numeric code for the status of a transport .
2018-04-06 19:28:04 +10:00
* /
try {
this . info = await this . p _info ( verbose ) ;
this . status = Transport . STATUS _CONNECTED ;
} catch ( err ) {
console . log ( this . name , ": Error in p_status.info" , err . message ) ;
this . status = Transport . STATUS _FAILED ;
}
2018-04-23 10:49:53 +10:00
return super . p _status ( verbose ) ;
2018-04-06 19:28:04 +10:00
}
_cmdurl ( command ) {
return ` ${ this . urlbase } / ${ command } `
}
_url ( url , command , parmstr ) {
if ( ! url ) throw new errors . CodingError ( ` ${ command } : requires url ` ) ;
if ( typeof url !== "string" ) { url = url . href }
url = url . replace ( 'contenthash:/contenthash' , this . _cmdurl ( command ) ) ; // Note leaves http: and https: urls unchanged
url = url . replace ( 'getall/table' , command ) ;
url = url + ( parmstr ? "?" + parmstr : "" ) ;
return url ;
}
async p _rawfetch ( url , opts = { } ) {
/ *
Fetch from underlying transport ,
Fetch is used both for contenthash requests and table as when passed to SmartDict . p _fetch may not know what we have
url : Of resource - which is turned into the HTTP url in p _httpfetch
opts : { start , end , verbose } see p _GET for documentation
throws : TransportError if fails
* /
//if (!(url && url.includes(':') ))
// throw new errors.CodingError("TransportHTTP.p_rawfetch bad url: "+url);
2018-06-13 09:39:44 -07:00
if ( url . href . includes ( 'contenthash//' ) )
console . error ( "XXX@91" , url )
2018-04-06 19:28:04 +10:00
if ( ( ( typeof url === "string" ) ? url : url . href ) . includes ( '/getall/table' ) ) {
2018-05-30 20:11:33 -07:00
throw new Error ( "Probably dont want to be calling p_rawfetch on a KeyValueTable, especially since dont know if its keyvaluetable or subclass" ) ; //TODO-NAMING
return { // I'm not sure what this return would have done - looks half finished to me?
2018-04-06 19:28:04 +10:00
table : "keyvaluetable" ,
}
} else {
2018-05-08 16:46:57 -07:00
return await httptools . p _GET ( this . _url ( url , servercommands . rawfetch ) , opts ) ;
2018-04-06 19:28:04 +10:00
}
}
2018-04-09 12:12:20 +10:00
p _rawlist ( url , { verbose = false } = { } ) {
2018-04-06 19:28:04 +10:00
// obj being loaded
// Locate and return a block, based on its url
if ( ! url ) throw new errors . CodingError ( "TransportHTTP.p_rawlist: requires url" ) ;
2018-05-08 16:46:57 -07:00
return httptools . p _GET ( this . _url ( url , servercommands . rawlist ) , { verbose } ) ;
2018-04-06 19:28:04 +10:00
}
rawreverse ( ) { throw new errors . ToBeImplementedError ( "Undefined function TransportHTTP.rawreverse" ) ; }
2018-04-09 12:12:20 +10:00
async p _rawstore ( data , { verbose = false } = { } ) {
2018-04-06 19:28:04 +10:00
/ *
Store data on http server ,
data : string
resolves to : { string } : url
throws : TransportError on failure in p _POST > p _httpfetch
* /
//PY: res = self._sendGetPost(True, "rawstore", headers={"Content-Type": "application/octet-stream"}, urlargs=[], data=data, verbose=verbose)
console . assert ( data , "TransportHttp.p_rawstore: requires data" ) ;
2018-05-08 16:46:57 -07:00
let res = await httptools . p _POST ( this . _cmdurl ( servercommands . rawstore ) , "application/octet-stream" , data , verbose ) ; // resolves to URL
2018-04-06 19:28:04 +10:00
let parsedurl = Url . parse ( res ) ;
let pathparts = parsedurl . pathname . split ( '/' ) ;
return ` contenthash:/contenthash/ ${ pathparts . slice ( - 1 ) } `
}
2018-05-30 20:11:33 -07:00
p _rawadd ( url , sig , { verbose = false } = { } ) {
2018-04-06 19:28:04 +10:00
//verbose=true;
if ( ! url || ! sig ) throw new errors . CodingError ( "TransportHTTP.p_rawadd: invalid parms" , url , sig ) ;
if ( verbose ) console . log ( "rawadd" , url , sig ) ;
let value = JSON . stringify ( sig . preflight ( Object . assign ( { } , sig ) ) ) + "\n" ;
2018-05-08 16:46:57 -07:00
return httptools . p _POST ( this . _url ( url , servercommands . rawadd ) , "application/json" , value , verbose ) ; // Returns immediately
2018-04-06 19:28:04 +10:00
}
2018-04-09 12:12:20 +10:00
p _newlisturls ( cl , { verbose = false } = { } ) {
2018-04-06 19:28:04 +10:00
let u = cl . _publicurls . map ( urlstr => Url . parse ( urlstr ) )
. find ( parsedurl =>
2018-06-12 11:36:10 -07:00
( ( parsedurl . protocol === "https:" && [ "gateway.dweb.me" , "dweb.me" ] . includes ( parsedurl . host )
2018-05-21 17:23:13 -07:00
&& ( parsedurl . pathname . includes ( '/content/rawfetch' ) || parsedurl . pathname . includes ( '/contenthash/' ) ) )
|| ( parsedurl . protocol === "contenthash:" ) && ( parsedurl . pathname . split ( '/' ) [ 1 ] === "contenthash" ) ) ) ;
2018-04-06 19:28:04 +10:00
if ( ! u ) {
u = ` contenthash:/contenthash/ ${ cl . keypair . verifyexportmultihashsha256 _58 ( ) } ` ; // Pretty random, but means same test will generate same list and server is expecting base58 of a hash
}
return [ u , u ] ;
}
// ============================== Key Value support
// Support for Key-Value pairs as per
// https://docs.google.com/document/d/1yfmLRqKPxKwB939wIy9sSaa7GKOzM5PrCZ4W1jRGW6M/edit#
2018-04-09 12:12:20 +10:00
async p _newdatabase ( pubkey , { verbose = false } = { } ) {
2018-04-06 19:28:04 +10:00
//if (pubkey instanceof Dweb.PublicPrivate)
if ( pubkey . hasOwnProperty ( "keypair" ) )
pubkey = pubkey . keypair . signingexport ( )
// By this point pubkey should be an export of a public key of form xyz:abc where xyz
// specifies the type of public key (NACL VERIFY being the only kind we expect currently)
let u = ` ${ this . urlbase } /getall/table/ ${ encodeURIComponent ( pubkey ) } ` ;
return { "publicurl" : u , "privateurl" : u } ;
}
2018-04-09 12:12:20 +10:00
async p _newtable ( pubkey , table , { verbose = false } = { } ) {
2018-04-06 19:28:04 +10:00
if ( ! pubkey ) throw new errors . CodingError ( "p_newtable currently requires a pubkey" ) ;
2018-04-09 12:12:20 +10:00
let database = await this . p _newdatabase ( pubkey , { verbose } ) ;
2018-04-06 19:28:04 +10:00
// If have use cases without a database, then call p_newdatabase first
return { privateurl : ` ${ database . privateurl } / ${ table } ` , publicurl : ` ${ database . publicurl } / ${ table } ` } // No action required to create it
}
//TODO-KEYVALUE needs signing with private key of list
2018-05-30 20:11:33 -07:00
async p _set ( url , keyvalues , value , { verbose = false } = { } ) { // url = yjs:/yjs/database/table/key
2018-04-06 19:28:04 +10:00
if ( ! url || ! keyvalues ) throw new errors . CodingError ( "TransportHTTP.p_set: invalid parms" , url , keyvalyes ) ;
if ( verbose ) console . log ( "p_set" , url , keyvalues , value ) ;
if ( typeof keyvalues === "string" ) {
let kv = JSON . stringify ( [ { key : keyvalues , value : value } ] ) ;
2018-05-08 16:46:57 -07:00
await httptools . p _POST ( this . _url ( url , servercommands . set ) , "application/json" , kv , verbose ) ; // Returns immediately
2018-04-06 19:28:04 +10:00
} else {
let kv = JSON . stringify ( Object . keys ( keyvalues ) . map ( ( k ) => ( { "key" : k , "value" : keyvalues [ k ] } ) ) ) ;
2018-05-08 16:46:57 -07:00
await httptools . p _POST ( this . _url ( url , servercommands . set ) , "application/json" , kv , verbose ) ; // Returns immediately
2018-04-06 19:28:04 +10:00
}
}
_keyparm ( key ) {
return ` key= ${ encodeURIComponent ( key ) } `
}
2018-04-09 12:12:20 +10:00
async p _get ( url , keys , { verbose = false } = { } ) {
2018-04-06 19:28:04 +10:00
if ( ! url && keys ) throw new errors . CodingError ( "TransportHTTP.p_get: requires url and at least one key" ) ;
let parmstr = Array . isArray ( keys ) ? keys . map ( k => this . _keyparm ( k ) ) . join ( '&' ) : this . _keyparm ( keys )
2018-05-08 16:46:57 -07:00
let res = await httptools . p _GET ( this . _url ( url , servercommands . get , parmstr ) , { verbose } ) ;
2018-04-06 19:28:04 +10:00
return Array . isArray ( keys ) ? res : res [ keys ]
}
2018-05-30 20:11:33 -07:00
async p _delete ( url , keys , { verbose = false } = { } ) {
2018-04-06 19:28:04 +10:00
if ( ! url && keys ) throw new errors . CodingError ( "TransportHTTP.p_get: requires url and at least one key" ) ;
let parmstr = keys . map ( k => this . _keyparm ( k ) ) . join ( '&' ) ;
2018-05-08 16:46:57 -07:00
await httptools . p _GET ( this . _url ( url , servercommands . delete , parmstr ) , { verbose } ) ;
2018-04-06 19:28:04 +10:00
}
2018-04-09 12:12:20 +10:00
async p _keys ( url , { verbose = false } = { } ) {
2018-04-06 19:28:04 +10:00
if ( ! url && keys ) throw new errors . CodingError ( "TransportHTTP.p_get: requires url and at least one key" ) ;
2018-05-08 16:46:57 -07:00
return await httptools . p _GET ( this . _url ( url , servercommands . keys ) , { verbose } ) ;
2018-04-06 19:28:04 +10:00
}
2018-04-09 12:12:20 +10:00
async p _getall ( url , { verbose = false } = { } ) {
2018-04-06 19:28:04 +10:00
if ( ! url && keys ) throw new errors . CodingError ( "TransportHTTP.p_get: requires url and at least one key" ) ;
2018-05-08 16:46:57 -07:00
return await httptools . p _GET ( this . _url ( url , servercommands . getall ) , { verbose } ) ;
2018-04-06 19:28:04 +10:00
}
/ * M a k e s u r e d o e s n t s h a d o w r e g u l a r p _ r a w f e t c h
async p _rawfetch ( url , verbose ) {
return {
table : "keyvaluetable" ,
2018-04-09 12:12:20 +10:00
_map : await this . p _getall ( url , { verbose } )
2018-04-06 19:28:04 +10:00
} ; // Data struc is ok as SmartDict.p_fetch will pass to KVT constructor
}
* /
2018-05-08 16:46:57 -07:00
p _info ( verbose ) { return httptools . p _GET ( ` ${ this . urlbase } /info ` , { verbose } ) ; }
2018-04-06 19:28:04 +10:00
static async p _test ( opts = { } , verbose = false ) {
if ( verbose ) { console . log ( "TransportHTTP.test" ) }
try {
let transport = await this . p _setup ( opts , verbose ) ;
if ( verbose ) console . log ( "HTTP connected" ) ;
let res = await transport . p _info ( verbose ) ;
if ( verbose ) console . log ( "TransportHTTP info=" , res ) ;
res = await transport . p _status ( verbose ) ;
console . assert ( res === Transport . STATUS _CONNECTED ) ;
await transport . p _test _kvt ( "NACL%20VERIFY" , verbose ) ;
} catch ( err ) {
console . log ( "Exception thrown in TransportHTTP.test:" , err . message ) ;
throw err ;
}
}
static async test ( ) {
return this ;
}
}
Transports . _transportclasses [ "HTTP" ] = TransportHTTP ;
exports = module . exports = TransportHTTP ;