Merge branch 'main' into i18n

# Conflicts:
#	src/app/main/index.ts
#	src/app/main/ipc.ts
#	src/app/main/menu.ts
#	src/app/main/monitor.ts
#	src/app/main/webservices.ts
This commit is contained in:
Samuel Elliott 2023-06-01 21:42:03 +01:00
commit 79e0a5516f
No known key found for this signature in database
GPG Key ID: 8420C7CDE43DC4D6
133 changed files with 771 additions and 458 deletions

View File

@ -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~~

View File

@ -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');

14
package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -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"
}
}

View File

@ -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 "$@"

View File

@ -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,

View File

@ -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<T> {
}
export default class CoralApi {
onTokenExpired: ((data: CoralErrorResponse, res: Response) => Promise<CoralAuthData | void>) | null = null;
onTokenExpired: ((data?: CoralErrorResponse, res?: Response) => Promise<CoralAuthData | void>) | null = null;
/** @internal */
_renewToken: Promise<void> | 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<T = unknown>(
url: string, method = 'GET', body?: string, headers?: object,
/** @internal */ _autoRenewToken = true,
/** @internal */ _attempt = 0
/** @internal */ _attempt = 0,
): Promise<Result<T>> {
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<T>;
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');

View File

@ -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<FResult>;
abstract genf(
token: string, hash_method: HashMethod,
user?: {na_id: string; coral_user_id?: string;},
): Promise<FResult>;
}
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;

View File

@ -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<MoonAuthData | PartialMoonAuthData | void>) | null = null;
onTokenExpired: ((data?: MoonError, res?: Response) => Promise<MoonAuthData | PartialMoonAuthData | void>) | null = null;
/** @internal */
_renewToken: Promise<void> | null = null;
protected _token_expired = false;
protected constructor(
public token: string,
@ -31,8 +32,18 @@ export default class MoonApi {
async fetch<T extends object>(
url: string, method = 'GET', body?: string, headers?: object,
/** @internal */ _autoRenewToken = true,
/** @internal */ _attempt = 0
/** @internal */ _attempt = 0,
): Promise<HasResponse<T, Response>> {
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) {

View File

@ -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';

View File

@ -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<NooklinkAuthData | void>) | null = null;
onTokenExpired: ((data?: WebServiceError, res?: Response) => Promise<NooklinkAuthData | void>) | null = null;
/** @internal */
_renewToken: Promise<void> | null = null;
protected _token_expired = false;
protected constructor(
public gtoken: string,
@ -30,8 +31,18 @@ export default class NooklinkApi {
async fetch<T extends object>(
url: string, method = 'GET', body?: string | FormData, headers?: object,
/** @internal */ _autoRenewToken = true,
/** @internal */ _attempt = 0
/** @internal */ _attempt = 0,
): Promise<HasResponse<T, Response>> {
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<NooklinkUserAuthData | PartialNooklinkUserAuthData | void>) | null = null;
onTokenExpired: ((data?: WebServiceError, res?: Response) => Promise<NooklinkUserAuthData | PartialNooklinkUserAuthData | void>) | null = null;
/** @internal */
_renewToken: Promise<void> | null = null;
protected _token_expired = false;
protected constructor(
public user_id: string,
@ -211,8 +225,18 @@ export class NooklinkUserApi {
async fetch<T extends object>(
url: string, method = 'GET', body?: string | FormData, headers?: object,
/** @internal */ _autoRenewToken = true,
/** @internal */ _attempt = 0
/** @internal */ _attempt = 0,
): Promise<HasResponse<T, Response>> {
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 */

View File

@ -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<T extends object>(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());
}

View File

@ -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<SplatNet3AuthData | void>) | null = null;
onTokenExpired: ((res: Response) => Promise<SplatNet3AuthData | void>) | null = null;
onTokenExpired: ((res?: Response) => Promise<SplatNet3AuthData | void>) | null = null;
/** @internal */
_renewToken: Promise<void> | null = null;
protected _token_expired = false;
graphql_strict = process.env.NXAPI_SPLATNET3_STRICT !== '0';
@ -93,8 +94,18 @@ export default class SplatNet3Api {
async fetch<T = unknown>(
url: string, method = 'GET', body?: string | FormData, headers?: object,
/** @internal */ _log?: string,
/** @internal */ _attempt = 0
/** @internal */ _attempt = 0,
): Promise<HasResponse<T, Response>> {
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) {

View File

@ -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<CoralAuthData | void>) | null = null;
onTokenExpired: ((data?: CoralErrorResponse, res?: Response) => Promise<CoralAuthData | void>) | null = null;
/** @internal */
_renewToken: Promise<void> | null = null;
/** @internal */
_token_expired = false;
/** @internal */
na_id = '';
/** @internal */
coral_user_id = '';
readonly znca_version = '';
readonly znca_useragent = '';

View File

@ -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))

5
src/app/app-init.ts Normal file
View File

@ -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'));

View File

@ -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,
});
}

View File

@ -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<BrowserWindow>();
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}),

View File

@ -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,
});
}
}

View File

@ -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';

View File

@ -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);

View File

@ -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');

View File

@ -10,7 +10,7 @@ const windows = new WeakMap<WebContents, WindowConfiguration>();
export function createWindow<T extends WindowType>(
type: T, props: WindowConfiguration<T>['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<BrowserWindow, number>();
const modal_window_shown = new WeakSet<BrowserWindow>();
export function createModalWindow<T extends WindowType>(
type: T, props: WindowConfiguration<T>['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';

View File

@ -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));

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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 {

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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');

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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');

View File

@ -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';

View File

@ -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');

View File

@ -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';

View File

@ -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');

Some files were not shown because too many files have changed in this diff Show More