From 252b66f59cd916de0fd9c706b905130099f824a5 Mon Sep 17 00:00:00 2001 From: Simon Davies Date: Wed, 19 Jul 2023 16:55:17 +0100 Subject: [PATCH 1/4] cleaned up userinfo code, moved to node-nexus --- app/yarn.lock | 4 +- .../nexus_integration/eventHandlers.ts | 20 +++--- src/extensions/nexus_integration/index.tsx | 24 +++++-- src/extensions/nexus_integration/util.ts | 69 ++++++++++++------- src/extensions/nexus_integration/util/api.ts | 3 +- .../nexus_integration/views/LoginIcon.tsx | 3 +- yarn.lock | 4 +- 7 files changed, 80 insertions(+), 47 deletions(-) diff --git a/app/yarn.lock b/app/yarn.lock index 1a605ff97..57fa9215d 100644 --- a/app/yarn.lock +++ b/app/yarn.lock @@ -67,8 +67,8 @@ integrity sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ== "@nexusmods/nexus-api@Nexus-Mods/node-nexus-api#oauth": - version "1.4.7" - resolved "https://codeload.github.com/Nexus-Mods/node-nexus-api/tar.gz/efaeb06a350c508d29bdb772c4c43d904e3ce644" + version "1.4.16" + resolved "https://codeload.github.com/Nexus-Mods/node-nexus-api/tar.gz/d94f0745a6c0a215228998b778bc5371ec49c215" dependencies: form-data "^4.0.0" jsonwebtoken "^9.0.0" diff --git a/src/extensions/nexus_integration/eventHandlers.ts b/src/extensions/nexus_integration/eventHandlers.ts index d8eecc249..d27cfb178 100644 --- a/src/extensions/nexus_integration/eventHandlers.ts +++ b/src/extensions/nexus_integration/eventHandlers.ts @@ -41,7 +41,7 @@ import * as semver from 'semver'; import { format as urlFormat } from 'url'; import { ITokenReply } from './util/oauth'; import { isLoggedIn } from './selectors'; -import { getUserInfo } from './util/api'; +//import { getUserInfo } from './util/api'; export function onChangeDownloads(api: IExtensionApi, nexus: Nexus) { const state: IState = api.store.getState(); @@ -99,7 +99,7 @@ export function onChangeDownloads(api: IExtensionApi, nexus: Nexus) { updateDebouncer.schedule(undefined, newValue); } - +/* export function onForceTokenRefresh(api: IExtensionApi, nexus: Nexus) { return () => { @@ -121,7 +121,7 @@ export function onForceTokenRefresh(api: IExtensionApi, nexus: Nexus) { nexus.forceJwtRefresh(); } } -} +}*/ @@ -756,17 +756,17 @@ export function onGetLatestMods(api: IExtensionApi, nexus: Nexus) { } -export function onRefreshUserInfo(api: IExtensionApi) { +export function onRefreshUserInfo(nexus: Nexus, api: IExtensionApi) { return (): Promise => { - const token = getOAuthTokenFromState(api); + //const token = getOAuthTokenFromState(api); //log('info', 'onRefreshUserInfo()', token); // we have an oauth token in state - if(token !== undefined) { + //if(token !== undefined) { // get userinfo from api - return Promise.resolve(getUserInfo(token)) + return Promise.resolve(nexus.getUserInfo()) .then(apiUserInfo => { api.store.dispatch(setUserInfo(transformUserInfoFromApi(apiUserInfo))); log('info', 'onRefreshUserInfo()', apiUserInfo); @@ -777,9 +777,9 @@ export function onRefreshUserInfo(api: IExtensionApi) { allowReport: false, }); }); - } else { - log('warn', 'onRefreshUserInfo() no oauth token'); - } + //} else { + // log('warn', 'onRefreshUserInfo() no oauth token'); + //} }; } diff --git a/src/extensions/nexus_integration/index.tsx b/src/extensions/nexus_integration/index.tsx index 7b7d2cca0..5feda336b 100644 --- a/src/extensions/nexus_integration/index.tsx +++ b/src/extensions/nexus_integration/index.tsx @@ -1,4 +1,4 @@ -import { setDownloadModInfo, setForcedLogout, setModAttribute } from '../../actions'; +import { clearOAuthCredentials, setDownloadModInfo, setForcedLogout, setModAttribute } from '../../actions'; import { IDialogResult, showDialog } from '../../actions/notifications'; import { IExtensionApi, IExtensionContext } from '../../types/IExtensionContext'; import { IModLookupResult } from '../../types/IModLookupResult'; @@ -889,7 +889,7 @@ function extendAPI(api: IExtensionApi, nexus: NexusT): INexusAPIExtension nexusModUpdate: eh.onModUpdate(api, nexus), nexusOpenCollectionPage: eh.onOpenCollectionPage(api), nexusOpenModPage: eh.onOpenModPage(api), - nexusRequestNexusLogin: callback => requestLogin(api, callback), + nexusRequestNexusLogin: callback => requestLogin(nexus, api, callback), nexusRequestOwnIssues: eh.onRequestOwnIssues(nexus), nexusRetrieveCategoryList: (isUpdate: boolean) => retrieveCategories(api, isUpdate), nexusGetModFiles: eh.onGetModFiles(api, nexus), @@ -1004,9 +1004,10 @@ function once(api: IExtensionApi, callbacks: Array<(nexus: NexusT) => void>) { } else { if (oauthCred !== undefined) { + log('info', 'OAuth credentials found in state. Updating nexus token'); updateToken(api, nexus, oauthCred); } else { - updateKey(api, nexus, apiKey); + //updateKey(api, nexus, apiKey); } } @@ -1026,7 +1027,7 @@ function once(api: IExtensionApi, callbacks: Array<(nexus: NexusT) => void>) { // register when window is focussed to do a userinfo check? getApplication().window.on('focus', (event, win) => { console.log('browser-window-focus'); - userInfoDebouncer.schedule(); + //userInfoDebouncer.schedule(); }) } @@ -1041,7 +1042,7 @@ function once(api: IExtensionApi, callbacks: Array<(nexus: NexusT) => void>) { api.onAsync('endorse-nexus-mod', eh.onEndorseDirect(api, nexus)); api.onAsync('get-latest-mods', eh.onGetLatestMods(api, nexus)); api.onAsync('get-trending-mods', eh.onGetTrendingMods(api, nexus)); - api.events.on('refresh-user-info', eh.onRefreshUserInfo(api)); + api.events.on('refresh-user-info', eh.onRefreshUserInfo(nexus, api)); //api.events.on('force-token-refresh', eh.onForceTokenRefresh(api, nexus)); api.events.on('endorse-mod', eh.onEndorseMod(api, nexus)); api.events.on('submit-feedback', eh.onSubmitFeedback(nexus)); @@ -1049,7 +1050,7 @@ function once(api: IExtensionApi, callbacks: Array<(nexus: NexusT) => void>) { api.events.on('mod-update', eh.onModUpdate(api, nexus)); api.events.on('open-collection-page', eh.onOpenCollectionPage(api)); api.events.on('open-mod-page', eh.onOpenModPage(api)); - api.events.on('request-nexus-login', callback => requestLogin(api, callback)); + api.events.on('request-nexus-login', callback => requestLogin(nexus, api, callback)); api.events.on('request-own-issues', eh.onRequestOwnIssues(nexus)); api.events.on('retrieve-category-list', (isUpdate: boolean) => { retrieveCategories(api, isUpdate); @@ -1530,6 +1531,17 @@ function init(context: IExtensionContextExt): boolean { const tracking = new Tracking(context.api); + /* + context.registerAction('global-icons', 100, 'feedback', {}, 'Clear OAuth State', () => { + log('info', 'Clear OAuth State'); + context.api.store.dispatch(clearOAuthCredentials(null)); + });*/ + + context.registerAction('global-icons', 100, 'nexus', {}, 'Refresh User Info', () => { + log('info', 'Refresh User Info'); + context.api.events.emit('refresh-user-info'); + }); + context.registerAction('mods-action-icons', 300, 'smart', {}, 'Fix missing IDs', instanceIds => { fixIds(context.api, instanceIds); }, instanceIds => includesMissingMetaId(context.api, instanceIds)); diff --git a/src/extensions/nexus_integration/util.ts b/src/extensions/nexus_integration/util.ts index 6b0d33170..bcb4ab17f 100644 --- a/src/extensions/nexus_integration/util.ts +++ b/src/extensions/nexus_integration/util.ts @@ -235,7 +235,7 @@ const oauth = new OAuth({ redirectUrl: OAUTH_REDIRECT_URL, }); -export function requestLogin(api: IExtensionApi, callback: (err: Error) => void) { +export function requestLogin(nexus: Nexus, api: IExtensionApi, callback: (err: Error) => void) { const stackErr = new Error(); return oauth.sendRequest(async (err: Error, token: ITokenReply) => { @@ -256,10 +256,10 @@ export function requestLogin(api: IExtensionApi, callback: (err: Error) => void) api.store.dispatch(setOAuthCredentials(token.access_token, token.refresh_token, tokenDecoded.fingerprint)); - const apiUserInfo = await getUserInfo(token.access_token); + //const apiUserInfo = await getUserInfo(nexus); - api.store.dispatch(setUserInfo(transformUserInfoFromApi(apiUserInfo))); - log('info', 'apiUserInfo', apiUserInfo); + //api.store.dispatch(setUserInfo(transformUserInfoFromApi(apiUserInfo))); + //log('info', 'apiUserInfo', apiUserInfo); callback(null); @@ -403,6 +403,7 @@ function startDownloadCollection(api: IExtensionApi, }); } +/* export function getUserInfo(token:string) : Promise { return Promise.resolve((async () => { try { @@ -413,6 +414,18 @@ export function getUserInfo(token:string) : Promise { throw err; } })()); +}*/ + +export function getUserInfo(nexus: Nexus) : Promise { + return Promise.resolve((async () => { + try { + const userInfo = await nexus.getUserInfo(); + return userInfo; + } catch (err) { + err['attachLogOnReport'] = true; + throw err; + } + })()); } export interface IRemoteInfo { @@ -1301,7 +1314,7 @@ export function getOAuthTokenFromState(api: IExtensionApi) { const apiKey = state.confidential.account?.['nexus']?.['APIKey']; const oauthCred:IOAuthCredentials = state.confidential.account?.['nexus']?.['OAuthCredentials']; - log('info', 'getOAuthTokenFromState()', { apiKey: apiKey, oauthCred: oauthCred }); + //log('info', 'getOAuthTokenFromState()'); //log('info', 'api key', apiKey !== undefined); //log('info', 'oauth cred', oauthCred !== undefined); @@ -1313,6 +1326,8 @@ function updateUserInfo(api: IExtensionApi, /*userInfo: IValidateKeyResponse*/) : Promise { + + log('info', 'updateUserInfo()') /** @@ -1321,28 +1336,27 @@ function updateUserInfo(api: IExtensionApi, * from the nexus api instead of the information that was supplied in * oauth token itself as this could be out of date */ - const token = getOAuthTokenFromState(api); + //const token = getOAuthTokenFromState(api); - // we have an oauth token in state - if(token !== undefined) { + if(isLoggedIn(api.getState())) { // get userinfo from api - return getUserInfo(token) - .then(apiUserInfo => { - // update state with new info from endpoint - api.store.dispatch(setUserInfo(transformUserInfoFromApi(apiUserInfo))); - log('info', 'apiUserInfo', apiUserInfo); - return true; - }) - .catch((err) => { - log('error', `onRefreshUserInfo() ${err.message}`, err); - showError(api.store.dispatch, 'An error occurred refreshing user info', err, { - allowReport: false, + return getUserInfo(nexus) + .then(apiUserInfo => { + // update state with new info from endpoint + api.store.dispatch(setUserInfo(transformUserInfoFromApi(apiUserInfo))); + log('info', 'apiUserInfo', apiUserInfo); + return true; + }) + .catch((err) => { + log('error', `onRefreshUserInfo() ${err.message}`, err); + showError(api.store.dispatch, 'An error occurred refreshing user info', err, { + allowReport: false, + }); + return false; }); - return false; - }); } else { - log('warn', 'updateUserInfo() no oauth token'); + log('warn', 'updateUserInfo() not logged in'); } return github.fetchConfig('api') @@ -1383,6 +1397,9 @@ export function updateToken(api: IExtensionApi, nexus: Nexus, token: any) : Promise { + + log('info', 'updateToken()', token); + setOauthToken(token); return Promise.resolve(nexus.setOAuthCredentials({ @@ -1392,7 +1409,8 @@ export function updateToken(api: IExtensionApi, }, { id: OAUTH_CLIENT_ID, }, (credentials: IOAuthCredentials) => onJWTTokenRefresh(api, credentials))) - .then(userInfo => updateUserInfo(api, nexus)) + .then(userInfo => updateUserInfo(api, nexus)) + .then(() => true) .catch(err => { api.showErrorNotification('Authentication failed, please log in again', err, { allowReport: false, @@ -1407,8 +1425,9 @@ export function updateToken(api: IExtensionApi, export function updateKey(api: IExtensionApi, nexus: Nexus, key: string): Promise { setApiKey(key); - return Promise.resolve(nexus.setKey(key)) - .then(userInfo => updateUserInfo(api, nexus)) + return Promise.resolve(nexus.setKey(key)) + .then(() => true) + //.then(userInfo => updateUserInfo(api, nexus)) // don't stop the login just because the github rate limit is exceeded .catch(RateLimitExceeded, () => Promise.resolve(true)) .catch(TimeoutError, err => { diff --git a/src/extensions/nexus_integration/util/api.ts b/src/extensions/nexus_integration/util/api.ts index 4540da2e9..de62c1f76 100644 --- a/src/extensions/nexus_integration/util/api.ts +++ b/src/extensions/nexus_integration/util/api.ts @@ -11,6 +11,7 @@ export interface IUserInfo { premium_expiry: number; } +/* export async function getUserInfo(token: string): Promise { const headers: Headers = new Headers() @@ -38,4 +39,4 @@ export async function getUserInfo(token: string): Promise { const userInfo:IUserInfo = await response.json(); return userInfo; -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/src/extensions/nexus_integration/views/LoginIcon.tsx b/src/extensions/nexus_integration/views/LoginIcon.tsx index 6d39655ca..9f7b9f7df 100644 --- a/src/extensions/nexus_integration/views/LoginIcon.tsx +++ b/src/extensions/nexus_integration/views/LoginIcon.tsx @@ -160,7 +160,7 @@ class LoginIcon extends ComponentEx { const { userInfo } = this.props; //this.context.api.events.emit('force-token-refresh'); - this.context.api.events.emit('refresh-user-info'); + //this.context.api.events.emit('refresh-user-info'); if (!this.isLoggedIn()) { this.context.api.events.emit('analytics-track-click-event', 'Profile', 'Site profile'); @@ -182,6 +182,7 @@ class LoginIcon extends ComponentEx { private isLoggedIn() { const { isLoggedIn, userInfo } = this.props; + //return isLoggedIn; return isLoggedIn && (userInfo !== undefined) && (userInfo !== null); } diff --git a/yarn.lock b/yarn.lock index 3ea130588..583bbe575 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1698,8 +1698,8 @@ integrity sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ== "@nexusmods/nexus-api@Nexus-Mods/node-nexus-api#oauth": - version "1.4.12" - resolved "https://codeload.github.com/Nexus-Mods/node-nexus-api/tar.gz/783984d4ef1f994621540e6b0f3365c59eb2ccf2" + version "1.4.16" + resolved "https://codeload.github.com/Nexus-Mods/node-nexus-api/tar.gz/d94f0745a6c0a215228998b778bc5371ec49c215" dependencies: form-data "^4.0.0" jsonwebtoken "^9.0.0" From a1ea7f054efd23725483ad63cf32e23da3b65587 Mon Sep 17 00:00:00 2001 From: Simon Davies Date: Tue, 25 Jul 2023 15:30:10 +0100 Subject: [PATCH 2/4] added debouncer to refresh-user-info --- src/extensions/nexus_integration/index.tsx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/extensions/nexus_integration/index.tsx b/src/extensions/nexus_integration/index.tsx index 5feda336b..05f9ec39f 100644 --- a/src/extensions/nexus_integration/index.tsx +++ b/src/extensions/nexus_integration/index.tsx @@ -944,12 +944,6 @@ function once(api: IExtensionApi, callbacks: Array<(nexus: NexusT) => void>) { const gameMode = activeGameId(state); - userInfoDebouncer = new Debouncer(() => { - console.log('debouncer '); - api.events.emit('refresh-user-info'); - return Promise.resolve(); - }, 10000, true, true); - nexus = new Proxy( new Proxy( new Nexus('Vortex', getApplication().version, nexusGameId(getGame(gameMode)), 30000), @@ -1537,9 +1531,16 @@ function init(context: IExtensionContextExt): boolean { context.api.store.dispatch(clearOAuthCredentials(null)); });*/ + + userInfoDebouncer = new Debouncer(() => { + console.log('debouncer'); + context.api.events.emit('refresh-user-info'); + return Promise.resolve(); + }, 10000, true, true); + context.registerAction('global-icons', 100, 'nexus', {}, 'Refresh User Info', () => { log('info', 'Refresh User Info'); - context.api.events.emit('refresh-user-info'); + userInfoDebouncer.schedule(); }); context.registerAction('mods-action-icons', 300, 'smart', {}, 'Fix missing IDs', From c0db82567176b720a61a0b66b72151ffb713e252 Mon Sep 17 00:00:00 2001 From: Simon Davies Date: Wed, 26 Jul 2023 13:15:10 +0100 Subject: [PATCH 3/4] added check button to free download dialog --- .../nexus_integration/eventHandlers.ts | 27 ------------ src/extensions/nexus_integration/index.tsx | 29 ++++++++----- src/extensions/nexus_integration/util.ts | 36 ++++++---------- src/extensions/nexus_integration/util/api.ts | 42 ------------------- .../views/FreeUserDLDialog.tsx | 11 ++++- .../nexus_integration/views/LoginIcon.tsx | 3 -- .../views/PremiumNagBanner.tsx | 27 +++++++++++- 7 files changed, 66 insertions(+), 109 deletions(-) delete mode 100644 src/extensions/nexus_integration/util/api.ts diff --git a/src/extensions/nexus_integration/eventHandlers.ts b/src/extensions/nexus_integration/eventHandlers.ts index d27cfb178..fb77b9e36 100644 --- a/src/extensions/nexus_integration/eventHandlers.ts +++ b/src/extensions/nexus_integration/eventHandlers.ts @@ -41,7 +41,6 @@ import * as semver from 'semver'; import { format as urlFormat } from 'url'; import { ITokenReply } from './util/oauth'; import { isLoggedIn } from './selectors'; -//import { getUserInfo } from './util/api'; export function onChangeDownloads(api: IExtensionApi, nexus: Nexus) { const state: IState = api.store.getState(); @@ -99,32 +98,6 @@ export function onChangeDownloads(api: IExtensionApi, nexus: Nexus) { updateDebouncer.schedule(undefined, newValue); } -/* -export function onForceTokenRefresh(api: IExtensionApi, nexus: Nexus) { - - return () => { - - log('info', 'onForceTokenRefresh'); - - // limit lifetime of state - const state = api.getState(); - - //const Nexus: typeof NexusT = require('@nexusmods/nexus-api').default; - const apiKey = state.confidential.account?.['nexus']?.['APIKey']; - const oauthCred = state.confidential.account?.['nexus']?.['OAuthCredentials']; - - log('info', 'api key', { isUndefined: apiKey !== undefined }); - log('info', 'oauth cred', { isUndefined: oauthCred !== undefined }); - - if (oauthCred !== undefined) { - log('info', 'nexus.forceJwtRefresh()'); - nexus.forceJwtRefresh(); - } - } -}*/ - - - /** * callback for when mods are changed * diff --git a/src/extensions/nexus_integration/index.tsx b/src/extensions/nexus_integration/index.tsx index 05f9ec39f..209d0903b 100644 --- a/src/extensions/nexus_integration/index.tsx +++ b/src/extensions/nexus_integration/index.tsx @@ -58,7 +58,7 @@ import { NEXUS_API_SUBDOMAIN, NEXUS_BASE_URL, NEXUS_DOMAIN, import * as eh from './eventHandlers'; import NXMUrl from './NXMUrl'; import * as sel from './selectors'; -import { bringToFront, endorseThing, ensureLoggedIn, getCollectionInfo, getInfo, getOAuthTokenFromState, getUserInfo, IRemoteInfo, +import { bringToFront, endorseThing, ensureLoggedIn, getCollectionInfo, getInfo, getOAuthTokenFromState, IRemoteInfo, nexusGames, nexusGamesProm, oauthCallback, onCancelLoginImpl, processErrorMessage, requestLogin, retrieveNexusGames, startDownload, transformUserInfoFromApi, updateKey, updateToken } from './util'; import { checkModVersion } from './util/checkModsVersion'; @@ -582,7 +582,7 @@ function makeNXMLinkCallback(api: IExtensionApi) { } else if (nxmUrl.type === 'premium') { try { log('info', 'makeNXMLinkCallback() premium'); - api.events.emit('refresh-user-info'); + userInfoDebouncer.schedule(); return false; } catch (err) { // ignore unexpected code @@ -1016,8 +1016,6 @@ function once(api: IExtensionApi, callbacks: Array<(nexus: NexusT) => void>) { }); }); - - // register when window is focussed to do a userinfo check? getApplication().window.on('focus', (event, win) => { console.log('browser-window-focus'); @@ -1037,7 +1035,6 @@ function once(api: IExtensionApi, callbacks: Array<(nexus: NexusT) => void>) { api.onAsync('get-latest-mods', eh.onGetLatestMods(api, nexus)); api.onAsync('get-trending-mods', eh.onGetTrendingMods(api, nexus)); api.events.on('refresh-user-info', eh.onRefreshUserInfo(nexus, api)); - //api.events.on('force-token-refresh', eh.onForceTokenRefresh(api, nexus)); api.events.on('endorse-mod', eh.onEndorseMod(api, nexus)); api.events.on('submit-feedback', eh.onSubmitFeedback(nexus)); api.events.on('submit-collection', eh.onSubmitCollection(nexus)); @@ -1350,6 +1347,8 @@ function makeNXMProtocol(api: IExtensionApi, onAwaitLink: AwaitLinkCB) { log('warn', 'userInfo checking for downloads?'); + + const userInfo: any = getSafe(state, ['persistent', 'nexus', 'userInfo'], undefined); if ((url.userId !== undefined) && (url.userId !== userInfo?.userId)) { const userName: string = @@ -1421,7 +1420,7 @@ function onSkip(inputUrl: string) { } } -function onRetryImpl(resolveFunc: ResolveFunc, inputUrl: string) { +function onRetryImpl(resolveFunc: ResolveFunc, api: IExtensionApi, inputUrl: string) { const queueItem = freeDLQueue.find(iter => iter.input === inputUrl); if (queueItem === undefined) { log('error', 'failed to find queue item', { inputUrl, queue: JSON.stringify(freeDLQueue) }); @@ -1430,10 +1429,13 @@ function onRetryImpl(resolveFunc: ResolveFunc, inputUrl: string) { const { url } = queueItem; + resolveFunc(queueItem.input) .then(queueItem.res) .catch(queueItem.rej); + + /* const awaitedLink = { gameId: url.gameId, @@ -1452,6 +1454,10 @@ function onRetryImpl(resolveFunc: ResolveFunc, inputUrl: string) { //console.log('awaitedLink', JSON.stringify(awaitedLink)); } +function onCheckStatusImpl() { + + userInfoDebouncer.schedule(); +} function onCancelImpl(api: IExtensionApi, inputUrl: string): boolean { const copy = freeDLQueue.slice(0); @@ -1536,11 +1542,11 @@ function init(context: IExtensionContextExt): boolean { console.log('debouncer'); context.api.events.emit('refresh-user-info'); return Promise.resolve(); - }, 10000, true, true); + }, 1000, true, true); context.registerAction('global-icons', 100, 'nexus', {}, 'Refresh User Info', () => { log('info', 'Refresh User Info'); - userInfoDebouncer.schedule(); + userInfoDebouncer.schedule(); }); context.registerAction('mods-action-icons', 300, 'smart', {}, 'Fix missing IDs', @@ -1583,8 +1589,10 @@ function init(context: IExtensionContextExt): boolean { const onDownload = (inputUrl: string) => onDownloadImpl(resolveFunc, inputUrl); const onCancel = (inputUrl: string) => onCancelImpl(context.api, inputUrl); + + const onCheckStatus = () => onCheckStatusImpl(); - const onRetry = (inputUrl: string) => onRetryImpl(resolveFunc, inputUrl); + const onRetry = (inputUrl: string) => onRetryImpl(resolveFunc, context.api, inputUrl); context.registerDialog('free-user-download', FreeUserDLDialog, () => ({ t: context.api.translate, @@ -1593,7 +1601,8 @@ function init(context: IExtensionContextExt): boolean { onDownload, onSkip, onCancel, - onRetry + onRetry, + onCheckStatus })); context.registerBanner('downloads', () => { diff --git a/src/extensions/nexus_integration/util.ts b/src/extensions/nexus_integration/util.ts index bcb4ab17f..f027d8d96 100644 --- a/src/extensions/nexus_integration/util.ts +++ b/src/extensions/nexus_integration/util.ts @@ -51,7 +51,6 @@ import OAuth, { ITokenReply } from './util/oauth'; import { IAccountStatus, IValidateKeyData, IValidateKeyDataV2 } from './types/IValidateKeyData'; import { getPageURL } from './util/sso'; import transformUserInfo from './util/transformUserInfo'; -import * as NexusAPI from './util/api'; const remote = lazyRequire(() => require('@electron/remote')); @@ -66,6 +65,16 @@ interface INexusLoginMessage { token?: string; } +interface IUserInfo { + sub: string; + name: string; + email: string; + avatar: string; + group_id: number; + membership_roles: string[]; + premium_expiry: number; +} + let cancelLogin: () => void; export function onCancelLoginImpl(api: IExtensionApi) { @@ -251,15 +260,9 @@ export function requestLogin(nexus: Nexus, api: IExtensionApi, callback: (err: E return callback(err); } - const tokenDecoded: IJWTAccessToken = jwt.decode(token.access_token); api.store.dispatch(setOAuthCredentials(token.access_token, token.refresh_token, tokenDecoded.fingerprint)); - - //const apiUserInfo = await getUserInfo(nexus); - - //api.store.dispatch(setUserInfo(transformUserInfoFromApi(apiUserInfo))); - //log('info', 'apiUserInfo', apiUserInfo); callback(null); @@ -403,20 +406,7 @@ function startDownloadCollection(api: IExtensionApi, }); } -/* -export function getUserInfo(token:string) : Promise { - return Promise.resolve((async () => { - try { - const userInfo = await NexusAPI.getUserInfo(token); - return userInfo; - } catch (err) { - err['attachLogOnReport'] = true; - throw err; - } - })()); -}*/ - -export function getUserInfo(nexus: Nexus) : Promise { +export function getUserInfo(nexus: Nexus) : Promise { return Promise.resolve((async () => { try { const userInfo = await nexus.getUserInfo(); @@ -1268,7 +1258,7 @@ function errorFromNexusError(err: NexusError): string { } -function getAccountStatus(apiUserInfo:NexusAPI.IUserInfo):IAccountStatus { +function getAccountStatus(apiUserInfo:IUserInfo):IAccountStatus { if(apiUserInfo.group_id === 5) return IAccountStatus.Banned; else if(apiUserInfo.group_id === 41) return IAccountStatus.Closed; @@ -1277,7 +1267,7 @@ function getAccountStatus(apiUserInfo:NexusAPI.IUserInfo):IAccountStatus { else return IAccountStatus.Free; } -export function transformUserInfoFromApi(input: NexusAPI.IUserInfo) { +export function transformUserInfoFromApi(input: IUserInfo) { const stateUserInfo:IValidateKeyDataV2 = { email: input.email, diff --git a/src/extensions/nexus_integration/util/api.ts b/src/extensions/nexus_integration/util/api.ts deleted file mode 100644 index de62c1f76..000000000 --- a/src/extensions/nexus_integration/util/api.ts +++ /dev/null @@ -1,42 +0,0 @@ - -import { USERINFO_ENDPOINT } from '../constants'; - -export interface IUserInfo { - sub: string; - name: string; - email: string; - avatar: string; - group_id: number; - membership_roles: string[]; - premium_expiry: number; -} - -/* -export async function getUserInfo(token: string): Promise { - - const headers: Headers = new Headers() - - // Add a few headers - headers.set('Content-Type', 'application/json'); - headers.set('Accept', 'application/json'); - - // Add a custom header, which we can use to check - headers.set('Authorization', `Bearer ${token}`); - - // Create the request object, which will be a RequestInfo type. - // Here, we will pass in the URL as well as the options object as parameters. - const request: RequestInfo = new Request(USERINFO_ENDPOINT, { - method: 'GET', - headers: headers - }) - - const response = await fetch(request); - - if (!response.ok) { - const message = `An error has occured: ${response.status}`; - throw new Error(message); - } - - const userInfo:IUserInfo = await response.json(); - return userInfo; -}*/ \ No newline at end of file diff --git a/src/extensions/nexus_integration/views/FreeUserDLDialog.tsx b/src/extensions/nexus_integration/views/FreeUserDLDialog.tsx index 65581cba3..71238d1b4 100644 --- a/src/extensions/nexus_integration/views/FreeUserDLDialog.tsx +++ b/src/extensions/nexus_integration/views/FreeUserDLDialog.tsx @@ -25,6 +25,7 @@ interface IFreeUserDLDialogProps { onCancel: (url: string) => boolean; onUpdated: () => void; onRetry: (url: string) => void; + onCheckStatus: () => void; } const FILE_QUERY: IModFileQuery = { @@ -83,10 +84,12 @@ function nop() { function FreeUserDLDialog(props: IFreeUserDLDialogProps) { - const { t, nexus, onCancel, onDownload, onSkip, onUpdated, onRetry } = props; + const { t, nexus, onCancel, onDownload, onSkip, onUpdated, onRetry, onCheckStatus } = props; //return oauthCred !== undefined ? oauthCred.token : undefined; + + const urls: string[] = useSelector(state => state.session['nexus'].freeUserDLQueue); @@ -135,6 +138,10 @@ function FreeUserDLDialog(props: IFreeUserDLDialogProps) { } }, [urls]); + const checkStatus = React.useCallback(() => { + onCheckStatus(); + }, []) + const retry = React.useCallback(() => { onRetry(urls[0]); }, [onRetry, urls]); @@ -203,7 +210,7 @@ function FreeUserDLDialog(props: IFreeUserDLDialogProps) { ) : null} - + diff --git a/src/extensions/nexus_integration/views/LoginIcon.tsx b/src/extensions/nexus_integration/views/LoginIcon.tsx index 9f7b9f7df..d7676db02 100644 --- a/src/extensions/nexus_integration/views/LoginIcon.tsx +++ b/src/extensions/nexus_integration/views/LoginIcon.tsx @@ -159,9 +159,6 @@ class LoginIcon extends ComponentEx { private showLoginLayer = async () => { const { userInfo } = this.props; - //this.context.api.events.emit('force-token-refresh'); - //this.context.api.events.emit('refresh-user-info'); - if (!this.isLoggedIn()) { this.context.api.events.emit('analytics-track-click-event', 'Profile', 'Site profile'); this.setDialogVisible(true); diff --git a/src/extensions/nexus_integration/views/PremiumNagBanner.tsx b/src/extensions/nexus_integration/views/PremiumNagBanner.tsx index b68edef27..8e516da26 100644 --- a/src/extensions/nexus_integration/views/PremiumNagBanner.tsx +++ b/src/extensions/nexus_integration/views/PremiumNagBanner.tsx @@ -13,16 +13,24 @@ import { PREMIUM_PATH } from '../constants'; export interface IPremiumNagBanner { t: TFunction; onDownload: () => void; + onCheckStatus: () => void; campaign: string | Campaign; } function PremiumNagBanner(props: IPremiumNagBanner) { - const { t, campaign, onDownload } = props; + const { t, campaign, onDownload, onCheckStatus } = props; const context = React.useContext(MainContext); + const [disableRefresh, setDisableRefresh] = React.useState(false); + const [showRefresh, setShowRefresh] = React.useState(false); + const goGetPremium = React.useCallback(() => { context.api.events.emit('analytics-track-click-event', 'Go Premium', 'Download Mod'); + + // show button in case user needs it to refresh status + setShowRefresh(true); + opn(nexusModsURL(PREMIUM_PATH, { section: Section.Users, campaign: Campaign.BuyPremium, @@ -30,6 +38,18 @@ function PremiumNagBanner(props: IPremiumNagBanner) { })).catch(() => null); }, [campaign]); + const checkStatus = React.useCallback(() => { + + // disable button + setDisableRefresh(true); + + // enable again in 5 seconds + setTimeout(() => setDisableRefresh(false), 5000); + + // go check status anyway (for now) + onCheckStatus(); + }, []); + return (
@@ -43,7 +63,10 @@ function PremiumNagBanner(props: IPremiumNagBanner) {
- +
+ + {(showRefresh) ? () : null} +
From 3252b69a648763f823d1c00e6f3dc18e939c0431 Mon Sep 17 00:00:00 2001 From: Simon Davies Date: Wed, 26 Jul 2023 14:00:34 +0100 Subject: [PATCH 4/4] added oauth check before userinfo check --- src/extensions/nexus_integration/eventHandlers.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/extensions/nexus_integration/eventHandlers.ts b/src/extensions/nexus_integration/eventHandlers.ts index fb77b9e36..bd7d830b5 100644 --- a/src/extensions/nexus_integration/eventHandlers.ts +++ b/src/extensions/nexus_integration/eventHandlers.ts @@ -732,12 +732,12 @@ export function onGetLatestMods(api: IExtensionApi, nexus: Nexus) { export function onRefreshUserInfo(nexus: Nexus, api: IExtensionApi) { return (): Promise => { - //const token = getOAuthTokenFromState(api); + const token = getOAuthTokenFromState(api); //log('info', 'onRefreshUserInfo()', token); // we have an oauth token in state - //if(token !== undefined) { + if(token !== undefined) { // get userinfo from api return Promise.resolve(nexus.getUserInfo()) .then(apiUserInfo => { @@ -750,9 +750,9 @@ export function onRefreshUserInfo(nexus: Nexus, api: IExtensionApi) { allowReport: false, }); }); - //} else { - // log('warn', 'onRefreshUserInfo() no oauth token'); - //} + } else { + log('warn', 'onRefreshUserInfo() no oauth token'); + } }; }