extension: Lint after TypeScript migration

This commit is contained in:
relrelb 2021-04-23 21:29:38 +03:00 committed by Mike Welsh
parent cfc57a2e1d
commit 488e4421e9
10 changed files with 201 additions and 67 deletions

View File

@ -1,9 +0,0 @@
{
"env": {
"browser": true,
"webextensions": true
},
"globals": {
"__webpack_public_path__": true
}
}

View File

@ -0,0 +1,17 @@
{
"env": {
"browser": true,
"webextensions": true
},
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
],
"rules": {
"@typescript-eslint/no-non-null-assertion": "off"
}
}

View File

@ -1,10 +1,15 @@
import * as utils from "./utils";
type WebResponseHeadersDetails = chrome.webRequest.WebResponseHeadersDetails | browser.webRequest._OnHeadersReceivedDetails;
function isSwf(details: WebResponseHeadersDetails) {
function isSwf(
details:
| chrome.webRequest.WebResponseHeadersDetails
| browser.webRequest._OnHeadersReceivedDetails
) {
// TypeScript doesn't compile without this explicit type delaration.
const headers: (chrome.webRequest.HttpHeader | browser.webRequest._HttpHeaders)[] = details.responseHeaders!;
const headers: (
| chrome.webRequest.HttpHeader
| browser.webRequest._HttpHeaders
)[] = details.responseHeaders!;
const typeHeader = headers.find(
({ name }) => name.toLowerCase() === "content-type"
);
@ -12,8 +17,8 @@ function isSwf(details: WebResponseHeadersDetails) {
return false;
}
const mime = typeHeader.value!
.toLowerCase()
const mime = typeHeader
.value!.toLowerCase()
.match(/^\s*(.*?)\s*(?:;.*)?$/)![1];
// Some sites (e.g. swfchan.net) might (wrongly?) send octet-stream, so check file extension too.
@ -26,7 +31,11 @@ function isSwf(details: WebResponseHeadersDetails) {
return mime === "application/x-shockwave-flash";
}
function onHeadersReceived(details: WebResponseHeadersDetails) {
function onHeadersReceived(
details:
| chrome.webRequest.WebResponseHeadersDetails
| browser.webRequest._OnHeadersReceivedDetails
) {
if (isSwf(details)) {
const baseUrl = utils.runtime.getURL("player.html");
return { redirectUrl: `${baseUrl}?url=${details.url}` };
@ -57,7 +66,7 @@ function disable() {
enable();
}
utils.storage.onChanged.addListener((changes: any, namespace: string) => {
utils.storage.onChanged.addListener((changes, namespace) => {
if (
namespace === "sync" &&
Object.prototype.hasOwnProperty.call(changes, "ruffleEnable")

View File

@ -5,12 +5,15 @@ function camelize(s: string) {
}
export interface Options {
ruffleEnable: boolean,
ignoreOptout: boolean,
ruffleEnable: boolean;
ignoreOptout: boolean;
}
function getBooleanElements() {
const elements: Record<string, any> = {};
const elements: Record<
string,
{ option: Element; checkbox: HTMLInputElement; label: HTMLLabelElement }
> = {};
for (const option of document.getElementsByClassName("option")) {
const [checkbox] = option.getElementsByTagName("input");
if (checkbox.type !== "checkbox") {
@ -23,7 +26,9 @@ function getBooleanElements() {
return elements;
}
export async function bindBooleanOptions(onChange?: (options: Options) => void) {
export async function bindBooleanOptions(
onChange?: (options: Options) => void
): Promise<void> {
const elements = getBooleanElements();
// Bind initial values.
@ -36,7 +41,7 @@ export async function bindBooleanOptions(onChange?: (options: Options) => void)
// TODO: click/change/input?
checkbox.addEventListener("click", () => {
const value = checkbox.checked;
options[key] = value;
options[key as keyof Options] = value;
utils.storage.sync.set({ [key]: value });
});
@ -50,7 +55,7 @@ export async function bindBooleanOptions(onChange?: (options: Options) => void)
}
// Listen for future changes.
utils.storage.onChanged.addListener((changes: Object, namespace: string) => {
utils.storage.onChanged.addListener((changes, namespace) => {
if (namespace !== "sync") {
return;
}
@ -60,7 +65,7 @@ export async function bindBooleanOptions(onChange?: (options: Options) => void)
continue;
}
elements[key].checkbox.checked = option.newValue;
options[key] = option.newValue;
options[key as keyof Options] = option.newValue;
}
if (onChange) {

View File

@ -20,7 +20,10 @@
import * as utils from "./utils";
const pendingMessages: ({ resolve(value: any): void, reject(reason?: any): void } | null)[] = [];
const pendingMessages: ({
resolve(value: unknown): void;
reject(reason?: unknown): void;
} | null)[] = [];
const uniqueMessageSuffix = Math.floor(Math.random() * 100000000000);
/**
@ -28,7 +31,7 @@ const uniqueMessageSuffix = Math.floor(Math.random() * 100000000000);
* @param {*} data - JSON-serializable data to send to main world.
* @returns {Promise<*>} JSON-serializable response from main world.
*/
function sendMessageToPage(data: any): Promise<any> {
function sendMessageToPage(data: unknown): Promise<unknown> {
const message = {
type: `FROM_RUFFLE${uniqueMessageSuffix}`,
index: pendingMessages.length,
@ -87,6 +90,12 @@ function checkPageOptout(): boolean {
return false;
}
declare global {
interface Document {
xmlVersion: string | null;
}
}
(async () => {
const options = await utils.getOptions(["ruffleEnable", "ignoreOptout"]);
const pageOptout = checkPageOptout();
@ -96,7 +105,7 @@ function checkPageOptout(): boolean {
!window.RufflePlayer &&
(options.ignoreOptout || !pageOptout);
utils.runtime.onMessage.addListener((message: any, _sender: any, sendResponse: (options: Object) => void) => {
utils.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (shouldLoad) {
sendMessageToPage(message).then((response) => {
sendResponse({

View File

@ -1 +1,2 @@
// eslint-disable-next-line @typescript-eslint/naming-convention
declare let __webpack_public_path__: string;

View File

@ -1,9 +1,15 @@
import { PublicAPI, SourceAPI, publicPath, Letterbox, LogLevel } from "ruffle-core";
import {
PublicAPI,
SourceAPI,
publicPath,
Letterbox,
LogLevel,
} from "ruffle-core";
const api = PublicAPI.negotiate(
window.RufflePlayer!,
"local",
new SourceAPI("local"),
new SourceAPI("local")
);
window.RufflePlayer = api;
__webpack_public_path__ = publicPath(api.config, "local");

View File

@ -22,7 +22,9 @@ const STATUS_COLORS = {
"status_result_disabled": "gray",
};
async function queryTabStatus(listener: (status: keyof typeof STATUS_COLORS) => void) {
async function queryTabStatus(
listener: (status: keyof typeof STATUS_COLORS) => void
) {
listener("status_init");
let tabs: chrome.tabs.Tab[] | browser.tabs.Tab[];
@ -77,7 +79,8 @@ async function queryTabStatus(listener: (status: keyof typeof STATUS_COLORS) =>
optionsChanged();
}
function objectsEqual<T extends Record<string, any>>(x: T, y: T) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function objectsEqual(x: any, y: any) {
for (const [key, value] of Object.entries(x)) {
if (y[key] !== value) {
return false;
@ -115,14 +118,20 @@ window.addEventListener("DOMContentLoaded", () => {
optionsChanged();
});
statusIndicator = document.getElementById("status-indicator") as HTMLDivElement;
statusIndicator = document.getElementById(
"status-indicator"
) as HTMLDivElement;
statusText = document.getElementById("status-text") as HTMLSpanElement;
const optionsButton = document.getElementById("options-button") as HTMLButtonElement;
const optionsButton = document.getElementById(
"options-button"
) as HTMLButtonElement;
optionsButton.textContent = utils.i18n.getMessage("open_settings_page");
optionsButton.addEventListener("click", () => utils.openOptionsPage());
reloadButton = document.getElementById("reload-button") as HTMLButtonElement;
reloadButton = document.getElementById(
"reload-button"
) as HTMLButtonElement;
reloadButton.textContent = utils.i18n.getMessage("action_reload");
reloadButton.addEventListener("click", async () => {
await utils.tabs.reload(activeTab.id!);

View File

@ -3,7 +3,7 @@ import { PublicAPI, SourceAPI, publicPath } from "ruffle-core";
const api = PublicAPI.negotiate(
window.RufflePlayer!,
"extension",
new SourceAPI("extension"),
new SourceAPI("extension")
);
window.RufflePlayer = api;
__webpack_public_path__ = publicPath(api.config, "extension");
@ -34,7 +34,11 @@ if (uniqueMessageSuffix) {
const { type, index, data } = event.data;
if (type === `FROM_RUFFLE${uniqueMessageSuffix}`) {
// Ping back.
const message = { type: `TO_RUFFLE${uniqueMessageSuffix}`, index, data };
const message = {
type: `TO_RUFFLE${uniqueMessageSuffix}`,
index,
data,
};
window.postMessage(message, "*");
}
});

View File

@ -1,19 +1,66 @@
import { Options } from "./common";
const DEFAULT_OPTIONS = {
ruffleEnable: true,
ignoreOptout: false,
};
export let i18n: any;
export let storage: any;
export let tabs: {
reload(tabId: number): Promise<void>,
query(query: chrome.tabs.QueryInfo & browser.tabs._QueryQueryInfo): Promise<chrome.tabs.Tab[] | browser.tabs.Tab[]>,
sendMessage(tabId: number, message: any, options?: chrome.tabs.MessageSendOptions & browser.tabs._SendMessageOptions): Promise<any>,
export let i18n: {
getMessage(name: string): string;
};
export let runtime: any;
export let openOptionsPage: any;
export let storage: {
local: {
get(keys: string[]): Promise<Record<string, unknown>>;
remove(keys: string[]): Promise<void>;
set(items: Record<string, unknown>): Promise<void>;
};
sync: {
get(keys: string[]): Promise<Record<string, unknown>>;
remove(keys: string[]): Promise<void>;
set(items: Record<string, unknown>): Promise<void>;
};
onChanged: {
addListener(
listener: (
changes:
| Record<string, chrome.storage.StorageChange>
| Record<string, browser.storage.StorageChange>,
areaName: string
) => void
): void;
};
};
export let tabs: {
reload(tabId: number): Promise<void>;
query(
query: chrome.tabs.QueryInfo & browser.tabs._QueryQueryInfo
): Promise<chrome.tabs.Tab[] | browser.tabs.Tab[]>;
sendMessage(
tabId: number,
message: unknown,
options?: chrome.tabs.MessageSendOptions &
browser.tabs._SendMessageOptions
): Promise<any>; // eslint-disable-line @typescript-eslint/no-explicit-any
};
export let runtime: {
onMessage: {
addListener(
listener: (
message: unknown,
sender:
| chrome.runtime.MessageSender
| browser.runtime.MessageSender,
sendResponse: (response?: unknown) => void
) => void
): void;
};
getURL(path: string): string;
};
export let openOptionsPage: () => Promise<void>;
function promisify<T>(func: (callback: (result?: T) => void) => void): Promise<T> {
function promisify<T>(
func: (callback: (result?: T) => void) => void
): Promise<T> {
return new Promise((resolve, reject) => {
func((result) => {
const error = chrome.runtime.lastError;
@ -37,38 +84,59 @@ if (typeof chrome !== "undefined") {
promisify((cb) => chrome.storage.local.get(keys, cb)),
remove: (keys: string[]) =>
promisify((cb) => chrome.storage.local.remove(keys, cb)),
set: (items: object) =>
set: (items: Record<string, unknown>) =>
promisify((cb) => chrome.storage.local.set(items, cb)),
},
sync: {
get: (keys: string[]) => promisify((cb) => chrome.storage.sync.get(keys, cb)),
get: (keys: string[]) =>
promisify((cb) => chrome.storage.sync.get(keys, cb)),
remove: (keys: string[]) =>
promisify((cb) => chrome.storage.sync.remove(keys, cb)),
set: (items: object) =>
set: (items: Record<string, unknown>) =>
promisify((cb) => chrome.storage.sync.set(items, cb)),
},
onChanged: {
addListener: (listener: (changes: chrome.storage.StorageChange, areaName: string) => void) =>
chrome.storage.onChanged.addListener(listener),
addListener: (
listener: (
changes: Record<string, chrome.storage.StorageChange>,
areaName: string
) => void
) => chrome.storage.onChanged.addListener(listener),
},
};
tabs = {
reload: (tabId: number) => promisify((cb) => chrome.tabs.reload(tabId, undefined, cb)),
query: (query: chrome.tabs.QueryInfo) => promisify((cb) => chrome.tabs.query(query, cb)),
sendMessage: (tabId: number, message: any, options: chrome.tabs.MessageSendOptions) =>
promisify((cb) => chrome.tabs.sendMessage(tabId, message, options, cb)),
reload: (tabId: number) =>
promisify((cb) => chrome.tabs.reload(tabId, undefined, cb)),
query: (query: chrome.tabs.QueryInfo) =>
promisify((cb) => chrome.tabs.query(query, cb)),
sendMessage: (
tabId: number,
message: unknown,
options: chrome.tabs.MessageSendOptions
) =>
promisify((cb) =>
chrome.tabs.sendMessage(tabId, message, options, cb)
),
};
runtime = {
onMessage: {
addListener: (listener: (message: any, sender: chrome.runtime.MessageSender, sendResponse: (response?: any) => void) => void) =>
chrome.runtime.onMessage.addListener(listener),
addListener: (
listener: (
message: unknown,
sender: chrome.runtime.MessageSender,
sendResponse: (response?: unknown) => void
) => void
) => chrome.runtime.onMessage.addListener(listener),
},
getURL: (path: string) => chrome.runtime.getURL(path),
};
openOptionsPage = () => chrome.tabs.create({ url: "/options.html" });
openOptionsPage = () =>
promisify((cb: () => void) =>
chrome.tabs.create({ url: "/options.html" }, cb)
);
} else if (typeof browser !== "undefined") {
i18n = {
getMessage: (name: string) => browser.i18n.getMessage(name),
@ -78,30 +146,45 @@ if (typeof chrome !== "undefined") {
local: {
get: (keys: string[]) => browser.storage.local.get(keys),
remove: (keys: string[]) => browser.storage.local.remove(keys),
set: (items: Record<string, any>) => browser.storage.local.set(items),
set: (items: Record<string, unknown>) =>
browser.storage.local.set(items),
},
sync: {
get: (keys: string[]) => browser.storage.sync.get(keys),
remove: (keys: string[]) => browser.storage.sync.remove(keys),
set: (items: Record<string, any>) => browser.storage.sync.set(items),
set: (items: Record<string, unknown>) =>
browser.storage.sync.set(items),
},
onChanged: {
addListener: (listener: (changes: Record<string, browser.storage.StorageChange>, areaName: string) => void) =>
browser.storage.onChanged.addListener(listener),
addListener: (
listener: (
changes: Record<string, browser.storage.StorageChange>,
areaName: string
) => void
) => browser.storage.onChanged.addListener(listener),
},
};
tabs = {
reload: (tabId: number) => browser.tabs.reload(tabId),
query: (query: browser.tabs._QueryQueryInfo) => browser.tabs.query(query),
sendMessage: (tabId: number, message: any, options: browser.tabs._SendMessageOptions) =>
browser.tabs.sendMessage(tabId, message, options),
query: (query: browser.tabs._QueryQueryInfo) =>
browser.tabs.query(query),
sendMessage: (
tabId: number,
message: unknown,
options: browser.tabs._SendMessageOptions
) => browser.tabs.sendMessage(tabId, message, options),
};
runtime = {
onMessage: {
addListener: (listener: (message: any, sender: browser.runtime.MessageSender, sendResponse: (response?: any) => void) => boolean | Promise<any> | void) =>
browser.runtime.onMessage.addListener(listener),
addListener: (
listener: (
message: unknown,
sender: browser.runtime.MessageSender,
sendResponse: (response?: unknown) => void
) => boolean | Promise<unknown> | void
) => browser.runtime.onMessage.addListener(listener),
},
getURL: (path: string) => browser.runtime.getURL(path),
};
@ -111,7 +194,7 @@ if (typeof chrome !== "undefined") {
throw new Error("Extension API not found.");
}
export async function getOptions(keys: string[]) {
export async function getOptions(keys: string[]): Promise<Options> {
const options = await storage.sync.get(keys);
// Copy over default options if they don't exist yet.