creating new groups form browser extensions

This commit is contained in:
antelle 2021-04-26 20:47:43 +02:00
parent 7a35ebec73
commit 3ca5f94b9b
No known key found for this signature in database
GPG Key ID: 63C9777AAB7C563C
6 changed files with 145 additions and 55 deletions

View File

@ -9,6 +9,7 @@ import { Locale } from 'util/locale';
import { RuntimeInfo } from 'const/runtime-info';
import { KnownAppVersions } from 'const/known-app-versions';
import { ExtensionConnectView } from 'views/extension/extension-connect-view';
import { ExtensionCreateGroupView } from 'views/extension/extension-create-group-view';
import { RuntimeDataModel } from 'models/runtime-data-model';
import { AppSettingsModel } from 'models/app-settings-model';
import { Timeouts } from 'const/timeouts';
@ -146,7 +147,6 @@ async function checkContentRequestPermissions(request) {
Timeouts.KeeWebConnectRequest
);
} catch {
Launcher?.hideApp();
throw makeError(Errors.noOpenFiles);
}
} else {
@ -159,57 +159,68 @@ async function checkContentRequestPermissions(request) {
return;
}
return new Promise((resolve, reject) => {
if (Alerts.alertDisplayed) {
return reject(new Error(Locale.extensionErrorAlertDisplayed));
if (Alerts.alertDisplayed) {
throw new Error(Locale.extensionErrorAlertDisplayed);
}
focusKeeWeb();
const config = RuntimeDataModel.extensionConnectConfig;
const files = appModel.files.map((f) => ({
id: f.id,
name: f.name,
checked: !config || config.allFiles || config.files.includes(f.id)
}));
if (!files.some((f) => f.checked)) {
for (const f of files) {
f.checked = true;
}
}
focusKeeWeb();
const extensionName = client.connection.appName
? `${client.connection.extensionName} (${client.connection.appName})`
: client.connection.extensionName;
const config = RuntimeDataModel.extensionConnectConfig;
const files = appModel.files.map((f) => ({
id: f.id,
name: f.name,
checked: !config || config.allFiles || config.files.includes(f.id)
}));
if (!files.some((f) => f.checked)) {
for (const f of files) {
f.checked = true;
}
}
const extensionConnectView = new ExtensionConnectView({
extensionName,
identityVerified: !Launcher,
files,
allFiles: config?.allFiles ?? true,
askGet: config?.askGet || 'multiple'
});
const extensionName = client.connection.appName
? `${client.connection.extensionName} (${client.connection.appName})`
: client.connection.extensionName;
const extensionConnectView = new ExtensionConnectView({
extensionName,
identityVerified: !Launcher,
files,
allFiles: config?.allFiles ?? true,
askGet: config?.askGet || 'multiple'
try {
await alertWithTimeout({
header: Locale.extensionConnectHeader,
icon: 'exchange-alt',
buttons: [Alerts.buttons.allow, Alerts.buttons.deny],
view: extensionConnectView,
wide: true,
opaque: true
});
} catch (e) {
client.permissionsDenied = true;
Events.emit('browser-extension-sessions-changed');
throw e;
}
RuntimeDataModel.extensionConnectConfig = extensionConnectView.config;
client.permissions = extensionConnectView.config;
Events.emit('browser-extension-sessions-changed');
}
function alertWithTimeout(config) {
return new Promise((resolve, reject) => {
let inactivityTimer = 0;
const alert = Alerts.alert({
header: Locale.extensionConnectHeader,
icon: 'exchange-alt',
view: extensionConnectView,
wide: true,
opaque: true,
buttons: [Alerts.buttons.allow, Alerts.buttons.deny],
success: () => {
...config,
success: (res) => {
clearTimeout(inactivityTimer);
RuntimeDataModel.extensionConnectConfig = extensionConnectView.config;
client.permissions = extensionConnectView.config;
Events.emit('browser-extension-sessions-changed');
resolve();
resolve(res);
},
cancel: () => {
client.permissionsDenied = true;
clearTimeout(inactivityTimer);
Events.emit('browser-extension-sessions-changed');
reject(makeError(Errors.userRejected));
}
});
@ -217,9 +228,6 @@ async function checkContentRequestPermissions(request) {
inactivityTimer = setTimeout(() => {
alert.closeWithResult('');
}, Timeouts.KeeWebConnectRequest);
}).catch((e) => {
Launcher?.hideApp();
throw e;
});
}
@ -461,15 +469,39 @@ const ProtocolHandlers = {
}
}
// TODO: create a new group
throw new Error('Not implemented');
const createGroupView = new ExtensionCreateGroupView({
groupPath: groupNames.join(' / '),
files: files.map((f, ix) => ({ id: f.id, name: f.name, selected: ix === 0 }))
});
// return encryptResponse(request, {
// success: 'true',
// version: getVersion(request),
// name: newGroup.title,
// uuid: kdbxweb.ByteUtils.bytesToHex(newGroup.group.uuid.bytes)
// });
await alertWithTimeout({
header: Locale.extensionNewGroupHeader,
icon: 'folder',
buttons: [Alerts.buttons.allow, Alerts.buttons.deny],
view: createGroupView
});
const selectedFile = files.find((f) => f.id === createGroupView.selectedFile);
let newGroup = selectedFile.groups[0];
const pendingGroups = [...groupNames];
while (pendingGroups.length) {
const title = pendingGroups.shift();
const item = newGroup.items.find((g) => g.title === title);
if (item) {
newGroup = item;
} else {
newGroup = appModel.createNewGroupWithName(newGroup, selectedFile, title);
}
}
return encryptResponse(request, {
success: 'true',
version: getVersion(request),
name: newGroup.title,
uuid: kdbxweb.ByteUtils.bytesToHex(newGroup.group.uuid.bytes)
});
}
};
@ -521,18 +553,27 @@ const ProtocolImpl = {
},
async handleRequest(request, connectionInfo) {
const appWindowWasFocused = Launcher?.isAppFocused();
let result;
try {
const handler = ProtocolHandlers[request.action];
if (!handler) {
throw new Error(`Handler not found: ${request.action}`);
}
return await handler(request, connectionInfo);
result = await handler(request, connectionInfo);
} catch (e) {
if (!e.code) {
logger.error(`Error in handler ${request.action}`, e);
}
return this.errorToResponse(e, request);
result = this.errorToResponse(e, request);
}
if (!appWindowWasFocused && Launcher?.isAppFocused()) {
Launcher.hideApp();
}
return result;
},
get sessions() {

View File

@ -774,7 +774,7 @@
"extensionErrorNoOpenFiles": "No open files",
"extensionErrorUserRejected": "The request was denied",
"extensionErrorAlertDisplayed": "Cannot ask user a question now, please try again",
"extensionErrorAlertDisplayed": "Cannot ask a question now because there's another alert displayed, please try again",
"extensionConnectHeader": "Extension data exchange",
"extensionConnectIntro": "A browser extension that identifies itself as {} tries to exchange data with KeeWeb.",
"extensionConnectUnknownActivity": "KeeWeb doesn't verify that the connected application is what it pretends to be. Approve the request only if you recognize this activity.",
@ -789,5 +789,8 @@
"extensionConnectAskSaveAuto": "when it's not possible to save automatically",
"extensionConnectSettingsAreForSession": "Settings you select here are valid only for the active session. You can view and manage sessions in KeeWeb settings.",
"extensionUnlockMessage": "Unlock to connect a browser extension",
"extensionNewGroup": ""
"extensionNewGroupHeader": "Create a new group?",
"extensionNewGroupBody": "A browser extension is trying to create a new group. Allow this?",
"extensionNewGroupPath": "Group path",
"extensionNewGroupFile": "This group will be created in:"
}

View File

@ -471,6 +471,12 @@ class AppModel {
return GroupModel.newGroup(sel.group, sel.file);
}
createNewGroupWithName(group, file, name) {
const newGroup = GroupModel.newGroup(group, file);
newGroup.setName(name);
return newGroup;
}
createNewTemplateEntry() {
const file = this.getFirstSelectedGroupForCreation().file;
const group = file.getEntryTemplatesGroup() || file.createEntryTemplatesGroup();

View File

@ -0,0 +1,26 @@
import { View } from 'framework/views/view';
import template from 'templates/extension/extension-create-group.hbs';
class ExtensionCreateGroupView extends View {
template = template;
events = {
'change #extension-create-group__file': 'fileChanged'
};
constructor(model) {
super(model);
this.selectedFile = model.files.find((f) => f.selected).id;
}
render() {
super.render(this.model);
}
fileChanged(e) {
this.selectedFile = e.target.value;
}
}
export { ExtensionCreateGroupView };

View File

@ -41,7 +41,7 @@ class ModalView extends View {
document.activeElement.blur();
}, 20);
if (this.model.view) {
this.model.view.parent = '.modal__body';
this.model.view.parent = this.el.querySelector('.modal__body');
this.model.view.render();
}
}

View File

@ -0,0 +1,14 @@
<div class="extension-create-group">
<p>{{res 'extensionNewGroupBody'}}</p>
<p>{{res 'extensionNewGroupPath'}}: {{groupPath}}</p>
<div>
<label for="extension-create-group__file">{{res 'extensionNewGroupFile'}}</label>
<select id="extension-create-group__file" class="input-base">
{{#each files as |file|}}
<option value="{{file.id}}" {{#if file.selected}}selected{{/if}}>
{{file.name}}
</option>
{{/each}}
</select>
</div>
</div>