diff --git a/app/src/helpers/helpers.ts b/app/src/helpers/helpers.ts index 60952ae..43cd55f 100644 --- a/app/src/helpers/helpers.ts +++ b/app/src/helpers/helpers.ts @@ -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 */ diff --git a/app/src/preload.ts b/app/src/preload.ts index f1832aa..6fdaa04 100644 --- a/app/src/preload.ts +++ b/app/src/preload.ts @@ -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) { diff --git a/docs/api.md b/docs/api.md index 53eebbb..0198747 100644 --- a/docs/api.md +++ b/docs/api.md @@ -514,7 +514,7 @@ Forces the maximum disk space to be used by the disk cache. Value is given in by --inject ``` -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. diff --git a/src/build/prepareElectronApp.ts b/src/build/prepareElectronApp.ts index 84cf90e..99a8783 100644 --- a/src/build/prepareElectronApp.ts +++ b/src/build/prepareElectronApp.ts @@ -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 { 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 { ); } - 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); } diff --git a/src/helpers/helpers.test.ts b/src/helpers/helpers.test.ts index fd4492e..f1d22ff 100644 --- a/src/helpers/helpers.test.ts +++ b/src/helpers/helpers.test.ts @@ -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); + }); +}); diff --git a/src/helpers/helpers.ts b/src/helpers/helpers.ts index 04eef50..72d80e8 100644 --- a/src/helpers/helpers.ts +++ b/src/helpers/helpers.ts @@ -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); +}