mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-07-26 03:52:19 +02:00
add support for local image proxy and cors server
This commit is contained in:
5
.changeset/grumpy-paws-judge.md
Normal file
5
.changeset/grumpy-paws-judge.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"nostrudel": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Add support for local image proxy and cors servers
|
@@ -4,17 +4,24 @@ volumes:
|
|||||||
data: {}
|
data: {}
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
# cors:
|
||||||
|
# image: ghcr.io/hzrd149/docker-cors-anywhere:latest
|
||||||
|
imageproxy:
|
||||||
|
image: ghcr.io/willnorris/imageproxy:v0.11.2
|
||||||
relay:
|
relay:
|
||||||
image: scsibug/nostr-rs-relay:0.8.13
|
image: scsibug/nostr-rs-relay:0.8.13
|
||||||
ports:
|
|
||||||
- 5000:8080
|
|
||||||
volumes:
|
volumes:
|
||||||
- data:/usr/src/app/db
|
- data:/usr/src/app/db
|
||||||
app:
|
app:
|
||||||
build: .
|
build: .
|
||||||
|
image: ghcr.io/hzrd149/nostrudel:latest
|
||||||
depends_on:
|
depends_on:
|
||||||
- relay
|
- relay
|
||||||
|
# - cors
|
||||||
|
- imageproxy
|
||||||
environment:
|
environment:
|
||||||
CACHE_RELAY: relay:8080
|
CACHE_RELAY: relay:8080
|
||||||
|
IMAGE_PROXY: imageproxy:8080
|
||||||
|
# CORS_PROXY: cors:8080
|
||||||
ports:
|
ports:
|
||||||
- 8080:80
|
- 8080:80
|
||||||
|
@@ -1,11 +1,12 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
CACHE_RELAY_PROXY=""
|
PROXY_PASS_BLOCK=""
|
||||||
|
|
||||||
if [ -n "$CACHE_RELAY" ]; then
|
if [ -n "$CACHE_RELAY" ]; then
|
||||||
echo "Cache relay set to $CACHE_RELAY"
|
echo "Cache relay set to $CACHE_RELAY"
|
||||||
sed -i 's/CACHE_RELAY_ENABLED = false/CACHE_RELAY_ENABLED = true/g' /usr/share/nginx/html/index.html
|
sed -i 's/CACHE_RELAY_ENABLED = false/CACHE_RELAY_ENABLED = true/g' /usr/share/nginx/html/index.html
|
||||||
CACHE_RELAY_PROXY="
|
PROXY_PASS_BLOCK="$PROXY_PASS_BLOCK
|
||||||
location /local-relay {
|
location /local-relay {
|
||||||
proxy_pass http://$CACHE_RELAY/;
|
proxy_pass http://$CACHE_RELAY/;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
@@ -17,6 +18,32 @@ else
|
|||||||
echo "No cache relay set"
|
echo "No cache relay set"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ -n "$CORS_PROXY" ]; then
|
||||||
|
echo "CORS proxy set to $CORS_PROXY"
|
||||||
|
sed -i 's/CORS_PROXY_PATH = ""/CORS_PROXY_PATH = "\/corsproxy"/g' /usr/share/nginx/html/index.html
|
||||||
|
PROXY_PASS_BLOCK="$PROXY_PASS_BLOCK
|
||||||
|
location /corsproxy/ {
|
||||||
|
proxy_pass http://$CORS_PROXY;
|
||||||
|
rewrite ^/corsproxy/(.*) /\$1 break;
|
||||||
|
}
|
||||||
|
"
|
||||||
|
else
|
||||||
|
echo "No CORS proxy set"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$IMAGE_PROXY" ]; then
|
||||||
|
echo "Image proxy set to $IMAGE_PROXY"
|
||||||
|
sed -i 's/IMAGE_PROXY_PATH = ""/IMAGE_PROXY_PATH = "\/imageproxy"/g' /usr/share/nginx/html/index.html
|
||||||
|
PROXY_PASS_BLOCK="$PROXY_PASS_BLOCK
|
||||||
|
location /imageproxy/ {
|
||||||
|
proxy_pass http://$IMAGE_PROXY;
|
||||||
|
rewrite ^/imageproxy/(.*) /\$1 break;
|
||||||
|
}
|
||||||
|
"
|
||||||
|
else
|
||||||
|
echo "No Image proxy set"
|
||||||
|
fi
|
||||||
|
|
||||||
CONF_FILE="/etc/nginx/conf.d/default.conf"
|
CONF_FILE="/etc/nginx/conf.d/default.conf"
|
||||||
NGINX_CONF="
|
NGINX_CONF="
|
||||||
server {
|
server {
|
||||||
@@ -24,13 +51,13 @@ server {
|
|||||||
|
|
||||||
server_name localhost;
|
server_name localhost;
|
||||||
|
|
||||||
|
$PROXY_PASS_BLOCK
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
index index.html index.htm;
|
index index.html index.htm;
|
||||||
}
|
}
|
||||||
|
|
||||||
$CACHE_RELAY_PROXY
|
|
||||||
|
|
||||||
# Gzip settings
|
# Gzip settings
|
||||||
gzip on;
|
gzip on;
|
||||||
gzip_disable "msie6";
|
gzip_disable "msie6";
|
||||||
|
@@ -13,6 +13,8 @@ EXPOSE 80
|
|||||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||||
|
|
||||||
ENV CACHE_RELAY=""
|
ENV CACHE_RELAY=""
|
||||||
|
ENV IMAGE_PROXY=""
|
||||||
|
ENV CORS_PROXY=""
|
||||||
ADD ./docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
|
ADD ./docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
|
||||||
RUN chmod a+x /usr/local/bin/docker-entrypoint.sh
|
RUN chmod a+x /usr/local/bin/docker-entrypoint.sh
|
||||||
|
|
||||||
|
@@ -22,6 +22,8 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
window.CACHE_RELAY_ENABLED = false;
|
window.CACHE_RELAY_ENABLED = false;
|
||||||
|
window.IMAGE_PROXY_PATH = "";
|
||||||
|
window.CORS_PROXY_PATH = "";
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import { MouseEventHandler, MutableRefObject, forwardRef, useCallback, useMemo, useRef } from "react";
|
import { MouseEventHandler, MutableRefObject, forwardRef, useCallback, useMemo, useRef } from "react";
|
||||||
import { Image, ImageProps, Link, LinkProps } from "@chakra-ui/react";
|
import { Image, ImageProps, Link, LinkProps } from "@chakra-ui/react";
|
||||||
|
|
||||||
import appSettings from "../../services/settings/app-settings";
|
|
||||||
import { useTrusted } from "../../providers/local/trust";
|
import { useTrusted } from "../../providers/local/trust";
|
||||||
import { EmbedableContent, defaultGetLocation } from "../../helpers/embeds";
|
import { EmbedableContent, defaultGetLocation } from "../../helpers/embeds";
|
||||||
import { getMatchLink } from "../../helpers/regexp";
|
import { getMatchLink } from "../../helpers/regexp";
|
||||||
@@ -12,6 +11,7 @@ import { NostrEvent } from "../../types/nostr-event";
|
|||||||
import useAppSettings from "../../hooks/use-app-settings";
|
import useAppSettings from "../../hooks/use-app-settings";
|
||||||
import { useBreakpointValue } from "../../providers/global/breakpoint-provider";
|
import { useBreakpointValue } from "../../providers/global/breakpoint-provider";
|
||||||
import useElementBlur from "../../hooks/use-element-blur";
|
import useElementBlur from "../../hooks/use-element-blur";
|
||||||
|
import { buildImageProxyURL } from "../../helpers/image";
|
||||||
|
|
||||||
export type TrustImageProps = ImageProps;
|
export type TrustImageProps = ImageProps;
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ export type EmbeddedImageProps = Omit<LinkProps, "children" | "href" | "onClick"
|
|||||||
};
|
};
|
||||||
|
|
||||||
function useImageThumbnail(src?: string) {
|
function useImageThumbnail(src?: string) {
|
||||||
return appSettings.value.imageProxy ? new URL(`/256,fit/${src}`, appSettings.value.imageProxy).toString() : src;
|
return (src && buildImageProxyURL(src, "256,fit")) ?? src;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EmbeddedImage = forwardRef<HTMLImageElement, EmbeddedImageProps>(
|
export const EmbeddedImage = forwardRef<HTMLImageElement, EmbeddedImageProps>(
|
||||||
|
@@ -242,5 +242,5 @@ export const ThingsIcon = Package;
|
|||||||
export const TorrentIcon = Magnet;
|
export const TorrentIcon = Magnet;
|
||||||
export const TrackIcon = Recording02;
|
export const TrackIcon = Recording02;
|
||||||
|
|
||||||
export const InboxIcon = Download01
|
export const InboxIcon = Download01;
|
||||||
export const OutboxIcon = Upload01
|
export const OutboxIcon = Upload01;
|
||||||
|
@@ -1,13 +1,14 @@
|
|||||||
import { forwardRef, memo, useMemo } from "react";
|
import { forwardRef, memo, useMemo } from "react";
|
||||||
import { Avatar, AvatarProps } from "@chakra-ui/react";
|
import { Avatar, AvatarProps } from "@chakra-ui/react";
|
||||||
import { useUserMetadata } from "../hooks/use-user-metadata";
|
|
||||||
import { useAsync } from "react-use";
|
import { useAsync } from "react-use";
|
||||||
|
|
||||||
|
import { useUserMetadata } from "../hooks/use-user-metadata";
|
||||||
import { getIdenticon } from "../helpers/identicon";
|
import { getIdenticon } from "../helpers/identicon";
|
||||||
import { safeUrl } from "../helpers/parse";
|
import { safeUrl } from "../helpers/parse";
|
||||||
import { getUserDisplayName } from "../helpers/user-metadata";
|
import { getUserDisplayName } from "../helpers/user-metadata";
|
||||||
import useAppSettings from "../hooks/use-app-settings";
|
import useAppSettings from "../hooks/use-app-settings";
|
||||||
import useCurrentAccount from "../hooks/use-current-account";
|
import useCurrentAccount from "../hooks/use-current-account";
|
||||||
|
import { buildImageProxyURL } from "../helpers/image";
|
||||||
|
|
||||||
export const UserIdenticon = memo(({ pubkey }: { pubkey: string }) => {
|
export const UserIdenticon = memo(({ pubkey }: { pubkey: string }) => {
|
||||||
const { value: identicon } = useAsync(() => getIdenticon(pubkey), [pubkey]);
|
const { value: identicon } = useAsync(() => getIdenticon(pubkey), [pubkey]);
|
||||||
@@ -15,6 +16,8 @@ export const UserIdenticon = memo(({ pubkey }: { pubkey: string }) => {
|
|||||||
return identicon ? <img src={`data:image/svg+xml;base64,${identicon}`} width="100%" /> : null;
|
return identicon ? <img src={`data:image/svg+xml;base64,${identicon}`} width="100%" /> : null;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const RESIZE_PROFILE_SIZE = 96;
|
||||||
|
|
||||||
export type UserAvatarProps = Omit<AvatarProps, "src"> & {
|
export type UserAvatarProps = Omit<AvatarProps, "src"> & {
|
||||||
pubkey: string;
|
pubkey: string;
|
||||||
relay?: string;
|
relay?: string;
|
||||||
@@ -28,17 +31,16 @@ export const UserAvatar = forwardRef<HTMLDivElement, UserAvatarProps>(({ pubkey,
|
|||||||
if (hideUsernames && pubkey !== account?.pubkey) return undefined;
|
if (hideUsernames && pubkey !== account?.pubkey) return undefined;
|
||||||
if (metadata?.picture) {
|
if (metadata?.picture) {
|
||||||
const src = safeUrl(metadata?.picture);
|
const src = safeUrl(metadata?.picture);
|
||||||
if (!noProxy) {
|
if (src) {
|
||||||
if (imageProxy && src) {
|
const proxyURL = buildImageProxyURL(src, RESIZE_PROFILE_SIZE);
|
||||||
return new URL(`/96/${src}`, imageProxy).toString();
|
if (proxyURL) return proxyURL;
|
||||||
} else if (proxyUserMedia) {
|
} else if (!noProxy && proxyUserMedia) {
|
||||||
const last4 = String(pubkey).slice(pubkey.length - 4, pubkey.length);
|
const last4 = String(pubkey).slice(pubkey.length - 4, pubkey.length);
|
||||||
return `https://media.nostr.band/thumbs/${last4}/${pubkey}-picture-64`;
|
return `https://media.nostr.band/thumbs/${last4}/${pubkey}-picture-64`;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return src;
|
return src;
|
||||||
}
|
}
|
||||||
}, [metadata?.picture, imageProxy, hideUsernames, account]);
|
}, [metadata?.picture, imageProxy, proxyUserMedia, hideUsernames, account]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Avatar
|
<Avatar
|
||||||
|
@@ -3,7 +3,9 @@ import { convertToUrl } from "./url";
|
|||||||
|
|
||||||
const corsFailedHosts = new Set();
|
const corsFailedHosts = new Set();
|
||||||
|
|
||||||
export function createCorsUrl(url: URL | string, corsProxy = appSettings.value.corsProxy) {
|
export function createCorsUrl(url: URL | string, corsProxy?: string) {
|
||||||
|
if (!corsProxy && window.CORS_PROXY_PATH) corsProxy = new URL(window.CORS_PROXY_PATH, location.origin).toString();
|
||||||
|
if (!corsProxy && appSettings.value.corsProxy) corsProxy = appSettings.value.corsProxy;
|
||||||
if (!corsProxy) return url;
|
if (!corsProxy) return url;
|
||||||
|
|
||||||
if (corsProxy.includes("<url>")) {
|
if (corsProxy.includes("<url>")) {
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
import appSettings from "../services/settings/app-settings";
|
||||||
|
|
||||||
export type ImageSize = { width: number; height: number };
|
export type ImageSize = { width: number; height: number };
|
||||||
const imageSizeCache = new Map<string, ImageSize>();
|
const imageSizeCache = new Map<string, ImageSize>();
|
||||||
|
|
||||||
@@ -17,3 +19,15 @@ export function getImageSize(src: string): Promise<{ width: number; height: numb
|
|||||||
image.onerror = () => rej(new Error("Failed to get image size"));
|
image.onerror = () => rej(new Error("Failed to get image size"));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildImageProxyURL(src: string, size: string | number) {
|
||||||
|
let url: URL | null = null;
|
||||||
|
if (window.IMAGE_PROXY_PATH) {
|
||||||
|
url = new URL(location.origin);
|
||||||
|
url.pathname = window.IMAGE_PROXY_PATH;
|
||||||
|
} else if (appSettings.value.imageProxy) url = new URL(appSettings.value.imageProxy);
|
||||||
|
if (url === null) return;
|
||||||
|
|
||||||
|
url.pathname = url.pathname.replace(/\/$/, "") + "/" + size + "/" + src;
|
||||||
|
return url.toString();
|
||||||
|
}
|
||||||
|
2
src/types/window.d.ts
vendored
2
src/types/window.d.ts
vendored
@@ -1,3 +1,5 @@
|
|||||||
interface Window {
|
interface Window {
|
||||||
CACHE_RELAY_ENABLED: boolean;
|
CACHE_RELAY_ENABLED: boolean;
|
||||||
|
IMAGE_PROXY_PATH: string;
|
||||||
|
CORS_PROXY_PATH: string;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user