diff --git a/.env.example b/.env.example index bbe81e3f..0499e038 100644 --- a/.env.example +++ b/.env.example @@ -14,6 +14,7 @@ NEXT_PUBLIC_CSB_SCAN="https://scan.crossbell.io" NEXT_PUBLIC_IPFS_GATEWAY="https://ipfs.4everland.xyz/ipfs/" NEXT_PUBLIC_MIRA_LINK="https://mira.crossbell.io" NEXT_PUBLIC_WALLET_CONNECT_V2_PROJECT_ID= +NEXT_PUBLIC_IMAGE_PROXY_DOMAIN= # NFT diff --git a/.github/workflows/docker-build-push-dev.yml b/.github/workflows/docker-build-push-dev.yml index d85b8e2f..67df0c81 100644 --- a/.github/workflows/docker-build-push-dev.yml +++ b/.github/workflows/docker-build-push-dev.yml @@ -50,6 +50,7 @@ jobs: "NEXT_PUBLIC_OUR_DOMAIN=${{ vars.NEXT_PUBLIC_OUR_DOMAIN }}" "NEXT_PUBLIC_TWITTER_LINK=${{ vars.NEXT_PUBLIC_TWITTER_LINK }}" "NEXT_PUBLIC_WALLET_CONNECT_V2_PROJECT_ID=${{ vars.NEXT_PUBLIC_WALLET_CONNECT_V2_PROJECT_ID }}" + "NEXT_PUBLIC_IMAGE_PROXY_DOMAIN=${{ vars.NEXT_PUBLIC_IMAGE_PROXY_DOMAIN }}" deploy-dev: runs-on: ubuntu-latest diff --git a/.github/workflows/docker-build-push-prod.yml b/.github/workflows/docker-build-push-prod.yml index f40bf922..66450825 100644 --- a/.github/workflows/docker-build-push-prod.yml +++ b/.github/workflows/docker-build-push-prod.yml @@ -50,6 +50,7 @@ jobs: "NEXT_PUBLIC_OUR_DOMAIN=${{ vars.NEXT_PUBLIC_OUR_DOMAIN }}" "NEXT_PUBLIC_TWITTER_LINK=${{ vars.NEXT_PUBLIC_TWITTER_LINK }}" "NEXT_PUBLIC_WALLET_CONNECT_V2_PROJECT_ID=${{ vars.NEXT_PUBLIC_WALLET_CONNECT_V2_PROJECT_ID }}" + "NEXT_PUBLIC_IMAGE_PROXY_DOMAIN=${{ vars.NEXT_PUBLIC_IMAGE_PROXY_DOMAIN }}" deploy-prod: runs-on: ubuntu-latest diff --git a/Dockerfile b/Dockerfile index 8ec67f20..e02d9272 100644 --- a/Dockerfile +++ b/Dockerfile @@ -34,6 +34,8 @@ ARG NEXT_PUBLIC_TWITTER_LINK ENV NEXT_PUBLIC_TWITTER_LINK=${NEXT_PUBLIC_TWITTER_LINK} ARG NEXT_PUBLIC_WALLET_CONNECT_V2_PROJECT_ID ENV NEXT_PUBLIC_WALLET_CONNECT_V2_PROJECT_ID=${NEXT_PUBLIC_WALLET_CONNECT_V2_PROJECT_ID} +ARG NEXT_PUBLIC_IMAGE_PROXY_DOMAIN +ENV NEXT_PUBLIC_IMAGE_PROXY_DOMAIN=${NEXT_PUBLIC_IMAGE_PROXY_DOMAIN} RUN pnpm build diff --git a/image-loader.js b/image-loader.js new file mode 100644 index 00000000..b2448d6f --- /dev/null +++ b/image-loader.js @@ -0,0 +1,30 @@ +import { IMAGE_PROXY_DOMAIN } from "~/lib/env" + +// Docs: https://developers.cloudflare.com/images/url-format +export default function cloudflareLoader({ src, width, quality }) { + const params = [ + `width=${width}`, + `quality=${quality || 75}`, + "format=auto", + "onerror=redirect", + ] + + let hostname = "" + try { + hostname = new URL(src).hostname + } catch (error) {} + + // Bypass the restriction that the Cloudflare Image Resizing service's onerror=redirect can only be used in xlog.app domain + if ( + !hostname === IMAGE_PROXY_DOMAIN && + !hostname.endsWith(`.${IMAGE_PROXY_DOMAIN}`) + ) { + src = `https://${IMAGE_PROXY_DOMAIN}/api/bypass?url=${encodeURIComponent( + src, + )}` + } + + return `https://${IMAGE_PROXY_DOMAIN}/cdn-cgi/image/${params.join( + ",", + )}/${src}` +} diff --git a/next.config.js b/next.config.js index faf9b8f0..4220af4f 100644 --- a/next.config.js +++ b/next.config.js @@ -54,6 +54,8 @@ module.exports = withBundleAnalyzer({ { protocol: "https", hostname: "pbs.twimg.com" }, { protocol: "https", hostname: "abs.twimg.com" }, ], + loader: "custom", + loaderFile: "./image-loader.js", }, async generateBuildId() { diff --git a/src/app/feed/hottest/route.ts b/src/app/feed/hottest/route.ts index 6c868558..e40264ee 100644 --- a/src/app/feed/hottest/route.ts +++ b/src/app/feed/hottest/route.ts @@ -1,4 +1,4 @@ -import { SITE_URL } from "~/lib/env" +import { IPFS_GATEWAY_FALLBACK, SITE_URL } from "~/lib/env" import { parsePost } from "~/lib/json-feed" import { NextServerResponse } from "~/lib/server-helper" import { ExpandedNote } from "~/lib/types" @@ -17,7 +17,7 @@ export async function GET(request: Request) { const data = { version: "https://jsonfeed.org/version/1", title: "xLog Latest", - icon: "https://ipfs.4everland.xyz/ipfs/bafkreigxdnr5lvtjxqin5upquomrti2s77hlgtjy5zaeu43uhpny75rbga", + icon: `${IPFS_GATEWAY_FALLBACK}bafkreigxdnr5lvtjxqin5upquomrti2s77hlgtjy5zaeu43uhpny75rbga`, home_page_url: `${SITE_URL}/`, feed_url: `${SITE_URL}/feed/latest`, items: feed?.list?.map((post: ExpandedNote) => diff --git a/src/app/feed/latest/route.ts b/src/app/feed/latest/route.ts index 67d0dac9..6e7b293a 100644 --- a/src/app/feed/latest/route.ts +++ b/src/app/feed/latest/route.ts @@ -1,4 +1,4 @@ -import { SITE_URL } from "~/lib/env" +import { IPFS_GATEWAY_FALLBACK, SITE_URL } from "~/lib/env" import { parsePost } from "~/lib/json-feed" import { NextServerResponse } from "~/lib/server-helper" import { ExpandedNote } from "~/lib/types" @@ -14,7 +14,7 @@ export async function GET(request: Request) { const data = { version: "https://jsonfeed.org/version/1", title: "xLog Latest", - icon: "https://ipfs.4everland.xyz/ipfs/bafkreigxdnr5lvtjxqin5upquomrti2s77hlgtjy5zaeu43uhpny75rbga", + icon: `${IPFS_GATEWAY_FALLBACK}bafkreigxdnr5lvtjxqin5upquomrti2s77hlgtjy5zaeu43uhpny75rbga`, home_page_url: `${SITE_URL}/`, feed_url: `${SITE_URL}/feed/latest`, items: feed?.list?.map((post: ExpandedNote) => parsePost(post)), diff --git a/src/components/ui/UniMedia.tsx b/src/components/ui/UniMedia.tsx index 1db4630e..58db689e 100644 --- a/src/components/ui/UniMedia.tsx +++ b/src/components/ui/UniMedia.tsx @@ -3,7 +3,7 @@ import React, { useState } from "react" import { Image } from "~/components/ui/Image" -import { IPFS_GATEWAY } from "~/lib/env" +import { IPFS_GATEWAY, IPFS_GATEWAY_FALLBACK } from "~/lib/env" export const UniMedia = ({ src, @@ -21,7 +21,7 @@ export const UniMedia = ({ if (src && !errorHandled) { try { const response = await fetch( - src.replace(IPFS_GATEWAY, "https://gateway.ipfs.io/ipfs/"), + src.replace(IPFS_GATEWAY, IPFS_GATEWAY_FALLBACK), { method: "HEAD", }, diff --git a/src/lib/env.ts b/src/lib/env.ts index dc9706f8..da8fbde2 100644 --- a/src/lib/env.ts +++ b/src/lib/env.ts @@ -6,6 +6,8 @@ export const APP_SLOGAN = export const OUR_DOMAIN = process.env.NEXT_PUBLIC_OUR_DOMAIN || process.env.NEXT_PUBLIC_VERCEL_URL export const SCORE_API_DOMAIN = process.env.NEXT_PUBLIC_SCORE_API_DOMAIN +export const IMAGE_PROXY_DOMAIN = + process.env.NEXT_PUBLIC_IMAGE_PROXY_DOMAIN || "xlog.app" export const DOCS_DOMAIN = `docs.${OUR_DOMAIN}` export const SITE_URL = `${IS_PROD ? "https" : "http"}://${OUR_DOMAIN}` export const APP_DESCRIPTION = @@ -23,6 +25,9 @@ export const CSB_XCHAR = process.env.NEXT_PUBLIC_CSB_XCHAR || "https://xchar.app" export const IPFS_GATEWAY = process.env.NEXT_PUBLIC_IPFS_GATEWAY || "https://ipfs.4everland.xyz/ipfs/" +export const IPFS_GATEWAY_FALLBACK = + process.env.NEXT_PUBLIC_IPFS_GATEWAY_FALLBACK || + "https://gateway.ipfs.io/ipfs/" export const MIRA_LINK = process.env.NEXT_PUBLIC_MIRA_LINK || "https://mira.crossbell.io" export const WALLET_CONNECT_V2_PROJECT_ID = diff --git a/src/lib/ipfs-parser.ts b/src/lib/ipfs-parser.ts index a991d495..1a7e755f 100644 --- a/src/lib/ipfs-parser.ts +++ b/src/lib/ipfs-parser.ts @@ -27,6 +27,7 @@ export const toIPFS = (url: string) => { .replaceAll("https://ipfs.4everland.xyz/ipfs/", IPFS_PREFIX) .replaceAll("https://rss3.mypinata.cloud/ipfs/", IPFS_PREFIX) .replaceAll("https://cloudflare-ipfs.com/ipfs/", IPFS_PREFIX) + .replaceAll("https://ipfs.xlog.app/ipfs/", IPFS_PREFIX) } export const toCid = (url: string) => { @@ -38,5 +39,6 @@ export const toCid = (url: string) => { .replaceAll("https://ipfs.4everland.xyz/ipfs/", "") .replaceAll("https://rss3.mypinata.cloud/ipfs/", "") .replaceAll("https://cloudflare-ipfs.com/ipfs/", "") + .replaceAll("https://ipfs.xlog.app/ipfs/", "") .replaceAll(IPFS_PREFIX, "") }