Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Safari Web Extension Port from App Extension #1491

Merged
merged 14 commits into from
Jan 13, 2021
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 2 additions & 9 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,6 @@ const filters = {
safari: [
'!build/safari/**/*'
],
webExt: [
'!build/manifest.json'
],
nonSafariApp: [
'!build/background.html',
'!build/popup/index.html'
],
};

function buildString() {
Expand Down Expand Up @@ -186,6 +179,7 @@ function safariCopyAssets(source, dest) {
.on('error', reject)
.pipe(gulpif('safari/Info.plist', replace('0.0.1', manifest.version)))
.pipe(gulpif('safari/Info.plist', replace('0.0.2', process.env.BUILD_NUMBER || manifest.version)))
.pipe(gulpif('desktop.xcodeproj/project.pbxproj', replace('../../../build', '../safari/app')))
.pipe(gulp.dest(dest))
.on('end', resolve);
});
Expand All @@ -195,8 +189,7 @@ function safariCopyBuild(source, dest) {
return new Promise((resolve, reject) => {
gulp.src(source)
.on('error', reject)
.pipe(filter(['**'].concat(filters.fonts)
.concat(filters.webExt).concat(filters.nonSafariApp)))
.pipe(filter(['**'].concat(filters.fonts)))
.pipe(gulp.dest(dest))
.on('end', resolve);
});
Expand Down
2 changes: 1 addition & 1 deletion src/background/commands.background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default class CommandsBackground {
}

async init() {
if (this.isSafari || this.isVivaldi) {
if (this.isVivaldi) {
BrowserApi.messageListener('commands.background', async (msg: any, sender: any, sendResponse: any) => {
if (msg.command === 'keyboardShortcutTriggered' && msg.shortcut) {
await this.processCommand(msg.shortcut, sender);
Expand Down
43 changes: 19 additions & 24 deletions src/background/main.background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,8 @@ export default class MainBackground {
return promise.then((result) => result.response === 'unlocked');
}
});
this.storageService = new BrowserStorageService(this.platformUtilsService);
this.secureStorageService = new BrowserStorageService(this.platformUtilsService);
this.storageService = new BrowserStorageService();
this.secureStorageService = new BrowserStorageService();
this.i18nService = new I18nService(BrowserApi.getUILanguage(window));
this.cryptoFunctionService = new WebCryptoFunctionService(window, this.platformUtilsService);
this.cryptoService = new CryptoService(this.storageService, this.secureStorageService,
Expand Down Expand Up @@ -249,21 +249,18 @@ export default class MainBackground {
this.commandsBackground = new CommandsBackground(this, this.passwordGenerationService,
this.platformUtilsService, this.analytics, this.vaultTimeoutService);

if (!this.isSafari) {
this.tabsBackground = new TabsBackground(this);
this.contextMenusBackground = new ContextMenusBackground(this, this.cipherService,
this.passwordGenerationService, this.analytics, this.platformUtilsService, this.vaultTimeoutService,
this.eventService, this.totpService);
this.idleBackground = new IdleBackground(this.vaultTimeoutService, this.storageService,
this.notificationsService);
this.webRequestBackground = new WebRequestBackground(this.platformUtilsService, this.cipherService,
this.vaultTimeoutService);
this.windowsBackground = new WindowsBackground(this);
}
this.tabsBackground = new TabsBackground(this);
this.contextMenusBackground = new ContextMenusBackground(this, this.cipherService,
this.passwordGenerationService, this.analytics, this.platformUtilsService, this.vaultTimeoutService,
this.eventService, this.totpService);
this.idleBackground = new IdleBackground(this.vaultTimeoutService, this.storageService,
this.notificationsService);
this.webRequestBackground = new WebRequestBackground(this.platformUtilsService, this.cipherService,
this.vaultTimeoutService);
this.windowsBackground = new WindowsBackground(this);
}

async bootstrap() {
SafariApp.init();
this.analytics.ga('send', 'pageview', '/background.html');
this.containerService.attachToWindow(window);

Expand All @@ -273,13 +270,11 @@ export default class MainBackground {
await this.runtimeBackground.init();
await this.commandsBackground.init();

if (!this.isSafari) {
await this.tabsBackground.init();
await this.contextMenusBackground.init();
await this.idleBackground.init();
await this.webRequestBackground.init();
await this.windowsBackground.init();
}
await this.tabsBackground.init();
await this.contextMenusBackground.init();
await this.idleBackground.init();
await this.webRequestBackground.init();
await this.windowsBackground.init();

return new Promise((resolve) => {
setTimeout(async () => {
Expand All @@ -294,7 +289,7 @@ export default class MainBackground {
}

async setIcon() {
if (this.isSafari || (!chrome.browserAction && !this.sidebarAction)) {
if (!chrome.browserAction && !this.sidebarAction) {
return;
}

Expand All @@ -313,7 +308,7 @@ export default class MainBackground {
}

async refreshBadgeAndMenu(forLocked: boolean = false) {
if (this.isSafari || !chrome.windows || !chrome.contextMenus) {
if (!chrome.windows || !chrome.contextMenus) {
return;
}

Expand Down Expand Up @@ -444,7 +439,7 @@ export default class MainBackground {
}

private async buildContextMenu() {
if (this.isSafari || !chrome.contextMenus || this.buildingContextMenu) {
if (!chrome.contextMenus || this.buildingContextMenu) {
return;
}

Expand Down
31 changes: 4 additions & 27 deletions src/background/runtime.background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { CipherView } from 'jslib/models/view/cipherView';
import { LoginUriView } from 'jslib/models/view/loginUriView';
import { LoginView } from 'jslib/models/view/loginView';

import { AuthService } from 'jslib/abstractions/auth.service';
import { AutofillService } from '../services/abstractions/autofill.service';
import BrowserPlatformUtilsService from '../services/browserPlatformUtils.service';
import { CipherService } from 'jslib/abstractions/cipher.service';
Expand All @@ -13,18 +12,14 @@ import { EnvironmentService } from 'jslib/abstractions/environment.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { NotificationsService } from 'jslib/abstractions/notifications.service';
import { PolicyService } from 'jslib/abstractions/policy.service';
import { PopupUtilsService } from '../popup/services/popup-utils.service';
import { StateService } from 'jslib/abstractions/state.service';
import { StorageService } from 'jslib/abstractions/storage.service';
import { SyncService } from 'jslib/abstractions/sync.service';
import { SystemService } from 'jslib/abstractions/system.service';
import { UserService } from 'jslib/abstractions/user.service';
import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service';

import { BrowserApi } from '../browser/browserApi';

import MainBackground from './main.background';
import { NativeMessagingBackground } from './nativeMessaging.background';

import { Analytics } from 'jslib/misc';
import { Utils } from 'jslib/misc/utils';
Expand All @@ -36,7 +31,6 @@ export default class RuntimeBackground {
private runtime: any;
private autofillTimeout: any;
private pageDetailsToAutoFill: any[] = [];
private isSafari: boolean;
private onInstalledReason: string = null;

constructor(private main: MainBackground, private autofillService: AutofillService,
Expand All @@ -46,15 +40,12 @@ export default class RuntimeBackground {
private systemService: SystemService, private vaultTimeoutService: VaultTimeoutService,
private environmentService: EnvironmentService, private policyService: PolicyService,
private userService: UserService) {
this.isSafari = this.platformUtilsService.isSafari();
this.runtime = this.isSafari ? {} : chrome.runtime;
this.runtime = chrome.runtime;

// onInstalled listener must be wired up before anything else, so we do it in the ctor
if (!this.isSafari) {
this.runtime.onInstalled.addListener((details: any) => {
this.onInstalledReason = details.reason;
});
}
this.runtime.onInstalled.addListener((details: any) => {
this.onInstalledReason = details.reason;
});
}

async init() {
Expand Down Expand Up @@ -399,20 +390,6 @@ export default class RuntimeBackground {
}

private async checkOnInstalled() {
if (this.isSafari) {
const installedVersion = await this.storageService.get<string>(ConstantsService.installedVersionKey);
if (installedVersion == null) {
this.onInstalledReason = 'install';
} else if (BrowserApi.getApplicationVersion() !== installedVersion) {
this.onInstalledReason = 'update';
}

if (this.onInstalledReason != null) {
await this.storageService.save(ConstantsService.installedVersionKey,
BrowserApi.getApplicationVersion());
}
}

setTimeout(async () => {
if (this.onInstalledReason != null) {
if (this.onInstalledReason === 'install') {
Expand Down
124 changes: 30 additions & 94 deletions src/browser/browserApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,16 @@ import { Utils } from 'jslib/misc/utils';

export class BrowserApi {
static isWebExtensionsApi: boolean = (typeof browser !== 'undefined');
static isSafariApi: boolean = (window as any).safariAppExtension === true;
static isSafariApi: boolean = navigator.userAgent.indexOf(' Safari/') !== -1;
static isChromeApi: boolean = !BrowserApi.isSafariApi && (typeof chrome !== 'undefined');
static isFirefoxOnAndroid: boolean = navigator.userAgent.indexOf('Firefox/') !== -1 &&
navigator.userAgent.indexOf('Android') !== -1;

static async getTabFromCurrentWindowId(): Promise<any> {
if (BrowserApi.isChromeApi) {
return await BrowserApi.tabsQueryFirst({
active: true,
windowId: chrome.windows.WINDOW_ID_CURRENT,
});
} else if (BrowserApi.isSafariApi) {
return await BrowserApi.getTabFromCurrentWindow();
}
return await BrowserApi.tabsQueryFirst({
active: true,
windowId: chrome.windows.WINDOW_ID_CURRENT,
});
}

static async getTabFromCurrentWindow(): Promise<any> {
Expand All @@ -34,16 +30,11 @@ export class BrowserApi {
}

static async tabsQuery(options: any): Promise<any[]> {
if (BrowserApi.isChromeApi) {
return new Promise((resolve) => {
chrome.tabs.query(options, (tabs: any[]) => {
resolve(tabs);
});
return new Promise((resolve) => {
chrome.tabs.query(options, (tabs: any[]) => {
resolve(tabs);
});
} else if (BrowserApi.isSafariApi) {
const tabs = await SafariApp.sendMessageToApp('tabs_query', JSON.stringify(options));
return tabs != null ? JSON.parse(tabs) : null;
}
});
}

static async tabsQueryFirst(options: any): Promise<any> {
Expand Down Expand Up @@ -72,81 +63,36 @@ export class BrowserApi {
return;
}

if (BrowserApi.isChromeApi) {
return new Promise((resolve) => {
chrome.tabs.sendMessage(tab.id, obj, options, () => {
if (chrome.runtime.lastError) {
// Some error happened
}
resolve();
});
return new Promise((resolve) => {
chrome.tabs.sendMessage(tab.id, obj, options, () => {
if (chrome.runtime.lastError) {
// Some error happened
}
resolve();
});
} else if (BrowserApi.isSafariApi) {
if (options != null && options.frameId != null && obj.bitwardenFrameId == null) {
obj.bitwardenFrameId = options.frameId;
}
await SafariApp.sendMessageToApp('tabs_message', JSON.stringify({
tab: tab,
obj: JSON.stringify(obj),
options: options,
}), true);
}
});
}

static getBackgroundPage(): any {
if (BrowserApi.isChromeApi) {
return chrome.extension.getBackgroundPage();
} else if (BrowserApi.isSafariApi) {
return window;
} else {
return null;
}
return chrome.extension.getBackgroundPage();
}

static getApplicationVersion(): string {
if (BrowserApi.isChromeApi) {
return chrome.runtime.getManifest().version;
} else if (BrowserApi.isSafariApi) {
return (window as any).bitwardenApplicationVersion;
} else {
return null;
}
return chrome.runtime.getManifest().version;
}

static async isPopupOpen(): Promise<boolean> {
if (BrowserApi.isChromeApi) {
return Promise.resolve(chrome.extension.getViews({ type: 'popup' }).length > 0);
} else if (BrowserApi.isSafariApi) {
const open = await SafariApp.sendMessageToApp('isPopoverOpen');
return open === 'true';
} else {
return Promise.resolve(false);
}
return Promise.resolve(chrome.extension.getViews({ type: 'popup' }).length > 0);
}

static createNewTab(url: string, extensionPage: boolean = false) {
if (BrowserApi.isChromeApi) {
chrome.tabs.create({ url: url });
} else if (BrowserApi.isSafariApi) {
SafariApp.sendMessageToApp('createNewTab', url, true);
}
chrome.tabs.create({ url: url });
}

static messageListener(name: string, callback: (message: any, sender: any, response: any) => void) {
if (BrowserApi.isChromeApi) {
chrome.runtime.onMessage.addListener((msg: any, sender: any, response: any) => {
callback(msg, sender, response);
});
} else if (BrowserApi.isSafariApi) {
SafariApp.addMessageListener(name, (message: any, sender: any, response: any) => {
if (message.bitwardenFrameId != null) {
if (sender != null && typeof (sender) === 'object' && sender.frameId == null) {
sender.frameId = message.bitwardenFrameId;
}
}
callback(message, sender, response);
});
}
chrome.runtime.onMessage.addListener((msg: any, sender: any, response: any) => {
callback(msg, sender, response);
});
}

static closePopup(win: Window) {
Expand All @@ -155,10 +101,8 @@ export class BrowserApi {
// condition is only called if the popup wasn't already dismissed (future proofing).
// ref: https://bugzilla.mozilla.org/show_bug.cgi?id=1433604
browser.tabs.update({ active: true }).finally(win.close);
} else if (BrowserApi.isWebExtensionsApi || BrowserApi.isChromeApi) {
} else {
win.close();
} else if (BrowserApi.isSafariApi) {
SafariApp.sendMessageToApp('hidePopover');
}
}

Expand Down Expand Up @@ -196,30 +140,22 @@ export class BrowserApi {
}

static getUILanguage(win: Window) {
if (BrowserApi.isSafariApi) {
return win.navigator.language;
} else {
return chrome.i18n.getUILanguage();
}
return chrome.i18n.getUILanguage();
}

static reloadExtension(win: Window) {
if (win != null) {
return win.location.reload(true);
} else if (BrowserApi.isSafariApi) {
SafariApp.sendMessageToApp('reloadExtension');
} else if (!BrowserApi.isSafariApi) {
} else {
return chrome.runtime.reload();
}
}

static reloadOpenWindows() {
if (!BrowserApi.isSafariApi) {
const views = chrome.extension.getViews() as Window[];
views.filter((w) => w.location.href != null).forEach((w) => {
w.location.reload();
});
}
const views = chrome.extension.getViews() as Window[];
views.filter((w) => w.location.href != null).forEach((w) => {
w.location.reload();
});
}

static connectNative(application: string): browser.runtime.Port | chrome.runtime.Port {
Expand Down
Loading