2018-04-06 19:28:04 +10:00
const Url = require ( 'url' ) ;
const stream = require ( 'readable-stream' ) ;
const errors = require ( './Errors' ) ; // Standard Dweb Errors
function delay ( ms , val ) { return new Promise ( resolve => { setTimeout ( ( ) => { resolve ( val ) ; } , ms ) } ) }
class Transport {
constructor ( options , verbose ) {
/ *
Doesnt do anything , its all done by SuperClasses ,
Superclass should merge with default options , call super
2018-04-08 14:53:19 +10:00
Fields :
2018-04-15 17:34:20 +10:00
statuselement : If set is an HTML Element that should be adjusted to indicate status ( this is managed by Transports , just stored on Transport )
statuscb : Callback when status changes
2018-04-08 14:53:19 +10:00
name : Short name of element e . g . HTTP IPFS WEBTORRENT
2018-04-06 19:28:04 +10:00
* /
}
2018-04-23 10:49:53 +10:00
static setup0 ( options , verbose ) {
2018-04-06 19:28:04 +10:00
/ *
First part of setup , create obj , add to Transports but dont attempt to connect , typically called instead of p _setup if want to parallelize connections .
* /
throw new errors . IntentionallyUnimplementedError ( "Intentionally undefined function Transport.setup0 should have been subclassed" ) ;
}
2018-04-23 10:49:53 +10:00
p _setup1 ( options , verbose ) {
/ *
Setup the resource and open any P2P connections etc required to be done just once . Asynchronous and should leave status = STATUS _STARTING until it resolves , or STATUS _FAILED if fails .
cb ( t ) => void If set , will be called back as status changes ( so could be multiple times )
Resolves to the Transport instance
* /
return this ;
}
p _setup2 ( options , verbose ) {
/ *
Works like p _setup1 but runs after p _setup1 has completed for all transports . This allows for example YJS to wait for IPFS to be connected in TransportIPFS . setup1 ( ) and then connect itself using the IPFS object .
2018-04-06 19:28:04 +10:00
2018-04-23 10:49:53 +10:00
cb ( t ) => void If set , will be called back as status changes ( so could be multiple times )
Resolves to the Transport instance
* /
return this ;
}
2018-04-09 12:12:20 +10:00
static async p _setup ( options , verbose , cb ) {
2018-04-23 10:49:53 +10:00
/ *
A deprecated utility to simply setup0 then p _setup1 then p _setup2 to allow a transport to be started in one step , normally Transports . p _setup should be called instead .
* /
let t = await this . setup0 ( options , verbose ) // Sync version that doesnt connect
. p _setup1 ( verbose , cb ) ; // And connect
return t . p _setup2 ( verbose , cb ) ; // And connect
2018-04-06 19:28:04 +10:00
}
2018-05-05 21:10:15 -07:00
togglePaused ( cb ) { //TODO-SW move to Transports > TransportsProxy > UI
2018-04-23 10:49:53 +10:00
/ *
Switch the state of the transport between STATUS _CONNECTED and STATUS _PAUSED ,
in the paused state it will not be used for transport but , in some cases , will still do background tasks like serving files .
cb ( transport ) => void a callback called after this is run , may be used for example to change the UI
* /
2018-04-06 19:28:04 +10:00
switch ( this . status ) {
case Transport . STATUS _CONNECTED :
this . status = Transport . STATUS _PAUSED ;
break ;
case Transport . STATUS _PAUSED :
this . status = Transport . STATUS _CONNECTED ; // Superclass might change to STATUS_STARTING if needs to stop/restart
break ;
}
2018-04-08 14:53:19 +10:00
if ( cb ) cb ( this ) ;
2018-04-06 19:28:04 +10:00
}
2018-04-23 10:49:53 +10:00
async p _status ( verbose ) {
/ *
Check the status of the underlying transport . This may update the "status" field from the underlying transport .
returns : a numeric code for the status of a transport .
* /
return this . status ;
}
2018-04-06 19:28:04 +10:00
supports ( url , func ) {
/ *
Determine if this transport supports a certain set of URLs and a func
: param url : String or parsed URL
: return : true if this protocol supports these URLs and this func
: throw : TransportError if invalid URL
* /
if ( typeof url === "string" ) {
url = Url . parse ( url ) ; // For efficiency, only parse once.
}
if ( url && ! url . protocol ) {
throw new Error ( "URL failed to specific a scheme (before :) " + url . href )
} //Should be TransportError but out of scope here
// noinspection Annotator supportURLs is defined in subclasses
return ( ( ! url || this . supportURLs . includes ( url . protocol . slice ( 0 , - 1 ) ) )
&& ( ! func || this . supportFunctions . includes ( func ) ) )
}
2018-04-09 12:12:20 +10:00
p _rawstore ( data , opts ) {
2018-04-06 19:28:04 +10:00
/ *
Store a blob of data onto the decentralised transport .
Returns a promise that resolves to the url of the data
: param string | Buffer data : Data to store - no assumptions made to size or content
: param boolean verbose : true for debugging output
: resolve string : url of data stored
* /
throw new errors . ToBeImplementedError ( "Intentionally undefined function Transport.p_rawstore should have been subclassed" ) ;
}
2018-04-09 12:12:20 +10:00
async p _rawstoreCaught ( data , { verbose } ) {
2018-04-06 19:28:04 +10:00
try {
2018-04-09 12:12:20 +10:00
return await this . p _rawstore ( data , { verbose } ) ;
2018-04-06 19:28:04 +10:00
} catch ( err ) {
}
}
p _store ( ) {
throw new errors . ToBeImplementedError ( "Undefined function Transport.p_store - may define higher level semantics here (see Python)" ) ;
}
//noinspection JSUnusedLocalSymbols
2018-04-23 10:49:53 +10:00
p _rawfetch ( url , { timeoutMS = undefined , start = undefined , end = undefined , relay = false , verbose = false } = { } ) {
2018-04-06 19:28:04 +10:00
/ *
Fetch some bytes based on a url , no assumption is made about the data in terms of size or structure .
Where required by the underlying transport it should retrieve a number if its "blocks" and concatenate them .
Returns a new Promise that resolves currently to a string .
There may also be need for a streaming version of this call , at this point undefined .
2018-04-23 10:49:53 +10:00
: param string url : URL of object being retrieved
: param verbose : true for debugging output
: param timeoutMS Max time to wait on transports that support it ( IPFS for fetch )
: param start , end Inclusive byte range wanted ( must be supported , uses a "slice" on output if transport ignores it .
: param relay If first transport fails , try and retrieve on 2 nd , then store on 1 st , and so on .
2018-04-06 19:28:04 +10:00
: resolve string : Return the object being fetched , ( note currently returned as a string , may refactor to return Buffer )
: throws : TransportError if url invalid - note this happens immediately , not as a catch in the promise
* /
console . assert ( false , "Intentionally undefined function Transport.p_rawfetch should have been subclassed" ) ;
}
p _fetch ( ) {
throw new errors . ToBeImplementedError ( "Undefined function Transport.p_fetch - may define higher level semantics here (see Python)" ) ;
}
2018-04-09 12:12:20 +10:00
p _rawadd ( url , sig , { verbose = false } = { } ) {
2018-04-06 19:28:04 +10:00
/ *
Store a new list item , ideally it should be stored so that it can be retrieved either by "signedby" ( using p _rawlist ) or
by "url" ( with p _rawreverse ) . The underlying transport does not need to guarantee the signature ,
an invalid item on a list should be rejected on higher layers .
: param string url : String identifying an object being added to the list .
: param Signature sig : A signature data structure .
: param boolean verbose : true for debugging output
: resolve undefined :
* /
throw new errors . ToBeImplementedError ( "Undefined function Transport.p_rawadd" ) ;
}
2018-04-09 12:12:20 +10:00
p _rawlist ( url , { verbose = false } = { } ) {
2018-04-06 19:28:04 +10:00
/ *
Fetch all the objects in a list , these are identified by the url of the public key used for signing .
( Note this is the 'signedby' parameter of the p _rawadd call , not the 'url' parameter
Returns a promise that resolves to the list .
Each item of the list is a dict : { "url" : url , "date" : date , "signature" : signature , "signedby" : signedby }
List items may have other data ( e . g . reference ids of underlying transport )
: param string url : String with the url that identifies the list .
: param boolean verbose : true for debugging output
: resolve array : An array of objects as stored on the list .
* /
throw new errors . ToBeImplementedError ( "Undefined function Transport.p_rawlist" ) ;
}
p _list ( ) {
throw new Error ( "Undefined function Transport.p_list" ) ;
}
2018-04-09 12:12:20 +10:00
p _newlisturls ( cl , { verbose = false } = { } ) {
2018-04-06 19:28:04 +10:00
/ *
Must be implemented by any list , return a pair of URLS that may be the same , private and public links to the list .
returns : ( privateurl , publicurl ) e . g . yjs : xyz / abc or orbitdb : a123
* /
throw new Error ( "undefined function Transport.p_newlisturls" ) ;
}
//noinspection JSUnusedGlobalSymbols
2018-04-09 12:12:20 +10:00
p _rawreverse ( url , { verbose = false } = { } ) {
2018-04-06 19:28:04 +10:00
/ *
Similar to p _rawlist , but return the list item of all the places where the object url has been listed .
The url here corresponds to the "url" parameter of p _rawadd
Returns a promise that resolves to the list .
: param string url : String with the url that identifies the object put on a list .
: param boolean verbose : true for debugging output
: resolve array : An array of objects as stored on the list .
* /
throw new errors . ToBeImplementedError ( "Undefined function Transport.p_rawreverse" ) ;
}
listmonitor ( url , callback , verbose ) {
/ *
Setup a callback called whenever an item is added to a list , typically it would be called immediately after a p _rawlist to get any more items not returned by p _rawlist .
: param url : string Identifier of list ( as used by p _rawlist and "signedby" parameter of p _rawadd
: param callback : function ( obj ) Callback for each new item added to the list
obj is same format as p _rawlist or p _rawreverse
: param verbose : boolean - true for debugging output
* /
console . log ( "Undefined function Transport.listmonitor" ) ; // Note intentionally a log, as legitamte to not implement it
}
// ==== TO SUPPORT KEY VALUE INTERFACES IMPLEMENT THESE =====
// 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
/ *
Create a new database based on some existing object
pubkey : Something that is , or has a pubkey , by default support Dweb . PublicPrivate , KeyPair or an array of strings as in the output of keypair . publicexport ( )
returns : { publicurl , privateurl } which may be the same if there is no write authentication
* /
throw new errors . ToBeImplementedError ( "Undefined function Transport.p_newdatabase" ) ;
}
//TODO maybe change the listmonitor / monitor code for to use "on" and the structure of PP.events
//TODO but note https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy about Proxy which might be suitable, prob not as doesnt map well to lists
2018-04-09 12:12:20 +10:00
async p _newtable ( pubkey , table , { verbose = false } = { } ) {
2018-04-06 19:28:04 +10:00
/ *
Create a new table ,
pubkey : Is or has a pubkey ( see p _newdatabase )
table : String representing the table - unique to the database
returns : { privateurl , publicurl } which may be the same if there is no write authentication
* /
throw new errors . ToBeImplementedError ( "Undefined function Transport.p_newtable" ) ;
}
2018-04-09 12:12:20 +10:00
async p _set ( url , keyvalues , value , { verbose = false } = { } ) { // url = yjs:/yjs/database/table/key
2018-04-06 19:28:04 +10:00
/ *
Set one or more keys in a table .
url : URL of the table
keyvalues : String representing a single key OR dictionary of keys
value : String or other object to be stored ( its not defined yet what objects should be supported , e . g . any object ?
* /
throw new errors . ToBeImplementedError ( "Undefined function Transport.p_set" ) ;
}
2018-04-09 12:12:20 +10:00
async p _get ( url , keys , { verbose = false } = { } ) {
2018-04-06 19:28:04 +10:00
/ * G e t o n e o r m o r e k e y s f r o m a t a b l e
url : URL of the table
keys : Array of keys
returns : Dictionary of values found ( undefined if not found )
* /
throw new errors . ToBeImplementedError ( "Undefined function Transport.p_get" ) ;
}
2018-04-09 12:12:20 +10:00
async p _delete ( url , keys , { verbose = false } = { } ) {
2018-04-06 19:28:04 +10:00
/ * D e l e t e o n e o r m o r e k e y s f r o m a t a b l e
url : URL of the table
keys : Array of keys
* /
throw new errors . ToBeImplementedError ( "Undefined function Transport.p_delete" ) ;
}
2018-04-09 12:12:20 +10:00
async p _keys ( url , { verbose = false } = { } ) {
2018-04-06 19:28:04 +10:00
/ * R e t u r n a l i s t o f k e y s i n a t a b l e ( s u i t a b l e f o r i t e r a t i n g t h r o u g h )
url : URL of the table
returns : Array of strings
* /
throw new errors . ToBeImplementedError ( "Undefined function Transport.p_keys" ) ;
}
2018-04-09 12:12:20 +10:00
async p _getall ( url , { verbose = false } = { } ) {
2018-04-06 19:28:04 +10:00
/ * R e t u r n a d i c t i o n a r y r e p r e s e n t i n g t h e t a b l e
url : URL of the table
returns : Dictionary of Key : Value pairs , note take care if this could be large .
* /
throw new errors . ToBeImplementedError ( "Undefined function Transport.p_keys" ) ;
}
2018-04-23 10:49:53 +10:00
static async p _f _createReadStream ( url , { wanturl = false , verbose = false } ) {
/ *
Provide a function of the form needed by tag and renderMedia library etc
url Urls of stream
wanturl True if want the URL of the stream ( for service workers )
returns f ( opts ) => stream returning bytes from opts . start || start of file to opts . end - 1 || end of file
* /
}
2018-04-06 19:28:04 +10:00
// ------ UTILITY FUNCTIONS, NOT REQD TO BE SUBCLASSED ----
static mergeoptions ( a ) {
/ *
Deep merge options dictionaries
* /
let c = { } ;
for ( let i = 0 ; i < arguments . length ; i ++ ) {
let b = arguments [ i ] ;
for ( let key in b ) {
let val = b [ key ] ;
if ( ( typeof val === "object" ) && ! Array . isArray ( val ) && c [ key ] ) {
c [ key ] = Transport . mergeoptions ( a [ key ] , b [ key ] ) ;
} else {
c [ key ] = b [ key ] ;
}
}
}
return c ;
}
async p _test _kvt ( urlexpectedsubstring , verbose = false ) {
/ *
Test the KeyValue functionality of any transport that supports it .
urlexpectedsubstring : Some string expected in the publicurl of the table .
* /
if ( verbose ) { console . log ( this . name , "p_test_kvt" ) }
try {
2018-04-09 12:12:20 +10:00
let table = await this . p _newtable ( "NACL VERIFY:1234567" , "mytable" , { verbose } ) ;
2018-04-06 19:28:04 +10:00
let mapurl = table . publicurl ;
if ( verbose ) console . log ( "newtable=" , mapurl ) ;
console . assert ( mapurl . includes ( urlexpectedsubstring ) ) ;
2018-04-09 12:12:20 +10:00
await this . p _set ( mapurl , "testkey" , "testvalue" , { verbose } ) ;
let res = await this . p _get ( mapurl , "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 ( mapurl , "testkey2" , { foo : "bar" } , { verbose } ) ; // Try setting to an object
res = await this . p _get ( mapurl , "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 ( mapurl , "testkey3" , [ 1 , 2 , 3 ] , { verbose } ) ; // Try setting to an array
res = await this . p _get ( mapurl , "testkey3" , { verbose } ) ;
2018-04-06 19:28:04 +10:00
console . assert ( res [ 1 ] === 2 ) ;
2018-04-09 12:12:20 +10:00
res = await this . p _keys ( mapurl , { verbose } ) ;
2018-04-06 19:28:04 +10:00
console . assert ( res . includes ( "testkey" ) && res . includes ( "testkey3" ) ) ;
2018-04-09 12:12:20 +10:00
res = await this . p _delete ( mapurl , [ "testkey" ] , { verbose } ) ;
res = await this . p _getall ( mapurl , { verbose } ) ;
2018-04-06 19:28:04 +10:00
if ( verbose ) console . log ( "getall=>" , res ) ;
console . assert ( res . testkey2 . foo === "bar" && res . testkey3 [ "1" ] === 2 && ! res . testkey1 ) ;
await delay ( 200 ) ;
if ( verbose ) console . log ( this . name , "p_test_kvt complete" )
} catch ( err ) {
console . log ( "Exception thrown in " , this . name , "p_test_kvt:" , err . message ) ;
throw err ;
}
}
}
Transport . STATUS _CONNECTED = 0 ; // Connected - all other numbers are some version of not ok to use
Transport . STATUS _FAILED = 1 ; // Failed to connect
Transport . STATUS _STARTING = 2 ; // In the process of connecting
Transport . STATUS _LOADED = 3 ; // Code loaded, but haven't tried to connect. (this is typically hard coded in subclasses constructor)
2018-05-05 21:04:15 -07:00
Transport . STATUS _PAUSED = 4 ; // It was launched, probably connected, but now paused so will be ignored by validFor // Note this is copied to dweb-archive/Nav.js so check if change
2018-04-06 19:28:04 +10:00
exports = module . exports = Transport ;