Skip to content

Commit

Permalink
feat: simplify wording for user messages
Browse files Browse the repository at this point in the history
- Extract UI message box logic from core part
- Remove message for key changed related event
- Replace some long message with two existing, shorter message
- Avoid double confirm for cache delete operation
  • Loading branch information
wdhongtw committed Jan 17, 2025
1 parent e6c475d commit 8c1832e
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 55 deletions.
11 changes: 2 additions & 9 deletions l10n/bundle.l10n.zh-cn.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@
"Don't ask again": "不再询问",
"OK": "好的",
"Open setting": "打开设置",
", ": "",
"Unable to retrieve any key in current folder.": "无法检索当前文件夹中的任何密钥。",
"Input the passphrase for the signing key": "输入密钥密码",
"Input the passphrase for the signing key, passphrase cache enabled": "输入密钥密码,密码安全存储功能已开启",
"For the key associated with {0}": "该密钥关联用户:{0}",
"Key unlocked, and the passphrase is stored in the SecretStorage of VSCode.": "密钥已解锁,密码已被安全存储在 VSCode 的机密存储区中。",
"The passphrase is stored in the SecretStorage of VSCode.": "密码已被安全存储在 VSCode 的机密存储区中。",
"Key unlocked.": "密钥已解锁。",
"Failed to unlock: {0}": "密钥解锁失败:{0}",
"There is no cached passphrase for your current key.": "您的当前密钥的密码并未被存储",
Expand All @@ -20,14 +18,9 @@
"Current key": "当前密钥",
"Rest keys": "其他密钥",
"List of keys with stored passphrases:": "已存储密码的密钥列表:",
"Do you really want to clear all your cached passphrase? This action CANNOT be reverted.": "您确认真的要删除您存储中的全部密码吗?该操作无法被撤销。",
"All Your cached passphrase has been cleared.": "您的全部密码已被从机密存储区中删除。",
"Key changed, and unlocked automatically using the previous-used stored passphrase.": "密钥已变更,并被通过机密存储区中的关联密码自动解锁。",
"Key re-locked, and unlocked automatically using the previous-used stored passphrase.": "密钥被自动锁定,现已被通过机密存储区中的关联密码自动解锁。",
"Key unlocked automatically using the previous-used stored passphrase.": "密钥已被通过机密存储区中的关联密码自动解锁。",
"Key changed, but the previous-used stored passphrase for this key is unable to unlock the key, so the passphrase has been deleted and you need to unlock the key manually.": "密钥已变更,但机密存储区中的关联密码无法解锁该密钥,故该密码已被删除,您需要手动解锁该密钥。",
"Key re-locked automatically, but the previous-used stored passphrase is unable to unlock the key, so the passphrase has been deleted and you need to unlock the key manually.": "密钥被自动锁定,但机密存储区中的关联密码无法解锁该密钥,故该密码已被删除,您需要手动解锁该密钥",
"The previous-used stored passphrase is unable to unlock current key, so the passphrase has been deleted and you need to unlock the key manually.": "机密存储区中的关联密码无法解锁当前密钥,故该密码已被删除,您需要手动解锁该密钥。",
"The previous-used stored passphrase is unable to unlock current key.": "机密存储区中的关联密码无法解锁当前密钥。",
"Key changed.": "密钥已变更。",
"Key re-locked.": "密钥被自动锁定。",
"No active folder": "当前无活动中的文件夹。",
Expand Down
2 changes: 1 addition & 1 deletion package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"gpgIndicator.l10n.commands.listPassphraseCache": "List your passphrase cache",
"gpgIndicator.l10n.configuration.statusRefreshInterval": "Background refresh interval (in seconds) for key status.",
"gpgIndicator.l10n.configuration.outputLogLevel": "Log level for extension log output.",
"gpgIndicator.l10n.configuration.enableSecurelyPassphraseCache": "Specifies whether to use the `SecretStorage` of VSCode to store your passphrase or not. [Read more](https://github.com/wdhongtw/vscode-gpg-indicator#passphrase-cache).\n\n**WARNING**: Turn off this option will clear all your passphrase cache, and cannot be reverted.",
"gpgIndicator.l10n.configuration.enableSecurelyPassphraseCache": "Specifies whether to use the `SecretStorage` of VSCode to store your passphrase or not. [Read more](https://github.com/wdhongtw/vscode-gpg-indicator#passphrase-cache).",
"gpgIndicator.l10n.configuration.statusStyle": "Specifies what to show about the current key in the status bar element",
"gpgIndicator.l10n.configuration.statusStyle.enumDescriptions.fingerprintWithUserId": "Show both fingerprint and user id (if any), if no user id found, show fingerprint only.\n\nExample: `0123456789ABCDEF - Example User <example@example.com>`",
"gpgIndicator.l10n.configuration.statusStyle.enumDescriptions.fingerprint": "Show fingerprint only.\n\nExample: `0123456789ABCDEF`",
Expand Down
2 changes: 1 addition & 1 deletion package.nls.zh-cn.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"gpgIndicator.l10n.commands.listPassphraseCache": "列举存有密码的密钥",
"gpgIndicator.l10n.configuration.statusRefreshInterval": "后台刷新间隔,以秒为单位。",
"gpgIndicator.l10n.configuration.outputLogLevel": "扩展日志输出等级,低于该等级的日志将不会被输出。",
"gpgIndicator.l10n.configuration.enableSecurelyPassphraseCache": "启用密钥密码的机密存储区存储功能,[查看详情](https://github.com/wdhongtw/vscode-gpg-indicator#passphrase-cache)。\n\n**警告**:禁用该功能会删除所有已存储的密钥,且无法撤销。",
"gpgIndicator.l10n.configuration.enableSecurelyPassphraseCache": "启用密钥密码的机密存储区存储功能,[查看详情](https://github.com/wdhongtw/vscode-gpg-indicator#passphrase-cache)。",
"gpgIndicator.l10n.configuration.statusStyle": "选择状态栏按钮显示的密钥信息内容。",
"gpgIndicator.l10n.configuration.statusStyle.enumDescriptions.fingerprintWithUserId": "同时显示密钥指纹和关联用户信息,若无关联用户信息则只显示密钥指纹。\n\n例如:`0123456789ABCDEF - Example User <example@example.com>`",
"gpgIndicator.l10n.configuration.statusStyle.enumDescriptions.fingerprint": "只显示密钥指纹。\n\n例如:`0123456789ABCDEF`",
Expand Down
50 changes: 34 additions & 16 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import * as util from 'util';
import * as git from './indicator/git';
import * as gpg from './indicator/gpg';
import * as locker from './indicator/locker';
import * as core from './manager';
import { Logger } from "./manager";
import KeyStatusManager from "./manager";
import { Storage, KeyStatusEvent } from "./manager";
Expand Down Expand Up @@ -77,6 +78,27 @@ async function generateKeyList(secretStorage: PassphraseStorage, keyStatusManage
return items;
}

