From 349980d3a6cb7f214c7ad6e943c8bf2f454e2f8e Mon Sep 17 00:00:00 2001 From: Matt Isenhower Date: Fri, 9 Sep 2022 16:02:55 -0700 Subject: [PATCH] Add SplatNet 3 client --- app/splatnet/NsoClient.mjs | 6 +- app/splatnet/SplatNet3Client.mjs | 107 +++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 3 deletions(-) create mode 100644 app/splatnet/SplatNet3Client.mjs diff --git a/app/splatnet/NsoClient.mjs b/app/splatnet/NsoClient.mjs index 1fb8129..9a6aed6 100644 --- a/app/splatnet/NsoClient.mjs +++ b/app/splatnet/NsoClient.mjs @@ -20,7 +20,7 @@ export default class NsoClient this.nintendoToken = nintendoToken || process.env.NINTENDO_TOKEN; } - get _cachePrefix() { + get cachePrefix() { let token = this.nintendoToken.slice(-8); return `nso.${token}`; @@ -36,7 +36,7 @@ export default class NsoClient // Coral API _getCoralCache() { - return new ValueCache(`${this._cachePrefix}.coral`); + return new ValueCache(`${this.cachePrefix}.coral`); } async getCoralApi() { @@ -63,7 +63,7 @@ export default class NsoClient // Web service tokens _getWebServiceTokenCache(id) { - return new ValueCache(`${this._cachePrefix}.webservicetoken.${id}`); + return new ValueCache(`${this.cachePrefix}.webservicetoken.${id}`); } async getWebServiceToken(id) { diff --git a/app/splatnet/SplatNet3Client.mjs b/app/splatnet/SplatNet3Client.mjs new file mode 100644 index 0000000..45ae845 --- /dev/null +++ b/app/splatnet/SplatNet3Client.mjs @@ -0,0 +1,107 @@ +import ValueCache from "../common/ValueCache.mjs"; +import NsoClient from "./NsoClient.mjs"; + +export default class SplatNet3Client +{ + baseUrl = 'https://api.lp1.av5ja.srv.nintendo.net'; + webViewVersion = '1.0.0-5e2bcdfb'; + webServiceId = '4834290508791808'; + bulletToken = null; + + constructor(nsoClient = null) { + this.nsoClient = nsoClient ?? new NsoClient; + } + + get hasSession() { + return !!this.bulletToken; + } + + _calculateCacheExpiry(expiresIn) { + let expires = Date.now() + expiresIn * 1000; + + // Expire 5min early to make sure we have time to execute requests + return expires - 5 * 60 * 1000; + } + + // Bullet token + + async _startSession() { + this.bulletToken = await this.getBulletToken(); + } + + async _maybeStartSession() { + if (!this.hasSession) { + await this._startSession(); + } + } + + async getBulletToken() { + let bulletTokenCache = new ValueCache(`${this.nsoClient.cachePrefix}.bulletToken`); + let bulletToken = await bulletTokenCache.getData(); + + if (!bulletToken) { + let webServiceToken = await this.nsoClient.getWebServiceToken(this.webServiceId); + bulletToken = await this._createBulletToken(webServiceToken, bulletTokenCache); + } + + return bulletToken; + } + + async _createBulletToken(webServiceToken, bulletTokenCache) { + console.debug('Creating bullet token'); + + let response = await fetch(this.baseUrl + '/api/bullet_tokens', { + method: 'POST', + headers: { + 'X-Web-View-Ver': this.webViewVersion, + 'X-NACOUNTRY': 'US', // TODO + 'X-GameWebToken': webServiceToken + } + }); + + if (!response.ok) { + throw new Error(`Invalid bullet token response code: ${response.status}`); + } + + let bulletToken = await response.json(); + + // We can assume the token expires after 7200 seconds + let expiry = this._calculateCacheExpiry(7200); + await bulletTokenCache.setData(bulletToken, expiry); + + console.debug(`Caching bullet token until: ${expiry}`); + + return bulletToken; + } + + // GraphQL + + async getGraphQL(body = {}) { + await this._maybeStartSession(); + + let response = await fetch(this.baseUrl + '/api/graphql', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${this.bulletToken.bulletToken}`, + 'X-Web-View-Ver': this.webViewVersion, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(body), + }); + + if (!response.ok) { + throw new Error(`Invalid GraphQL response code: ${response.status}`); + } + + return await response.json(); + } + + async getGraphQLPersistedQuery(version, sha256Hash, variables = {}) { + let body = { + extensions: { persistedQuery: { version, sha256Hash } }, + variables, + }; + + return await this.getGraphQL(body); + } +}