Companion support & option to enable insecure requests on desktop

This commit is contained in:
WardPearce
2025-06-04 17:17:10 +12:00
parent 6cc4fd9655
commit f8cdac4487
10 changed files with 99 additions and 42 deletions
+1
View File
@@ -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 && \
+21 -8
View File
@@ -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;
});
+4 -1
View File
@@ -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);
}
});
+1
View File
@@ -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' {} +
+1
View File
@@ -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",
+10 -1
View File
@@ -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