mirror of
https://github.com/samuelthomas2774/nxapi.git
synced 2026-03-21 18:04:10 -05:00
Automatically renew SplatNet 3 tokens
This commit is contained in:
parent
4e6a29f74f
commit
c7fbe15047
|
|
@ -1,8 +1,8 @@
|
|||
import fetch from 'node-fetch';
|
||||
import fetch, { Response } from 'node-fetch';
|
||||
import createDebug from 'debug';
|
||||
import { WebServiceToken } from './coral-types.js';
|
||||
import { NintendoAccountUser } from './na.js';
|
||||
import { defineResponse, ErrorResponse } from './util.js';
|
||||
import { defineResponse, ErrorResponse, HasResponse } from './util.js';
|
||||
import CoralApi from './coral.js';
|
||||
import { timeoutSignal } from '../util/misc.js';
|
||||
import { BankaraBattleHistoriesResult, BattleHistoryCurrentPlayerResult, BulletToken, CurrentFestResult, FriendListResult, GraphQLRequest, GraphQLResponse, HistoryRecordResult, HomeResult, LatestBattleHistoriesResult, PrivateBattleHistoriesResult, RegularBattleHistoriesResult, RequestId, SettingResult, StageScheduleResult, VsHistoryDetailResult, CoopHistoryResult, CoopHistoryDetailResult, FestRecordResult, FestRecordRefetchResult, DetailFestRecordDetailResult, DetailVotingStatusResult, DetailFestVotingStatusRefetchResult, VotesUpdateFestVoteResult } from './splatnet3-types.js';
|
||||
|
|
@ -20,8 +20,14 @@ const languages = [
|
|||
];
|
||||
|
||||
const SPLATNET3_URL = SPLATNET3_WEBSERVICE_URL + '/api';
|
||||
const SHOULD_RENEW_TOKEN_AT = 300; // 5 minutes in seconds
|
||||
|
||||
export default class SplatNet3Api {
|
||||
onTokenShouldRenew: ((remaining: number, res: Response) => Promise<void>) | null = null;
|
||||
onTokenExpired: ((res: Response) => Promise<void>) | null = null;
|
||||
/** @internal */
|
||||
_renewToken: Promise<void> | null = null;
|
||||
|
||||
protected constructor(
|
||||
public bullet_token: string,
|
||||
public version: string,
|
||||
|
|
@ -31,8 +37,13 @@ export default class SplatNet3Api {
|
|||
|
||||
async fetch<T = unknown>(
|
||||
url: string, method = 'GET', body?: string | FormData, headers?: object,
|
||||
/** @internal */ _log?: string
|
||||
) {
|
||||
/** @internal */ _log?: string,
|
||||
/** @internal */ _attempt = 0
|
||||
): Promise<HasResponse<T, Response>> {
|
||||
if (this._renewToken) {
|
||||
await this._renewToken;
|
||||
}
|
||||
|
||||
const [signal, cancel] = timeoutSignal();
|
||||
const response = await fetch(SPLATNET3_URL + url, {
|
||||
method,
|
||||
|
|
@ -50,12 +61,31 @@ export default class SplatNet3Api {
|
|||
signal,
|
||||
}).finally(cancel);
|
||||
|
||||
debug('fetch %s %s%s, response %s', method, url, _log ? ', ' + _log : '', response.status);
|
||||
const version = response.headers.get('x-be-version');
|
||||
debug('fetch %s %s%s, response %s, server revision %s', method, url, _log ? ', ' + _log : '',
|
||||
response.status, version);
|
||||
|
||||
if (response.status === 401 && !_attempt && this.onTokenExpired) {
|
||||
// _renewToken will be awaited when calling fetch
|
||||
this._renewToken = this._renewToken ?? this.onTokenExpired.call(null, response).finally(() => {
|
||||
this._renewToken = null;
|
||||
});
|
||||
return this.fetch(url, method, body, headers, _log, _attempt + 1);
|
||||
}
|
||||
|
||||
if (response.status !== 200) {
|
||||
throw new ErrorResponse('[splatnet3] Non-200 status code', response, await response.text());
|
||||
}
|
||||
|
||||
const remaining = parseInt(response.headers.get('x-bullettoken-remaining') ?? '0');
|
||||
|
||||
if (remaining <= SHOULD_RENEW_TOKEN_AT && !_attempt && this.onTokenShouldRenew) {
|
||||
// _renewToken will be awaited when calling fetch
|
||||
this._renewToken = this._renewToken ?? this.onTokenShouldRenew.call(null, remaining, response).finally(() => {
|
||||
this._renewToken = null;
|
||||
});
|
||||
}
|
||||
|
||||
const data = await response.json() as T;
|
||||
|
||||
return defineResponse(data, response);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import createDebug from 'debug';
|
||||
import persist from 'node-persist';
|
||||
import { Response } from 'node-fetch';
|
||||
import { getToken, Login } from './coral.js';
|
||||
import SplatNet3Api, { SplatNet3AuthData } from '../../api/splatnet3.js';
|
||||
import { checkUseLimit, SHOULD_LIMIT_USE } from './util.js';
|
||||
|
|
@ -50,16 +51,64 @@ export async function getBulletToken(
|
|||
|
||||
await storage.setItem('BulletToken.' + token, existingToken);
|
||||
|
||||
return {
|
||||
splatnet: SplatNet3Api.createWithSavedToken(existingToken),
|
||||
data: existingToken,
|
||||
};
|
||||
const splatnet = SplatNet3Api.createWithSavedToken(existingToken);
|
||||
splatnet.onTokenExpired = createTokenExpiredHandler(storage, token, splatnet, existingToken, proxy_url);
|
||||
splatnet.onTokenShouldRenew = createTokenShouldRenewHandler(storage, token, splatnet, existingToken, proxy_url);
|
||||
|
||||
return {splatnet, data: existingToken};
|
||||
}
|
||||
|
||||
debug('Using existing token');
|
||||
|
||||
return {
|
||||
splatnet: SplatNet3Api.createWithSavedToken(existingToken),
|
||||
data: existingToken,
|
||||
const splatnet = SplatNet3Api.createWithSavedToken(existingToken);
|
||||
|
||||
if (allow_fetch_token) {
|
||||
splatnet.onTokenExpired = createTokenExpiredHandler(storage, token, splatnet, existingToken, proxy_url);
|
||||
splatnet.onTokenShouldRenew = createTokenShouldRenewHandler(storage, token, splatnet, existingToken, proxy_url);
|
||||
}
|
||||
|
||||
return {splatnet, data: existingToken};
|
||||
}
|
||||
|
||||
function createTokenExpiredHandler(
|
||||
storage: persist.LocalStorage, token: string, splatnet: SplatNet3Api, existingToken: SavedBulletToken,
|
||||
znc_proxy_url?: string
|
||||
) {
|
||||
return (response: Response) => {
|
||||
debug('Token expired, renewing');
|
||||
return renewToken(storage, token, splatnet, existingToken, znc_proxy_url);
|
||||
};
|
||||
}
|
||||
|
||||
function createTokenShouldRenewHandler(
|
||||
storage: persist.LocalStorage, token: string, splatnet: SplatNet3Api, existingToken: SavedBulletToken,
|
||||
znc_proxy_url?: string
|
||||
) {
|
||||
return (remaining: number, response: Response) => {
|
||||
debug('Token will expire in %d seconds, renewing', remaining);
|
||||
return renewToken(storage, token, splatnet, existingToken, znc_proxy_url);
|
||||
};
|
||||
}
|
||||
|
||||
async function renewToken(
|
||||
storage: persist.LocalStorage, token: string, splatnet: SplatNet3Api, previousToken: SavedBulletToken,
|
||||
znc_proxy_url?: string
|
||||
) {
|
||||
const {nso, data} = await getToken(storage, token, znc_proxy_url);
|
||||
|
||||
if (data[Login]) {
|
||||
const announcements = await nso.getAnnouncements();
|
||||
const friends = await nso.getFriendList();
|
||||
const webservices = await nso.getWebServices();
|
||||
const activeevent = await nso.getActiveEvent();
|
||||
}
|
||||
|
||||
const existingToken: SavedBulletToken = await SplatNet3Api.loginWithCoral(nso, data.user);
|
||||
|
||||
splatnet.bullet_token = existingToken.bullet_token.bulletToken;
|
||||
splatnet.version = existingToken.version;
|
||||
splatnet.language = existingToken.bullet_token.lang;
|
||||
splatnet.useragent = existingToken.useragent;
|
||||
|
||||
await storage.setItem('BulletToken.' + token, existingToken);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import { product } from '../../../util/product.js';
|
|||
const debug = createDebug('nxapi:discord:splatnet3');
|
||||
|
||||
export default class SplatNet3Monitor extends EmbeddedLoop {
|
||||
update_interval: number = 3 * 60; // 3 minutes in seconds
|
||||
update_interval: number = 1 * 60; // 1 minute in seconds
|
||||
|
||||
splatnet: SplatNet3Api | null = null;
|
||||
data: SavedBulletToken | null = null;
|
||||
|
|
@ -35,7 +35,7 @@ export default class SplatNet3Monitor extends EmbeddedLoop {
|
|||
|
||||
constructor(
|
||||
readonly discord_presence: ExternalMonitorPresenceInterface,
|
||||
readonly config: SplatNet3MonitorConfig | null,
|
||||
protected config: SplatNet3MonitorConfig | null,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
|
@ -48,6 +48,7 @@ export default class SplatNet3Monitor extends EmbeddedLoop {
|
|||
if (config?.znc_proxy_url !== this.config?.znc_proxy_url) return false;
|
||||
if (config?.allow_fetch_token !== this.config?.allow_fetch_token) return false;
|
||||
|
||||
this.config = config;
|
||||
this.skipIntervalInCurrentLoop();
|
||||
|
||||
return true;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user