From 35543489e565e363def54c9cc2db73c5855909f6 Mon Sep 17 00:00:00 2001 From: Juan Sebastian velez Posada Date: Wed, 22 Feb 2023 08:11:16 -0500 Subject: [PATCH] Propmt when a api key can not be use + house keeping (#173) * chore: remove old files * chore: change JS tests to TS, delete dummy tests * chore: bump manifests version --- .vscode/settings.json | 3 + assets/js/app.jsx | 11 - assets/js/components/Alert.jsx | 11 - assets/js/components/MainList.jsx | 112 ------- assets/js/components/NavBar.jsx | 102 ------ assets/js/components/Options.jsx | 231 -------------- assets/js/components/SitesList.jsx | 44 --- assets/js/components/WakaTime.jsx | 163 ---------- assets/js/config.js | 60 ---- assets/js/core/WakaTimeCore.js | 300 ------------------ assets/js/devtools.js | 12 - assets/js/events.js | 101 ------ assets/js/helpers/changeExtensionIcon.js | 49 --- assets/js/helpers/changeExtensionState.js | 42 --- assets/js/helpers/changeExtensionTooltip.js | 20 -- assets/js/helpers/contains.js | 30 -- assets/js/helpers/getDomainFromUrl.js | 13 - assets/js/helpers/in_array.js | 18 -- assets/js/options.jsx | 13 - devtools.html | 3 - manifest.json | 43 --- options.html | 17 - package-lock.json | 13 + package.json | 1 + popup.html | 17 - src/components/WakaTime.tsx | 18 +- src/manifests/chrome.json | 2 +- src/manifests/firefox.json | 2 +- src/utils/changeExtensionState.ts | 2 +- tests/components/Alert.react.jest.js | 17 - tests/components/MainList.react.jest.js | 17 - tests/components/Navbar.react.jest.js | 17 - tests/components/Options.react.jest.js | 17 - tests/components/SitesList.react.jest.js | 17 - tests/components/Wakatime.react.jest.js | 17 - tests/helpers/changeExtensionIcon.spec.js | 10 - tests/helpers/changeExtensionState.spec.js | 13 - tests/helpers/in_array.spec.js | 18 -- .../Chrome.spec.js => utils/Chrome.spec.ts} | 9 +- tests/utils/changeExtensionIcon.spec.ts | 20 ++ tests/utils/changeExtensionState.spec.ts | 20 ++ .../changeExtensionTooltip.spec.ts} | 19 +- .../contains.spec.ts} | 14 +- .../getDomainFromUrl.spec.ts} | 6 +- tests/utils/in_array.spec.ts | 18 ++ xclap.ts | 41 +-- 46 files changed, 132 insertions(+), 1611 deletions(-) create mode 100644 .vscode/settings.json delete mode 100644 assets/js/app.jsx delete mode 100644 assets/js/components/Alert.jsx delete mode 100644 assets/js/components/MainList.jsx delete mode 100644 assets/js/components/NavBar.jsx delete mode 100644 assets/js/components/Options.jsx delete mode 100644 assets/js/components/SitesList.jsx delete mode 100644 assets/js/components/WakaTime.jsx delete mode 100644 assets/js/config.js delete mode 100644 assets/js/core/WakaTimeCore.js delete mode 100644 assets/js/devtools.js delete mode 100644 assets/js/events.js delete mode 100644 assets/js/helpers/changeExtensionIcon.js delete mode 100644 assets/js/helpers/changeExtensionState.js delete mode 100644 assets/js/helpers/changeExtensionTooltip.js delete mode 100644 assets/js/helpers/contains.js delete mode 100644 assets/js/helpers/getDomainFromUrl.js delete mode 100644 assets/js/helpers/in_array.js delete mode 100644 assets/js/options.jsx delete mode 100644 devtools.html delete mode 100644 manifest.json delete mode 100644 options.html delete mode 100644 popup.html delete mode 100644 tests/components/Alert.react.jest.js delete mode 100644 tests/components/MainList.react.jest.js delete mode 100644 tests/components/Navbar.react.jest.js delete mode 100644 tests/components/Options.react.jest.js delete mode 100644 tests/components/SitesList.react.jest.js delete mode 100644 tests/components/Wakatime.react.jest.js delete mode 100644 tests/helpers/changeExtensionIcon.spec.js delete mode 100644 tests/helpers/changeExtensionState.spec.js delete mode 100644 tests/helpers/in_array.spec.js rename tests/{helpers/Chrome.spec.js => utils/Chrome.spec.ts} (62%) create mode 100644 tests/utils/changeExtensionIcon.spec.ts create mode 100644 tests/utils/changeExtensionState.spec.ts rename tests/{helpers/changeExtensionTooltip.spec.js => utils/changeExtensionTooltip.spec.ts} (60%) rename tests/{helpers/contains.spec.js => utils/contains.spec.ts} (59%) rename tests/{helpers/getDomainFromUrl.spec.js => utils/getDomainFromUrl.spec.ts} (82%) create mode 100644 tests/utils/in_array.spec.ts diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..be8857d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.codeActionsOnSave": ["source.organizeImports", "source.fixAll"] +} diff --git a/assets/js/app.jsx b/assets/js/app.jsx deleted file mode 100644 index 6da1c43..0000000 --- a/assets/js/app.jsx +++ /dev/null @@ -1,11 +0,0 @@ -/* This is a fix for Bootstrap requiring jQuery */ -global.jQuery = require('jquery'); -require('bootstrap'); - -var React = require('react'); -var ReactDOM = require('react-dom'); - -// React components -var WakaTime = require('./components/WakaTime.jsx'); - -ReactDOM.render(, document.getElementById('wakatime')); diff --git a/assets/js/components/Alert.jsx b/assets/js/components/Alert.jsx deleted file mode 100644 index 04e121f..0000000 --- a/assets/js/components/Alert.jsx +++ /dev/null @@ -1,11 +0,0 @@ -var React = require('react'); -var reactCreateClass = require('create-react-class'); -var classNames = require('classnames'); - -var Alert = reactCreateClass({ - render: function () { - return
{this.props.text}
; - }, -}); - -module.exports = Alert; diff --git a/assets/js/components/MainList.jsx b/assets/js/components/MainList.jsx deleted file mode 100644 index 7098203..0000000 --- a/assets/js/components/MainList.jsx +++ /dev/null @@ -1,112 +0,0 @@ -/* global browser */ - -var React = require('react'); -var reactCreateClass = require('create-react-class'); -var MainList = reactCreateClass({ - _openOptionsPage: function () { - if (browser.runtime.openOptionsPage) { - // New way to open options pages, if supported (Chrome 42+). - browser.runtime.openOptionsPage(); - } else { - // Reasonable fallback. - window.open(browser.runtime.getURL('options.html')); - } - }, - - render: function () { - var that = this; - - var loginLogoutButton = function () { - if (that.props.loggedIn === true) { - return ( -
- - - Logout - -
- ); - } - - return ( - - - Login - - ); - }; - - // If logging is enabled, display that info to user - var loggingStatus = function () { - if (that.props.loggingEnabled === true && that.props.loggedIn === true) { - return ( -
-
-

- - Disable logging - -

-
-
- ); - } else if (that.props.loggingEnabled === false && that.props.loggedIn === true) { - return ( -
-
-

- - Enable logging - -

-
-
- ); - } - }; - - var totalTimeLoggedToday = function () { - if (that.props.loggedIn === true) { - return ( -
-
-
-

{that.props.totalTimeLoggedToday}

- - TOTAL TIME LOGGED TODAY - -
-
-
- ); - } - }; - - return ( -
- {totalTimeLoggedToday()} - - {loggingStatus()} - -
- - - Options - - - {loginLogoutButton()} -
-
- ); - }, -}); - -module.exports = MainList; diff --git a/assets/js/components/NavBar.jsx b/assets/js/components/NavBar.jsx deleted file mode 100644 index 790a173..0000000 --- a/assets/js/components/NavBar.jsx +++ /dev/null @@ -1,102 +0,0 @@ -var React = require('react'); -var reactCreateClass = require('create-react-class'); - -var NavBar = reactCreateClass({ - render: function () { - var that = this; - - var signedInAs = function () { - if (that.props.loggedIn === true) { - return ( -

- Signed in as {that.props.user.full_name} -

- ); - } - }; - - var dashboard = function () { - if (that.props.loggedIn === true) { - return ( -
  • - - - Dashboard - -
  • - ); - } - }; - - var customRules = function () { - if (that.props.loggedIn === true) { - return ( -
  • - - - Custom Rules - -
  • - ); - } - }; - - return ( - - ); - }, -}); - -module.exports = NavBar; diff --git a/assets/js/components/Options.jsx b/assets/js/components/Options.jsx deleted file mode 100644 index 419675a..0000000 --- a/assets/js/components/Options.jsx +++ /dev/null @@ -1,231 +0,0 @@ -/* global browser */ - -var React = require('react'); -var reactCreateClass = require('create-react-class'); -var ReactCSSTransitionGroup = require('react-transition-group/CSSTransitionGroup'); - -var config = require('../config'); - -// React components -var Alert = require('./Alert.jsx'); -var SitesList = require('./SitesList.jsx'); - -/** - * One thing to keep in mind is that you cannot use this.refs.blacklist if - * the blacklist select box is not being rendered on the form. - * - * @type {*|Function} - */ -var Options = reactCreateClass({ - getInitialState: function () { - return { - theme: config.theme, - blacklist: '', - whitelist: '', - loggingType: config.loggingType, - loggingStyle: config.loggingStyle, - displayAlert: false, - alertType: config.alert.success.type, - alertText: config.alert.success.text, - }; - }, - - componentDidMount: function () { - this.restoreSettings(); - }, - - restoreSettings: function () { - var that = this; - - browser.storage.sync - .get({ - theme: config.theme, - blacklist: '', - whitelist: '', - loggingType: config.loggingType, - loggingStyle: config.loggingStyle, - }) - .then(function (items) { - that.setState({ - theme: items.theme, - blacklist: items.blacklist, - whitelist: items.whitelist, - loggingType: items.loggingType, - loggingStyle: items.loggingStyle, - }); - - that.refs.theme.value = items.theme; - that.refs.loggingType.value = items.loggingType; - that.refs.loggingStyle.value = items.loggingStyle; - }); - }, - - _handleSubmit: function (e) { - e.preventDefault(); - - this.saveSettings(); - }, - - saveSettings: function () { - var that = this; - - var theme = this.refs.theme.value.trim(); - var loggingType = this.refs.loggingType.value.trim(); - var loggingStyle = this.refs.loggingStyle.value.trim(); - // Trimming blacklist and whitelist removes blank lines and spaces. - var blacklist = that.state.blacklist.trim(); - var whitelist = that.state.whitelist.trim(); - - // Sync options with google storage. - browser.storage.sync - .set({ - theme: theme, - blacklist: blacklist, - whitelist: whitelist, - loggingType: loggingType, - loggingStyle: loggingStyle, - }) - .then(function () { - // Set state to be newly entered values. - that.setState({ - theme: theme, - blacklist: blacklist, - whitelist: whitelist, - loggingType: loggingType, - loggingStyle: loggingStyle, - displayAlert: true, - }); - }); - }, - - _displayBlackOrWhiteList: function () { - var loggingStyle = this.refs.loggingStyle.value.trim(); - - this.setState({ loggingStyle: loggingStyle }); - }, - - _updateBlacklistState: function (sites) { - this.setState({ - blacklist: sites, - }); - }, - - _updateWhitelistState: function (sites) { - this.setState({ - whitelist: sites, - }); - }, - - render: function () { - var that = this; - - var alert = function () { - if (that.state.displayAlert === true) { - setTimeout(function () { - that.setState({ displayAlert: false }); - }, 2000); - - return ( - - ); - } - }; - - var loggingStyle = function () { - if (that.state.loggingStyle == 'blacklist') { - return ( - - ); - } - - return ( - - ); - }; - - return ( -
    -
    -
    - - {alert()} - - -
    -
    - - -
    - -
    -
    - - {loggingStyle()} - -
    - - -
    - -
    -
    - -
    - - -
    - -
    -
    - -
    -
    - -
    -
    -
    -
    -
    -
    - ); - }, -}); - -module.exports = Options; diff --git a/assets/js/components/SitesList.jsx b/assets/js/components/SitesList.jsx deleted file mode 100644 index 8aeaf60..0000000 --- a/assets/js/components/SitesList.jsx +++ /dev/null @@ -1,44 +0,0 @@ -var React = require('react'); -var reactCreateClass = require('create-react-class'); - -var SitesList = reactCreateClass({ - getDefaultProps: function () { - return { - placeholder: 'http://google.com', - }; - }, - - _handleChange: function (event) { - var sites = event.target.value; - - this.props.handleChange(sites); - }, - - render: function () { - return ( -
    - - -
    - - - {this.props.helpText} -
    - One line per site. -
    -
    -
    - ); - }, -}); - -module.exports = SitesList; diff --git a/assets/js/components/WakaTime.jsx b/assets/js/components/WakaTime.jsx deleted file mode 100644 index d953c0b..0000000 --- a/assets/js/components/WakaTime.jsx +++ /dev/null @@ -1,163 +0,0 @@ -/* global browser */ - -var React = require('react'); -var reactCreateClass = require('create-react-class'); -var $ = require('jquery'); - -var config = require('../config'); - -// React components -var NavBar = require('./NavBar.jsx'); -var MainList = require('./MainList.jsx'); - -// Core -var WakaTimeCore = require('../core/WakaTimeCore').default; - -// Helpers -var changeExtensionState = require('../helpers/changeExtensionState'); - -var Wakatime = reactCreateClass({ - getInitialState: function () { - return { - user: { - full_name: null, - email: null, - photo: null, - }, - loggedIn: false, - loggingEnabled: config.loggingEnabled, - totalTimeLoggedToday: '0 minutes', - }; - }, - - componentDidMount: function () { - var wakatime = new WakaTimeCore(); - - var that = this; - - wakatime.checkAuth().done(function (data) { - if (data !== false) { - browser.storage.sync - .get({ - loggingEnabled: config.loggingEnabled, - }) - .then(function (items) { - that.setState({ loggingEnabled: items.loggingEnabled }); - - if (items.loggingEnabled === true) { - changeExtensionState('allGood'); - } else { - changeExtensionState('notLogging'); - } - }); - - that.setState({ - user: { - full_name: data.full_name, - email: data.email, - photo: data.photo, - }, - loggedIn: true, - }); - - wakatime.getTotalTimeLoggedToday().done(function (grand_total) { - that.setState({ - totalTimeLoggedToday: grand_total.text, - }); - }); - - wakatime.recordHeartbeat(); - } else { - changeExtensionState('notSignedIn'); - } - }); - }, - - logoutUser: function () { - var deferredObject = $.Deferred(); - - var that = this; - - $.ajax({ - url: config.logoutUserUrl, - method: 'GET', - success: function () { - deferredObject.resolve(that); - }, - error: function (xhr, status, err) { - console.error(config.logoutUserUrl, status, err.toString()); - - deferredObject.resolve(that); - }, - }); - - return deferredObject.promise(); - }, - - _logoutUser: function () { - var that = this; - - this.logoutUser().done(function () { - that.setState({ - user: { - full_name: null, - email: null, - photo: null, - }, - loggedIn: false, - loggingEnabled: false, - }); - - changeExtensionState('notSignedIn'); - }); - }, - - _disableLogging: function () { - this.setState({ - loggingEnabled: false, - }); - - changeExtensionState('notLogging'); - - browser.storage.sync.set({ - loggingEnabled: false, - }); - }, - - _enableLogging: function () { - this.setState({ - loggingEnabled: true, - }); - - changeExtensionState('allGood'); - - browser.storage.sync.set({ - loggingEnabled: true, - }); - }, - - render: function () { - return ( -
    - -
    -
    -
    - -
    -
    -
    -
    - ); - }, -}); - -module.exports = Wakatime; diff --git a/assets/js/config.js b/assets/js/config.js deleted file mode 100644 index 01d9d06..0000000 --- a/assets/js/config.js +++ /dev/null @@ -1,60 +0,0 @@ -/* global browser */ -//jshint esnext:true -var config = { - // Extension name - name: 'WakaTime', - // Extension version - version: process.env.NODE_ENV === 'test' ? 'test' : browser.runtime.getManifest().version, - // Time for idle state of the browser - // The user is considered idle if there was - // no activity in the browser for x seconds - detectionIntervalInSeconds: 60, - // Default logging style - // Log all except blacklisted sites - // or log only the white listed sites. - loggingStyle: 'blacklist', - // Default logging type - loggingType: 'domain', - // By default logging is enabled - loggingEnabled: true, - // Url to which to send the heartbeat - heartbeatApiUrl: 'https://wakatime.com/api/v1/users/current/heartbeats', - // Url from which to detect if the user is logged in - currentUserApiUrl: 'https://wakatime.com/api/v1/users/current', - // The url to logout the user from wakatime - logoutUserUrl: 'https://wakatime.com/logout', - // Gets stats from the WakaTime API - summariesApiUrl: 'https://wakatime.com/api/v1/users/current/summaries', - // Different colors for different states of the extension - colors: { - allGood: '', - notLogging: 'gray', - notSignedIn: 'red', - lightTheme: 'white', - }, - // Tooltips for each of the extension states - tooltips: { - allGood: '', - notLogging: 'Not logging', - notSignedIn: 'Not signed In', - blacklisted: 'This URL is blacklisted', - whitelisted: 'This URL is not on your whitelist', - }, - // Default theme - theme: 'light', - // Valid extension states - states: ['allGood', 'notLogging', 'notSignedIn', 'blacklisted', 'whitelisted'], - // Predefined alert type and text for success and failure. - alert: { - success: { - type: 'success', - text: 'Options have been saved!', - }, - failure: { - type: 'danger', - text: 'There was an error while saving the options!', - }, - }, -}; - -module.exports = config; diff --git a/assets/js/core/WakaTimeCore.js b/assets/js/core/WakaTimeCore.js deleted file mode 100644 index 7dd93e2..0000000 --- a/assets/js/core/WakaTimeCore.js +++ /dev/null @@ -1,300 +0,0 @@ -/* global browser */ -//jshint esnext:true - -var $ = require('jquery'); -var moment = require('moment'); -var config = require('./../config'); - -// Helpers -var getDomainFromUrl = require('./../helpers/getDomainFromUrl'); -var changeExtensionState = require('../helpers/changeExtensionState'); -var in_array = require('./../helpers/in_array'); -var contains = require('./../helpers/contains'); - -class WakaTimeCore { - constructor() { - this.tabsWithDevtoolsOpen = []; - } - - /** - * Settter for tabsWithDevtoolsOpen - * - * @param tabs - */ - setTabsWithDevtoolsOpen(tabs) { - this.tabsWithDevtoolsOpen = tabs; - } - - getTotalTimeLoggedToday() { - var deferredObject = $.Deferred(); - var today = moment().format('YYYY-MM-DD'); - - $.ajax({ - url: config.summariesApiUrl + '?start=' + today + '&end=' + today, - dataType: 'json', - success: (data) => { - deferredObject.resolve(data.data[0].grand_total); - }, - error: (xhr, status, err) => { - console.error(config.summariesApiUrl, status, err.toString()); - - deferredObject.resolve(false); - }, - }); - - return deferredObject.promise(); - } - - /** - * Checks if the user is logged in. - * - * @returns {*} - */ - checkAuth() { - var deferredObject = $.Deferred(); - - $.ajax({ - url: config.currentUserApiUrl, - dataType: 'json', - success: (data) => { - deferredObject.resolve(data.data); - }, - error: (xhr, status, err) => { - console.error(config.currentUserApiUrl, status, err.toString()); - deferredObject.resolve(false); - }, - }); - - return deferredObject.promise(); - } - - /** - * Depending on various factors detects the current active tab URL or domain, - * and sends it to WakaTime for logging. - */ - recordHeartbeat() { - browser.storage.sync - .get({ - loggingEnabled: config.loggingEnabled, - loggingStyle: config.loggingStyle, - blacklist: '', - whitelist: '', - }) - .then((items) => { - if (items.loggingEnabled === true) { - changeExtensionState('allGood'); - - browser.idle.queryState(config.detectionIntervalInSeconds).then((newState) => { - if (newState === 'active') { - // Get current tab URL. - browser.tabs.query({ currentWindow: true, active: true }).then((tabs) => { - if (tabs.length == 0) return; - - var currentActiveTab = tabs[0]; - var debug = false; - - // If the current active tab has devtools open - if (in_array(currentActiveTab.id, this.tabsWithDevtoolsOpen)) { - debug = true; - } - - if (items.loggingStyle == 'blacklist') { - if (!contains(currentActiveTab.url, items.blacklist)) { - this.sendHeartbeat( - { - url: currentActiveTab.url, - project: null, - }, - debug, - ); - } else { - changeExtensionState('blacklisted'); - console.log(currentActiveTab.url + ' is on a blacklist.'); - } - } - - if (items.loggingStyle == 'whitelist') { - var heartbeat = this.getHeartbeat(currentActiveTab.url, items.whitelist); - if (heartbeat.url) { - this.sendHeartbeat(heartbeat, debug); - } else { - changeExtensionState('whitelisted'); - console.log(currentActiveTab.url + ' is not on a whitelist.'); - } - } - }); - } - }); - } else { - changeExtensionState('notLogging'); - } - }); - } - - /** - * Creates an array from list using \n as delimiter - * and checks if any element in list is contained in the url. - * Also checks if element is assigned to a project using @@ as delimiter - * - * @param url - * @param list - * @returns {object} - */ - getHeartbeat(url, list) { - var projectIndicatorCharacters = '@@'; - - var lines = list.split('\n'); - for (var i = 0; i < lines.length; i++) { - // strip (http:// or https://) and trailing (`/` or `@@`) - var cleanLine = lines[i] - .trim() - .replace(/(\/|@@)$/, '') - .replace(/^(?:https?:\/\/)?/i, ''); - if (cleanLine === '') continue; - - var projectIndicatorIndex = cleanLine.lastIndexOf(projectIndicatorCharacters); - var projectIndicatorExists = projectIndicatorIndex > -1; - var projectName = null; - var urlFromLine = cleanLine; - if (projectIndicatorExists) { - var start = projectIndicatorIndex + projectIndicatorCharacters.length; - projectName = cleanLine.substring(start); - urlFromLine = cleanLine - .replace(cleanLine.substring(projectIndicatorIndex), '') - .replace(/\/$/, ''); - } - var schemaHttpExists = url.match(/^http:\/\//i); - var schemaHttpsExists = url.match(/^https:\/\//i); - var schema = ''; - if (schemaHttpExists) { - schema = 'http://'; - } - if (schemaHttpsExists) { - schema = 'https://'; - } - var cleanUrl = url - .trim() - .replace(/(\/|@@)$/, '') - .replace(/^(?:https?:\/\/)?/i, ''); - var startsWithUrl = cleanUrl.toLowerCase().indexOf(urlFromLine.toLowerCase()) > -1; - if (startsWithUrl) { - return { - url: schema + urlFromLine, - project: projectName, - }; - } - } - - return { - url: null, - project: null, - }; - } - - /** - * Creates payload for the heartbeat and returns it as JSON. - * - * @param heartbeat - * @param type - * @param debug - * @returns {*} - * @private - */ - _preparePayload(heartbeat, type, debug = false) { - return JSON.stringify({ - entity: heartbeat.url, - type: type, - time: moment().format('X'), - project: heartbeat.project || '<>', - is_debugging: debug, - plugin: 'browser-wakatime/' + config.version, - }); - } - - /** - * Returns a promise with logging type variable. - * - * @returns {*} - * @private - */ - _getLoggingType() { - var deferredObject = $.Deferred(); - - browser.storage.sync - .get({ - loggingType: config.loggingType, - }) - .then(function (items) { - deferredObject.resolve(items.loggingType); - }); - - return deferredObject.promise(); - } - - /** - * Given the heartbeat and logging type it creates a payload and - * sends an ajax post request to the API. - * - * @param heartbeat - * @param debug - */ - sendHeartbeat(heartbeat, debug) { - var payload = null; - - this._getLoggingType().done((loggingType) => { - // Get only the domain from the entity. - // And send that in heartbeat - if (loggingType == 'domain') { - heartbeat.url = getDomainFromUrl(heartbeat.url); - payload = this._preparePayload(heartbeat, 'domain', debug); - console.log(payload); - this.sendAjaxRequestToApi(payload); - } - // Send entity in heartbeat - else if (loggingType == 'url') { - payload = this._preparePayload(heartbeat, 'url', debug); - console.log(payload); - this.sendAjaxRequestToApi(payload); - } - }); - } - - /** - * Sends AJAX request with payload to the heartbeat API as JSON. - * - * @param payload - * @param method - * @returns {*} - */ - sendAjaxRequestToApi(payload, method = 'POST') { - var deferredObject = $.Deferred(); - - $.ajax({ - url: config.heartbeatApiUrl, - dataType: 'json', - contentType: 'application/json', - method: method, - data: payload, - statusCode: { - 401: function () { - changeExtensionState('notSignedIn'); - }, - 201: function () { - // nothing to do here - }, - }, - success: (response) => { - deferredObject.resolve(this); - }, - error: (xhr, status, err) => { - console.error(config.heartbeatApiUrl, status, err.toString()); - - deferredObject.resolve(this); - }, - }); - - return deferredObject.promise(); - } -} - -export default WakaTimeCore; diff --git a/assets/js/devtools.js b/assets/js/devtools.js deleted file mode 100644 index b54892d..0000000 --- a/assets/js/devtools.js +++ /dev/null @@ -1,12 +0,0 @@ -/* global browser */ - -// Create a connection to the background page -var backgroundPageConnection = browser.runtime.connect({ - name: 'devtools-page', -}); - -// Send a message to background page with the current active tabId -backgroundPageConnection.postMessage({ - name: 'init', - tabId: browser.devtools.inspectedWindow.tabId, -}); diff --git a/assets/js/events.js b/assets/js/events.js deleted file mode 100644 index f7daea0..0000000 --- a/assets/js/events.js +++ /dev/null @@ -1,101 +0,0 @@ -/* global browser */ - -// Core -var WakaTimeCore = require('./core/WakaTimeCore').default; - -// initialize class -var wakatime = new WakaTimeCore(); - -// Holds currently open connections (ports) with devtools -// Uses tabId as index key. -var connections = {}; - -// Add a listener to resolve alarms -browser.alarms.onAlarm.addListener(function (alarm) { - // |alarm| can be undefined because onAlarm also gets called from - // window.setTimeout on old chrome versions. - if (alarm && alarm.name == 'heartbeatAlarm') { - console.log('recording a heartbeat - alarm triggered'); - - wakatime.recordHeartbeat(); - } -}); - -// Create a new alarm for heartbeats. -browser.alarms.create('heartbeatAlarm', { periodInMinutes: 2 }); - -/** - * Whenever a active tab is changed it records a heartbeat with that tab url. - */ -browser.tabs.onActivated.addListener(function (activeInfo) { - browser.tabs.get(activeInfo.tabId).then(function (tab) { - console.log('recording a heartbeat - active tab changed'); - - wakatime.recordHeartbeat(); - }); -}); - -/** - * Whenever a active window is changed it records a heartbeat with the active tab url. - */ -browser.windows.onFocusChanged.addListener(function (windowId) { - if (windowId != browser.windows.WINDOW_ID_NONE) { - console.log('recording a heartbeat - active window changed'); - - wakatime.recordHeartbeat(); - } else { - console.log('lost focus'); - } -}); - -/** - * Whenever any tab is updated it checks if the updated tab is the tab that is - * currently active and if it is, then it records a heartbeat. - */ -browser.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) { - if (changeInfo.status === 'complete') { - // Get current tab URL. - browser.tabs.query({ currentWindow: true, active: true }).then(function (tabs) { - // If tab updated is the same as active tab - if (tabId == tabs[0].id) { - console.log('recording a heartbeat - tab updated'); - - wakatime.recordHeartbeat(); - } - }); - } -}); - -/** - * This is in charge of detecting if devtools are opened or closed - * and sending a heartbeat depending on that. - */ -browser.runtime.onConnect.addListener(function (port) { - if (port.name == 'devtools-page') { - // Listen to messages sent from the DevTools page - port.onMessage.addListener(function (message, sender, sendResponse) { - if (message.name == 'init') { - connections[message.tabId] = port; - - wakatime.setTabsWithDevtoolsOpen(Object.keys(connections)); - - wakatime.recordHeartbeat(); - } - }); - - port.onDisconnect.addListener(function (port) { - var tabs = Object.keys(connections); - - for (var i = 0, len = tabs.length; i < len; i++) { - if (connections[tabs[i]] == port) { - delete connections[tabs[i]]; - break; - } - } - - wakatime.setTabsWithDevtoolsOpen(Object.keys(connections)); - - wakatime.recordHeartbeat(); - }); - } -}); diff --git a/assets/js/helpers/changeExtensionIcon.js b/assets/js/helpers/changeExtensionIcon.js deleted file mode 100644 index 7905eb0..0000000 --- a/assets/js/helpers/changeExtensionIcon.js +++ /dev/null @@ -1,49 +0,0 @@ -/* global browser */ - -var config = require('../config'); - -/** - * It changes the extension icon color. - * Supported values are: 'red', 'white', 'gray' and ''. - * - * @param color - */ -function changeExtensionIcon(color) { - color = color ? color : ''; - - var path = null; - - if (color !== '') { - color = '-' + color; - - path = './graphics/wakatime-logo-38' + color + '.png'; - - browser.browserAction.setIcon({ - path: path, - }); - } - - if (color === '') { - browser.storage.sync - .get({ - theme: config.theme, - }) - .then(function (items) { - if (items.theme == config.theme) { - path = './graphics/wakatime-logo-38.png'; - - browser.browserAction.setIcon({ - path: path, - }); - } else { - path = './graphics/wakatime-logo-38-white.png'; - - browser.browserAction.setIcon({ - path: path, - }); - } - }); - } -} - -module.exports = changeExtensionIcon; diff --git a/assets/js/helpers/changeExtensionState.js b/assets/js/helpers/changeExtensionState.js deleted file mode 100644 index f881bc9..0000000 --- a/assets/js/helpers/changeExtensionState.js +++ /dev/null @@ -1,42 +0,0 @@ -var config = require('../config'); - -// Helpers -var changeExtensionIcon = require('./changeExtensionIcon'); -var changeExtensionTooltip = require('./changeExtensionTooltip'); -var in_array = require('./in_array'); - -/** - * Sets the current state of the extension. - * - * @param state - */ -function changeExtensionState(state) { - if (!in_array(state, config.states)) { - throw new Error('Not a valid state!'); - } - - switch (state) { - case 'allGood': - changeExtensionIcon(config.colors.allGood); - changeExtensionTooltip(config.tooltips.allGood); - break; - case 'notLogging': - changeExtensionIcon(config.colors.notLogging); - changeExtensionTooltip(config.tooltips.notLogging); - break; - case 'notSignedIn': - changeExtensionIcon(config.colors.notSignedIn); - changeExtensionTooltip(config.tooltips.notSignedIn); - break; - case 'blacklisted': - changeExtensionIcon(config.colors.notLogging); - changeExtensionTooltip(config.tooltips.blacklisted); - break; - case 'whitelisted': - changeExtensionIcon(config.colors.notLogging); - changeExtensionTooltip(config.tooltips.whitelisted); - break; - } -} - -module.exports = changeExtensionState; diff --git a/assets/js/helpers/changeExtensionTooltip.js b/assets/js/helpers/changeExtensionTooltip.js deleted file mode 100644 index b65caf0..0000000 --- a/assets/js/helpers/changeExtensionTooltip.js +++ /dev/null @@ -1,20 +0,0 @@ -/* global browser */ - -var config = require('../config'); - -/** - * It changes the extension title - * - * @param text - */ -function changeExtensionTooltip(text) { - if (text === '') { - text = config.name; - } else { - text = config.name + ' - ' + text; - } - - browser.browserAction.setTitle({ title: text }); -} - -module.exports = changeExtensionTooltip; diff --git a/assets/js/helpers/contains.js b/assets/js/helpers/contains.js deleted file mode 100644 index 62751ad..0000000 --- a/assets/js/helpers/contains.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Creates an array from list using \n as delimiter - * and checks if any element in list is contained in the url. - * - * @param url - * @param list - * @returns {boolean} - */ -function contains(url, list) { - var lines = list.split('\n'); - - for (var i = 0; i < lines.length; i++) { - // Trim all lines from the list one by one - var cleanLine = lines[i].trim(); - - // If by any chance one line in the list is empty, ignore it - if (cleanLine === '') continue; - - var lineRe = new RegExp(cleanLine.replace('.', '.').replace('*', '.*')); - - // If url matches the current line return true - if (lineRe.test(url)) { - return true; - } - } - - return false; -} - -module.exports = contains; diff --git a/assets/js/helpers/getDomainFromUrl.js b/assets/js/helpers/getDomainFromUrl.js deleted file mode 100644 index fd45232..0000000 --- a/assets/js/helpers/getDomainFromUrl.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Returns domain from given URL. - * - * @param url - * @returns {string} - */ -function getDomainFromUrl(url) { - var parts = url.split('/'); - - return parts[0] + '//' + parts[2]; -} - -module.exports = getDomainFromUrl; diff --git a/assets/js/helpers/in_array.js b/assets/js/helpers/in_array.js deleted file mode 100644 index 2a6774b..0000000 --- a/assets/js/helpers/in_array.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Returns boolean if needle is found in haystack or not. - * - * @param needle - * @param haystack - * @returns {boolean} - */ -function in_array(needle, haystack) { - for (var i = 0; i < haystack.length; i++) { - if (needle == haystack[i]) { - return true; - } - } - - return false; -} - -module.exports = in_array; diff --git a/assets/js/options.jsx b/assets/js/options.jsx deleted file mode 100644 index de9c79f..0000000 --- a/assets/js/options.jsx +++ /dev/null @@ -1,13 +0,0 @@ -/* global browser */ - -/* This is a fix for Bootstrap requiring jQuery */ -global.jQuery = require('jquery'); -require('bootstrap'); - -var React = require('react'); -var ReactDOM = require('react-dom'); - -// React components -var Options = require('./components/Options.jsx'); - -ReactDOM.render(, document.getElementById('wakatime-options')); diff --git a/devtools.html b/devtools.html deleted file mode 100644 index 20696ee..0000000 --- a/devtools.html +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/manifest.json b/manifest.json deleted file mode 100644 index bdbb377..0000000 --- a/manifest.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "background": { - "scripts": ["public/js/browser-polyfill.min.js", "public/js/events.js"], - "persistent": false - }, - "browser_action": { - "default_icon": { - "19": "graphics/wakatime-logo-19.png", - "38": "graphics/wakatime-logo-38.png" - }, - "default_title": "WakaTime", - "default_popup": "popup.html" - }, - "browser_specific_settings": { - "gecko": { - "id": "addon@wakatime.com", - "strict_min_version": "48.0" - } - }, - "description": "Automatic time tracking for Chrome.", - "devtools_page": "devtools.html", - "homepage_url": "https://wakatime.com", - "icons": { - "16": "graphics/wakatime-logo-16.png", - "48": "graphics/wakatime-logo-48.png", - "128": "graphics/wakatime-logo-128.png" - }, - "manifest_version": 2, - "name": "WakaTime", - "options_ui": { - "page": "options.html", - "chrome_style": false - }, - "permissions": [ - "https://api.wakatime.com/*", - "https://wakatime.com/*", - "alarms", - "tabs", - "storage", - "idle" - ], - "version": "3.0.0" -} diff --git a/options.html b/options.html deleted file mode 100644 index ab85418..0000000 --- a/options.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - WakaTime options - - - - -
    - - - - - diff --git a/package-lock.json b/package-lock.json index 9a37ac3..3515542 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,7 @@ "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^12.6.0", + "@types/chai": "^4.3.4", "@types/chrome": "0.0.128", "@types/classnames": "^2.2.11", "@types/copy-webpack-plugin": "^10.1.0", @@ -3631,6 +3632,12 @@ "@babel/types": "^7.3.0" } }, + "node_modules/@types/chai": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==", + "dev": true + }, "node_modules/@types/chrome": { "version": "0.0.128", "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.128.tgz", @@ -26005,6 +26012,12 @@ "@babel/types": "^7.3.0" } }, + "@types/chai": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==", + "dev": true + }, "@types/chrome": { "version": "0.0.128", "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.128.tgz", diff --git a/package.json b/package.json index bb155be..2a3bdf3 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^12.6.0", + "@types/chai": "^4.3.4", "@types/chrome": "0.0.128", "@types/classnames": "^2.2.11", "@types/copy-webpack-plugin": "^10.1.0", diff --git a/popup.html b/popup.html deleted file mode 100644 index e1c8f3c..0000000 --- a/popup.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - WakaTime - - - - -
    - - - - - diff --git a/src/components/WakaTime.tsx b/src/components/WakaTime.tsx index 6b29727..faf0641 100644 --- a/src/components/WakaTime.tsx +++ b/src/components/WakaTime.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { ApiKeyReducer, ReduxSelector } from '../types/store'; import { fetchUserData } from '../utils/user'; @@ -10,6 +10,7 @@ import MainList from './MainList'; export default function WakaTime(): JSX.Element { const dispatch = useDispatch(); + const [extensionState, setExtensionState] = useState(''); const { apiKey: apiKeyFromRedux, @@ -18,7 +19,12 @@ export default function WakaTime(): JSX.Element { }: ApiKeyReducer = useSelector((selector: ReduxSelector) => selector.config); useEffect(() => { - void fetchUserData(apiKeyFromRedux, dispatch); + const fetchData = async () => { + await fetchUserData(apiKeyFromRedux, dispatch); + const items = await browser.storage.sync.get({ extensionState: '' }); + setExtensionState(items.extensionState as string); + }; + void fetchData(); }, []); const isApiKeyValid = apiKeyInvalid(apiKeyFromRedux) === ''; @@ -26,6 +32,14 @@ export default function WakaTime(): JSX.Element { return (
    + {isApiKeyValid && extensionState === 'notSignedIn' && ( + browser.runtime.openOptionsPage()} + style={{ cursor: 'pointer' }} + /> + )} {!isApiKeyValid && ( { - browser = window; - }); - it('should be a function', function () { - expect(changeExtensionState).to.be.a('function'); - }); -}); diff --git a/tests/helpers/in_array.spec.js b/tests/helpers/in_array.spec.js deleted file mode 100644 index 68c046a..0000000 --- a/tests/helpers/in_array.spec.js +++ /dev/null @@ -1,18 +0,0 @@ -var chai = require('chai'); -var expect = chai.expect; - -var in_array = require('../../assets/js/helpers/in_array'); - -describe('in_array', function () { - it('should be a function', function () { - expect(in_array).to.be.a('function'); - }); - - it('should find the needle and return true', function () { - expect(in_array('4', ['4', '3', '2', '1'])).to.equal(true); - }); - - it('should not find the needle and it should return false', function () { - expect(in_array('5', ['4', '3', '2', '1'])).to.equal(false); - }); -}); diff --git a/tests/helpers/Chrome.spec.js b/tests/utils/Chrome.spec.ts similarity index 62% rename from tests/helpers/Chrome.spec.js rename to tests/utils/Chrome.spec.ts index bdc0e89..5c19081 100644 --- a/tests/helpers/Chrome.spec.js +++ b/tests/utils/Chrome.spec.ts @@ -1,7 +1,8 @@ -var chai = require('chai'); -var sinon = require('sinon'); -var chrome = require('sinon-chrome'); -var expect = chai.expect; +import chai from 'chai'; +import sinon from 'sinon'; +import chrome from 'sinon-chrome'; + +const expect = chai.expect; describe('Chrome Dev Tools', function () { it('should work', function () { diff --git a/tests/utils/changeExtensionIcon.spec.ts b/tests/utils/changeExtensionIcon.spec.ts new file mode 100644 index 0000000..7934aaa --- /dev/null +++ b/tests/utils/changeExtensionIcon.spec.ts @@ -0,0 +1,20 @@ +import chai from 'chai'; +import changeExtensionIcon from '../../src/utils/changeExtensionIcon'; + +const expect = chai.expect; + +jest.mock('webextension-polyfill', () => { + return { + runtime: { + getManifest: () => { + return { version: 'test-version' }; + }, + }, + }; +}); + +describe('changeExtensionIcon', function () { + it('should be a function', function () { + expect(changeExtensionIcon).to.be.a('function'); + }); +}); diff --git a/tests/utils/changeExtensionState.spec.ts b/tests/utils/changeExtensionState.spec.ts new file mode 100644 index 0000000..17b3340 --- /dev/null +++ b/tests/utils/changeExtensionState.spec.ts @@ -0,0 +1,20 @@ +import chai from 'chai'; +import changeExtensionState from '../../src/utils/changeExtensionState'; + +const expect = chai.expect; + +jest.mock('webextension-polyfill', () => { + return { + runtime: { + getManifest: () => { + return { version: 'test-version' }; + }, + }, + }; +}); + +describe('changeExtensionState', function () { + it('should be a function', function () { + expect(changeExtensionState).to.be.a('function'); + }); +}); diff --git a/tests/helpers/changeExtensionTooltip.spec.js b/tests/utils/changeExtensionTooltip.spec.ts similarity index 60% rename from tests/helpers/changeExtensionTooltip.spec.js rename to tests/utils/changeExtensionTooltip.spec.ts index b244f80..d564f2e 100644 --- a/tests/helpers/changeExtensionTooltip.spec.js +++ b/tests/utils/changeExtensionTooltip.spec.ts @@ -1,9 +1,18 @@ -var chai = require('chai'); -var sinon = require('sinon-chai'); -var chrome = require('sinon-chrome'); -var expect = chai.expect; +import chai from 'chai'; +import changeExtensionTooltip from '../../src/utils/changeExtensionTooltip'; + +const expect = chai.expect; + +jest.mock('webextension-polyfill', () => { + return { + runtime: { + getManifest: () => { + return { version: 'test-version' }; + }, + }, + }; +}); -var changeExtensionTooltip = require('../../assets/js/helpers/changeExtensionTooltip'); describe('changeExtensionTooltip', function () { it('should be a function', function () { expect(changeExtensionTooltip).to.be.a('function'); diff --git a/tests/helpers/contains.spec.js b/tests/utils/contains.spec.ts similarity index 59% rename from tests/helpers/contains.spec.js rename to tests/utils/contains.spec.ts index 017fd7d..b319869 100644 --- a/tests/helpers/contains.spec.js +++ b/tests/utils/contains.spec.ts @@ -1,7 +1,7 @@ -var chai = require('chai'); -var expect = chai.expect; +import chai from 'chai'; +import contains from '../../src/utils/contains'; -var contains = require('../../assets/js/helpers/contains'); +const expect = chai.expect; describe('contains', function () { it('should be a function', function () { @@ -9,16 +9,16 @@ describe('contains', function () { }); it('should match url against blacklist and return true', function () { - var list = 'localhost\ntest.com'; + const list = 'localhost\ntest.com'; - var url = 'http://localhost/fooapp'; + const url = 'http://localhost/fooapp'; expect(contains(url, list)).to.equal(true); }); it('should not match url against blacklist and return false', function () { - var list = 'localhost2\ntest.com'; + const list = 'localhost2\ntest.com'; - var url = 'http://localhost/fooapp'; + const url = 'http://localhost/fooapp'; expect(contains(url, list)).to.equal(false); }); }); diff --git a/tests/helpers/getDomainFromUrl.spec.js b/tests/utils/getDomainFromUrl.spec.ts similarity index 82% rename from tests/helpers/getDomainFromUrl.spec.js rename to tests/utils/getDomainFromUrl.spec.ts index 7233c20..8ccc97a 100644 --- a/tests/helpers/getDomainFromUrl.spec.js +++ b/tests/utils/getDomainFromUrl.spec.ts @@ -1,7 +1,7 @@ -var chai = require('chai'); -var expect = chai.expect; +import chai from 'chai'; +import getDomainFromUrl from '../../src/utils/getDomainFromUrl'; -var getDomainFromUrl = require('../../assets/js/helpers/getDomainFromUrl'); +const expect = chai.expect; describe('getDomainFromUrl', function () { it('should be a function', function () { diff --git a/tests/utils/in_array.spec.ts b/tests/utils/in_array.spec.ts new file mode 100644 index 0000000..4455294 --- /dev/null +++ b/tests/utils/in_array.spec.ts @@ -0,0 +1,18 @@ +import chai from 'chai'; +import inArray from '../../src/utils/inArray'; + +const expect = chai.expect; + +describe('inArray', function () { + it('should be a function', function () { + expect(inArray).to.be.a('function'); + }); + + it('should find the needle and return true', function () { + expect(inArray('4', ['4', '3', '2', '1'])).to.equal(true); + }); + + it('should not find the needle and it should return false', function () { + expect(inArray('5', ['4', '3', '2', '1'])).to.equal(false); + }); +}); diff --git a/xclap.ts b/xclap.ts index 21e62dc..cb8bc47 100644 --- a/xclap.ts +++ b/xclap.ts @@ -69,22 +69,17 @@ load({ serial('postinstall'), 'webpack', concurrent( - exec('web-ext build'), - exec(`web-ext build -a dist/firefox/web-ext-artifacts --source-dir ${ffNextBuildFolder}`), + exec( + `web-ext build --artifacts-dir dist/chrome/web-ext-artifacts --source-dir ${chromeNextBuildFolder}`, + ), + exec( + `web-ext build --artifacts-dir dist/firefox/web-ext-artifacts --source-dir ${ffNextBuildFolder}`, + ), ), ], clean: [exec('rimraf public coverage vendor web-ext-artifacts'), 'clean:webpack'], 'clean:webpack': exec('rimraf dist'), - dev: [ - 'clean', - 'postinstall', - concurrent('watch', 'web-ext:run:firefox-next', 'web-ext:run:chrome-next'), - ], - 'dev:legacy': [ - 'clean', - 'postinstall', - concurrent(exec('gulp watch'), 'web-ext:run:firefox-legacy', 'web-ext:run:chrome-legacy'), - ], + dev: ['clean', 'postinstall', concurrent('watch', 'web-ext:run:firefox', 'web-ext:run:chrome')], eslint: exec('eslint src . --fix'), less: exec('lessc assets/less/app.less public/css/app.css'), lint: ['prettier', 'eslint'], @@ -93,34 +88,16 @@ load({ 'remotedev-server': exec('remotedev --hostname=localhost --port=8000'), test: ['build', 'lint', 'test-jest'], 'test-jest': [exec('jest --clearCache'), exec('jest --verbose --coverage')], - 'test-jest-update': exec('jest -u'), - 'wait:legacy-files': waitForFilesTask( - 'manifest.json', - 'public/js/browser-polyfill.min.js', - 'public/js/events.js', - 'options.html', - ), watch: concurrent('watch-jest', 'webpack:watch'), 'watch-jest': exec('jest --watch'), - 'web-ext:run:chrome': concurrent('web-ext:run:chrome-next', 'web-ext:run:chrome-legacy'), - 'web-ext:run:chrome-legacy': [ - 'wait:legacy-files', - exec('web-ext run -t chromium --source-dir .'), - ], - 'web-ext:run:chrome-next': [ + 'web-ext:run:chrome': [ chromeNextBuildFileWaitTask, exec('web-ext run -t chromium --source-dir dist/chrome'), ], - 'web-ext:run:firefox': concurrent('web-ext:run:firefox-next', 'web-ext:run:firefox-legacy'), - 'web-ext:run:firefox-legacy': [ - 'wait:legacy-files', - exec('web-ext run -t firefox-desktop --source-dir .'), - ], - 'web-ext:run:firefox-next': [ + 'web-ext:run:firefox': [ ffNextBuildFileWaitTask, exec('web-ext run -t firefox-desktop --source-dir dist/firefox'), ], webpack: ['clean:webpack', exec('webpack --mode production')], - 'webpack:dev': ['clean:webpack', exec('webpack --mode development')], 'webpack:watch': ['clean:webpack', exec('webpack --mode development --watch')], });