From 3c6025790efe42e119d4c9c74d2d92489b460fb6 Mon Sep 17 00:00:00 2001 From: Samuel Elliott Date: Tue, 4 Jul 2023 22:01:22 +0100 Subject: [PATCH] API error classes --- src/api/coral-types.ts | 4 +- src/api/coral.ts | 24 +++--- src/api/moon.ts | 6 +- src/api/na.ts | 29 ++++--- src/api/nooklink.ts | 18 +++-- src/api/splatnet2.ts | 18 +++-- src/api/splatnet3.ts | 139 ++++++++++++++++++++++++-------- src/api/znc-proxy.ts | 10 ++- src/app/main/monitor.ts | 12 +-- src/app/main/na-auth.ts | 22 ++--- src/cli/nso/http-server.ts | 5 +- src/client/coral.ts | 4 +- src/client/splatnet3.ts | 5 +- src/common/auth/coral.ts | 4 +- src/common/auth/splatnet3.ts | 5 +- src/common/notify.ts | 4 +- src/common/presence.ts | 6 +- src/common/splatnet2/monitor.ts | 9 +-- src/exports/coral.ts | 2 + src/exports/moon.ts | 2 + src/exports/nintendo-account.ts | 3 + src/exports/nooklink.ts | 2 + src/exports/splatnet2.ts | 2 + src/exports/splatnet3.ts | 5 ++ src/util/errors.ts | 3 +- 25 files changed, 217 insertions(+), 126 deletions(-) diff --git a/src/api/coral-types.ts b/src/api/coral-types.ts index 1ab8646..f0d0ae4 100644 --- a/src/api/coral-types.ts +++ b/src/api/coral-types.ts @@ -5,7 +5,7 @@ export interface CoralSuccessResponse { correlationId: string; } -export interface CoralErrorResponse { +export interface CoralError { status: CoralStatus | number; errorMessage: string; correlationId: string; @@ -53,7 +53,7 @@ export enum CoralStatus { // UNKNOWN = -1, } -export type CoralResponse = CoralSuccessResponse | CoralErrorResponse; +export type CoralResponse = CoralSuccessResponse | CoralError; export interface AccountLoginParameter { naIdToken: string; diff --git a/src/api/coral.ts b/src/api/coral.ts index 90dbc93..537ef00 100644 --- a/src/api/coral.ts +++ b/src/api/coral.ts @@ -5,7 +5,7 @@ import { JwtPayload } from '../util/jwt.js'; import { timeoutSignal } from '../util/misc.js'; import { getAdditionalUserAgents } from '../util/useragent.js'; import type { CoralRemoteConfig } from '../common/remote-config.js'; -import { AccountLogin, AccountLoginParameter, AccountToken, AccountTokenParameter, Announcements, CoralErrorResponse, CoralResponse, CoralStatus, CoralSuccessResponse, CurrentUser, CurrentUserPermissions, Event, FriendCodeUrl, FriendCodeUser, Friends, GetActiveEventResult, PresencePermissions, User, WebServices, WebServiceToken, WebServiceTokenParameter } from './coral-types.js'; +import { AccountLogin, AccountLoginParameter, AccountToken, AccountTokenParameter, Announcements, CoralError, CoralResponse, CoralStatus, CoralSuccessResponse, CurrentUser, CurrentUserPermissions, Event, FriendCodeUrl, FriendCodeUser, Friends, GetActiveEventResult, PresencePermissions, User, WebServices, WebServiceToken, WebServiceTokenParameter } from './coral-types.js'; import { f, FResult, HashMethod } from './f.js'; import { generateAuthData, getNintendoAccountToken, getNintendoAccountUser, NintendoAccountSessionAuthorisation, NintendoAccountToken, NintendoAccountUser } from './na.js'; import { ErrorResponse, ResponseSymbol } from './util.js'; @@ -74,7 +74,7 @@ export default class CoralApi implements CoralApiInterface { [CoralUserIdSymbol]: string; [NintendoAccountIdSymbol]: string; - onTokenExpired: ((data?: CoralErrorResponse, res?: Response) => Promise) | null = null; + onTokenExpired: ((data?: CoralError, res?: Response) => Promise) | null = null; /** @internal */ _renewToken: Promise | null = null; /** @internal */ @@ -144,7 +144,7 @@ export default class CoralApi implements CoralApiInterface { debug('fetch %s %s, response %s', method, url, response.status); if (response.status !== 200) { - throw new ErrorResponse('[znc] Non-200 status code', response, await response.text()); + throw new CoralErrorResponse('[znc] Non-200 status code', response, await response.text()); } const data = await response.json() as CoralResponse; @@ -161,10 +161,10 @@ export default class CoralApi implements CoralApiInterface { } if ('errorMessage' in data) { - throw new ErrorResponse('[znc] ' + data.errorMessage, response, data); + throw new CoralErrorResponse('[znc] ' + data.errorMessage, response, data); } if (data.status !== CoralStatus.OK) { - throw new ErrorResponse('[znc] Unknown error', response, data); + throw new CoralErrorResponse('[znc] Unknown error', response, data); } const result = data.result; @@ -295,7 +295,7 @@ export default class CoralApi implements CoralApiInterface { try { return await this.call('/v2/Game/GetWebServiceToken', req, false); } catch (err) { - if (err instanceof ErrorResponse && err.data.status === CoralStatus.TOKEN_EXPIRED && !_attempt && this.onTokenExpired) { + if (err instanceof CoralErrorResponse && err.status === CoralStatus.TOKEN_EXPIRED && !_attempt && this.onTokenExpired) { debug('Error getting web service token, renewing token before retrying', err); // _renewToken will be awaited when calling getWebServiceToken this._renewToken = this._renewToken ?? this.onTokenExpired.call(null, err.data, err.response as Response).then(data => { @@ -448,16 +448,16 @@ export default class CoralApi implements CoralApiInterface { debug('fetch %s %s, response %s', 'POST', '/v3/Account/Login', response.status); if (response.status !== 200) { - throw new ErrorResponse('[znc] Non-200 status code', response, await response.text()); + throw new CoralErrorResponse('[znc] Non-200 status code', response, await response.text()); } const data = await response.json() as CoralResponse; if ('errorMessage' in data) { - throw new ErrorResponse('[znc] ' + data.errorMessage, response, data); + throw new CoralErrorResponse('[znc] ' + data.errorMessage, response, data); } if (data.status !== CoralStatus.OK) { - throw new ErrorResponse('[znc] Unknown error', response, data); + throw new CoralErrorResponse('[znc] Unknown error', response, data); } debug('Got Nintendo Switch Online app token', data); @@ -474,6 +474,12 @@ export default class CoralApi implements CoralApiInterface { } } +export class CoralErrorResponse extends ErrorResponse { + get status(): CoralStatus | null { + return this.data?.status ?? null; + } +} + const na_client_settings = { client_id: ZNCA_CLIENT_ID, scope: 'openid user user.birthday user.mii user.screenName', diff --git a/src/api/moon.ts b/src/api/moon.ts index 9dd471c..8b15142 100644 --- a/src/api/moon.ts +++ b/src/api/moon.ts @@ -86,13 +86,13 @@ export default class MoonApi { } if (response.status !== 200) { - throw new ErrorResponse('[moon] Non-200 status code', response, await response.text()); + throw new MoonErrorResponse('[moon] Non-200 status code', response, await response.text()); } const data = await response.json() as T | MoonError; if ('errorCode' in data) { - throw new ErrorResponse('[moon] ' + data.title, response, data); + throw new MoonErrorResponse('[moon] ' + data.title, response, data); } return defineResponse(data, response); @@ -177,6 +177,8 @@ export default class MoonApi { } } +export class MoonErrorResponse extends ErrorResponse {} + const na_client_settings = { client_id: ZNMA_CLIENT_ID, scope: [ diff --git a/src/api/na.ts b/src/api/na.ts index eabd4f6..8231a5f 100644 --- a/src/api/na.ts +++ b/src/api/na.ts @@ -126,16 +126,16 @@ export async function getNintendoAccountSessionToken(code: string, verifier: str }).finally(cancel); if (response.status !== 200) { - throw new ErrorResponse('[na] Non-200 status code', response, await response.text()); + throw new NintendoAccountAuthErrorResponse('[na] Non-200 status code', response, await response.text()); } const token = await response.json() as NintendoAccountSessionToken | NintendoAccountAuthError | NintendoAccountError; - if ('errorCode' in token) { - throw new ErrorResponse('[na] ' + token.detail, response, token); - } if ('error' in token) { - throw new ErrorResponse('[na] ' + token.error_description ?? token.error, response, token); + throw new NintendoAccountAuthErrorResponse('[na] ' + token.error_description ?? token.error, response, token); + } + if ('errorCode' in token) { + throw new NintendoAccountErrorResponse('[na] ' + token.detail, response, token); } debug('Got Nintendo Account session token', token); @@ -163,16 +163,17 @@ export async function getNintendoAccountToken(token: string, client_id: string) }).finally(cancel); if (response.status !== 200) { - throw new ErrorResponse('[na] Non-200 status code', response, await response.text()); + throw new NintendoAccountAuthErrorResponse('[na] Non-200 status code', response, await response.text()); } const nintendoAccountToken = await response.json() as NintendoAccountToken | NintendoAccountAuthError | NintendoAccountError; - if ('errorCode' in nintendoAccountToken) { - throw new ErrorResponse('[na] ' + nintendoAccountToken.detail, response, nintendoAccountToken); - } if ('error' in nintendoAccountToken) { - throw new ErrorResponse('[na] ' + nintendoAccountToken.error_description ?? nintendoAccountToken.error, response, nintendoAccountToken); + throw new NintendoAccountAuthErrorResponse('[na] ' + nintendoAccountToken.error_description ?? + nintendoAccountToken.error, response, nintendoAccountToken); + } + if ('errorCode' in nintendoAccountToken) { + throw new NintendoAccountErrorResponse('[na] ' + nintendoAccountToken.detail, response, nintendoAccountToken); } debug('Got Nintendo Account token', nintendoAccountToken); @@ -196,13 +197,13 @@ export async function getNintendoAccountUser(token: NintendoAccountToken) { }).finally(cancel); if (response.status !== 200) { - throw new ErrorResponse('[na] Non-200 status code', response, await response.text()); + throw new NintendoAccountErrorResponse('[na] Non-200 status code', response, await response.text()); } const user = await response.json() as NintendoAccountUser | NintendoAccountError; if ('errorCode' in user) { - throw new ErrorResponse('[na] ' + user.detail, response, user); + throw new NintendoAccountErrorResponse('[na] ' + user.detail, response, user); } debug('Got Nintendo Account user info', user); @@ -406,3 +407,7 @@ export interface NintendoAccountError { status: number; type: string; } + +export class NintendoAccountAuthErrorResponse extends ErrorResponse {} + +export class NintendoAccountErrorResponse extends ErrorResponse {} diff --git a/src/api/nooklink.ts b/src/api/nooklink.ts index e400082..cb23609 100644 --- a/src/api/nooklink.ts +++ b/src/api/nooklink.ts @@ -80,14 +80,14 @@ export default class NooklinkApi { return this.fetch(url, method, body, headers, _autoRenewToken, _attempt + 1); } - if (response.status !== 200 && response.status !== 201) { - throw new ErrorResponse('[nooklink] Non-200/201 status code', response, await response.text()); + if (!response.ok) { + throw new NooklinkErrorResponse('[nooklink] Non-2xx status code', response, await response.text()); } const data = await response.json() as T | WebServiceError; if ('code' in data) { - throw new ErrorResponse('[nooklink] Error ' + data.code, response, data); + throw new NooklinkErrorResponse('[nooklink] Error ' + data.code, response, data); } return defineResponse(data, response); @@ -175,14 +175,14 @@ export default class NooklinkApi { const body = await response.text(); if (response.status !== 200) { - throw new ErrorResponse('[nooklink] Non-200 status code', response, body); + throw new NooklinkErrorResponse('[nooklink] Non-200 status code', response, body); } const cookies = response.headers.get('Set-Cookie'); const match = cookies?.match(/\b_gtoken=([^;]*)(;(\s*((?!expires)[a-z]+=([^;]*));?)*(\s*(expires=([^;]*));?)?|$)/i); if (!match) { - throw new ErrorResponse('[nooklink] Response didn\'t include _gtoken cookie', response, body); + throw new NooklinkErrorResponse('[nooklink] Response didn\'t include _gtoken cookie', response, body); } const gtoken = decodeURIComponent(match[1]); @@ -275,14 +275,14 @@ export class NooklinkUserApi { return this.fetch(url, method, body, headers, _autoRenewToken, _attempt + 1); } - if (response.status !== 200 && response.status !== 201) { - throw new ErrorResponse('[nooklink] Non-200/201 status code', response, await response.text()); + if (!response.ok) { + throw new NooklinkErrorResponse('[nooklink] Non-2xx status code', response, await response.text()); } const data = await response.json() as T | WebServiceError; if ('code' in data) { - throw new ErrorResponse('[nooklink] Error ' + data.code, response, data); + throw new NooklinkErrorResponse('[nooklink] Error ' + data.code, response, data); } return defineResponse(data, response); @@ -391,6 +391,8 @@ export class NooklinkUserApi { } } +export class NooklinkErrorResponse extends ErrorResponse {} + export interface NooklinkAuthData { webserviceToken: WebServiceToken; url: string; diff --git a/src/api/splatnet2.ts b/src/api/splatnet2.ts index 3e4ab30..3c7096d 100644 --- a/src/api/splatnet2.ts +++ b/src/api/splatnet2.ts @@ -63,7 +63,7 @@ export default class SplatNet2Api { } if (response.status !== 200) { - throw new ErrorResponse('[splatnet2] Non-200 status code', response, await response.text()); + throw new SplatNet2ErrorResponse('[splatnet2] Non-200 status code', response, await response.text()); } updateIksmSessionLastUsed.handler?.call(null, this.iksm_session); @@ -71,7 +71,7 @@ export default class SplatNet2Api { const data = await response.json() as T | WebServiceError; if ('code' in data) { - throw new ErrorResponse('[splatnet2] ' + data.message, response, data); + throw new SplatNet2ErrorResponse('[splatnet2] ' + data.message, response, data); } return defineResponse(data, response); @@ -305,14 +305,14 @@ ${colour} const body = await response.text(); if (response.status !== 200) { - throw new ErrorResponse('[splatnet2] Non-200 status code', response, body); + throw new SplatNet2ErrorResponse('[splatnet2] Non-200 status code', response, body); } const cookies = response.headers.get('Set-Cookie'); const match = cookies?.match(/\biksm_session=([^;]*)(;(\s*((?!expires)[a-z]+=([^;]*));?)*(\s*(expires=([^;]*));?)?|$)/i); if (!match) { - throw new ErrorResponse('[splatnet2] Response didn\'t include iksm_session cookie', response, body); + throw new SplatNet2ErrorResponse('[splatnet2] Response didn\'t include iksm_session cookie', response, body); } const iksm_session = decodeURIComponent(match[1]); @@ -330,10 +330,10 @@ ${colour} const mn = body.match(/]*))?)*\s+data-nsa-id=(?:"([^"]*)"|([^\s>]*))/i); const [language, region, user_id, nsa_id] = [ml, mr, mu, mn].map(m => m?.[1] || m?.[2] || null); - if (!language) throw new Error('[splatnet2] Invalid language in response'); - if (!region) throw new Error('[splatnet2] Invalid region in response'); - if (!user_id) throw new Error('[splatnet2] Invalid unique player ID in response'); - if (!nsa_id) throw new Error('[splatnet2] Invalid NSA ID in response'); + if (!language) throw new ErrorResponse('[splatnet2] Invalid language in response', response, body); + if (!region) throw new ErrorResponse('[splatnet2] Invalid region in response', response, body); + if (!user_id) throw new ErrorResponse('[splatnet2] Invalid unique player ID in response', response, body); + if (!nsa_id) throw new ErrorResponse('[splatnet2] Invalid NSA ID in response', response, body); debug('SplatNet 2 user', { language, @@ -359,6 +359,8 @@ ${colour} } } +export class SplatNet2ErrorResponse extends ErrorResponse {} + export interface SplatNet2AuthData { webserviceToken: WebServiceToken; url: string; diff --git a/src/api/splatnet3.ts b/src/api/splatnet3.ts index 60d176d..e614e43 100644 --- a/src/api/splatnet3.ts +++ b/src/api/splatnet3.ts @@ -1,5 +1,5 @@ import fetch, { Response } from 'node-fetch'; -import { BankaraBattleHistoriesRefetchResult, BankaraBattleHistoriesRefetchVariables, GraphQLRequest, GraphQLResponse, GraphQLSuccessResponse, KnownRequestId, LatestBattleHistoriesRefetchResult, LatestBattleHistoriesRefetchVariables, MyOutfitInput, PagerUpdateBattleHistoriesByVsModeResult, PagerUpdateBattleHistoriesByVsModeVariables, PrivateBattleHistoriesRefetchResult, PrivateBattleHistoriesRefetchVariables, RegularBattleHistoriesRefetchResult, RegularBattleHistoriesRefetchVariables, RequestId, ResultTypes, VariablesTypes, XBattleHistoriesRefetchResult, XBattleHistoriesRefetchVariables } from 'splatnet3-types/splatnet3'; +import { BankaraBattleHistoriesRefetchResult, BankaraBattleHistoriesRefetchVariables, GraphQLError, GraphQLErrorResponse, GraphQLRequest, GraphQLResponse, GraphQLSuccessResponse, KnownRequestId, LatestBattleHistoriesRefetchResult, LatestBattleHistoriesRefetchVariables, MyOutfitInput, PagerUpdateBattleHistoriesByVsModeResult, PagerUpdateBattleHistoriesByVsModeVariables, PrivateBattleHistoriesRefetchResult, PrivateBattleHistoriesRefetchVariables, RegularBattleHistoriesRefetchResult, RegularBattleHistoriesRefetchVariables, RequestId, ResultTypes, VariablesTypes, XBattleHistoriesRefetchResult, XBattleHistoriesRefetchVariables } from 'splatnet3-types/splatnet3'; import { WebServiceToken } from './coral-types.js'; import { CoralApiInterface } from './coral.js'; import { NintendoAccountUser } from './na.js'; @@ -27,15 +27,25 @@ const SPLATNET3_URL = SPLATNET3_WEBSERVICE_URL + '/api'; const SHOULD_RENEW_TOKEN_AT = 300; // 5 minutes in seconds const TOKEN_EXPIRES_IN = 2 * 60 * 60 * 1000; // 2 hours in milliseconds +export enum SplatNet3AuthErrorCode { + USER_NOT_REGISTERED = 'USER_NOT_REGISTERED', + ERROR_INVALID_PARAMETERS = 'ERROR_INVALID_PARAMETERS', + ERROR_INVALID_GAME_WEB_TOKEN = 'ERROR_INVALID_GAME_WEB_TOKEN', + ERROR_OBSOLETE_VERSION = 'ERROR_OBSOLETE_VERSION', + ERROR_RATE_LIMIT = 'ERROR_RATE_LIMIT', + ERROR_SERVER = 'ERROR_SERVER', + ERROR_SERVER_MAINTENANCE = 'ERROR_SERVER_MAINTENANCE', +} + const AUTH_ERROR_CODES = { - 204: 'USER_NOT_REGISTERED', - 400: 'ERROR_INVALID_PARAMETERS', - 401: 'ERROR_INVALID_GAME_WEB_TOKEN', - 403: 'ERROR_OBSOLETE_VERSION', - 429: 'ERROR_RATE_LIMIT', - 500: 'ERROR_SERVER', - 503: 'ERROR_SERVER_MAINTENANCE', - 599: 'ERROR_SERVER', + 204: SplatNet3AuthErrorCode.USER_NOT_REGISTERED, + 400: SplatNet3AuthErrorCode.ERROR_INVALID_PARAMETERS, + 401: SplatNet3AuthErrorCode.ERROR_INVALID_GAME_WEB_TOKEN, + 403: SplatNet3AuthErrorCode.ERROR_OBSOLETE_VERSION, + 429: SplatNet3AuthErrorCode.ERROR_RATE_LIMIT, + 500: SplatNet3AuthErrorCode.ERROR_SERVER, + 503: SplatNet3AuthErrorCode.ERROR_SERVER_MAINTENANCE, + 599: SplatNet3AuthErrorCode.ERROR_SERVER, } as const; const REPLAY_CODE_REGEX = /^[A-Z0-9]{16}$/; @@ -143,7 +153,7 @@ export default class SplatNet3Api { } if (response.status !== 200) { - throw new ErrorResponse('[splatnet3] Non-200 status code', response, await response.text()); + throw new SplatNet3ErrorResponse('[splatnet3] Non-200 status code', response, await response.text()); } const remaining = parseInt(response.headers.get('x-bullettoken-remaining') ?? '0'); @@ -189,9 +199,9 @@ export default class SplatNet3Api { const data = await this.fetch>('/graphql', 'POST', JSON.stringify(req), undefined, 'graphql query ' + id); - if (!('data' in data) || (this.graphql_strict && data.errors?.length)) { - throw new ErrorResponse('[splatnet3] GraphQL error: ' + data.errors!.map(e => e.message).join(', '), - data[ResponseSymbol], data); + if (data.errors && (!('data' in data) || this.graphql_strict)) { + throw SplatNet3GraphQLErrorResponse.from(data[ResponseSymbol], data as GraphQLResponseWithErrors, + id, variables); } for (const error of data.errors ?? []) { @@ -377,7 +387,7 @@ export default class SplatNet3Api { }); if (!result.data.journey) { - throw new ErrorResponse('[splatnet3] Journey not found', result[ResponseSymbol], result); + throw SplatNet3GraphQLResourceNotFoundResponse.from('[splatnet3] Journey not found', result); } return result as NotNullPersistedQueryResult; @@ -390,7 +400,7 @@ export default class SplatNet3Api { }); if (!result.data.journey) { - throw new ErrorResponse('[splatnet3] Journey not found', result[ResponseSymbol], result); + throw SplatNet3GraphQLResourceNotFoundResponse.from('[splatnet3] Journey not found', result); } return result as NotNullPersistedQueryResult; @@ -403,7 +413,7 @@ export default class SplatNet3Api { }); if (!result.data.journey) { - throw new ErrorResponse('[splatnet3] Journey not found', result[ResponseSymbol], result); + throw SplatNet3GraphQLResourceNotFoundResponse.from('[splatnet3] Journey not found', result); } return result as NotNullPersistedQueryResult; @@ -416,7 +426,7 @@ export default class SplatNet3Api { }); if (!result.data.journey) { - throw new ErrorResponse('[splatnet3] Journey not found', result[ResponseSymbol], result); + throw SplatNet3GraphQLResourceNotFoundResponse.from('[splatnet3] Journey not found', result); } return result as NotNullPersistedQueryResult; @@ -450,7 +460,7 @@ export default class SplatNet3Api { }); if (!result.data.fest) { - throw new ErrorResponse('[splatnet3] Fest not found', result[ResponseSymbol], result); + throw SplatNet3GraphQLResourceNotFoundResponse.from('[splatnet3] Fest not found', result); } return result as NotNullPersistedQueryResult; @@ -463,7 +473,7 @@ export default class SplatNet3Api { }); if (!result.data.fest) { - throw new ErrorResponse('[splatnet3] Fest not found', result[ResponseSymbol], result); + throw SplatNet3GraphQLResourceNotFoundResponse.from('[splatnet3] Fest not found', result); } return result as NotNullPersistedQueryResult; @@ -476,7 +486,7 @@ export default class SplatNet3Api { }); if (!result.data.fest) { - throw new ErrorResponse('[splatnet3] Fest not found', result[ResponseSymbol], result); + throw SplatNet3GraphQLResourceNotFoundResponse.from('[splatnet3] Fest not found', result); } return result as NotNullPersistedQueryResult; @@ -489,7 +499,7 @@ export default class SplatNet3Api { }); if (!result.data.fest) { - throw new ErrorResponse('[splatnet3] Fest not found', result[ResponseSymbol], result); + throw SplatNet3GraphQLResourceNotFoundResponse.from('[splatnet3] Fest not found', result); } return result as NotNullPersistedQueryResult; @@ -509,7 +519,7 @@ export default class SplatNet3Api { }); if (!result.data.fest) { - throw new ErrorResponse('[splatnet3] Fest not found', result[ResponseSymbol], result); + throw SplatNet3GraphQLResourceNotFoundResponse.from('[splatnet3] Fest not found', result); } return result as NotNullPersistedQueryResult; @@ -528,7 +538,7 @@ export default class SplatNet3Api { }); if (!result.data.node) { - throw new ErrorResponse('[splatnet3] FestTeam not found', result[ResponseSymbol], result); + throw SplatNet3GraphQLResourceNotFoundResponse.from('[splatnet3] FestTeam not found', result); } return result as NotNullPersistedQueryResult; @@ -589,7 +599,7 @@ export default class SplatNet3Api { null : null; - if (!query) throw new Error('Invalid leaderboard'); + if (!query) throw new TypeError('Invalid leaderboard'); return this.persistedQuery<{ [XRankingLeaderboardType.X_RANKING]: { @@ -633,7 +643,7 @@ export default class SplatNet3Api { }); if (!result.data.saleGear) { - throw new ErrorResponse('[splatnet3] Sale gear not found', result[ResponseSymbol], result); + throw SplatNet3GraphQLResourceNotFoundResponse.from('[splatnet3] Sale gear not found', result); } return result as NotNullPersistedQueryResult; @@ -670,7 +680,7 @@ export default class SplatNet3Api { }); if (!result.data.myOutfit) { - throw new ErrorResponse('[splatnet3] My outfit not found', result[ResponseSymbol], result); + throw SplatNet3GraphQLResourceNotFoundResponse.from('[splatnet3] My outfit not found', result); } return result as NotNullPersistedQueryResult; @@ -741,7 +751,7 @@ export default class SplatNet3Api { }); if (!result.data.replay) { - throw new ErrorResponse('[splatnet3] Replay not found', result[ResponseSymbol], result); + throw SplatNet3GraphQLResourceNotFoundResponse.from('[splatnet3] Replay not found', result); } return result as NotNullPersistedQueryResult; @@ -842,7 +852,7 @@ export default class SplatNet3Api { }); if (!result.data.vsHistoryDetail) { - throw new ErrorResponse('[splatnet3] Battle history not found', result[ResponseSymbol], result); + throw SplatNet3GraphQLResourceNotFoundResponse.from('[splatnet3] Battle history not found', result); } return result as NotNullPersistedQueryResult; @@ -855,7 +865,7 @@ export default class SplatNet3Api { }); if (!result.data.vsHistoryDetail) { - throw new ErrorResponse('[splatnet3] Battle history not found', result[ResponseSymbol], result); + throw SplatNet3GraphQLResourceNotFoundResponse.from('[splatnet3] Battle history not found', result); } return result as NotNullPersistedQueryResult; @@ -901,7 +911,7 @@ export default class SplatNet3Api { }); if (!result.data.coopHistoryDetail) { - throw new ErrorResponse('[splatnet3] Co-op history not found', result[ResponseSymbol], result); + throw SplatNet3GraphQLResourceNotFoundResponse.from('[splatnet3] Co-op history not found', result); } return result as NotNullPersistedQueryResult; @@ -914,7 +924,7 @@ export default class SplatNet3Api { }); if (!result.data.node) { - throw new ErrorResponse('[splatnet3] Co-op history not found', result[ResponseSymbol], result); + throw SplatNet3GraphQLResourceNotFoundResponse.from('[splatnet3] Co-op history not found', result); } return result as NotNullPersistedQueryResult; @@ -1022,7 +1032,7 @@ export default class SplatNet3Api { const body = await response.text(); if (response.status !== 200) { - throw new ErrorResponse('[splatnet3] Non-200 status code', response, body); + throw new SplatNet3ErrorResponse('[splatnet3] Non-200 status code', response, body); } const cookies = response.headers.get('Set-Cookie'); @@ -1047,9 +1057,9 @@ export default class SplatNet3Api { debug('fetch %s %s, response %s', 'POST', '/bullet_tokens', response.status); - const error: string | undefined = AUTH_ERROR_CODES[tr.status as keyof typeof AUTH_ERROR_CODES]; - if (error) throw new ErrorResponse('[splatnet3] ' + error, tr, await tr.text()); - if (tr.status !== 201) throw new ErrorResponse('[splatnet3] Non-201 status code', tr, await tr.text()); + const error: SplatNet3AuthErrorCode | undefined = AUTH_ERROR_CODES[tr.status as keyof typeof AUTH_ERROR_CODES]; + if (error) throw new SplatNet3AuthErrorResponse('[splatnet3] ' + error, tr, await tr.text(), error); + if (tr.status !== 201) throw new SplatNet3ErrorResponse('[splatnet3] Non-201 status code', tr, await tr.text()); const bullet_token = await tr.json() as BulletToken; const created_at = Date.now(); @@ -1083,6 +1093,65 @@ function getMapPersistedQueriesModeFromEnvironment(): MapQueriesMode { return MapQueriesMode.ALL; } +export class SplatNet3ErrorResponse extends ErrorResponse {} + +export class SplatNet3AuthErrorResponse extends SplatNet3ErrorResponse { + constructor( + message: string, response: Response | globalThis.Response, + body?: string | unknown | undefined, + readonly code = SplatNet3AuthErrorCode.ERROR_SERVER, + ) { + super(message, response, body); + } +} + +type GraphQLResponseWithErrors = (GraphQLSuccessResponse & {errors: GraphQLError[]}) | GraphQLErrorResponse; + +export class SplatNet3GraphQLErrorResponse< + Id extends string = string, + /** @private */ + _Variables extends Id extends KnownRequestId ? VariablesTypes[Id] : unknown = + Id extends KnownRequestId ? VariablesTypes[Id] : unknown, +> extends SplatNet3ErrorResponse { + constructor( + message: string, response: Response | globalThis.Response, + body?: string | GraphQLResponseWithErrors | undefined, + readonly request_id?: Id | string, + readonly variables?: _Variables, + ) { + super(message, response, body); + } + + static from(response: Response, data: GraphQLResponseWithErrors, id: string, variables: unknown) { + return new SplatNet3GraphQLErrorResponse('[splatnet3] GraphQL error: ' + + data.errors.map(e => e.message).join(', '), response, data, id, variables); + } +} + +export class SplatNet3GraphQLResourceNotFoundResponse< + Id extends string = string, + /** @private */ + _Result extends Id extends KnownRequestId ? ResultTypes[Id] : unknown = + Id extends KnownRequestId ? ResultTypes[Id] : unknown, + /** @private */ + _Variables extends Id extends KnownRequestId ? VariablesTypes[Id] : unknown = + Id extends KnownRequestId ? VariablesTypes[Id] : unknown, +> extends SplatNet3ErrorResponse> { + constructor( + message: string, response: Response | globalThis.Response, + body?: string | PersistedQueryResult<_Result> | undefined, + readonly request_id?: Id | string, + readonly variables?: _Variables, + ) { + super(message, response, body); + } + + static from(message: string, data: PersistedQueryResult) { + return new SplatNet3GraphQLResourceNotFoundResponse( + message, data[ResponseSymbol], data, data[RequestIdSymbol], data[VariablesSymbol]); + } +} + export interface SplatNet3AuthData { webserviceToken: WebServiceToken; url: string; diff --git a/src/api/znc-proxy.ts b/src/api/znc-proxy.ts index dd88eb3..f2346b7 100644 --- a/src/api/znc-proxy.ts +++ b/src/api/znc-proxy.ts @@ -33,8 +33,8 @@ export default class ZncProxyApi implements CoralApiInterface { debug('fetch %s %s, response %s', method, url, response.status); - if (response.status !== 200 && response.status !== 204) { - throw new ErrorResponse('[zncproxy] Non-200/204 status code', response, await response.text()); + if (!response.ok) { + throw new ZncProxyErrorResponse('[zncproxy] Non-2xx status code', response, await response.text()); } const data = (response.status === 204 ? {} : await response.json()) as T; @@ -182,6 +182,8 @@ function createResult(data: R & {[ResponseSymbol]: Response}, r return result as Result; } +export class ZncProxyErrorResponse extends ErrorResponse {} + export interface AuthToken { user: string; policy?: AuthPolicy; @@ -227,12 +229,12 @@ export async function getPresenceFromUrl(presence_url: string, useragent?: strin debug('fetch %s %s, response %s', 'GET', presence_url, response.status); if (response.status !== 200) { - throw new ErrorResponse('[zncproxy] Unknown error', response, await response.text()); + throw new ZncProxyErrorResponse('[zncproxy] Unknown error', response, await response.text()); } if (!response.headers.get('Content-Type')?.match(/^application\/json(;|$)$/)) { controller.abort(); - throw new ErrorResponse('[zncproxy] Unacceptable content type', response); + throw new ZncProxyErrorResponse('[zncproxy] Unacceptable content type', response); } const data = await response.json() as PresenceUrlResponse; diff --git a/src/app/main/monitor.ts b/src/app/main/monitor.ts index 75277c2..f156592 100644 --- a/src/app/main/monitor.ts +++ b/src/app/main/monitor.ts @@ -2,7 +2,7 @@ import { dialog, Notification } from './electron.js'; import { App } from './index.js'; import { tryGetNativeImageFromUrl } from './util.js'; import { DiscordPresenceConfiguration, DiscordPresenceExternalMonitorsConfiguration, DiscordPresenceSource } from '../common/types.js'; -import { CurrentUser, Friend, Game, CoralErrorResponse } from '../../api/coral-types.js'; +import { CurrentUser, Friend, Game, CoralError } from '../../api/coral-types.js'; import { ErrorResponse } from '../../api/util.js'; import { ZncDiscordPresence, ZncProxyDiscordPresence } from '../../common/presence.js'; import { NotificationManager } from '../../common/notify.js'; @@ -346,7 +346,7 @@ export class PresenceMonitorManager { async handleError( monitor: EmbeddedPresenceMonitor | EmbeddedProxyPresenceMonitor, - err: ErrorResponse | NodeJS.ErrnoException + err: ErrorResponse | NodeJS.ErrnoException ): Promise { const {response} = await dialog.showMessageBox({ message: err.name + ' updating presence monitor', @@ -366,7 +366,7 @@ export class PresenceMonitorManager { export class EmbeddedPresenceMonitor extends ZncDiscordPresence { notifications = new ElectronNotificationManager(); - onError?: (error: ErrorResponse | NodeJS.ErrnoException) => + onError?: (error: ErrorResponse | NodeJS.ErrnoException) => Promise | LoopResult | void = undefined; enable() { @@ -409,7 +409,7 @@ export class EmbeddedPresenceMonitor extends ZncDiscordPresence { } } - async handleError(err: ErrorResponse | NodeJS.ErrnoException): Promise { + async handleError(err: ErrorResponse | NodeJS.ErrnoException): Promise { try { return await super.handleError(err); } catch (err: any) { @@ -425,7 +425,7 @@ export class EmbeddedPresenceMonitor extends ZncDiscordPresence { export class EmbeddedProxyPresenceMonitor extends ZncProxyDiscordPresence { notifications = new ElectronNotificationManager(); - onError?: (error: ErrorResponse | NodeJS.ErrnoException) => + onError?: (error: ErrorResponse | NodeJS.ErrnoException) => Promise | LoopResult | void = undefined; enable() { @@ -468,7 +468,7 @@ export class EmbeddedProxyPresenceMonitor extends ZncProxyDiscordPresence { } } - async handleError(err: ErrorResponse | NodeJS.ErrnoException): Promise { + async handleError(err: ErrorResponse | NodeJS.ErrnoException): Promise { try { return await super.handleError(err); } catch (err: any) { diff --git a/src/app/main/na-auth.ts b/src/app/main/na-auth.ts index c62225c..d27f857 100644 --- a/src/app/main/na-auth.ts +++ b/src/app/main/na-auth.ts @@ -5,7 +5,7 @@ import { protocol_registration_options } from './index.js'; import { createModalWindow } from './windows.js'; import { tryGetNativeImageFromUrl } from './util.js'; import { WindowType } from '../common/types.js'; -import { NintendoAccountAuthError, NintendoAccountSessionAuthorisation, NintendoAccountSessionAuthorisationError, NintendoAccountSessionToken } from '../../api/na.js'; +import { NintendoAccountAuthError, NintendoAccountAuthErrorResponse, NintendoAccountSessionAuthorisation, NintendoAccountSessionAuthorisationError, NintendoAccountSessionToken } from '../../api/na.js'; import { NintendoAccountSessionAuthorisationCoral } from '../../api/coral.js'; import { NintendoAccountSessionAuthorisationMoon } from '../../api/moon.js'; import { ErrorResponse } from '../../api/util.js'; @@ -329,13 +329,9 @@ export async function addNsoAccount(storage: persist.LocalStorage, use_in_app_br return {nso, data}; } catch (err) { - if (err instanceof ErrorResponse && err.response.url.startsWith('https://accounts.nintendo.com/')) { - const data: NintendoAccountAuthError = err.data; - - if (data.error === 'invalid_grant') { - // The session token has expired/was revoked - return authenticateCoralSessionToken(storage, authenticator, code, true); - } + if (err instanceof NintendoAccountAuthErrorResponse && err.data?.error === 'invalid_grant') { + // The session token has expired/was revoked + return authenticateCoralSessionToken(storage, authenticator, code, true); } throw err; @@ -494,13 +490,9 @@ export async function addPctlAccount(storage: persist.LocalStorage, use_in_app_b return {moon, data}; } catch (err) { - if (err instanceof ErrorResponse && err.response.url.startsWith('https://accounts.nintendo.com/')) { - const data: NintendoAccountAuthError = err.data; - - if (data.error === 'invalid_grant') { - // The session token has expired/was revoked - return authenticateMoonSessionToken(storage, authenticator, code, true); - } + if (err instanceof NintendoAccountAuthErrorResponse && err.data?.error === 'invalid_grant') { + // The session token has expired/was revoked + return authenticateMoonSessionToken(storage, authenticator, code, true); } throw err; diff --git a/src/cli/nso/http-server.ts b/src/cli/nso/http-server.ts index 927abca..8b48800 100644 --- a/src/cli/nso/http-server.ts +++ b/src/cli/nso/http-server.ts @@ -5,10 +5,9 @@ import express, { Request, RequestHandler, Response } from 'express'; import bodyParser from 'body-parser'; import { v4 as uuidgen } from 'uuid'; import type { Arguments as ParentArguments } from '../nso.js'; -import CoralApi, { CoralApiInterface } from '../../api/coral.js'; +import CoralApi, { CoralApiInterface, CoralErrorResponse } from '../../api/coral.js'; import { Announcement, CoralStatus, CurrentUser, Friend, FriendCodeUrl, FriendCodeUser, Presence } from '../../api/coral-types.js'; import { AuthPolicy, AuthToken, ZncPresenceEventStreamEvent } from '../../api/znc-proxy.js'; -import { ErrorResponse } from '../../api/util.js'; import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; @@ -685,7 +684,7 @@ class Server extends HttpServer { const user = await coral.getUserByFriendCode(friendcode); return [user, id]; } catch (err) { - if (err instanceof ErrorResponse && err.data?.status === CoralStatus.RESOURCE_NOT_FOUND) { + if (err instanceof CoralErrorResponse && err.status === CoralStatus.RESOURCE_NOT_FOUND) { // A user with this friend code doesn't exist // This should be cached return [null, id]; diff --git a/src/client/coral.ts b/src/client/coral.ts index 9a12716..be56238 100644 --- a/src/client/coral.ts +++ b/src/client/coral.ts @@ -1,6 +1,6 @@ import { Response } from 'node-fetch'; import CoralApi, { CoralApiInterface, CoralAuthData, Result, ZNCA_CLIENT_ID } from '../api/coral.js'; -import { Announcements, Friends, Friend, GetActiveEventResult, WebServices, CoralErrorResponse } from '../api/coral-types.js'; +import { Announcements, Friends, Friend, GetActiveEventResult, WebServices, CoralError } from '../api/coral-types.js'; import ZncProxyApi from '../api/znc-proxy.js'; import { NintendoAccountSession, Storage } from './storage/index.js'; import { checkUseLimit } from './util.js'; @@ -262,7 +262,7 @@ function createTokenExpiredHandler( session: NintendoAccountSession, coral: CoralApi, renew_token_data: {auth_data: SavedToken}, ratelimit = true ) { - return (data: CoralErrorResponse, response: Response) => { + return (data: CoralError, response: Response) => { debug('Token expired', renew_token_data.auth_data.user.id, data); return renewToken(session, coral, renew_token_data, ratelimit); }; diff --git a/src/client/splatnet3.ts b/src/client/splatnet3.ts index e4cbf12..51b768d 100644 --- a/src/client/splatnet3.ts +++ b/src/client/splatnet3.ts @@ -3,9 +3,8 @@ import { ConfigureAnalyticsResult, CurrentFestResult, DetailVotingStatusResult, import createDebug from '../util/debug.js'; import { ZNCA_CLIENT_ID } from '../api/coral.js'; import { NintendoAccountSession, Storage } from './storage/index.js'; -import SplatNet3Api, { PersistedQueryResult, SplatNet3AuthData } from '../api/splatnet3.js'; +import SplatNet3Api, { PersistedQueryResult, SplatNet3AuthData, SplatNet3AuthErrorCode, SplatNet3AuthErrorResponse } from '../api/splatnet3.js'; import Coral, { SavedToken as SavedCoralToken } from './coral.js'; -import { ErrorResponse } from '../api/util.js'; import Users from './users.js'; import { checkUseLimit } from './util.js'; @@ -191,7 +190,7 @@ async function renewToken( debug('Unable to renew bullet token with saved web services token - cached data for this session token doesn\'t exist??'); } } catch (err) { - if (err instanceof ErrorResponse && err.response.status === 401) { + if (err instanceof SplatNet3AuthErrorResponse && err.code === SplatNet3AuthErrorCode.ERROR_INVALID_GAME_WEB_TOKEN) { // Web service token invalid/expired... debug('Web service token expired, authenticating with new token', err); } else { diff --git a/src/common/auth/coral.ts b/src/common/auth/coral.ts index c4e7956..070a5da 100644 --- a/src/common/auth/coral.ts +++ b/src/common/auth/coral.ts @@ -1,7 +1,7 @@ import * as persist from 'node-persist'; import { Response } from 'node-fetch'; import CoralApi, { CoralAuthData, ZNCA_CLIENT_ID } from '../../api/coral.js'; -import { CoralErrorResponse } from '../../api/coral-types.js'; +import { CoralError } from '../../api/coral-types.js'; import ZncProxyApi from '../../api/znc-proxy.js'; import { getNintendoAccountUser, NintendoAccountSessionTokenJwtPayload } from '../../api/na.js'; import createDebug from '../../util/debug.js'; @@ -120,7 +120,7 @@ function createTokenExpiredHandler( storage: persist.LocalStorage, token: string, nso: CoralApi, renew_token_data: {existingToken: SavedToken}, ratelimit = true ) { - return (data?: CoralErrorResponse, response?: Response) => { + return (data?: CoralError, response?: Response) => { debug('Token expired', renew_token_data.existingToken.user.id, data); return renewToken(storage, token, nso, renew_token_data, ratelimit); }; diff --git a/src/common/auth/splatnet3.ts b/src/common/auth/splatnet3.ts index 67f7ae8..add70e0 100644 --- a/src/common/auth/splatnet3.ts +++ b/src/common/auth/splatnet3.ts @@ -1,12 +1,11 @@ import persist from 'node-persist'; import { Response } from 'node-fetch'; import { getToken, Login, SavedToken } from './coral.js'; -import SplatNet3Api, { SplatNet3AuthData } from '../../api/splatnet3.js'; +import SplatNet3Api, { SplatNet3AuthData, SplatNet3AuthErrorCode, SplatNet3AuthErrorResponse } from '../../api/splatnet3.js'; import { checkUseLimit, SHOULD_LIMIT_USE } from './util.js'; import createDebug from '../../util/debug.js'; import { Jwt } from '../../util/jwt.js'; import { NintendoAccountSessionTokenJwtPayload } from '../../api/na.js'; -import { ErrorResponse } from '../../api/util.js'; const debug = createDebug('nxapi:auth:splatnet3'); @@ -121,7 +120,7 @@ async function renewToken( debug('Unable to renew bullet token with saved web services token - cached data for this session token doesn\'t exist??'); } } catch (err) { - if (err instanceof ErrorResponse && err.response.status === 401) { + if (err instanceof SplatNet3AuthErrorResponse && err.code === SplatNet3AuthErrorCode.ERROR_INVALID_GAME_WEB_TOKEN) { // Web service token invalid/expired... debug('Web service token expired, authenticating with new token', err); } else { diff --git a/src/common/notify.ts b/src/common/notify.ts index 2b29ad4..b2a8b98 100644 --- a/src/common/notify.ts +++ b/src/common/notify.ts @@ -1,6 +1,6 @@ import persist from 'node-persist'; import { CoralApiInterface } from '../api/coral.js'; -import { ActiveEvent, Announcements, CurrentUser, Friend, Game, Presence, PresenceState, WebServices, CoralErrorResponse, GetActiveEventResult } from '../api/coral-types.js'; +import { ActiveEvent, Announcements, CurrentUser, Friend, Game, Presence, PresenceState, WebServices, CoralError, GetActiveEventResult } from '../api/coral-types.js'; import ZncProxyApi from '../api/znc-proxy.js'; import { ErrorResponse } from '../api/util.js'; import { SavedToken } from './auth/coral.js'; @@ -179,7 +179,7 @@ export class ZncNotifications extends Loop { if (user) await this.updatePresenceForSplatNet2Monitors([user]); } - async handleError(err: ErrorResponse | NodeJS.ErrnoException): Promise { + async handleError(err: ErrorResponse | NodeJS.ErrnoException): Promise { return handleError(err, this); } } diff --git a/src/common/presence.ts b/src/common/presence.ts index 7bc3c3a..1b847ce 100644 --- a/src/common/presence.ts +++ b/src/common/presence.ts @@ -3,7 +3,7 @@ import { DiscordRpcClient, findDiscordRpcClient } from '../discord/rpc.js'; import { getDiscordPresence, getInactiveDiscordPresence } from '../discord/util.js'; import { DiscordPresencePlayTime, DiscordPresenceContext, DiscordPresence, ExternalMonitorConstructor, ExternalMonitor, ErrorResult } from '../discord/types.js'; import { EmbeddedSplatNet2Monitor, ZncNotifications } from './notify.js'; -import { ActiveEvent, CurrentUser, Friend, Game, Presence, PresenceState, CoralErrorResponse } from '../api/coral-types.js'; +import { ActiveEvent, CurrentUser, Friend, Game, Presence, PresenceState, CoralError } from '../api/coral-types.js'; import { getPresenceFromUrl } from '../api/znc-proxy.js'; import createDebug from '../util/debug.js'; import { ErrorResponse, ResponseSymbol } from '../api/util.js'; @@ -499,7 +499,7 @@ export class ZncDiscordPresence extends ZncNotifications { this.discord.title = {id: title_id, since: saved_presence.title_since}; } - async handleError(err: ErrorResponse | NodeJS.ErrnoException): Promise { + async handleError(err: ErrorResponse | NodeJS.ErrnoException): Promise { this.discord.onError(err); return super.handleError(err); @@ -759,7 +759,7 @@ export class ZncProxyDiscordPresence extends Loop { } } - async handleError(err: ErrorResponse | NodeJS.ErrnoException): Promise { + async handleError(err: ErrorResponse | NodeJS.ErrnoException): Promise { this.discord.onError(err); return handleError(err, this); diff --git a/src/common/splatnet2/monitor.ts b/src/common/splatnet2/monitor.ts index ccd452e..f7b9bd5 100644 --- a/src/common/splatnet2/monitor.ts +++ b/src/common/splatnet2/monitor.ts @@ -2,12 +2,11 @@ import * as path from 'node:path'; import * as fs from 'node:fs/promises'; import persist from 'node-persist'; import mkdirp from 'mkdirp'; -import SplatNet2Api from '../../api/splatnet2.js'; +import SplatNet2Api, { SplatNet2ErrorResponse } from '../../api/splatnet2.js'; import { renewIksmToken } from '../auth/splatnet2.js'; -import { Records, Stages, WebServiceError } from '../../api/splatnet2-types.js'; +import { Records, Stages } from '../../api/splatnet2-types.js'; import { dumpCoopResults, dumpResults } from './dump-results.js'; import { dumpProfileImage, dumpRecords } from './dump-records.js'; -import { ErrorResponse } from '../../api/util.js'; import createDebug from '../../util/debug.js'; import Loop, { LoopResult } from '../../util/loop.js'; @@ -90,8 +89,8 @@ export class SplatNet2RecordsMonitor extends Loop { } } - async handleError(err: Error | ErrorResponse): Promise { - if ('response' in err && err.data?.code === 'AUTHENTICATION_ERROR') { + async handleError(err: Error): Promise { + if (err instanceof SplatNet2ErrorResponse && err.data?.code === 'AUTHENTICATION_ERROR') { // Token expired debug('Renewing iksm_session cookie'); diff --git a/src/exports/coral.ts b/src/exports/coral.ts index 7cbe6ed..b083ea2 100644 --- a/src/exports/coral.ts +++ b/src/exports/coral.ts @@ -8,6 +8,8 @@ export { ResponseDataSymbol, CorrelationIdSymbol, + CoralErrorResponse, + NintendoAccountSessionAuthorisationCoral, } from '../api/coral.js'; diff --git a/src/exports/moon.ts b/src/exports/moon.ts index ff599a0..4a72fa2 100644 --- a/src/exports/moon.ts +++ b/src/exports/moon.ts @@ -3,6 +3,8 @@ export { MoonAuthData, PartialMoonAuthData, + MoonErrorResponse, + NintendoAccountSessionAuthorisationMoon, } from '../api/moon.js'; diff --git a/src/exports/nintendo-account.ts b/src/exports/nintendo-account.ts index c51f0d8..efb3979 100644 --- a/src/exports/nintendo-account.ts +++ b/src/exports/nintendo-account.ts @@ -2,6 +2,9 @@ export { NintendoAccountSessionAuthorisation, NintendoAccountSessionAuthorisationError, + NintendoAccountAuthErrorResponse, + NintendoAccountErrorResponse, + NintendoAccountSessionToken, NintendoAccountToken, NintendoAccountAuthError, diff --git a/src/exports/nooklink.ts b/src/exports/nooklink.ts index ffb317f..54e29f3 100644 --- a/src/exports/nooklink.ts +++ b/src/exports/nooklink.ts @@ -2,6 +2,8 @@ export { default as NooklinkApi, NooklinkAuthData, + NooklinkErrorResponse, + NooklinkUserApi, NooklinkUserAuthData, PartialNooklinkUserAuthData, diff --git a/src/exports/splatnet2.ts b/src/exports/splatnet2.ts index 6196161..e391512 100644 --- a/src/exports/splatnet2.ts +++ b/src/exports/splatnet2.ts @@ -3,6 +3,8 @@ export { SplatNet2AuthData, SplatNet2CliTokenData, + SplatNet2ErrorResponse, + LeagueType, LeagueRegion, ShareColour as ShareProfileColour, diff --git a/src/exports/splatnet3.ts b/src/exports/splatnet3.ts index 9f28950..947c804 100644 --- a/src/exports/splatnet3.ts +++ b/src/exports/splatnet3.ts @@ -6,6 +6,11 @@ export { RequestIdSymbol, VariablesSymbol, + SplatNet3ErrorResponse, + SplatNet3AuthErrorResponse, + SplatNet3GraphQLErrorResponse, + SplatNet3GraphQLResourceNotFoundResponse, + XRankingRegion, XRankingLeaderboardType, XRankingLeaderboardRule, diff --git a/src/util/errors.ts b/src/util/errors.ts index ed4fcca..7e7a4e6 100644 --- a/src/util/errors.ts +++ b/src/util/errors.ts @@ -2,7 +2,6 @@ import { AbortError } from 'node-fetch'; import createDebug from './debug.js'; import Loop, { LoopResult } from './loop.js'; import { TemporaryErrorSymbol } from './misc.js'; -import { CoralErrorResponse } from '../api/coral-types.js'; import { ErrorResponse } from '../api/util.js'; const debug = createDebug('nxapi:util:errors'); @@ -28,7 +27,7 @@ export const temporary_http_errors = [ ]; export async function handleError( - err: ErrorResponse | NodeJS.ErrnoException, + err: ErrorResponse | NodeJS.ErrnoException, loop: Loop, ): Promise { if (TemporaryErrorSymbol in err && err[TemporaryErrorSymbol]) {