Use fetch to load images if insecure request enabled.

This commit is contained in:
WardPearce
2025-06-04 15:44:53 +12:00
parent 246869d83e
commit 1f71469276
9 changed files with 70 additions and 96 deletions
+9 -6
View File
@@ -1,17 +1,17 @@
{
"name": "Materialious",
"version": "1.8.10",
"version": "1.9.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "Materialious",
"version": "1.8.10",
"version": "1.9.0",
"license": "MIT",
"dependencies": {
"@capacitor-community/electron": "^5.0.0",
"bgutils-js": "^3.2.0",
"capacitor-nodejs": "https://github.com/EdenwareApps/Capacitor-NodeJS/releases/download/v1.0.0-beta.7/capacitor6-nodejs.tgz",
"capacitor-nodejs": "https://github.com/hampoelz/capacitor-nodejs/releases/download/v1.0.0-beta.8/capacitor-nodejs.tgz",
"chokidar": "~4.0.3",
"electron-is-dev": "~2.0.0",
"electron-serve": "~1.1.0",
@@ -1799,10 +1799,13 @@
}
},
"node_modules/capacitor-nodejs": {
"version": "1.0.0-beta.7",
"resolved": "https://github.com/EdenwareApps/Capacitor-NodeJS/releases/download/v1.0.0-beta.7/capacitor6-nodejs.tgz",
"integrity": "sha512-7NwSMxE06nqpuFKyQlt3mWo+VG6Od0d6PBxPKVyPmaptE6sSoimzU/uzQXPWoHV5bwbjgcF+R0BSJAZAyy4gsg==",
"version": "1.0.0-beta.8",
"resolved": "https://github.com/hampoelz/capacitor-nodejs/releases/download/v1.0.0-beta.8/capacitor-nodejs.tgz",
"integrity": "sha512-VAMCTBqz+sA3762kFhSyzXS0unm0EpsVSHLmDYsTYPTiXXZaFcGk27vfXakRyyXJexAUG2Y5rZka5BvH6EmeZw==",
"license": "MIT",
"engines": {
"node": ">=20.10"
},
"peerDependencies": {
"@capacitor/core": "^6.0.0"
}
+2 -2
View File
@@ -24,7 +24,7 @@
"dependencies": {
"@capacitor-community/electron": "^5.0.0",
"bgutils-js": "^3.2.0",
"capacitor-nodejs": "https://github.com/EdenwareApps/Capacitor-NodeJS/releases/download/v1.0.0-beta.7/capacitor6-nodejs.tgz",
"capacitor-nodejs": "https://github.com/hampoelz/capacitor-nodejs/releases/download/v1.0.0-beta.8/capacitor-nodejs.tgz",
"chokidar": "~4.0.3",
"electron-is-dev": "~2.0.0",
"electron-serve": "~1.1.0",
@@ -44,4 +44,4 @@
"capacitor",
"electron"
]
}
}
@@ -5,7 +5,7 @@
import { _ } from 'svelte-i18n';
import { get } from 'svelte/store';
import type { Channel } from '../api/model';
import { truncate } from '../misc';
import { insecureRequestImageHandler, truncate } from '../misc';
import { interfaceLowBandwidthMode } from '../store';
interface Props {
@@ -19,9 +19,9 @@
onMount(async () => {
if (get(interfaceLowBandwidthMode)) return;
const img = new Image();
img.src = proxyGoogleImage(getBestThumbnail(channel.authorThumbnails));
const img = await insecureRequestImageHandler(
proxyGoogleImage(getBestThumbnail(channel.authorThumbnails))
);
img.onload = () => {
channelPfp = img;
};
+19 -5
View File
@@ -4,7 +4,9 @@
import { getBestThumbnail, proxyGoogleImage } from '$lib/images';
import { numberWithCommas } from '$lib/numbers';
import { interfaceLowBandwidthMode } from '$lib/store';
import { onMount } from 'svelte';
import CommentSelf from './Comment.svelte';
import { insecureRequestImageHandler } from '$lib/misc';
interface Props {
comment: Comment;
@@ -36,16 +38,28 @@
return processedHtml;
}
let userPfp = $state('');
onMount(async () => {
if ($interfaceLowBandwidthMode) return;
const img = await insecureRequestImageHandler(
proxyGoogleImage(getBestThumbnail(comment.authorThumbnails))
);
img.onload = () => {
userPfp = img.src;
};
});
</script>
<div class="comment">
{#if !$interfaceLowBandwidthMode}
<div class="comment-header">
<img
class="circle small"
src={proxyGoogleImage(getBestThumbnail(comment.authorThumbnails))}
alt="comment profile"
/>
{#if userPfp}
<img class="circle small" src={userPfp} alt="comment profile" />
{:else}
<progress class="circle"></progress>
{/if}
<div class="comment-info">
<a href={`/channel/${comment.authorId}`} class="author">
<span class="bold" class:channel-owner={comment.authorIsChannelOwner}>
@@ -9,8 +9,6 @@
}
let { hashtag }: Props = $props();
let channelPfp: HTMLImageElement | undefined;
</script>
<a href={hashtag.url} class="wave" style="min-width: 100%;min-height: 100%;">
@@ -5,7 +5,7 @@
import { _ } from 'svelte-i18n';
import { get } from 'svelte/store';
import type { Playlist } from '../api/model';
import { truncate } from '../misc';
import { insecureRequestImageHandler, truncate } from '../misc';
import { interfaceLowBandwidthMode } from '../store';
interface Props {
@@ -21,20 +21,23 @@
const playlistLink = `/playlist/${playlist.playlistId}`;
onMount(() => {
onMount(async () => {
if (get(interfaceLowBandwidthMode)) return;
img = new Image();
let imgSrc = '';
if (playlist.videos.length > 0) {
img.src = getBestThumbnail(playlist.videos[0].videoThumbnails) || '';
imgSrc = getBestThumbnail(playlist.videos[0].videoThumbnails) || '';
} else if (playlist.playlistThumbnail) {
img.src = playlist.playlistThumbnail;
imgSrc = playlist.playlistThumbnail;
} else {
img.src = '';
imgSrc = '';
loading = false;
return;
}
img = await insecureRequestImageHandler(imgSrc);
img.onload = () => {
loading = false;
};
@@ -20,7 +20,6 @@
interfaceAutoExpandComments,
interfaceAutoExpandDesc,
interfaceDefaultPage,
interfaceDisplayThumbnailAvatars,
interfaceForceCase,
interfaceLowBandwidthMode,
interfaceRegionStore,
@@ -171,22 +170,6 @@
</div>
{/if}
<div class="field no-margin">
<nav class="no-padding">
<div class="max">
<div>{$_('layout.displayThumbnailAvatars')}</div>
</div>
<label class="switch">
<input
type="checkbox"
bind:checked={$interfaceDisplayThumbnailAvatars}
onclick={() => interfaceDisplayThumbnailAvatars.set(!$interfaceDisplayThumbnailAvatars)}
/>
<span></span>
</label>
</nav>
</div>
<div class="field no-margin">
<nav class="no-padding">
<div class="max">
@@ -1,18 +1,17 @@
<script lang="ts">
import { getBestThumbnail, proxyGoogleImage } from '$lib/images';
import { getBestThumbnail } from '$lib/images';
import { letterCase } from '$lib/letterCasing';
import { cleanNumber, videoLength } from '$lib/numbers';
import { onDestroy, onMount } from 'svelte';
import { _ } from 'svelte-i18n';
import { get } from 'svelte/store';
import { getChannel, getDeArrow, getThumbnail, getVideoProgress } from '../api';
import { getDeArrow, getThumbnail, getVideoProgress } from '../api';
import type { Notification, PlaylistPageVideo, Video, VideoBase } from '../api/model';
import { truncate } from '../misc';
import { insecureRequestImageHandler, truncate } from '../misc';
import type { PlayerEvents } from '../player';
import {
authStore,
deArrowEnabledStore,
interfaceDisplayThumbnailAvatars,
interfaceLowBandwidthMode,
playerSavePlaybackPositionStore,
syncPartyConnectionsStore,
@@ -29,8 +28,6 @@
let { video = $bindable(), playlistId = '', sideways = $bindable(false) }: Props = $props();
let authorImg: HTMLImageElement | undefined = $state();
let placeholderHeight: number = $state(0);
let watchUrl = new URL(`${location.origin}/watch/${video.videoId}`);
@@ -64,24 +61,9 @@
if (window.innerWidth <= 1500) {
sideways = false;
if (typeof authorImg === 'undefined') loadAuthor();
} else sideways = true;
}
async function loadAuthor() {
if (!get(interfaceDisplayThumbnailAvatars)) return;
try {
const channel = await getChannel(video.authorId, { priority: 'low' });
const loadedPfp = new Image();
loadedPfp.src = proxyGoogleImage(getBestThumbnail(channel.authorThumbnails, 75, 75));
loadedPfp.onload = () => {
authorImg = loadedPfp;
};
} catch {}
}
onMount(async () => {
calcThumbnailPlaceholderHeight();
@@ -95,16 +77,9 @@
if (get(interfaceLowBandwidthMode)) return;
// Load author details in background.
if (!sideways) {
loadAuthor();
}
let imageSrc = getBestThumbnail(video.videoThumbnails) as string;
if (get(deArrowEnabledStore)) {
let locatedThumbnail = false;
try {
const deArrow = await getDeArrow(video.videoId);
for (const title of deArrow.titles) {
@@ -119,16 +94,13 @@
if (thumbnail.timestamp !== null) {
imageSrc = await getThumbnail(video.videoId, thumbnail.timestamp);
}
locatedThumbnail = true;
break;
}
}
} catch {}
}
const img = new Image();
img.src = imageSrc;
const img = await insecureRequestImageHandler(imageSrc);
img.onload = () => {
thumbnail = img;
};
@@ -225,15 +197,6 @@
</div>
<div class="thumbnail-details video-title">
{#if !sideways && !$interfaceLowBandwidthMode && $interfaceDisplayThumbnailAvatars}
<div style="margin-right: 1em;">
{#if authorImg}
<img src={authorImg.src} alt="Author" class="circle small" />
{:else}
<progress style="padding: 15px;" class="circle small"></progress>
{/if}
</div>
{/if}
<div class="video-title">
<a
style="padding-left: 1px;"
+23 -13
View File
@@ -3,7 +3,7 @@ import { page } from '$app/stores';
import he from 'he';
import type Peer from 'peerjs';
import { get } from 'svelte/store';
import { instanceStore } from './store';
import { instanceStore, interfaceAllowInsecureRequests } from './store';
export function truncate(value: string, maxLength: number = 50): string {
return value.length > maxLength ? `${value.substring(0, maxLength)}...` : value;
@@ -16,10 +16,7 @@ export function decodeHtmlCharCodes(str: string): string {
export function proxyVideoUrl(source: string): string {
const rawSrc = new URL(source);
rawSrc.host = get(instanceStore).replace('http://', '').replace(
'https://',
''
);
rawSrc.host = get(instanceStore).replace('http://', '').replace('https://', '');
return rawSrc.toString();
}
@@ -46,14 +43,11 @@ export async function peerJs(peerId: string): Promise<Peer> {
if (typeof PeerInstance === 'undefined') {
PeerInstance = (await import('peerjs')).Peer;
}
return new PeerInstance(
peerId,
{
host: import.meta.env.VITE_DEFAULT_PEERJS_HOST || '0.peerjs.com',
path: import.meta.env.VITE_DEFAULT_PEERJS_PATH || '/',
port: import.meta.env.VITE_DEFAULT_PEERJS_PORT || 443
}
);
return new PeerInstance(peerId, {
host: import.meta.env.VITE_DEFAULT_PEERJS_HOST || '0.peerjs.com',
path: import.meta.env.VITE_DEFAULT_PEERJS_PATH || '/',
port: import.meta.env.VITE_DEFAULT_PEERJS_PORT || 443
});
}
export function ensureNoTrailingSlash(url: any): string {
@@ -61,3 +55,19 @@ export function ensureNoTrailingSlash(url: any): string {
return url.endsWith('/') ? url.slice(0, -1) : url;
}
export async function insecureRequestImageHandler(source: string): Promise<HTMLImageElement> {
const img = new Image();
if (get(interfaceAllowInsecureRequests)) {
const imgResp = await fetch(source);
if (!imgResp.ok) {
img.src = '';
}
img.src = URL.createObjectURL(await imgResp.blob());
} else {
img.src = source;
}
return img;
}