From ae13e9380ca7666dbbce8e1d24c00ee328a36064 Mon Sep 17 00:00:00 2001 From: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com> Date: Wed, 28 Feb 2024 14:50:40 -0700 Subject: [PATCH 1/4] fix: helia-sw query from _redirects works --- src/lib/ipfs-hosted-redirect-utils.ts | 15 +++++++++++++++ src/sw.ts | 3 ++- 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 src/lib/ipfs-hosted-redirect-utils.ts diff --git a/src/lib/ipfs-hosted-redirect-utils.ts b/src/lib/ipfs-hosted-redirect-utils.ts new file mode 100644 index 00000000..ddcec5ed --- /dev/null +++ b/src/lib/ipfs-hosted-redirect-utils.ts @@ -0,0 +1,15 @@ +/** + * If you host helia-service-worker-gateway on an IPFS domain, the redirects file will route some requests from + * `/` to `https:///?helia-sw=`. + * + * This function will check for "?helia-sw=" in the URL and modify the URL so that it works with the rest of our logic + */ +export function getActualUrl (urlString: string): string { + const url = new URL(urlString) + const heliaSw = url.searchParams.get('helia-sw') + if (heliaSw != null) { + url.searchParams.delete('helia-sw') + url.pathname = heliaSw + } + return url.toString() +} diff --git a/src/sw.ts b/src/sw.ts index 8334aaf8..23dd91f2 100644 --- a/src/sw.ts +++ b/src/sw.ts @@ -3,6 +3,7 @@ import { getHelia } from './get-helia.ts' import { HeliaServiceWorkerCommsChannel, type ChannelMessage } from './lib/channel.ts' import { getSubdomainParts } from './lib/get-subdomain-parts.ts' import { heliaFetch } from './lib/heliaFetch.ts' +import { getActualUrl } from './lib/ipfs-hosted-redirect-utils.ts' import { error, log, trace } from './lib/logger.ts' import { findOriginIsolationRedirect } from './lib/path-or-subdomain.ts' import type { Helia } from '@helia/interface' @@ -51,7 +52,7 @@ interface FetchHandlerArg { const fetchHandler = async ({ path, request }: FetchHandlerArg): Promise => { // test and enforce origin isolation before anything else is executed - const originLocation = await findOriginIsolationRedirect(new URL(request.url)) + const originLocation = await findOriginIsolationRedirect(new URL(getActualUrl(request.url))) if (originLocation !== null) { const body = 'Gateway supports subdomain mode, redirecting to ensure Origin isolation..' return new Response(body, { From 143c7e10ade3175c318af0d1d52c5d06dad06a4c Mon Sep 17 00:00:00 2001 From: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com> Date: Wed, 28 Feb 2024 15:10:45 -0700 Subject: [PATCH 2/4] chore: check for helia-sw queryparam in app.tsx --- src/app.tsx | 3 ++- src/lib/ipfs-hosted-redirect-utils.ts | 4 ++-- src/sw.ts | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/app.tsx b/src/app.tsx index 9182f732..ca9afa48 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -2,6 +2,7 @@ import React, { useContext, useEffect } from 'react' import Config from './components/config.tsx' import { ConfigContext } from './context/config-context.tsx' import HelperUi from './helper-ui.tsx' +import { getActualUrl } from './lib/ipfs-hosted-redirect-utils.ts' import { isConfigPage } from './lib/is-config-page.ts' import { isPathOrSubdomainRequest, findOriginIsolationRedirect } from './lib/path-or-subdomain.ts' import RedirectPage from './redirectPage.tsx' @@ -12,7 +13,7 @@ function App (): JSX.Element { useEffect(() => { async function originEnforcement (): Promise { // enforce early when loaded before SW was registered - const originRedirect = await findOriginIsolationRedirect(window.location) + const originRedirect = await findOriginIsolationRedirect(getActualUrl(window.location.href)) if (originRedirect !== null) { window.location.replace(originRedirect) } diff --git a/src/lib/ipfs-hosted-redirect-utils.ts b/src/lib/ipfs-hosted-redirect-utils.ts index ddcec5ed..ce5bd5f4 100644 --- a/src/lib/ipfs-hosted-redirect-utils.ts +++ b/src/lib/ipfs-hosted-redirect-utils.ts @@ -4,12 +4,12 @@ * * This function will check for "?helia-sw=" in the URL and modify the URL so that it works with the rest of our logic */ -export function getActualUrl (urlString: string): string { +export function getActualUrl (urlString: string): URL { const url = new URL(urlString) const heliaSw = url.searchParams.get('helia-sw') if (heliaSw != null) { url.searchParams.delete('helia-sw') url.pathname = heliaSw } - return url.toString() + return url } diff --git a/src/sw.ts b/src/sw.ts index 23dd91f2..03349966 100644 --- a/src/sw.ts +++ b/src/sw.ts @@ -52,7 +52,7 @@ interface FetchHandlerArg { const fetchHandler = async ({ path, request }: FetchHandlerArg): Promise => { // test and enforce origin isolation before anything else is executed - const originLocation = await findOriginIsolationRedirect(new URL(getActualUrl(request.url))) + const originLocation = await findOriginIsolationRedirect(getActualUrl(request.url)) if (originLocation !== null) { const body = 'Gateway supports subdomain mode, redirecting to ensure Origin isolation..' return new Response(body, { From 6570354eaa0b4330864216f1ec61bd1eb5dfa859 Mon Sep 17 00:00:00 2001 From: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com> Date: Wed, 28 Feb 2024 15:25:20 -0700 Subject: [PATCH 3/4] fix: smarter _redirects ?helia-sw handling --- src/app.tsx | 26 +++++++++++++++++++++----- src/sw.ts | 3 +-- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/app.tsx b/src/app.tsx index ca9afa48..cb2c54ef 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -1,6 +1,7 @@ -import React, { useContext, useEffect } from 'react' +import React, { useContext, useEffect, useState } from 'react' import Config from './components/config.tsx' import { ConfigContext } from './context/config-context.tsx' +import { ServiceWorkerContext } from './context/service-worker-context.tsx' import HelperUi from './helper-ui.tsx' import { getActualUrl } from './lib/ipfs-hosted-redirect-utils.ts' import { isConfigPage } from './lib/is-config-page.ts' @@ -9,18 +10,33 @@ import RedirectPage from './redirectPage.tsx' function App (): JSX.Element { const { isConfigExpanded, setConfigExpanded } = useContext(ConfigContext) + const [originRedirect, setOriginRedirect] = useState(null) + const { isServiceWorkerRegistered } = useContext(ServiceWorkerContext) useEffect(() => { async function originEnforcement (): Promise { // enforce early when loaded before SW was registered - const originRedirect = await findOriginIsolationRedirect(getActualUrl(window.location.href)) - if (originRedirect !== null) { - window.location.replace(originRedirect) - } + const originRedirect = await findOriginIsolationRedirect(window.location) + setOriginRedirect(originRedirect) } void originEnforcement() }, []) + useEffect(() => { + if (originRedirect !== null) { + window.location.replace(originRedirect) + return + } + const actualUrl = getActualUrl(window.location.href) + if (isServiceWorkerRegistered && window.location.href !== actualUrl.href) { + /** + * If hosted on ipfs site and we were redirected to a ?helia-sw=/ip[fn]s/ url, + * this will reload with a request to /ip[fn]s/, because the SW can capture it now. + */ + window.location.replace(actualUrl) + } + }, [isServiceWorkerRegistered, originRedirect]) + if (isConfigPage()) { setConfigExpanded(true) return diff --git a/src/sw.ts b/src/sw.ts index 03349966..8334aaf8 100644 --- a/src/sw.ts +++ b/src/sw.ts @@ -3,7 +3,6 @@ import { getHelia } from './get-helia.ts' import { HeliaServiceWorkerCommsChannel, type ChannelMessage } from './lib/channel.ts' import { getSubdomainParts } from './lib/get-subdomain-parts.ts' import { heliaFetch } from './lib/heliaFetch.ts' -import { getActualUrl } from './lib/ipfs-hosted-redirect-utils.ts' import { error, log, trace } from './lib/logger.ts' import { findOriginIsolationRedirect } from './lib/path-or-subdomain.ts' import type { Helia } from '@helia/interface' @@ -52,7 +51,7 @@ interface FetchHandlerArg { const fetchHandler = async ({ path, request }: FetchHandlerArg): Promise => { // test and enforce origin isolation before anything else is executed - const originLocation = await findOriginIsolationRedirect(getActualUrl(request.url)) + const originLocation = await findOriginIsolationRedirect(new URL(request.url)) if (originLocation !== null) { const body = 'Gateway supports subdomain mode, redirecting to ensure Origin isolation..' return new Response(body, { From f25aba90a06d81efedf125a25c205ef67bd236f5 Mon Sep 17 00:00:00 2001 From: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com> Date: Sat, 2 Mar 2024 16:01:27 -0700 Subject: [PATCH 4/4] fix: handle helia-sw query from _redirects --- src/app.tsx | 32 ++------------------ src/context/service-worker-context.tsx | 42 +++++++++++++++++++++++++- src/lib/ipfs-hosted-redirect-utils.ts | 2 +- 3 files changed, 44 insertions(+), 32 deletions(-) diff --git a/src/app.tsx b/src/app.tsx index cb2c54ef..f35c2259 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -1,41 +1,13 @@ -import React, { useContext, useEffect, useState } from 'react' +import React, { useContext } from 'react' import Config from './components/config.tsx' import { ConfigContext } from './context/config-context.tsx' -import { ServiceWorkerContext } from './context/service-worker-context.tsx' import HelperUi from './helper-ui.tsx' -import { getActualUrl } from './lib/ipfs-hosted-redirect-utils.ts' import { isConfigPage } from './lib/is-config-page.ts' -import { isPathOrSubdomainRequest, findOriginIsolationRedirect } from './lib/path-or-subdomain.ts' +import { isPathOrSubdomainRequest } from './lib/path-or-subdomain.ts' import RedirectPage from './redirectPage.tsx' function App (): JSX.Element { const { isConfigExpanded, setConfigExpanded } = useContext(ConfigContext) - const [originRedirect, setOriginRedirect] = useState(null) - const { isServiceWorkerRegistered } = useContext(ServiceWorkerContext) - - useEffect(() => { - async function originEnforcement (): Promise { - // enforce early when loaded before SW was registered - const originRedirect = await findOriginIsolationRedirect(window.location) - setOriginRedirect(originRedirect) - } - void originEnforcement() - }, []) - - useEffect(() => { - if (originRedirect !== null) { - window.location.replace(originRedirect) - return - } - const actualUrl = getActualUrl(window.location.href) - if (isServiceWorkerRegistered && window.location.href !== actualUrl.href) { - /** - * If hosted on ipfs site and we were redirected to a ?helia-sw=/ip[fn]s/ url, - * this will reload with a request to /ip[fn]s/, because the SW can capture it now. - */ - window.location.replace(actualUrl) - } - }, [isServiceWorkerRegistered, originRedirect]) if (isConfigPage()) { setConfigExpanded(true) diff --git a/src/context/service-worker-context.tsx b/src/context/service-worker-context.tsx index c58db779..dce50eb0 100644 --- a/src/context/service-worker-context.tsx +++ b/src/context/service-worker-context.tsx @@ -1,5 +1,21 @@ +/** + * @file This file contains the ServiceWorkerProvider component which is used to register the service worker, + * and provide the isServiceWorkerRegistered state to the rest of the app. + * + * URL / location logic dependent upon the service worker being registered should be handled here. Some examples of this are: + * + * Before the Service Worker is registered (e.g. first requests to root hosted domain or subdomains): + * + * 1. Being redirected from _redirects file to a ?helia-sw= url + * 2. The app is loaded because service worker is not yet registered, we need to reload the page so the service worker intercepts the request + * + * After the service worker is loaded. Usually any react code isn't loaded, but some edge cases are: + * 1. The page being loaded using some /ip[fn]s/ url, but subdomain isolation is supported, so we need to redirect to the isolated origin + */ import React, { createContext, useEffect, useState } from 'react' +import { translateIpfsRedirectUrl } from '../lib/ipfs-hosted-redirect-utils.ts' import { error } from '../lib/logger.ts' +import { findOriginIsolationRedirect } from '../lib/path-or-subdomain.ts' import { registerServiceWorker } from '../service-worker-utils.ts' export const ServiceWorkerContext = createContext({ @@ -9,8 +25,32 @@ export const ServiceWorkerContext = createContext({ export const ServiceWorkerProvider = ({ children }): JSX.Element => { const [isServiceWorkerRegistered, setIsServiceWorkerRegistered] = useState(false) + const windowLocation = translateIpfsRedirectUrl(window.location.href) + useEffect(() => { if (isServiceWorkerRegistered) { + /** + * The service worker is registered, now we need to check for "helia-sw" and origin isolation support + */ + if (windowLocation.href !== window.location.href) { + /** + * We're at a domain with ?helia-sw=, we can reload the page so the service worker will + * capture the request + */ + window.location.replace(windowLocation.href) + } else { + /** + * ?helia-sw= url handling is done, now we can check for origin isolation redirects + */ + void findOriginIsolationRedirect(windowLocation).then((originRedirect) => { + if (originRedirect !== null) { + window.location.replace(originRedirect) + } + }) + } + /** + * The service worker is registered, we don't need to do any more work + */ return } async function doWork (): Promise { @@ -32,7 +72,7 @@ export const ServiceWorkerProvider = ({ children }): JSX.Element => { } } void doWork() - }, []) + }, [isServiceWorkerRegistered]) return ( diff --git a/src/lib/ipfs-hosted-redirect-utils.ts b/src/lib/ipfs-hosted-redirect-utils.ts index ce5bd5f4..d56da03d 100644 --- a/src/lib/ipfs-hosted-redirect-utils.ts +++ b/src/lib/ipfs-hosted-redirect-utils.ts @@ -4,7 +4,7 @@ * * This function will check for "?helia-sw=" in the URL and modify the URL so that it works with the rest of our logic */ -export function getActualUrl (urlString: string): URL { +export function translateIpfsRedirectUrl (urlString: string): URL { const url = new URL(urlString) const heliaSw = url.searchParams.get('helia-sw') if (heliaSw != null) {