Skip to content
This repository has been archived by the owner on Sep 29, 2023. It is now read-only.

Improve user agent handling/provide user agent "short" codes #1198

Merged
merged 12 commits into from May 22, 2021
Merged
75 changes: 39 additions & 36 deletions API.md
Expand Up @@ -52,10 +52,13 @@
- [[zoom]](#zoom)
- [Internal Browser Options](#internal-browser-options)
- [[file-download-options]](#file-download-options)
- [[honest]](#honest)
- [[inject]](#inject)
- [[lang]](#lang)
- [[user-agent]](#user-agent)
- [[user-agent-honest]](#user-agent-honest)
- [Internal Browser Cache Options](#internal-browser-cache-options)
- [[clear-cache]](#clear-cache)
- [[disk-cache-size]](#disk-cache-size)
- [URL Handling Options](#url-handling-options)
- [[block-external-urls]](#block-external-urls)
- [[internal-urls]](#internal-urls)
Expand All @@ -66,9 +69,6 @@
- [[disable-gpu]](#disable-gpu)
- [[enable-es3-apis]](#enable-es3-apis)
- [[ignore-gpu-blacklist]](#ignore-gpu-blacklist)
- [Caching Options](#caching-options)
- [[clear-cache]](#clear-cache)
- [[disk-cache-size]](#disk-cache-size)
- [(In)Security Options](#in-security-options)
- [[disable-old-build-warning-yesiknowitisinsecure]](#disable-old-build-warning-yesiknowitisinsecure)
- [[ignore-certificate]](#ignore-certificate)
Expand Down Expand Up @@ -672,16 +672,6 @@ Example `shortcuts.json` for `https://deezer.com` & `https://soundcloud.com` to

On MacOS 10.14+, if you have set a global shortcut that includes a Media key, the user will need to be prompted for permissions to enable these keys in System Preferences > Security & Privacy > Accessibility.

#### [honest]

```
--honest
```

By default, Nativefier uses a preset user agent string for your OS and masquerades as a regular Google Chrome browser, so that sites like WhatsApp Web will not say that the current browser is unsupported.

If this flag is passed, it will not override the user agent.

#### [inject]

```
Expand Down Expand Up @@ -712,7 +702,41 @@ Set the language or locale to render the web site as (e.g., "fr", "en-US", "es",
-u, --user-agent <value>
```

Set the user agent to run the created app with.
Set the user agent to run the created app with. Use `--user-agent-honest` to use the true Electron user agent.

The following short codes are also supported to generate a user agent: `edge`, `firefox`, `safari`.

- `edge` will generate a Microsoft Edge user agent matching the Chrome version of Electron being used
- `firefox` will generate a Mozilla Firefox user agent matching the latest stable release of that browser
- `safari` will generate an Apple Safari user agent matching the latest stable release of that browser

#### [user-agent-honest]

```
--user-agent-honest, --honest
```

By default, Nativefier uses a preset user agent string for your OS and masquerades as a regular Google Chrome browser, so that for some sites, it will not say that the current browser is unsupported.

If this flag is passed, it will not override the user agent, and use Electron's default generated one for your app.

### Internal Browser Cache Options

#### [clear-cache]

```
--clear-cache
```

Prevents the application from preserving cache between launches.

#### [disk-cache-size]

```
--disk-cache-size <value>
```

Forces the maximum disk space to be used by the disk cache. Value is given in bytes.

### URL Handling Options

Expand Down Expand Up @@ -818,27 +842,6 @@ Passes the enable-es3-apis flag to the Chrome engine, to force the activation of

Passes the ignore-gpu-blacklist flag to the Chrome engine, to allow for WebGl apps to work on non supported graphics cards.




### Caching Options

#### [clear-cache]

```
--clear-cache
```

Prevents the application from preserving cache between launches.

#### [disk-cache-size]

```
--disk-cache-size <value>
```

Forces the maximum disk space to be used by the disk cache. Value is given in bytes.

### (In)Security Options

#### [ignore-certificate]
Expand Down
4 changes: 2 additions & 2 deletions CATALOG.md
Expand Up @@ -15,7 +15,7 @@ Below you'll find a list of build commands contributed by the Nativefier communi

```sh
nativefier 'https://docs.google.com/spreadsheets' \
--user-agent 'user agent of current stable Firefox'
--user-agent firefox
```

Note: lying about the User Agent is required, else Google will notice your "Chrome" isn't a real Chrome, and will refuse access.
Expand Down Expand Up @@ -58,7 +58,7 @@ Note: as for Udemy, `--widevine` + [app signing](https://github.com/nativefier/n
```sh
nativefier 'https://open.spotify.com/'
--widevine
-u 'useragent of a non-Chrome browser, e.g. the current stable Firefox'
--user-agent firefox
--inject spotify.js
--inject spotify.css
```
Expand Down
2 changes: 1 addition & 1 deletion app/package.json
Expand Up @@ -20,6 +20,6 @@
"source-map-support": "^0.5.19"
},
"devDependencies": {
"electron": "^12.0.1"
"electron": "^12.0.7"
}
}
22 changes: 21 additions & 1 deletion app/src/helpers/helpers.test.ts
@@ -1,4 +1,8 @@
import { linkIsInternal, getCounterValue } from './helpers';
import {
linkIsInternal,
getCounterValue,
removeUserAgentSpecifics,
} from './helpers';

const internalUrl = 'https://medium.com/';
const internalUrlWww = 'https://www.medium.com/';
Expand Down Expand Up @@ -146,3 +150,19 @@ test('getCounterValue should return a string for small counter numbers in the ti
test('getCounterValue should return a string for large counter numbers in the title', () => {
expect(getCounterValue(largeCounterTitle)).toEqual('8,756');
});

describe('removeUserAgentSpecifics', () => {
test('removes Electron and App specific info', () => {
const userAgentFallback =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) app-nativefier-804458/1.0.0 Chrome/89.0.4389.128 Electron/12.0.7 Safari/537.36';
expect(
removeUserAgentSpecifics(
userAgentFallback,
'app-nativefier-804458',
'1.0.0',
),
).not.toBe(
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36',
);
});
});
15 changes: 15 additions & 0 deletions app/src/helpers/helpers.ts
Expand Up @@ -160,3 +160,18 @@ export function getCounterValue(title: string): string {
const match = itemCountRegex.exec(title);
return match ? match[1] : undefined;
}

export function removeUserAgentSpecifics(
userAgentFallback: string,
appName: string,
appVersion: string,
): string {
// Electron userAgentFallback is the user agent used if none is specified when creating a window.
// For our purposes, it's useful because its format is similar enough to a real Chrome's user agent to not need
// to infer the userAgent. userAgentFallback normally looks like this:
// Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) app-nativefier-804458/1.0.0 Chrome/89.0.4389.128 Electron/12.0.7 Safari/537.36
// We just need to strip out the appName/1.0.0 and Electron/electronVersion
return userAgentFallback
.replace(`Electron/${process.versions.electron} `, '')
.replace(`${appName}/${appVersion} `, ' ');
}
10 changes: 9 additions & 1 deletion app/src/main.ts
Expand Up @@ -21,7 +21,7 @@ import {
APP_ARGS_FILE_PATH,
} from './components/mainWindow';
import { createTrayIcon } from './components/trayIcon';
import { isOSX } from './helpers/helpers';
import { isOSX, removeUserAgentSpecifics } from './helpers/helpers';
import { inferFlashPath } from './helpers/inferFlash';

// Entrypoint for Squirrel, a windows update framework. See https://github.com/nativefier/nativefier/pull/744
Expand All @@ -46,6 +46,14 @@ if (appArgs.portable) {
app.setPath('userData', path.join(__dirname, '..', 'appData'));
}

if (!appArgs.userAgentHonest) {
app.userAgentFallback = removeUserAgentSpecifics(
app.userAgentFallback,
app.getName(),
app.getVersion(),
);
}

// Take in a URL on the command line as an override
if (process.argv.length > 1) {
const maybeUrl = process.argv[1];
Expand Down
3 changes: 1 addition & 2 deletions src/build/prepareElectronApp.ts
Expand Up @@ -53,7 +53,6 @@ function pickElectronAppArgs(options: AppOptions): any {
height: options.nativefier.height,
helperBundleId: options.packager.helperBundleId,
hideWindowFrame: options.nativefier.hideWindowFrame,
honest: options.nativefier.honest,
ignoreCertificate: options.nativefier.ignoreCertificate,
ignoreGpuBlacklist: options.nativefier.ignoreGpuBlacklist,
insecure: options.nativefier.insecure,
Expand Down Expand Up @@ -83,7 +82,7 @@ function pickElectronAppArgs(options: AppOptions): any {
tray: options.nativefier.tray,
usageDescription: options.packager.usageDescription,
userAgent: options.nativefier.userAgent,
userAgentOverriden: options.nativefier.userAgentOverriden,
userAgentHonest: options.nativefier.userAgentHonest,
versionString: options.nativefier.versionString,
width: options.nativefier.width,
widevine: options.nativefier.widevine,
Expand Down
3 changes: 2 additions & 1 deletion src/cli.ts
Expand Up @@ -300,7 +300,8 @@ export function initArgs(argv: string[]): yargs.Argv<any> {
})
.option('u', {
alias: 'user-agent',
description: "set the app's user agent string",
description:
"set the app's user agent string; may also use 'edge', 'firefox', or 'safari' to have one auto-generated",
type: 'string',
})
.option('user-agent-honest', {
Expand Down
14 changes: 13 additions & 1 deletion src/constants.ts
Expand Up @@ -2,10 +2,22 @@ import * as path from 'path';

export const DEFAULT_APP_NAME = 'APP';

// Update both together, and update app / package.json / devDeps / electron
// Update both DEFAULT_ELECTRON_VERSION and DEFAULT_CHROME_VERSION together,
// and update app / package.json / devDeps / electron to value of DEFAULT_ELECTRON_VERSION
export const DEFAULT_ELECTRON_VERSION = '12.0.7';
export const DEFAULT_CHROME_VERSION = '89.0.4389.128';

// Update each of these periodically
// https://product-details.mozilla.org/1.0/firefox_versions.json
export const DEFAULT_FIREFOX_VERSION = '88.0.1';

// https://en.wikipedia.org/wiki/Safari_version_history
export const DEFAULT_SAFARI_VERSION = {
majorVersion: 14,
version: '14.0.3',
webkitVersion: '610.4.3.1.7',
};

export const ELECTRON_MAJOR_VERSION = parseInt(
DEFAULT_ELECTRON_VERSION.split('.')[0],
10,
Expand Down
7 changes: 0 additions & 7 deletions src/helpers/upgrade/upgrade.ts
Expand Up @@ -184,13 +184,6 @@ export function useOldAppOptions(
log.debug('rawOptions', rawOptions);
log.debug('oldApp', oldApp);

if (
oldApp.options.userAgentOverriden === undefined ||
oldApp.options.userAgentOverriden === false
) {
oldApp.options.userAgent = undefined;
}

const combinedOptions = { ...rawOptions, ...oldApp.options };

log.debug('Combined options', combinedOptions);
Expand Down
58 changes: 58 additions & 0 deletions src/infer/browsers/inferChromeVersion.ts
@@ -0,0 +1,58 @@
import axios from 'axios';
import * as log from 'loglevel';
import {
DEFAULT_CHROME_VERSION,
DEFAULT_ELECTRON_VERSION,
} from '../../constants';

type ElectronRelease = {
version: string;
date: string;
node: string;
v8: string;
uv: string;
zlib: string;
openssl: string;
modules: string;
chrome: string;
files: string[];
};

const ELECTRON_VERSIONS_URL = 'https://atom.io/download/atom-shell/index.json';

export async function getChromeVersionForElectronVersion(
electronVersion: string,
url = ELECTRON_VERSIONS_URL,
): Promise<string> {
if (!electronVersion || electronVersion === DEFAULT_ELECTRON_VERSION) {
// Exit quickly for the scenario that we already know about
return DEFAULT_CHROME_VERSION;
}

try {
log.debug('Grabbing electron<->chrome versions file from', url);
const response = await axios.get(url, { timeout: 5000 });
if (response.status !== 200) {
throw new Error(`Bad request: Status code ${response.status}`);
}
const electronReleases: ElectronRelease[] = response.data;
const electronVersionToChromeVersion: { [key: string]: string } = {};
for (const release of electronReleases) {
electronVersionToChromeVersion[release.version] = release.chrome;
}
if (!(electronVersion in electronVersionToChromeVersion)) {
throw new Error(
`Electron version '${electronVersion}' not found in retrieved version list!`,
);
}
const chromeVersion = electronVersionToChromeVersion[electronVersion];
log.debug(
`Associated electron v${electronVersion} to chrome v${chromeVersion}`,
);
return chromeVersion;
} catch (err) {
log.error('getChromeVersionForElectronVersion ERROR', err);
log.debug('Falling back to default Chrome version', DEFAULT_CHROME_VERSION);
return DEFAULT_CHROME_VERSION;
}
}
49 changes: 49 additions & 0 deletions src/infer/browsers/inferFirefoxVersion.ts
@@ -0,0 +1,49 @@
import axios from 'axios';
import * as log from 'loglevel';
import { DEFAULT_FIREFOX_VERSION } from '../../constants';

type FirefoxVersions = {
FIREFOX_AURORA: string;
FIREFOX_DEVEDITION: string;
FIREFOX_ESR: string;
FIREFOX_ESR_NEXT: string;
FIREFOX_NIGHTLY: string;
LAST_MERGE_DATE: string;
LAST_RELEASE_DATE: string;
LAST_SOFTFREEZE_DATE: string;
LATEST_FIREFOX_DEVEL_VERSION: string;
LATEST_FIREFOX_OLDER_VERSION: string;
LATEST_FIREFOX_RELEASED_DEVEL_VERSION: string;
LATEST_FIREFOX_VERSION: string;
NEXT_MERGE_DATE: string;
NEXT_RELEASE_DATE: string;
NEXT_SOFTFREEZE_DATE: string;
};

const FIREFOX_VERSIONS_URL =
'https://product-details.mozilla.org/1.0/firefox_versions.json';

export async function getLatestFirefoxVersion(
url = FIREFOX_VERSIONS_URL,
): Promise<string> {
try {
log.debug('Grabbing Firefox version data from', url);
const response = await axios.get(url, { timeout: 5000 });
if (response.status !== 200) {
throw new Error(`Bad request: Status code ${response.status}`);
}
const firefoxVersions: FirefoxVersions = response.data;

TheCleric marked this conversation as resolved.
Show resolved Hide resolved
log.debug(
`Got latest Firefox version ${firefoxVersions.LATEST_FIREFOX_VERSION}`,
);
return firefoxVersions.LATEST_FIREFOX_VERSION;
} catch (err) {
log.error('getLatestFirefoxVersion ERROR', err);
log.debug(
'Falling back to default Firefox version',
DEFAULT_FIREFOX_VERSION,
);
return DEFAULT_FIREFOX_VERSION;
}
}