mirror of
https://github.com/samuelthomas2774/nxapi.git
synced 2026-04-26 00:13:08 -05:00
Add support for HTTP proxies
This commit is contained in:
parent
d7a64b9807
commit
0457746e5e
|
|
@ -30,6 +30,7 @@ export type Menu = import('electron').Menu;
|
||||||
export type MenuItem = import('electron').MenuItem;
|
export type MenuItem = import('electron').MenuItem;
|
||||||
export type MessageBoxOptions = import('electron').MessageBoxOptions;
|
export type MessageBoxOptions = import('electron').MessageBoxOptions;
|
||||||
export type Notification = import('electron').Notification;
|
export type Notification = import('electron').Notification;
|
||||||
|
export type Session = import('electron').Session;
|
||||||
export type Settings = import('electron').Settings;
|
export type Settings = import('electron').Settings;
|
||||||
export type ShareMenu = import('electron').ShareMenu;
|
export type ShareMenu = import('electron').ShareMenu;
|
||||||
export type SharingItem = import('electron').SharingItem;
|
export type SharingItem = import('electron').SharingItem;
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
import { app, BrowserWindow, ipcMain, LoginItemSettingsOptions } from './electron.js';
|
import { app, BrowserWindow, ipcMain, LoginItemSettingsOptions, session } from './electron.js';
|
||||||
import process from 'node:process';
|
import process from 'node:process';
|
||||||
import * as path from 'node:path';
|
import * as path from 'node:path';
|
||||||
import { EventEmitter } from 'node:events';
|
import { EventEmitter } from 'node:events';
|
||||||
|
import { setGlobalDispatcher } from 'undici';
|
||||||
import * as persist from 'node-persist';
|
import * as persist from 'node-persist';
|
||||||
import MenuApp from './menu.js';
|
import MenuApp from './menu.js';
|
||||||
import { handleOpenWebServiceUri } from './webservices.js';
|
import { handleOpenWebServiceUri } from './webservices.js';
|
||||||
import { EmbeddedPresenceMonitor, PresenceMonitorManager } from './monitor.js';
|
import { EmbeddedPresenceMonitor, PresenceMonitorManager } from './monitor.js';
|
||||||
import { createModalWindow, createWindow } from './windows.js';
|
import { createModalWindow, createWindow } from './windows.js';
|
||||||
import { setupIpc } from './ipc.js';
|
import { setupIpc } from './ipc.js';
|
||||||
import { askUserForUri, showErrorDialog } from './util.js';
|
import { askUserForUri, buildElectronProxyAgent, showErrorDialog } from './util.js';
|
||||||
import { setAppInstance } from './app-menu.js';
|
import { setAppInstance } from './app-menu.js';
|
||||||
import { handleAuthUri } from './na-auth.js';
|
import { handleAuthUri } from './na-auth.js';
|
||||||
import { DiscordPresenceConfiguration, LoginItem, LoginItemOptions, WindowType } from '../common/types.js';
|
import { DiscordPresenceConfiguration, LoginItem, LoginItemOptions, WindowType } from '../common/types.js';
|
||||||
|
|
@ -119,6 +120,11 @@ export async function init() {
|
||||||
initGlobals();
|
initGlobals();
|
||||||
addUserAgent('nxapi-app (Chromium ' + process.versions.chrome + '; Electron ' + process.versions.electron + ')');
|
addUserAgent('nxapi-app (Chromium ' + process.versions.chrome + '; Electron ' + process.versions.electron + ')');
|
||||||
|
|
||||||
|
const agent = buildElectronProxyAgent({
|
||||||
|
session: session.defaultSession,
|
||||||
|
});
|
||||||
|
setGlobalDispatcher(agent);
|
||||||
|
|
||||||
app.setAboutPanelOptions({
|
app.setAboutPanelOptions({
|
||||||
applicationName: 'nxapi-app',
|
applicationName: 'nxapi-app',
|
||||||
applicationVersion: process.platform === 'darwin' ? version : version +
|
applicationVersion: process.platform === 'darwin' ? version : version +
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
import { BrowserWindow, dialog, Menu, MenuItem, MessageBoxOptions, nativeImage } from './electron.js';
|
import { BrowserWindow, dialog, Menu, MenuItem, MessageBoxOptions, nativeImage, Session } from './electron.js';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { Buffer } from 'node:buffer';
|
import { Buffer } from 'node:buffer';
|
||||||
|
import createDebug from '../../util/debug.js';
|
||||||
import { fetch } from 'undici';
|
import { fetch } from 'undici';
|
||||||
import { dir } from '../../util/product.js';
|
import { dir } from '../../util/product.js';
|
||||||
import { App, Store } from './index.js';
|
import { App, Store } from './index.js';
|
||||||
import { SavedToken } from '../../common/auth/coral.js';
|
import { SavedToken } from '../../common/auth/coral.js';
|
||||||
import { ErrorDescription } from '../../util/errors.js';
|
import { ErrorDescription } from '../../util/errors.js';
|
||||||
|
import { buildProxyAgent, ProxyAgentOptions } from '../../util/undici-proxy.js';
|
||||||
|
|
||||||
|
const debug = createDebug('app:main:util');
|
||||||
|
|
||||||
export const bundlepath = path.resolve(dir, 'dist', 'app', 'bundle');
|
export const bundlepath = path.resolve(dir, 'dist', 'app', 'bundle');
|
||||||
|
|
||||||
|
|
@ -87,3 +91,35 @@ export function showErrorDialog(options: ErrorBoxOptions) {
|
||||||
dialog.showMessageBox(window, message_box_options) :
|
dialog.showMessageBox(window, message_box_options) :
|
||||||
dialog.showMessageBox(message_box_options);
|
dialog.showMessageBox(message_box_options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildElectronProxyAgent(options: ProxyAgentOptions & {
|
||||||
|
session: Session;
|
||||||
|
}) {
|
||||||
|
let warned_proxy_unsupported: string | null = null;
|
||||||
|
|
||||||
|
return buildProxyAgent({
|
||||||
|
...options,
|
||||||
|
resolveProxy: async origin => {
|
||||||
|
// https://chromium.googlesource.com/chromium/src/+/HEAD/net/docs/proxy.md
|
||||||
|
const proxies = await options.session.resolveProxy(origin);
|
||||||
|
const proxy = proxies.split(';')[0].trim();
|
||||||
|
|
||||||
|
if (proxy === 'DIRECT') return null;
|
||||||
|
|
||||||
|
if (proxy.startsWith('PROXY ')) {
|
||||||
|
return new URL('http://' + proxy.substr(6));
|
||||||
|
}
|
||||||
|
if (proxy.startsWith('HTTPS ')) {
|
||||||
|
return new URL('https://' + proxy.substr(6));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (warned_proxy_unsupported !== proxy) {
|
||||||
|
warned_proxy_unsupported = proxy;
|
||||||
|
|
||||||
|
debug('Unsupported proxy', proxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import process from 'node:process';
|
import process from 'node:process';
|
||||||
import Yargs from 'yargs';
|
import Yargs from 'yargs';
|
||||||
|
import { setGlobalDispatcher } from 'undici';
|
||||||
import * as commands from './cli/index.js';
|
import * as commands from './cli/index.js';
|
||||||
import { checkUpdates } from './common/update.js';
|
import { checkUpdates } from './common/update.js';
|
||||||
import createDebug from './util/debug.js';
|
import createDebug from './util/debug.js';
|
||||||
|
|
@ -9,11 +10,15 @@ import { YargsArguments } from './util/yargs.js';
|
||||||
import { addUserAgent } from './util/useragent.js';
|
import { addUserAgent } from './util/useragent.js';
|
||||||
import { USER_AGENT_INFO_URL } from './common/constants.js';
|
import { USER_AGENT_INFO_URL } from './common/constants.js';
|
||||||
import { init as initGlobals } from './common/globals.js';
|
import { init as initGlobals } from './common/globals.js';
|
||||||
|
import { buildEnvironmentProxyAgent } from './util/undici-proxy.js';
|
||||||
|
|
||||||
const debug = createDebug('cli');
|
const debug = createDebug('cli');
|
||||||
|
|
||||||
initGlobals();
|
initGlobals();
|
||||||
|
|
||||||
|
const agent = buildEnvironmentProxyAgent();
|
||||||
|
setGlobalDispatcher(agent);
|
||||||
|
|
||||||
export function createYargs(argv: string[]) {
|
export function createYargs(argv: string[]) {
|
||||||
const yargs = Yargs(argv).option('data-path', {
|
const yargs = Yargs(argv).option('data-path', {
|
||||||
describe: 'Data storage path',
|
describe: 'Data storage path',
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,12 @@ export class ErrorDescription {
|
||||||
|
|
||||||
if (description) {
|
if (description) {
|
||||||
return description.message +
|
return description.message +
|
||||||
(err instanceof Error ? '\n\n--\n\n' + (err.stack ?? err.message) : '');
|
(err instanceof Error ? '\n\n--\n\n' + util.inspect(err) : '');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (err instanceof Error) {
|
if (err instanceof Error) {
|
||||||
return err.stack || err.message;
|
return util.inspect(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.inspect(err, {compact: true});
|
return util.inspect(err, {compact: true});
|
||||||
|
|
|
||||||
94
src/util/undici-proxy.ts
Normal file
94
src/util/undici-proxy.ts
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
import { Agent, buildConnector, Dispatcher, errors } from 'undici';
|
||||||
|
import createDebug from './debug.js';
|
||||||
|
|
||||||
|
const debug = createDebug('nxapi:util:undici-proxy');
|
||||||
|
|
||||||
|
function defaultProtocolPort(protocol: string) {
|
||||||
|
return protocol === 'https:' ? 443 : 80;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProxyAgentOptions {
|
||||||
|
agent?: Agent;
|
||||||
|
requestTls?: buildConnector.BuildOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildProxyAgent(options: ProxyAgentOptions & {
|
||||||
|
resolveProxy: (origin: string) => Promise<URL | null>;
|
||||||
|
}) {
|
||||||
|
const agent = options.agent ?? new Agent();
|
||||||
|
const connectEndpoint = buildConnector(options.requestTls ?? {});
|
||||||
|
|
||||||
|
return new Agent({
|
||||||
|
connect: async (opts, callback) => {
|
||||||
|
let requestedHost = opts.host!;
|
||||||
|
|
||||||
|
if (!opts.port) {
|
||||||
|
requestedHost += `:${defaultProtocolPort(opts.protocol)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const request_origin = opts.protocol + '//' + opts.hostname +
|
||||||
|
(opts.port ? ':' + opts.port : '');
|
||||||
|
|
||||||
|
const proxy = await options.resolveProxy.call(null, request_origin);
|
||||||
|
|
||||||
|
debug('resolved proxy for %s as %s', request_origin, proxy?.toString());
|
||||||
|
|
||||||
|
if (!proxy) {
|
||||||
|
connectEndpoint(opts, callback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { origin, port, host } = proxy;
|
||||||
|
|
||||||
|
const { socket, statusCode } = await agent.connect({
|
||||||
|
// @ts-expect-error
|
||||||
|
origin,
|
||||||
|
port,
|
||||||
|
path: requestedHost,
|
||||||
|
// @ts-expect-error
|
||||||
|
signal: opts.signal,
|
||||||
|
headers: {
|
||||||
|
host,
|
||||||
|
},
|
||||||
|
}) as unknown as Dispatcher.ConnectData;
|
||||||
|
|
||||||
|
if (statusCode !== 200) {
|
||||||
|
socket.on('error', () => {}).destroy();
|
||||||
|
callback(new errors.RequestAbortedError('Proxy response !== 200 when HTTP Tunneling'), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.protocol !== 'https:') {
|
||||||
|
// @ts-expect-error
|
||||||
|
callback(null, socket);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-expect-error
|
||||||
|
connectEndpoint({ ...opts, httpSocket: socket }, callback);
|
||||||
|
} catch (err) {
|
||||||
|
callback(err as Error, null);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildEnvironmentProxyAgent(options?: ProxyAgentOptions) {
|
||||||
|
return buildProxyAgent({
|
||||||
|
...options,
|
||||||
|
resolveProxy: resolveProxyFromEnvironment,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function resolveProxyFromEnvironment(origin: string) {
|
||||||
|
const { protocol } = new URL(origin);
|
||||||
|
|
||||||
|
if (protocol === 'http:' && process.env.HTTP_PROXY) {
|
||||||
|
return new URL(process.env.HTTP_PROXY);
|
||||||
|
}
|
||||||
|
if (protocol === 'https:' && process.env.HTTPS_PROXY) {
|
||||||
|
return new URL(process.env.HTTPS_PROXY);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user