diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 23dbb9226..bb632fdd1 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -11,5 +11,5 @@ about: Report a bug in Authenticator **Platform:** - - Browser: [Chrome, Firefox, Edge] + - Browser: [Chrome, Firefox] - Browser Version: diff --git a/README.md b/README.md index 5a82b2452..d596a6024 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ npm install -g gts # install development dependencies npm install # compile -npm run [chrome, firefox, edge] +npm run [chrome, firefox] ``` -Note that Windows users should download a tool like [Git Bash](https://git-scm.com/download/win) or [Cygwin](http://cygwin.com/) to build. Building for Edge requires the [Windows App Certification Kit](https://developer.microsoft.com/en-us/windows/develop/app-certification-kit) +Note that Windows users should download a tool like [Git Bash](https://git-scm.com/download/win) or [Cygwin](http://cygwin.com/) to build. diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 807359c97..a975dcbb7 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -20,7 +20,7 @@ "description": "QR Error." }, "errorsecret": { - "message": "Secret Error. Only Base32(A-Z, 2-7 and =) and HEX(0-9 and A-F) are supported. However, your secret is: ", + "message": "Invalid account secret", "description": "Secret Error." }, "add_qr": { diff --git a/edge-files/AppXManifest.xml b/edge-files/AppXManifest.xml deleted file mode 100644 index 597596e96..000000000 --- a/edge-files/AppXManifest.xml +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - Authenticator Extension - mymindstorm - Assets/icon_50.png - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/edge-files/Assets/icon_150.png b/edge-files/Assets/icon_150.png deleted file mode 100644 index 7487ea57f..000000000 Binary files a/edge-files/Assets/icon_150.png and /dev/null differ diff --git a/edge-files/Assets/icon_44.png b/edge-files/Assets/icon_44.png deleted file mode 100644 index ceb40b862..000000000 Binary files a/edge-files/Assets/icon_44.png and /dev/null differ diff --git a/edge-files/Assets/icon_50.png b/edge-files/Assets/icon_50.png deleted file mode 100644 index a83fc650f..000000000 Binary files a/edge-files/Assets/icon_50.png and /dev/null differ diff --git a/edge-files/backgroundScriptsAPIBridge.js b/edge-files/backgroundScriptsAPIBridge.js deleted file mode 100644 index 3947f31bb..000000000 --- a/edge-files/backgroundScriptsAPIBridge.js +++ /dev/null @@ -1,1068 +0,0 @@ -try { - if (!Range.prototype.hasOwnProperty("intersectsNode")) { - Range.prototype["intersectsNode"] = function (node) { - let range = document.createRange(); - range.selectNode(node); - return 0 > this.compareBoundaryPoints(Range.END_TO_START, range) - && 0 < this.compareBoundaryPoints(Range.START_TO_END, range); - }; - } -} -catch (e) { } -try { - if (!Navigator.prototype.hasOwnProperty("languages")) { - Navigator.prototype["languages"] = [navigator.language]; - } -} -catch (e) { } -var getExtensionProtocol = function () { - if (typeof browser == "undefined") { - if (typeof chrome !== "undefined") - return "chrome-extension://"; - } - else { - return "ms-browser-extension://"; - } -}; -class BridgeAlarmEvent { - constructor() { - this.listeners = new Array(); - } - addListener(callback) { - this.listeners.push(callback); - } - addRules(rules, callback) { } - getRules(ruleIdentifiers, callback) { } - hasListener(callback) { return false; } - hasListeners() { return this.listeners.length > 0; } - removeRules(ruleIdentifiers, callback) { } - removeListener(callback) { } -} -class EdgeBridgeAlarms { - constructor() { - this.alarms = {}; - this.onAlarm = new BridgeAlarmEvent(); - } - create(name, alarmInfo) { - if (arguments.length < 1 || arguments.length > 2) { - throw "Unexpected set of arguments. Expecting (alarmInfo) or (name, alarmInfo)"; - } - var alarmName = ""; - var startMilliseconds = 0; - var startSet = false; - if (typeof name === "string") { - alarmName = name; - } - else if (typeof name === "object") { - alarmInfo = name; - } - else - throw "Unexpected set of arguments. Expecting (alarmInfo) or (name, alarmInfo)"; - if (!alarmInfo) { - throw "You must specify an alarmInfo argument!!"; - } - if (!alarmInfo.when && !alarmInfo.delayInMinutes && !alarmInfo.periodInMinutes) { - throw "Invalid alarmInfo argument!!"; - } - else if (alarmInfo.when && alarmInfo.delayInMinutes) { - throw "Invalid alarmInfo argument!! Either 'when' or 'delayInMinutes' but not both!!"; - } - else if (alarmInfo.when) { - startMilliseconds = alarmInfo.when; - startSet = true; - } - else if (alarmInfo.delayInMinutes) { - startMilliseconds = alarmInfo.delayInMinutes * 60 * 1000; - startSet = true; - } - else if (alarmInfo.periodInMinutes) { - startMilliseconds = alarmInfo.periodInMinutes * 60 * 1000; - startSet = true; - } - else - throw "Invalid alarmInfo argument!!"; - var timerHandle; - if (startSet) { - if (this.alarms[alarmName]) { - this.clearAlarm(alarmName); - } - var alarm = { name: alarmName, scheduledTime: Date.now() + startMilliseconds }; - var alarmAndHandle = { alarm: alarm, timerHandle: 0, startedInterval: false }; - this.alarms[alarmName] = alarmAndHandle; - if (alarmInfo.periodInMinutes) { - this.alarms[alarmName].alarm.periodInMinutes = alarmInfo.periodInMinutes; - this.alarms[alarmName].timerHandle = window.setTimeout(function (alarmName, that) { - that.soundAlarm(alarmName, that); - that.alarms[alarmName].timerHandle = window.setInterval(that.soundAlarm, alarmInfo.periodInMinutes * 60 * 1000, alarmName, that); - that.alarms[alarmName].startedInterval = true; - }, startMilliseconds, alarmName, this); - } - else { - this.alarms[alarmName].timerHandle = window.setTimeout(this.soundAlarm, startMilliseconds, alarmName, this); - } - } - } - getAll(callback) { - for (var key in this.alarms) { - if (this.alarms.hasOwnProperty(key)) { - var alarm = this.alarms[key].alarm; - callback(alarm); - } - } - } - clearAll(callback) { - var clearedAll = true; - for (var key in this.alarms) { - if (this.alarms.hasOwnProperty(key)) { - var alarm = this.alarms[key].alarm; - if (!this.clearAlarm(alarm.name)) { - clearedAll = false; - } - } - } - if (callback) { - callback(clearedAll); - } - } - clear(name, callback) { - var alarmName = ""; - if (typeof name === "string") { - alarmName = name; - } - else if (typeof name === "function") { - callback = name; - } - var wasCleared = this.clearAlarm(alarmName); - if (callback) { - callback(wasCleared); - } - } - get(name, callback) { - if (this.alarms.hasOwnProperty(name)) { - var alarm = this.alarms[name].alarm; - callback(alarm); - } - } - clearAlarm(name) { - var wasCleared = false; - if (this.alarms[name]) { - if (this.alarms[name].alarm.startedInterval) { - window.clearInterval(this.alarms[name].timerHandle); - } - else { - window.clearTimeout(this.alarms[name].timerHandle); - } - delete this.alarms[name]; - wasCleared = true; - } - return wasCleared; - } - soundAlarm(name, that) { - for (var index = 0; index < that.onAlarm.listeners.length; index++) { - var listener = that.onAlarm.listeners[index]; - listener({ name: name }); - } - } -} -class FakeEvent { - addListener(callback) { } - addRules(rules, callback) { } - getRules(ruleIdentifiers, callback) { } - hasListener(callback) { return false; } - hasListeners() { return false; } - removeRules(ruleIdentifiers, callback) { } - removeListener(callback) { } -} -class EdgeBridgeHelper { - constructor() { - this.fakeEvent = new FakeEvent(); - this.alarms = new EdgeBridgeAlarms(); - } - toAbsolutePath(relativePath) { - if (relativePath.indexOf("ms-browser-extension://") == 0) { - return relativePath.replace(myBrowser.runtime.getURL(""), ""); - } - else if (relativePath.indexOf("/") != 0) { - var absolutePath = ""; - var documentPath = document.location.pathname; - absolutePath = documentPath.substring(0, documentPath.lastIndexOf("/") + 1); - absolutePath += relativePath; - return absolutePath; - } - return relativePath; - } -} -var bridgeHelper = new EdgeBridgeHelper(); -class EdgeBridgeDebugLog { - constructor() { - this.CatchOnException = true; - this.VerboseLogging = true; - this.FailedCalls = {}; - this.SuccededCalls = {}; - this.DeprecatedCalls = {}; - this.BridgedCalls = {}; - this.UnavailableApis = {}; - this.EdgeIssues = {}; - } - log(message) { - try { - if (this.VerboseLogging) { - console.log(message); - } - } - catch (e) { - } - } - info(message) { - try { - if (this.VerboseLogging) { - console.info(message); - } - } - catch (e) { - } - } - warn(message) { - try { - if (this.VerboseLogging) { - console.warn(message); - } - } - catch (e) { - } - } - error(message) { - try { - if (this.VerboseLogging) { - console.error(message); - } - } - catch (e) { - } - } - DoActionAndLog(action, name, deprecatedTo, bridgedTo) { - var result; - try { - result = action(); - this.AddToCalledDictionary(this.SuccededCalls, name); - if (typeof deprecatedTo !== "undefined") { - this.warn("API Call Deprecated - Name: " + name + ", Please use " + deprecatedTo + " instead!"); - this.AddToCalledDictionary(this.DeprecatedCalls, name); - } - if (typeof bridgedTo !== "undefined") { - this.info("API Call '" + name + "' has been bridged to another Edge API: " + bridgedTo); - this.AddToCalledDictionary(this.BridgedCalls, name); - } - this.info("API Call: '" + name + "'"); - return result; - } - catch (ex) { - this.AddToCalledDictionary(this.FailedCalls, name); - if (this.CatchOnException) - this.error("API Call Failed: " + name + " - " + ex); - else - throw ex; - } - } - LogEdgeIssue(name, message) { - this.warn(message); - this.AddToCalledDictionary(this.EdgeIssues, name); - } - LogUnavailbleApi(name, deprecatedTo) { - this.warn("API Call '" + name + "' is not supported in Edge"); - this.AddToCalledDictionary(this.UnavailableApis, name); - if (typeof deprecatedTo !== "undefined") { - this.warn("API Call Deprecated - Name: " + name + ", Please use " + deprecatedTo + " instead!"); - this.AddToCalledDictionary(this.DeprecatedCalls, name); - } - } - AddToCalledDictionary(dictionary, name) { - if (typeof dictionary[name] !== "undefined") { - dictionary[name]++; - } - else { - dictionary[name] = 1; - } - } -} -var bridgeLog = new EdgeBridgeDebugLog(); -class EdgeChromeAlarmBridge { - get onAlarm() { - return bridgeLog.DoActionAndLog(() => { - return bridgeHelper.alarms.onAlarm; - }, "alarms.onAlarm", undefined, "bridgeHelper.alarms.onAlarm"); - } - create(name, alarmInfo) { - bridgeLog.DoActionAndLog(() => { - bridgeHelper.alarms.create.apply(null, arguments); - }, "alarms.create", undefined, "bridgeHelper.alarms.create"); - } - getAll(callback) { - bridgeLog.DoActionAndLog(() => { - bridgeHelper.alarms.getAll.apply(null, arguments); - }, "alarms.getAll", undefined, "bridgeHelper.alarms.getAll"); - } - clearAll(callback) { - bridgeLog.DoActionAndLog(() => { - bridgeHelper.alarms.clearAll.apply(null, arguments); - }, "alarms.clearAll", undefined, "bridgeHelper.alarms.clearAll"); - } - clear(name, callback) { - bridgeLog.DoActionAndLog(() => { - bridgeHelper.alarms.clear.apply(null, arguments); - }, "alarms.clear", undefined, "bridgeHelper.alarms.clear"); - } - get(name, callback) { - bridgeLog.DoActionAndLog(() => { - bridgeHelper.alarms.get.apply(null, arguments); - }, "alarms.get", undefined, "bridgeHelper.alarms.get"); - } -} -class EdgeChromeAppBridge { - getDetails() { - return bridgeLog.DoActionAndLog(() => { - return EdgeChromeRuntimeBridge.prototype.getManifest.apply(null, arguments); - }, "app.getManifest", undefined, "runtime.getManifest"); - } - get isInstalled() { return bridgeLog.DoActionAndLog(() => { throw "app.isInstalled is not available in Edge"; }, "app.isInstalled"); } - getIsInstalled() { return bridgeLog.DoActionAndLog(() => { throw "app.getIsInstalled is not available in the Edge"; }, "app.getIsInstalled"); } - installState() { return bridgeLog.DoActionAndLog(() => { throw "app.installState is not available in Edge"; }, "app.installState"); } - runningState() { return bridgeLog.DoActionAndLog(() => { throw "app.runningState is not available in Edge"; }, "app.runningState"); } -} -class EdgeBookmarksBridge { - create(bookmark, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.bookmarks.create.apply(null, arguments); - }, "bookmarks.create"); - } - getTree(callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.bookmarks.getTree.apply(null, arguments); - }, "bookmarks.getTree"); - } - move(id, destination, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.bookmarks.move.apply(null, arguments); - }, "bookmarks.move"); - } - remove(id, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.bookmarks.remove.apply(null, arguments); - }, "bookmarks.remove"); - } - removeTree(id, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.bookmarks.removeTree.apply(null, arguments); - }, "bookmarks.removeTree"); - } - update(id, changes, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.bookmarks.update.apply(null, arguments); - }, "bookmarks.update"); - } -} -class EdgeChromeBookmarksBridge extends EdgeBookmarksBridge { - get onRemoved() { bridgeLog.LogUnavailbleApi("bookmarks.onRemoved"); return bridgeHelper.fakeEvent; } - get onImportEnded() { bridgeLog.LogUnavailbleApi("bookmarks.onImportEnded"); return bridgeHelper.fakeEvent; } - get onImportBegan() { bridgeLog.LogUnavailbleApi("bookmarks.onImportBegan"); return bridgeHelper.fakeEvent; } - get onChanged() { bridgeLog.LogUnavailbleApi("bookmarks.onChanged"); return bridgeHelper.fakeEvent; } - get onMoved() { bridgeLog.LogUnavailbleApi("bookmarks.onMoved"); return bridgeHelper.fakeEvent; } - get onCreated() { bridgeLog.LogUnavailbleApi("bookmarks.onCreated"); return bridgeHelper.fakeEvent; } - get onChildrenReordered() { bridgeLog.LogUnavailbleApi("bookmarks.onChildrenReordered"); return bridgeHelper.fakeEvent; } - getRecent(numberOfItems, callback) { - bridgeLog.LogUnavailbleApi("bookmarks.getRecent"); - } - get(idList, callback) { - bridgeLog.LogUnavailbleApi("bookmarks.get"); - } - getChildren(id, callback) { - bridgeLog.LogUnavailbleApi("bookmarks.getChildren"); - } - getSubTree(id, callback) { - bridgeLog.LogUnavailbleApi("bookmarks.getSubTree"); - } - search(query, callback) { - bridgeLog.LogUnavailbleApi("bookmarks.search"); - } -} -class EdgeBrowserActionBridge { - get onClicked() { return bridgeLog.DoActionAndLog(() => { return myBrowser.browserAction.onClicked; }, "browserAction.onClicked"); } - disable(tabId) { - bridgeLog.DoActionAndLog(() => { - myBrowser.browserAction.disable.apply(null, arguments); - }, "browserAction.disable"); - } - enable(tabId) { - bridgeLog.DoActionAndLog(() => { - myBrowser.browserAction.enable.apply(null, arguments); - }, "browserAction.Enable"); - } - getBadgeBackgroundColor(details, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.browserAction.getBadgeBackgroundColor.apply(null, arguments); - }, "browserAction.getBadgeBackgroundColor"); - } - getBadgeText(details, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.browserAction.getBadgeText.apply(null, arguments); - ; - }, "browserAction.getBadgeText"); - } - getPopup(details, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.browserAction.getPopup.apply(null, arguments); - }, "browserAction.getPopup"); - } - getTitle(details, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.browserAction.getTitle.apply(null, arguments); - }, "browserAction.getTitle"); - } - setBadgeBackgroundColor(details) { - bridgeLog.DoActionAndLog(() => { - myBrowser.browserAction.setBadgeBackgroundColor.apply(null, arguments); - }, "browserAction.setBadgeBackgroundColor"); - } - setBadgeText(details) { - bridgeLog.DoActionAndLog(() => { - myBrowser.browserAction.setBadgeText.apply(null, arguments); - }, "browserAction.setBadgeText"); - } - setIcon(details, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.browserAction.setIcon.apply(null, arguments); - }, "browserAction.setIcon"); - } - setPopup(details) { - bridgeLog.DoActionAndLog(() => { - myBrowser.browserAction.setPopup.apply(null, arguments); - }, "browserAction.setPopup"); - } - setTitle(details) { - bridgeLog.DoActionAndLog(() => { - myBrowser.browserAction.setTitle.apply(null, arguments); - }, "browserAction.setTitle"); - } -} -class EdgeChromeBrowserActionBridge extends EdgeBrowserActionBridge { - getPopup(details, callback) { - if (myBrowser.browserAction.getPopup) { - EdgeBrowserActionBridge.prototype.getPopup.apply(null, arguments); - } - else { - bridgeLog.LogUnavailbleApi("browserAction.getPopup"); - } - } - getTitle(details, callback) { - if (myBrowser.browserAction.getTitle) { - EdgeBrowserActionBridge.prototype.getTitle.apply(null, arguments); - } - else { - bridgeLog.LogUnavailbleApi("browserAction.getTitle"); - } - } - setTitle(details) { - if (myBrowser.browserAction.setTitle) { - EdgeBrowserActionBridge.prototype.setTitle.apply(null, arguments); - } - else { - bridgeLog.LogUnavailbleApi("browserAction.setTitle"); - } - } -} -class EdgeChromeCommandsBridge { - get onCommand() { bridgeLog.LogUnavailbleApi("commands.onCommand"); return bridgeHelper.fakeEvent; } - getAll(callback) { - bridgeLog.LogUnavailbleApi("commands.getAll"); - } -} -class EdgeContextMenusBridge { - get ACTION_MENU_TOP_LEVEL_LIMIT() { return bridgeLog.DoActionAndLog(() => { return myBrowser.contextMenus.ACTION_MENU_TOP_LEVEL_LIMIT; }, "contextMenus.ACTION_MENU_TOP_LEVEL_LIMIT"); } - get onClicked() { return bridgeLog.DoActionAndLog(() => { return myBrowser.contextMenus.onClicked; }, "contextMenus.onClicked"); } - create(createProperties, callback) { - return bridgeLog.DoActionAndLog(() => { - return myBrowser.contextMenus.create.apply(null, arguments); - }, "contextMenus.create"); - } - remove(menuItemId, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.contextMenus.remove.apply(null, arguments); - }, "contextMenus.remove"); - } - removeAll(callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.contextMenus.removeAll.apply(null, arguments); - }, "contextMenus.removeAll"); - } - update(id, updateProperties, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.contextMenus.update.apply(null, arguments); - }, "contextMenus.update"); - } -} -class EdgeCookiesBridge { - get(details, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.cookies.get.apply(null, arguments); - }, "cookies.get"); - } - getAll(details, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.cookies.getAll.apply(null, arguments); - }, "cookies.getAll"); - } - remove(details, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.cookies.remove.apply(null, arguments); - }, "cookies.remove"); - } - set(details, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.cookies.set.apply(null, arguments); - }, "cookies.set"); - } -} -class EdgeChromeCookiesBridge extends EdgeCookiesBridge { - get onChanged() { bridgeLog.LogUnavailbleApi("cookies.onChanged"); return bridgeHelper.fakeEvent; } -} -class EdgeExtensionBridge { - get inIncognitoContext() { - return bridgeLog.DoActionAndLog(() => { - return myBrowser.extension.inIncognitoContext; - }, "extension.inIncognitoContext"); - } - getBackgroundPage() { - return bridgeLog.DoActionAndLog(() => { - return myBrowser.extension.getBackgroundPage.apply(null, arguments); - }, "extension.getBackgroundPage"); - } - getURL(path) { - return bridgeLog.DoActionAndLog(() => { - return myBrowser.extension.getURL.apply(null, arguments); - }, "extension.getURL"); - } - getViews(fetchProperties) { - return bridgeLog.DoActionAndLog(() => { - return myBrowser.extension.getViews.apply(null, arguments); - }, "extension.getViews"); - } - isAllowedIncognitoAccess(callback) { - return bridgeLog.DoActionAndLog(() => { - return myBrowser.extension.isAllowedIncognitoAccess.apply(null, arguments); - }, "extension.isAllowedIncognitoAccess"); - } -} -class EdgeChromeExtensionBridge extends EdgeExtensionBridge { - get onConnect() { return bridgeLog.DoActionAndLog(() => { return EdgeRuntimeBridge.prototype.onConnect; }, "extension.onConnect", "runtime.onConnect", "runtime.onConnect"); } - get onMessage() { return bridgeLog.DoActionAndLog(() => { return myBrowser.runtime.onMessage; }, "extension.onMessage", "runtime.onMessage", "runtime.onMessage"); } - get onRequest() { return bridgeLog.DoActionAndLog(() => { return myBrowser.runtime.onMessage; }, "extension.onRequest", "runtime.onMessage", "runtime.onMessage"); } - get onRequestExternal() { return bridgeLog.DoActionAndLog(() => { return myBrowser.runtime.onMessageExternal; }, "extension.onRequestExternal", "runtime.onMessageExternal", "runtime.onMessageExternal"); } - get lastError() { return bridgeLog.DoActionAndLog(() => { return myBrowser.runtime.lastError; }, "extension.lastError", undefined, "runtime.lastError"); } - connect(extensionId, connectInfo) { - return bridgeLog.DoActionAndLog(() => { - return EdgeRuntimeBridge.prototype.connect.apply(null, arguments); - }, "extension.connect", "runtime.connect", "runtime.connect"); - } - sendMessage(message, responseCallback) { - return bridgeLog.DoActionAndLog(() => { - return EdgeRuntimeBridge.prototype.sendMessage.apply(null, arguments); - }, "extension.sendMessage", "runtime.sendMessage", "runtime.sendMessage"); - } - sendRequest(extensionId, message, options, responseCallback) { - return bridgeLog.DoActionAndLog(() => { - return EdgeRuntimeBridge.prototype.sendMessage.apply(null, arguments); - }, "extension.sendRequest", "runtime.sendMessage", "runtime.sendMessage"); - } - isAllowedFileSchemeAccess(callback) { - bridgeLog.LogUnavailbleApi("extension.isAllowedFileSchemeAccess"); - } - setUpdateUrlData(data) { - bridgeLog.LogUnavailbleApi("extension.setUpdateUrlData"); - } -} -class EdgeHistoryBridge { - get onVisited() { bridgeLog.LogUnavailbleApi("history.onVisited"); return bridgeHelper.fakeEvent; } - get onVisitRemoved() { bridgeLog.LogUnavailbleApi("history.onVisitRemoved"); return bridgeHelper.fakeEvent; } - addUrl(details, callback) { - bridgeLog.LogUnavailbleApi("history.addUrl"); - } - deleteAll(callback) { - bridgeLog.LogUnavailbleApi("history.deleteAll"); - } - deleteRange(range, callback) { - bridgeLog.LogUnavailbleApi("history.deleteRange"); - } - deleteUrl(details, callback) { - bridgeLog.LogUnavailbleApi("history.deleteUrl"); - } - getVisits(details, callback) { - bridgeLog.LogUnavailbleApi("history.getVisits"); - } - search(query, callback) { - bridgeLog.LogUnavailbleApi("history.search"); - } -} -class EdgeI18nBridge { - getAcceptLanguages(callback) { - return bridgeLog.DoActionAndLog(() => { - return myBrowser.i18n.getAcceptLanguages.apply(null, arguments); - }, "i18n.getAcceptLanguages"); - } - getMessage(messageName, substitutions) { - return bridgeLog.DoActionAndLog(() => { - if (messageName.indexOf("@@extension_id") > -1) { - return myBrowser.runtime.id; - } - return myBrowser.i18n.getMessage.apply(null, arguments); - }, "i18n.getMessage"); - } - getUILanguage() { - return bridgeLog.DoActionAndLog(() => { - return myBrowser.i18n.getUILanguage.apply(null, arguments); - }, "i18n.getUILanguage"); - } -} -class EdgeChromeIdleBridge { - get onStateChanged() { bridgeLog.LogUnavailbleApi("idle.onStateChanged"); return bridgeHelper.fakeEvent; } - queryState(detectionIntervalInSeconds, callback) { - bridgeLog.LogUnavailbleApi("idle.queryState"); - } - setDetectionInterval(intervalInSeconds) { - bridgeLog.LogUnavailbleApi("idle.setDetectionInterval"); - } -} -class EdgeNotificationBridge { - get onButtonClicked() { return bridgeLog.DoActionAndLog(() => { return myBrowser.notifications.onButtonClicked; }, "notifications.onButtonClicked"); } - get onClicked() { return bridgeLog.DoActionAndLog(() => { return myBrowser.notifications.onClicked; }, "notifications.onClicked"); } - get onClosed() { return bridgeLog.DoActionAndLog(() => { return myBrowser.notifications.onClosed; }, "notifications.onClosed"); } - get onPermissionLevelChanged() { return bridgeLog.DoActionAndLog(() => { return myBrowser.notifications.onPermissionLevelChanged; }, "notifications.onPermissionLevelChanged"); } - get onShowSettings() { bridgeLog.LogUnavailbleApi("notifications.onShowSettings"); return bridgeHelper.fakeEvent; } - clear(notificationId, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.notifications.clear.apply(null, arguments); - }, "notifications.clear"); - } - create(notificationId, options, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.notifications.create.apply(null, arguments); - }, "notifications.create"); - } - getAll(callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.notifications.getAll.apply(null, arguments); - }, "notifications.getAll"); - } - getPermissionLevel(callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.notifications.getPermissionLevel.apply(null, arguments); - }, "notifications.getPermissionLevel"); - } - update(notificationId, options, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.notifications.update.apply(null, arguments); - }, "notifications.update"); - } -} -class EdgePageActionBridge { - get onClicked() { return bridgeLog.DoActionAndLog(() => { return myBrowser.pageAction.onClicked; }, "pageAction.onClicked"); } - getPopup(details, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.pageAction.getPopup.apply(null, arguments); - }, "pageAction.getPopup"); - } - getTitle(details, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.pageAction.getTitle.apply(null, arguments); - }, "pageAction.getTitle"); - } - hide(tabId) { - bridgeLog.DoActionAndLog(() => { - myBrowser.pageAction.hide.apply(null, arguments); - }, "pageAction.hide"); - } - setTitle(details) { - bridgeLog.DoActionAndLog(() => { - myBrowser.pageAction.setTitle.apply(null, arguments); - }, "pageAction.setTitle"); - } - setIcon(details, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.pageAction.setIcon.apply(null, arguments); - }, "pageAction.setIcon"); - } - setPopup(details) { - bridgeLog.DoActionAndLog(() => { - myBrowser.pageAction.setPopup.apply(null, arguments); - }, "pageAction.setPopup"); - } - show(tabId) { - bridgeLog.DoActionAndLog(() => { - myBrowser.pageAction.show.apply(null, arguments); - }, "pageAction.show"); - } -} -class EdgePermissionsBridge { - get onAdded() { bridgeLog.LogUnavailbleApi("permissions.onAdded"); return bridgeHelper.fakeEvent; } - get onRemoved() { bridgeLog.LogUnavailbleApi("permissions.onRemoved"); return bridgeHelper.fakeEvent; } - contains(permissions, callback) { - bridgeLog.LogUnavailbleApi("permissions.contains"); - } - getAll(callback) { - bridgeLog.LogUnavailbleApi("permissions.getAll"); - } - remove(permissions, callback) { - bridgeLog.LogUnavailbleApi("permissions.remove"); - } - request(permissions, callback) { - bridgeLog.LogUnavailbleApi("permissions.request"); - } -} -class EdgeRuntimeBridge { - get id() { return bridgeLog.DoActionAndLog(() => { return myBrowser.runtime.id; }, "runtime.id"); } - get lastError() { return bridgeLog.DoActionAndLog(() => { return myBrowser.runtime.lastError; }, "runtime.lastError"); } - get onConnect() { return bridgeLog.DoActionAndLog(() => { return myBrowser.runtime.onConnect; }, "runtime.onConnect"); } - get onInstalled() { return bridgeLog.DoActionAndLog(() => { return myBrowser.runtime.onInstalled; }, "runtime.onInstalled"); } - get onMessage() { return bridgeLog.DoActionAndLog(() => { return myBrowser.runtime.onMessage; }, "runtime.onMessage"); } - get onMessageExternal() { return bridgeLog.DoActionAndLog(() => { return myBrowser.runtime.onMessageExternal; }, "runtime.onMessageExternal"); } - connect(extensionId, connectInfo) { - return bridgeLog.DoActionAndLog(() => { - return myBrowser.runtime.connect.apply(null, arguments); - }, "runtime.connect"); - } - connectNative(application) { - return bridgeLog.DoActionAndLog(() => { - return myBrowser.runtime.connectNative.apply(null, arguments); - }, "runtime.connectNative"); - } - getBackgroundPage(callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.runtime.getBackgroundPage.apply(null, arguments); - }, "runtime.getBackgroundPage"); - } - getManifest() { - return bridgeLog.DoActionAndLog(() => { - return myBrowser.runtime.getManifest.apply(null, arguments); - }, "runtime.getManifest"); - } - getURL(path) { - return bridgeLog.DoActionAndLog(() => { - return myBrowser.runtime.getURL.apply(null, arguments); - }, "runtime.getURL"); - } - reload() { - bridgeLog.DoActionAndLog(() => { - myBrowser.runtime.reload.apply(null, arguments); - }, "runtime.reload"); - } - sendMessage(extensionId, message, options, responseCallback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.runtime.sendMessage.apply(null, arguments); - }, "runtime.sendMessage"); - } - sendNativeMessage(application, message, responseCallback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.runtime.sendNativeMessage.apply(null, arguments); - }, "runtime.sendNativeMessage"); - } - setUninstallURL(url, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.runtime.setUninstallURL.apply(null, arguments); - }, "runtime.setUninstallURL"); - } -} -class EdgeChromeRuntimeBridge extends EdgeRuntimeBridge { - get onConnectExternal() { bridgeLog.LogUnavailbleApi("runtime.onConnectExternal"); return bridgeHelper.fakeEvent; } - get onRestartRequired() { bridgeLog.LogUnavailbleApi("runtime.onRestartRequired"); return bridgeHelper.fakeEvent; } - get onSuspend() { bridgeLog.LogUnavailbleApi("runtime.onSuspend"); return bridgeHelper.fakeEvent; } - get onSuspendCanceled() { bridgeLog.LogUnavailbleApi("runtime.onSuspendCanceled"); return bridgeHelper.fakeEvent; } - get onUpdateAvailable() { bridgeLog.LogUnavailbleApi("runtime.onUpdateAvailable"); return bridgeHelper.fakeEvent; } - get onStartup() { return bridgeLog.DoActionAndLog(() => { return myBrowser.windows.onCreated; }, "runtime.onStartup", undefined, "windows.onCreated"); } - openOptionsPage(callback) { - bridgeLog.DoActionAndLog(() => { - var optionsPage = myBrowser.runtime.getManifest()["options_page"]; - var optionsPageUrl = myBrowser.runtime.getURL(optionsPage); - if (typeof callback !== "undefined") { - myBrowser.tabs.create({ url: optionsPageUrl }, callback); - } - else { - myBrowser.tabs.create({ url: optionsPageUrl }); - } - }, "runtime.openOptionsPage", undefined, "tabs.create({ url: optionsPageUrl })"); - } - setUninstallURL(url, callback) { - if (myBrowser.runtime.setUninstallURL) { - EdgeRuntimeBridge.prototype.setUninstallURL.apply(null, arguments); - } - else { - bridgeLog.LogUnavailbleApi("runtime.setUninstallURL"); - } - } - getPackageDirectoryEntry(callback) { - bridgeLog.LogUnavailbleApi("runtime.getPackageDirectoryEntry"); - } - getPlatformInfo(callback) { - bridgeLog.LogUnavailbleApi("runtime.getPlatformInfo"); - } - requestUpdateCheck(callback) { - bridgeLog.LogUnavailbleApi("runtime.requestUpdateCheck"); - } - restart() { - bridgeLog.LogUnavailbleApi("runtime.restart"); - } -} -class EdgeStorageBridge { - get local() { return bridgeLog.DoActionAndLog(() => { return myBrowser.storage.local; }, "storage.local"); } - get sync() { return bridgeLog.DoActionAndLog(() => { return myBrowser.storage.sync; }, "storage.sync"); } - get onChanged() { return bridgeLog.DoActionAndLog(() => { return myBrowser.storage.onChanged; }, "storage.onChanged"); } -} -class EdgeChromeStorageBridge extends EdgeStorageBridge { - get sync() { - if (myBrowser.storage.sync) { - return EdgeStorageBridge.prototype.sync; - } - else { - return bridgeLog.DoActionAndLog(() => { - return myBrowser.storage.local; - }, "storage.sync", undefined, "storage.local"); - } - } - get managed() { return bridgeLog.DoActionAndLog(() => { return myBrowser.storage.local; }, "storage.managed", undefined, "storage.local"); } -} -class EdgeTabsBridge { - get onActivated() { return bridgeLog.DoActionAndLog(() => { return myBrowser.tabs.onActivated; }, "tabs.onActivated"); } - get onAttached() { return bridgeLog.DoActionAndLog(() => { return myBrowser.tabs.onAttached; }, "tabs.onAttached"); } - get onCreated() { return bridgeLog.DoActionAndLog(() => { return myBrowser.tabs.onCreated; }, "tabs.onCreated"); } - get onDetached() { return bridgeLog.DoActionAndLog(() => { return myBrowser.tabs.onDetached; }, "tabs.onDetached"); } - get onRemoved() { return bridgeLog.DoActionAndLog(() => { return myBrowser.tabs.onRemoved; }, "tabs.onRemoved"); } - get onReplaced() { return bridgeLog.DoActionAndLog(() => { return myBrowser.tabs.onReplaced; }, "tabs.onReplaced"); } - get onUpdated() { return bridgeLog.DoActionAndLog(() => { return myBrowser.tabs.onUpdated; }, "tabs.onUpdated"); } - captureVisibleTab(windowId, options, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.tabs.captureVisibleTab.apply(null, arguments); - }, "tabs.captureVisibleTab"); - } - create(createProperties, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.tabs.create.apply(null, arguments); - }, "tabs.create"); - } - detectLanguage(tabId, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.tabs.detectLanguage.apply(null, arguments); - }, "tabs.detectLanguage"); - } - executeScript(tabId, details, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.tabs.executeScript.apply(null, arguments); - }, "tabs.executeScript"); - } - get(tabId, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.tabs.get.apply(null, arguments); - }, "tabs.get"); - } - getCurrent(callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.tabs.getCurrent.apply(null, arguments); - }, "tabs.getCurrent"); - } - insertCSS(tabId, details, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.tabs.insertCSS.apply(null, arguments); - }, "tabs.insertCSS"); - } - query(queryInfo, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.tabs.query.apply(null, arguments); - }, "tabs.query"); - } - reload(tabId, reloadProperties, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.tabs.reload.apply(null, arguments); - }, "tabs.reload"); - } - remove(tabId, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.tabs.remove.apply(null, arguments); - }, "tabs.remove"); - } - sendMessage(tabId, message, responseCallback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.tabs.sendMessage.apply(null, arguments); - }, "tabs.sendMessage"); - } - update(tabId, updateProperties, callback) { - var updatePropertiesBridged = false; - for (var index = 0; index < arguments.length; index++) { - var argument = arguments[index]; - if (typeof argument === "object") { - if (!argument.active && (argument.highlighted || argument.selected)) { - argument.active = argument.highlighted || argument.selected; - updatePropertiesBridged = true; - } - } - } - bridgeLog.DoActionAndLog(() => { - myBrowser.tabs.update.apply(null, arguments); - }, "tabs.update", undefined, updatePropertiesBridged ? "tabs.update with UpdateProperties modified" : undefined); - } -} -class EdgeChromeTabsBridge extends EdgeTabsBridge { - get onHighlighted() { bridgeLog.LogUnavailbleApi("tabs.onHighlighted"); return bridgeHelper.fakeEvent; } - get onMoved() { bridgeLog.LogUnavailbleApi("tabs.onMoved"); return bridgeHelper.fakeEvent; } - get onSelectionChanged() { - return bridgeLog.DoActionAndLog(() => { - var fakeEvent = bridgeHelper.fakeEvent; - fakeEvent.addListener = (callback) => { - myBrowser.tabs.onActivated.addListener((activeInfo) => { - callback(activeInfo.tabId, { windowId: activeInfo.windowId }); - }); - }; - return fakeEvent; - }, "tabs.onSelectionChanged", "tabs.onActivated", "tabs.onActivated"); - } - duplicate(tabId, callback) { - bridgeLog.DoActionAndLog(() => { - var tabGetCallback = function (tab) { - if (typeof callback !== "undefined") { - myBrowser.tabs.create({ url: tab.url }, callback); - } - else { - myBrowser.tabs.create({ url: tab.url }); - } - }; - EdgeTabsBridge.prototype.get(tabId, tabGetCallback); - }, "tabs.duplicate", undefined, "tabs.create"); - } - getAllInWindow(windowId, callback) { - bridgeLog.DoActionAndLog(() => { - EdgeTabsBridge.prototype.query({ windowId: windowId }, callback); - }, "tabs.getAllInWindow", "tabs.query", "tabs.query"); - } - getSelected(windowId, callback) { - bridgeLog.DoActionAndLog(() => { - EdgeTabsBridge.prototype.query({ active: true }, (tabs) => callback(tabs[0])); - }, "tabs.getSelected", "tabs.query", "tabs.query"); - } - sendRequest(tabId, request, responseCallback) { - bridgeLog.DoActionAndLog(() => { - EdgeTabsBridge.prototype.sendMessage.apply(null, arguments); - }, "tabs.sendRequest", "tabs.sendMessage", "tabs.sendMessage"); - } - connect(tabId, connectInfo) { - bridgeLog.LogUnavailbleApi("tabs.connect"); - return null; - } - highlight(highlightInfo, callback) { - bridgeLog.LogUnavailbleApi("tabs.highlight"); - } - move(tabId, moveProperties, callback) { - bridgeLog.LogUnavailbleApi("tabs.move"); - } -} -class EdgeWebNavigationBridge { - get onBeforeNavigate() { return bridgeLog.DoActionAndLog(() => { return myBrowser.webNavigation.onBeforeNavigate; }, "webNavigation.onBeforeNavigate"); } - get onCommitted() { return bridgeLog.DoActionAndLog(() => { return myBrowser.webNavigation.onCommitted; }, "webNavigation.onCommitted"); } - get onCompleted() { return bridgeLog.DoActionAndLog(() => { return myBrowser.webNavigation.onCompleted; }, "webNavigation.onCompleted"); } - get onCreatedNavigationTarget() { return bridgeLog.DoActionAndLog(() => { return myBrowser.webNavigation.onCreatedNavigationTarget; }, "webNavigation.onCreatedNavigationTarget"); } - get onDOMContentLoaded() { return bridgeLog.DoActionAndLog(() => { return myBrowser.webNavigation.onDOMContentLoaded; }, "webNavigation.onDOMContentLoaded"); } - get onErrorOccurred() { return bridgeLog.DoActionAndLog(() => { return myBrowser.webNavigation.onErrorOccurred; }, "webNavigation.onErrorOccurred"); } - get onHistoryStateUpdated() { return bridgeLog.DoActionAndLog(() => { return myBrowser.webNavigation.onHistoryStateUpdated; }, "webNavigation.onHistoryStateUpdated"); } - get onReferenceFragmentUpdated() { return bridgeLog.DoActionAndLog(() => { return myBrowser.webNavigation.onReferenceFragmentUpdated; }, "webNavigation.onReferenceFragmentUpdated"); } - get onTabReplaced() { return bridgeLog.DoActionAndLog(() => { return myBrowser.webNavigation.onTabReplaced; }, "webNavigation.onTabReplaced"); } - getAllFrames(details, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.webNavigation.getAllFrames.apply(null, arguments); - }, "webNavigation.getAllFrames"); - } - getFrame(details, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.webNavigation.getFrame.apply(null, arguments); - }, "webNavigation.getFrame"); - } -} -class EdgeWebRequestBridge { - get MAX_HANDLER_BEHAVIOR_CHANGED_CALLS_PER_10_MINUTES() { return bridgeLog.DoActionAndLog(() => { return myBrowser.webRequest.MAX_HANDLER_BEHAVIOR_CHANGED_CALLS_PER_10_MINUTES; }, "webNavigation.MAX_HANDLER_BEHAVIOR_CHANGED_CALLS_PER_10_MINUTES"); } - get onAuthRequired() { return bridgeLog.DoActionAndLog(() => { return myBrowser.webRequest.onAuthRequired; }, "webRequest.onAuthRequired"); } - get onBeforeRedirect() { return bridgeLog.DoActionAndLog(() => { return myBrowser.webRequest.onBeforeRedirect; }, "webRequest.onBeforeRedirect"); } - get onBeforeRequest() { return bridgeLog.DoActionAndLog(() => { return myBrowser.webRequest.onBeforeRequest; }, "webRequest.onBeforeRequest"); } - get onBeforeSendHeaders() { return bridgeLog.DoActionAndLog(() => { return myBrowser.webRequest.onBeforeSendHeaders; }, "webRequest.onBeforeSendHeaders"); } - get onCompleted() { return bridgeLog.DoActionAndLog(() => { return myBrowser.webRequest.onCompleted; }, "webRequest.onCompleted"); } - get onErrorOccurred() { return bridgeLog.DoActionAndLog(() => { return myBrowser.webRequest.onErrorOccurred; }, "webRequest.onErrorOccurred"); } - get onHeadersReceived() { return bridgeLog.DoActionAndLog(() => { return myBrowser.webRequest.onHeadersReceived; }, "webRequest.onHeadersReceived"); } - get onResponseStarted() { return bridgeLog.DoActionAndLog(() => { return myBrowser.webRequest.onResponseStarted; }, "webRequest.onResponseStarted"); } - get onSendHeaders() { return bridgeLog.DoActionAndLog(() => { return myBrowser.webRequest.onSendHeaders; }, "webRequest.onSendHeaders"); } - handlerBehaviorChanged(callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.webRequest.handlerBehaviorChanged.apply(null, arguments); - }, "webRequest.handlerBehaviorChanged"); - } -} -class EdgeWindowsBridge { - get WINDOW_ID_CURRENT() { return bridgeLog.DoActionAndLog(() => { return myBrowser.windows.WINDOW_ID_CURRENT; }, "windows.WINDOW_ID_CURRENT"); } - get WINDOW_ID_NONE() { return bridgeLog.DoActionAndLog(() => { return myBrowser.windows.WINDOW_ID_NONE; }, "windows.WINDOW_ID_NONE"); } - get onCreated() { return bridgeLog.DoActionAndLog(() => { return myBrowser.windows.onCreated; }, "windows.onCreated"); } - get onFocusChanged() { return bridgeLog.DoActionAndLog(() => { return myBrowser.windows.onFocusChanged; }, "windows.onFocusChanged"); } - get onRemoved() { return bridgeLog.DoActionAndLog(() => { return myBrowser.windows.onRemoved; }, "windows.onRemoved"); } - create(createData, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.windows.create.apply(null, arguments); - }, "windows.create"); - } - get(windowId, getInfo, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.windows.get.apply(null, arguments); - }, "windows.get"); - } - getAll(getInfo, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.windows.getAll.apply(null, arguments); - }, "windows.getAll"); - } - getCurrent(getInfo, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.windows.getCurrent.apply(null, arguments); - }, "windows.getCurrent"); - } - getLastFocused(getInfo, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.windows.getLastFocused.apply(null, arguments); - }, "windows.getLastFocused"); - } - update(windowId, updateInfo, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.windows.update.apply(null, arguments); - }, "windows.update"); - } -} -class EdgeChromeWindowsBridge extends EdgeWindowsBridge { - remove(windowId, callback) { - bridgeLog.LogUnavailbleApi("windows.remove"); - } -} -class EdgeBackgroundBridge { - constructor() { - this.alarms = new EdgeChromeAlarmBridge(); - this.app = new EdgeChromeAppBridge(); - this.commands = new EdgeChromeCommandsBridge(); - this.idle = new EdgeChromeIdleBridge(); - this.notifications = new EdgeNotificationBridge(); - this.bookmarks = typeof browser["bookmarks"] !== "undefined" ? new EdgeChromeBookmarksBridge() : undefined; - this.browserAction = typeof browser.browserAction !== "undefined" ? new EdgeChromeBrowserActionBridge() : undefined; - this.contextMenus = typeof browser.contextMenus !== "undefined" ? new EdgeContextMenusBridge() : undefined; - this.cookies = typeof browser.cookies !== "undefined" ? new EdgeChromeCookiesBridge() : undefined; - this.extension = typeof browser.extension !== "undefined" ? new EdgeChromeExtensionBridge() : undefined; - this.history = typeof browser.history !== "undefined" ? new EdgeHistoryBridge() : undefined; - this.i18n = typeof browser.i18n !== "undefined" ? new EdgeI18nBridge() : undefined; - this.pageAction = typeof browser.pageAction !== "undefined" ? new EdgePageActionBridge() : undefined; - this.permissions = typeof browser.permissions !== "undefined" ? new EdgePermissionsBridge() : undefined; - this.runtime = typeof browser.runtime !== "undefined" ? new EdgeChromeRuntimeBridge() : undefined; - this.storage = typeof browser.storage !== "undefined" ? new EdgeChromeStorageBridge() : undefined; - this.tabs = typeof browser.tabs !== "undefined" ? new EdgeChromeTabsBridge() : undefined; - this.webNavigation = typeof browser.webNavigation !== "undefined" ? new EdgeWebNavigationBridge() : undefined; - this.webRequest = typeof browser.webRequest !== "undefined" ? new EdgeWebRequestBridge() : undefined; - this.windows = typeof browser.windows !== "undefined" ? new EdgeChromeWindowsBridge() : undefined; - } -} -var myBrowser = browser; -var chrome = new EdgeBackgroundBridge(); diff --git a/edge-files/contentScriptsAPIBridge.js b/edge-files/contentScriptsAPIBridge.js deleted file mode 100644 index 0a84c346b..000000000 --- a/edge-files/contentScriptsAPIBridge.js +++ /dev/null @@ -1,484 +0,0 @@ -try { - if (!Range.prototype.hasOwnProperty("intersectsNode")) { - Range.prototype["intersectsNode"] = function (node) { - let range = document.createRange(); - range.selectNode(node); - return 0 > this.compareBoundaryPoints(Range.END_TO_START, range) - && 0 < this.compareBoundaryPoints(Range.START_TO_END, range); - }; - } -} -catch (e) { } -try { - if (!Navigator.prototype.hasOwnProperty("languages")) { - Navigator.prototype["languages"] = [navigator.language]; - } -} -catch (e) { } -var getExtensionProtocol = function () { - if (typeof browser == "undefined") { - if (typeof chrome !== "undefined") - return "chrome-extension://"; - } - else { - return "ms-browser-extension://"; - } -}; -class BridgeAlarmEvent { - constructor() { - this.listeners = new Array(); - } - addListener(callback) { - this.listeners.push(callback); - } - addRules(rules, callback) { } - getRules(ruleIdentifiers, callback) { } - hasListener(callback) { return false; } - hasListeners() { return this.listeners.length > 0; } - removeRules(ruleIdentifiers, callback) { } - removeListener(callback) { } -} -class EdgeBridgeAlarms { - constructor() { - this.alarms = {}; - this.onAlarm = new BridgeAlarmEvent(); - } - create(name, alarmInfo) { - if (arguments.length < 1 || arguments.length > 2) { - throw "Unexpected set of arguments. Expecting (alarmInfo) or (name, alarmInfo)"; - } - var alarmName = ""; - var startMilliseconds = 0; - var startSet = false; - if (typeof name === "string") { - alarmName = name; - } - else if (typeof name === "object") { - alarmInfo = name; - } - else - throw "Unexpected set of arguments. Expecting (alarmInfo) or (name, alarmInfo)"; - if (!alarmInfo) { - throw "You must specify an alarmInfo argument!!"; - } - if (!alarmInfo.when && !alarmInfo.delayInMinutes && !alarmInfo.periodInMinutes) { - throw "Invalid alarmInfo argument!!"; - } - else if (alarmInfo.when && alarmInfo.delayInMinutes) { - throw "Invalid alarmInfo argument!! Either 'when' or 'delayInMinutes' but not both!!"; - } - else if (alarmInfo.when) { - startMilliseconds = alarmInfo.when; - startSet = true; - } - else if (alarmInfo.delayInMinutes) { - startMilliseconds = alarmInfo.delayInMinutes * 60 * 1000; - startSet = true; - } - else if (alarmInfo.periodInMinutes) { - startMilliseconds = alarmInfo.periodInMinutes * 60 * 1000; - startSet = true; - } - else - throw "Invalid alarmInfo argument!!"; - var timerHandle; - if (startSet) { - if (this.alarms[alarmName]) { - this.clearAlarm(alarmName); - } - var alarm = { name: alarmName, scheduledTime: Date.now() + startMilliseconds }; - var alarmAndHandle = { alarm: alarm, timerHandle: 0, startedInterval: false }; - this.alarms[alarmName] = alarmAndHandle; - if (alarmInfo.periodInMinutes) { - this.alarms[alarmName].alarm.periodInMinutes = alarmInfo.periodInMinutes; - this.alarms[alarmName].timerHandle = window.setTimeout(function (alarmName, that) { - that.soundAlarm(alarmName, that); - that.alarms[alarmName].timerHandle = window.setInterval(that.soundAlarm, alarmInfo.periodInMinutes * 60 * 1000, alarmName, that); - that.alarms[alarmName].startedInterval = true; - }, startMilliseconds, alarmName, this); - } - else { - this.alarms[alarmName].timerHandle = window.setTimeout(this.soundAlarm, startMilliseconds, alarmName, this); - } - } - } - getAll(callback) { - for (var key in this.alarms) { - if (this.alarms.hasOwnProperty(key)) { - var alarm = this.alarms[key].alarm; - callback(alarm); - } - } - } - clearAll(callback) { - var clearedAll = true; - for (var key in this.alarms) { - if (this.alarms.hasOwnProperty(key)) { - var alarm = this.alarms[key].alarm; - if (!this.clearAlarm(alarm.name)) { - clearedAll = false; - } - } - } - if (callback) { - callback(clearedAll); - } - } - clear(name, callback) { - var alarmName = ""; - if (typeof name === "string") { - alarmName = name; - } - else if (typeof name === "function") { - callback = name; - } - var wasCleared = this.clearAlarm(alarmName); - if (callback) { - callback(wasCleared); - } - } - get(name, callback) { - if (this.alarms.hasOwnProperty(name)) { - var alarm = this.alarms[name].alarm; - callback(alarm); - } - } - clearAlarm(name) { - var wasCleared = false; - if (this.alarms[name]) { - if (this.alarms[name].alarm.startedInterval) { - window.clearInterval(this.alarms[name].timerHandle); - } - else { - window.clearTimeout(this.alarms[name].timerHandle); - } - delete this.alarms[name]; - wasCleared = true; - } - return wasCleared; - } - soundAlarm(name, that) { - for (var index = 0; index < that.onAlarm.listeners.length; index++) { - var listener = that.onAlarm.listeners[index]; - listener({ name: name }); - } - } -} -class FakeEvent { - addListener(callback) { } - addRules(rules, callback) { } - getRules(ruleIdentifiers, callback) { } - hasListener(callback) { return false; } - hasListeners() { return false; } - removeRules(ruleIdentifiers, callback) { } - removeListener(callback) { } -} -class EdgeBridgeHelper { - constructor() { - this.fakeEvent = new FakeEvent(); - this.alarms = new EdgeBridgeAlarms(); - } - toAbsolutePath(relativePath) { - if (relativePath.indexOf("ms-browser-extension://") == 0) { - return relativePath.replace(myBrowser.runtime.getURL(""), ""); - } - else if (relativePath.indexOf("/") != 0) { - var absolutePath = ""; - var documentPath = document.location.pathname; - absolutePath = documentPath.substring(0, documentPath.lastIndexOf("/") + 1); - absolutePath += relativePath; - return absolutePath; - } - return relativePath; - } -} -var bridgeHelper = new EdgeBridgeHelper(); -class EdgeBridgeDebugLog { - constructor() { - this.CatchOnException = true; - this.VerboseLogging = true; - this.FailedCalls = {}; - this.SuccededCalls = {}; - this.DeprecatedCalls = {}; - this.BridgedCalls = {}; - this.UnavailableApis = {}; - this.EdgeIssues = {}; - } - log(message) { - try { - if (this.VerboseLogging) { - console.log(message); - } - } - catch (e) { - } - } - info(message) { - try { - if (this.VerboseLogging) { - console.info(message); - } - } - catch (e) { - } - } - warn(message) { - try { - if (this.VerboseLogging) { - console.warn(message); - } - } - catch (e) { - } - } - error(message) { - try { - if (this.VerboseLogging) { - console.error(message); - } - } - catch (e) { - } - } - DoActionAndLog(action, name, deprecatedTo, bridgedTo) { - var result; - try { - result = action(); - this.AddToCalledDictionary(this.SuccededCalls, name); - if (typeof deprecatedTo !== "undefined") { - this.warn("API Call Deprecated - Name: " + name + ", Please use " + deprecatedTo + " instead!"); - this.AddToCalledDictionary(this.DeprecatedCalls, name); - } - if (typeof bridgedTo !== "undefined") { - this.info("API Call '" + name + "' has been bridged to another Edge API: " + bridgedTo); - this.AddToCalledDictionary(this.BridgedCalls, name); - } - this.info("API Call: '" + name + "'"); - return result; - } - catch (ex) { - this.AddToCalledDictionary(this.FailedCalls, name); - if (this.CatchOnException) - this.error("API Call Failed: " + name + " - " + ex); - else - throw ex; - } - } - LogEdgeIssue(name, message) { - this.warn(message); - this.AddToCalledDictionary(this.EdgeIssues, name); - } - LogUnavailbleApi(name, deprecatedTo) { - this.warn("API Call '" + name + "' is not supported in Edge"); - this.AddToCalledDictionary(this.UnavailableApis, name); - if (typeof deprecatedTo !== "undefined") { - this.warn("API Call Deprecated - Name: " + name + ", Please use " + deprecatedTo + " instead!"); - this.AddToCalledDictionary(this.DeprecatedCalls, name); - } - } - AddToCalledDictionary(dictionary, name) { - if (typeof dictionary[name] !== "undefined") { - dictionary[name]++; - } - else { - dictionary[name] = 1; - } - } -} -var bridgeLog = new EdgeBridgeDebugLog(); -class EdgeExtensionBridge { - get inIncognitoContext() { - return bridgeLog.DoActionAndLog(() => { - return myBrowser.extension.inIncognitoContext; - }, "extension.inIncognitoContext"); - } - getBackgroundPage() { - return bridgeLog.DoActionAndLog(() => { - return myBrowser.extension.getBackgroundPage.apply(null, arguments); - }, "extension.getBackgroundPage"); - } - getURL(path) { - return bridgeLog.DoActionAndLog(() => { - return myBrowser.extension.getURL.apply(null, arguments); - }, "extension.getURL"); - } - getViews(fetchProperties) { - return bridgeLog.DoActionAndLog(() => { - return myBrowser.extension.getViews.apply(null, arguments); - }, "extension.getViews"); - } - isAllowedIncognitoAccess(callback) { - return bridgeLog.DoActionAndLog(() => { - return myBrowser.extension.isAllowedIncognitoAccess.apply(null, arguments); - }, "extension.isAllowedIncognitoAccess"); - } -} -class EdgeChromeExtensionBridge extends EdgeExtensionBridge { - get onConnect() { return bridgeLog.DoActionAndLog(() => { return EdgeRuntimeBridge.prototype.onConnect; }, "extension.onConnect", "runtime.onConnect", "runtime.onConnect"); } - get onMessage() { return bridgeLog.DoActionAndLog(() => { return myBrowser.runtime.onMessage; }, "extension.onMessage", "runtime.onMessage", "runtime.onMessage"); } - get onRequest() { return bridgeLog.DoActionAndLog(() => { return myBrowser.runtime.onMessage; }, "extension.onRequest", "runtime.onMessage", "runtime.onMessage"); } - get onRequestExternal() { return bridgeLog.DoActionAndLog(() => { return myBrowser.runtime.onMessageExternal; }, "extension.onRequestExternal", "runtime.onMessageExternal", "runtime.onMessageExternal"); } - get lastError() { return bridgeLog.DoActionAndLog(() => { return myBrowser.runtime.lastError; }, "extension.lastError", undefined, "runtime.lastError"); } - connect(extensionId, connectInfo) { - return bridgeLog.DoActionAndLog(() => { - return EdgeRuntimeBridge.prototype.connect.apply(null, arguments); - }, "extension.connect", "runtime.connect", "runtime.connect"); - } - sendMessage(message, responseCallback) { - return bridgeLog.DoActionAndLog(() => { - return EdgeRuntimeBridge.prototype.sendMessage.apply(null, arguments); - }, "extension.sendMessage", "runtime.sendMessage", "runtime.sendMessage"); - } - sendRequest(extensionId, message, options, responseCallback) { - return bridgeLog.DoActionAndLog(() => { - return EdgeRuntimeBridge.prototype.sendMessage.apply(null, arguments); - }, "extension.sendRequest", "runtime.sendMessage", "runtime.sendMessage"); - } - isAllowedFileSchemeAccess(callback) { - bridgeLog.LogUnavailbleApi("extension.isAllowedFileSchemeAccess"); - } - setUpdateUrlData(data) { - bridgeLog.LogUnavailbleApi("extension.setUpdateUrlData"); - } -} -class EdgeI18nBridge { - getAcceptLanguages(callback) { - return bridgeLog.DoActionAndLog(() => { - return myBrowser.i18n.getAcceptLanguages.apply(null, arguments); - }, "i18n.getAcceptLanguages"); - } - getMessage(messageName, substitutions) { - return bridgeLog.DoActionAndLog(() => { - if (messageName.indexOf("@@extension_id") > -1) { - return myBrowser.runtime.id; - } - return myBrowser.i18n.getMessage.apply(null, arguments); - }, "i18n.getMessage"); - } - getUILanguage() { - return bridgeLog.DoActionAndLog(() => { - return myBrowser.i18n.getUILanguage.apply(null, arguments); - }, "i18n.getUILanguage"); - } -} -class EdgeRuntimeBridge { - get id() { return bridgeLog.DoActionAndLog(() => { return myBrowser.runtime.id; }, "runtime.id"); } - get lastError() { return bridgeLog.DoActionAndLog(() => { return myBrowser.runtime.lastError; }, "runtime.lastError"); } - get onConnect() { return bridgeLog.DoActionAndLog(() => { return myBrowser.runtime.onConnect; }, "runtime.onConnect"); } - get onInstalled() { return bridgeLog.DoActionAndLog(() => { return myBrowser.runtime.onInstalled; }, "runtime.onInstalled"); } - get onMessage() { return bridgeLog.DoActionAndLog(() => { return myBrowser.runtime.onMessage; }, "runtime.onMessage"); } - get onMessageExternal() { return bridgeLog.DoActionAndLog(() => { return myBrowser.runtime.onMessageExternal; }, "runtime.onMessageExternal"); } - connect(extensionId, connectInfo) { - return bridgeLog.DoActionAndLog(() => { - return myBrowser.runtime.connect.apply(null, arguments); - }, "runtime.connect"); - } - connectNative(application) { - return bridgeLog.DoActionAndLog(() => { - return myBrowser.runtime.connectNative.apply(null, arguments); - }, "runtime.connectNative"); - } - getBackgroundPage(callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.runtime.getBackgroundPage.apply(null, arguments); - }, "runtime.getBackgroundPage"); - } - getManifest() { - return bridgeLog.DoActionAndLog(() => { - return myBrowser.runtime.getManifest.apply(null, arguments); - }, "runtime.getManifest"); - } - getURL(path) { - return bridgeLog.DoActionAndLog(() => { - return myBrowser.runtime.getURL.apply(null, arguments); - }, "runtime.getURL"); - } - reload() { - bridgeLog.DoActionAndLog(() => { - myBrowser.runtime.reload.apply(null, arguments); - }, "runtime.reload"); - } - sendMessage(extensionId, message, options, responseCallback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.runtime.sendMessage.apply(null, arguments); - }, "runtime.sendMessage"); - } - sendNativeMessage(application, message, responseCallback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.runtime.sendNativeMessage.apply(null, arguments); - }, "runtime.sendNativeMessage"); - } - setUninstallURL(url, callback) { - bridgeLog.DoActionAndLog(() => { - myBrowser.runtime.setUninstallURL.apply(null, arguments); - }, "runtime.setUninstallURL"); - } -} -class EdgeChromeRuntimeBridge extends EdgeRuntimeBridge { - get onConnectExternal() { bridgeLog.LogUnavailbleApi("runtime.onConnectExternal"); return bridgeHelper.fakeEvent; } - get onRestartRequired() { bridgeLog.LogUnavailbleApi("runtime.onRestartRequired"); return bridgeHelper.fakeEvent; } - get onSuspend() { bridgeLog.LogUnavailbleApi("runtime.onSuspend"); return bridgeHelper.fakeEvent; } - get onSuspendCanceled() { bridgeLog.LogUnavailbleApi("runtime.onSuspendCanceled"); return bridgeHelper.fakeEvent; } - get onUpdateAvailable() { bridgeLog.LogUnavailbleApi("runtime.onUpdateAvailable"); return bridgeHelper.fakeEvent; } - get onStartup() { return bridgeLog.DoActionAndLog(() => { return myBrowser.windows.onCreated; }, "runtime.onStartup", undefined, "windows.onCreated"); } - openOptionsPage(callback) { - bridgeLog.DoActionAndLog(() => { - var optionsPage = myBrowser.runtime.getManifest()["options_page"]; - var optionsPageUrl = myBrowser.runtime.getURL(optionsPage); - if (typeof callback !== "undefined") { - myBrowser.tabs.create({ url: optionsPageUrl }, callback); - } - else { - myBrowser.tabs.create({ url: optionsPageUrl }); - } - }, "runtime.openOptionsPage", undefined, "tabs.create({ url: optionsPageUrl })"); - } - setUninstallURL(url, callback) { - if (myBrowser.runtime.setUninstallURL) { - EdgeRuntimeBridge.prototype.setUninstallURL.apply(null, arguments); - } - else { - bridgeLog.LogUnavailbleApi("runtime.setUninstallURL"); - } - } - getPackageDirectoryEntry(callback) { - bridgeLog.LogUnavailbleApi("runtime.getPackageDirectoryEntry"); - } - getPlatformInfo(callback) { - bridgeLog.LogUnavailbleApi("runtime.getPlatformInfo"); - } - requestUpdateCheck(callback) { - bridgeLog.LogUnavailbleApi("runtime.requestUpdateCheck"); - } - restart() { - bridgeLog.LogUnavailbleApi("runtime.restart"); - } -} -class EdgeStorageBridge { - get local() { return bridgeLog.DoActionAndLog(() => { return myBrowser.storage.local; }, "storage.local"); } - get sync() { return bridgeLog.DoActionAndLog(() => { return myBrowser.storage.sync; }, "storage.sync"); } - get onChanged() { return bridgeLog.DoActionAndLog(() => { return myBrowser.storage.onChanged; }, "storage.onChanged"); } -} -class EdgeChromeStorageBridge extends EdgeStorageBridge { - get sync() { - if (myBrowser.storage.sync) { - return EdgeStorageBridge.prototype.sync; - } - else { - return bridgeLog.DoActionAndLog(() => { - return myBrowser.storage.local; - }, "storage.sync", undefined, "storage.local"); - } - } - get managed() { return bridgeLog.DoActionAndLog(() => { return myBrowser.storage.local; }, "storage.managed", undefined, "storage.local"); } -} -class EdgeContentBridge { - constructor() { - this.extension = typeof browser.extension !== "undefined" ? new EdgeChromeExtensionBridge() : undefined; - this.i18n = typeof browser.i18n !== "undefined" ? new EdgeI18nBridge() : undefined; - this.runtime = typeof browser.runtime !== "undefined" ? new EdgeChromeRuntimeBridge() : undefined; - this.storage = typeof browser.storage !== "undefined" ? new EdgeChromeStorageBridge() : undefined; - } -} -var myBrowser = browser; -var chrome = new EdgeContentBridge(); diff --git a/edge-files/images/icon128.png b/edge-files/images/icon128.png deleted file mode 100644 index b96b52c57..000000000 Binary files a/edge-files/images/icon128.png and /dev/null differ diff --git a/edge-files/images/icon176.png b/edge-files/images/icon176.png deleted file mode 100644 index bba0f98c9..000000000 Binary files a/edge-files/images/icon176.png and /dev/null differ diff --git a/edge-files/images/icon20.png b/edge-files/images/icon20.png deleted file mode 100644 index 6147ccd0a..000000000 Binary files a/edge-files/images/icon20.png and /dev/null differ diff --git a/edge-files/images/icon25.png b/edge-files/images/icon25.png deleted file mode 100644 index 7e59f8e12..000000000 Binary files a/edge-files/images/icon25.png and /dev/null differ diff --git a/edge-files/images/icon30.png b/edge-files/images/icon30.png deleted file mode 100644 index 2a1907f48..000000000 Binary files a/edge-files/images/icon30.png and /dev/null differ diff --git a/edge-files/images/icon40.png b/edge-files/images/icon40.png deleted file mode 100644 index 8f3d0795f..000000000 Binary files a/edge-files/images/icon40.png and /dev/null differ diff --git a/edge-files/images/icon48.png b/edge-files/images/icon48.png deleted file mode 100644 index d6fedbad6..000000000 Binary files a/edge-files/images/icon48.png and /dev/null differ diff --git a/edge-files/images/scan.gif b/edge-files/images/scan.gif deleted file mode 100644 index ae5698b01..000000000 Binary files a/edge-files/images/scan.gif and /dev/null differ diff --git a/edge-files/store-icon.png b/edge-files/store-icon.png deleted file mode 100644 index 35c6bd6b6..000000000 Binary files a/edge-files/store-icon.png and /dev/null differ diff --git a/manifest-edge.json b/manifest-edge.json deleted file mode 100644 index 6147e1084..000000000 --- a/manifest-edge.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "author": "Authenticator Extension", - "background": { - "scripts": [ - "js/jsqrcode/grid.js", - "js/jsqrcode/version.js", - "js/jsqrcode/detector.js", - "js/jsqrcode/formatinf.js", - "js/jsqrcode/errorlevel.js", - "js/jsqrcode/bitmat.js", - "js/jsqrcode/datablock.js", - "js/jsqrcode/bmparser.js", - "js/jsqrcode/datamask.js", - "js/jsqrcode/rsdecoder.js", - "js/jsqrcode/gf256poly.js", - "js/jsqrcode/gf256.js", - "js/jsqrcode/decoder.js", - "js/jsqrcode/qrcode.js", - "js/jsqrcode/findpat.js", - "js/jsqrcode/alignpat.js", - "js/jsqrcode/databr.js", - "js/md5.js", - "js/aes.js", - "js/sha.js", - "js/qrcode.js", - "build/models/encryption.js", - "build/models/interface.js", - "build/models/otp.js", - "build/models/storage.js", - "build/background.js" - ], - "persistent": false - }, - "browser_action": { - "default_icon": { - "20": "images/icon20.png", - "25": "images/icon25.png", - "30": "images/icon30.png", - "40": "images/icon40.png" - }, - "default_title": "__MSG_extShortName__", - "default_popup": "view/popup.html" - }, - "content_security_policy": "script-src 'self'; font-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; connect-src https://www.google.com/; default-src 'none'", - "default_locale": "en", - "description": "__MSG_extDesc__", - "icons": { - "48": "images/icon48.png", - "128": "images/icon128.png", - "176": "images/icon176.png" - }, - "manifest_version": 2, - "name": "__MSG_extName__", - "permissions": [ - "", - "storage" - ], - "short_name": "__MSG_extShortName__", - "version": "5.2.2", - "web_accessible_resources": [ - "view/qr.html", - "images/scan.gif" - ], - "-ms-preload": { - "backgroundScript": "edge-files/backgroundScriptsAPIBridge.js", - "contentScript": "edge-files/contentScriptsAPIBridge.js" - } -} diff --git a/package-lock.json b/package-lock.json index 0e1b1c7fb..980508fb9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,25 +4,45 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, "@types/chrome": { - "version": "0.0.59", - "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.59.tgz", - "integrity": "sha512-wbzlTyfybnPgKDgc6krdbZW+8QPTYedWptjB4szZU4mWmhm9+Gn8yjDJQe2rM+HiYp3iTdP6yOa0kBDURStjEg==", + "version": "0.0.86", + "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.86.tgz", + "integrity": "sha512-7ehebPf/5IR64SYdD2Vig0q0oUCNbWMQ29kASlNJaHVVtA5/J/DnrxnYVPCID70o7LgHdYsav6I4/XdqAxjTLQ==", "dev": true, "requires": { "@types/filesystem": "*" } }, "@types/crypto-js": { - "version": "3.1.38", - "resolved": "http://registry.npm.taobao.org/@types/crypto-js/download/@types/crypto-js-3.1.38.tgz", - "integrity": "sha1-4TZ/dz7d4phrqa6+478c3z6Bil0=", + "version": "3.1.43", + "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-3.1.43.tgz", + "integrity": "sha512-EHe/YKctU3IYNBsDmSOPX/7jLHPRlx8WaiDKSY9JCTnJ8XJeM4c0ZJvx+9Gxmr2s2ihI92R+3U/gNL1sq5oRuQ==", "dev": true }, "@types/filesystem": { - "version": "0.0.28", - "resolved": "http://registry.npm.taobao.org/@types/filesystem/download/@types/filesystem-0.0.28.tgz", - "integrity": "sha1-P9dzWDDyx0E8taxFeAvEWQRpew4=", + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.29.tgz", + "integrity": "sha512-85/1KfRedmfPGsbK8YzeaQUyV1FQAvMPMTuWFQ5EkLd2w7szhNO96bk3Rh/SKmOfd9co2rCLf0Voy4o7ECBOvw==", "dev": true, "requires": { "@types/filewriter": "*" @@ -30,14 +50,20 @@ }, "@types/filewriter": { "version": "0.0.28", - "resolved": "http://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.28.tgz", + "resolved": "http://registry.npm.taobao.org/@types/filewriter/download/@types/filewriter-0.0.28.tgz", "integrity": "sha1-wFTor02d11205jq8dviFFocU1LM=", "dev": true }, "@types/jssha": { - "version": "0.0.29", - "resolved": "http://registry.npmjs.org/@types/jssha/-/jssha-0.0.29.tgz", - "integrity": "sha1-leg9uph4f/eW0tXzehklq/Qbycs=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/jssha/-/jssha-2.0.0.tgz", + "integrity": "sha512-oBnY3csYnXfqZXDRBJwP1nDDJCW/+VMJ88UHT4DCy0deSXpJIQvMCwYlnmdW4M+u7PiSfQc44LmiFcUbJ8hLEw==", + "dev": true + }, + "@types/q": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz", + "integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==", "dev": true }, "@vue/component-compiler-utils": { @@ -299,9 +325,9 @@ } }, "ansi-escapes": { - "version": "3.1.0", - "resolved": "http://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", - "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", "dev": true }, "ansi-regex": { @@ -327,6 +353,17 @@ "requires": { "micromatch": "^3.1.4", "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } } }, "aproba": { @@ -392,11 +429,12 @@ } }, "assert": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", - "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", + "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", "dev": true, "requires": { + "object-assign": "^4.1.1", "util": "0.10.3" }, "dependencies": { @@ -423,16 +461,10 @@ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", "dev": true }, - "async": { - "version": "1.5.2", - "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true - }, "async-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", - "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", "dev": true }, "atoa": { @@ -482,6 +514,12 @@ "supports-color": "^2.0.0" } }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, "strip-ansi": { "version": "3.0.1", "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", @@ -573,9 +611,9 @@ "dev": true }, "binary-extensions": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.12.0.tgz", - "integrity": "sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", "dev": true }, "bluebird": { @@ -590,6 +628,12 @@ "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", "dev": true }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "dev": true + }, "boxen": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", @@ -859,24 +903,23 @@ "dev": true }, "chokidar": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", - "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.6.tgz", + "integrity": "sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g==", "dev": true, "requires": { "anymatch": "^2.0.0", - "async-each": "^1.0.0", - "braces": "^2.3.0", - "fsevents": "^1.2.2", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", "glob-parent": "^3.1.0", - "inherits": "^2.0.1", + "inherits": "^2.0.3", "is-binary-path": "^1.0.0", "is-glob": "^4.0.0", - "lodash.debounce": "^4.0.8", - "normalize-path": "^2.1.1", + "normalize-path": "^3.0.0", "path-is-absolute": "^1.0.0", - "readdirp": "^2.0.0", - "upath": "^1.0.5" + "readdirp": "^2.2.1", + "upath": "^1.1.1" } }, "chownr": { @@ -886,9 +929,9 @@ "dev": true }, "chrome-trace-event": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.0.tgz", - "integrity": "sha512-xDbVgyfDTT2piup/h8dK/y4QZfJRSa73bw1WZ8b4XM1o7fsFubUVGYcE+1ANtOzJJELGpYoG2961z0Z6OAld9A==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", + "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", "dev": true, "requires": { "tslib": "^1.9.0" @@ -910,17 +953,6 @@ "safe-buffer": "^5.0.1" } }, - "clang-format": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/clang-format/-/clang-format-1.2.3.tgz", - "integrity": "sha512-x90Hac4ERacGDcZSvHKK58Ga0STuMD+Doi5g0iG2zf7wlJef5Huvhs/3BvMRFxwRYyYSdl6mpQNrtfMxE8MQzw==", - "dev": true, - "requires": { - "async": "^1.5.2", - "glob": "^7.0.0", - "resolve": "^1.1.6" - } - }, "class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", @@ -976,6 +1008,17 @@ "wrap-ansi": "^2.0.0" } }, + "coa": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", + "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", + "dev": true, + "requires": { + "@types/q": "^1.5.1", + "chalk": "^2.4.1", + "q": "^1.1.2" + } + }, "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", @@ -1020,9 +1063,9 @@ "dev": true }, "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", "dev": true }, "concat-map": { @@ -1244,12 +1287,73 @@ } } }, + "css-select": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.0.2.tgz", + "integrity": "sha512-dSpYaDVoWaELjvZ3mS6IKZM/y2PMPa/XYoEfYNZePL4U/XgyxZNroHEHReDx/d+VgXh9VbCTtFqLkFbmeqeaRQ==", + "dev": true, + "requires": { + "boolbase": "^1.0.0", + "css-what": "^2.1.2", + "domutils": "^1.7.0", + "nth-check": "^1.0.2" + } + }, + "css-select-base-adapter": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", + "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==", + "dev": true + }, + "css-tree": { + "version": "1.0.0-alpha.28", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.28.tgz", + "integrity": "sha512-joNNW1gCp3qFFzj4St6zk+Wh/NBv0vM5YbEreZk0SD4S23S+1xBKb6cLDg2uj4P4k/GUMlIm6cKIDqIG+vdt0w==", + "dev": true, + "requires": { + "mdn-data": "~1.1.0", + "source-map": "^0.5.3" + } + }, + "css-url-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/css-url-regex/-/css-url-regex-1.1.0.tgz", + "integrity": "sha1-g4NCMMyfdMRX3lnuvRVD/uuDt+w=", + "dev": true + }, + "css-what": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", + "dev": true + }, "cssesc": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", "dev": true }, + "csso": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/csso/-/csso-3.5.1.tgz", + "integrity": "sha512-vrqULLffYU1Q2tLdJvaCYbONStnfkfimRxXNaGjxMldI0C7JPBC4rB1RyjhfdZ4m1frm8pM9uRPKH3d2knZ8gg==", + "dev": true, + "requires": { + "css-tree": "1.0.0-alpha.29" + }, + "dependencies": { + "css-tree": { + "version": "1.0.0-alpha.29", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.29.tgz", + "integrity": "sha512-sRNb1XydwkW9IOci6iB2xmy8IGCj6r/fr+JWitvJ2JxQRPzN3T4AGGVWCMlVmVwM1gtgALJRmGIlWv5ppnGGkg==", + "dev": true, + "requires": { + "mdn-data": "~1.1.0", + "source-map": "^0.5.3" + } + } + } + }, "currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", @@ -1327,6 +1431,15 @@ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, "define-property": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", @@ -1385,9 +1498,9 @@ "dev": true }, "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz", + "integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==", "dev": true }, "diffie-hellman": { @@ -1401,12 +1514,38 @@ "randombytes": "^2.0.0" } }, + "dom-serializer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", + "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "dev": true, + "requires": { + "domelementtype": "^1.3.0", + "entities": "^1.1.1" + } + }, "domain-browser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", "dev": true }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true + }, + "domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, "dot-prop": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", @@ -1484,6 +1623,12 @@ "tapable": "^1.0.0" } }, + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true + }, "errno": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", @@ -1502,6 +1647,31 @@ "is-arrayish": "^0.2.1" } }, + "es-abstract": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", + "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.0", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-keys": "^1.0.12" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -1850,6 +2020,30 @@ "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", "dev": true }, + "fork-ts-checker-webpack-plugin": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-1.3.5.tgz", + "integrity": "sha512-LpjSKaEVM17pst4MZeZWoYxwozZm4AB+bz4fR5oY9ksSwea86Dhj7J2dDEiHLlb+HtEwarh741kgMi25i+V6iw==", + "dev": true, + "requires": { + "babel-code-frame": "^6.22.0", + "chalk": "^2.4.1", + "chokidar": "^2.0.4", + "micromatch": "^3.1.10", + "minimatch": "^3.0.4", + "semver": "^5.6.0", + "tapable": "^1.0.0", + "worker-rpc": "^0.1.0" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } + } + }, "fragment-cache": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", @@ -1888,14 +2082,14 @@ "dev": true }, "fsevents": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.7.tgz", - "integrity": "sha512-Pxm6sI2MeBD7RdD12RYsqaP0nMiwx8eZBXCa6z2L+mRHm2DYrOYwihmhjpkdjUHwQhslWQjRpEgNq4XvBmaAuw==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", + "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", "dev": true, "optional": true, "requires": { - "nan": "^2.9.2", - "node-pre-gyp": "^0.10.0" + "nan": "^2.12.1", + "node-pre-gyp": "^0.12.0" }, "dependencies": { "abbrev": { @@ -1907,7 +2101,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -1928,12 +2123,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1948,17 +2145,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -1967,12 +2167,12 @@ "optional": true }, "debug": { - "version": "2.6.9", + "version": "4.1.1", "bundled": true, "dev": true, "optional": true, "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } }, "deep-extend": { @@ -2075,7 +2275,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -2087,6 +2288,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -2101,6 +2303,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -2108,12 +2311,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -2132,29 +2337,30 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } }, "ms": { - "version": "2.0.0", + "version": "2.1.1", "bundled": true, "dev": true, "optional": true }, "needle": { - "version": "2.2.4", + "version": "2.3.0", "bundled": true, "dev": true, "optional": true, "requires": { - "debug": "^2.1.2", + "debug": "^4.1.0", "iconv-lite": "^0.4.4", "sax": "^1.2.4" } }, "node-pre-gyp": { - "version": "0.10.3", + "version": "0.12.0", "bundled": true, "dev": true, "optional": true, @@ -2182,13 +2388,13 @@ } }, "npm-bundled": { - "version": "1.0.5", + "version": "1.0.6", "bundled": true, "dev": true, "optional": true }, "npm-packlist": { - "version": "1.2.0", + "version": "1.4.1", "bundled": true, "dev": true, "optional": true, @@ -2212,7 +2418,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -2224,6 +2431,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -2309,7 +2517,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -2324,7 +2533,7 @@ "optional": true }, "semver": { - "version": "5.6.0", + "version": "5.7.0", "bundled": true, "dev": true, "optional": true @@ -2345,6 +2554,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -2364,6 +2574,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -2407,15 +2618,23 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, "get-caller-file": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", @@ -2528,22 +2747,33 @@ "dev": true }, "gts": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/gts/-/gts-0.8.0.tgz", - "integrity": "sha512-VB9LQLFR+10cJhDBLYu9i2t7vTkewTXeBJbvw5+M2LqGgjiaKIUTIFbVBLjIknDpuaRpAzVcvhiHWy/30c09jg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gts/-/gts-1.0.0.tgz", + "integrity": "sha512-/CRhAi0/xkH1l9UveIlTxLLBcTzUNa2PHyBrllLuYtVXZc8NPh/hZmxO6JwxF6OD1GxJyAwyMt5+RjnBncI5jw==", "dev": true, "requires": { "chalk": "^2.4.1", - "clang-format": "1.2.3", + "diff": "^4.0.1", + "entities": "^1.1.1", "inquirer": "^6.0.0", "meow": "^5.0.0", - "pify": "^3.0.0", + "pify": "^4.0.0", + "prettier": "^1.15.3", "rimraf": "^2.6.2", - "tslint": "^5.9.1", + "tslint": "^5.12.0", "update-notifier": "^2.5.0", "write-file-atomic": "^2.3.0" } }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, "has-ansi": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", @@ -2567,6 +2797,12 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, "has-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", @@ -2762,24 +2998,52 @@ "dev": true }, "inquirer": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.0.tgz", - "integrity": "sha512-QIEQG4YyQ2UYZGDC4srMZ7BjHOmNk1lR2JQj5UknBapklm6WHA+VVH7N+sUdX3A7NeCfGF8o4X1S3Ao7nAcIeg==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.3.1.tgz", + "integrity": "sha512-MmL624rfkFt4TG9y/Jvmt8vdmOo836U7Y0Hxr2aFk3RelZEGX4Igk0KabWrcaaZaTv9uzglOqWh1Vly+FAWAXA==", "dev": true, "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.0", + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", "cli-cursor": "^2.1.0", "cli-width": "^2.0.0", - "external-editor": "^3.0.0", + "external-editor": "^3.0.3", "figures": "^2.0.0", - "lodash": "^4.17.10", + "lodash": "^4.17.11", "mute-stream": "0.0.7", "run-async": "^2.2.0", - "rxjs": "^6.1.0", + "rxjs": "^6.4.0", "string-width": "^2.1.0", - "strip-ansi": "^4.0.0", + "strip-ansi": "^5.1.0", "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } } }, "interpret": { @@ -2835,14 +3099,11 @@ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, - "is-builtin-module": { - "version": "1.0.0", - "resolved": "http://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", - "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", - "dev": true, - "requires": { - "builtin-modules": "^1.0.0" - } + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "dev": true }, "is-ci": { "version": "1.2.1", @@ -2873,6 +3134,12 @@ } } }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true + }, "is-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", @@ -2911,9 +3178,9 @@ "dev": true }, "is-glob": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", - "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", "dev": true, "requires": { "is-extglob": "^2.1.1" @@ -2997,6 +3264,15 @@ "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", "dev": true }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "requires": { + "has": "^1.0.1" + } + }, "is-retry-allowed": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", @@ -3009,12 +3285,27 @@ "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", "dev": true }, + "is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.0" + } + }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", "dev": true }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -3034,9 +3325,9 @@ "dev": true }, "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, "js-yaml": { @@ -3109,6 +3400,14 @@ "parse-json": "^4.0.0", "pify": "^3.0.0", "strip-bom": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } } }, "loader-runner": { @@ -3144,12 +3443,6 @@ "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", "dev": true }, - "lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", - "dev": true - }, "loud-rejection": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", @@ -3183,6 +3476,14 @@ "dev": true, "requires": { "pify": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } } }, "mamacro": { @@ -3232,6 +3533,12 @@ "safe-buffer": "^5.1.2" } }, + "mdn-data": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-1.1.4.tgz", + "integrity": "sha512-FSYbp3lyKjyj3E7fMl6rYvUdX0FBXaluGqlFoYESWQlyUTq8R+wp0rkFxoYFqZlHCvsUXGjyJmLQSnXToYhOSA==", + "dev": true + }, "mem": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", @@ -3295,6 +3602,12 @@ } } }, + "microevent.ts": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/microevent.ts/-/microevent.ts-0.1.1.tgz", + "integrity": "sha512-jo1OfR4TaEwd5HOrt5+tAZ9mqT4jmpNAusXtyfNzqVm9uiSYFZlKM1wYL4oU7azZW/PxQW53wM0S6OR1JHNa2g==", + "dev": true + }, "micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", @@ -3452,9 +3765,9 @@ "dev": true }, "nan": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.12.1.tgz", - "integrity": "sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", "dev": true, "optional": true }, @@ -3478,9 +3791,9 @@ } }, "neo-async": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.0.tgz", - "integrity": "sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", + "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", "dev": true }, "nice-try": { @@ -3529,25 +3842,22 @@ } }, "normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, "requires": { "hosted-git-info": "^2.1.4", - "is-builtin-module": "^1.0.0", + "resolve": "^1.10.0", "semver": "2 || 3 || 4 || 5", "validate-npm-package-license": "^3.0.1" } }, "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true }, "npm-run-path": { "version": "2.0.2", @@ -3558,12 +3868,27 @@ "path-key": "^2.0.0" } }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dev": true, + "requires": { + "boolbase": "~1.0.0" + } + }, "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, "object-copy": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", @@ -3595,6 +3920,12 @@ } } }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, "object-visit": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", @@ -3604,6 +3935,16 @@ "isobject": "^3.0.0" } }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" + } + }, "object.pick": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", @@ -3613,6 +3954,18 @@ "isobject": "^3.0.1" } }, + "object.values": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.0.tgz", + "integrity": "sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.12.0", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -3689,7 +4042,7 @@ }, "os-tmpdir": { "version": "1.0.2", - "resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true }, @@ -3820,7 +4173,7 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, @@ -3849,6 +4202,14 @@ "dev": true, "requires": { "pify": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } } }, "pbkdf2": { @@ -3864,10 +4225,16 @@ "sha.js": "^2.4.8" } }, + "picomatch": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.0.7.tgz", + "integrity": "sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==", + "dev": true + }, "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true }, "pkg-dir": { @@ -4170,6 +4537,12 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true + }, "qrcode-generator": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/qrcode-generator/-/qrcode-generator-1.4.3.tgz", @@ -4297,9 +4670,9 @@ } }, "registry-auth-token": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", - "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz", + "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==", "dev": true, "requires": { "rc": "^1.1.6", @@ -4346,12 +4719,12 @@ "dev": true }, "resolve": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", - "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.1.tgz", + "integrity": "sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw==", "dev": true, "requires": { - "path-parse": "^1.0.5" + "path-parse": "^1.0.6" } }, "resolve-cwd": { @@ -4439,9 +4812,9 @@ } }, "rxjs": { - "version": "6.3.3", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", - "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.2.tgz", + "integrity": "sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg==", "dev": true, "requires": { "tslib": "^1.9.0" @@ -4469,14 +4842,20 @@ "dev": true }, "sass": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.16.1.tgz", - "integrity": "sha512-lKoiOI/zsAHrdYAdcWBM0pynYCmK0t7N9OAVjxAoYvo0mDBQmlhM6w+zNuFQYeS6d3VF+7KVWwkX6oWNMJxVag==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.21.0.tgz", + "integrity": "sha512-67hIIOZZtarbhI2aSgKBPDUgn+VqetduKoD+ZSYeIWg+ksNioTzeX+R2gUdebDoolvKNsQ/GY9NDxctbXluTNA==", "dev": true, "requires": { "chokidar": "^2.0.0" } }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, "schema-utils": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", @@ -4504,9 +4883,9 @@ } }, "serialize-javascript": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.6.1.tgz", - "integrity": "sha512-A5MOagrPFga4YaKQSWHryl7AXvbQkEqpw4NNYMTNYUNV51bA8ABHgYFpqKx+YFFrw59xMV1qGH1R4AgoNIVgCw==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.7.0.tgz", + "integrity": "sha512-ke8UG8ulpFOxO8f8gRYabHQe/ZntKlcig2Mp+8+URDP1D8vJZ0KUt7LYo07q25Z/+JVSgpr/cui9PIp5H6/+nA==", "dev": true }, "set-blocking": { @@ -4732,9 +5111,9 @@ "dev": true }, "spdx-correct": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.2.tgz", - "integrity": "sha512-q9hedtzyXHr5S0A1vEPoK/7l8NpfkFYTq6iCY+Pno2ZbdZR6WexZFtqeVGkGxW3TEJMN914Z55EnAGMmenlIQQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", @@ -4758,9 +5137,9 @@ } }, "spdx-license-ids": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.1.tgz", - "integrity": "sha512-TfOfPcYGBB5sDuPn3deByxPhmfegAhpDYKSOXZQN81Oyrrif8ZCodOLzK3AesELnCx03kikhyDwh0pfvvQvF8w==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz", + "integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==", "dev": true }, "split-string": { @@ -4787,6 +5166,12 @@ "figgy-pudding": "^3.5.1" } }, + "stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "dev": true + }, "static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", @@ -4883,7 +5268,7 @@ }, "strip-eof": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", "dev": true }, @@ -4908,6 +5293,37 @@ "has-flag": "^3.0.0" } }, + "svg-to-vue": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/svg-to-vue/-/svg-to-vue-0.4.0.tgz", + "integrity": "sha512-g/ZHtEFf4QDsDtTk9tuYX/MJ2HESTUBMTkuLoffQGQ3xMtlmD9Ec4YyTgmMkP1P8QJtWWu2FiGdOnlKaXc/X/Q==", + "dev": true, + "requires": { + "svgo": "^1.1.1" + } + }, + "svgo": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.2.2.tgz", + "integrity": "sha512-rAfulcwp2D9jjdGu+0CuqlrAUin6bBWrpoqXWwKDZZZJfXcUXQSxLJOFJCQCSA0x0pP2U0TxSlJu2ROq5Bq6qA==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "coa": "^2.0.2", + "css-select": "^2.0.0", + "css-select-base-adapter": "^0.1.1", + "css-tree": "1.0.0-alpha.28", + "css-url-regex": "^1.1.0", + "csso": "^3.5.1", + "js-yaml": "^3.13.1", + "mkdirp": "~0.5.1", + "object.values": "^1.1.0", + "sax": "~1.2.4", + "stable": "^0.1.8", + "unquote": "~1.1.1", + "util.promisify": "~1.0.0" + } + }, "tapable": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", @@ -4924,9 +5340,9 @@ } }, "terser": { - "version": "3.17.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-3.17.0.tgz", - "integrity": "sha512-/FQzzPJmCpjAH9Xvk2paiWrFq+5M6aVOf+2KRbwhByISDX/EujxsK+BAvrhb6H+2rtrLCHK9N01wO014vrIwVQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.0.0.tgz", + "integrity": "sha512-dOapGTU0hETFl1tCo4t56FN+2jffoKyER9qBGoUFyZ6y7WLoKT0bF+lAYi6B6YsILcGF3q1C2FBh8QcKSCgkgA==", "dev": true, "requires": { "commander": "^2.19.0", @@ -4943,19 +5359,21 @@ } }, "terser-webpack-plugin": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.2.3.tgz", - "integrity": "sha512-GOK7q85oAb/5kE12fMuLdn2btOS9OBZn4VsecpHDywoUC/jLhSAKOiYo0ezx7ss2EXPMzyEWFoE0s1WLE+4+oA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.3.0.tgz", + "integrity": "sha512-W2YWmxPjjkUcOWa4pBEv4OP4er1aeQJlSo2UhtCFQCuRXEHjOFscO8VyWHj9JLlA0RzQb8Y2/Ta78XZvT54uGg==", "dev": true, "requires": { - "cacache": "^11.0.2", + "cacache": "^11.3.2", "find-cache-dir": "^2.0.0", + "is-wsl": "^1.1.0", + "loader-utils": "^1.2.3", "schema-utils": "^1.0.0", - "serialize-javascript": "^1.4.0", + "serialize-javascript": "^1.7.0", "source-map": "^0.6.1", - "terser": "^3.16.1", - "webpack-sources": "^1.1.0", - "worker-farm": "^1.5.2" + "terser": "^4.0.0", + "webpack-sources": "^1.3.0", + "worker-farm": "^1.7.0" }, "dependencies": { "source-map": { @@ -5066,16 +5484,67 @@ "dev": true }, "ts-loader": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-5.3.3.tgz", - "integrity": "sha512-KwF1SplmOJepnoZ4eRIloH/zXL195F51skt7reEsS6jvDqzgc/YSbz9b8E07GxIUwLXdcD4ssrJu6v8CwaTafA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-6.0.2.tgz", + "integrity": "sha512-kkF3sGf3oBUehlvXI9fkbItbFTnNgGkYAz91vtWnsKAU4m+LAmQjuby7uTZNo3As+/zHLuyB052SkQDY6vLXtg==", "dev": true, "requires": { "chalk": "^2.3.0", "enhanced-resolve": "^4.0.0", "loader-utils": "^1.0.2", - "micromatch": "^3.1.4", - "semver": "^5.0.1" + "micromatch": "^4.0.0", + "semver": "^6.0.0" + }, + "dependencies": { + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "semver": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.1.1.tgz", + "integrity": "sha512-rWYq2e5iYW+fFe/oPPtYJxYgjBm8sC4rmoGdUOgBB7VnwKt6HrL793l2voH1UlsyYZpJ4g0wfjnTEO1s1NP2eQ==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } } }, "tslib": { @@ -5085,23 +5554,32 @@ "dev": true }, "tslint": { - "version": "5.11.0", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.11.0.tgz", - "integrity": "sha1-mPMMAurjzecAYgHkwzywi0hYHu0=", + "version": "5.17.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.17.0.tgz", + "integrity": "sha512-pflx87WfVoYepTet3xLfDOLDm9Jqi61UXIKePOuca0qoAZyrGWonDG9VTbji58Fy+8gciUn8Bt7y69+KEVjc/w==", "dev": true, "requires": { - "babel-code-frame": "^6.22.0", + "@babel/code-frame": "^7.0.0", "builtin-modules": "^1.1.1", "chalk": "^2.3.0", "commander": "^2.12.1", "diff": "^3.2.0", "glob": "^7.1.1", - "js-yaml": "^3.7.0", + "js-yaml": "^3.13.1", "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", "resolve": "^1.3.2", "semver": "^5.3.0", "tslib": "^1.8.0", - "tsutils": "^2.27.2" + "tsutils": "^2.29.0" + }, + "dependencies": { + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + } } }, "tsutils": { @@ -5126,9 +5604,9 @@ "dev": true }, "typescript": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz", - "integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.1.tgz", + "integrity": "sha512-64HkdiRv1yYZsSe4xC1WVgamNigVYjlssIoaH2HcZF0+ijsk5YK2g0G34w9wJkze8+5ow4STd22AynfO6ZYYLw==", "dev": true }, "union-value": { @@ -5199,6 +5677,12 @@ "crypto-random-string": "^1.0.0" } }, + "unquote": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", + "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=", + "dev": true + }, "unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", @@ -5246,9 +5730,9 @@ "dev": true }, "upath": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz", - "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz", + "integrity": "sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==", "dev": true }, "update-notifier": { @@ -5332,10 +5816,20 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, + "util.promisify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", + "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "object.getownpropertydescriptors": "^2.0.3" + } + }, "v8-compile-cache": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.0.2.tgz", - "integrity": "sha512-1wFuMUIM16MDJRCrpbpuEPTUGmM5QMUg0cr3KFwra2XgOgFcPGDQHDh3CszSCD2Zewc/dh/pamNEW8CbfDebUw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz", + "integrity": "sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==", "dev": true }, "validate-npm-package-license": { @@ -5391,6 +5885,16 @@ "loader-utils": "^1.0.2" } }, + "vue-svg-loader": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/vue-svg-loader/-/vue-svg-loader-0.12.0.tgz", + "integrity": "sha512-pg8H6iKCj+DAC7FZuxdfGJMHiFpJPv/YyoN1M7Iqlf+Hu4eU6Q/W/sEFx978syQA+aOx0NXrp+uQUAajqQvXbQ==", + "dev": true, + "requires": { + "loader-utils": "^1.2.3", + "svg-to-vue": "^0.4.0" + } + }, "vue-template-compiler": { "version": "2.6.10", "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.10.tgz", @@ -5415,6 +5919,11 @@ "dragula": "3.7.2" } }, + "vuex": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/vuex/-/vuex-3.1.1.tgz", + "integrity": "sha512-ER5moSbLZuNSMBFnEBVGhQ1uCBNJslH9W/Dw2W7GZN23UQA69uapP5GTT9Vm8Trc0PzBSVt6LzF3hGjmv41xcg==" + }, "watchpack": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", @@ -5427,9 +5936,9 @@ } }, "webpack": { - "version": "4.30.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.30.0.tgz", - "integrity": "sha512-4hgvO2YbAFUhyTdlR4FNyt2+YaYBYHavyzjCMbZzgglo02rlKi/pcsEzwCuCpsn1ryzIl1cq/u8ArIKu8JBYMg==", + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.33.0.tgz", + "integrity": "sha512-ggWMb0B2QUuYso6FPZKUohOgfm+Z0sVFs8WwWuSH1IAvkWs428VDNmOlAxvHGTB9Dm/qOB/qtE5cRx5y01clxw==", "dev": true, "requires": { "@webassemblyjs/ast": "1.8.5", @@ -5459,9 +5968,9 @@ } }, "webpack-cli": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.0.tgz", - "integrity": "sha512-t1M7G4z5FhHKJ92WRKwZ1rtvi7rHc0NZoZRbSkol0YKl4HvcC8+DsmGDmK7MmZxHSAetHagiOsjOB6MmzC2TUw==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.3.tgz", + "integrity": "sha512-/qBxTvsxZ7bIFQtSa08QRY5BZuiJb27cbJM/nzmgXg9NEaudP20D7BruKKIuWfABqWoMEJQcNYYq/OxxSbPHlg==", "dev": true, "requires": { "chalk": "^2.4.1", @@ -5472,6 +5981,7 @@ "import-local": "^2.0.0", "interpret": "^1.1.0", "loader-utils": "^1.1.0", + "prettier": "^1.17.0", "supports-color": "^5.5.0", "v8-compile-cache": "^2.0.2", "yargs": "^12.0.5" @@ -5489,6 +5999,12 @@ "shebang-command": "^1.2.0", "which": "^1.2.9" } + }, + "prettier": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.18.2.tgz", + "integrity": "sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==", + "dev": true } } }, @@ -5535,23 +6051,32 @@ "dev": true }, "widest-line": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.0.tgz", - "integrity": "sha1-AUKk6KJD+IgsAjOqDgKBqnYVInM=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", + "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", "dev": true, "requires": { "string-width": "^2.1.1" } }, "worker-farm": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.6.0.tgz", - "integrity": "sha512-6w+3tHbM87WnSWnENBUvA2pxJPLhQUg5LKwUQHq3r+XPhIM+Gh2R5ycbwPCyuGbNg+lPgdcnQUhuC02kJCvffQ==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", + "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", "dev": true, "requires": { "errno": "~0.1.7" } }, + "worker-rpc": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/worker-rpc/-/worker-rpc-0.1.1.tgz", + "integrity": "sha512-P1WjMrUB3qgJNI9jfmpZ/htmBEjFh//6l/5y8SD9hg1Ef5zTTVVoRjTrTEzPrNBQvmhMxkoTsjOXN10GWU7aCg==", + "dev": true, + "requires": { + "microevent.ts": "~0.1.1" + } + }, "wrap-ansi": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", @@ -5606,9 +6131,9 @@ "dev": true }, "write-file-atomic": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz", - "integrity": "sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA==", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", "dev": true, "requires": { "graceful-fs": "^4.1.11", diff --git a/package.json b/package.json index 5918d0297..75b6e1f49 100644 --- a/package.json +++ b/package.json @@ -6,16 +6,13 @@ "test": "echo \"Error: no test specified\" && exit 1", "check": "gts check", "clean": "gts clean", - "packageEdge": "cmd /C \"\"C:\\Program Files (x86)\\Windows Kits\\10\\App Certification Kit\\makeappx.exe\" pack /h SHA256 /d edge /p edge/Authenticator.appx\"", - "installEdge": "rm -rf edge && npm run edge && powershell -Command \"Add-AppxPackage -Path edge\\AppxManifest.xml -Register\"", "compile": "gts clean && tsc -p .", "fix": "gts fix", "prepare": "npm run compile", "pretest": "npm run compile", "posttest": "npm run check", "chrome": "bash scripts/build.sh chrome", - "firefox": "bash scripts/build.sh firefox", - "edge": "bash scripts/build.sh edge" + "firefox": "bash scripts/build.sh firefox" }, "repository": { "type": "git", @@ -28,18 +25,20 @@ }, "homepage": "https://github.com/Authenticator-Extension/Authenticator#readme", "devDependencies": { - "@types/chrome": "^0.0.59", - "@types/crypto-js": "^3.1.38", - "@types/jssha": "0.0.29", + "@types/chrome": "^0.0.86", + "@types/crypto-js": "^3.1.43", + "@types/jssha": "2.0.0", "css-loader": "^2.1.1", - "gts": "^0.8.0", - "sass": "^1.16.1", - "ts-loader": "^5.3.3", - "typescript": "^2.9.2", + "fork-ts-checker-webpack-plugin": "^1.3.5", + "gts": "^1.0.0", + "sass": "^1.21.0", + "ts-loader": "^6.0.2", + "typescript": "^3.5.1", "vue-loader": "^15.7.0", + "vue-svg-loader": "^0.12.0", "vue-template-compiler": "^2.6.10", - "webpack": "^4.30.0", - "webpack-cli": "^3.3.0", + "webpack": "^4.33.0", + "webpack-cli": "^3.3.3", "webpack-merge": "^4.2.1" }, "dependencies": { @@ -48,6 +47,7 @@ "qrcode-generator": "^1.4.3", "qrcode-reader": "^1.0.4", "vue": "^2.6.10", - "vue2-dragula": "^2.5.4" + "vue2-dragula": "^2.5.4", + "vuex": "^3.1.1" } } diff --git a/sass/_ui.scss b/sass/_ui.scss index 87217d862..23177ab47 100644 --- a/sass/_ui.scss +++ b/sass/_ui.scss @@ -56,6 +56,8 @@ $themes: ( ), ); +$theme-map: null; + @mixin themify($themes: $themes) { @each $theme, $map in $themes { .theme-#{$theme} & { diff --git a/scripts/build.sh b/scripts/build.sh index d981e672e..c87b53734 100644 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -4,7 +4,7 @@ # Syntax: # build.sh # Platforms: -# 'chrome', 'firefox', and 'edge' +# 'chrome', 'firefox', or 'prod' PLATFORM=$1 REMOTE=$(git config --get remote.origin.url) @@ -12,14 +12,14 @@ CREDS=$(cat ./src/models/credentials.ts | tr -d '\n') CREDREGEX="^.*'.+'.*'.+'.*'.+'.*$" set -e -if [[ $PLATFORM != "chrome" ]] && [[ $PLATFORM != "firefox" ]] && [[ $PLATFORM != "edge" ]] && [[ $PLATFORM != "prod" ]]; then - echo "Invalid platform type. Supported platforms are 'chrome', 'firefox', and 'edge'" +if [[ $PLATFORM != "chrome" ]] && [[ $PLATFORM != "firefox" ]] && [[ $PLATFORM != "prod" ]]; then + echo "Invalid platform type. Supported platforms are 'chrome', 'firefox', and 'prod'" exit 1 fi echo "Removing old build files..." rm -rf build dist -rm -rf firefox edge chrome release +rm -rf firefox chrome release echo "Checking code style..." if gts check 1> /dev/null ; then true @@ -29,7 +29,12 @@ else fi if ! [[ $CREDS =~ $CREDREGEX ]] ; then - echo -e "\e[7m\033[33mWarning: Missing info in credentials.ts\033[0m" + if [[ $PLATFORM = "prod" ]]; then + echo -e "\e[7m\033[33mError: Missing info in credentials.ts\033[0m" + exit 1 + else + echo -e "\e[7m\033[33mWarning: Missing info in credentials.ts\033[0m" + fi fi if ! [[ $REMOTE = *"https://github.com/Authenticator-Extension/Authenticator.git"* || $REMOTE = *"git@github.com:Authenticator-Extension/Authenticator.git"* ]] ; then @@ -53,30 +58,18 @@ fi postCompile () { mkdir $1 - if [[ $1 = "edge" ]]; then - mkdir $1/Extension - mkdir $1/Assets - cp -r dist css _locales LICENSE view edge-files $1/Extension - mv $1/Extension/edge-files/AppXManifest.xml $1 - mv $1/Extension/edge-files/images $1/Extension - mv $1/Extension/edge-files/Assets/icon*.png $1/Assets - cp manifest-$1.json $1/Extension/manifest.json - else - cp -r dist css images _locales LICENSE view $1 - cp manifest-$1.json $1/manifest.json - if [[ $1 = "chrome" ]]; then - cp schema-chrome.json $1/schema.json - fi + cp -r dist css images _locales LICENSE view $1 + cp manifest-$1.json $1/manifest.json + if [[ $1 = "chrome" ]]; then + cp schema-chrome.json $1/schema.json fi - } if [[ $PLATFORM = "prod" ]]; then postCompile "chrome" postCompile "firefox" - postCompile "edge" mkdir release - mv chrome firefox edge release + mv chrome firefox release else postCompile $PLATFORM fi diff --git a/src/background.ts b/src/background.ts index 5ae658572..23c49b4aa 100644 --- a/src/background.ts +++ b/src/background.ts @@ -1,11 +1,12 @@ import * as CryptoJS from 'crypto-js'; +// tslint:disable-next-line:ban-ts-ignore // @ts-ignore import QRCode from 'qrcode-reader'; -import {getCredentials} from './models/credentials'; -import {Encryption} from './models/encryption'; -import {OTPStorage} from './models/interface'; -import {EntryStorage, ManagedStorage} from './models/storage'; +import { getCredentials } from './models/credentials'; +import { Encryption } from './models/encryption'; +import { EntryStorage, ManagedStorage } from './models/storage'; +import { Dropbox, Drive } from './models/backup'; let cachedPassphrase = ''; chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { @@ -14,8 +15,13 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { return; } getQr( - sender.tab, message.info.left, message.info.top, message.info.width, - message.info.height, message.info.windowWidth); + sender.tab, + message.info.left, + message.info.top, + message.info.width, + message.info.height, + message.info.windowWidth + ); } else if (message.action === 'cachePassphrase') { cachedPassphrase = message.value; } else if (message.action === 'passphrase') { @@ -30,9 +36,14 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { let contentTab: chrome.tabs.Tab; function getQr( - tab: chrome.tabs.Tab, left: number, top: number, width: number, - height: number, windowWidth: number) { - chrome.tabs.captureVisibleTab(tab.windowId, {format: 'png'}, (dataUrl) => { + tab: chrome.tabs.Tab, + left: number, + top: number, + width: number, + height: number, + windowWidth: number +) { + chrome.tabs.captureVisibleTab(tab.windowId, { format: 'png' }, dataUrl => { contentTab = tab; const qr = new Image(); qr.src = dataUrl; @@ -46,27 +57,37 @@ function getQr( return; } ctx.drawImage( - qr, left * devicePixelRatio, top * devicePixelRatio, - width * devicePixelRatio, height * devicePixelRatio, 0, 0, - width * devicePixelRatio, height * devicePixelRatio); + qr, + left * devicePixelRatio, + top * devicePixelRatio, + width * devicePixelRatio, + height * devicePixelRatio, + 0, + 0, + width * devicePixelRatio, + height * devicePixelRatio + ); const url = captureCanvas.toDataURL(); const qrReader = new QRCode(); - qrReader.callback = (error: string, text: { - result: string, - points: Array<{ - x: number, - y: number, - count: number, - estimatedModuleSize: number - }> - }) => { + qrReader.callback = ( + error: string, + text: { + result: string; + points: Array<{ + x: number; + y: number; + count: number; + estimatedModuleSize: number; + }>; + } + ) => { if (error) { console.error(error); const id = contentTab.id; if (!id) { return; } - chrome.tabs.sendMessage(id, {action: 'errorqr'}); + chrome.tabs.sendMessage(id, { action: 'errorqr' }); } else { getTotp(text.result); } @@ -84,9 +105,9 @@ async function getTotp(text: string) { if (text.indexOf('otpauth://') !== 0) { if (text === 'error decoding QR Code') { - chrome.tabs.sendMessage(id, {action: 'errorqr'}); + chrome.tabs.sendMessage(id, { action: 'errorqr' }); } else { - chrome.tabs.sendMessage(id, {action: 'text', text}); + chrome.tabs.sendMessage(id, { action: 'text', text }); } } else { let uri = text.split('otpauth://')[1]; @@ -95,12 +116,12 @@ async function getTotp(text: string) { let label = uri.split('?')[0]; const parameterPart = uri.split('?')[1]; if (!label || !parameterPart) { - chrome.tabs.sendMessage(id, {action: 'errorqr'}); + chrome.tabs.sendMessage(id, { action: 'errorqr' }); } else { let account = ''; let secret = ''; let issuer = ''; - let period: number|undefined = undefined; + let period: number | undefined = undefined; try { label = decodeURIComponent(label); @@ -114,7 +135,7 @@ async function getTotp(text: string) { account = label; } const parameters = parameterPart.split('&'); - parameters.forEach((item) => { + parameters.forEach(item => { const parameter = item.split('='); if (parameter[0].toLowerCase() === 'secret') { secret = parameter[1]; @@ -126,33 +147,40 @@ async function getTotp(text: string) { } } else if (parameter[0].toLowerCase() === 'counter') { let counter = Number(parameter[1]); - counter = (isNaN(counter) || counter < 0) ? 0 : counter; + counter = isNaN(counter) || counter < 0 ? 0 : counter; } else if (parameter[0].toLowerCase() === 'period') { period = Number(parameter[1]); - period = (isNaN(period) || period < 0 || period > 60 || - 60 % period !== 0) ? - undefined : - period; + period = + isNaN(period) || period < 0 || period > 60 || 60 % period !== 0 + ? undefined + : period; } }); if (!secret) { - chrome.tabs.sendMessage(id, {action: 'errorqr'}); + chrome.tabs.sendMessage(id, { action: 'errorqr' }); } else if ( - !/^[0-9a-f]+$/i.test(secret) && !/^[2-7a-z]+=*$/i.test(secret)) { - chrome.tabs.sendMessage(id, {action: 'secretqr', secret}); + !/^[0-9a-f]+$/i.test(secret) && + !/^[2-7a-z]+=*$/i.test(secret) + ) { + chrome.tabs.sendMessage(id, { action: 'secretqr', secret }); } else { const encryption = new Encryption(cachedPassphrase); const hash = CryptoJS.MD5(secret).toString(); - if (!/^[2-7a-z]+=*$/i.test(secret) && /^[0-9a-f]+$/i.test(secret) && - type === 'totp') { + if ( + !/^[2-7a-z]+=*$/i.test(secret) && + /^[0-9a-f]+$/i.test(secret) && + type === 'totp' + ) { type = 'hex'; } else if ( - !/^[2-7a-z]+=*$/i.test(secret) && /^[0-9a-f]+$/i.test(secret) && - type === 'hotp') { + !/^[2-7a-z]+=*$/i.test(secret) && + /^[0-9a-f]+$/i.test(secret) && + type === 'hotp' + ) { type = 'hhex'; } - const entryData: {[hash: string]: OTPStorage} = {}; + const entryData: { [hash: string]: OTPStorage } = {}; entryData[hash] = { account, hash, @@ -161,13 +189,13 @@ async function getTotp(text: string) { type, encrypted: false, index: 0, - counter: 0 + counter: 0, }; if (period) { entryData[hash].period = period; } await EntryStorage.import(encryption, entryData); - chrome.tabs.sendMessage(id, {action: 'added', account}); + chrome.tabs.sendMessage(id, { action: 'added', account }); } } } @@ -177,126 +205,152 @@ async function getTotp(text: string) { function getBackupToken(service: string) { if (navigator.userAgent.indexOf('Chrome') !== -1 && service === 'drive') { chrome.identity.getAuthToken( - { - 'interactive': true, - 'scopes': ['https://www.googleapis.com/auth/drive.file'] - }, - (value) => { - localStorage.driveToken = value; - chrome.runtime.sendMessage({action: 'drivetoken', value}); - return true; - }); + { + interactive: true, + scopes: ['https://www.googleapis.com/auth/drive.file'], + }, + value => { + localStorage.driveToken = value; + chrome.runtime.sendMessage({ action: 'drivetoken', value }); + return true; + } + ); } else { let authUrl = ''; if (service === 'dropbox') { authUrl = - 'https://www.dropbox.com/oauth2/authorize?response_type=token&client_id=' + - getCredentials().dropbox.client_id + '&redirect_uri=' + - encodeURIComponent(chrome.identity.getRedirectURL()); + 'https://www.dropbox.com/oauth2/authorize?response_type=token&client_id=' + + getCredentials().dropbox.client_id + + '&redirect_uri=' + + encodeURIComponent(chrome.identity.getRedirectURL()); } else if (service === 'drive') { authUrl = - 'https://accounts.google.com/o/oauth2/v2/auth?response_type=code&access_type=offline&client_id=' + - getCredentials().drive.client_id + - '&scope=https%3A//www.googleapis.com/auth/drive.file&prompt=consent&redirect_uri=' + - encodeURIComponent('https://authenticator.cc/oauth'); + 'https://accounts.google.com/o/oauth2/v2/auth?response_type=code&access_type=offline&client_id=' + + getCredentials().drive.client_id + + '&scope=https%3A//www.googleapis.com/auth/drive.file&prompt=consent&redirect_uri=' + + encodeURIComponent('https://authenticator.cc/oauth'); } chrome.identity.launchWebAuthFlow( - {url: authUrl, interactive: true}, async (url) => { - if (!url) { - return; - } - let hashMatches = url.split('#'); - if (service === 'drive') { - hashMatches = url.slice(0, -1).split('?'); - } - if (hashMatches.length < 2) { - return; - } + { url: authUrl, interactive: true }, + async url => { + if (!url) { + return; + } + let hashMatches = url.split('#'); + if (service === 'drive') { + hashMatches = url.slice(0, -1).split('?'); + } + if (hashMatches.length < 2) { + return; + } - const hash = hashMatches[1]; + const hash = hashMatches[1]; - const resData = hash.split('&'); - for (let i = 0; i < resData.length; i++) { - const kv = resData[i]; - if (/^(.*?)=(.*?)$/.test(kv)) { - const kvMatches = kv.match(/^(.*?)=(.*?)$/); - if (!kvMatches) { - continue; + const resData = hash.split('&'); + for (let i = 0; i < resData.length; i++) { + const kv = resData[i]; + if (/^(.*?)=(.*?)$/.test(kv)) { + const kvMatches = kv.match(/^(.*?)=(.*?)$/); + if (!kvMatches) { + continue; + } + const key = kvMatches[1]; + const value = kvMatches[2]; + if (key === 'access_token') { + if (service === 'dropbox') { + localStorage.dropboxToken = value; + uploadBackup('dropbox'); + return; } - const key = kvMatches[1]; - const value = kvMatches[2]; - if (key === 'access_token') { - if (service === 'dropbox') { - localStorage.dropboxToken = value; - chrome.runtime.sendMessage({action: 'dropboxtoken', value}); - return; - } - } else if (key === 'code') { - if (service === 'drive') { - const xhr = new XMLHttpRequest(); - // Need to trade code we got from launchWebAuthFlow for a - // token & refresh token - await new Promise( - (resolve: (value: boolean) => void, - reject: (reason: Error) => void) => { - xhr.open( - 'POST', - 'https://www.googleapis.com/oauth2/v4/token?client_id=' + - getCredentials().drive.client_id + - '&client_secret=' + - getCredentials().drive.client_secret + - '&code=' + value + - '&redirect_uri=https://authenticator.cc/oauth&grant_type=authorization_code'); - xhr.setRequestHeader('Accept', 'application/json'); - xhr.setRequestHeader( - 'Content-Type', - 'application/x-www-form-urlencoded'); - xhr.onreadystatechange = () => { - if (xhr.readyState === 4) { - try { - const res = JSON.parse(xhr.responseText); - if (res.error) { - console.error(res.error_description); - resolve(false); - } else { - localStorage.driveToken = res.access_token; - localStorage.driveRefreshToken = - res.refresh_token; - resolve(true); - } - } catch (error) { - console.error(error); - reject(error); - } + } else if (key === 'code') { + if (service === 'drive') { + const xhr = new XMLHttpRequest(); + // Need to trade code we got from launchWebAuthFlow for a + // token & refresh token + await new Promise( + ( + resolve: (value: boolean) => void, + reject: (reason: Error) => void + ) => { + xhr.open( + 'POST', + 'https://www.googleapis.com/oauth2/v4/token?client_id=' + + getCredentials().drive.client_id + + '&client_secret=' + + getCredentials().drive.client_secret + + '&code=' + + value + + '&redirect_uri=https://authenticator.cc/oauth&grant_type=authorization_code' + ); + xhr.setRequestHeader('Accept', 'application/json'); + xhr.setRequestHeader( + 'Content-Type', + 'application/x-www-form-urlencoded' + ); + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + try { + const res = JSON.parse(xhr.responseText); + if (res.error) { + console.error(res.error_description); + resolve(false); + } else { + localStorage.driveToken = res.access_token; + localStorage.driveRefreshToken = res.refresh_token; + resolve(true); } - return; - }; - xhr.send(); - }); - chrome.runtime.sendMessage({action: 'drivetoken', value}); - } + } catch (error) { + console.error(error); + reject(error); + } + } + return; + }; + xhr.send(); + } + ); + uploadBackup('drive'); } } } - return; - }); + } + return; + } + ); + } +} + +async function uploadBackup(service: string) { + const encryption = new Encryption(cachedPassphrase); + + switch (service) { + case 'dropbox': + const dbox = new Dropbox(); + await dbox.upload(encryption); + break; + + case 'drive': + const drive = new Drive(); + await drive.upload(encryption); + break; + + default: + break; } } // Show issue page after first install -chrome.runtime.onInstalled.addListener(async (details) => { +chrome.runtime.onInstalled.addListener(async details => { if (details.reason !== 'install') { return; } else if (await ManagedStorage.get('disableInstallHelp')) { return; } - let url: string|null = null; + let url: string | null = null; if (navigator.userAgent.indexOf('Chrome') !== -1) { url = 'https://authenticator.cc/docs/en/chrome-issues'; - } else if (navigator.userAgent.indexOf('Edge') !== -1) { - url = 'https://authenticator.cc/docs/en/edge-issues'; } if (url) { diff --git a/src/components/Import.vue b/src/components/Import.vue new file mode 100644 index 000000000..01a5ca1ec --- /dev/null +++ b/src/components/Import.vue @@ -0,0 +1,50 @@ + + diff --git a/src/components/Import/FileImport.vue b/src/components/Import/FileImport.vue new file mode 100644 index 000000000..b66ab48e1 --- /dev/null +++ b/src/components/Import/FileImport.vue @@ -0,0 +1,125 @@ + + + diff --git a/src/components/Import/TextImport.vue b/src/components/Import/TextImport.vue new file mode 100644 index 000000000..104be3997 --- /dev/null +++ b/src/components/Import/TextImport.vue @@ -0,0 +1,70 @@ + + + diff --git a/src/components/Popup.vue b/src/components/Popup.vue new file mode 100644 index 000000000..4fdc9384d --- /dev/null +++ b/src/components/Popup.vue @@ -0,0 +1,60 @@ + + diff --git a/src/components/Popup/AboutPage.vue b/src/components/Popup/AboutPage.vue new file mode 100644 index 000000000..4fa05019d --- /dev/null +++ b/src/components/Popup/AboutPage.vue @@ -0,0 +1,26 @@ + + diff --git a/src/components/Popup/AddAccountPage.vue b/src/components/Popup/AddAccountPage.vue new file mode 100644 index 000000000..d5d493a6c --- /dev/null +++ b/src/components/Popup/AddAccountPage.vue @@ -0,0 +1,85 @@ + + diff --git a/src/components/Popup/AddMethodPage.vue b/src/components/Popup/AddMethodPage.vue new file mode 100644 index 000000000..3a28911bd --- /dev/null +++ b/src/components/Popup/AddMethodPage.vue @@ -0,0 +1,58 @@ + + diff --git a/src/components/Popup/DrivePage.vue b/src/components/Popup/DrivePage.vue new file mode 100644 index 000000000..355090056 --- /dev/null +++ b/src/components/Popup/DrivePage.vue @@ -0,0 +1,101 @@ + + diff --git a/src/components/Popup/DropboxPage.vue b/src/components/Popup/DropboxPage.vue new file mode 100644 index 000000000..855e3555f --- /dev/null +++ b/src/components/Popup/DropboxPage.vue @@ -0,0 +1,92 @@ + + diff --git a/src/components/Popup/EnterPasswordPage.vue b/src/components/Popup/EnterPasswordPage.vue new file mode 100644 index 000000000..140954597 --- /dev/null +++ b/src/components/Popup/EnterPasswordPage.vue @@ -0,0 +1,26 @@ + + + diff --git a/src/components/Popup/EntryComponent.vue b/src/components/Popup/EntryComponent.vue new file mode 100644 index 000000000..f3613e3f3 --- /dev/null +++ b/src/components/Popup/EntryComponent.vue @@ -0,0 +1,249 @@ + + + diff --git a/src/components/Popup/ExportPage.vue b/src/components/Popup/ExportPage.vue new file mode 100644 index 000000000..7854bebbe --- /dev/null +++ b/src/components/Popup/ExportPage.vue @@ -0,0 +1,121 @@ + + diff --git a/src/components/Popup/MainBody.vue b/src/components/Popup/MainBody.vue new file mode 100644 index 000000000..15288ff7b --- /dev/null +++ b/src/components/Popup/MainBody.vue @@ -0,0 +1,104 @@ + + diff --git a/src/components/Popup/MainHeader.vue b/src/components/Popup/MainHeader.vue new file mode 100644 index 000000000..02b167fac --- /dev/null +++ b/src/components/Popup/MainHeader.vue @@ -0,0 +1,105 @@ + + diff --git a/src/components/Popup/MenuPage.vue b/src/components/Popup/MenuPage.vue new file mode 100644 index 000000000..8be456b9a --- /dev/null +++ b/src/components/Popup/MenuPage.vue @@ -0,0 +1,105 @@ + + diff --git a/src/components/Popup/NotificationHandler.vue b/src/components/Popup/NotificationHandler.vue new file mode 100644 index 000000000..ee6dcf66c --- /dev/null +++ b/src/components/Popup/NotificationHandler.vue @@ -0,0 +1,48 @@ + + diff --git a/src/components/Popup/PageHandler.vue b/src/components/Popup/PageHandler.vue new file mode 100644 index 000000000..bded8800a --- /dev/null +++ b/src/components/Popup/PageHandler.vue @@ -0,0 +1,48 @@ + + diff --git a/src/components/Popup/PrefrencesPage.vue b/src/components/Popup/PrefrencesPage.vue new file mode 100644 index 000000000..6396c83dd --- /dev/null +++ b/src/components/Popup/PrefrencesPage.vue @@ -0,0 +1,77 @@ + + diff --git a/src/components/Popup/SetPasswordPage.vue b/src/components/Popup/SetPasswordPage.vue new file mode 100644 index 000000000..218aebc53 --- /dev/null +++ b/src/components/Popup/SetPasswordPage.vue @@ -0,0 +1,40 @@ + + diff --git a/src/components/Popup/StorageSyncConfPage.vue b/src/components/Popup/StorageSyncConfPage.vue new file mode 100644 index 000000000..04817df82 --- /dev/null +++ b/src/components/Popup/StorageSyncConfPage.vue @@ -0,0 +1,92 @@ + + diff --git a/src/content.ts b/src/content.ts index 27fa58b8e..70a16b288 100644 --- a/src/content.ts +++ b/src/content.ts @@ -38,18 +38,20 @@ function showGrayLayout() { document.body.appendChild(grayLayout); const scan = document.createElement('div'); scan.className = 'scan'; - scan.style.background = 'url(' + - chrome.extension.getURL('images/scan.gif') + ') no-repeat center'; + scan.style.background = + 'url(' + + chrome.extension.getURL('images/scan.gif') + + ') no-repeat center'; grayLayout.appendChild(scan); const captureBox = document.createElement('div'); captureBox.id = '__ga_captureBox__'; grayLayout.appendChild(captureBox); grayLayout.onmousedown = grayLayoutDown; grayLayout.onmousemove = grayLayoutMove; - grayLayout.onmouseup = (event) => { + grayLayout.onmouseup = event => { grayLayoutUp(event); }; - grayLayout.oncontextmenu = (event) => { + grayLayout.oncontextmenu = event => { event.preventDefault(); return; }; @@ -88,19 +90,21 @@ function grayLayoutMove(event: MouseEvent) { } const captureBoxLeft = Math.min( - Number(sessionStorage.getItem('captureBoxPositionLeft')), event.clientX); + Number(sessionStorage.getItem('captureBoxPositionLeft')), + event.clientX + ); const captureBoxTop = Math.min( - Number(sessionStorage.getItem('captureBoxPositionTop')), event.clientY); + Number(sessionStorage.getItem('captureBoxPositionTop')), + event.clientY + ); const captureBoxWidth = - Math.abs( - Number(sessionStorage.getItem('captureBoxPositionLeft')) - - event.clientX) - - 1; + Math.abs( + Number(sessionStorage.getItem('captureBoxPositionLeft')) - event.clientX + ) - 1; const captureBoxHeight = - Math.abs( - Number(sessionStorage.getItem('captureBoxPositionTop')) - - event.clientY) - - 1; + Math.abs( + Number(sessionStorage.getItem('captureBoxPositionTop')) - event.clientY + ) - 1; captureBox.style.left = captureBoxLeft + 'px'; captureBox.style.top = captureBoxTop + 'px'; captureBox.style.width = captureBoxWidth + 'px'; @@ -126,59 +130,73 @@ function grayLayoutUp(event: MouseEvent) { } const captureBoxLeft = - Math.min( - Number(sessionStorage.getItem('captureBoxPositionLeft')), - event.clientX) + - 1; + Math.min( + Number(sessionStorage.getItem('captureBoxPositionLeft')), + event.clientX + ) + 1; const captureBoxTop = - Math.min( - Number(sessionStorage.getItem('captureBoxPositionTop')), - event.clientY) + - 1; + Math.min( + Number(sessionStorage.getItem('captureBoxPositionTop')), + event.clientY + ) + 1; const captureBoxWidth = - Math.abs( - Number(sessionStorage.getItem('captureBoxPositionLeft')) - - event.clientX) - - 1; + Math.abs( + Number(sessionStorage.getItem('captureBoxPositionLeft')) - event.clientX + ) - 1; const captureBoxHeight = - Math.abs( - Number(sessionStorage.getItem('captureBoxPositionTop')) - - event.clientY) - - 1; + Math.abs( + Number(sessionStorage.getItem('captureBoxPositionTop')) - event.clientY + ) - 1; // make sure captureBox and grayLayout is hidden setTimeout(() => { sendPosition( - captureBoxLeft, captureBoxTop, captureBoxWidth, captureBoxHeight); + captureBoxLeft, + captureBoxTop, + captureBoxWidth, + captureBoxHeight + ); }, 200); return false; } function sendPosition( - left: number, top: number, width: number, height: number) { + left: number, + top: number, + width: number, + height: number +) { chrome.runtime.sendMessage({ action: 'position', - info: {left, top, width, height, windowWidth: window.innerWidth} + info: { left, top, width, height, windowWidth: window.innerWidth }, }); } function showQrCode(msg: string) { - const left = (screen.width / 2) - 200; - const top = (screen.height / 2) - 100; + const left = screen.width / 2 - 200; + const top = screen.height / 2 - 100; const url = - chrome.extension.getURL('view/qr.html') + '?' + encodeURIComponent(msg); + chrome.extension.getURL('view/qr.html') + '?' + encodeURIComponent(msg); window.open( - url, '_blank', - 'toolbar=no, location=no, status=no, menubar=no, scrollbars=yes, copyhistory=no, width=400, height=200, left=' + - left + ',top=' + top); + url, + '_blank', + 'toolbar=no, location=no, status=no, menubar=no, scrollbars=yes, copyhistory=no, width=400, height=200, left=' + + left + + ',top=' + + top + ); } function pasteCode(code: string) { const _inputBoxes = document.getElementsByTagName('input'); const inputBoxes: HTMLInputElement[] = []; for (let i = 0; i < _inputBoxes.length; i++) { - if (_inputBoxes[i].type === 'text' || _inputBoxes[i].type === 'number' || - _inputBoxes[i].type === 'tel' || _inputBoxes[i].type === 'password') { + if ( + _inputBoxes[i].type === 'text' || + _inputBoxes[i].type === 'number' || + _inputBoxes[i].type === 'tel' || + _inputBoxes[i].type === 'password' + ) { inputBoxes.push(_inputBoxes[i]); } } @@ -186,12 +204,20 @@ function pasteCode(code: string) { return; } const identities = [ - '2fa', 'otp', 'authenticator', 'factor', 'code', 'totp', 'twoFactorCode' + '2fa', + 'otp', + 'authenticator', + 'factor', + 'code', + 'totp', + 'twoFactorCode', ]; for (const inputBox of inputBoxes) { for (const identity of identities) { - if (inputBox.name.toLowerCase().indexOf(identity) >= 0 || - inputBox.id.toLowerCase().indexOf(identity) >= 0) { + if ( + inputBox.name.toLowerCase().indexOf(identity) >= 0 || + inputBox.id.toLowerCase().indexOf(identity) >= 0 + ) { if (!inputBox.value) { inputBox.value = code; fireInputEvents(inputBox); @@ -202,9 +228,9 @@ function pasteCode(code: string) { } const activeInputBox = - document.activeElement && document.activeElement.tagName === 'INPUT' ? - document.activeElement : - null; + document.activeElement && document.activeElement.tagName === 'INPUT' + ? document.activeElement + : null; if (activeInputBox) { const inputBox = activeInputBox as HTMLInputElement; if (!inputBox.value) { @@ -226,9 +252,11 @@ function pasteCode(code: string) { function fireInputEvents(inputBox: HTMLInputElement) { const events = [ - new KeyboardEvent('keydown'), new KeyboardEvent('keyup'), - new KeyboardEvent('keypress'), new Event('input', {'bubbles': true}), - new Event('change', {'bubbles': true}) + new KeyboardEvent('keydown'), + new KeyboardEvent('keyup'), + new KeyboardEvent('keypress'), + new Event('input', { bubbles: true }), + new Event('change', { bubbles: true }), ]; for (const event of events) { inputBox.dispatchEvent(event); @@ -236,8 +264,8 @@ function fireInputEvents(inputBox: HTMLInputElement) { return; } -window.onkeydown = (event) => { - if (event.keyCode === 27) { +window.onkeydown = event => { + if (event.key === 'Escape') { event.preventDefault(); const grayLayout = document.getElementById('__ga_grayLayout__'); const captureBox = document.getElementById('__ga_captureBox__'); diff --git a/src/definitions/i18n.d.ts b/src/definitions/i18n.d.ts new file mode 100644 index 000000000..1b17ca9d4 --- /dev/null +++ b/src/definitions/i18n.d.ts @@ -0,0 +1,3 @@ +interface I18nMessage { + [key: string]: { message: string, description: string }; +} diff --git a/src/definitions/module-interface.d.ts b/src/definitions/module-interface.d.ts new file mode 100644 index 000000000..bc1927c27 --- /dev/null +++ b/src/definitions/module-interface.d.ts @@ -0,0 +1,81 @@ +interface IModule { + getModule(): Promise | VuexConstructor; +} + +interface VuexConstructor { + state?: { + [key: string]: Object | Function + }, + mutations?: { + [key: string]: Function; + }, + actions?: { + [key: string]: Function | + { + root: Boolean; + handler: Function; + } + }, + getters?: { + [key: string]: Function; + }, + modules?: Object; + plugins?: Array; + strict?: Boolean; + devtools?: Boolean; +} + +interface MenuState { + version: String; + zoom: Number; + useAutofill: Boolean; + useHighContrast: Boolean; + backupDisabled: Boolean; + storageArea: String; +} + +interface StyleState { + style: { + timeout: Boolean; + isEditing: Boolean; + slidein: Boolean; + slideout: Boolean; + fadein: Boolean; + fadeout: Boolean; + qrfadein: Boolean; + qrfadeout: Boolean; + notificationFadein: Boolean; + notificationFadeout: Boolean; + hotpDisabled: Boolean; + } +} + +interface AccountsState { + entries: IOTPEntry[]; + encryption: IEncryption; + OTPType: Number; + shouldShowPassphrase: Boolean; + sectorStart: Boolean; + sectorOffset: Number; + second: Number; + notification: String; + filter: Boolean; + siteName: (string | null)[]; + showSearch: Boolean; + exportData: { [k: string]: IOTPEntry }; + exportEncData: { [k: string]: IOTPEntry }; +} + +interface NotificationState { + message: Array; + confirmMessage: String; + messageIdle: Boolean; + notification: String; +} + +interface BackupState { + dropboxEncrypted: Boolean; + driveEncrypted: Boolean; + dropboxToken: Boolean; + driveToken: Boolean; +} diff --git a/src/definitions/otp.d.ts b/src/definitions/otp.d.ts new file mode 100644 index 000000000..3355a863f --- /dev/null +++ b/src/definitions/otp.d.ts @@ -0,0 +1,38 @@ +interface IOTPEntry { + type: number; // OTPType + index: number; + issuer: string; + encSecret: string | null; + secret: string | null; + account: string; + hash: string; + counter: number; + code: string; + period: number; + create(encryption: IEncryption): Promise; + update(encryption: IEncryption): Promise; + next(encryption: IEncryption): Promise; + applyEncryption(encryption: IEncryption): void; + delete(): Promise; + generate(): void; +} + +interface IEncryption { + getEncryptedSecret(entry: IOTPEntry): string; + getEncryptedString(data: string): string; + getDecryptedSecret(entry: OTPStorage): string | null; + getEncryptionStatus(): boolean; + updateEncryptionPassword(password: string): void; +} + +interface OTPStorage { + account: string; + encrypted: boolean; + hash: string; + index: number; + issuer: string; + secret: string; + type: string; + counter: number; + period?: number; +} diff --git a/src/definitions/shims-vue.d.ts b/src/definitions/shims-vue.d.ts new file mode 100644 index 000000000..26217b08d --- /dev/null +++ b/src/definitions/shims-vue.d.ts @@ -0,0 +1,10 @@ +declare module '*.vue' { + import Vue, { VueConstructor } from 'vue' + export default Vue +} + +declare module '*.svg' { + import { ComponentOptions } from 'vue'; + const a: ComponentOptions; + export default a; +} diff --git a/src/definitions/vue.d.ts b/src/definitions/vue.d.ts new file mode 100644 index 000000000..6aa02263c --- /dev/null +++ b/src/definitions/vue.d.ts @@ -0,0 +1,15 @@ +import Vue from 'vue' +import { Store } from 'vuex'; + +declare module 'vue/types/vue' { + interface Vue { + // Only in Popup + $store: Store; + $dragula: any; + // Only in Import + $entries: IOTPEntry[] + $encryption: IEncryption + // In all + i18n: { [key: string]: string }; + } +} diff --git a/src/definitions/vue2-dragula.d.ts b/src/definitions/vue2-dragula.d.ts new file mode 100644 index 000000000..79c442b28 --- /dev/null +++ b/src/definitions/vue2-dragula.d.ts @@ -0,0 +1,6 @@ + +declare module 'vue2-dragula' { + import { PluginFunction, VueConstructor } from "vue"; + + const Vue2Dragula: PluginFunction; +} diff --git a/src/import.ts b/src/import.ts index 414d8ba2e..b63e71fb5 100644 --- a/src/import.ts +++ b/src/import.ts @@ -1,19 +1,187 @@ -import {entry} from './ui/entry'; -import {i18n} from './ui/i18n'; -import {UI} from './ui/ui'; -// @ts-ignore -import ImportView from './view/import'; +import Vue from 'vue'; +import ImportView from './components/Import.vue'; +import { loadI18nMessages } from './store/i18n'; + +import { Encryption } from './models/encryption'; +import { EntryStorage } from './models/storage'; +import * as CryptoJS from 'crypto-js'; async function init() { - const ui = new UI(ImportView, {el: '#import'}); + // i18n + Vue.prototype.i18n = await loadI18nMessages(); + + // Load entries to global + const encryption = new Encryption(await getCachedPassphrase()); + Vue.prototype.$entries = await EntryStorage.get(encryption); + Vue.prototype.$encryption = encryption; - const vm = await ui.load(i18n).load(entry).render(); + const instance = new Vue({ + render: h => h(ImportView), + }).$mount('#import'); + // Set title try { - document.title = ui.instance.i18n.extName; + document.title = instance.i18n.extName; } catch (e) { console.error(e); } } init(); + +function getCachedPassphrase() { + return new Promise( + (resolve: (value: string) => void, reject: (reason: Error) => void) => { + const cookie = document.cookie; + const cookieMatch = cookie + ? document.cookie.match(/passphrase=([^;]*)/) + : null; + const cachedPassphrase = + cookieMatch && cookieMatch.length > 1 ? cookieMatch[1] : null; + if (cachedPassphrase) { + return resolve(cachedPassphrase); + } + + chrome.runtime.sendMessage( + { action: 'passphrase' }, + (passphrase: string) => { + return resolve(passphrase); + } + ); + } + ); +} + +export function decryptBackupData( + backupData: { [hash: string]: OTPStorage }, + passphrase: string | null +) { + const decryptedbackupData: { [hash: string]: OTPStorage } = {}; + for (const hash of Object.keys(backupData)) { + if (typeof backupData[hash] !== 'object') { + continue; + } + if (!backupData[hash].secret) { + continue; + } + if (backupData[hash].encrypted && !passphrase) { + continue; + } + if (backupData[hash].encrypted && passphrase) { + try { + backupData[hash].secret = CryptoJS.AES.decrypt( + backupData[hash].secret, + passphrase + ).toString(CryptoJS.enc.Utf8); + backupData[hash].encrypted = false; + } catch (error) { + continue; + } + } + // backupData[hash].secret may be empty after decrypt with wrong + // passphrase + if (!backupData[hash].secret) { + continue; + } + decryptedbackupData[hash] = backupData[hash]; + } + return decryptedbackupData; +} + +export function getEntryDataFromOTPAuthPerLine(importCode: string) { + const lines = importCode.split('\n'); + const exportData: { [hash: string]: OTPStorage } = {}; + for (let item of lines) { + item = item.trim(); + if (!item.startsWith('otpauth:')) { + continue; + } + + let uri = item.split('otpauth://')[1]; + let type = uri.substr(0, 4).toLowerCase(); + uri = uri.substr(5); + let label = uri.split('?')[0]; + const parameterPart = uri.split('?')[1]; + if (!parameterPart) { + continue; + } else { + let account = ''; + let secret = ''; + let issuer = ''; + let period: number | undefined = undefined; + + try { + label = decodeURIComponent(label); + } catch (error) { + console.error(error); + } + if (label.indexOf(':') !== -1) { + issuer = label.split(':')[0]; + account = label.split(':')[1]; + } else { + account = label; + } + const parameters = parameterPart.split('&'); + parameters.forEach(item => { + const parameter = item.split('='); + if (parameter[0].toLowerCase() === 'secret') { + secret = parameter[1]; + } else if (parameter[0].toLowerCase() === 'issuer') { + try { + issuer = decodeURIComponent(parameter[1]); + } catch { + issuer = parameter[1]; + } + } else if (parameter[0].toLowerCase() === 'counter') { + let counter = Number(parameter[1]); + counter = isNaN(counter) || counter < 0 ? 0 : counter; + } else if (parameter[0].toLowerCase() === 'period') { + period = Number(parameter[1]); + period = + isNaN(period) || period < 0 || period > 60 || 60 % period !== 0 + ? undefined + : period; + } + }); + + if (!secret) { + continue; + } else if ( + !/^[0-9a-f]+$/i.test(secret) && + !/^[2-7a-z]+=*$/i.test(secret) + ) { + continue; + } else { + const hash = CryptoJS.MD5(secret).toString(); + if ( + !/^[2-7a-z]+=*$/i.test(secret) && + /^[0-9a-f]+$/i.test(secret) && + type === 'totp' + ) { + type = 'hex'; + } else if ( + !/^[2-7a-z]+=*$/i.test(secret) && + /^[0-9a-f]+$/i.test(secret) && + type === 'hotp' + ) { + type = 'hhex'; + } + + exportData[hash] = { + account, + hash, + issuer, + secret, + type, + encrypted: false, + index: 0, + counter: 0, + }; + if (period) { + exportData[hash].period = period; + } + } + } + } + return exportData; +} diff --git a/src/models/backup.ts b/src/models/backup.ts index 2ea681476..e382e9c95 100644 --- a/src/models/backup.ts +++ b/src/models/backup.ts @@ -1,6 +1,6 @@ -import {getCredentials} from './credentials'; -import {Encryption} from './encryption'; -import {EntryStorage} from './storage'; +import { getCredentials } from './credentials'; +import { Encryption } from './encryption'; +import { EntryStorage } from './storage'; export class Dropbox { private async getToken() { @@ -13,160 +13,183 @@ export class Dropbox { localStorage.dropboxEncrypted = 'true'; } const exportData = await EntryStorage.getExport( - encryption, (localStorage.dropboxEncrypted === 'true')); + encryption, + localStorage.dropboxEncrypted === 'true' + ); const backup = JSON.stringify(exportData, null, 2); const url = 'https://content.dropboxapi.com/2/files/upload'; const token = await this.getToken(); return new Promise( - (resolve: (value: boolean) => void, - reject: (reason: Error) => void) => { - if (!token) { - resolve(false); - } - try { - const xhr = new XMLHttpRequest(); - const now = - (new Date()).toISOString().slice(0, 10).replace(/-/g, ''); - const apiArg = { - path: `/${now}.json`, - mode: 'add', - autorename: true - }; - xhr.open('POST', url); - xhr.setRequestHeader('Authorization', 'Bearer ' + token); - xhr.setRequestHeader('Content-type', 'application/octet-stream'); - xhr.setRequestHeader('Dropbox-API-Arg', JSON.stringify(apiArg)); - xhr.onreadystatechange = () => { - if (xhr.readyState === 4) { - if (xhr.status === 401) { - localStorage.removeItem('dropboxToken'); - localStorage.dropboxRevoked = true; + (resolve: (value: boolean) => void, reject: (reason: Error) => void) => { + if (!token) { + resolve(false); + } + try { + const xhr = new XMLHttpRequest(); + const now = new Date() + .toISOString() + .slice(0, 10) + .replace(/-/g, ''); + const apiArg = { + path: `/${now}.json`, + mode: 'add', + autorename: true, + }; + xhr.open('POST', url); + xhr.setRequestHeader('Authorization', 'Bearer ' + token); + xhr.setRequestHeader('Content-type', 'application/octet-stream'); + xhr.setRequestHeader('Dropbox-API-Arg', JSON.stringify(apiArg)); + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + if (xhr.status === 401) { + localStorage.removeItem('dropboxToken'); + localStorage.dropboxRevoked = true; + resolve(false); + } + try { + const res = JSON.parse(xhr.responseText); + if (res.name) { + resolve(true); + } else { resolve(false); } - try { - const res = JSON.parse(xhr.responseText); - if (res.name) { - resolve(true); - } else { - resolve(false); - } - } catch (error) { - reject(error); - } + } catch (error) { + reject(error); } - return; - }; - xhr.send(backup); - } catch (error) { - return reject(error); - } - }); + } + return; + }; + xhr.send(backup); + } catch (error) { + return reject(error); + } + } + ); } } export class Drive { private async getToken() { - if (!localStorage.driveToken || - await new Promise( - (resolve: (value: boolean) => void, - reject: (reason: Error) => void) => { - const xhr = new XMLHttpRequest(); - xhr.open('GET', 'https://www.googleapis.com/drive/v3/files'); - xhr.setRequestHeader( - 'Authorization', 'Bearer ' + localStorage.driveToken); - xhr.onreadystatechange = async () => { - if (xhr.readyState === 4) { - try { - const res = JSON.parse(xhr.responseText); - if (res.error) { - if (res.error.code === 401) { - if (navigator.userAgent.indexOf('Chrome') !== -1 && - navigator.userAgent.indexOf('OPR') === -1) { - // Clear invalid token from - // chrome://identity-internals/ - await chrome.identity.removeCachedAuthToken( - {'token': localStorage.driveToken}); - } - localStorage.driveToken = ''; - resolve(true); - } - } else { - resolve(false); + if ( + !localStorage.driveToken || + (await new Promise( + ( + resolve: (value: boolean) => void, + reject: (reason: Error) => void + ) => { + const xhr = new XMLHttpRequest(); + xhr.open('GET', 'https://www.googleapis.com/drive/v3/files'); + xhr.setRequestHeader( + 'Authorization', + 'Bearer ' + localStorage.driveToken + ); + xhr.onreadystatechange = async () => { + if (xhr.readyState === 4) { + try { + const res = JSON.parse(xhr.responseText); + if (res.error) { + if (res.error.code === 401) { + if ( + navigator.userAgent.indexOf('Chrome') !== -1 && + navigator.userAgent.indexOf('OPR') === -1 + ) { + // Clear invalid token from + // chrome://identity-internals/ + await chrome.identity.removeCachedAuthToken({ + token: localStorage.driveToken, + }); } - } catch (error) { - console.error(error); - reject(error); + localStorage.driveToken = ''; + resolve(true); } + } else { + resolve(false); } - return; - }; - xhr.send(); - })) { + } catch (error) { + console.error(error); + reject(error); + } + } + return; + }; + xhr.send(); + } + )) + ) { await this.refreshToken(); } return localStorage.driveToken; } async refreshToken() { - if (navigator.userAgent.indexOf('Chrome') !== -1 && - navigator.userAgent.indexOf('OPR') === -1) { + if ( + navigator.userAgent.indexOf('Chrome') !== -1 && + navigator.userAgent.indexOf('OPR') === -1 + ) { return new Promise((resolve: (value: boolean) => void) => { return chrome.identity.getAuthToken( - { - 'interactive': false, - 'scopes': ['https://www.googleapis.com/auth/drive.file'] - }, - (token) => { - localStorage.driveToken = token; - if (!Boolean(token)) { - localStorage.driveRevoked = true; - } - resolve(Boolean(token)); - }); + { + interactive: false, + scopes: ['https://www.googleapis.com/auth/drive.file'], + }, + token => { + localStorage.driveToken = token; + if (!Boolean(token)) { + localStorage.driveRevoked = true; + } + resolve(Boolean(token)); + } + ); }); } else { return new Promise( - (resolve: (value: boolean) => void, - reject: (reason: Error) => void) => { - const xhr = new XMLHttpRequest(); - xhr.open( - 'POST', - 'https://www.googleapis.com/oauth2/v4/token?client_id=' + - getCredentials().drive.client_id + - '&client_secret=' + getCredentials().drive.client_secret + - '&refresh_token=' + localStorage.driveRefreshToken + - '&grant_type=refresh_token'); - xhr.setRequestHeader('Accept', 'application/json'); - xhr.onreadystatechange = () => { - if (xhr.readyState === 4) { - if (xhr.status === 401) { - localStorage.removeItem('driveRefreshToken'); - localStorage.driveRevoked = true; - resolve(false); - } - try { - const res = JSON.parse(xhr.responseText); - if (res.error) { - if (res.error === 'invalid_grant') { - localStorage.removeItem('driveRefreshToken'); - localStorage.driveRevoked = true; - } - console.error(res.error_description); - resolve(false); - } else { - localStorage.driveToken = res.access_token; - resolve(true); + ( + resolve: (value: boolean) => void, + reject: (reason: Error) => void + ) => { + const xhr = new XMLHttpRequest(); + xhr.open( + 'POST', + 'https://www.googleapis.com/oauth2/v4/token?client_id=' + + getCredentials().drive.client_id + + '&client_secret=' + + getCredentials().drive.client_secret + + '&refresh_token=' + + localStorage.driveRefreshToken + + '&grant_type=refresh_token' + ); + xhr.setRequestHeader('Accept', 'application/json'); + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + if (xhr.status === 401) { + localStorage.removeItem('driveRefreshToken'); + localStorage.driveRevoked = true; + resolve(false); + } + try { + const res = JSON.parse(xhr.responseText); + if (res.error) { + if (res.error === 'invalid_grant') { + localStorage.removeItem('driveRefreshToken'); + localStorage.driveRevoked = true; } - } catch (error) { - console.error(error); - reject(error); + console.error(res.error_description); + resolve(false); + } else { + localStorage.driveToken = res.access_token; + resolve(true); } + } catch (error) { + console.error(error); + reject(error); } - return; - }; - xhr.send(); - }); + } + return; + }; + xhr.send(); + } + ); } } @@ -179,84 +202,94 @@ export class Drive { } if (localStorage.driveFolder) { await new Promise( - (resolve: (value: boolean) => void, - reject: (reason: Error) => void) => { - const xhr = new XMLHttpRequest(); - xhr.open( - 'GET', - 'https://www.googleapis.com/drive/v3/files/' + - localStorage.driveFolder + '?fields=trashed'); - xhr.setRequestHeader('Authorization', 'Bearer ' + token); - xhr.setRequestHeader('Accept', 'application/json'); - xhr.onreadystatechange = () => { - if (xhr.readyState === 4) { - if (xhr.status === 401) { - localStorage.removeItem('driveToken'); - resolve(false); - } - try { - const res = JSON.parse(xhr.responseText); - if (res.error) { - if (res.error.code === 404) { - localStorage.driveFolder = ''; - resolve(true); - } - } else if (res.trashed) { + ( + resolve: (value: boolean) => void, + reject: (reason: Error) => void + ) => { + const xhr = new XMLHttpRequest(); + xhr.open( + 'GET', + 'https://www.googleapis.com/drive/v3/files/' + + localStorage.driveFolder + + '?fields=trashed' + ); + xhr.setRequestHeader('Authorization', 'Bearer ' + token); + xhr.setRequestHeader('Accept', 'application/json'); + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + if (xhr.status === 401) { + localStorage.removeItem('driveToken'); + resolve(false); + } + try { + const res = JSON.parse(xhr.responseText); + if (res.error) { + if (res.error.code === 404) { localStorage.driveFolder = ''; resolve(true); - } else if (res.error) { - console.error(res.error.message); - resolve(false); - } else { - resolve(true); } - } catch (error) { - console.error(error); - reject(error); + } else if (res.trashed) { + localStorage.driveFolder = ''; + resolve(true); + } else if (res.error) { + console.error(res.error.message); + resolve(false); + } else { + resolve(true); } + } catch (error) { + console.error(error); + reject(error); } - return; - }; - xhr.send(); - }); + } + return; + }; + xhr.send(); + } + ); } if (!localStorage.driveFolder) { await new Promise( - (resolve: (value: boolean) => void, - reject: (reason: Error) => void) => { - // create folder - const xhr = new XMLHttpRequest(); - xhr.open('POST', 'https://www.googleapis.com/drive/v3/files/'); - xhr.setRequestHeader('Authorization', 'Bearer ' + token); - xhr.setRequestHeader('Accept', 'application/json'); - xhr.setRequestHeader('Content-Type', 'application/json'); - xhr.onreadystatechange = () => { - if (xhr.readyState === 4) { - if (xhr.status === 401) { - localStorage.removeItem('driveToken'); + ( + resolve: (value: boolean) => void, + reject: (reason: Error) => void + ) => { + // create folder + const xhr = new XMLHttpRequest(); + xhr.open('POST', 'https://www.googleapis.com/drive/v3/files/'); + xhr.setRequestHeader('Authorization', 'Bearer ' + token); + xhr.setRequestHeader('Accept', 'application/json'); + xhr.setRequestHeader('Content-Type', 'application/json'); + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + if (xhr.status === 401) { + localStorage.removeItem('driveToken'); + resolve(false); + } + try { + const res = JSON.parse(xhr.responseText); + if (!res.error) { + localStorage.driveFolder = res.id; + resolve(true); + } else { + console.error(res.error.message); resolve(false); } - try { - const res = JSON.parse(xhr.responseText); - if (!res.error) { - localStorage.driveFolder = res.id; - resolve(true); - } else { - console.error(res.error.message); - resolve(false); - } - } catch (error) { - console.error(error); - reject(error); - } + } catch (error) { + console.error(error); + reject(error); } - return; - }; - xhr.send(JSON.stringify({ - 'name': 'Authenticator Backups', - 'mimeType': 'application/vnd.google-apps.folder' - })); - }); + } + return; + }; + xhr.send( + JSON.stringify({ + name: 'Authenticator Backups', + mimeType: 'application/vnd.google-apps.folder', + }) + ); + } + ); } return localStorage.driveFolder; } @@ -266,7 +299,9 @@ export class Drive { localStorage.driveEncrypted = 'true'; } const exportData = await EntryStorage.getExport( - encryption, (localStorage.driveEncrypted === 'true')); + encryption, + localStorage.driveEncrypted === 'true' + ); const backup = JSON.stringify(exportData, null, 2); const token = await this.getToken(); @@ -277,57 +312,69 @@ export class Drive { } const folderId = await this.getFolder(); return new Promise( - (resolve: (value: boolean) => void, - reject: (reason: Error) => void) => { - if (!token || !folderId) { - resolve(false); - } - try { - const xhr = new XMLHttpRequest(); - const now = - (new Date()).toISOString().slice(0, 10).replace(/-/g, ''); - xhr.open( - 'POST', - 'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart'); - xhr.setRequestHeader('Authorization', 'Bearer ' + token); - xhr.setRequestHeader( - 'Content-type', 'multipart/related; boundary=segment_marker'); - xhr.onreadystatechange = () => { - if (xhr.readyState === 4) { - if (xhr.status === 401) { - localStorage.removeItem('driveToken'); + (resolve: (value: boolean) => void, reject: (reason: Error) => void) => { + if (!token || !folderId) { + resolve(false); + } + try { + const xhr = new XMLHttpRequest(); + const now = new Date() + .toISOString() + .slice(0, 10) + .replace(/-/g, ''); + xhr.open( + 'POST', + 'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart' + ); + xhr.setRequestHeader('Authorization', 'Bearer ' + token); + xhr.setRequestHeader( + 'Content-type', + 'multipart/related; boundary=segment_marker' + ); + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + if (xhr.status === 401) { + localStorage.removeItem('driveToken'); + resolve(false); + } + try { + const res = JSON.parse(xhr.responseText); + if (!res.error) { + resolve(true); + } else { + console.error(res.error.message); resolve(false); } - try { - const res = JSON.parse(xhr.responseText); - if (!res.error) { - resolve(true); - } else { - console.error(res.error.message); - resolve(false); - } - } catch (error) { - reject(error); - } + } catch (error) { + reject(error); } - return; - }; - const requestDataPrototype = [ - '--segment_marker', - 'Content-Type: application/json; charset=UTF-8', '', - JSON.stringify( - {name: `${now}.json`, parents: [localStorage.driveFolder]}), - '', '--segment_marker', 'Content-Type: application/octet-stream', - '', backup, '--segment_marker--' - ]; - let requestData = ''; - requestDataPrototype.forEach((line) => { - requestData = requestData + line + '\n'; - }); - xhr.send(requestData); - } catch (error) { - return reject(error); - } - }); + } + return; + }; + const requestDataPrototype = [ + '--segment_marker', + 'Content-Type: application/json; charset=UTF-8', + '', + JSON.stringify({ + name: `${now}.json`, + parents: [localStorage.driveFolder], + }), + '', + '--segment_marker', + 'Content-Type: application/octet-stream', + '', + backup, + '--segment_marker--', + ]; + let requestData = ''; + requestDataPrototype.forEach(line => { + requestData = requestData + line + '\n'; + }); + xhr.send(requestData); + } catch (error) { + return reject(error); + } + } + ); } } diff --git a/src/models/credentials.ts b/src/models/credentials.ts index 3ba39d3ca..dab30c1b7 100644 --- a/src/models/credentials.ts +++ b/src/models/credentials.ts @@ -1,11 +1,11 @@ export function getCredentials() { return { drive: { - client_id: '', // Google client ID - client_secret: '' // Google client secret + client_id: '', // Google client ID + client_secret: '', // Google client secret }, dropbox: { - client_id: '' // Dropbox client ID - } + client_id: '', // Dropbox client ID + }, }; } diff --git a/src/models/encryption.ts b/src/models/encryption.ts index f955f57ed..9b36485e9 100644 --- a/src/models/encryption.ts +++ b/src/models/encryption.ts @@ -1,52 +1,80 @@ import * as CryptoJS from 'crypto-js'; -export class Encryption { +export class Encryption implements IEncryption { private password: string; constructor(password: string) { this.password = password; } - getEncryptedSecret(secret: string): string { - if (!this.password) { - return secret; + getEncryptedSecret(entry: IOTPEntry): string { + if (entry.encSecret) { + return entry.encSecret; + } else if (entry.secret) { + if (!this.password) { + // Not encrypted, give unencrypted. + return entry.secret; + } else { + // Not encrypted and password is set, encrypt. + return CryptoJS.AES.encrypt(entry.secret, this.password).toString(); + } + } else { + console.error(entry); + throw new Error('Invalid entry'); } - return CryptoJS.AES.encrypt(secret, this.password).toString(); } - getDecryptedSecret(secret: string, hash: string): string { + getEncryptedString(data: string): string { if (!this.password) { - return secret; + return data; + } else { + return CryptoJS.AES.encrypt(data, this.password).toString(); } + } + getDecryptedSecret(entry: { secret: string; hash: string }): string | null { try { - let decryptedSecret = CryptoJS.AES.decrypt(secret, this.password) - .toString(CryptoJS.enc.Utf8); + if (entry.hash === CryptoJS.MD5(entry.secret).toString()) { + return entry.secret; + } + + let decryptedSecret = CryptoJS.AES.decrypt( + entry.secret, + this.password + ).toString(CryptoJS.enc.Utf8); if (!decryptedSecret) { - return 'Encrypted'; + return null; } if (decryptedSecret.length < 8) { - return 'Encrypted'; + return null; } - if (hash === CryptoJS.MD5(decryptedSecret).toString()) { + if (entry.hash === CryptoJS.MD5(decryptedSecret).toString()) { return decryptedSecret; } decryptedSecret = decryptedSecret.replace(/ /g, ''); - if (!/^[a-z2-7]+=*$/i.test(decryptedSecret) && - !/^[0-9a-f]+$/i.test(decryptedSecret) && - !/^blz\-/.test(decryptedSecret) && !/^bliz\-/.test(decryptedSecret) && - !/^stm\-/.test(decryptedSecret)) { - return 'Encrypted'; + if ( + !/^[a-z2-7]+=*$/i.test(decryptedSecret) && + !/^[0-9a-f]+$/i.test(decryptedSecret) && + !/^blz\-/.test(decryptedSecret) && + !/^bliz\-/.test(decryptedSecret) && + !/^stm\-/.test(decryptedSecret) + ) { + return null; } - return decryptedSecret; + console.warn( + `Account ${ + entry.hash + } may have secret ${decryptedSecret}, but hash did not match.` + ); + return null; } catch (error) { - return 'Encrypted'; + return null; } } diff --git a/src/models/interface.ts b/src/models/interface.ts deleted file mode 100644 index b12ad4035..000000000 --- a/src/models/interface.ts +++ /dev/null @@ -1,63 +0,0 @@ -import {Encryption} from './encryption'; - -export enum OTPType { - totp = 1, - hotp, - battle, - steam, - hex, - hhex -} - -export interface OTP { - type: OTPType; - index: number; - issuer: string; - secret: string; - account: string; - hash: string; - counter: number; - code: string; - period: number; - create(encryption: Encryption): Promise; - update(encryption: Encryption): Promise; - next(encryption: Encryption): Promise; - delete(): Promise; - generate(): void; -} - -export interface OTPStorage { - account: string; - encrypted: boolean; - hash: string; - index: number; - issuer: string; - secret: string; - type: string; - counter: number; - period?: number; -} - -/* tslint:disable-next-line:interface-name */ -export interface I18nMessage { - [key: string]: {message: string, description: string}; -} - -export interface UIConfig { - el?: string; - data?: { - /* tslint:disable-next-line:no-any */ - [name: string]: any - }; - methods?: { - /* tslint:disable-next-line:no-any */ - [name: string]: (...arg: any[]) => any - }; - /* tslint:disable-next-line:no-any */ - mounted?: (...arg: any[]) => any; - /* tslint:disable-next-line:no-any */ - beforeCreate?: (...arg: any[]) => any; - /* tslint:disable-next-line:no-any */ - render?: (h: any) => any; - /* tslint:disable-next-line:no-any */ -} diff --git a/src/models/key-utilities.ts b/src/models/key-utilities.ts index bd786b3c7..9ec73e0d9 100644 --- a/src/models/key-utilities.ts +++ b/src/models/key-utilities.ts @@ -1,6 +1,5 @@ import * as jsSHA from 'jssha'; - -import {OTPType} from './interface'; +import { OTPType } from './otp'; // Originally based on the JavaScript implementation as provided by Russell // Sayers on his Tin Isles blog: @@ -93,7 +92,11 @@ export class KeyUtilities { } static generate( - type: OTPType, secret: string, counter: number, period: number) { + type: OTPType, + secret: string, + counter: number, + period: number + ) { secret = secret.replace(/\s/g, ''); let len = 6; let b26 = false; @@ -154,8 +157,8 @@ export class KeyUtilities { } let otp = - (this.hex2dec(hmac.substr(offset * 2, 8)) & this.hex2dec('7fffffff')) + - ''; + (this.hex2dec(hmac.substr(offset * 2, 8)) & this.hex2dec('7fffffff')) + + ''; if (b26) { return this.base26(Number(otp)); @@ -164,6 +167,6 @@ export class KeyUtilities { if (otp.length < len) { otp = new Array(len - otp.length + 1).join('0') + otp; } - return (otp).substr(otp.length - len, len).toString(); + return otp.substr(otp.length - len, len).toString(); } } diff --git a/src/models/otp.ts b/src/models/otp.ts index fb4b00011..9b3b6765f 100644 --- a/src/models/otp.ts +++ b/src/models/otp.ts @@ -1,35 +1,54 @@ -import * as CryptoJS from 'crypto-js'; +import { Encryption } from './encryption'; +import { KeyUtilities } from './key-utilities'; +import { EntryStorage } from './storage'; -import {Encryption} from './encryption'; -import {OTP, OTPType} from './interface'; -import {KeyUtilities} from './key-utilities'; -import {EntryStorage} from './storage'; +export enum OTPType { + totp = 1, + hotp, + battle, + steam, + hex, + hhex, +} -export class OTPEntry implements OTP { +export class OTPEntry implements IOTPEntry { type: OTPType; index: number; issuer: string; - secret: string; + secret: string | null; + encSecret: string | null; account: string; hash: string; counter: number; period: number; code = '••••••'; - constructor( - type: OTPType, issuer: string, secret: string, account: string, - index: number, counter: number, period?: number, hash?: string) { - this.type = type; - this.index = index; - this.issuer = issuer; - this.secret = secret; - this.account = account; - this.hash = hash && /^[0-9a-f]{32}$/.test(hash) ? - hash : - CryptoJS.MD5(secret).toString(); - this.counter = counter; - if (this.type === OTPType.totp && period) { - this.period = period; + constructor(entry: { + account: string; + encrypted: boolean; + hash: string; + index: number; + issuer: string; + secret: string; + type: OTPType; + counter: number; + period?: number; + }) { + this.type = entry.type; + this.index = entry.index; + this.issuer = entry.issuer; + if (entry.encrypted) { + this.encSecret = entry.secret; + this.secret = null; + } else { + this.secret = entry.secret; + this.encSecret = null; + } + this.account = entry.account; + this.hash = entry.hash; + this.counter = entry.counter; + if (this.type === OTPType.totp && entry.period) { + this.period = entry.period; } else { this.period = 30; } @@ -48,6 +67,17 @@ export class OTPEntry implements OTP { return; } + applyEncryption(encryption: Encryption) { + const secret = this.encSecret ? this.encSecret : null; + if (secret) { + this.secret = encryption.getDecryptedSecret({ hash: this.hash, secret }); + if (this.type !== OTPType.hotp && this.type !== OTPType.hhex) { + this.generate(); + } + } + return; + } + async delete() { await EntryStorage.delete(this); return; @@ -58,18 +88,26 @@ export class OTPEntry implements OTP { return; } this.generate(); - this.counter++; - await this.update(encryption); + if (this.secret !== null) { + this.counter++; + await this.update(encryption); + } return; } generate() { - if (this.secret === 'Encrypted') { + if (!this.secret && !this.encSecret) { + this.code = 'Invalid'; + } else if (!this.secret) { this.code = 'Encrypted'; } else { try { this.code = KeyUtilities.generate( - this.type, this.secret, this.counter, this.period); + this.type, + this.secret, + this.counter, + this.period + ); } catch (error) { this.code = 'Invalid'; if (parent) { diff --git a/src/models/storage.ts b/src/models/storage.ts index f74a6c877..7ccbb3bae 100644 --- a/src/models/storage.ts +++ b/src/models/storage.ts @@ -1,14 +1,13 @@ import * as CryptoJS from 'crypto-js'; -import {Encryption} from './encryption'; -import {OTPStorage, OTPType} from './interface'; -import {OTPEntry} from './otp'; +import { Encryption } from './encryption'; +import { OTPEntry, OTPType } from './otp'; export class BrowserStorage { private static async getStorageLocation() { const managedLocation = await ManagedStorage.get('storageArea'); if (managedLocation === 'sync' || managedLocation === 'local') { - return new Promise((resolve) => { + return new Promise(resolve => { if (localStorage.storageLocation !== managedLocation) { localStorage.storageLocation = managedLocation; } @@ -16,15 +15,16 @@ export class BrowserStorage { return; }); } else if ( - localStorage.storageLocation !== 'sync' && - localStorage.storageLocation !== 'local') { + localStorage.storageLocation !== 'sync' && + localStorage.storageLocation !== 'local' + ) { return new Promise((resolve, reject) => { let amountSync: number; let amountLocal: number; - chrome.storage.local.get((local) => { + chrome.storage.local.get(local => { amountLocal = Object.keys(local).length; try { - chrome.storage.sync.get((sync) => { + chrome.storage.sync.get(sync => { amountSync = Object.keys(sync).length; // If storage location can't be found try to auto-detect storage // location @@ -34,11 +34,7 @@ export class BrowserStorage { localStorage.storageLocation = 'sync'; } else { // Use default - if (navigator.userAgent.indexOf('Edge') !== -1) { - localStorage.storageLocation = 'local'; - } else { - localStorage.storageLocation = 'sync'; - } + localStorage.storageLocation = 'sync'; } resolve(localStorage.storageLocation); return; @@ -50,7 +46,7 @@ export class BrowserStorage { }); }); } else { - return new Promise((resolve) => { + return new Promise(resolve => { resolve(localStorage.storageLocation); return; }); @@ -58,7 +54,7 @@ export class BrowserStorage { } /* tslint:disable-next-line:no-any */ - static async get(callback: (items: {[key: string]: any;}) => void) { + static async get(callback: (items: { [key: string]: any }) => void) { const storageLocation = await this.getStorageLocation(); if (storageLocation === 'local') { chrome.storage.local.get(callback); @@ -68,7 +64,7 @@ export class BrowserStorage { return; } - static async set(data: object, callback?: (() => void)|undefined) { + static async set(data: object, callback?: (() => void) | undefined) { const storageLocation = await this.getStorageLocation(); if (storageLocation === 'local') { chrome.storage.local.set(data, callback); @@ -79,7 +75,9 @@ export class BrowserStorage { } static async remove( - data: string|string[], callback?: (() => void)|undefined) { + data: string | string[], + callback?: (() => void) | undefined + ) { const storageLocation = await this.getStorageLocation(); if (storageLocation === 'local') { chrome.storage.local.remove(data, callback); @@ -92,24 +90,28 @@ export class BrowserStorage { export class EntryStorage { private static getOTPStorageFromEntry( - encryption: Encryption, entry: OTPEntry): OTPStorage { + encryption: Encryption, + entry: OTPEntry + ): OTPStorage { const storageItem: OTPStorage = { account: entry.account, + encrypted: encryption.getEncryptionStatus(), hash: entry.hash, index: entry.index, issuer: entry.issuer, type: OTPType[entry.type], - counter: entry.counter, - secret: encryption.getEncryptedSecret(entry.secret), - encrypted: encryption.getEncryptionStatus() + counter: entry.counter, // TODO: Make this optional for non HOTP accounts + secret: encryption.getEncryptedSecret(entry), }; + if (entry.period && entry.period !== 30) { storageItem.period = entry.period; } + return storageItem; } - private static ensureUniqueIndex(_data: {[hash: string]: OTPStorage}) { + private static ensureUniqueIndex(_data: { [hash: string]: OTPStorage }) { const tempEntryArray: OTPStorage[] = []; for (const hash of Object.keys(_data)) { @@ -123,7 +125,7 @@ export class EntryStorage { return a.index - b.index; }); - const newData: {[hash: string]: OTPStorage} = {}; + const newData: { [hash: string]: OTPStorage } = {}; for (let i = 0; i < tempEntryArray.length; i++) { tempEntryArray[i].index = i; newData[tempEntryArray[i].hash] = tempEntryArray[i]; @@ -142,7 +144,9 @@ export class EntryStorage { } private static isValidEntry( - _data: {[hash: string]: OTPStorage}, hash: string) { + _data: { [hash: string]: OTPStorage }, + hash: string + ) { if (typeof _data[hash] !== 'object') { console.log('Key "' + hash + '" is not an object'); return false; @@ -157,383 +161,357 @@ export class EntryStorage { static hasEncryptedEntry() { return new Promise( - (resolve: (value: boolean) => void, - reject: (reason: Error) => void) => { - BrowserStorage.get((_data: {[hash: string]: OTPStorage}) => { - for (const hash of Object.keys(_data)) { - if (!this.isValidEntry(_data, hash)) { - continue; - } - if (_data[hash].encrypted) { - return resolve(true); - } + (resolve: (value: boolean) => void, reject: (reason: Error) => void) => { + BrowserStorage.get((_data: { [hash: string]: OTPStorage }) => { + for (const hash of Object.keys(_data)) { + if (!this.isValidEntry(_data, hash)) { + continue; } - return resolve(false); - }); - return; + if (_data[hash].encrypted) { + return resolve(true); + } + } + return resolve(false); }); + return; + } + ); } static getExport(encryption: Encryption, encrypted?: boolean) { return new Promise( - (resolve: (value: {[hash: string]: OTPStorage}) => void, - reject: (reason: Error) => void) => { - try { - BrowserStorage.get((_data: {[hash: string]: OTPStorage}) => { - for (const hash of Object.keys(_data)) { - if (!this.isValidEntry(_data, hash)) { - delete _data[hash]; - continue; - } - if (!encrypted) { - // decrypt the data to export - if (_data[hash].encrypted) { - const decryptedSecret = - encryption.getDecryptedSecret(_data[hash].secret, hash); - if (decryptedSecret !== _data[hash].secret && - decryptedSecret !== 'Encrypted') { - _data[hash].secret = decryptedSecret; - _data[hash].encrypted = false; - } - } - // we need correct hash - if (hash !== _data[hash].hash) { - _data[_data[hash].hash] = _data[hash]; - delete _data[hash]; + ( + resolve: (value: { [hash: string]: OTPStorage }) => void, + reject: (reason: Error) => void + ) => { + try { + BrowserStorage.get((_data: { [hash: string]: OTPStorage }) => { + for (const hash of Object.keys(_data)) { + if (!this.isValidEntry(_data, hash)) { + delete _data[hash]; + continue; + } + if (!encrypted) { + // decrypt the data to export + if (_data[hash].encrypted) { + const decryptedSecret = encryption.getDecryptedSecret( + _data[hash] + ); + if ( + decryptedSecret !== _data[hash].secret && + decryptedSecret !== null + ) { + _data[hash].secret = decryptedSecret; + _data[hash].encrypted = false; } } + // we need correct hash + if (hash !== _data[hash].hash) { + _data[_data[hash].hash] = _data[hash]; + delete _data[hash]; + } } - return resolve(_data); - }); - return; - } catch (error) { - return reject(error); - } - }); + } + return resolve(_data); + }); + return; + } catch (error) { + return reject(error); + } + } + ); } - static import(encryption: Encryption, data: {[hash: string]: OTPStorage}) { + static import(encryption: Encryption, data: { [hash: string]: OTPStorage }) { return new Promise( - (resolve: () => void, reject: (reason: Error) => void) => { - try { - BrowserStorage.get((_data: {[hash: string]: OTPStorage}) => { - for (const hash of Object.keys(data)) { - // never trust data import from user - // we do not support encrypted data import any longer - if (!data[hash].secret || data[hash].encrypted) { - // we need give a failed warning - continue; - } + (resolve: () => void, reject: (reason: Error) => void) => { + try { + BrowserStorage.get((_data: { [hash: string]: OTPStorage }) => { + for (const hash of Object.keys(data)) { + // never trust data import from user + // we do not support encrypted data import any longer + if (!data[hash].secret || data[hash].encrypted) { + // we need give a failed warning + continue; + } - data[hash].hash = data[hash].hash || hash; - data[hash].account = data[hash].account || ''; - data[hash].encrypted = encryption.getEncryptionStatus(); - data[hash].index = data[hash].index || 0; - data[hash].issuer = data[hash].issuer || ''; - data[hash].type = data[hash].type || OTPType[OTPType.totp]; - data[hash].counter = data[hash].counter || 0; - const period = data[hash].period; - if (data[hash].type !== OTPType[OTPType.totp] || - period && (isNaN(period) || period <= 0)) { - delete data[hash].period; - } + data[hash].hash = data[hash].hash || hash; + data[hash].account = data[hash].account || ''; + data[hash].encrypted = encryption.getEncryptionStatus(); + data[hash].index = data[hash].index || 0; + data[hash].issuer = data[hash].issuer || ''; + data[hash].type = data[hash].type || OTPType[OTPType.totp]; + data[hash].counter = data[hash].counter || 0; + const period = data[hash].period; + if ( + data[hash].type !== OTPType[OTPType.totp] || + (period && (isNaN(period) || period <= 0)) + ) { + delete data[hash].period; + } - if (/^(blz\-|bliz\-)/.test(data[hash].secret)) { - const secretMatches = - data[hash].secret.match(/^(blz\-|bliz\-)(.*)/); - if (secretMatches && secretMatches.length >= 3) { - data[hash].secret = secretMatches[2]; - data[hash].type = OTPType[OTPType.battle]; - } + if (/^(blz\-|bliz\-)/.test(data[hash].secret)) { + const secretMatches = data[hash].secret.match( + /^(blz\-|bliz\-)(.*)/ + ); + if (secretMatches && secretMatches.length >= 3) { + data[hash].secret = secretMatches[2]; + data[hash].type = OTPType[OTPType.battle]; } + } - if (/^stm\-/.test(data[hash].secret)) { - const secretMatches = data[hash].secret.match(/^stm\-(.*)/); - if (secretMatches && secretMatches.length >= 2) { - data[hash].secret = secretMatches[1]; - data[hash].type = OTPType[OTPType.steam]; - } + if (/^stm\-/.test(data[hash].secret)) { + const secretMatches = data[hash].secret.match(/^stm\-(.*)/); + if (secretMatches && secretMatches.length >= 2) { + data[hash].secret = secretMatches[1]; + data[hash].type = OTPType[OTPType.steam]; } + } - if (!/^[a-z2-7]+=*$/i.test(data[hash].secret) && - /^[0-9a-f]+$/i.test(data[hash].secret) && - data[hash].type === OTPType[OTPType.totp]) { - data[hash].type = OTPType[OTPType.hex]; - } + if ( + !/^[a-z2-7]+=*$/i.test(data[hash].secret) && + /^[0-9a-f]+$/i.test(data[hash].secret) && + data[hash].type === OTPType[OTPType.totp] + ) { + data[hash].type = OTPType[OTPType.hex]; + } - if (!/^[a-z2-7]+=*$/i.test(data[hash].secret) && - /^[0-9a-f]+$/i.test(data[hash].secret) && - data[hash].type === OTPType[OTPType.hotp]) { - data[hash].type = OTPType[OTPType.hhex]; - } + if ( + !/^[a-z2-7]+=*$/i.test(data[hash].secret) && + /^[0-9a-f]+$/i.test(data[hash].secret) && + data[hash].type === OTPType[OTPType.hotp] + ) { + data[hash].type = OTPType[OTPType.hhex]; + } - const _hash = CryptoJS.MD5(data[hash].secret).toString(); - // not a valid hash - if (!/^[0-9a-f]{32}$/.test(hash)) { - data[_hash] = data[hash]; - data[_hash].hash = _hash; - delete data[hash]; - } + const _hash = CryptoJS.MD5(data[hash].secret).toString(); + // not a valid hash + if (!/^[0-9a-f]{32}$/.test(hash)) { + data[_hash] = data[hash]; + data[_hash].hash = _hash; + delete data[hash]; + } - if (data[_hash]) { - data[_hash].secret = - encryption.getEncryptedSecret(data[_hash].secret); - _data[_hash] = data[_hash]; - } else { - data[hash].secret = - encryption.getEncryptedSecret(data[hash].secret); - _data[hash] = data[hash]; - } + if (data[_hash]) { + data[_hash].secret = encryption.getEncryptedString( + data[_hash].secret + ); + _data[_hash] = data[_hash]; + } else { + data[hash].secret = encryption.getEncryptedString( + data[hash].secret + ); + _data[hash] = data[hash]; } - _data = this.ensureUniqueIndex(_data); - BrowserStorage.set(_data, resolve); - }); - return; - } catch (error) { - return reject(error); - } - }); + } + _data = this.ensureUniqueIndex(_data); + BrowserStorage.set(_data, resolve); + }); + return; + } catch (error) { + return reject(error); + } + } + ); } static add(encryption: Encryption, entry: OTPEntry) { return new Promise( - (resolve: () => void, reject: (reason: Error) => void) => { - try { - BrowserStorage.get((_data: {[hash: string]: OTPStorage}) => { - if (_data.hasOwnProperty(entry.hash)) { - throw new Error('The specific entry has already existed.'); - } - const storageItem = - this.getOTPStorageFromEntry(encryption, entry); - _data[entry.hash] = storageItem; - _data = this.ensureUniqueIndex(_data); - BrowserStorage.set(_data, resolve); - }); - return; - } catch (error) { - return reject(error); - } - }); + (resolve: () => void, reject: (reason: Error) => void) => { + try { + BrowserStorage.get((_data: { [hash: string]: OTPStorage }) => { + if (_data.hasOwnProperty(entry.hash)) { + throw new Error('The specific entry has already existed.'); + } + const storageItem = this.getOTPStorageFromEntry(encryption, entry); + _data[entry.hash] = storageItem; + _data = this.ensureUniqueIndex(_data); + BrowserStorage.set(_data, resolve); + }); + return; + } catch (error) { + return reject(error); + } + } + ); } static update(encryption: Encryption, entry: OTPEntry) { return new Promise( - (resolve: () => void, reject: (reason: Error) => void) => { - try { - BrowserStorage.get((_data: {[hash: string]: OTPStorage}) => { - if (!_data.hasOwnProperty(entry.hash)) { - throw new Error('The specific entry is not existing.'); - } - const storageItem = - this.getOTPStorageFromEntry(encryption, entry); - _data[entry.hash] = storageItem; - _data = this.ensureUniqueIndex(_data); - BrowserStorage.set(_data, resolve); - }); - return; - } catch (error) { - return reject(error); - } - }); + (resolve: () => void, reject: (reason: Error) => void) => { + try { + BrowserStorage.get((_data: { [hash: string]: OTPStorage }) => { + if (!_data.hasOwnProperty(entry.hash)) { + throw new Error('Entry to change does not exist.'); + } + const storageItem = this.getOTPStorageFromEntry(encryption, entry); + _data[entry.hash] = storageItem; + _data = this.ensureUniqueIndex(_data); + BrowserStorage.set(_data, resolve); + }); + return; + } catch (error) { + return reject(error); + } + } + ); } static set(encryption: Encryption, entries: OTPEntry[]) { return new Promise( - (resolve: () => void, reject: (reason: Error) => void) => { - try { - BrowserStorage.get((_data: {[hash: string]: OTPStorage}) => { - entries.forEach(entry => { - const storageItem = - this.getOTPStorageFromEntry(encryption, entry); - _data[entry.hash] = storageItem; - }); - _data = this.ensureUniqueIndex(_data); - BrowserStorage.set(_data, resolve); + (resolve: () => void, reject: (reason: Error) => void) => { + try { + BrowserStorage.get((_data: { [hash: string]: OTPStorage }) => { + entries.forEach(entry => { + const storageItem = this.getOTPStorageFromEntry( + encryption, + entry + ); + _data[entry.hash] = storageItem; }); - return; - } catch (error) { - reject(error); - } - }); + _data = this.ensureUniqueIndex(_data); + BrowserStorage.set(_data, resolve); + }); + return; + } catch (error) { + reject(error); + } + } + ); } static get(encryption: Encryption) { return new Promise( - (resolve: (value: OTPEntry[]) => void, - reject: (reason: Error) => void) => { - try { - BrowserStorage.get(async (_data: {[hash: string]: OTPStorage}) => { - const data: OTPEntry[] = []; - for (const hash of Object.keys(_data)) { - if (!this.isValidEntry(_data, hash)) { - continue; - } - const entryData = _data[hash]; - let needMigrate = false; - - if (!entryData.hash) { - entryData.hash = hash; - needMigrate = true; - } - - if (!entryData.type) { - entryData.type = OTPType[OTPType.totp]; - needMigrate = true; - } - - let type: OTPType; - switch (entryData.type) { - case 'totp': - case 'hotp': - case 'battle': - case 'steam': - case 'hex': - case 'hhex': - type = OTPType[entryData.type]; - break; - default: - // we need correct the type here - // and save it - type = OTPType.totp; - entryData.type = OTPType[OTPType.totp]; - needMigrate = true; - } - - let period = 30; - if (entryData.type === OTPType[OTPType.totp] && - entryData.period && entryData.period > 0) { - period = entryData.period; - } - - entryData.secret = entryData.encrypted ? - encryption.getDecryptedSecret(entryData.secret, hash) : - entryData.secret; - - // we need migrate secret in old format here - if (/^(blz\-|bliz\-)/.test(entryData.secret)) { - const secretMatches = - entryData.secret.match(/^(blz\-|bliz\-)(.*)/); - if (secretMatches && secretMatches.length >= 3) { - entryData.secret = secretMatches[2]; - entryData.type = OTPType[OTPType.battle]; - entryData.hash = CryptoJS.MD5(entryData.secret).toString(); - await this.remove(hash); - needMigrate = true; - } - } + ( + resolve: (value: OTPEntry[]) => void, + reject: (reason: Error) => void + ) => { + try { + BrowserStorage.get(async (_data: { [hash: string]: OTPStorage }) => { + const data: OTPEntry[] = []; + for (const hash of Object.keys(_data)) { + if (!this.isValidEntry(_data, hash)) { + continue; + } + const entryData = _data[hash]; + let needMigrate = false; - if (/^stm\-/.test(entryData.secret)) { - const secretMatches = entryData.secret.match(/^stm\-(.*)/); - if (secretMatches && secretMatches.length >= 2) { - entryData.secret = secretMatches[1]; - entryData.type = OTPType[OTPType.steam]; - entryData.hash = CryptoJS.MD5(entryData.secret).toString(); - await this.remove(hash); - needMigrate = true; - } - } + if (!entryData.hash) { + entryData.hash = hash; + needMigrate = true; + } - if (!/^[a-z2-7]+=*$/i.test(entryData.secret) && - /^[0-9a-f]+$/i.test(entryData.secret) && - entryData.type === OTPType[OTPType.totp]) { - entryData.type = OTPType[OTPType.hex]; - needMigrate = true; - } + if (!entryData.type) { + entryData.type = OTPType[OTPType.totp]; + needMigrate = true; + } - if (!/^[a-z2-7]+=*$/i.test(entryData.secret) && - /^[0-9a-f]+$/i.test(entryData.secret) && - entryData.type === OTPType[OTPType.hotp]) { - entryData.type = OTPType[OTPType.hhex]; + let type: OTPType; + switch (entryData.type) { + case 'totp': + case 'hotp': + case 'battle': + case 'steam': + case 'hex': + case 'hhex': + type = OTPType[entryData.type]; + break; + default: + // we need correct the type here + // and save it + type = OTPType.totp; + entryData.type = OTPType[OTPType.totp]; needMigrate = true; - } - - const entry = new OTPEntry( - type, entryData.issuer, entryData.secret, entryData.account, - entryData.index, entryData.counter, period, entryData.hash); - data.push(entry); - - // we need correct the hash - - // Do not correct hash, wrong password - // may not only 'Encrypted', but also - // other wrong secret. We cannot know - // if the hash doesn't match the correct - // secret - - // Only correct invalid hash here - - if (entry.secret !== 'Encrypted' && - !/^[0-9a-f]{32}$/.test(hash)) { - const _hash = CryptoJS.MD5(entryData.secret).toString(); - if (hash !== _hash) { - await this.remove(hash); - entryData.hash = _hash; - needMigrate = true; - } - } + } - if (needMigrate) { - const _entry: {[hash: string]: OTPStorage} = {}; - _entry[entryData.hash] = entryData; - _entry[entryData.hash].encrypted = false; - this.import(encryption, _entry); - } + let period = 30; + if ( + entryData.type === OTPType[OTPType.totp] && + entryData.period && + entryData.period > 0 + ) { + period = entryData.period; } - data.sort((a, b) => { - return a.index - b.index; + const entry = new OTPEntry({ + account: entryData.account, + encrypted: entryData.encrypted, + hash: entryData.hash, + index: entryData.index, + issuer: entryData.issuer, + secret: entryData.secret, + type, + counter: entryData.counter, + period, }); + entry.applyEncryption(encryption); + data.push(entry); - for (let i = 0; i < data.length; i++) { - if (data[i].index !== i) { - const exportData = await this.getExport(encryption); - await this.import(encryption, exportData); - break; + if (entry.secret !== null && !/^[0-9a-f]{32}$/.test(hash)) { + const _hash = CryptoJS.MD5(entry.secret).toString(); + if (hash !== _hash) { + console.warn('Invalid hash:', entry); } } + } - return resolve(data); + data.sort((a, b) => { + return a.index - b.index; }); - return; - } catch (error) { - return reject(error); - } - }); + + for (let i = 0; i < data.length; i++) { + if (data[i].index !== i) { + const exportData = await this.getExport(encryption); + await this.import(encryption, exportData); + break; + } + } + + return resolve(data); + }); + return; + } catch (error) { + return reject(error); + } + } + ); } static remove(hash: string) { return new Promise( - (resolve: () => void, reject: (reason: Error) => void) => { - return BrowserStorage.remove(hash, resolve); - }); + (resolve: () => void, reject: (reason: Error) => void) => { + return BrowserStorage.remove(hash, resolve); + } + ); } static delete(entry: OTPEntry) { return new Promise( - (resolve: () => void, reject: (reason: Error) => void) => { - try { - BrowserStorage.get((_data: {[hash: string]: OTPStorage}) => { - if (_data.hasOwnProperty(entry.hash)) { - delete _data[entry.hash]; - } - _data = this.ensureUniqueIndex(_data); - BrowserStorage.remove(entry.hash, () => { - BrowserStorage.set(_data, resolve); - }); - return; + (resolve: () => void, reject: (reason: Error) => void) => { + try { + BrowserStorage.get((_data: { [hash: string]: OTPStorage }) => { + if (_data.hasOwnProperty(entry.hash)) { + delete _data[entry.hash]; + } + _data = this.ensureUniqueIndex(_data); + BrowserStorage.remove(entry.hash, () => { + BrowserStorage.set(_data, resolve); }); return; - } catch (error) { - return reject(error); - } - }); + }); + return; + } catch (error) { + return reject(error); + } + } + ); } } export class ManagedStorage { static get(key: string) { - return new Promise((resolve: (result: boolean|string) => void) => { - chrome.storage.managed.get((data) => { + return new Promise((resolve: (result: boolean | string) => void) => { + chrome.storage.managed.get(data => { if (chrome.runtime.lastError) { resolve(false); } diff --git a/src/popup.ts b/src/popup.ts index 4cec6880f..9a0ddedc3 100644 --- a/src/popup.ts +++ b/src/popup.ts @@ -1,57 +1,85 @@ -import {addAccount} from './ui/add-account'; -import {backup} from './ui/backup'; -import {className} from './ui/class'; -import {entry} from './ui/entry'; -import {i18n} from './ui/i18n'; -import {info} from './ui/info'; -import {menu, syncTimeWithGoogle} from './ui/menu'; -import {message} from './ui/message'; -import {passphrase} from './ui/passphrase'; -import {qr} from './ui/qr'; -import {UI} from './ui/ui'; -// @ts-ignore -import Authenticator from './view/popup'; +// Vue +import Vue from 'vue'; +import Vuex from 'vuex'; +import { Vue2Dragula } from 'vue2-dragula'; + +// Components +import Popup from './components/Popup.vue'; + +// Other +import { loadI18nMessages } from './store/i18n'; +import { Style } from './store/Style'; +import { Accounts } from './store/Accounts'; +import { Backup } from './store/Backup'; +import { CurrentView } from './store/CurrentView'; +import { Menu } from './store/Menu'; +import { Notification } from './store/Notification'; +import { Qr } from './store/Qr'; +import { Dropbox, Drive } from './models/backup'; async function init() { - const ui = new UI(Authenticator, {el: '#authenticator'}); - - const authenticator = await ui.load(className) - .load(i18n) - .load(menu) - .load(info) - .load(passphrase) - .load(entry) - .load(qr) - .load(message) - .load(addAccount) - .load(backup) - .render(); + // Add globals + Vue.prototype.i18n = await loadI18nMessages(); + + // Load modules + Vue.use(Vuex); + Vue.use(Vue2Dragula); + + // State + const store = new Vuex.Store({ + modules: { + accounts: await new Accounts().getModule(), + backup: new Backup().getModule(), + currentView: new CurrentView().getModule(), + menu: await new Menu().getModule(), + notification: new Notification().getModule(), + qr: new Qr().getModule(), + style: new Style().getModule(), + }, + }); + // Render + const instance = new Vue({ + render: h => h(Popup), + store, + mounted() { + // Update time based entries' codes + this.$store.commit('accounts/updateCodes'); + setInterval(() => { + this.$store.commit('accounts/updateCodes'); + }, 1000); + }, + }).$mount('#authenticator'); + + // Prompt for password if needed + if (instance.$store.state.accounts.shouldShowPassphrase) { + instance.$store.commit('style/showInfo'); + instance.$store.commit('currentView/changeView', 'EnterPasswordPage'); + } + + // Set document title try { - document.title = ui.instance.i18n.extName; + document.title = instance.i18n.extName; } catch (e) { console.error(e); } - if (authenticator.shouldShowPassphrase) { - authenticator.showInfo('passphrase'); - } - - // localStorage passphrase warning + // Warn if legacy password is set if (localStorage.encodedPhrase) { - authenticator.alert(authenticator.i18n.local_passphrase_warning); + instance.$store.commit( + 'notification/alert', + instance.i18n.local_passphrase_warning + ); } - // Remind backup + // Backup reminder / run backup const backupReminder = setInterval(() => { - if (authenticator.entries.length === 0) { + if (instance.$store.state.accounts.entries.length === 0) { return; } - for (let i = 0; i < authenticator.entries.length; i++) { - if (authenticator.entries[i].secret === 'Encrypted') { - return; - } + if (instance.$store.getters['accounts/currentlyEncrypted']) { + return; } clearInterval(backupReminder); @@ -60,55 +88,201 @@ async function init() { if (!localStorage.lastRemindingBackupTime) { localStorage.lastRemindingBackupTime = clientTime; } else if ( - clientTime - localStorage.lastRemindingBackupTime >= 30 || - clientTime - localStorage.lastRemindingBackupTime < 0) { - // backup to cloud - authenticator.runScheduledBackup(clientTime); + clientTime - localStorage.lastRemindingBackupTime >= 30 || + clientTime - localStorage.lastRemindingBackupTime < 0 + ) { + runScheduledBackup(clientTime, instance); } return; - }, 1000); + }, 5000); - document.addEventListener('keyup', (e) => { - ui.instance.searchListener(e); - }, false); + // Open search if '/' is pressed + document.addEventListener( + 'keyup', + e => { + if (e.key === '/') { + if (instance.$store.state.style.style.fadein === true) { + return; + } + instance.$store.commit('accounts/stopFilter'); + // It won't focus the texfield if vue unhides the div + instance.$store.commit('accounts/showSearch'); + const searchDiv = document.getElementById('search'); + const searchInput = document.getElementById('searchInput'); + if (!searchInput || !searchDiv) { + return; + } + searchDiv.style.display = 'block'; + searchInput.focus(); + } + }, + false + ); - if (ui.instance.entries.length >= 10 && - !(ui.instance.shouldFilter && ui.instance.filter)) { - ui.instance.showSearch = true; + // Show search box if more than 10 entries + if ( + instance.$store.state.accounts.entries.length >= 10 && + !( + instance.$store.getters['accounts/shouldFilter'] && + instance.$store.state.accounts.filter + ) + ) { + instance.$store.commit('accounts/showSearch'); } - chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { - if (['dropboxtoken', 'drivetoken'].indexOf(message.action) > -1) { - if (message.action === 'dropboxtoken') { - authenticator.dropboxToken = message.value; - } else if (message.action === 'drivetoken') { - authenticator.driveToken = message.value; - } - authenticator.backupUpload( - String(message.action) - .substring(0, String(message.action).indexOf('token'))); - if (['dropbox', 'drive'].indexOf(authenticator.info) > -1) { - setTimeout(authenticator.closeInfo, 500); - } + // Resize window to proper size if popup + if (new URLSearchParams(document.location.search.substring(1)).get('popup')) { + const zoom = Number(localStorage.zoom) / 100 || 1; + const correctHeight = 480 * zoom; + const correctWidth = 320 * zoom; + if ( + window.innerHeight !== correctHeight || + window.innerWidth !== correctWidth + ) { + // window update to correct size + const adjustedHeight = + correctHeight + (window.outerHeight - window.innerHeight); + const adjustedWidth = + correctWidth + (window.outerWidth - window.innerWidth); + chrome.windows.update(chrome.windows.WINDOW_ID_CURRENT, { + height: adjustedHeight, + width: adjustedWidth, + }); } - }); - - if (ui.instance.isPopup() && !ui.instance.isEdge()) { - ui.instance.fixPopupSize(); } - return; + // TODO: give an option for this + chrome.permissions.contains( + { origins: ['https://www.google.com/'] }, + hasPermission => { + if (hasPermission) { + syncTimeWithGoogle(); + } + } + ); } -if (navigator.userAgent.indexOf('Edge') !== -1) { - syncTimeWithGoogle(); -} else { - chrome.permissions.contains( - {origins: ['https://www.google.com/']}, (hasPermission) => { +init(); + +async function runScheduledBackup(clientTime: number, instance: Vue) { + if (instance.$store.state.backup.dropboxToken) { + chrome.permissions.contains( + { origins: ['https://*.dropboxapi.com/*'] }, + async hasPermission => { if (hasPermission) { - syncTimeWithGoogle(); + try { + const dropbox = new Dropbox(); + const res = await dropbox.upload( + instance.$store.state.accounts.encryption + ); + if (res) { + // we have uploaded backup to Dropbox + // no need to remind + localStorage.lastRemindingBackupTime = clientTime; + return; + } else if (localStorage.dropboxRevoked === 'true') { + instance.$store.commit( + 'notificaion/alert', + chrome.i18n.getMessage('token_revoked', ['Dropbox']) + ); + localStorage.removeItem('dropboxRevoked'); + } + } catch (error) { + // ignore + } } - }); + instance.$store.commit( + 'notificaion/alert', + instance.i18n.remind_backup + ); + localStorage.lastRemindingBackupTime = clientTime; + } + ); + } + if (instance.$store.state.backup.driveToken) { + chrome.permissions.contains( + { + origins: [ + 'https://www.googleapis.com/*', + 'https://accounts.google.com/o/oauth2/revoke', + ], + }, + async hasPermission => { + if (hasPermission) { + try { + const drive = new Drive(); + const res = await drive.upload( + instance.$store.state.accounts.encryption + ); + if (res) { + localStorage.lastRemindingBackupTime = clientTime; + return; + } else if (localStorage.driveRevoked === 'true') { + instance.$store.commit( + 'notificaion/alert', + chrome.i18n.getMessage('token_revoked', ['Google Drive']) + ); + localStorage.removeItem('driveRevoked'); + } + } catch (error) { + // ignore + } + } + instance.$store.commit( + 'notificaion/alert', + instance.i18n.remind_backup + ); + localStorage.lastRemindingBackupTime = clientTime; + } + ); + } + if ( + !instance.$store.state.backup.driveToken && + !instance.$store.state.backup.dropboxToken + ) { + instance.$store.commit('notificaion/alert', instance.i18n.remind_backup); + localStorage.lastRemindingBackupTime = clientTime; + } } -init(); +export function syncTimeWithGoogle() { + return new Promise( + (resolve: (value: string) => void, reject: (reason: Error) => void) => { + try { + // tslint:disable-next-line:ban-ts-ignore + // @ts-ignore + const xhr = new XMLHttpRequest({ mozAnon: true }); + xhr.open('HEAD', 'https://www.google.com/generate_204'); + const xhrAbort = setTimeout(() => { + xhr.abort(); + return resolve('updateFailure'); + }, 5000); + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + clearTimeout(xhrAbort); + const date = xhr.getResponseHeader('date'); + if (!date) { + return resolve('updateFailure'); + } + const serverTime = new Date(date).getTime(); + const clientTime = new Date().getTime(); + const offset = Math.round((serverTime - clientTime) / 1000); + + if (Math.abs(offset) <= 300) { + // within 5 minutes + localStorage.offset = Math.round( + (serverTime - clientTime) / 1000 + ); + return resolve('updateSuccess'); + } else { + return resolve('clock_too_far_off'); + } + } + }; + xhr.send(); + } catch (error) { + return reject(error); + } + } + ); +} diff --git a/src/qrdebug.ts b/src/qrdebug.ts index e445e8334..3ebb8bed0 100644 --- a/src/qrdebug.ts +++ b/src/qrdebug.ts @@ -4,15 +4,25 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { return; } getQrDebug( - sender.tab, message.info.left, message.info.top, message.info.width, - message.info.height, message.info.windowWidth); + sender.tab, + message.info.left, + message.info.top, + message.info.width, + message.info.height, + message.info.windowWidth + ); } }); function getQrDebug( - tab: chrome.tabs.Tab, left: number, top: number, width: number, - height: number, windowWidth: number) { - chrome.tabs.captureVisibleTab(tab.windowId, {format: 'png'}, (dataUrl) => { + tab: chrome.tabs.Tab, + left: number, + top: number, + width: number, + height: number, + windowWidth: number +) { + chrome.tabs.captureVisibleTab(tab.windowId, { format: 'png' }, dataUrl => { const qr = new Image(); qr.src = dataUrl; qr.onload = () => { @@ -25,28 +35,37 @@ function getQrDebug( return; } ctx.drawImage( - qr, left * devicePixelRatio, top * devicePixelRatio, - width * devicePixelRatio, height * devicePixelRatio, 0, 0, - width * devicePixelRatio, height * devicePixelRatio); + qr, + left * devicePixelRatio, + top * devicePixelRatio, + width * devicePixelRatio, + height * devicePixelRatio, + 0, + 0, + width * devicePixelRatio, + height * devicePixelRatio + ); const url = captureCanvas.toDataURL(); const infoDom = document.getElementById('info'); if (infoDom) { - infoDom.innerHTML = 'Scan Data:
' + - `
` + - `Window Inner Width: ${windowWidth}
` + - `Width: ${width}
` + - `Height: ${height}
` + - `Left: ${left}
` + - `Top: ${top}
` + - `Screen Width: ${window.screen.width}
` + - `Screen Height: ${window.screen.height}
` + - `Capture Width: ${qr.width}
` + - `Capture Height: ${qr.height}
` + - `Device Pixel Ratio: ${devicePixelRatio} / ${ - window.devicePixelRatio}
` + - `Tab ID: ${tab.id}
` + - '
' + - 'Captured Screenshot:'; + infoDom.innerHTML = + 'Scan Data:
' + + `
` + + `Window Inner Width: ${windowWidth}
` + + `Width: ${width}
` + + `Height: ${height}
` + + `Left: ${left}
` + + `Top: ${top}
` + + `Screen Width: ${window.screen.width}
` + + `Screen Height: ${window.screen.height}
` + + `Capture Width: ${qr.width}
` + + `Capture Height: ${qr.height}
` + + `Device Pixel Ratio: ${devicePixelRatio} / ${ + window.devicePixelRatio + }
` + + `Tab ID: ${tab.id}
` + + '
' + + 'Captured Screenshot:'; } const qrDom = document.getElementById('qr') as HTMLImageElement; diff --git a/src/store/Accounts.ts b/src/store/Accounts.ts new file mode 100644 index 000000000..3f74218ba --- /dev/null +++ b/src/store/Accounts.ts @@ -0,0 +1,436 @@ +import { EntryStorage } from '../models/storage'; +import { Encryption } from '../models/encryption'; +import * as CryptoJS from 'crypto-js'; +import { OTPType } from '../models/otp'; +import { ActionContext } from 'vuex'; + +export class Accounts implements IModule { + async getModule() { + const cachedPassphrase = await this.getCachedPassphrase(); + const encryption: Encryption = new Encryption(cachedPassphrase); + let shouldShowPassphrase = cachedPassphrase + ? false + : await EntryStorage.hasEncryptedEntry(); + const entries = shouldShowPassphrase + ? [] + : await this.getEntries(encryption); + + for (let i = 0; i < entries.length; i++) { + if (entries[i].code === 'Encrypted') { + shouldShowPassphrase = true; + break; + } + } + + return { + state: { + entries, + encryption, + OTPType, + shouldShowPassphrase, + sectorStart: false, // Should display timer circles? + sectorOffset: 0, // Offset in seconds for animations + second: 0, // Offset in seconds for math + filter: true, + siteName: await this.getSiteName(), + showSearch: false, + exportData: await EntryStorage.getExport(encryption), + exportEncData: await EntryStorage.getExport(encryption, true), + }, + getters: { + shouldFilter( + state: AccountsState, + getters: { matchedEntries: string[] } + ) { + return getters.matchedEntries.length; + }, + matchedEntries: (state: AccountsState) => { + return this.matchedEntries(state.siteName, state.entries); + }, + currentlyEncrypted(state: AccountsState) { + for (const entry of state.entries) { + if (entry.secret === null) { + return true; + } + } + return false; + }, + }, + mutations: { + stopFilter(state: AccountsState) { + state.filter = false; + }, + showSearch(state: AccountsState) { + state.showSearch = true; + }, + updateCodes(state: AccountsState) { + let second = new Date().getSeconds(); + if (localStorage.offset) { + // prevent second from negative + second += Number(localStorage.offset) + 60; + } + + second = second % 60; + state.second = second; + + let currentlyEncrypted = false; + + for (const entry of state.entries) { + if (entry.secret === null) { + currentlyEncrypted = true; + } + } + + if ( + !state.sectorStart && + state.entries.length > 0 && + !currentlyEncrypted + ) { + state.sectorStart = true; + state.sectorOffset = -second; + } + + // if (second > 25) { + // app.class.timeout = true; + // } else { + // app.class.timeout = false; + // } + // if (second < 1) { + // const entries = app.entries as OTP[]; + // for (let i = 0; i < entries.length; i++) { + // if (entries[i].type !== OTPType.hotp && + // entries[i].type !== OTPType.hhex) { + // entries[i].generate(); + // } + // } + // } + const entries = state.entries as IOTPEntry[]; + for (let i = 0; i < entries.length; i++) { + if ( + entries[i].type !== OTPType.hotp && + entries[i].type !== OTPType.hhex + ) { + entries[i].generate(); + } + } + }, + loadCodes(state: AccountsState, newCodes: IOTPEntry[]) { + state.entries = newCodes; + + if (state.encryption.getEncryptionStatus()) { + for (const entry of state.entries) { + entry.applyEncryption(state.encryption); + } + } + }, + moveCode(state: AccountsState, opts: { from: number; to: number }) { + state.entries.splice( + opts.to, + 0, + state.entries.splice(opts.from, 1)[0] + ); + + for (let i = 0; i < state.entries.length; i++) { + if (state.entries[i].index !== i) { + state.entries[i].index = i; + } + } + }, + updateExport( + state: AccountsState, + exportData: { [k: string]: IOTPEntry } + ) { + state.exportData = exportData; + }, + updateEncExport( + state: AccountsState, + exportData: { [k: string]: IOTPEntry } + ) { + state.exportEncData = exportData; + }, + }, + actions: { + applyPassphrase: async ( + state: ActionContext, + password: string + ) => { + if (!password) { + return; + } + + state.state.encryption.updateEncryptionPassword(password); + await state.dispatch('updateEntries'); + state.commit('style/hideInfo', null, { root: true }); + + document.cookie = 'passphrase=' + password; + chrome.runtime.sendMessage({ + action: 'cachePassphrase', + value: password, + }); + return; + }, + changePassphrase: async ( + state: ActionContext, + password: string + ) => { + await EntryStorage.import( + new Encryption(password), + await EntryStorage.getExport(state.state.encryption as Encryption) + ); + + state.state.encryption.updateEncryptionPassword(password); + document.cookie = 'passphrase=' + password; + chrome.runtime.sendMessage({ + action: 'cachePassphrase', + value: password, + }); + + await state.dispatch('updateEntries'); + + // remove cached passphrase in old version + localStorage.removeItem('encodedPhrase'); + }, + updateEntries: async (state: ActionContext) => { + state.commit( + 'loadCodes', + await this.getEntries(state.state.encryption as Encryption) + ); + state.commit('updateCodes'); + state.commit( + 'updateExport', + await EntryStorage.getExport(state.state.encryption as Encryption) + ); + state.commit( + 'updateEncExport', + await EntryStorage.getExport( + state.state.encryption as Encryption, + true + ) + ); + return; + }, + clearFilter: (state: ActionContext) => { + state.commit('stopFilter'); + if (state.state.entries.length >= 10) { + state.commit('showSearch'); + } + }, + migrateStorage: async ( + state: ActionContext, + newStorageLocation: string + ) => { + // sync => local + if ( + localStorage.storageLocation === 'sync' && + newStorageLocation === 'local' + ) { + return new Promise((resolve, reject) => { + chrome.storage.sync.get(syncData => { + chrome.storage.local.set(syncData, () => { + chrome.storage.local.get(localData => { + // Double check if data was set + if ( + Object.keys(syncData).every( + value => Object.keys(localData).indexOf(value) >= 0 + ) + ) { + localStorage.storageLocation = 'local'; + chrome.storage.sync.clear(); + resolve('updateSuccess'); + return; + } else { + reject(' All data not transferred successfully.'); + return; + } + }); + }); + }); + }); + // local => sync + } else if ( + localStorage.storageLocation === 'local' && + newStorageLocation === 'sync' + ) { + return new Promise((resolve, reject) => { + chrome.storage.local.get(localData => { + chrome.storage.sync.set(localData, () => { + chrome.storage.sync.get(syncData => { + // Double check if data was set + if ( + Object.keys(localData).every( + value => Object.keys(syncData).indexOf(value) >= 0 + ) + ) { + localStorage.storageLocation = 'sync'; + chrome.storage.local.clear(); + resolve('updateSuccess'); + return; + } else { + reject(' All data not transferred successfully.'); + return; + } + }); + }); + }); + }); + } + }, + }, + namespaced: true, + }; + } + + private async getSiteName() { + return new Promise( + ( + resolve: (value: Array) => void, + reject: (reason: Error) => void + ) => { + chrome.tabs.query({ active: true, lastFocusedWindow: true }, tabs => { + const tab = tabs[0]; + if (!tab) { + return resolve([null, null]); + } + + const title = tab.title + ? tab.title.replace(/[^a-z0-9]/gi, '').toLowerCase() + : null; + + if (!tab.url) { + return resolve([title, null]); + } + + const urlParser = document.createElement('a'); + urlParser.href = tab.url; + const hostname = urlParser.hostname.toLowerCase(); + + // try to parse name from hostname + // i.e. hostname is www.example.com + // name should be example + let nameFromDomain = ''; + + // ip address + if (/^\d+\.\d+\.\d+\.\d+$/.test(hostname)) { + nameFromDomain = hostname; + } + + // local network + if (hostname.indexOf('.') === -1) { + nameFromDomain = hostname; + } + + const hostLevelUnits = hostname.split('.'); + + if (hostLevelUnits.length === 2) { + nameFromDomain = hostLevelUnits[0]; + } + + // www.example.com + // example.com.cn + if (hostLevelUnits.length > 2) { + // example.com.cn + if ( + ['com', 'net', 'org', 'edu', 'gov', 'co'].indexOf( + hostLevelUnits[hostLevelUnits.length - 2] + ) !== -1 + ) { + nameFromDomain = hostLevelUnits[hostLevelUnits.length - 3]; + } else { + // www.example.com + nameFromDomain = hostLevelUnits[hostLevelUnits.length - 2]; + } + } + + nameFromDomain = nameFromDomain.replace(/-/g, '').toLowerCase(); + + return resolve([title, nameFromDomain, hostname]); + }); + } + ); + } + + private getCachedPassphrase() { + return new Promise( + (resolve: (value: string) => void, reject: (reason: Error) => void) => { + const cookie = document.cookie; + const cookieMatch = cookie + ? document.cookie.match(/passphrase=([^;]*)/) + : null; + const cachedPassphrase = + cookieMatch && cookieMatch.length > 1 ? cookieMatch[1] : null; + const cachedPassphraseLocalStorage = localStorage.encodedPhrase + ? CryptoJS.AES.decrypt(localStorage.encodedPhrase, '').toString( + CryptoJS.enc.Utf8 + ) + : ''; + if (cachedPassphrase || cachedPassphraseLocalStorage) { + return resolve(cachedPassphrase || cachedPassphraseLocalStorage); + } + + chrome.runtime.sendMessage( + { action: 'passphrase' }, + (passphrase: string) => { + return resolve(passphrase); + } + ); + } + ); + } + + private async getEntries(encryption: Encryption) { + const otpEntries = await EntryStorage.get(encryption); + return otpEntries; + } + + private matchedEntries(siteName: Array, entries: IOTPEntry[]) { + if (siteName.length < 2) { + return false; + } + + const matched = []; + + for (const entry of entries) { + if (this.isMatchedEntry(siteName, entry)) { + matched.push(entry.hash); + } + } + + return matched; + } + + private isMatchedEntry(siteName: Array, entry: IOTPEntry) { + if (!entry.issuer) { + return false; + } + + const issuerHostMatches = entry.issuer.split('::'); + const issuer = issuerHostMatches[0] + .replace(/[^0-9a-z]/gi, '') + .toLowerCase(); + + if (!issuer) { + return false; + } + + const siteTitle = siteName[0] || ''; + const siteNameFromHost = siteName[1] || ''; + const siteHost = siteName[2] || ''; + + if (issuerHostMatches.length > 1) { + if (siteHost && siteHost.indexOf(issuerHostMatches[1]) !== -1) { + return true; + } + } + // site title should be more detailed + // so we use siteTitle.indexOf(issuer) + if (siteTitle && siteTitle.indexOf(issuer) !== -1) { + return true; + } + + if (siteNameFromHost && issuer.indexOf(siteNameFromHost) !== -1) { + return true; + } + + return false; + } +} diff --git a/src/store/Backup.ts b/src/store/Backup.ts new file mode 100644 index 000000000..7652b714f --- /dev/null +++ b/src/store/Backup.ts @@ -0,0 +1,46 @@ +export class Backup implements IModule { + getModule() { + return { + state: { + dropboxEncrypted: localStorage.dropboxEncrypted === 'true', + driveEncrypted: localStorage.driveEncrypted === 'true', + dropboxToken: Boolean(localStorage.dropboxToken), + driveToken: Boolean(localStorage.driveToken), + }, + mutations: { + setToken( + state: BackupState, + args: { service: string; value: boolean } + ) { + switch (args.service) { + case 'dropbox': + state.dropboxToken = args.value; + break; + + case 'drive': + state.driveToken = args.value; + break; + + default: + break; + } + }, + setEnc(state: BackupState, args: { service: string; value: boolean }) { + switch (args.service) { + case 'dropbox': + state.dropboxEncrypted = args.value; + break; + + case 'drive': + state.driveEncrypted = args.value; + break; + + default: + break; + } + }, + }, + namespaced: true, + }; + } +} diff --git a/src/store/CurrentView.ts b/src/store/CurrentView.ts new file mode 100644 index 000000000..45e6d0a38 --- /dev/null +++ b/src/store/CurrentView.ts @@ -0,0 +1,15 @@ +export class CurrentView implements IModule { + getModule() { + return { + state: { + info: '', + }, + mutations: { + changeView(state: { info: string }, viewName: string) { + state.info = viewName; + }, + }, + namespaced: true, + }; + } +} diff --git a/src/store/Menu.ts b/src/store/Menu.ts new file mode 100644 index 000000000..9fbdd8394 --- /dev/null +++ b/src/store/Menu.ts @@ -0,0 +1,45 @@ +import { ManagedStorage } from '../models/storage'; + +export class Menu implements IModule { + async getModule() { + const menuState = { + state: { + version: chrome.runtime.getManifest().version, + zoom: Number(localStorage.zoom) || 100, + useAutofill: localStorage.autofill === 'true', + useHighContrast: localStorage.highContrast === 'true', + backupDisabled: await ManagedStorage.get('disableBackup'), + storageArea: await ManagedStorage.get('storageArea'), + feedbackURL: await ManagedStorage.get('feedbackURL'), + }, + mutations: { + setZoom: (state: MenuState, zoom: number) => { + state.zoom = zoom; + localStorage.zoom = zoom; + this.resize(zoom); + }, + setAutofill(state: MenuState, useAutofill: boolean) { + state.useAutofill = useAutofill; + localStorage.autofill = useAutofill; + }, + setHighContrast(state: MenuState, useHighContrast: boolean) { + state.useHighContrast = useHighContrast; + localStorage.highContrast = useHighContrast; + }, + }, + namespaced: true, + }; + + this.resize(menuState.state.zoom); + + return menuState; + } + + private resize(zoom: number) { + if (zoom !== 100) { + document.body.style.marginBottom = 480 * (zoom / 100 - 1) + 'px'; + document.body.style.marginRight = 320 * (zoom / 100 - 1) + 'px'; + document.body.style.transform = 'scale(' + zoom / 100 + ')'; + } + } +} diff --git a/src/store/Notification.ts b/src/store/Notification.ts new file mode 100644 index 000000000..ca725b965 --- /dev/null +++ b/src/store/Notification.ts @@ -0,0 +1,63 @@ +import { ActionContext } from 'vuex'; + +export class Notification implements IModule { + getModule() { + return { + state: { + message: [], // Message content for alert with ok button + confirmMessage: '', // Message content for alert with yes / no + messageIdle: true, // Should show alert box? + notification: '', // Ephermal message text + }, + mutations: { + alert: (state: NotificationState, message: string) => { + state.message.unshift(message); + }, + closeAlert: (state: NotificationState) => { + state.messageIdle = false; + state.message.shift(); + setTimeout(() => { + state.messageIdle = true; + }, 200); + }, + setConfirm: (state: NotificationState, message: string) => { + state.confirmMessage = message; + }, + setNotification: (state: NotificationState, message: string) => { + state.notification = message; + }, + }, + actions: { + confirm: async ( + state: ActionContext, + message: string + ) => { + return new Promise((resolve: (value: boolean) => void) => { + state.commit('setConfirm', message); + window.addEventListener('confirm', event => { + state.commit('setConfirm', ''); + if (!this.isCustomEvent(event)) { + resolve(false); + return; + } + resolve(event.detail); + return; + }); + }); + }, + ephermalMessage: ( + state: ActionContext, + message: string + ) => { + state.commit('setNotification', message); + state.commit('style/showNotification', null, { root: true }); + }, + }, + namespaced: true, + }; + } + + private isCustomEvent(event: Event): event is CustomEvent { + return 'detail' in event; + } +} diff --git a/src/store/Qr.ts b/src/store/Qr.ts new file mode 100644 index 000000000..bb8a3c711 --- /dev/null +++ b/src/store/Qr.ts @@ -0,0 +1,15 @@ +export class Qr implements IModule { + getModule() { + return { + state: { + qr: '', + }, + mutations: { + setQr(state: { qr: string }, url: string) { + state.qr = `url(${url})`; + }, + }, + namespaced: true, + }; + } +} diff --git a/src/store/Style.ts b/src/store/Style.ts new file mode 100644 index 000000000..3c4dcc071 --- /dev/null +++ b/src/store/Style.ts @@ -0,0 +1,74 @@ +export class Style implements IModule { + getModule() { + return { + state: { + style: { + timeout: false, + isEditing: false, + slidein: false, // menu + slideout: false, // menu + fadein: false, // info + fadeout: false, // info + qrfadein: false, + qrfadeout: false, + notificationFadein: false, + notificationFadeout: false, + hotpDisabled: false, + }, + }, + mutations: { + showMenu(state: StyleState) { + state.style.slidein = true; + state.style.slideout = false; + }, + hideMenu(state: StyleState) { + state.style.slidein = false; + state.style.slideout = true; + setTimeout(() => { + state.style.slideout = false; + }, 200); + }, + showInfo(state: StyleState) { + state.style.fadein = true; + state.style.fadeout = false; + }, + hideInfo(state: StyleState) { + state.style.fadein = false; + state.style.fadeout = true; + setTimeout(() => { + state.style.fadeout = false; + }, 200); + }, + showQr(state: StyleState) { + state.style.qrfadein = true; + state.style.qrfadeout = false; + }, + hideQr(state: StyleState) { + state.style.qrfadein = false; + state.style.qrfadeout = true; + setTimeout(() => { + state.style.qrfadeout = false; + }, 200); + }, + showNotification(state: StyleState) { + state.style.notificationFadein = true; + state.style.notificationFadeout = false; + setTimeout(() => { + state.style.notificationFadein = false; + state.style.notificationFadeout = true; + setTimeout(() => { + state.style.notificationFadeout = false; + }, 200); + }, 1000); + }, + toggleEdit(state: StyleState) { + state.style.isEditing = !state.style.isEditing; + }, + toggleHotpDisabled(state: StyleState) { + state.style.hotpDisabled = !state.style.hotpDisabled; + }, + }, + namespaced: true, + }; + } +} diff --git a/src/store/i18n.ts b/src/store/i18n.ts new file mode 100644 index 000000000..87d7caa84 --- /dev/null +++ b/src/store/i18n.ts @@ -0,0 +1,28 @@ +export async function loadI18nMessages() { + return new Promise( + ( + resolve: (value: { [key: string]: string }) => void, + reject: (reason: Error) => void + ) => { + try { + const xhr = new XMLHttpRequest(); + xhr.overrideMimeType('application/json'); + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + const i18nMessage: I18nMessage = JSON.parse(xhr.responseText); + const i18nData: { [key: string]: string } = {}; + for (const key of Object.keys(i18nMessage)) { + i18nData[key] = chrome.i18n.getMessage(key); + } + return resolve(i18nData); + } + return; + }; + xhr.open('GET', chrome.extension.getURL('/_locales/en/messages.json')); + xhr.send(); + } catch (error) { + return reject(error); + } + } + ); +} diff --git a/src/test/test.ts b/src/test/test.ts index 5c73dc916..05e24a675 100644 --- a/src/test/test.ts +++ b/src/test/test.ts @@ -4,7 +4,7 @@ interface TestCase { /* tslint:disable-next-line:no-any */ [hash: string]: { /* tslint:disable-next-line:no-any */ - [key: string]: any + [key: string]: any; }; }; } @@ -12,206 +12,206 @@ interface TestCase { const cases: TestCase[] = [ { name: 'Missing fields', - data: {'7733be61632fa6af88d31218e6c4afb2': {'secret': 'abcd2345'}} + data: { '7733be61632fa6af88d31218e6c4afb2': { secret: 'abcd2345' } }, }, { name: 'Bad hash in key', data: { - 'badhash': { - 'account': 'test', - 'counter': 0, - 'encrypted': false, - 'hash': '7733be61632fa6af88d31218e6c4afb2', - 'index': 0, - 'issuer': '', - 'secret': 'abcd2345', - 'type': 'totp' - } - } + badhash: { + account: 'test', + counter: 0, + encrypted: false, + hash: '7733be61632fa6af88d31218e6c4afb2', + index: 0, + issuer: '', + secret: 'abcd2345', + type: 'totp', + }, + }, }, { name: 'Bad hash', data: { - 'badhash': { - 'account': 'test', - 'counter': 0, - 'encrypted': false, - 'hash': 'badhash', - 'index': 0, - 'issuer': '', - 'secret': 'abcd2345', - 'type': 'totp' - } - } + badhash: { + account: 'test', + counter: 0, + encrypted: false, + hash: 'badhash', + index: 0, + issuer: '', + secret: 'abcd2345', + type: 'totp', + }, + }, }, { name: 'Bad type for HEX', data: { - 'e19d5cd5af0378da05f63f891c7467af': { - 'account': 'test', - 'counter': 0, - 'encrypted': false, - 'hash': 'e19d5cd5af0378da05f63f891c7467af', - 'index': 0, - 'issuer': '', - 'secret': 'abcd1234', - 'type': 'totp' - } - } + e19d5cd5af0378da05f63f891c7467af: { + account: 'test', + counter: 0, + encrypted: false, + hash: 'e19d5cd5af0378da05f63f891c7467af', + index: 0, + issuer: '', + secret: 'abcd1234', + type: 'totp', + }, + }, }, { name: 'Unicode in issuer', data: { '7733be61632fa6af88d31218e6c4afb2': { - 'account': 'test', - 'counter': 0, - 'encrypted': false, - 'hash': '7733be61632fa6af88d31218e6c4afb2', - 'index': 0, - 'issuer': '✓ à la mode', - 'secret': 'abcd2345', - 'type': 'totp' - } - } + account: 'test', + counter: 0, + encrypted: false, + hash: '7733be61632fa6af88d31218e6c4afb2', + index: 0, + issuer: '✓ à la mode', + secret: 'abcd2345', + type: 'totp', + }, + }, }, { name: 'Battle migrate', data: { '95c869de1221960c7f7e6892f78d7062': { - 'account': 'test', - 'counter': 0, - 'encrypted': false, - 'hash': '95c869de1221960c7f7e6892f78d7062', - 'index': 0, - 'issuer': '', - 'secret': 'blz-abcd2345', - 'type': 'totp' - } - } + account: 'test', + counter: 0, + encrypted: false, + hash: '95c869de1221960c7f7e6892f78d7062', + index: 0, + issuer: '', + secret: 'blz-abcd2345', + type: 'totp', + }, + }, }, { name: 'Steam migrate', data: { '95c869de1221960c7f7e6892f78d7062': { - 'account': 'test', - 'counter': 0, - 'encrypted': false, - 'hash': '95c869de1221960c7f7e6892f78d7062', - 'index': 0, - 'issuer': '', - 'secret': 'stm-abcd2345', - 'type': 'totp' - } - } + account: 'test', + counter: 0, + encrypted: false, + hash: '95c869de1221960c7f7e6892f78d7062', + index: 0, + issuer: '', + secret: 'stm-abcd2345', + type: 'totp', + }, + }, }, { name: 'Missing field with HEX secret', - data: {'e19d5cd5af0378da05f63f891c7467af': {'secret': 'abcd1234'}} + data: { e19d5cd5af0378da05f63f891c7467af: { secret: 'abcd1234' } }, }, { name: 'Mess index', data: { '7733be61632fa6af88d31218e6c4afb2': { - 'account': 'test', - 'counter': 0, - 'encrypted': false, - 'hash': '7733be61632fa6af88d31218e6c4afb2', - 'index': 6, - 'issuer': '', - 'secret': 'abcd2345', - 'type': 'totp' + account: 'test', + counter: 0, + encrypted: false, + hash: '7733be61632fa6af88d31218e6c4afb2', + index: 6, + issuer: '', + secret: 'abcd2345', + type: 'totp', }, '770f51f23603ddae810e446630c2f673': { - 'account': 'test', - 'counter': 0, - 'encrypted': false, - 'hash': '770f51f23603ddae810e446630c2f673', - 'index': 6, - 'issuer': '', - 'secret': 'abcd2346', - 'type': 'totp' - } - } + account: 'test', + counter: 0, + encrypted: false, + hash: '770f51f23603ddae810e446630c2f673', + index: 6, + issuer: '', + secret: 'abcd2346', + type: 'totp', + }, + }, }, { name: 'Base32 with padding', data: { - 'b905232a977347a0a113a7d1c924fb8d': { - 'account': 'test', - 'counter': 0, - 'encrypted': false, - 'hash': 'b905232a977347a0a113a7d1c924fb8d', - 'index': 0, - 'issuer': '', - 'secret': 'DKCE3SQPHJRJQGBGI322QA7Z5E======', - 'type': 'totp' - } - } + b905232a977347a0a113a7d1c924fb8d: { + account: 'test', + counter: 0, + encrypted: false, + hash: 'b905232a977347a0a113a7d1c924fb8d', + index: 0, + issuer: '', + secret: 'DKCE3SQPHJRJQGBGI322QA7Z5E======', + type: 'totp', + }, + }, }, { name: 'Incorrect but valid hash', data: { - 'ffffffffffffffffffffffffffffffff': { - 'account': 'test', - 'counter': 0, - 'encrypted': false, - 'hash': 'ffffffffffffffffffffffffffffffff', - 'index': 0, - 'issuer': '', - 'secret': 'abcd2345', - 'type': 'totp' - } - } + ffffffffffffffffffffffffffffffff: { + account: 'test', + counter: 0, + encrypted: false, + hash: 'ffffffffffffffffffffffffffffffff', + index: 0, + issuer: '', + secret: 'abcd2345', + type: 'totp', + }, + }, }, { name: 'HOTP with HEX secret', data: { '7c117a118e015b6232ff359958b9e270': { - 'account': 'test', - 'counter': 0, - 'encrypted': false, - 'hash': '7c117a118e015b6232ff359958b9e270', - 'index': 0, - 'issuer': '', - 'secret': '2c52e8fcfac34091da63ef7b118f1cc50b925a42', - 'type': 'hhex' - } - } + account: 'test', + counter: 0, + encrypted: false, + hash: '7c117a118e015b6232ff359958b9e270', + index: 0, + issuer: '', + secret: '2c52e8fcfac34091da63ef7b118f1cc50b925a42', + type: 'hhex', + }, + }, }, { name: 'Amazon 2FA', data: { '0e00b601f60a4d7154d54ba94c429afb': { - 'account': 'test', - 'counter': 0, - 'encrypted': false, - 'hash': '0e00b601f60a4d7154d54ba94c429afb', - 'index': 0, - 'issuer': '', - 'secret': 'QLGNXJ2KLSOACXOEKJ47X6VA6ZPGT5HE2GBO5NPXTLD7FJAKD4JQ', - 'type': 'totp' - } - } + account: 'test', + counter: 0, + encrypted: false, + hash: '0e00b601f60a4d7154d54ba94c429afb', + index: 0, + issuer: '', + secret: 'QLGNXJ2KLSOACXOEKJ47X6VA6ZPGT5HE2GBO5NPXTLD7FJAKD4JQ', + type: 'totp', + }, + }, }, { name: 'Secret contains spaces', data: { '1b0c21ad1ec44264f665708ef82dae84': { - 'account': 'test', - 'counter': 0, - 'encrypted': false, - 'hash': '1b0c21ad1ec44264f665708ef82dae84', - 'index': 0, - 'issuer': '', - 'secret': 'p5s7 k2in z3mj oqfg', - 'type': 'totp' - } - } - } + account: 'test', + counter: 0, + encrypted: false, + hash: '1b0c21ad1ec44264f665708ef82dae84', + index: 0, + issuer: '', + secret: 'p5s7 k2in z3mj oqfg', + type: 'totp', + }, + }, + }, ]; let testCaseIndex = 0; -let testRes: Array<{pass: boolean, error: string | Event}> = []; +let testRes: Array<{ pass: boolean; error: string | Event }> = []; let testResData: string[] = []; function testStart() { @@ -255,17 +255,20 @@ async function clear() { async function get() { return new Promise( - (resolve: (items: {[key: string]: T}) => void, - reject: (reason: Error) => void) => { - try { - chrome.storage.sync.get(resolve); - } catch (error) { - reject(error); - } - }); + ( + resolve: (items: { [key: string]: T }) => void, + reject: (reason: Error) => void + ) => { + try { + chrome.storage.sync.get(resolve); + } catch (error) { + reject(error); + } + } + ); } -async function set(items: {[key: string]: {}}) { +async function set(items: { [key: string]: {} }) { /* tslint:disable-next-line:no-any */ return new Promise((resolve: () => void, reject: (reason: Error) => void) => { try { @@ -283,8 +286,9 @@ async function test() { } console.log( - cases[Math.floor(testCaseIndex / 2)].name, - testCaseIndex % 2 ? 'Reopen' : ''); + cases[Math.floor(testCaseIndex / 2)].name, + testCaseIndex % 2 ? 'Reopen' : '' + ); if (testCaseIndex % 2 === 0) { clear(); @@ -293,7 +297,7 @@ async function test() { const iframe = document.getElementsByTagName('iframe')[0]; if (iframe) { - testRes[testCaseIndex] = {pass: true, error: ''}; + testRes[testCaseIndex] = { pass: true, error: '' }; iframe.src = 'popup.html'; iframe.onload = () => { @@ -302,11 +306,11 @@ async function test() { } iframe.contentWindow.addEventListener('unhandledrejection', event => { const rejectionEvent = event as PromiseRejectionEvent; - testRes[testCaseIndex] = {pass: false, error: rejectionEvent.reason}; + testRes[testCaseIndex] = { pass: false, error: rejectionEvent.reason }; }); iframe.contentWindow.onerror = error => { - testRes[testCaseIndex] = {pass: false, error}; + testRes[testCaseIndex] = { pass: false, error }; }; }; } @@ -314,27 +318,35 @@ async function test() { setTimeout(async () => { const data = await get<{ /* tslint:disable-next-line:no-any */ - [key: string]: any + [key: string]: any; }>(); testResData[testCaseIndex] = JSON.stringify(data, null, 2); if (testRes[testCaseIndex].pass) { - if (Object.keys(data).length !== - Object.keys(cases[Math.floor(testCaseIndex / 2)].data).length) { - testRes[testCaseIndex] = {pass: false, error: `Missing data`}; + if ( + Object.keys(data).length !== + Object.keys(cases[Math.floor(testCaseIndex / 2)].data).length + ) { + testRes[testCaseIndex] = { pass: false, error: `Missing data` }; } else { for (const hash of Object.keys(data)) { const item = data[hash]; const keys = [ - 'issuer', 'account', 'secret', 'hash', 'index', 'type', 'counter', - 'encrypted' + 'issuer', + 'account', + 'secret', + 'hash', + 'index', + 'type', + 'counter', + 'encrypted', ]; for (const key of keys) { if (item[key] === undefined) { testRes[testCaseIndex] = { pass: false, - error: `Missing key<${key}>: ${JSON.stringify(item)}` + error: `Missing key<${key}>: ${JSON.stringify(item)}`, }; break; } @@ -364,13 +376,13 @@ function showTestResult() { for (let i = 0; i < testRes.length; i++) { const el = document.createElement('tr'); el.innerHTML = `[${testRes[i].pass ? 'Pass' : 'Fail'}]`; - el.innerHTML += - `

