keeweb/app/scripts/auto-type/index.js

309 lines
11 KiB
JavaScript
Raw Normal View History

2019-09-16 22:57:56 +02:00
import { Events } from 'framework/events';
2019-09-15 14:16:32 +02:00
import { AutoTypeFilter } from 'auto-type/auto-type-filter';
import { AutoTypeHelperFactory } from 'auto-type/auto-type-helper-factory';
import { AutoTypeParser } from 'auto-type/auto-type-parser';
import { Launcher } from 'comp/launcher';
import { Alerts } from 'comp/ui/alerts';
import { Timeouts } from 'const/timeouts';
import { AppSettingsModel } from 'models/app-settings-model';
2020-05-05 16:34:39 +02:00
import { AppModel } from 'models/app-model';
2019-09-15 14:16:32 +02:00
import { Locale } from 'util/locale';
import { Logger } from 'util/logger';
import { AutoTypeSelectView } from 'views/auto-type/auto-type-select-view';
2017-01-31 07:50:28 +01:00
const logger = new Logger('auto-type');
2019-10-12 08:20:44 +02:00
const clearTextAutoTypeLog = !!localStorage.debugAutoType;
2017-01-31 07:50:28 +01:00
const AutoType = {
2016-04-09 20:58:22 +02:00
helper: AutoTypeHelperFactory.create(),
enabled: !!(Launcher && Launcher.autoTypeSupported),
2020-05-22 12:30:01 +02:00
supportsEventsWithWindowId: !!(Launcher && Launcher.platform() === 'linux'),
2016-07-24 22:57:12 +02:00
selectEntryView: false,
2016-08-07 19:34:31 +02:00
pendingEvent: null,
2016-08-18 22:01:08 +02:00
running: false,
2016-04-23 16:50:40 +02:00
2020-05-05 16:34:39 +02:00
init() {
2016-07-24 22:57:12 +02:00
if (!this.enabled) {
return;
}
2020-06-01 16:53:51 +02:00
Events.on('auto-type', (e) => this.handleEvent(e));
Events.on('main-window-blur', (e) => this.mainWindowBlur(e));
Events.on('main-window-focus', (e) => this.mainWindowFocus(e));
Events.on('main-window-will-close', (e) => this.mainWindowWillClose(e));
Events.on('closed-open-view', (e) => this.processPendingEvent(e));
2016-07-24 19:11:25 +02:00
},
2016-08-07 19:34:31 +02:00
handleEvent(e) {
2019-08-16 23:05:39 +02:00
const entry = (e && e.entry) || null;
const sequence = (e && e.sequence) || null;
2020-05-09 10:37:34 +02:00
const context = (e && e.context) || null;
2016-07-24 19:11:25 +02:00
logger.debug('Auto type event', entry);
2016-08-18 22:01:08 +02:00
if (this.running) {
logger.debug('Already running, skipping event');
return;
}
2016-07-24 19:11:25 +02:00
if (entry) {
2019-08-16 23:05:39 +02:00
this.hideWindow(() => {
2020-05-09 10:37:34 +02:00
this.runAndHandleResult({ entry, sequence, context });
2019-08-16 23:05:39 +02:00
});
2016-07-24 19:11:25 +02:00
} else {
if (this.selectEntryView) {
return;
}
if (Launcher.isAppFocused()) {
return Alerts.error({
header: Locale.autoTypeError,
body: Locale.autoTypeErrorGlobal,
skipIfAlertDisplayed: true
});
}
this.selectEntryAndRun();
}
},
runAndHandleResult(result, windowId) {
2020-06-01 16:53:51 +02:00
this.run(result, windowId, (err) => {
2016-07-24 19:11:25 +02:00
if (err) {
Alerts.error({
header: Locale.autoTypeError,
body: Locale.autoTypeErrorGeneric.replace('{}', err.toString())
});
}
});
2017-04-09 10:31:05 +02:00
2019-09-17 19:50:42 +02:00
if (AppSettingsModel.lockOnAutoType) {
2019-09-16 22:57:56 +02:00
Events.emit('lock-workspace');
2017-04-09 10:31:05 +02:00
}
2016-07-24 19:11:25 +02:00
},
run(result, windowId, callback) {
2016-08-18 22:01:08 +02:00
this.running = true;
2019-09-12 19:59:35 +02:00
const sequence = result.sequence || result.entry.getEffectiveAutoTypeSeq();
2020-05-09 10:37:34 +02:00
const context = result.context;
2016-04-08 17:40:00 +02:00
logger.debug('Start', sequence);
2017-01-31 07:50:28 +01:00
const ts = logger.ts();
2016-04-08 17:40:00 +02:00
try {
2017-01-31 07:50:28 +01:00
const parser = new AutoTypeParser(sequence);
const runner = parser.parse();
2016-07-17 13:30:38 +02:00
logger.debug('Parsed', this.printOps(runner.ops));
2020-06-01 16:53:51 +02:00
runner.resolve(result.entry, context, (err) => {
2016-04-08 17:40:00 +02:00
if (err) {
2016-08-18 22:01:08 +02:00
this.running = false;
2016-04-08 23:14:09 +02:00
logger.error('Resolve error', err);
2016-04-23 17:05:33 +02:00
return callback && callback(err);
2016-04-08 17:40:00 +02:00
}
2016-07-17 13:30:38 +02:00
logger.debug('Resolved', this.printOps(runner.ops));
2018-10-13 11:04:58 +02:00
if (result.entry.autoTypeObfuscation) {
2016-04-09 14:55:27 +02:00
try {
runner.obfuscate();
} catch (e) {
2016-08-18 22:01:08 +02:00
this.running = false;
2016-04-09 14:55:27 +02:00
logger.error('Obfuscate error', e);
2016-04-23 17:05:33 +02:00
return callback && callback(e);
2016-04-09 14:55:27 +02:00
}
2016-04-10 09:31:08 +02:00
logger.debug('Obfuscated');
2016-04-09 10:41:52 +02:00
}
2020-06-01 16:53:51 +02:00
runner.run((err) => {
2016-08-18 22:01:08 +02:00
this.running = false;
2016-04-08 17:40:00 +02:00
if (err) {
logger.error('Run error', err);
2016-04-23 17:05:33 +02:00
return callback && callback(err);
2016-04-08 17:40:00 +02:00
}
2016-04-23 17:05:33 +02:00
logger.debug('Complete', logger.ts(ts));
return callback && callback();
}, windowId);
2016-04-08 17:40:00 +02:00
});
} catch (ex) {
2016-08-18 22:01:08 +02:00
this.running = false;
2016-04-08 17:40:00 +02:00
logger.error('Parse error', ex);
2016-04-23 17:05:33 +02:00
return callback && callback(ex);
2016-04-08 17:40:00 +02:00
}
2016-04-09 11:17:01 +02:00
},
2016-08-07 19:34:31 +02:00
validate(entry, sequence, callback) {
2016-04-23 16:50:40 +02:00
try {
2017-01-31 07:50:28 +01:00
const parser = new AutoTypeParser(sequence);
const runner = parser.parse();
2020-05-09 10:37:34 +02:00
runner.resolve(entry, null, callback);
2016-04-23 16:50:40 +02:00
} catch (ex) {
return callback(ex);
}
},
2016-08-07 19:34:31 +02:00
printOps(ops) {
2016-04-09 11:17:01 +02:00
return '[' + ops.map(this.printOp, this).join(',') + ']';
},
2016-08-07 19:34:31 +02:00
printOp(op) {
2017-01-31 07:50:28 +01:00
const mod = op.mod ? Object.keys(op.mod).join('') : '';
2016-04-09 11:17:01 +02:00
if (op.type === 'group') {
return mod + this.printOps(op.value);
}
if (op.type === 'text') {
2017-01-31 07:50:28 +01:00
let value = op.value;
2016-04-09 11:28:14 +02:00
if (!clearTextAutoTypeLog) {
value = value.replace(/./g, '*');
}
return mod + value;
2016-04-09 11:17:01 +02:00
}
return mod + op.type + ':' + op.value;
2016-04-09 20:58:22 +02:00
},
2016-08-07 19:34:31 +02:00
hideWindow(callback) {
2016-04-09 20:58:22 +02:00
logger.debug('Hide window');
2016-07-24 19:11:25 +02:00
if (Launcher.isAppFocused()) {
Launcher.hideApp();
2016-04-09 20:58:22 +02:00
setTimeout(callback, Timeouts.AutoTypeAfterHide);
} else {
callback();
}
},
getActiveWindowInfo(callback) {
logger.debug('Getting window info');
return this.helper.getActiveWindowInfo((err, windowInfo) => {
2016-04-09 20:58:22 +02:00
if (err) {
logger.error('Error getting window info', err);
2016-04-09 20:58:22 +02:00
} else {
if (!windowInfo.url) {
// try to find a URL in the title
const urlMatcher = new RegExp(
'https?:\\/\\/(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,4}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)'
);
const urlMatches = urlMatcher.exec(windowInfo.title);
windowInfo.url = urlMatches && urlMatches.length > 0 ? urlMatches[0] : null;
}
logger.debug('Window info', windowInfo.id, windowInfo.title, windowInfo.url);
2016-04-09 20:58:22 +02:00
}
return callback(err, windowInfo);
});
},
activeWindowMatches(windowInfo, callback) {
if (!windowInfo || !windowInfo.id) {
logger.debug('Skipped active window check because window id is unknown');
return callback(true);
}
this.getActiveWindowInfo((err, activeWindowInfo) => {
if (!activeWindowInfo) {
logger.debug('Error during active window check, something is wrong', err);
return callback(false);
}
2020-05-22 12:30:01 +02:00
if (activeWindowInfo.id !== windowInfo.id && !this.supportsEventsWithWindowId) {
logger.info(
`Active window doesn't match: ID is different. ` +
`Expected ${windowInfo.id}, got ${activeWindowInfo.id}`
);
return callback(false, activeWindowInfo);
}
if (activeWindowInfo.url !== windowInfo.url && !this.supportsEventsWithWindowId) {
logger.info(
`Active window doesn't match: url is different. ` +
`Expected "${windowInfo.url}", got "${activeWindowInfo.url}"`
);
return callback(false, activeWindowInfo);
}
logger.info('Active window matches');
callback(true, activeWindowInfo);
2016-07-24 19:11:25 +02:00
});
},
2016-08-07 19:34:31 +02:00
selectEntryAndRun() {
this.getActiveWindowInfo((e, windowInfo) => {
2020-05-05 16:34:39 +02:00
const filter = new AutoTypeFilter(windowInfo, AppModel.instance);
const evt = { filter, windowInfo };
2020-05-05 16:34:39 +02:00
if (!AppModel.instance.files.hasOpenFiles()) {
2016-08-07 19:34:31 +02:00
this.pendingEvent = evt;
logger.debug('auto-type event delayed');
2016-08-18 23:26:35 +02:00
this.focusMainWindow();
2016-08-07 19:34:31 +02:00
} else {
this.processEventWithFilter(evt);
2016-07-24 19:11:25 +02:00
}
2016-08-07 19:34:31 +02:00
});
},
2016-08-18 23:26:35 +02:00
focusMainWindow() {
setTimeout(() => Launcher.showMainWindow(), Timeouts.RedrawInactiveWindow);
},
2016-08-07 19:34:31 +02:00
processEventWithFilter(evt) {
2017-01-31 07:50:28 +01:00
const entries = evt.filter.getEntries();
2019-09-17 19:50:42 +02:00
if (entries.length === 1 && AppSettingsModel.directAutotype) {
this.hideWindow(() => {
this.runAndHandleResult({ entry: entries[0] }, evt.windowInfo.id);
});
2016-08-07 19:34:31 +02:00
return;
}
2016-08-18 23:26:35 +02:00
this.focusMainWindow();
2017-05-07 19:45:53 +02:00
evt.filter.ignoreWindowInfo = true;
2019-09-15 23:02:51 +02:00
this.selectEntryView = new AutoTypeSelectView({ filter: evt.filter });
2020-06-01 16:53:51 +02:00
this.selectEntryView.on('result', (result) => {
2016-08-07 19:34:31 +02:00
logger.debug('Entry selected', result);
this.selectEntryView.off('result');
this.selectEntryView.remove();
this.selectEntryView = null;
this.hideWindow(() => {
if (result) {
this.activeWindowMatches(evt.windowInfo, (matches, activeWindowInfo) => {
if (matches) {
this.runAndHandleResult(result, evt.windowInfo.id);
}
});
2016-07-24 22:57:12 +02:00
}
2016-07-24 19:11:25 +02:00
});
2016-04-09 20:58:22 +02:00
});
2019-09-15 23:02:51 +02:00
this.selectEntryView.render();
2019-03-31 14:30:14 +02:00
this.selectEntryView.on('show-open-files', () => {
this.selectEntryView.hide();
2019-09-16 22:57:56 +02:00
Events.emit('open-file');
2019-03-31 14:30:14 +02:00
});
2016-08-07 19:34:31 +02:00
},
mainWindowBlur() {
this.mainWindowBlurTimer = setTimeout(() => {
// macOS emits focus-blur-focus event in a row when triggering auto-type from minimized state
delete this.mainWindowBlurTimer;
this.resetPendingEvent();
if (this.selectEntryView) {
this.selectEntryView.emit('result', undefined);
}
}, Timeouts.AutoTypeWindowFocusAfterBlur);
},
mainWindowFocus() {
if (this.mainWindowBlurTimer) {
clearTimeout(this.mainWindowBlurTimer);
this.mainWindowBlurTimer = null;
}
},
mainWindowWillClose() {
this.resetPendingEvent();
if (this.selectEntryView) {
this.selectEntryView.emit('result', undefined);
}
},
2016-08-07 19:34:31 +02:00
resetPendingEvent() {
if (this.pendingEvent) {
this.pendingEvent = null;
2020-05-30 16:02:02 +02:00
logger.debug('auto-type event canceled');
2016-08-07 19:34:31 +02:00
}
},
processPendingEvent() {
2019-10-12 08:20:44 +02:00
if (this.selectEntryView) {
this.selectEntryView.show();
}
2016-08-07 19:34:31 +02:00
if (!this.pendingEvent) {
return;
}
logger.debug('processing pending auto-type event');
2017-01-31 07:50:28 +01:00
const evt = this.pendingEvent;
2016-08-07 19:34:31 +02:00
this.pendingEvent = null;
this.processEventWithFilter(evt);
2016-04-08 17:40:00 +02:00
}
};
2019-09-15 14:16:32 +02:00
export { AutoType };