Gaëtan Renaudeau
7 years ago
11 changed files with 306 additions and 67 deletions
@ -0,0 +1,193 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import logger from 'logger' |
||||
|
import invariant from 'invariant' |
||||
|
import network from 'api/network' |
||||
|
|
||||
|
const lib = require('@ledgerhq/ledger-core') |
||||
|
|
||||
|
const crypto = require('crypto') |
||||
|
const path = require('path') |
||||
|
const fs = require('fs') |
||||
|
|
||||
|
const MAX_RANDOM = 2684869021 |
||||
|
|
||||
|
const bytesArrayToString = (bytesArray = []) => Buffer.from(bytesArray).toString() |
||||
|
|
||||
|
const stringToBytesArray = str => Array.from(Buffer.from(str)) |
||||
|
|
||||
|
const NJSExecutionContextImpl = { |
||||
|
execute: runnable => { |
||||
|
try { |
||||
|
runnable.run() |
||||
|
} catch (e) { |
||||
|
logger.log(e) |
||||
|
} |
||||
|
}, |
||||
|
delay: (runnable, ms) => setTimeout(() => runnable.run(), ms), |
||||
|
} |
||||
|
|
||||
|
const ThreadContexts = {} |
||||
|
|
||||
|
const getSerialExecutionContext = name => { |
||||
|
let currentContext = ThreadContexts[name] |
||||
|
if (!currentContext) { |
||||
|
currentContext = new lib.NJSExecutionContext(NJSExecutionContextImpl) |
||||
|
ThreadContexts[name] = currentContext |
||||
|
} |
||||
|
return currentContext |
||||
|
} |
||||
|
|
||||
|
const NJSThreadDispatcher = new lib.NJSThreadDispatcher({ |
||||
|
contexts: ThreadContexts, |
||||
|
getThreadPoolExecutionContext: name => getSerialExecutionContext(name), |
||||
|
getMainExecutionContext: () => getSerialExecutionContext('main'), |
||||
|
getSerialExecutionContext, |
||||
|
newLock: () => { |
||||
|
logger.warn('libcore NJSThreadDispatcher: newLock: Not implemented') |
||||
|
}, |
||||
|
}) |
||||
|
|
||||
|
function createHttpConnection(res, err) { |
||||
|
if (!res) { |
||||
|
return null |
||||
|
} |
||||
|
const headersMap = new Map() |
||||
|
Object.keys(res.headers).forEach(key => { |
||||
|
if (typeof res.headers[key] === 'string') { |
||||
|
headersMap.set(key, res.headers[key]) |
||||
|
} |
||||
|
}) |
||||
|
const NJSHttpUrlConnectionImpl = { |
||||
|
getStatusCode: () => Number(res.status), |
||||
|
getStatusText: () => res.statusText, |
||||
|
getHeaders: () => headersMap, |
||||
|
readBody: () => ({ |
||||
|
error: err ? { code: 0, message: 'something went wrong' } : null, |
||||
|
data: stringToBytesArray(JSON.stringify(res.data)), |
||||
|
}), |
||||
|
} |
||||
|
return new lib.NJSHttpUrlConnection(NJSHttpUrlConnectionImpl) |
||||
|
} |
||||
|
|
||||
|
const NJSHttpClient = new lib.NJSHttpClient({ |
||||
|
execute: async r => { |
||||
|
const method = r.getMethod() |
||||
|
const headersMap = r.getHeaders() |
||||
|
let data = r.getBody() |
||||
|
if (Array.isArray(data)) { |
||||
|
const dataStr = bytesArrayToString(data) |
||||
|
try { |
||||
|
data = JSON.parse(dataStr) |
||||
|
} catch (e) { |
||||
|
// not a json !?
|
||||
|
} |
||||
|
} |
||||
|
const url = r.getUrl() |
||||
|
const headers = {} |
||||
|
headersMap.forEach((v, k) => { |
||||
|
headers[k] = v |
||||
|
}) |
||||
|
let res |
||||
|
try { |
||||
|
// $FlowFixMe
|
||||
|
res = await network({ method: lib.METHODS[method], url, headers, data }) |
||||
|
const urlConnection = createHttpConnection(res) |
||||
|
r.complete(urlConnection, null) |
||||
|
} catch (err) { |
||||
|
const urlConnection = createHttpConnection(res, err.message) |
||||
|
r.complete(urlConnection, { code: 0, message: err.message }) |
||||
|
} |
||||
|
}, |
||||
|
}) |
||||
|
|
||||
|
const NJSWebSocketClient = new lib.NJSWebSocketClient({ |
||||
|
connect: (url, connection) => { |
||||
|
connection.OnConnect() |
||||
|
}, |
||||
|
send: (connection, data) => { |
||||
|
connection.OnMessage(data) |
||||
|
}, |
||||
|
disconnect: connection => { |
||||
|
connection.OnClose() |
||||
|
}, |
||||
|
}) |
||||
|
|
||||
|
const NJSLogPrinter = new lib.NJSLogPrinter({ |
||||
|
context: {}, |
||||
|
printError: message => logger.libcore('Error', message), |
||||
|
printInfo: message => logger.libcore('Info', message), |
||||
|
printDebug: message => logger.libcore('Debug', message), |
||||
|
printWarning: message => logger.libcore('Warning', message), |
||||
|
printApdu: message => logger.libcore('Apdu', message), |
||||
|
printCriticalError: message => logger.libcore('CriticalError', message), |
||||
|
getContext: () => NJSThreadDispatcher.getMainExecutionContext(), |
||||
|
}) |
||||
|
|
||||
|
const NJSRandomNumberGenerator = new lib.NJSRandomNumberGenerator({ |
||||
|
getRandomBytes: size => crypto.randomBytes(size), |
||||
|
getRandomInt: () => Math.random() * MAX_RANDOM, |
||||
|
getRandomLong: () => Math.random() * MAX_RANDOM * MAX_RANDOM, |
||||
|
}) |
||||
|
|
||||
|
const NJSDatabaseBackend = new lib.NJSDatabaseBackend() |
||||
|
const NJSDynamicObject = new lib.NJSDynamicObject() |
||||
|
|
||||
|
let walletPoolInstance = null |
||||
|
|
||||
|
const instanciateWalletPool = ({ dbPath }) => { |
||||
|
try { |
||||
|
fs.mkdirSync(dbPath) |
||||
|
} catch (err) { |
||||
|
if (err.code !== 'EEXIST') { |
||||
|
throw err |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const NJSPathResolver = new lib.NJSPathResolver({ |
||||
|
resolveLogFilePath: pathToResolve => { |
||||
|
const hash = pathToResolve.replace(/\//g, '__') |
||||
|
return path.resolve(dbPath, `./log_file_${hash}`) |
||||
|
}, |
||||
|
resolvePreferencesPath: pathToResolve => { |
||||
|
const hash = pathToResolve.replace(/\//g, '__') |
||||
|
return path.resolve(dbPath, `./preferences_${hash}`) |
||||
|
}, |
||||
|
resolveDatabasePath: pathToResolve => { |
||||
|
const hash = pathToResolve.replace(/\//g, '__') |
||||
|
return path.resolve(dbPath, `./database_${hash}`) |
||||
|
}, |
||||
|
}) |
||||
|
|
||||
|
walletPoolInstance = new lib.NJSWalletPool( |
||||
|
'ledger_live_desktop', |
||||
|
'', |
||||
|
NJSHttpClient, |
||||
|
NJSWebSocketClient, |
||||
|
NJSPathResolver, |
||||
|
NJSLogPrinter, |
||||
|
NJSThreadDispatcher, |
||||
|
NJSRandomNumberGenerator, |
||||
|
NJSDatabaseBackend, |
||||
|
NJSDynamicObject, |
||||
|
) |
||||
|
|
||||
|
return walletPoolInstance |
||||
|
} |
||||
|
|
||||
|
const getPoolInstance = () => { |
||||
|
if (!walletPoolInstance) { |
||||
|
instanciateWalletPool({ |
||||
|
// sqlite files will be located in the app local data folder
|
||||
|
dbPath: process.env.LEDGER_LIVE_SQLITE_PATH, |
||||
|
}) |
||||
|
} |
||||
|
invariant(walletPoolInstance, "can't initialize walletPoolInstance") |
||||
|
return walletPoolInstance |
||||
|
} |
||||
|
|
||||
|
export default { |
||||
|
...lib, |
||||
|
getSerialExecutionContext, |
||||
|
getPoolInstance, |
||||
|
} |
@ -1,26 +1,10 @@ |
|||||
// @flow
|
// @flow
|
||||
|
|
||||
import invariant from 'invariant' |
// TODO: `core` should be typed
|
||||
import network from 'api/network' |
type Job<A> = Object => Promise<A> |
||||
|
|
||||
const core = require('@ledgerhq/ledger-core') |
|
||||
|
|
||||
core.setHttpQueryImplementation(network) |
|
||||
|
|
||||
let walletPoolInstance: ?Object = null |
|
||||
|
|
||||
// TODO: `core` and `NJSWalletPool` should be typed
|
|
||||
type Job<A> = (Object, Object) => Promise<A> |
|
||||
|
|
||||
export default function withLibcore<A>(job: Job<A>): Promise<A> { |
export default function withLibcore<A>(job: Job<A>): Promise<A> { |
||||
if (!walletPoolInstance) { |
const core = require('./init-libcore').default |
||||
walletPoolInstance = core.instanciateWalletPool({ |
core.getPoolInstance() |
||||
// sqlite files will be located in the app local data folder
|
return job(core) |
||||
dbPath: process.env.LEDGER_LIVE_SQLITE_PATH, |
|
||||
}) |
|
||||
} |
|
||||
const walletPool = walletPoolInstance |
|
||||
invariant(walletPool, 'core.instanciateWalletPool returned null !!') |
|
||||
|
|
||||
return job(core, walletPool) |
|
||||
} |
} |
||||
|
Loading…
Reference in new issue