From ef85c217f7a6288321eb8c785c8bb33f23b582df Mon Sep 17 00:00:00 2001
From: Jorrin
Date: Mon, 8 Jan 2024 17:06:27 +0100
Subject: [PATCH 001/246] first mvp extension
---
package.json | 1 +
pnpm-lock.yaml | 21 +++
src/@types/plasmo.d.ts | 37 ++++++
src/components/player/display/base.ts | 120 ++++++++++--------
.../player/utils/convertRunoutputToSource.ts | 2 +
src/stores/player/utils/qualities.ts | 5 +-
src/utils/providers.ts | 4 +-
7 files changed, 134 insertions(+), 56 deletions(-)
create mode 100644 src/@types/plasmo.d.ts
diff --git a/package.json b/package.json
index 388054df..3148f860 100644
--- a/package.json
+++ b/package.json
@@ -31,6 +31,7 @@
"@ladjs/country-language": "^1.0.3",
"@movie-web/providers": "^2.0.5",
"@noble/hashes": "^1.3.3",
+ "@plasmohq/messaging": "^0.6.1",
"@react-spring/web": "^9.7.3",
"@scure/bip39": "^1.2.2",
"@sozialhelden/ietf-language-tags": "^5.4.2",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 6d6bb9bd..5c1d1c9e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -27,6 +27,9 @@ dependencies:
'@noble/hashes':
specifier: ^1.3.3
version: 1.3.3
+ '@plasmohq/messaging':
+ specifier: ^0.6.1
+ version: 0.6.1(react@18.2.0)
'@react-spring/web':
specifier: ^9.7.3
version: 9.7.3(react-dom@18.2.0)(react@18.2.0)
@@ -1980,6 +1983,18 @@ packages:
tslib: 2.6.2
dev: true
+ /@plasmohq/messaging@0.6.1(react@18.2.0):
+ resolution: {integrity: sha512-/nn1k8SG5z++o/NnZu+byHWcC9MhPLxfmvj+AP3buqMn7uwfYDcYWURLuMW2Knw08HBg+wku2v1Ltt4evN0nzA==}
+ peerDependencies:
+ react: ^16.8.6 || ^17 || ^18
+ peerDependenciesMeta:
+ react:
+ optional: true
+ dependencies:
+ nanoid: 5.0.3
+ react: 18.2.0
+ dev: false
+
/@react-spring/animated@9.7.3(react@18.2.0):
resolution: {integrity: sha512-5CWeNJt9pNgyvuSzQH+uy2pvTg8Y4/OisoscZIR8/ZNLIOI+CatFBhGZpDGTF/OzdNFsAoGk3wiUYTwoJ0YIvw==}
peerDependencies:
@@ -5156,6 +5171,12 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
+ /nanoid@5.0.3:
+ resolution: {integrity: sha512-I7X2b22cxA4LIHXPSqbBCEQSL+1wv8TuoefejsX4HFWyC6jc5JG7CEaxOltiKjc1M+YCS2YkrZZcj4+dytw9GA==}
+ engines: {node: ^18 || >=20}
+ hasBin: true
+ dev: false
+
/nanoid@5.0.4:
resolution: {integrity: sha512-vAjmBf13gsmhXSgBrtIclinISzFFy22WwCYoyilZlsrRXNIHSwgFQ1bEdjRwMT3aoadeIF6HMuDRlOxzfXV8ig==}
engines: {node: ^18 || >=20}
diff --git a/src/@types/plasmo.d.ts b/src/@types/plasmo.d.ts
new file mode 100644
index 00000000..4570cae4
--- /dev/null
+++ b/src/@types/plasmo.d.ts
@@ -0,0 +1,37 @@
+/* eslint-disable @typescript-eslint/ban-types */
+import "@plasmohq/messaging";
+
+export interface PlasmoRequestBody {
+ ruleId: number;
+ domain: string;
+ requestHeaders?: Record;
+ responseHeaders?: Record;
+}
+
+export type PlasmoResponseBody =
+ | {
+ success: true;
+ ruleId: number;
+ }
+ | {
+ success: false;
+ error: string;
+ };
+
+interface MmMetadata {
+ "declarative-net-request": {
+ req: PlasmoRequestBody;
+ res: PlasmoResponseBody;
+ };
+ "proxy-request": {
+ req: PlasmoRequestBody;
+ res: PlasmoResponseBody;
+ };
+}
+
+interface MpMetadata {}
+
+declare module "@plasmohq/messaging" {
+ interface MessagesMetadata extends MmMetadata {}
+ interface PortsMetadata extends MpMetadata {}
+}
diff --git a/src/components/player/display/base.ts b/src/components/player/display/base.ts
index 7a9ce041..11a0799c 100644
--- a/src/components/player/display/base.ts
+++ b/src/components/player/display/base.ts
@@ -1,6 +1,8 @@
+import { sendToBackgroundViaRelay } from "@plasmohq/messaging";
import fscreen from "fscreen";
import Hls, { Level } from "hls.js";
+import { PlasmoRequestBody, PlasmoResponseBody } from "@/@types/plasmo";
import {
DisplayInterface,
DisplayInterfaceEvents,
@@ -100,65 +102,75 @@ export function makeVideoElementDisplayInterface(): DisplayInterface {
}
function setupSource(vid: HTMLVideoElement, src: LoadableSource) {
- if (src.type === "hls") {
- if (canPlayHlsNatively(vid)) {
- vid.src = processCdnLink(src.url);
+ // TODO: Add check whether the extension is installed
+ sendToBackgroundViaRelay({
+ 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));
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));
+ vid.src = processCdnLink(src.url);
vid.currentTime = startAt;
- return;
- }
-
- vid.src = processCdnLink(src.url);
- vid.currentTime = startAt;
+ });
}
function setSource() {
diff --git a/src/components/player/utils/convertRunoutputToSource.ts b/src/components/player/utils/convertRunoutputToSource.ts
index fba59e63..f54c5396 100644
--- a/src/components/player/utils/convertRunoutputToSource.ts
+++ b/src/components/player/utils/convertRunoutputToSource.ts
@@ -28,6 +28,7 @@ export function convertRunoutputToSource(out: {
return {
type: "hls",
url: out.stream.playlist,
+ preferredHeaders: out.stream.preferredHeaders,
};
}
if (out.stream.type === "file") {
@@ -49,6 +50,7 @@ export function convertRunoutputToSource(out: {
return {
type: "file",
qualities,
+ preferredHeaders: out.stream.preferredHeaders,
};
}
throw new Error("unrecognized type");
diff --git a/src/stores/player/utils/qualities.ts b/src/stores/player/utils/qualities.ts
index dbd84b5c..e5140d53 100644
--- a/src/stores/player/utils/qualities.ts
+++ b/src/stores/player/utils/qualities.ts
@@ -1,4 +1,4 @@
-import { Qualities } from "@movie-web/providers";
+import { Qualities, Stream } from "@movie-web/providers";
import { QualityStore } from "@/stores/quality";
@@ -14,16 +14,19 @@ export type SourceFileStream = {
export type LoadableSource = {
type: StreamType;
url: string;
+ preferredHeaders?: Stream["preferredHeaders"];
};
export type SourceSliceSource =
| {
type: "file";
qualities: Partial>;
+ preferredHeaders?: Stream["preferredHeaders"];
}
| {
type: "hls";
url: string;
+ preferredHeaders?: Stream["preferredHeaders"];
};
const qualitySorting: Record = {
diff --git a/src/utils/providers.ts b/src/utils/providers.ts
index e5c8503c..73c95662 100644
--- a/src/utils/providers.ts
+++ b/src/utils/providers.ts
@@ -62,5 +62,7 @@ function makeLoadBalancedSimpleProxyFetcher() {
export const providers = makeProviders({
fetcher: makeStandardFetcher(fetch),
proxiedFetcher: makeLoadBalancedSimpleProxyFetcher(),
- target: targets.BROWSER,
+ // TODO: Add check whether the extension is installed
+ // target: targets.BROWSER,
+ target: targets.BROWSER_EXTENSION,
}) as any as ProviderControls;
From f70d13f2c9e3b91b5b3caccbdf29e9ae77827944 Mon Sep 17 00:00:00 2001
From: mrjvs
Date: Tue, 9 Jan 2024 20:07:22 +0100
Subject: [PATCH 002/246] Clean up extension code
---
src/backend/extension/messaging.ts | 51 ++++++++
.../extension/plasmo.ts} | 17 ++-
src/backend/helpers/fetch.ts | 2 +-
.../providers/fetchers.ts} | 25 ++--
src/backend/providers/providers.ts | 26 ++++
src/components/player/display/base.ts | 120 ++++++++----------
.../player/hooks/useSourceSelection.ts | 9 +-
src/hooks/useProviderScrape.tsx | 5 +-
src/pages/parts/player/MetaPart.tsx | 15 ++-
9 files changed, 172 insertions(+), 98 deletions(-)
create mode 100644 src/backend/extension/messaging.ts
rename src/{@types/plasmo.d.ts => backend/extension/plasmo.ts} (72%)
rename src/{utils/providers.ts => backend/providers/fetchers.ts} (74%)
create mode 100644 src/backend/providers/providers.ts
diff --git a/src/backend/extension/messaging.ts b/src/backend/extension/messaging.ts
new file mode 100644
index 00000000..ec0b4bf6
--- /dev/null
+++ b/src/backend/extension/messaging.ts
@@ -0,0 +1,51 @@
+import {
+ MessagesMetadata,
+ sendToBackgroundViaRelay,
+} from "@plasmohq/messaging";
+
+let activeExtension = false;
+
+export interface ExtensionHello {
+ version: string;
+}
+
+function sendMessage(
+ message: keyof MessagesMetadata,
+ payload: any,
+ timeout: number = -1,
+) {
+ return new Promise((resolve) => {
+ if (timeout >= 0) setTimeout(() => resolve(null), timeout);
+ sendToBackgroundViaRelay({
+ name: message,
+ body: payload,
+ })
+ .then((res) => {
+ activeExtension = true;
+ resolve(res);
+ })
+ .catch(() => {
+ activeExtension = false;
+ resolve(null);
+ });
+ });
+}
+
+export async function sendExtensionRequest(
+ url: string,
+ ops: any,
+): Promise {
+ return sendMessage("proxy-request", { url, ...ops });
+}
+
+export async function extensionInfo(): Promise {
+ return sendMessage("hello", null, 300);
+}
+
+export function isExtensionActiveCached(): boolean {
+ return activeExtension;
+}
+
+export async function isExtensionActive(): Promise {
+ return !!(await extensionInfo());
+}
diff --git a/src/@types/plasmo.d.ts b/src/backend/extension/plasmo.ts
similarity index 72%
rename from src/@types/plasmo.d.ts
rename to src/backend/extension/plasmo.ts
index 4570cae4..2e9cacbf 100644
--- a/src/@types/plasmo.d.ts
+++ b/src/backend/extension/plasmo.ts
@@ -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;
}
-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;
};
}
diff --git a/src/backend/helpers/fetch.ts b/src/backend/helpers/fetch.ts
index cc3e735e..f9aa145a 100644
--- a/src/backend/helpers/fetch.ts
+++ b/src/backend/helpers/fetch.ts
@@ -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 = Parameters>;
type R = ReturnType>;
diff --git a/src/utils/providers.ts b/src/backend/providers/fetchers.ts
similarity index 74%
rename from src/utils/providers.ts
rename to src/backend/providers/fetchers.ts
index 73c95662..596e8376 100644
--- a/src/utils/providers.ts
+++ b/src/backend/providers/fetchers.ts
@@ -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;
+}
diff --git a/src/backend/providers/providers.ts b/src/backend/providers/providers.ts
new file mode 100644
index 00000000..1a7b484a
--- /dev/null
+++ b/src/backend/providers/providers.ts
@@ -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,
+ });
+}
diff --git a/src/components/player/display/base.ts b/src/components/player/display/base.ts
index 11a0799c..91f36722 100644
--- a/src/components/player/display/base.ts
+++ b/src/components/player/display/base.ts
@@ -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({
- 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() {
diff --git a/src/components/player/hooks/useSourceSelection.ts b/src/components/player/hooks/useSourceSelection.ts
index e28507cf..bca884f7 100644
--- a/src/components/player/hooks/useSourceSelection.ts
+++ b/src/components/player/hooks/useSourceSelection.ts
@@ -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,
});
diff --git a/src/hooks/useProviderScrape.tsx b/src/hooks/useProviderScrape.tsx
index 43328184..21cb985e 100644
--- a/src/hooks/useProviderScrape.tsx
+++ b/src/hooks/useProviderScrape.tsx
@@ -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: {
diff --git a/src/pages/parts/player/MetaPart.tsx b/src/pages/parts/player/MetaPart.tsx
index 4930fffb..6d5b64ef 100644
--- a/src/pages/parts/player/MetaPart.tsx
+++ b/src/pages/parts/player/MetaPart.tsx
@@ -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 = null;
try {
if (!params.media) throw new Error("no media params");
From 421186cb54737655446c3c87469697b4f82d180c Mon Sep 17 00:00:00 2001
From: mrjvs
Date: Tue, 9 Jan 2024 21:56:39 +0100
Subject: [PATCH 003/246] Version checking + preparing streams
---
src/backend/extension/compatibility.ts | 5 +++
src/backend/extension/messaging.ts | 17 +++++++-
src/backend/extension/plasmo.ts | 4 +-
src/backend/extension/streams.ts | 40 +++++++++++++++++++
.../player/hooks/useSourceSelection.ts | 3 ++
src/hooks/useProviderScrape.tsx | 3 ++
6 files changed, 68 insertions(+), 4 deletions(-)
create mode 100644 src/backend/extension/compatibility.ts
create mode 100644 src/backend/extension/streams.ts
diff --git a/src/backend/extension/compatibility.ts b/src/backend/extension/compatibility.ts
new file mode 100644
index 00000000..31471d26
--- /dev/null
+++ b/src/backend/extension/compatibility.ts
@@ -0,0 +1,5 @@
+const allowedExtensionVersion = ["0.0.1"];
+
+export function isAllowedExtensionVersion(version: string): boolean {
+ return allowedExtensionVersion.includes(version);
+}
diff --git a/src/backend/extension/messaging.ts b/src/backend/extension/messaging.ts
index ec0b4bf6..3c5480c1 100644
--- a/src/backend/extension/messaging.ts
+++ b/src/backend/extension/messaging.ts
@@ -3,6 +3,8 @@ import {
sendToBackgroundViaRelay,
} from "@plasmohq/messaging";
+import { isAllowedExtensionVersion } from "@/backend/extension/compatibility";
+
let activeExtension = false;
export interface ExtensionHello {
@@ -35,7 +37,14 @@ export async function sendExtensionRequest(
url: string,
ops: any,
): Promise {
- return sendMessage("proxy-request", { url, ...ops });
+ return sendMessage("make-request", { url, ...ops });
+}
+
+export async function setDomainRule(
+ domains: string[],
+ headers: Record,
+): Promise {
+ return sendMessage("prepare-stream", { domains, headers });
}
export async function extensionInfo(): Promise {
@@ -47,5 +56,9 @@ export function isExtensionActiveCached(): boolean {
}
export async function isExtensionActive(): Promise {
- return !!(await extensionInfo());
+ const info = await extensionInfo();
+ if (!info) return false;
+ const allowedVersion = isAllowedExtensionVersion(info.version);
+ if (!allowedVersion) return false;
+ return true;
}
diff --git a/src/backend/extension/plasmo.ts b/src/backend/extension/plasmo.ts
index 2e9cacbf..8c0093f0 100644
--- a/src/backend/extension/plasmo.ts
+++ b/src/backend/extension/plasmo.ts
@@ -20,11 +20,11 @@ export type ExtensionRequestReply =
};
interface MmMetadata {
- "declarative-net-request": {
+ "prepare-stream": {
req: PlasmoRequestBody;
res: ExtensionRequestReply;
};
- "proxy-request": {
+ "make-request": {
req: PlasmoRequestBody;
res: ExtensionRequestReply;
};
diff --git a/src/backend/extension/streams.ts b/src/backend/extension/streams.ts
new file mode 100644
index 00000000..8d202992
--- /dev/null
+++ b/src/backend/extension/streams.ts
@@ -0,0 +1,40 @@
+import { Stream } from "@movie-web/providers";
+
+import { setDomainRule } from "@/backend/extension/messaging";
+
+function extractDomain(url: string): string {
+ try {
+ const u = new URL(url);
+ return u.hostname;
+ } catch {
+ return url;
+ }
+}
+
+function extractDomainsFromStream(stream: Stream): string[] {
+ if (stream.type === "hls") {
+ return [extractDomain(stream.playlist)];
+ }
+ if (stream.type === "file") {
+ return Object.values(stream.qualities).map((v) => extractDomain(v.url));
+ }
+ return [];
+}
+
+function buildHeadersFromStream(stream: Stream): Record {
+ const headers: Record = {};
+ Object.entries(stream.headers ?? {}).forEach((entry) => {
+ headers[entry[0]] = entry[1];
+ });
+ Object.entries(stream.preferredHeaders ?? {}).forEach((entry) => {
+ headers[entry[0]] = entry[1];
+ });
+ return headers;
+}
+
+export async function prepareStream(stream: Stream) {
+ await setDomainRule(
+ extractDomainsFromStream(stream),
+ buildHeadersFromStream(stream),
+ );
+}
diff --git a/src/components/player/hooks/useSourceSelection.ts b/src/components/player/hooks/useSourceSelection.ts
index bca884f7..b9e167d6 100644
--- a/src/components/player/hooks/useSourceSelection.ts
+++ b/src/components/player/hooks/useSourceSelection.ts
@@ -5,6 +5,7 @@ import {
} from "@movie-web/providers";
import { useAsyncFn } from "react-use";
+import { prepareStream } from "@/backend/extension/streams";
import {
connectServerSideEvents,
makeProviderUrl,
@@ -131,6 +132,7 @@ export function useSourceScraping(sourceId: string | null, routerId: string) {
]);
if (result.stream) {
+ await prepareStream(result.stream[0]);
setCaption(null);
setSource(
convertRunoutputToSource({ stream: result.stream[0] }),
@@ -187,6 +189,7 @@ export function useSourceScraping(sourceId: string | null, routerId: string) {
]);
setSourceId(sourceId);
setCaption(null);
+ await prepareStream(embedResult.stream[0]);
setSource(
convertRunoutputToSource({ stream: embedResult.stream[0] }),
convertProviderCaption(embedResult.stream[0].captions),
diff --git a/src/hooks/useProviderScrape.tsx b/src/hooks/useProviderScrape.tsx
index 21cb985e..d6b48063 100644
--- a/src/hooks/useProviderScrape.tsx
+++ b/src/hooks/useProviderScrape.tsx
@@ -5,6 +5,7 @@ import {
} from "@movie-web/providers";
import { RefObject, useCallback, useEffect, useRef, useState } from "react";
+import { prepareStream } from "@/backend/extension/streams";
import {
connectServerSideEvents,
getCachedMetadata,
@@ -169,6 +170,7 @@ export function useScrape() {
conn.on("update", updateEvent);
conn.on("discoverEmbeds", discoverEmbedsEvent);
const sseOutput = await conn.promise();
+ if (sseOutput) await prepareStream(sseOutput.stream);
return getResult(sseOutput === "" ? null : sseOutput);
}
@@ -184,6 +186,7 @@ export function useScrape() {
discoverEmbeds: discoverEmbedsEvent,
},
});
+ if (output) await prepareStream(output.stream);
return getResult(output);
},
[
From 52bc66e7dd934c22913a562757048ec48bde0e58 Mon Sep 17 00:00:00 2001
From: Jorrin
Date: Tue, 9 Jan 2024 23:00:54 +0100
Subject: [PATCH 004/246] improve typings
---
src/backend/extension/messaging.ts | 51 ++++++++++++++---------
src/backend/extension/plasmo.ts | 67 ++++++++++++++++++++----------
src/backend/extension/streams.ts | 9 ++--
src/backend/providers/fetchers.ts | 9 +++-
4 files changed, 87 insertions(+), 49 deletions(-)
diff --git a/src/backend/extension/messaging.ts b/src/backend/extension/messaging.ts
index 3c5480c1..28f80ad2 100644
--- a/src/backend/extension/messaging.ts
+++ b/src/backend/extension/messaging.ts
@@ -7,18 +7,17 @@ import { isAllowedExtensionVersion } from "@/backend/extension/compatibility";
let activeExtension = false;
-export interface ExtensionHello {
- version: string;
-}
-
-function sendMessage(
- message: keyof MessagesMetadata,
- payload: any,
+function sendMessage(
+ message: MessageKey,
+ payload: MessagesMetadata[MessageKey]["req"],
timeout: number = -1,
) {
- return new Promise((resolve) => {
+ return new Promise((resolve) => {
if (timeout >= 0) setTimeout(() => resolve(null), timeout);
- sendToBackgroundViaRelay({
+ sendToBackgroundViaRelay<
+ MessagesMetadata[MessageKey]["req"],
+ MessagesMetadata[MessageKey]["res"]
+ >({
name: message,
body: payload,
})
@@ -34,21 +33,33 @@ function sendMessage(
}
export async function sendExtensionRequest(
- url: string,
- ops: any,
-): Promise {
- return sendMessage("make-request", { url, ...ops });
+ ops: Omit,
+): Promise {
+ return sendMessage("makeRequest", {
+ requestDomain: window.location.origin,
+ ...ops,
+ });
}
export async function setDomainRule(
- domains: string[],
- headers: Record,
-): Promise {
- return sendMessage("prepare-stream", { domains, headers });
+ ops: Omit,
+): Promise {
+ return sendMessage("prepareStream", {
+ requestDomain: window.location.origin,
+ ...ops,
+ });
}
-export async function extensionInfo(): Promise {
- return sendMessage("hello", null, 300);
+export async function extensionInfo(): Promise<
+ MessagesMetadata["hello"]["res"] | null
+> {
+ return sendMessage(
+ "hello",
+ {
+ requestDomain: window.location.origin,
+ },
+ 300,
+ );
}
export function isExtensionActiveCached(): boolean {
@@ -57,7 +68,7 @@ export function isExtensionActiveCached(): boolean {
export async function isExtensionActive(): Promise {
const info = await extensionInfo();
- if (!info) return false;
+ if (!info?.success) return false;
const allowedVersion = isAllowedExtensionVersion(info.version);
if (!allowedVersion) return false;
return true;
diff --git a/src/backend/extension/plasmo.ts b/src/backend/extension/plasmo.ts
index 8c0093f0..6c37ad14 100644
--- a/src/backend/extension/plasmo.ts
+++ b/src/backend/extension/plasmo.ts
@@ -1,6 +1,37 @@
-export interface PlasmoRequestBody {
+export interface ExtensionBaseRequest {
+ requestDomain: string;
+}
+
+export type ExtensionBaseResponse =
+ | ({
+ success: true;
+ } & T)
+ | {
+ success: false;
+ error: string;
+ };
+
+export type ExtensionHelloResponse = ExtensionBaseResponse<{
+ version: string;
+}>;
+
+export interface ExtensionMakeRequest extends ExtensionBaseRequest {
+ url: string;
+ method: string;
+ headers?: Record;
+ body?: string | FormData | URLSearchParams | Record;
+}
+
+export type ExtensionMakeRequestResponse = ExtensionBaseResponse<{
+ status: number;
+ requestHeaders: Record;
+ responseHeaders: Record;
+ data: string | Record;
+}>;
+
+export interface ExtensionPrepareStreamRequest extends ExtensionBaseRequest {
ruleId: number;
- domain: string;
+ targetDomains: string[];
requestHeaders?: Record;
responseHeaders?: Record;
}
@@ -9,28 +40,18 @@ export interface ExtensionHelloReply {
version: string;
}
-export type ExtensionRequestReply =
- | {
- success: true;
- ruleId: number;
- }
- | {
- success: false;
- error: string;
- };
-
-interface MmMetadata {
- "prepare-stream": {
- req: PlasmoRequestBody;
- res: ExtensionRequestReply;
- };
- "make-request": {
- req: PlasmoRequestBody;
- res: ExtensionRequestReply;
- };
+export interface MmMetadata {
hello: {
- req: null;
- res: ExtensionHelloReply;
+ req: ExtensionBaseRequest;
+ res: ExtensionHelloResponse;
+ };
+ makeRequest: {
+ req: ExtensionMakeRequest;
+ res: ExtensionMakeRequestResponse;
+ };
+ prepareStream: {
+ req: ExtensionPrepareStreamRequest;
+ res: ExtensionBaseResponse;
};
}
diff --git a/src/backend/extension/streams.ts b/src/backend/extension/streams.ts
index 8d202992..2afb900d 100644
--- a/src/backend/extension/streams.ts
+++ b/src/backend/extension/streams.ts
@@ -33,8 +33,9 @@ function buildHeadersFromStream(stream: Stream): Record {
}
export async function prepareStream(stream: Stream) {
- await setDomainRule(
- extractDomainsFromStream(stream),
- buildHeadersFromStream(stream),
- );
+ await setDomainRule({
+ ruleId: 1,
+ targetDomains: extractDomainsFromStream(stream),
+ requestHeaders: buildHeadersFromStream(stream),
+ });
}
diff --git a/src/backend/providers/fetchers.ts b/src/backend/providers/fetchers.ts
index 596e8376..e133199c 100644
--- a/src/backend/providers/fetchers.ts
+++ b/src/backend/providers/fetchers.ts
@@ -54,8 +54,13 @@ export function makeLoadBalancedSimpleProxyFetcher() {
}
export function makeExtensionFetcher() {
- const fetcher: Fetcher = async (a, b) => {
- return sendExtensionRequest(a, b) as any;
+ const fetcher: Fetcher = async (url, ops) => {
+ return sendExtensionRequest({
+ url,
+ method: ops?.method ?? "GET",
+ headers: ops?.headers,
+ body: ops?.body,
+ }) as any;
};
return fetcher;
}
From 4bdb95ed0fd88fb456f03691c5a689c17b381a28 Mon Sep 17 00:00:00 2001
From: Jorrin
Date: Tue, 9 Jan 2024 23:35:23 +0100
Subject: [PATCH 005/246] fix
---
src/backend/providers/fetchers.ts | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/src/backend/providers/fetchers.ts b/src/backend/providers/fetchers.ts
index e133199c..f1dbbfb8 100644
--- a/src/backend/providers/fetchers.ts
+++ b/src/backend/providers/fetchers.ts
@@ -57,9 +57,7 @@ export function makeExtensionFetcher() {
const fetcher: Fetcher = async (url, ops) => {
return sendExtensionRequest({
url,
- method: ops?.method ?? "GET",
- headers: ops?.headers,
- body: ops?.body,
+ ...ops,
}) as any;
};
return fetcher;
From caa82e15a3d5bc2c5eb3e9f3b710b90278d65281 Mon Sep 17 00:00:00 2001
From: Isra
Date: Tue, 9 Jan 2024 21:14:03 -0600
Subject: [PATCH 006/246] Instructions
---
src/assets/locales/en.json | 4 ++--
src/pages/parts/settings/ConnectionsPart.tsx | 11 ++++++++---
2 files changed, 10 insertions(+), 5 deletions(-)
diff --git a/src/assets/locales/en.json b/src/assets/locales/en.json
index 9329ec0e..425f6c32 100644
--- a/src/assets/locales/en.json
+++ b/src/assets/locales/en.json
@@ -393,14 +393,14 @@
},
"connections": {
"server": {
- "description": "If you would like to connect to a custom backend to store your data, enable this and provide the URL.",
+ "description": "If you would like to connect to a custom backend to store your data, enable this and provide the URL. <0>Instructions.0>",
"label": "Custom server",
"urlLabel": "Custom server URL"
},
"title": "Connections",
"workers": {
"addButton": "Add new worker",
- "description": "To make the application function, all traffic is routed through proxies. Enable this if you want to bring your own workers.",
+ "description": "To make the application function, all traffic is routed through proxies. Enable this if you want to bring your own workers. <0>Instructions.0>",
"emptyState": "No workers yet, add one below",
"label": "Use custom proxy workers",
"urlLabel": "Worker URLs",
diff --git a/src/pages/parts/settings/ConnectionsPart.tsx b/src/pages/parts/settings/ConnectionsPart.tsx
index 007a6220..11aa8171 100644
--- a/src/pages/parts/settings/ConnectionsPart.tsx
+++ b/src/pages/parts/settings/ConnectionsPart.tsx
@@ -1,10 +1,11 @@
import { Dispatch, SetStateAction, useCallback } from "react";
-import { useTranslation } from "react-i18next";
+import { Trans, useTranslation } from "react-i18next";
import { Button } from "@/components/buttons/Button";
import { Toggle } from "@/components/buttons/Toggle";
import { Icon, Icons } from "@/components/Icon";
import { SettingsCard } from "@/components/layout/SettingsCard";
+import { MwLink } from "@/components/text/Link";
import { AuthInputBox } from "@/components/text-inputs/AuthInputBox";
import { Divider } from "@/components/utils/Divider";
import { Heading1 } from "@/components/utils/Text";
@@ -52,7 +53,9 @@ function ProxyEdit({ proxyUrls, setProxyUrls }: ProxyEditProps) {
{t("settings.connections.workers.label")}
- {t("settings.connections.workers.description")}
+
+ Google
+
@@ -118,7 +121,9 @@ function BackendEdit({ backendUrl, setBackendUrl }: BackendEditProps) {
{t("settings.connections.server.label")}
- {t("settings.connections.server.description")}
+
+ Google
+
From b11a7016be6874956a5f33e4d25ad90cc3064ab4 Mon Sep 17 00:00:00 2001
From: Isra
Date: Tue, 9 Jan 2024 21:18:41 -0600
Subject: [PATCH 007/246] Actual links
---
src/pages/parts/settings/ConnectionsPart.tsx | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/src/pages/parts/settings/ConnectionsPart.tsx b/src/pages/parts/settings/ConnectionsPart.tsx
index 11aa8171..e9183764 100644
--- a/src/pages/parts/settings/ConnectionsPart.tsx
+++ b/src/pages/parts/settings/ConnectionsPart.tsx
@@ -54,7 +54,9 @@ function ProxyEdit({ proxyUrls, setProxyUrls }: ProxyEditProps) {
- Google
+
+ Proxy documentation
+
@@ -122,7 +124,9 @@ function BackendEdit({ backendUrl, setBackendUrl }: BackendEditProps) {
- Google
+
+ Backend documentation
+
From e1be30dde9ad62760b44300b945556791cad5e13 Mon Sep 17 00:00:00 2001
From: mrjvs
Date: Wed, 10 Jan 2024 18:20:51 +0100
Subject: [PATCH 008/246] Request messaging
---
src/backend/extension/messaging.ts | 5 +++--
src/backend/extension/plasmo.ts | 14 ++++++++------
src/backend/providers/fetchers.ts | 24 ++++++++++++++++++++++--
3 files changed, 33 insertions(+), 10 deletions(-)
diff --git a/src/backend/extension/messaging.ts b/src/backend/extension/messaging.ts
index 28f80ad2..3d835a0e 100644
--- a/src/backend/extension/messaging.ts
+++ b/src/backend/extension/messaging.ts
@@ -4,6 +4,7 @@ import {
} from "@plasmohq/messaging";
import { isAllowedExtensionVersion } from "@/backend/extension/compatibility";
+import { ExtensionMakeRequestResponse } from "@/backend/extension/plasmo";
let activeExtension = false;
@@ -32,9 +33,9 @@ function sendMessage(
});
}
-export async function sendExtensionRequest(
+export async function sendExtensionRequest(
ops: Omit,
-): Promise {
+): Promise | null> {
return sendMessage("makeRequest", {
requestDomain: window.location.origin,
...ops,
diff --git a/src/backend/extension/plasmo.ts b/src/backend/extension/plasmo.ts
index 6c37ad14..0a2b3dc5 100644
--- a/src/backend/extension/plasmo.ts
+++ b/src/backend/extension/plasmo.ts
@@ -22,11 +22,13 @@ export interface ExtensionMakeRequest extends ExtensionBaseRequest {
body?: string | FormData | URLSearchParams | Record;
}
-export type ExtensionMakeRequestResponse = ExtensionBaseResponse<{
- status: number;
- requestHeaders: Record;
- responseHeaders: Record;
- data: string | Record;
+export type ExtensionMakeRequestResponse = ExtensionBaseResponse<{
+ response: {
+ statusCode: number;
+ headers: Record;
+ finalUrl: string;
+ body: T;
+ };
}>;
export interface ExtensionPrepareStreamRequest extends ExtensionBaseRequest {
@@ -47,7 +49,7 @@ export interface MmMetadata {
};
makeRequest: {
req: ExtensionMakeRequest;
- res: ExtensionMakeRequestResponse;
+ res: ExtensionMakeRequestResponse;
};
prepareStream: {
req: ExtensionPrepareStreamRequest;
diff --git a/src/backend/providers/fetchers.ts b/src/backend/providers/fetchers.ts
index f1dbbfb8..9db649f5 100644
--- a/src/backend/providers/fetchers.ts
+++ b/src/backend/providers/fetchers.ts
@@ -53,12 +53,32 @@ export function makeLoadBalancedSimpleProxyFetcher() {
return fetcher;
}
+function makeFinalHeaders(
+ readHeaders: string[],
+ headers: Record,
+): Headers {
+ const lowercasedHeaders = readHeaders.map((v) => v.toLowerCase());
+ return new Headers(
+ Object.entries(headers).filter((entry) =>
+ lowercasedHeaders.includes(entry[0].toLowerCase()),
+ ),
+ );
+}
+
export function makeExtensionFetcher() {
const fetcher: Fetcher = async (url, ops) => {
- return sendExtensionRequest({
+ const result = await sendExtensionRequest({
url,
...ops,
- }) as any;
+ });
+ if (!result?.success) throw new Error(`extension error: ${result?.error}`);
+ const res = result.response;
+ return {
+ body: res.body,
+ finalUrl: res.finalUrl,
+ statusCode: res.statusCode,
+ headers: makeFinalHeaders(ops.readHeaders, res.headers),
+ };
};
return fetcher;
}
From d32ef6ed9ae88db294a8c232c65018a65f1b4abc Mon Sep 17 00:00:00 2001
From: Jorrin
Date: Wed, 10 Jan 2024 19:43:54 +0100
Subject: [PATCH 009/246] fix extension not preparing stream on embed selection
---
src/components/player/hooks/useSourceSelection.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/components/player/hooks/useSourceSelection.ts b/src/components/player/hooks/useSourceSelection.ts
index b9e167d6..3016e332 100644
--- a/src/components/player/hooks/useSourceSelection.ts
+++ b/src/components/player/hooks/useSourceSelection.ts
@@ -72,6 +72,7 @@ export function useEmbedScraping(
report([
scrapeSourceOutputToProviderMetric(meta, sourceId, null, "success", null),
]);
+ await prepareStream(result.stream[0]);
setSourceId(sourceId);
setCaption(null);
setSource(
From 3704dfba10ae8ada3573c084848c7e0ebf8fb5c4 Mon Sep 17 00:00:00 2001
From: mrjvs
Date: Wed, 10 Jan 2024 22:04:21 +0100
Subject: [PATCH 010/246] Fix styling bugs, fix player not switching source
after error, fix allowed state in extension, add ip locked sourced for
extension
Co-authored-by: Jip Frijlink
---
src/backend/extension/messaging.ts | 7 +++++--
src/backend/extension/plasmo.ts | 5 +----
src/backend/providers/providers.ts | 1 +
.../player/atoms/settings/SourceSelectingView.tsx | 2 +-
src/stores/player/slices/source.ts | 10 +++++++++-
src/utils/language.ts | 2 +-
6 files changed, 18 insertions(+), 9 deletions(-)
diff --git a/src/backend/extension/messaging.ts b/src/backend/extension/messaging.ts
index 3d835a0e..4594fb23 100644
--- a/src/backend/extension/messaging.ts
+++ b/src/backend/extension/messaging.ts
@@ -37,7 +37,7 @@ export async function sendExtensionRequest(
ops: Omit,
): Promise | null> {
return sendMessage("makeRequest", {
- requestDomain: window.location.origin,
+ requestDomain: window.location.origin, // TODO unsafe
...ops,
});
}
@@ -54,13 +54,16 @@ export async function setDomainRule(
export async function extensionInfo(): Promise<
MessagesMetadata["hello"]["res"] | null
> {
- return sendMessage(
+ const message = await sendMessage(
"hello",
{
requestDomain: window.location.origin,
},
300,
);
+ if (!message?.success) return null;
+ if (!message.allowed) return null;
+ return message;
}
export function isExtensionActiveCached(): boolean {
diff --git a/src/backend/extension/plasmo.ts b/src/backend/extension/plasmo.ts
index 0a2b3dc5..e142ef6a 100644
--- a/src/backend/extension/plasmo.ts
+++ b/src/backend/extension/plasmo.ts
@@ -13,6 +13,7 @@ export type ExtensionBaseResponse =
export type ExtensionHelloResponse = ExtensionBaseResponse<{
version: string;
+ allowed: boolean;
}>;
export interface ExtensionMakeRequest extends ExtensionBaseRequest {
@@ -38,10 +39,6 @@ export interface ExtensionPrepareStreamRequest extends ExtensionBaseRequest {
responseHeaders?: Record;
}
-export interface ExtensionHelloReply {
- version: string;
-}
-
export interface MmMetadata {
hello: {
req: ExtensionBaseRequest;
diff --git a/src/backend/providers/providers.ts b/src/backend/providers/providers.ts
index 1a7b484a..ac4a7dfa 100644
--- a/src/backend/providers/providers.ts
+++ b/src/backend/providers/providers.ts
@@ -15,6 +15,7 @@ export function getProviders() {
return makeProviders({
fetcher: makeExtensionFetcher(),
target: targets.BROWSER_EXTENSION,
+ consistentIpForRequests: true,
});
}
diff --git a/src/components/player/atoms/settings/SourceSelectingView.tsx b/src/components/player/atoms/settings/SourceSelectingView.tsx
index 03d0875d..f995308e 100644
--- a/src/components/player/atoms/settings/SourceSelectingView.tsx
+++ b/src/components/player/atoms/settings/SourceSelectingView.tsx
@@ -147,7 +147,7 @@ export function SourceSelectionView({
router.navigate("/")}>
{t("player.menus.sources.title")}
-
+
{sources.map((v) => (
= (set, get) => ({
},
setSourceId(id) {
set((s) => {
+ s.status = playerStatus.PLAYING;
s.sourceId = id;
});
},
@@ -153,6 +154,8 @@ export const createSourceSlice: MakeSlice = (set, get) => ({
s.qualities = qualities as SourceQuality[];
s.currentQuality = loadableStream.quality;
s.captionList = captions;
+ s.interface.error = undefined;
+ s.status = playerStatus.PLAYING;
});
const store = get();
store.redisplaySource(startAt);
@@ -166,7 +169,10 @@ export const createSourceSlice: MakeSlice = (set, get) => ({
automaticQuality: qualityPreferences.quality.automaticQuality,
lastChosenQuality: quality,
});
-
+ set((s) => {
+ s.interface.error = undefined;
+ s.status = playerStatus.PLAYING;
+ });
store.display?.load({
source: loadableStream.stream,
startAt,
@@ -182,6 +188,8 @@ export const createSourceSlice: MakeSlice = (set, get) => ({
if (!selectedQuality) return;
set((s) => {
s.currentQuality = quality;
+ s.status = playerStatus.PLAYING;
+ s.interface.error = undefined;
});
store.display?.load({
source: selectedQuality,
diff --git a/src/utils/language.ts b/src/utils/language.ts
index 6fda7df8..41b8168b 100644
--- a/src/utils/language.ts
+++ b/src/utils/language.ts
@@ -86,7 +86,7 @@ function populateLanguageCode(language: string): string {
* @returns pretty format for language, null if it no info can be found for language
*/
export function getPrettyLanguageNameFromLocale(locale: string): string | null {
- const tag = getTag(populateLanguageCode(locale), true);
+ const tag = getTag(locale, true);
const lang = tag?.language?.Description?.[0] ?? null;
if (!lang) return null;
From 6c7f1aceceab21bd5db888d2e1c2604084f0c0ae Mon Sep 17 00:00:00 2001
From: mrjvs
Date: Thu, 11 Jan 2024 19:16:22 +0100
Subject: [PATCH 011/246] Remove requestDomain
---
src/backend/extension/messaging.ts | 18 +++---------------
src/backend/extension/plasmo.ts | 4 +---
2 files changed, 4 insertions(+), 18 deletions(-)
diff --git a/src/backend/extension/messaging.ts b/src/backend/extension/messaging.ts
index 4594fb23..6d1f32fd 100644
--- a/src/backend/extension/messaging.ts
+++ b/src/backend/extension/messaging.ts
@@ -36,31 +36,19 @@ function sendMessage(
export async function sendExtensionRequest(
ops: Omit,
): Promise | null> {
- return sendMessage("makeRequest", {
- requestDomain: window.location.origin, // TODO unsafe
- ...ops,
- });
+ return sendMessage("makeRequest", ops);
}
export async function setDomainRule(
ops: Omit,
): Promise {
- return sendMessage("prepareStream", {
- requestDomain: window.location.origin,
- ...ops,
- });
+ return sendMessage("prepareStream", ops);
}
export async function extensionInfo(): Promise<
MessagesMetadata["hello"]["res"] | null
> {
- const message = await sendMessage(
- "hello",
- {
- requestDomain: window.location.origin,
- },
- 300,
- );
+ const message = await sendMessage("hello", {}, 300);
if (!message?.success) return null;
if (!message.allowed) return null;
return message;
diff --git a/src/backend/extension/plasmo.ts b/src/backend/extension/plasmo.ts
index e142ef6a..12222b22 100644
--- a/src/backend/extension/plasmo.ts
+++ b/src/backend/extension/plasmo.ts
@@ -1,6 +1,4 @@
-export interface ExtensionBaseRequest {
- requestDomain: string;
-}
+export interface ExtensionBaseRequest {}
export type ExtensionBaseResponse =
| ({
From ccbf888946082d339234f06317517a09aa65fa69 Mon Sep 17 00:00:00 2001
From: Jorrin
Date: Thu, 11 Jan 2024 23:45:33 +0100
Subject: [PATCH 012/246] firefox support (kinda with manual permission set)
---
src/backend/extension/streams.ts | 2 +-
src/backend/providers/fetchers.ts | 36 ++++++++++++++++++++++---------
src/backend/providers/utils.ts | 29 +++++++++++++++++++++++++
3 files changed, 56 insertions(+), 11 deletions(-)
create mode 100644 src/backend/providers/utils.ts
diff --git a/src/backend/extension/streams.ts b/src/backend/extension/streams.ts
index 2afb900d..daa7a54c 100644
--- a/src/backend/extension/streams.ts
+++ b/src/backend/extension/streams.ts
@@ -34,7 +34,7 @@ function buildHeadersFromStream(stream: Stream): Record {
export async function prepareStream(stream: Stream) {
await setDomainRule({
- ruleId: 1,
+ ruleId: 2,
targetDomains: extractDomainsFromStream(stream),
requestHeaders: buildHeadersFromStream(stream),
});
diff --git a/src/backend/providers/fetchers.ts b/src/backend/providers/fetchers.ts
index 9db649f5..83e42835 100644
--- a/src/backend/providers/fetchers.ts
+++ b/src/backend/providers/fetchers.ts
@@ -1,9 +1,11 @@
import { Fetcher, makeSimpleProxyFetcher } from "@movie-web/providers";
-import { sendExtensionRequest } from "@/backend/extension/messaging";
+import { setDomainRule } from "@/backend/extension/messaging";
import { getApiToken, setApiToken } from "@/backend/helpers/providerApi";
import { getProviderApiUrls, getProxyUrls } from "@/utils/proxyUrls";
+import { makeFullUrl } from "./utils";
+
function makeLoadbalancedList(getter: () => string[]) {
let listIndex = -1;
return () => {
@@ -67,17 +69,31 @@ function makeFinalHeaders(
export function makeExtensionFetcher() {
const fetcher: Fetcher = async (url, ops) => {
- const result = await sendExtensionRequest({
- url,
- ...ops,
+ const fullUrl = makeFullUrl(url, ops);
+ const res = await setDomainRule({
+ ruleId: 1,
+ targetDomains: [fullUrl],
+ requestHeaders: ops.headers,
});
- if (!result?.success) throw new Error(`extension error: ${result?.error}`);
- const res = result.response;
+ console.log(res, fullUrl);
+ const response = await fetch(fullUrl, {
+ method: ops.method,
+ headers: ops.headers,
+ body: ops.body as any,
+ });
+ const contentType = response.headers.get("content-type");
+ const body = contentType?.includes("application/json")
+ ? await response.json()
+ : await response.text();
+
return {
- body: res.body,
- finalUrl: res.finalUrl,
- statusCode: res.statusCode,
- headers: makeFinalHeaders(ops.readHeaders, res.headers),
+ body,
+ finalUrl: response.url,
+ statusCode: response.status,
+ headers: makeFinalHeaders(
+ ops.readHeaders,
+ Object.fromEntries(response.headers.entries()),
+ ),
};
};
return fetcher;
diff --git a/src/backend/providers/utils.ts b/src/backend/providers/utils.ts
new file mode 100644
index 00000000..7149046c
--- /dev/null
+++ b/src/backend/providers/utils.ts
@@ -0,0 +1,29 @@
+import { DefaultedFetcherOptions } from "@movie-web/providers";
+
+export function makeFullUrl(
+ url: string,
+ ops?: DefaultedFetcherOptions,
+): string {
+ // glue baseUrl and rest of url together
+ let leftSide = ops?.baseUrl ?? "";
+ let rightSide = url;
+
+ // left side should always end with slash, if its set
+ if (leftSide.length > 0 && !leftSide.endsWith("/")) leftSide += "/";
+
+ // right side should never start with slash
+ if (rightSide.startsWith("/")) rightSide = rightSide.slice(1);
+
+ const fullUrl = leftSide + rightSide;
+ if (!fullUrl.startsWith("http://") && !fullUrl.startsWith("https://"))
+ throw new Error(
+ `Invald URL -- URL doesn't start with a http scheme: '${fullUrl}'`,
+ );
+
+ const parsedUrl = new URL(fullUrl);
+ Object.entries(ops?.query ?? {}).forEach(([k, v]) => {
+ parsedUrl.searchParams.set(k, v as string);
+ });
+
+ return parsedUrl.toString();
+}
From a4b925dc1b7749aa4caed3b4b4c174d38f018795 Mon Sep 17 00:00:00 2001
From: Jorrin
Date: Fri, 12 Jan 2024 18:46:51 +0100
Subject: [PATCH 013/246] revert back to makeRequest
---
src/backend/extension/streams.ts | 2 +-
src/backend/providers/fetchers.ts | 38 +++++++++----------------------
src/backend/providers/utils.ts | 29 -----------------------
3 files changed, 12 insertions(+), 57 deletions(-)
delete mode 100644 src/backend/providers/utils.ts
diff --git a/src/backend/extension/streams.ts b/src/backend/extension/streams.ts
index daa7a54c..2afb900d 100644
--- a/src/backend/extension/streams.ts
+++ b/src/backend/extension/streams.ts
@@ -34,7 +34,7 @@ function buildHeadersFromStream(stream: Stream): Record {
export async function prepareStream(stream: Stream) {
await setDomainRule({
- ruleId: 2,
+ ruleId: 1,
targetDomains: extractDomainsFromStream(stream),
requestHeaders: buildHeadersFromStream(stream),
});
diff --git a/src/backend/providers/fetchers.ts b/src/backend/providers/fetchers.ts
index 83e42835..95267595 100644
--- a/src/backend/providers/fetchers.ts
+++ b/src/backend/providers/fetchers.ts
@@ -1,11 +1,9 @@
import { Fetcher, makeSimpleProxyFetcher } from "@movie-web/providers";
-import { setDomainRule } from "@/backend/extension/messaging";
+import { sendExtensionRequest } from "@/backend/extension/messaging";
import { getApiToken, setApiToken } from "@/backend/helpers/providerApi";
import { getProviderApiUrls, getProxyUrls } from "@/utils/proxyUrls";
-import { makeFullUrl } from "./utils";
-
function makeLoadbalancedList(getter: () => string[]) {
let listIndex = -1;
return () => {
@@ -69,31 +67,17 @@ function makeFinalHeaders(
export function makeExtensionFetcher() {
const fetcher: Fetcher = async (url, ops) => {
- const fullUrl = makeFullUrl(url, ops);
- const res = await setDomainRule({
- ruleId: 1,
- targetDomains: [fullUrl],
- requestHeaders: ops.headers,
- });
- console.log(res, fullUrl);
- const response = await fetch(fullUrl, {
- method: ops.method,
- headers: ops.headers,
- body: ops.body as any,
- });
- const contentType = response.headers.get("content-type");
- const body = contentType?.includes("application/json")
- ? await response.json()
- : await response.text();
-
+ const result = (await sendExtensionRequest({
+ url,
+ ...ops,
+ })) as any;
+ if (!result?.success) throw new Error(`extension error: ${result?.error}`);
+ const res = result.response;
return {
- body,
- finalUrl: response.url,
- statusCode: response.status,
- headers: makeFinalHeaders(
- ops.readHeaders,
- Object.fromEntries(response.headers.entries()),
- ),
+ body: res.body,
+ finalUrl: res.finalUrl,
+ statusCode: res.statusCode,
+ headers: makeFinalHeaders(ops.readHeaders, res.headers),
};
};
return fetcher;
diff --git a/src/backend/providers/utils.ts b/src/backend/providers/utils.ts
deleted file mode 100644
index 7149046c..00000000
--- a/src/backend/providers/utils.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { DefaultedFetcherOptions } from "@movie-web/providers";
-
-export function makeFullUrl(
- url: string,
- ops?: DefaultedFetcherOptions,
-): string {
- // glue baseUrl and rest of url together
- let leftSide = ops?.baseUrl ?? "";
- let rightSide = url;
-
- // left side should always end with slash, if its set
- if (leftSide.length > 0 && !leftSide.endsWith("/")) leftSide += "/";
-
- // right side should never start with slash
- if (rightSide.startsWith("/")) rightSide = rightSide.slice(1);
-
- const fullUrl = leftSide + rightSide;
- if (!fullUrl.startsWith("http://") && !fullUrl.startsWith("https://"))
- throw new Error(
- `Invald URL -- URL doesn't start with a http scheme: '${fullUrl}'`,
- );
-
- const parsedUrl = new URL(fullUrl);
- Object.entries(ops?.query ?? {}).forEach(([k, v]) => {
- parsedUrl.searchParams.set(k, v as string);
- });
-
- return parsedUrl.toString();
-}
From 948843769820630fdc49e323bd64a788d968f7e6 Mon Sep 17 00:00:00 2001
From: Jorrin
Date: Sun, 14 Jan 2024 18:19:38 +0100
Subject: [PATCH 014/246] check if extension has permissions
---
src/backend/extension/messaging.ts | 10 +++++--
src/backend/extension/plasmo.ts | 8 ++++++
src/pages/parts/player/MetaPart.tsx | 43 ++++++++++++++++++++++++++---
3 files changed, 55 insertions(+), 6 deletions(-)
diff --git a/src/backend/extension/messaging.ts b/src/backend/extension/messaging.ts
index 6d1f32fd..2e2747c1 100644
--- a/src/backend/extension/messaging.ts
+++ b/src/backend/extension/messaging.ts
@@ -34,17 +34,23 @@ function sendMessage(
}
export async function sendExtensionRequest(
- ops: Omit,
+ ops: MessagesMetadata["makeRequest"]["req"],
): Promise | null> {
return sendMessage("makeRequest", ops);
}
export async function setDomainRule(
- ops: Omit,
+ ops: MessagesMetadata["prepareStream"]["req"],
): Promise {
return sendMessage("prepareStream", ops);
}
+export async function sendPage(
+ ops: MessagesMetadata["openPage"]["req"],
+): Promise {
+ return sendMessage("openPage", ops);
+}
+
export async function extensionInfo(): Promise<
MessagesMetadata["hello"]["res"] | null
> {
diff --git a/src/backend/extension/plasmo.ts b/src/backend/extension/plasmo.ts
index 12222b22..c13898be 100644
--- a/src/backend/extension/plasmo.ts
+++ b/src/backend/extension/plasmo.ts
@@ -12,6 +12,7 @@ export type ExtensionBaseResponse =
export type ExtensionHelloResponse = ExtensionBaseResponse<{
version: string;
allowed: boolean;
+ hasPermission: boolean;
}>;
export interface ExtensionMakeRequest extends ExtensionBaseRequest {
@@ -50,6 +51,13 @@ export interface MmMetadata {
req: ExtensionPrepareStreamRequest;
res: ExtensionBaseResponse;
};
+ openPage: {
+ req: ExtensionBaseRequest & {
+ page: string;
+ redirectUrl: string;
+ };
+ res: ExtensionBaseResponse;
+ };
}
interface MpMetadata {}
diff --git a/src/pages/parts/player/MetaPart.tsx b/src/pages/parts/player/MetaPart.tsx
index 6d5b64ef..fb40379a 100644
--- a/src/pages/parts/player/MetaPart.tsx
+++ b/src/pages/parts/player/MetaPart.tsx
@@ -3,7 +3,8 @@ 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 { isAllowedExtensionVersion } from "@/backend/extension/compatibility";
+import { extensionInfo, sendPage } from "@/backend/extension/messaging";
import {
fetchMetadata,
setCachedMetadata,
@@ -43,12 +44,16 @@ export function MetaPart(props: MetaPartProps) {
const navigate = useNavigate();
const { error, value, loading } = useAsync(async () => {
- // check extension
- const isActive = await isExtensionActive();
+ const info = await extensionInfo();
+ const isAllowed = info?.success && isAllowedExtensionVersion(info.version);
+
+ if (isAllowed) {
+ if (!info.hasPermission) throw new Error("extension-no-permission");
+ }
// use api metadata or providers metadata
const providerApiUrl = getLoadbalancedProviderApiUrl();
- if (providerApiUrl && !isActive) {
+ if (providerApiUrl && !isAllowed) {
try {
await fetchMetadata(providerApiUrl);
} catch (err) {
@@ -105,6 +110,36 @@ export function MetaPart(props: MetaPartProps) {
props.onGetMeta?.(meta, epId);
}, []);
+ if (error && error.message === "extension-no-permission") {
+ return (
+
+
+
+ {t("player.metadata.failed.badge")}
+
+ Configure the extension
+
+ You have the browser extension, but we need your permission to get
+ started using the extension.
+
+
+
+
+ );
+ }
+
if (error && error.message === "dmca") {
return (
From fa2b610ea6f3e4fc3d6481ed5f5c2090478adbd4 Mon Sep 17 00:00:00 2001
From: Jorrin
Date: Sun, 14 Jan 2024 22:33:46 +0100
Subject: [PATCH 015/246] fix permission check on domain level
---
src/backend/extension/messaging.ts | 6 ++----
src/pages/parts/player/MetaPart.tsx | 10 ++++++----
2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/src/backend/extension/messaging.ts b/src/backend/extension/messaging.ts
index 2e2747c1..b738184a 100644
--- a/src/backend/extension/messaging.ts
+++ b/src/backend/extension/messaging.ts
@@ -10,7 +10,7 @@ let activeExtension = false;
function sendMessage(
message: MessageKey,
- payload: MessagesMetadata[MessageKey]["req"],
+ payload: MessagesMetadata[MessageKey]["req"] | undefined = undefined,
timeout: number = -1,
) {
return new Promise((resolve) => {
@@ -54,9 +54,7 @@ export async function sendPage(
export async function extensionInfo(): Promise<
MessagesMetadata["hello"]["res"] | null
> {
- const message = await sendMessage("hello", {}, 300);
- if (!message?.success) return null;
- if (!message.allowed) return null;
+ const message = await sendMessage("hello", undefined, 300);
return message;
}
diff --git a/src/pages/parts/player/MetaPart.tsx b/src/pages/parts/player/MetaPart.tsx
index fb40379a..1e18b6d1 100644
--- a/src/pages/parts/player/MetaPart.tsx
+++ b/src/pages/parts/player/MetaPart.tsx
@@ -45,15 +45,17 @@ export function MetaPart(props: MetaPartProps) {
const { error, value, loading } = useAsync(async () => {
const info = await extensionInfo();
- const isAllowed = info?.success && isAllowedExtensionVersion(info.version);
+ const isValidExtension =
+ info?.success && isAllowedExtensionVersion(info.version);
- if (isAllowed) {
- if (!info.hasPermission) throw new Error("extension-no-permission");
+ if (isValidExtension) {
+ if (!info.allowed || !info.hasPermission)
+ throw new Error("extension-no-permission");
}
// use api metadata or providers metadata
const providerApiUrl = getLoadbalancedProviderApiUrl();
- if (providerApiUrl && !isAllowed) {
+ if (providerApiUrl && !isValidExtension) {
try {
await fetchMetadata(providerApiUrl);
} catch (err) {
From 925f3dff199fc6457eb9f97209addf780700e565 Mon Sep 17 00:00:00 2001
From: mrjvs
Date: Tue, 16 Jan 2024 20:28:33 +0100
Subject: [PATCH 016/246] Basic onboarding structure
---
src/components/layout/Stepper.tsx | 25 ++++++++++
src/components/layout/ThinContainer.tsx | 14 ++++++
src/pages/PlayerView.tsx | 32 ++++++++++++-
src/pages/layouts/MinimalPageLayout.tsx | 28 ++++++++++++
src/pages/onboarding/Onboarding.tsx | 48 ++++++++++++++++++++
src/pages/onboarding/OnboardingExtension.tsx | 27 +++++++++++
src/pages/onboarding/OnboardingProxy.tsx | 27 +++++++++++
src/pages/onboarding/onboardingHooks.ts | 22 +++++++++
src/setup/App.tsx | 9 ++++
src/setup/config.ts | 4 ++
src/stores/history/index.ts | 3 +-
src/stores/onboarding/index.tsx | 22 +++++++++
src/utils/onboarding.ts | 23 ++++++++++
13 files changed, 281 insertions(+), 3 deletions(-)
create mode 100644 src/components/layout/Stepper.tsx
create mode 100644 src/pages/layouts/MinimalPageLayout.tsx
create mode 100644 src/pages/onboarding/Onboarding.tsx
create mode 100644 src/pages/onboarding/OnboardingExtension.tsx
create mode 100644 src/pages/onboarding/OnboardingProxy.tsx
create mode 100644 src/pages/onboarding/onboardingHooks.ts
create mode 100644 src/stores/onboarding/index.tsx
create mode 100644 src/utils/onboarding.ts
diff --git a/src/components/layout/Stepper.tsx b/src/components/layout/Stepper.tsx
new file mode 100644
index 00000000..d0c9c499
--- /dev/null
+++ b/src/components/layout/Stepper.tsx
@@ -0,0 +1,25 @@
+export interface StepperProps {
+ current: number;
+ steps: number;
+ className?: string;
+}
+
+export function Stepper(props: StepperProps) {
+ const percentage = (props.current / (props.steps + 1)) * 100;
+
+ return (
+
+
+ {props.current}/{props.steps}
+
+
+
+ );
+}
diff --git a/src/components/layout/ThinContainer.tsx b/src/components/layout/ThinContainer.tsx
index f7f90acb..345afa5c 100644
--- a/src/components/layout/ThinContainer.tsx
+++ b/src/components/layout/ThinContainer.tsx
@@ -1,3 +1,4 @@
+import classNames from "classnames";
import { ReactNode } from "react";
interface ThinContainerProps {
@@ -16,3 +17,16 @@ export function ThinContainer(props: ThinContainerProps) {
);
}
+
+export function CenterContainer(props: ThinContainerProps) {
+ return (
+
+ );
+}
diff --git a/src/pages/PlayerView.tsx b/src/pages/PlayerView.tsx
index aa73bd83..5282d9b3 100644
--- a/src/pages/PlayerView.tsx
+++ b/src/pages/PlayerView.tsx
@@ -1,6 +1,12 @@
import { RunOutput } from "@movie-web/providers";
import { useCallback, useEffect, useState } from "react";
-import { useNavigate, useParams } from "react-router-dom";
+import {
+ Navigate,
+ useLocation,
+ useNavigate,
+ useParams,
+} from "react-router-dom";
+import { useAsync } from "react-use";
import { usePlayer } from "@/components/player/hooks/usePlayer";
import { usePlayerMeta } from "@/components/player/hooks/usePlayerMeta";
@@ -15,9 +21,10 @@ import { ScrapeErrorPart } from "@/pages/parts/player/ScrapeErrorPart";
import { ScrapingPart } from "@/pages/parts/player/ScrapingPart";
import { useLastNonPlayerLink } from "@/stores/history";
import { PlayerMeta, playerStatus } from "@/stores/player/slices/source";
+import { needsOnboarding } from "@/utils/onboarding";
import { parseTimestamp } from "@/utils/timestamp";
-export function PlayerView() {
+export function RealPlayerView() {
const navigate = useNavigate();
const params = useParams<{
media: string;
@@ -109,4 +116,25 @@ export function PlayerView() {
);
}
+export function PlayerView() {
+ const loc = useLocation();
+ const { loading, error, value } = useAsync(() => {
+ return needsOnboarding();
+ });
+
+ if (error) throw new Error("Failed to detect onboarding");
+ if (loading) return null;
+ if (value)
+ return (
+
+ );
+ return ;
+}
+
export default PlayerView;
diff --git a/src/pages/layouts/MinimalPageLayout.tsx b/src/pages/layouts/MinimalPageLayout.tsx
new file mode 100644
index 00000000..6d2cf34d
--- /dev/null
+++ b/src/pages/layouts/MinimalPageLayout.tsx
@@ -0,0 +1,28 @@
+import { Link } from "react-router-dom";
+
+import { BrandPill } from "@/components/layout/BrandPill";
+import { BlurEllipsis } from "@/pages/layouts/SubPageLayout";
+
+export function MinimalPageLayout(props: { children: React.ReactNode }) {
+ return (
+
+
+ {/* Main page */}
+
+
+
+
+
+
{props.children}
+
+ );
+}
diff --git a/src/pages/onboarding/Onboarding.tsx b/src/pages/onboarding/Onboarding.tsx
new file mode 100644
index 00000000..439cc87d
--- /dev/null
+++ b/src/pages/onboarding/Onboarding.tsx
@@ -0,0 +1,48 @@
+import { useNavigate } from "react-router-dom";
+
+import { Button } from "@/components/buttons/Button";
+import { Stepper } from "@/components/layout/Stepper";
+import { CenterContainer } from "@/components/layout/ThinContainer";
+import { Modal, ModalCard, useModal } from "@/components/overlays/Modal";
+import { Heading2, Paragraph } from "@/components/utils/Text";
+import { MinimalPageLayout } from "@/pages/layouts/MinimalPageLayout";
+import { useRedirectBack } from "@/pages/onboarding/onboardingHooks";
+import { PageTitle } from "@/pages/parts/util/PageTitle";
+
+export function OnboardingPage() {
+ const navigate = useNavigate();
+ const skipModal = useModal("skip");
+ const { skipAndRedirect } = useRedirectBack();
+
+ return (
+
+
+
+
+
+ Lorem ipsum
+ Lorem ipsum Lorem ipsum Lorem ipsum
+
+
+
+
+
+
+
+ Lorem ipsum
+ Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum
+
+
+
+
+
+ );
+}
diff --git a/src/pages/onboarding/OnboardingExtension.tsx b/src/pages/onboarding/OnboardingExtension.tsx
new file mode 100644
index 00000000..8ee85be7
--- /dev/null
+++ b/src/pages/onboarding/OnboardingExtension.tsx
@@ -0,0 +1,27 @@
+import { useNavigate } from "react-router-dom";
+
+import { Button } from "@/components/buttons/Button";
+import { Stepper } from "@/components/layout/Stepper";
+import { CenterContainer } from "@/components/layout/ThinContainer";
+import { Heading2, Paragraph } from "@/components/utils/Text";
+import { MinimalPageLayout } from "@/pages/layouts/MinimalPageLayout";
+import { PageTitle } from "@/pages/parts/util/PageTitle";
+
+export function OnboardingExtensionPage() {
+ const navigate = useNavigate();
+
+ return (
+
+
+
+
+ Lorem ipsum
+ Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum
+
+
+
+
+ );
+}
diff --git a/src/pages/onboarding/OnboardingProxy.tsx b/src/pages/onboarding/OnboardingProxy.tsx
new file mode 100644
index 00000000..45d22aea
--- /dev/null
+++ b/src/pages/onboarding/OnboardingProxy.tsx
@@ -0,0 +1,27 @@
+import { useNavigate } from "react-router-dom";
+
+import { Button } from "@/components/buttons/Button";
+import { Stepper } from "@/components/layout/Stepper";
+import { CenterContainer } from "@/components/layout/ThinContainer";
+import { Heading2, Paragraph } from "@/components/utils/Text";
+import { MinimalPageLayout } from "@/pages/layouts/MinimalPageLayout";
+import { PageTitle } from "@/pages/parts/util/PageTitle";
+
+export function OnboardingProxyPage() {
+ const navigate = useNavigate();
+
+ return (
+
+
+
+
+ Lorem ipsum
+ Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum
+
+
+
+
+ );
+}
diff --git a/src/pages/onboarding/onboardingHooks.ts b/src/pages/onboarding/onboardingHooks.ts
new file mode 100644
index 00000000..e80234e7
--- /dev/null
+++ b/src/pages/onboarding/onboardingHooks.ts
@@ -0,0 +1,22 @@
+import { useCallback } from "react";
+import { useNavigate } from "react-router-dom";
+
+import { useQueryParam } from "@/hooks/useQueryParams";
+import { useOnboardingStore } from "@/stores/onboarding";
+
+export function useRedirectBack() {
+ const [url] = useQueryParam("redirect");
+ const navigate = useNavigate();
+ const setSkipped = useOnboardingStore((s) => s.setSkipped);
+
+ const redirectBack = useCallback(() => {
+ navigate(url ?? "/");
+ }, [navigate, url]);
+
+ const skipAndRedirect = useCallback(() => {
+ setSkipped(true);
+ redirectBack();
+ }, [redirectBack, setSkipped]);
+
+ return { redirectBack, skipAndRedirect };
+}
diff --git a/src/setup/App.tsx b/src/setup/App.tsx
index afa5f8ba..37c55e0f 100644
--- a/src/setup/App.tsx
+++ b/src/setup/App.tsx
@@ -19,6 +19,9 @@ import { DmcaPage, shouldHaveDmcaPage } from "@/pages/Dmca";
import { NotFoundPage } from "@/pages/errors/NotFoundPage";
import { HomePage } from "@/pages/HomePage";
import { LoginPage } from "@/pages/Login";
+import { OnboardingPage } from "@/pages/onboarding/Onboarding";
+import { OnboardingExtensionPage } from "@/pages/onboarding/OnboardingExtension";
+import { OnboardingProxyPage } from "@/pages/onboarding/OnboardingProxy";
import { RegisterPage } from "@/pages/Register";
import { Layout } from "@/setup/Layout";
import { useHistoryListener } from "@/stores/history";
@@ -119,6 +122,12 @@ function App() {
} />
} />
} />
+ } />
+ }
+ />
+ } />
{shouldHaveDmcaPage() ? (
} />
diff --git a/src/setup/config.ts b/src/setup/config.ts
index 2e1634a4..d6aca1cb 100644
--- a/src/setup/config.ts
+++ b/src/setup/config.ts
@@ -19,6 +19,7 @@ interface Config {
DISALLOWED_IDS: string;
TURNSTILE_KEY: string;
CDN_REPLACEMENTS: string;
+ HAS_ONBOARDING: string;
}
export interface RuntimeConfig {
@@ -34,6 +35,7 @@ export interface RuntimeConfig {
DISALLOWED_IDS: string[];
TURNSTILE_KEY: string | null;
CDN_REPLACEMENTS: Array;
+ HAS_ONBOARDING: boolean;
}
const env: Record = {
@@ -49,6 +51,7 @@ const env: Record = {
DISALLOWED_IDS: import.meta.env.VITE_DISALLOWED_IDS,
TURNSTILE_KEY: import.meta.env.VITE_TURNSTILE_KEY,
CDN_REPLACEMENTS: import.meta.env.VITE_CDN_REPLACEMENTS,
+ HAS_ONBOARDING: import.meta.env.VITE_HAS_ONBOARDING,
};
// loads from different locations, in order: environment (VITE_{KEY}), window (public/config.js)
@@ -82,6 +85,7 @@ export function conf(): RuntimeConfig {
.split(",")
.map((v) => v.trim()),
NORMAL_ROUTER: getKey("NORMAL_ROUTER", "false") === "true",
+ HAS_ONBOARDING: getKey("HAS_ONBOARDING", "false") === "true",
TURNSTILE_KEY: turnstileKey.length > 0 ? turnstileKey : null,
DISALLOWED_IDS: getKey("DISALLOWED_IDS", "")
.split(",")
diff --git a/src/stores/history/index.ts b/src/stores/history/index.ts
index c90a7309..c1f507ee 100644
--- a/src/stores/history/index.ts
+++ b/src/stores/history/index.ts
@@ -46,7 +46,8 @@ export function useLastNonPlayerLink() {
(v) =>
!v.path.startsWith("/media") && // cannot be a player link
location.pathname !== v.path && // cannot be current link
- !v.path.startsWith("/s/"), // cannot be a quick search link
+ !v.path.startsWith("/s/") && // cannot be a quick search link
+ !v.path.startsWith("/onboarding"), // cannot be an onboarding link
);
return route?.path ?? "/";
}, [routes, location]);
diff --git a/src/stores/onboarding/index.tsx b/src/stores/onboarding/index.tsx
new file mode 100644
index 00000000..17d1965d
--- /dev/null
+++ b/src/stores/onboarding/index.tsx
@@ -0,0 +1,22 @@
+import { create } from "zustand";
+import { persist } from "zustand/middleware";
+import { immer } from "zustand/middleware/immer";
+
+export interface OnboardingStore {
+ skipped: boolean;
+ setSkipped(v: boolean): void;
+}
+
+export const useOnboardingStore = create(
+ persist(
+ immer((set) => ({
+ skipped: false,
+ setSkipped(v) {
+ set((s) => {
+ s.skipped = v;
+ });
+ },
+ })),
+ { name: "__MW::onboarding" },
+ ),
+);
diff --git a/src/utils/onboarding.ts b/src/utils/onboarding.ts
new file mode 100644
index 00000000..7289fd1c
--- /dev/null
+++ b/src/utils/onboarding.ts
@@ -0,0 +1,23 @@
+import { isExtensionActive } from "@/backend/extension/messaging";
+import { conf } from "@/setup/config";
+import { useAuthStore } from "@/stores/auth";
+import { useOnboardingStore } from "@/stores/onboarding";
+
+export async function needsOnboarding(): Promise {
+ // if onboarding is dislabed, no onboarding needed
+ if (!conf().HAS_ONBOARDING) return false;
+
+ // if extension is active and working, no onboarding needed
+ const extensionActive = await isExtensionActive();
+ if (extensionActive) return false;
+
+ // if there is any custom proxy urls, no onboarding needed
+ const proxyUrls = useAuthStore.getState().proxySet;
+ if (proxyUrls) return false;
+
+ // if onboarding has been skipped, no onboarding needed
+ const skipped = useOnboardingStore.getState().skipped;
+ if (skipped) return false;
+
+ return true;
+}
From a226f3347cb77affffca21dd00fb512f7bc0388b Mon Sep 17 00:00:00 2001
From: mrjvs
Date: Tue, 16 Jan 2024 22:07:21 +0100
Subject: [PATCH 017/246] Add onboarding functionality
---
src/pages/onboarding/Onboarding.tsx | 4 +-
src/pages/onboarding/OnboardingExtension.tsx | 55 +++++++++++++++++++-
src/pages/onboarding/OnboardingProxy.tsx | 27 ++++++++--
src/pages/onboarding/onboardingHooks.ts | 10 ++--
src/stores/onboarding/index.tsx | 10 ++--
src/utils/onboarding.ts | 6 +--
6 files changed, 92 insertions(+), 20 deletions(-)
diff --git a/src/pages/onboarding/Onboarding.tsx b/src/pages/onboarding/Onboarding.tsx
index 439cc87d..3247a7ee 100644
--- a/src/pages/onboarding/Onboarding.tsx
+++ b/src/pages/onboarding/Onboarding.tsx
@@ -12,7 +12,7 @@ import { PageTitle } from "@/pages/parts/util/PageTitle";
export function OnboardingPage() {
const navigate = useNavigate();
const skipModal = useModal("skip");
- const { skipAndRedirect } = useRedirectBack();
+ const { completeAndRedirect } = useRedirectBack();
return (
@@ -25,7 +25,7 @@ export function OnboardingPage() {
-