Skip to content

Commit

Permalink
Merge pull request #223 from viktor44/hotfix/fix_link_opening
Browse files Browse the repository at this point in the history
Open links in new browser window
  • Loading branch information
viktor44 authored Oct 5, 2023
2 parents b6f0da2 + 3ccb8ee commit cd5710a
Show file tree
Hide file tree
Showing 13 changed files with 127 additions and 57 deletions.
10 changes: 9 additions & 1 deletion app/applications/duck.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,15 @@ export type RemoteUpdateInstalledApplicationsAction = {
};
export type DispatchURLAction = {
type: DISPATCH_URL,
url: string, origin?: { tabId?: string, applicationId?: string }, options?: { target: Targets },
url: string,
origin?: {
tabId?: string,
applicationId?: string
},
options?: {
target?: Targets,
loadInBackground?: boolean,
},
};
export type ToggleNotificationsAction = {
type: TOGGLE_NOTIFICATIONS, applicationId: string,
Expand Down
8 changes: 7 additions & 1 deletion app/services/services/browser-window/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,15 @@ export class BrowserWindowService extends ServiceBase implements RPC.Interface<B
setMetadata<T extends object>(metadata: T): Promise<void> {}
// @ts-ignore
isFocused(): Promise<boolean> {}

// @ts-ignore
addObserver(observer: RPC.ObserverNode<BrowserWindowServiceObserver>): Promise<RPC.Subscription> {}
/**
* Sets whether the window should show always on top of other windows. After
* setting this, the window is still a normal window, not a toolbox window which
* can not be focused on.
*/
// @ts-ignore
setAlwaysOnTop(flag: boolean, level?: 'normal' | 'floating' | 'torn-off-menu' | 'modal-panel' | 'main-menu' | 'status' | 'pop-up-menu' | 'screen-saver', relativeLevel?: number): Promise<void> {}
}

@service('browser-window')
Expand Down
4 changes: 4 additions & 0 deletions app/services/services/browser-window/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,4 +241,8 @@ export class BrowserWindowServiceImpl extends BrowserWindowService implements RP
if (!this.stateManager) return;
this.stateManager.manage(this.window);
}

async setAlwaysOnTop(flag: boolean, level?: 'normal' | 'floating' | 'torn-off-menu' | 'modal-panel' | 'main-menu' | 'status' | 'pop-up-menu' | 'screen-saver', relativeLevel?: number) {
this.window.setAlwaysOnTop(flag, level, relativeLevel);
}
}
4 changes: 2 additions & 2 deletions app/services/services/tab-webcontents/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Account } from '../../../password-managers/types';
import { ServiceBase } from '../../lib/class';
import { service, timeout } from '../../lib/decorator';
import { RPC } from '../../lib/types';

import { NEW_TAB, Targets } from '../../../urlrouter/constants';
/**
* Service used to interact with WebContents related to tabs
*/
Expand Down Expand Up @@ -122,7 +122,7 @@ export class TabWebContentsNotificationsObserver extends ServiceBase implements
export class UrlDispatcherProviderService extends ServiceBase
implements RPC.Interface<UrlDispatcherProviderService> {
// @ts-ignore
dispatchUrl(url: string, originWebContentsId: number): void { }
dispatchUrl(url: string, originWebContentsId: number, target: Targets = NEW_TAB): void { }
}

@service('webcontents')
Expand Down
73 changes: 44 additions & 29 deletions app/services/services/tab-webcontents/main.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as BluebirdPromise from 'bluebird';
import { app, ipcMain, session } from 'electron';
import { app, ipcMain, session, HandlerDetails } from 'electron';
import log from 'electron-log';
import { omit } from 'ramda';
import { fromEvent, Observable, Subject } from 'rxjs';
Expand Down Expand Up @@ -32,6 +32,7 @@ import {
UrlDispatcherProviderService,
WebContentsOverrideProviderService,
} from './interface';
import { DEFAULT_BROWSER, DEFAULT_BROWSER_BACKGROUND, NEW_WINDOW } from '../../../urlrouter/constants';

