diff --git a/gulpfile.js b/gulpfile.js index c90d6361ef..7fc0146e9b 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -30,13 +30,6 @@ const filters = { safari: [ '!build/safari/**/*' ], - webExt: [ - '!build/manifest.json' - ], - nonSafariApp: [ - '!build/background.html', - '!build/popup/index.html' - ], }; function buildString() { @@ -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); }); @@ -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); }); diff --git a/src/background/commands.background.ts b/src/background/commands.background.ts index 625eb4cd67..463862852a 100644 --- a/src/background/commands.background.ts +++ b/src/background/commands.background.ts @@ -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); diff --git a/src/background/main.background.ts b/src/background/main.background.ts index a929d52749..82d1f739a6 100644 --- a/src/background/main.background.ts +++ b/src/background/main.background.ts @@ -167,8 +167,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.consoleLogService = new ConsoleLogService(false); @@ -252,21 +252,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); @@ -276,13 +273,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 () => { @@ -297,7 +292,7 @@ export default class MainBackground { } async setIcon() { - if (this.isSafari || (!chrome.browserAction && !this.sidebarAction)) { + if (!chrome.browserAction && !this.sidebarAction) { return; } @@ -316,7 +311,7 @@ export default class MainBackground { } async refreshBadgeAndMenu(forLocked: boolean = false) { - if (this.isSafari || !chrome.windows || !chrome.contextMenus) { + if (!chrome.windows || !chrome.contextMenus) { return; } @@ -447,7 +442,7 @@ export default class MainBackground { } private async buildContextMenu() { - if (this.isSafari || !chrome.contextMenus || this.buildingContextMenu) { + if (!chrome.contextMenus || this.buildingContextMenu) { return; } diff --git a/src/background/runtime.background.ts b/src/background/runtime.background.ts index f08915352b..685ffc1414 100644 --- a/src/background/runtime.background.ts +++ b/src/background/runtime.background.ts @@ -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'; @@ -13,10 +12,7 @@ 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'; @@ -24,7 +20,6 @@ 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'; @@ -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, @@ -46,19 +40,15 @@ 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; // 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; - }); - } + chrome.runtime.onInstalled.addListener((details: any) => { + this.onInstalledReason = details.reason; + }); } async init() { - if (!this.runtime) { + if (!chrome.runtime) { return; } @@ -399,20 +389,6 @@ export default class RuntimeBackground { } private async checkOnInstalled() { - if (this.isSafari) { - const installedVersion = await this.storageService.get(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') { diff --git a/src/browser/browserApi.ts b/src/browser/browserApi.ts index 1ee54a45b1..6bfd977b61 100644 --- a/src/browser/browserApi.ts +++ b/src/browser/browserApi.ts @@ -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 { - 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 { @@ -34,16 +30,11 @@ export class BrowserApi { } static async tabsQuery(options: any): Promise { - 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 { @@ -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 { - 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) { @@ -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'); } } @@ -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 { diff --git a/src/browser/safariApp.ts b/src/browser/safariApp.ts index 894ae2c6d4..b53d3754b7 100644 --- a/src/browser/safariApp.ts +++ b/src/browser/safariApp.ts @@ -1,23 +1,6 @@ import { BrowserApi } from './browserApi'; export class SafariApp { - static init() { - if ((window as any).bitwardenSafariAppInited) { - return; - } - (window as any).bitwardenSafariAppInited = true; - if (BrowserApi.isSafariApi) { - (window as any).bitwardenSafariAppRequests = - new Map void, timeoutDate: Date }>(); - (window as any).bitwardenSafariAppMessageListeners = - new Map void>(); - (window as any).bitwardenSafariAppMessageReceiver = (message: any) => { - SafariApp.receiveMessageFromApp(message); - }; - setInterval(() => SafariApp.cleanupOldRequests(), 5 * 60000); // every 5 mins - } - } - static sendMessageToApp(command: string, data: any = null, resolveNow = false): Promise { if (!BrowserApi.isSafariApi) { return Promise.resolve(null); @@ -25,69 +8,14 @@ export class SafariApp { return new Promise((resolve) => { const now = new Date(); const messageId = now.getTime().toString() + '_' + Math.floor(Math.random() * Number.MAX_SAFE_INTEGER); - if (typeof safari === typeof undefined) { - (window as any).webkit.messageHandlers.bitwardenApp.postMessage(JSON.stringify({ - id: messageId, - command: command, - data: data, - responseData: null, - })); - } else { - safari.extension.dispatchMessage('bitwarden', { - command: command, - data: data, - responseData: null, - }); - } - if (resolveNow) { - resolve(); - } else { - (window as any).bitwardenSafariAppRequests.set(messageId, { - resolve: resolve, - timeoutDate: new Date(now.getTime() + 5 * 60000), - }); - } - }); - } - - static addMessageListener(name: string, callback: (message: any, sender: any, response: any) => void) { - (window as any).bitwardenSafariAppMessageListeners.set(name, callback); - } - - static sendMessageToListeners(message: any, sender: any, response: any) { - (window as any).bitwardenSafariAppMessageListeners.forEach((f: any) => f(message, sender, response)); - } - - private static receiveMessageFromApp(message: any) { - if (message == null) { - return; - } - if ((message.id == null || message.id === '') && message.command === 'app_message') { - try { - const msg = JSON.parse(message.data); - SafariApp.sendMessageToListeners(msg, { - id: 'app_message', - tab: message.senderTab, - }, null); - } catch { } - } else if (message.id != null && (window as any).bitwardenSafariAppRequests.has(message.id)) { - const p = (window as any).bitwardenSafariAppRequests.get(message.id); - p.resolve(message.responseData); - (window as any).bitwardenSafariAppRequests.delete(message.id); - } - } - - private static cleanupOldRequests() { - const removeIds: string[] = []; - ((window as any).bitwardenSafariAppRequests as - Map void, timeoutDate: Date }>) - .forEach((v, key) => { - if (v.timeoutDate < new Date()) { - removeIds.push(key); - } + (browser as any).runtime.sendNativeMessage('com.bitwarden.desktop', { + id: messageId, + command: command, + data: data, + responseData: null, + }, (response: any) => { + resolve(response); }); - removeIds.forEach((id) => { - (window as any).bitwardenSafariAppRequests.delete(id); }); } } diff --git a/src/content/autofill.js b/src/content/autofill.js index ca6c75ae08..956708fe3e 100644 --- a/src/content/autofill.js +++ b/src/content/autofill.js @@ -989,35 +989,6 @@ End 1Password Extension */ - if ((typeof safari !== 'undefined') && navigator.userAgent.indexOf(' Safari/') !== -1 && - navigator.userAgent.indexOf('Chrome') === -1) { - if (window.__bitwardenFrameId == null) { - window.__bitwardenFrameId = Math.floor(Math.random() * Math.floor(99999999)); - } - safari.self.addEventListener('message', function (msgEvent) { - var msg = JSON.parse(msgEvent.message.msg); - if (msg.bitwardenFrameId != null && window.__bitwardenFrameId !== msg.bitwardenFrameId) { - return; - } - - if (msg.command === 'collectPageDetails') { - var pageDetails = collect(document); - var pageDetailsObj = JSON.parse(pageDetails); - safari.extension.dispatchMessage('bitwarden', { - command: 'collectPageDetailsResponse', - tab: msg.tab, - details: pageDetailsObj, - sender: msg.sender, - bitwardenFrameId: window.__bitwardenFrameId - }); - } - else if (msg.command === 'fillForm') { - fill(document, msg.fillScript); - } - }, false); - return; - } - chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) { if (msg.command === 'collectPageDetails') { var pageDetails = collect(document); diff --git a/src/content/autofiller.ts b/src/content/autofiller.ts index 253c82603c..1d756b8451 100644 --- a/src/content/autofiller.ts +++ b/src/content/autofiller.ts @@ -3,44 +3,17 @@ document.addEventListener('DOMContentLoaded', (event) => { let filledThisHref = false; let delayFillTimeout: number; - const isSafari = (typeof safari !== 'undefined') && navigator.userAgent.indexOf(' Safari/') !== -1 && - navigator.userAgent.indexOf('Chrome') === -1; - - if (isSafari) { - if ((window as any).__bitwardenFrameId == null) { - (window as any).__bitwardenFrameId = Math.floor(Math.random() * Math.floor(99999999)); + const enabledKey = 'enableAutoFillOnPageLoad'; + chrome.storage.local.get(enabledKey, (obj: any) => { + if (obj != null && obj[enabledKey] === true) { + setInterval(() => doFillIfNeeded(), 500); } - const responseCommand = 'autofillerAutofillOnPageLoadEnabledResponse'; - safari.extension.dispatchMessage('bitwarden', { - command: 'bgGetDataForTab', - responseCommand: responseCommand, - bitwardenFrameId: (window as any).__bitwardenFrameId, - }); - safari.self.addEventListener('message', (msgEvent: any) => { - const msg = JSON.parse(msgEvent.message.msg); - if (msg.bitwardenFrameId != null && (window as any).__bitwardenFrameId !== msg.bitwardenFrameId) { - return; - } - if (msg.command === responseCommand && msg.data.autofillEnabled === true) { - setInterval(() => doFillIfNeeded(), 500); - } else if (msg.command === 'fillForm' && pageHref === msg.url) { - filledThisHref = true; - } - }, false); - return; - } else { - const enabledKey = 'enableAutoFillOnPageLoad'; - chrome.storage.local.get(enabledKey, (obj: any) => { - if (obj != null && obj[enabledKey] === true) { - setInterval(() => doFillIfNeeded(), 500); - } - }); - chrome.runtime.onMessage.addListener((msg: any, sender: any, sendResponse: Function) => { - if (msg.command === 'fillForm' && pageHref === msg.url) { - filledThisHref = true; - } - }); - } + }); + chrome.runtime.onMessage.addListener((msg: any, sender: any, sendResponse: Function) => { + if (msg.command === 'fillForm' && pageHref === msg.url) { + filledThisHref = true; + } + }); function doFillIfNeeded(force: boolean = false) { if (force || pageHref !== window.location.href) { @@ -64,12 +37,7 @@ document.addEventListener('DOMContentLoaded', (event) => { sender: 'autofiller', }; - if (isSafari) { - msg.bitwardenFrameId = (window as any).__bitwardenFrameId; - safari.extension.dispatchMessage('bitwarden', msg); - } else { - chrome.runtime.sendMessage(msg); - } + chrome.runtime.sendMessage(msg); } } }); diff --git a/src/content/notificationBar.ts b/src/content/notificationBar.ts index 4974f53ad8..70fbd5e413 100644 --- a/src/content/notificationBar.ts +++ b/src/content/notificationBar.ts @@ -17,71 +17,30 @@ document.addEventListener('DOMContentLoaded', (event) => { const logInButtonNames = new Set(['log in', 'sign in', 'login', 'go', 'submit', 'continue', 'next']); const changePasswordButtonNames = new Set(['save password', 'update password', 'change password', 'change']); const changePasswordButtonContainsNames = new Set(['pass', 'change', 'contras', 'senha']); - let notificationBarData = null; - const isSafari = (typeof safari !== 'undefined') && navigator.userAgent.indexOf(' Safari/') !== -1 && - navigator.userAgent.indexOf('Chrome') === -1; let disabledAddLoginNotification = false; let disabledChangedPasswordNotification = false; - if (isSafari) { - if ((window as any).__bitwardenFrameId == null) { - (window as any).__bitwardenFrameId = Math.floor(Math.random() * Math.floor(99999999)); - } - if (inIframe) { + chrome.storage.local.get('neverDomains', (ndObj: any) => { + const domains = ndObj.neverDomains; + if (domains != null && domains.hasOwnProperty(window.location.hostname)) { return; } - const responseCommand = 'notificationBarDataResponse'; - safari.extension.dispatchMessage('bitwarden', { - command: 'bgGetDataForTab', - responseCommand: responseCommand, - bitwardenFrameId: (window as any).__bitwardenFrameId, - }); - safari.self.addEventListener('message', (msgEvent: any) => { - const msg = JSON.parse(msgEvent.message.msg); - if (msg.bitwardenFrameId != null && (window as any).__bitwardenFrameId !== msg.bitwardenFrameId) { - return; - } - if (msg.command === responseCommand && msg.data) { - notificationBarData = msg.data; - if (notificationBarData.neverDomains && - notificationBarData.neverDomains.hasOwnProperty(window.location.hostname)) { - return; - } - - disabledAddLoginNotification = notificationBarData.disabledAddLoginNotification === true; - disabledChangedPasswordNotification = notificationBarData.disabledChangedPasswordNotification === true; + chrome.storage.local.get('disableAddLoginNotification', (disAddObj: any) => { + disabledAddLoginNotification = disAddObj != null && disAddObj.disableAddLoginNotification === true; + chrome.storage.local.get('disableChangedPasswordNotification', (disChangedObj: any) => { + disabledChangedPasswordNotification = disChangedObj != null && + disChangedObj.disableChangedPasswordNotification === true; if (!disabledAddLoginNotification || !disabledChangedPasswordNotification) { collectIfNeededWithTimeout(); } - } - - processMessages(msg, () => { /* do nothing on send response for Safari */ }); - }, false); - return; - } else { - chrome.storage.local.get('neverDomains', (ndObj: any) => { - const domains = ndObj.neverDomains; - if (domains != null && domains.hasOwnProperty(window.location.hostname)) { - return; - } - - chrome.storage.local.get('disableAddLoginNotification', (disAddObj: any) => { - disabledAddLoginNotification = disAddObj != null && disAddObj.disableAddLoginNotification === true; - chrome.storage.local.get('disableChangedPasswordNotification', (disChangedObj: any) => { - disabledChangedPasswordNotification = disChangedObj != null && - disChangedObj.disableChangedPasswordNotification === true; - if (!disabledAddLoginNotification || !disabledChangedPasswordNotification) { - collectIfNeededWithTimeout(); - } - }); }); }); + }); - chrome.runtime.onMessage.addListener((msg: any, sender: any, sendResponse: Function) => { - processMessages(msg, sendResponse); - }); - } + chrome.runtime.onMessage.addListener((msg: any, sender: any, sendResponse: Function) => { + processMessages(msg, sendResponse); + }); function processMessages(msg: any, sendResponse: Function) { if (msg.command === 'openNotificationBar') { @@ -470,7 +429,7 @@ document.addEventListener('DOMContentLoaded', (event) => { } function closeExistingAndOpenBar(type: string, typeData: any) { - let barPage = (isSafari ? 'app/' : '') + 'notification/bar.html'; + let barPage = 'notification/bar.html'; switch (type) { case 'info': barPage = barPage + '?info=' + typeData.text; @@ -510,7 +469,7 @@ document.addEventListener('DOMContentLoaded', (event) => { return; } - const barPageUrl: string = isSafari ? (safari.extension.baseURI + barPage) : chrome.extension.getURL(barPage); + const barPageUrl: string = chrome.extension.getURL(barPage); const iframe = document.createElement('iframe'); iframe.style.cssText = 'height: 42px; width: 100%; border: 0; min-height: initial;'; @@ -580,11 +539,6 @@ document.addEventListener('DOMContentLoaded', (event) => { } function sendPlatformMessage(msg: any) { - if (isSafari) { - msg.bitwardenFrameId = (window as any).__bitwardenFrameId; - safari.extension.dispatchMessage('bitwarden', msg); - } else { - chrome.runtime.sendMessage(msg); - } + chrome.runtime.sendMessage(msg); } }); diff --git a/src/content/shortcuts.ts b/src/content/shortcuts.ts index f2d8de67dc..fe9699ac52 100644 --- a/src/content/shortcuts.ts +++ b/src/content/shortcuts.ts @@ -45,11 +45,6 @@ document.addEventListener('DOMContentLoaded', (event) => { shortcut: shortcut, }; - if (isSafari) { - msg.bitwardenFrameId = (window as any).__bitwardenFrameId; - safari.extension.dispatchMessage('bitwarden', msg); - } else { - chrome.runtime.sendMessage(msg); - } + chrome.runtime.sendMessage(msg); } }); diff --git a/src/content/sso.ts b/src/content/sso.ts index a127a39d55..508bc2aea3 100644 --- a/src/content/sso.ts +++ b/src/content/sso.ts @@ -3,15 +3,6 @@ window.addEventListener('message', (event) => { return; if (event.data.command && (event.data.command === 'authResult')) { - if (typeof chrome === typeof undefined) { - safari.extension.dispatchMessage('bitwarden', { - command: event.data.command, - code: event.data.code, - state: event.data.state, - referrer: event.source.location.hostname, - }); - return; - } chrome.runtime.sendMessage({ command: event.data.command, code: event.data.code, diff --git a/src/notification/bar.js b/src/notification/bar.js index c882bd2931..a5e55ca664 100644 --- a/src/notification/bar.js +++ b/src/notification/bar.js @@ -3,34 +3,21 @@ require('./bar.scss'); document.addEventListener('DOMContentLoaded', () => { var i18n = {}; var lang = window.navigator.language; - if (typeof safari !== 'undefined') { - const responseCommand = 'notificationBarFrameDataResponse'; - sendPlatformMessage({ - command: 'bgGetDataForTab', - responseCommand: responseCommand - }); - safari.self.addEventListener('message', (msgEvent) => { - const msg = JSON.parse(msgEvent.message.msg); - if (msg.command === responseCommand && msg.data) { - i18n = msg.data.i18n; - load(); - } - }, false); - } else { - i18n.appName = chrome.i18n.getMessage('appName'); - i18n.close = chrome.i18n.getMessage('close'); - i18n.yes = chrome.i18n.getMessage('yes'); - i18n.never = chrome.i18n.getMessage('never'); - i18n.notificationAddSave = chrome.i18n.getMessage('notificationAddSave'); - i18n.notificationNeverSave = chrome.i18n.getMessage('notificationNeverSave'); - i18n.notificationAddDesc = chrome.i18n.getMessage('notificationAddDesc'); - i18n.notificationChangeSave = chrome.i18n.getMessage('notificationChangeSave'); - i18n.notificationChangeDesc = chrome.i18n.getMessage('notificationChangeDesc'); - lang = chrome.i18n.getUILanguage(); + + i18n.appName = chrome.i18n.getMessage('appName'); + i18n.close = chrome.i18n.getMessage('close'); + i18n.yes = chrome.i18n.getMessage('yes'); + i18n.never = chrome.i18n.getMessage('never'); + i18n.notificationAddSave = chrome.i18n.getMessage('notificationAddSave'); + i18n.notificationNeverSave = chrome.i18n.getMessage('notificationNeverSave'); + i18n.notificationAddDesc = chrome.i18n.getMessage('notificationAddDesc'); + i18n.notificationChangeSave = chrome.i18n.getMessage('notificationChangeSave'); + i18n.notificationChangeDesc = chrome.i18n.getMessage('notificationChangeDesc'); + lang = chrome.i18n.getUILanguage(); - // delay 50ms so that we get proper body dimensions - setTimeout(load, 50); - } + // delay 50ms so that we get proper body dimensions + setTimeout(load, 50); + function load() { var closeButton = document.getElementById('close-button'), @@ -131,10 +118,6 @@ document.addEventListener('DOMContentLoaded', () => { } function sendPlatformMessage(msg) { - if (typeof safari !== 'undefined') { - safari.extension.dispatchMessage('bitwarden', msg); - } else { - chrome.runtime.sendMessage(msg); - } + chrome.runtime.sendMessage(msg); } }); diff --git a/src/popup/accounts/two-factor.component.ts b/src/popup/accounts/two-factor.component.ts index a84ed5070a..57aef6e4d9 100644 --- a/src/popup/accounts/two-factor.component.ts +++ b/src/popup/accounts/two-factor.component.ts @@ -58,13 +58,12 @@ export class TwoFactorComponent extends BaseTwoFactorComponent { // ref: https://bugzilla.mozilla.org/show_bug.cgi?id=1562620 this.initU2f = false; } - const isSafari = this.platformUtilsService.isSafari(); await super.ngOnInit(); if (this.selectedProviderType == null) { return; } - if (!isSafari && this.selectedProviderType === TwoFactorProviderType.Email && + if (this.selectedProviderType === TwoFactorProviderType.Email && this.popupUtilsService.inPopup(window)) { const confirmed = await this.platformUtilsService.showDialog(this.i18nService.t('popup2faCloseMessage'), null, this.i18nService.t('yes'), this.i18nService.t('no')); diff --git a/src/popup/app-routing.animations.ts b/src/popup/app-routing.animations.ts index b75b15e2a7..0c44460d62 100644 --- a/src/popup/app-routing.animations.ts +++ b/src/popup/app-routing.animations.ts @@ -185,8 +185,6 @@ export const routerTransition = trigger('routerTransition', [ transition('tabs => premium', inSlideLeft), transition('premium => tabs', outSlideRight), -]); -if (!BrowserApi.isSafariApi) { - routerTransition.definitions.push(transition('tabs => lock', inSlideDown)); -} + transition('tabs => lock', inSlideDown), +]); diff --git a/src/popup/components/pop-out.component.html b/src/popup/components/pop-out.component.html index 2f14e7c0cb..1c34a1c762 100644 --- a/src/popup/components/pop-out.component.html +++ b/src/popup/components/pop-out.component.html @@ -1,4 +1,4 @@ - + diff --git a/src/popup/components/pop-out.component.ts b/src/popup/components/pop-out.component.ts index 40e02c6906..df9ceba57f 100644 --- a/src/popup/components/pop-out.component.ts +++ b/src/popup/components/pop-out.component.ts @@ -22,8 +22,7 @@ export class PopOutComponent implements OnInit { ngOnInit() { if (this.show) { - this.show = !this.platformUtilsService.isSafari(); - if (this.show && this.popupUtilsService.inSidebar(window) && this.platformUtilsService.isFirefox()) { + if (this.popupUtilsService.inSidebar(window) && this.platformUtilsService.isFirefox()) { this.show = false; } } diff --git a/src/popup/services/popup-utils.service.ts b/src/popup/services/popup-utils.service.ts index 8f694c87b4..297c6c1470 100644 --- a/src/popup/services/popup-utils.service.ts +++ b/src/popup/services/popup-utils.service.ts @@ -68,8 +68,6 @@ export class PopupUtilsService { chrome.tabs.create({ url: href, }); - } else if ((typeof safari !== 'undefined')) { - // Safari can't open popup in full page tab :( } } } diff --git a/src/popup/settings/options.component.html b/src/popup/settings/options.component.html index 6104b85d84..938bcd756e 100644 --- a/src/popup/settings/options.component.html +++ b/src/popup/settings/options.component.html @@ -96,7 +96,7 @@ -
+
diff --git a/src/popup/settings/options.component.ts b/src/popup/settings/options.component.ts index 4050060617..e5cf6322bb 100644 --- a/src/popup/settings/options.component.ts +++ b/src/popup/settings/options.component.ts @@ -29,7 +29,6 @@ export class OptionsComponent implements OnInit { disableChangedPasswordNotification = false; dontShowCards = false; dontShowIdentities = false; - showDisableContextMenu = true; showClearClipboard = true; theme: string; themeOptions: any[]; @@ -68,8 +67,6 @@ export class OptionsComponent implements OnInit { } async ngOnInit() { - this.showDisableContextMenu = !this.platformUtilsService.isSafari(); - this.enableAutoFillOnPageLoad = await this.storageService.get( ConstantsService.enableAutoFillOnPageLoadKey); diff --git a/src/popup/vault/current-tab.component.html b/src/popup/vault/current-tab.component.html index 4fe67ae9b2..0d4181f5cb 100644 --- a/src/popup/vault/current-tab.component.html +++ b/src/popup/vault/current-tab.component.html @@ -1,5 +1,5 @@
-
+
+ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/safari/desktop/Info.plist b/src/safari/desktop/Info.plist index 3c9b847a73..201669d037 100644 --- a/src/safari/desktop/Info.plist +++ b/src/safari/desktop/Info.plist @@ -11,13 +11,13 @@ CFBundleIconFile CFBundleIdentifier - com.bitwarden.desktop + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName - Bitwarden + $(PRODUCT_NAME) CFBundlePackageType - APPL + $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString 1.0 CFBundleVersion diff --git a/src/safari/desktop/ViewController.swift b/src/safari/desktop/ViewController.swift index 5af73ffee0..fbda5decf8 100644 --- a/src/safari/desktop/ViewController.swift +++ b/src/safari/desktop/ViewController.swift @@ -1,14 +1,44 @@ import Cocoa +import SafariServices.SFSafariApplication +import SafariServices.SFSafariExtensionManager + +let appName = "desktop" +let extensionBundleIdentifier = "com.bitwarden.desktop.Extension" class ViewController: NSViewController { + + @IBOutlet var appNameLabel: NSTextField! + override func viewDidLoad() { super.viewDidLoad() - // Do any additional setup after loading the view. - } + self.appNameLabel.stringValue = appName + SFSafariExtensionManager.getStateOfSafariExtension(withIdentifier: extensionBundleIdentifier) { (state, error) in + guard let state = state, error == nil else { + // Insert code to inform the user that something went wrong. + return + } - override var representedObject: Any? { - didSet { - // Update the view, if already loaded. + DispatchQueue.main.async { + if (state.isEnabled) { + self.appNameLabel.stringValue = "\(appName)'s extension is currently on." + } else { + self.appNameLabel.stringValue = "\(appName)'s extension is currently off. You can turn it on in Safari Extensions preferences." + } + } } } + + @IBAction func openSafariExtensionPreferences(_ sender: AnyObject?) { + SFSafariApplication.showPreferencesForExtension(withIdentifier: extensionBundleIdentifier) { error in + guard error == nil else { + // Insert code to inform the user that something went wrong. + return + } + + DispatchQueue.main.async { + NSApplication.shared.terminate(nil) + } + } + } + } diff --git a/src/safari/safari/Base.lproj/SafariExtensionViewController.xib b/src/safari/safari/Base.lproj/SafariExtensionViewController.xib deleted file mode 100644 index 3716df7567..0000000000 --- a/src/safari/safari/Base.lproj/SafariExtensionViewController.xib +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/src/safari/safari/Info.plist b/src/safari/safari/Info.plist index 82f785e9c6..8f23fb23e0 100644 --- a/src/safari/safari/Info.plist +++ b/src/safari/safari/Info.plist @@ -9,13 +9,13 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - com.bitwarden.desktop.safari + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName - Bitwarden + $(PRODUCT_NAME) CFBundlePackageType - XPC! + $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString 0.0.1 CFBundleVersion @@ -25,63 +25,17 @@ NSExtension NSExtensionPointIdentifier - com.apple.Safari.extension + com.apple.Safari.web-extension NSExtensionPrincipalClass - $(PRODUCT_MODULE_NAME).SafariExtensionHandler - SFSafariStyleSheet - - - Style Sheet - app/content/autofill.css - - - SFSafariContentScript - - - Script - app/content/autofill.js - - - Script - app/content/autofiller.js - - - Script - app/content/notificationBar.js - - - Script - app/content/shortcuts.js - - - Script - app/content/sso.js - - - SFSafariToolbarItem - - Action - Popover - Identifier - Button - Image - ToolbarItemIcon.pdf - Label - Bitwarden - - SFSafariWebsiteAccess - - Level - All - - SFSafariExtensionBundleIdentifiersToUninstall - - com.bitwarden.safari - + $(PRODUCT_MODULE_NAME).SafariWebExtensionHandler NSHumanReadableCopyright Copyright © 2020 Bitwarden Inc. All rights reserved. NSHumanReadableDescription A secure and free password manager for all of your devices. + SFSafariAppExtensionBundleIdentifiersToReplace + + com.bitwarden.desktop.safari + diff --git a/src/safari/safari/SafariExtensionHandler.swift b/src/safari/safari/SafariExtensionHandler.swift deleted file mode 100644 index 3d8711f401..0000000000 --- a/src/safari/safari/SafariExtensionHandler.swift +++ /dev/null @@ -1,101 +0,0 @@ -import SafariServices - -class SafariExtensionHandler: SFSafariExtensionHandler { - override init() { - super.init() - SafariExtensionViewController.shared.initWebView() - } - - override func messageReceived(withName messageName: String, from page: SFSafariPage, userInfo: [String: Any]?) { - // This method will be called when a content script provided by your extension - // calls safari.extension.dispatchMessage("message"). - if messageName == "bitwarden" { - page.getPropertiesWithCompletionHandler { properties in - DispatchQueue.main.async { - makeSenderTabObject(page: page, props: properties, complete: { senderTab in - DispatchQueue.main.async { - self.sendMessage(msg: userInfo, sender: senderTab) - } - }) - } - } - } - } - - override func toolbarItemClicked(in _: SFSafariWindow) { - // This method will be called when your toolbar item is clicked. - } - - override func validateToolbarItem(in _: SFSafariWindow, validationHandler: @escaping ((Bool, String) -> Void)) { - // This is called when Safari's state changed in some way that would require the extension's - // toolbar item to be validated again. - validationHandler(true, "") - } - - override func popoverViewController() -> SFSafariExtensionViewController { - return SafariExtensionViewController.shared - } - - override func popoverWillShow(in _: SFSafariWindow) { - SafariExtensionViewController.shared.popoverOpenCount += 1 - DispatchQueue.main.async { - self.sendMessage(msg: ["command": "reloadPopup"], sender: nil) - } - } - - override func popoverDidClose(in _: SFSafariWindow) { - SafariExtensionViewController.shared.popoverOpenCount -= 1 - } - - func sendMessage(msg: [String: Any]?, sender: Tab? = nil) { - if SafariExtensionViewController.shared.webView == nil { - return - } - let newMsg = AppMessage() - newMsg.command = "app_message" - newMsg.senderTab = sender - do { - let jsonData = try JSONSerialization.data(withJSONObject: msg as Any, options: []) - newMsg.data = String(data: jsonData, encoding: .utf8) - } catch let error { - print("error converting to json: \(error)") - } - SafariExtensionViewController.shared.replyMessage(message: newMsg) - } -} - -func makeSenderTabObject(page: SFSafariPage, props: SFSafariPageProperties?, complete: @escaping (Tab) -> Void) { - let t = Tab() - t.title = props?.title - t.url = props?.url?.absoluteString - page.getContainingTab { tab in - tab.getContainingWindow(completionHandler: { win in - guard let window = win else { - t.active = false; - t.windowId = -100 - SFSafariApplication.getAllWindows(completionHandler: { allWins in - if (allWins.count == 0) { - return - } - allWins[0].getAllTabs { allWinTabs in - t.index = allWinTabs.firstIndex(of: tab) ?? -1 - t.id = "\(t.windowId)_\(t.index)" - complete(t) - } - }) - return - } - window.getActiveTab(completionHandler: { activeTab in - t.active = activeTab != nil && tab == activeTab - SFSafariApplication.getAllWindows(completionHandler: { allWins in - t.windowId = allWins.firstIndex(of: window) ?? -100 - window.getAllTabs { allWinTabs in - t.index = allWinTabs.firstIndex(of: tab) ?? -1 - t.id = "\(t.windowId)_\(t.index)" - complete(t) - } - }) - }) - }) - } -} diff --git a/src/safari/safari/SafariExtensionViewController.swift b/src/safari/safari/SafariExtensionViewController.swift deleted file mode 100644 index 0241df00ce..0000000000 --- a/src/safari/safari/SafariExtensionViewController.swift +++ /dev/null @@ -1,440 +0,0 @@ -import SafariServices -import WebKit - -class SafariExtensionViewController: SFSafariExtensionViewController, WKScriptMessageHandler, WKNavigationDelegate { - var webView: WKWebView! - var initedWebView: Bool = false - var popoverOpenCount: Int = 0 - - static let shared: SafariExtensionViewController = { - let shared = SafariExtensionViewController() - shared.preferredContentSize = NSSize(width: 375, height: 600) - return shared - }() - - func initWebView() { - if initedWebView { - return - } - initedWebView = true - let parentHeight = SafariExtensionViewController.shared.preferredContentSize.height - let parentWidth = SafariExtensionViewController.shared.preferredContentSize.width - let webViewConfig = WKWebViewConfiguration() - webViewConfig.preferences.setValue(true, forKey: "allowFileAccessFromFileURLs") - webViewConfig.preferences.setValue(true, forKey: "developerExtrasEnabled") - webViewConfig.userContentController.add(self, name: "bitwardenApp") - webView = WKWebView(frame: CGRect(x: 0, y: 0, width: parentWidth, height: parentHeight), - configuration: webViewConfig) - webView.navigationDelegate = self - webView.allowsLinkPreview = false - navigateWebView("app/popup/index.html") - webView.alphaValue = 0.0 - webView.uiDelegate = self - view.addSubview(webView) - } - - func navigateWebView(_ relativeUrl: String){ - let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String - let bundleUrl = Bundle.main.resourceURL!.absoluteURL - - if var urlComponents = URLComponents(string: bundleUrl.absoluteString + relativeUrl) { - if (urlComponents.queryItems?.first(where: { $0.name == "appVersion" })?.value == nil) { - urlComponents.queryItems = urlComponents.queryItems ?? [] - urlComponents.queryItems!.append(URLQueryItem(name: "appVersion", value: version)) - } - - webView.loadFileURL(urlComponents.url!, allowingReadAccessTo: bundleUrl) - } - } - - func webView(_ webView: WKWebView, didFinish _: WKNavigation!) { - if #available(OSXApplicationExtension 10.12, *) { - NSAnimationContext.runAnimationGroup({ _ in - NSAnimationContext.current.duration = 0.35 - webView.animator().alphaValue = 1.0 - }) - } else { - // Fallback on earlier versions - } - } - - override func viewDidLoad() { - super.viewDidLoad() - let backgroundColor = NSColor(red: (39 / 255.0), green: (42 / 255.0), blue: (46 / 255.0), alpha: 1.0) - view.setValue(backgroundColor, forKey: "backgroundColor") - initWebView() - } - - func userContentController(_: WKUserContentController, didReceive message: WKScriptMessage) { - if message.name != "bitwardenApp" { - return - } - guard let messageBody = message.body as? String else { - return - } - guard let m: AppMessage = jsonDeserialize(json: messageBody) else { - return - } - let command = m.command - NSLog("Command: \(command)") - if command == "storage_get" { - if let data = m.data { - let obj = UserDefaults.standard.string(forKey: data) - m.responseData = obj - replyMessage(message: m) - } - } else if command == "storage_save" { - guard let data: StorageData = jsonDeserialize(json: m.data) else { - return - } - if let obj = data.obj { - UserDefaults.standard.set(obj, forKey: data.key) - } else { - UserDefaults.standard.removeObject(forKey: data.key) - } - replyMessage(message: m) - } else if command == "storage_remove" { - if let data = m.data { - UserDefaults.standard.removeObject(forKey: data) - replyMessage(message: m) - } - } else if command == "getLocaleStrings" { - let language = m.data ?? "en" - guard let bundleUrl = Bundle.main.resourceURL?.absoluteURL else { - return - } - let messagesUrl = bundleUrl.appendingPathComponent("app/_locales/\(language)/messages.json") - do { - let json = try String(contentsOf: messagesUrl, encoding: .utf8) - webView.evaluateJavaScript("window.bitwardenLocaleStrings = \(json);", completionHandler: {(result, error) in - guard let err = error else { - return; - } - NSLog("evaluateJavaScript error : %@", err.localizedDescription); - }) - } catch { - NSLog("ERROR on getLocaleStrings, \(error)") - } - replyMessage(message: m) - } else if command == "tabs_query" { - guard let options: TabQueryOptions = jsonDeserialize(json: m.data) else { - return - } - if options.currentWindow ?? false { - SFSafariApplication.getActiveWindow { win in - if win != nil { - processWindowsForTabs(wins: [win!], options: options, complete: { tabs in - m.responseData = jsonSerialize(obj: tabs) - self.replyMessage(message: m) - }) - } else { - SFSafariApplication.getAllWindows { wins in - processWindowsForTabs(wins: wins, options: options, complete: { tabs in - m.responseData = jsonSerialize(obj: tabs) - self.replyMessage(message: m) - }) - } - } - } - } else { - SFSafariApplication.getAllWindows { wins in - processWindowsForTabs(wins: wins, options: options, complete: { tabs in - m.responseData = jsonSerialize(obj: tabs) - self.replyMessage(message: m) - }) - } - } - } else if command == "tabs_message" { - guard let tabMsg: TabMessage = jsonDeserialize(json: m.data) else { - return - } - SFSafariApplication.getAllWindows { wins in - var theWin: SFSafariWindow? - var winIndex = 0 - for win in wins { - if tabMsg.tab.windowId == winIndex { - theWin = win - break - } - winIndex = winIndex + 1 - } - var theTab: SFSafariTab? - theWin?.getAllTabs { tabs in - var tabIndex = 0 - for tab in tabs { - if tabMsg.tab.index == tabIndex { - theTab = tab - break - } - tabIndex = tabIndex + 1 - } - theTab?.getActivePage { activePage in - activePage?.dispatchMessageToScript(withName: "bitwarden", userInfo: ["msg": tabMsg.obj]) - } - } - } - } else if command == "hidePopover" { - dismissPopover() - replyMessage(message: m) - } else if command == "showPopover" { - if popoverOpenCount <= 0 { - SFSafariApplication.getActiveWindow { win in - win?.getToolbarItem(completionHandler: { item in - item?.showPopover() - }) - } - } - } else if command == "isPopoverOpen" { - m.responseData = popoverOpenCount > 0 ? "true" : "false" - replyMessage(message: m) - } else if command == "createNewTab" { - if let data = m.data, let url = URL(string: data) { - if !data.starts(with: "https://") && !data.starts(with: "http://") { - SFSafariApplication.getActiveWindow { win in - win?.getToolbarItem(completionHandler: { item in - item?.showPopover() - self.navigateWebView("app/" + url.absoluteString) - }) - } - } - SFSafariApplication.getActiveWindow { win in - win?.openTab(with: url, makeActiveIfPossible: true, completionHandler: { _ in - // Tab opened - }) - } - } - } else if command == "reloadExtension" { - webView?.reload() - replyMessage(message: m) - } else if command == "copyToClipboard" { - let pasteboard = NSPasteboard.general - pasteboard.declareTypes([NSPasteboard.PasteboardType.string], owner: nil) - pasteboard.setString(m.data ?? "", forType: NSPasteboard.PasteboardType.string) - replyMessage(message: m) - } else if command == "readFromClipboard" { - let pasteboard = NSPasteboard.general - m.responseData = pasteboard.pasteboardItems?.first?.string(forType: .string) - replyMessage(message: m) - } else if command == "downloadFile" { - guard let jsonData = m.data else { - return - } - guard let dlMsg: DownloadFileMessage = jsonDeserialize(json: jsonData) else { - return - } - var blobData: Data? - if dlMsg.blobOptions?.type == "text/plain" { - blobData = dlMsg.blobData?.data(using: .utf8) - } else if let blob = dlMsg.blobData { - blobData = Data(base64Encoded: blob) - } - guard let data = blobData else { - return - } - let panel = NSSavePanel() - panel.canCreateDirectories = true - panel.nameFieldStringValue = dlMsg.fileName - panel.begin { response in - if response == NSApplication.ModalResponse.OK { - if let url = panel.url { - do { - let fileManager = FileManager.default - if !fileManager.fileExists(atPath: url.absoluteString) { - fileManager.createFile(atPath: url.absoluteString, contents: Data(), - attributes: nil) - } - try data.write(to: url) - } catch { - print(error) - NSLog("ERROR in downloadFile, \(error)") - } - } - } - } - } - } - - func replyMessage(message: AppMessage) { - if webView == nil { - return - } - let json = (jsonSerialize(obj: message) ?? "null") - webView.evaluateJavaScript("window.bitwardenSafariAppMessageReceiver(\(json));", completionHandler: {(result, error) in - guard let err = error else { - return; - } - NSLog("evaluateJavaScript error : %@", err.localizedDescription); - }) - } -} - -extension SafariExtensionViewController: WKUIDelegate { - @available(OSXApplicationExtension 10.12, *) - func webView(_: WKWebView, runOpenPanelWith _: WKOpenPanelParameters, initiatedByFrame _: WKFrameInfo, - completionHandler: @escaping ([URL]?) -> Void) { - let openPanel = NSOpenPanel() - openPanel.canChooseFiles = true - openPanel.begin { result in - if result == NSApplication.ModalResponse.OK && openPanel.url != nil { - completionHandler([openPanel.url!]) - } else { - completionHandler(nil) - } - } - } -} - -func processWindowsForTabs(wins: [SFSafariWindow], options: TabQueryOptions?, complete: @escaping ([Tab]) -> Void) { - if wins.count == 0 { - complete([]) - return - } - var newTabs: [Tab] = [] - let winGroup = DispatchGroup() - for win in wins { - winGroup.enter() - win.getActiveTab { activeTab in - win.getAllTabs { allTabs in - let tabGroup = DispatchGroup() - for tab in allTabs { - tabGroup.enter() - if options?.active ?? false { - if activeTab != nil && activeTab == tab { - let windowIndex = wins.firstIndex(of: win) ?? -100 - let tabIndex = allTabs.firstIndex(of: tab) ?? -1 - makeTabObject(tab: tab, activeTab: activeTab, windowIndex: windowIndex, - tabIndex: tabIndex, complete: { t in - newTabs.append(t) - tabGroup.leave() - }) - } else { - tabGroup.leave() - } - } else { - let windowIndex = wins.firstIndex(of: win) ?? -100 - let tabIndex = allTabs.firstIndex(of: tab) ?? -1 - makeTabObject(tab: tab, activeTab: activeTab, windowIndex: windowIndex, - tabIndex: tabIndex, complete: { t in - newTabs.append(t) - tabGroup.leave() - }) - } - } - tabGroup.notify(queue: .main) { - winGroup.leave() - } - } - } - } - winGroup.notify(queue: .main) { - complete(newTabs) - } -} - -func makeTabObject(tab: SFSafariTab, activeTab: SFSafariTab?, windowIndex: Int, tabIndex: Int, - complete: @escaping (Tab) -> Void) { - let t = Tab() - t.active = activeTab != nil && tab == activeTab - t.windowId = windowIndex - t.index = tabIndex - t.id = "\(windowIndex)_\(tabIndex)" - tab.getActivePage { page in - guard let activePage = page else { - complete(t) - return - } - activePage.getPropertiesWithCompletionHandler({ props in - t.title = props?.title - t.url = props?.url?.absoluteString - complete(t) - }) - } -} - -func jsonSerialize(obj: T?) -> String? { - let encoder = JSONEncoder() - do { - let data = try encoder.encode(obj) - return String(data: data, encoding: .utf8) ?? "null" - } catch _ { - return "null" - } -} - -func jsonDeserialize(json: String?) -> T? { - if json == nil { - return nil - } - let decoder = JSONDecoder() - do { - let obj = try decoder.decode(T.self, from: json!.data(using: .utf8)!) - return obj - } catch _ { - return nil - } -} - -class AppMessage: Decodable, Encodable { - init() { - id = "" - command = "" - data = nil - responseData = nil - responseError = nil - } - - var id: String - var command: String - var data: String? - var responseData: String? - var responseError: Bool? - var senderTab: Tab? -} - -class StorageData: Decodable, Encodable { - var key: String - var obj: String? -} - -class TabQueryOptions: Decodable, Encodable { - var currentWindow: Bool? - var active: Bool? -} - -class Tab: Decodable, Encodable { - init() { - id = "" - index = -1 - windowId = -100 - title = "" - active = false - url = "" - } - - var id: String - var index: Int - var windowId: Int - var title: String? - var active: Bool - var url: String? -} - -class TabMessage: Decodable, Encodable { - var tab: Tab - var obj: String - var options: TabMessageOptions? -} - -class TabMessageOptions: Decodable, Encodable { - var frameId: Int? -} - -class DownloadFileMessage: Decodable, Encodable { - var fileName: String - var blobData: String? - var blobOptions: DownloadFileMessageBlobOptions? -} - -class DownloadFileMessageBlobOptions: Decodable, Encodable { - var type: String? -} diff --git a/src/safari/safari/SafariWebExtensionHandler.swift b/src/safari/safari/SafariWebExtensionHandler.swift new file mode 100644 index 0000000000..001d388e6e --- /dev/null +++ b/src/safari/safari/SafariWebExtensionHandler.swift @@ -0,0 +1,109 @@ +import SafariServices +import os.log + +let SFExtensionMessageKey = "message" + +class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling { + + func beginRequest(with context: NSExtensionContext) { + let item = context.inputItems[0] as! NSExtensionItem + let message = item.userInfo?[SFExtensionMessageKey] as AnyObject? + os_log(.default, "Received message from browser.runtime.sendNativeMessage: %@", message as! CVarArg) + + let response = NSExtensionItem() + + guard let command = message?["command"] as? String else { + return + } + + switch (command) { + case "readFromClipboard": + let pasteboard = NSPasteboard.general + response.userInfo = [ SFExtensionMessageKey: pasteboard.pasteboardItems?.first?.string(forType: .string) as Any ] + break + case "showPopover": + SFSafariApplication.getActiveWindow { win in + win?.getToolbarItem(completionHandler: { item in + item?.showPopover() + }) + } + break + case "downloadFile": + guard let jsonData = message?["data"] as? String else { + return + } + guard let dlMsg: DownloadFileMessage = jsonDeserialize(json: jsonData) else { + return + } + var blobData: Data? + if dlMsg.blobOptions?.type == "text/plain" { + blobData = dlMsg.blobData?.data(using: .utf8) + } else if let blob = dlMsg.blobData { + blobData = Data(base64Encoded: blob) + } + guard let data = blobData else { + return + } + let panel = NSSavePanel() + panel.canCreateDirectories = true + panel.nameFieldStringValue = dlMsg.fileName + panel.begin { response in + if response == NSApplication.ModalResponse.OK { + if let url = panel.url { + do { + let fileManager = FileManager.default + if !fileManager.fileExists(atPath: url.absoluteString) { + fileManager.createFile(atPath: url.absoluteString, contents: Data(), + attributes: nil) + } + try data.write(to: url) + } catch { + print(error) + NSLog("ERROR in downloadFile, \(error)") + } + } + } + } + break + + default: + return + } + + context.completeRequest(returningItems: [response], completionHandler: nil) + } + +} + +func jsonSerialize(obj: T?) -> String? { + let encoder = JSONEncoder() + do { + let data = try encoder.encode(obj) + return String(data: data, encoding: .utf8) ?? "null" + } catch _ { + return "null" + } +} + +func jsonDeserialize(json: String?) -> T? { + if json == nil { + return nil + } + let decoder = JSONDecoder() + do { + let obj = try decoder.decode(T.self, from: json!.data(using: .utf8)!) + return obj + } catch _ { + return nil + } +} + +class DownloadFileMessage: Decodable, Encodable { + var fileName: String + var blobData: String? + var blobOptions: DownloadFileMessageBlobOptions? +} + +class DownloadFileMessageBlobOptions: Decodable, Encodable { + var type: String? +} diff --git a/src/safari/safari/ToolbarItemIcon.pdf b/src/safari/safari/ToolbarItemIcon.pdf deleted file mode 100644 index dbf98ce90e..0000000000 Binary files a/src/safari/safari/ToolbarItemIcon.pdf and /dev/null differ diff --git a/src/safari/safari/app/popup/index.html b/src/safari/safari/app/popup/index.html deleted file mode 100644 index a6282445c3..0000000000 --- a/src/safari/safari/app/popup/index.html +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - Bitwarden - - - - - - -
-
- - - - - - - - - diff --git a/src/services/browserMessaging.service.ts b/src/services/browserMessaging.service.ts index 01f94269fb..e5bd9b1e98 100644 --- a/src/services/browserMessaging.service.ts +++ b/src/services/browserMessaging.service.ts @@ -1,16 +1,8 @@ -import { BrowserApi } from '../browser/browserApi'; -import { SafariApp } from '../browser/safariApp'; - import { MessagingService } from 'jslib/abstractions/messaging.service'; export default class BrowserMessagingService implements MessagingService { send(subscriber: string, arg: any = {}) { const message = Object.assign({}, { command: subscriber }, arg); - if (BrowserApi.isSafariApi) { - SafariApp.sendMessageToApp(subscriber, arg); - SafariApp.sendMessageToListeners(message, 'BrowserMessagingService', null); - } else { - chrome.runtime.sendMessage(message); - } + chrome.runtime.sendMessage(message); } } diff --git a/src/services/browserPlatformUtils.service.ts b/src/services/browserPlatformUtils.service.ts index 38bab53b43..88fd5b2ee0 100644 --- a/src/services/browserPlatformUtils.service.ts +++ b/src/services/browserPlatformUtils.service.ts @@ -27,7 +27,7 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService return this.deviceCache; } - if (this.isSafariExtension()) { + if (navigator.userAgent.indexOf(' Safari/') !== -1) { this.deviceCache = DeviceType.SafariExtension; } else if (navigator.userAgent.indexOf(' Firefox/') !== -1 || navigator.userAgent.indexOf(' Gecko/') !== -1) { this.deviceCache = DeviceType.FirefoxExtension; @@ -190,13 +190,7 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService } const clearing = options ? !!options.clearing : false; const clearMs: number = options && options.clearMs ? options.clearMs : null; - if (this.isSafariExtension()) { - SafariApp.sendMessageToApp('copyToClipboard', text).then(() => { - if (!clearing && this.clipboardWriteCallback != null) { - this.clipboardWriteCallback(text, clearMs); - } - }); - } else if (this.isFirefox() && (win as any).navigator.clipboard && (win as any).navigator.clipboard.writeText) { + if (this.isFirefox() && (win as any).navigator.clipboard && (win as any).navigator.clipboard.writeText) { (win as any).navigator.clipboard.writeText(text).then(() => { if (!clearing && this.clipboardWriteCallback != null) { this.clipboardWriteCallback(text, clearMs); @@ -244,7 +238,7 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService doc = options.doc; } - if (this.isSafariExtension()) { + if (this.isSafari()) { return await SafariApp.sendMessageToApp('readFromClipboard'); } else if (this.isFirefox() && (win as any).navigator.clipboard && (win as any).navigator.clipboard.readText) { return await (win as any).navigator.clipboard.readText(); @@ -311,10 +305,6 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService return false; } - private isSafariExtension(): boolean { - return (window as any).safariAppExtension === true; - } - getDefaultSystemTheme() { return this.prefersColorSchemeDark.matches ? 'dark' : 'light'; } diff --git a/src/services/browserStorage.service.ts b/src/services/browserStorage.service.ts index 7510327d7d..9d3a6bc677 100644 --- a/src/services/browserStorage.service.ts +++ b/src/services/browserStorage.service.ts @@ -1,63 +1,47 @@ -import { - PlatformUtilsService, - StorageService, -} from 'jslib/abstractions'; - -import { SafariApp } from '../browser/safariApp'; +import { StorageService } from 'jslib/abstractions/storage.service'; export default class BrowserStorageService implements StorageService { private chromeStorageApi: any; - private isSafari: boolean; - constructor(platformUtilsService: PlatformUtilsService) { - this.isSafari = platformUtilsService.isSafari(); - if (!this.isSafari) { - this.chromeStorageApi = chrome.storage.local; - } + constructor() { + this.chromeStorageApi = chrome.storage.local; } async get(key: string): Promise { - if (this.isSafari) { - const obj = await SafariApp.sendMessageToApp('storage_get', key); - return obj == null ? null : JSON.parse(obj) as T; - } else { - return new Promise((resolve) => { - this.chromeStorageApi.get(key, (obj: any) => { - if (obj != null && obj[key] != null) { - resolve(obj[key] as T); - return; - } - resolve(null); - }); + return new Promise((resolve) => { + this.chromeStorageApi.get(key, (obj: any) => { + if (obj != null && obj[key] != null) { + resolve(obj[key] as T); + return; + } + resolve(null); }); - } + }); } async save(key: string, obj: any): Promise { - const keyedObj = { [key]: obj }; - if (this.isSafari) { - await SafariApp.sendMessageToApp('storage_save', JSON.stringify({ - key: key, - obj: JSON.stringify(obj), - })); - } else { - return new Promise((resolve) => { - this.chromeStorageApi.set(keyedObj, () => { - resolve(); - }); - }); - } - } - - async remove(key: string): Promise { - if (this.isSafari) { - await SafariApp.sendMessageToApp('storage_remove', key); - } else { + if (obj == null) { + // Fix safari not liking null in set return new Promise((resolve) => { this.chromeStorageApi.remove(key, () => { resolve(); }); }); } + + const keyedObj = { [key]: obj }; + return new Promise((resolve) => { + this.chromeStorageApi.set(keyedObj, () => { + resolve(); + }); + }); + } + + async remove(key: string): Promise { + return new Promise((resolve) => { + this.chromeStorageApi.remove(key, () => { + resolve(); + }); + }); } } diff --git a/src/services/i18n.service.ts b/src/services/i18n.service.ts index 4eb880424d..542dbb6f35 100644 --- a/src/services/i18n.service.ts +++ b/src/services/i18n.service.ts @@ -1,19 +1,11 @@ import { I18nService as BaseI18nService } from 'jslib/services/i18n.service'; -import { BrowserApi } from '../browser/browserApi'; -import { SafariApp } from '../browser/safariApp'; - export default class I18nService extends BaseI18nService { constructor(systemLanguage: string) { - super(systemLanguage, BrowserApi.isSafariApi ? 'safari' : null, async (formattedLocale: string) => { - if (BrowserApi.isSafariApi) { - await SafariApp.sendMessageToApp('getLocaleStrings', formattedLocale); - return (window as any).bitwardenLocaleStrings; - } else { - // Deprecated - const file = await fetch(this.localesDirectory + formattedLocale + '/messages.json'); - return await file.json(); - } + super(systemLanguage, null, async (formattedLocale: string) => { + // Deprecated + const file = await fetch(this.localesDirectory + formattedLocale + '/messages.json'); + return await file.json(); }); this.supportedTranslationLocales = [