diff --git a/src/js/handlers/remote_connect.js b/src/js/handlers/remote_connect.js index f2c38ae..b627b68 100644 --- a/src/js/handlers/remote_connect.js +++ b/src/js/handlers/remote_connect.js @@ -41,8 +41,7 @@ export async function remoteCallback(uRL) { refreshToken: resultToken.refresh_token, }; await localRepo.set({ remoteConnectInfo }); - const { profile } = await oauthClient.getUserConfig(resultToken.id_token); - return { profile }; + return await oauthClient.getUserConfig(resultToken.id_token); } export async function getRemoteConnectInfo() { @@ -58,7 +57,6 @@ export function deleteRemoteConnectInfo() { export async function deleteRefreshTokenFromRemoteConnectInfo() { const localRepo = StorageProvider.getLocalRepository(); - localRepo.delete(['remoteConnectInfo']); const { remoteConnectInfo } = await localRepo.get(['remoteConnectInfo']); delete remoteConnectInfo.refreshToken; await localRepo.set({ remoteConnectInfo }); diff --git a/src/js/handlers/update_profiles.js b/src/js/handlers/update_profiles.js index 5812f4b..a817931 100644 --- a/src/js/handlers/update_profiles.js +++ b/src/js/handlers/update_profiles.js @@ -1,10 +1,9 @@ import { nowEpochSeconds } from "../lib/util.js"; import { DataProfilesSplitter } from "../lib/data_profiles_splitter.js"; -import { writeProfileSetToTable, writeProfileItemsToTable, refreshDB } from "../lib/profile_db.js"; +import { writeProfileItemsToTable, refreshDB } from "../lib/profile_db.js"; import { StorageProvider } from "../lib/storage_repository.js"; import { saveConfigIni } from "../lib/config_ini.js"; -import { OAuthClient } from "../remote/oauth-client.js"; -import { deleteRefreshTokenFromRemoteConnectInfo } from "./remote_connect.js"; +import { reloadConfig } from "../lib/reload-config.js"; export async function updateProfilesTable() { const syncRepo = StorageProvider.getSyncRepository(); @@ -14,15 +13,10 @@ export async function updateProfilesTable() { const { profilesTableUpdated = 0, remoteConnectInfo = null } = await localRepo.get(['profilesTableUpdated', 'remoteConnectInfo']); if (remoteConnectInfo) { - const oaClient = new OAuthClient(remoteConnectInfo.subdomain, remoteConnectInfo.clientId); try { - const idToken = await oaClient.getIdTokenByRefresh(remoteConnectInfo.refreshToken); - const { profile } = await oaClient.getUserConfig(idToken); - await writeProfileSetToTable(profile); - console.log('Updated profile from Config Hub'); - } catch { - await deleteRefreshTokenFromRemoteConnectInfo(); - console.warn('Failed to profile from Config Hub, so the refresh token was deleted.'); + await reloadConfig(remoteConnectInfo); + } catch (err) { + console.warn('Failed to get profile from Config Hub'); } return; } diff --git a/src/js/lib/profile_db.js b/src/js/lib/profile_db.js index c30b5a1..ea81f5d 100644 --- a/src/js/lib/profile_db.js +++ b/src/js/lib/profile_db.js @@ -11,15 +11,17 @@ export async function writeProfileSetToTable(profileSet) { }); await dbManager.transaction('profiles', async dbTable => { + const { singles = [], complexes = [] } = profileSet; let i = 0; - for (const profile of profileSet.singles) { + + for (const profile of singles) { await dbTable.insert({ profilePath: `[SINGLE];${formatNum(++i)}`, ...profile, }); } - for (const baseProfile of profileSet.complexes) { + for (const baseProfile of complexes) { const { targets, ...props } = baseProfile; await dbTable.insert({ profilePath: `[COMPLEX];${formatNum(++i)}`, diff --git a/src/js/lib/reload-config.js b/src/js/lib/reload-config.js new file mode 100644 index 0000000..f96ea9a --- /dev/null +++ b/src/js/lib/reload-config.js @@ -0,0 +1,21 @@ +import { OAuthClient, RefreshTokenError } from "../remote/oauth-client.js"; +import { writeProfileSetToTable } from "./profile_db.js"; +import { deleteRefreshTokenFromRemoteConnectInfo } from "../handlers/remote_connect.js"; + +export async function reloadConfig(remoteConnectInfo) { + const oaClient = new OAuthClient(remoteConnectInfo.subdomain, remoteConnectInfo.clientId); + try { + const idToken = await oaClient.getIdTokenByRefresh(remoteConnectInfo.refreshToken); + const { profile } = await oaClient.getUserConfig(idToken); + await writeProfileSetToTable(profile); + console.log('Updated profile from Config Hub'); + return true; + } catch (err) { + if (err instanceof RefreshTokenError) { + await deleteRefreshTokenFromRemoteConnectInfo(); + console.log('Refresh token is expired'); + return false; + } + throw err; + } +} diff --git a/src/js/options.js b/src/js/options.js index c60c3e3..9088831 100644 --- a/src/js/options.js +++ b/src/js/options.js @@ -5,7 +5,7 @@ import { ColorPicker } from './lib/color_picker.js'; import { SessionMemory, StorageProvider } from './lib/storage_repository.js'; import { writeProfileSetToTable } from "./lib/profile_db.js"; import { remoteConnect, getRemoteConnectInfo, deleteRemoteConnectInfo } from './handlers/remote_connect.js'; -import { OAuthClient } from './remote/oauth-client.js'; +import { reloadConfig } from './lib/reload-config.js'; function elById(id) { return document.getElementById(id); @@ -36,18 +36,20 @@ window.onload = function() { deleteRemoteConnectInfo(); } elById('reloadConfigHubButton').onclick = function() { - getRemoteConnectInfo().then(({ subdomain, clientId, refreshToken }) => { - if (subdomain && clientId) { - const oaClient = new OAuthClient(subdomain, clientId); - oaClient.getIdTokenByRefresh(refreshToken).then(idToken => { - return oaClient.getUserConfig(idToken); - }).then(({ profile }) => { - return writeProfileSetToTable(profile); - }).then(() => { - updateMessage('remoteMsgSpan', "Successfully reloaded config from Hub!"); + getRemoteConnectInfo().then(rci => { + if (rci && rci.subdomain && rci.clientId) { + reloadConfig(rci).then(result => { + if (result) { + updateMessage('remoteMsgSpan', "Successfully reloaded config from Hub!"); + } else { + updateMessage('remoteMsgSpan', `Failed to reload because the connection expired.`, 'warn'); + updateRemoteFieldsState('disconnected'); + } }).catch(e => { updateMessage('remoteMsgSpan', `Failed to reload because ${e.message}`, 'warn'); }); + } else { + updateMessage('remoteMsgSpan', `Failed to reload because the connection is broken.`, 'warn'); } }); } @@ -102,11 +104,11 @@ window.onload = function() { syncStorageRepo.set({ signinEndpointInHere: this.checked }); } - getRemoteConnectInfo().then(({ subdomain, clientId, refreshToken }) => { - if (subdomain && clientId) { - elById('configHubDomain').value = subdomain; - elById('configHubClientId').value = clientId; - if (refreshToken) { + getRemoteConnectInfo().then(rci => { + if (rci && rci.subdomain && rci.clientId) { + elById('configHubDomain').value = rci.subdomain; + elById('configHubClientId').value = rci.clientId; + if (rci.refreshToken) { updateRemoteFieldsState('connected'); } else { updateRemoteFieldsState('disconnected'); diff --git a/src/js/popup.js b/src/js/popup.js index 39bc410..177cfaf 100644 --- a/src/js/popup.js +++ b/src/js/popup.js @@ -3,6 +3,7 @@ import { CurrentContext } from './lib/current_context.js'; import { findTargetProfiles } from './lib/target_profiles.js'; import { SessionMemory, SyncStorageRepository } from './lib/storage_repository.js'; import { remoteCallback } from './handlers/remote_connect.js'; +import { writeProfileSetToTable } from './lib/profile_db.js'; const sessionMemory = new SessionMemory(chrome || browser); @@ -26,6 +27,12 @@ async function getCurrentTab() { return tab; } +async function moveTabToOption(tabId) { + const brw = chrome || browser; + const url = await brw.runtime.getURL('options.html'); + await brw.tabs.update(tabId, { url }); +} + async function executeAction(tabId, action, data) { return (chrome || browser).tabs.sendMessage(tabId, { action, data }); } @@ -95,15 +102,16 @@ function main() { }) } else if (url.host.endsWith('.aesr.dev') && url.pathname.startsWith('/callback')) { remoteCallback(url) - .then(() => { + .then(userCfg => { const p = noMain.querySelector('p'); p.textContent = "Successfully connected to AESR Config Hub!"; noMain.style.display = 'block'; + return writeProfileSetToTable(userCfg.profile); }) + .then(() => moveTabToOption(tab.id)) .catch(err => { - console.error(err); const p = noMain.querySelector('p'); - p.textContent = "Failed to connected to AESR Config Hub."; + p.textContent = `Failed to connect to AESR Config Hub because.\n${err.message}`; noMain.style.display = 'block'; }); } else { diff --git a/src/js/remote/oauth-client.js b/src/js/remote/oauth-client.js index 581bab7..38c3b8d 100644 --- a/src/js/remote/oauth-client.js +++ b/src/js/remote/oauth-client.js @@ -28,6 +28,13 @@ export class OAuthClient { validateCallbackUrl(uRL) { if (uRL.host === `api.${this.domain}` && uRL.pathname === '/callback') { + const error = uRL.searchParams.get('error'); + if (error) { + let errmsg = error; + const errDesc = uRL.searchParams.get('error_description'); + if (errDesc) errmsg += ': ' + errDesc; + throw new Error(errmsg); + } const authCode = uRL.searchParams.get('code'); if (authCode) return authCode; } @@ -50,6 +57,9 @@ export class OAuthClient { body: new URLSearchParams(params), }); const result = await res.json(); + if (!res.ok) { + throw new Error(result.error); + } return result; } @@ -68,6 +78,12 @@ export class OAuthClient { body: new URLSearchParams(params), }); const result = await res.json(); + if (!res.ok) { + if (result.error === 'invalid_grant') { + throw new RefreshTokenError('refresh token is invalid'); + } + throw new Error(result.error); + } return result.id_token; } @@ -79,6 +95,11 @@ export class OAuthClient { }, }) const result = await res.json(); + if (!res.ok) { + throw new Error(result.message); + } return result; } } + +export class RefreshTokenError extends Error {} diff --git a/src/popup.html b/src/popup.html index c4b295f..b58ca98 100644 --- a/src/popup.html +++ b/src/popup.html @@ -88,6 +88,7 @@ margin: 12px 5px 5px 5px; line-height: 1.66; color: #666; + white-space: pre-wrap; } #supportComment p { line-height: 1.25; diff --git a/src/updated.html b/src/updated.html index de93b47..e95dd29 100644 --- a/src/updated.html +++ b/src/updated.html @@ -72,7 +72,12 @@