From 627c0f091bdebbdd52a45f5a6dc30d33a5888e6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Uhrbach?= Date: Fri, 8 Mar 2024 20:09:40 +0100 Subject: [PATCH] Improved authentication handling (#57) --- src/deepl.ts | 126 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 94 insertions(+), 32 deletions(-) diff --git a/src/deepl.ts b/src/deepl.ts index cf9bd16..8ee468a 100644 --- a/src/deepl.ts +++ b/src/deepl.ts @@ -1,9 +1,9 @@ import * as vscode from 'vscode'; import * as debug from './debug'; import { state } from './state'; -import { Language, SourceLanguageCode, TargetLanguageCode, TextResult, Translator } from 'deepl-node'; import { EXTENSION_IDENTIFIER } from './constants'; -import { showSourceLanguagePrompt, showTargetLanguagePrompt } from './prompts'; +import { showApiKeyPrompt, showSourceLanguagePrompt, showTargetLanguagePrompt } from './prompts'; +import { AuthorizationError, Language, SourceLanguageCode, TargetLanguageCode, TextResult, Translator } from 'deepl-node'; enum ResolveUnsuccessfulTranslationActions { // eslint-disable-next-line @typescript-eslint/naming-convention @@ -12,6 +12,11 @@ enum ResolveUnsuccessfulTranslationActions { CHANGE_TARGET_LANGUAGE = 'Change target language and try again' } +enum ResolveAuthenticationError { + // eslint-disable-next-line @typescript-eslint/naming-convention + CHANGE_API_KEY = 'Change api key and try again' +} + const cache = { targetLanguages: [] as readonly Language[], sourceLanguages: [] as readonly Language[], @@ -65,38 +70,77 @@ const handleTranslationFailure = async (texts: T, r return results; }; +const handleAuthorizationFailure = async (): Promise<{ apiKeyUpdated: boolean }> => { + let apiKeyUpdated = false; + + const selectedAction = await vscode.window.showWarningMessage( + 'Authentication failed!', + { + detail: 'Please check if you are api key correct.', + modal: true + }, + ResolveAuthenticationError.CHANGE_API_KEY + ); + + if (selectedAction === ResolveAuthenticationError.CHANGE_API_KEY) { + const newApiKey = await showApiKeyPrompt(); + if (newApiKey && newApiKey !== state.apiKey) { + apiKeyUpdated = true; + state.apiKey = newApiKey; + } + } + + return { apiKeyUpdated }; +}; + export async function translate(texts: T, sourceLanguage: SourceLanguageCode | undefined, targetLanguage: TargetLanguageCode, retries: number = 1): Promise { + if (!state.apiKey) { + state.apiKey = await showApiKeyPrompt(); + } + const translator = createTranslator(state.apiKey!); debug.write( sourceLanguage ? `Start translating '${texts}' to '${targetLanguage}'` : `Start translating '${texts}' from '${sourceLanguage}' to '${targetLanguage}'` ); - const results = await translator.translateText( - texts, - sourceLanguage ?? null, - targetLanguage, - { - formality: state.formality || undefined, - glossary: state.glossaryId || undefined, - ignoreTags: state.ignoreTags || undefined, - nonSplittingTags: state.nonSplittingTags || undefined, - splittingTags: state.splittingTags || undefined, - preserveFormatting: state.preserveFormatting || undefined, - splitSentences: state.splitSentences || undefined, - tagHandling: state.tagHandling || undefined + + try { + const results = await translator.translateText( + texts, + sourceLanguage ?? null, + targetLanguage, + { + formality: state.formality || undefined, + glossary: state.glossaryId || undefined, + ignoreTags: state.ignoreTags || undefined, + nonSplittingTags: state.nonSplittingTags || undefined, + splittingTags: state.splittingTags || undefined, + preserveFormatting: state.preserveFormatting || undefined, + splitSentences: state.splitSentences || undefined, + tagHandling: state.tagHandling || undefined + } + ); + const wasTranslationSuccessful = typeof texts === "string" + ? (results as TextResult).text !== texts + : (results as TextResult[]).filter((result, index) => result.text === texts[index]).length === 0; + + return !wasTranslationSuccessful && retries > 0 + ? handleTranslationFailure(texts, results, sourceLanguage, targetLanguage, retries - 1) + : results; + } catch (e: unknown) { + if (e instanceof AuthorizationError && retries > 0) { + const { apiKeyUpdated } = await handleAuthorizationFailure(); + if (apiKeyUpdated) { + return translate(texts, sourceLanguage, targetLanguage, retries - 1); + } } - ); - const wasTranslationSuccessful = typeof texts === "string" - ? (results as TextResult).text !== texts - : (results as TextResult[]).filter((result, index) => result.text === texts[index]).length === 0; - return !wasTranslationSuccessful && retries > 0 - ? handleTranslationFailure(texts, results, sourceLanguage, targetLanguage, retries) - : results; + throw e; + } } -export async function getTargetLanguages() { +export async function getTargetLanguages(retries: number = 1) { if (!state.apiKey) { debug.write('Could not load target languages - api key is not configured!'); return []; @@ -106,13 +150,22 @@ export async function getTargetLanguages() { return cache.targetLanguages; } - const translator = createTranslator(state.apiKey); - const languages = await translator.getTargetLanguages(); - cache.targetLanguages = languages; - return languages; + try { + const translator = createTranslator(state.apiKey); + cache.targetLanguages = await translator.getTargetLanguages(); + } catch (e: unknown) { + if (e instanceof AuthorizationError && retries > 0) { + const { apiKeyUpdated } = await handleAuthorizationFailure(); + if (apiKeyUpdated) { + return getTargetLanguages(retries - 1); + } + } + } + + return cache.targetLanguages; } -export async function getSourceLanguages() { +export async function getSourceLanguages(retries: number = 1) { if (!state.apiKey) { debug.write('Could not load source languages - api key is not configured!'); return []; @@ -122,8 +175,17 @@ export async function getSourceLanguages() { return cache.sourceLanguages; } - const translator = createTranslator(state.apiKey); - const languages = await translator.getSourceLanguages(); - cache.sourceLanguages = languages; - return languages; + try { + const translator = createTranslator(state.apiKey); + cache.sourceLanguages = await translator.getSourceLanguages(); + } catch (e) { + if (e instanceof AuthorizationError && retries > 0) { + const { apiKeyUpdated } = await handleAuthorizationFailure(); + if (apiKeyUpdated) { + return getSourceLanguages(retries - 1); + } + } + } + + return cache.sourceLanguages; } \ No newline at end of file