Companion support & option to enable insecure requests on desktop
This commit is contained in:
@@ -11,6 +11,7 @@ COPY . .
|
||||
|
||||
# Create placeholder env vars
|
||||
RUN echo "VITE_DEFAULT_INVIDIOUS_INSTANCE=VITE_DEFAULT_INVIDIOUS_INSTANCE_PLACEHOLDER" > .env && \
|
||||
echo "VITE_DEFAULT_COMPANION_INSTANCE=VITE_DEFAULT_COMPANION_INSTANCE_PLACEHOLDER" >> .env && \
|
||||
echo "VITE_DEFAULT_SPONSERBLOCK_INSTANCE=VITE_DEFAULT_SPONSERBLOCK_INSTANCE_PLACEHOLDER" >> .env && \
|
||||
echo "VITE_DEFAULT_DEARROW_INSTANCE=VITE_DEFAULT_DEARROW_INSTANCE_PLACEHOLDER" >> .env && \
|
||||
echo "VITE_DEFAULT_DEARROW_THUMBNAIL_INSTANCE=VITE_DEFAULT_DEARROW_THUMBNAIL_INSTANCE_PLACEHOLDER" >> .env && \
|
||||
|
||||
@@ -4,19 +4,13 @@ import {
|
||||
setupElectronDeepLinking
|
||||
} from '@capacitor-community/electron';
|
||||
import type { MenuItemConstructorOptions } from 'electron';
|
||||
import { app, ipcMain, MenuItem } from 'electron';
|
||||
import { app, ipcMain, MenuItem, session, webContents } from 'electron';
|
||||
import electronIsDev from 'electron-is-dev';
|
||||
import unhandled from 'electron-unhandled';
|
||||
import { autoUpdater } from 'electron-updater';
|
||||
import { JSDOM } from 'jsdom';
|
||||
|
||||
import BG, {
|
||||
buildURL,
|
||||
DescrambledChallenge,
|
||||
GOOG_API_KEY,
|
||||
USER_AGENT,
|
||||
WebPoSignalOutput
|
||||
} from 'bgutils-js';
|
||||
import BG, { buildURL, GOOG_API_KEY, USER_AGENT, WebPoSignalOutput } from 'bgutils-js';
|
||||
import { ElectronCapacitorApp, setupContentSecurityPolicy, setupReloadWatcher } from './setup';
|
||||
|
||||
// Graceful handling of unhandled errors.
|
||||
@@ -54,6 +48,8 @@ if (electronIsDev) {
|
||||
setupReloadWatcher(myCapacitorApp);
|
||||
}
|
||||
|
||||
let allowInsecureSSL = false;
|
||||
|
||||
// Run Application
|
||||
(async () => {
|
||||
// Wait for electron app to be ready.
|
||||
@@ -66,6 +62,15 @@ if (electronIsDev) {
|
||||
autoUpdater.checkForUpdatesAndNotify();
|
||||
})();
|
||||
|
||||
app.on('certificate-error', (event, webContents, url, error, certificate, callback) => {
|
||||
if (allowInsecureSSL) {
|
||||
event.preventDefault(); // prevent default behavior (blocking the cert)
|
||||
callback(true); // trust the certificate
|
||||
} else {
|
||||
callback(false); // reject others
|
||||
}
|
||||
});
|
||||
|
||||
// Handle when all of our windows are close (platforms have their own expectations).
|
||||
app.on('window-all-closed', function () {
|
||||
// On OS X it is common for applications and their menu bar
|
||||
@@ -154,3 +159,11 @@ ipcMain.handle(
|
||||
return await integrityTokenBasedMinter.mintAsWebsafeString(decodeURIComponent(visitorData));
|
||||
}
|
||||
);
|
||||
|
||||
ipcMain.handle('setAllowInsecureSSL', async (_, allow) => {
|
||||
allowInsecureSSL = allow;
|
||||
await session.defaultSession.clearAuthCache();
|
||||
await session.defaultSession.clearCache();
|
||||
|
||||
return allowInsecureSSL;
|
||||
});
|
||||
|
||||
@@ -5,5 +5,8 @@ require('./rt/electron-rt');
|
||||
|
||||
contextBridge.exposeInMainWorld('electronAPI', {
|
||||
generatePoToken: (bgChallenge: DescrambledChallenge, requestKey: string, visitorData: string) =>
|
||||
ipcRenderer.invoke('generatePoToken', bgChallenge, requestKey, visitorData)
|
||||
ipcRenderer.invoke('generatePoToken', bgChallenge, requestKey, visitorData),
|
||||
setAllowInsecureSSL: async (allow: boolean) => {
|
||||
return await ipcRenderer.invoke('setAllowInsecureSSL', allow);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ ROOT_DIR=/usr/share/nginx/html
|
||||
|
||||
# Replace env vars in JavaScript and HTML files served by NGINX
|
||||
find "$ROOT_DIR" -type f \( -name "*.js" -o -name "*.html" \) -exec sed -i 's|VITE_DEFAULT_INVIDIOUS_INSTANCE_PLACEHOLDER|'"$VITE_DEFAULT_INVIDIOUS_INSTANCE"'|g' {} +
|
||||
find "$ROOT_DIR" -type f \( -name "*.js" -o -name "*.html" \) -exec sed -i 's|VITE_DEFAULT_COMPANION_INSTANCE_PLACEHOLDER|'"$VITE_DEFAULT_COMPANION_INSTANCE"'|g' {} +
|
||||
find "$ROOT_DIR" -type f \( -name "*.js" -o -name "*.html" \) -exec sed -i 's|VITE_DEFAULT_RETURNYTDISLIKES_INSTANCE_PLACEHOLDER|'"$VITE_DEFAULT_RETURNYTDISLIKES_INSTANCE"'|g' {} +
|
||||
find "$ROOT_DIR" -type f \( -name "*.js" -o -name "*.html" \) -exec sed -i 's|VITE_DEFAULT_SPONSERBLOCK_INSTANCE_PLACEHOLDER|'"$VITE_DEFAULT_SPONSERBLOCK_INSTANCE"'|g' {} +
|
||||
find "$ROOT_DIR" -type f \( -name "*.js" -o -name "*.html" \) -exec sed -i 's|VITE_DEFAULT_DEARROW_THUMBNAIL_INSTANCE_PLACEHOLDER|'"$VITE_DEFAULT_DEARROW_THUMBNAIL_INSTANCE"'|g' {} +
|
||||
|
||||
Vendored
+1
@@ -18,6 +18,7 @@ declare global {
|
||||
requestKey: string,
|
||||
visitorData: string
|
||||
) => Promise<string>;
|
||||
setAllowInsecureSSL: (allowInsecureSSL: boolean) => Promoise<boolean>;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import { interfaceAllowInsecureRequests } from '$lib/store';
|
||||
import { Capacitor } from '@capacitor/core';
|
||||
|
||||
const originalFetch = window.fetch;
|
||||
@@ -53,14 +52,5 @@ if (Capacitor.getPlatform() === 'android') {
|
||||
return originalXhrOpen.apply(this, args);
|
||||
};
|
||||
|
||||
// Used to toggle rejectUnauthorized in the backend
|
||||
interfaceAllowInsecureRequests.subscribe(async (isAllowed) => {
|
||||
if (isAllowed) {
|
||||
await fetch('http://materialious__allow-insecure-requests');
|
||||
} else {
|
||||
await fetch('http://materialious__deny-insecure-requests');
|
||||
}
|
||||
});
|
||||
|
||||
setTimeout(() => goto('/', { replaceState: true }), 100);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
import type { VideoPlay } from '../api/model';
|
||||
import {
|
||||
authStore,
|
||||
companionStore,
|
||||
instanceStore,
|
||||
playerAndroidLockOrientation,
|
||||
playerAutoPlayStore,
|
||||
@@ -76,7 +77,6 @@
|
||||
const sessionId = Array.from(Array(16), () => Math.floor(Math.random() * 36).toString(36)).join(
|
||||
''
|
||||
);
|
||||
let isLive = false;
|
||||
let videoPlaybackUstreamerConfig: string | undefined;
|
||||
let serverAbrStreamingUrl: URL | undefined = undefined;
|
||||
let drmParams: string | undefined;
|
||||
@@ -251,7 +251,7 @@
|
||||
'language',
|
||||
'statistics'
|
||||
],
|
||||
playbackRates: [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.25, 2.5, 2.75, 3],
|
||||
playbackRates: [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.25, 2.5, 2.75, 3],
|
||||
enableTooltips: false,
|
||||
seekBarColors: {
|
||||
played: (await getDynamicTheme())['--primary']
|
||||
@@ -259,8 +259,6 @@
|
||||
});
|
||||
|
||||
if (data.video.fallbackPatch === 'youtubejs' && data.video.ytjs) {
|
||||
isLive = !!data.video.ytjs.video.basic_info.is_live;
|
||||
|
||||
if (
|
||||
data.video.ytjs.rawApiResponse.data.streamingData &&
|
||||
(data.video.ytjs.rawApiResponse.data.streamingData as any).drmParams
|
||||
@@ -343,7 +341,7 @@
|
||||
type === shaka.net.NetworkingEngine.RequestType.SEGMENT &&
|
||||
url.pathname.includes('videoplayback')
|
||||
) {
|
||||
if (!isLive) {
|
||||
if (!data.video.liveNow) {
|
||||
const currentFormat = formatList.find(
|
||||
(format) =>
|
||||
fromFormat(format) === (new URL(request.uris[0]).searchParams.get('___key') || '')
|
||||
@@ -632,7 +630,12 @@
|
||||
await player.attach(playerElement);
|
||||
|
||||
if (!data.video.hlsUrl) {
|
||||
let dashUrl = data.video.dashUrl;
|
||||
let dashUrl: string = '';
|
||||
if ($companionStore) {
|
||||
dashUrl = `${$companionStore}/api/manifest/dash/id/${data.video.videoId}`;
|
||||
} else {
|
||||
dashUrl = data.video.dashUrl;
|
||||
}
|
||||
|
||||
if (!data.video.fallbackPatch && (!Capacitor.isNativePlatform() || proxyVideos)) {
|
||||
dashUrl += '?local=true';
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
import { getPages } from '../../navPages';
|
||||
import {
|
||||
authStore,
|
||||
companionStore,
|
||||
darkModeStore,
|
||||
instanceStore,
|
||||
interfaceAllowInsecureRequests,
|
||||
@@ -30,6 +31,7 @@
|
||||
} from '../../store';
|
||||
|
||||
let invidiousInstance = $state(get(instanceStore));
|
||||
let companionInstance = $state(get(companionStore));
|
||||
let region = $state(get(interfaceRegionStore));
|
||||
let forceCase = $state(get(interfaceForceCase));
|
||||
let defaultPage = $state(get(interfaceDefaultPage));
|
||||
@@ -91,10 +93,28 @@
|
||||
ui('#dialog-settings');
|
||||
}
|
||||
|
||||
function setCompanion(event: Event) {
|
||||
event.preventDefault();
|
||||
companionStore.set(ensureNoTrailingSlash(companionInstance));
|
||||
}
|
||||
|
||||
function allowInsecureRequests() {
|
||||
if (Capacitor.getPlatform() !== 'android') return;
|
||||
if (!Capacitor.isNativePlatform()) return;
|
||||
interfaceAllowInsecureRequests.set(!$interfaceAllowInsecureRequests);
|
||||
}
|
||||
|
||||
// Used to toggle rejectUnauthorized in the backend
|
||||
interfaceAllowInsecureRequests.subscribe(async (isAllowed) => {
|
||||
if (Capacitor.getPlatform() === 'android') {
|
||||
if (isAllowed) {
|
||||
await fetch('http://materialious__allow-insecure-requests');
|
||||
} else {
|
||||
await fetch('http://materialious__deny-insecure-requests');
|
||||
}
|
||||
} else if (Capacitor.getPlatform() === 'electron') {
|
||||
await window.electronAPI.setAllowInsecureSSL(isAllowed);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if Capacitor.isNativePlatform()}
|
||||
@@ -115,24 +135,39 @@
|
||||
{#if invalidInstance}
|
||||
<div style="margin-bottom: 6em;"></div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if Capacitor.getPlatform() === 'android' && (invalidInstance || $interfaceAllowInsecureRequests)}
|
||||
<div class="field no-margin">
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<div>{$_('layout.allowInsecureRequests')}</div>
|
||||
{#if Capacitor.isNativePlatform() && (invalidInstance || $interfaceAllowInsecureRequests)}
|
||||
<div class="field no-margin">
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<div>{$_('layout.allowInsecureRequests')}</div>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$interfaceAllowInsecureRequests}
|
||||
onclick={allowInsecureRequests}
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<form onsubmit={setCompanion}>
|
||||
<nav>
|
||||
<div class="field label border max">
|
||||
<input bind:value={companionInstance} name="companion-instance" type="text" />
|
||||
<label for="companion-instance">{$_('layout.companionUrl')}</label>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$interfaceAllowInsecureRequests}
|
||||
onclick={allowInsecureRequests}
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
<button class="square round">
|
||||
<i>done</i>
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
</form>
|
||||
{#if invalidInstance}
|
||||
<div style="margin-bottom: 6em;"></div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<button onclick={toggleDarkMode} class="no-margin">
|
||||
|
||||
@@ -110,6 +110,7 @@
|
||||
"lowBandwidthMode": "Low bandwidth mode",
|
||||
"displayThumbnailAvatars": "Thumbnail avatars (Slow)",
|
||||
"searchHistory": "Search history",
|
||||
"companionUrl": "Companion URL",
|
||||
"theme": {
|
||||
"theme": "Theme",
|
||||
"darkMode": "Dark mode",
|
||||
|
||||
@@ -5,6 +5,7 @@ import { persisted } from 'svelte-persisted-store';
|
||||
import { writable, type Writable } from 'svelte/store';
|
||||
import type { TitleCase } from './letterCasing';
|
||||
import type { Channel, HashTag, Playlist, PlaylistPageVideo, Video, VideoBase } from './api/model';
|
||||
import { ensureNoTrailingSlash } from './misc';
|
||||
|
||||
function platformDependentDefault(givenValue: any, defaultValue: any): any {
|
||||
if (typeof givenValue !== 'undefined' && typeof givenValue !== null) {
|
||||
@@ -17,11 +18,19 @@ function platformDependentDefault(givenValue: any, defaultValue: any): any {
|
||||
export const instanceStore: Writable<string> = persisted(
|
||||
'invidiousInstance',
|
||||
platformDependentDefault(
|
||||
import.meta.env.VITE_DEFAULT_INVIDIOUS_INSTANCE,
|
||||
ensureNoTrailingSlash(import.meta.env.VITE_DEFAULT_INVIDIOUS_INSTANCE),
|
||||
'https://invidious.materialio.us'
|
||||
)
|
||||
);
|
||||
|
||||
export const companionStore: Writable<string | undefined> = persisted(
|
||||
'companionInstance',
|
||||
platformDependentDefault(
|
||||
ensureNoTrailingSlash(import.meta.env.VITE_DEFAULT_COMPANION_INSTANCE),
|
||||
undefined
|
||||
)
|
||||
);
|
||||
|
||||
export const authStore: Writable<null | { username: string; token: string }> = persisted(
|
||||
'authToken',
|
||||
null
|
||||
|
||||
Reference in New Issue
Block a user