diff --git a/global.d.ts b/global.d.ts index ddd21c7..2305609 100644 --- a/global.d.ts +++ b/global.d.ts @@ -1,4 +1,9 @@ declare module '*.md' { var _: string export default _ +} + +declare namespace chrome +{ + export const action = chrome.browserAction } \ No newline at end of file diff --git a/src/manifest.json b/src/manifest.json index 685e9aa..cbb83d7 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -1,7 +1,17 @@ { + "manifest_version": 3, "name": "Watch on LBRY", "version": "2.0.0", + "icons": { + "16": "assets/icons/wol/icon16.png", + "48": "assets/icons/wol/icon48.png", + "128": "assets/icons/wol/icon128.png" + }, "permissions": [ + "tabs", + "storage" + ], + "host_permissions": [ "https://www.youtube.com/", "https://yewtu.be/", "https://vid.puffyan.us/", @@ -11,10 +21,24 @@ "https://lbry.tv/", "https://odysee.com/", "https://madiator.com/", - "https://finder.madiator.com/", - "tabs", - "storage" + "https://finder.madiator.com/" ], + "web_accessible_resources": [{ + "resources": [ + "pages/popup/index.html", + "pages/YTtoLBRY/index.html", + "pages/import/index.html", + "assets/icons/lbry/lbry-logo.svg", + "assets/icons/lbry/odysee-logo.svg", + "assets/icons/lbry/madiator-logo.svg" + ], + "matches": [""], + "extension_ids": [] + }], + "action": { + "default_title": "Watch on LBRY", + "default_popup": "pages/popup/index.html" + }, "content_scripts": [ { "matches": [ @@ -30,28 +54,6 @@ } ], "background": { - "scripts": [ - "settings/background.js", - "scripts/background.js" - ], - "persistent": true - }, - "browser_action": { - "default_title": "Watch on LBRY", - "default_popup": "pages/popup/index.html" - }, - "web_accessible_resources": [ - "pages/popup/index.html", - "pages/YTtoLBRY/index.html", - "pages/import/index.html", - "assets/icons/lbry/lbry-logo.svg", - "assets/icons/lbry/odysee-logo.svg", - "assets/icons/lbry/madiator-logo.svg" - ], - "icons": { - "16": "assets/icons/wol/icon16.png", - "48": "assets/icons/wol/icon48.png", - "128": "assets/icons/wol/icon128.png" - }, - "manifest_version": 2 + "service_worker": "service-worker-entry-point.js" + } } \ No newline at end of file diff --git a/src/modules/crypto/index.ts b/src/modules/crypto/index.ts index 442e06a..7953d9c 100644 --- a/src/modules/crypto/index.ts +++ b/src/modules/crypto/index.ts @@ -1,10 +1,10 @@ import path from 'path' -import { DialogManager } from '../../components/dialogs' +import type { DialogManager } from '../../components/dialogs' import { getExtensionSettingsAsync, setExtensionSetting, ytUrlResolversSettings } from "../../settings" import { getFileContent } from '../file' async function generateKeys() { - const keys = await window.crypto.subtle.generateKey( + const keys = await crypto.subtle.generateKey( { name: "RSASSA-PKCS1-v1_5", // Consider using a 4096-bit key for systems that require long-term security @@ -23,7 +23,7 @@ async function generateKeys() { } async function exportPrivateKey(key: CryptoKey) { - const exported = await window.crypto.subtle.exportKey( + const exported = await crypto.subtle.exportKey( "pkcs8", key ) @@ -34,7 +34,7 @@ const publicKeyPrefix = `MEwwDQYJKoZIhvcNAQEBBQADOwAwOAIxA` const publicKeySuffix = `IDAQAB` //`wIDAQAB` `WIDAQAB` const publicKeyLength = 65 async function exportPublicKey(key: CryptoKey) { - const exported = await window.crypto.subtle.exportKey( + const exported = await crypto.subtle.exportKey( "spki", key ) @@ -43,7 +43,7 @@ async function exportPublicKey(key: CryptoKey) { } function importPrivateKey(base64: string) { - return window.crypto.subtle.importKey( + return crypto.subtle.importKey( "pkcs8", Buffer.from(base64, 'base64'), { @@ -56,7 +56,7 @@ function importPrivateKey(base64: string) { } export async function sign(data: string, privateKey: string) { - return Buffer.from(await window.crypto.subtle.sign( + return Buffer.from(await crypto.subtle.sign( { name: "RSASSA-PKCS1-v1_5" }, await importPrivateKey(privateKey), await crypto.subtle.digest({ name: 'SHA-1' }, Buffer.from(data)) diff --git a/src/modules/yt/index.ts b/src/modules/yt/index.ts index 243462e..7fd0bdb 100644 --- a/src/modules/yt/index.ts +++ b/src/modules/yt/index.ts @@ -1,4 +1,3 @@ - interface YtExportedJsonSubscription { id: string etag: string diff --git a/src/modules/yt/urlCache.ts b/src/modules/yt/urlCache.ts index 9d2535d..7bb5e22 100644 --- a/src/modules/yt/urlCache.ts +++ b/src/modules/yt/urlCache.ts @@ -2,6 +2,8 @@ let db: IDBDatabase | null = null +if (typeof chrome.extension === 'undefined') throw new Error("YT urlCache can only be accessed from extension windows and service-workers.") + if (typeof self.indexedDB !== 'undefined') { const openRequest = indexedDB.open("yt-url-resolver-cache") openRequest.addEventListener('upgradeneeded', () => openRequest.result.createObjectStore("store").createIndex("expireAt", "expireAt")) @@ -81,5 +83,5 @@ async function get(id: string): Promise { return response.value } -export const LbryPathnameCache = { put, get, clearAll } +export const lbryUrlCache = { put, get, clearAll } diff --git a/src/modules/yt/urlResolve.ts b/src/modules/yt/urlResolve.ts index 6792237..212108e 100644 --- a/src/modules/yt/urlResolve.ts +++ b/src/modules/yt/urlResolve.ts @@ -2,7 +2,7 @@ import { chunk } from "lodash" import path from "path" import { getExtensionSettingsAsync, ytUrlResolversSettings } from "../../settings" import { sign } from "../crypto" -import { LbryPathnameCache } from "./urlCache" +import { lbryUrlCache } from "./urlCache" const QUERY_CHUNK_SIZE = 100 @@ -27,7 +27,7 @@ export async function resolveById(params: Paramaters, progressCallback?: (progre // Check for cache first, add them to the results if there are any cache // And remove them from the params, so we dont request for them params = (await Promise.all(params.map(async (item) => { - const cachedLbryUrl = await LbryPathnameCache.get(item.id) + const cachedLbryUrl = await lbryUrlCache.get(item.id) // Cache can be null, if there is no lbry url yet if (cachedLbryUrl !== undefined) { @@ -58,7 +58,7 @@ export async function resolveById(params: Paramaters, progressCallback?: (progre for (const item of params) { const lbryUrl = (item.type === 'channel' ? response.data.channels : response.data.videos)?.[item.id]?.replaceAll('#', ':') ?? null // we cache it no matter if its null or not - await LbryPathnameCache.put(lbryUrl, item.id) + await lbryUrlCache.put(lbryUrl, item.id) if (lbryUrl) results[item.id] = { id: lbryUrl, type: item.type } } diff --git a/src/pages/import/main.tsx b/src/pages/import/main.tsx index 7c71e81..0af9aed 100644 --- a/src/pages/import/main.tsx +++ b/src/pages/import/main.tsx @@ -3,6 +3,21 @@ import { useState } from 'preact/hooks' import { createDialogManager, Dialogs } from '../../components/dialogs' import { importProfileKeysFromFile, inputKeyFile } from '../../modules/crypto' +export async function openImportPopup() { + const importPopupWindow = open( + '/pages/import/index.html', + 'Import Profile', + [ + `height=${Math.max(document.body.clientHeight, screen.height * .5)}`, + `width=${document.body.clientWidth}`, + `toolbar=0,menubar=0,location=0`, + `top=${screenY}`, + `left=${screenX}` + ].join(',')) + close() + importPopupWindow?.focus() +} + function ImportPage() { const [loading, updateLoading] = useState(() => false) diff --git a/src/pages/popup/main.tsx b/src/pages/popup/main.tsx index 0216879..1b9e319 100644 --- a/src/pages/popup/main.tsx +++ b/src/pages/popup/main.tsx @@ -2,8 +2,9 @@ import { h, render } from 'preact' import { useState } from 'preact/hooks' import { createDialogManager, Dialogs } from '../../components/dialogs' import { exportProfileKeysAsFile, friendlyPublicKey, generateProfileAndSetNickname, getProfile, purgeProfile, resetProfileSettings } from '../../modules/crypto' -import { LbryPathnameCache } from '../../modules/yt/urlCache' +import { lbryUrlCache } from '../../modules/yt/urlCache' import { getTargetPlatfromSettingsEntiries, getYtUrlResolversSettingsEntiries, setExtensionSetting, useExtensionSettings } from '../../settings' +import { openImportPopup } from '../import/main' /** Gets all the options for redirect destinations as selection options */ @@ -31,21 +32,6 @@ function WatchOnLbryPopup(params: { profile: Awaited { @@ -100,7 +86,7 @@ function WatchOnLbryPopup(params: { profile: Awaited exportProfileKeysAsFile()} className={`button active`}> Export - importButtonClick()} + openImportPopup()} className={`button`} > Import @@ -139,7 +125,7 @@ function WatchOnLbryPopup(params: { profile: AwaitedYou don't have a profile.

You can either import keypair for an existing profile or generate a new profile keypair.

- loads(LbryPathnameCache.clearAll().then(() => dialogManager.alert("Cleared Cache!")))} className={`button active`}> + loads(lbryUrlCache.clearAll().then(() => dialogManager.alert("Cleared Cache!")))} className={`button active`}> Clear Resolver Cache diff --git a/src/scripts/ytContent.tsx b/src/scripts/ytContent.tsx index 286922e..b1f97cb 100644 --- a/src/scripts/ytContent.tsx +++ b/src/scripts/ytContent.tsx @@ -1,6 +1,6 @@ import { h, render } from 'preact' import { parseYouTubeURLTimeString } from '../modules/yt' -import { resolveById } from '../modules/yt/urlResolve' +import type { resolveById } from '../modules/yt/urlResolve' import { getExtensionSettingsAsync, getSourcePlatfromSettingsFromHostname, TargetPlatform, targetPlatformSettings } from '../settings' const sleep = (t: number) => new Promise(resolve => setTimeout(resolve, t)) diff --git a/src/service-worker-entry-point.ts b/src/service-worker-entry-point.ts new file mode 100644 index 0000000..918688b --- /dev/null +++ b/src/service-worker-entry-point.ts @@ -0,0 +1,2 @@ +import './settings/background' +import './scripts/background' \ No newline at end of file diff --git a/src/settings/background.ts b/src/settings/background.ts index 4472a2d..19dc28f 100644 --- a/src/settings/background.ts +++ b/src/settings/background.ts @@ -1,31 +1,29 @@ -import { DEFAULT_SETTINGS, ExtensionSettings, getExtensionSettingsAsync, setExtensionSetting, targetPlatformSettings, ytUrlResolversSettings } from '.' - +import { DEFAULT_SETTINGS, ExtensionSettings, getExtensionSettingsAsync, setExtensionSetting, targetPlatformSettings, ytUrlResolversSettings } from '../settings' /** Reset settings to default value and update the browser badge text */ async function initSettings() { - let settings = await getExtensionSettingsAsync() + let settings = await getExtensionSettingsAsync() - // get all the values that aren't set and use them as a change set - const invalidEntries = (Object.entries(DEFAULT_SETTINGS) as Array<[keyof ExtensionSettings, ExtensionSettings[keyof ExtensionSettings]]>) - .filter(([k]) => settings[k] === undefined || settings[k] === null) + // get all the values that aren't set and use them as a change set + const invalidEntries = (Object.entries(DEFAULT_SETTINGS) as Array<[keyof ExtensionSettings, ExtensionSettings[keyof ExtensionSettings]]>) + .filter(([k]) => settings[k] === undefined || settings[k] === null) - // fix our local var and set it in storage for later - if (invalidEntries.length > 0) { - const changeSet = Object.fromEntries(invalidEntries) - chrome.storage.local.set(changeSet) - settings = await getExtensionSettingsAsync() - } + // fix our local var and set it in storage for later + if (invalidEntries.length > 0) { + const changeSet = Object.fromEntries(invalidEntries) + chrome.storage.local.set(changeSet) + settings = await getExtensionSettingsAsync() + } - if (!Object.keys(targetPlatformSettings).includes(settings.targetPlatform)) setExtensionSetting('targetPlatform', DEFAULT_SETTINGS.targetPlatform) - if (!Object.keys(ytUrlResolversSettings).includes(settings.urlResolver)) setExtensionSetting('urlResolver', DEFAULT_SETTINGS.urlResolver) + if (!Object.keys(targetPlatformSettings).includes(settings.targetPlatform)) setExtensionSetting('targetPlatform', DEFAULT_SETTINGS.targetPlatform) + if (!Object.keys(ytUrlResolversSettings).includes(settings.urlResolver)) setExtensionSetting('urlResolver', DEFAULT_SETTINGS.urlResolver) - chrome.browserAction.setBadgeText({ text: settings.redirect ? 'ON' : 'OFF' }) + chrome.action.setBadgeText({ text: settings.redirect ? 'ON' : 'OFF' }) } chrome.storage.onChanged.addListener((changes, areaName) => { - if (areaName !== 'local' || !changes.redirect) return - chrome.browserAction.setBadgeText({ text: changes.redirect.newValue ? 'ON' : 'OFF' }) + if (areaName !== 'local' || !changes.redirect) return + chrome.action.setBadgeText({ text: changes.redirect.newValue ? 'ON' : 'OFF' }) }) - chrome.runtime.onStartup.addListener(initSettings) -chrome.runtime.onInstalled.addListener(initSettings) +chrome.runtime.onInstalled.addListener(initSettings) \ No newline at end of file diff --git a/src/settings/index.ts b/src/settings/index.ts index 9a17990..eda0ee5 100644 --- a/src/settings/index.ts +++ b/src/settings/index.ts @@ -1,4 +1,4 @@ -import { JSX } from "preact" +import type { JSX } from "preact" import { useEffect, useReducer } from "preact/hooks" export interface ExtensionSettings { diff --git a/tsconfig.json b/tsconfig.json index 01f4ad3..79de8a7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -41,6 +41,7 @@ // "noUnusedParameters": true, /* Report errors on unused parameters. */ // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + "importsNotUsedAsValues": "error", /* Import types always with `import type` */ /* Module Resolution Options */ // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ @@ -53,7 +54,6 @@ "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - /* Source Map Options */ // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */