diff --git a/README.md b/README.md index 9662caa..1ea367b 100644 --- a/README.md +++ b/README.md @@ -212,6 +212,18 @@ DEBUG=nxapi:api:* nxapi ... DEBUG=* nxapi ... ``` +By default all nxapi logs will be written to a platform-specific location: + +Platform | Log path +----------------|---------------- +macOS | `Library/Logs/nxapi-nodejs` +Windows | `%localappdata%\nxapi-nodejs\Log` +Linux | `$XDG_STATE_HOME/nxapi-nodejs` or `.local/state/nxapi-nodejs` + +This only applies to the command line and Electron app and can be disabled by setting `NXAPI_DEBUG_FILE` to `0`. Each process writes to a new file. nxapi will automatically delete log files older than 14 days. + +nxapi logs may contain sensitive information such as Nintendo Account access tokens. + #### Environment variables Some options can be set using environment variables. These can be stored in a `.env` file in the data location. Environment variables will be read from the `.env` file in the default location, then the `.env` file in `NXAPI_DATA_PATH` location. `.env` files will not be read from the location set in the `--data-path` option. @@ -234,6 +246,7 @@ Environment variable | Description `NXAPI_SPLATNET3_UPGRADE_QUERIES` | Sets when the SplatNet 3 client is allowed to upgrade persisted query IDs to newer versions. If `0` queries are never upgraded (not recommended). If `1` queries are upgraded if they do not contain potentially breaking changes (not recommended, as like `0` this allows older queries to be sent to the API). If `2` queries are upgraded, requests that would include breaking changes are rejected. If `3` all queries are upgraded, even if they contain potentially breaking changes (default). `NXAPI_SPLATNET3_STRICT` | Disables strict handling of errors from the SplatNet 3 GraphQL API if set to `0`. If set to `1` (default) requests will be rejected if the response includes any errors, even if the response includes a result. `DEBUG` | Used by the [debug](https://github.com/debug-js/debug) package. Sets which modules should have debug logging enabled. See [debug logs](#debug-logs). +`NXAPI_DEBUG_FILE` | Disables writing debug logs to a file if set to `0`. Other environment variables may also be used by Node.js, Electron or other packages nxapi depends on. @@ -302,7 +315,7 @@ The reason Nintendo added this is probably to try and stop people automating acc - Nintendo Switch Online app API docs - https://github.com/ZekeSnider/NintendoSwitchRESTAPI - https://dev.to/mathewthe2/intro-to-nintendo-switch-rest-api-2cm7 - - nxapi includes TypeScript definitions of all API resources and JSON Web Token payloads at [src/api](src/api) + - nxapi includes TypeScript definitions for all API resources and JSON Web Token payloads at [src/api](src/api) - Coral client authentication (`f` parameter) - https://github.com/samuelthomas2774/nxapi/discussions/10 - ~~https://github.com/frozenpandaman/splatnet2statink/wiki/api-docs - splatnet2statink and flapg API docs~~ diff --git a/bin/nxapi.js b/bin/nxapi.js index afa8cef..b0fde86 100755 --- a/bin/nxapi.js +++ b/bin/nxapi.js @@ -1,7 +1,3 @@ #!/usr/bin/env node -import createDebug from 'debug'; - -createDebug.log = console.warn.bind(console); - -import('../dist/cli.js').then(cli => cli.main.call(null)); +import('../dist/cli-entry.js'); diff --git a/package-lock.json b/package-lock.json index 1675a24..4167ef8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,7 @@ "node-notifier": "^10.0.1", "node-persist": "^3.1.0", "read": "^1.0.7", - "splatnet3-types": "^0.2.20230227204004", + "splatnet3-types": "^0.2.20230601143335", "supports-color": "^8.1.1", "tslib": "^2.4.1", "uuid": "^8.3.2", @@ -4115,9 +4115,9 @@ "dev": true }, "node_modules/splatnet3-types": { - "version": "0.2.20230227204004", - "resolved": "https://registry.npmjs.org/splatnet3-types/-/splatnet3-types-0.2.20230227204004.tgz", - "integrity": "sha512-FAY6pbUcrp5O8c49BNXSKxoyM3UlCrRx2AtA9Y3qlvqOLdHqwxtzcdzbk1b1hRam8ZcrxRzE/ii6ESRiPIAnZw==" + "version": "0.2.20230601143335", + "resolved": "https://registry.npmjs.org/splatnet3-types/-/splatnet3-types-0.2.20230601143335.tgz", + "integrity": "sha512-gZO2DUohuPhhPhwJrEcOR07fYgAvA42ZuXEvaw3x7c5LQzjNzgjkwOQNnpHAMZcE6lIfT2L45v8cjfyu8VcBgA==" }, "node_modules/sprintf-js": { "version": "1.1.2", @@ -7861,9 +7861,9 @@ "dev": true }, "splatnet3-types": { - "version": "0.2.20230227204004", - "resolved": "https://registry.npmjs.org/splatnet3-types/-/splatnet3-types-0.2.20230227204004.tgz", - "integrity": "sha512-FAY6pbUcrp5O8c49BNXSKxoyM3UlCrRx2AtA9Y3qlvqOLdHqwxtzcdzbk1b1hRam8ZcrxRzE/ii6ESRiPIAnZw==" + "version": "0.2.20230601143335", + "resolved": "https://registry.npmjs.org/splatnet3-types/-/splatnet3-types-0.2.20230601143335.tgz", + "integrity": "sha512-gZO2DUohuPhhPhwJrEcOR07fYgAvA42ZuXEvaw3x7c5LQzjNzgjkwOQNnpHAMZcE6lIfT2L45v8cjfyu8VcBgA==" }, "sprintf-js": { "version": "1.1.2", diff --git a/package.json b/package.json index b5e6685..9c8f828 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "node-notifier": "^10.0.1", "node-persist": "^3.1.0", "read": "^1.0.7", - "splatnet3-types": "^0.2.20230227204004", + "splatnet3-types": "^0.2.20230601143335", "supports-color": "^8.1.1", "tslib": "^2.4.1", "uuid": "^8.3.2", diff --git a/resources/common/remote-config.json b/resources/common/remote-config.json index c4282b3..8ac6a9e 100644 --- a/resources/common/remote-config.json +++ b/resources/common/remote-config.json @@ -1,7 +1,7 @@ { "require_version": [], "coral": { - "znca_version": "2.5.0" + "znca_version": "2.5.1" }, "coral_auth": { "default": "imink", @@ -17,8 +17,8 @@ "blanco_version": "2.1.1" }, "coral_gws_splatnet3": { - "app_ver": "3.0.0-0742bda0", - "version": "3.0.0", - "revision": "0742bda0f28edfcda33ec743b8afc4a95700e27d" + "app_ver": "4.0.0-e2ee936d", + "version": "4.0.0", + "revision": "e2ee936dbecad1fd8582c2a35c2603c63767263f" } } diff --git a/resources/docker-entrypoint.sh b/resources/docker-entrypoint.sh index 6ab0109..8410801 100755 --- a/resources/docker-entrypoint.sh +++ b/resources/docker-entrypoint.sh @@ -2,4 +2,9 @@ mkdir -p /data/android +# Logs will be captured by Docker if enabled +# This is set here so that running another process with `docker exec` (which +# doesn't capture logs) will still write to a file by default +export NXAPI_DEBUG_FILE=0 + exec /app/bin/nxapi.js --data-path /data "$@" diff --git a/rollup.config.js b/rollup.config.js index 575a09a..0bb6d3d 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -65,13 +65,14 @@ const watch = { * @type {import('rollup').RollupOptions} */ const main = { - input: ['dist/cli-entry.js', 'dist/app/main/index.js'], + input: ['dist/cli-entry.js', 'dist/app/app-init.js', 'dist/app/main/index.js'], output: { dir: 'dist/bundle', format: 'es', sourcemap: true, entryFileNames: chunk => { if (chunk.name === 'cli-entry') return 'cli-bundle.js'; + if (chunk.name === 'app-init') return 'app-init-bundle.js'; if (chunk.name === 'index') return 'app-main-bundle.js'; return 'entry-' + chunk.name + '.js'; }, @@ -124,6 +125,7 @@ const app_entry = { include: ['dist/app/app-entry.cjs'], values: { '__NXAPI_BUNDLE_APP_MAIN__': JSON.stringify('./app-main-bundle.js'), + '__NXAPI_BUNDLE_APP_INIT__': JSON.stringify('./app-init-bundle.js'), }, preventAssignment: true, }), @@ -142,6 +144,8 @@ const app_entry = { external: [ 'electron', path.resolve(__dirname, 'dist/app/app-main-bundle.js'), + path.resolve(__dirname, 'dist/app/app-init-bundle.js'), + path.resolve(__dirname, 'dist/app/app-init.js'), path.resolve(__dirname, 'dist/app/main/index.js'), ], watch, diff --git a/src/api/coral.ts b/src/api/coral.ts index 2506fcf..0a90d46 100644 --- a/src/api/coral.ts +++ b/src/api/coral.ts @@ -1,10 +1,10 @@ import fetch, { Response } from 'node-fetch'; import { v4 as uuidgen } from 'uuid'; -import createDebug from 'debug'; import { f, FResult, HashMethod } from './f.js'; import { AccountLogin, AccountToken, Announcements, CurrentUser, CurrentUserPermissions, Event, Friends, GetActiveEventResult, PresencePermissions, User, WebServices, WebServiceToken, CoralErrorResponse, CoralResponse, CoralStatus, CoralSuccessResponse, FriendCodeUser, FriendCodeUrl, AccountTokenParameter, AccountLoginParameter, WebServiceTokenParameter } from './coral-types.js'; import { getNintendoAccountToken, getNintendoAccountUser, NintendoAccountToken, NintendoAccountUser } from './na.js'; import { ErrorResponse, ResponseSymbol } from './util.js'; +import createDebug from '../util/debug.js'; import { JwtPayload } from '../util/jwt.js'; import { getAdditionalUserAgents } from '../util/useragent.js'; import { timeoutSignal } from '../util/misc.js'; @@ -41,13 +41,17 @@ export interface ResultData { } export default class CoralApi { - onTokenExpired: ((data: CoralErrorResponse, res: Response) => Promise) | null = null; + onTokenExpired: ((data?: CoralErrorResponse, res?: Response) => Promise) | null = null; /** @internal */ _renewToken: Promise | null = null; + /** @internal */ + _token_expired = false; protected constructor( public token: string, public useragent: string | null = getAdditionalUserAgents(), + public coral_user_id: string, + public na_id: string, readonly znca_version = ZNCA_VERSION, readonly znca_useragent = ZNCA_USER_AGENT, ) {} @@ -55,8 +59,18 @@ export default class CoralApi { async fetch( url: string, method = 'GET', body?: string, headers?: object, /** @internal */ _autoRenewToken = true, - /** @internal */ _attempt = 0 + /** @internal */ _attempt = 0, ): Promise> { + if (this._token_expired && _autoRenewToken && !this._renewToken) { + if (!this.onTokenExpired || _attempt) throw new Error('Token expired'); + + this._renewToken = this.onTokenExpired.call(null).then(data => { + if (data) this.setTokenWithSavedToken(data); + }).finally(() => { + this._renewToken = null; + }); + } + if (this._renewToken && _autoRenewToken) { await this._renewToken; } @@ -84,6 +98,7 @@ export default class CoralApi { const data = await response.json() as CoralResponse; if (data.status === CoralStatus.TOKEN_EXPIRED && _autoRenewToken && !_attempt && this.onTokenExpired) { + this._token_expired = true; // _renewToken will be awaited when calling fetch this._renewToken = this._renewToken ?? this.onTokenExpired.call(null, data, response).then(data => { if (data) this.setTokenWithSavedToken(data); @@ -214,6 +229,7 @@ export default class CoralApi { platform: ZNCA_PLATFORM, version: this.znca_version, useragent: this.useragent ?? getAdditionalUserAgents(), + user: {na_id: this.na_id, coral_user_id: this.coral_user_id}, }); const req: WebServiceTokenParameter = { @@ -250,6 +266,7 @@ export default class CoralApi { platform: ZNCA_PLATFORM, version: this.znca_version, useragent: this.useragent ?? getAdditionalUserAgents(), + user: {na_id: user.id, coral_user_id: this.coral_user_id}, }); const req: AccountTokenParameter = { @@ -280,6 +297,9 @@ export default class CoralApi { /** @private */ setTokenWithSavedToken(data: CoralAuthData | PartialCoralAuthData) { this.token = data.credential.accessToken; + this.coral_user_id = '' + data.nsoAccount.user.id; + if ('user' in data) this.na_id = data.user.id; + this._token_expired = false; } static async createWithSessionToken(token: string, useragent = getAdditionalUserAgents()) { @@ -299,6 +319,8 @@ export default class CoralApi { return new this( data.credential.accessToken, useragent, + '' + data.nsoAccount.user.id, + data.user.id, data.znca_version, data.znca_useragent, ); @@ -320,7 +342,7 @@ export default class CoralApi { static async loginWithNintendoAccountToken( nintendoAccountToken: NintendoAccountToken, user: NintendoAccountUser, - useragent = getAdditionalUserAgents() + useragent = getAdditionalUserAgents(), ) { const { default: { coral: config } } = await import('../common/remote-config.js'); @@ -331,6 +353,7 @@ export default class CoralApi { platform: ZNCA_PLATFORM, version: config.znca_version, useragent, + user: {na_id: user.id}, }); debug('Getting Nintendo Switch Online app token'); diff --git a/src/api/f.ts b/src/api/f.ts index 22c5b0b..8b5df48 100644 --- a/src/api/f.ts +++ b/src/api/f.ts @@ -1,12 +1,11 @@ import process from 'node:process'; import fetch, { Headers } from 'node-fetch'; -import createDebug from 'debug'; import { v4 as uuidgen } from 'uuid'; import { defineResponse, ErrorResponse } from './util.js'; +import createDebug from '../util/debug.js'; import { timeoutSignal } from '../util/misc.js'; import { getUserAgent } from '../util/useragent.js'; -const debugS2s = createDebug('nxapi:api:s2s'); const debugFlapg = createDebug('nxapi:api:flapg'); const debugImink = createDebug('nxapi:api:imink'); const debugZncaApi = createDebug('nxapi:api:znca-api'); @@ -16,7 +15,10 @@ export abstract class ZncaApi { public useragent?: string ) {} - abstract genf(token: string, hash_method: HashMethod): Promise; + abstract genf( + token: string, hash_method: HashMethod, + user?: {na_id: string; coral_user_id?: string;}, + ): Promise; } export enum HashMethod { @@ -28,49 +30,6 @@ export enum HashMethod { // flapg // -/** @deprecated The flapg API no longer requires client authentication */ -export async function getLoginHash(token: string, timestamp: string | number, useragent?: string) { - const { default: { coral_auth: { splatnet2statink: config } } } = await import('../common/remote-config.js'); - if (!config) throw new Error('Remote configuration prevents splatnet2statink API use'); - - debugS2s('Getting login hash'); - - const [signal, cancel] = timeoutSignal(); - const response = await fetch('https://elifessler.com/s2s/api/gen2', { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'User-Agent': getUserAgent(useragent), - }, - body: new URLSearchParams({ - naIdToken: token, - timestamp: '' + timestamp, - }).toString(), - signal, - }).finally(cancel); - - if (response.status !== 200) { - throw new ErrorResponse('[s2s] Non-200 status code', response, await response.text()); - } - - const data = await response.json() as LoginHashApiResponse | LoginHashApiError; - - if ('error' in data) { - throw new ErrorResponse('[s2s] ' + data.error, response, data); - } - - debugS2s('Got login hash "%s"', data.hash, data); - - return data.hash; -} - -export interface LoginHashApiResponse { - hash: string; -} -export interface LoginHashApiError { - error: string; -} - export async function flapg( hash_method: HashMethod, token: string, timestamp?: string | number, request_id?: string, @@ -131,11 +90,6 @@ export type FlapgApiResponse = IminkFResponse; export type FlapgApiError = IminkFError; export class ZncaApiFlapg extends ZncaApi { - /** @deprecated */ - async getLoginHash(id_token: string, timestamp: string) { - return getLoginHash(id_token, timestamp, this.useragent); - } - async genf(token: string, hash_method: HashMethod) { const request_id = uuidgen(); @@ -158,7 +112,8 @@ export class ZncaApiFlapg extends ZncaApi { export async function iminkf( hash_method: HashMethod, token: string, timestamp?: number, request_id?: string, - useragent?: string + user?: {na_id: string; coral_user_id?: string;}, + useragent?: string, ) { const { default: { coral_auth: { imink: config } } } = await import('../common/remote-config.js'); if (!config) throw new Error('Remote configuration prevents imink API use'); @@ -172,6 +127,7 @@ export async function iminkf( token, timestamp: typeof timestamp === 'number' ? '' + timestamp : undefined, request_id, + ...user, }; const [signal, cancel] = timeoutSignal(); @@ -217,16 +173,17 @@ export interface IminkFError { } export class ZncaApiImink extends ZncaApi { - async genf(token: string, hash_method: HashMethod) { + async genf(token: string, hash_method: HashMethod, user?: {na_id: string; coral_user_id?: string;}) { const request_id = uuidgen(); - const result = await iminkf(hash_method, token, undefined, request_id, this.useragent); + const result = await iminkf(hash_method, token, undefined, request_id, user, this.useragent); return { provider: 'imink' as const, hash_method, token, request_id, timestamp: result.timestamp, f: result.f, + user, result, }; } @@ -239,10 +196,12 @@ export class ZncaApiImink extends ZncaApi { export async function genf( url: string, hash_method: HashMethod, token: string, timestamp?: number, request_id?: string, - app?: {platform?: string; version?: string;}, useragent?: string + user?: {na_id: string; coral_user_id?: string;}, + app?: {platform?: string; version?: string;}, + useragent?: string, ) { debugZncaApi('Getting f parameter', { - url, hash_method, token, timestamp, request_id, + url, hash_method, token, timestamp, request_id, user, znca_platform: app?.platform, znca_version: app?.version, }); @@ -251,6 +210,7 @@ export async function genf( token, timestamp, request_id, + ...user, }; const headers = new Headers({ @@ -294,10 +254,15 @@ export interface AndroidZncaFResponse { f: string; timestamp?: number; request_id?: string; + + warnings?: {error: string; error_message: string}[]; } export interface AndroidZncaFError { error: string; error_message?: string; + + errors?: {error: string; error_message: string}[]; + warnings?: {error: string; error_message: string}[]; } export class ZncaApiNxapi extends ZncaApi { @@ -305,10 +270,11 @@ export class ZncaApiNxapi extends ZncaApi { super(useragent); } - async genf(token: string, hash_method: HashMethod) { + async genf(token: string, hash_method: HashMethod, user?: {na_id: string; coral_user_id?: string}) { const request_id = uuidgen(); - const result = await genf(this.url + '/f', hash_method, token, undefined, request_id, this.app, this.useragent); + const result = await genf(this.url + '/f', hash_method, token, undefined, request_id, + user, this.app, this.useragent); return { provider: 'nxapi' as const, @@ -316,6 +282,7 @@ export class ZncaApiNxapi extends ZncaApi { hash_method, token, request_id, timestamp: result.timestamp!, // will be included as not sent in request f: result.f, + user, result, }; } @@ -329,7 +296,7 @@ export async function f(token: string, hash_method: HashMethod | `${HashMethod}` const provider = getPreferredZncaApiFromEnvironment(options) ?? await getDefaultZncaApi(options); - return provider.genf(token, hash_method); + return provider.genf(token, hash_method, options?.user); } export type FResult = { @@ -339,6 +306,7 @@ export type FResult = { timestamp: number; request_id: string; f: string; + user?: {na_id: string; coral_user_id?: string;}; result: unknown; } & ({ provider: 'flapg'; @@ -356,6 +324,7 @@ interface ZncaApiOptions { useragent?: string; platform?: string; version?: string; + user?: {na_id: string; coral_user_id?: string;}; } export function getPreferredZncaApiFromEnvironment(options?: ZncaApiOptions): ZncaApi | null; diff --git a/src/api/moon.ts b/src/api/moon.ts index d5d0c0d..82dbb19 100644 --- a/src/api/moon.ts +++ b/src/api/moon.ts @@ -1,8 +1,8 @@ import fetch, { Response } from 'node-fetch'; -import createDebug from 'debug'; import { getNintendoAccountToken, getNintendoAccountUser, NintendoAccountToken, NintendoAccountUser } from './na.js'; import { defineResponse, ErrorResponse, HasResponse } from './util.js'; import { DailySummaries, Devices, MonthlySummaries, MonthlySummary, MoonError, ParentalControlSettingState, SmartDevices, User } from './moon-types.js'; +import createDebug from '../util/debug.js'; import { timeoutSignal } from '../util/misc.js'; const debug = createDebug('nxapi:api:moon'); @@ -16,9 +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) | null = null; + onTokenExpired: ((data?: MoonError, res?: Response) => Promise) | null = null; /** @internal */ _renewToken: Promise | null = null; + protected _token_expired = false; protected constructor( public token: string, @@ -31,8 +32,18 @@ export default class MoonApi { async fetch( url: string, method = 'GET', body?: string, headers?: object, /** @internal */ _autoRenewToken = true, - /** @internal */ _attempt = 0 + /** @internal */ _attempt = 0, ): Promise> { + if (this._token_expired && _autoRenewToken && !this._renewToken) { + if (!this.onTokenExpired || _attempt) throw new Error('Token expired'); + + this._renewToken = this.onTokenExpired.call(null).then(data => { + if (data) this.setTokenWithSavedToken(data); + }).finally(() => { + this._renewToken = null; + }); + } + if (this._renewToken && _autoRenewToken) { await this._renewToken; } @@ -62,6 +73,7 @@ export default class MoonApi { debug('fetch %s %s, response %s', method, url, response.status); if (response.status === 401 && _autoRenewToken && !_attempt && this.onTokenExpired) { + this._token_expired = true; const data = await response.json() as MoonError; // _renewToken will be awaited when calling fetch @@ -123,6 +135,7 @@ export default class MoonApi { private setTokenWithSavedToken(data: MoonAuthData | PartialMoonAuthData) { this.token = data.nintendoAccountToken.access_token!; if ('user' in data) this.naId = data.user.id; + this._token_expired = false; } static async createWithSessionToken(token: string) { diff --git a/src/api/na.ts b/src/api/na.ts index 269bd69..9ae4fb9 100644 --- a/src/api/na.ts +++ b/src/api/na.ts @@ -1,6 +1,6 @@ import fetch from 'node-fetch'; -import createDebug from 'debug'; import { defineResponse, ErrorResponse } from './util.js'; +import createDebug from '../util/debug.js'; import { JwtPayload } from '../util/jwt.js'; import { timeoutSignal } from '../util/misc.js'; diff --git a/src/api/nooklink.ts b/src/api/nooklink.ts index 9854f37..e5a6625 100644 --- a/src/api/nooklink.ts +++ b/src/api/nooklink.ts @@ -1,10 +1,10 @@ import fetch, { Response } from 'node-fetch'; -import createDebug from 'debug'; import { WebServiceToken } from './coral-types.js'; import { NintendoAccountUser } from './na.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 createDebug from '../util/debug.js'; import { timeoutSignal } from '../util/misc.js'; const debug = createDebug('nxapi:api:nooklink'); @@ -17,9 +17,10 @@ const NOOKLINK_URL = NOOKLINK_WEBSERVICE_URL + '/api'; const BLANCO_VERSION = '2.1.1'; export default class NooklinkApi { - onTokenExpired: ((data: WebServiceError, res: Response) => Promise) | null = null; + onTokenExpired: ((data?: WebServiceError, res?: Response) => Promise) | null = null; /** @internal */ _renewToken: Promise | null = null; + protected _token_expired = false; protected constructor( public gtoken: string, @@ -30,8 +31,18 @@ export default class NooklinkApi { async fetch( url: string, method = 'GET', body?: string | FormData, headers?: object, /** @internal */ _autoRenewToken = true, - /** @internal */ _attempt = 0 + /** @internal */ _attempt = 0, ): Promise> { + if (this._token_expired && _autoRenewToken && !this._renewToken) { + if (!this.onTokenExpired || _attempt) throw new Error('Token expired'); + + this._renewToken = this.onTokenExpired.call(null).then(data => { + if (data) this.setTokenWithSavedToken(data); + }).finally(() => { + this._renewToken = null; + }); + } + if (this._renewToken && _autoRenewToken) { await this._renewToken; } @@ -57,6 +68,7 @@ export default class NooklinkApi { debug('fetch %s %s, response %s', method, url, response.status); if (response.status === 401 && _autoRenewToken && !_attempt && this.onTokenExpired) { + this._token_expired = true; const data = await response.json() as WebServiceError; // _renewToken will be awaited when calling fetch @@ -109,6 +121,7 @@ export default class NooklinkApi { private setTokenWithSavedToken(data: NooklinkAuthData) { this.gtoken = data.gtoken; + this._token_expired = false; } static async createWithCoral(nso: CoralApi, user: NintendoAccountUser) { @@ -195,9 +208,10 @@ export default class NooklinkApi { } export class NooklinkUserApi { - onTokenExpired: ((data: WebServiceError, res: Response) => Promise) | null = null; + onTokenExpired: ((data?: WebServiceError, res?: Response) => Promise) | null = null; /** @internal */ _renewToken: Promise | null = null; + protected _token_expired = false; protected constructor( public user_id: string, @@ -211,8 +225,18 @@ export class NooklinkUserApi { async fetch( url: string, method = 'GET', body?: string | FormData, headers?: object, /** @internal */ _autoRenewToken = true, - /** @internal */ _attempt = 0 + /** @internal */ _attempt = 0, ): Promise> { + if (this._token_expired && _autoRenewToken && !this._renewToken) { + if (!this.onTokenExpired || _attempt) throw new Error('Token expired'); + + this._renewToken = this.onTokenExpired.call(null).then(data => { + if (data) this.setTokenWithSavedToken(data); + }).finally(() => { + this._renewToken = null; + }); + } + if (this._renewToken && _autoRenewToken) { await this._renewToken; } @@ -239,6 +263,7 @@ export class NooklinkUserApi { debug('fetch %s %s, response %s', method, url, response.status); if (response.status === 401 && _autoRenewToken && !_attempt && this.onTokenExpired) { + this._token_expired = true; const data = await response.json() as WebServiceError; // _renewToken will be awaited when calling fetch @@ -327,6 +352,7 @@ export class NooklinkUserApi { this.user_id = data.user_id; this.auth_token = data.token.token; this.gtoken = data.gtoken; + this._token_expired = false; } /** @internal */ diff --git a/src/api/splatnet2.ts b/src/api/splatnet2.ts index ae7d7b9..0816106 100644 --- a/src/api/splatnet2.ts +++ b/src/api/splatnet2.ts @@ -1,11 +1,11 @@ import fetch from 'node-fetch'; -import createDebug from 'debug'; import { v4 as uuidgen } from 'uuid'; import { WebServiceToken } from './coral-types.js'; import { NintendoAccountUser } from './na.js'; import { defineResponse, ErrorResponse } from './util.js'; import CoralApi from './coral.js'; import { ActiveFestivals, CoopResult, CoopResults, CoopSchedules, HeroRecords, LeagueMatchRankings, NicknameAndIcons, PastFestivals, Records, Result, Results, Schedules, ShareResponse, ShopMerchandises, Stages, Timeline, WebServiceError, XPowerRankingRecords, XPowerRankingSummary } from './splatnet2-types.js'; +import createDebug from '../util/debug.js'; import { timeoutSignal } from '../util/misc.js'; import { toSeasonId, Rule as XPowerRankingRule, Season } from './splatnet2-xrank.js'; @@ -25,6 +25,8 @@ export const updateIksmSessionLastUsed: { } = {}; export default class SplatNet2Api { + protected _session_expired = false; + protected constructor( public iksm_session: string, public unique_id: string, @@ -32,6 +34,10 @@ export default class SplatNet2Api { ) {} async fetch(url: string, method = 'GET', body?: string | FormData, headers?: object) { + if (this._session_expired) { + throw new Error('Session expired'); + } + const [signal, cancel] = timeoutSignal(); const response = await fetch(SPLATNET2_URL + url, { method, @@ -52,6 +58,10 @@ export default class SplatNet2Api { debug('fetch %s %s, response %s', method, url, response.status); + if (response.status === 401) { + this._session_expired = true; + } + if (response.status !== 200) { throw new ErrorResponse('[splatnet2] Non-200 status code', response, await response.text()); } diff --git a/src/api/splatnet3.ts b/src/api/splatnet3.ts index 6756b6e..577c019 100644 --- a/src/api/splatnet3.ts +++ b/src/api/splatnet3.ts @@ -1,12 +1,12 @@ -import createDebug from 'debug'; 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 { timeoutSignal } from '../util/misc.js'; import { WebServiceToken } from './coral-types.js'; import CoralApi from './coral.js'; import { NintendoAccountUser } from './na.js'; import { BulletToken } from './splatnet3-types.js'; import { defineResponse, ErrorResponse, HasResponse, ResponseSymbol } from './util.js'; +import createDebug from '../util/debug.js'; +import { timeoutSignal } from '../util/misc.js'; const debug = createDebug('nxapi:api:splatnet3'); const debugGraphQl = createDebug('nxapi:api:splatnet3:graphql'); @@ -74,9 +74,10 @@ enum MapQueriesMode { export default class SplatNet3Api { onTokenShouldRenew: ((remaining: number, res: Response) => Promise) | null = null; - onTokenExpired: ((res: Response) => Promise) | null = null; + onTokenExpired: ((res?: Response) => Promise) | null = null; /** @internal */ _renewToken: Promise | null = null; + protected _token_expired = false; graphql_strict = process.env.NXAPI_SPLATNET3_STRICT !== '0'; @@ -93,8 +94,18 @@ export default class SplatNet3Api { async fetch( url: string, method = 'GET', body?: string | FormData, headers?: object, /** @internal */ _log?: string, - /** @internal */ _attempt = 0 + /** @internal */ _attempt = 0, ): Promise> { + if (this._token_expired && !this._renewToken) { + if (!this.onTokenExpired || _attempt) throw new Error('Token expired'); + + this._renewToken = this.onTokenExpired.call(null).then(data => { + if (data) this.setTokenWithSavedToken(data); + }).finally(() => { + this._renewToken = null; + }); + } + if (this._renewToken) { await this._renewToken; } @@ -121,6 +132,7 @@ export default class SplatNet3Api { response.status, version); if (response.status === 401 && !_attempt && this.onTokenExpired) { + this._token_expired = true; // _renewToken will be awaited when calling fetch this._renewToken = this._renewToken ?? this.onTokenExpired.call(null, response).then(data => { if (data) this.setTokenWithSavedToken(data); @@ -861,7 +873,7 @@ export default class SplatNet3Api { PagerUpdateBattleHistoriesByVsModeVariables >(RequestId.PagerUpdateBattleHistoriesByVsModeQuery, { isBankara: false, - isLeague: false, + isEvent: false, isPrivate: false, isRegular: false, isXBattle: false, @@ -933,6 +945,7 @@ export default class SplatNet3Api { this.version = data.version; this.language = data.bullet_token.lang; this.useragent = data.useragent; + this._token_expired = false; } static async createWithCoral(nso: CoralApi, user: NintendoAccountUser) { diff --git a/src/api/znc-proxy.ts b/src/api/znc-proxy.ts index 1eb1fdb..3a6fb04 100644 --- a/src/api/znc-proxy.ts +++ b/src/api/znc-proxy.ts @@ -1,10 +1,10 @@ import fetch, { Response } from 'node-fetch'; -import createDebug from 'debug'; import { ActiveEvent, Announcements, CurrentUser, Event, Friend, Presence, PresencePermissions, User, WebService, WebServiceToken, CoralErrorResponse, CoralStatus, CoralSuccessResponse, FriendCodeUser, FriendCodeUrl } from './coral-types.js'; import { defineResponse, ErrorResponse, ResponseSymbol } from './util.js'; import CoralApi, { CoralAuthData, CorrelationIdSymbol, PartialCoralAuthData, ResponseDataSymbol, Result } from './coral.js'; import { NintendoAccountUser } from './na.js'; import { SavedToken } from '../common/auth/coral.js'; +import createDebug from '../util/debug.js'; import { timeoutSignal } from '../util/misc.js'; import { getAdditionalUserAgents, getUserAgent } from '../util/useragent.js'; @@ -12,10 +12,16 @@ const debug = createDebug('nxapi:api:znc-proxy'); export default class ZncProxyApi implements CoralApi { // Not used by ZncProxyApi - onTokenExpired: ((data: CoralErrorResponse, res: Response) => Promise) | null = null; + onTokenExpired: ((data?: CoralErrorResponse, res?: Response) => Promise) | null = null; /** @internal */ _renewToken: Promise | null = null; + /** @internal */ + _token_expired = false; + /** @internal */ + na_id = ''; + /** @internal */ + coral_user_id = ''; readonly znca_version = ''; readonly znca_useragent = ''; diff --git a/src/app/app-entry.cts b/src/app/app-entry.cts index fd460e8..cfcf62a 100644 --- a/src/app/app-entry.cts +++ b/src/app/app-entry.cts @@ -2,7 +2,11 @@ const electron = require('electron'); // Do anything that must be run before the app is ready... -electron.app.whenReady() +Promise.all([ + // @ts-expect-error + typeof __NXAPI_BUNDLE_APP_INIT__ !== 'undefined' ? import(__NXAPI_BUNDLE_APP_INIT__) : import('./app-init.js'), + electron.app.whenReady(), +]) // @ts-expect-error .then(() => typeof __NXAPI_BUNDLE_APP_MAIN__ !== 'undefined' ? import(__NXAPI_BUNDLE_APP_MAIN__) : import('./main/index.js')) .then(m => m.init.call(null)) diff --git a/src/app/app-init.ts b/src/app/app-init.ts new file mode 100644 index 0000000..2eb3e3a --- /dev/null +++ b/src/app/app-init.ts @@ -0,0 +1,5 @@ +import { join } from 'node:path'; +import { init as initDebug } from '../util/debug.js'; +import { paths } from '../util/product.js'; + +await initDebug(join(paths.log, 'app')); diff --git a/src/app/main/index.ts b/src/app/main/index.ts index 612a482..14596ea 100644 --- a/src/app/main/index.ts +++ b/src/app/main/index.ts @@ -2,25 +2,25 @@ import { app, BrowserWindow, dialog, ipcMain, LoginItemSettingsOptions, Menu } f import process from 'node:process'; import * as path from 'node:path'; import { EventEmitter } from 'node:events'; -import createDebug from 'debug'; import * as persist from 'node-persist'; import { i18n } from 'i18next'; -import { init as initGlobals } from '../../common/globals.js'; import MenuApp from './menu.js'; import { handleOpenWebServiceUri } from './webservices.js'; import { EmbeddedPresenceMonitor, PresenceMonitorManager } from './monitor.js'; -import { createWindow } from './windows.js'; -import { DiscordPresenceConfiguration, LoginItem, LoginItemOptions, WindowType } from '../common/types.js'; -import { initStorage, paths } from '../../util/storage.js'; -import { checkUpdates, UpdateCacheData } from '../../common/update.js'; -import Users, { CoralUser } from '../../common/users.js'; +import { createModalWindow, createWindow } from './windows.js'; import { sendToAllWindows, setupIpc } from './ipc.js'; -import { dev, dir, git, release, version } from '../../util/product.js'; -import { addUserAgent } from '../../util/useragent.js'; import { askUserForUri } from './util.js'; import { setAppInstance, updateMenuLanguage } from './app-menu.js'; import { handleAuthUri } from './na-auth.js'; +import { DiscordPresenceConfiguration, LoginItem, LoginItemOptions, WindowType } from '../common/types.js'; +import { init as initGlobals } from '../../common/globals.js'; import { CREDITS_NOTICE, GITLAB_URL, LICENCE_NOTICE } from '../../common/constants.js'; +import { checkUpdates, UpdateCacheData } from '../../common/update.js'; +import Users, { CoralUser } from '../../common/users.js'; +import createDebug from '../../util/debug.js'; +import { dev, dir, git, release, version } from '../../util/product.js'; +import { addUserAgent } from '../../util/useragent.js'; +import { initStorage, paths } from '../../util/storage.js'; import createI18n, { languages } from '../i18n/index.js'; const debug = createDebug('app:main'); @@ -101,17 +101,7 @@ export class App { return this.preferences_window; } - const window = createWindow(WindowType.PREFERENCES, {}, { - show: false, - maximizable: false, - minimizable: false, - width: 580, - height: 400, - minWidth: 580, - maxWidth: 580, - minHeight: 400, - maxHeight: 400, - }); + const window = createModalWindow(WindowType.PREFERENCES, {}); window.on('closed', () => this.preferences_window = null); @@ -290,19 +280,9 @@ export async function handleOpenFriendCodeUri(app: App, uri: string) { const selected_user = await askUserForUri(app, uri, app.i18n.t('handle_uri:friend_code_select')); if (!selected_user) return; - createWindow(WindowType.ADD_FRIEND, { + createModalWindow(WindowType.ADD_FRIEND, { user: selected_user[1].user.id, friendcode, - }, { - // show: false, - maximizable: false, - minimizable: false, - width: 560, - height: 300, - minWidth: 450, - maxWidth: 700, - minHeight: 300, - maxHeight: 300, }); } diff --git a/src/app/main/ipc.ts b/src/app/main/ipc.ts index 3ed0f3d..139a1ef 100644 --- a/src/app/main/ipc.ts +++ b/src/app/main/ipc.ts @@ -1,27 +1,24 @@ -import { BrowserWindow, clipboard, dialog, IpcMain, KeyboardEvent, Menu, MenuItem, Settings, ShareMenu, SharingItem, shell, systemPreferences } from './electron.js'; +import { BrowserWindow, clipboard, dialog, IpcMain, KeyboardEvent, Menu, MenuItem, ShareMenu, SharingItem, shell, systemPreferences } from './electron.js'; import * as util from 'node:util'; -import createDebug from 'debug'; import { User } from 'discord-rpc'; import openWebService, { QrCodeReaderOptions, WebServiceIpc, WebServiceValidationError } from './webservices.js'; -import { createWindow, getWindowConfiguration } from './windows.js'; -import { DiscordPresenceConfiguration, DiscordPresenceSource, LoginItemOptions, WindowType } from '../common/types.js'; -import { CurrentUser, Friend, Game, PresenceState, WebService } from '../../api/coral-types.js'; +import { createModalWindow, createWindow, getWindowConfiguration, setWindowHeight } from './windows.js'; import { askAddNsoAccount, askAddPctlAccount } from './na-auth.js'; import { App } from './index.js'; +import { EmbeddedPresenceMonitor } from './monitor.js'; +import { DiscordPresenceConfiguration, DiscordPresenceSource, LoginItemOptions, WindowType } from '../common/types.js'; +import { CurrentUser, Friend, Game, PresenceState, WebService } from '../../api/coral-types.js'; import { NintendoAccountUser } from '../../api/na.js'; -import { hrduration } from '../../util/misc.js'; +import createDebug from '../../util/debug.js'; import { DiscordPresence } from '../../discord/types.js'; import { getDiscordRpcClients } from '../../discord/rpc.js'; import { defaultTitle } from '../../discord/titles.js'; import type { FriendProps } from '../browser/friend/index.js'; import type { DiscordSetupProps } from '../browser/discord/index.js'; import type { AddFriendProps } from '../browser/add-friend/index.js'; -import { EmbeddedPresenceMonitor } from './monitor.js'; const debug = createDebug('app:main:ipc'); -const shown_modal_windows = new WeakSet(); - export function setupIpc(appinstance: App, ipcMain: IpcMain) { const store = appinstance.store; const storage = appinstance.store.storage; @@ -85,61 +82,15 @@ export function setupIpc(appinstance: App, ipcMain: IpcMain) { ipcMain.handle('nxapi:coral:addfriend', (e, token: string, nsaid: string) => store.users.get(token).then(u => u.addFriend(nsaid))); ipcMain.handle('nxapi:window:showpreferences', () => appinstance.showPreferencesWindow().id); - ipcMain.handle('nxapi:window:showfriend', (e, props: FriendProps) => createWindow(WindowType.FRIEND, props, { - parent: BrowserWindow.fromWebContents(e.sender) ?? undefined, - modal: true, - show: false, - maximizable: false, - minimizable: false, - width: 560, - height: 300, - minWidth: 450, - maxWidth: 700, - minHeight: 300, - maxHeight: 300, - }).id); - ipcMain.handle('nxapi:window:discord', (e, props: DiscordSetupProps) => createWindow(WindowType.DISCORD_PRESENCE, props, { - parent: BrowserWindow.fromWebContents(e.sender) ?? undefined, - modal: true, - show: false, - maximizable: false, - minimizable: false, - width: 560, - height: 300, - minWidth: 450, - maxWidth: 700, - minHeight: 300, - maxHeight: 300, - }).id); - ipcMain.handle('nxapi:window:addfriend', (e, props: AddFriendProps) => createWindow(WindowType.ADD_FRIEND, props, { - parent: BrowserWindow.fromWebContents(e.sender) ?? undefined, - modal: true, - show: false, - maximizable: false, - minimizable: false, - width: 560, - height: 300, - minWidth: 450, - maxWidth: 700, - minHeight: 300, - maxHeight: 300, - }).id); + ipcMain.handle('nxapi:window:showfriend', (e, props: FriendProps) => + createModalWindow(WindowType.FRIEND, props, e.sender).id); + ipcMain.handle('nxapi:window:discord', (e, props: DiscordSetupProps) => + createModalWindow(WindowType.DISCORD_PRESENCE, props).id); + ipcMain.handle('nxapi:window:addfriend', (e, props: AddFriendProps) => + createModalWindow(WindowType.ADD_FRIEND, props, e.sender).id); ipcMain.handle('nxapi:window:setheight', (e, height: number) => { const window = BrowserWindow.fromWebContents(e.sender)!; - const [curWidth, curHeight] = window.getSize(); - const [curContentWidth, curContentHeight] = window.getContentSize(); - const [minWidth, minHeight] = window.getMinimumSize(); - const [maxWidth, maxHeight] = window.getMaximumSize(); - if (height !== curContentHeight && curHeight === minHeight && curHeight === maxHeight) { - window.setMinimumSize(minWidth, height + (curHeight - curContentHeight)); - window.setMaximumSize(maxWidth, height + (curHeight - curContentHeight)); - } - window.setContentSize(curContentWidth, height); - - if (!shown_modal_windows.has(window)) { - window.show(); - shown_modal_windows.add(window); - } + setWindowHeight(window, height); }); ipcMain.handle('nxapi:discord:config', () => appinstance.monitors.getDiscordPresenceConfiguration()); @@ -256,21 +207,9 @@ function buildUserMenu(app: App, user: NintendoAccountUser, nso?: CurrentUser, m click: () => app.menu?.setActiveDiscordPresenceUser(null)}), ] : [ new MenuItem({label: t('discord_enable')!, - click: () => createWindow(WindowType.DISCORD_PRESENCE, { + click: () => createModalWindow(WindowType.DISCORD_PRESENCE, { friend_nsa_id: nso.nsaId, - }, { - parent: window, - modal: true, - show: false, - maximizable: false, - minimizable: false, - width: 560, - height: 300, - minWidth: 450, - maxWidth: 700, - minHeight: 300, - maxHeight: 300, - })}), + }, window)}), ]), new MenuItem({label: t('friend_notifications_enable')!, type: 'checkbox', checked: monitor?.friend_notifications, @@ -279,21 +218,9 @@ function buildUserMenu(app: App, user: NintendoAccountUser, nso?: CurrentUser, m click: () => monitor?.skipIntervalInCurrentLoop(true)}), new MenuItem({type: 'separator'}), new MenuItem({label: t('add_friend')!, - click: () => createWindow(WindowType.ADD_FRIEND, { + click: () => createModalWindow(WindowType.ADD_FRIEND, { user: user.id, - }, { - parent: window, - modal: true, - show: false, - maximizable: false, - minimizable: false, - width: 560, - height: 300, - minWidth: 450, - maxWidth: 700, - minHeight: 300, - maxHeight: 300, - })}), + }, window)}), ] : []), new MenuItem({type: 'separator'}), new MenuItem({label: t('remove_help')!, enabled: false}), diff --git a/src/app/main/menu.ts b/src/app/main/menu.ts index 4d5a8c0..2e4fbd7 100644 --- a/src/app/main/menu.ts +++ b/src/app/main/menu.ts @@ -1,19 +1,19 @@ import { app, dialog, Menu, Tray, nativeImage, MenuItem, BrowserWindow, KeyboardEvent } from './electron.js'; import path from 'node:path'; import * as util from 'node:util'; -import createDebug from 'debug'; import { askAddNsoAccount, askAddPctlAccount } from './na-auth.js'; import { App } from './index.js'; -import { WebService } from '../../api/coral-types.js'; import openWebService, { WebServiceValidationError } from './webservices.js'; -import { SavedToken } from '../../common/auth/coral.js'; -import { SavedMoonToken } from '../../common/auth/moon.js'; -import { dev, dir } from '../../util/product.js'; import { EmbeddedPresenceMonitor, EmbeddedProxyPresenceMonitor } from './monitor.js'; -import { createWindow } from './windows.js'; +import { createModalWindow, createWindow } from './windows.js'; import { WindowType } from '../common/types.js'; import CoralApi from '../../api/coral.js'; +import { WebService } from '../../api/coral-types.js'; +import { SavedToken } from '../../common/auth/coral.js'; +import { SavedMoonToken } from '../../common/auth/moon.js'; import { CachedWebServicesList } from '../../common/users.js'; +import createDebug from '../../util/debug.js'; +import { dev, dir } from '../../util/product.js'; import { languages } from '../i18n/index.js'; const debug = createDebug('app:main:menu'); @@ -300,18 +300,8 @@ export default class MenuApp { } showAddFriendWindow(user: string) { - createWindow(WindowType.ADD_FRIEND, { + createModalWindow(WindowType.ADD_FRIEND, { user, - }, { - show: false, - maximizable: false, - minimizable: false, - width: 560, - height: 300, - minWidth: 450, - maxWidth: 700, - minHeight: 300, - maxHeight: 300, }); } } diff --git a/src/app/main/monitor.ts b/src/app/main/monitor.ts index 5b53ff4..0fe12be 100644 --- a/src/app/main/monitor.ts +++ b/src/app/main/monitor.ts @@ -1,14 +1,14 @@ import { dialog, Notification } from './electron.js'; -import createDebug from 'debug'; import { i18n } from 'i18next'; +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 { ErrorResponse } from '../../api/util.js'; import { ZncDiscordPresence, ZncProxyDiscordPresence } from '../../common/presence.js'; import { NotificationManager } from '../../common/notify.js'; +import createDebug from '../../util/debug.js'; import { LoopResult } from '../../util/loop.js'; -import { tryGetNativeImageFromUrl } from './util.js'; -import { App } from './index.js'; -import { DiscordPresenceConfiguration, DiscordPresenceExternalMonitorsConfiguration, DiscordPresenceSource } from '../common/types.js'; import { DiscordPresence, DiscordPresencePlayTime, ErrorResult } from '../../discord/types.js'; import { DiscordRpcClient } from '../../discord/rpc.js'; import SplatNet3Monitor, { getConfigFromAppConfig as getSplatNet3MonitorConfigFromAppConfig } from '../../discord/monitor/splatoon3.js'; diff --git a/src/app/main/na-auth.ts b/src/app/main/na-auth.ts index f99ad5c..700d5f3 100644 --- a/src/app/main/na-auth.ts +++ b/src/app/main/na-auth.ts @@ -1,19 +1,20 @@ +import { app, BrowserWindow, dialog, MessageBoxOptions, Notification, session, shell } from './electron.js'; import process from 'node:process'; import * as crypto from 'node:crypto'; -import createDebug from 'debug'; import * as persist from 'node-persist'; -import { app, BrowserWindow, dialog, MessageBoxOptions, Notification, session, shell } from './electron.js'; -import { getNintendoAccountSessionToken, NintendoAccountSessionToken } from '../../api/na.js'; +import { protocol_registration_options } from './index.js'; +import { createModalWindow, createWindow } from './windows.js'; +import { tryGetNativeImageFromUrl } from './util.js'; +import { WindowType } from '../common/types.js'; +import { getNintendoAccountSessionToken, NintendoAccountAuthError, NintendoAccountSessionToken } from '../../api/na.js'; import { ZNCA_CLIENT_ID } from '../../api/coral.js'; import { ZNMA_CLIENT_ID } from '../../api/moon.js'; -import { getToken, SavedToken } from '../../common/auth/coral.js'; -import { getPctlToken, SavedMoonToken } from '../../common/auth/moon.js'; +import { ErrorResponse } from '../../api/util.js'; +import { getToken } from '../../common/auth/coral.js'; +import { getPctlToken } from '../../common/auth/moon.js'; +import createDebug from '../../util/debug.js'; import { Jwt } from '../../util/jwt.js'; -import { tryGetNativeImageFromUrl } from './util.js'; import { ZNCA_API_USE_URL } from '../../common/constants.js'; -import { createWindow } from './windows.js'; -import { WindowType } from '../common/types.js'; -import { protocol_registration_options } from './index.js'; const debug = createDebug('app:main:na-auth'); @@ -312,19 +313,9 @@ function askUserForRedirectUri( authoriseurl: string, client_id: string, handleAuthUrl: (url: URL) => void, rj: (reason: any) => void ) { - const window = createWindow(WindowType.ADD_ACCOUNT_MANUAL_PROMPT, { + const window = createModalWindow(WindowType.ADD_ACCOUNT_MANUAL_PROMPT, { authoriseurl, client_id, - }, { - show: false, - maximizable: false, - minimizable: false, - width: 560, - height: 300, - minWidth: 450, - maxWidth: 700, - minHeight: 300, - maxHeight: 300, }); window.webContents.on('will-navigate', (event, url_string) => { @@ -368,43 +359,69 @@ export async function addNsoAccount(storage: persist.LocalStorage, use_in_app_br const nsotoken = await storage.getItem('NintendoAccountToken.' + jwt.payload.sub) as string | undefined; if (nsotoken) { - const data = await storage.getItem('NsoToken.' + nsotoken) as SavedToken | undefined; + debug('Already authenticated', jwt.payload); - debug('Already authenticated', data); + try { + const {nso, data} = await getToken(storage, nsotoken, process.env.ZNC_PROXY_URL, false); - new Notification({ - title: 'Nintendo Switch Online', - body: 'Already signed in as ' + data?.nsoAccount.user.name + ' (' + data?.user.nickname + ')', - icon: await tryGetNativeImageFromUrl(data!.nsoAccount.user.imageUri), - }).show(); + new Notification({ + title: 'Nintendo Switch Online', + body: 'Already signed in as ' + data.nsoAccount.user.name + ' (Nintendo Account ' + + data.user.nickname + ' / ' + data.user.screenName + ')', + icon: await tryGetNativeImageFromUrl(data.nsoAccount.user.imageUri), + }).show(); - return getToken(storage, nsotoken, process.env.ZNC_PROXY_URL, false); + 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, code, verifier, true); + } + } + + throw err; + } } await checkZncaApiUseAllowed(storage, window); - const token = await getNintendoAccountSessionToken(code, verifier, ZNCA_CLIENT_ID); - - debug('session token', token); - - const {nso, data} = await getToken(storage, token.session_token, process.env.ZNC_PROXY_URL, false); - - const users = new Set(await storage.getItem('NintendoAccountIds') ?? []); - users.add(data.user.id); - await storage.setItem('NintendoAccountIds', [...users]); - - new Notification({ - title: 'Nintendo Switch Online', - body: 'Authenticated as ' + data.nsoAccount.user.name + ' (NSO ' + data.user.nickname + ')', - icon: await tryGetNativeImageFromUrl(data.nsoAccount.user.imageUri), - }).show(); - - return {nso, data}; + return authenticateCoralSessionToken(storage, code, verifier); } finally { window?.close(); } } +async function authenticateCoralSessionToken( + storage: persist.LocalStorage, + code: string, verifier: string, + reauthenticate = false, +) { + const token = await getNintendoAccountSessionToken(code, verifier, ZNCA_CLIENT_ID); + + debug('session token', token); + + const {nso, data} = await getToken(storage, token.session_token, process.env.ZNC_PROXY_URL, false); + + const users = new Set(await storage.getItem('NintendoAccountIds') ?? []); + users.add(data.user.id); + await storage.setItem('NintendoAccountIds', [...users]); + + new Notification({ + title: 'Nintendo Switch Online', + body: reauthenticate ? + 'Reauthenticated to ' + data.nsoAccount.user.name + ' (Nintendo Account ' + data.user.nickname + ' / ' + + data.user.screenName + ')' : + 'Authenticated as ' + data.nsoAccount.user.name + ' (Nintendo Account ' + data.user.nickname + ' / ' + + data.user.screenName + ')', + icon: await tryGetNativeImageFromUrl(data.nsoAccount.user.imageUri), + }).show(); + + return {nso, data}; +} + export async function askAddNsoAccount(storage: persist.LocalStorage, iab = true) { try { return await addNsoAccount(storage, iab); @@ -507,39 +524,63 @@ export async function addPctlAccount(storage: persist.LocalStorage, use_in_app_b const moontoken = await storage.getItem('NintendoAccountToken-pctl.' + jwt.payload.sub) as string | undefined; if (moontoken) { - const data = await storage.getItem('MoonToken.' + moontoken) as SavedMoonToken | undefined; + debug('Already authenticated', jwt.payload); - debug('Already authenticated', data); + try { + const {moon, data} = await getPctlToken(storage, moontoken, false); - new Notification({ - title: 'Nintendo Switch Parental Controls', - body: 'Already signed in as ' + data?.user.nickname, - }).show(); + new Notification({ + title: 'Nintendo Switch Parental Controls', + body: 'Already signed in as ' + data.user.nickname + ' (' + data.user.screenName + ')', + }).show(); - return getPctlToken(storage, moontoken, false); + 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, code, verifier, true); + } + } + + throw err; + } } - const token = await getNintendoAccountSessionToken(code, verifier, ZNMA_CLIENT_ID); - - debug('session token', token); - - const {moon, data} = await getPctlToken(storage, token.session_token, false); - - const users = new Set(await storage.getItem('NintendoAccountIds') ?? []); - users.add(data.user.id); - await storage.setItem('NintendoAccountIds', [...users]); - - new Notification({ - title: 'Nintendo Switch Parental Controls', - body: 'Authenticated as ' + data.user.nickname, - }).show(); - - return {moon, data}; + return authenticateMoonSessionToken(storage, code, verifier); } finally { window?.close(); } } +async function authenticateMoonSessionToken( + storage: persist.LocalStorage, + code: string, verifier: string, + reauthenticate = false, +) { + + const token = await getNintendoAccountSessionToken(code, verifier, ZNMA_CLIENT_ID); + + debug('session token', token); + + const {moon, data} = await getPctlToken(storage, token.session_token, false); + + const users = new Set(await storage.getItem('NintendoAccountIds') ?? []); + users.add(data.user.id); + await storage.setItem('NintendoAccountIds', [...users]); + + new Notification({ + title: 'Nintendo Switch Parental Controls', + body: reauthenticate ? + 'Reauthenticated to ' + data.user.nickname + ' (' + data.user.screenName + ')' : + 'Authenticated as ' + data.user.nickname + ' (' + data.user.screenName + ')', + }).show(); + + return {moon, data}; +} + export async function askAddPctlAccount(storage: persist.LocalStorage, iab = true) { try { return await addPctlAccount(storage, iab); diff --git a/src/app/main/webservices.ts b/src/app/main/webservices.ts index d695e24..acacfb7 100644 --- a/src/app/main/webservices.ts +++ b/src/app/main/webservices.ts @@ -1,19 +1,19 @@ +import { app, BrowserWindow, clipboard, dialog, IpcMainInvokeEvent, nativeImage, nativeTheme, Notification, ShareMenu, shell, WebContents } from './electron.js'; import * as path from 'node:path'; import { constants } from 'node:fs'; import * as fs from 'node:fs/promises'; import { Buffer } from 'node:buffer'; import * as util from 'node:util'; -import createDebug from 'debug'; -import { app, BrowserWindow, clipboard, dialog, IpcMainInvokeEvent, nativeImage, nativeTheme, Notification, ShareMenu, shell, WebContents } from './electron.js'; import fetch from 'node-fetch'; -import CoralApi from '../../api/coral.js'; -import { CurrentUser, WebService, WebServiceToken } from '../../api/coral-types.js'; import { App, Store } from './index.js'; -import type { DownloadImagesRequest, NativeShareRequest, NativeShareUrlRequest, QrCodeReaderCameraOptions, QrCodeReaderCheckinOptions, QrCodeReaderCheckinResult, QrCodeReaderPhotoLibraryOptions, SendMessageOptions } from '../preload-webservice/znca-js-api.js'; -import { SavedToken } from '../../common/auth/coral.js'; import { createWebServiceWindow } from './windows.js'; import { askUserForUri } from './util.js'; +import type { DownloadImagesRequest, NativeShareRequest, NativeShareUrlRequest, QrCodeReaderCameraOptions, QrCodeReaderCheckinOptions, QrCodeReaderCheckinResult, QrCodeReaderPhotoLibraryOptions, SendMessageOptions } from '../preload-webservice/znca-js-api.js'; +import createDebug from '../../util/debug.js'; +import CoralApi from '../../api/coral.js'; +import { CurrentUser, WebService, WebServiceToken } from '../../api/coral-types.js'; import { NintendoAccountUser } from '../../api/na.js'; +import { SavedToken } from '../../common/auth/coral.js'; const debug = createDebug('app:main:webservices'); diff --git a/src/app/main/windows.ts b/src/app/main/windows.ts index 8e7628a..2a3134e 100644 --- a/src/app/main/windows.ts +++ b/src/app/main/windows.ts @@ -10,7 +10,7 @@ const windows = new WeakMap(); export function createWindow( type: T, props: WindowConfiguration['props'], - options?: BrowserWindowConstructorOptions + options?: BrowserWindowConstructorOptions, ) { // Create the browser window const window = new BrowserWindow({ @@ -51,6 +51,62 @@ export function getWindowConfiguration(webcontents: WebContents): WindowConfigur return data; } +const modal_window_width = new WeakMap(); +const modal_window_shown = new WeakSet(); + +export function createModalWindow( + type: T, props: WindowConfiguration['props'], + parent?: BrowserWindow | WebContents, + options?: BrowserWindowConstructorOptions, +) { + if (parent && !(parent instanceof BrowserWindow)) { + parent = BrowserWindow.fromWebContents(parent) ?? undefined; + } + + const window = createWindow(type, props, { + parent, + modal: !!parent, + show: false, + maximizable: false, + minimizable: false, + width: 560, + height: 300, + minWidth: 450, + maxWidth: 700, + minHeight: 300, + maxHeight: 300, + + ...options, + }); + + if (process.platform === 'win32') { + // Use a fixed window width on Windows due to a bug getting/setting window size + window.setResizable(false); + modal_window_width.set(window, options?.width ?? 560); + } + + return window; +} + +export function setWindowHeight(window: BrowserWindow, height: number) { + const [curWidth, curHeight] = window.getSize(); + const [curContentWidth, curContentHeight] = window.getContentSize(); + const [minWidth, minHeight] = window.getMinimumSize(); + const [maxWidth, maxHeight] = window.getMaximumSize(); + + if (height !== curContentHeight && curHeight === minHeight && curHeight === maxHeight) { + window.setMinimumSize(minWidth, height + (curHeight - curContentHeight)); + window.setMaximumSize(maxWidth, height + (curHeight - curContentHeight)); + } + + window.setContentSize(modal_window_width.get(window) ?? curContentWidth, height); + + if (!modal_window_shown.has(window)) { + window.show(); + modal_window_shown.add(window); + } +} + const BACKGROUND_COLOUR_MAIN_LIGHT = process.platform === 'win32' ? '#ffffff' : '#ececec'; const BACKGROUND_COLOUR_MAIN_DARK = process.platform === 'win32' ? '#000000' : '#252424'; diff --git a/src/cli-entry.ts b/src/cli-entry.ts index 6ff9091..7a3ac97 100644 --- a/src/cli-entry.ts +++ b/src/cli-entry.ts @@ -1,9 +1,13 @@ -import createDebug from 'debug'; +import { join } from 'node:path'; +import { init as initDebug } from './util/debug.js'; +import { paths } from './util/product.js'; // -// cli entrypoint for Rollup bundle +// cli entrypoint // -createDebug.log = console.warn.bind(console); +if (process.env.NXAPI_DEBUG_FILE !== '0') { + await initDebug(join(paths.log, 'cli')); +} import('./cli.js').then(cli => cli.main.call(null)); diff --git a/src/cli.ts b/src/cli.ts index 923075e..db8d3c5 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,8 +1,8 @@ import process from 'node:process'; -import createDebug from 'debug'; import Yargs from 'yargs'; import * as commands from './cli/index.js'; import { checkUpdates } from './common/update.js'; +import createDebug from './util/debug.js'; import { dev } from './util/product.js'; import { paths } from './util/storage.js'; import { YargsArguments } from './util/yargs.js'; diff --git a/src/cli/android-znca-api-server-frida.ts b/src/cli/android-znca-api-server-frida.ts index 544d9a9..3b904ac 100644 --- a/src/cli/android-znca-api-server-frida.ts +++ b/src/cli/android-znca-api-server-frida.ts @@ -1,5 +1,5 @@ import process from 'node:process'; -import createDebug from 'debug'; +import createDebug from '../util/debug.js'; import type { Arguments as ParentArguments } from '../cli.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../util/yargs.js'; diff --git a/src/cli/app.ts b/src/cli/app.ts index 8e09ebe..d5f1406 100644 --- a/src/cli/app.ts +++ b/src/cli/app.ts @@ -2,8 +2,8 @@ import process from 'node:process'; import { createRequire } from 'node:module'; import * as path from 'node:path'; import { execFileSync } from 'node:child_process'; -import createDebug from 'debug'; import type { Arguments as ParentArguments } from '../cli.js'; +import createDebug from '../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../util/yargs.js'; import { dir } from '../util/product.js'; diff --git a/src/cli/nooklink.ts b/src/cli/nooklink.ts index 65a5314..bea5e75 100644 --- a/src/cli/nooklink.ts +++ b/src/cli/nooklink.ts @@ -1,6 +1,6 @@ import process from 'node:process'; -import createDebug from 'debug'; import type { Arguments as ParentArguments } from '../cli.js'; +import createDebug from '../util/debug.js'; import { Argv, YargsArguments } from '../util/yargs.js'; import * as commands from './nooklink/index.js'; diff --git a/src/cli/nooklink/dump-newspapers.ts b/src/cli/nooklink/dump-newspapers.ts index d8eac5b..3e73f9b 100644 --- a/src/cli/nooklink/dump-newspapers.ts +++ b/src/cli/nooklink/dump-newspapers.ts @@ -1,8 +1,8 @@ import * as fs from 'node:fs/promises'; import * as path from 'node:path'; -import createDebug from 'debug'; import mkdirp from 'mkdirp'; import type { Arguments as ParentArguments } from '../nooklink.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getUserToken } from '../../common/auth/nooklink.js'; diff --git a/src/cli/nooklink/island.ts b/src/cli/nooklink/island.ts index 13458d1..bab3e39 100644 --- a/src/cli/nooklink/island.ts +++ b/src/cli/nooklink/island.ts @@ -1,6 +1,6 @@ -import createDebug from 'debug'; import Table from '../util/table.js'; import type { Arguments as ParentArguments } from '../nooklink.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getUserToken, getWebServiceToken } from '../../common/auth/nooklink.js'; diff --git a/src/cli/nooklink/keyboard.ts b/src/cli/nooklink/keyboard.ts index fc434ea..610f8b0 100644 --- a/src/cli/nooklink/keyboard.ts +++ b/src/cli/nooklink/keyboard.ts @@ -1,5 +1,5 @@ -import createDebug from 'debug'; import type { Arguments as ParentArguments } from '../nooklink.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getUserToken } from '../../common/auth/nooklink.js'; diff --git a/src/cli/nooklink/newspaper.ts b/src/cli/nooklink/newspaper.ts index bd0b0f6..29aaf62 100644 --- a/src/cli/nooklink/newspaper.ts +++ b/src/cli/nooklink/newspaper.ts @@ -1,6 +1,6 @@ -import createDebug from 'debug'; import Table from '../util/table.js'; import type { Arguments as ParentArguments } from '../nooklink.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getUserToken } from '../../common/auth/nooklink.js'; diff --git a/src/cli/nooklink/newspapers.ts b/src/cli/nooklink/newspapers.ts index 7086945..f8aef86 100644 --- a/src/cli/nooklink/newspapers.ts +++ b/src/cli/nooklink/newspapers.ts @@ -1,6 +1,6 @@ -import createDebug from 'debug'; import Table from '../util/table.js'; import type { Arguments as ParentArguments } from '../nooklink.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getUserToken } from '../../common/auth/nooklink.js'; diff --git a/src/cli/nooklink/post-reaction.ts b/src/cli/nooklink/post-reaction.ts index 25ab983..741063f 100644 --- a/src/cli/nooklink/post-reaction.ts +++ b/src/cli/nooklink/post-reaction.ts @@ -1,5 +1,5 @@ -import createDebug from 'debug'; import type { Arguments as ParentArguments } from '../nooklink.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getUserToken } from '../../common/auth/nooklink.js'; diff --git a/src/cli/nooklink/reactions.ts b/src/cli/nooklink/reactions.ts index bdd8b80..3de844b 100644 --- a/src/cli/nooklink/reactions.ts +++ b/src/cli/nooklink/reactions.ts @@ -1,6 +1,6 @@ -import createDebug from 'debug'; import Table from '../util/table.js'; import type { Arguments as ParentArguments } from '../nooklink.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getUserToken } from '../../common/auth/nooklink.js'; diff --git a/src/cli/nooklink/user-token.ts b/src/cli/nooklink/user-token.ts index 06bddda..375ac73 100644 --- a/src/cli/nooklink/user-token.ts +++ b/src/cli/nooklink/user-token.ts @@ -1,5 +1,5 @@ -import createDebug from 'debug'; import type { Arguments as ParentArguments } from '../nooklink.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getUserToken } from '../../common/auth/nooklink.js'; diff --git a/src/cli/nooklink/user.ts b/src/cli/nooklink/user.ts index 9168fbe..3fc65fe 100644 --- a/src/cli/nooklink/user.ts +++ b/src/cli/nooklink/user.ts @@ -1,5 +1,5 @@ -import createDebug from 'debug'; import type { Arguments as ParentArguments } from '../nooklink.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getUserToken } from '../../common/auth/nooklink.js'; diff --git a/src/cli/nooklink/users.ts b/src/cli/nooklink/users.ts index e5e1fad..f594f5d 100644 --- a/src/cli/nooklink/users.ts +++ b/src/cli/nooklink/users.ts @@ -1,6 +1,6 @@ -import createDebug from 'debug'; import Table from '../util/table.js'; import type { Arguments as ParentArguments } from '../nooklink.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getWebServiceToken } from '../../common/auth/nooklink.js'; diff --git a/src/cli/nso.ts b/src/cli/nso.ts index 9f14484..341e65a 100644 --- a/src/cli/nso.ts +++ b/src/cli/nso.ts @@ -1,6 +1,6 @@ import process from 'node:process'; -import createDebug from 'debug'; import type { Arguments as ParentArguments } from '../cli.js'; +import createDebug from '../util/debug.js'; import { Argv, YargsArguments } from '../util/yargs.js'; import * as commands from './nso/index.js'; diff --git a/src/cli/nso/active-event.ts b/src/cli/nso/active-event.ts index 1ad0722..946e839 100644 --- a/src/cli/nso/active-event.ts +++ b/src/cli/nso/active-event.ts @@ -1,5 +1,5 @@ -import createDebug from 'debug'; import type { Arguments as ParentArguments } from '../nso.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getToken, Login } from '../../common/auth/coral.js'; diff --git a/src/cli/nso/add-friend.ts b/src/cli/nso/add-friend.ts index 190d1aa..ad4a800 100644 --- a/src/cli/nso/add-friend.ts +++ b/src/cli/nso/add-friend.ts @@ -1,5 +1,5 @@ -import createDebug from 'debug'; import type { Arguments as ParentArguments } from '../nso.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getToken } from '../../common/auth/coral.js'; diff --git a/src/cli/nso/announcements.ts b/src/cli/nso/announcements.ts index 0384de4..5ec1083 100644 --- a/src/cli/nso/announcements.ts +++ b/src/cli/nso/announcements.ts @@ -1,6 +1,6 @@ -import createDebug from 'debug'; import Table from '../util/table.js'; import type { Arguments as ParentArguments } from '../nso.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getToken } from '../../common/auth/coral.js'; diff --git a/src/cli/nso/auth.ts b/src/cli/nso/auth.ts index 9d85f41..7288573 100644 --- a/src/cli/nso/auth.ts +++ b/src/cli/nso/auth.ts @@ -1,6 +1,6 @@ import * as crypto from 'node:crypto'; -import createDebug from 'debug'; import type { Arguments as ParentArguments } from '../nso.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getToken } from '../../common/auth/coral.js'; diff --git a/src/cli/nso/friendcode.ts b/src/cli/nso/friendcode.ts index 65734d4..862381f 100644 --- a/src/cli/nso/friendcode.ts +++ b/src/cli/nso/friendcode.ts @@ -1,5 +1,5 @@ -import createDebug from 'debug'; import type { Arguments as ParentArguments } from '../nso.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getToken, Login } from '../../common/auth/coral.js'; diff --git a/src/cli/nso/friends.ts b/src/cli/nso/friends.ts index 5b88496..52535e2 100644 --- a/src/cli/nso/friends.ts +++ b/src/cli/nso/friends.ts @@ -1,7 +1,7 @@ -import createDebug from 'debug'; import Table from '../util/table.js'; import { PresenceState } from '../../api/coral-types.js'; import type { Arguments as ParentArguments } from '../nso.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { hrduration } from '../../util/misc.js'; diff --git a/src/cli/nso/http-server.ts b/src/cli/nso/http-server.ts index d9139f2..740080b 100644 --- a/src/cli/nso/http-server.ts +++ b/src/cli/nso/http-server.ts @@ -1,24 +1,24 @@ import * as net from 'node:net'; import * as os from 'node:os'; -import createDebug from 'debug'; import * as persist from 'node-persist'; import express, { Request, RequestHandler, Response } from 'express'; import bodyParser from 'body-parser'; import { v4 as uuidgen } from 'uuid'; -import { Announcement, CoralStatus, CurrentUser, Friend, FriendCodeUrl, FriendCodeUser, Presence } from '../../api/coral-types.js'; -import CoralApi from '../../api/coral.js'; import type { Arguments as ParentArguments } from '../nso.js'; +import CoralApi 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'; -import { SavedToken } from '../../common/auth/coral.js'; -import { NotificationManager, PresenceEvent, ZncNotifications } from '../../common/notify.js'; import { product } from '../../util/product.js'; import { parseListenAddress } from '../../util/net.js'; -import { AuthPolicy, AuthToken, ZncPresenceEventStreamEvent } from '../../api/znc-proxy.js'; import { addCliFeatureUserAgent } from '../../util/useragent.js'; -import { ErrorResponse } from '../../api/util.js'; -import Users, { CoralUser } from '../../common/users.js'; import { EventStreamResponse, HttpServer, ResponseError } from '../util/http-server.js'; +import { SavedToken } from '../../common/auth/coral.js'; +import { NotificationManager, PresenceEvent, ZncNotifications } from '../../common/notify.js'; +import Users, { CoralUser } from '../../common/users.js'; declare global { namespace Express { diff --git a/src/cli/nso/lookup.ts b/src/cli/nso/lookup.ts index 6d8943a..5c22741 100644 --- a/src/cli/nso/lookup.ts +++ b/src/cli/nso/lookup.ts @@ -1,5 +1,5 @@ -import createDebug from 'debug'; import type { Arguments as ParentArguments } from '../nso.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getToken, Login } from '../../common/auth/coral.js'; diff --git a/src/cli/nso/notify.ts b/src/cli/nso/notify.ts index ae031b1..7c34f97 100644 --- a/src/cli/nso/notify.ts +++ b/src/cli/nso/notify.ts @@ -1,7 +1,7 @@ import * as path from 'node:path'; -import createDebug from 'debug'; import persist from 'node-persist'; import type { Arguments as ParentArguments } from '../nso.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getToken } from '../../common/auth/coral.js'; diff --git a/src/cli/nso/permissions.ts b/src/cli/nso/permissions.ts index 6a131f8..b061436 100644 --- a/src/cli/nso/permissions.ts +++ b/src/cli/nso/permissions.ts @@ -1,6 +1,6 @@ -import createDebug from 'debug'; import { PresencePermissions } from '../../api/coral-types.js'; import type { Arguments as ParentArguments } from '../nso.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getToken, Login } from '../../common/auth/coral.js'; diff --git a/src/cli/nso/presence.ts b/src/cli/nso/presence.ts index 548d272..f8e360d 100644 --- a/src/cli/nso/presence.ts +++ b/src/cli/nso/presence.ts @@ -1,5 +1,5 @@ -import createDebug from 'debug'; import type { Arguments as ParentArguments } from '../nso.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getToken } from '../../common/auth/coral.js'; diff --git a/src/cli/nso/token.ts b/src/cli/nso/token.ts index b299eab..001acac 100644 --- a/src/cli/nso/token.ts +++ b/src/cli/nso/token.ts @@ -1,5 +1,5 @@ -import createDebug from 'debug'; import type { Arguments as ParentArguments } from '../nso.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getToken } from '../../common/auth/coral.js'; diff --git a/src/cli/nso/user.ts b/src/cli/nso/user.ts index dfce6b2..3aa3aed 100644 --- a/src/cli/nso/user.ts +++ b/src/cli/nso/user.ts @@ -1,5 +1,5 @@ -import createDebug from 'debug'; import type { Arguments as ParentArguments } from '../nso.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getToken, Login } from '../../common/auth/coral.js'; diff --git a/src/cli/nso/webservices.ts b/src/cli/nso/webservices.ts index 4417181..ae37ab6 100644 --- a/src/cli/nso/webservices.ts +++ b/src/cli/nso/webservices.ts @@ -1,6 +1,6 @@ -import createDebug from 'debug'; import Table from '../util/table.js'; import type { Arguments as ParentArguments } from '../nso.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getToken, Login } from '../../common/auth/coral.js'; diff --git a/src/cli/nso/webservicetoken.ts b/src/cli/nso/webservicetoken.ts index 51d1b4f..ccc2a66 100644 --- a/src/cli/nso/webservicetoken.ts +++ b/src/cli/nso/webservicetoken.ts @@ -1,5 +1,5 @@ -import createDebug from 'debug'; import type { Arguments as ParentArguments } from '../nso.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getToken, Login } from '../../common/auth/coral.js'; diff --git a/src/cli/nso/znc-proxy-tokens.ts b/src/cli/nso/znc-proxy-tokens.ts index a8c315f..415085a 100644 --- a/src/cli/nso/znc-proxy-tokens.ts +++ b/src/cli/nso/znc-proxy-tokens.ts @@ -1,9 +1,9 @@ -import createDebug from 'debug'; import fetch from 'node-fetch'; import Table from '../util/table.js'; import type { Arguments as ParentArguments } from '../nso.js'; import { getToken } from '../../common/auth/coral.js'; import { AuthPolicy, AuthToken } from '../../api/znc-proxy.js'; +import createDebug from '../../util/debug.js'; import { Argv } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getUserAgent } from '../../util/useragent.js'; diff --git a/src/cli/pctl.ts b/src/cli/pctl.ts index 32cec16..f36856d 100644 --- a/src/cli/pctl.ts +++ b/src/cli/pctl.ts @@ -1,5 +1,5 @@ -import createDebug from 'debug'; import type { Arguments as ParentArguments } from '../cli.js'; +import createDebug from '../util/debug.js'; import { Argv, YargsArguments } from '../util/yargs.js'; import * as commands from './pctl/index.js'; diff --git a/src/cli/pctl/auth.ts b/src/cli/pctl/auth.ts index d4c3db3..717f1cb 100644 --- a/src/cli/pctl/auth.ts +++ b/src/cli/pctl/auth.ts @@ -1,6 +1,6 @@ import * as crypto from 'node:crypto'; -import createDebug from 'debug'; import type { Arguments as ParentArguments } from '../pctl.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getPctlToken } from '../../common/auth/moon.js'; diff --git a/src/cli/pctl/daily-summaries.ts b/src/cli/pctl/daily-summaries.ts index baf8492..ad1c471 100644 --- a/src/cli/pctl/daily-summaries.ts +++ b/src/cli/pctl/daily-summaries.ts @@ -1,6 +1,6 @@ -import createDebug from 'debug'; import Table from '../util/table.js'; import type { Arguments as ParentArguments } from '../pctl.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { hrduration } from '../../util/misc.js'; diff --git a/src/cli/pctl/devices.ts b/src/cli/pctl/devices.ts index 42e48ec..f4b7f00 100644 --- a/src/cli/pctl/devices.ts +++ b/src/cli/pctl/devices.ts @@ -1,6 +1,6 @@ -import createDebug from 'debug'; import Table from '../util/table.js'; import type { Arguments as ParentArguments } from '../pctl.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getPctlToken } from '../../common/auth/moon.js'; diff --git a/src/cli/pctl/dump-summaries.ts b/src/cli/pctl/dump-summaries.ts index 17ef4d6..6191535 100644 --- a/src/cli/pctl/dump-summaries.ts +++ b/src/cli/pctl/dump-summaries.ts @@ -1,8 +1,8 @@ import * as path from 'node:path'; import * as fs from 'node:fs/promises'; -import createDebug from 'debug'; import mkdirp from 'mkdirp'; import type { Arguments as ParentArguments } from '../pctl.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getPctlToken } from '../../common/auth/moon.js'; diff --git a/src/cli/pctl/monthly-summaries.ts b/src/cli/pctl/monthly-summaries.ts index c8f39f1..32e35fe 100644 --- a/src/cli/pctl/monthly-summaries.ts +++ b/src/cli/pctl/monthly-summaries.ts @@ -1,6 +1,6 @@ -import createDebug from 'debug'; import Table from '../util/table.js'; import type { Arguments as ParentArguments } from '../pctl.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getPctlToken } from '../../common/auth/moon.js'; diff --git a/src/cli/pctl/monthly-summary.ts b/src/cli/pctl/monthly-summary.ts index 0b74ba0..385f08c 100644 --- a/src/cli/pctl/monthly-summary.ts +++ b/src/cli/pctl/monthly-summary.ts @@ -1,6 +1,6 @@ -import createDebug from 'debug'; import Table from '../util/table.js'; import type { Arguments as ParentArguments } from '../pctl.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getPctlToken } from '../../common/auth/moon.js'; diff --git a/src/cli/pctl/settings.ts b/src/cli/pctl/settings.ts index 92e9959..095cfba 100644 --- a/src/cli/pctl/settings.ts +++ b/src/cli/pctl/settings.ts @@ -1,5 +1,5 @@ -import createDebug from 'debug'; import type { Arguments as ParentArguments } from '../pctl.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getPctlToken } from '../../common/auth/moon.js'; diff --git a/src/cli/pctl/token.ts b/src/cli/pctl/token.ts index ba709e5..6c67e6d 100644 --- a/src/cli/pctl/token.ts +++ b/src/cli/pctl/token.ts @@ -1,5 +1,5 @@ -import createDebug from 'debug'; import type { Arguments as ParentArguments } from '../pctl.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getPctlToken } from '../../common/auth/moon.js'; diff --git a/src/cli/pctl/user.ts b/src/cli/pctl/user.ts index 3be9b40..d3fe397 100644 --- a/src/cli/pctl/user.ts +++ b/src/cli/pctl/user.ts @@ -1,6 +1,6 @@ -import createDebug from 'debug'; import type { Arguments as ParentArguments } from '../pctl.js'; import { getPctlToken } from '../../common/auth/moon.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; diff --git a/src/cli/presence-server.ts b/src/cli/presence-server.ts index 3089237..c91b873 100644 --- a/src/cli/presence-server.ts +++ b/src/cli/presence-server.ts @@ -2,26 +2,26 @@ import * as net from 'node:net'; import * as os from 'node:os'; import * as fs from 'node:fs/promises'; import * as path from 'node:path'; -import createDebug from 'debug'; import express, { Request, Response } from 'express'; import fetch from 'node-fetch'; import * as persist from 'node-persist'; import mkdirp from 'mkdirp'; import { BankaraMatchMode, BankaraMatchSetting_schedule, CoopRule, CoopSetting_schedule, DetailFestRecordDetailResult, DetailVotingStatusResult, FestMatchSetting_schedule, FestRecordResult, FestState, FestTeam_schedule, FestTeam_votingStatus, FestVoteState, Fest_schedule, FriendListResult, FriendOnlineState, Friend_friendList, GraphQLSuccessResponse, KnownRequestId, LeagueMatchSetting_schedule, RegularMatchSetting_schedule, StageScheduleResult, VsMode, XMatchSetting_schedule } from 'splatnet3-types/splatnet3'; +import StageScheduleQuery_730cd98 from 'splatnet3-types/graphql/730cd98e84f1030d3e9ac86b6f1aae13'; import type { Arguments as ParentArguments } from '../cli.js'; -import { ArgumentsCamelCase, Argv, YargsArguments } from '../util/yargs.js'; -import { initStorage } from '../util/storage.js'; -import { addCliFeatureUserAgent, getUserAgent } from '../util/useragent.js'; -import { parseListenAddress } from '../util/net.js'; import { product, version } from '../util/product.js'; import Users, { CoralUser } from '../common/users.js'; import { Friend } from '../api/coral-types.js'; -import { getBulletToken, SavedBulletToken } from '../common/auth/splatnet3.js'; import SplatNet3Api, { PersistedQueryResult, RequestIdSymbol } from '../api/splatnet3.js'; import { ErrorResponse, ResponseSymbol } from '../api/util.js'; +import { getBulletToken, SavedBulletToken } from '../common/auth/splatnet3.js'; +import createDebug from '../util/debug.js'; +import { initStorage } from '../util/storage.js'; +import { addCliFeatureUserAgent, getUserAgent } from '../util/useragent.js'; +import { parseListenAddress } from '../util/net.js'; import { EventStreamResponse, HttpServer, ResponseError } from './util/http-server.js'; +import { ArgumentsCamelCase, Argv, YargsArguments } from '../util/yargs.js'; import { getTitleIdFromEcUrl } from '../util/misc.js'; -import StageScheduleQuery_730cd98 from 'splatnet3-types/graphql/730cd98e84f1030d3e9ac86b6f1aae13'; const debug = createDebug('cli:presence-server'); const debugSplatnet3Proxy = createDebug('cli:presence-server:splatnet3-proxy'); diff --git a/src/cli/splatnet2.ts b/src/cli/splatnet2.ts index 8bc806f..1ad7d70 100644 --- a/src/cli/splatnet2.ts +++ b/src/cli/splatnet2.ts @@ -1,6 +1,6 @@ import process from 'node:process'; -import createDebug from 'debug'; import type { Arguments as ParentArguments } from '../cli.js'; +import createDebug from '../util/debug.js'; import { Argv, YargsArguments } from '../util/yargs.js'; import * as commands from './splatnet2/index.js'; diff --git a/src/cli/splatnet2/battles.ts b/src/cli/splatnet2/battles.ts index 08fef7e..eb82e93 100644 --- a/src/cli/splatnet2/battles.ts +++ b/src/cli/splatnet2/battles.ts @@ -1,6 +1,6 @@ -import createDebug from 'debug'; import Table from '../util/table.js'; import type { Arguments as ParentArguments } from '../splatnet2.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getIksmToken } from '../../common/auth/splatnet2.js'; diff --git a/src/cli/splatnet2/challenges.ts b/src/cli/splatnet2/challenges.ts index 4372570..7aa7390 100644 --- a/src/cli/splatnet2/challenges.ts +++ b/src/cli/splatnet2/challenges.ts @@ -1,6 +1,6 @@ -import createDebug from 'debug'; import Table from '../util/table.js'; import type { Arguments as ParentArguments } from '../splatnet2.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getIksmToken } from '../../common/auth/splatnet2.js'; diff --git a/src/cli/splatnet2/dump-records.ts b/src/cli/splatnet2/dump-records.ts index 04a1ad5..798acce 100644 --- a/src/cli/splatnet2/dump-records.ts +++ b/src/cli/splatnet2/dump-records.ts @@ -1,8 +1,8 @@ import * as path from 'node:path'; import * as fs from 'node:fs/promises'; -import createDebug from 'debug'; import mkdirp from 'mkdirp'; import type { Arguments as ParentArguments } from '../splatnet2.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getIksmToken } from '../../common/auth/splatnet2.js'; diff --git a/src/cli/splatnet2/dump-results.ts b/src/cli/splatnet2/dump-results.ts index c33e1df..118243e 100644 --- a/src/cli/splatnet2/dump-results.ts +++ b/src/cli/splatnet2/dump-results.ts @@ -1,7 +1,7 @@ import * as path from 'node:path'; -import createDebug from 'debug'; import mkdirp from 'mkdirp'; import type { Arguments as ParentArguments } from '../splatnet2.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getIksmToken } from '../../common/auth/splatnet2.js'; diff --git a/src/cli/splatnet2/hero.ts b/src/cli/splatnet2/hero.ts index 164db7d..970e43e 100644 --- a/src/cli/splatnet2/hero.ts +++ b/src/cli/splatnet2/hero.ts @@ -1,6 +1,6 @@ -import createDebug from 'debug'; import Table from '../util/table.js'; import type { Arguments as ParentArguments } from '../splatnet2.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getIksmToken } from '../../common/auth/splatnet2.js'; diff --git a/src/cli/splatnet2/monitor.ts b/src/cli/splatnet2/monitor.ts index 8374929..d686ad1 100644 --- a/src/cli/splatnet2/monitor.ts +++ b/src/cli/splatnet2/monitor.ts @@ -1,6 +1,6 @@ import * as path from 'node:path'; -import createDebug from 'debug'; import { getIksmToken } from '../../common/auth/splatnet2.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { Arguments as ParentArguments } from '../splatnet2.js'; diff --git a/src/cli/splatnet2/schedule.ts b/src/cli/splatnet2/schedule.ts index 5913dde..85272b5 100644 --- a/src/cli/splatnet2/schedule.ts +++ b/src/cli/splatnet2/schedule.ts @@ -1,6 +1,6 @@ -import createDebug from 'debug'; import Table from '../util/table.js'; import type { Arguments as ParentArguments } from '../splatnet2.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getIksmToken } from '../../common/auth/splatnet2.js'; diff --git a/src/cli/splatnet2/stages.ts b/src/cli/splatnet2/stages.ts index f07bccb..23f215c 100644 --- a/src/cli/splatnet2/stages.ts +++ b/src/cli/splatnet2/stages.ts @@ -1,6 +1,6 @@ -import createDebug from 'debug'; import Table from '../util/table.js'; import type { Arguments as ParentArguments } from '../splatnet2.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getIksmToken } from '../../common/auth/splatnet2.js'; diff --git a/src/cli/splatnet2/token.ts b/src/cli/splatnet2/token.ts index ec81aac..7472d92 100644 --- a/src/cli/splatnet2/token.ts +++ b/src/cli/splatnet2/token.ts @@ -1,5 +1,5 @@ -import createDebug from 'debug'; import type { Arguments as ParentArguments } from '../splatnet2.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getIksmToken } from '../../common/auth/splatnet2.js'; diff --git a/src/cli/splatnet2/user.ts b/src/cli/splatnet2/user.ts index 640f6f9..bf0950e 100644 --- a/src/cli/splatnet2/user.ts +++ b/src/cli/splatnet2/user.ts @@ -1,5 +1,5 @@ -import createDebug from 'debug'; import type { Arguments as ParentArguments } from '../splatnet2.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getIksmToken } from '../../common/auth/splatnet2.js'; diff --git a/src/cli/splatnet2/weapons.ts b/src/cli/splatnet2/weapons.ts index 1e12eda..ed0db96 100644 --- a/src/cli/splatnet2/weapons.ts +++ b/src/cli/splatnet2/weapons.ts @@ -1,6 +1,6 @@ -import createDebug from 'debug'; import Table from '../util/table.js'; import type { Arguments as ParentArguments } from '../splatnet2.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getIksmToken } from '../../common/auth/splatnet2.js'; diff --git a/src/cli/splatnet2/x-rank-seasons.ts b/src/cli/splatnet2/x-rank-seasons.ts index 2410ab7..def49bd 100644 --- a/src/cli/splatnet2/x-rank-seasons.ts +++ b/src/cli/splatnet2/x-rank-seasons.ts @@ -1,6 +1,6 @@ -import createDebug from 'debug'; import Table from '../util/table.js'; import type { Arguments as ParentArguments } from '../splatnet2.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { getAllSeasons } from '../../api/splatnet2-xrank.js'; diff --git a/src/cli/splatnet3.ts b/src/cli/splatnet3.ts index 8e8fb79..9778c39 100644 --- a/src/cli/splatnet3.ts +++ b/src/cli/splatnet3.ts @@ -1,6 +1,6 @@ import process from 'node:process'; -import createDebug from 'debug'; import type { Arguments as ParentArguments } from '../cli.js'; +import createDebug from '../util/debug.js'; import { Argv, YargsArguments } from '../util/yargs.js'; import * as commands from './splatnet3/index.js'; diff --git a/src/cli/splatnet3/battles.ts b/src/cli/splatnet3/battles.ts index faaece5..c9bf886 100644 --- a/src/cli/splatnet3/battles.ts +++ b/src/cli/splatnet3/battles.ts @@ -1,7 +1,7 @@ -import createDebug from 'debug'; import { Judgement } from 'splatnet3-types/splatnet3'; import Table from '../util/table.js'; import type { Arguments as ParentArguments } from '../splatnet3.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getBulletToken } from '../../common/auth/splatnet3.js'; diff --git a/src/cli/splatnet3/dump-album.ts b/src/cli/splatnet3/dump-album.ts index 848e440..52f29ee 100644 --- a/src/cli/splatnet3/dump-album.ts +++ b/src/cli/splatnet3/dump-album.ts @@ -1,10 +1,10 @@ import * as path from 'node:path'; import * as fs from 'node:fs/promises'; -import createDebug from 'debug'; import mkdirp from 'mkdirp'; import fetch from 'node-fetch'; import { PhotoAlbumResult } from 'splatnet3-types/splatnet3'; import type { Arguments as ParentArguments } from '../splatnet3.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getBulletToken } from '../../common/auth/splatnet3.js'; diff --git a/src/cli/splatnet3/dump-fests.ts b/src/cli/splatnet3/dump-fests.ts index e2f10a3..a00bc59 100644 --- a/src/cli/splatnet3/dump-fests.ts +++ b/src/cli/splatnet3/dump-fests.ts @@ -1,9 +1,9 @@ import * as path from 'node:path'; import * as fs from 'node:fs/promises'; -import createDebug from 'debug'; import mkdirp from 'mkdirp'; import { FestState, Fest_detail, RequestId } from 'splatnet3-types/splatnet3'; import type { Arguments as ParentArguments } from '../splatnet3.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getBulletToken } from '../../common/auth/splatnet3.js'; diff --git a/src/cli/splatnet3/dump-records.ts b/src/cli/splatnet3/dump-records.ts index 2b66b7e..f43b770 100644 --- a/src/cli/splatnet3/dump-records.ts +++ b/src/cli/splatnet3/dump-records.ts @@ -1,8 +1,8 @@ import * as path from 'node:path'; import * as fs from 'node:fs/promises'; -import createDebug from 'debug'; import mkdirp from 'mkdirp'; import type { Arguments as ParentArguments } from '../splatnet3.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getBulletToken } from '../../common/auth/splatnet3.js'; diff --git a/src/cli/splatnet3/dump-results.ts b/src/cli/splatnet3/dump-results.ts index 69228e8..70d2160 100644 --- a/src/cli/splatnet3/dump-results.ts +++ b/src/cli/splatnet3/dump-results.ts @@ -1,9 +1,9 @@ import * as path from 'node:path'; import * as fs from 'node:fs/promises'; -import createDebug from 'debug'; import mkdirp from 'mkdirp'; import { BankaraBattleHistoriesRefetchResult, CoopHistoryResult, LatestBattleHistoriesRefetchResult, LatestBattleHistoriesResult, PrivateBattleHistoriesRefetchResult, RefetchableCoopHistory_CoopResultResult, RegularBattleHistoriesRefetchResult, RequestId, XBattleHistoriesRefetchResult } from 'splatnet3-types/splatnet3'; import type { Arguments as ParentArguments } from '../splatnet3.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getBulletToken } from '../../common/auth/splatnet3.js'; diff --git a/src/cli/splatnet3/festival.ts b/src/cli/splatnet3/festival.ts index 5f18145..10b8287 100644 --- a/src/cli/splatnet3/festival.ts +++ b/src/cli/splatnet3/festival.ts @@ -1,7 +1,7 @@ -import createDebug from 'debug'; import { FestState } from 'splatnet3-types/splatnet3'; import Table from '../util/table.js'; import type { Arguments as ParentArguments } from '../splatnet3.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getBulletToken } from '../../common/auth/splatnet3.js'; diff --git a/src/cli/splatnet3/festivals.ts b/src/cli/splatnet3/festivals.ts index 95c750d..3528554 100644 --- a/src/cli/splatnet3/festivals.ts +++ b/src/cli/splatnet3/festivals.ts @@ -1,6 +1,6 @@ -import createDebug from 'debug'; import Table from '../util/table.js'; import type { Arguments as ParentArguments } from '../splatnet3.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getBulletToken } from '../../common/auth/splatnet3.js'; diff --git a/src/cli/splatnet3/friends.ts b/src/cli/splatnet3/friends.ts index 243ddf1..e2939a8 100644 --- a/src/cli/splatnet3/friends.ts +++ b/src/cli/splatnet3/friends.ts @@ -1,7 +1,7 @@ -import createDebug from 'debug'; import { FriendOnlineState, Friend_friendList } from 'splatnet3-types/splatnet3'; import Table from '../util/table.js'; import type { Arguments as ParentArguments } from '../splatnet3.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getBulletToken } from '../../common/auth/splatnet3.js'; diff --git a/src/cli/splatnet3/monitor.ts b/src/cli/splatnet3/monitor.ts index 03b0778..027bc8d 100644 --- a/src/cli/splatnet3/monitor.ts +++ b/src/cli/splatnet3/monitor.ts @@ -1,7 +1,7 @@ import * as path from 'node:path'; -import createDebug from 'debug'; import mkdirp from 'mkdirp'; import type { Arguments as ParentArguments } from '../splatnet3.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getBulletToken } from '../../common/auth/splatnet3.js'; diff --git a/src/cli/splatnet3/schedule.ts b/src/cli/splatnet3/schedule.ts index 99b56f2..d4b7a42 100644 --- a/src/cli/splatnet3/schedule.ts +++ b/src/cli/splatnet3/schedule.ts @@ -1,6 +1,6 @@ -import createDebug from 'debug'; import Table from '../util/table.js'; import type { Arguments as ParentArguments } from '../splatnet3.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getBulletToken } from '../../common/auth/splatnet3.js'; diff --git a/src/cli/splatnet3/token.ts b/src/cli/splatnet3/token.ts index 2b9782b..c8e647e 100644 --- a/src/cli/splatnet3/token.ts +++ b/src/cli/splatnet3/token.ts @@ -1,5 +1,5 @@ -import createDebug from 'debug'; import type { Arguments as ParentArguments } from '../splatnet3.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getBulletToken } from '../../common/auth/splatnet3.js'; diff --git a/src/cli/splatnet3/user.ts b/src/cli/splatnet3/user.ts index 7e261a0..c039e1f 100644 --- a/src/cli/splatnet3/user.ts +++ b/src/cli/splatnet3/user.ts @@ -1,5 +1,5 @@ -import createDebug from 'debug'; import type { Arguments as ParentArguments } from '../splatnet3.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getBulletToken } from '../../common/auth/splatnet3.js'; diff --git a/src/cli/users.ts b/src/cli/users.ts index 956c3a9..4254ec8 100644 --- a/src/cli/users.ts +++ b/src/cli/users.ts @@ -1,7 +1,7 @@ -import createDebug from 'debug'; import * as persist from 'node-persist'; import Table from './util/table.js'; import type { Arguments as ParentArguments } from '../cli.js'; +import createDebug from '../util/debug.js'; import { Argv } from '../util/yargs.js'; import { initStorage, iterateLocalStorage } from '../util/storage.js'; import { SavedToken } from '../common/auth/coral.js'; diff --git a/src/cli/util.ts b/src/cli/util.ts index e4816e5..35febf7 100644 --- a/src/cli/util.ts +++ b/src/cli/util.ts @@ -1,5 +1,5 @@ -import createDebug from 'debug'; import type { Arguments as ParentArguments } from '../cli.js'; +import createDebug from '../util/debug.js'; import { Argv, YargsArguments } from '../util/yargs.js'; import { dev } from '../util/product.js'; import * as commands from './util/index.js'; diff --git a/src/cli/util/captureid.ts b/src/cli/util/captureid.ts index 8082ce2..5acf11f 100644 --- a/src/cli/util/captureid.ts +++ b/src/cli/util/captureid.ts @@ -1,7 +1,7 @@ import * as crypto from 'node:crypto'; import { Buffer } from 'node:buffer'; -import createDebug from 'debug'; import type { Arguments as ParentArguments } from '../util.js'; +import createDebug from '../../util/debug.js'; import { Argv } from '../../util/yargs.js'; const debug = createDebug('cli:util:captureid'); diff --git a/src/cli/util/discord-activity.ts b/src/cli/util/discord-activity.ts index ee71484..f24f55b 100644 --- a/src/cli/util/discord-activity.ts +++ b/src/cli/util/discord-activity.ts @@ -1,11 +1,11 @@ import process from 'node:process'; -import createDebug from 'debug'; import fetch from 'node-fetch'; import { getPresenceFromUrl } from '../../api/znc-proxy.js'; import { ActiveEvent, CurrentUser, Friend, Game, Presence, PresenceState } from '../../api/coral-types.js'; import type { Arguments as ParentArguments } from '../util.js'; import { getDiscordPresence, getInactiveDiscordPresence } from '../../discord/util.js'; import { DiscordPresenceContext, DiscordPresencePlayTime } from '../../discord/types.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { initStorage } from '../../util/storage.js'; import { getToken, Login } from '../../common/auth/coral.js'; diff --git a/src/cli/util/discord-rpc.ts b/src/cli/util/discord-rpc.ts index 4995251..f488345 100644 --- a/src/cli/util/discord-rpc.ts +++ b/src/cli/util/discord-rpc.ts @@ -1,7 +1,7 @@ -import createDebug from 'debug'; import type { Arguments as ParentArguments } from '../util.js'; import { DiscordRpcClient, getAllIpcSockets } from '../../discord/rpc.js'; import { defaultTitle } from '../../discord/titles.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; const debug = createDebug('cli:util:discord-rpc'); diff --git a/src/cli/util/export-discord-titles.ts b/src/cli/util/export-discord-titles.ts index 1520541..a7005fc 100644 --- a/src/cli/util/export-discord-titles.ts +++ b/src/cli/util/export-discord-titles.ts @@ -1,6 +1,6 @@ -import createDebug from 'debug'; import fetch from 'node-fetch'; import type { Arguments as ParentArguments } from '../util.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; import { titles as unsorted_titles } from '../../discord/titles.js'; import { DiscordApplicationRpc, getDiscordApplicationRpc } from './discord-activity.js'; diff --git a/src/cli/util/http-server.ts b/src/cli/util/http-server.ts index f66d00f..00d65be 100644 --- a/src/cli/util/http-server.ts +++ b/src/cli/util/http-server.ts @@ -1,6 +1,6 @@ -import createDebug from 'debug'; import { NextFunction, Request, RequestHandler, Response } from 'express'; import { ErrorResponse } from '../../api/util.js'; +import createDebug from '../../util/debug.js'; import { temporary_http_errors, temporary_system_errors } from '../../util/errors.js'; const debug = createDebug('cli:util:http-server'); diff --git a/src/cli/util/remote-config.ts b/src/cli/util/remote-config.ts index 080beb4..f2e1e38 100644 --- a/src/cli/util/remote-config.ts +++ b/src/cli/util/remote-config.ts @@ -1,5 +1,5 @@ -import createDebug from 'debug'; import type { Arguments as ParentArguments } from '../util.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase, Argv, YargsArguments } from '../../util/yargs.js'; const debug = createDebug('cli:util:remote-config'); diff --git a/src/cli/util/storage.ts b/src/cli/util/storage.ts index f6bdfe1..b468e92 100644 --- a/src/cli/util/storage.ts +++ b/src/cli/util/storage.ts @@ -1,6 +1,6 @@ import * as util from 'node:util'; -import createDebug from 'debug'; import type { Arguments as ParentArguments } from '../util.js'; +import createDebug from '../../util/debug.js'; import { Argv } from '../../util/yargs.js'; import { initStorage, iterateLocalStorage } from '../../util/storage.js'; import Table from './table.js'; diff --git a/src/cli/util/validate-discord-titles.ts b/src/cli/util/validate-discord-titles.ts index 8b683b9..9cc2f14 100644 --- a/src/cli/util/validate-discord-titles.ts +++ b/src/cli/util/validate-discord-titles.ts @@ -1,6 +1,6 @@ import process from 'node:process'; -import createDebug from 'debug'; import type { Arguments as ParentArguments } from '../util.js'; +import createDebug from '../../util/debug.js'; import { ArgumentsCamelCase } from '../../util/yargs.js'; import * as publishers from '../../discord/titles/index.js'; diff --git a/src/client/coral.ts b/src/client/coral.ts index 88521d7..6a87ed3 100644 --- a/src/client/coral.ts +++ b/src/client/coral.ts @@ -1,10 +1,10 @@ -import createDebug from 'debug'; import { Response } from 'node-fetch'; import CoralApi, { CoralAuthData, Result, ZNCA_CLIENT_ID } from '../api/coral.js'; import { Announcements, Friends, Friend, GetActiveEventResult, WebServices, CoralErrorResponse } from '../api/coral-types.js'; +import ZncProxyApi from '../api/znc-proxy.js'; import { NintendoAccountSession, Storage } from './storage/index.js'; import { checkUseLimit } from './util.js'; -import ZncProxyApi from '../api/znc-proxy.js'; +import createDebug from '../util/debug.js'; import { ArgumentsCamelCase } from '../util/yargs.js'; import { initStorage } from '../util/storage.js'; import NintendoAccountOIDC from './na.js'; diff --git a/src/client/na.ts b/src/client/na.ts index a4e6c09..e8053f3 100644 --- a/src/client/na.ts +++ b/src/client/na.ts @@ -1,4 +1,4 @@ -import createDebug from 'debug'; +import createDebug from '../util/debug.js'; import { NintendoAccountSession } from './storage/index.js'; import { getNintendoAccountToken, getNintendoAccountUser, NintendoAccountToken, NintendoAccountUser } from '../api/na.js'; diff --git a/src/client/splatnet3.ts b/src/client/splatnet3.ts index 1e9015c..e4cbf12 100644 --- a/src/client/splatnet3.ts +++ b/src/client/splatnet3.ts @@ -1,6 +1,6 @@ -import createDebug from 'debug'; import { Response } from 'node-fetch'; import { ConfigureAnalyticsResult, CurrentFestResult, DetailVotingStatusResult, FriendListResult, Friend_friendList, HomeResult, StageScheduleResult } from 'splatnet3-types/splatnet3'; +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'; @@ -148,7 +148,7 @@ function createTokenExpiredHandler( data: {coral: Coral; auth_data: SavedToken; znc_proxy_url?: string}, ratelimit = true ) { - return (response: Response) => { + return (response?: Response) => { debug('Token expired, renewing'); return renewToken(session, splatnet, data, ratelimit); }; diff --git a/src/client/storage/index.ts b/src/client/storage/index.ts index 02ee5ca..3182806 100644 --- a/src/client/storage/index.ts +++ b/src/client/storage/index.ts @@ -1,5 +1,5 @@ -import createDebug from 'debug'; import { NintendoAccountSessionTokenJwtPayload } from '../../api/na.js'; +import createDebug from '../../util/debug.js'; import { Jwt } from '../../util/jwt.js'; import { SavedToken as SavedNaToken } from '../na.js'; diff --git a/src/client/storage/local.ts b/src/client/storage/local.ts index 546de93..746c3ed 100644 --- a/src/client/storage/local.ts +++ b/src/client/storage/local.ts @@ -1,8 +1,8 @@ import * as path from 'node:path'; import { fileURLToPath } from 'node:url'; import * as fs from 'node:fs/promises'; -import createDebug from 'debug'; import mkdirp from 'mkdirp'; +import createDebug from '../../util/debug.js'; import { StorageProvider } from './index.js'; const debug = createDebug('nxapi:client:storage:local'); diff --git a/src/client/util.ts b/src/client/util.ts index 11f4240..4538574 100644 --- a/src/client/util.ts +++ b/src/client/util.ts @@ -1,4 +1,4 @@ -import createDebug from 'debug'; +import createDebug from '../util/debug.js'; import { LIMIT_PERIOD, LIMIT_REQUESTS } from '../common/auth/util.js'; import { NintendoAccountSession } from './storage/index.js'; diff --git a/src/common/auth/coral.ts b/src/common/auth/coral.ts index dce8b35..dd0e759 100644 --- a/src/common/auth/coral.ts +++ b/src/common/auth/coral.ts @@ -1,11 +1,11 @@ -import createDebug from 'debug'; import * as persist from 'node-persist'; import { Response } from 'node-fetch'; -import { NintendoAccountSessionTokenJwtPayload } from '../../api/na.js'; -import { Jwt } from '../../util/jwt.js'; -import { CoralErrorResponse } from '../../api/coral-types.js'; import CoralApi, { CoralAuthData, ZNCA_CLIENT_ID } from '../../api/coral.js'; +import { CoralErrorResponse } from '../../api/coral-types.js'; import ZncProxyApi from '../../api/znc-proxy.js'; +import { NintendoAccountSessionTokenJwtPayload } from '../../api/na.js'; +import createDebug from '../../util/debug.js'; +import { Jwt } from '../../util/jwt.js'; import { checkUseLimit, SHOULD_LIMIT_USE } from './util.js'; const debug = createDebug('nxapi:auth:coral'); @@ -99,7 +99,7 @@ function createTokenExpiredHandler( storage: persist.LocalStorage, token: string, nso: CoralApi, renew_token_data: {existingToken: SavedToken}, ratelimit = true ) { - return (data: CoralErrorResponse, response: Response) => { + return (data?: CoralErrorResponse, 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/moon.ts b/src/common/auth/moon.ts index 0eed3c9..378a547 100644 --- a/src/common/auth/moon.ts +++ b/src/common/auth/moon.ts @@ -1,8 +1,8 @@ -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 createDebug from '../../util/debug.js'; import { Jwt } from '../../util/jwt.js'; import MoonApi from '../../api/moon.js'; import { checkUseLimit, LIMIT_REQUESTS, SHOULD_LIMIT_USE } from './util.js'; @@ -76,7 +76,7 @@ function createTokenExpiredHandler( storage: persist.LocalStorage, token: string, moon: MoonApi, renew_token_data: {existingToken: SavedMoonToken}, ratelimit = true ) { - return (data: MoonError, response: Response) => { + return (data?: MoonError, response?: Response) => { debug('Token expired', renew_token_data.existingToken.user.id, data); return renewToken(storage, token, moon, renew_token_data, ratelimit); }; diff --git a/src/common/auth/nooklink.ts b/src/common/auth/nooklink.ts index 33be277..70f2f22 100644 --- a/src/common/auth/nooklink.ts +++ b/src/common/auth/nooklink.ts @@ -1,10 +1,10 @@ -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, WebServiceError } from '../../api/nooklink-types.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'; @@ -79,7 +79,7 @@ function createTokenExpiredHandler( renew_token_data: {existingToken: SavedToken; znc_proxy_url?: string}, ratelimit = true ) { - return (data: WebServiceError, response: Response) => { + return (data?: WebServiceError, response?: Response) => { debug('Token expired, renewing', data); return renewToken(storage, token, nooklink, renew_token_data, ratelimit); }; @@ -196,7 +196,7 @@ function createUserTokenExpiredHandler( renew_token_data: {existingToken: SavedUserToken; znc_proxy_url?: string; nooklink: NooklinkApi | null}, ratelimit = true ) { - return (data: WebServiceError, response: Response) => { + return (data?: WebServiceError, response?: Response) => { debug('Token expired', nooklinkuser.user_id, data); return renewUserToken(storage, token, nooklinkuser, renew_token_data); }; diff --git a/src/common/auth/splatnet2.ts b/src/common/auth/splatnet2.ts index 2948a68..a1a3892 100644 --- a/src/common/auth/splatnet2.ts +++ b/src/common/auth/splatnet2.ts @@ -1,10 +1,10 @@ import process from 'node:process'; import * as fs from 'node:fs'; -import createDebug from 'debug'; import persist from 'node-persist'; import { getToken, Login } from './coral.js'; import SplatNet2Api, { SplatNet2AuthData, updateIksmSessionLastUsed } from '../../api/splatnet2.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'; diff --git a/src/common/auth/splatnet3.ts b/src/common/auth/splatnet3.ts index d2897cf..67f7ae8 100644 --- a/src/common/auth/splatnet3.ts +++ b/src/common/auth/splatnet3.ts @@ -1,9 +1,9 @@ -import createDebug from 'debug'; 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 { 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'; @@ -79,7 +79,7 @@ function createTokenExpiredHandler( data: {existingToken: SavedBulletToken; znc_proxy_url?: string}, ratelimit = true ) { - return (response: Response) => { + return (response?: Response) => { debug('Token expired, renewing'); return renewToken(storage, token, splatnet, data, ratelimit); }; diff --git a/src/common/auth/util.ts b/src/common/auth/util.ts index 464c05b..67dc284 100644 --- a/src/common/auth/util.ts +++ b/src/common/auth/util.ts @@ -1,5 +1,5 @@ -import createDebug from 'debug'; import * as persist from 'node-persist'; +import createDebug from '../../util/debug.js'; const debug = createDebug('nxapi:auth:util'); diff --git a/src/common/globals.ts b/src/common/globals.ts index 9a10568..6586ec4 100644 --- a/src/common/globals.ts +++ b/src/common/globals.ts @@ -1,7 +1,7 @@ import * as path from 'node:path'; -import createDebug from 'debug'; import dotenv from 'dotenv'; import dotenvExpand from 'dotenv-expand'; +import createDebug from '../util/debug.js'; import { paths } from '../util/storage.js'; let done = false; diff --git a/src/common/notify.ts b/src/common/notify.ts index 3b01e38..21b7aa2 100644 --- a/src/common/notify.ts +++ b/src/common/notify.ts @@ -1,4 +1,3 @@ -import createDebug from 'debug'; import persist from 'node-persist'; import CoralApi from '../api/coral.js'; import { ActiveEvent, Announcements, CurrentUser, Friend, Game, Presence, PresenceState, WebServices, CoralErrorResponse, GetActiveEventResult } from '../api/coral-types.js'; @@ -6,10 +5,11 @@ import ZncProxyApi from '../api/znc-proxy.js'; import { ErrorResponse } from '../api/util.js'; import { SavedToken } from './auth/coral.js'; import { SplatNet2RecordsMonitor } from './splatnet2/monitor.js'; +import createDebug from '../util/debug.js'; import Loop, { LoopResult } from '../util/loop.js'; import { getTitleIdFromEcUrl, hrduration } from '../util/misc.js'; -import { CoralUser } from './users.js'; import { handleError } from '../util/errors.js'; +import { CoralUser } from './users.js'; const debug = createDebug('nxapi:nso:notify'); const debugFriends = createDebug('nxapi:nso:notify:friends'); diff --git a/src/common/presence.ts b/src/common/presence.ts index 20cecae..7bc3c3a 100644 --- a/src/common/presence.ts +++ b/src/common/presence.ts @@ -1,17 +1,16 @@ -import createDebug from 'debug'; import EventSource from 'eventsource'; 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 { getPresenceFromUrl } from '../api/znc-proxy.js'; import { ActiveEvent, CurrentUser, Friend, Game, Presence, PresenceState, CoralErrorResponse } 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'; import Loop, { LoopResult } from '../util/loop.js'; -import { getTitleIdFromEcUrl } from '../index.js'; import { parseLinkHeader } from '../util/http.js'; import { getUserAgent } from '../util/useragent.js'; -import { TemporaryErrorSymbol } from '../util/misc.js'; +import { getTitleIdFromEcUrl, TemporaryErrorSymbol } from '../util/misc.js'; import { handleError } from '../util/errors.js'; const debug = createDebug('nxapi:nso:presence'); diff --git a/src/common/remote-config.ts b/src/common/remote-config.ts index f407f8b..8097b15 100644 --- a/src/common/remote-config.ts +++ b/src/common/remote-config.ts @@ -1,9 +1,10 @@ import * as path from 'node:path'; import * as fs from 'node:fs/promises'; +import { fileURLToPath } from 'node:url'; import fetch from 'node-fetch'; -import createDebug from 'debug'; import mkdirp from 'mkdirp'; import { ErrorResponse, ResponseSymbol } from '../api/util.js'; +import createDebug from '../util/debug.js'; import { timeoutSignal } from '../util/misc.js'; import { getUserAgent } from '../util/useragent.js'; import { paths } from '../util/storage.js'; @@ -32,6 +33,34 @@ async function loadRemoteConfig() { const config_cache_path = path.resolve(paths.cache, 'config.json'); const url = process.env.NXAPI_CONFIG_URL ?? CONFIG_URL; + const url_parsed = new URL(url); + + if (url_parsed.protocol === 'file:') { + const file = fileURLToPath(url_parsed); + + const stats = await fs.stat(file); + + const config: NxapiRemoteConfig = { + ...JSON.parse(await fs.readFile(file, 'utf-8')), + [SourceSymbol]: url_parsed.toString(), + }; + + const cache: RemoteConfigCacheData = { + created_at: stats.ctimeMs, + updated_at: stats.mtimeMs, + etag: null, + revalidated_at: Date.now(), + stale_at: null, + expires_at: Date.now(), + version, + revision: git?.revision ?? null, + url: url_parsed.toString(), + headers: {}, + data: config, + }; + + return cache; + } let data: RemoteConfigCacheData | undefined = undefined; let must_revalidate = true; diff --git a/src/common/splatnet2/dump-records.ts b/src/common/splatnet2/dump-records.ts index 1c5c873..986a7a8 100644 --- a/src/common/splatnet2/dump-records.ts +++ b/src/common/splatnet2/dump-records.ts @@ -2,10 +2,10 @@ import * as path from 'node:path'; import * as fs from 'node:fs/promises'; import * as crypto from 'node:crypto'; import { Buffer } from 'node:buffer'; -import createDebug from 'debug'; import fetch from 'node-fetch'; import SplatNet2Api, { ShareColour } from '../../api/splatnet2.js'; import { Challenge, NicknameAndIcon, Records, Stages } from '../../api/splatnet2-types.js'; +import createDebug from '../../util/debug.js'; import { timeoutSignal } from '../../util/misc.js'; const debug = createDebug('nxapi:splatnet2:dump-records'); diff --git a/src/common/splatnet2/dump-results.ts b/src/common/splatnet2/dump-results.ts index c6c3bcf..62a4e84 100644 --- a/src/common/splatnet2/dump-results.ts +++ b/src/common/splatnet2/dump-results.ts @@ -1,10 +1,10 @@ import * as path from 'node:path'; import * as fs from 'node:fs/promises'; import { Buffer } from 'node:buffer'; -import createDebug from 'debug'; import fetch from 'node-fetch'; import SplatNet2Api from '../../api/splatnet2.js'; import { NicknameAndIcon } from '../../api/splatnet2-types.js'; +import createDebug from '../../util/debug.js'; import { timeoutSignal } from '../../util/misc.js'; const debug = createDebug('nxapi:splatnet2:dump-results'); diff --git a/src/common/splatnet2/monitor.ts b/src/common/splatnet2/monitor.ts index 2d12fa8..ccd452e 100644 --- a/src/common/splatnet2/monitor.ts +++ b/src/common/splatnet2/monitor.ts @@ -1,6 +1,5 @@ import * as path from 'node:path'; import * as fs from 'node:fs/promises'; -import createDebug from 'debug'; import persist from 'node-persist'; import mkdirp from 'mkdirp'; import SplatNet2Api from '../../api/splatnet2.js'; @@ -9,6 +8,7 @@ import { Records, Stages, WebServiceError } 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'; const debug = createDebug('nxapi:splatnet2:monitor'); diff --git a/src/common/update.ts b/src/common/update.ts index c87ddd9..c5b8de7 100644 --- a/src/common/update.ts +++ b/src/common/update.ts @@ -1,8 +1,8 @@ import * as path from 'node:path'; import * as fs from 'node:fs/promises'; import fetch from 'node-fetch'; -import createDebug from 'debug'; import mkdirp from 'mkdirp'; +import createDebug from '../util/debug.js'; import { dir, docker, version } from '../util/product.js'; import { paths } from '../util/storage.js'; import { timeoutSignal } from '../util/misc.js'; diff --git a/src/common/users.ts b/src/common/users.ts index 9cd5174..994a1c7 100644 --- a/src/common/users.ts +++ b/src/common/users.ts @@ -1,6 +1,6 @@ import * as crypto from 'node:crypto'; -import createDebug from 'debug'; import * as persist from 'node-persist'; +import createDebug from '../util/debug.js'; import CoralApi, { Result } from '../api/coral.js'; import ZncProxyApi from '../api/znc-proxy.js'; import { Announcements, Friends, Friend, GetActiveEventResult, CoralSuccessResponse, WebService, WebServices } from '../api/coral-types.js'; diff --git a/src/discord/monitor/splatoon3.ts b/src/discord/monitor/splatoon3.ts index a54a328..f3135b8 100644 --- a/src/discord/monitor/splatoon3.ts +++ b/src/discord/monitor/splatoon3.ts @@ -1,18 +1,18 @@ -import createDebug from 'debug'; import persist from 'node-persist'; import DiscordRPC from 'discord-rpc'; import { BankaraMatchMode, BankaraMatchSetting, CoopRule, CoopSchedule_schedule, CoopSetting_schedule, DetailVotingStatusResult, FestMatchSetting, FestTeam_schedule, FestTeam_votingStatus, Fest_schedule, FriendListResult, FriendOnlineState, GraphQLSuccessResponse, LeagueMatchSetting, RegularMatchSetting, StageScheduleResult, VsSchedule_bankara, VsSchedule_fest, VsSchedule_league, VsSchedule_regular, VsSchedule_xMatch, XMatchSetting } from 'splatnet3-types/splatnet3'; +import StageScheduleQuery_730cd98 from 'splatnet3-types/graphql/730cd98e84f1030d3e9ac86b6f1aae13'; import { Game } from '../../api/coral-types.js'; import SplatNet3Api from '../../api/splatnet3.js'; import { DiscordPresenceExternalMonitorsConfiguration } from '../../app/common/types.js'; import { Arguments } from '../../cli/nso/presence.js'; import { getBulletToken, SavedBulletToken } from '../../common/auth/splatnet3.js'; import { ExternalMonitorPresenceInterface } from '../../common/presence.js'; +import createDebug from '../../util/debug.js'; import { EmbeddedLoop, LoopResult } from '../../util/loop.js'; import { ArgumentsCamelCase } from '../../util/yargs.js'; -import { DiscordPresenceContext, ErrorResult } from '../types.js'; import { product } from '../../util/product.js'; -import StageScheduleQuery_730cd98 from 'splatnet3-types/graphql/730cd98e84f1030d3e9ac86b6f1aae13'; +import { DiscordPresenceContext, ErrorResult } from '../types.js'; const debug = createDebug('nxapi:discord:splatnet3'); diff --git a/src/discord/rpc.ts b/src/discord/rpc.ts index dff2e83..06bb5e3 100644 --- a/src/discord/rpc.ts +++ b/src/discord/rpc.ts @@ -1,11 +1,11 @@ import process from 'node:process'; import * as net from 'node:net'; import { EventEmitter } from 'node:events'; -import createDebug from 'debug'; import fetch from 'node-fetch'; import DiscordRPC from 'discord-rpc'; // @ts-expect-error import __BaseIpcTransport from 'discord-rpc/src/transports/ipc.js'; +import createDebug from '../util/debug.js'; const debug = createDebug('nxapi:discord:rpc'); diff --git a/src/discord/util.ts b/src/discord/util.ts index bfe6193..0686dcc 100644 --- a/src/discord/util.ts +++ b/src/discord/util.ts @@ -1,7 +1,7 @@ -import createDebug from 'debug'; import DiscordRPC from 'discord-rpc'; import { Game, PresenceState } from '../api/coral-types.js'; import { defaultTitle, titles } from './titles.js'; +import createDebug from '../util/debug.js'; import { product, version } from '../util/product.js'; import { getTitleIdFromEcUrl, hrduration } from '../util/misc.js'; import { DiscordPresence, DiscordPresenceContext, DiscordPresencePlayTime } from './types.js'; diff --git a/src/util/debug.ts b/src/util/debug.ts new file mode 100644 index 0000000..db84f08 --- /dev/null +++ b/src/util/debug.ts @@ -0,0 +1,194 @@ +import { WriteStream } from 'node:fs'; +import { FileHandle, open, opendir, stat, unlink } from 'node:fs/promises'; +import * as util from 'node:util'; +import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import createDebug from 'debug'; +import mkdirp from 'mkdirp'; +import { dev, dir, docker, git, product, release, version } from './product.js'; + +const MAX_FILE_SIZE = 1000 * 1000 * 2; // 2 MB +const DELETE_AFTER_MS = 1000 * 60 * 60 * 24 * 14; // 14 days + +createDebug.log = console.warn.bind(console); + +export default new Proxy(createDebug, { + apply(target, thisArg, args: [string]) { + const debug = target.apply(thisArg, args); + + return new Proxy(debug, handler); + }, +}); + +const handler: ProxyHandler = { + apply(target, thisArg, args: [formatter: any, ...args: any[]]) { + if (log_file) { + args[0] = createDebug.coerce(args[0]); + + if (typeof args[0] !== 'string') { + // Anything else let's inspect with %O + args.unshift('%O'); + } + + // Apply any `formatters` transformations + applyFormatters(args, target); + + writeToFile(target.namespace, args); + + args[0] = args[0].replace(/%/g, '%%'); + } + + return target.apply(thisArg, args); + }, +}; + +const debug = new Proxy(createDebug('nxapi:util:debug'), handler); +debug.enabled = true; + +function applyFormatters(args: [formatter: string, ...args: unknown[]], self = debug) { + // https://github.com/debug-js/debug/blob/d1616622e4d404863c5a98443f755b4006e971dc/src/common.js#L89-L107 + + let index = 0; + args[0] = args[0].replace(/%([a-zA-Z%])/g, (match, format) => { + // If we encounter an escaped % then don't increase the array index + if (match === '%%') { + return '%'; + } + index++; + const formatter = createDebug.formatters[format]; + if (typeof formatter === 'function') { + const val = args[index]; + match = formatter.call(self, val); + + // Now we need to remove `args[index]` since it's inlined in the `format` + args.splice(index, 1); + index--; + } + return match; + }); + return args; +} + +const censor_fields = [ + 'token', + // NA OIDC + 'access_token', 'id_token', + // Coral + 'accessToken', 'supportId', + // Moon + 'serialNumber', 'notificationToken', +]; +const censor_regexp = new RegExp('^(\\s*(' + censor_fields.join('|') + '): \')(([^\']{10})[^\']{20,}([^\']{10})|[^\']{1,39})(\'( \})*,?)$', 'gm'); + +function writeToFile(namespace: string, _args: [formatter: string, ...args: unknown[]], skip_new = false) { + if (!log_file) return; + + const [formatter, ...args] = _args; + + const data = (new Date()).toISOString() + ' ' + namespace + ' ' + + util.format(formatter, ...args) + .replace(/\u001B\[[0-9;]*m/g, '') + .replace(censor_regexp, '$1$4••••••••$5$6') + '\n'; + const buffer = (new TextEncoder()).encode(data); + + const [, stream, path, start, i] = log_file; + + stream.write(buffer); + log_file[5] += buffer.length; + + if (!skip_new && log_file[5] !== buffer.length && log_file[5] >= MAX_FILE_SIZE && !log_file[6]) { + log_file[6] = openLogFile(path, start, i + 1).catch(err => { + if (log_file) log_file[6] = null; + + debug('Error opening next log file', path, start, i + 1, err); + }); + } +} + +let log_file: [ + handle: FileHandle, stream: WriteStream, + path: string, start: Date, i: number, bytes: number, replace: Promise | null, +] | null = null; + +export async function init(path: string | URL, ignore_errors = true) { + if (log_file) throw new Error('Already initialised log file'); + + if (path instanceof URL) path = fileURLToPath(path); + + const start = new Date(); + + try { + await openLogFile(path, start); + } catch (err) { + if (!ignore_errors) throw err; + + debug('Error opening log file', path); + } + + deleteOldLogs(path).catch(err => { + debug('Error deleting old logs', path, err); + }); +} + +async function openLogFile(path: string, start: Date, i = 0) { + const filename = + start.toISOString().replace(/^(\d+)-(\d+)-(\d+)T(\d+):(\d+):(\d+)\..*/, '$1$2$3-$4$5') + + '-' + process.pid + '-' + i + '.log'; + const file = join(path, filename); + + await mkdirp(path, {mode: 0o700}); + + const file_handle = await open(file, 'a', 0o600); + const stream = file_handle.createWriteStream(); + + if (log_file) { + const [prev_handle, prev_stream] = log_file; + prev_stream.close(); + prev_handle.close(); + } + + log_file = [file_handle, stream, path, start, i, 0, null]; + + debug('start log file %s', file); + + if (i === 0) { + writeToFile('nxapi:util:debug', applyFormatters(['product %O, versions %O', { + dir, version, release, docker, git, dev, product, + }, process.versions]), true); + writeToFile('nxapi:util:debug', applyFormatters(['argv %O', process.argv]), true); + writeToFile('nxapi:util:debug', applyFormatters(['env %O', process.env]), true); + } +} + +async function deleteOldLogs(path: string) { + // Files that will be excluded by recently_accessed may not be included + const log_files: [id: string, name: string][] = []; + const recently_accessed: string[] = []; + + for await (const dirent of await opendir(path)) { + if (!dirent.isFile()) continue; + + const match = dirent.name.match(/^(((\d+)-(\d+))-(\d+))(-(\d+))?\.log$/); + if (!match) continue; + + const [, id, datetime, date, time, pid, i] = match; + if (recently_accessed.includes(id)) continue; + + const stats = await stat(join(path, dirent.name)); + + if ((stats.mtimeMs + DELETE_AFTER_MS) >= Date.now()) { + recently_accessed.push(id); + continue; + } + + log_files.push([id, dirent.name]); + } + + const to_delete = log_files.filter(([id, name]) => !recently_accessed.includes(id)); + + for (const [id, name] of to_delete) { + const file = join(path, name); + debug('delete old log file', file); + await unlink(file); + } +} diff --git a/src/util/errors.ts b/src/util/errors.ts index c845829..ed4fcca 100644 --- a/src/util/errors.ts +++ b/src/util/errors.ts @@ -1,9 +1,9 @@ -import createDebug from 'debug'; 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'; -import { TemporaryErrorSymbol } from './misc.js'; const debug = createDebug('nxapi:util:errors'); diff --git a/src/util/jwt.ts b/src/util/jwt.ts index c912991..d35d8ca 100644 --- a/src/util/jwt.ts +++ b/src/util/jwt.ts @@ -1,8 +1,8 @@ import * as crypto from 'node:crypto'; import { Buffer } from 'node:buffer'; -import createDebug from 'debug'; import persist from 'node-persist'; import fetch from 'node-fetch'; +import createDebug from './debug.js'; import { timeoutSignal } from './misc.js'; const debug = createDebug('nxapi:util:jwt'); diff --git a/src/util/loop.ts b/src/util/loop.ts index 44acf27..6084488 100644 --- a/src/util/loop.ts +++ b/src/util/loop.ts @@ -1,4 +1,4 @@ -import createDebug from 'debug'; +import createDebug from './debug.js'; const debug = createDebug('nxapi:util:loop'); diff --git a/src/util/product.ts b/src/util/product.ts index ba941ee..e44a092 100644 --- a/src/util/product.ts +++ b/src/util/product.ts @@ -3,6 +3,11 @@ import * as path from 'node:path'; import { fileURLToPath } from 'node:url'; import * as fs from 'node:fs/promises'; import * as util from 'node:util'; +import getPaths from 'env-paths'; + +// This file is used by debug.ts +// debug is only used before this module completes execution, so logging to a +// file will not have been initialised then anyway import createDebug from 'debug'; const debug = createDebug('nxapi:util:product'); @@ -79,3 +84,5 @@ export const dev = process.env.NODE_ENV !== 'production' && export const product = 'nxapi ' + version + (!release && git ? '-' + git.revision.substr(0, 7) + (git.branch ? ' (' + git.branch + ')' : '') : !release ? '-?' : ''); + +export const paths = getPaths('nxapi'); diff --git a/src/util/storage.ts b/src/util/storage.ts index b0791b8..35f21e7 100644 --- a/src/util/storage.ts +++ b/src/util/storage.ts @@ -1,12 +1,11 @@ import * as path from 'node:path'; import * as fs from 'node:fs/promises'; -import createDebug from 'debug'; import persist from 'node-persist'; -import getPaths from 'env-paths'; +import createDebug from './debug.js'; const debug = createDebug('nxapi:util:storage'); -export const paths = getPaths('nxapi'); +export { paths } from './product.js'; export async function initStorage(dir: string) { const storage = persist.create({