2
2
mirror of https://github.com/Llewellynvdm/nativefier.git synced 2024-11-11 07:41:04 +00:00

Compare commits

..

No commits in common. "45d7981761249c0acb5e0f0923502353a1f8ba58" and "be418d4349c5c13f17faf0e91416818367c2cfb5" have entirely different histories.

21 changed files with 8272 additions and 2309 deletions

View File

@ -8,7 +8,7 @@ jobs:
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2 # Setup .npmrc file to publish to npm
- uses: actions/setup-node@v1 # Setup .npmrc file to publish to npm
with:
node-version: '20' # Align the version of Node here with ci.yml.
registry-url: 'https://registry.npmjs.org'
@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2 # Setup .npmrc file to publish to npm
- uses: actions/setup-node@v1 # Setup .npmrc file to publish to npm
with:
node-version: '20' # Align the version of Node here with ci.yml.
registry-url: 'https://registry.npmjs.org'

1
.gitignore vendored
View File

@ -44,6 +44,7 @@ build/Release
# Dependency directory
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
node_modules
.nvmrc
# Python virtual environment in case it's created for the Castlabs code signing tool
venv

1
.nvmrc
View File

@ -1 +0,0 @@
16

View File

@ -1,10 +1,4 @@
52.0.0 / 2023-08-25
===================
**[BREAKING]**
* Update to Electron 25.7 (#1566)
* Update to Electron 25 (#1559)
51.0.1 / 2023-08-04
===================

1333
app/npm-shrinkwrap.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,14 +12,14 @@
],
"scripts": {},
"dependencies": {
"electron-context-menu": "^3.6.1",
"electron-dl": "^3.5.0",
"electron-context-menu": "^3.1.1",
"electron-dl": "^3.2.1",
"electron-squirrel-startup": "^1.0.0",
"electron-window-state": "^5.0.3",
"loglevel": "^1.8.1",
"source-map-support": "^0.5.21"
"loglevel": "^1.7.1",
"source-map-support": "^0.5.19"
},
"devDependencies": {
"electron": "^25.7.0"
"electron": "^21.4.4"
}
}

View File

