mirror of
https://github.com/samuelthomas2774/nxapi.git
synced 2026-04-25 07:27:19 -05:00
Add automatic token renewal for Moon/NookLink and fix authentication limit with automatic token renewal
This commit is contained in:
parent
4fcecd9f5b
commit
14e7793b7c
|
|
@ -56,6 +56,31 @@ const data = await moon.renewToken(na_session_token);
|
|||
// data should be saved and reused
|
||||
```
|
||||
|
||||
#### `MoonApi.onTokenExpired`
|
||||
|
||||
Function called when a `401 Unauthorized` response is received from the API.
|
||||
|
||||
This function should either call `MoonApi.loginWithSessionToken` to renew the token, then return the `MoonAuthData` object, or call `MoonApi.renewToken`.
|
||||
|
||||
```ts
|
||||
import MoonApi, { MoonAuthData } from 'nxapi/moon';
|
||||
import { Response } from 'node-fetch';
|
||||
|
||||
const moon = MoonApi.createWithSavedToken(...);
|
||||
let auth_data: MoonAuthData;
|
||||
const na_session_token: string;
|
||||
|
||||
moon.onTokenExpired = async (error: MoonError, response: Response) => {
|
||||
const data = await MoonApi.loginWithSessionToken(na_session_token);
|
||||
// data is a plain object of type MoonAuthData
|
||||
// data should be saved and reused
|
||||
|
||||
auth_data = data;
|
||||
|
||||
return data;
|
||||
};
|
||||
```
|
||||
|
||||
### API types
|
||||
|
||||
`nxapi/moon` exports all API types from [src/api/moon-types.ts](../../src/api/moon-types.ts).
|
||||
|
|
|
|||
|
|
@ -76,6 +76,31 @@ const {nooklinkuser, data} = await nooklink.createUserClient(user_id);
|
|||
// data is a plain object of type NooklinkUserAuthData
|
||||
```
|
||||
|
||||
#### `NooklinkApi.onTokenExpired`
|
||||
|
||||
Function called when a `401 Unauthorized` response is received from the API.
|
||||
|
||||
This function should either call `NooklinkApi.loginWithWebServiceToken` or `NooklinkApi.loginWithCoral` to renew the token, then return the `NooklinkAuthData` object, or call `NooklinkApi.renewTokenWithWebServiceToken` or `NooklinkApi.renewTokenWithCoral`.
|
||||
|
||||
```ts
|
||||
import NooklinkApi, { NooklinkAuthData, WebServiceError } from 'nxapi/nooklink';
|
||||
import { Response } from 'node-fetch';
|
||||
|
||||
const nooklink = NooklinkApi.createWithSavedToken(...);
|
||||
let auth_data: NooklinkAuthData;
|
||||
const na_session_token: string;
|
||||
|
||||
nooklink.onTokenExpired = async (error: WebServiceError, response: Response) => {
|
||||
const data = await NooklinkApi.loginWithSessionToken(na_session_token);
|
||||
// data is a plain object of type NooklinkAuthData
|
||||
// data should be saved and reused
|
||||
|
||||
auth_data = data;
|
||||
|
||||
return data;
|
||||
};
|
||||
```
|
||||
|
||||
### `NooklinkUserApi`
|
||||
|
||||
NookLink ACNH-level API client. An instance of this class should not be created directly; instead `NooklinkApi.createUserClient` or one of the `createWith*` static methods should be used.
|
||||
|
|
@ -110,6 +135,37 @@ const nooklinkuser = NooklinkUserApi.createWithCliTokenData(data);
|
|||
// nooklinkuser instanceof NooklinkUserApi
|
||||
```
|
||||
|
||||
#### `NooklinkUserApi.onTokenExpired`
|
||||
|
||||
Function called when a `401 Unauthorized` response is received from the API.
|
||||
|
||||
This function should either call `NooklinkUserApi.getToken` to renew the token, then return the `PartialNooklinkUserAuthData` object, or call `NooklinkUserApi.renewToken`.
|
||||
|
||||
```ts
|
||||
import NooklinkApi, { NooklinkAuthData, NooklinkUserApi, PartialNooklinkUserAuthData, WebServiceError } from 'nxapi/nooklink';
|
||||
import { Response } from 'node-fetch';
|
||||
|
||||
const nooklink: NooklinkApi;
|
||||
|
||||
const nooklinkuser = NooklinkUserApi.createWithSavedToken(...);
|
||||
let auth_data: NooklinkUserAuthData;
|
||||
const na_session_token: string;
|
||||
|
||||
nooklinkuser.onTokenExpired = async (error: WebServiceError, response: Response) => {
|
||||
const data = await nooklinkuser.getToken(nooklink);
|
||||
// data is a plain object of type PartialNooklinkUserAuthData
|
||||
// data should be saved and reused
|
||||
|
||||
const new_auth_data = Object.assign({}, auth_data, data);
|
||||
// new_auth_data is a plain object of type NooklinkUserAuthData
|
||||
// new_auth_data should be saved and reused
|
||||
|
||||
auth_data = new_auth_data;
|
||||
|
||||
return data;
|
||||
};
|
||||
```
|
||||
|
||||
### API types
|
||||
|
||||
`nxapi/nooklink` exports all API types from [src/api/nooklink-types.ts](../../src/api/nooklink-types.ts).
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import fetch from 'node-fetch';
|
||||
import fetch, { Response } from 'node-fetch';
|
||||
import createDebug from 'debug';
|
||||
import { getNintendoAccountToken, getNintendoAccountUser, NintendoAccountToken, NintendoAccountUser } from './na.js';
|
||||
import { defineResponse, ErrorResponse } from './util.js';
|
||||
import { defineResponse, ErrorResponse, HasResponse } from './util.js';
|
||||
import { DailySummaries, Devices, MonthlySummaries, MonthlySummary, MoonError, ParentalControlSettingState, SmartDevices, User } from './moon-types.js';
|
||||
import { timeoutSignal } from '../util/misc.js';
|
||||
|
||||
|
|
@ -16,6 +16,10 @@ const ZNMA_USER_AGENT = 'moon_ANDROID/' + ZNMA_VERSION + ' (com.nintendo.znma; b
|
|||
'; ANDROID 26)';
|
||||
|
||||
export default class MoonApi {
|
||||
onTokenExpired: ((data: MoonError, res: Response) => Promise<MoonAuthData | PartialMoonAuthData | void>) | null = null;
|
||||
/** @internal */
|
||||
_renewToken: Promise<void> | null = null;
|
||||
|
||||
protected constructor(
|
||||
public token: string,
|
||||
public naId: string,
|
||||
|
|
@ -24,7 +28,15 @@ export default class MoonApi {
|
|||
readonly znma_useragent = ZNMA_USER_AGENT,
|
||||
) {}
|
||||
|
||||
async fetch<T extends object>(url: string, method = 'GET', body?: string, headers?: object) {
|
||||
async fetch<T extends object>(
|
||||
url: string, method = 'GET', body?: string, headers?: object,
|
||||
/** @internal */ _autoRenewToken = true,
|
||||
/** @internal */ _attempt = 0
|
||||
): Promise<HasResponse<T, Response>> {
|
||||
if (this._renewToken && _autoRenewToken) {
|
||||
await this._renewToken;
|
||||
}
|
||||
|
||||
const [signal, cancel] = timeoutSignal();
|
||||
const response = await fetch(MOON_URL + url, {
|
||||
method,
|
||||
|
|
@ -49,6 +61,18 @@ export default class MoonApi {
|
|||
|
||||
debug('fetch %s %s, response %s', method, url, response.status);
|
||||
|
||||
if (response.status === 401 && _autoRenewToken && !_attempt && this.onTokenExpired) {
|
||||
const data = await response.json() as MoonError;
|
||||
|
||||
// _renewToken will be awaited when calling fetch
|
||||
this._renewToken = this._renewToken ?? this.onTokenExpired.call(null, data, response).then(data => {
|
||||
if (data) this.setTokenWithSavedToken(data);
|
||||
}).finally(() => {
|
||||
this._renewToken = null;
|
||||
});
|
||||
return this.fetch(url, method, body, headers, _autoRenewToken, _attempt + 1);
|
||||
}
|
||||
|
||||
if (response.status !== 200) {
|
||||
throw new ErrorResponse('[moon] Non-200 status code', response, await response.text());
|
||||
}
|
||||
|
|
@ -92,13 +116,15 @@ export default class MoonApi {
|
|||
|
||||
async renewToken(token: string) {
|
||||
const data = await MoonApi.loginWithSessionToken(token);
|
||||
|
||||
this.token = data.nintendoAccountToken.access_token!;
|
||||
this.naId = data.user.id;
|
||||
|
||||
this.setTokenWithSavedToken(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
private setTokenWithSavedToken(data: MoonAuthData | PartialMoonAuthData) {
|
||||
this.token = data.nintendoAccountToken.access_token!;
|
||||
if ('user' in data) this.naId = data.user.id;
|
||||
}
|
||||
|
||||
static async createWithSessionToken(token: string) {
|
||||
const data = await this.loginWithSessionToken(token);
|
||||
return {moon: this.createWithSavedToken(data), data};
|
||||
|
|
@ -145,3 +171,6 @@ export interface MoonAuthData {
|
|||
znma_build: string;
|
||||
znma_useragent: string;
|
||||
}
|
||||
export interface PartialMoonAuthData {
|
||||
nintendoAccountToken: NintendoAccountToken;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 { WebServiceError, Users, AuthToken, UserProfile, Newspapers, Newspaper, Emoticons, Reaction, IslandProfile } from './nooklink-types.js';
|
||||
import { timeoutSignal } from '../util/misc.js';
|
||||
|
|
@ -17,13 +17,25 @@ const NOOKLINK_URL = NOOKLINK_WEBSERVICE_URL + '/api';
|
|||
const BLANCO_VERSION = '2.1.1';
|
||||
|
||||
export default class NooklinkApi {
|
||||
onTokenExpired: ((data: WebServiceError, res: Response) => Promise<NooklinkAuthData | void>) | null = null;
|
||||
/** @internal */
|
||||
_renewToken: Promise<void> | null = null;
|
||||
|
||||
protected constructor(
|
||||
public gtoken: string,
|
||||
public useragent: string,
|
||||
readonly client_version = BLANCO_VERSION,
|
||||
) {}
|
||||
|
||||
async fetch<T extends object>(url: string, method = 'GET', body?: string | FormData, headers?: object) {
|
||||
async fetch<T extends object>(
|
||||
url: string, method = 'GET', body?: string | FormData, headers?: object,
|
||||
/** @internal */ _autoRenewToken = true,
|
||||
/** @internal */ _attempt = 0
|
||||
): Promise<HasResponse<T, Response>> {
|
||||
if (this._renewToken && _autoRenewToken) {
|
||||
await this._renewToken;
|
||||
}
|
||||
|
||||
const [signal, cancel] = timeoutSignal();
|
||||
const response = await fetch(NOOKLINK_URL + url, {
|
||||
method,
|
||||
|
|
@ -44,6 +56,18 @@ export default class NooklinkApi {
|
|||
|
||||
debug('fetch %s %s, response %s', method, url, response.status);
|
||||
|
||||
if (response.status === 401 && _autoRenewToken && !_attempt && this.onTokenExpired) {
|
||||
const data = await response.json() as WebServiceError;
|
||||
|
||||
// _renewToken will be awaited when calling fetch
|
||||
this._renewToken = this._renewToken ?? this.onTokenExpired.call(null, data, response).then(data => {
|
||||
if (data) this.setTokenWithSavedToken(data);
|
||||
}).finally(() => {
|
||||
this._renewToken = null;
|
||||
});
|
||||
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());
|
||||
}
|
||||
|
|
@ -71,6 +95,22 @@ export default class NooklinkApi {
|
|||
return NooklinkUserApi._createWithNooklinkApi(this, user_id);
|
||||
}
|
||||
|
||||
async renewTokenWithCoral(nso: CoralApi, user: NintendoAccountUser) {
|
||||
const data = await NooklinkApi.loginWithCoral(nso, user);
|
||||
this.setTokenWithSavedToken(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
async renewTokenWithWebServiceToken(webserviceToken: WebServiceToken, user: NintendoAccountUser) {
|
||||
const data = await NooklinkApi.loginWithWebServiceToken(webserviceToken, user);
|
||||
this.setTokenWithSavedToken(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
private setTokenWithSavedToken(data: NooklinkAuthData) {
|
||||
this.gtoken = data.gtoken;
|
||||
}
|
||||
|
||||
static async createWithCoral(nso: CoralApi, user: NintendoAccountUser) {
|
||||
const data = await this.loginWithCoral(nso, user);
|
||||
return {nooklink: this.createWithSavedToken(data), data};
|
||||
|
|
@ -155,6 +195,10 @@ export default class NooklinkApi {
|
|||
}
|
||||
|
||||
export class NooklinkUserApi {
|
||||
onTokenExpired: ((data: WebServiceError, res: Response) => Promise<NooklinkUserAuthData | PartialNooklinkUserAuthData | void>) | null = null;
|
||||
/** @internal */
|
||||
_renewToken: Promise<void> | null = null;
|
||||
|
||||
protected constructor(
|
||||
public user_id: string,
|
||||
public auth_token: string,
|
||||
|
|
@ -164,7 +208,15 @@ export class NooklinkUserApi {
|
|||
readonly client_version = BLANCO_VERSION,
|
||||
) {}
|
||||
|
||||
async fetch<T extends object>(url: string, method = 'GET', body?: string | FormData, headers?: object) {
|
||||
async fetch<T extends object>(
|
||||
url: string, method = 'GET', body?: string | FormData, headers?: object,
|
||||
/** @internal */ _autoRenewToken = true,
|
||||
/** @internal */ _attempt = 0
|
||||
): Promise<HasResponse<T, Response>> {
|
||||
if (this._renewToken && _autoRenewToken) {
|
||||
await this._renewToken;
|
||||
}
|
||||
|
||||
const [signal, cancel] = timeoutSignal();
|
||||
const response = await fetch(NOOKLINK_URL + url, {
|
||||
method,
|
||||
|
|
@ -186,6 +238,18 @@ export class NooklinkUserApi {
|
|||
|
||||
debug('fetch %s %s, response %s', method, url, response.status);
|
||||
|
||||
if (response.status === 401 && _autoRenewToken && !_attempt && this.onTokenExpired) {
|
||||
const data = await response.json() as WebServiceError;
|
||||
|
||||
// _renewToken will be awaited when calling fetch
|
||||
this._renewToken = this._renewToken ?? this.onTokenExpired.call(null, data, response).then(data => {
|
||||
if (data) this.setTokenWithSavedToken(data);
|
||||
}).finally(() => {
|
||||
this._renewToken = null;
|
||||
});
|
||||
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());
|
||||
}
|
||||
|
|
@ -243,6 +307,28 @@ export class NooklinkUserApi {
|
|||
return this.postMessage(reaction.label, MessageType.EMOTICON);
|
||||
}
|
||||
|
||||
async getToken(client: NooklinkApi): Promise<PartialNooklinkUserAuthData> {
|
||||
const token = await client.getAuthToken(this.user_id);
|
||||
|
||||
return {
|
||||
gtoken: client.gtoken,
|
||||
user_id: this.user_id,
|
||||
token,
|
||||
};
|
||||
}
|
||||
|
||||
async renewToken(client: NooklinkApi) {
|
||||
const data = await this.getToken(client);
|
||||
this.setTokenWithSavedToken(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
private setTokenWithSavedToken(data: NooklinkUserAuthData | PartialNooklinkUserAuthData) {
|
||||
this.user_id = data.user_id;
|
||||
this.auth_token = data.token.token;
|
||||
this.gtoken = data.gtoken;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
static async _loginWithNooklinkApi(client: NooklinkApi, user_id: string): Promise<NooklinkUserAuthData> {
|
||||
const token = await client.getAuthToken(user_id);
|
||||
|
|
@ -300,6 +386,8 @@ export interface NooklinkUserAuthData {
|
|||
token: AuthToken;
|
||||
language: string;
|
||||
}
|
||||
export type PartialNooklinkUserAuthData =
|
||||
Pick<NooklinkUserAuthData, 'gtoken' | 'user_id' | 'token'>;
|
||||
|
||||
export interface NooklinkUserCliTokenData {
|
||||
gtoken: string;
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ export async function getToken(
|
|||
expires_at: Date.now() + (data.credential.expiresIn * 1000),
|
||||
};
|
||||
|
||||
nso.onTokenExpired = createTokenExpiredHandler(storage, token, nso, existingToken);
|
||||
nso.onTokenExpired = createTokenExpiredHandler(storage, token, nso, {existingToken});
|
||||
|
||||
await storage.setItem('NsoToken.' + token, existingToken);
|
||||
await storage.setItem('NintendoAccountToken.' + data.user.id, token);
|
||||
|
|
@ -90,30 +90,38 @@ export async function getToken(
|
|||
new ZncProxyApi(proxy_url, token) :
|
||||
CoralApi.createWithSavedToken(existingToken);
|
||||
|
||||
nso.onTokenExpired = createTokenExpiredHandler(storage, token, nso, existingToken);
|
||||
nso.onTokenExpired = createTokenExpiredHandler(storage, token, nso, {existingToken});
|
||||
|
||||
return {nso, data: existingToken};
|
||||
}
|
||||
|
||||
function createTokenExpiredHandler(
|
||||
storage: persist.LocalStorage, token: string, nso: CoralApi, existingToken: SavedToken
|
||||
storage: persist.LocalStorage, token: string, nso: CoralApi,
|
||||
renew_token_data: {existingToken: SavedToken}, ratelimit = true
|
||||
) {
|
||||
return (data: CoralErrorResponse, response: Response) => {
|
||||
debug('Token expired', existingToken.user.id, data);
|
||||
return renewToken(storage, token, nso, existingToken);
|
||||
debug('Token expired', renew_token_data.existingToken.user.id, data);
|
||||
return renewToken(storage, token, nso, renew_token_data, ratelimit);
|
||||
};
|
||||
}
|
||||
|
||||
async function renewToken(
|
||||
storage: persist.LocalStorage, token: string, nso: CoralApi, previousToken: SavedToken
|
||||
storage: persist.LocalStorage, token: string, nso: CoralApi,
|
||||
renew_token_data: {existingToken: SavedToken}, ratelimit = true
|
||||
) {
|
||||
const data = await nso.renewToken(token, previousToken.user);
|
||||
if (ratelimit) {
|
||||
const [jwt, sig] = Jwt.decode<NintendoAccountSessionTokenJwtPayload>(token);
|
||||
await checkUseLimit(storage, 'coral', jwt.payload.sub, ratelimit);
|
||||
}
|
||||
|
||||
const data = await nso.renewToken(token, renew_token_data.existingToken.user);
|
||||
|
||||
const existingToken: SavedToken = {
|
||||
...previousToken,
|
||||
...renew_token_data.existingToken,
|
||||
...data,
|
||||
expires_at: Date.now() + (data.credential.expiresIn * 1000),
|
||||
};
|
||||
|
||||
await storage.setItem('NsoToken.' + token, existingToken);
|
||||
renew_token_data.existingToken = existingToken;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import createDebug from 'debug';
|
||||
import * as persist from 'node-persist';
|
||||
import { Response } from 'node-fetch';
|
||||
import { MoonAuthData, ZNMA_CLIENT_ID } from '../../api/moon.js';
|
||||
import { NintendoAccountSessionTokenJwtPayload } from '../../api/na.js';
|
||||
import { Jwt } from '../../util/jwt.js';
|
||||
import MoonApi from '../../api/moon.js';
|
||||
import { checkUseLimit, LIMIT_REQUESTS, SHOULD_LIMIT_USE } from './util.js';
|
||||
import { MoonError } from '../../api/moon-types.js';
|
||||
|
||||
const debug = createDebug('nxapi:auth:moon');
|
||||
|
||||
|
|
@ -53,6 +55,8 @@ export async function getPctlToken(storage: persist.LocalStorage, token: string,
|
|||
expires_at: Date.now() + (data.nintendoAccountToken.expires_in * 1000),
|
||||
};
|
||||
|
||||
moon.onTokenExpired = createTokenExpiredHandler(storage, token, moon, {existingToken});
|
||||
|
||||
await storage.setItem('MoonToken.' + token, existingToken);
|
||||
await storage.setItem('NintendoAccountToken-pctl.' + data.user.id, token);
|
||||
|
||||
|
|
@ -61,9 +65,40 @@ export async function getPctlToken(storage: persist.LocalStorage, token: string,
|
|||
|
||||
debug('Using existing token');
|
||||
await storage.setItem('NintendoAccountToken-pctl.' + existingToken.user.id, token);
|
||||
|
||||
const moon = MoonApi.createWithSavedToken(existingToken);
|
||||
moon.onTokenExpired = createTokenExpiredHandler(storage, token, moon, {existingToken});
|
||||
|
||||
return {
|
||||
moon: MoonApi.createWithSavedToken(existingToken),
|
||||
data: existingToken,
|
||||
return {moon, data: existingToken};
|
||||
}
|
||||
|
||||
function createTokenExpiredHandler(
|
||||
storage: persist.LocalStorage, token: string, moon: MoonApi,
|
||||
renew_token_data: {existingToken: SavedMoonToken}, ratelimit = true
|
||||
) {
|
||||
return (data: MoonError, response: Response) => {
|
||||
debug('Token expired', renew_token_data.existingToken.user.id, data);
|
||||
return renewToken(storage, token, moon, renew_token_data, ratelimit);
|
||||
};
|
||||
}
|
||||
|
||||
async function renewToken(
|
||||
storage: persist.LocalStorage, token: string, moon: MoonApi,
|
||||
renew_token_data: {existingToken: SavedMoonToken}, ratelimit = true
|
||||
) {
|
||||
if (ratelimit) {
|
||||
const [jwt, sig] = Jwt.decode<NintendoAccountSessionTokenJwtPayload>(token);
|
||||
await checkUseLimit(storage, 'moon', jwt.payload.sub, ratelimit, [LIMIT_REQUESTS, LIMIT_PERIOD]);
|
||||
}
|
||||
|
||||
const data = await moon.renewToken(token);
|
||||
|
||||
const existingToken: SavedMoonToken = {
|
||||
...renew_token_data.existingToken,
|
||||
...data,
|
||||
expires_at: Date.now() + (data.nintendoAccountToken.expires_in * 1000),
|
||||
};
|
||||
|
||||
await storage.setItem('MoonToken.' + token, existingToken);
|
||||
renew_token_data.existingToken = existingToken;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import createDebug from 'debug';
|
||||
import persist from 'node-persist';
|
||||
import { Response } from 'node-fetch';
|
||||
import { getToken, Login } from './coral.js';
|
||||
import NooklinkApi, { NooklinkAuthData, NooklinkUserApi, NooklinkUserAuthData } from '../../api/nooklink.js';
|
||||
import { Users } from '../../api/nooklink-types.js';
|
||||
import { Users, WebServiceError } from '../../api/nooklink-types.js';
|
||||
import { checkUseLimit, SHOULD_LIMIT_USE } from './util.js';
|
||||
import { Jwt } from '../../util/jwt.js';
|
||||
import { NintendoAccountSessionTokenJwtPayload } from '../../api/na.js';
|
||||
|
|
@ -51,20 +52,64 @@ export async function getWebServiceToken(
|
|||
|
||||
await storage.setItem('NookToken.' + token, existingToken);
|
||||
|
||||
return {
|
||||
nooklink: NooklinkApi.createWithSavedToken(existingToken),
|
||||
data: existingToken,
|
||||
};
|
||||
const nooklink = NooklinkApi.createWithSavedToken(existingToken);
|
||||
|
||||
nooklink.onTokenExpired = createTokenExpiredHandler(storage, token, nooklink, {
|
||||
existingToken,
|
||||
znc_proxy_url: proxy_url,
|
||||
});
|
||||
|
||||
return {nooklink, data: existingToken};
|
||||
}
|
||||
|
||||
debug('Using existing web service token');
|
||||
|
||||
return {
|
||||
nooklink: NooklinkApi.createWithSavedToken(existingToken),
|
||||
data: existingToken,
|
||||
const nooklink = NooklinkApi.createWithSavedToken(existingToken);
|
||||
|
||||
nooklink.onTokenExpired = createTokenExpiredHandler(storage, token, nooklink, {
|
||||
existingToken,
|
||||
znc_proxy_url: proxy_url,
|
||||
});
|
||||
|
||||
return {nooklink, data: existingToken};
|
||||
}
|
||||
|
||||
function createTokenExpiredHandler(
|
||||
storage: persist.LocalStorage, token: string, nooklink: NooklinkApi,
|
||||
renew_token_data: {existingToken: SavedToken; znc_proxy_url?: string},
|
||||
ratelimit = true
|
||||
) {
|
||||
return (data: WebServiceError, response: Response) => {
|
||||
debug('Token expired, renewing', data);
|
||||
return renewToken(storage, token, nooklink, renew_token_data, ratelimit);
|
||||
};
|
||||
}
|
||||
|
||||
async function renewToken(
|
||||
storage: persist.LocalStorage, token: string, nooklink: NooklinkApi,
|
||||
renew_token_data: {existingToken: SavedToken; znc_proxy_url?: string},
|
||||
ratelimit = true
|
||||
) {
|
||||
if (ratelimit) {
|
||||
const [jwt, sig] = Jwt.decode<NintendoAccountSessionTokenJwtPayload>(token);
|
||||
await checkUseLimit(storage, 'nooklink', jwt.payload.sub);
|
||||
}
|
||||
|
||||
const {nso, data} = await getToken(storage, token, renew_token_data.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: SavedToken = await nooklink.renewTokenWithCoral(nso, data.user);
|
||||
|
||||
await storage.setItem('NookToken.' + token, existingToken);
|
||||
renew_token_data.existingToken = existingToken;
|
||||
}
|
||||
|
||||
export interface SavedUserToken extends NooklinkUserAuthData {}
|
||||
|
||||
type PromiseValue<T> = T extends PromiseLike<infer R> ? R : never;
|
||||
|
|
@ -122,6 +167,12 @@ export async function getUserToken(
|
|||
const {nooklinkuser, data} = await nooklink.createUserClient(user);
|
||||
const existingToken: SavedUserToken = data;
|
||||
|
||||
nooklinkuser.onTokenExpired = createUserTokenExpiredHandler(storage, nintendoAccountToken, nooklinkuser, {
|
||||
existingToken,
|
||||
znc_proxy_url: proxy_url,
|
||||
nooklink,
|
||||
});
|
||||
|
||||
await storage.setItem('NookAuthToken.' + nintendoAccountToken + '.' + user, existingToken);
|
||||
|
||||
return {nooklinkuser, data: existingToken};
|
||||
|
|
@ -129,8 +180,52 @@ export async function getUserToken(
|
|||
|
||||
debug('Using existing NookLink auth token');
|
||||
|
||||
return {
|
||||
nooklinkuser: NooklinkUserApi.createWithSavedToken(existingToken),
|
||||
data: existingToken,
|
||||
const nooklinkuser = NooklinkUserApi.createWithSavedToken(existingToken);
|
||||
|
||||
nooklinkuser.onTokenExpired = createUserTokenExpiredHandler(storage, nintendoAccountToken, nooklinkuser, {
|
||||
existingToken,
|
||||
znc_proxy_url: proxy_url,
|
||||
nooklink: null,
|
||||
});
|
||||
|
||||
return {nooklinkuser, data: existingToken};
|
||||
}
|
||||
|
||||
function createUserTokenExpiredHandler(
|
||||
storage: persist.LocalStorage, token: string, nooklinkuser: NooklinkUserApi,
|
||||
renew_token_data: {existingToken: SavedUserToken; znc_proxy_url?: string; nooklink: NooklinkApi | null},
|
||||
ratelimit = true
|
||||
) {
|
||||
return (data: WebServiceError, response: Response) => {
|
||||
debug('Token expired', nooklinkuser.user_id, data);
|
||||
return renewUserToken(storage, token, nooklinkuser, renew_token_data);
|
||||
};
|
||||
}
|
||||
|
||||
async function renewUserToken(
|
||||
storage: persist.LocalStorage, token: string, nooklinkuser: NooklinkUserApi,
|
||||
renew_token_data: {existingToken: SavedUserToken; znc_proxy_url?: string; nooklink: NooklinkApi | null},
|
||||
ratelimit = true
|
||||
) {
|
||||
if (!renew_token_data.nooklink) {
|
||||
const wst = await getWebServiceToken(storage, token, renew_token_data.znc_proxy_url, true, ratelimit);
|
||||
const {nooklink, data: webserviceToken} = wst;
|
||||
|
||||
renew_token_data.nooklink = nooklink;
|
||||
}
|
||||
|
||||
if (ratelimit) {
|
||||
const [jwt, sig] = Jwt.decode<NintendoAccountSessionTokenJwtPayload>(token);
|
||||
await checkUseLimit(storage, 'nooklink-user', jwt.payload.sub);
|
||||
}
|
||||
|
||||
const data = await nooklinkuser.renewToken(renew_token_data.nooklink);
|
||||
|
||||
const existingToken: SavedUserToken = {
|
||||
...renew_token_data.existingToken,
|
||||
...data,
|
||||
};
|
||||
|
||||
await storage.setItem('NookAuthToken.' + token + '.' + nooklinkuser.user_id, existingToken);
|
||||
renew_token_data.existingToken = existingToken;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,28 +76,36 @@ export async function getBulletToken(
|
|||
|
||||
function createTokenExpiredHandler(
|
||||
storage: persist.LocalStorage, token: string, splatnet: SplatNet3Api,
|
||||
data: {existingToken: SavedBulletToken; znc_proxy_url?: string}
|
||||
data: {existingToken: SavedBulletToken; znc_proxy_url?: string},
|
||||
ratelimit = true
|
||||
) {
|
||||
return (response: Response) => {
|
||||
debug('Token expired, renewing');
|
||||
return renewToken(storage, token, splatnet, data);
|
||||
return renewToken(storage, token, splatnet, data, ratelimit);
|
||||
};
|
||||
}
|
||||
|
||||
function createTokenShouldRenewHandler(
|
||||
storage: persist.LocalStorage, token: string, splatnet: SplatNet3Api,
|
||||
data: {existingToken: SavedBulletToken; znc_proxy_url?: string}
|
||||
data: {existingToken: SavedBulletToken; znc_proxy_url?: string},
|
||||
ratelimit = true
|
||||
) {
|
||||
return (remaining: number, response: Response) => {
|
||||
debug('Token will expire in %d seconds, renewing', remaining);
|
||||
return renewToken(storage, token, splatnet, data);
|
||||
return renewToken(storage, token, splatnet, data, ratelimit);
|
||||
};
|
||||
}
|
||||
|
||||
async function renewToken(
|
||||
storage: persist.LocalStorage, token: string, splatnet: SplatNet3Api,
|
||||
renew_token_data: {existingToken: SavedBulletToken; znc_proxy_url?: string}
|
||||
renew_token_data: {existingToken: SavedBulletToken; znc_proxy_url?: string},
|
||||
ratelimit = true
|
||||
) {
|
||||
if (ratelimit) {
|
||||
const [jwt, sig] = Jwt.decode<NintendoAccountSessionTokenJwtPayload>(token);
|
||||
await checkUseLimit(storage, 'splatnet3', jwt.payload.sub);
|
||||
}
|
||||
|
||||
try {
|
||||
const data: SavedToken | undefined = await storage.getItem('NsoToken.' + token);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
export {
|
||||
default,
|
||||
MoonAuthData,
|
||||
PartialMoonAuthData,
|
||||
} from '../api/moon.js';
|
||||
|
||||
export * from '../api/moon-types.js';
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ export {
|
|||
|
||||
NooklinkUserApi,
|
||||
NooklinkUserAuthData,
|
||||
PartialNooklinkUserAuthData,
|
||||
NooklinkUserCliTokenData,
|
||||
|
||||
MessageType,
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user