Rename most instances of znc to coral

This commit is contained in:
Samuel Elliott
2022-06-28 19:21:33 +01:00
parent a2f3abaebd
commit a6a69b1799
37 changed files with 197 additions and 165 deletions

View File

@@ -1,16 +1,16 @@
export interface ZncSuccessResponse<T = unknown> {
status: ZncStatus.OK;
export interface CoralSuccessResponse<T = unknown> {
status: CoralStatus.OK;
result: T;
correlationId: string;
}
export interface ZncErrorResponse {
status: ZncStatus | number;
export interface CoralErrorResponse {
status: CoralStatus | number;
errorMessage: string;
correlationId: string;
}
export enum ZncStatus {
export enum CoralStatus {
OK = 0,
BAD_REQUEST = 9400,
@@ -43,7 +43,7 @@ export enum ZncStatus {
// UNKNOWN = -1,
}
export type ZncResponse<T = unknown> = ZncSuccessResponse<T> | ZncErrorResponse;
export type CoralResponse<T = unknown> = CoralSuccessResponse<T> | CoralErrorResponse;
/** /v3/Account/Login */
export interface AccountLogin {

View File

@@ -2,12 +2,12 @@ import fetch, { Response } from 'node-fetch';
import { v4 as uuidgen } from 'uuid';
import createDebug from 'debug';
import { f, FlapgIid } from './f.js';
import { AccountLogin, AccountToken, Announcements, CurrentUser, CurrentUserPermissions, Event, Friends, GetActiveEventResult, PresencePermissions, User, WebServices, WebServiceToken, ZncErrorResponse, ZncResponse, ZncStatus, ZncSuccessResponse } from './znc-types.js';
import { AccountLogin, AccountToken, Announcements, CurrentUser, CurrentUserPermissions, Event, Friends, GetActiveEventResult, PresencePermissions, User, WebServices, WebServiceToken, CoralErrorResponse, CoralResponse, CoralStatus, CoralSuccessResponse } from './coral-types.js';
import { getNintendoAccountToken, getNintendoAccountUser, NintendoAccountUser } from './na.js';
import { ErrorResponse } from './util.js';
import { JwtPayload } from '../util/jwt.js';
const debug = createDebug('nxapi:api:znc');
const debug = createDebug('nxapi:api:coral');
const ZNCA_PLATFORM = 'Android';
const ZNCA_PLATFORM_VERSION = '8.0.0';
@@ -17,23 +17,23 @@ const ZNCA_USER_AGENT = `com.nintendo.znca/${ZNCA_VERSION}(${ZNCA_PLATFORM}/${ZN
const ZNC_URL = 'https://api-lp1.znc.srv.nintendo.net';
export const ZNCA_CLIENT_ID = '71b963c1b7b6d119';
export default class ZncApi {
export default class CoralApi {
static useragent: string | null = null;
onTokenExpired: ((data: ZncErrorResponse, res: Response) => Promise<void>) | null = null;
onTokenExpired: ((data: CoralErrorResponse, res: Response) => Promise<void>) | null = null;
/** @internal */
_renewToken: Promise<void> | null = null;
constructor(
public token: string,
public useragent: string | null = ZncApi.useragent
public useragent: string | null = CoralApi.useragent
) {}
async fetch<T = unknown>(
url: string, method = 'GET', body?: string, headers?: object,
/** @internal */ _autoRenewToken = true,
/** @internal */ _attempt = 0
): Promise<ZncSuccessResponse<T>> {
): Promise<CoralSuccessResponse<T>> {
if (this._renewToken && _autoRenewToken) {
await this._renewToken;
}
@@ -52,9 +52,9 @@ export default class ZncApi {
debug('fetch %s %s, response %s', method, url, response.status);
const data = await response.json() as ZncResponse<T>;
const data = await response.json() as CoralResponse<T>;
if (data.status === ZncStatus.TOKEN_EXPIRED && _autoRenewToken && !_attempt && this.onTokenExpired) {
if (data.status === CoralStatus.TOKEN_EXPIRED && _autoRenewToken && !_attempt && this.onTokenExpired) {
// _renewToken will be awaited when calling fetch
this._renewToken = this._renewToken ?? this.onTokenExpired.call(null, data, response).finally(() => {
this._renewToken = null;
@@ -65,7 +65,7 @@ export default class ZncApi {
if ('errorMessage' in data) {
throw new ErrorResponse('[znc] ' + data.errorMessage, response, data);
}
if (data.status !== ZncStatus.OK) {
if (data.status !== CoralStatus.OK) {
throw new ErrorResponse('[znc] Unknown error', response, data);
}
@@ -194,7 +194,7 @@ export default class ZncApi {
};
}
static async createWithSessionToken(token: string, useragent = ZncApi.useragent) {
static async createWithSessionToken(token: string, useragent = CoralApi.useragent) {
const data = await this.loginWithSessionToken(token, useragent);
return {
@@ -211,7 +211,7 @@ export default class ZncApi {
return data;
}
static async loginWithSessionToken(token: string, useragent = ZncApi.useragent) {
static async loginWithSessionToken(token: string, useragent = CoralApi.useragent) {
const uuid = uuidgen();
const timestamp = '' + Math.floor(Date.now() / 1000);
@@ -248,7 +248,7 @@ export default class ZncApi {
});
debug('fetch %s %s, response %s', 'POST', '/v3/Account/Login', response.status);
const data = await response.json() as ZncResponse<AccountLogin>;
const data = await response.json() as CoralResponse<AccountLogin>;
if ('errorMessage' in data) {
throw new ErrorResponse('[znc] ' + data.errorMessage, response, data);
@@ -271,7 +271,7 @@ export default class ZncApi {
}
}
export interface ZncJwtPayload extends JwtPayload {
export interface CoralJwtPayload extends JwtPayload {
isChildRestricted: boolean;
membership: {
active: boolean;
@@ -280,18 +280,18 @@ export interface ZncJwtPayload extends JwtPayload {
exp: number;
iat: number;
iss: 'api-lp1.znc.srv.nintendo.net';
/** User ID (CurrentUser.id, not CurrentUser.nsaID) */
/** Coral user ID (CurrentUser.id, not CurrentUser.nsaID) */
sub: number;
typ: 'id_token';
}
export interface ZncWebServiceJwtPayload extends JwtPayload {
export interface CoralWebServiceJwtPayload extends JwtPayload {
isChildRestricted: boolean;
aud: string;
exp: number;
iat: number;
iss: 'api-lp1.znc.srv.nintendo.net';
jti: string;
/** User ID (CurrentUser.id, not CurrentUser.nsaID) */
/** Coral user ID (CurrentUser.id, not CurrentUser.nsaID) */
sub: number;
links: {
networkServiceAccount: {

View File

@@ -10,6 +10,14 @@ const debugFlapg = createDebug('nxapi:api:flapg');
const debugImink = createDebug('nxapi:api:imink');
const debugZncaApi = createDebug('nxapi:api:znca-api');
abstract class ZncaApi {
constructor(
public useragent?: string
) {}
abstract genf(token: string, timestamp: string, uuid: string, type: FlapgIid): Promise<FResult>;
}
//
// splatnet2statink + flapg
//
@@ -96,6 +104,23 @@ export interface FlapgApiResponse {
};
}
export class ZncaApiFlapg extends ZncaApi {
async getLoginHash(id_token: string, timestamp: string) {
return getLoginHash(id_token, timestamp, this.useragent);
}
async genf(token: string, timestamp: string, uuid: string, type: FlapgIid) {
const result = await flapg(token, timestamp, uuid, type, this.useragent);
return {
provider: 'flapg' as const,
token, timestamp, uuid, type,
f: result.result.f,
result,
};
}
}
//
// imink
//
@@ -151,6 +176,19 @@ export interface IminkFError {
error: true;
}
export class ZncaApiImink extends ZncaApi {
async genf(token: string, timestamp: string, uuid: string, type: FlapgIid) {
const result = await iminkf(token, timestamp, uuid, type === FlapgIid.APP ? '2' : '1', this.useragent);
return {
provider: 'imink' as const,
token, timestamp, uuid, type,
f: result.f,
result,
};
}
}
//
// nxapi znca API server
//
@@ -206,43 +244,32 @@ export interface AndroidZncaFError {
error: string;
}
export async function f(
token: string, timestamp: string | number, uuid: string, type: FlapgIid,
useragent?: string,
provider: FApi = getEnvApi()
): Promise<FResult> {
if (provider === 'flapg') {
const result = await flapg(token, timestamp, uuid, type, useragent);
return {
provider: 'flapg',
token, timestamp: '' + timestamp, uuid, type,
f: result.result.f, result,
};
}
if (provider === 'imink') {
const result = await iminkf(token, timestamp, uuid, type === FlapgIid.APP ? '2' : '1', useragent);
return {
provider: 'imink',
token, timestamp: '' + timestamp, uuid, type,
f: result.f, result,
};
}
if (provider[0] === 'nxapi') {
const result = await genf(provider[1], token, timestamp, uuid, type, useragent);
return {
provider: 'nxapi', url: provider[1],
token, timestamp: '' + timestamp, uuid, type,
f: result.f, result,
};
export class ZncaApiNxapi extends ZncaApi {
constructor(readonly url: string, useragent?: string) {
super(useragent);
}
throw new Error('Unknown znca API provider');
async genf(token: string, timestamp: string, uuid: string, type: FlapgIid) {
const result = await genf(this.url + '/f', token, timestamp, uuid, type, this.useragent);
return {
provider: 'nxapi' as const,
url: this.url + '/f',
token, timestamp, uuid, type,
f: result.f,
result,
};
}
}
export type FApi =
'flapg' |
'imink' |
['nxapi', string];
export async function f(
token: string, timestamp: string | number, uuid: string, type: FlapgIid,
useragent?: string
): Promise<FResult> {
const provider = getZncaApiFromEnvironment(useragent);
return provider.genf(token, '' + timestamp, uuid, type);
}
export type FResult = {
provider: string;
@@ -264,21 +291,21 @@ export type FResult = {
result: AndroidZncaFResponse;
});
function getEnvApi(): FApi {
function getZncaApiFromEnvironment(useragent?: string): ZncaApi {
if (process.env.NXAPI_ZNCA_API) {
if (process.env.NXAPI_ZNCA_API === 'flapg') {
return 'flapg';
return new ZncaApiFlapg(useragent);
}
if (process.env.NXAPI_ZNCA_API === 'imink') {
return 'imink';
return new ZncaApiImink(useragent);
}
throw new Error('Unknown znca API provider');
}
if (process.env.ZNCA_API_URL) {
return ['nxapi', process.env.ZNCA_API_URL + '/f'];
return new ZncaApiNxapi(process.env.ZNCA_API_URL, useragent);
}
return 'flapg';
return new ZncaApiFlapg(useragent);
}

View File

@@ -1,9 +1,9 @@
import fetch from 'node-fetch';
import createDebug from 'debug';
import { WebServiceToken } from './znc-types.js';
import { WebServiceToken } from './coral-types.js';
import { NintendoAccountUser } from './na.js';
import { ErrorResponse } from './util.js';
import ZncApi from './znc.js';
import CoralApi from './coral.js';
import { WebServiceError, Users, AuthToken, UserProfile, Newspapers, Newspaper, Emoticons, Reaction, IslandProfile } from './nooklink-types.js';
import { timeoutSignal } from '../util/misc.js';
@@ -76,8 +76,8 @@ export default class NooklinkApi {
};
}
static async createWithZnc(nso: ZncApi, user: NintendoAccountUser) {
const data = await this.loginWithZnc(nso, user);
static async createWithCoral(nso: CoralApi, user: NintendoAccountUser) {
const data = await this.loginWithCoral(nso, user);
return {
nooklink: new this(data.gtoken, data.useragent),
@@ -85,7 +85,7 @@ export default class NooklinkApi {
};
}
static async loginWithZnc(nso: ZncApi, user: NintendoAccountUser) {
static async loginWithCoral(nso: CoralApi, user: NintendoAccountUser) {
const webserviceToken = await nso.getWebServiceToken(NOOKLINK_WEBSERVICE_ID);
return this.loginWithWebServiceToken(webserviceToken.result, user);

View File

@@ -1,10 +1,10 @@
import fetch from 'node-fetch';
import createDebug from 'debug';
import { v4 as uuidgen } from 'uuid';
import { WebServiceToken } from './znc-types.js';
import { WebServiceToken } from './coral-types.js';
import { NintendoAccountUser } from './na.js';
import { ErrorResponse } from './util.js';
import ZncApi from './znc.js';
import CoralApi from './coral.js';
import { ActiveFestivals, CoopResult, CoopResults, CoopSchedules, HeroRecords, NicknameAndIcons, PastFestivals, Records, Result, Results, Schedules, ShareResponse, ShopMerchandises, Stages, Timeline, WebServiceError, XPowerRankingSummary } from './splatnet2-types.js';
import { timeoutSignal } from '../util/misc.js';
import { toSeasonId, Rule as XPowerRankingRule } from './splatnet2-xrank.js';
@@ -211,8 +211,8 @@ ${colour}
});
}
static async createWithZnc(nso: ZncApi, user: NintendoAccountUser) {
const data = await this.loginWithZnc(nso, user);
static async createWithCoral(nso: CoralApi, user: NintendoAccountUser) {
const data = await this.loginWithCoral(nso, user);
return {
splatnet: new this(data.iksm_session, data.useragent),
@@ -220,7 +220,7 @@ ${colour}
};
}
static async loginWithZnc(nso: ZncApi, user: NintendoAccountUser) {
static async loginWithCoral(nso: CoralApi, user: NintendoAccountUser) {
const webserviceToken = await nso.getWebServiceToken(SPLATNET2_WEBSERVICE_ID);
return this.loginWithWebServiceToken(webserviceToken.result, user);

View File

@@ -1,8 +1,8 @@
import fetch, { Response } from 'node-fetch';
import createDebug from 'debug';
import { ActiveEvent, Announcements, CurrentUser, Event, Friend, Presence, PresencePermissions, User, WebService, WebServiceToken, ZncErrorResponse, ZncStatus, ZncSuccessResponse } from './znc-types.js';
import { ActiveEvent, Announcements, CurrentUser, Event, Friend, Presence, PresencePermissions, User, WebService, WebServiceToken, CoralErrorResponse, CoralStatus, CoralSuccessResponse } from './coral-types.js';
import { ErrorResponse } from './util.js';
import ZncApi from './znc.js';
import CoralApi from './coral.js';
import { version } from '../util/product.js';
import { NintendoAccountUser } from './na.js';
import { SavedToken } from '../common/auth/nso.js';
@@ -10,11 +10,11 @@ import { timeoutSignal } from '../util/misc.js';
const debug = createDebug('nxapi:api:znc-proxy');
export default class ZncProxyApi implements ZncApi {
export default class ZncProxyApi implements CoralApi {
static useragent: string | null = null;
// Not used by ZncProxyApi
onTokenExpired: ((data: ZncErrorResponse, res: Response) => Promise<void>) | null = null;
onTokenExpired: ((data: CoralErrorResponse, res: Response) => Promise<void>) | null = null;
/** @internal */
_renewToken: Promise<void> | null = null;
@@ -51,64 +51,64 @@ export default class ZncProxyApi implements ZncApi {
return data;
}
async call<T = unknown>(url: string, parameter = {}): Promise<ZncSuccessResponse<T>> {
async call<T = unknown>(url: string, parameter = {}): Promise<CoralSuccessResponse<T>> {
throw new Error('Not supported in ZncProxyApi');
}
async getAnnouncements() {
const response = await this.fetch<{announcements: Announcements}>('/announcements');
return {status: ZncStatus.OK as const, result: response.announcements, correlationId: ''};
return {status: CoralStatus.OK as const, result: response.announcements, correlationId: ''};
}
async getFriendList() {
const response = await this.fetch<{friends: Friend[]}>('/friends');
return {status: ZncStatus.OK as const, result: response, correlationId: ''};
return {status: CoralStatus.OK as const, result: response, correlationId: ''};
}
async addFavouriteFriend(nsaid: string) {
await this.fetch('/friend/' + nsaid, 'POST', JSON.stringify({
isFavoriteFriend: true,
}));
return {status: ZncStatus.OK as const, result: {}, correlationId: ''};
return {status: CoralStatus.OK as const, result: {}, correlationId: ''};
}
async removeFavouriteFriend(nsaid: string) {
await this.fetch('/friend/' + nsaid, 'POST', JSON.stringify({
isFavoriteFriend: false,
}));
return {status: ZncStatus.OK as const, result: {}, correlationId: ''};
return {status: CoralStatus.OK as const, result: {}, correlationId: ''};
}
async getWebServices() {
const response = await this.fetch<{webservices: WebService[]}>('/webservices');
return {status: ZncStatus.OK as const, result: response.webservices, correlationId: ''};
return {status: CoralStatus.OK as const, result: response.webservices, correlationId: ''};
}
async getActiveEvent() {
const response = await this.fetch<{activeevent: ActiveEvent}>('/activeevent');
return {status: ZncStatus.OK as const, result: response.activeevent, correlationId: ''};
return {status: CoralStatus.OK as const, result: response.activeevent, correlationId: ''};
}
async getEvent(id: number) {
const response = await this.fetch<{event: Event}>('/event/' + id);
return {status: ZncStatus.OK as const, result: response.event, correlationId: ''};
return {status: CoralStatus.OK as const, result: response.event, correlationId: ''};
}
async getUser(id: number) {
const response = await this.fetch<{user: User}>('/user/' + id);
return {status: ZncStatus.OK as const, result: response.user, correlationId: ''};
return {status: CoralStatus.OK as const, result: response.user, correlationId: ''};
}
async getCurrentUser() {
const response = await this.fetch<{user: CurrentUser}>('/user');
return {status: ZncStatus.OK as const, result: response.user, correlationId: ''};
return {status: CoralStatus.OK as const, result: response.user, correlationId: ''};
}
async getCurrentUserPermissions() {
const user = await this.getCurrentUser();
return {
status: ZncStatus.OK as const,
status: CoralStatus.OK as const,
result: {
etag: user.result.etag,
permissions: user.result.permissions,
@@ -119,16 +119,16 @@ export default class ZncProxyApi implements ZncApi {
async updateCurrentUserPermissions(
to: PresencePermissions, from: PresencePermissions, etag: string
): Promise<ZncSuccessResponse<{}>> {
): Promise<CoralSuccessResponse<{}>> {
throw new Error('Not supported in ZncProxyApi');
}
async getWebServiceToken(id: string) {
const response = await this.fetch<{token: WebServiceToken}>('/webservice/' + id + '/token');
return {status: ZncStatus.OK as const, result: response.token, correlationId: ''};
return {status: CoralStatus.OK as const, result: response.token, correlationId: ''};
}
async getToken(token: string, user: NintendoAccountUser): ReturnType<ZncApi['getToken']> {
async getToken(token: string, user: NintendoAccountUser): ReturnType<CoralApi['getToken']> {
throw new Error('Not supported in ZncProxyApi');
}