@ -1,7 +1,7 @@
import {
BrowserWindow,
ContextMenuParams,
Event as ElectronEvent,
NewWindowWebContentsEvent,
} from 'electron';
import contextMenu from 'electron-context-menu';
@ -25,7 +25,7 @@ export function initContextMenu(
prepend: (actions: contextMenu.Actions, params: ContextMenuParams) => {
log.debug('contextMenu.prepend', { actions, params });
const items = [];
if (params.linkURL && window) {
if (params.linkURL) {
items.push({
label: 'Open Link in Default Browser',
click: () => {
@ -38,40 +38,36 @@ export function initContextMenu(
label: 'Open Link in New Window',
click: () =>
createNewWindow(
outputOptionsToWindowOptions(options, nativeTabsSupported()),
outputOptionsToWindowOptions(options),
setupNativefierWindow,
params.linkURL,
// window,
window,
),
});
if (nativeTabsSupported()) {
items.push({
label: 'Open Link in New Tab',
click: () =>
// // Fire a new window event for a foreground tab
// // Previously we called createNewTab directly, but it had incosistent and buggy behavior
// // as it was mostly designed for running off of events. So this will create a new event
// // for a foreground-tab for the event handler to grab and take care of instead.
// (window as BrowserWindow).webContents.emit(
// // event name
// 'new-window',
// // event object
// {
// // Leave to the default for a NewWindowWebContentsEvent
// newGuest: undefined,
// ...new Event('new-window'),
// }, // as NewWindowWebContentsEvent,
// // url
// params.linkURL,
// // frameName
// window?.webContents.mainFrame.name ?? '',
// // disposition
// 'foreground-tab',
// ),
window.emit('new-window-for-tab', {
...new Event('new-window-for-tab'),
url: params.linkURL,
} as ElectronEvent<{ url: string }>),
// Fire a new window event for a foreground tab
// Previously we called createNewTab directly, but it had incosistent and buggy behavior
// as it was mostly designed for running off of events. So this will create a new event
// for a foreground-tab for the event handler to grab and take care of instead.
(window as BrowserWindow).webContents.emit(
// event name
'new-window',
// event object
{
// Leave to the default for a NewWindowWebContentsEvent
newGuest: undefined,
...new Event('new-window'),
} as NewWindowWebContentsEvent,
// url
params.linkURL,
// frameName
window?.webContents.mainFrame.name ?? '',
// disposition
'foreground-tab',
),
});
}
}

View File

@ -3,19 +3,15 @@ import * as path from 'path';
import { BrowserWindow, ipcMain } from 'electron';
import * as log from '../helpers/loggingHelper';
import { nativeTabsSupported } from '../helpers/helpers';
export async function createLoginWindow(
loginCallback: (username?: string, password?: string) => void,
parent?: BrowserWindow,
): Promise<BrowserWindow> {
log.debug('createLoginWindow', {
loginCallback,
parent,
});
log.debug('createLoginWindow', { loginCallback, parent });
const loginWindow = new BrowserWindow({
parent: nativeTabsSupported() ? undefined : parent,
parent,
width: 300,
height: 400,
frame: false,

View File

@ -1,13 +1,7 @@
import * as fs from 'fs';
import * as path from 'path';
import {
desktopCapturer,
ipcMain,
BrowserWindow,
Event,
HandlerDetails,
} from 'electron';
import { ipcMain, desktopCapturer, BrowserWindow, Event } from 'electron';
import windowStateKeeper from 'electron-window-state';
import { initContextMenu } from './contextMenu';
@ -42,9 +36,9 @@ type SessionInteractionRequest = {
propertyValue?: unknown;
};
type SessionInteractionResult<T = unknown> = {
type SessionInteractionResult = {
id?: string;
value?: T | Promise<T>;
value?: unknown | Promise<unknown>;
error?: Error;
};
@ -84,9 +78,7 @@ export async function createMainWindow(
// So, we manually mainWindow.show() later, see a few lines below
show: options.tray !== 'start-in-tray' && process.platform !== 'win32',
backgroundColor: options.backgroundColor,
...getDefaultWindowOptions(
outputOptionsToWindowOptions(options, nativeTabsSupported()),
),
...getDefaultWindowOptions(outputOptionsToWindowOptions(options)),
});
// Just load about:blank to start, gives playwright something to latch onto initially for testing.
@ -110,32 +102,44 @@ export async function createMainWindow(
mainWindow.show();
}
const windowOptions = outputOptionsToWindowOptions(
options,
nativeTabsSupported(),
);
const windowOptions = outputOptionsToWindowOptions(options);
createMenu(options, mainWindow);
createContextMenu(options, mainWindow);
setupNativefierWindow(windowOptions, mainWindow);
// .on('new-window', ...) is deprected in favor of setWindowOpenHandler(...)
// We can't quite cut over to that yet for a few reasons:
// 1. Our version of Electron does not yet support a parameter to
// setWindowOpenHandler that contains `disposition', which we need.
// See https://github.com/electron/electron/issues/28380
// 2. setWindowOpenHandler doesn't support newGuest as well
// Though at this point, 'new-window' bugs seem to be coming up and downstream
// users are being pointed to use setWindowOpenHandler.
// E.g., https://github.com/electron/electron/issues/28374
// Note it is important to add these handlers only to the *main* window,
// else we run into weird behavior like opening tabs twice
mainWindow.webContents.setWindowOpenHandler((details: HandlerDetails) => {
return onNewWindow(
mainWindow.webContents.on(
'new-window',
(event, url, frameName, disposition) => {
onNewWindow(
windowOptions,
setupNativefierWindow,
details,
mainWindow,
event,
url,
frameName,
disposition,
).catch((err) => log.error('onNewWindow ERROR', err));
},
);
});
mainWindow.on('new-window-for-tab', (event?: Event<{ url?: string }>) => {
log.debug('mainWindow.new-window-for-tab', { event });
// @ts-expect-error new-tab isn't in the type definition, but it does exist
mainWindow.on('new-tab', () => {
createNewTab(
windowOptions,
setupNativefierWindow,
event?.url ?? options.targetUrl,
options.targetUrl,
true,
// mainWindow,
mainWindow,
);
});
@ -150,7 +154,7 @@ export async function createMainWindow(
mainWindow.show();
});
setupSessionInteraction(mainWindow);
setupSessionInteraction(options, mainWindow);
setupSessionPermissionHandler(mainWindow);
if (options.clearCache) {
@ -261,7 +265,10 @@ function setupNotificationBadge(
});
}
function setupSessionInteraction(window: BrowserWindow): void {
function setupSessionInteraction(
options: OutputOptions,
window: BrowserWindow,
): void {
// See API.md / "Accessing The Electron Session"
ipcMain.on(
'session-interaction',

View File

@ -2,7 +2,7 @@ jest.mock('./helpers');
jest.mock('./windowEvents');
jest.mock('./windowHelpers');
import { dialog, BrowserWindow, HandlerDetails, WebContents } from 'electron';
import { dialog, BrowserWindow } from 'electron';
import { WindowOptions } from '../../../shared/src/options/model';
import { linkIsInternal, openExternal, nativeTabsSupported } from './helpers';
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
@ -14,9 +14,11 @@ const {
onNewWindowHelper: (
options: WindowOptions,
setupWindow: (options: WindowOptions, window: BrowserWindow) => void,
details: Partial<HandlerDetails>,
urlToGo: string,
disposition: string | undefined,
preventDefault: (newGuest: BrowserWindow) => void,
parent?: BrowserWindow,
) => ReturnType<Parameters<WebContents['setWindowOpenHandler']>[0]>;
) => Promise<void>;
onWillNavigate: (
options: {
blockExternalUrls: boolean;
@ -41,13 +43,12 @@ describe('onNewWindowHelper', () => {
const foregroundDisposition = 'foreground-tab';
const backgroundDisposition = 'background-tab';
const baseOptions = {
autoHideMenuBar: true,
blockExternalUrls: false,
insecure: false,
name: 'TEST_APP',
targetUrl: originalURL,
zoom: 1.0,
} as WindowOptions;
};
const mockShowNavigationBlockedMessage: jest.SpyInstance =
showNavigationBlockedMessage as jest.Mock;
const mockCreateAboutBlank: jest.SpyInstance =
@ -59,6 +60,7 @@ describe('onNewWindowHelper', () => {
const mockNativeTabsSupported: jest.SpyInstance =
nativeTabsSupported as jest.Mock;
const mockOpenExternal: jest.SpyInstance = openExternal as jest.Mock;
const preventDefault = jest.fn();
const setupWindow = jest.fn();
beforeEach(() => {
@ -70,6 +72,7 @@ describe('onNewWindowHelper', () => {
mockLinkIsInternal.mockReset().mockReturnValue(true);
mockNativeTabsSupported.mockReset().mockReturnValue(false);
mockOpenExternal.mockReset();
preventDefault.mockReset();
setupWindow.mockReset();
});
@ -82,84 +85,105 @@ describe('onNewWindowHelper', () => {
mockOpenExternal.mockRestore();
});
test('internal urls should not be handled', () => {
const result = onNewWindowHelper(baseOptions, setupWindow, {
url: internalURL,
});
test('internal urls should not be handled', async () => {
await onNewWindowHelper(
baseOptions,
setupWindow,
internalURL,
undefined,
preventDefault,
);
expect(mockCreateAboutBlank).not.toHaveBeenCalled();
expect(mockCreateNewTab).not.toHaveBeenCalled();
expect(mockShowNavigationBlockedMessage).not.toHaveBeenCalled();
expect(mockOpenExternal).not.toHaveBeenCalled();
expect(result.action).toEqual('allow');
expect(preventDefault).not.toHaveBeenCalled();
});
test('external urls should be opened externally', () => {
test('external urls should be opened externally', async () => {
mockLinkIsInternal.mockReturnValue(false);
const result = onNewWindowHelper(baseOptions, setupWindow, {
url: externalURL,
});
await onNewWindowHelper(
baseOptions,
setupWindow,
externalURL,
undefined,
preventDefault,
);
expect(mockCreateAboutBlank).not.toHaveBeenCalled();
expect(mockCreateNewTab).not.toHaveBeenCalled();
expect(mockShowNavigationBlockedMessage).not.toHaveBeenCalled();
expect(mockOpenExternal).toHaveBeenCalledTimes(1);
expect(result.action).toEqual('deny');
expect(preventDefault).toHaveBeenCalledTimes(1);
});
test('external urls should be ignored if blockExternalUrls is true', () => {
test('external urls should be ignored if blockExternalUrls is true', async () => {
mockLinkIsInternal.mockReturnValue(false);
const options = {
...baseOptions,
blockExternalUrls: true,
};
const result = onNewWindowHelper(options, setupWindow, {
url: externalURL,
});
await onNewWindowHelper(
options,
setupWindow,
externalURL,
undefined,
preventDefault,
);
expect(mockCreateAboutBlank).not.toHaveBeenCalled();
expect(mockCreateNewTab).not.toHaveBeenCalled();
expect(mockShowNavigationBlockedMessage).toHaveBeenCalledTimes(1);
expect(mockOpenExternal).not.toHaveBeenCalled();
expect(result.action).toEqual('deny');
expect(preventDefault).toHaveBeenCalledTimes(1);
});
test('tab disposition should be ignored if tabs are not enabled', () => {
const result = onNewWindowHelper(baseOptions, setupWindow, {
url: internalURL,
disposition: foregroundDisposition,
});
test('tab disposition should be ignored if tabs are not enabled', async () => {
await onNewWindowHelper(
baseOptions,
setupWindow,
internalURL,
foregroundDisposition,
preventDefault,
);
expect(mockCreateAboutBlank).not.toHaveBeenCalled();
expect(mockCreateNewTab).not.toHaveBeenCalled();
expect(mockShowNavigationBlockedMessage).not.toHaveBeenCalled();
expect(mockOpenExternal).not.toHaveBeenCalled();
expect(result.action).toEqual('allow');
expect(preventDefault).not.toHaveBeenCalled();
});
test('tab disposition should be ignored if url is external', () => {
test('tab disposition should be ignored if url is external', async () => {
mockLinkIsInternal.mockReturnValue(false);
const result = onNewWindowHelper(baseOptions, setupWindow, {
url: externalURL,
disposition: foregroundDisposition,
});
await onNewWindowHelper(
baseOptions,
setupWindow,
externalURL,
foregroundDisposition,
preventDefault,
);
expect(mockCreateAboutBlank).not.toHaveBeenCalled();
expect(mockCreateNewTab).not.toHaveBeenCalled();
expect(mockShowNavigationBlockedMessage).not.toHaveBeenCalled();
expect(mockOpenExternal).toHaveBeenCalledTimes(1);
expect(result.action).toEqual('deny');
expect(preventDefault).toHaveBeenCalledTimes(1);
});
test('foreground tabs with internal urls should be opened in the foreground', () => {
test('foreground tabs with internal urls should be opened in the foreground', async () => {
mockNativeTabsSupported.mockReturnValue(true);
const result = onNewWindowHelper(baseOptions, setupWindow, {
url: internalURL,
disposition: foregroundDisposition,
});
await onNewWindowHelper(
baseOptions,
setupWindow,
internalURL,
foregroundDisposition,
preventDefault,
);
expect(mockCreateAboutBlank).not.toHaveBeenCalled();
expect(mockCreateNewTab).toHaveBeenCalledTimes(1);
@ -168,19 +192,23 @@ describe('onNewWindowHelper', () => {
setupWindow,
internalURL,
true,
undefined,
);
expect(mockShowNavigationBlockedMessage).not.toHaveBeenCalled();
expect(mockOpenExternal).not.toHaveBeenCalled();
expect(result.action).toEqual('deny');
expect(preventDefault).toHaveBeenCalledTimes(1);
});
test('background tabs with internal urls should be opened in background tabs', () => {
test('background tabs with internal urls should be opened in background tabs', async () => {
mockNativeTabsSupported.mockReturnValue(true);
const result = onNewWindowHelper(baseOptions, setupWindow, {
url: internalURL,
disposition: backgroundDisposition,
});
await onNewWindowHelper(
baseOptions,
setupWindow,
internalURL,
backgroundDisposition,
preventDefault,
);
expect(mockCreateAboutBlank).not.toHaveBeenCalled();
expect(mockCreateNewTab).toHaveBeenCalledTimes(1);
@ -189,46 +217,59 @@ describe('onNewWindowHelper', () => {
setupWindow,
internalURL,
false,
undefined,
);
expect(mockShowNavigationBlockedMessage).not.toHaveBeenCalled();
expect(mockOpenExternal).not.toHaveBeenCalled();
expect(result.action).toEqual('deny');
expect(preventDefault).toHaveBeenCalledTimes(1);
});
test('about:blank urls should be handled', () => {
const result = onNewWindowHelper(baseOptions, setupWindow, {
url: 'about:blank',
});
test('about:blank urls should be handled', async () => {
await onNewWindowHelper(
baseOptions,
setupWindow,
'about:blank',
undefined,
preventDefault,
);
expect(mockCreateAboutBlank).toHaveBeenCalledTimes(1);
expect(mockCreateNewTab).not.toHaveBeenCalled();
expect(mockShowNavigationBlockedMessage).not.toHaveBeenCalled();
expect(mockOpenExternal).not.toHaveBeenCalled();
expect(result.action).toEqual('deny');
expect(preventDefault).toHaveBeenCalledTimes(1);
});
test('about:blank#blocked urls should be handled', () => {
const result = onNewWindowHelper(baseOptions, setupWindow, {
url: 'about:blank#blocked',
});
test('about:blank#blocked urls should be handled', async () => {
await onNewWindowHelper(
baseOptions,
setupWindow,
'about:blank#blocked',
undefined,
preventDefault,
);
expect(mockCreateAboutBlank).toHaveBeenCalledTimes(1);
expect(mockCreateNewTab).not.toHaveBeenCalled();
expect(mockShowNavigationBlockedMessage).not.toHaveBeenCalled();
expect(mockOpenExternal).not.toHaveBeenCalled();
expect(result.action).toEqual('deny');
expect(preventDefault).toHaveBeenCalledTimes(1);
});
test('about:blank#other urls should not be handled', () => {
const result = onNewWindowHelper(baseOptions, setupWindow, {
url: 'about:blank#other',
});
test('about:blank#other urls should not be handled', async () => {
await onNewWindowHelper(
baseOptions,
setupWindow,
'about:blank#other',
undefined,
preventDefault,
);
expect(mockCreateAboutBlank).not.toHaveBeenCalled();
expect(mockCreateNewTab).not.toHaveBeenCalled();
expect(mockShowNavigationBlockedMessage).not.toHaveBeenCalled();
expect(mockOpenExternal).not.toHaveBeenCalled();
expect(result.action).toEqual('allow');
expect(preventDefault).not.toHaveBeenCalled();
});
});

View File

@ -2,8 +2,8 @@ import {
dialog,
BrowserWindow,
Event,
NewWindowWebContentsEvent,
WebContents,
HandlerDetails,
} from 'electron';
import { linkIsInternal, nativeTabsSupported, openExternal } from './helpers';
@ -18,64 +18,84 @@ import {
} from './windowHelpers';
import { WindowOptions } from '../../../shared/src/options/model';
type NewWindowHandlerResult = ReturnType<
Parameters<WebContents['setWindowOpenHandler']>[0]
>;
export function onNewWindow(
options: WindowOptions,
setupWindow: (options: WindowOptions, window: BrowserWindow) => void,
details: HandlerDetails,
event: NewWindowWebContentsEvent,
urlToGo: string,
frameName: string,
disposition:
| 'default'
| 'foreground-tab'
| 'background-tab'
| 'new-window'
| 'save-to-disk'
| 'other',
parent?: BrowserWindow,
): NewWindowHandlerResult {
): Promise<void> {
log.debug('onNewWindow', {
details,
event,
urlToGo,
frameName,
disposition,
parent,
});
const preventDefault = (newGuest?: BrowserWindow): void => {
log.debug('onNewWindow.preventDefault', { newGuest, event });
if (event.preventDefault) {
event.preventDefault();
}
if (newGuest) {
event.newGuest = newGuest;
}
};
return onNewWindowHelper(
options,
setupWindow,
details,
nativeTabsSupported() ? undefined : parent,
urlToGo,
disposition,
preventDefault,
parent,
);
}
export function onNewWindowHelper(
options: WindowOptions,
setupWindow: (options: WindowOptions, window: BrowserWindow) => void,
details: HandlerDetails,
urlToGo: string,
disposition: string | undefined,
preventDefault: (newGuest?: BrowserWindow) => void,
parent?: BrowserWindow,
): NewWindowHandlerResult {
): Promise<void> {
log.debug('onNewWindowHelper', {
options,
details,
urlToGo,
disposition,
preventDefault,
parent,
});
try {
if (
!linkIsInternal(
options.targetUrl,
details.url,
urlToGo,
options.internalUrls,
options.strictInternalUrls,
)
) {
preventDefault();
if (options.blockExternalUrls) {
return new Promise((resolve) => {
showNavigationBlockedMessage(
`Navigation to external URL blocked by options: ${details.url}`,
`Navigation to external URL blocked by options: ${urlToGo}`,
)
.then(() => {
// blockExternalURL(details.url).then(resolve).catch((err: unknown) => {
// log.error('blockExternalURL', err);
// });
})
.then(() => resolve())
.catch((err: unknown) => {
throw err;
});
return { action: 'deny' };
} else {
openExternal(details.url).catch((err: unknown) => {
log.error('openExternal', err);
});
return { action: 'deny' };
} else {
return openExternal(urlToGo);
}
}
// Normally the following would be:
@ -83,25 +103,26 @@ export function onNewWindowHelper(
// But due to a bug we resolved in https://github.com/nativefier/nativefier/issues/1197
// Some sites use about:blank#something to use as placeholder windows to fill
// with content via JavaScript. So we'll stay specific for now...
else if (['about:blank', 'about:blank#blocked'].includes(details.url)) {
createAboutBlankWindow(
options,
setupWindow,
nativeTabsSupported() ? undefined : parent,
else if (['about:blank', 'about:blank#blocked'].includes(urlToGo)) {
return Promise.resolve(
preventDefault(createAboutBlankWindow(options, setupWindow, parent)),
);
return { action: 'deny' };
} else if (nativeTabsSupported()) {
return Promise.resolve(
preventDefault(
createNewTab(
options,
setupWindow,
details.url,
details.disposition === 'foreground-tab',
urlToGo,
disposition === 'foreground-tab',
parent,
),
),
);
return { action: 'deny' };
}
return { action: 'allow' };
return Promise.resolve(undefined);
} catch (err: unknown) {
return { action: 'deny' };
return Promise.reject(err);
}
}

View File

@ -52,15 +52,14 @@ describe('clearAppData', () => {
});
describe('createNewTab', () => {
// const window = new BrowserWindow();
const window = new BrowserWindow();
const options: WindowOptions = {
autoHideMenuBar: true,
blockExternalUrls: false,
insecure: false,
name: 'Test App',
targetUrl: 'https://github.com/nativefier/natifefier',
zoom: 1.0,
} as WindowOptions;
};
const setupWindow = jest.fn();
const url = 'https://github.com/nativefier/nativefier';
const mockAddTabbedWindow: jest.SpyInstance = jest.spyOn(
@ -79,7 +78,7 @@ describe('createNewTab', () => {
test('creates new foreground tab', () => {
const foreground = true;
const tab = createNewTab(options, setupWindow, url, foreground);
const tab = createNewTab(options, setupWindow, url, foreground, window);
expect(mockAddTabbedWindow).toHaveBeenCalledWith(tab);
expect(setupWindow).toHaveBeenCalledWith(options, tab);
@ -90,13 +89,7 @@ describe('createNewTab', () => {
test('creates new background tab', () => {
const foreground = false;
const tab = createNewTab(
options,
setupWindow,
url,
foreground,
// window
);
const tab = createNewTab(options, setupWindow, url, foreground, window);
expect(mockAddTabbedWindow).toHaveBeenCalledWith(tab);
expect(setupWindow).toHaveBeenCalledWith(options, tab);

View File

@ -13,7 +13,6 @@ import {
import { getCSSToInject, isOSX, nativeTabsSupported } from './helpers';
import * as log from './loggingHelper';
import { TrayValue, WindowOptions } from '../../../shared/src/options/model';
import { randomUUID } from 'crypto';
const ZOOM_INTERVAL = 0.1;
@ -74,7 +73,7 @@ export function createAboutBlankWindow(
{ ...options, show: false },
setupWindow,
'about:blank',
nativeTabsSupported() ? undefined : parent,
parent,
);
window.webContents.once('did-stop-loading', () => {
if (window.webContents.getURL() === 'about:blank') {
@ -91,16 +90,11 @@ export function createNewTab(
setupWindow: (options: WindowOptions, window: BrowserWindow) => void,
url: string,
foreground: boolean,
parent?: BrowserWindow,
): BrowserWindow | undefined {
const focusedWindow = BrowserWindow.getFocusedWindow();
log.debug('createNewTab', {
url,
foreground,
focusedWindow,
});
log.debug('createNewTab', { url, foreground, parent });
return withFocusedWindow((focusedWindow) => {
const newTab = createNewWindow(options, setupWindow, url);
log.debug('createNewTab.withFocusedWindow', { focusedWindow, newTab });
const newTab = createNewWindow(options, setupWindow, url, parent);
focusedWindow.addTabbedWindow(newTab);
if (!foreground) {
focusedWindow.focus();
@ -115,12 +109,9 @@ export function createNewWindow(
url: string,
parent?: BrowserWindow,
): BrowserWindow {
log.debug('createNewWindow', {
url,
parent,
});
log.debug('createNewWindow', { url, parent });
const window = new BrowserWindow({
parent: nativeTabsSupported() ? undefined : parent,
parent,
...getDefaultWindowOptions(options),
});
setupWindow(options, window);
@ -150,11 +141,8 @@ export function getDefaultWindowOptions(
};
const defaultOptions: BrowserWindowConstructorOptions = {
autoHideMenuBar: options.autoHideMenuBar,
fullscreenable: true,
tabbingIdentifier: nativeTabsSupported()
? options.tabbingIdentifier ?? randomUUID()
: undefined,
tabbingIdentifier: nativeTabsSupported() ? options.name : undefined,
title: options.name,
webPreferences: {
javascript: true,

View File

@ -202,8 +202,7 @@ const setDockBadge = isOSX()
? (count?: number | string, bounce = false): void => {
if (count !== undefined) {
app.dock.setBadge(count.toString());
if (bounce && typeof count === 'number' && count > currentBadgeCount)
app.dock.bounce();
if (bounce && count > currentBadgeCount) app.dock.bounce();
currentBadgeCount = typeof count === 'number' ? count : 0;
}
}
@ -310,10 +309,10 @@ if (shouldQuit) {
});
}
app.on('new-window-for-tab', (event: Event) => {
log.debug('app.new-window-for-tab', { event });
app.on('new-window-for-tab', () => {
log.debug('app.new-window-for-tab');
if (mainWindow) {
mainWindow.emit('new-window-for-tab', event);
mainWindow.emit('new-tab');
}
});
@ -333,10 +332,9 @@ app.on(
if (appArgs.basicAuthUsername && appArgs.basicAuthPassword) {
callback(appArgs.basicAuthUsername, appArgs.basicAuthPassword);
} else {
createLoginWindow(
callback,
// mainWindow
).catch((err) => log.error('createLoginWindow ERROR', err));
createLoginWindow(callback, mainWindow).catch((err) =>
log.error('createLoginWindow ERROR', err),
);
}
},
);

View File

@ -21,6 +21,7 @@ module.exports = {
'@typescript-eslint/no-confusing-non-null-assertion': 'error',
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-extraneous-class': 'error',
'@typescript-eslint/no-implicit-any-catch': 'error',
'@typescript-eslint/no-invalid-void-type': 'error',
'@typescript-eslint/prefer-ts-expect-error': 'error',
'@typescript-eslint/type-annotation-spacing': 'error',

8679
npm-shrinkwrap.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "nativefier",
"version": "52.0.0",
"version": "51.0.1",
"description": "Wrap web apps natively",
"license": "MIT",
"author": "Goh Jia Hao",
@ -41,10 +41,10 @@
"lint:fix": "cd src && eslint . --ext .ts --fix && cd ../shared && eslint src --ext .ts --fix && cd ../app && eslint src --ext .ts --fix",
"lint:format": "prettier --write 'src/**/*.ts' 'app/src/**/*.ts' 'shared/src/**/*.ts'",
"lint": "eslint shared app src --ext .ts",
"list-outdated-deps": "npm out -l; cd app && npm out -l; true",
"list-outdated-deps": "npm out; cd app && npm out; true",
"prepare": "cd app && npm ci && cd .. && npm run build",
"relock:cli": "rm -rf ./node_modules/ ./npm-shrinkwrap.json && npm install --ignore-scripts --package-lock && mv package-lock.json npm-shrinkwrap.json && npm out -l",
"relock:app": "rm -rf ./app/node_modules/ ./app/npm-shrinkwrap.json && cd app && npm install --ignore-scripts --package-lock && mv package-lock.json npm-shrinkwrap.json && npm out -l",
"relock:cli": "rm -rf ./node_modules/ ./npm-shrinkwrap.json && npm install --ignore-scripts --package-lock && mv package-lock.json npm-shrinkwrap.json && npm out",
"relock:app": "rm -rf ./app/node_modules/ ./app/npm-shrinkwrap.json && cd app && npm install --ignore-scripts --package-lock && mv package-lock.json npm-shrinkwrap.json && npm out",
"relock": "npm run relock:cli; npm run relock:app",
"test:integration": "jest --testRegex=integration-test",
"test:manual": "npm run build && bash .github/manual-test",
@ -58,44 +58,43 @@
"watch": "npx concurrently \"npm:*:watch\""
},
"dependencies": {
"@electron/asar": "^3.2.4",
"axios": "^1.4.0",
"electron-packager": "^17.1.1",
"fs-extra": "^11.1.1",
"gitcloud": "^0.2.4",
"axios": "^1.1.3",
"electron-packager": "^15.5.1",
"fs-extra": "^10.0.0",
"gitcloud": "^0.2.3",
"hasbin": "^1.2.3",
"loglevel": "^1.8.1",
"loglevel": "^1.7.1",
"ncp": "^2.0.0",
"page-icon": "^0.4.0",
"sanitize-filename": "^1.6.3",
"source-map-support": "^0.5.21",
"source-map-support": "^0.5.19",
"tmp": "^0.2.1",
"yargs": "^17.7.2"
"yargs": "^17.1.1"
},
"devDependencies": {
"@types/debug": "^4.1.8",
"@types/fs-extra": "^11.0.1",
"@types/debug": "^4.1.6",
"@types/fs-extra": "^9.0.13",
"@types/hasbin": "^1.2.0",
"@types/jest": "^29.5.4",
"@types/jest": "^28.1.6",
"@types/ncp": "^2.0.5",
"@types/node": "^20.5.6",
"@types/node": "^16.0.0",
"@types/page-icon": "^0.3.4",
"@types/tmp": "^0.2.3",
"@types/yargs": "^17.0.24",
"@typescript-eslint/eslint-plugin": "^6.4.1",
"@typescript-eslint/parser": "^6.4.1",
"electron": "^25.7.0",
"eslint": "^8.46.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0",
"jest": "^29.6.2",
"playwright": "^1.36.2",
"prettier": "^3.0.1",
"rimraf": "^5.0.1",
"ts-loader": "^9.4.4",
"typescript": "^5.1.6",
"webpack": "^5.88.2",
"webpack-cli": "^5.1.4"
"@types/tmp": "^0.2.1",
"@types/yargs": "^17.0.10",
"@typescript-eslint/eslint-plugin": "^5.3.0",
"@typescript-eslint/parser": "^5.3.0",
"electron": "^21.4.4",
"eslint": "^8.1.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"jest": "^28.1.3",
"playwright": "^1.24.0",
"prettier": "^2.3.2",
"rimraf": "^3.0.2",
"ts-loader": "^9.2.3",
"typescript": "^4.3.5",
"webpack": "^5.45.1",
"webpack-cli": "^4.7.2"
},
"jest_COMMENTS": {
"testPathIgnorePatterns": "See https://jestjs.io/docs/configuration#testpathignorepatterns-arraystring . We set it to 1. ignore coverage for deps, and 2. be sure we test the compiled JS, which is in `lib`, not `src` or `dist`",

View File

@ -1,5 +1,4 @@
import { CreateOptions } from '@electron/asar';
import { randomUUID } from 'crypto';
import { CreateOptions } from 'asar';
import * as electronPackager from 'electron-packager';
export type TitleBarValue =
@ -118,7 +117,6 @@ export type OutputOptions = NativefierOptions & {
nativefierVersion: string;
oldBuildWarningText: string;
strictInternalUrls: boolean;
tabbingIdentifier?: string;
targetUrl: string;
userAgent?: string;
zoom?: number;
@ -207,7 +205,6 @@ export type RawOptions = {
};
export type WindowOptions = {
autoHideMenuBar: boolean;
blockExternalUrls: boolean;
browserwindowOptions?: BrowserWindowOptions;
insecure: boolean;
@ -216,7 +213,6 @@ export type WindowOptions = {
name: string;
proxyRules?: string;
show?: boolean;
tabbingIdentifier?: string;
targetUrl: string;
userAgent?: string;
zoom: number;
@ -224,15 +220,10 @@ export type WindowOptions = {
export function outputOptionsToWindowOptions(
options: OutputOptions,
generateTabbingIdentifierIfMissing: boolean,
): WindowOptions {
return {
...options,
autoHideMenuBar: !options.showMenuBar,
insecure: options.insecure ?? false,
tabbingIdentifier: generateTabbingIdentifierIfMissing
? options.tabbingIdentifier ?? randomUUID()
: options.tabbingIdentifier,
zoom: options.zoom ?? 1.0,
};
}

View File

@ -6,19 +6,19 @@ export const DEFAULT_APP_NAME = 'APP';
// - upgrade app / package.json / "devDependencies" / "electron"
// - upgrade package.json / "devDependencies" / "electron"
// Doing a *major* upgrade? Read https://github.com/nativefier/nativefier/blob/master/HACKING.md#deps-major-upgrading-electron
export const DEFAULT_ELECTRON_VERSION = '25.7.0';
export const DEFAULT_ELECTRON_VERSION = '21.4.4';
// https://atom.io/download/atom-shell/index.json
// https://www.electronjs.org/releases/stable
export const DEFAULT_CHROME_VERSION = '114.0.5735.289';
export const DEFAULT_CHROME_VERSION = '106.0.5249.199';
// Update each of these periodically
// https://product-details.mozilla.org/1.0/firefox_versions.json
export const DEFAULT_FIREFOX_VERSION = '116.0.3';
export const DEFAULT_FIREFOX_VERSION = '116.0';
// https://en.wikipedia.org/wiki/Safari_version_history
export const DEFAULT_SAFARI_VERSION = {
majorVersion: 16,
version: '16.6',
majorVersion: 65,
version: '16.5.2',
webkitVersion: '605.1.15',
};

View File

@ -71,8 +71,9 @@ async function edgeUserAgent(
platform: string,
electronVersion: string,
): Promise<string> {
const chromeVersion =
await getChromeVersionForElectronVersion(electronVersion);
const chromeVersion = await getChromeVersionForElectronVersion(
electronVersion,
);
return `Mozilla/5.0 (${platform}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVersion} Safari/537.36 Edg/${chromeVersion}`;
}

View File

@ -6,7 +6,6 @@
"incremental": true,
"module": "commonjs",
"moduleResolution": "node",
"noImplicitAny": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,