diff --git a/manifest.json b/manifest.json index 0b916a8..b201fd3 100644 --- a/manifest.json +++ b/manifest.json @@ -1,6 +1,6 @@ { - "manifest_version": 2, - "version": "0.0.0.1", + "manifest_version": 3, + "version": "0.0.0", "name": "__MSG_extName__", "short_name": "__MSG_extShortName__", "description": "__MSG_extDesc__", @@ -10,13 +10,13 @@ "48": "imgs/icons/deezer_48x48.png", "128": "imgs/icons/deezer_128x128.png" }, - "browser_action": { + "action": { "default_icon": "imgs/icons/deezer_19x19.png", "default_title": "__MSG_defaultTitle__" }, "background": { - "scripts": ["scripts/localstorage.js", "scripts/notifications.js", "scripts/background.js"], - "persistent": false + "service_worker": "scripts/service_worker.js", + "type": "module" }, "options_page": "options.html", "content_scripts": [ @@ -25,8 +25,16 @@ "js": ["scripts/player_listener.js", "scripts/deezer/bootstrap.js"] } ], - "permissions": ["*://*.deezer.com/*", "notifications", "tabs", "webNavigation", "storage"], - "content_security_policy": "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' https://e-cdns-images.dzcdn.net/images/;", - "optional_permissions": [""], - "web_accessible_resources": ["scripts/deezer/player_observer.js"] + "permissions": ["notifications", "scripting", "storage", "tabs", "webNavigation"], + "host_permissions": ["*://*.deezer.com/*"], + "optional_host_permissions": [""], + "content_security_policy": { + "extension_pages": "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' https://e-cdns-images.dzcdn.net/images/;" + }, + "web_accessible_resources": [ + { + "resources": ["scripts/deezer/player_observer.js"], + "matches": ["*://*.deezer.com/*"] + } + ] } diff --git a/options.html b/options.html index dd04112..822e9d8 100644 --- a/options.html +++ b/options.html @@ -4,12 +4,9 @@ Deezer Control - - - - + diff --git a/popup.html b/popup.html index ee55723..3436f08 100644 --- a/popup.html +++ b/popup.html @@ -4,8 +4,7 @@ Deezer Control Popup - - +
diff --git a/scripts/Version.js b/scripts/Version.js new file mode 100644 index 0000000..6be4ce1 --- /dev/null +++ b/scripts/Version.js @@ -0,0 +1,32 @@ +//------------------------------------------------------------------------------------------------------ +// +//------------------------------------------------------------------------------------------------------ +export default class Version { + constructor(strVersion) { + const regex = /^(\d+)(?:\.(\d+))?(?:\.(\d+))?$/; + + // if no match, default to version 0.0.0 + const results = regex.exec(strVersion) ?? regex.exec('0.0.0'); + + this.major = parseInt(results[1] || 0, 10); + this.minor = parseInt(results[2] || 0, 10); + this.rev = parseInt(results[3] || 0, 10); + } + + toString() { + return this.major + '.' + this.minor + '.' + this.rev; + } + + // returns -1 if this < otherVersion, 0 if this == otherVersion, and +1 if otherVersion < this + compare(otherVersion) { + if (this.major !== otherVersion.major) { + return this.major - otherVersion.major; + } + + if (this.minor !== otherVersion.minor) { + return this.minor - otherVersion.minor; + } + + return this.rev - otherVersion.rev; + } +} diff --git a/scripts/background.js b/scripts/background.js index b295dfc..47ba6fd 100644 --- a/scripts/background.js +++ b/scripts/background.js @@ -6,67 +6,6 @@ chrome.runtime.onSuspend.addListener(function () { LOCSTO.saveSession(); }); -// return a function that will execute all scripts in js_to_inject -// on each tabs of the function's argument 'tabs' -function executeAllScripts(js_to_inject) { - return function (tabs) { - for (var j = 0; j < tabs.length; j++) { - for (var k = 0; k < js_to_inject.length; k++) { - chrome.tabs.executeScript(tabs[j].id, { file: js_to_inject[k] }, ignoreLastError); - } - } - }; -} - -// install, update, or chrome update -chrome.runtime.onInstalled.addListener(function (details) { - 'use strict'; - - // inject content script on player tabs - if (details.reason === 'install' || details.reason === 'update') { - // insert expected scripts on all pages matching the patterns in the manifest - var content_scripts = chrome.runtime.getManifest().content_scripts; - for (var i = 0; i < content_scripts.length; i++) { - var content_script = content_scripts[i]; - chrome.tabs.query({ url: content_script.matches[0] }, executeAllScripts(content_script.js)); - } - - // re-inject hotkeys on all opened tabs - chrome.permissions.contains({ origins: [''] }, function (granted) { - if (granted) { - extensionOnMessageListener({ type: 'injectHotKeysJsOnAllTabs' }); - } - }); - - // update local storage if needed - LOCSTO.updateModel(); - } -}); - -// if no popup is set, it means that we should open a new tab with default player -chrome.browserAction.onClicked.addListener(function () { - 'use strict'; - - // extension has just been updated, a click will open the option page - if (LOCSTO.newOptionsToShow) { - chrome.tabs.create({ url: '/options.html' }); - - // user has seen what's new, restore normal use case - LOCSTO.newOptionsToShow = false; - LOCSTO.saveNewOptionsToShow(); - - chrome.browserAction.setBadgeText({ text: '' }); - setUpPopup(); - - propagatePlayingDataToAllTabs(); - } - // else: normal use case - else { - // TODO add default player - chrome.tabs.create({ url: 'http://www.deezer.com' }); - } -}); - function ignoreLastError() { var lastError = chrome.runtime.lastError; if (lastError !== undefined && lastError.message !== undefined) { @@ -74,155 +13,12 @@ function ignoreLastError() { } } -// inject hotkeys.js on any page if user allowed it -chrome.webNavigation.onCommitted.addListener(function (data) { - 'use strict'; - - // ignore sub frames and chrome urls - if (data.frameId !== 0 || data.url.indexOf('chrome', 0) === 0) { - return; - } - - chrome.permissions.contains({ origins: [''] }, function (granted) { - if (granted) { - chrome.tabs.executeScript(data.tabId, { file: '/scripts/hotkeys.js', runAt: 'document_start' }, ignoreLastError); - } - }); - - // recount number of opened player tabs - setUpPopup(); -}); - -// remove closing tab id from players tabs -chrome.tabs.onRemoved.addListener(removePlayerTabId); -function removePlayerTabId(tabId) { - var index = LOCSTO.session.playersTabs.indexOf(tabId); - if (index > -1) { - LOCSTO.session.playersTabs.splice(index, 1); - - // current player is closed, move to the next and resume playing - if (index === 0 && LOCSTO.session.playersTabs.length > 0) { - var isPlaying = LOCSTO.session.deezerData.dz_playing; - LOCSTO.session.deezerData = LOCSTO.session.playersData[LOCSTO.session.playersTabs[0]]; - LOCSTO.session.deezerData.dz_playing = isPlaying; - propagatePlayingDataToAllTabs(); - - if (isPlaying === 'true') extensionOnMessageListener({ type: 'controlPlayer', command: 'play' }); - } - } - - if (LOCSTO.session.playersData.hasOwnProperty(tabId)) delete LOCSTO.session.playersData[tabId]; - - // recount number of opened player tabs - setUpPopup(); -} - -// save active tab any time it changes to be able to go back to it -chrome.tabs.onActivated.addListener(function (iActiveTabInfo) { - 'use strict'; - chrome.tabs.get(iActiveTabInfo.tabId, function (aActiveTab) { - if (!aActiveTab) { - return; - } - - // ignore active tab if current active player: we don't want to go back to it! - if (LOCSTO.session.playersTabs.indexOf(aActiveTab.id) !== 0) { - LOCSTO.session.jumpBackToActiveTab.windowId = aActiveTab.windowId; - LOCSTO.session.jumpBackToActiveTab.tabId = aActiveTab.id; - } - }); -}); - -// set up the popup: if at least one deezer tab is opened, we'll show the popup -// otherwise, open a new deezer tab -function setUpPopup() { - 'use strict'; - - // extension has just been updated, show new items - if (LOCSTO.newOptionsToShow) { - chrome.browserAction.setBadgeBackgroundColor({ color: '#FF0000' }); - chrome.browserAction.setBadgeText({ text: chrome.app.getDetails().version }); - chrome.browserAction.setTitle({ title: chrome.i18n.getMessage('showNewItemsTitle') }); - chrome.browserAction.setPopup({ popup: '' }); // don't create a popup, we want to open the options page - } - // else: normal use case - else if (LOCSTO.session.playersTabs.length === 0) { - LOCSTO.session.deezerData = null; // reset playing data - chrome.browserAction.setTitle({ title: chrome.i18n.getMessage('defaultTitle') }); - chrome.browserAction.setPopup({ popup: '' }); // no deezer tab is opened, so don't create a popup - NOTIFS.destroyNotif(); - } else { - chrome.browserAction.setPopup({ popup: '/popup.html' }); // at least one deezer tab is opened, create a popup - } -} - // this will react to an event fired in player_listener.js chrome.runtime.onMessage.addListener(extensionOnMessageListener); function extensionOnMessageListener(request, sender, sendResponse) { 'use strict'; switch (request.type) { - case 'remove_me': - removePlayerTabId(sender.tab.id); - break; - - case 'now_playing_updated': - // player has just been loaded - var playerTabId = sender.tab.id; - if (LOCSTO.session.playersTabs.indexOf(playerTabId) === -1) { - // check limit one page per player - if (LOCSTO.miscOptions.limitDeezerToOneTab === true) { - for (var key in LOCSTO.session.playersData) { - if (request.nowPlayingData.dz_name === LOCSTO.session.playersData[key].dz_name) { - // close opening tab - chrome.tabs.remove(playerTabId); - - // move to already opened tab - jumpToTab(parseInt(key, 10)); - return false; - } - } - } - - LOCSTO.session.playersTabs.push(playerTabId); - } - - // player is inactive, remove it - if (request.nowPlayingData.dz_is_active !== 'true') { - removePlayerTabId(playerTabId); - return false; - } - - // update player info - LOCSTO.session.playersData[playerTabId] = request.nowPlayingData; - - // change of player? - if (playerTabId !== LOCSTO.session.playersTabs[0]) { - // current player is not playing / new player is playing - if (LOCSTO.session.deezerData.dz_playing !== 'true' || request.nowPlayingData.dz_playing === 'true') { - // stops current player - extensionOnMessageListener({ type: 'controlPlayer', command: 'pause' }); - - // reorder tabs order - var index = LOCSTO.session.playersTabs.indexOf(playerTabId); - LOCSTO.session.playersTabs.splice(index, 1); - LOCSTO.session.playersTabs.splice(0, 0, playerTabId); - - LOCSTO.session.deezerData = request.nowPlayingData; - } - } else { - // same player - LOCSTO.session.deezerData = request.nowPlayingData; - } - - propagatePlayingDataToAllTabs(); - - // reset the fact that action is on media key event - gActionOnHotKey = false; - gActionOnNotifButton = false; - - break; - case 'controlPlayer': gActionOnHotKey = request.source === 'hotkey'; gActionOnNotifButton = request.source === 'notif'; @@ -240,10 +36,6 @@ function extensionOnMessageListener(request, sender, sendResponse) { } break; - case 'showNotif': - showNotif(true); - break; - case 'jumpToDeezer': // find current active tab // if not deezer tab, jump to the deezer tab @@ -262,39 +54,6 @@ function extensionOnMessageListener(request, sender, sendResponse) { }); } break; - - case 'getLOCSTO': - sendResponse(LOCSTO); - return true; - - case 'getDeezerData': - sendResponse(LOCSTO.session.deezerData); - return true; - - case 'optionsChanged': - LOCSTO.loadOptions(); - break; - - case 'injectHotKeysJsOnAllTabs': - chrome.windows.getAll({ populate: true }, function (windows) { - var i, aWindow, j, aTab; - for (i = 0; i < windows.length; i++) { - aWindow = windows[i]; - for (j = 0; j < aWindow.tabs.length; j++) { - aTab = aWindow.tabs[j]; - - // ignore all chrome internal urls - if (aTab.url.lastIndexOf('chrome', 0) !== 0) { - chrome.tabs.executeScript( - aTab.id, - { file: '/scripts/hotkeys.js', runAt: 'document_start' }, - ignoreLastError - ); - } - } - } - }); - break; } return false; @@ -306,90 +65,3 @@ function jumpToTab(iTabId) { chrome.tabs.update(tab.id, { selected: true }); }); } - -function showNotif(iForceRedisplay) { - 'use strict'; - - // if no deezer data, close notif, otherwise show it - if (LOCSTO.session.deezerData === null) { - NOTIFS.destroyNotif(); - } - // we have data to show - else { - // update or create notification - if (LOCSTO.notifications.never) { - NOTIFS.destroyNotif(); - return; - } - - // force a full redisplay of the notifs - var forceRedisplay = - LOCSTO.notifications.onSongChange || - (LOCSTO.notifications.onHotKeyOnly && gActionOnHotKey) || - gActionOnNotifButton || - iForceRedisplay === true; - - // if we don't have permission to display notifications, close notif if present - chrome.permissions.contains({ permissions: ['notifications'] }, function (granted) { - if (granted) { - NOTIFS.createNotif(forceRedisplay); - } else { - NOTIFS.destroyNotif(); - } - }); - } -} - -function propagatePlayingDataToAllTabs() { - 'use strict'; - - // refresh all opened popups, tabs (i.e. option page), and notifications - chrome.extension.getViews({ type: 'tab' }).forEach(refreshPopupOnWindow); - chrome.extension.getViews({ type: 'popup' }).forEach(refreshPopupOnWindow); - - // show / hide notif if needed - showNotif(); - - // update the button's tooltip only if no update should be shown - if (!LOCSTO.newOptionsToShow) { - var newTitle = ''; - if (LOCSTO.session.deezerData !== null) - newTitle = LOCSTO.session.deezerData.dz_track + ' - ' + LOCSTO.session.deezerData.dz_artist; - chrome.browserAction.setTitle({ title: newTitle }); - } - - // precache covers - if (LOCSTO.session.deezerData !== null) { - new Image().src = LOCSTO.session.deezerData.dz_prev_cover; - new Image().src = LOCSTO.session.deezerData.dz_cover; - new Image().src = LOCSTO.session.deezerData.dz_next_cover; - } -} - -function refreshPopupOnWindow(win) { - 'use strict'; - win.refreshPopup(); -} - -// actions to perform when the event page is loaded -(function () { - 'use strict'; - - // clean up current players queue - var len = LOCSTO.session.playersTabs.length, - tabId; - - // TODO beware of closure - while (len--) { - tabId = LOCSTO.session.playersTabs[len]; - chrome.tabs.get(tabId, function () { - if (chrome.runtime.lastError !== undefined) { - LOCSTO.session.playersTabs.splice(len, 1); - if (LOCSTO.session.playersData.hasOwnProperty(tabId)) delete LOCSTO.session.playersData[tabId]; - } - }); - } - - setUpPopup(); - LOCSTO.saveSession(); -})(); diff --git a/scripts/deezer/bootstrap.js b/scripts/deezer/bootstrap.js index e3a56b6..1f3cae4 100644 --- a/scripts/deezer/bootstrap.js +++ b/scripts/deezer/bootstrap.js @@ -1,7 +1,7 @@ function loadObserver() { // inject a new JS script that can interact with the JS objects of the page var s = document.createElement('script'); - s.src = chrome.extension.getURL('scripts/deezer/player_observer.js'); + s.src = chrome.runtime.getURL('scripts/deezer/player_observer.js'); (document.head || document.documentElement).appendChild(s); s.onload = function () { 'use strict'; diff --git a/scripts/deezer/player_observer.js b/scripts/deezer/player_observer.js index bb000a7..ef2bc71 100644 --- a/scripts/deezer/player_observer.js +++ b/scripts/deezer/player_observer.js @@ -53,7 +53,7 @@ function GetCover(type, albumId) { albumId = albumId || ''; - return 'https://e-cdns-images.dzcdn.net/images/' + type + '/' + albumId + '/250x250-000000-80-0-0.jpg'; + return 'https://e-cdns-images.dzcdn.net/images/' + type + '/' + albumId + '/500x500.jpg'; } function GetCoverFromAlbumId(streamId) { @@ -218,6 +218,7 @@ deezerControl.executeAction(evt.detail.action); }); + // Events is defined in the global scope by Deezer itself Events.subscribe(Events.player.paused, updateDeezerControlData); Events.subscribe(Events.player.trackChange, updateDeezerControlData); Events.subscribe(Events.user.addFavorite, updateDeezerControlData); diff --git a/scripts/hotkeys.js b/scripts/hotkeys.js index 2a52479..cff3e63 100644 --- a/scripts/hotkeys.js +++ b/scripts/hotkeys.js @@ -1,36 +1,27 @@ -function eventMatchHotKey(e, iHotKey) { - 'use strict'; - return ( - e.shiftKey === iHotKey.shiftKey && - e.altKey === iHotKey.altKey && - e.ctrlKey === iHotKey.ctrlKey && - e.keyCode === iHotKey.keyCode - ); -} +var deezerControlHotKeys; +if (!deezerControlHotKeys) { + function eventMatchHotKey(e, iHotKey) { + return ( + e.shiftKey === iHotKey.shiftKey && + e.altKey === iHotKey.altKey && + e.ctrlKey === iHotKey.ctrlKey && + e.keyCode === iHotKey.keyCode + ); + } -var HotKeysListener = function HotKeysListener() { - window.addEventListener('keydown', this, false); -}; + function handleEvent(e) { + Object.keys(deezerControlHotKeys).forEach((hotkey) => { + if (eventMatchHotKey(e, deezerControlHotKeys[hotkey])) { + chrome.runtime.sendMessage({ type: 'hotkey', action: hotkey }); + } + }); + } -HotKeysListener.prototype.handleEvent = function (e) { - 'use strict'; - chrome.runtime.sendMessage({ type: 'getLOCSTO' }, function (LOCSTO) { - if (!LOCSTO.miscOptions.hasHotkeysPermission) return; + // TODO access storage here to get hotkeys chrome.storage.sync.get() + // TODO add chrome.storage.onChanged listener to update hotkeys - if (eventMatchHotKey(e, LOCSTO.prevHotKey)) { - chrome.runtime.sendMessage({ type: 'controlPlayer', command: 'previoustrack', source: 'hotkey' }); - } else if (eventMatchHotKey(e, LOCSTO.playPauseHotKey)) { - chrome.runtime.sendMessage({ type: 'controlPlayer', command: 'playpause', source: 'hotkey' }); - } else if (eventMatchHotKey(e, LOCSTO.nextHotKey)) { - chrome.runtime.sendMessage({ type: 'controlPlayer', command: 'nexttrack', source: 'hotkey' }); - } else if (eventMatchHotKey(e, LOCSTO.addToFavoriteHotKey)) { - chrome.runtime.sendMessage({ type: 'controlPlayer', command: 'like', source: 'hotkey' }); - } else if (eventMatchHotKey(e, LOCSTO.whatZatSongHotKey)) { - chrome.runtime.sendMessage({ type: 'showNotif', source: 'hotkey' }); - } else if (eventMatchHotKey(e, LOCSTO.jumpToDeezerHotKey)) { - chrome.runtime.sendMessage({ type: 'jumpToDeezer', source: 'hotkey' }); - } + chrome.runtime.sendMessage({ type: 'loadHotKeys' }).then((hotkeys) => { + deezerControlHotKeys = hotkeys; + window.addEventListener('keydown', handleEvent, false); }); -}; - -var HOTKEYS = HOTKEYS || new HotKeysListener(); +} diff --git a/scripts/localstorage.js b/scripts/localstorage.js index d806b1a..90011a1 100644 --- a/scripts/localstorage.js +++ b/scripts/localstorage.js @@ -1,9 +1,11 @@ +import Version from './Version.js'; + function fillDictWithDefaults(iDictWithRealValues, iDictWithDefaultValues) { 'use strict'; var aMyNewObject = {}, key; - if (iDictWithRealValues === null) { + if (!iDictWithRealValues) { iDictWithRealValues = {}; } @@ -22,228 +24,143 @@ function fillDictWithDefaults(iDictWithRealValues, iDictWithDefaultValues) { return aMyNewObject; } -//------------------------------------------------------------------------------------------------------ -// -//------------------------------------------------------------------------------------------------------ -var Version = function Version(strVersion) { - 'use strict'; - var regexS = '(\\d+)(\\.(\\d+))?(\\.(\\d+))?', - regex = new RegExp(regexS), - results = regex.exec(strVersion); - - // if no match, default to version 0.0.0 - if (results === null) { - results = regex.exec('0.0.0'); - } - - this.major = parseInt(results[1] || 0, 10); - this.minor = parseInt(results[3] || 0, 10); - this.rev = parseInt(results[5] || 0, 10); +const defaultHotKeys = { + playPause: { + ctrlKey: false, + altKey: false, + shiftKey: false, + keyCode: 179, + }, + prev: { + ctrlKey: false, + altKey: false, + shiftKey: false, + keyCode: 177, + }, + next: { + ctrlKey: false, + altKey: false, + shiftKey: false, + keyCode: 176, + }, + addToFavorite: { + ctrlKey: false, + altKey: true, + shiftKey: false, + keyCode: 76, + }, + whatZatSong: { + ctrlKey: false, + altKey: true, + shiftKey: false, + keyCode: 87, + }, + jumpToDeezer: { + ctrlKey: false, + altKey: true, + shiftKey: false, + keyCode: 74, + }, }; -Version.prototype.toString = function () { - 'use strict'; - return this.major + '.' + this.minor + '.' + this.rev; -}; - -// returns -1 if this < otherVersion, 0 if this == otherVersion, and +1 if otherVersion < this -Version.prototype.compare = function (otherVersion) { - 'use strict'; - if (this.major !== otherVersion.major) { - return this.major - otherVersion.major; - } - - if (this.minor !== otherVersion.minor) { - return this.minor - otherVersion.minor; - } - - return this.rev - otherVersion.rev; -}; +export const hotkeyNames = Object.keys(defaultHotKeys); //------------------------------------------------------------------------------------------------------ // //------------------------------------------------------------------------------------------------------ -var LOCSTO = LOCSTO || { - loadOptions: function () { - 'use strict'; - this.popupStyle = this.get('popup_style') || 'large'; +export class LocalStorage { + async loadOptions() { + const storage = await chrome.storage.sync.get('options'); + const options = storage.options || {}; + + this.installedVersion = options.installedVersion || '0.0.0'; + this.popup = fillDictWithDefaults(options.popup, { style: 'large' }); // notifications - this.notifications = fillDictWithDefaults(this.get('notifications'), { + this.notifications = fillDictWithDefaults(options.notifications, { never: true, onSongChange: false, onHotKeyOnly: false, }); // hot keys - this.prevHotKey = fillDictWithDefaults(this.get('prevHotKey'), { - ctrlKey: false, - altKey: false, - shiftKey: false, - keyCode: 177, - }); - this.playPauseHotKey = fillDictWithDefaults(this.get('playPauseHotKey'), { - ctrlKey: false, - altKey: false, - shiftKey: false, - keyCode: 179, - }); - this.nextHotKey = fillDictWithDefaults(this.get('nextHotKey'), { - ctrlKey: false, - altKey: false, - shiftKey: false, - keyCode: 176, - }); - this.addToFavoriteHotKey = fillDictWithDefaults(this.get('addToFavoriteHotKey'), { - ctrlKey: false, - altKey: true, - shiftKey: false, - keyCode: 76, - }); - this.whatZatSongHotKey = fillDictWithDefaults(this.get('whatZatSongHotKey'), { - ctrlKey: false, - altKey: true, - shiftKey: false, - keyCode: 87, - }); - this.jumpToDeezerHotKey = fillDictWithDefaults(this.get('jumpToDeezerHotKey'), { - ctrlKey: false, - altKey: true, - shiftKey: false, - keyCode: 74, + const optionsHotKeys = options.hotkeys || {}; + this.hotkeys = {}; + hotkeyNames.forEach((name) => { + this.hotkeys[name] = fillDictWithDefaults(optionsHotKeys[name], defaultHotKeys[name]); }); // misc options - this.miscOptions = fillDictWithDefaults(this.get('miscOptions'), { + this.misc = fillDictWithDefaults(options.misc, { limitDeezerToOneTab: true, hasHotkeysPermission: false, }); // new options to show the user - this.newOptionsToShow = this.get('newOptionsToShow') || false; - }, + this.newOptionsToShow = options.hasNewOptions || false; + } + + async loadSession() { + const storage = await chrome.storage.session.get('session'); + const session = storage.session || {}; - loadSession: function () { // session data, needed for event page reload // playersTabs: tab id of all opened players, ordered by playing order // playersData: infos on all opened players // deezerData: currently playing info - this.session = fillDictWithDefaults(this.get('session'), { + this.session = fillDictWithDefaults(session, { playersTabs: [], playersData: {}, deezerData: null, notifData: null, - jumpBackToActiveTab: { windowId: 0, tabId: 0 }, }); - }, + } - updateModel: function () { - 'use strict'; - var installedVersion = new Version(this.get('installedVersion')), - extensionVersion = new Version(chrome.app.getDetails().version), - this_ = this; // for async function calls + async loadPreviousActiveTab() { + const storage = await chrome.storage.session.get('previousActiveTab'); + const previousActiveTab = storage.previousActiveTab || {}; - // migrate from window.localStorage to chrome.storage.sync - chrome.storage.sync.get(null, function (items) { - if (items) { - return; - } - - this_.storeToChromeStorage(); + this.previousActiveTab = fillDictWithDefaults(previousActiveTab, { + windowId: 0, + tabId: 0, }); + } - // model update finished, store newly installed version - this.set('installedVersion', extensionVersion.toString()); - }, - - savePopupStyle: function () { - 'use strict'; - this.set('popup_style', this.popupStyle); - }, - - saveHotKeys: function () { - 'use strict'; - this.set('prevHotKey', this.prevHotKey); - this.set('playPauseHotKey', this.playPauseHotKey); - this.set('nextHotKey', this.nextHotKey); - this.set('whatZatSongHotKey', this.whatZatSongHotKey); - this.set('jumpToDeezerHotKey', this.jumpToDeezerHotKey); - }, - - saveNotifications: function () { - 'use strict'; - this.set('notifications', this.notifications); - }, - - saveMiscOptions: function () { - 'use strict'; - this.set('miscOptions', this.miscOptions); - }, - - saveNewOptionsToShow: function () { - 'use strict'; - this.set('newOptionsToShow', this.newOptionsToShow); - }, + async updateModel() { + const storage = await chrome.storage.sync.get('installedVersion'); + const installedVersion = new Version(storage.installedVersion); + const extensionVersion = new Version(chrome.runtime.getManifest().version); - saveSession: function () { - 'use strict'; - this.set('session', this.session); - }, + // NOOP - /* - * generic methods - */ - - set: function (iKey, iValue) { - 'use strict'; - try { - LOCSTO.remove(iKey); - window.localStorage.setItem(iKey, JSON.stringify(iValue)); - this.storeToChromeStorage(); - } catch (ignore) {} - }, + // model update finished, store newly installed version + this.installedVersion = extensionVersion.toString(); + await chrome.storage.sync.set({ + installedVersion: extensionVersion.toString(), + }); + } - storeToChromeStorage() { - 'use strict'; - chrome.storage.sync.set({ - installedVersion: this.get('installedVersion'), + async saveOptions() { + await chrome.storage.sync.set({ options: { - popup: { style: this.popupStyle }, + popup: this.popup, notifications: this.notifications, - misc: this.miscOptions, + misc: this.misc, hasNewOptions: this.newOptionsToShow, - hotkeys: { - prev: this.prevHotKey, - playPause: this.playPauseHotKey, - next: this.nextHotKey, - addToFavorite: this.addToFavoriteHotKey, - whatZatSong: this.whatZatSongHotKey, - jumpToDeezer: this.jumpToDeezerHotKey, - }, + hotkeys: this.hotkeys, }, - session: this.session, }); - }, - - get: function (iKey) { - 'use strict'; - var aLocalValue = window.localStorage.getItem(iKey); - try { - return JSON.parse(aLocalValue); - } catch (e) { - return aLocalValue; - } - }, + } - remove: function (iKey) { - 'use strict'; - return window.localStorage.removeItem(iKey); - }, + async saveSession() { + await chrome.storage.session.set({ + session: this.session, + }); + } - clear: function () { - 'use strict'; - window.localStorage.clear(); - }, -}; -LOCSTO.loadOptions(); -LOCSTO.loadSession(); + async savePreviousActiveTab() { + await chrome.storage.session.set({ + previousActiveTab: this.previousActiveTab, + }); + } +} diff --git a/scripts/options.js b/scripts/options.js index 7bf3926..8cf5149 100644 --- a/scripts/options.js +++ b/scripts/options.js @@ -1,311 +1,316 @@ -window.addEventListener('hashchange', function (/*e*/) { - 'use strict'; - showSection(location.hash.replace(/^\#/, '')); -}); -window.addEventListener('load', preparePage); +import { LocalStorage, hotkeyNames } from './localstorage.js'; -function sendOptionsChangedMessage() { - chrome.runtime.sendMessage({ type: 'optionsChanged' }); -} +const LOCSTO = new LocalStorage(); +await LOCSTO.loadOptions(); -function showSaveStatus(statusId) { - $('#' + statusId) - .stop(true, true) - .text(chrome.i18n.getMessage('options_page_options_saved')) - .show() - .fadeOut(1500); -} +$(() => { + window.addEventListener('hashchange', function (/*e*/) { + 'use strict'; + showSection(location.hash.replace(/^\#/, '')); + }); -function preparePage() { - 'use strict'; - i18n.process(document); + function showSaveStatus(statusId) { + $('#' + statusId) + .stop(true, true) + .text(chrome.i18n.getMessage('options_page_options_saved')) + .show() + .fadeOut(1500); + } - preparePage_welcome(); - preparePage_style(); - preparePage_hotKeys(); - preparePage_notifs(); - preparePage_misc(); + function preparePage() { + 'use strict'; + i18n.process(document); - resetSections(); -} + preparePage_welcome(); + preparePage_style(); + preparePage_hotKeys(); + preparePage_notifs(); + preparePage_misc(); -function preparePage_welcome() { - 'use strict'; - $('#button_rate_extension').attr( - 'href', - 'https://chrome.google.com/webstore/detail/' + chrome.i18n.getMessage('@@extension_id') - ); -} + resetSections(); + } -function changeStyle(iPopupStyle) { - 'use strict'; - loadStyle(iPopupStyle); -} + function preparePage_welcome() { + 'use strict'; + $('#button_rate_extension').attr( + 'href', + 'https://chrome.google.com/webstore/detail/' + chrome.i18n.getMessage('@@extension_id') + ); + } -function preparePage_style() { - 'use strict'; + function changeStyle(iPopupStyle) { + 'use strict'; + $('#popup_style_css').attr('href', 'css/' + iPopupStyle + '/popup.css'); + } - // create interactivity - $('#popup_style_chooser').change(function () { - changeStyle($('#popup_style_chooser').val()); - }); - $('#button_save_style').click(savePopupStyle); + function preparePage_style() { + 'use strict'; - // restore value - $('#popup_style_chooser').val(LOCSTO.popupStyle); -} + // create interactivity + $('#popup_style_chooser').change(function () { + changeStyle($('#popup_style_chooser').val()); + }); + $('#button_save_style').click(savePopupStyle); -function preparePage_hotKeys() { - 'use strict'; + // restore value + $('#popup_style_chooser').val(LOCSTO.popup.style); + } - // create interactivity - $('#button_activate_hotkeys').click(activateHotKeys); - $('#button_disable_hotkeys').click(disableHotKeys); - $('#button_save_hotkeys').click(saveHotKeys); - - // restore value - restoreHotkey('prevHotKey'); - restoreHotkey('playPauseHotKey'); - restoreHotkey('nextHotKey'); - restoreHotkey('addToFavoriteHotKey'); - restoreHotkey('whatZatSongHotKey'); - restoreHotkey('jumpToDeezerHotKey'); - - // hot keys are activated only if we have permission on all tabs - // if we don't permission, show an explanation - chrome.permissions.contains({ origins: [''] }, function (granted) { - displayHotKeysOptions(granted); - }); -} + function preparePage_hotKeys() { + 'use strict'; -function preparePage_notifs() { - 'use strict'; + // create interactivity + $('#button_activate_hotkeys').click(activateHotKeys); + $('#button_disable_hotkeys').click(disableHotKeys); + $('#button_save_hotkeys').click(saveHotKeys); - // create interactivity - $('#button_save_notifications').click(saveNotifications); + // restore value + hotkeyNames.forEach(restoreHotkey); - // restore value - if (LOCSTO.notifications.onSongChange) { - $('input:radio[name="notifs_show_when"]').filter('[value="on_song_change"]').prop('checked', true); - } else if (LOCSTO.notifications.onHotKeyOnly) { - $('input:radio[name="notifs_show_when"]').filter('[value="on_hotkey_only"]').prop('checked', true); - } else { - $('input:radio[name="notifs_show_when"]').filter('[value="never"]').prop('checked', true); + // hot keys are activated only if we have permission on all tabs + // if we don't permission, show an explanation + chrome.permissions.contains({ origins: [''] }, function (granted) { + displayHotKeysOptions(granted); + }); } -} -function preparePage_misc() { - 'use strict'; + function preparePage_notifs() { + 'use strict'; - // create interactivity - $('#miscLimitDeezerToOneTab > .yes_no_bar') - .children('button') - .click(function () { - if (!$(this).hasClass('btn_selected')) { - $(this).parent().children('button').toggleClass('btn_selected btn_unselected'); - } - }); - $('#button_save_misc').click(saveMiscOptions); - - // restore value - $('#miscLimitDeezerToOneTab > .yes_no_bar') - .children('button:eq(0)') - .toggleClass('btn_unselected', !LOCSTO.miscOptions.limitDeezerToOneTab) - .toggleClass('btn_selected', LOCSTO.miscOptions.limitDeezerToOneTab); - $('#miscLimitDeezerToOneTab > .yes_no_bar') - .children('button:eq(1)') - .toggleClass('btn_unselected', LOCSTO.miscOptions.limitDeezerToOneTab) - .toggleClass('btn_selected', !LOCSTO.miscOptions.limitDeezerToOneTab); -} + // create interactivity + $('#button_save_notifications').click(saveNotifications); -function displayHotKeysOptions(granted) { - 'use strict'; + // restore value + if (LOCSTO.notifications.onSongChange) { + $('input:radio[name="notifs_show_when"]').filter('[value="on_song_change"]').prop('checked', true); + } else if (LOCSTO.notifications.onHotKeyOnly) { + $('input:radio[name="notifs_show_when"]').filter('[value="on_hotkey_only"]').prop('checked', true); + } else { + $('input:radio[name="notifs_show_when"]').filter('[value="never"]').prop('checked', true); + } + } - $('#hotkey_permission_ko').toggle(!granted); - $('#hotkey_permission_ok').toggle(granted); -} + function preparePage_misc() { + 'use strict'; + + // create interactivity + $('#miscLimitDeezerToOneTab > .yes_no_bar') + .children('button') + .click(function () { + if (!$(this).hasClass('btn_selected')) { + $(this).parent().children('button').toggleClass('btn_selected btn_unselected'); + } + }); + $('#button_save_misc').click(saveMiscOptions); + + // restore value + $('#miscLimitDeezerToOneTab > .yes_no_bar') + .children('button:eq(0)') + .toggleClass('btn_unselected', !LOCSTO.misc.limitDeezerToOneTab) + .toggleClass('btn_selected', LOCSTO.misc.limitDeezerToOneTab); + $('#miscLimitDeezerToOneTab > .yes_no_bar') + .children('button:eq(1)') + .toggleClass('btn_unselected', LOCSTO.misc.limitDeezerToOneTab) + .toggleClass('btn_selected', !LOCSTO.misc.limitDeezerToOneTab); + } -function restoreHotkey(iHotKeyName) { - 'use strict'; + function displayHotKeysOptions(granted) { + 'use strict'; - var aHotKeySelect = $('#' + iHotKeyName); + $('#hotkey_permission_ko').toggle(!granted); + $('#hotkey_permission_ok').toggle(granted); + } - // on click, all button are selected - aHotKeySelect.children('button').click(function () { - $(this).toggleClass('btn_selected btn_unselected'); - }); - aHotKeySelect.children('span').css('visibility', 'visible'); - - // last button: click reveals the textbox to enter new key - aHotKeySelect - .children('button:eq(3)') - .unbind('click') - .click(function () { - $(this).parent().children('span').text(''); - $(this).css('display', 'none'); - $(this).parent().children('input:eq(0)').css('display', 'inline').focus(); - }); + function restoreHotkey(iHotKeyName) { + 'use strict'; - // on key up in the input box, update last button with the new key code - aHotKeySelect.children('input:eq(0)').keydown(function () { - // if ctrl, shift or alt, prevent update and warn user - if (event.keyCode >= 16 && event.keyCode <= 18) { - $(this).parent().children('span').text(chrome.i18n.getMessage('options_page_tab_hotkeys_warning_illegal_hotkey')); - } else { - $(this).parent().children('button:eq(3)').text(convertKeyCode(event.keyCode)); - $(this).next().val(event.keyCode); // input:eq(1) + var aHotKeySelect = $('#' + iHotKeyName + 'HotKey'); + if (!aHotKeySelect.length) { + return; } - // redisplay - $(this).parent().children('button:eq(3)').css('display', 'inline').focus().blur(); - $(this).css('display', 'none').val(''); - }); + // on click, all button are selected + aHotKeySelect.children('button').click(function () { + $(this).toggleClass('btn_selected btn_unselected'); + }); + aHotKeySelect.children('span').css('visibility', 'visible'); + + // last button: click reveals the textbox to enter new key + aHotKeySelect + .children('button:eq(3)') + .unbind('click') + .click(function () { + $(this).parent().children('span').text(''); + $(this).css('display', 'none'); + $(this).parent().children('input:eq(0)').css('display', 'inline').focus(); + }); + + // on key up in the input box, update last button with the new key code + aHotKeySelect.children('input:eq(0)').keydown(function () { + // if ctrl, shift or alt, prevent update and warn user + if (event.keyCode >= 16 && event.keyCode <= 18) { + $(this) + .parent() + .children('span') + .text(chrome.i18n.getMessage('options_page_tab_hotkeys_warning_illegal_hotkey')); + } else { + $(this).parent().children('button:eq(3)').text(convertKeyCode(event.keyCode)); + $(this).next().val(event.keyCode); // input:eq(1) + } - aHotKeySelect.children('input:eq(0)').blur(function () { - $(this).css('display', 'none').val(''); - $(this).parent().children('button:eq(3)').css('display', 'inline').focus().blur(); - }); + // redisplay + $(this).parent().children('button:eq(3)').css('display', 'inline').focus().blur(); + $(this).css('display', 'none').val(''); + }); - // create display - aHotKeySelect - .children('button:eq(0)') - .toggleClass('btn_unselected', !LOCSTO[iHotKeyName].shiftKey) - .toggleClass('btn_selected', LOCSTO[iHotKeyName].shiftKey); - aHotKeySelect - .children('button:eq(1)') - .toggleClass('btn_unselected', !LOCSTO[iHotKeyName].ctrlKey) - .toggleClass('btn_selected', LOCSTO[iHotKeyName].ctrlKey); - aHotKeySelect - .children('button:eq(2)') - .toggleClass('btn_unselected', !LOCSTO[iHotKeyName].altKey) - .toggleClass('btn_selected', LOCSTO[iHotKeyName].altKey); - aHotKeySelect.children('button:eq(3)').text(convertKeyCode(LOCSTO[iHotKeyName].keyCode)); - - aHotKeySelect.children('input').attr('size', '3').css('display', 'none'); - aHotKeySelect.children('input:eq(1)').val(LOCSTO[iHotKeyName].keyCode); -} + aHotKeySelect.children('input:eq(0)').blur(function () { + $(this).css('display', 'none').val(''); + $(this).parent().children('button:eq(3)').css('display', 'inline').focus().blur(); + }); -function savePopupStyle() { - 'use strict'; + // create display + const hotkeyConfig = LOCSTO.hotkeys[iHotKeyName]; + aHotKeySelect + .children('button:eq(0)') + .toggleClass('btn_unselected', !hotkeyConfig.shiftKey) + .toggleClass('btn_selected', hotkeyConfig.shiftKey); + aHotKeySelect + .children('button:eq(1)') + .toggleClass('btn_unselected', !hotkeyConfig.ctrlKey) + .toggleClass('btn_selected', hotkeyConfig.ctrlKey); + aHotKeySelect + .children('button:eq(2)') + .toggleClass('btn_unselected', !hotkeyConfig.altKey) + .toggleClass('btn_selected', hotkeyConfig.altKey); + aHotKeySelect.children('button:eq(3)').text(convertKeyCode(hotkeyConfig.keyCode)); + + aHotKeySelect.children('input').attr('size', '3').css('display', 'none'); + aHotKeySelect.children('input:eq(1)').val(hotkeyConfig.keyCode); + } - LOCSTO.popupStyle = $('#popup_style_chooser').val(); - LOCSTO.savePopupStyle(); - sendOptionsChangedMessage(); + async function savePopupStyle() { + 'use strict'; - // Update status to let user know options were saved. - showSaveStatus('status_style'); -} + LOCSTO.popup.style = $('#popup_style_chooser').val(); + await LOCSTO.saveOptions(); -function saveHotKeys() { - 'use strict'; + // Update status to let user know options were saved. + showSaveStatus('status_style'); + } - storeHotKey('prevHotKey'); - storeHotKey('playPauseHotKey'); - storeHotKey('nextHotKey'); - storeHotKey('addToFavoriteHotKey'); - storeHotKey('whatZatSongHotKey'); - storeHotKey('jumpToDeezerHotKey'); - LOCSTO.saveHotKeys(); - sendOptionsChangedMessage(); - - // Update status to let user know options were saved. - showSaveStatus('status_hotkeys'); -} + async function saveHotKeys() { + 'use strict'; -function storeHotKey(iHotKeyName) { - 'use strict'; + hotkeyNames.forEach((name) => { + storeHotKey(name); + }); - var aHotKeyDiv = $('#' + iHotKeyName); - LOCSTO[iHotKeyName].shiftKey = aHotKeyDiv.children('button:eq(0)').hasClass('btn_selected'); - LOCSTO[iHotKeyName].ctrlKey = aHotKeyDiv.children('button:eq(1)').hasClass('btn_selected'); - LOCSTO[iHotKeyName].altKey = aHotKeyDiv.children('button:eq(2)').hasClass('btn_selected'); - LOCSTO[iHotKeyName].keyCode = parseInt(aHotKeyDiv.children('input:eq(1)').val(), 10); -} + await LOCSTO.saveOptions(); -function saveHotkeysPermission(granted) { - LOCSTO.miscOptions.hasHotkeysPermission = granted; - LOCSTO.saveMiscOptions(); - sendOptionsChangedMessage(); -} + // Update status to let user know options were saved. + showSaveStatus('status_hotkeys'); + } -function saveNotifications() { - 'use strict'; + function storeHotKey(iHotKeyName) { + 'use strict'; - var aNotifsShowWhen = $('input:radio[name="notifs_show_when"]:checked').val(); + var aHotKeyDiv = $('#' + iHotKeyName + 'HotKey'); + if (!aHotKeyDiv.length) { + return; + } - if (aNotifsShowWhen === 'on_song_change') { - LOCSTO.notifications = { never: false, onSongChange: true, onHotKeyOnly: false }; - } else if (aNotifsShowWhen === 'on_hotkey_only') { - LOCSTO.notifications = { never: false, onSongChange: false, onHotKeyOnly: true }; + LOCSTO.hotkeys[iHotKeyName].shiftKey = aHotKeyDiv.children('button:eq(0)').hasClass('btn_selected'); + LOCSTO.hotkeys[iHotKeyName].ctrlKey = aHotKeyDiv.children('button:eq(1)').hasClass('btn_selected'); + LOCSTO.hotkeys[iHotKeyName].altKey = aHotKeyDiv.children('button:eq(2)').hasClass('btn_selected'); + LOCSTO.hotkeys[iHotKeyName].keyCode = parseInt(aHotKeyDiv.children('input:eq(1)').val(), 10); } - LOCSTO.saveNotifications(); - sendOptionsChangedMessage(); + async function saveHotkeysPermission(granted) { + LOCSTO.misc.hasHotkeysPermission = granted; + await LOCSTO.saveOptions(); + } - // Update status_style to let user know options were saved. - showSaveStatus('status_notifs'); + async function saveNotifications() { + 'use strict'; - // reshow notifs so that the user sees the change straight away - chrome.runtime.sendMessage({ type: 'showNotif' }); -} + var aNotifsShowWhen = $('input:radio[name="notifs_show_when"]:checked').val(); -function saveMiscOptions() { - 'use strict'; + if (aNotifsShowWhen === 'on_song_change') { + LOCSTO.notifications = { never: false, onSongChange: true, onHotKeyOnly: false }; + } else if (aNotifsShowWhen === 'on_hotkey_only') { + LOCSTO.notifications = { never: false, onSongChange: false, onHotKeyOnly: true }; + } - // limit deezer to one tab - LOCSTO.miscOptions.limitDeezerToOneTab = $('#miscLimitDeezerToOneTab > .yes_no_bar') - .children('button:eq(0)') - .hasClass('btn_selected'); - LOCSTO.saveMiscOptions(); - sendOptionsChangedMessage(); + await LOCSTO.saveOptions(); - // Update status to let user know options were saved. - showSaveStatus('status_misc'); -} + // Update status_style to let user know options were saved. + showSaveStatus('status_notifs'); -// deal with chrome permissions -function activateHotKeys() { - 'use strict'; - chrome.permissions.request({ origins: [''] }, function (granted) { - if (granted) { - chrome.runtime.sendMessage({ type: 'injectHotKeysJsOnAllTabs' }); - } - displayHotKeysOptions(granted); - saveHotkeysPermission(granted); - }); -} + // reshow notifs so that the user sees the change straight away + chrome.runtime.sendMessage({ type: 'showNotif' }); + } -function disableHotKeys() { - 'use strict'; - chrome.permissions.remove({ origins: [''] }, function (removed) { - displayHotKeysOptions(!removed); - saveHotkeysPermission(!removed); - }); -} + async function saveMiscOptions() { + 'use strict'; -// navigation bar -function resetSections() { - 'use strict'; + // limit deezer to one tab + LOCSTO.misc.limitDeezerToOneTab = $('#miscLimitDeezerToOneTab > .yes_no_bar') + .children('button:eq(0)') + .hasClass('btn_selected'); - $('#tab_chooser > nav > a:first').addClass('currentone'); - $('#center_block > section:first').css('display', 'block'); - if (location.hash) { - showSection(location.hash.replace(/^\#/, '')); + await LOCSTO.saveOptions(); + + // Update status to let user know options were saved. + showSaveStatus('status_misc'); } -} -function showSection(id) { - 'use strict'; - var aNewLeft; + // deal with chrome permissions + const hotkeyPermissions = { origins: [''], permissions: ['scripting'] }; + function activateHotKeys() { + 'use strict'; + chrome.permissions.request(hotkeyPermissions, function (granted) { + if (granted) { + chrome.runtime.sendMessage({ type: 'injectHotKeysJsOnAllTabs' }); + } + displayHotKeysOptions(granted); + saveHotkeysPermission(granted); + }); + } - $('#tab_chooser > nav > a').removeClass('currentone'); - $('#center_block > section:not(#' + id + ')').css('display', 'none'); - $('#' + id).css('display', 'block'); - $('#' + id + '_nav').addClass('currentone'); + function disableHotKeys() { + 'use strict'; + chrome.permissions.remove(hotkeyPermissions, function (removed) { + displayHotKeysOptions(!removed); + saveHotkeysPermission(!removed); + }); + } - aNewLeft = $('#' + id + '_nav').position().left - $('#tab_chooser > nav > a:first').position().left + 73; - $('#arrow_line > span').css('left', aNewLeft + 'px'); -} + // navigation bar + function resetSections() { + 'use strict'; + + $('#tab_chooser > nav > a:first').addClass('currentone'); + $('#center_block > section:first').css('display', 'block'); + if (location.hash) { + showSection(location.hash.replace(/^\#/, '')); + } + } + + function showSection(id) { + 'use strict'; + var aNewLeft; + + $('#tab_chooser > nav > a').removeClass('currentone'); + $('#center_block > section:not(#' + id + ')').css('display', 'none'); + $('#' + id).css('display', 'block'); + $('#' + id + '_nav').addClass('currentone'); + + aNewLeft = $('#' + id + '_nav').position().left - $('#tab_chooser > nav > a:first').position().left + 73; + $('#arrow_line > span').css('left', aNewLeft + 'px'); + } + + preparePage(); +}); function convertKeyCode(iKeyCode) { 'use strict'; diff --git a/scripts/player_listener.js b/scripts/player_listener.js index 2469cd9..0e85bb7 100644 --- a/scripts/player_listener.js +++ b/scripts/player_listener.js @@ -57,7 +57,7 @@ function getDeezerData() { // send player's data to the background page function sendJsonPlayerInfo() { 'use strict'; - chrome.runtime.sendMessage({ type: 'now_playing_updated', nowPlayingData: getDeezerData() }); + chrome.runtime.sendMessage({ type: 'updateSession', nowPlayingData: getDeezerData() }); } // perform actions on the deezer page diff --git a/scripts/popup.js b/scripts/popup.js index 31b50fa..3351a6a 100644 --- a/scripts/popup.js +++ b/scripts/popup.js @@ -1,7 +1,9 @@ -window.addEventListener('load', function (/*e*/) { - 'use strict'; - jQueryExtension(); - preparePopup(); +$(() => { + $.fn.extend({ + visibilityToggle: function (showOrHide) { + return this.css('visibility', showOrHide === true ? 'visible' : 'hidden'); + }, + }); }); function preparePopup() { @@ -9,43 +11,43 @@ function preparePopup() { loadStyle(); // add interactivity - $('#control-prev').click(function () { + $('#control-prev').on('click', () => { executePlayerAction('previoustrack'); return false; }); - $('#control-pause').click(function () { + $('#control-pause').on('click', () => { executePlayerAction('pause'); return false; }); - $('#control-play').click(function () { + $('#control-play').on('click', () => { executePlayerAction('play'); return false; }); - $('#control-next').click(function () { + $('#control-next').on('click', () => { executePlayerAction('nexttrack'); return false; }); - $('#control-like').click(function () { + $('#control-like').on('click', () => { executePlayerAction('like'); return false; }); - $('#now_playing_info_track').click(function () { + $('#now_playing_info_track').on('click', () => { executeDoAction('linkCurrentSong'); return false; }); - $('#now_playing_info_artist').click(function () { + $('#now_playing_info_artist').on('click', () => { executeDoAction('linkCurrentArtist'); return false; }); // add tooltip in case of ellipsis (onmouseover to force recompute in the event of style change) - $('#now_playing_info > span').mouseover(function () { + $('#now_playing_info > span').mouseover(() => { var title = ''; if (this.offsetWidth < this.scrollWidth) { title = $(this).text(); @@ -61,7 +63,7 @@ function preparePopup() { function loadStyle(iPopupStyle) { 'use strict'; - var aPopupStyle = iPopupStyle || LOCSTO.popupStyle, + var aPopupStyle = iPopupStyle || LOCSTO.popup.style, aPopupCss = $('#popup_style_css'); if (aPopupStyle !== null && aPopupCss.length) { aPopupCss.attr('href', 'css/' + aPopupStyle + '/popup.css'); @@ -80,46 +82,52 @@ function executeDoAction(iAction) { chrome.runtime.sendMessage({ type: 'doAction', action: iAction }); } -function refreshPopup() { - 'use strict'; +function updatePopup(playingData) { + if (!playingData) { + $('#now_playing_info').hide(); + $('#control-pause').hide(); + $('#control-like').removeClass('is_liked').addClass('not_liked'); + $('#cover').attr('src', 'imgs/unknown_cd.png'); + return; + } - // get now playing info from background page - chrome.runtime.sendMessage({ type: 'getDeezerData' }, function (aNowPlayingData) { - if (aNowPlayingData !== null) { - // is song liked? - var isLiked = aNowPlayingData.dz_is_liked === 'true'; - $('#control-like').toggleClass('not_liked', !isLiked).toggleClass('is_liked', isLiked); - - // show pause or play button - var showPause = aNowPlayingData.dz_playing === 'true'; - $('#control-play').css('display', 'inline-block').toggle(!showPause); - $('#control-pause').css('display', 'inline-block').toggle(showPause); - - // set track title and artist - $('#now_playing_info').show(); - $('#now_playing_info_track').text(aNowPlayingData.dz_track); - $('#now_playing_info_artist').text(aNowPlayingData.dz_artist); - - // show or hide prev / next buttons if needed - $('#control-prev').visibilityToggle(aNowPlayingData.dz_is_prev_active === 'true'); - $('#control-next').visibilityToggle(aNowPlayingData.dz_is_next_active === 'true'); - - // get the cover - $('#cover').attr('src', aNowPlayingData.dz_cover); - } else { - $('#now_playing_info').hide(); - $('#control-pause').hide(); - $('#control-like').removeClass('is_liked').addClass('not_liked'); - $('#cover').attr('src', 'imgs/unknown_cd.png'); - } - }); -} + // precache covers + new Image().src = playingData.dz_prev_cover; + new Image().src = playingData.dz_cover; + new Image().src = playingData.dz_next_cover; -function jQueryExtension() { - 'use strict'; - $.fn.extend({ - visibilityToggle: function (showOrHide) { - return this.css('visibility', showOrHide === true ? 'visible' : 'hidden'); - }, - }); + // is song liked? + var isLiked = playingData.dz_is_liked === 'true'; + $('#control-like').toggleClass('not_liked', !isLiked).toggleClass('is_liked', isLiked); + + // show pause or play button + var showPause = playingData.dz_playing === 'true'; + $('#control-play').css('display', 'inline-block').toggle(!showPause); + $('#control-pause').css('display', 'inline-block').toggle(showPause); + + // set track title and artist + $('#now_playing_info').show(); + $('#now_playing_info_track').text(playingData.dz_track); + $('#now_playing_info_artist').text(playingData.dz_artist); + + // show or hide prev / next buttons if needed + $('#control-prev').visibilityToggle(playingData.dz_is_prev_active === 'true'); + $('#control-next').visibilityToggle(playingData.dz_is_next_active === 'true'); + + // get the cover + $('#cover').attr('src', playingData.dz_cover); } + +chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { + switch (request.type) { + case 'updateSession': + updatePopup(request.nowPlayingData); + break; + } + + return false; +}); + +// initial load +const playingData = await chrome.runtime.sendMessage({ type: 'getDeezerData' }); +updatePopup(playingData); diff --git a/scripts/service_worker.js b/scripts/service_worker.js new file mode 100644 index 0000000..68148e0 --- /dev/null +++ b/scripts/service_worker.js @@ -0,0 +1,22 @@ +import { LocalStorage } from './localstorage.js'; + +import './worker/install.js'; +import './worker/session.js'; +import './worker/actionbutton.js'; +import './worker/optionalhotkeys.js'; + +async function executePlayerAction({ action, source }) { + console.log('player', action, source); + + const LOCSTO = new LocalStorage(); + await LOCSTO.loadSession(); + + // send the wanted action to the deezer tab + if (LOCSTO.session.playersTabs.length > 0) { + chrome.tabs.sendMessage(LOCSTO.session.playersTabs[0], { action }); + } +} + +function executeDoAction({ action, source }) { + console.log('do', action, source); +} diff --git a/scripts/worker/actionbutton.js b/scripts/worker/actionbutton.js new file mode 100644 index 0000000..d25b5a0 --- /dev/null +++ b/scripts/worker/actionbutton.js @@ -0,0 +1,86 @@ +import { LocalStorage } from '../localstorage.js'; + +// if no popup is set, it means that we should open a new tab with default player +chrome.action.onClicked.addListener(() => { + chrome.tabs.create({ url: 'https://www.deezer.com' }); +}); + +// if no popup is set, it means that we should open a new tab with default player +// chrome.action.onClicked.addListener(function () { +// 'use strict'; + +// // extension has just been updated, a click will open the option page +// if (LOCSTO.newOptionsToShow) { +// chrome.tabs.create({ url: '/options.html' }); + +// // user has seen what's new, restore normal use case +// LOCSTO.newOptionsToShow = false; +// LOCSTO.saveNewOptionsToShow(); + +// chrome.action.setBadgeText({ text: '' }); +// setUpPopup(); + +// propagatePlayingDataToAllTabs(); +// } +// // else: normal use case +// else { +// // TODO add default player +// chrome.tabs.create({ url: 'http://www.deezer.com' }); +// } +// }); + +// set up the popup: if at least one deezer tab is opened, we'll show the popup +// otherwise, open a new deezer tab +// function setUpPopup() { +// 'use strict'; + +// // extension has just been updated, show new items +// if (LOCSTO.newOptionsToShow) { +// chrome.action.setBadgeBackgroundColor({ color: '#FF0000' }); +// chrome.action.setBadgeText({ text: chrome.app.getDetails().version }); +// chrome.action.setTitle({ title: chrome.i18n.getMessage('showNewItemsTitle') }); +// chrome.action.setPopup({ popup: '' }); // don't create a popup, we want to open the options page +// } +// // else: normal use case +// else if (LOCSTO.session.playersTabs.length === 0) { +// LOCSTO.session.deezerData = null; // reset playing data +// chrome.action.setTitle({ title: chrome.i18n.getMessage('defaultTitle') }); +// chrome.action.setPopup({ popup: '' }); // no deezer tab is opened, so don't create a popup +// NOTIFS.destroyNotif(); +// } else { +// chrome.action.setPopup({ popup: '/popup.html' }); // at least one deezer tab is opened, create a popup +// } +// } + +function updateActionTitle(nowPlayingData) { + const track = nowPlayingData?.dz_track; + const artist = nowPlayingData?.dz_artist; + const newTitle = track && artist ? track + ' - ' + artist : undefined; + chrome.action.setTitle({ title: newTitle }); + chrome.action.setPopup({ popup: '/popup.html' }); +} + +function setupOpenDeezerAction() { + chrome.action.setTitle({ title: chrome.i18n.getMessage('defaultTitle') }); + chrome.action.setPopup({ popup: '' }); +} + +chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { + switch (request.type) { + case 'updateSession': + updateActionTitle(request.nowPlayingData); + break; + } + + return false; +}); + +chrome.storage.onChanged.addListener((changes, areaName) => { + if (areaName !== 'session') { + return; + } + + if (changes.session && changes.session.newValue.playersTabs.length === 0) { + setupOpenDeezerAction(); + } +}); diff --git a/scripts/worker/install.js b/scripts/worker/install.js new file mode 100644 index 0000000..e6048dd --- /dev/null +++ b/scripts/worker/install.js @@ -0,0 +1,25 @@ +import { LocalStorage } from '../localstorage.js'; + +// install, update, or chrome update +chrome.runtime.onInstalled.addListener(async (details) => { + if (details.reason !== 'install' && details.reason !== 'update') { + return; + } + + // insert expected scripts on all pages matching the patterns in the manifest + const contentScripts = chrome.runtime.getManifest().content_scripts; + contentScripts.forEach(async (contentScript) => { + const matchingTabs = await chrome.tabs.query({ url: contentScript.matches }); + matchingTabs.forEach((tab) => { + chrome.scripting.executeScript({ + target: { tabId: tab.id }, + files: contentScript.js, + }); + }); + }); + + // update local storage if needed + const LOCSTO = new LocalStorage(); + await LOCSTO.loadOptions(); + await LOCSTO.updateModel(); +}); diff --git a/scripts/notifications.js b/scripts/worker/notifications.js similarity index 75% rename from scripts/notifications.js rename to scripts/worker/notifications.js index 54e2634..138d9bd 100644 --- a/scripts/notifications.js +++ b/scripts/worker/notifications.js @@ -95,3 +95,48 @@ if (chrome.notifications !== undefined) { } }); } + +chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { + (async () => { + switch (request.type) { + case 'showNotif': + await showNotif(true); + break; + } + })(); + + return false; +}); + +function showNotif(iForceRedisplay) { + 'use strict'; + + // if no deezer data, close notif, otherwise show it + if (LOCSTO.session.deezerData === null) { + NOTIFS.destroyNotif(); + } + // we have data to show + else { + // update or create notification + if (LOCSTO.notifications.never) { + NOTIFS.destroyNotif(); + return; + } + + // force a full redisplay of the notifs + var forceRedisplay = + LOCSTO.notifications.onSongChange || + (LOCSTO.notifications.onHotKeyOnly && gActionOnHotKey) || + gActionOnNotifButton || + iForceRedisplay === true; + + // if we don't have permission to display notifications, close notif if present + chrome.permissions.contains({ permissions: ['notifications'] }, function (granted) { + if (granted) { + NOTIFS.createNotif(forceRedisplay); + } else { + NOTIFS.destroyNotif(); + } + }); + } +} diff --git a/scripts/worker/optionalhotkeys.js b/scripts/worker/optionalhotkeys.js new file mode 100644 index 0000000..ebc1d63 --- /dev/null +++ b/scripts/worker/optionalhotkeys.js @@ -0,0 +1,99 @@ +import { LocalStorage } from '../localstorage.js'; + +const hotkeysPermissions = { origins: [''] }; + +chrome.runtime.onInstalled.addListener(async (details) => { + if (details.reason !== 'install' && details.reason !== 'update') { + return; + } + + // re-inject hotkeys on all opened tabs + chrome.permissions.contains(hotkeysPermissions, (granted) => { + if (granted) { + injectHotKeysJsOnAllTabs(); + } + }); +}); + +chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { + (async () => { + switch (request.type) { + case 'hotkey': + processHotKey(request.action); + break; + + case 'injectHotKeysJsOnAllTabs': + injectHotKeysJsOnAllTabs(); + break; + } + })(); + + return false; +}); + +// using sendResponse requires return true +chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { + (async () => { + switch (request.type) { + case 'loadHotKeys': + sendResponse(await loadHotKeys()); + break; + } + })(); + + return true; +}); + +// inject hotkeys.js on any page if user allowed it +chrome.webNavigation.onCommitted.addListener((details) => { + chrome.permissions.contains(hotkeysPermissions, (granted) => { + if (granted) { + injectHotKeysJsOnTab(details); + } + }); + + // recount number of opened player tabs + //setUpPopup(); +}); + +async function processHotKey(action) { + switch (action) { + case 'playPause': + case 'next': + case 'prev': + case 'addToFavorite': + await chrome.runtime.sendMessage({ type: 'controlPlayer', command: action, source: 'hotkey' }); + break; + + case 'whatZatSong': + case 'jumpToDeezer': + await chrome.runtime.sendMessage({ type: 'doAction', action: action, source: 'hotkey' }); + break; + } +} + +async function loadHotKeys() { + const LOCSTO = new LocalStorage(); + await LOCSTO.loadOptions(); + return LOCSTO.hotkeys; +} + +function injectHotKeysJsOnTab(tab) { + if (tab.frameId !== 0 || !tab.url.startsWith('http')) { + return; + } + + chrome.scripting.executeScript({ + target: { tabId: tab.id || tab.tabId }, + files: ['/scripts/hotkeys.js'], + }); +} + +async function injectHotKeysJsOnAllTabs() { + const windows = await chrome.windows.getAll({ populate: true }); + windows.forEach(async (window) => { + window.tabs.forEach(async (tab) => { + injectHotKeysJsOnTab(tab); + }); + }); +} diff --git a/scripts/worker/session.js b/scripts/worker/session.js new file mode 100644 index 0000000..3ccbb04 --- /dev/null +++ b/scripts/worker/session.js @@ -0,0 +1,151 @@ +import { LocalStorage } from '../localstorage.js'; + +chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { + (async () => { + switch (request.type) { + case 'updateSession': + await updateSession(sender.tab, request.nowPlayingData); + break; + + case 'remove_me': + await removePlayerTabId(sender.tab.id); + break; + } + })(); + + return false; +}); + +// using sendResponse requires return true +chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { + (async () => { + switch (request.type) { + case 'getDeezerData': + const LOCSTO = new LocalStorage(); + await LOCSTO.loadSession(); + sendResponse(LOCSTO.session.deezerData); + break; + } + })(); + + return true; +}); + +// save active tab any time it changes to be able to go back to it +chrome.tabs.onActivated.addListener(async (tabInfo) => { + const LOCSTO = new LocalStorage(); + await LOCSTO.loadSession(); + await LOCSTO.loadPreviousActiveTab(); + + const windowId = tabInfo.windowId; + const tabId = tabInfo.tabId; + + // ignore active tab if current active player: we don't want to go back to it! + if (LOCSTO.session.playersTabs.indexOf(tabId) !== 0) { + LOCSTO.previousActiveTab.windowId = windowId; + LOCSTO.previousActiveTab.tabId = tabId; + await LOCSTO.savePreviousActiveTab(); + } +}); + +async function updateSession(playerTab, nowPlayingData) { + const LOCSTO = new LocalStorage(); + await LOCSTO.loadSession(); + await LOCSTO.loadOptions(); + + console.log('updateSession', nowPlayingData); + + // player has just been loaded + const playerTabId = playerTab.id; + + // player is inactive, remove it + if (nowPlayingData.dz_is_active !== 'true') { + removePlayerTabId(playerTabId); + return; + } + + // check limit one page per player + if (LOCSTO.session.playersTabs.indexOf(playerTabId) === -1) { + if (LOCSTO.misc.limitDeezerToOneTab) { + for (const tabIdStr in LOCSTO.session.playersData) { + if (nowPlayingData.dz_name === LOCSTO.session.playersData[tabIdStr].dz_name) { + // close opening tab + chrome.tabs.remove(playerTabId); + + // move to already opened tab + await jumpToTab(parseInt(tabIdStr, 10)); + return; + } + } + } + + LOCSTO.session.playersTabs.push(playerTabId); + } + + // update player info + LOCSTO.session.playersData[playerTabId] = nowPlayingData; + + // change of player? + if (playerTabId !== LOCSTO.session.playersTabs[0]) { + // current player is not playing / new player is playing + if (LOCSTO.session.deezerData.dz_playing !== 'true' || nowPlayingData.dz_playing === 'true') { + // stops current player + await chrome.runtime.sendMessage({ type: 'controlPlayer', action: 'pause' }); + + // reorder tabs order + const index = LOCSTO.session.playersTabs.indexOf(playerTabId); + LOCSTO.session.playersTabs.splice(index, 1); + LOCSTO.session.playersTabs.splice(0, 0, playerTabId); + + LOCSTO.session.deezerData = nowPlayingData; + } + } else { + // same player + LOCSTO.session.deezerData = nowPlayingData; + } + + await LOCSTO.saveSession(); + + // reset the fact that action is on media key event + // gActionOnHotKey = false; + // gActionOnNotifButton = false; +} + +// remove closing tab id from players tabs +chrome.tabs.onRemoved.addListener(removePlayerTabId); +async function removePlayerTabId(tabId) { + const LOCSTO = new LocalStorage(); + await LOCSTO.loadSession(); + + const index = LOCSTO.session.playersTabs.indexOf(tabId); + if (index > -1) { + LOCSTO.session.playersTabs.splice(index, 1); + + // current player is closed, move to the next and resume playing + if (index === 0 && LOCSTO.session.playersTabs.length > 0) { + const isPlaying = LOCSTO.session.deezerData.dz_playing; + LOCSTO.session.deezerData = LOCSTO.session.playersData[LOCSTO.session.playersTabs[0]]; + LOCSTO.session.deezerData.dz_playing = isPlaying; + + if (isPlaying === 'true') { + chrome.runtime.sendMessage({ type: 'controlPlayer', command: 'play' }); + } + } + } + + if (LOCSTO.session.playersData.hasOwnProperty(tabId)) { + delete LOCSTO.session.playersData[tabId]; + } + + await LOCSTO.saveSession(); +} + +async function jumpToTab(tabId) { + const tab = await chrome.tabs.get(tabId); + if (!tab) { + return; + } + + chrome.windows.update(tab.windowId, { focused: true }); + chrome.tabs.update(tab.id, { selected: true }); +}