/** MessageEventReceiver transform event into VSCode information message. */
class MessageEventReceiver implements core.EventReceiver {

async onEvent(event: core.Event): Promise<void> {
const message: string = (() => {
switch (event) {
case core.Event.StoredPassphraseUnlockSucceed:
return vscode.l10n.t(m['keyAutomaticallyUnlocked']);
case core.Event.StoredPassphraseUnlockFailed:
return vscode.l10n.t(m['keyAutomaticallyUnlockFailed']);
case core.Event.StoredPassphraseBeDeleted:
return vscode.l10n.t(m['passphraseDeleted']);
case core.Event.LockedStateEntered:
return vscode.l10n.t(m['keyRelocked']);
}
})();

await vscode.window.showInformationMessage(message);
}
}

export async function activate(context: vscode.ExtensionContext) {
const masterKey = await initializeMasterKey(context.secrets);

Expand All @@ -99,6 +121,7 @@ export async function activate(context: vscode.ExtensionContext) {
new git.CliGit(),
new gpg.CliGpg(logger),
secretStorage,
new MessageEventReceiver(),
configuration.get<boolean>('enablePassphraseCache', false),
vscode.workspace.isTrusted,
os.homedir(),
Expand All @@ -118,9 +141,7 @@ export async function activate(context: vscode.ExtensionContext) {
return;
}
const passphrase = await vscode.window.showInputBox({
prompt: keyStatusManager.enablePassphraseCache
? vscode.l10n.t(m['passphraseInputPromptTitleWhenSecurelyPassphraseCacheEnabled'])
: vscode.l10n.t(m['passphraseInputPromptTitle']),
prompt: vscode.l10n.t(m['passphraseInputPromptTitle']),
password: true,
placeHolder: currentKey.userId
? vscode.l10n.t(m['keyDescriptionWithUserId'], currentKey.userId)
Expand All @@ -138,11 +159,10 @@ export async function activate(context: vscode.ExtensionContext) {

if (keyStatusManager.enablePassphraseCache) {
await secretStorage.set(currentKey.fingerprint, passphrase);
vscode.window.showInformationMessage(vscode.l10n.t(m['keyUnlockedWithCachedPassphrase']));
} else {
vscode.window.showInformationMessage(vscode.l10n.t(m['keyUnlocked']));
await introduceCacheFeature(context);
vscode.window.showInformationMessage(vscode.l10n.t(m['passphraseStored']));
}
vscode.window.showInformationMessage(vscode.l10n.t(m['keyUnlocked']));
await introduceCacheFeature(context);
}));
keyStatusItem.tooltip = 'Unlock this key';
keyStatusItem.command = commandId;
Expand Down Expand Up @@ -186,14 +206,9 @@ export async function activate(context: vscode.ExtensionContext) {
vscode.window.showInformationMessage(vscode.l10n.t(m['noCachedPassphrase']));
return;
}
if ((await vscode.window.showInformationMessage<vscode.MessageItem>(
vscode.l10n.t(m["passphraseClearanceConfirm"]),
{ modal: true },
{ title: actions.YES },
{ title: actions.NO, isCloseAffordance: true },
))?.title !== actions.YES) {
return;
}

// We do not confirm again whether user really want to delete, just trust our user.

await Promise.all([...secretStorage].map((key) => secretStorage.delete(key)));
vscode.window.showInformationMessage(vscode.l10n.t(m['passphraseCleared']));
}));
Expand Down Expand Up @@ -299,6 +314,9 @@ export async function deactivate() {

async function introduceCacheFeature(context: vscode.ExtensionContext) {
const configuration = vscode.workspace.getConfiguration('gpgIndicator');
if (configuration.get<boolean>('enablePassphraseCache', false)) {
return;
}

if (await context.globalState.get("user:is-cache-notice-read")) {
return;
Expand All @@ -315,7 +333,7 @@ async function introduceCacheFeature(context: vscode.ExtensionContext) {
}
await context.globalState.update("user:is-cache-notice-read", true);
if (result === actions.YES) {
configuration.update("enablePassphraseCache", true, true);
await configuration.update("enablePassphraseCache", true, true);
}

let postMessage: string;
Expand Down
51 changes: 32 additions & 19 deletions src/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,19 +64,13 @@ export default class KeyStatusManager {
private git: GitAdapter,
private gpg: GpgAdapter,
private secretStorage: Storage,
private receiver: EventReceiver,
public enablePassphraseCache: boolean,
private isWorkspaceTrusted: boolean,
private defaultFolder: string,
) {
}

private show(isChanged: boolean, changedMsg: string, defaultMsg: string) {
vscode.window.showInformationMessage(isChanged
? vscode.l10n.t(changedMsg)
: vscode.l10n.t(defaultMsg),
);
}

/** Trigger key status update once, coroutine-safe is ensured. */
async syncStatus(): Promise<void> {
await this.syncStatusLock.with(async () => {
Expand Down Expand Up @@ -144,22 +138,15 @@ export default class KeyStatusManager {

try {
await this.unlockCurrentKey(passphrase);
if (isUnlockedPrev) {
this.show(isChanged, m['keyChangedAndAutomaticallyUnlocked'], m['keyRelockedAndAutomaticallyUnlocked']);
} else {
this.show(isChanged, m['keyChangedAndAutomaticallyUnlocked'], m['keyAutomaticallyUnlocked']);
}
await this.receiver.onEvent(Event.StoredPassphraseUnlockSucceed);
} catch (err) {
if (!(err instanceof Error)) {
throw err;
}
this.logger.error(`Cannot unlock the key with the cached passphrase: ${err.message}`);
await this.secretStorage.delete(keyInfo.fingerprint);
if (isUnlockedPrev) {
this.show(isChanged, m['keyChangedButAutomaticallyUnlockFailed'], m['keyRelockedButAutomaticallyUnlockFailed']);
} else {
this.show(isChanged, m['keyChangedButAutomaticallyUnlockFailed'], m['keyAutomaticallyUnlockFailed']);
}
await this.receiver.onEvent(Event.StoredPassphraseBeDeleted);
await this.receiver.onEvent(Event.StoredPassphraseUnlockFailed);
}

return await this.gpg.isKeyUnlocked(keyInfo.keygrip);
Expand All @@ -173,8 +160,11 @@ export default class KeyStatusManager {
*/
private async showInfoOnly(isChanged: boolean, isUnlockedPrev: boolean, keyInfo: GpgKeyInfo): Promise<boolean> {
const isUnlocked = await this.gpg.isKeyUnlocked(keyInfo.keygrip);
if (isUnlockedPrev && !isUnlocked) {
this.show(isChanged, m['keyChanged'], m['keyRelocked']);

// We do not notify key changed event, since that it could be noisy potentially.
// For the same key, we only notify the change from "unlocked" to "locked".
if (!isChanged && isUnlockedPrev && !isUnlocked) {
await this.receiver.onEvent(Event.LockedStateEntered);
}

return isUnlocked;
Expand Down Expand Up @@ -276,6 +266,29 @@ export default class KeyStatusManager {
}
}

/** Types for events from key manager. */
export enum Event {

/** Use stored passphrase to unlock and succeed. */
StoredPassphraseUnlockSucceed,

/** Use stored passphrase to unlock buf failed. */
StoredPassphraseUnlockFailed,

/** Previously stored passphrase be deleted. */
StoredPassphraseBeDeleted,

/** Some key changed into locked state. */
LockedStateEntered,
};

/** Receiver interface for events from key manager. */
export interface EventReceiver {

/** Handle given event. */
onEvent(event: Event): Promise<void>;
}

/** The abstract storage for our application, focusing on string type. */
export interface Storage {

Expand Down
11 changes: 2 additions & 9 deletions src/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@ export const m = {
"actionDoNotAskAgain": "Don't ask again",
"actionOK": "OK",
"actionOpenSetting": "Open setting",
"separator": ", ",
"noKeyInCurrentFolder": "Unable to retrieve any key in current folder.",
"passphraseInputPromptTitle": "Input the passphrase for the signing key",
"passphraseInputPromptTitleWhenSecurelyPassphraseCacheEnabled": "Input the passphrase for the signing key, passphrase cache enabled",
"keyDescriptionWithUserId": "For the key associated with {0}",
"keyUnlockedWithCachedPassphrase": "Key unlocked, and the passphrase is stored in the SecretStorage of VSCode.",
"passphraseStored": "The passphrase is stored in the SecretStorage of VSCode.",
"keyUnlocked": "Key unlocked.",
"keyUnlockFailedWithId": "Failed to unlock: {0}",
"noCachedPassphraseForCurrentKey": "There is no cached passphrase for your current key.",
Expand All @@ -20,14 +18,9 @@ export const m = {
"currentKey": "Current key",
"restKey": "Rest keys",
"cachedPassphraseList": "List of keys with stored passphrases:",
"passphraseClearanceConfirm": "Do you really want to clear all your cached passphrase? This action CANNOT be reverted.",
"passphraseCleared": "All Your cached passphrase has been cleared.",
"keyChangedAndAutomaticallyUnlocked": "Key changed, and unlocked automatically using the previous-used stored passphrase.",
"keyRelockedAndAutomaticallyUnlocked": "Key re-locked, and unlocked automatically using the previous-used stored passphrase.",
"keyAutomaticallyUnlocked": "Key unlocked automatically using the previous-used stored passphrase.",
"keyChangedButAutomaticallyUnlockFailed": "Key changed, but the previous-used stored passphrase for this key is unable to unlock the key, so the passphrase has been deleted and you need to unlock the key manually.",
"keyRelockedButAutomaticallyUnlockFailed": "Key re-locked automatically, but the previous-used stored passphrase is unable to unlock the key, so the passphrase has been deleted and you need to unlock the key manually.",
"keyAutomaticallyUnlockFailed": "The previous-used stored passphrase is unable to unlock current key, so the passphrase has been deleted and you need to unlock the key manually.",
"keyAutomaticallyUnlockFailed": "The previous-used stored passphrase is unable to unlock current key.",
"keyChanged": "Key changed.",
"keyRelocked": "Key re-locked.",
"noActiveFolder": "No active folder",
Expand Down

0 comments on commit 8c1832e

Please sign in to comment.