mirror of
https://github.com/movie-web/movie-web.git
synced 2024-11-13 10:35:08 +01:00
Merge pull request #819 from movie-web/extension-fixes
Small extension fixes
This commit is contained in:
commit
9dd59479f0
1
.github/workflows/linting_testing.yml
vendored
1
.github/workflows/linting_testing.yml
vendored
@ -71,5 +71,4 @@ jobs:
|
|||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
push: false
|
push: false
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
context: .
|
context: .
|
||||||
|
@ -38,6 +38,7 @@
|
|||||||
"@types/node-forge": "^1.3.10",
|
"@types/node-forge": "^1.3.10",
|
||||||
"classnames": "^2.3.2",
|
"classnames": "^2.3.2",
|
||||||
"core-js": "^3.34.0",
|
"core-js": "^3.34.0",
|
||||||
|
"detect-browser": "^5.3.0",
|
||||||
"dompurify": "^3.0.6",
|
"dompurify": "^3.0.6",
|
||||||
"flag-icons": "^7.1.0",
|
"flag-icons": "^7.1.0",
|
||||||
"focus-trap-react": "^10.2.3",
|
"focus-trap-react": "^10.2.3",
|
||||||
|
@ -48,6 +48,9 @@ dependencies:
|
|||||||
core-js:
|
core-js:
|
||||||
specifier: ^3.34.0
|
specifier: ^3.34.0
|
||||||
version: 3.34.0
|
version: 3.34.0
|
||||||
|
detect-browser:
|
||||||
|
specifier: ^5.3.0
|
||||||
|
version: 5.3.0
|
||||||
dompurify:
|
dompurify:
|
||||||
specifier: ^3.0.6
|
specifier: ^3.0.6
|
||||||
version: 3.0.6
|
version: 3.0.6
|
||||||
@ -3338,6 +3341,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-M1Ob1zPSIvlARiJUkKqvAZ3VAqQY6Jcuth/pBKQ2b1dX/Qx0OnJ8Vux6J2H5PTMQeRzWrrbTu70VxBfv/OPDJA==}
|
resolution: {integrity: sha512-M1Ob1zPSIvlARiJUkKqvAZ3VAqQY6Jcuth/pBKQ2b1dX/Qx0OnJ8Vux6J2H5PTMQeRzWrrbTu70VxBfv/OPDJA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/detect-browser@5.3.0:
|
||||||
|
resolution: {integrity: sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/didyoumean@1.2.2:
|
/didyoumean@1.2.2:
|
||||||
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
|
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -232,7 +232,7 @@
|
|||||||
"downloadSubtitle": "Download current subtitle",
|
"downloadSubtitle": "Download current subtitle",
|
||||||
"downloadPlaylist": "Download playlist",
|
"downloadPlaylist": "Download playlist",
|
||||||
"downloadVideo": "Download video",
|
"downloadVideo": "Download video",
|
||||||
"hlsDisclaimer": "Downloads are taken directly from the provider. movie-web does not have control over how the downloads are provided.<br /><br />Please note that you are downloading an HLS playlist, it is <bold>not recommended to download if you are not familiar with advanced streaming formats</bold>. Try different sources for different formats.",
|
"hlsDisclaimer": "Downloads are taken directly from the provider. movie-web does not have control over how the downloads are provided.<br /><br />Please note that you are downloading an HLS playlist, <bold>it is not recommended to download if you are not familiar with advanced streaming formats</bold>. Try different sources for different formats.",
|
||||||
"onAndroid": {
|
"onAndroid": {
|
||||||
"1": "To download on Android, click the download button then, on the new page, <bold>tap and hold</bold> on the video, then select <bold>save</bold>.",
|
"1": "To download on Android, click the download button then, on the new page, <bold>tap and hold</bold> on the video, then select <bold>save</bold>.",
|
||||||
"shortTitle": "Download / Android",
|
"shortTitle": "Download / Android",
|
||||||
@ -506,8 +506,12 @@
|
|||||||
"extension": {
|
"extension": {
|
||||||
"title": "Let's start with an extension",
|
"title": "Let's start with an extension",
|
||||||
"explainer": "Using the browser extension, you can get the best streams we have to offer. With just a simple install.",
|
"explainer": "Using the browser extension, you can get the best streams we have to offer. With just a simple install.",
|
||||||
|
"explainerIos": "Unfortunately, the browser extension is not supported on IOS, Press <bold>Go back</bold> to choose another option.",
|
||||||
"extensionHelp": "If you've installed the extension but it's not detected. <bold>Open the extension through your browsers extension menu</bold> and follow the steps on screen.",
|
"extensionHelp": "If you've installed the extension but it's not detected. <bold>Open the extension through your browsers extension menu</bold> and follow the steps on screen.",
|
||||||
"link": "Install extension",
|
"notDetecting": "Installed on chrome but not showing up? Try reloading the page!",
|
||||||
|
"notDetectingAction": "Reload page",
|
||||||
|
"linkChrome": "Install Chrome extension",
|
||||||
|
"linkFirefox": "Install Firefox extension",
|
||||||
"back": "Go back",
|
"back": "Go back",
|
||||||
"status": {
|
"status": {
|
||||||
"loading": "Waiting for you to install the extension",
|
"loading": "Waiting for you to install the extension",
|
||||||
|
@ -6,13 +6,22 @@ import {
|
|||||||
import { isAllowedExtensionVersion } from "@/backend/extension/compatibility";
|
import { isAllowedExtensionVersion } from "@/backend/extension/compatibility";
|
||||||
import { ExtensionMakeRequestResponse } from "@/backend/extension/plasmo";
|
import { ExtensionMakeRequestResponse } from "@/backend/extension/plasmo";
|
||||||
|
|
||||||
|
// for some reason, about 500 ms is needed after
|
||||||
|
// page load before the extension starts responding properly
|
||||||
|
const isExtensionReady = new Promise<void>((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve();
|
||||||
|
}, 500);
|
||||||
|
});
|
||||||
|
|
||||||
let activeExtension = false;
|
let activeExtension = false;
|
||||||
|
|
||||||
function sendMessage<MessageKey extends keyof MessagesMetadata>(
|
async function sendMessage<MessageKey extends keyof MessagesMetadata>(
|
||||||
message: MessageKey,
|
message: MessageKey,
|
||||||
payload: MessagesMetadata[MessageKey]["req"] | undefined = undefined,
|
payload: MessagesMetadata[MessageKey]["req"] | undefined = undefined,
|
||||||
timeout: number = -1,
|
timeout: number = -1,
|
||||||
) {
|
) {
|
||||||
|
await isExtensionReady;
|
||||||
return new Promise<MessagesMetadata[MessageKey]["res"] | null>((resolve) => {
|
return new Promise<MessagesMetadata[MessageKey]["res"] | null>((resolve) => {
|
||||||
if (timeout >= 0) setTimeout(() => resolve(null), timeout);
|
if (timeout >= 0) setTimeout(() => resolve(null), timeout);
|
||||||
sendToBackgroundViaRelay<
|
sendToBackgroundViaRelay<
|
||||||
@ -54,7 +63,7 @@ export async function sendPage(
|
|||||||
export async function extensionInfo(): Promise<
|
export async function extensionInfo(): Promise<
|
||||||
MessagesMetadata["hello"]["res"] | null
|
MessagesMetadata["hello"]["res"] | null
|
||||||
> {
|
> {
|
||||||
const message = await sendMessage("hello", undefined, 300);
|
const message = await sendMessage("hello", undefined, 500);
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useMemo } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { Button } from "@/components/buttons/Button";
|
import { Button } from "@/components/buttons/Button";
|
||||||
@ -46,13 +46,13 @@ export function DownloadView({ id }: { id: string }) {
|
|||||||
|
|
||||||
const sourceType = usePlayerStore((s) => s.source?.type);
|
const sourceType = usePlayerStore((s) => s.source?.type);
|
||||||
const selectedCaption = usePlayerStore((s) => s.caption?.selected);
|
const selectedCaption = usePlayerStore((s) => s.caption?.selected);
|
||||||
const subtitleUrl = useMemo(
|
const openSubtitleDownload = useCallback(() => {
|
||||||
() =>
|
const dataUrl = selectedCaption
|
||||||
selectedCaption
|
|
||||||
? convertSubtitlesToSrtDataurl(selectedCaption?.srtData)
|
? convertSubtitlesToSrtDataurl(selectedCaption?.srtData)
|
||||||
: null,
|
: null;
|
||||||
[selectedCaption],
|
if (!dataUrl) return;
|
||||||
);
|
window.open(dataUrl);
|
||||||
|
}, [selectedCaption]);
|
||||||
|
|
||||||
if (!downloadUrl) return null;
|
if (!downloadUrl) return null;
|
||||||
|
|
||||||
@ -74,10 +74,9 @@ export function DownloadView({ id }: { id: string }) {
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className="w-full mt-2"
|
className="w-full mt-2"
|
||||||
href={subtitleUrl ?? undefined}
|
onClick={openSubtitleDownload}
|
||||||
disabled={!subtitleUrl}
|
disabled={!selectedCaption}
|
||||||
theme="secondary"
|
theme="secondary"
|
||||||
download="subtitles.srt"
|
|
||||||
>
|
>
|
||||||
{t("player.menus.downloads.downloadSubtitle")}
|
{t("player.menus.downloads.downloadSubtitle")}
|
||||||
</Button>
|
</Button>
|
||||||
@ -109,8 +108,8 @@ export function DownloadView({ id }: { id: string }) {
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className="w-full mt-2"
|
className="w-full mt-2"
|
||||||
href={subtitleUrl ?? undefined}
|
onClick={openSubtitleDownload}
|
||||||
disabled={!subtitleUrl}
|
disabled={!selectedCaption}
|
||||||
theme="secondary"
|
theme="secondary"
|
||||||
download="subtitles.srt"
|
download="subtitles.srt"
|
||||||
>
|
>
|
||||||
|
@ -39,7 +39,7 @@ export function OnboardingPage() {
|
|||||||
<Paragraph className="!mt-1 !mb-12">
|
<Paragraph className="!mt-1 !mb-12">
|
||||||
{t("onboarding.defaultConfirm.description")}
|
{t("onboarding.defaultConfirm.description")}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<div className="flex items-end justify-between">
|
<div className="flex flex-col-reverse gap-3 md:flex-row md:justify-between">
|
||||||
<Button theme="secondary" onClick={skipModal.hide}>
|
<Button theme="secondary" onClick={skipModal.hide}>
|
||||||
{t("onboarding.defaultConfirm.cancel")}
|
{t("onboarding.defaultConfirm.cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
@ -58,7 +58,7 @@ export function OnboardingPage() {
|
|||||||
{t("onboarding.start.explainer")}
|
{t("onboarding.start.explainer")}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
|
|
||||||
<div className="w-full grid grid-cols-[1fr,auto,1fr] gap-3">
|
<div className="w-full flex flex-col-reverse md:flex-row gap-3">
|
||||||
<Card onClick={() => navigate("/onboarding/proxy")}>
|
<Card onClick={() => navigate("/onboarding/proxy")}>
|
||||||
<CardContent
|
<CardContent
|
||||||
colorClass="!text-onboarding-good"
|
colorClass="!text-onboarding-good"
|
||||||
@ -69,7 +69,7 @@ export function OnboardingPage() {
|
|||||||
<Link>{t("onboarding.start.options.proxy.action")}</Link>
|
<Link>{t("onboarding.start.options.proxy.action")}</Link>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<div className="grid grid-rows-[1fr,auto,1fr] justify-center gap-4">
|
<div className="hidden md:grid grid-rows-[1fr,auto,1fr] justify-center gap-4">
|
||||||
<VerticalLine className="items-end" />
|
<VerticalLine className="items-end" />
|
||||||
<span className="text-xs uppercase font-bold">or</span>
|
<span className="text-xs uppercase font-bold">or</span>
|
||||||
<VerticalLine />
|
<VerticalLine />
|
||||||
@ -86,7 +86,7 @@ export function OnboardingPage() {
|
|||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-center mt-12">
|
<p className="text-center hidden md:block mt-12">
|
||||||
<Trans i18nKey="onboarding.start.options.default.text">
|
<Trans i18nKey="onboarding.start.options.default.text">
|
||||||
<br />
|
<br />
|
||||||
<a
|
<a
|
||||||
@ -96,6 +96,21 @@ export function OnboardingPage() {
|
|||||||
/>
|
/>
|
||||||
</Trans>
|
</Trans>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<div className=" max-w-[300px] mx-auto md:hidden mt-12 ">
|
||||||
|
<Button
|
||||||
|
className="!text-type-text !bg-opacity-50"
|
||||||
|
theme="secondary"
|
||||||
|
onClick={skipModal.show}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<Trans i18nKey="onboarding.start.options.default.text">
|
||||||
|
<span />
|
||||||
|
<span />
|
||||||
|
</Trans>
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</CenterContainer>
|
</CenterContainer>
|
||||||
</MinimalPageLayout>
|
</MinimalPageLayout>
|
||||||
);
|
);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { ReactNode } from "react";
|
import { ReactNode, useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
import { useAsyncFn, useInterval } from "react-use";
|
import { useAsyncFn, useInterval } from "react-use";
|
||||||
|
|
||||||
@ -18,6 +18,10 @@ import {
|
|||||||
import { Card, Link } from "@/pages/onboarding/utils";
|
import { Card, Link } from "@/pages/onboarding/utils";
|
||||||
import { PageTitle } from "@/pages/parts/util/PageTitle";
|
import { PageTitle } from "@/pages/parts/util/PageTitle";
|
||||||
import { conf } from "@/setup/config";
|
import { conf } from "@/setup/config";
|
||||||
|
import {
|
||||||
|
ExtensionDetectionResult,
|
||||||
|
detectExtensionInstall,
|
||||||
|
} from "@/utils/detectFeatures";
|
||||||
|
|
||||||
type ExtensionStatus =
|
type ExtensionStatus =
|
||||||
| "unknown"
|
| "unknown"
|
||||||
@ -37,11 +41,33 @@ async function getExtensionState(): Promise<ExtensionStatus> {
|
|||||||
return "success"; // no problems
|
return "success"; // no problems
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function RefreshBar() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const reload = useCallback(() => {
|
||||||
|
window.location.reload();
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<Card className="mt-4">
|
||||||
|
<div className="flex items-center space-x-7">
|
||||||
|
<p className="flex-1">{t("onboarding.extension.notDetecting")}</p>
|
||||||
|
<Button theme="secondary" onClick={reload}>
|
||||||
|
{t("onboarding.extension.notDetectingAction")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function ExtensionStatus(props: {
|
export function ExtensionStatus(props: {
|
||||||
status: ExtensionStatus;
|
status: ExtensionStatus;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
|
showHelp?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const [lastKnownStatus, setLastKnownStatus] = useState(props.status);
|
||||||
|
useEffect(() => {
|
||||||
|
if (!props.loading) setLastKnownStatus(props.status);
|
||||||
|
}, [props.status, props.loading]);
|
||||||
|
|
||||||
let content: ReactNode = null;
|
let content: ReactNode = null;
|
||||||
if (props.loading || props.status === "unknown")
|
if (props.loading || props.status === "unknown")
|
||||||
@ -88,6 +114,8 @@ export function ExtensionStatus(props: {
|
|||||||
{content}
|
{content}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
{lastKnownStatus === "unknown" ? <RefreshBar /> : null}
|
||||||
|
{props.showHelp ? (
|
||||||
<Card className="mt-4">
|
<Card className="mt-4">
|
||||||
<div className="flex items-center space-x-7">
|
<div className="flex items-center space-x-7">
|
||||||
<Icon icon={Icons.WARNING} className="text-type-danger text-2xl" />
|
<Icon icon={Icons.WARNING} className="text-type-danger text-2xl" />
|
||||||
@ -101,6 +129,105 @@ export function ExtensionStatus(props: {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExtensionPageProps {
|
||||||
|
status: ExtensionStatus;
|
||||||
|
loading: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ChromeExtensionPage(props: ExtensionPageProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const installLink = conf().ONBOARDING_CHROME_EXTENSION_INSTALL_LINK;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Heading2 className="!mt-0 !text-3xl max-w-[435px]">
|
||||||
|
{t("onboarding.extension.title")}
|
||||||
|
</Heading2>
|
||||||
|
<Paragraph className="max-w-[320px] mb-4">
|
||||||
|
{t("onboarding.extension.explainer")}
|
||||||
|
</Paragraph>
|
||||||
|
{installLink ? (
|
||||||
|
<Link href={installLink} target="_blank" className="mb-12">
|
||||||
|
{t("onboarding.extension.linkChrome")}
|
||||||
|
</Link>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<ExtensionStatus status={props.status} loading={props.loading} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function FirefoxExtensionPage(props: ExtensionPageProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const installLink = conf().ONBOARDING_FIREFOX_EXTENSION_INSTALL_LINK;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Heading2 className="!mt-0 !text-3xl max-w-[435px]">
|
||||||
|
{t("onboarding.extension.title")}
|
||||||
|
</Heading2>
|
||||||
|
<Paragraph className="max-w-[320px] mb-4">
|
||||||
|
{t("onboarding.extension.explainer")}
|
||||||
|
</Paragraph>
|
||||||
|
{installLink ? (
|
||||||
|
<Link href={installLink} target="_blank" className="mb-12">
|
||||||
|
{t("onboarding.extension.linkFirefox")}
|
||||||
|
</Link>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<ExtensionStatus status={props.status} loading={props.loading} showHelp />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function IosExtensionPage(_props: ExtensionPageProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Heading2 className="!mt-0 !text-3xl max-w-[435px]">
|
||||||
|
{t("onboarding.extension.title")}
|
||||||
|
</Heading2>
|
||||||
|
<Paragraph className="max-w-[320px] mb-4">
|
||||||
|
<Trans
|
||||||
|
i18nKey="onboarding.extension.explainerIos"
|
||||||
|
components={{ bold: <span className="text-white font-bold" /> }}
|
||||||
|
/>
|
||||||
|
</Paragraph>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function UnknownExtensionPage(props: ExtensionPageProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const installChromeLink = conf().ONBOARDING_CHROME_EXTENSION_INSTALL_LINK;
|
||||||
|
const installFirefoxLink = conf().ONBOARDING_FIREFOX_EXTENSION_INSTALL_LINK;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Heading2 className="!mt-0 !text-3xl max-w-[435px]">
|
||||||
|
{t("onboarding.extension.title")}
|
||||||
|
</Heading2>
|
||||||
|
<Paragraph className="max-w-[320px] mb-4">
|
||||||
|
{t("onboarding.extension.explainer")}
|
||||||
|
</Paragraph>
|
||||||
|
<div className="mb-4">
|
||||||
|
{installChromeLink ? (
|
||||||
|
<Link href={installChromeLink} target="_blank">
|
||||||
|
{t("onboarding.extension.linkChrome")}
|
||||||
|
</Link>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
<div className="mb-12">
|
||||||
|
{installFirefoxLink ? (
|
||||||
|
<Link href={installFirefoxLink} target="_blank">
|
||||||
|
{t("onboarding.extension.linkFirefox")}
|
||||||
|
</Link>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ExtensionStatus status={props.status} loading={props.loading} showHelp />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -109,7 +236,7 @@ export function OnboardingExtensionPage() {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigateOnboarding();
|
const navigate = useNavigateOnboarding();
|
||||||
const { completeAndRedirect } = useRedirectBack();
|
const { completeAndRedirect } = useRedirectBack();
|
||||||
const installLink = conf().ONBOARDING_EXTENSION_INSTALL_LINK;
|
const extensionSupport = useMemo(() => detectExtensionInstall(), []);
|
||||||
|
|
||||||
const [{ loading, value }, exec] = useAsyncFn(
|
const [{ loading, value }, exec] = useAsyncFn(
|
||||||
async (triggeredManually: boolean = false) => {
|
async (triggeredManually: boolean = false) => {
|
||||||
@ -121,24 +248,23 @@ export function OnboardingExtensionPage() {
|
|||||||
);
|
);
|
||||||
useInterval(exec, 1000);
|
useInterval(exec, 1000);
|
||||||
|
|
||||||
|
const componentMap: Record<
|
||||||
|
ExtensionDetectionResult,
|
||||||
|
typeof UnknownExtensionPage
|
||||||
|
> = {
|
||||||
|
chrome: ChromeExtensionPage,
|
||||||
|
firefox: FirefoxExtensionPage,
|
||||||
|
ios: IosExtensionPage,
|
||||||
|
unknown: UnknownExtensionPage,
|
||||||
|
};
|
||||||
|
const PageContent = componentMap[extensionSupport];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MinimalPageLayout>
|
<MinimalPageLayout>
|
||||||
<PageTitle subpage k="global.pages.onboarding" />
|
<PageTitle subpage k="global.pages.onboarding" />
|
||||||
<CenterContainer>
|
<CenterContainer>
|
||||||
<Stepper steps={2} current={2} className="mb-12" />
|
<Stepper steps={2} current={2} className="mb-12" />
|
||||||
<Heading2 className="!mt-0 !text-3xl max-w-[435px]">
|
<PageContent loading={loading} status={value ?? "unknown"} />
|
||||||
{t("onboarding.extension.title")}
|
|
||||||
</Heading2>
|
|
||||||
<Paragraph className="max-w-[320px] mb-4">
|
|
||||||
{t("onboarding.extension.explainer")}
|
|
||||||
</Paragraph>
|
|
||||||
{installLink ? (
|
|
||||||
<Link href={installLink} target="_blank" className="mb-12">
|
|
||||||
{t("onboarding.extension.link")}
|
|
||||||
</Link>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<ExtensionStatus status={value ?? "unknown"} loading={loading} />
|
|
||||||
<div className="flex justify-between items-center mt-8">
|
<div className="flex justify-between items-center mt-8">
|
||||||
<Button onClick={() => navigate("/onboarding")} theme="secondary">
|
<Button onClick={() => navigate("/onboarding")} theme="secondary">
|
||||||
{t("onboarding.extension.back")}
|
{t("onboarding.extension.back")}
|
||||||
|
@ -29,7 +29,7 @@ type SetupData = {
|
|||||||
|
|
||||||
function testProxy(url: string) {
|
function testProxy(url: string) {
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
setTimeout(() => reject(new Error("Timed out!")), 1000);
|
setTimeout(() => reject(new Error("Timed out!")), 3000);
|
||||||
singularProxiedFetch(url, testUrl, {})
|
singularProxiedFetch(url, testUrl, {})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.url !== testUrl) return reject(new Error("Not a proxy"));
|
if (res.url !== testUrl) return reject(new Error("Not a proxy"));
|
||||||
|
@ -20,7 +20,8 @@ interface Config {
|
|||||||
TURNSTILE_KEY: string;
|
TURNSTILE_KEY: string;
|
||||||
CDN_REPLACEMENTS: string;
|
CDN_REPLACEMENTS: string;
|
||||||
HAS_ONBOARDING: string;
|
HAS_ONBOARDING: string;
|
||||||
ONBOARDING_EXTENSION_INSTALL_LINK: string;
|
ONBOARDING_CHROME_EXTENSION_INSTALL_LINK: string;
|
||||||
|
ONBOARDING_FIREFOX_EXTENSION_INSTALL_LINK: string;
|
||||||
ONBOARDING_PROXY_INSTALL_LINK: string;
|
ONBOARDING_PROXY_INSTALL_LINK: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,7 +39,8 @@ export interface RuntimeConfig {
|
|||||||
TURNSTILE_KEY: string | null;
|
TURNSTILE_KEY: string | null;
|
||||||
CDN_REPLACEMENTS: Array<string[]>;
|
CDN_REPLACEMENTS: Array<string[]>;
|
||||||
HAS_ONBOARDING: boolean;
|
HAS_ONBOARDING: boolean;
|
||||||
ONBOARDING_EXTENSION_INSTALL_LINK: string | null;
|
ONBOARDING_CHROME_EXTENSION_INSTALL_LINK: string | null;
|
||||||
|
ONBOARDING_FIREFOX_EXTENSION_INSTALL_LINK: string | null;
|
||||||
ONBOARDING_PROXY_INSTALL_LINK: string | null;
|
ONBOARDING_PROXY_INSTALL_LINK: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,8 +50,10 @@ const env: Record<keyof Config, undefined | string> = {
|
|||||||
GITHUB_LINK: undefined,
|
GITHUB_LINK: undefined,
|
||||||
DONATION_LINK: undefined,
|
DONATION_LINK: undefined,
|
||||||
DISCORD_LINK: undefined,
|
DISCORD_LINK: undefined,
|
||||||
ONBOARDING_EXTENSION_INSTALL_LINK: import.meta.env
|
ONBOARDING_CHROME_EXTENSION_INSTALL_LINK: import.meta.env
|
||||||
.VITE_ONBOARDING_EXTENSION_INSTALL_LINK,
|
.VITE_ONBOARDING_CHROME_EXTENSION_INSTALL_LINK,
|
||||||
|
ONBOARDING_FIREFOX_EXTENSION_INSTALL_LINK: import.meta.env
|
||||||
|
.VITE_ONBOARDING_FIREFOX_EXTENSION_INSTALL_LINK,
|
||||||
ONBOARDING_PROXY_INSTALL_LINK: import.meta.env
|
ONBOARDING_PROXY_INSTALL_LINK: import.meta.env
|
||||||
.VITE_ONBOARDING_PROXY_INSTALL_LINK,
|
.VITE_ONBOARDING_PROXY_INSTALL_LINK,
|
||||||
DMCA_EMAIL: import.meta.env.VITE_DMCA_EMAIL,
|
DMCA_EMAIL: import.meta.env.VITE_DMCA_EMAIL,
|
||||||
@ -80,7 +84,8 @@ function getKey(key: keyof Config, defaultString?: string): string {
|
|||||||
|
|
||||||
export function conf(): RuntimeConfig {
|
export function conf(): RuntimeConfig {
|
||||||
const dmcaEmail = getKey("DMCA_EMAIL");
|
const dmcaEmail = getKey("DMCA_EMAIL");
|
||||||
const extensionLink = getKey("ONBOARDING_EXTENSION_INSTALL_LINK");
|
const chromeExtension = getKey("ONBOARDING_CHROME_EXTENSION_INSTALL_LINK");
|
||||||
|
const firefoxExtension = getKey("ONBOARDING_FIREFOX_EXTENSION_INSTALL_LINK");
|
||||||
const proxyInstallLink = getKey("ONBOARDING_PROXY_INSTALL_LINK");
|
const proxyInstallLink = getKey("ONBOARDING_PROXY_INSTALL_LINK");
|
||||||
const turnstileKey = getKey("TURNSTILE_KEY");
|
const turnstileKey = getKey("TURNSTILE_KEY");
|
||||||
return {
|
return {
|
||||||
@ -89,8 +94,10 @@ export function conf(): RuntimeConfig {
|
|||||||
DONATION_LINK,
|
DONATION_LINK,
|
||||||
DISCORD_LINK,
|
DISCORD_LINK,
|
||||||
DMCA_EMAIL: dmcaEmail.length > 0 ? dmcaEmail : null,
|
DMCA_EMAIL: dmcaEmail.length > 0 ? dmcaEmail : null,
|
||||||
ONBOARDING_EXTENSION_INSTALL_LINK:
|
ONBOARDING_CHROME_EXTENSION_INSTALL_LINK:
|
||||||
extensionLink.length > 0 ? extensionLink : null,
|
chromeExtension.length > 0 ? chromeExtension : null,
|
||||||
|
ONBOARDING_FIREFOX_EXTENSION_INSTALL_LINK:
|
||||||
|
firefoxExtension.length > 0 ? firefoxExtension : null,
|
||||||
ONBOARDING_PROXY_INSTALL_LINK:
|
ONBOARDING_PROXY_INSTALL_LINK:
|
||||||
proxyInstallLink.length > 0 ? proxyInstallLink : null,
|
proxyInstallLink.length > 0 ? proxyInstallLink : null,
|
||||||
BACKEND_URL: getKey("BACKEND_URL", BACKEND_URL),
|
BACKEND_URL: getKey("BACKEND_URL", BACKEND_URL),
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { detect } from "detect-browser";
|
||||||
import fscreen from "fscreen";
|
import fscreen from "fscreen";
|
||||||
import Hls from "hls.js";
|
import Hls from "hls.js";
|
||||||
|
|
||||||
@ -52,3 +53,27 @@ export function canPlayHlsNatively(video: HTMLVideoElement): boolean {
|
|||||||
if (Hls.isSupported()) return false; // no need to play natively
|
if (Hls.isSupported()) return false; // no need to play natively
|
||||||
return !!video.canPlayType("application/vnd.apple.mpegurl");
|
return !!video.canPlayType("application/vnd.apple.mpegurl");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ExtensionDetectionResult =
|
||||||
|
| "unknown" // unknown detection or weird browser
|
||||||
|
| "firefox" // firefox extensions
|
||||||
|
| "chrome" // chrome extension (could be chromium, but still works with chrome extensions)
|
||||||
|
| "ios"; // ios, no extensions
|
||||||
|
|
||||||
|
export function detectExtensionInstall(): ExtensionDetectionResult {
|
||||||
|
const res = detect();
|
||||||
|
|
||||||
|
// not a browser or failed to detect
|
||||||
|
if (res?.type !== "browser") return "unknown";
|
||||||
|
|
||||||
|
if (res.name === "ios" || res.name === "ios-webview") return "ios";
|
||||||
|
if (
|
||||||
|
res.name === "chrome" ||
|
||||||
|
res.name === "chromium-webview" ||
|
||||||
|
res.name === "edge-chromium" ||
|
||||||
|
res.name === "opera"
|
||||||
|
)
|
||||||
|
return "chrome";
|
||||||
|
if (res.name === "firefox") return "firefox";
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user