mirror of https://github.com/keeweb/keeweb.git
251 lines
7.3 KiB
JavaScript
251 lines
7.3 KiB
JavaScript
import * as kdbxweb from 'kdbxweb';
|
|
import { Events } from 'framework/events';
|
|
import { Logger } from 'util/logger';
|
|
import { Launcher } from 'comp/launcher';
|
|
import { Timeouts } from 'const/timeouts';
|
|
|
|
let NativeModules;
|
|
|
|
if (Launcher) {
|
|
const logger = new Logger('native-module-connector');
|
|
|
|
let hostRunning = false;
|
|
let hostStartPromise;
|
|
let callId = 0;
|
|
let promises = {};
|
|
let ykChalRespCallbacks = {};
|
|
|
|
const { ipcRenderer } = Launcher.electron();
|
|
ipcRenderer.on('nativeModuleCallback', (e, msg) => NativeModules.hostCallback(msg));
|
|
ipcRenderer.on('nativeModuleHostError', (e, err) => NativeModules.hostError(err));
|
|
ipcRenderer.on('nativeModuleHostExit', (e, { code, sig }) => NativeModules.hostExit(code, sig));
|
|
ipcRenderer.on('nativeModuleHostDisconnect', () => NativeModules.hostDisconnect());
|
|
ipcRenderer.on('log', (e, ...args) => NativeModules.log(...args));
|
|
|
|
const handlers = {
|
|
yubikeys(numYubiKeys) {
|
|
Events.emit('native-modules-yubikeys', { numYubiKeys });
|
|
},
|
|
|
|
log(...args) {
|
|
logger.info('Message from host', ...args);
|
|
},
|
|
|
|
result({ callId, result, error }) {
|
|
const promise = promises[callId];
|
|
if (promise) {
|
|
delete promises[callId];
|
|
if (error) {
|
|
logger.error('Received an error', promise.cmd, error);
|
|
promise.reject(error);
|
|
} else {
|
|
promise.resolve(result);
|
|
}
|
|
}
|
|
},
|
|
|
|
yubiKeyChallengeResponseResult({ callbackId, error, result }) {
|
|
const callback = ykChalRespCallbacks[callbackId];
|
|
if (callback) {
|
|
const willBeCalledAgain = error && error.touchRequested;
|
|
if (!willBeCalledAgain) {
|
|
delete ykChalRespCallbacks[callbackId];
|
|
}
|
|
callback(error, result);
|
|
}
|
|
}
|
|
};
|
|
|
|
NativeModules = {
|
|
startHost() {
|
|
if (hostRunning) {
|
|
return Promise.resolve();
|
|
}
|
|
if (hostStartPromise) {
|
|
return hostStartPromise;
|
|
}
|
|
|
|
logger.debug('Starting native module host');
|
|
|
|
hostStartPromise = this.callNoWait('start').then(() => {
|
|
hostStartPromise = undefined;
|
|
hostRunning = true;
|
|
|
|
if (this.usbListenerRunning) {
|
|
return this.call('startUsbListener');
|
|
}
|
|
});
|
|
|
|
return hostStartPromise;
|
|
},
|
|
|
|
hostError(e) {
|
|
logger.error('Host error', e);
|
|
},
|
|
|
|
hostDisconnect() {
|
|
logger.error('Host disconnected');
|
|
},
|
|
|
|
hostExit(code, sig) {
|
|
logger.error(`Host exited with code ${code} and signal ${sig}`);
|
|
|
|
hostRunning = false;
|
|
|
|
const err = new Error('Native module host crashed');
|
|
|
|
for (const promise of Object.values(promises)) {
|
|
promise.reject(err);
|
|
}
|
|
promises = {};
|
|
|
|
for (const callback of Object.values(ykChalRespCallbacks)) {
|
|
callback(err);
|
|
}
|
|
ykChalRespCallbacks = {};
|
|
|
|
if (code !== 0) {
|
|
this.autoRestartHost();
|
|
}
|
|
},
|
|
|
|
hostCallback(message) {
|
|
const { cmd, args } = message;
|
|
// logger.debug('Callback', cmd, args);
|
|
if (handlers[cmd]) {
|
|
handlers[cmd](...args);
|
|
} else {
|
|
logger.error('No callback', cmd);
|
|
}
|
|
},
|
|
|
|
log(name, level, ...args) {
|
|
if (!name) {
|
|
return;
|
|
}
|
|
const logger = new Logger(name);
|
|
logger[level](...args);
|
|
},
|
|
|
|
autoRestartHost() {
|
|
setTimeout(() => {
|
|
try {
|
|
this.startHost();
|
|
} catch (e) {
|
|
logger.error('Native module host failed to auto-restart', e);
|
|
}
|
|
}, Timeouts.NativeModuleHostRestartTime);
|
|
},
|
|
|
|
call(cmd, ...args) {
|
|
return this.startHost().then(() => this.callNoWait(cmd, ...args));
|
|
},
|
|
|
|
callNoWait(cmd, ...args) {
|
|
return new Promise((resolve, reject) => {
|
|
callId++;
|
|
if (callId === Number.MAX_SAFE_INTEGER) {
|
|
callId = 1;
|
|
}
|
|
// logger.debug('Call', cmd, args, callId);
|
|
promises[callId] = { cmd, resolve, reject };
|
|
|
|
ipcRenderer.send('nativeModuleCall', { cmd, args, callId });
|
|
});
|
|
},
|
|
|
|
startUsbListener() {
|
|
this.call('startUsbListener');
|
|
this.usbListenerRunning = true;
|
|
},
|
|
|
|
stopUsbListener() {
|
|
this.usbListenerRunning = false;
|
|
if (hostRunning) {
|
|
this.call('stopUsbListener');
|
|
}
|
|
},
|
|
|
|
getYubiKeys(config) {
|
|
return this.call('getYubiKeys', config);
|
|
},
|
|
|
|
yubiKeyChallengeResponse(yubiKey, challenge, slot, callback) {
|
|
ykChalRespCallbacks[callId] = callback;
|
|
return this.call('yubiKeyChallengeResponse', yubiKey, challenge, slot, callId);
|
|
},
|
|
|
|
yubiKeyCancelChallengeResponse() {
|
|
if (hostRunning) {
|
|
this.call('yubiKeyCancelChallengeResponse');
|
|
}
|
|
},
|
|
|
|
argon2(password, salt, options) {
|
|
return this.call('argon2', password, salt, options);
|
|
},
|
|
|
|
hardwareCryptoDeleteKey: async () => {
|
|
await ipcRenderer.invoke('hardwareCryptoDeleteKey');
|
|
},
|
|
|
|
hardwareEncrypt: async (value) => {
|
|
const { data, salt } = await ipcRenderer.invoke('hardwareEncrypt', value.dataAndSalt());
|
|
return new kdbxweb.ProtectedValue(data, salt);
|
|
},
|
|
|
|
hardwareDecrypt: async (value, touchIdPrompt) => {
|
|
const { data, salt } = await ipcRenderer.invoke(
|
|
'hardwareDecrypt',
|
|
value.dataAndSalt(),
|
|
touchIdPrompt
|
|
);
|
|
return new kdbxweb.ProtectedValue(data, salt);
|
|
},
|
|
|
|
kbdGetActiveWindow(options) {
|
|
return this.call('kbdGetActiveWindow', options);
|
|
},
|
|
|
|
kbdGetActivePid() {
|
|
return this.call('kbdGetActivePid');
|
|
},
|
|
|
|
kbdShowWindow(win) {
|
|
return this.call('kbdShowWindow', win);
|
|
},
|
|
|
|
kbdText(str) {
|
|
return this.call('kbdText', str);
|
|
},
|
|
|
|
kbdTextAsKeys(str, mods) {
|
|
return this.call('kbdTextAsKeys', str, mods);
|
|
},
|
|
|
|
kbdKeyPress(code, modifiers) {
|
|
return this.call('kbdKeyPress', code, modifiers);
|
|
},
|
|
|
|
kbdShortcut(code, modifiers) {
|
|
return this.call('kbdShortcut', code, modifiers);
|
|
},
|
|
|
|
kbdKeyMoveWithModifier(down, modifiers) {
|
|
return this.call('kbdKeyMoveWithModifier', down, modifiers);
|
|
},
|
|
|
|
kbdKeyPressWithCharacter(character, code, modifiers) {
|
|
return this.call('kbdKeyPressWithCharacter', character, code, modifiers);
|
|
},
|
|
|
|
kbdEnsureModifierNotPressed() {
|
|
return this.call('kbdEnsureModifierNotPressed');
|
|
}
|
|
};
|
|
|
|
global.NativeModules = NativeModules;
|
|
}
|
|
|
|
export { NativeModules };
|