Clean up extension code

This commit is contained in:
mrjvs 2024-01-09 20:07:22 +01:00
parent ef85c217f7
commit f70d13f2c9
9 changed files with 172 additions and 98 deletions

View file

@ -0,0 +1,51 @@
import {
MessagesMetadata,
sendToBackgroundViaRelay,
} from "@plasmohq/messaging";
let activeExtension = false;
export interface ExtensionHello {
version: string;
}
function sendMessage<T, Payload>(
message: keyof MessagesMetadata,
payload: any,
timeout: number = -1,
) {
return new Promise<T | null>((resolve) => {
if (timeout >= 0) setTimeout(() => resolve(null), timeout);
sendToBackgroundViaRelay<Payload, T>({
name: message,
body: payload,
})
.then((res) => {
activeExtension = true;
resolve(res);
})
.catch(() => {
activeExtension = false;
resolve(null);
});
});
}
export async function sendExtensionRequest(
url: string,
ops: any,
): Promise<ExtensionHello | null> {
return sendMessage("proxy-request", { url, ...ops });
}
export async function extensionInfo(): Promise<ExtensionHello | null> {
return sendMessage("hello", null, 300);
}
export function isExtensionActiveCached(): boolean {
return activeExtension;
}
export async function isExtensionActive(): Promise<boolean> {
return !!(await extensionInfo());
}

View file

