Automatically upgrade SplatNet 3 persisted queries

This commit is contained in:
Samuel Elliott
2022-12-14 19:50:02 +00:00
parent 3fcc7b35ae
commit cb78b3f5a7
8 changed files with 89 additions and 6 deletions

View File

@@ -9,6 +9,9 @@ import { BulletToken } from './splatnet3-types.js';
import { defineResponse, ErrorResponse, HasResponse, ResponseSymbol } from './util.js';
const debug = createDebug('nxapi:api:splatnet3');
const debugGraphQl = createDebug('nxapi:api:splatnet3:graphql');
debugGraphQl.enabled = true;
const debugUpgradeQuery = createDebug('nxapi:api:splatnet3:upgrade-query');
export const SPLATNET3_WEBSERVICE_ID = 4834290508791808;
export const SPLATNET3_WEBSERVICE_URL = 'https://api.lp1.av5ja.srv.nintendo.net';
@@ -48,6 +51,17 @@ export interface PersistedQueryResultData {
[VariablesSymbol]: {};
}
enum MapQueriesMode {
/** NXAPI_SPLATNET3_UPGRADE_QUERIES=0 - never upgrade persisted query IDs (not recommended) */
NEVER,
/** NXAPI_SPLATNET3_UPGRADE_QUERIES=1 - upgrade persisted query IDs that do not contain potentially breaking changes (not recommended) */
ONLY_SAFE_NO_REJECT,
/** NXAPI_SPLATNET3_UPGRADE_QUERIES=2 - upgrade persisted query IDs, but reject requests that contain potentially breaking changes */
ONLY_SAFE,
/** NXAPI_SPLATNET3_UPGRADE_QUERIES=3 - upgrade persisted query IDs, including requests that contain potentially breaking changes (default) */
ALL,
}
export default class SplatNet3Api {
onTokenShouldRenew: ((remaining: number, res: Response) => Promise<SplatNet3AuthData | void>) | null = null;
onTokenExpired: ((res: Response) => Promise<SplatNet3AuthData | void>) | null = null;
@@ -59,6 +73,8 @@ export default class SplatNet3Api {
protected constructor(
public bullet_token: string,
public version: string,
public map_queries: Partial<Record<string, [/** new query ID */ string, /** unsafe */ boolean]>>,
readonly map_queries_mode: MapQueriesMode,
public language: string,
public useragent: string,
) {}
@@ -135,6 +151,8 @@ export default class SplatNet3Api {
_Variables extends (V extends object ? V : _Id extends KnownRequestId ? VariablesTypes[_Id] : unknown) =
(V extends object ? V : _Id extends KnownRequestId ? VariablesTypes[_Id] : unknown),
>(id: _Id, variables: _Variables): Promise<PersistedQueryResult<_Result>> {
id = this.getUpgradedPersistedQueryId(id) as _Id;
const req: GraphQLRequest<_Variables> = {
variables,
extensions: {
@@ -163,6 +181,37 @@ export default class SplatNet3Api {
return data as PersistedQueryResult<_Result>;
}
getPersistedQueryId(id: RequestId) {
return this.getUpgradedPersistedQueryId(id, false);
}
private getUpgradedPersistedQueryId(id: string, reject = true) {
if (this.map_queries_mode === MapQueriesMode.NEVER) return id;
let new_id = id;
let unsafe = false;
while (this.map_queries[new_id]) {
const [map_id, map_unsafe] = this.map_queries[new_id]!;
if (map_unsafe && this.map_queries_mode === MapQueriesMode.ONLY_SAFE && reject) {
throw new Error('[splatnet3] Updated persisted query ' + map_id + ' for ' + id +
' contains potentially breaking changes');
}
if (map_unsafe && this.map_queries_mode !== MapQueriesMode.ALL) break;
new_id = map_id;
unsafe = unsafe || map_unsafe;
}
if (reject) {
debugUpgradeQuery('Using persisted query %s for %s', new_id, id);
if (unsafe) console.warn('[warn] Upgrading SplatNet 3 persisted query %s with potentially breaking changes', id);
}
return new_id;
}
/** * */
async getCurrentFest() {
return this.persistedQuery(RequestId.CurrentFestQuery, {});
@@ -862,6 +911,8 @@ export default class SplatNet3Api {
return new this(
data.bullet_token.bulletToken,
data.version,
{},
getMapPersistedQueriesModeFromEnvironment(),
data.bullet_token.lang,
data.useragent,
);
@@ -871,6 +922,8 @@ export default class SplatNet3Api {
return new this(
data.bullet_token,
data.version,
{},
getMapPersistedQueriesModeFromEnvironment(),
data.language,
SPLATNET3_WEBSERVICE_USERAGENT,
);
@@ -972,6 +1025,15 @@ export default class SplatNet3Api {
}
}
function getMapPersistedQueriesModeFromEnvironment(): MapQueriesMode {
if (process.env.NXAPI_SPLATNET3_UPGRADE_QUERIES === '0') return MapQueriesMode.NEVER;
if (process.env.NXAPI_SPLATNET3_UPGRADE_QUERIES === '1') return MapQueriesMode.ONLY_SAFE_NO_REJECT;
if (process.env.NXAPI_SPLATNET3_UPGRADE_QUERIES === '2') return MapQueriesMode.ONLY_SAFE;
if (process.env.NXAPI_SPLATNET3_UPGRADE_QUERIES === '3') return MapQueriesMode.ALL;
return MapQueriesMode.ALL;
}
export interface SplatNet3AuthData {
webserviceToken: WebServiceToken;
url: string;

View File

@@ -104,7 +104,7 @@ export async function dumpFestRecords(splatnet: SplatNet3Api, directory: string,
if (fest_record.state !== FestState.CLOSED) {
const filename = 'splatnet3-festvotes-' + id + '-' + Date.now() + '-' +
RequestId.DetailVotingStatusQuery + '.json';
splatnet.getPersistedQueryId(RequestId.DetailVotingStatusQuery) + '.json';
const file = path.join(directory, filename);
// Fetch this now to match the behavour of Nintendo's app
@@ -128,7 +128,8 @@ export async function dumpFestRecords(splatnet: SplatNet3Api, directory: string,
}
if (include_rankings) {
const filename = 'splatnet3-festranking-' + id + '-' + RequestId.DetailRankingQuery + '.json';
const filename = 'splatnet3-festranking-' + id + '-' +
splatnet.getPersistedQueryId(RequestId.DetailRankingQuery) + '.json';
const file = path.join(directory, filename);
try {

View File

@@ -157,7 +157,8 @@ export async function dumpResults(
latest_unique_ids.push(id);
const filename = 'splatnet3-result-' + id + '-' + RequestId.VsHistoryDetailQuery + '.json';
const filename = 'splatnet3-result-' + id + '-' +
splatnet.getPersistedQueryId(RequestId.VsHistoryDetailQuery) + '.json';
const file = path.join(directory, filename);
try {
@@ -195,7 +196,8 @@ export async function dumpResults(
if (latest_unique_ids.includes(id)) continue;
const filename = 'splatnet3-result-' + id + '-' + RequestId.VsHistoryDetailQuery + '.json';
const filename = 'splatnet3-result-' + id + '-' +
splatnet.getPersistedQueryId(RequestId.VsHistoryDetailQuery) + '.json';
const file = path.join(directory, filename);
try {
@@ -267,7 +269,8 @@ export async function dumpCoopResults(
const match = id_str.match(/^CoopHistoryDetail-(u-[0-9a-z]{20}):((\d{8,}T\d{6})_([0-9a-f-]{36}))$/);
const id = match ? match[1] + '-' + match[2] : id_str;
const filename = 'splatnet3-coop-result-' + id + '-' + RequestId.CoopHistoryDetailQuery + '.json';
const filename = 'splatnet3-coop-result-' + id + '-' +
splatnet.getPersistedQueryId(RequestId.CoopHistoryDetailQuery) + '.json';
const file = path.join(directory, filename);
try {

View File

@@ -2,6 +2,9 @@ export {
default,
CoralAuthData,
PartialCoralAuthData,
ResponseDataSymbol,
CorrelationIdSymbol,
} from '../api/coral.js';
export * from '../api/coral-types.js';

View File

@@ -1,5 +1,5 @@
export { getTitleIdFromEcUrl } from '../util/misc.js';
export { ErrorResponse } from '../api/util.js';
export { ErrorResponse, ResponseSymbol } from '../api/util.js';
export { addUserAgent } from '../util/useragent.js';
export { version } from '../util/product.js';

View File

@@ -1,6 +1,11 @@
export {
default as NooklinkApi,
NooklinkAuthData,
NooklinkUserApi,
NooklinkUserAuthData,
NooklinkUserCliTokenData,
MessageType,
} from '../api/nooklink.js';

View File

@@ -1,6 +1,7 @@
export {
default,
SplatNet2AuthData,
SplatNet2CliTokenData,
LeagueType,
LeagueRegion,

View File

@@ -1,6 +1,14 @@
export {
default,
SplatNet3AuthData,
SplatNet3CliTokenData,
RequestIdSymbol,
VariablesSymbol,
XRankingRegion,
XRankingLeaderboardType,
XRankingLeaderboardRule,
} from '../api/splatnet3.js';
// export * from '../api/splatnet3-types.js';