export class TabWebContentsServiceImpl extends TabWebContentsService implements RPC.Interface<TabWebContentsService> {
protected webviews: Subject<Electron.WebContents>;
Expand Down Expand Up @@ -212,35 +213,49 @@ export class TabWebContentsServiceImpl extends TabWebContentsService implements

async setUrlDispatcherProvider(provider: RPC.Node<UrlDispatcherProviderService>) {
return new ServiceSubscription(this.onNewWebviews().subscribe(wc => {
return fromEvent(wc, 'new-window', (event, url, _, disposition, options) => ({ event, url, disposition, options }))
.subscribe(async ({ event, url, disposition, options }) => {
if (disposition === 'new-window') {
if (options) {
options.fullscreen = false;
}
} else if (disposition === 'foreground-tab' && String(url).startsWith('about:blank')) {
// Gmail PDF hack. Will download a printed PDF from thumbnail
// EDIT: not only Gmail or just PDF but most of the URL link on a Google app with overriden User Agent
// also falls here
event.preventDefault();
const guest = await handleHackGoogleAppsURLs(event, options);

if (guest) { // not a download
const newWindowUrl = guest.webContents.getURL();
if (newWindowUrl.startsWith('about:blank')) {
// if popup is still blank after 2 seconds, we show it to let it finish
guest.show();
} else {
guest.close();
// otherwise dispatch the current URL of the guest window into our URLRouter
await provider.dispatchUrl(newWindowUrl, wc.id);
}
}
} else {
event.preventDefault();
await provider.dispatchUrl(url, wc.id);

wc.setWindowOpenHandler((details: HandlerDetails) => {

log.debug('WindowOpen', details, process.type);

if (details.disposition === 'new-window') {
provider.dispatchUrl(details.url, wc.id, DEFAULT_BROWSER_BACKGROUND);
return { action: 'deny' };
}
else if (details.disposition === 'background-tab') {
provider.dispatchUrl(details.url, wc.id, DEFAULT_BROWSER);
return { action: 'deny' };
}

//vk: 2023.09.29 don't know how to verify this hack
// will fix it later

// else if (disposition === 'foreground-tab' && String(url).startsWith('about:blank')) {
// // Gmail PDF hack. Will download a printed PDF from thumbnail
// // EDIT: not only Gmail or just PDF but most of the URL link on a Google app with overriden User Agent
// // also falls here
// const guest = await handleHackGoogleAppsURLs(event, options);

// if (guest) { // not a download
// const newWindowUrl = guest.webContents.getURL();
// if (newWindowUrl.startsWith('about:blank')) {
// // if popup is still blank after 2 seconds, we show it to let it finish
// guest.show();
// } else {
// guest.close();
// // otherwise dispatch the current URL of the guest window into our URLRouter
// await provider.dispatchUrl(newWindowUrl, wc.id);
// }
// }
// }

return {
action: 'allow',
overrideBrowserWindowOptions: {
fullscreen: false,
}
});
};
});
}));
}

Expand Down
4 changes: 2 additions & 2 deletions app/urlrouter/URLRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { ApplicationImmutable } from '../applications/types';
import { handleError } from '../services/api/helpers';
import { getTabIdMatchingURL } from '../tabs/selectors';
import { StationState, StationStore } from '../types';
import { DEFAULT_BROWSER, NEW_TAB, NEW_WINDOW, Targets } from './constants';
import { DEFAULT_BROWSER, DEFAULT_BROWSER_BACKGROUND, NEW_TAB, NEW_WINDOW, Targets } from './constants';
import {
ApplicationConnectionNode,
ApplicationItem,
Expand Down Expand Up @@ -61,7 +61,7 @@ export default class URLRouter {
// Handy re-usable checks
const newTab = options.target === NEW_TAB;
const newWindow = options.target === NEW_WINDOW;
const defaultBrowser = options.target === DEFAULT_BROWSER;
const defaultBrowser = options.target === DEFAULT_BROWSER || options.target == DEFAULT_BROWSER_BACKGROUND;

if (defaultBrowser) {
return [URLRouterAction.DEFAULT_BROWSER, null];
Expand Down
8 changes: 2 additions & 6 deletions app/urlrouter/constants.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
export const NEW_TAB = 'URLROUTER/NEW_TAB';
export const NEW_WINDOW = 'URLROUTER/NEW_WINDOW';
export const DEFAULT_BROWSER = 'URLROUTER/DEFAULT_BROWSER';
export const DEFAULT_BROWSER_BACKGROUND = 'URLROUTER/DEFAULT_BROWSER_BACKGROUND';

export type DispatchUrlOptions = {
afterFollowingRedirects?: boolean,
originalUrl?: string,
};

export type Targets = typeof NEW_TAB | typeof NEW_WINDOW | typeof DEFAULT_BROWSER;
export type Targets = typeof NEW_TAB | typeof NEW_WINDOW | typeof DEFAULT_BROWSER | typeof DEFAULT_BROWSER_BACKGROUND;
38 changes: 35 additions & 3 deletions app/urlrouter/sagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,16 @@ import { navigateTabToURL, changeHashAndNavigateToTab } from '../tab-webcontents
import { setCursorIcon } from '../ui/duck';
import { getCursorIcon } from '../ui/selectors';
import { callService, takeEveryWitness, tryCatch } from '../utils/sagas';
import { DispatchUrlOptions } from './constants';
import { ApplicationItem, URLRouterAction, URLRouterActionAndDestination } from './types';
import { BrowserWindowService } from '../services/services/browser-window/interface';
import services from '../services/servicesManager';
import { RPC } from '../services/lib/types';
import { service } from 'app/services/lib/decorator';

export type DispatchUrlOptions = {
afterFollowingRedirects?: boolean,
originalUrl?: string,
};

const queue = new Set();
const followRedirectsTimeout = 3000;
Expand Down Expand Up @@ -62,7 +70,9 @@ export function* dispatchUrlSaga(

const [action, destination]: URLRouterActionAndDestination = yield call([router, router.routeURL], url, origin!, options!);

if (process.env.NODE_ENV !== 'test') log.debug('[dispatch url]', url, origin, options, action, destination);
if (process.env.NODE_ENV !== 'test') {
log.debug('[dispatch url]', url, 'origin:', origin, 'options:', options, 'action: ', action, 'destination:', destination);
}

if (action === URLRouterAction.DEFAULT_BROWSER && !afterFollowingRedirects) {
const urlFromRedirects = yield call(urlFromRedirections, dispatch);
Expand Down Expand Up @@ -102,7 +112,29 @@ function* triggerCorrespondingAction(
// dispatch(executeWebviewMethod(destination, 'reload'));
break;
case URLRouterAction.DEFAULT_BROWSER:
remote.shell.openExternal(url);
if (!options || !options.loadInBackground) {
remote.shell.openExternal(url)
}
else {
services.browserWindow.getFocusedWindow()
.then(lastFocusedWindow => {
if (lastFocusedWindow) {
lastFocusedWindow.setAlwaysOnTop(true)
.then(() => remote.shell.openExternal(url)
.then(() => setTimeout(() => {
lastFocusedWindow.show();
lastFocusedWindow.setAlwaysOnTop(false);
},
100) //vk: I can't explain why, but without this timeout, open in background doesn't work
)
);
}
else {
log.info('Focused window is empty');
remote.shell.openExternal(url);
}
});
}
break;
case URLRouterAction.NAV_TO_TAB:
yield put(navigateToApplicationTabAutomatically(destination.tabId));
Expand Down
18 changes: 13 additions & 5 deletions app/urlrouter/urlDispatcherProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { RPC } from '../services/lib/types';
import { UrlDispatcherProviderService } from '../services/services/tab-webcontents/interface';
import { getTabWebcontentsByWebContentsId, getWebcontentsTabId } from '../tab-webcontents/selectors';
import { getApplicationIdByTabId } from '../tabs/selectors';
import { NEW_TAB } from './constants';
import { DEFAULT_BROWSER_BACKGROUND, NEW_TAB, Targets } from './constants';
import { dispatchUrlSaga } from './sagas';
import { StationStoreWorker } from 'app/types';

Expand All @@ -16,16 +16,24 @@ export class UrlDispatcherProviderServiceImpl extends UrlDispatcherProviderServi
this.store = store;
}

async dispatchUrl(url: string, originWebContentsId: number) {
async dispatchUrl(url: string, originWebContentsId: number, target: Targets = NEW_TAB) {
const state = this.store.getState();

const twc = getTabWebcontentsByWebContentsId(state, originWebContentsId);
const tabId = getWebcontentsTabId(twc);
const applicationId = getApplicationIdByTabId(state, tabId);

// run the `dispatchUrlSaga`, which is the entry point to dispatch a new URL inside bx
await this.store.runSaga(dispatchUrlSaga, {
type: DISPATCH_URL, url, origin: { tabId, applicationId }, options: { target: NEW_TAB },
}).toPromise();
await this.store.runSaga(
dispatchUrlSaga,
{
type: DISPATCH_URL,
url,
origin: { tabId, applicationId },
options: {
target,
loadInBackground: target === DEFAULT_BROWSER_BACKGROUND,
},
}).toPromise();
}
}
5 changes: 3 additions & 2 deletions manifests/definitions/131.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
{
"name": "Microsoft Teams",
"category": "Communication & Collaboration",
"start_url": "https://teams.microsoft.com",
"start_url": "https://teams.live.com",
"icons": [
{
"src": "https://cdn.filestackcontent.com/5sZEOL6RZesRsLdCozwv",
"platform": "browserx"
}
],
"theme_color": "#5559AE",
"scope": "https://teams.microsoft.com",
"scope": "https://teams.live.com",
"bx_legacy_service_id": "microsoft-teams",
"bx_override_user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36",
"recommendedPosition": "25"
}
7 changes: 3 additions & 4 deletions manifests/definitions/254.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,14 @@
}
],
"theme_color": "#C75B12",
"scope": "https://login.microsoftonline.com",
"scope": "https://www.office.com",
"bx_legacy_service_id": "office-365",
"bx_override_user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36",
"extended_scopes": [
"https://office.live.com",
"https://people.live.com",
"https://www.onenote.com",
"https://calendar.live.com",
"https://outlook.live.com",
"https://onedrive.live.com"
"https://www.microsoft365.com"
],
"recommendedPosition": "28"
}
3 changes: 2 additions & 1 deletion manifests/definitions/284.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@
],
"theme_color": "#2494CA",
"scope": "https://workflowy.com",
"bx_legacy_service_id": "workflowy"
"bx_legacy_service_id": "workflowy",
"bx_override_user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36"
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "station-desktop-app",
"private": true,
"productName": "Station",
"version": "2.5.0-b1",
"version": "2.5.0-b2",
"description": "Station",
"homepage": "https://getstation.com",
"author": {
Expand Down

0 comments on commit cd5710a

Please sign in to comment.