Fix injecting multiple css/js files (fix #458) (#1162)

* Add ability to inject multiple css/js files

* API doc: Move misplaced macOS shortcuts doc (PR #1158)

When I added this documentation originally, I guess I placed it in the wrong location.

* README: use quotes in example, to divert users from shell globbing pitfalls

Follow-up of https://github.com/nativefier/nativefier/issues/1159#issuecomment-827184112

* Support opening URLs passed as arg to Nativefied application (fix #405) (PR #1154)

Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>

* macOS: Fix crash when using --tray (fix #527), and invisible icon (fix #942, fix #668) (#1156)

This fixes:

1. A startup crash on macOS when using the `--tray` option; see #527.
  ![image](https://user-images.githubusercontent.com/22625791/115987741-99544600-a5b6-11eb-866a-dadb5640eecb.png)
2. Invisible tray icon on macOS; see #942 and #668.  
   ![image](https://user-images.githubusercontent.com/22625791/115988276-24364000-a5b9-11eb-80c3-561a8a646754.png)

Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>

* API.md / --widevine: document signing apps to make some sites like HBO Max & Udemy work (fix #1147)

* Prompt to confirm when page is attempting to prevent unload (#1163)

Should alleviate part of the issue in #1151

* Add an option to upgrade an existing app (fix #1131) (PR #1138)

This adds a `--upgrade` option to upgrade-in-place an old app, re-using its options it can.
Should help fix #1131

Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>

* Bump to Electron 12.0.5 with Chrome 89.0.4389.128

* Add newly discovered Google internal login page (#1167)

* Fix Widevine by properly listening to widevine-... events, and update docs (fix #1153) (PR #1164)

As documented in #1153, for Widevine support to work properly, we need to listen for the Widevine ready event, and as well for certain sites the app must be signed.

This PR adds the events, and as well adds better documentation on the signing limitation.

This may also help resolve #1147

* Improve suffix creation + tests

* API: clarif in existing doc by the way

* Typo

Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>
Co-authored-by: Ben Curtis <github@nosolutions.com>
Co-authored-by: Fabian Wolf <22625791+fabiwlf@users.noreply.github.com>
This commit is contained in:
Adam Weeden 2021-04-30 11:04:10 -04:00 committed by GitHub
parent 1ad1844619
commit dd6e15fb5c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 80 additions and 35 deletions

View File

@ -4,7 +4,7 @@ import * as path from 'path';
import { BrowserWindow } from 'electron';
const INJECT_CSS_PATH = path.join(__dirname, '..', 'inject/inject.css');
export const INJECT_DIR = path.join(__dirname, '..', 'inject');
export function isOSX(): boolean {
return os.platform() === 'darwin';
@ -80,17 +80,29 @@ export function linkIsInternal(
export function shouldInjectCss(): boolean {
try {
fs.accessSync(INJECT_CSS_PATH);
return true;
return fs.existsSync(INJECT_DIR);
} catch (e) {
return false;
}
}
export function getCssToInject(): string {
return fs.readFileSync(INJECT_CSS_PATH).toString();
let cssToInject = '';
const cssFiles = fs
.readdirSync(INJECT_DIR, { withFileTypes: true })
.filter(
(injectFile) => injectFile.isFile() && injectFile.name.endsWith('.css'),
)
.map((cssFileStat) =>
path.resolve(path.join(INJECT_DIR, cssFileStat.name)),
);
for (const cssFile of cssFiles) {
console.log(`Injecting CSS file: ${cssFile}`);
const cssFileData = fs.readFileSync(cssFile);
cssToInject += `/* ${cssFile} */\n\n ${cssFileData}\n\n`;
}
return cssToInject;
}
/**
* Helper to print debug messages from the main process in the browser window
*/

View File

@ -12,7 +12,8 @@ import * as path from 'path';
import { ipcRenderer } from 'electron';
const INJECT_JS_PATH = path.join(__dirname, '..', 'inject/inject.js');
export const INJECT_DIR = path.join(__dirname, '..', 'inject');
/**
* Patches window.Notification to:
* - set a callback on a new Notification
@ -38,12 +39,25 @@ function setNotificationCallback(createCallback, clickCallback) {
}
function injectScripts() {
const needToInject = fs.existsSync(INJECT_JS_PATH);
const needToInject = fs.existsSync(INJECT_DIR);
if (!needToInject) {
return;
}
// Dynamically require scripts
require(INJECT_JS_PATH);
try {
const jsFiles = fs
.readdirSync(INJECT_DIR, { withFileTypes: true })
.filter(
(injectFile) => injectFile.isFile() && injectFile.name.endsWith('.js'),
)
.map((jsFileStat) => path.join('..', 'inject', jsFileStat.name));
for (const jsFile of jsFiles) {
console.log(`Injecting JS file: ${jsFile}`);
require(jsFile);
}
} catch (error) {
console.warn('Error encoutered injecting JS files', error);
}
}
function notifyNotificationCreate(title, opt) {

View File

@ -514,7 +514,7 @@ Forces the maximum disk space to be used by the disk cache. Value is given in by
--inject <value>
```
Allows you to inject a javascript or css file. This command can be run multiple times to inject the files.
Allows you to inject javascript or css files. This command can be repeated multiple times to inject multiple files.
_Note:_ The javascript file is loaded _after_ `DOMContentLoaded`, so you can assume the DOM is complete & available.

View File

@ -1,11 +1,10 @@
import * as fs from 'fs';
import * as crypto from 'crypto';
import * as path from 'path';
import { promisify } from 'util';
import * as log from 'loglevel';
import { copyFileOrDir } from '../helpers/helpers';
import { copyFileOrDir, generateRandomSuffix } from '../helpers/helpers';
import { AppOptions } from '../options/model';
const writeFileAsync = promisify(fs.writeFile);
@ -103,6 +102,8 @@ async function maybeCopyScripts(srcs: string[], dest: string): Promise<void> {
return;
}
const supportedInjectionExtensions = ['.css', '.js'];
log.debug(`Copying ${srcs.length} files to inject in app.`);
for (const src of srcs) {
if (!fs.existsSync(src)) {
@ -111,26 +112,22 @@ async function maybeCopyScripts(srcs: string[], dest: string): Promise<void> {
);
}
let destFileName: string;
if (path.extname(src) === '.js') {
destFileName = 'inject.js';
} else if (path.extname(src) === '.css') {
destFileName = 'inject.css';
} else {
return;
if (supportedInjectionExtensions.indexOf(path.extname(src)) < 0) {
console.log(`Skipping unsupported injection file: ${src}`);
continue;
}
const postFixHash = generateRandomSuffix();
const destFileName = `inject-${postFixHash}.${path.extname(src)}`;
const destPath = path.join(dest, 'inject', destFileName);
log.debug(`Copying injection file "${src}" to "${destPath}"`);
await copyFileOrDir(src, destPath);
}
}
function normalizeAppName(appName: string, url: string): string {
// use a simple 3 byte random string to prevent collision
const hash = crypto.createHash('md5');
hash.update(url);
const postFixHash = hash.digest('hex').substring(0, 6);
function normalizeAppName(appName: string): string {
// use a simple random string to prevent collisions
const postFixHash = generateRandomSuffix();
const normalized = appName
.toLowerCase()
.replace(/[,:.]/g, '')
@ -138,14 +135,10 @@ function normalizeAppName(appName: string, url: string): string {
return `${normalized}-nativefier-${postFixHash}`;
}
function changeAppPackageJsonName(
appPath: string,
name: string,
url: string,
): void {
function changeAppPackageJsonName(appPath: string, name: string): void {
const packageJsonPath = path.join(appPath, '/package.json');
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath).toString());
const normalizedAppName = normalizeAppName(name, url);
const normalizedAppName = normalizeAppName(name);
packageJson.name = normalizedAppName;
log.debug(`Updating ${packageJsonPath} 'name' field to ${normalizedAppName}`);
@ -189,9 +182,5 @@ export async function prepareElectronApp(
} catch (err) {
log.error('Error copying injection files.', err);
}
changeAppPackageJsonName(
dest,
options.packager.name,
options.packager.targetUrl,
);
changeAppPackageJsonName(dest, options.packager.name);
}

View File

@ -1,4 +1,4 @@
import { isArgFormatInvalid } from './helpers';
import { isArgFormatInvalid, generateRandomSuffix } from './helpers';
describe('isArgFormatInvalid', () => {
test('is false for correct short args', () => {
@ -34,3 +34,25 @@ describe('isArgFormatInvalid', () => {
expect(isArgFormatInvalid('--test-run-with-many-dashes')).toBe(false);
});
});
describe('generateRandomSuffix', () => {
test('is not empty', () => {
expect(generateRandomSuffix()).not.toBe('');
});
test('is not null', () => {
expect(generateRandomSuffix()).not.toBeNull();
});
test('is not undefined', () => {
expect(generateRandomSuffix()).toBeDefined();
});
test('is different per call', () => {
expect(generateRandomSuffix()).not.toBe(generateRandomSuffix());
});
test('respects the length param', () => {
expect(generateRandomSuffix(10).length).toBe(10);
});
});

View File

@ -1,4 +1,5 @@
import { spawnSync } from 'child_process';
import * as crypto from 'crypto';
import * as os from 'os';
import * as path from 'path';
@ -163,3 +164,10 @@ export function isArgFormatInvalid(arg: string): boolean {
!['--x', '--y'].includes(arg) // exception for long args --{x,y}
);
}
export function generateRandomSuffix(length = 6): string {
const hash = crypto.createHash('md5');
// Add a random salt to help avoid collisions
hash.update(crypto.randomBytes(256));
return hash.digest('hex').substring(0, length);
}