<commit_before>'use strict';

/*
 * The extension uses `browser.runtime.sendMessage` function for communication
 * between different modules using the following message types:
 *
 * 1) events:
 *  - EVENT_STATE_CHANGED: The connector state is changed
 *    @param  {Object} state Connector state
 *  - EVENT_SONG_UPDATED: The current song is updated
 *    @param  {Object} data Song instance copy
 *  - EVENT_READY: The connector is injected and the controller is created
 *  - EVENT_PING: The 'ping' event to check if connector is injected
 *
 * 2) requests:
 *  - REQUEST_GET_SONG: Get now playing song
 *    @return {Object} Song instance copy
 *  - REQUEST_CORRECT_SONG: Correct song info
 *    @param  {Object} data Object contains corrected song info
 *  - REQUEST_TOGGLE_LOVE: Toggle song love status
 *    @param  {Boolean} isLoved Flag indicates song is loved
 *  - REQUEST_RESET_SONG: Reset corrected song info
 *  - REQUEST_SKIP_SONG: Ignore (don't scrobble) current song
 *  - REQUEST_AUTHENTICATE: Authenticate scrobbler
 *    @param  {String} scrobbler Scrobbler label
 */

define((require) => {
	const GA = require('service/ga');
	const Util = require('util/util');
	const Inject = require('browser/inject');
	const Options = require('storage/options');
	const Migrate = require('util/migrate');
	const browser = require('webextension-polyfill');
	const Controller = require('object/controller');
	const InjectResult = require('object/inject-result');
	const BrowserAction = require('browser/browser-action');
	const Notifications = require('browser/notifications');
	const ControllerMode = require('object/controller-mode');
	const BrowserStorage = require('storage/browser-storage');
	const ScrobbleService = require('object/scrobble-service');

	/**
	 * How many times to show auth notification.
	 *
	 * @type {Number}
	 */
	const AUTH_NOTIFICATION_DISPLAY_COUNT = 3;

	/**
	 * Current version of the extension.
	 *
	 * @type {String}
	 */
	const extVersion = browser.runtime.getManifest().version;

	/**
	 * Single controller instance for each tab with injected script.
	 * This allows us to work with tabs independently.
	 *
	 * @type {Object}
	 */
	const tabControllers = {};

	/**
	 * Array of versions have notable changes.
	 *
	 * @type {Array}
	 */
	const versionsToNotify = [];

	/**
	 * Flag for "page session" where at least single injection occurred.
	 * Used for tracking number of actually active users.
	 *
	 * @type {Boolean}
	 */
	let isActiveSession = false;

	const notificationStorage = BrowserStorage.getStorage(BrowserStorage.NOTIFICATIONS);

	const browserAction = new BrowserAction();

	/**
	 * ID of a recent active tab.
	 * @type {Number}
	 */
	let activeTabId = null;

	/**
	 * ID of a current tab.
	 * @type {Number}
	 */
	let currentTabId = null;

	/**
	 * Setup browser event listeners. Called on startup.
	 */
	function setupEventListeners() {
		try {
			browser.commands.onCommand.addListener(onCommand);
		} catch (e) {
			// Don't let the extension fail on Firefox for Android.
		}

		browser.tabs.onUpdated.addListener(onTabUpdated);
		browser.tabs.onRemoved.addListener(onTabRemoved);
		browser.tabs.onActivated.addListener((activeInfo) => {
			onTabChanged(activeInfo.tabId);
		});

		browser.runtime.onMessage.addListener(onMessage);
		browser.runtime.onConnect.addListener((port) => {
			port.onMessage.addListener((message) => {
				onPortMessage(message, port.sender);
			});
		});
	}

	/**
	 * Called when a command is executed.
	 *
	 * @param  {String} command Command ID
	 */
	async function onCommand(command) {
		const ctrl = tabControllers[activeTabId] || tabControllers[currentTabId];
		if (!ctrl) {
			return;
		}

		switch (command) {
			case 'toggle-connector':
				setConnectorState(ctrl, !ctrl.isEnabled);
				break;
			case 'love-song':
			case 'unlove-song': {
				const isLoved = command === 'love-song';

				await ctrl.toggleLove(isLoved);
				browserAction.setSongLoved(isLoved, ctrl.getCurrentSong());
				break;
			}
		}
	}

	/**
	 * Called when something sent message to background script.
	 *
	 * @param  {Object} request Message sent by the calling script
	 */
	async function onMessage(request) {
		switch (request.type) {
			case 'REQUEST_AUTHENTICATE': {
				const scrobblerLabel = request.scrobbler;
				const scrobbler = ScrobbleService.getScrobblerByLabel(scrobblerLabel);

				if (scrobbler) {
					authenticateScrobbler(scrobbler);
				}

				return;
			}

			case 'REQUEST_ACTIVE_TABID':
				return activeTabId;
		}

		const tabId = request.tabId;
		const ctrl = tabControllers[tabId];

		if (!ctrl) {
			console.warn(
				`Attempted to send event to controller for tab ${tabId}`);
			return;
		}

		switch (request.type) {
			case 'REQUEST_GET_SONG':
				return ctrl.getCurrentSong().getCloneableData();

			case 'REQUEST_CORRECT_SONG':
				ctrl.setUserSongData(request.data);
				break;

			case 'REQUEST_TOGGLE_LOVE':
				await ctrl.toggleLove(request.data.isLoved);
				return request.data.isLoved;

			case 'REQUEST_SKIP_SONG':
				ctrl.skipCurrentSong();
				break;

			case 'REQUEST_RESET_SONG':
				ctrl.resetSongData();
				break;
		}
	}

	/**
	 * Called when something sent message to background script via port.
	 *<commit_msg>Show request type in warning<commit_after>'use strict';

/*
 * The extension uses `browser.runtime.sendMessage` function for communication
 * between different modules using the following message types:
 *
 * 1) events:
 *  - EVENT_STATE_CHANGED: The connector state is changed
 *    @param  {Object} state Connector state
 *  - EVENT_SONG_UPDATED: The current song is updated
 *    @param  {Object} data Song instance copy
 *  - EVENT_READY: The connector is injected and the controller is created
 *  - EVENT_PING: The 'ping' event to check if connector is injected
 *
 * 2) requests:
 *  - REQUEST_GET_SONG: Get now playing song
 *    @return {Object} Song instance copy
 *  - REQUEST_CORRECT_SONG: Correct song info
 *    @param  {Object} data Object contains corrected song info
 *  - REQUEST_TOGGLE_LOVE: Toggle song love status
 *    @param  {Boolean} isLoved Flag indicates song is loved
 *  - REQUEST_RESET_SONG: Reset corrected song info
 *  - REQUEST_SKIP_SONG: Ignore (don't scrobble) current song
 *  - REQUEST_AUTHENTICATE: Authenticate scrobbler
 *    @param  {String} scrobbler Scrobbler label
 */

define((require) => {
	const GA = require('service/ga');
	const Util = require('util/util');
	const Inject = require('browser/inject');
	const Options = require('storage/options');
	const Migrate = require('util/migrate');
	const browser = require('webextension-polyfill');
	const Controller = require('object/controller');
	const InjectResult = require('object/inject-result');
	const BrowserAction = require('browser/browser-action');
	const Notifications = require('browser/notifications');
	const ControllerMode = require('object/controller-mode');
	const BrowserStorage = require('storage/browser-storage');
	const ScrobbleService = require('object/scrobble-service');

	/**
	 * How many times to show auth notification.
	 *
	 * @type {Number}
	 */
	const AUTH_NOTIFICATION_DISPLAY_COUNT = 3;

	/**
	 * Current version of the extension.
	 *
	 * @type {String}
	 */
	const extVersion = browser.runtime.getManifest().version;

	/**
	 * Single controller instance for each tab with injected script.
	 * This allows us to work with tabs independently.
	 *
	 * @type {Object}
	 */
	const tabControllers = {};

	/**
	 * Array of versions have notable changes.
	 *
	 * @type {Array}
	 */
	const versionsToNotify = [];

	/**
	 * Flag for "page session" where at least single injection occurred.
	 * Used for tracking number of actually active users.
	 *
	 * @type {Boolean}
	 */
	let isActiveSession = false;

	const notificationStorage = BrowserStorage.getStorage(BrowserStorage.NOTIFICATIONS);

	const browserAction = new BrowserAction();

	/**
	 * ID of a recent active tab.
	 * @type {Number}
	 */
	let activeTabId = null;

	/**
	 * ID of a current tab.
	 * @type {Number}
	 */
	let currentTabId = null;

	/**
	 * Setup browser event listeners. Called on startup.
	 */
	function setupEventListeners() {
		try {
			browser.commands.onCommand.addListener(onCommand);
		} catch (e) {
			// Don't let the extension fail on Firefox for Android.
		}

		browser.tabs.onUpdated.addListener(onTabUpdated);
		browser.tabs.onRemoved.addListener(onTabRemoved);
		browser.tabs.onActivated.addListener((activeInfo) => {
			onTabChanged(activeInfo.tabId);
		});

		browser.runtime.onMessage.addListener(onMessage);
		browser.runtime.onConnect.addListener((port) => {
			port.onMessage.addListener((message) => {
				onPortMessage(message, port.sender);
			});
		});
	}

	/**
	 * Called when a command is executed.
	 *
	 * @param  {String} command Command ID
	 */
	async function onCommand(command) {
		const ctrl = tabControllers[activeTabId] || tabControllers[currentTabId];
		if (!ctrl) {
			return;
		}

		switch (command) {
			case 'toggle-connector':
				setConnectorState(ctrl, !ctrl.isEnabled);
				break;
			case 'love-song':
			case 'unlove-song': {
				const isLoved = command === 'love-song';

				await ctrl.toggleLove(isLoved);
				browserAction.setSongLoved(isLoved, ctrl.getCurrentSong());
				break;
			}
		}
	}

	/**
	 * Called when something sent message to background script.
	 *
	 * @param  {Object} request Message sent by the calling script
	 */
	async function onMessage(request) {
		const { data, tabId, type } = request;

		switch (type) {
			case 'REQUEST_AUTHENTICATE': {
				const scrobblerLabel = request.scrobbler;
				const scrobbler = ScrobbleService.getScrobblerByLabel(scrobblerLabel);

				if (scrobbler) {
					authenticateScrobbler(scrobbler);
				}

				return;
			}

			case 'REQUEST_ACTIVE_TABID':
				return activeTabId;
		}

		const ctrl = tabControllers[tabId];

		if (!ctrl) {
			console.warn(
				`Attempted to send ${type} event to controller for tab ${tabId}`);
			return;
		}

		switch (request.type) {
			case 'REQUEST_GET_SONG':
				return ctrl.getCurrentSong().getCloneableData();

			case 'REQUEST_CORRECT_SONG':
				ctrl.setUserSongData(data);
				break;

			case 'REQUEST_TOGGLE_LOVE':
				await ctrl.toggleLove(data.isLoved);
				return data.isLoved;

			case 'REQUEST_SKIP_SONG':
				ctrl.skipCurrentSong();
				break;

			case 'REQUEST_RESET_SONG':
				ctrl.resetSongData();
				break;
		}
	}

	/**
	 * Called when something sent message to background script via port.
	 *
{'fraction_non_alphanumeric': 0.11474469305794607, 'fraction_numerical': 0.0005737234652897303, 'mean_word_length': 3.6985624438454625, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1986420', 'n_tokens_mistral': 3630, 'n_tokens_neox': 3164, 'n_words': 857}