${cases[Math.floor(i / 2)].name}${ - i % 2 === 1 ? ' (Reopen)' : - ''}

${testRes[i].error}
${
-            testResData[i]}

`; + testRes[i].pass ? 'green' : 'red' + }">[${testRes[i].pass ? 'Pass' : 'Fail'}]`; + el.innerHTML += `

${ + cases[Math.floor(i / 2)].name + }${i % 2 === 1 ? ' (Reopen)' : ''}

${testRes[i].error}
${
+      testResData[i]
+    }

`; testResultContainer.appendChild(el); } @@ -381,6 +393,10 @@ if (startBtn) { startBtn.onclick = testStart; } -window.addEventListener('message', (event) => { - testRes[testCaseIndex] = {pass: false, error: event.data}; -}, false); +window.addEventListener( + 'message', + event => { + testRes[testCaseIndex] = { pass: false, error: event.data }; + }, + false +); diff --git a/src/ui/add-account.ts b/src/ui/add-account.ts deleted file mode 100644 index 995aadc2c..000000000 --- a/src/ui/add-account.ts +++ /dev/null @@ -1,120 +0,0 @@ -import {OTPType, UIConfig} from '../models/interface'; -import {OTPEntry} from '../models/otp'; - -import {UI} from './ui'; - -export async function insertContentScript() { - return new Promise((resolve: () => void, reject: (reason: Error) => void) => { - try { - return chrome.tabs.executeScript({file: '/dist/content.js'}, () => { - chrome.tabs.insertCSS({file: '/css/content.css'}, resolve); - }); - } catch (error) { - return reject(error); - } - }); -} - -export async function addAccount(_ui: UI) { - const ui: UIConfig = { - data: { - newAccount: {show: false, account: '', secret: '', type: OTPType.totp}, - newPassphrase: {phrase: '', confirm: ''} - }, - methods: { - addNewAccount: async () => { - _ui.instance.newAccount.secret = - _ui.instance.newAccount.secret.replace(/ /g, ''); - - if (!/^[a-z2-7]+=*$/i.test(_ui.instance.newAccount.secret) && - !/^[0-9a-f]+$/i.test(_ui.instance.newAccount.secret)) { - _ui.instance.alert( - _ui.instance.i18n.errorsecret + _ui.instance.newAccount.secret); - return; - } - - let type: OTPType; - if (!/^[a-z2-7]+=*$/i.test(_ui.instance.newAccount.secret) && - /^[0-9a-f]+$/i.test(_ui.instance.newAccount.secret) && - _ui.instance.newAccount.type === 'totp') { - type = OTPType.hex; - } else if ( - !/^[a-z2-7]+=*$/i.test(_ui.instance.newAccount.secret) && - /^[0-9a-f]+$/i.test(_ui.instance.newAccount.secret) && - _ui.instance.newAccount.type === 'hotp') { - type = OTPType.hhex; - } else { - type = _ui.instance.newAccount.type; - } - - const entry = new OTPEntry( - type, '', _ui.instance.newAccount.secret, - _ui.instance.newAccount.account, 0, 0); - await entry.create(_ui.instance.encryption); - await _ui.instance.updateEntries(); - _ui.instance.newAccount.type = OTPType.totp; - _ui.instance.account = ''; - _ui.instance.secret = ''; - _ui.instance.newAccount.show = false; - _ui.instance.closeInfo(); - _ui.instance.currentClass.edit = false; - - const codes = document.getElementById('codes'); - if (codes) { - // wait vue apply changes to dom - setTimeout(() => { - codes.scrollTop = 0; - }, 0); - } - return; - }, - beginCapture: async () => { - await insertContentScript(); - - const entries = _ui.instance.entries as OTPEntry[]; - for (let i = 0; i < entries.length; i++) { - // we have encrypted entry - // the current passphrase is incorrect - // shouldn't add new account with - // the current passphrase - if (entries[i].code === 'Encrypted') { - _ui.instance.alert(_ui.instance.i18n.phrase_incorrect); - return; - } - } - - chrome.tabs.query({active: true, lastFocusedWindow: true}, (tabs) => { - const tab = tabs[0]; - if (!tab || !tab.id) { - return; - } - chrome.tabs.sendMessage(tab.id, {action: 'capture'}, (result) => { - if (result !== 'beginCapture') { - _ui.instance.alert(_ui.instance.i18n.capture_failed); - } else { - window.close(); - } - }); - }); - return; - }, - addAccountManually: () => { - const entries = _ui.instance.entries as OTPEntry[]; - for (let i = 0; i < entries.length; i++) { - // we have encrypted entry - // the current passphrase is incorrect - // shouldn't add new account with - // the current passphrase - if (entries[i].code === 'Encrypted') { - _ui.instance.alert(_ui.instance.i18n.phrase_incorrect); - return; - } - } - - _ui.instance.newAccount.show = true; - } - } - }; - - _ui.update(ui); -} diff --git a/src/ui/backup.ts b/src/ui/backup.ts deleted file mode 100644 index 49c9ff824..000000000 --- a/src/ui/backup.ts +++ /dev/null @@ -1,162 +0,0 @@ -import {Drive, Dropbox} from '../models/backup'; -import {UIConfig} from '../models/interface'; - -import {UI} from './ui'; - -export async function backup(_ui: UI) { - const ui: UIConfig = { - data: { - dropboxEncrypted: localStorage.dropboxEncrypted, - driveEncrypted: localStorage.driveEncrypted, - dropboxToken: localStorage.dropboxToken || '', - driveToken: localStorage.driveToken || '' - }, - methods: { - backupUpload: async (service: string) => { - if (service === 'dropbox') { - const dbox = new Dropbox(); - const response = await dbox.upload(_ui.instance.encryption); - if (response === true) { - _ui.instance.alert(_ui.instance.i18n.updateSuccess); - } else if (localStorage.dropboxRevoked === 'true') { - _ui.instance.alert( - chrome.i18n.getMessage('token_revoked', ['Dropbox'])); - localStorage.removeItem('dropboxRevoked'); - _ui.instance.dropboxToken = ''; - } else { - _ui.instance.alert(_ui.instance.i18n.updateFailure); - } - } else if (service === 'drive') { - const drive = new Drive(); - const response = await drive.upload(_ui.instance.encryption); - if (response === true) { - _ui.instance.alert(_ui.instance.i18n.updateSuccess); - } else if (localStorage.driveRevoked === 'true') { - _ui.instance.alert( - chrome.i18n.getMessage('token_revoked', ['Google Drive'])); - localStorage.removeItem('driveRevoked'); - _ui.instance.driveToken = ''; - } else { - _ui.instance.alert(_ui.instance.i18n.updateFailure); - } - } - }, - backupUpdateEncryption: (service: string) => { - if (service === 'dropbox') { - localStorage.dropboxEncrypted = _ui.instance.dropboxEncrypted; - } else if (service === 'drive') { - localStorage.driveEncrypted = _ui.instance.driveEncrypted; - } - }, - backupLogout: async (service: string) => { - if (service === 'dropbox') { - _ui.instance.dropboxToken = ''; - await new Promise((resolve: (value: boolean) => void) => { - const xhr = new XMLHttpRequest(); - xhr.open('POST', 'https://api.dropboxapi.com/2/auth/token/revoke'); - xhr.setRequestHeader( - 'Authorization', 'Bearer ' + localStorage.dropboxToken); - xhr.onreadystatechange = () => { - if (xhr.readyState === 4) { - resolve(true); - return; - } - }; - xhr.send(); - }); - } else if (service === 'drive') { - _ui.instance.driveToken = ''; - await new Promise((resolve: (value: boolean) => void) => { - const xhr = new XMLHttpRequest(); - xhr.open( - 'POST', - 'https://accounts.google.com/o/oauth2/revoke?token=' + - localStorage.driveToken); - xhr.onreadystatechange = () => { - if (xhr.readyState === 4) { - if (navigator.userAgent.indexOf('Chrome') !== -1) { - chrome.identity.removeCachedAuthToken( - {token: localStorage.driveToken}, () => { - resolve(true); - }); - } else { - resolve(true); - } - return; - } - }; - xhr.send(); - }); - } - localStorage.removeItem(service + 'Token'); - setTimeout(_ui.instance.closeInfo, 500); - }, - getBackupToken: (service: string) => { - chrome.runtime.sendMessage({action: service}); - }, - runScheduledBackup: (clientTime: number) => { - if (_ui.instance.dropboxToken) { - chrome.permissions.contains( - {origins: ['https://*.dropboxapi.com/*']}, - async (hasPermission) => { - if (hasPermission) { - try { - const dropbox = new Dropbox(); - const res = await dropbox.upload(_ui.instance.encryption); - if (res) { - // we have uploaded backup to Dropbox - // no need to remind - localStorage.lastRemindingBackupTime = clientTime; - return; - } else if (localStorage.dropboxRevoked === 'true') { - _ui.instance.alert( - chrome.i18n.getMessage('token_revoked', ['Dropbox'])); - localStorage.removeItem('dropboxRevoked'); - } - } catch (error) { - // ignore - } - } - _ui.instance.alert(_ui.instance.i18n.remind_backup); - localStorage.lastRemindingBackupTime = clientTime; - }); - } - if (_ui.instance.driveToken) { - chrome.permissions.contains( - { - origins: [ - 'https://www.googleapis.com/*', - 'https://accounts.google.com/o/oauth2/revoke' - ] - }, - async (hasPermission) => { - if (hasPermission) { - try { - const drive = new Drive(); - const res = await drive.upload(_ui.instance.encryption); - if (res) { - localStorage.lastRemindingBackupTime = clientTime; - return; - } else if (localStorage.driveRevoked === 'true') { - _ui.instance.alert(chrome.i18n.getMessage( - 'token_revoked', ['Google Drive'])); - localStorage.removeItem('driveRevoked'); - } - } catch (error) { - // ignore - } - } - _ui.instance.alert(_ui.instance.i18n.remind_backup); - localStorage.lastRemindingBackupTime = clientTime; - }); - } - if (!_ui.instance.driveToken && !_ui.instance.dropboxToken) { - _ui.instance.alert(_ui.instance.i18n.remind_backup); - localStorage.lastRemindingBackupTime = clientTime; - } - } - } - }; - - _ui.update(ui); -} diff --git a/src/ui/class.ts b/src/ui/class.ts deleted file mode 100644 index de9c3ea31..000000000 --- a/src/ui/class.ts +++ /dev/null @@ -1,25 +0,0 @@ -import {UIConfig} from '../models/interface'; - -import {UI} from './ui'; - -export async function className(_ui: UI) { - const ui: UIConfig = { - data: { - currentClass: { - timeout: false, - edit: false, - slidein: false, - slideout: false, - fadein: false, - fadeout: false, - qrfadein: false, - qrfadeout: false, - notificationFadein: false, - notificationFadeout: false, - hotpDiabled: false - } - } - }; - - _ui.update(ui); -} diff --git a/src/ui/entry.ts b/src/ui/entry.ts deleted file mode 100644 index 5190e8158..000000000 --- a/src/ui/entry.ts +++ /dev/null @@ -1,756 +0,0 @@ -import * as CryptoJS from 'crypto-js'; - -import {Encryption} from '../models/encryption'; -import {OTP, OTPStorage, OTPType, UIConfig} from '../models/interface'; -import {OTPEntry} from '../models/otp'; -import {EntryStorage} from '../models/storage'; - -import {insertContentScript} from './add-account'; -import {UI} from './ui'; - -async function getEntries(encryption: Encryption) { - const otpEntries: OTPEntry[] = await EntryStorage.get(encryption); - return otpEntries; -} - -/* tslint:disable-next-line:no-any */ -async function updateCode(app: any) { - let second = new Date().getSeconds(); - if (localStorage.offset) { - // prevent second from negative - second += Number(localStorage.offset) + 60; - } - - second = second % 60; - app.second = second; - - // only when sector is not started (timer is not initialized), - // passphrase box should not be shown (no passphrase set) or - // there are entiries shown and passphrase box isn't shown (the user has - // already provided password) - if (!app.sectorStart && - (!app.shouldShowPassphrase || - app.entries.length > 0 && app.info !== 'passphrase')) { - app.sectorStart = true; - app.sectorOffset = -second; - } - - // if (second > 25) { - // app.class.timeout = true; - // } else { - // app.class.timeout = false; - // } - // if (second < 1) { - // const entries = app.entries as OTP[]; - // for (let i = 0; i < entries.length; i++) { - // if (entries[i].type !== OTPType.hotp && - // entries[i].type !== OTPType.hhex) { - // entries[i].generate(); - // } - // } - // } - const entries = app.entries as OTP[]; - for (let i = 0; i < entries.length; i++) { - if (entries[i].type !== OTPType.hotp && entries[i].type !== OTPType.hhex) { - entries[i].generate(); - } - } -} - -function getBackupFile(entryData: {[hash: string]: OTPStorage}) { - let json = JSON.stringify(entryData, null, 2); - // for windows notepad - json = json.replace(/\n/g, '\r\n'); - const base64Data = - CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(json)); - return `data:application/octet-stream;base64,${base64Data}`; -} - -function removeUnsafeData(data: string) { - return encodeURIComponent(data.split('::')[0].replace(/:/g, '')); -} - -function getOneLineOtpBackupFile(entryData: {[hash: string]: OTPStorage}) { - const otpAuthLines: string[] = []; - for (const hash of Object.keys(entryData)) { - const otpStorage = entryData[hash]; - otpStorage.issuer = removeUnsafeData(otpStorage.issuer); - otpStorage.account = removeUnsafeData(otpStorage.account); - const label = otpStorage.issuer ? - (otpStorage.issuer + ':' + otpStorage.account) : - otpStorage.account; - let type = ''; - if (otpStorage.type === 'totp' || otpStorage.type === 'hex') { - type = 'totp'; - } else if (otpStorage.type === 'hotp' || otpStorage.type === 'hhex') { - type = 'hotp'; - } else { - continue; - } - - const otpAuthLine = 'otpauth://' + type + '/' + label + - '?secret=' + otpStorage.secret + - (otpStorage.issuer ? ('&issuer=' + otpStorage.issuer) : '') + - (type === 'hotp' ? ('&counter=' + otpStorage.counter) : '') + - (type === 'totp' && otpStorage.period ? - ('&period=' + otpStorage.period) : - ''); - - otpAuthLines.push(otpAuthLine); - } - - const base64Data = CryptoJS.enc.Base64.stringify( - CryptoJS.enc.Utf8.parse(otpAuthLines.join('\r\n'))); - return `data:application/octet-stream;base64,${base64Data}`; -} - -export async function getSiteName() { - return new Promise( - (resolve: (value: Array) => void, - reject: (reason: Error) => void) => { - chrome.tabs.query({active: true, lastFocusedWindow: true}, (tabs) => { - const tab = tabs[0]; - if (!tab) { - return resolve([null, null]); - } - - const title = tab.title ? - tab.title.replace(/[^a-z0-9]/ig, '').toLowerCase() : - null; - - if (!tab.url) { - return resolve([title, null]); - } - - const urlParser = document.createElement('a'); - urlParser.href = tab.url; - const hostname = urlParser.hostname.toLowerCase(); - - // try to parse name from hostname - // i.e. hostname is www.example.com - // name should be example - let nameFromDomain = ''; - - // ip address - if (/^\d+\.\d+\.\d+\.\d+$/.test(hostname)) { - nameFromDomain = hostname; - } - - // local network - if (hostname.indexOf('.') === -1) { - nameFromDomain = hostname; - } - - const hostLevelUnits = hostname.split('.'); - - if (hostLevelUnits.length === 2) { - nameFromDomain = hostLevelUnits[0]; - } - - // www.example.com - // example.com.cn - if (hostLevelUnits.length > 2) { - // example.com.cn - if (['com', 'net', 'org', 'edu', 'gov', 'co'].indexOf( - hostLevelUnits[hostLevelUnits.length - 2]) !== -1) { - nameFromDomain = hostLevelUnits[hostLevelUnits.length - 3]; - } else { // www.example.com - nameFromDomain = hostLevelUnits[hostLevelUnits.length - 2]; - } - } - - nameFromDomain = nameFromDomain.replace(/-/g, '').toLowerCase(); - - return resolve([title, nameFromDomain, hostname]); - }); - }); -} - -export function hasMatchedEntry( - siteName: Array, entries: OTPEntry[]) { - if (siteName.length < 2) { - return false; - } - - for (let i = 0; i < entries.length; i++) { - if (isMatchedEntry(siteName, entries[i])) { - return true; - } - } - return false; -} - -function isMatchedEntry(siteName: Array, entry: OTPEntry) { - if (!entry.issuer) { - return false; - } - - const issuerHostMatches = entry.issuer.split('::'); - const issuer = issuerHostMatches[0].replace(/[^0-9a-z]/ig, '').toLowerCase(); - - if (!issuer) { - return false; - } - - const siteTitle = siteName[0] || ''; - const siteNameFromHost = siteName[1] || ''; - const siteHost = siteName[2] || ''; - - if (issuerHostMatches.length > 1) { - if (siteHost && siteHost.indexOf(issuerHostMatches[1]) !== -1) { - return true; - } - } - // site title should be more detailed - // so we use siteTitle.indexOf(issuer) - if (siteTitle && siteTitle.indexOf(issuer) !== -1) { - return true; - } - - if (siteNameFromHost && issuer.indexOf(siteNameFromHost) !== -1) { - return true; - } - - return false; -} - -async function getCachedPassphrase() { - return new Promise( - (resolve: (value: string) => void, reject: (reason: Error) => void) => { - const cookie = document.cookie; - const cookieMatch = - cookie ? document.cookie.match(/passphrase=([^;]*)/) : null; - const cachedPassphrase = - cookieMatch && cookieMatch.length > 1 ? cookieMatch[1] : null; - const cachedPassphraseLocalStorage = localStorage.encodedPhrase ? - CryptoJS.AES.decrypt(localStorage.encodedPhrase, '') - .toString(CryptoJS.enc.Utf8) : - ''; - if (cachedPassphrase || cachedPassphraseLocalStorage) { - return resolve(cachedPassphrase || cachedPassphraseLocalStorage); - } - - chrome.runtime.sendMessage( - {action: 'passphrase'}, (passphrase: string) => { - return resolve(passphrase); - }); - }); -} - -function getEntryDataFromOTPAuthPerLine(importCode: string) { - const lines = importCode.split('\n'); - const exportData: {[hash: string]: OTPStorage} = {}; - for (let item of lines) { - item = item.trim(); - if (!item.startsWith('otpauth:')) { - continue; - } - - let uri = item.split('otpauth://')[1]; - let type = uri.substr(0, 4).toLowerCase(); - uri = uri.substr(5); - let label = uri.split('?')[0]; - const parameterPart = uri.split('?')[1]; - if (!parameterPart) { - continue; - } else { - let account = ''; - let secret = ''; - let issuer = ''; - let period: number|undefined = undefined; - - try { - label = decodeURIComponent(label); - } catch (error) { - console.error(error); - } - if (label.indexOf(':') !== -1) { - issuer = label.split(':')[0]; - account = label.split(':')[1]; - } else { - account = label; - } - const parameters = parameterPart.split('&'); - parameters.forEach((item) => { - const parameter = item.split('='); - if (parameter[0].toLowerCase() === 'secret') { - secret = parameter[1]; - } else if (parameter[0].toLowerCase() === 'issuer') { - try { - issuer = decodeURIComponent(parameter[1]); - } catch { - issuer = parameter[1]; - } - } else if (parameter[0].toLowerCase() === 'counter') { - let counter = Number(parameter[1]); - counter = (isNaN(counter) || counter < 0) ? 0 : counter; - } else if (parameter[0].toLowerCase() === 'period') { - period = Number(parameter[1]); - period = (isNaN(period) || period < 0 || period > 60 || - 60 % period !== 0) ? - undefined : - period; - } - }); - - if (!secret) { - continue; - } else if ( - !/^[0-9a-f]+$/i.test(secret) && !/^[2-7a-z]+=*$/i.test(secret)) { - continue; - } else { - const hash = CryptoJS.MD5(secret).toString(); - if (!/^[2-7a-z]+=*$/i.test(secret) && /^[0-9a-f]+$/i.test(secret) && - type === 'totp') { - type = 'hex'; - } else if ( - !/^[2-7a-z]+=*$/i.test(secret) && /^[0-9a-f]+$/i.test(secret) && - type === 'hotp') { - type = 'hhex'; - } - - exportData[hash] = { - account, - hash, - issuer, - secret, - type, - encrypted: false, - index: 0, - counter: 0 - }; - if (period) { - exportData[hash].period = period; - } - } - } - } - return exportData; -} - -export async function entry(_ui: UI) { - const cachedPassphrase = await getCachedPassphrase(); - const encryption: Encryption = new Encryption(cachedPassphrase); - let shouldShowPassphrase = - cachedPassphrase ? false : await EntryStorage.hasEncryptedEntry(); - const exportData = - shouldShowPassphrase ? {} : await EntryStorage.getExport(encryption); - const exportEncData = shouldShowPassphrase ? - {} : - await EntryStorage.getExport(encryption, true); - const entries = shouldShowPassphrase ? [] : await getEntries(encryption); - - for (let i = 0; i < entries.length; i++) { - if (entries[i].code === 'Encrypted') { - shouldShowPassphrase = true; - break; - } - } - - async function hasUnsupportedAccounts() { - const entries = await EntryStorage.getExport(new Encryption('')); - for (const entry of Object.keys(entries)) { - if (entries[entry].type === 'battle' || entries[entry].type === 'steam') { - console.log(entries[entry]); - return true; - } - } - return false; - } - - const unsupportedAccounts = await hasUnsupportedAccounts(); - - const exportFile = getBackupFile(exportData); - const exportEncryptedFile = getBackupFile(exportEncData); - const exportOneLineOtpAuthFile = getOneLineOtpBackupFile(exportData); - const siteName = await getSiteName(); - const shouldFilter = hasMatchedEntry(siteName, entries); - const showSearch = false; - - const ui: UIConfig = { - data: { - entries, - encryption, - OTPType, - shouldShowPassphrase, - exportData: JSON.stringify(exportData, null, 2), - exportEncData: JSON.stringify(exportEncData, null, 2), - exportFile, - exportEncryptedFile, - exportOneLineOtpAuthFile, - getFilePassphrase: false, - sector: '', - sectorStart: false, - sectorOffset: 0, - second: 0, - notification: '', - notificationTimeout: 0, - filter: true, - shouldFilter, - showSearch, - importType: 'import_file', - importCode: '', - importEncrypted: false, - importPassphrase: '', - importFilePassphrase: '', - unsupportedAccounts, - searchText: '' - }, - methods: { - isMatchedEntry: (entry: OTPEntry) => { - return isMatchedEntry(siteName, entry); - }, - searchListener: (e) => { - if (e.keyCode === 191) { - if (_ui.instance.info !== '') { - return; - } - _ui.instance.filter = false; - // It won't focus the texfield if vue unhides the div - //_ui.instance.showSearch = true; - const searchDiv = document.getElementById('search'); - const searchInput = document.getElementById('searchInput'); - if (!searchInput || !searchDiv) { - return; - } - searchDiv.style.display = 'block'; - searchInput.focus(); - } - }, - searchUpdate: () => { - if (_ui.instance.filter) { - _ui.instance.filter = false; - } - if (!_ui.instance.showSearch) { - _ui.instance.showSearch = true; - } - }, - isSearchedEntry: (entry: OTPEntry) => { - // This gets called before _ui.instance exists sometimes - if (!_ui.instance) { - return true; - } - if (_ui.instance.searchText === '') { - return true; - } - - if (entry.issuer.toLowerCase().includes( - _ui.instance.searchText.toLowerCase()) || - entry.account.toLowerCase().includes( - _ui.instance.searchText.toLowerCase())) { - return true; - } else { - return false; - } - }, - updateCode: async () => { - return await updateCode(_ui.instance); - }, - decryptBackupData: - (backupData: {[hash: string]: OTPStorage}, - passphrase: string|null) => { - const decryptedbackupData: {[hash: string]: OTPStorage} = {}; - for (const hash of Object.keys(backupData)) { - if (typeof backupData[hash] !== 'object') { - continue; - } - if (!backupData[hash].secret) { - continue; - } - if (backupData[hash].encrypted && !passphrase) { - continue; - } - if (backupData[hash].encrypted && passphrase) { - try { - backupData[hash].secret = - CryptoJS.AES.decrypt(backupData[hash].secret, passphrase) - .toString(CryptoJS.enc.Utf8); - backupData[hash].encrypted = false; - } catch (error) { - continue; - } - } - // backupData[hash].secret may be empty after decrypt with wrong - // passphrase - if (!backupData[hash].secret) { - continue; - } - decryptedbackupData[hash] = backupData[hash]; - } - return decryptedbackupData; - }, - importBackupCode: async () => { - let exportData: {[hash: string]: OTPStorage} = {}; - try { - exportData = JSON.parse(_ui.instance.importCode); - - } catch (error) { - // Maybe one-otpauth-per line text - exportData = getEntryDataFromOTPAuthPerLine(_ui.instance.importCode); - } - - try { - const passphrase: string|null = - _ui.instance.importEncrypted && _ui.instance.importPassphrase ? - _ui.instance.importPassphrase : - null; - const decryptedbackupData: {[hash: string]: OTPStorage} = - _ui.instance.decryptBackupData(exportData, passphrase); - if (Object.keys(decryptedbackupData).length) { - await EntryStorage.import( - _ui.instance.encryption, decryptedbackupData); - await _ui.instance.updateEntries(); - alert(_ui.instance.i18n.updateSuccess); - window.close(); - } else { - alert(_ui.instance.i18n.updateFailure); - } - return; - } catch (error) { - throw error; - } - }, - noCopy: (code: string) => { - return code === 'Encrypted' || code === 'Invalid' || - code.startsWith('•'); - }, - updateStorage: async () => { - await EntryStorage.set(_ui.instance.encryption, _ui.instance.entries); - return; - }, - showBulls: (code: string) => { - if (code.startsWith('•')) { - return code; - } - return new Array(code.length).fill('•').join(''); - }, - importEntries: async () => { - await EntryStorage.import( - _ui.instance.encryption, JSON.parse(_ui.instance.exportData)); - await _ui.instance.updateEntries(); - _ui.instance.alert(_ui.instance.i18n.updateSuccess); - return; - }, - updateEntries: async () => { - const exportData = - await EntryStorage.getExport(_ui.instance.encryption); - const exportEncData = - await EntryStorage.getExport(_ui.instance.encryption, true); - _ui.instance.exportData = JSON.stringify(exportData, null, 2); - _ui.instance.entries = await getEntries(_ui.instance.encryption); - _ui.instance.exportFile = getBackupFile(exportData); - _ui.instance.exportEncryptedFile = getBackupFile(exportEncData); - _ui.instance.exportOneLineOtpAuthFile = - getOneLineOtpBackupFile(exportData); - await _ui.instance.updateCode(); - return; - }, - getOldPassphrase: async () => { - _ui.instance.getFilePassphrase = true; - while (true) { - if (_ui.instance.readFilePassphrase) { - if (_ui.instance.importFilePassphrase) { - _ui.instance.readFilePassphrase = false; - break; - } else { - _ui.instance.readFilePassphrase = false; - } - } - await new Promise(resolve => setTimeout(resolve, 250)); - } - return _ui.instance.importFilePassphrase as string; - }, - importFile: (event: Event, closeWindow: boolean) => { - const target = event.target as HTMLInputElement; - if (!target || !target.files) { - return; - } - if (target.files[0]) { - const reader = new FileReader(); - let decryptedFileData: {[hash: string]: OTPStorage} = {}; - reader.onload = async () => { - let importData: {[hash: string]: OTPStorage} = {}; - try { - importData = JSON.parse(reader.result as string); - } catch (e) { - importData = - getEntryDataFromOTPAuthPerLine(reader.result as string); - } - - let encrypted = false; - for (const hash in importData) { - if (importData[hash].encrypted) { - encrypted = true; - try { - const oldPassphrase: string|null = - await _ui.instance.getOldPassphrase(); - decryptedFileData = - _ui.instance.decryptBackupData(importData, oldPassphrase); - break; - } catch { - break; - } - } - } - if (!encrypted) { - decryptedFileData = importData; - } - if (Object.keys(decryptedFileData).length) { - await EntryStorage.import( - _ui.instance.encryption, decryptedFileData); - await _ui.instance.updateEntries(); - alert(_ui.instance.i18n.updateSuccess); - if (closeWindow) { - window.close(); - } - } else { - alert(_ui.instance.i18n.updateFailure); - _ui.instance.getFilePassphrase = false; - _ui.instance.importFilePassphrase = ''; - } - }; - reader.readAsText(target.files[0], 'utf8'); - } else { - _ui.instance.alert(_ui.instance.i18n.updateFailure); - if (closeWindow) { - window.alert(_ui.instance.i18n.updateFailure); - window.close(); - } - } - return; - }, - removeEntry: async (entry: OTPEntry) => { - if (await _ui.instance.confirm(_ui.instance.i18n.confirm_delete)) { - await entry.delete(); - await _ui.instance.updateEntries(); - } - return; - }, - editEntry: () => { - _ui.instance.currentClass.edit = !_ui.instance.currentClass.edit; - if (_ui.instance.filter) { - _ui.instance.filter = false; - } - if (!_ui.instance.currentClass.edit) { - _ui.instance.updateEntries(); - } - const codes = document.getElementById('codes'); - if (codes) { - // wait vue apply changes to dom - setTimeout(() => { - codes.scrollTop = - _ui.instance.currentClass.edit ? codes.scrollHeight : 0; - }, 0); - } - return; - }, - nextCode: async (entry: OTPEntry) => { - if (_ui.instance.currentClass.Diabled) { - return; - } - _ui.instance.currentClass.hotpDiabled = true; - await entry.next(_ui.instance.encryption); - setTimeout(() => { - _ui.instance.currentClass.hotpDiabled = false; - }, 3000); - return; - }, - copyCode: async (entry: OTPEntry) => { - if (_ui.instance.currentClass.edit || entry.code === 'Invalid' || - entry.code.startsWith('•')) { - return; - } - - if (entry.code === 'Encrypted') { - _ui.instance.showInfo('passphrase'); - return; - } - - if (navigator.userAgent.indexOf('Edge') !== -1) { - const codeClipboard = - document.getElementById('codeClipboard') as HTMLInputElement; - if (!codeClipboard) { - return; - } - - if (_ui.instance.useAutofill) { - await insertContentScript(); - - chrome.tabs.query( - {active: true, lastFocusedWindow: true}, (tabs) => { - const tab = tabs[0]; - if (!tab || !tab.id) { - return; - } - - chrome.tabs.sendMessage( - tab.id, {action: 'pastecode', code: entry.code}); - }); - } - - codeClipboard.value = entry.code; - codeClipboard.focus(); - codeClipboard.select(); - document.execCommand('Copy'); - _ui.instance.notification = _ui.instance.i18n.copied; - clearTimeout(_ui.instance.notificationTimeout); - _ui.instance.currentClass.notificationFadein = true; - _ui.instance.currentClass.notificationFadeout = false; - _ui.instance.notificationTimeout = setTimeout(() => { - _ui.instance.currentClass.notificationFadein = false; - _ui.instance.currentClass.notificationFadeout = true; - setTimeout(() => { - _ui.instance.currentClass.notificationFadeout = false; - }, 200); - }, 1000); - } else { - chrome.permissions.request( - {permissions: ['clipboardWrite']}, async (granted) => { - if (granted) { - const codeClipboard = - document.getElementById('codeClipboard') as - HTMLInputElement; - if (!codeClipboard) { - return; - } - - if (_ui.instance.useAutofill) { - await insertContentScript(); - - chrome.tabs.query( - {active: true, lastFocusedWindow: true}, (tabs) => { - const tab = tabs[0]; - if (!tab || !tab.id) { - return; - } - - chrome.tabs.sendMessage( - tab.id, {action: 'pastecode', code: entry.code}); - }); - } - - codeClipboard.value = entry.code; - codeClipboard.focus(); - codeClipboard.select(); - document.execCommand('Copy'); - _ui.instance.notification = _ui.instance.i18n.copied; - clearTimeout(_ui.instance.notificationTimeout); - _ui.instance.currentClass.notificationFadein = true; - _ui.instance.currentClass.notificationFadeout = false; - _ui.instance.notificationTimeout = setTimeout(() => { - _ui.instance.currentClass.notificationFadein = false; - _ui.instance.currentClass.notificationFadeout = true; - setTimeout(() => { - _ui.instance.currentClass.notificationFadeout = false; - }, 200); - }, 1000); - } - }); - } - return; - }, - } - }; - - _ui.update(ui); -} diff --git a/src/ui/i18n.ts b/src/ui/i18n.ts deleted file mode 100644 index abd116bd0..000000000 --- a/src/ui/i18n.ts +++ /dev/null @@ -1,42 +0,0 @@ -import {I18nMessage, UIConfig} from '../models/interface'; - -import {UI} from './ui'; - -export async function loadI18nMessages() { - return new Promise( - (resolve: (value: {[key: string]: string}) => void, - reject: (reason: Error) => void) => { - try { - const xhr = new XMLHttpRequest(); - xhr.overrideMimeType('application/json'); - xhr.onreadystatechange = () => { - if (xhr.readyState === 4) { - const i18nMessage: I18nMessage = JSON.parse(xhr.responseText); - const i18nData: {[key: string]: string} = {}; - for (const key of Object.keys(i18nMessage)) { - i18nData[key] = chrome.i18n.getMessage(key); - } - return resolve(i18nData); - } - return; - }; - xhr.open( - 'GET', chrome.extension.getURL('/_locales/en/messages.json')); - xhr.send(); - } catch (error) { - return reject(error); - } - }); -} - -export async function i18n(_ui: UI) { - const i18n = await loadI18nMessages(); - - const ui: UIConfig = {data: {i18n}}; - - _ui.update(ui); -} - -export async function ri18n() { - return await loadI18nMessages(); -} diff --git a/src/ui/info.ts b/src/ui/info.ts deleted file mode 100644 index 3bfaf1bf1..000000000 --- a/src/ui/info.ts +++ /dev/null @@ -1,88 +0,0 @@ -import {UIConfig} from '../models/interface'; -import {OTPEntry} from '../models/otp'; - -import {UI} from './ui'; - -export async function info(_ui: UI) { - const ui: UIConfig = { - data: {info: ''}, - methods: { - showInfo: (tab: string) => { - if (tab === 'export' || tab === 'security') { - const entries = _ui.instance.entries as OTPEntry[]; - for (let i = 0; i < entries.length; i++) { - // we have encrypted entry - // the current passphrase is incorrect - // cannot export account data - // or change passphrase - if (entries[i].code === 'Encrypted') { - _ui.instance.alert(_ui.instance.i18n.phrase_incorrect); - return; - } - } - } else if (tab === 'dropbox') { - if (localStorage.dropboxEncrypted !== 'true' && - localStorage.dropboxEncrypted !== 'false') { - localStorage.dropboxEncrypted = 'true'; - _ui.instance.dropboxEncrypted = localStorage.dropboxEncrypted; - } - - chrome.permissions.request( - {origins: ['https://*.dropboxapi.com/*']}, async (granted) => { - if (granted) { - _ui.instance.currentClass.fadein = true; - _ui.instance.currentClass.fadeout = false; - _ui.instance.info = tab; - } - return; - }); - return; - } else if (tab === 'drive') { - if (localStorage.driveEncrypted !== 'true' && - localStorage.driveEncrypted !== 'false') { - localStorage.driveEncrypted = 'true'; - _ui.instance.driveEncrypted = localStorage.driveEncrypted; - } - chrome.permissions.request( - { - origins: [ - 'https://www.googleapis.com/*', - 'https://accounts.google.com/o/oauth2/revoke' - ] - }, - async (granted) => { - if (granted) { - _ui.instance.currentClass.fadein = true; - _ui.instance.currentClass.fadeout = false; - _ui.instance.info = tab; - } - return; - }); - return; - } else if (tab === 'storage') { - if (_ui.instance.newStorageLocation !== 'sync' && - _ui.instance.newStorageLocation !== 'local') { - _ui.instance.newStorageLocation = localStorage.storageLocation; - } - } - - _ui.instance.currentClass.fadein = true; - _ui.instance.currentClass.fadeout = false; - _ui.instance.info = tab; - return; - }, - closeInfo: () => { - _ui.instance.currentClass.fadein = false; - _ui.instance.currentClass.fadeout = true; - setTimeout(() => { - _ui.instance.currentClass.fadeout = false; - _ui.instance.info = ''; - _ui.instance.newAccount.show = false; - }, 200); - return; - } - } - }; - - _ui.update(ui); -} diff --git a/src/ui/menu.ts b/src/ui/menu.ts deleted file mode 100644 index 3d6fd48df..000000000 --- a/src/ui/menu.ts +++ /dev/null @@ -1,286 +0,0 @@ -import {UIConfig} from '../models/interface'; -import {ManagedStorage} from '../models/storage'; - -import {UI} from './ui'; - -function getVersion() { - return chrome.runtime.getManifest().version; -} - -export async function syncTimeWithGoogle() { - return new Promise( - (resolve: (value: string) => void, reject: (reason: Error) => void) => { - try { - // @ts-ignore - const xhr = new XMLHttpRequest({'mozAnon': true}); - xhr.open('HEAD', 'https://www.google.com/generate_204'); - const xhrAbort = setTimeout(() => { - xhr.abort(); - return resolve('updateFailure'); - }, 5000); - xhr.onreadystatechange = () => { - if (xhr.readyState === 4) { - clearTimeout(xhrAbort); - const date = xhr.getResponseHeader('date'); - if (!date) { - return resolve('updateFailure'); - } - const serverTime = new Date(date).getTime(); - const clientTime = new Date().getTime(); - const offset = Math.round((serverTime - clientTime) / 1000); - - if (Math.abs(offset) <= 300) { // within 5 minutes - localStorage.offset = - Math.round((serverTime - clientTime) / 1000); - return resolve('updateSuccess'); - } else { - return resolve('clock_too_far_off'); - } - } - }; - xhr.send(); - } catch (error) { - return reject(error); - } - }); -} - -function resize(zoom: number) { - if (zoom !== 100) { - document.body.style.marginBottom = 480 * (zoom / 100 - 1) + 'px'; - document.body.style.marginRight = 320 * (zoom / 100 - 1) + 'px'; - document.body.style.transform = 'scale(' + (zoom / 100) + ')'; - } -} - -async function openHelp() { - let url = 'https://authenticator.cc/docs/en/chrome-issues'; - - if (navigator.userAgent.indexOf('Firefox') !== -1) { - url = 'https://authenticator.cc/docs/en/firefox-issues'; - } else if (navigator.userAgent.indexOf('Edge') !== -1) { - url = 'https://authenticator.cc/docs/en/edge-issues'; - } - - const feedbackURL = await ManagedStorage.get('feedbackURL'); - if (typeof feedbackURL === 'string' && feedbackURL) { - url = feedbackURL; - } - - chrome.tabs.create({url}); -} - -let backupDisabled: boolean|string; -let storageArea: boolean|string; - -ManagedStorage.get('disableBackup').then((value) => { - backupDisabled = value; -}); - -ManagedStorage.get('storageArea').then((value) => { - storageArea = value; -}); - -export async function menu(_ui: UI) { - const version = getVersion(); - const zoom = Number(localStorage.zoom) || 100; - resize(zoom); - let useAutofill = (localStorage.autofill === 'true'); - let useHighContrast = (localStorage.highContrast === 'true'); - - const ui: UIConfig = { - data: { - version, - zoom, - useAutofill, - useHighContrast, - newStorageLocation: localStorage.storageLocation, - backupDisabled, - storageArea - }, - methods: { - openLink: (url: string) => { - window.open(url, '_blank'); - return; - }, - createWindow: (url: string) => { - chrome.windows.create({type: 'normal', url}); - return; - }, - showMenu: () => { - _ui.instance.currentClass.slidein = true; - _ui.instance.currentClass.slideout = false; - return; - }, - closeMenu: () => { - _ui.instance.currentClass.slidein = false; - _ui.instance.currentClass.slideout = true; - setTimeout(() => { - _ui.instance.currentClass.slideout = false; - }, 200); - return; - }, - openHelp: () => { - openHelp(); - return; - }, - clearFilter: () => { - _ui.instance.filter = false; - if (_ui.instance.entries.length >= 10) { - _ui.instance.showSearch = true; - } - return; - }, - isChrome: () => { - if (navigator.userAgent.indexOf('Chrome') !== -1) { - return true; - } else { - return false; - } - }, - isEdge: () => { - if (navigator.userAgent.indexOf('Edge') !== -1) { - return true; - } else { - return false; - } - }, - showEdgeBugWarning: () => { - _ui.instance.alert( - 'Due to a bug in Edge, downloading backups is not supported at this time. More info on feedback page.'); - }, - saveAutofill: () => { - localStorage.autofill = _ui.instance.useAutofill; - useAutofill = - (localStorage.autofill === 'true') ? true : false || false; - return; - }, - saveHighContrast: () => { - localStorage.highContrast = _ui.instance.useHighContrast; - useHighContrast = - (localStorage.highContrast === 'true') ? true : false || false; - return; - }, - saveZoom: () => { - localStorage.zoom = _ui.instance.zoom; - resize(_ui.instance.zoom); - return; - }, - syncClock: async () => { - if (navigator.userAgent.indexOf('Edge') !== -1) { - const message = await syncTimeWithGoogle(); - _ui.instance.alert(_ui.instance.i18n[message]); - } else { - chrome.permissions.request( - {origins: ['https://www.google.com/']}, async (granted) => { - if (granted) { - const message = await syncTimeWithGoogle(); - _ui.instance.alert(_ui.instance.i18n[message]); - } - return; - }); - } - return; - }, - popOut: () => { - let windowType; - if (navigator.userAgent.indexOf('Firefox') !== -1) { - windowType = 'detached_panel'; - } else if (navigator.userAgent.indexOf('Edge') !== -1) { - windowType = 'popup'; - } else { - windowType = 'panel'; - } - chrome.windows.create({ - url: chrome.extension.getURL('view/popup.html?popup=true'), - type: windowType, - height: window.innerHeight, - width: window.innerWidth - }); - }, - isPopup: () => { - const params = - new URLSearchParams(document.location.search.substring(1)); - return params.get('popup'); - }, - fixPopupSize: () => { - const zoom = Number(localStorage.zoom) / 100 || 1; - const correctHeight = 480 * zoom; - const correctWidth = 320 * zoom; - if (window.innerHeight !== correctHeight || - window.innerWidth !== correctWidth) { - // window update to correct size - const adjustedHeight = - correctHeight + (window.outerHeight - window.innerHeight); - const adjustedWidth = - correctWidth + (window.outerWidth - window.innerWidth); - chrome.windows.update( - chrome.windows.WINDOW_ID_CURRENT, - {height: adjustedHeight, width: adjustedWidth}); - } - }, - migrateStorage: async () => { - // sync => local - if (localStorage.storageLocation === 'sync' && - _ui.instance.newStorageLocation === 'local') { - return new Promise((resolve, reject) => { - chrome.storage.sync.get(syncData => { - chrome.storage.local.set(syncData, () => { - chrome.storage.local.get((localData) => { - // Double check if data was set - if (Object.keys(syncData).every( - (value) => - Object.keys(localData).indexOf(value) >= 0)) { - localStorage.storageLocation = 'local'; - chrome.storage.sync.clear(); - _ui.instance.alert(_ui.instance.i18n.updateSuccess); - resolve(); - return; - } else { - _ui.instance.alert( - _ui.instance.i18n.updateFailure + - ' All data not transferred successfully.'); - reject('Transfer failure'); - return; - } - }); - }); - }); - }); - // local => sync - } else if ( - localStorage.storageLocation === 'local' && - _ui.instance.newStorageLocation === 'sync') { - return new Promise((resolve, reject) => { - chrome.storage.local.get(localData => { - chrome.storage.sync.set(localData, () => { - chrome.storage.sync.get((syncData) => { - // Double check if data was set - if (Object.keys(localData).every( - (value) => - Object.keys(syncData).indexOf(value) >= 0)) { - localStorage.storageLocation = 'sync'; - chrome.storage.local.clear(); - _ui.instance.alert(_ui.instance.i18n.updateSuccess); - resolve(); - return; - } else { - _ui.instance.alert( - _ui.instance.i18n.updateFailure + - ' All data not transferred successfully.'); - reject('Transfer failure'); - return; - } - }); - }); - }); - }); - } else { - return; - } - } - } - }; - - _ui.update(ui); -} diff --git a/src/ui/message.ts b/src/ui/message.ts deleted file mode 100644 index 84778a8c8..000000000 --- a/src/ui/message.ts +++ /dev/null @@ -1,52 +0,0 @@ -import {UIConfig} from '../models/interface'; - -import {UI} from './ui'; - -function isCustomEvent(event: Event): event is CustomEvent { - return 'detail' in event; -} - -export async function message(_ui: UI) { - const ui: UIConfig = { - data: {message: [], messageIdle: true, confirmMessage: ''}, - methods: { - alert: (message: string) => { - _ui.instance.message.unshift(message); - }, - closeAlert: () => { - _ui.instance.messageIdle = false; - _ui.instance.message.shift(); - setTimeout(() => { - _ui.instance.messageIdle = true; - }, 200); - }, - confirm: async (message: string) => { - return new Promise( - (resolve: (value: boolean) => void, - reject: (reason: Error) => void) => { - _ui.instance.confirmMessage = message; - window.addEventListener('confirm', (event) => { - _ui.instance.confirmMessage = ''; - if (!isCustomEvent(event)) { - return resolve(false); - } - return resolve(event.detail); - }); - return; - }); - }, - confirmOK: () => { - const confirmEvent = new CustomEvent('confirm', {detail: true}); - window.dispatchEvent(confirmEvent); - return; - }, - confirmCancel: () => { - const confirmEvent = new CustomEvent('confirm', {detail: false}); - window.dispatchEvent(confirmEvent); - return; - } - } - }; - - _ui.update(ui); -} diff --git a/src/ui/passphrase.ts b/src/ui/passphrase.ts deleted file mode 100644 index 70c4dc629..000000000 --- a/src/ui/passphrase.ts +++ /dev/null @@ -1,58 +0,0 @@ -import {UIConfig} from '../models/interface'; - -import {getSiteName, hasMatchedEntry} from './entry'; -import {UI} from './ui'; - -function cachePassword(password: string) { - document.cookie = 'passphrase=' + password; - chrome.runtime.sendMessage({action: 'cachePassphrase', value: password}); -} - -export async function passphrase(_ui: UI) { - const ui: UIConfig = { - data: {passphrase: ''}, - methods: { - lock: () => { - document.cookie = 'passphrase=";expires=Thu, 01 Jan 1970 00:00:00 GMT"'; - chrome.runtime.sendMessage({action: 'lock'}, window.close); - return; - }, - removePassphrase: async () => { - _ui.instance.newPassphrase.phrase = ''; - _ui.instance.newPassphrase.confirm = ''; - await _ui.instance.changePassphrase(); - return; - }, - applyPassphrase: async () => { - if (!_ui.instance.passphrase) { - return; - } - _ui.instance.encryption.updateEncryptionPassword( - _ui.instance.passphrase); - await _ui.instance.updateEntries(); - const siteName = await getSiteName(); - _ui.instance.shouldFilter = - hasMatchedEntry(siteName, _ui.instance.entries); - _ui.instance.closeInfo(); - cachePassword(_ui.instance.passphrase); - return; - }, - changePassphrase: async () => { - if (_ui.instance.newPassphrase.phrase !== - _ui.instance.newPassphrase.confirm) { - _ui.instance.alert(_ui.instance.i18n.phrase_not_match); - return; - } - _ui.instance.encryption.updateEncryptionPassword( - _ui.instance.newPassphrase.phrase); - cachePassword(_ui.instance.newPassphrase.phrase); - await _ui.instance.importEntries(); - // remove cached passphrase in old version - localStorage.removeItem('encodedPhrase'); - return; - } - } - }; - - _ui.update(ui); -} diff --git a/src/ui/qr.ts b/src/ui/qr.ts deleted file mode 100644 index 530abb304..000000000 --- a/src/ui/qr.ts +++ /dev/null @@ -1,61 +0,0 @@ -import * as QRGen from 'qrcode-generator'; - -import {OTPType, UIConfig} from '../models/interface'; -import {OTPEntry} from '../models/otp'; - -import {UI} from './ui'; - -async function getQrUrl(entry: OTPEntry) { - return new Promise( - (resolve: (value: string) => void, reject: (reason: Error) => void) => { - const label = - entry.issuer ? (entry.issuer + ':' + entry.account) : entry.account; - const type = entry.type === OTPType.hex ? - OTPType[OTPType.totp] : - (entry.type === OTPType.hhex ? OTPType[OTPType.hotp] : - OTPType[entry.type]); - const otpauth = 'otpauth://' + type + '/' + label + - '?secret=' + entry.secret + - (entry.issuer ? ('&issuer=' + entry.issuer.split('::')[0]) : '') + - ((entry.type === OTPType.hotp || entry.type === OTPType.hhex) ? - ('&counter=' + entry.counter) : - '') + - (entry.type === OTPType.totp && entry.period ? - ('&period=' + entry.period) : - ''); - const qr = QRGen(0, 'L'); - qr.addData(otpauth); - qr.make(); - resolve(qr.createDataURL(5)); - return; - }); -} - -export async function qr(_ui: UI) { - const ui: UIConfig = { - data: {qr: ''}, - methods: { - shouldShowQrIcon: (entry: OTPEntry) => { - return entry.secret !== 'Encrypted' && entry.type !== OTPType.battle && - entry.type !== OTPType.steam; - }, - showQr: async (entry: OTPEntry) => { - const qrUrl = await getQrUrl(entry); - _ui.instance.qr = `url(${qrUrl})`; - _ui.instance.currentClass.qrfadein = true; - _ui.instance.currentClass.qrfadeout = false; - return; - }, - hideQr: () => { - _ui.instance.currentClass.qrfadein = false; - _ui.instance.currentClass.qrfadeout = true; - setTimeout(() => { - _ui.instance.currentClass.qrfadeout = false; - }, 200); - return; - } - } - }; - - _ui.update(ui); -} diff --git a/src/ui/ui.ts b/src/ui/ui.ts deleted file mode 100644 index d3cff043e..000000000 --- a/src/ui/ui.ts +++ /dev/null @@ -1,83 +0,0 @@ -import Vue, {Component} from 'vue'; -// @ts-ignore -import {Vue2Dragula} from 'vue2-dragula'; -import {UIConfig} from '../models/interface'; -import {OTPEntry} from '../models/otp'; - -export class UI { - private ui: UIConfig; - private modules: Array<(ui: UI) => void> = []; - /* tslint:disable-next-line:no-any */ - private componenet: any; - // Vue instance - /* tslint:disable-next-line:no-any */ - instance: any; - - /* tslint:disable-next-line:no-any */ - constructor(componenet: any, ui: UIConfig) { - this.ui = ui; - this.componenet = Vue.extend(componenet); - } - - update(ui: UIConfig) { - if (ui.data) { - this.ui.data = this.ui.data || {}; - for (const key of Object.keys(ui.data)) { - this.ui.data[key] = ui.data[key]; - } - } - - if (ui.methods) { - this.ui.methods = this.ui.methods || {}; - for (const key of Object.keys(ui.methods)) { - this.ui.methods[key] = ui.methods[key]; - } - } - } - - load(module: (ui: UI) => void) { - this.modules.push(module); - return this; - } - - async render() { - for (let i = 0; i < this.modules.length; i++) { - await this.modules[i](this); - } - Vue.use(Vue2Dragula); - this.ui.mounted = () => { - // @ts-ignore - Vue.$dragula.$service.eventBus.$on('drop', async () => { - // wait for this.instance.entries sync from dom - setTimeout(async () => { - let needUpdate = false; - for (let i = 0; i < this.instance.entries.length; i++) { - const entry: OTPEntry = this.instance.entries[i]; - if (entry.index !== i) { - needUpdate = true; - entry.index = i; - } - } - - if (needUpdate) { - await this.instance.updateStorage(); - } - return; - }, 0); - return; - }); - }; - - this.instance = new this.componenet(this.ui); - - // wait for all modules loaded - setTimeout(() => { - this.instance.updateCode(); - setInterval(async () => { - await this.instance.updateCode(); - }, 1000); - }, 0); - - return this.instance; - } -} diff --git a/src/view/import.vue b/src/view/import.vue deleted file mode 100644 index 79d2319e4..000000000 --- a/src/view/import.vue +++ /dev/null @@ -1,50 +0,0 @@ - diff --git a/src/view/popup.vue b/src/view/popup.vue deleted file mode 100644 index a8047f784..000000000 --- a/src/view/popup.vue +++ /dev/null @@ -1,257 +0,0 @@ - diff --git a/svg/arrow-left.svg b/svg/arrow-left.svg new file mode 100644 index 000000000..350ab68e0 --- /dev/null +++ b/svg/arrow-left.svg @@ -0,0 +1,4 @@ + + arrow-left + + diff --git a/svg/bars.svg b/svg/bars.svg new file mode 100644 index 000000000..78c290f45 --- /dev/null +++ b/svg/bars.svg @@ -0,0 +1,4 @@ + + Bars + + diff --git a/svg/check.svg b/svg/check.svg new file mode 100644 index 000000000..582fe7aa7 --- /dev/null +++ b/svg/check.svg @@ -0,0 +1,4 @@ + + Check + + diff --git a/svg/code.svg b/svg/code.svg new file mode 100644 index 000000000..621745ac6 --- /dev/null +++ b/svg/code.svg @@ -0,0 +1,4 @@ + + Code + + diff --git a/svg/cog.svg b/svg/cog.svg new file mode 100644 index 000000000..d8fae9a6d --- /dev/null +++ b/svg/cog.svg @@ -0,0 +1,4 @@ + + cog + + diff --git a/svg/comments.svg b/svg/comments.svg new file mode 100644 index 000000000..d7f766cf2 --- /dev/null +++ b/svg/comments.svg @@ -0,0 +1,4 @@ + + comments + + diff --git a/svg/database.svg b/svg/database.svg new file mode 100644 index 000000000..9a8bdd16b --- /dev/null +++ b/svg/database.svg @@ -0,0 +1,4 @@ + + Database + + diff --git a/svg/exchange.svg b/svg/exchange.svg new file mode 100644 index 000000000..21993d9ee --- /dev/null +++ b/svg/exchange.svg @@ -0,0 +1,4 @@ + + Alternate Exchange + + diff --git a/svg/globe.svg b/svg/globe.svg new file mode 100644 index 000000000..5396b5e6b --- /dev/null +++ b/svg/globe.svg @@ -0,0 +1,4 @@ + + Globe with Americas shown + + diff --git a/svg/info.svg b/svg/info.svg new file mode 100644 index 000000000..c8b400df4 --- /dev/null +++ b/svg/info.svg @@ -0,0 +1,4 @@ + + Info + + diff --git a/svg/lock.svg b/svg/lock.svg new file mode 100644 index 000000000..8fd9883e7 --- /dev/null +++ b/svg/lock.svg @@ -0,0 +1,4 @@ + + lock + + diff --git a/svg/minus-circle.svg b/svg/minus-circle.svg new file mode 100644 index 000000000..b7bbcc022 --- /dev/null +++ b/svg/minus-circle.svg @@ -0,0 +1,4 @@ + + Minus Circle + + diff --git a/svg/pencil.svg b/svg/pencil.svg new file mode 100644 index 000000000..36edcba56 --- /dev/null +++ b/svg/pencil.svg @@ -0,0 +1,4 @@ + + Alternate Pencil + + diff --git a/svg/plus.svg b/svg/plus.svg new file mode 100644 index 000000000..f484c9e85 --- /dev/null +++ b/svg/plus.svg @@ -0,0 +1,4 @@ + + plus + + diff --git a/svg/qrcode.svg b/svg/qrcode.svg new file mode 100644 index 000000000..0873399b0 --- /dev/null +++ b/svg/qrcode.svg @@ -0,0 +1,4 @@ + + qrcode + + diff --git a/svg/redo.svg b/svg/redo.svg new file mode 100644 index 000000000..a8d6a7489 --- /dev/null +++ b/svg/redo.svg @@ -0,0 +1,4 @@ + + Alternate Redo + + diff --git a/svg/scan.svg b/svg/scan.svg new file mode 100644 index 000000000..17075b108 --- /dev/null +++ b/svg/scan.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/svg/sync.svg b/svg/sync.svg new file mode 100644 index 000000000..109f9446f --- /dev/null +++ b/svg/sync.svg @@ -0,0 +1,4 @@ + + Alternate Sync + + diff --git a/svg/wrench.svg b/svg/wrench.svg new file mode 100644 index 000000000..ff29e231d --- /dev/null +++ b/svg/wrench.svg @@ -0,0 +1,4 @@ + + Wrench + + diff --git a/svg/x-circle.svg b/svg/x-circle.svg new file mode 100644 index 000000000..d352ccf4a --- /dev/null +++ b/svg/x-circle.svg @@ -0,0 +1,4 @@ + + Times Circle + + diff --git a/tsconfig.json b/tsconfig.json index 15280d2ed..29b158b78 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,14 +2,16 @@ "extends": "./node_modules/gts/tsconfig-google.json", "compilerOptions": { "lib": ["es6", "dom"], - "target": "es6", + "target": "es5", "strict": true, + "module": "es2015", "rootDir": "src", + "moduleResolution": "node", "outDir": "build" }, "include": [ "src/*.ts", - "src/**/*.ts" + "src/**/*" ], "exclude": [ "node_modules" diff --git a/webpack.config.js b/webpack.config.js index b4657d92b..450109374 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,5 +1,6 @@ const path = require('path'); const VueLoaderPlugin = require('vue-loader/lib/plugin'); +const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); module.exports = { mode: 'development', @@ -17,20 +18,45 @@ module.exports = { rules: [ { test: /\.tsx?$/, - use: 'ts-loader', + loader: 'ts-loader', + options: { + appendTsSuffixTo: [/\.vue$/], + transpileOnly: true + }, exclude: /node_modules/ }, { test: /\.vue$/, loader: 'vue-loader' + }, + { + test: /\.svg$/, + loader: 'vue-svg-loader' } ] }, plugins: [ - new VueLoaderPlugin() + new VueLoaderPlugin(), + new ForkTsCheckerWebpackPlugin( + { + vue: true + } + ) ], resolve: { - extensions: ['.vue', '.tsx', '.ts', '.js'], + extensions: [ + '.mjs', + '.js', + '.jsx', + '.vue', + '.json', + '.wasm', + '.ts', + '.tsx' + ], + modules: [ + 'node_modules' + ] }, output: { path: path.resolve(__dirname, 'dist')