mirror of
https://github.com/samuelthomas2774/nxapi.git
synced 2026-04-24 23:16:53 -05:00
Improve error handling when launching web services
This commit is contained in:
parent
a6cc0e6254
commit
809658ac7b
|
|
@ -2,7 +2,7 @@ import { app, BrowserWindow, clipboard, dialog, IpcMain, LoginItemSettings, Menu
|
|||
import * as util from 'node:util';
|
||||
import createDebug from 'debug';
|
||||
import { User } from 'discord-rpc';
|
||||
import openWebService, { WebServiceIpc } from './webservices.js';
|
||||
import openWebService, { WebServiceIpc, WebServiceValidationError } from './webservices.js';
|
||||
import { createWindow, getWindowConfiguration } from './windows.js';
|
||||
import { DiscordPresenceConfiguration, DiscordPresenceSource, WindowType } from '../common/types.js';
|
||||
import { CurrentUser, Friend, Game, PresenceState, WebService } from '../../api/coral-types.js';
|
||||
|
|
@ -62,7 +62,7 @@ export function setupIpc(appinstance: App, ipcMain: IpcMain) {
|
|||
ipcMain.handle('nxapi:coral:webservices', (e, token: string) => store.users.get(token).then(u => u.getWebServices()));
|
||||
ipcMain.handle('nxapi:coral:openwebservice', (e, webservice: WebService, token: string, qs?: string) =>
|
||||
store.users.get(token).then(u => openWebService(store, token, u.nso, u.data, webservice, qs)
|
||||
.catch(err => dialog.showMessageBox(BrowserWindow.fromWebContents(e.sender)!, {
|
||||
.catch(err => err instanceof WebServiceValidationError ? dialog.showMessageBox(BrowserWindow.fromWebContents(e.sender)!, {
|
||||
type: 'error',
|
||||
message: (err instanceof Error ? err.name : 'Error') + ' opening web service',
|
||||
detail: (err instanceof Error ? err.stack ?? err.message : err) + '\n\n' + util.inspect({
|
||||
|
|
@ -76,7 +76,7 @@ export function setupIpc(appinstance: App, ipcMain: IpcMain) {
|
|||
user_nsa_id: u.data.nsoAccount.user.nsaId,
|
||||
user_coral_id: u.data.nsoAccount.user.id,
|
||||
}, {compact: true}),
|
||||
}))));
|
||||
}) : null)));
|
||||
ipcMain.handle('nxapi:coral:activeevent', (e, token: string) => store.users.get(token).then(u => u.getActiveEvent()));
|
||||
ipcMain.handle('nxapi:coral:friendcodeurl', (e, token: string) => store.users.get(token).then(u => u.nso.getFriendCodeUrl()));
|
||||
ipcMain.handle('nxapi:coral:friendcode', (e, token: string, friendcode: string, hash?: string) => store.users.get(token).then(u => u.nso.getUserByFriendCode(friendcode, hash)));
|
||||
|
|
|
|||
|
|
@ -1,16 +1,18 @@
|
|||
import { app, dialog, Menu, Tray, nativeImage, MenuItem } 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 from './webservices.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 { WindowType } from '../common/types.js';
|
||||
import CoralApi from '../../api/coral.js';
|
||||
|
||||
const debug = createDebug('app:main:menu');
|
||||
|
||||
|
|
@ -176,9 +178,13 @@ export default class MenuApp {
|
|||
try {
|
||||
const {nso, data} = await this.app.store.users.get(token);
|
||||
|
||||
await openWebService(this.app.store, token, nso, data, webservice);
|
||||
await this.openWebService(token, nso, data, webservice);
|
||||
} catch (err) {
|
||||
dialog.showErrorBox('Error loading web service', (err as any).stack ?? (err as any).message);
|
||||
dialog.showMessageBox({
|
||||
type: 'error',
|
||||
message: (err instanceof Error ? err.name : 'Error') + ' opening web service',
|
||||
detail: '' + (err instanceof Error ? err.stack ?? err.message : err),
|
||||
});
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
|
@ -187,6 +193,29 @@ export default class MenuApp {
|
|||
return items;
|
||||
}
|
||||
|
||||
async openWebService(token: string, nso: CoralApi, data: SavedToken, webservice: WebService) {
|
||||
try {
|
||||
await openWebService(this.app.store, token, nso, data, webservice);
|
||||
} catch (err) {
|
||||
if (!(err instanceof WebServiceValidationError)) return;
|
||||
|
||||
dialog.showMessageBox({
|
||||
type: 'error',
|
||||
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,
|
||||
uri: webservice.uri,
|
||||
},
|
||||
user_na_id: data.user.id,
|
||||
user_nsa_id: data.nsoAccount.user.nsaId,
|
||||
user_coral_id: data.nsoAccount.user.id,
|
||||
}, {compact: true}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getActiveDiscordPresenceMonitor() {
|
||||
for (const monitor of this.app.monitors.monitors) {
|
||||
if (!monitor.presence_enabled) continue;
|
||||
|
|
|
|||
|
|
@ -8,12 +8,13 @@ import { app, BrowserWindow, dialog, IpcMainInvokeEvent, nativeTheme, ShareMenu,
|
|||
import fetch from 'node-fetch';
|
||||
import CoralApi from '../../api/coral.js';
|
||||
import { dev } from '../../util/product.js';
|
||||
import { WebService } from '../../api/coral-types.js';
|
||||
import { CurrentUser, WebService, WebServiceToken } from '../../api/coral-types.js';
|
||||
import { Store } from './index.js';
|
||||
import type { NativeShareRequest, NativeShareUrlRequest } 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 { NintendoAccountUser } from '../../api/na.js';
|
||||
|
||||
const debug = createDebug('app:main:webservices');
|
||||
|
||||
|
|
@ -39,7 +40,7 @@ export default async function openWebService(
|
|||
if (verifymembership?.attrValue === 'true') {
|
||||
const membership = data.nsoAccount.user.links.nintendoAccount.membership;
|
||||
const active = typeof membership.active === 'object' ? membership.active.active : membership.active;
|
||||
if (!active) throw new Error('Nintendo Switch Online membership required');
|
||||
if (!active) throw new WebServiceValidationError('Nintendo Switch Online membership required');
|
||||
}
|
||||
|
||||
const user_title_prefix = '[' + data.user.nickname +
|
||||
|
|
@ -77,7 +78,7 @@ export default async function openWebService(
|
|||
return {action: 'deny'};
|
||||
});
|
||||
|
||||
const webserviceToken = await nso.getWebServiceToken(webservice.id);
|
||||
const webserviceToken = await getWebServiceToken(nso, webservice, data.user, data.nsoAccount.user, window);
|
||||
|
||||
const url = new URL(webservice.uri);
|
||||
url.search = new URLSearchParams({
|
||||
|
|
@ -99,8 +100,6 @@ export default async function openWebService(
|
|||
qs,
|
||||
});
|
||||
|
||||
if (dev) window.webContents.openDevTools();
|
||||
|
||||
window.loadURL(url.toString(), {
|
||||
extraHeaders: Object.entries({
|
||||
'x-appcolorscheme': nativeTheme.shouldUseDarkColors ? 'DARK' : 'LIGHT',
|
||||
|
|
@ -111,6 +110,44 @@ export default async function openWebService(
|
|||
});
|
||||
}
|
||||
|
||||
export class WebServiceValidationError extends Error {}
|
||||
|
||||
async function getWebServiceToken(
|
||||
nso: CoralApi, webservice: WebService,
|
||||
user: NintendoAccountUser, nsoAccount: CurrentUser,
|
||||
window: BrowserWindow
|
||||
): Promise<WebServiceToken> {
|
||||
try {
|
||||
return await nso.getWebServiceToken(webservice.id);
|
||||
} catch (err) {
|
||||
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.nsaId,
|
||||
user_coral_id: nsoAccount.id,
|
||||
}, {compact: true}),
|
||||
buttons: ['Retry', 'Close ' + webservice.name, 'Ignore'],
|
||||
});
|
||||
|
||||
if (result.response === 0) {
|
||||
return getWebServiceToken(nso, webservice, user, nsoAccount, window);
|
||||
}
|
||||
if (result.response === 1) {
|
||||
window.close();
|
||||
throw new Error('Error requesting web service token, closing web service');
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
function isWebServiceUrlAllowed(webservice: WebService, url: string | URL) {
|
||||
if (!webservice.whiteList) return true;
|
||||
|
||||
|
|
@ -279,42 +316,13 @@ export class WebServiceIpc {
|
|||
|
||||
async requestGameWebToken(event: IpcMainInvokeEvent): Promise<string> {
|
||||
const {nso, user, nsoAccount, webservice} = this.getWindowData(event.sender);
|
||||
const window = BrowserWindow.fromWebContents(event.sender)!;
|
||||
|
||||
debug('Web service %s, user %s, called requestGameWebToken', webservice.name, nsoAccount.user.name);
|
||||
|
||||
try {
|
||||
const webserviceToken = await nso.getWebServiceToken(webservice.id);
|
||||
const webserviceToken = await getWebServiceToken(nso, webservice, user, nsoAccount.user, window);
|
||||
|
||||
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;
|
||||
}
|
||||
return webserviceToken.accessToken;
|
||||
}
|
||||
|
||||
async restorePersistentData(event: IpcMainInvokeEvent): Promise<string | undefined> {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { app, BrowserWindow, BrowserWindowConstructorOptions, Menu, session, WebContents } from './electron.js';
|
||||
import { app, BrowserWindow, BrowserWindowConstructorOptions, Menu, nativeTheme, session, WebContents } from './electron.js';
|
||||
import * as path from 'node:path';
|
||||
import { dev } from '../../util/product.js';
|
||||
import { WindowConfiguration, WindowType } from '../common/types.js';
|
||||
|
|
@ -61,6 +61,9 @@ export function getWindowConfiguration(webcontents: WebContents): WindowConfigur
|
|||
return data;
|
||||
}
|
||||
|
||||
const BACKGROUND_COLOUR_MAIN_LIGHT = process.platform === 'win32' ? '#ffffff' : '#ececec';
|
||||
const BACKGROUND_COLOUR_MAIN_DARK = process.platform === 'win32' ? '#000000' : '#252424';
|
||||
|
||||
export function createWebServiceWindow(nsa_id: string, webservice: WebService, title_prefix?: string) {
|
||||
const browser_session = session.fromPartition('persist:webservices-' + nsa_id, {
|
||||
cache: false,
|
||||
|
|
@ -71,6 +74,7 @@ export function createWebServiceWindow(nsa_id: string, webservice: WebService, t
|
|||
height: 667,
|
||||
resizable: false,
|
||||
title: (title_prefix ?? '') + webservice.name,
|
||||
backgroundColor: nativeTheme.shouldUseDarkColors ? BACKGROUND_COLOUR_MAIN_DARK : BACKGROUND_COLOUR_MAIN_LIGHT,
|
||||
webPreferences: {
|
||||
session: browser_session,
|
||||
preload: path.join(bundlepath, 'preload-webservice.cjs'),
|
||||
|
|
@ -82,5 +86,8 @@ export function createWebServiceWindow(nsa_id: string, webservice: WebService, t
|
|||
|
||||
menus.set(window, createWindowMenu(window));
|
||||
|
||||
window.loadURL('about:blank');
|
||||
if (dev) window.webContents.openDevTools();
|
||||
|
||||
return window;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import createDebug from 'debug';
|
|||
// Logs are written to the browser window developer tools, and are hidden by default (enable verbose logs)
|
||||
const debug = createDebug('app:preload-webservice');
|
||||
|
||||
import './loading.js';
|
||||
import './znca-js-api.js';
|
||||
import './quirks/splatnet2.js';
|
||||
import './quirks/nooklink.js';
|
||||
|
|
|
|||
26
src/app/preload-webservice/loading.ts
Normal file
26
src/app/preload-webservice/loading.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import createDebug from 'debug';
|
||||
|
||||
const debug = createDebug('app:preload-webservice:loading');
|
||||
|
||||
if (location.href === 'about:blank') {
|
||||
const BACKGROUND_COLOUR_MAIN_LIGHT = process.platform === 'win32' ? '#ffffff' : '#ececec';
|
||||
const BACKGROUND_COLOUR_MAIN_DARK = process.platform === 'win32' ? '#000000' : '#252424';
|
||||
|
||||
const style = window.document.createElement('style');
|
||||
|
||||
style.textContent = `
|
||||
:root {
|
||||
background-color: ${BACKGROUND_COLOUR_MAIN_DARK};
|
||||
}
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
background-color: ${BACKGROUND_COLOUR_MAIN_LIGHT};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
(document.scrollingElement as HTMLElement).style.overflow = 'hidden';
|
||||
window.document.head.appendChild(style);
|
||||
});
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user