Pull out common got error handling for generic use. Initial distribution loading (no application state storage yet).
This commit is contained in:
parent
15fd2c842a
commit
bc43d842e3
654
package-lock.json
generated
654
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
46
package.json
46
package.json
@ -31,13 +31,13 @@
|
||||
"dependencies": {
|
||||
"adm-zip": "^0.4.16",
|
||||
"async": "^3.2.0",
|
||||
"discord-rpc": "^3.1.1",
|
||||
"electron-updater": "^4.3.1",
|
||||
"discord-rpc": "^3.1.3",
|
||||
"electron-updater": "^4.3.4",
|
||||
"fs-extra": "^9.0.1",
|
||||
"github-syntax-dark": "^0.5.0",
|
||||
"got": "^11.5.0",
|
||||
"got": "^11.5.2",
|
||||
"jquery": "^3.5.1",
|
||||
"lodash": "^4.17.19",
|
||||
"lodash": "^4.17.20",
|
||||
"moment": "^2.27.0",
|
||||
"request": "^2.88.2",
|
||||
"semver": "^7.3.2",
|
||||
@ -50,46 +50,46 @@
|
||||
"@babel/preset-react": "^7.10.4",
|
||||
"@types/adm-zip": "^0.4.33",
|
||||
"@types/async": "^3.2.3",
|
||||
"@types/chai": "^4.2.11",
|
||||
"@types/chai": "^4.2.12",
|
||||
"@types/discord-rpc": "^3.0.4",
|
||||
"@types/fs-extra": "^9.0.1",
|
||||
"@types/jquery": "^3.5.0",
|
||||
"@types/lodash": "^4.14.157",
|
||||
"@types/mocha": "^8.0.0",
|
||||
"@types/node": "^12.12.50",
|
||||
"@types/react": "^16.9.43",
|
||||
"@types/jquery": "^3.5.1",
|
||||
"@types/lodash": "^4.14.160",
|
||||
"@types/mocha": "^8.0.3",
|
||||
"@types/node": "^12.12.54",
|
||||
"@types/react": "^16.9.46",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
"@types/react-redux": "^7.1.9",
|
||||
"@types/react-transition-group": "^4.4.0",
|
||||
"@types/request": "^2.48.5",
|
||||
"@types/tar-fs": "^2.0.0",
|
||||
"@types/triple-beam": "^1.3.1",
|
||||
"@types/triple-beam": "^1.3.2",
|
||||
"@types/winreg": "^1.2.30",
|
||||
"@typescript-eslint/eslint-plugin": "^3.6.1",
|
||||
"@typescript-eslint/parser": "^3.6.1",
|
||||
"@typescript-eslint/eslint-plugin": "^3.10.0",
|
||||
"@typescript-eslint/parser": "^3.10.0",
|
||||
"chai": "^4.2.0",
|
||||
"cross-env": "^7.0.2",
|
||||
"electron": "^9.1.0",
|
||||
"electron-builder": "^22.7.0",
|
||||
"electron-devtools-installer": "^3.1.0",
|
||||
"electron": "^9.2.1",
|
||||
"electron-builder": "^22.8.0",
|
||||
"electron-devtools-installer": "^3.1.1",
|
||||
"electron-webpack": "^2.8.2",
|
||||
"electron-webpack-ts": "^4.0.1",
|
||||
"eslint": "^7.4.0",
|
||||
"eslint-plugin-react": "^7.20.3",
|
||||
"eslint": "^7.7.0",
|
||||
"eslint-plugin-react": "^7.20.6",
|
||||
"helios-distribution-types": "1.0.0-pre.1",
|
||||
"mocha": "^8.0.1",
|
||||
"nock": "^13.0.2",
|
||||
"mocha": "^8.1.1",
|
||||
"nock": "^13.0.4",
|
||||
"react": "^16.13.0",
|
||||
"react-dom": "^16.13.0",
|
||||
"react-hot-loader": "^4.12.21",
|
||||
"react-redux": "^7.2.0",
|
||||
"react-redux": "^7.2.1",
|
||||
"react-transition-group": "^4.4.1",
|
||||
"redux": "^4.0.5",
|
||||
"rimraf": "^3.0.2",
|
||||
"ts-node": "^8.10.2",
|
||||
"tsconfig-paths": "^3.9.0",
|
||||
"typescript": "^3.9.6",
|
||||
"webpack": "^4.43.0"
|
||||
"typescript": "^3.9.7",
|
||||
"webpack": "^4.44.1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
106
src/common/distribution/distribution.ts
Normal file
106
src/common/distribution/distribution.ts
Normal file
@ -0,0 +1,106 @@
|
||||
import { resolve } from 'path'
|
||||
import { Distribution } from 'helios-distribution-types'
|
||||
import got from 'got'
|
||||
import { LoggerUtil } from 'common/logging/loggerutil'
|
||||
import { RestResponse, handleGotError, RestResponseStatus } from 'common/got/RestResponse'
|
||||
import { pathExists, readFile, writeFile } from 'fs-extra'
|
||||
|
||||
export class DistributionAPI {
|
||||
|
||||
private static readonly logger = LoggerUtil.getLogger('DistributionAPI')
|
||||
|
||||
private readonly REMOTE_URL = 'http://mc.westeroscraft.com/WesterosCraftLauncher/distribution.json'
|
||||
|
||||
private readonly DISTRO_FILE = 'distribution.json'
|
||||
private readonly DISTRO_FILE_DEV = 'distribution_dev.json'
|
||||
|
||||
private readonly DEV_MODE = false // placeholder
|
||||
|
||||
private distroPath: string
|
||||
private distroDevPath: string
|
||||
|
||||
private rawDistribution!: Distribution
|
||||
|
||||
constructor(
|
||||
private launcherDirectory: string
|
||||
) {
|
||||
this.distroPath = resolve(launcherDirectory, this.DISTRO_FILE)
|
||||
this.distroDevPath = resolve(launcherDirectory, this.DISTRO_FILE_DEV)
|
||||
}
|
||||
|
||||
public async testLoad(): Promise<Distribution> {
|
||||
await this.loadDistribution()
|
||||
return this.rawDistribution
|
||||
}
|
||||
|
||||
protected async loadDistribution(): Promise<void> {
|
||||
|
||||
let distro
|
||||
|
||||
if(!this.DEV_MODE) {
|
||||
|
||||
distro = (await this.pullRemote()).data
|
||||
if(distro == null) {
|
||||
distro = await this.pullLocal(false)
|
||||
} else {
|
||||
this.writeDistributionToDisk(distro)
|
||||
}
|
||||
|
||||
} else {
|
||||
distro = await this.pullLocal(true)
|
||||
}
|
||||
|
||||
if(distro == null) {
|
||||
// TODO Bubble this up nicer
|
||||
throw new Error('FATAL: Unable to load distribution from remote server or local disk.')
|
||||
}
|
||||
|
||||
this.rawDistribution = distro
|
||||
}
|
||||
|
||||
protected async pullRemote(): Promise<RestResponse<Distribution | null>> {
|
||||
|
||||
try {
|
||||
|
||||
const res = await got.get<Distribution>(this.REMOTE_URL, { responseType: 'json' })
|
||||
|
||||
return {
|
||||
data: res.body,
|
||||
responseStatus: RestResponseStatus.SUCCESS
|
||||
}
|
||||
|
||||
} catch(error) {
|
||||
|
||||
return handleGotError('Pull Remote', error, DistributionAPI.logger, () => null)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected async writeDistributionToDisk(distribution: Distribution): Promise<void> {
|
||||
await writeFile(this.distroPath, distribution)
|
||||
}
|
||||
|
||||
protected async pullLocal(dev: boolean): Promise<Distribution | null> {
|
||||
return await this.readDistributionFromFile(!dev ? this.distroPath : this.distroDevPath)
|
||||
}
|
||||
|
||||
|
||||
protected async readDistributionFromFile(path: string): Promise<Distribution | null> {
|
||||
|
||||
if(await pathExists(path)) {
|
||||
const raw = await readFile(path, 'utf-8')
|
||||
try {
|
||||
return JSON.parse(raw)
|
||||
} catch(error) {
|
||||
DistributionAPI.logger.error(`Malformed distribution file at ${path}`)
|
||||
return null
|
||||
}
|
||||
} else {
|
||||
DistributionAPI.logger.error(`No distribution file found at ${path}!`)
|
||||
return null
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
43
src/common/got/RestResponse.ts
Normal file
43
src/common/got/RestResponse.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { RequestError, HTTPError, TimeoutError, ParseError } from 'got'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
export enum RestResponseStatus {
|
||||
|
||||
SUCCESS,
|
||||
ERROR
|
||||
|
||||
}
|
||||
|
||||
export interface RestResponse<T> {
|
||||
|
||||
data: T
|
||||
responseStatus: RestResponseStatus
|
||||
error?: RequestError
|
||||
|
||||
}
|
||||
|
||||
export function handleGotError<T>(operation: string, error: RequestError, logger: Logger, dataProvider: () => T): RestResponse<T> {
|
||||
const response: RestResponse<T> = {
|
||||
data: dataProvider(),
|
||||
responseStatus: RestResponseStatus.ERROR,
|
||||
error
|
||||
}
|
||||
|
||||
if(error instanceof HTTPError) {
|
||||
logger.error(`Error during ${operation} request (HTTP Response ${error.response.statusCode})`, error)
|
||||
logger.debug('Response Details:')
|
||||
logger.debug('Body:', error.response.body)
|
||||
logger.debug('Headers:', error.response.headers)
|
||||
} else if(Object.getPrototypeOf(error) instanceof RequestError) {
|
||||
logger.error(`${operation} request recieved no response (${error.code}).`, error)
|
||||
} else if(error instanceof TimeoutError) {
|
||||
logger.error(`${operation} request timed out (${error.timings.phases.total}ms).`)
|
||||
} else if(error instanceof ParseError) {
|
||||
logger.error(`${operation} request recieved unexepected body (Parse Error).`)
|
||||
} else {
|
||||
// CacheError, ReadError, MaxRedirectsError, UnsupportedProtocolError, CancelError
|
||||
logger.error(`Error during ${operation} request.`, error)
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
@ -1,8 +1,12 @@
|
||||
import { createLogger, format, transports, Logger } from 'winston'
|
||||
import { SPLAT } from 'triple-beam'
|
||||
import { SPLAT as SPLAT_Symbol } from 'triple-beam'
|
||||
import moment from 'moment'
|
||||
import { inspect } from 'util'
|
||||
|
||||
// Workaround until fixed.
|
||||
// https://github.com/winstonjs/logform/issues/111
|
||||
const SPLAT = SPLAT_Symbol as unknown as string
|
||||
|
||||
export class LoggerUtil {
|
||||
|
||||
public static getLogger(label: string): Logger {
|
||||
|
87
src/common/mojang/model/internal/MojangResponse.ts
Normal file
87
src/common/mojang/model/internal/MojangResponse.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import { RestResponse } from 'common/got/RestResponse'
|
||||
|
||||
/**
|
||||
* @see https://wiki.vg/Authentication#Errors
|
||||
*/
|
||||
export enum MojangErrorCode {
|
||||
ERROR_METHOD_NOT_ALLOWED, // INTERNAL
|
||||
ERROR_NOT_FOUND, // INTERNAL
|
||||
ERROR_USER_MIGRATED,
|
||||
ERROR_INVALID_CREDENTIALS,
|
||||
ERROR_RATELIMIT,
|
||||
ERROR_INVALID_TOKEN,
|
||||
ERROR_ACCESS_TOKEN_HAS_PROFILE, // ??
|
||||
ERROR_CREDENTIALS_ARE_NULL, // INTERNAL
|
||||
ERROR_INVALID_SALT_VERSION, // ??
|
||||
ERROR_UNSUPPORTED_MEDIA_TYPE, // INTERNAL
|
||||
UNKNOWN
|
||||
}
|
||||
|
||||
export interface MojangResponse<T> extends RestResponse<T> {
|
||||
mojangErrorCode?: MojangErrorCode
|
||||
isInternalError?: boolean
|
||||
}
|
||||
|
||||
export interface MojangErrorBody {
|
||||
error: string
|
||||
errorMessage: string
|
||||
cause?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the error response code from the response body.
|
||||
*
|
||||
* @param body The mojang error body response.
|
||||
*/
|
||||
export function decipherErrorCode(body: MojangErrorBody): MojangErrorCode {
|
||||
|
||||
if(body.error === 'Method Not Allowed') {
|
||||
return MojangErrorCode.ERROR_METHOD_NOT_ALLOWED
|
||||
} else if(body.error === 'Not Found') {
|
||||
return MojangErrorCode.ERROR_NOT_FOUND
|
||||
} else if(body.error === 'Unsupported Media Type') {
|
||||
return MojangErrorCode.ERROR_UNSUPPORTED_MEDIA_TYPE
|
||||
} else if(body.error === 'ForbiddenOperationException') {
|
||||
|
||||
if(body.cause && body.cause === 'UserMigratedException') {
|
||||
return MojangErrorCode.ERROR_USER_MIGRATED
|
||||
}
|
||||
|
||||
if(body.errorMessage === 'Invalid credentials. Invalid username or password.') {
|
||||
return MojangErrorCode.ERROR_INVALID_CREDENTIALS
|
||||
} else if(body.errorMessage === 'Invalid credentials.') {
|
||||
return MojangErrorCode.ERROR_RATELIMIT
|
||||
} else if(body.errorMessage === 'Invalid token.') {
|
||||
return MojangErrorCode.ERROR_INVALID_TOKEN
|
||||
}
|
||||
|
||||
} else if(body.error === 'IllegalArgumentException') {
|
||||
|
||||
if(body.errorMessage === 'Access token already has a profile assigned.') {
|
||||
return MojangErrorCode.ERROR_ACCESS_TOKEN_HAS_PROFILE
|
||||
} else if(body.errorMessage === 'credentials is null') {
|
||||
return MojangErrorCode.ERROR_CREDENTIALS_ARE_NULL
|
||||
} else if(body.errorMessage === 'Invalid salt version') {
|
||||
return MojangErrorCode.ERROR_INVALID_SALT_VERSION
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return MojangErrorCode.UNKNOWN
|
||||
|
||||
}
|
||||
|
||||
// These indicate problems with the code and not the data.
|
||||
export function isInternalError(errorCode: MojangErrorCode): boolean {
|
||||
switch(errorCode) {
|
||||
case MojangErrorCode.ERROR_METHOD_NOT_ALLOWED: // We've sent the wrong method to an endpoint. (ex. GET to POST)
|
||||
case MojangErrorCode.ERROR_NOT_FOUND: // Indicates endpoint has changed. (404)
|
||||
case MojangErrorCode.ERROR_ACCESS_TOKEN_HAS_PROFILE: // Selecting profiles isn't implemented yet. (Shouldnt happen)
|
||||
case MojangErrorCode.ERROR_CREDENTIALS_ARE_NULL: // Username/password was not submitted. (UI should forbid this)
|
||||
case MojangErrorCode.ERROR_INVALID_SALT_VERSION: // ??? (Shouldnt happen)
|
||||
case MojangErrorCode.ERROR_UNSUPPORTED_MEDIA_TYPE: // Data was not submitted as application/json
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
import { RequestError } from 'got'
|
||||
|
||||
/**
|
||||
* @see https://wiki.vg/Authentication#Errors
|
||||
*/
|
||||
export enum MojangResponseCode {
|
||||
SUCCESS,
|
||||
ERROR,
|
||||
ERROR_METHOD_NOT_ALLOWED, // INTERNAL
|
||||
ERROR_NOT_FOUND, // INTERNAL
|
||||
ERROR_USER_MIGRATED,
|
||||
ERROR_INVALID_CREDENTIALS,
|
||||
ERROR_RATELIMIT,
|
||||
ERROR_INVALID_TOKEN,
|
||||
ERROR_ACCESS_TOKEN_HAS_PROFILE, // ??
|
||||
ERROR_CREDENTIALS_ARE_NULL, // INTERNAL
|
||||
ERROR_INVALID_SALT_VERSION, // ??
|
||||
ERROR_UNSUPPORTED_MEDIA_TYPE // INTERNAL
|
||||
}
|
||||
|
||||
export interface MojangResponse<T> {
|
||||
|
||||
data: T
|
||||
responseCode: MojangResponseCode
|
||||
error?: RequestError
|
||||
isInternalError?: boolean
|
||||
|
||||
}
|
||||
|
||||
export interface MojangErrorBody {
|
||||
error: string
|
||||
errorMessage: string
|
||||
cause?: string
|
||||
}
|
||||
|
||||
export function deciperResponseCode(body: MojangErrorBody): MojangResponseCode {
|
||||
|
||||
if(body.error === 'Method Not Allowed') {
|
||||
return MojangResponseCode.ERROR_METHOD_NOT_ALLOWED
|
||||
} else if(body.error === 'Not Found') {
|
||||
return MojangResponseCode.ERROR_NOT_FOUND
|
||||
} else if(body.error === 'Unsupported Media Type') {
|
||||
return MojangResponseCode.ERROR_UNSUPPORTED_MEDIA_TYPE
|
||||
} else if(body.error === 'ForbiddenOperationException') {
|
||||
|
||||
if(body.cause && body.cause === 'UserMigratedException') {
|
||||
return MojangResponseCode.ERROR_USER_MIGRATED
|
||||
}
|
||||
|
||||
if(body.errorMessage === 'Invalid credentials. Invalid username or password.') {
|
||||
return MojangResponseCode.ERROR_INVALID_CREDENTIALS
|
||||
} else if(body.errorMessage === 'Invalid credentials.') {
|
||||
return MojangResponseCode.ERROR_RATELIMIT
|
||||
} else if(body.errorMessage === 'Invalid token.') {
|
||||
return MojangResponseCode.ERROR_INVALID_TOKEN
|
||||
}
|
||||
|
||||
} else if(body.error === 'IllegalArgumentException') {
|
||||
|
||||
if(body.errorMessage === 'Access token already has a profile assigned.') {
|
||||
return MojangResponseCode.ERROR_ACCESS_TOKEN_HAS_PROFILE
|
||||
} else if(body.errorMessage === 'credentials is null') {
|
||||
return MojangResponseCode.ERROR_CREDENTIALS_ARE_NULL
|
||||
} else if(body.errorMessage === 'Invalid salt version') {
|
||||
return MojangResponseCode.ERROR_INVALID_SALT_VERSION
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return MojangResponseCode.ERROR
|
||||
|
||||
}
|
||||
|
||||
// These indicate problems with the code and not the data.
|
||||
export function isInternalError(responseCode: MojangResponseCode): boolean {
|
||||
switch(responseCode) {
|
||||
case MojangResponseCode.ERROR_METHOD_NOT_ALLOWED: // We've sent the wrong method to an endpoint. (ex. GET to POST)
|
||||
case MojangResponseCode.ERROR_NOT_FOUND: // Indicates endpoint has changed. (404)
|
||||
case MojangResponseCode.ERROR_ACCESS_TOKEN_HAS_PROFILE: // Selecting profiles isn't implemented yet. (Shouldnt happen)
|
||||
case MojangResponseCode.ERROR_CREDENTIALS_ARE_NULL: // Username/password was not submitted. (UI should forbid this)
|
||||
case MojangResponseCode.ERROR_INVALID_SALT_VERSION: // ??? (Shouldnt happen)
|
||||
case MojangResponseCode.ERROR_UNSUPPORTED_MEDIA_TYPE: // Data was not submitted as application/json
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
@ -1,10 +1,11 @@
|
||||
import { LoggerUtil } from '../logging/loggerutil'
|
||||
import { Agent } from './model/auth/Agent'
|
||||
import { Status, StatusColor } from './model/internal/Status'
|
||||
import got, { RequestError, HTTPError, TimeoutError, ParseError } from 'got'
|
||||
import got, { RequestError, HTTPError } from 'got'
|
||||
import { Session } from './model/auth/Session'
|
||||
import { AuthPayload } from './model/auth/AuthPayload'
|
||||
import { MojangResponse, MojangResponseCode, deciperResponseCode, isInternalError, MojangErrorBody } from './model/internal/Response'
|
||||
import { MojangResponse, MojangErrorCode, decipherErrorCode, isInternalError, MojangErrorBody } from './model/internal/MojangResponse'
|
||||
import { RestResponseStatus, handleGotError } from 'common/got/RestResponse'
|
||||
|
||||
export class Mojang {
|
||||
|
||||
@ -90,30 +91,15 @@ export class Mojang {
|
||||
}
|
||||
|
||||
private static handleGotError<T>(operation: string, error: RequestError, dataProvider: () => T): MojangResponse<T> {
|
||||
const response: MojangResponse<T> = {
|
||||
data: dataProvider(),
|
||||
responseCode: MojangResponseCode.ERROR,
|
||||
error
|
||||
}
|
||||
|
||||
const response: MojangResponse<T> = handleGotError(operation, error, Mojang.logger, dataProvider)
|
||||
|
||||
if(error instanceof HTTPError) {
|
||||
response.responseCode = deciperResponseCode(error.response.body as MojangErrorBody)
|
||||
Mojang.logger.error(`Error during ${operation} request (HTTP Response ${error.response.statusCode})`, error)
|
||||
Mojang.logger.debug('Response Details:')
|
||||
Mojang.logger.debug('Body:', error.response.body)
|
||||
Mojang.logger.debug('Headers:', error.response.headers)
|
||||
} else if(Object.getPrototypeOf(error) instanceof RequestError) {
|
||||
Mojang.logger.error(`${operation} request recieved no response (${error.code}).`, error)
|
||||
} else if(error instanceof TimeoutError) {
|
||||
Mojang.logger.error(`${operation} request timed out (${error.timings.phases.total}ms).`)
|
||||
} else if(error instanceof ParseError) {
|
||||
Mojang.logger.error(`${operation} request recieved unexepected body (Parse Error).`)
|
||||
response.mojangErrorCode = decipherErrorCode(error.response.body as MojangErrorBody)
|
||||
} else {
|
||||
// CacheError, ReadError, MaxRedirectsError, UnsupportedProtocolError, CancelError
|
||||
Mojang.logger.error(`Error during ${operation} request.`, error)
|
||||
response.mojangErrorCode = MojangErrorCode.UNKNOWN
|
||||
}
|
||||
|
||||
response.isInternalError = isInternalError(response.responseCode)
|
||||
response.isInternalError = isInternalError(response.mojangErrorCode)
|
||||
|
||||
return response
|
||||
}
|
||||
@ -151,7 +137,7 @@ export class Mojang {
|
||||
|
||||
return {
|
||||
data: Mojang.statuses,
|
||||
responseCode: MojangResponseCode.SUCCESS
|
||||
responseStatus: RestResponseStatus.SUCCESS
|
||||
}
|
||||
|
||||
} catch(error) {
|
||||
@ -201,7 +187,7 @@ export class Mojang {
|
||||
Mojang.expectSpecificSuccess('Mojang Authenticate', 200, res.statusCode)
|
||||
return {
|
||||
data: res.body,
|
||||
responseCode: MojangResponseCode.SUCCESS
|
||||
responseStatus: RestResponseStatus.SUCCESS
|
||||
}
|
||||
|
||||
} catch(err) {
|
||||
@ -233,14 +219,14 @@ export class Mojang {
|
||||
|
||||
return {
|
||||
data: res.statusCode === 204,
|
||||
responseCode: MojangResponseCode.SUCCESS
|
||||
responseStatus: RestResponseStatus.SUCCESS
|
||||
}
|
||||
|
||||
} catch(err) {
|
||||
if(err instanceof HTTPError && err.response.statusCode === 403) {
|
||||
return {
|
||||
data: false,
|
||||
responseCode: MojangResponseCode.SUCCESS
|
||||
responseStatus: RestResponseStatus.SUCCESS
|
||||
}
|
||||
}
|
||||
return Mojang.handleGotError('Mojang Validate', err, () => false)
|
||||
@ -271,7 +257,7 @@ export class Mojang {
|
||||
|
||||
return {
|
||||
data: undefined,
|
||||
responseCode: MojangResponseCode.SUCCESS
|
||||
responseStatus: RestResponseStatus.SUCCESS
|
||||
}
|
||||
|
||||
} catch(err) {
|
||||
@ -306,7 +292,7 @@ export class Mojang {
|
||||
|
||||
return {
|
||||
data: res.body,
|
||||
responseCode: MojangResponseCode.SUCCESS
|
||||
responseStatus: RestResponseStatus.SUCCESS
|
||||
}
|
||||
|
||||
} catch(err) {
|
||||
|
@ -113,7 +113,9 @@ async function createWindow() {
|
||||
webPreferences: {
|
||||
preload: join(__dirname, '..', 'out', 'preloader.js'),
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false
|
||||
contextIsolation: false,
|
||||
enableRemoteModule: true,
|
||||
worldSafeExecuteJavaScript: true
|
||||
},
|
||||
backgroundColor: '#171614'
|
||||
})
|
||||
|
@ -17,6 +17,8 @@ import { join } from 'path'
|
||||
import Overlay from './overlay/Overlay'
|
||||
import { OverlayPushAction, OverlayActionDispatch } from '../redux/actions/overlayActions'
|
||||
|
||||
import { DistributionAPI } from 'common/distribution/distribution'
|
||||
|
||||
import './Application.css'
|
||||
|
||||
declare const __static: string
|
||||
@ -120,9 +122,14 @@ class Application extends React.Component<ApplicationProps & typeof mapDispatch,
|
||||
setTimeout(() => {
|
||||
//this.props.setView(View.WELCOME)
|
||||
this.props.pushGenericOverlay({
|
||||
title: 'Test Title',
|
||||
description: 'Test Description',
|
||||
dismissible: true
|
||||
title: 'Load Distribution',
|
||||
description: 'This is a test. Will load the distribution.',
|
||||
dismissible: false,
|
||||
acknowledgeCallback: async () => {
|
||||
const distro = new DistributionAPI('C:\\Users\\user\\AppData\\Roaming\\Helios Launcher')
|
||||
const x = await distro.testLoad()
|
||||
console.log(x)
|
||||
}
|
||||
})
|
||||
this.props.pushGenericOverlay({
|
||||
title: 'Test Title 2',
|
||||
|
@ -10,8 +10,8 @@ export interface GenericOverlayProps {
|
||||
acknowledgeText?: string
|
||||
dismissText?: string
|
||||
dismissible: boolean
|
||||
acknowledgeCallback?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void
|
||||
dismissCallback?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void
|
||||
acknowledgeCallback?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => Promise<void>
|
||||
dismissCallback?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => Promise<void>
|
||||
}
|
||||
|
||||
const mapDispatch = {
|
||||
@ -30,16 +30,16 @@ class GenericOverlay extends React.Component<InternalGenericOverlayProps> {
|
||||
return this.props.dismissText || 'Dismiss'
|
||||
}
|
||||
|
||||
private onAcknowledgeClick = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>): void => {
|
||||
private onAcknowledgeClick = async (event: React.MouseEvent<HTMLButtonElement, MouseEvent>): Promise<void> => {
|
||||
if(this.props.acknowledgeCallback) {
|
||||
this.props.acknowledgeCallback(event)
|
||||
await this.props.acknowledgeCallback(event)
|
||||
}
|
||||
this.props.popOverlayContent()
|
||||
}
|
||||
|
||||
private onDismissClick = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>): void => {
|
||||
private onDismissClick = async (event: React.MouseEvent<HTMLButtonElement, MouseEvent>): Promise<void> => {
|
||||
if(this.props.dismissCallback) {
|
||||
this.props.dismissCallback(event)
|
||||
await this.props.dismissCallback(event)
|
||||
}
|
||||
this.props.popOverlayContent()
|
||||
}
|
||||
|
@ -1,17 +1,33 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { Mojang } from 'common/mojang/mojang'
|
||||
import { expect } from 'chai'
|
||||
import nock from 'nock'
|
||||
import { Session } from 'common/mojang/model/auth/Session'
|
||||
import { MojangResponseCode } from 'common/mojang/model/internal/Response'
|
||||
import { MojangErrorCode, MojangResponse } from 'common/mojang/model/internal/MojangResponse'
|
||||
import { RestResponseStatus, RestResponse } from 'common/got/RestResponse'
|
||||
|
||||
function expectMojangResponse(res: any, responseCode: MojangResponseCode, negate = false) {
|
||||
function assertResponse(res: RestResponse<unknown>) {
|
||||
expect(res).to.not.be.an('error')
|
||||
expect(res).to.be.an('object')
|
||||
expect(res).to.have.property('responseCode')
|
||||
}
|
||||
|
||||
function expectSuccess(res: RestResponse<unknown>) {
|
||||
assertResponse(res)
|
||||
expect(res).to.have.property('responseStatus')
|
||||
expect(res.responseStatus).to.equal(RestResponseStatus.SUCCESS)
|
||||
}
|
||||
|
||||
function expectFailure(res: RestResponse<unknown>) {
|
||||
expect(res.responseStatus).to.not.equal(RestResponseStatus.SUCCESS)
|
||||
}
|
||||
|
||||
function expectMojangResponse(res: MojangResponse<unknown>, responseCode: MojangErrorCode, negate = false) {
|
||||
assertResponse(res)
|
||||
expect(res).to.have.property('mojangErrorCode')
|
||||
if(!negate) {
|
||||
expect(res.responseCode).to.equal(responseCode)
|
||||
expect(res.mojangErrorCode).to.equal(responseCode)
|
||||
} else {
|
||||
expect(res.responseCode).to.not.equal(responseCode)
|
||||
expect(res.mojangErrorCode).to.not.equal(responseCode)
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,7 +46,7 @@ describe('Mojang Errors', () => {
|
||||
.reply(500, 'Service temprarily offline.')
|
||||
|
||||
const res = await Mojang.status()
|
||||
expectMojangResponse(res, MojangResponseCode.SUCCESS, true)
|
||||
expectFailure(res)
|
||||
expect(res.data).to.be.an('array')
|
||||
expect(res.data).to.deep.equal(defStatusHack)
|
||||
|
||||
@ -40,7 +56,8 @@ describe('Mojang Errors', () => {
|
||||
|
||||
nock(Mojang.AUTH_ENDPOINT)
|
||||
.post('/authenticate')
|
||||
.reply(403, (uri, requestBody: any): { error: string, errorMessage: string } => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
.reply(403, (uri, requestBody: unknown): { error: string, errorMessage: string } => {
|
||||
return {
|
||||
error: 'ForbiddenOperationException',
|
||||
errorMessage: 'Invalid credentials. Invalid username or password.'
|
||||
@ -48,7 +65,7 @@ describe('Mojang Errors', () => {
|
||||
})
|
||||
|
||||
const res = await Mojang.authenticate('user', 'pass', 'xxx', true)
|
||||
expectMojangResponse(res, MojangResponseCode.ERROR_INVALID_CREDENTIALS)
|
||||
expectMojangResponse(res, MojangErrorCode.ERROR_INVALID_CREDENTIALS)
|
||||
expect(res.data).to.be.a('null')
|
||||
expect(res.error).to.not.be.a('null')
|
||||
|
||||
@ -66,7 +83,7 @@ describe('Mojang Status', () => {
|
||||
.reply(200, defStatusHack)
|
||||
|
||||
const res = await Mojang.status()
|
||||
expectMojangResponse(res, MojangResponseCode.SUCCESS)
|
||||
expectSuccess(res)
|
||||
expect(res.data).to.be.an('array')
|
||||
expect(res.data).to.deep.equal(defStatusHack)
|
||||
|
||||
@ -101,7 +118,7 @@ describe('Mojang Auth', () => {
|
||||
})
|
||||
|
||||
const res = await Mojang.authenticate('user', 'pass', 'xxx', true)
|
||||
expectMojangResponse(res, MojangResponseCode.SUCCESS)
|
||||
expectSuccess(res)
|
||||
expect(res.data!.clientToken).to.equal('xxx')
|
||||
expect(res.data).to.have.property('user')
|
||||
|
||||
@ -120,13 +137,13 @@ describe('Mojang Auth', () => {
|
||||
|
||||
const res = await Mojang.validate('abc', 'def')
|
||||
|
||||
expectMojangResponse(res, MojangResponseCode.SUCCESS)
|
||||
expectSuccess(res)
|
||||
expect(res.data).to.be.a('boolean')
|
||||
expect(res.data).to.equal(true)
|
||||
|
||||
const res2 = await Mojang.validate('def', 'def')
|
||||
|
||||
expectMojangResponse(res2, MojangResponseCode.SUCCESS)
|
||||
expectSuccess(res2)
|
||||
expect(res2.data).to.be.a('boolean')
|
||||
expect(res2.data).to.equal(false)
|
||||
|
||||
@ -140,7 +157,7 @@ describe('Mojang Auth', () => {
|
||||
|
||||
const res = await Mojang.invalidate('adc', 'def')
|
||||
|
||||
expectMojangResponse(res, MojangResponseCode.SUCCESS)
|
||||
expectSuccess(res)
|
||||
|
||||
})
|
||||
|
||||
@ -169,7 +186,7 @@ describe('Mojang Auth', () => {
|
||||
})
|
||||
|
||||
const res = await Mojang.refresh('gfd', 'xxx', true)
|
||||
expectMojangResponse(res, MojangResponseCode.SUCCESS)
|
||||
expectSuccess(res)
|
||||
expect(res.data!.clientToken).to.equal('xxx')
|
||||
expect(res.data).to.have.property('user')
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user