Skip to content

Commit

Permalink
preset can now directly trigger log-in
Browse files Browse the repository at this point in the history
also made the on-demand log-in use the pretty login screen (that also supports fallback)

This also improves error handling in the preset system so that user can manually resume after an interruption
  • Loading branch information
TanninOne committed Sep 9, 2022
1 parent 874f706 commit c1455ba
Show file tree
Hide file tree
Showing 8 changed files with 74 additions and 23 deletions.
4 changes: 4 additions & 0 deletions src/extensions/nexus_integration/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import lazyRequire from '../../util/lazyRequire';
import { log, LogLevel } from '../../util/log';
import { prettifyNodeErrorMessage, showError } from '../../util/message';
import opn from '../../util/opn';
import presetManager from '../../util/PresetManager';
import { activeGameId, downloadPathForGame, gameById, knownGames } from '../../util/selectors';
import { currentGame, getSafe } from '../../util/storeHelper';
import { batchDispatch, decodeHTML, nexusModsURL, Section, toPromise, truthy } from '../../util/util';
Expand Down Expand Up @@ -157,6 +158,7 @@ class Disableable {
.then((userInfo) => {
if (truthy(userInfo)) {
that.mApi.store.dispatch(setUserInfo(transformUserInfo(userInfo)));
that.mApi.events.emit('did-login', null);
}
return obj[prop](...args);
});
Expand Down Expand Up @@ -929,6 +931,8 @@ function once(api: IExtensionApi, callbacks: Array<(nexus: NexusT) => void>) {

checkModsWithMissingMeta(api);

presetManager.on('login_nexus', () => ensureLoggedIn(api));

callbacks.forEach(cb => cb(nexus));
}

Expand Down
28 changes: 15 additions & 13 deletions src/extensions/nexus_integration/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import * as Redux from 'redux';
import * as semver from 'semver';
import * as util from 'util';
import WebSocket from 'ws';
import { addNotification, dismissNotification, setExtensionEndorsed, setModAttribute, setUserAPIKey } from '../../actions';
import { addNotification, dismissNotification, setDialogVisible, setExtensionEndorsed, setModAttribute, setUserAPIKey } from '../../actions';
import { IExtensionApi, IMod, IState, ThunkStore } from '../../types/api';
import { getApplication } from '../../util/application';
import { DataInvalid, HTTPError, ProcessCanceled, ServiceTemporarilyUnavailable, TemporaryError, UserCanceled } from '../../util/CustomErrors';
Expand Down Expand Up @@ -70,6 +70,7 @@ export function onCancelLoginImpl(api: IExtensionApi) {
}
}
api.store.dispatch(setLoginId(undefined));
api.events.emit('did-login', new UserCanceled());
}

