2020-11-05 18:58:23 +02:00
import shim from './shim' ;
import { _ } from './locale' ;
2021-01-22 19:41:11 +02:00
import Logger from './Logger' ;
2020-11-05 18:58:23 +02:00
const JoplinError = require ( './JoplinError' ) ;
const { rtrimSlashes } = require ( './path-utils' ) ;
2019-12-13 03:16:34 +02:00
const base64 = require ( 'base-64' ) ;
interface JoplinServerApiOptions {
2020-11-12 21:29:22 +02:00
username : Function ;
password : Function ;
baseUrl : Function ;
2019-12-13 03:16:34 +02:00
}
export default class JoplinServerApi {
2020-11-12 21:13:28 +02:00
logger_ : any ;
options_ : JoplinServerApiOptions ;
kvStore_ : any ;
2019-12-13 03:16:34 +02:00
2020-11-12 21:13:28 +02:00
constructor ( options : JoplinServerApiOptions ) {
2019-12-13 03:16:34 +02:00
this . logger_ = new Logger ( ) ;
this . options_ = options ;
this . kvStore_ = null ;
}
2020-11-12 21:13:28 +02:00
setLogger ( l : any ) {
2019-12-13 03:16:34 +02:00
this . logger_ = l ;
}
2020-11-12 21:13:28 +02:00
logger ( ) : any {
2019-12-13 03:16:34 +02:00
return this . logger_ ;
}
2020-11-12 21:13:28 +02:00
setKvStore ( v : any ) {
2019-12-13 03:16:34 +02:00
this . kvStore_ = v ;
}
kvStore() {
if ( ! this . kvStore_ ) throw new Error ( 'JoplinServerApi.kvStore_ is not set!!' ) ;
return this . kvStore_ ;
}
2020-11-12 21:13:28 +02:00
authToken ( ) : string {
2019-12-13 03:16:34 +02:00
if ( ! this . options_ . username ( ) || ! this . options_ . password ( ) ) return null ;
try {
// Note: Non-ASCII passwords will throw an error about Latin1 characters - https://github.com/laurent22/joplin/issues/246
// Tried various things like the below, but it didn't work on React Native:
// return base64.encode(utf8.encode(this.options_.username() + ':' + this.options_.password()));
return base64 . encode ( ` ${ this . options_ . username ( ) } : ${ this . options_ . password ( ) } ` ) ;
} catch ( error ) {
error . message = ` Cannot encode username/password: ${ error . message } ` ;
throw error ;
}
}
2020-11-12 21:13:28 +02:00
baseUrl ( ) : string {
2019-12-13 03:16:34 +02:00
return rtrimSlashes ( this . options_ . baseUrl ( ) ) ;
}
2020-11-12 21:13:28 +02:00
static baseUrlFromNextcloudWebDavUrl ( webDavUrl : string ) {
2019-12-13 03:16:34 +02:00
// http://nextcloud.local/remote.php/webdav/Joplin
// http://nextcloud.local/index.php/apps/joplin/api
const splitted = webDavUrl . split ( '/remote.php/webdav' ) ;
if ( splitted . length !== 2 ) throw new Error ( ` Unsupported WebDAV URL format: ${ webDavUrl } ` ) ;
return ` ${ splitted [ 0 ] } /index.php/apps/joplin/api ` ;
}
2020-11-12 21:13:28 +02:00
syncTargetId ( settings : any ) {
2019-12-13 03:16:34 +02:00
const s = settings [ 'sync.5.syncTargets' ] [ settings [ 'sync.5.path' ] ] ;
if ( ! s ) throw new Error ( ` Joplin Nextcloud app not configured for URL: ${ this . baseUrl ( ) } ` ) ;
return s . uuid ;
}
2020-11-12 21:13:28 +02:00
static connectionErrorMessage ( error : any ) {
2019-12-13 03:16:34 +02:00
const msg = error && error . message ? error . message : 'Unknown error' ;
return _ ( 'Could not connect to the Joplin Nextcloud app. Please check the configuration in the Synchronisation config screen. Full error was:\n\n%s' , msg ) ;
}
2020-11-12 21:13:28 +02:00
async setupSyncTarget ( webDavUrl : string ) {
2019-12-13 03:16:34 +02:00
return this . exec ( 'POST' , 'sync_targets' , {
webDavUrl : webDavUrl ,
} ) ;
}
2020-11-12 21:13:28 +02:00
requestToCurl_ ( url : string , options : any ) {
2020-03-14 01:46:14 +02:00
const output = [ ] ;
2019-12-13 03:16:34 +02:00
output . push ( 'curl' ) ;
output . push ( '-v' ) ;
if ( options . method ) output . push ( ` -X ${ options . method } ` ) ;
if ( options . headers ) {
2020-03-14 01:46:14 +02:00
for ( const n in options . headers ) {
2019-12-13 03:16:34 +02:00
if ( ! options . headers . hasOwnProperty ( n ) ) continue ;
output . push ( ` ${ '-H ' + '"' } ${ n } : ${ options . headers [ n ] } " ` ) ;
}
}
if ( options . body ) output . push ( ` ${ '--data ' + '\'' } ${ options . body } ' ` ) ;
output . push ( url ) ;
return output . join ( ' ' ) ;
}
2020-11-12 21:13:28 +02:00
async exec ( method : string , path : string = '' , body : any = null , headers : any = null , options : any = null ) : Promise < any > {
2019-12-13 03:16:34 +02:00
if ( headers === null ) headers = { } ;
if ( options === null ) options = { } ;
const authToken = this . authToken ( ) ;
if ( authToken ) headers [ 'Authorization' ] = ` Basic ${ authToken } ` ;
headers [ 'Content-Type' ] = 'application/json' ;
if ( typeof body === 'object' && body !== null ) body = JSON . stringify ( body ) ;
2020-11-12 21:13:28 +02:00
const fetchOptions : any = { } ;
2019-12-13 03:16:34 +02:00
fetchOptions . headers = headers ;
fetchOptions . method = method ;
if ( options . path ) fetchOptions . path = options . path ;
if ( body ) fetchOptions . body = body ;
const url = ` ${ this . baseUrl ( ) } / ${ path } ` ;
let response = null ;
// console.info('WebDAV Call', method + ' ' + url, headers, options);
console . info ( this . requestToCurl_ ( url , fetchOptions ) ) ;
if ( typeof body === 'string' ) fetchOptions . headers [ 'Content-Length' ] = ` ${ shim . stringByteLength ( body ) } ` ;
response = await shim . fetch ( url , fetchOptions ) ;
const responseText = await response . text ( ) ;
2020-11-12 21:13:28 +02:00
const responseJson_ : any = null ;
2019-12-13 03:16:34 +02:00
const loadResponseJson = async ( ) = > {
if ( ! responseText ) return null ;
if ( responseJson_ ) return responseJson_ ;
2019-12-18 17:32:19 +02:00
try {
return JSON . parse ( responseText ) ;
} catch ( error ) {
throw new Error ( ` Cannot parse JSON: ${ responseText . substr ( 0 , 8192 ) } ` ) ;
}
2019-12-13 03:16:34 +02:00
} ;
2020-11-12 21:13:28 +02:00
const newError = ( message : string , code : number = 0 ) = > {
2019-12-13 03:16:34 +02:00
return new JoplinError ( ` ${ method } ${ path } : ${ message } ( ${ code } ) ` , code ) ;
} ;
if ( ! response . ok ) {
let json = null ;
try {
json = await loadResponseJson ( ) ;
} catch ( error ) {
2019-12-18 17:32:19 +02:00
throw newError ( ` Unknown error: ${ responseText . substr ( 0 , 8192 ) } ` , response . status ) ;
2019-12-13 03:16:34 +02:00
}
const trace = json . stacktrace ? ` \ n ${ json . stacktrace } ` : '' ;
2019-12-18 17:32:19 +02:00
let message = json . error ;
if ( ! message ) message = responseText . substr ( 0 , 8192 ) ;
throw newError ( message + trace , response . status ) ;
2019-12-13 03:16:34 +02:00
}
const output = await loadResponseJson ( ) ;
return output ;
}
}