2018-04-06 19:28:04 +10:00
const Url = require ( 'url' ) ;
const errors = require ( './Errors' ) ;
2018-04-09 12:12:20 +10:00
const utils = require ( './utils' ) ;
2018-04-06 19:28:04 +10:00
/ *
Handles multiple transports , API should be ( almost ) the same as for an individual transport )
* /
class Transports {
constructor ( options , verbose ) {
if ( verbose ) console . log ( "Transports(%o)" , options ) ;
}
static _connected ( ) {
/ *
Get an array of transports that are connected , i . e . currently usable
* /
return this . _transports . filter ( ( t ) => ( ! t . status ) ) ;
}
2018-04-13 17:26:44 +10:00
static async p _connectedNames ( ) {
2018-04-09 12:12:20 +10:00
/ *
2018-04-23 10:49:53 +10:00
resolves to : an array of the names of connected transports
2018-04-09 12:12:20 +10:00
* /
2018-04-06 19:28:04 +10:00
return this . _connected ( ) . map ( t => t . name ) ;
}
2018-04-13 17:26:44 +10:00
static async p _connectedNamesParm ( ) { // Doesnt strictly need to be async, but for consistency with Proxy it has to be.
return ( await this . p _connectedNames ( ) ) . map ( n => "transport=" + n ) . join ( '&' )
2018-04-06 19:28:04 +10:00
}
2018-04-23 10:49:53 +10:00
static async p _statuses ( ) { //TODO-API
2018-04-15 17:34:20 +10:00
/ *
2018-04-23 10:49:53 +10:00
resolves to : a dictionary of statuses of transports , e . g . { TransportHTTP : STATUS _CONNECTED }
2018-04-15 17:34:20 +10:00
* /
return this . _transports . map ( ( t ) => { return { "name" : t . name , "status" : t . status } } )
}
2018-04-06 19:28:04 +10:00
static validFor ( urls , func , options ) {
/ *
Finds an array or Transports that can support this URL .
Excludes any transports whose status != 0 as they aren ' t connected
urls : Array of urls
func : Function to check support for : fetch , store , add , list , listmonitor , reverse - see supportFunctions on each Transport class
2018-04-23 10:49:53 +10:00
options : For future use
2018-04-06 19:28:04 +10:00
returns : Array of pairs of url & transport instance [ [ u1 , t1 ] , [ u1 , t2 ] , [ u2 , t1 ] ]
* /
console . assert ( ( urls && urls [ 0 ] ) || [ "store" , "newlisturls" , "newdatabase" , "newtable" ] . includes ( func ) , "Transports.validFor failed - coding error - urls=" , urls , "func=" , func ) ; // FOr debugging old calling patterns with [ undefined ]
if ( ! ( urls && urls . length > 0 ) ) {
return this . _connected ( ) . filter ( ( t ) => ( t . supports ( undefined , func ) ) )
. map ( ( t ) => [ undefined , t ] ) ;
} else {
return [ ] . concat (
... urls . map ( ( url ) => typeof url === 'string' ? Url . parse ( url ) : url ) // parse URLs once
. map ( ( url ) =>
this . _connected ( ) . filter ( ( t ) => ( t . supports ( url , func ) ) ) // [ t1, t2 ]
. map ( ( t ) => [ url , t ] ) ) ) ; // [[ u, t1], [u, t2]]
}
}
2018-04-23 10:49:53 +10:00
static async p _urlsValidFor ( urls , func , options ) {
// Need a async version of this for serviceworker and TransportsProxy
return this . validFor ( urls , func , options ) . map ( ( ut ) => ut [ 0 ] ) ;
}
2018-04-06 19:28:04 +10:00
static http ( verbose ) {
// Find an http transport if it exists, so for example YJS can use it.
return Transports . _connected ( ) . find ( ( t ) => t . name === "HTTP" )
}
static ipfs ( verbose ) {
// Find an ipfs transport if it exists, so for example YJS can use it.
return Transports . _connected ( ) . find ( ( t ) => t . name === "IPFS" )
}
2018-04-16 08:56:13 +10:00
static webtorrent ( verbose ) {
// Find an ipfs transport if it exists, so for example ServiceWorker.p_respondWebTorrent can use it.
return Transports . _connected ( ) . find ( ( t ) => t . name === "WEBTORRENT" )
}
2018-04-06 19:28:04 +10:00
static async p _resolveNames ( urls ) {
/ * I f a n d o n l y i f T r a n s p o r t N A M E w a s l o a d e d ( i t m i g h t n o t b e a s i t d e p e n d s o n h i g h e r l e v e l c l a s s e s l i k e D o m a i n a n d S m a r t D i c t )
then resolve urls that might be names , returning a modified array .
* /
2018-04-09 12:12:20 +10:00
if ( this . namingcb ) {
2018-04-06 19:28:04 +10:00
return await this . namingcb ( urls ) ; // Array of resolved urls
} else {
return urls ;
}
}
2018-04-09 12:12:20 +10:00
static resolveNamesWith ( cb ) { //TODO-API
2018-04-06 19:28:04 +10:00
// Set a callback for p_resolveNames
this . namingcb = cb ;
}
2018-04-09 12:12:20 +10:00
static async _p _rawstore ( tt , data , { verbose } ) {
2018-04-06 19:28:04 +10:00
// Internal method to store at known transports
let errs = [ ] ;
let rr = await Promise . all ( tt . map ( async function ( t ) {
try {
2018-04-09 12:12:20 +10:00
return await t . p _rawstore ( data , { verbose } ) ; //url
2018-04-06 19:28:04 +10:00
} catch ( err ) {
console . log ( "Could not rawstore to" , t . name , err . message ) ;
errs . push ( err ) ;
return undefined ;
}
} ) ) ;
rr = rr . filter ( ( r ) => ! ! r ) ; // Trim any that had errors
if ( ! rr . length ) {
throw new errors . TransportError ( errs . map ( ( err ) => err . message ) . join ( ', ' ) ) ; // New error with concatenated messages
}
return rr ;
}
2018-04-09 12:12:20 +10:00
static async p _rawstore ( data , { verbose } ) {
2018-04-06 19:28:04 +10:00
/ *
data : Raw data to store - typically a string , but its passed on unmodified here
returns : Array of urls of where stored
throws : TransportError with message being concatenated messages of transports if NONE of them succeed .
* /
let tt = this . validFor ( undefined , "store" ) . map ( ( [ u , t ] ) => t ) ; // Valid connected transports that support "store"
if ( verbose ) console . log ( "Valid for transports:" , tt . map ( t => t . name ) ) ;
if ( ! tt . length ) {
throw new errors . TransportError ( 'Transports.p_rawstore: Cant find transport for store' ) ;
}
2018-04-09 12:12:20 +10:00
return this . _p _rawstore ( tt , data , { verbose } ) ;
2018-04-06 19:28:04 +10:00
}
2018-04-09 12:12:20 +10:00
static async p _rawlist ( urls , { verbose = false } = { } ) {
2018-04-06 19:28:04 +10:00
urls = await this . p _resolveNames ( urls ) ; // If naming is loaded then convert to a name
let tt = this . validFor ( urls , "list" ) ; // Valid connected transports that support "store"
if ( ! tt . length ) {
throw new errors . TransportError ( 'Transports.p_rawlist: Cant find transport for urls:' + urls . join ( ',' ) ) ;
}
let errs = [ ] ;
let ttlines = await Promise . all ( tt . map ( async function ( [ url , t ] ) {
try {
2018-04-09 12:12:20 +10:00
return await t . p _rawlist ( url , { verbose } ) ; // [sig]
2018-04-06 19:28:04 +10:00
} catch ( err ) {
console . log ( "Could not rawlist " , url , "from" , t . name , err . message ) ;
errs . push ( err ) ;
return [ ] ;
}
} ) ) ; // [[sig,sig],[sig,sig]]
if ( errs . length >= tt . length ) {
// All Transports failed (maybe only 1)
throw new errors . TransportError ( errs . map ( ( err ) => err . message ) . join ( ', ' ) ) ; // New error with concatenated messages
}
let uniques = { } ; // Used to filter duplicates
return [ ] . concat ( ... ttlines )
. filter ( ( x ) => ( ! uniques [ x . signature ] && ( uniques [ x . signature ] = true ) ) ) ;
}
static async p _rawfetch ( urls , opts ) {
/ *
Fetch the data for a url , transports act on the data , typically storing it .
urls : array of urls to retrieve ( any are valid )
opts {
verbose ,
start , integer - first byte wanted
end integer - last byte wanted ( note this is inclusive start = 0 , end = 1023 is 1024 bytes
timeoutMS integer - max time to wait on transports ( IPFS ) that support it
}
returns : string - arbitrary bytes retrieved .
throws : TransportError with concatenated error messages if none succeed .
* /
let verbose = opts . verbose ;
urls = await this . p _resolveNames ( urls ) ; // If naming is loaded then convert to a name
let tt = this . validFor ( urls , "fetch" ) ; //[ [Url,t],[Url,t]]
if ( ! tt . length ) {
throw new errors . TransportError ( "Transports.p_fetch cant find any transport for urls: " + urls ) ;
}
//With multiple transports, it should return when the first one returns something.
let errs = [ ] ;
let failedtransports = [ ] ; // Will accumulate any transports fail on before the success
for ( const [ url , t ] of tt ) {
try {
let data = await t . p _rawfetch ( url , opts ) ; // throws errors if fails or timesout
2018-04-09 12:12:20 +10:00
//TODO-MULTI-GATEWAY working here - it doesnt quite work yet as the "Add" on browser gets different url than on server
2018-04-06 19:28:04 +10:00
if ( opts . relay && failedtransports . length ) {
console . log ( ` Relaying ${ data . length } bytes from ${ typeof url === "string" ? url : url . href } to ${ failedtransports . map ( t => t . name ) } ` ) ;
2018-04-09 12:12:20 +10:00
this . _p _rawstore ( failedtransports , data , { verbose } )
2018-04-06 19:28:04 +10:00
. then ( uu => console . log ( ` Relayed to ${ uu } ` ) ) ; // Happening async, not waiting and dont care if fails
}
//END TODO-MULTI-GATEWAY
return data ;
} catch ( err ) {
failedtransports . push ( t ) ;
errs . push ( err ) ;
console . log ( "Could not retrieve " , url . href , "from" , t . name , err . message ) ;
// Don't throw anything here, loop round for next, only throw if drop out bottom
//TODO-MULTI-GATEWAY potentially copy from success to failed URLs.
}
}
throw new errors . TransportError ( errs . map ( ( err ) => err . message ) . join ( ', ' ) ) ; //Throw err with combined messages if none succeed
}
2018-04-09 12:12:20 +10:00
static async p _rawadd ( urls , sig , { verbose = false } = { } ) {
2018-04-06 19:28:04 +10:00
/ *
urls : of lists to add to
sig : Sig to add
returns : undefined
throws : TransportError with message being concatenated messages of transports if NONE of them succeed .
* /
//TODO-MULTI-GATEWAY might be smarter about not waiting but Promise.race is inappropriate as returns after a failure as well.
urls = await this . p _resolveNames ( urls ) ; // If naming is loaded then convert to a name
let tt = this . validFor ( urls , "add" ) ; // Valid connected transports that support "store"
if ( ! tt . length ) {
throw new errors . TransportError ( 'Transports.p_rawstore: Cant find transport for urls:' + urls . join ( ',' ) ) ;
}
let errs = [ ] ;
await Promise . all ( tt . map ( async function ( [ u , t ] ) {
try {
2018-04-09 12:12:20 +10:00
await t . p _rawadd ( u , sig , { verbose } ) ; //undefined
2018-04-06 19:28:04 +10:00
return undefined ;
} catch ( err ) {
console . log ( "Could not rawlist " , u , "from" , t . name , err . message ) ;
errs . push ( err ) ;
return undefined ;
}
} ) ) ;
if ( errs . length >= tt . length ) {
// All Transports failed (maybe only 1)
throw new errors . TransportError ( errs . map ( ( err ) => err . message ) . join ( ', ' ) ) ; // New error with concatenated messages
}
return undefined ;
}
2018-04-23 10:49:53 +10:00
static listmonitor ( urls , cb , opts = { } ) {
2018-04-06 19:28:04 +10:00
/ *
Add a listmonitor for each transport - note this means if multiple transports support it , then will get duplicate events back if everyone else is notifying all of them .
* /
// Note cant do p_resolveNames since sync but should know real urls of resource by here.
this . validFor ( urls , "listmonitor" )
2018-04-23 10:49:53 +10:00
. map ( ( [ u , t ] ) => t . listmonitor ( u , cb , opts ) ) ;
2018-04-06 19:28:04 +10:00
}
2018-04-09 12:12:20 +10:00
static async p _newlisturls ( cl , { verbose = false } = { } ) {
2018-04-06 19:28:04 +10:00
// Create a new list in any transport layer that supports lists.
// cl is a CommonList or subclass and can be used by the Transport to get info for choosing the list URL (normally it won't use it)
// Note that normally the CL will not have been stored yet, so you can't use its urls.
let uuu = await Promise . all ( this . validFor ( undefined , "newlisturls" )
2018-04-09 12:12:20 +10:00
. map ( ( [ u , t ] ) => t . p _newlisturls ( cl , { verbose } ) ) ) ; // [ [ priv, pub] [ priv, pub] [priv pub] ]
2018-04-06 19:28:04 +10:00
return [ uuu . map ( uu => uu [ 0 ] ) , uuu . map ( uu => uu [ 1 ] ) ] ; // [[ priv priv priv ] [ pub pub pub ] ]
}
// Stream handling ===========================================
2018-04-16 08:56:13 +10:00
static async p _f _createReadStream ( urls , { verbose = false , wanturl = false } = { } ) { // Note options is options for selecting a stream, not the start/end in a createReadStream call
2018-04-06 19:28:04 +10:00
/ *
urls : Urls of the stream
returns : f ( opts ) => stream returning bytes from opts . start || start of file to opts . end - 1 || end of file
* /
2018-04-16 08:56:13 +10:00
let tt = this . validFor ( urls , "createReadStream" , { } ) ; //[ [Url,t],[Url,t]] // Can pass options TODO-STREAM support options in validFor
2018-04-06 19:28:04 +10:00
if ( ! tt . length ) {
throw new errors . TransportError ( "Transports.p_createReadStream cant find any transport for urls: " + urls ) ;
}
//With multiple transports, it should return when the first one returns something.
let errs = [ ] ;
for ( const [ url , t ] of tt ) {
try {
2018-04-16 08:56:13 +10:00
return await t . p _f _createReadStream ( url , { verbose , wanturl } ) ;
2018-04-06 19:28:04 +10:00
} catch ( err ) {
errs . push ( err ) ;
console . log ( "Could not retrieve " , url . href , "from" , t . name , err . message ) ;
// Don't throw anything here, loop round for next, only throw if drop out bottom
//TODO-MULTI-GATEWAY potentially copy from success to failed URLs.
}
}
throw new errors . TransportError ( errs . map ( ( err ) => err . message ) . join ( ', ' ) ) ; //Throw err with combined messages if none succeed
}
// KeyValue support ===========================================
2018-04-09 12:12:20 +10:00
static async p _get ( urls , keys , { verbose = false } = { } ) {
2018-04-06 19:28:04 +10:00
/ *
Fetch the values for a url and one or more keys , transports act on the data , typically storing it .
urls : array of urls to retrieve ( any are valid )
keys : array of keys wanted or single key
returns : string - arbitrary bytes retrieved or dict of key : value
throws : TransportError with concatenated error messages if none succeed .
* /
let tt = this . validFor ( urls , "get" ) ; //[ [Url,t],[Url,t]]
if ( ! tt . length ) {
throw new errors . TransportError ( "Transports.p_get cant find any transport for urls: " + urls ) ;
}
//With multiple transports, it should return when the first one returns something.
let errs = [ ] ;
for ( const [ url , t ] of tt ) {
try {
2018-04-09 12:12:20 +10:00
return await t . p _get ( url , keys , { verbose } ) ; //TODO-MULTI-GATEWAY potentially copy from success to failed URLs.
2018-04-06 19:28:04 +10:00
} catch ( err ) {
errs . push ( err ) ;
console . log ( "Could not retrieve " , url . href , "from" , t . name , err . message ) ;
// Don't throw anything here, loop round for next, only throw if drop out bottom
}
}
throw new errors . TransportError ( errs . map ( ( err ) => err . message ) . join ( ', ' ) ) ; //Throw err with combined messages if none succeed
}
2018-04-09 12:12:20 +10:00
static async p _set ( urls , keyvalues , value , { verbose = false } = { } ) {
2018-04-06 19:28:04 +10:00
/ * S e t a s e r i e s o f k e y / v a l u e s o r a s i n g l e v a l u e
keyvalues : Either dict or a string
value : if kv is a string , this is the value to set
throws : TransportError with message being concatenated messages of transports if NONE of them succeed .
* /
urls = await this . p _resolveNames ( urls ) ; // If naming is loaded then convert to a name
let tt = this . validFor ( urls , "set" ) ; //[ [Url,t],[Url,t]]
if ( ! tt . length ) {
throw new errors . TransportError ( "Transports.p_set cant find any transport for urls: " + urls ) ;
}
let errs = [ ] ;
let success = false ;
await Promise . all ( tt . map ( async function ( [ url , t ] ) {
try {
2018-04-09 12:12:20 +10:00
await t . p _set ( url , keyvalues , value , { verbose } ) ;
2018-04-06 19:28:04 +10:00
success = true ; // Any one success will return true
} catch ( err ) {
console . log ( "Could not rawstore to" , t . name , err . message ) ;
errs . push ( err ) ;
}
} ) ) ;
if ( ! success ) {
throw new errors . TransportError ( errs . map ( ( err ) => err . message ) . join ( ', ' ) ) ; // New error with concatenated messages
}
}
2018-04-09 12:12:20 +10:00
static async p _delete ( urls , keys , { verbose = false } = { } ) { //TODO-KEYVALUE-API
2018-04-06 19:28:04 +10:00
/ * D e l e t e a k e y o r a l i s t o f k e y s
kv : Either dict or a string
value : if kv is a string , this is the value to set
throws : TransportError with message being concatenated messages of transports if NONE of them succeed .
* /
urls = await this . p _resolveNames ( urls ) ; // If naming is loaded then convert to a name
let tt = this . validFor ( urls , "set" ) ; //[ [Url,t],[Url,t]]
if ( ! tt . length ) {
throw new errors . TransportError ( "Transports.p_set cant find any transport for urls: " + urls ) ;
}
let errs = [ ] ;
let success = false ;
await Promise . all ( tt . map ( async function ( [ url , t ] ) {
try {
2018-04-09 12:12:20 +10:00
await t . p _delete ( url , keys , { verbose } ) ;
2018-04-06 19:28:04 +10:00
success = true ; // Any one success will return true
} catch ( err ) {
console . log ( "Could not rawstore to" , t . name , err . message ) ;
errs . push ( err ) ;
}
} ) ) ;
if ( ! success ) {
throw new errors . TransportError ( errs . map ( ( err ) => err . message ) . join ( ', ' ) ) ; // New error with concatenated messages
}
}
2018-04-09 12:12:20 +10:00
static async p _keys ( urls , { verbose = false } = { } ) {
2018-04-06 19:28:04 +10:00
/ *
Fetch the values for a url and one or more keys , transports act on the data , typically storing it .
urls : array of urls to retrieve ( any are valid )
keys : array of keys wanted
returns : string - arbitrary bytes retrieved or dict of key : value
throws : TransportError with concatenated error messages if none succeed .
* /
urls = await this . p _resolveNames ( urls ) ; // If naming is loaded then convert to a name
let tt = this . validFor ( urls , "keys" ) ; //[ [Url,t],[Url,t]]
if ( ! tt . length ) {
throw new errors . TransportError ( "Transports.p_keys cant find any transport for urls: " + urls ) ;
}
//With multiple transports, it should return when the first one returns something.
let errs = [ ] ;
for ( const [ url , t ] of tt ) {
try {
2018-04-09 12:12:20 +10:00
return await t . p _keys ( url , { verbose } ) ; //TODO-MULTI-GATEWAY potentially copy from success to failed URLs.
2018-04-06 19:28:04 +10:00
} catch ( err ) {
errs . push ( err ) ;
console . log ( "Could not retrieve keys for" , url . href , "from" , t . name , err . message ) ;
// Don't throw anything here, loop round for next, only throw if drop out bottom
}
}
throw new errors . TransportError ( errs . map ( ( err ) => err . message ) . join ( ', ' ) ) ; //Throw err with combined messages if none succeed
}
2018-04-09 12:12:20 +10:00
static async p _getall ( urls , { verbose = false } = { } ) {
2018-04-06 19:28:04 +10:00
/ *
Fetch the values for a url and one or more keys , transports act on the data , typically storing it .
urls : array of urls to retrieve ( any are valid )
keys : array of keys wanted
returns : array of strings returned for the keys . //TODO consider issues around return a data type rather than array of strings
throws : TransportError with concatenated error messages if none succeed .
* /
urls = await this . p _resolveNames ( urls ) ; // If naming is loaded then convert to a name
let tt = this . validFor ( urls , "getall" ) ; //[ [Url,t],[Url,t]]
if ( ! tt . length ) {
throw new errors . TransportError ( "Transports.p_getall cant find any transport for urls: " + urls ) ;
}
//With multiple transports, it should return when the first one returns something.
let errs = [ ] ;
for ( const [ url , t ] of tt ) {
try {
2018-04-09 12:12:20 +10:00
return await t . p _getall ( url , { verbose } ) ; //TODO-MULTI-GATEWAY potentially copy from success to failed URLs.
2018-04-06 19:28:04 +10:00
} catch ( err ) {
errs . push ( err ) ;
console . log ( "Could not retrieve all keys for" , url . href , "from" , t . name , err . message ) ;
// Don't throw anything here, loop round for next, only throw if drop out bottom
}
}
throw new errors . TransportError ( errs . map ( ( err ) => err . message ) . join ( ', ' ) ) ; //Throw err with combined messages if none succeed
}
2018-04-09 12:12:20 +10:00
static async p _newdatabase ( pubkey , { verbose = false } = { } ) {
2018-04-06 19:28:04 +10:00
/ *
Create a new database in any transport layer that supports databases ( key value pairs ) .
pubkey : CommonList , KeyPair , or exported public key
resolves to : [ privateurl , publicurl ]
* /
let uuu = await Promise . all ( this . validFor ( undefined , "newdatabase" )
2018-04-09 12:12:20 +10:00
. map ( ( [ u , t ] ) => t . p _newdatabase ( pubkey , { verbose } ) ) ) ; // [ { privateurl, publicurl} { privateurl, publicurl} { privateurl, publicurl} ]
2018-04-06 19:28:04 +10:00
return { privateurls : uuu . map ( uu => uu . privateurl ) , publicurls : uuu . map ( uu => uu . publicurl ) } ; // { privateurls: [], publicurls: [] }
}
2018-04-09 12:12:20 +10:00
static async p _newtable ( pubkey , table , { verbose = false } = { } ) {
2018-04-06 19:28:04 +10:00
/ *
Create a new table in any transport layer that supports the function ( key value pairs ) .
pubkey : CommonList , KeyPair , or exported public key
resolves to : [ privateurl , publicurl ]
* /
let uuu = await Promise . all ( this . validFor ( undefined , "newtable" )
2018-04-09 12:12:20 +10:00
. map ( ( [ u , t ] ) => t . p _newtable ( pubkey , table , { verbose } ) ) ) ; // [ [ priv, pub] [ priv, pub] [priv pub] ]
2018-04-06 19:28:04 +10:00
return { privateurls : uuu . map ( uu => uu . privateurl ) , publicurls : uuu . map ( uu => uu . publicurl ) } ; // {privateurls: [ priv priv priv ], publicurls: [ pub pub pub ] }
}
static async p _connection ( urls , verbose ) {
/ *
Do any asynchronous connection opening work prior to potentially synchronous methods ( like monitor )
* /
urls = await this . p _resolveNames ( urls ) ; // If naming is loaded then convert to a name
await Promise . all (
this . validFor ( urls , "connection" )
. map ( ( [ u , t ] ) => t . p _connection ( u , verbose ) ) ) ;
}
static monitor ( urls , cb , verbose ) {
/ *
Add a listmonitor for each transport - note this means if multiple transports support it , then will get duplicate events back if everyone else is notifying all of them .
Stack : KVT ( ) | KVT . p _new => KVT . monitor => ( a : Transports . monitor => YJS . monitor ) ( b : dispatchEvent )
* /
//Cant' its async. urls = await this.p_resolveNames(urls); // If naming is loaded then convert to a name
this . validFor ( urls , "monitor" )
. map ( ( [ u , t ] ) => t . monitor ( u , cb , verbose ) ) ;
}
2018-04-09 12:12:20 +10:00
// Setup and connection
2018-04-06 19:28:04 +10:00
static addtransport ( t ) {
/ *
Add a transport to _transports ,
* /
Transports . _transports . push ( t ) ;
}
// Setup Transports - setup0 is called once, and should return quickly, p_setup1 and p_setup2 are asynchronous and p_setup2 relies on p_setup1 having resolved.
2018-04-08 14:53:19 +10:00
static setup0 ( tabbrevs , options , verbose , cb ) {
2018-04-06 19:28:04 +10:00
/ *
Setup Transports for a range of classes
2018-04-08 14:53:19 +10:00
tabbrevs is abbreviation HTTP , IPFS , LOCAL or list of them e . g . "HTTP,IPFS"
2018-04-06 19:28:04 +10:00
Handles "LOCAL" specially , turning into a HTTP to a local server ( for debugging )
returns array of transport instances
* /
// "IPFS" or "IPFS,LOCAL,HTTP"
let localoptions = { http : { urlbase : "http://localhost:4244" } } ;
2018-04-08 14:53:19 +10:00
return tabbrevs . map ( ( tabbrev ) => {
2018-04-06 19:28:04 +10:00
let transportclass ;
if ( tabbrev === "LOCAL" ) {
transportclass = this . _transportclasses [ "HTTP" ] ;
} else {
transportclass = this . _transportclasses [ tabbrev ] ;
}
if ( ! transportclass ) {
let tt = Object . keys ( this . _transportclasses ) ;
console . error ( ` Requested ${ tabbrev } but ${ tt . length ? tt : "No" } transports have been loaded ` ) ;
return undefined ;
} else {
return transportclass . setup0 ( tabbrev === "LOCAL" ? localoptions : options , verbose ) ;
}
} ) . filter ( f => ! ! f ) ; // Trim out any undefined
}
2018-04-08 14:53:19 +10:00
static async p _setup1 ( verbose , cb ) {
2018-04-06 19:28:04 +10:00
/* Second stage of setup, connect if possible */
// Does all setup1a before setup1b since 1b can rely on ones with 1a, e.g. YJS relies on IPFS
2018-04-08 14:53:19 +10:00
await Promise . all ( this . _transports . map ( ( t ) => t . p _setup1 ( verbose , cb ) ) )
2018-04-06 19:28:04 +10:00
}
2018-04-08 14:53:19 +10:00
static async p _setup2 ( verbose , cb ) {
2018-04-06 19:28:04 +10:00
/* Second stage of setup, connect if possible */
// Does all setup1a before setup1b since 1b can rely on ones with 1a, e.g. YJS relies on IPFS
2018-04-08 14:53:19 +10:00
await Promise . all ( this . _transports . map ( ( t ) => t . p _setup2 ( verbose , cb ) ) )
2018-04-06 19:28:04 +10:00
}
2018-04-09 12:12:20 +10:00
static async refreshstatus ( t ) {
2018-04-15 17:34:20 +10:00
//Note "this' undefined as called as callback
2018-04-09 12:12:20 +10:00
let statusclasses = [ "transportstatus0" , "transportstatus1" , "transportstatus2" , "transportstatus3" , "transportstatus4" ] ;
let el = t . statuselement ;
if ( el ) {
el . classList . remove ( ... statusclasses ) ;
el . classList . add ( statusclasses [ t . status ] ) ;
}
2018-04-15 17:34:20 +10:00
if ( Transports . statuscb ) {
Transports . statuscb ( t ) ;
}
2018-04-09 12:12:20 +10:00
}
static async p _connect ( options , verbose ) {
/ *
This is a standardish starting process , feel free to subclass or replace !
It will connect to a set of standard transports and is intended to work inside a browser .
2018-04-13 17:26:44 +10:00
options = { defaulttransports : [ "IPFS" ] , statuselement : el , http : { } , ipfs : { } }
2018-04-09 12:12:20 +10:00
* /
if ( verbose ) console . group ( "p_connect ---" ) ;
try {
options = options || { } ;
let setupoptions = { } ;
let tabbrevs = options . transports ; // Array of transport abbreviations
2018-04-13 17:26:44 +10:00
if ( ! ( tabbrevs && tabbrevs . length ) ) { tabbrevs = options . defaulttransports || [ ] }
if ( ! tabbrevs . length ) { tabbrevs = [ "HTTP" , "YJS" , "IPFS" , "WEBTORRENT" ] ; }
2018-04-09 12:12:20 +10:00
tabbrevs = tabbrevs . map ( n => n . toUpperCase ( ) ) ;
let transports = this . setup0 ( tabbrevs , options , verbose ) ;
2018-04-15 17:34:20 +10:00
if ( options . statuscb ) {
this . statuscb = options . statuscb ;
}
2018-04-09 12:12:20 +10:00
if ( ! ! options . statuselement ) {
2018-04-10 09:56:48 +10:00
while ( statuselement . lastChild ) { statuselement . removeChild ( statuselement . lastChild ) ; } // Remove any exist status
2018-04-09 12:12:20 +10:00
statuselement . appendChild (
utils . createElement ( "UL" , { } , transports . map ( t => {
let el = utils . createElement ( "LI" ,
2018-04-15 17:34:20 +10:00
{ onclick : "this.source.togglePaused(DwebTransports.refreshstatus);" , source : t , name : t . name } , //TODO-SW figure out how t osend this back
2018-04-09 12:12:20 +10:00
t . name ) ;
t . statuselement = el ; // Save status element on transport
return el ;
}
) ) ) ;
}
await this . p _setup1 ( verbose , this . refreshstatus ) ;
await this . p _setup2 ( verbose , this . refreshstatus ) ;
} catch ( err ) {
console . error ( "ERROR in p_connect:" , err . message ) ;
throw ( err ) ;
}
if ( verbose ) console . groupEnd ( "p_connect ---" ) ;
}
2018-04-13 17:26:44 +10:00
static async p _urlsFrom ( url ) { //TODO backport to main repo - copy from htmlutils to utils
2018-04-09 12:12:20 +10:00
/ * U t i l i t y t o c o n v e r t t o u r l s f o r m w a n t e d f o r T r a n s p o r t s f u n c t i o n s , e . g . f r o m u s e r i n p u t
url : Array of urls , or string representing url or representing array of urls
return : Array of strings representing url
* /
if ( typeof ( url ) === "string" ) {
if ( url [ 0 ] === '[' )
url = JSON . parse ( url ) ;
else if ( url . includes ( ',' ) )
url = url . split ( ',' ) ;
else
url = [ url ] ;
}
if ( ! Array . isArray ( url ) ) throw new Error ( ` Unparsable url: ${ url } ` ) ;
return url ;
}
2018-04-23 10:49:53 +10:00
static async p _httpfetchurl ( urls ) {
2018-04-13 17:26:44 +10:00
/ *
Utility to take a array of Transport urls , convert back to a single url that can be used for a fetch , typically
this is done when cant handle a stream , so want to give the url to the < VIDEO > tag .
* /
return Transports . http ( ) . _url ( urls . find ( u => ( u . startsWith ( "contenthash" ) || u . startsWith ( "http" ) ) ) , "content/rawfetch" ) ;
}
2018-04-06 19:28:04 +10:00
static async test ( verbose ) {
if ( verbose ) { console . log ( "Transports.test" ) }
try {
/ * C o u l d c o n v e r t t h i s - c o p i e d f o m Y J S t o d o a t e s t a t t h e " T r a n s p o r t s " l e v e l
let testurl = "yjs:/yjs/THISATEST" ; // Just a predictable number can work with
let res = await transport . p _rawlist ( testurl , verbose ) ;
let listlen = res . length ; // Holds length of list run intermediate
if ( verbose ) console . log ( "rawlist returned " , ... utils . consolearr ( res ) ) ;
transport . listmonitor ( testurl , ( obj ) => console . log ( "Monitored" , obj ) , verbose ) ;
let sig = new Dweb . Signature ( { urls : [ "123" ] , date : new Date ( Date . now ( ) ) , signature : "Joe Smith" , signedby : [ testurl ] } , verbose ) ;
2018-04-09 12:12:20 +10:00
await transport . p _rawadd ( testurl , sig , { verbose } ) ;
2018-04-06 19:28:04 +10:00
if ( verbose ) console . log ( "TransportIPFS.p_rawadd returned " ) ;
res = await transport . p _rawlist ( testurl , verbose ) ;
if ( verbose ) console . log ( "rawlist returned " , ... utils . consolearr ( res ) ) ; // Note not showing return
await delay ( 500 ) ;
res = await transport . p _rawlist ( testurl , verbose ) ;
console . assert ( res . length === listlen + 1 , "Should have added one item" ) ;
* /
//console.log("TransportYJS test complete");
/ * T O D O - K E Y V A L U E r e e n a b l e t h e s e t e s t s , s b u t c a t c h h t t p e x a m p l e s
2018-04-09 12:12:20 +10:00
let db = await this . p _newdatabase ( "TESTNOTREALLYAKEY" , { verbose } ) ; // { privateurls, publicurls }
2018-04-06 19:28:04 +10:00
console . assert ( db . privateurls [ 0 ] === "yjs:/yjs/TESTNOTREALLYAKEY" ) ;
2018-04-09 12:12:20 +10:00
let table = await this . p _newtable ( "TESTNOTREALLYAKEY" , "TESTTABLE" , { verbose } ) ; // { privateurls, publicurls }
2018-04-06 19:28:04 +10:00
let mapurls = table . publicurls ;
console . assert ( mapurls [ 0 ] === "yjs:/yjs/TESTNOTREALLYAKEY/TESTTABLE" ) ;
2018-04-09 12:12:20 +10:00
await this . p _set ( mapurls , "testkey" , "testvalue" , { verbose } ) ;
let res = await this . p _get ( mapurls , "testkey" , { verbose } ) ;
2018-04-06 19:28:04 +10:00
console . assert ( res === "testvalue" ) ;
2018-04-09 12:12:20 +10:00
await this . p _set ( mapurls , "testkey2" , { foo : "bar" } , { verbose } ) ;
res = await this . p _get ( mapurls , "testkey2" , { verbose } ) ;
2018-04-06 19:28:04 +10:00
console . assert ( res . foo === "bar" ) ;
2018-04-09 12:12:20 +10:00
await this . p _set ( mapurls , "testkey3" , [ 1 , 2 , 3 ] , { verbose } ) ;
res = await this . p _get ( mapurls , "testkey3" , { verbose } ) ;
2018-04-06 19:28:04 +10:00
console . assert ( res [ 1 ] === 2 ) ;
res = await this . p _keys ( mapurls ) ;
console . assert ( res . length === 3 && res . includes ( "testkey3" ) ) ;
2018-04-09 12:12:20 +10:00
res = await this . p _getall ( mapurls , { verbose } ) ;
2018-04-06 19:28:04 +10:00
console . assert ( res . testkey2 . foo === "bar" ) ;
* /
} catch ( err ) {
console . log ( "Exception thrown in Transports.test:" , err . message ) ;
throw err ;
}
}
}
Transports . _transports = [ ] ; // Array of transport instances connected
2018-04-09 12:12:20 +10:00
Transports . namingcb = undefined ; // Will be defined by the naming component (turns URLs for names into URLs for transport)
2018-04-06 19:28:04 +10:00
Transports . _transportclasses = { } ; // Pointers to classes whose code is loaded.
exports = module . exports = Transports ;