diff --git a/src/api/coral.ts b/src/api/coral.ts index 40b9db9..e85e22e 100644 --- a/src/api/coral.ts +++ b/src/api/coral.ts @@ -205,7 +205,7 @@ export default class CoralApi { }); } - async getWebServiceToken(id: string) { + async getWebServiceToken(id: number) { const data = await f(this.token, HashMethod.WEB_SERVICE, this.useragent ?? getAdditionalUserAgents()); const req = { diff --git a/src/api/nooklink.ts b/src/api/nooklink.ts index ae0b19d..667d118 100644 --- a/src/api/nooklink.ts +++ b/src/api/nooklink.ts @@ -9,7 +9,7 @@ import { timeoutSignal } from '../util/misc.js'; const debug = createDebug('nxapi:api:nooklink'); -export const NOOKLINK_WEBSERVICE_ID = '4953919198265344'; +export const NOOKLINK_WEBSERVICE_ID = 4953919198265344; export const NOOKLINK_WEBSERVICE_URL = 'https://web.sd.lp1.acbaa.srv.nintendo.net'; export const NOOKLINK_WEBSERVICE_USERAGENT = 'Mozilla/5.0 (Linux; Android 8.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.125 Mobile Safari/537.36'; diff --git a/src/api/splatnet2.ts b/src/api/splatnet2.ts index 54aae0c..050ac94 100644 --- a/src/api/splatnet2.ts +++ b/src/api/splatnet2.ts @@ -11,7 +11,7 @@ import { toSeasonId, Rule as XPowerRankingRule, Season } from './splatnet2-xrank const debug = createDebug('nxapi:api:splatnet2'); -export const SPLATNET2_WEBSERVICE_ID = '5741031244955648'; +export const SPLATNET2_WEBSERVICE_ID = 5741031244955648; export const SPLATNET2_WEBSERVICE_URL = 'https://app.splatoon2.nintendo.net/'; export const SPLATNET2_WEBSERVICE_USERAGENT = 'Mozilla/5.0 (Linux; Android 8.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.125 Mobile Safari/537.36'; diff --git a/src/api/splatnet3.ts b/src/api/splatnet3.ts index 1928730..cf122d0 100644 --- a/src/api/splatnet3.ts +++ b/src/api/splatnet3.ts @@ -9,7 +9,7 @@ import { BankaraBattleHistoriesResult, BattleHistoryCurrentPlayerResult, BulletT const debug = createDebug('nxapi:api:splatnet3'); -export const SPLATNET3_WEBSERVICE_ID = '4834290508791808'; +export const SPLATNET3_WEBSERVICE_ID = 4834290508791808; export const SPLATNET3_WEBSERVICE_URL = 'https://api.lp1.av5ja.srv.nintendo.net'; export const SPLATNET3_WEBSERVICE_USERAGENT = 'Mozilla/5.0 (Linux; Android 8.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.125 Mobile Safari/537.36'; diff --git a/src/api/znc-proxy.ts b/src/api/znc-proxy.ts index 87fdb99..37635c4 100644 --- a/src/api/znc-proxy.ts +++ b/src/api/znc-proxy.ts @@ -132,7 +132,7 @@ export default class ZncProxyApi implements CoralApi { throw new Error('Not supported in ZncProxyApi'); } - async getWebServiceToken(id: string) { + async getWebServiceToken(id: number) { const result = await this.fetch<{token: WebServiceToken}>('/webservice/' + id + '/token'); return createResult(result, result.token); } diff --git a/src/app/main/ipc.ts b/src/app/main/ipc.ts index b70a21c..0b6fd97 100644 --- a/src/app/main/ipc.ts +++ b/src/app/main/ipc.ts @@ -64,9 +64,8 @@ export function setupIpc(appinstance: App, ipcMain: IpcMain) { store.users.get(token).then(u => openWebService(store, token, u.nso, u.data, webservice, qs) .catch(err => dialog.showMessageBox(BrowserWindow.fromWebContents(e.sender)!, { type: 'error', - title: (err instanceof Error ? err.name : 'Error') + ' opening web service', - message: err.message, - detail: (err instanceof Error ? err.stack ?? '' : err) + '\n\n' + util.inspect({ + message: (err instanceof Error ? err.name : 'Error') + ' opening web service', + detail: (err instanceof Error ? err.stack ?? err.message : err) + '\n\n' + util.inspect({ webservice: { id: webservice.id, name: webservice.name, diff --git a/src/app/main/webservices.ts b/src/app/main/webservices.ts index eab4d11..9a649a6 100644 --- a/src/app/main/webservices.ts +++ b/src/app/main/webservices.ts @@ -2,8 +2,9 @@ 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, dialog, IpcMainInvokeEvent, Menu, MenuItem, nativeTheme, ShareMenu, shell, WebContents } from './electron.js'; +import { app, BrowserWindow, dialog, IpcMainInvokeEvent, nativeTheme, ShareMenu, shell, WebContents } from './electron.js'; import fetch from 'node-fetch'; import CoralApi from '../../api/coral.js'; import { dev } from '../../util/product.js'; @@ -76,7 +77,7 @@ export default async function openWebService( return {action: 'deny'}; }); - const webserviceToken = await nso.getWebServiceToken('' + webservice.id); + const webserviceToken = await nso.getWebServiceToken(webservice.id); const url = new URL(webservice.uri); url.search = new URLSearchParams({ @@ -277,13 +278,43 @@ export class WebServiceIpc { } async requestGameWebToken(event: IpcMainInvokeEvent): Promise { - const {nso, nsoAccount, webservice} = this.getWindowData(event.sender); + const {nso, user, nsoAccount, webservice} = this.getWindowData(event.sender); debug('Web service %s, user %s, called requestGameWebToken', webservice.name, nsoAccount.user.name); - const webserviceToken = await nso.getWebServiceToken('' + webservice.id); + try { + const webserviceToken = await nso.getWebServiceToken(webservice.id); - return webserviceToken.accessToken; + return webserviceToken.accessToken; + } catch (err) { + const window = BrowserWindow.fromWebContents(event.sender)!; + + const result = await dialog.showMessageBox(window, { + type: 'error', + message: (err instanceof Error ? err.name : 'Error') + ' requesting web service token', + detail: (err instanceof Error ? err.stack ?? err.message : err) + '\n\n' + util.inspect({ + webservice: { + id: webservice.id, + name: webservice.name, + uri: webservice.uri, + }, + user_na_id: user.id, + user_nsa_id: nsoAccount.user.nsaId, + user_coral_id: nsoAccount.user.id, + }, {compact: true}), + buttons: ['Retry', 'Close ' + webservice.name, 'Ignore'], + }); + + if (result.response === 0) { + return this.requestGameWebToken(event); + } + if (result.response === 1) { + window.close(); + throw new Error('Error requesting web service token, closing web service'); + } + + throw err; + } } async restorePersistentData(event: IpcMainInvokeEvent): Promise { diff --git a/src/app/preload-webservice/znca-js-api.ts b/src/app/preload-webservice/znca-js-api.ts index 2330d74..73bfac0 100644 --- a/src/app/preload-webservice/znca-js-api.ts +++ b/src/app/preload-webservice/znca-js-api.ts @@ -43,14 +43,18 @@ declare global { // Unused sendMessage?(data: string): void; - // Unused + // SplatNet 3 copyToClipboard?(data: string): void; - + // SplatNet 3 openQRCodeReaderForCheckin?(data: string): void; onQRCodeReadForCheckin?(data: string): void; + // SplatNet 3 downloadImages?(imagesJson: string): void; + // SplatNet 3 completeLoading?(): void; + // SplatNet 3 closeWebView?(): void; + // SplatNet 3 reloadExtension?(): void; } } @@ -58,8 +62,6 @@ declare global { // // Share // -// Used by SplatNet 2 and Smash World (but both have a different implementation because Nintendo) -// export interface NativeShareRequest { text: string; @@ -90,14 +92,14 @@ window.invokeNativeShareUrl = invokeNativeShareUrl; // // Web service token // -// Used by NookLink -// function requestGameWebToken() { debug('requestGameWebToken called'); ipc.requestGameWebToken().then(token => { window.onGameWebTokenReceive?.call(null, token); + }).catch(async err => { + debug('Error requesting web service token', err); }); } @@ -106,8 +108,6 @@ window.requestGameWebToken = requestGameWebToken; // // Persistent data // -// Used by NookLink -// function restorePersistentData() { debug('restorePersistentData called'); @@ -131,8 +131,6 @@ window.storePersistentData = storePersistentData; // // QR code scanner // -// Used by NookLink -// function openQrCodeReader(data: string) { debug('openQRCodeReader called', data); @@ -179,14 +177,16 @@ window.openQRCodeReaderForCheckin = openQRCodeReaderForCheckin; function sendMessage(data: string) { // + debug('sendMessage called', data); } function copyToClipboard(data: string) { // + debug('copyToClipboard called', data); } function downloadImages(imagesJson: string) { - // + debug('downloadImages called', imagesJson); } function completeLoading() { @@ -198,7 +198,7 @@ function closeWebView() { } function reloadExtension() { - // + debug('reloadExtension called'); } window.sendMessage = sendMessage; diff --git a/src/cli/nso/http-server.ts b/src/cli/nso/http-server.ts index 50ab798..d23ec07 100644 --- a/src/cli/nso/http-server.ts +++ b/src/cli/nso/http-server.ts @@ -627,7 +627,7 @@ function createApp( app.get('/api/znc/webservice/:id/token', nsoAuth, async (req, res) => { try { - const token = await req.znc!.getWebServiceToken(req.params.id); + const token = await req.znc!.getWebServiceToken(parseInt(req.params.id)); res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify({token})); diff --git a/src/cli/nso/webservicetoken.ts b/src/cli/nso/webservicetoken.ts index 8eadd91..51d1b4f 100644 --- a/src/cli/nso/webservicetoken.ts +++ b/src/cli/nso/webservicetoken.ts @@ -61,7 +61,7 @@ export async function handler(argv: ArgumentsCamelCase) { if (!active) throw new Error('Nintendo Switch Online membership required'); } - const webserviceToken = await nso.getWebServiceToken(argv.id); + const webserviceToken = await nso.getWebServiceToken(webservice.id); // https://app.splatoon2.nintendo.net/?lang=en-GB&na_country=GB&na_lang=en-GB const url = new URL(webservice.uri);