@ -1,6 +1,3 @@
/* eslint-disable @typescript-eslint/ban-types */
import "@plasmohq/messaging";
export interface PlasmoRequestBody {
ruleId: number;
domain: string;
@ -8,7 +5,11 @@ export interface PlasmoRequestBody {
responseHeaders?: Record<string, string>;
}
export type PlasmoResponseBody =
export interface ExtensionHelloReply {
version: string;
}
export type ExtensionRequestReply =
| {
success: true;
ruleId: number;
@ -21,11 +22,15 @@ export type PlasmoResponseBody =
interface MmMetadata {
"declarative-net-request": {
req: PlasmoRequestBody;
res: PlasmoResponseBody;
res: ExtensionRequestReply;
};
"proxy-request": {
req: PlasmoRequestBody;
res: PlasmoResponseBody;
res: ExtensionRequestReply;
};
hello: {
req: null;
res: ExtensionHelloReply;
};
}

View file

@ -1,7 +1,7 @@
import { ofetch } from "ofetch";
import { getApiToken, setApiToken } from "@/backend/helpers/providerApi";
import { getLoadbalancedProxyUrl } from "@/utils/providers";
import { getLoadbalancedProxyUrl } from "@/backend/providers/fetchers";
type P<T> = Parameters<typeof ofetch<T, any>>;
type R<T> = ReturnType<typeof ofetch<T, any>>;

View file

@ -1,12 +1,6 @@
import {
Fetcher,
ProviderControls,
makeProviders,
makeSimpleProxyFetcher,
makeStandardFetcher,
targets,
} from "@movie-web/providers";
import { Fetcher, makeSimpleProxyFetcher } from "@movie-web/providers";
import { sendExtensionRequest } from "@/backend/extension/messaging";
import { getApiToken, setApiToken } from "@/backend/helpers/providerApi";
import { getProviderApiUrls, getProxyUrls } from "@/utils/proxyUrls";
@ -48,7 +42,7 @@ async function fetchButWithApiTokens(
return response;
}
function makeLoadBalancedSimpleProxyFetcher() {
export function makeLoadBalancedSimpleProxyFetcher() {
const fetcher: Fetcher = async (a, b) => {
const currentFetcher = makeSimpleProxyFetcher(
getLoadbalancedProxyUrl(),
@ -59,10 +53,9 @@ function makeLoadBalancedSimpleProxyFetcher() {
return fetcher;
}
export const providers = makeProviders({
fetcher: makeStandardFetcher(fetch),
proxiedFetcher: makeLoadBalancedSimpleProxyFetcher(),
// TODO: Add check whether the extension is installed
// target: targets.BROWSER,
target: targets.BROWSER_EXTENSION,
}) as any as ProviderControls;
export function makeExtensionFetcher() {
const fetcher: Fetcher = async (a, b) => {
return sendExtensionRequest(a, b) as any;
};
return fetcher;
}

View file

@ -0,0 +1,26 @@
import {
makeProviders,
makeStandardFetcher,
targets,
} from "@movie-web/providers";
import { isExtensionActiveCached } from "@/backend/extension/messaging";
import {
makeExtensionFetcher,
makeLoadBalancedSimpleProxyFetcher,
} from "@/backend/providers/fetchers";
export function getProviders() {
if (isExtensionActiveCached()) {
return makeProviders({
fetcher: makeExtensionFetcher(),
target: targets.BROWSER_EXTENSION,
});
}
return makeProviders({
fetcher: makeStandardFetcher(fetch),
proxiedFetcher: makeLoadBalancedSimpleProxyFetcher(),
target: targets.BROWSER,
});
}

View file

@ -1,8 +1,6 @@
import { sendToBackgroundViaRelay } from "@plasmohq/messaging";
import fscreen from "fscreen";
import Hls, { Level } from "hls.js";
import { PlasmoRequestBody, PlasmoResponseBody } from "@/@types/plasmo";
import {
DisplayInterface,
DisplayInterfaceEvents,
@ -43,6 +41,7 @@ function qualityToHlsLevel(quality: SourceQuality): number | null {
);
return found ? +found[0] : null;
}
function hlsLevelsToQualities(levels: Level[]): SourceQuality[] {
return levels
.map((v) => hlsLevelToQuality(v))
@ -103,74 +102,65 @@ export function makeVideoElementDisplayInterface(): DisplayInterface {
function setupSource(vid: HTMLVideoElement, src: LoadableSource) {
// TODO: Add check whether the extension is installed
sendToBackgroundViaRelay<PlasmoRequestBody, PlasmoResponseBody>({
name: "declarative-net-request",
body: {
ruleId: 1,
domain: src.type === "hls" ? new URL(src.url).hostname : src.url,
requestHeaders: src.preferredHeaders,
},
}).then(() => {
if (src.type === "hls") {
if (canPlayHlsNatively(vid)) {
vid.src = processCdnLink(src.url);
vid.currentTime = startAt;
return;
}
if (!Hls.isSupported()) throw new Error("HLS not supported");
if (!hls) {
hls = new Hls({
maxBufferSize: 500 * 1000 * 1000, // 500 mb of buffering, should load more fragments at once
fragLoadPolicy: {
default: {
maxLoadTimeMs: 30 * 1000, // allow it load extra long, fragments are slow if requested for the first time on an origin
maxTimeToFirstByteMs: 30 * 1000,
errorRetry: {
maxNumRetry: 2,
retryDelayMs: 1000,
maxRetryDelayMs: 8000,
},
timeoutRetry: {
maxNumRetry: 3,
maxRetryDelayMs: 0,
retryDelayMs: 0,
},
},
},
});
hls.on(Hls.Events.ERROR, (event, data) => {
console.error("HLS error", data);
if (data.fatal) {
emit("error", {
message: data.error.message,
stackTrace: data.error.stack,
errorName: data.error.name,
type: "hls",
});
}
});
hls.on(Hls.Events.MANIFEST_LOADED, () => {
if (!hls) return;
reportLevels();
setupQualityForHls();
});
hls.on(Hls.Events.LEVEL_SWITCHED, () => {
if (!hls) return;
const quality = hlsLevelToQuality(hls.levels[hls.currentLevel]);
emit("changedquality", quality);
});
}
hls.attachMedia(vid);
hls.loadSource(processCdnLink(src.url));
if (src.type === "hls") {
if (canPlayHlsNatively(vid)) {
vid.src = processCdnLink(src.url);
vid.currentTime = startAt;
return;
}
vid.src = processCdnLink(src.url);
if (!Hls.isSupported()) throw new Error("HLS not supported");
if (!hls) {
hls = new Hls({
maxBufferSize: 500 * 1000 * 1000, // 500 mb of buffering, should load more fragments at once
fragLoadPolicy: {
default: {
maxLoadTimeMs: 30 * 1000, // allow it load extra long, fragments are slow if requested for the first time on an origin
maxTimeToFirstByteMs: 30 * 1000,
errorRetry: {
maxNumRetry: 2,
retryDelayMs: 1000,
maxRetryDelayMs: 8000,
},
timeoutRetry: {
maxNumRetry: 3,
maxRetryDelayMs: 0,
retryDelayMs: 0,
},
},
},
});
hls.on(Hls.Events.ERROR, (event, data) => {
console.error("HLS error", data);
if (data.fatal) {
emit("error", {
message: data.error.message,
stackTrace: data.error.stack,
errorName: data.error.name,
type: "hls",
});
}
});
hls.on(Hls.Events.MANIFEST_LOADED, () => {
if (!hls) return;
reportLevels();
setupQualityForHls();
});
hls.on(Hls.Events.LEVEL_SWITCHED, () => {
if (!hls) return;
const quality = hlsLevelToQuality(hls.levels[hls.currentLevel]);
emit("changedquality", quality);
});
}
hls.attachMedia(vid);
hls.loadSource(processCdnLink(src.url));
vid.currentTime = startAt;
});
return;
}
vid.src = processCdnLink(src.url);
vid.currentTime = startAt;
}
function setSource() {

View file

@ -13,12 +13,13 @@ import {
scrapeSourceOutputToProviderMetric,
useReportProviders,
} from "@/backend/helpers/report";
import { getLoadbalancedProviderApiUrl } from "@/backend/providers/fetchers";
import { getProviders } from "@/backend/providers/providers";
import { convertProviderCaption } from "@/components/player/utils/captions";
import { convertRunoutputToSource } from "@/components/player/utils/convertRunoutputToSource";
import { useOverlayRouter } from "@/hooks/useOverlayRouter";
import { metaToScrapeMedia } from "@/stores/player/slices/source";
import { usePlayerStore } from "@/stores/player/store";
import { getLoadbalancedProviderApiUrl, providers } from "@/utils/providers";
export function useEmbedScraping(
routerId: string,
@ -47,7 +48,7 @@ export function useEmbedScraping(
);
result = await conn.promise();
} else {
result = await providers.runEmbedScraper({
result = await getProviders().runEmbedScraper({
id: embedId,
url,
});
@ -111,7 +112,7 @@ export function useSourceScraping(sourceId: string | null, routerId: string) {
);
result = await conn.promise();
} else {
result = await providers.runSourceScraper({
result = await getProviders().runSourceScraper({
id: sourceId,
media: scrapeMedia,
});
@ -155,7 +156,7 @@ export function useSourceScraping(sourceId: string | null, routerId: string) {
);
embedResult = await conn.promise();
} else {
embedResult = await providers.runEmbedScraper({
embedResult = await getProviders().runEmbedScraper({
id: result.embeds[0].embedId,
url: result.embeds[0].url,
});

View file

@ -10,7 +10,8 @@ import {
getCachedMetadata,
makeProviderUrl,
} from "@/backend/helpers/providerApi";
import { getLoadbalancedProviderApiUrl, providers } from "@/utils/providers";
import { getLoadbalancedProviderApiUrl } from "@/backend/providers/fetchers";
import { getProviders } from "@/backend/providers/providers";
export interface ScrapingItems {
id: string;
@ -172,8 +173,8 @@ export function useScrape() {
return getResult(sseOutput === "" ? null : sseOutput);
}
if (!providers) return null;
startScrape();
const providers = getProviders();
const output = await providers.runAll({
media,
events: {

View file

@ -3,6 +3,7 @@ import { useNavigate, useParams } from "react-router-dom";
import { useAsync } from "react-use";
import type { AsyncReturnType } from "type-fest";
import { isExtensionActive } from "@/backend/extension/messaging";
import {
fetchMetadata,
setCachedMetadata,
@ -10,6 +11,8 @@ import {
import { DetailedMeta, getMetaFromId } from "@/backend/metadata/getmeta";
import { decodeTMDBId } from "@/backend/metadata/tmdb";
import { MWMediaType } from "@/backend/metadata/types/mw";
import { getLoadbalancedProviderApiUrl } from "@/backend/providers/fetchers";
import { getProviders } from "@/backend/providers/providers";
import { Button } from "@/components/buttons/Button";
import { Icons } from "@/components/Icon";
import { IconPill } from "@/components/layout/IconPill";
@ -18,7 +21,6 @@ import { Paragraph } from "@/components/text/Paragraph";
import { Title } from "@/components/text/Title";
import { ErrorContainer, ErrorLayout } from "@/pages/layouts/ErrorLayout";
import { conf } from "@/setup/config";
import { getLoadbalancedProviderApiUrl, providers } from "@/utils/providers";
export interface MetaPartProps {
onGetMeta?: (meta: DetailedMeta, episodeId?: string) => void;
@ -41,8 +43,12 @@ export function MetaPart(props: MetaPartProps) {
const navigate = useNavigate();
const { error, value, loading } = useAsync(async () => {
// check extension
const isActive = await isExtensionActive();
// use api metadata or providers metadata
const providerApiUrl = getLoadbalancedProviderApiUrl();
if (providerApiUrl) {
if (providerApiUrl && !isActive) {
try {
await fetchMetadata(providerApiUrl);
} catch (err) {
@ -50,11 +56,12 @@ export function MetaPart(props: MetaPartProps) {
}
} else {
setCachedMetadata([
...providers.listSources(),
...providers.listEmbeds(),
...getProviders().listSources(),
...getProviders().listEmbeds(),
]);
}
// get media meta data
let data: ReturnType<typeof decodeTMDBId> = null;
try {
if (!params.media) throw new Error("no media params");