export function bringToFront() {
Expand Down Expand Up @@ -217,20 +218,16 @@ export function requestLogin(api: IExtensionApi, callback: (err: Error) => void)

export function ensureLoggedIn(api: IExtensionApi): Promise<void> {
if (sel.apiKey(api.store.getState()) === undefined) {
log('info', 'user not logged in, triggering log in dialog');
return api.showDialog('info', 'Not logged in', {
text: 'Nexus Mods requires Vortex to be logged in for downloading',
}, [
{ label: 'Cancel' },
{ label: 'Log in' },
])
.then(result => {
if (result.action === 'Log in') {
return toPromise(cb => requestLogin(api, cb));
return new Promise((resolve, reject) => {
api.events.on('did-login', (err: Error) => {
if (err !== null) {
reject(err);
} else {
return Promise.reject(new UserCanceled());
resolve();
}
});
api.store.dispatch(setDialogVisible('login-dialog'));
});
} else {
return Promise.resolve();
}
Expand Down Expand Up @@ -1162,6 +1159,7 @@ export function updateKey(api: IExtensionApi, nexus: Nexus, key: string): Promis
.then(userInfo => {
if (userInfo !== null) {
api.store.dispatch(setUserInfo(transformUserInfo(userInfo)));
api.events.emit('did-login', null);
}
return github.fetchConfig('api')
.then(configObj => {
Expand Down Expand Up @@ -1190,7 +1188,7 @@ export function updateKey(api: IExtensionApi, nexus: Nexus, key: string): Promis
})
// don't stop the login just because the github rate limit is exceeded
.catch(RateLimitExceeded, () => Promise.resolve(true))
.catch(TimeoutError, () => {
.catch(TimeoutError, err => {
api.sendNotification({
type: 'error',
message: 'API Key validation timed out',
Expand All @@ -1199,6 +1197,7 @@ export function updateKey(api: IExtensionApi, nexus: Nexus, key: string): Promis
],
});
api.store.dispatch(setUserInfo(undefined));
api.events.emit('did-login', err);
return false;
})
.catch(NexusError, err => {
Expand All @@ -1215,6 +1214,7 @@ export function updateKey(api: IExtensionApi, nexus: Nexus, key: string): Promis
],
});
api.store.dispatch(setUserInfo(undefined));
api.events.emit('did-login', err);
return false;
})
.catch(ProcessCanceled, err => {
Expand All @@ -1232,6 +1232,7 @@ export function updateKey(api: IExtensionApi, nexus: Nexus, key: string): Promis
],
});
api.store.dispatch(setUserInfo(undefined));
api.events.emit('did-login', err);
return false;
})
.catch(err => {
Expand All @@ -1249,6 +1250,7 @@ export function updateKey(api: IExtensionApi, nexus: Nexus, key: string): Promis
}],
});
api.store.dispatch(setUserInfo(undefined));
api.events.emit('did-login', err);
return false;
});
}
Expand Down
1 change: 1 addition & 0 deletions src/renderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,7 @@ function init() {
replayActionRenderer(store);
extensions.setStore(store);
setOutdated(extensions.getApi());
presetManager.setApi(extensions.getApi());
extensions.applyExtensionsOfExtensions();
log('debug', 'renderer connected to store');

Expand Down
9 changes: 7 additions & 2 deletions src/types/IPreset.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const StepTypeList = ['commandline', 'hydrate', 'setgame', 'installmod', 'restart'] as const;
const StepTypeList = ['commandline', 'hydrate', 'setgame', 'installmod', 'restart', 'login_nexus'] as const;

export type PresetStepType = typeof StepTypeList[number];

Expand All @@ -12,6 +12,10 @@ export interface ICommandLineArg {
value?: any;
}

export interface IPresetStepLoginNexus extends IPresetStepBase {
type: 'login_nexus';
}

export interface IPresetStepCommandLine extends IPresetStepBase {
type: 'commandline';
arguments: ICommandLineArg[];
Expand Down Expand Up @@ -40,7 +44,8 @@ export type IPresetStep = IPresetStepCommandLine
| IPresetStepInstallMod
| IPresetStepHydrateState
| IPresetStepSetGame
| IPresetStepRestart;
| IPresetStepRestart
| IPresetStepLoginNexus;

export interface IPresetState {
completed: string[];
Expand Down
42 changes: 40 additions & 2 deletions src/util/PresetManager.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { ipcMain, ipcRenderer } from 'electron';
import * as path from 'path';
import { IExtensionApi } from '../types/IExtensionContext';

import { IPreset, IPresetsState, IPresetStep, PresetStepType } from '../types/IPreset';

import * as validation from '../validationCode/validation';
import { getApplication } from './application';
import { makeRemoteCallSync } from './electronRemote';

import * as fs from './fs';
Expand Down Expand Up @@ -47,12 +49,17 @@ class PresetManager {
private mStatePath: string;

private mError: Error;
private mApi: IExtensionApi;

constructor() {
this.readPresets();
this.setupState();
}

public setApi(api: IExtensionApi) {
this.mApi = api;
}

public readPresets() {
const packagePath = getVortexPath('package');

Expand Down Expand Up @@ -104,7 +111,7 @@ class PresetManager {
this.mState.presets = {};
}

Object.keys(this.mPresets).forEach(presetId => {
this.presetIds().forEach(presetId => {
if (this.mState.presets[presetId] === undefined) {
this.mState.presets[presetId] = { completed: [], data: {} };
}
Expand Down Expand Up @@ -182,6 +189,31 @@ class PresetManager {
log('info', 'done processing preset step', { step, forwarded });
} catch (err) {
log('error', 'preset step failed', { step: JSON.stringify(step), error: err.message, process: process.type });
this.mApi?.sendNotification?.({
type: 'warning',
message: 'Automatic setup interrupted',
noDismiss: true,
actions: [{
title: 'More',
action: () => {
this.mApi.showDialog('info', 'Automatic setup interrupted', {
text: 'Your copy of Vortex is set up to run a sequence of setup steps on startup. '
+ 'Since one step failed, that sequence was interrupted. '
+ 'If you have resolved the issue you can retry the failed step.',
}, [
{ label: 'Close' },
{ label: 'Retry' },
])
.then(result => {
if (result.action === 'Retry') {
this.processNext();
} else {
return Promise.resolve();
}
});
},
}]
});
return true;
}
try {
Expand Down Expand Up @@ -211,9 +243,14 @@ class PresetManager {
return incompleteStep?.id;
}

private presetIds(): string[] {
return Object.keys(this.mPresets).sort();
}

private nextIncompletePreset(): string {
log('info', 'next incomplete preset', this.mState.processing ?? 'not yet set');
// find a preset with incomplete steps
return Object.keys(this.mPresets)
return this.presetIds()
.find(presetId =>
this.mPresets[presetId].steps.find(step =>
!this.mState.presets[presetId].completed.includes(step.id)) !== undefined);
Expand All @@ -226,6 +263,7 @@ class PresetManager {
private nextStep(): { presetId: string, stepId: string } {
let { processing } = this.mState;
let nextStepId = this.nextStepInPreset(processing);
log('info', 'next step', { processing, nextStepId });

if (nextStepId === undefined) {
processing = this.mState.processing = this.nextIncompletePreset();
Expand Down
2 changes: 1 addition & 1 deletion src/validationCode/IPreset.validate.js

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions src/validationCode/validation.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import validateLnwojjqti from "./IPreset.validate";export function validateIPreset(data): any[] {
var res = validateLnwojjqti(data);
return (res === false) ? validateLnwojjqti.prototype.constructor.errors : [];
import validateeYTbsgyP from "./IPreset.validate";export function validateIPreset(data): any[] {
var res = validateeYTbsgyP(data);
return (res === false) ? validateeYTbsgyP.prototype.constructor.errors : [];
}export function validateIPresetsState(data): any[] {
var res = validateLnwojjqti(data);
return (res === false) ? validateLnwojjqti.prototype.constructor.errors : [];
var res = validateeYTbsgyP(data);
return (res === false) ? validateeYTbsgyP.prototype.constructor.errors : [];
}
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"target": "esnext",
"module": "commonjs",
"moduleResolution": "node",
"allowJs": true,
"noImplicitAny": false,
"removeComments": true,
"preserveConstEnums": true,
Expand Down

0 comments on commit c1455ba

Please sign in to comment.