Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions components/providers/ArweaveFallbackSwRegistration.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"use client";

import { useEffect } from "react";

const SW_PATH = "/arweave-fallback-sw.js";

export default function ArweaveFallbackSwRegistration() {
useEffect(() => {
if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) {
return;
}
navigator.serviceWorker
.register(SW_PATH, { scope: "/" })
.catch((e) => {
if (process.env.NODE_ENV !== "production") {
console.error(`[ArweaveFallbackSW] Failed to register ${SW_PATH}:`, e);
}
});
}, []);
return null;
}
2 changes: 2 additions & 0 deletions components/providers/Providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { WaveEligibilityProvider } from "@/contexts/wave/WaveEligibilityContext"
import { AppWebSocketProvider } from "@/services/websocket/AppWebSocketProvider";
import { LayoutProvider } from "../brain/my-stream/layout/LayoutContext";
import { ViewProvider } from "../navigation/ViewContext";
import ArweaveFallbackSwRegistration from "./ArweaveFallbackSwRegistration";
import CapacitorSetup from "./CapacitorSetup";
import IpfsImageSetup from "./IpfsImageSetup";
import QueryClientSetup from "./QueryClientSetup";
Expand All @@ -34,6 +35,7 @@ export default function Providers({
<AppWalletsProvider>
<WagmiSetup>
<CapacitorSetup />
<ArweaveFallbackSwRegistration />
<IpfsImageSetup />
<ReactQueryWrapper>
<RefreshProvider>
Expand Down
1 change: 1 addition & 0 deletions config/nextConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export function sharedConfig(
{ protocol: "https", hostname: "6529.io" },
{ protocol: "https", hostname: "staging.6529.io" },
{ protocol: "https", hostname: "arweave.net" },
{ protocol: "https", hostname: "ar-io.net" },
{ protocol: "http", hostname: "localhost" },
{ protocol: "https", hostname: "media.generator.seize.io" },
{ protocol: "https", hostname: "d3lqz0a4bldqgf.cloudfront.net" },
Expand Down
2 changes: 1 addition & 1 deletion config/securityHeaders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export function createSecurityHeaders(apiEndpoint: string | undefined = "") {
},
{
key: "Content-Security-Policy",
value: `default-src 'none'; script-src 'self' 'unsafe-inline' https://dnclu2fna0b2b.cloudfront.net https://www.google-analytics.com https://www.googletagmanager.com/ https://dataplane.rum.us-east-1.amazonaws.com 'unsafe-eval'; connect-src * 'self' blob: ${apiEndpoint} https://registry.walletconnect.com/api/v2/wallets wss://*.bridge.walletconnect.org wss://*.walletconnect.com wss://www.walletlink.org/rpc https://explorer-api.walletconnect.com/v3/wallets https://www.googletagmanager.com https://*.google-analytics.com https://cloudflare-eth.com/ https://arweave.net/* https://rpc.walletconnect.com/v1/ https://sts.us-east-1.amazonaws.com https://sts.us-west-2.amazonaws.com; font-src 'self' data: https://fonts.gstatic.com https://fonts.reown.com https://dnclu2fna0b2b.cloudfront.net https://cdnjs.cloudflare.com; img-src 'self' data: blob: ipfs: https://artblocks.io https://*.artblocks.io *; media-src 'self' blob: https://*.cloudfront.net https://videos.files.wordpress.com https://arweave.net https://*.arweave.net https://cf-ipfs.com/ipfs/* https://*.twimg.com https://artblocks.io https://*.artblocks.io; frame-src 'self' https://ipfs.io https://ipfs.io/ipfs/ https://cf-ipfs.com https://cf-ipfs.com/ipfs/ https://media.generator.seize.io https://media.generator.6529.io https://generator.seize.io https://arweave.net https://*.arweave.net https://nftstorage.link https://*.ipfs.nftstorage.link https://verify.walletconnect.com https://verify.walletconnect.org https://secure.walletconnect.com https://d3lqz0a4bldqgf.cloudfront.net https://www.youtube.com https://www.youtube-nocookie.com https://*.youtube.com https://artblocks.io https://*.artblocks.io https://docs.google.com https://drive.google.com https://*.google.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com/css2 https://dnclu2fna0b2b.cloudfront.net https://cdnjs.cloudflare.com http://cdnjs.cloudflare.com https://cdn.jsdelivr.net; object-src data:;`,
value: `default-src 'none'; script-src 'self' 'unsafe-inline' https://dnclu2fna0b2b.cloudfront.net https://www.google-analytics.com https://www.googletagmanager.com/ https://dataplane.rum.us-east-1.amazonaws.com 'unsafe-eval'; connect-src * 'self' blob: ${apiEndpoint} https://registry.walletconnect.com/api/v2/wallets wss://*.bridge.walletconnect.org wss://*.walletconnect.com wss://www.walletlink.org/rpc https://explorer-api.walletconnect.com/v3/wallets https://www.googletagmanager.com https://*.google-analytics.com https://cloudflare-eth.com/ https://arweave.net/* https://ar-io.net https://ar-io.net/* https://rpc.walletconnect.com/v1/ https://sts.us-east-1.amazonaws.com https://sts.us-west-2.amazonaws.com; font-src 'self' data: https://fonts.gstatic.com https://fonts.reown.com https://dnclu2fna0b2b.cloudfront.net https://cdnjs.cloudflare.com; img-src 'self' data: blob: ipfs: https://artblocks.io https://*.artblocks.io *; media-src 'self' blob: https://*.cloudfront.net https://videos.files.wordpress.com https://arweave.net https://*.arweave.net https://ar-io.net https://*.ar-io.net https://cf-ipfs.com/ipfs/* https://*.twimg.com https://artblocks.io https://*.artblocks.io; frame-src 'self' https://ipfs.io https://ipfs.io/ipfs/ https://cf-ipfs.com https://cf-ipfs.com/ipfs/ https://media.generator.seize.io https://media.generator.6529.io https://generator.seize.io https://arweave.net https://*.arweave.net https://ar-io.net https://*.ar-io.net https://nftstorage.link https://*.ipfs.nftstorage.link https://verify.walletconnect.com https://verify.walletconnect.org https://secure.walletconnect.com https://d3lqz0a4bldqgf.cloudfront.net https://www.youtube.com https://www.youtube-nocookie.com https://*.youtube.com https://artblocks.io https://*.artblocks.io https://docs.google.com https://drive.google.com https://*.google.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com/css2 https://dnclu2fna0b2b.cloudfront.net https://cdnjs.cloudflare.com http://cdnjs.cloudflare.com https://cdn.jsdelivr.net; object-src data:;`,
},
{ key: "X-Frame-Options", value: "SAMEORIGIN" },
{ key: "X-Content-Type-Options", value: "nosniff" },
Expand Down
23 changes: 23 additions & 0 deletions lib/arweave-fallback.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export const ARWEAVE_HOST = "arweave.net";
export const FALLBACK_HOST = "ar-io.net";

export function isArweaveUrl(url: string): boolean {
try {
const u = new URL(url);
const h = u.hostname.toLowerCase();
return h === ARWEAVE_HOST || h.endsWith("." + ARWEAVE_HOST);
} catch {
return false;
}
}

export function getArweaveFallbackUrl(url: string): string | null {
if (!isArweaveUrl(url)) return null;
try {
const u = new URL(url);
u.host = FALLBACK_HOST + (u.port ? ":" + u.port : "");
return u.toString();
} catch {
return null;
}
}
37 changes: 37 additions & 0 deletions public/arweave-fallback-sw.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
self.addEventListener("fetch", (event) => {
const url = new URL(event.request.url);

if (url.hostname !== "arweave.net") return;
if (event.request.mode === "navigate") return;

// Safety: only handle normal asset GETs
if (event.request.method !== "GET") return;
if (event.request.headers.has("range")) return;

event.respondWith(handleRequest(event.request));
});

async function handleRequest(request) {
try {
const response = await fetch(request);

if (response && (response.type === "opaque" || response.ok)) {
return response;
}

return fetchFallback(request);
} catch (err) {
return fetchFallback(request);
}
}

function fetchFallback(request) {
const originalUrl = new URL(request.url);
const fallbackUrl =
"https://ar-io.net" +
originalUrl.pathname +
originalUrl.search +
originalUrl.hash;

return fetch(fallbackUrl);
}
Comment on lines +28 to +37
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Fetch API default request properties mode credentials when calling fetch(url) vs original request semantics

💡 Result:

When you call fetch(url) with a string URL, the Fetch spec creates a new Request using default “request semantics” for any RequestInit fields you didn’t provide:

  • method: GET (and no request body)
  • mode: "cors" (so cross-origin requests use CORS; same-origin also works) [1][2]
  • credentials: "same-origin" (send cookies/auth headers only for same-origin requests; not for cross-origin) [1][3]
  • (Common other defaults: redirect: "follow", cache: "default".) [1]

In contrast, when you call fetch(request) with an existing Request object, it uses that object’s already-set properties (including mode, credentials, etc.), unless you explicitly override them in the second argument (which “clones” with overrides per the Fetch algorithms). The key difference is: string URL ⇒ defaults apply; Request object ⇒ its stored semantics apply. [1][3]

Sources:
[1] WHATWG Fetch Standard (request creation defaults for mode/credentials) (fetch.spec.whatwg.org)
[2] MDN: Using the Fetch API (default mode is cors) (developer.mozilla.org)
[3] MDN: Request.credentials (default is same-origin) (developer.mozilla.org)

Citations:


🏁 Script executed:

cd public && cat -n arweave-fallback-sw.js | head -50

Repository: 6529-Collections/6529seize-frontend

Length of output: 1232


Preserve request mode/semantics when calling the fallback host.

On Line 36, fetch(fallbackUrl) creates a new request with default fetch options (specifically mode: "cors" and credentials: "same-origin"). This can break cross-origin asset requests that were originally made with no-cors mode, since the fallback request will now attempt standard CORS negotiation instead of requesting an opaque response. If ar-io.net lacks the appropriate CORS headers, the fallback will fail.

🔧 Proposed fix
 function fetchFallback(request) {
   const originalUrl = new URL(request.url);
   const fallbackUrl =
     "https://ar-io.net" +
     originalUrl.pathname +
     originalUrl.search +
     originalUrl.hash;

-  return fetch(fallbackUrl);
+  return fetch(fallbackUrl, {
+    method: request.method,
+    mode: request.mode,
+    cache: request.cache,
+    redirect: request.redirect,
+    referrer: request.referrer,
+    referrerPolicy: request.referrerPolicy,
+    integrity: request.integrity,
+    credentials: "omit",
+  });
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@public/arweave-fallback-sw.js` around lines 28 - 37, The fallback currently
calls fetch(fallbackUrl) which loses the original Request's
mode/credentials/headers; change fetchFallback to create a new Request for the
fallback URL that preserves the original request semantics (e.g. use new
Request(fallbackUrl, request) or explicitly copy request.method,
request.headers, request.mode, request.credentials, cache, redirect,
referrerPolicy, integrity, keepalive and body/clone when needed) and pass that
Request to fetch so mode/no-cors and credentials are preserved; update the
function fetchFallback to build and fetch this new Request.