feat: refetch token when 401 close #5
parent
390b7ce279
commit
3e2eede47c
74
src/app.ts
74
src/app.ts
|
|
@ -1,11 +1,16 @@
|
|||
import { getBulletToken, getGToken, loginManually } from "./iksm.ts";
|
||||
import { MultiProgressBar, Mutex } from "../deps.ts";
|
||||
import { DEFAULT_STATE, FileStateBackend, State, StateBackend } from "./state.ts";
|
||||
import {
|
||||
checkToken,
|
||||
DEFAULT_STATE,
|
||||
FileStateBackend,
|
||||
State,
|
||||
StateBackend,
|
||||
} from "./state.ts";
|
||||
import {
|
||||
getBankaraBattleHistories,
|
||||
getBattleDetail,
|
||||
getBattleList,
|
||||
isTokenExpired,
|
||||
} from "./splatnet3.ts";
|
||||
import {
|
||||
BattleExporter,
|
||||
|
|
@ -17,7 +22,14 @@ import {
|
|||
import { Cache, FileCache, MemoryCache } from "./cache.ts";
|
||||
import { StatInkExporter } from "./exporters/stat.ink.ts";
|
||||
import { FileExporter } from "./exporters/file.ts";
|
||||
import { battleId, delay, readline, showError } from "./utils.ts";
|
||||
import {
|
||||
battleId,
|
||||
delay,
|
||||
readline,
|
||||
RecoverableError,
|
||||
retryRecoverableError,
|
||||
showError,
|
||||
} from "./utils.ts";
|
||||
|
||||
export type Opts = {
|
||||
profilePath: string;
|
||||
|
|
@ -159,9 +171,19 @@ type Progress = {
|
|||
export class App {
|
||||
state: State = DEFAULT_STATE;
|
||||
stateBackend: StateBackend;
|
||||
recoveryToken: RecoverableError = {
|
||||
name: "Refetch Token",
|
||||
is: isTokenExpired,
|
||||
recovery: async () => {
|
||||
console.log("Token expired, refetch tokens.");
|
||||
|
||||
await this.fetchToken();
|
||||
},
|
||||
};
|
||||
|
||||
constructor(public opts: Opts) {
|
||||
this.stateBackend = opts.stateBackend ?? new FileStateBackend(opts.profilePath);
|
||||
this.stateBackend = opts.stateBackend ??
|
||||
new FileStateBackend(opts.profilePath);
|
||||
}
|
||||
async writeState(newState: State) {
|
||||
this.state = newState;
|
||||
|
|
@ -212,7 +234,10 @@ export class App {
|
|||
|
||||
return out;
|
||||
}
|
||||
async exportOnce() {
|
||||
exportOnce() {
|
||||
return retryRecoverableError(() => this._exportOnce(), this.recoveryToken);
|
||||
}
|
||||
async _exportOnce() {
|
||||
const bar = !this.opts.noProgress
|
||||
? new MultiProgressBar({
|
||||
title: "Export battles",
|
||||
|
|
@ -220,6 +245,7 @@ export class App {
|
|||
})
|
||||
: undefined;
|
||||
|
||||
try {
|
||||
const exporters = await this.getExporters();
|
||||
|
||||
const fetcher = new BattleFetcher({
|
||||
|
|
@ -272,6 +298,9 @@ export class App {
|
|||
.join(", ")
|
||||
}`,
|
||||
);
|
||||
} finally {
|
||||
bar?.end();
|
||||
}
|
||||
}
|
||||
async monitor() {
|
||||
while (true) {
|
||||
|
|
@ -295,25 +324,12 @@ export class App {
|
|||
}
|
||||
bar?.end();
|
||||
}
|
||||
async run() {
|
||||
await this.readState();
|
||||
async fetchToken() {
|
||||
const sessionToken = this.state.loginState?.sessionToken;
|
||||
|
||||
if (!this.state.loginState?.sessionToken) {
|
||||
const sessionToken = await loginManually();
|
||||
|
||||
await this.writeState({
|
||||
...this.state,
|
||||
loginState: {
|
||||
...this.state.loginState,
|
||||
sessionToken,
|
||||
},
|
||||
});
|
||||
if (!sessionToken) {
|
||||
throw new Error("Session token is not set.");
|
||||
}
|
||||
const sessionToken = this.state.loginState!.sessionToken!;
|
||||
|
||||
console.log("Checking token...");
|
||||
if (!await checkToken(this.state)) {
|
||||
console.log("Token expired, refetch tokens.");
|
||||
|
||||
const { webServiceToken, userCountry, userLang } = await getGToken({
|
||||
fApi: this.state.fGen,
|
||||
|
|
@ -338,6 +354,20 @@ export class App {
|
|||
userCountry: this.state.userCountry ?? userCountry,
|
||||
});
|
||||
}
|
||||
async run() {
|
||||
await this.readState();
|
||||
|
||||
if (!this.state.loginState?.sessionToken) {
|
||||
const sessionToken = await loginManually();
|
||||
|
||||
await this.writeState({
|
||||
...this.state,
|
||||
loginState: {
|
||||
...this.state.loginState,
|
||||
sessionToken,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (this.opts.monitor) {
|
||||
await this.monitor();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { StatInkPostBody, VsHistoryDetail } from "./types.ts";
|
||||
|
||||
export const AGENT_NAME = "s3si.ts";
|
||||
export const S3SI_VERSION = "0.1.6";
|
||||
export const S3SI_VERSION = "0.1.7";
|
||||
export const NSOAPP_VERSION = "2.3.1";
|
||||
export const WEB_VIEW_VERSION = "1.0.0-216d0219";
|
||||
export const S3SI_LINK = "https://github.com/spacemeowx2/s3si.ts"
|
||||
|
|
|
|||
|
|
@ -66,6 +66,14 @@ async function request<Q extends Queries>(
|
|||
return json.data;
|
||||
}
|
||||
|
||||
export const isTokenExpired = (e: unknown) => {
|
||||
if (e instanceof APIError) {
|
||||
return e.response.status === 401;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export async function checkToken(state: State) {
|
||||
if (
|
||||
!state.loginState?.sessionToken || !state.loginState?.bulletToken ||
|
||||
|
|
|
|||
36
src/utils.ts
36
src/utils.ts
|
|
@ -63,9 +63,9 @@ export function cache<F extends () => Promise<unknown>>(
|
|||
};
|
||||
}
|
||||
|
||||
export async function showError(p: Promise<void>) {
|
||||
export async function showError<T>(p: Promise<T>): Promise<T> {
|
||||
try {
|
||||
await p;
|
||||
return await p;
|
||||
} catch (e) {
|
||||
if (e instanceof APIError) {
|
||||
console.error(
|
||||
|
|
@ -116,3 +116,35 @@ export function parseVsHistoryDetailId(id: string) {
|
|||
|
||||
export const delay = (ms: number) =>
|
||||
new Promise<void>((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
export type RecoverableError = {
|
||||
name: string;
|
||||
is: (err: unknown) => boolean;
|
||||
recovery: () => Promise<void>;
|
||||
retryTimes?: number;
|
||||
delayTime?: number;
|
||||
};
|
||||
export async function retryRecoverableError<F extends () => Promise<unknown>>(
|
||||
f: F,
|
||||
...errors: RecoverableError[]
|
||||
): Promise<PromiseReturnType<F>> {
|
||||
const retryTimes: Record<string, number> = Object.fromEntries(
|
||||
errors.map(({ name, retryTimes }) => [name, retryTimes ?? 1]),
|
||||
);
|
||||
while (true) {
|
||||
try {
|
||||
return await f() as PromiseReturnType<F>;
|
||||
} catch (e) {
|
||||
const error = errors.find((error) => error.is(e));
|
||||
if (error) {
|
||||
if (retryTimes[error.name] > 0) {
|
||||
retryTimes[error.name]--;
|
||||
await error.recovery();
|
||||
await delay(error.delayTime ?? 1000);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue