mirror of https://github.com/keeweb/keeweb.git
creating new groups form browser extensions
This commit is contained in:
parent
7a35ebec73
commit
3ca5f94b9b
|
@ -9,6 +9,7 @@ import { Locale } from 'util/locale';
|
||||||
import { RuntimeInfo } from 'const/runtime-info';
|
import { RuntimeInfo } from 'const/runtime-info';
|
||||||
import { KnownAppVersions } from 'const/known-app-versions';
|
import { KnownAppVersions } from 'const/known-app-versions';
|
||||||
import { ExtensionConnectView } from 'views/extension/extension-connect-view';
|
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 { RuntimeDataModel } from 'models/runtime-data-model';
|
||||||
import { AppSettingsModel } from 'models/app-settings-model';
|
import { AppSettingsModel } from 'models/app-settings-model';
|
||||||
import { Timeouts } from 'const/timeouts';
|
import { Timeouts } from 'const/timeouts';
|
||||||
|
@ -146,7 +147,6 @@ async function checkContentRequestPermissions(request) {
|
||||||
Timeouts.KeeWebConnectRequest
|
Timeouts.KeeWebConnectRequest
|
||||||
);
|
);
|
||||||
} catch {
|
} catch {
|
||||||
Launcher?.hideApp();
|
|
||||||
throw makeError(Errors.noOpenFiles);
|
throw makeError(Errors.noOpenFiles);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -159,57 +159,68 @@ async function checkContentRequestPermissions(request) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
if (Alerts.alertDisplayed) {
|
||||||
if (Alerts.alertDisplayed) {
|
throw new Error(Locale.extensionErrorAlertDisplayed);
|
||||||
return reject(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 extensionConnectView = new ExtensionConnectView({
|
||||||
const files = appModel.files.map((f) => ({
|
extensionName,
|
||||||
id: f.id,
|
identityVerified: !Launcher,
|
||||||
name: f.name,
|
files,
|
||||||
checked: !config || config.allFiles || config.files.includes(f.id)
|
allFiles: config?.allFiles ?? true,
|
||||||
}));
|
askGet: config?.askGet || 'multiple'
|
||||||
if (!files.some((f) => f.checked)) {
|
});
|
||||||
for (const f of files) {
|
|
||||||
f.checked = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const extensionName = client.connection.appName
|
try {
|
||||||
? `${client.connection.extensionName} (${client.connection.appName})`
|
await alertWithTimeout({
|
||||||
: client.connection.extensionName;
|
header: Locale.extensionConnectHeader,
|
||||||
|
icon: 'exchange-alt',
|
||||||
const extensionConnectView = new ExtensionConnectView({
|
buttons: [Alerts.buttons.allow, Alerts.buttons.deny],
|
||||||
extensionName,
|
view: extensionConnectView,
|
||||||
identityVerified: !Launcher,
|
wide: true,
|
||||||
files,
|
opaque: true
|
||||||
allFiles: config?.allFiles ?? true,
|
|
||||||
askGet: config?.askGet || 'multiple'
|
|
||||||
});
|
});
|
||||||
|
} 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;
|
let inactivityTimer = 0;
|
||||||
|
|
||||||
const alert = Alerts.alert({
|
const alert = Alerts.alert({
|
||||||
header: Locale.extensionConnectHeader,
|
...config,
|
||||||
icon: 'exchange-alt',
|
success: (res) => {
|
||||||
view: extensionConnectView,
|
|
||||||
wide: true,
|
|
||||||
opaque: true,
|
|
||||||
buttons: [Alerts.buttons.allow, Alerts.buttons.deny],
|
|
||||||
success: () => {
|
|
||||||
clearTimeout(inactivityTimer);
|
clearTimeout(inactivityTimer);
|
||||||
RuntimeDataModel.extensionConnectConfig = extensionConnectView.config;
|
resolve(res);
|
||||||
client.permissions = extensionConnectView.config;
|
|
||||||
Events.emit('browser-extension-sessions-changed');
|
|
||||||
resolve();
|
|
||||||
},
|
},
|
||||||
cancel: () => {
|
cancel: () => {
|
||||||
client.permissionsDenied = true;
|
|
||||||
clearTimeout(inactivityTimer);
|
clearTimeout(inactivityTimer);
|
||||||
Events.emit('browser-extension-sessions-changed');
|
|
||||||
reject(makeError(Errors.userRejected));
|
reject(makeError(Errors.userRejected));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -217,9 +228,6 @@ async function checkContentRequestPermissions(request) {
|
||||||
inactivityTimer = setTimeout(() => {
|
inactivityTimer = setTimeout(() => {
|
||||||
alert.closeWithResult('');
|
alert.closeWithResult('');
|
||||||
}, Timeouts.KeeWebConnectRequest);
|
}, Timeouts.KeeWebConnectRequest);
|
||||||
}).catch((e) => {
|
|
||||||
Launcher?.hideApp();
|
|
||||||
throw e;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -461,15 +469,39 @@ const ProtocolHandlers = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: create a new group
|
const createGroupView = new ExtensionCreateGroupView({
|
||||||
throw new Error('Not implemented');
|
groupPath: groupNames.join(' / '),
|
||||||
|
files: files.map((f, ix) => ({ id: f.id, name: f.name, selected: ix === 0 }))
|
||||||
|
});
|
||||||
|
|
||||||
// return encryptResponse(request, {
|
await alertWithTimeout({
|
||||||
// success: 'true',
|
header: Locale.extensionNewGroupHeader,
|
||||||
// version: getVersion(request),
|
icon: 'folder',
|
||||||
// name: newGroup.title,
|
buttons: [Alerts.buttons.allow, Alerts.buttons.deny],
|
||||||
// uuid: kdbxweb.ByteUtils.bytesToHex(newGroup.group.uuid.bytes)
|
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) {
|
async handleRequest(request, connectionInfo) {
|
||||||
|
const appWindowWasFocused = Launcher?.isAppFocused();
|
||||||
|
|
||||||
|
let result;
|
||||||
try {
|
try {
|
||||||
const handler = ProtocolHandlers[request.action];
|
const handler = ProtocolHandlers[request.action];
|
||||||
if (!handler) {
|
if (!handler) {
|
||||||
throw new Error(`Handler not found: ${request.action}`);
|
throw new Error(`Handler not found: ${request.action}`);
|
||||||
}
|
}
|
||||||
return await handler(request, connectionInfo);
|
result = await handler(request, connectionInfo);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!e.code) {
|
if (!e.code) {
|
||||||
logger.error(`Error in handler ${request.action}`, e);
|
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() {
|
get sessions() {
|
||||||
|
|
|
@ -774,7 +774,7 @@
|
||||||
|
|
||||||
"extensionErrorNoOpenFiles": "No open files",
|
"extensionErrorNoOpenFiles": "No open files",
|
||||||
"extensionErrorUserRejected": "The request was denied",
|
"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",
|
"extensionConnectHeader": "Extension data exchange",
|
||||||
"extensionConnectIntro": "A browser extension that identifies itself as {} tries to exchange data with KeeWeb.",
|
"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.",
|
"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",
|
"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.",
|
"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",
|
"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:"
|
||||||
}
|
}
|
||||||
|
|
|
@ -471,6 +471,12 @@ class AppModel {
|
||||||
return GroupModel.newGroup(sel.group, sel.file);
|
return GroupModel.newGroup(sel.group, sel.file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createNewGroupWithName(group, file, name) {
|
||||||
|
const newGroup = GroupModel.newGroup(group, file);
|
||||||
|
newGroup.setName(name);
|
||||||
|
return newGroup;
|
||||||
|
}
|
||||||
|
|
||||||
createNewTemplateEntry() {
|
createNewTemplateEntry() {
|
||||||
const file = this.getFirstSelectedGroupForCreation().file;
|
const file = this.getFirstSelectedGroupForCreation().file;
|
||||||
const group = file.getEntryTemplatesGroup() || file.createEntryTemplatesGroup();
|
const group = file.getEntryTemplatesGroup() || file.createEntryTemplatesGroup();
|
||||||
|
|
|
@ -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 };
|
|
@ -41,7 +41,7 @@ class ModalView extends View {
|
||||||
document.activeElement.blur();
|
document.activeElement.blur();
|
||||||
}, 20);
|
}, 20);
|
||||||
if (this.model.view) {
|
if (this.model.view) {
|
||||||
this.model.view.parent = '.modal__body';
|
this.model.view.parent = this.el.querySelector('.modal__body');
|
||||||
this.model.view.render();
|
this.model.view.render();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
Loading…
Reference in New Issue