mirror of
https://github.com/samuelthomas2774/nxapi.git
synced 2026-10-23 10:06:55 -05:00
API error classes
This commit is contained in:
@@ -5,7 +5,7 @@ export interface CoralSuccessResponse<T = unknown> {
|
||||
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<T = unknown> = CoralSuccessResponse<T> | CoralErrorResponse;
|
||||
export type CoralResponse<T = unknown> = CoralSuccessResponse<T> | CoralError;
|
||||
|
||||
export interface AccountLoginParameter {
|
||||
naIdToken: string;
|
||||
|
||||
@@ -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<CoralAuthData | void>) | null = null;
|
||||
onTokenExpired: ((data?: CoralError, res?: Response) => Promise<CoralAuthData | void>) | null = null;
|
||||
/** @internal */
|
||||
_renewToken: Promise<void> | 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<T>;
|
||||
@@ -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<WebServiceToken>('/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<AccountLogin>;
|
||||
|
||||
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<CoralError> {
|
||||
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',
|
||||
|
||||
@@ -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<MoonError> {}
|
||||
|
||||
const na_client_settings = {
|
||||
client_id: ZNMA_CLIENT_ID,
|
||||
scope: [
|
||||
|
||||
@@ -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<NintendoAccountError>('[na] ' + token.detail, response, token);
|
||||
}
|
||||
if ('error' in token) {
|
||||
throw new ErrorResponse<NintendoAccountAuthError>('[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<NintendoAccountError>('[na] ' + nintendoAccountToken.detail, response, nintendoAccountToken);
|
||||
}
|
||||
if ('error' in nintendoAccountToken) {
|
||||
throw new ErrorResponse<NintendoAccountAuthError>('[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<NintendoAccountError>('[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<NintendoAccountAuthError> {}
|
||||
|
||||
export class NintendoAccountErrorResponse extends ErrorResponse<NintendoAccountError> {}
|
||||
|
||||
@@ -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<WebServiceError>('[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<WebServiceError>('[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<WebServiceError> {}
|
||||
|
||||
export interface NooklinkAuthData {
|
||||
webserviceToken: WebServiceToken;
|
||||
url: string;
|
||||
|
||||
@@ -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<WebServiceError>('[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(/<html(?:\s+[a-z0-9-]+(?:=(?:"[^"]*"|[^\s>]*))?)*\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<WebServiceError> {}
|
||||
|
||||
export interface SplatNet2AuthData {
|
||||
webserviceToken: WebServiceToken;
|
||||
url: string;
|
||||
|
||||
@@ -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<GraphQLResponse<_Result>>('/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<typeof result, 'journey'>;
|
||||
@@ -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<typeof result, 'journey'>;
|
||||
@@ -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<typeof result, 'journey'>;
|
||||
@@ -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<typeof result, 'journey'>;
|
||||
@@ -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<typeof result, 'fest'>;
|
||||
@@ -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<typeof result, 'fest'>;
|
||||
@@ -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<typeof result, 'fest'>;
|
||||
@@ -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<typeof result, 'fest'>;
|
||||
@@ -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<typeof result, 'fest'>;
|
||||
@@ -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<typeof result, 'node'>;
|
||||
@@ -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<typeof result, 'saleGear'>;
|
||||
@@ -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<typeof result, 'myOutfit'>;
|
||||
@@ -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<typeof result, 'replay'>;
|
||||
@@ -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<typeof result, 'vsHistoryDetail'>;
|
||||
@@ -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<typeof result, 'vsHistoryDetail'>;
|
||||
@@ -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<typeof result, 'coopHistoryDetail'>;
|
||||
@@ -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<typeof result, 'node'>;
|
||||
@@ -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<T = unknown> extends ErrorResponse<T> {}
|
||||
|
||||
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<GraphQLResponseWithErrors> {
|
||||
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<PersistedQueryResult<_Result>> {
|
||||
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<any>) {
|
||||
return new SplatNet3GraphQLResourceNotFoundResponse<any, any>(
|
||||
message, data[ResponseSymbol], data, data[RequestIdSymbol], data[VariablesSymbol]);
|
||||
}
|
||||
}
|
||||
|
||||
export interface SplatNet3AuthData {
|
||||
webserviceToken: WebServiceToken;
|
||||
url: string;
|
||||
|
||||
@@ -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<T extends {}, R>(data: R & {[ResponseSymbol]: Response}, r
|
||||
return result as Result<T>;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user