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
6 changes: 6 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
FROM node:20-alpine as frontend

# Set the base path for the frontend build
# This can be overridden at build time with --build-arg BASE_PATH=<url>
# Allows to build a frontend that can be served from a subpath, e.g. /hub/
ARG BASE_PATH="/"
Copy link
Contributor

Choose a reason for hiding this comment

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

there is BASE_PATH and BASE_URL, we only need one, right?

Copy link
Contributor

Choose a reason for hiding this comment

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

Currently BASE_URL is an already declared property used by the hub go app.
Also note there is a FRONTEND_URL used by the go app too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So BASE_URL is a vite specific, I think they call it URL since they allow to set the whole URL in cases resources come from a specific server (CDN for example). This feature is focused on allowing to server the frontend behind a proxy using sub path. That case is usually named base_path and I wanted to keep the top level variable reflecting that specific use case. URL would suggest to allow to change the whole URL.

I could't see a specific group set up for the /api path in the echo framework boilerplate, and a quick search seems to suggest FRONTEND_URL and BASE_PATH is used when connecting the alby account with the local hub instance ( from default https://getalby.com/).

Yes that has a potential for creating confusion. Maybe the BASE_PATH should be called VITE_FRONTEND_BASE_PATH.
Since you have a better understanding of the already existing variables, do you have any suggestion that helps differentiate ?

And I would really not focus on BASE_URL since it is a vite internal and is a constant injected into the build artefact. Runtime env variables won't reflect on that, it is just build time relevant.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for the details, after looking into it I understand this is how vite works. And the FRONTEND_URL we have is somewhat different and only applies to Alby Cloud. So that one can probably be ignored for now.

I made some changes in this PR - could you review?

8de2fdb0#1

WORKDIR /build
COPY frontend ./frontend
RUN echo "Building frontend with base path $BASE_PATH"
RUN cd frontend && yarn install --network-timeout 3000000 && yarn build:http

FROM golang:1.24 as builder
Expand Down
15 changes: 15 additions & 0 deletions frontend/platform_specific/http/src/utils/request.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
import { getAuthToken } from "src/lib/auth";
import { ErrorResponse } from "src/types";

const BASE_URL = import.meta.env.BASE_URL;
const PREFIXES = ["/api", "/images"];

function startsWithPrefix(path: string, prefixes: string[]): boolean {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think the changes in this file should be needed - requests always go to /api (this is a special request function to abstract the differences between HTTP and Wails APIs)

Copy link
Contributor

Choose a reason for hiding this comment

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

Can we just do:
const BASE_URL = import.meta.env.BASE_URL || "";
args[0] = BASE_URL + args[0];

Copy link
Contributor Author

@8de2fdb0 8de2fdb0 Jun 11, 2025

Choose a reason for hiding this comment

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

I don't think the changes in this file should be needed - requests always go to /api (this is a special request function to abstract the differences between HTTP and Wails APIs)

Running vite build will inject the import.meta.env.BASE_URL to all resources loaded from the server, but not into the API requests, there is probably a better way to have vite modify the API urls from where they are used, but injecting it here seemed simpler then refactoring all the individual api hooks.

I only added it to the platform_specific/http variant since that feature won't make any sense with wails.

Can we just do:
const BASE_URL = import.meta.env.BASE_URL || "";
args[0] = BASE_URL + args[0];

The const import.meta.env.BASE_URL is injected from vite during build, see https://vite.dev/guide/build.html#public-base-path.
Looking at the docs for the specific vite config option it is default set to "/", my changes also added that value in the config in case the env variable BASE_PATH is unset. So it must be always "/" at that point.

About the path concatenation, yes I think so, but it would need to be some kind of path.join so /base_url/ + /api will result in /base_url/api. The slice[1] kind of expects the first char to be / and removes it and since only parameters starting with /api and /images are matched that will always be true. But that will fail in case BASE_URL is /hub.

Best is probably to add a small joinPath function.

Copy link
Contributor

Choose a reason for hiding this comment

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

I simplified this in 8de2fdb0#1 and it works from my testing with caddy. Please let me know if that works for you.

One thing to note is you will need to update to use /hub rather than /hub/ for the BASE_PATH.

return prefixes.some((prefix) => path.startsWith(prefix));
}

export const request = async <T>(
...args: Parameters<typeof fetch>
): Promise<T | undefined> => {
if (
BASE_URL !== "/" &&
typeof args[0] === "string" &&
startsWithPrefix(args[0], PREFIXES)
) {
args[0] = BASE_URL + args[0].slice(1);
}

const token = getAuthToken();
if (token) {
if (!args[1]) {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import routes from "src/routes.tsx";
import { isHttpMode } from "src/utils/isHttpMode";

const createRouterFunc = isHttpMode() ? createBrowserRouter : createHashRouter;
const router = createRouterFunc(routes);
const router = createRouterFunc(routes, { basename: import.meta.env.BASE_URL });

function App() {
const { data: info } = useInfo();
Expand Down
7 changes: 5 additions & 2 deletions frontend/src/components/connections/AlbyConnectionCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ import { useAlbyMe } from "src/hooks/useAlbyMe";
import { LinkStatus, useLinkAccount } from "src/hooks/useLinkAccount";
import { App, BudgetRenewalType } from "src/types";

import AlbyAccountDarkSVG from "public/images/illustrations/alby-account-dark.svg";
import AlbyAccountLightSVG from "public/images/illustrations/alby-account-light.svg";

function AlbyConnectionCard({ connection }: { connection?: App }) {
const { data: albyMe } = useAlbyMe();
const { loading, linkStatus, loadingLinkStatus, linkAccount } =
Expand Down Expand Up @@ -109,11 +112,11 @@ function AlbyConnectionCard({ connection }: { connection?: App }) {
every app you access through your Alby Account will
handle payments via the Hub.
<img
src="/images/illustrations/alby-account-dark.svg"
src={AlbyAccountDarkSVG}
className="w-full hidden dark:block"
/>
<img
src="/images/illustrations/alby-account-light.svg"
src={AlbyAccountLightSVG}
className="w-full dark:hidden"
/>
You can add a budget that will restrict how much can be
Expand Down
25 changes: 17 additions & 8 deletions frontend/src/components/layouts/TwoColumnFullScreenLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ import { AlbyHubLogo } from "src/components/icons/AlbyHubLogo";
import { Button } from "src/components/ui/button.tsx";
import { useInfo } from "src/hooks/useInfo";

import AntonopoulosSVG from "public/images/quotes/antonopoulos.svg";
import BackSVG from "public/images/quotes/back.svg";
import FinneySVG from "public/images/quotes/finney.svg";
import HayekSVG from "public/images/quotes/hayek.svg";
import NakamotoSVG from "public/images/quotes/nakamoto.svg";
import ObamaSVG from "public/images/quotes/obama.svg";
import RolandSVG from "public/images/quotes/roland.svg";
import WilsonSVG from "public/images/quotes/wilson.svg";

const quotes = [
{
content: `This isn't about nation-states anymore. This isn't about who adopts
Expand All @@ -13,42 +22,42 @@ const quotes = [
largest economy. It is the first transnational economy, and it needs a
transnational currency.`,
author: "Andreas M. Antonopoulos",
imageUrl: "/images/quotes/antonopoulos.svg",
imageUrl: AntonopoulosSVG,
},
{
content: `It might make sense just to get some in case it catches on. If enough people think the same way, that becomes a self fulfilling prophecy. Once it gets bootstrapped, there are so many applications if you could effortlessly pay a few cents to a website as easily as dropping coins in a vending machine.`,
author: "Satoshi Nakamoto",
imageUrl: "/images/quotes/nakamoto.svg",
imageUrl: NakamotoSVG,
},
{
content: `Since we're all rich with bitcoins, or we will be once they're worth a million dollars like everyone expects, we ought to put some of this unearned wealth to good use.`,
author: "Hal Finney",
imageUrl: "/images/quotes/finney.svg",
imageUrl: FinneySVG,
},
{
content: `I don't believe we shall ever have a good money again before we take the thing out of the hands of government, that is, we can't take it violently out of the hands of government, all we can do is by some sly roundabout way introduce something that they can't stop.`,
author: "Friedrich August von Hayek",
imageUrl: "/images/quotes/hayek.svg",
imageUrl: HayekSVG,
},
{
content: `Bitcoin is what they fear it is.`,
author: "Cody Wilson",
imageUrl: "/images/quotes/wilson.svg",
imageUrl: WilsonSVG,
},
{
content: `If in fact you can't crack that at all, government can't get in then —everybody's walking around with a Swiss bank account in their pocket.`,
author: "Barack Obama",
imageUrl: "/images/quotes/obama.svg",
imageUrl: ObamaSVG,
},
{
content: `Bitcoin is the new wonder of the world, more work and human ingenuity, than went into the great pyramids of Egypt. The biggest computation ever done, a digital monument, a verifiable artefact of digital gold - the foundation of a new digital age.`,
author: "Adam Back",
imageUrl: "/images/quotes/back.svg",
imageUrl: BackSVG,
},
{
content: `We who choose Bitcoin, are pioneers of a new world. A world filled with freedom, hope and peace.`,
author: "Roland",
imageUrl: "/images/quotes/roland.svg",
imageUrl: RolandSVG,
},
];

Expand Down
10 changes: 5 additions & 5 deletions frontend/src/screens/channels/IncreaseIncomingCapacity.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ import {
RecommendedChannelPeer,
} from "src/types";

import LightningNetworkDarkSVG from "public/images/illustrations/lightning-network-dark.svg";
import LightningNetworkLightSVG from "public/images/illustrations/lightning-network-light.svg";

function getPeerKey(peer: RecommendedChannelPeer) {
return JSON.stringify(peer);
}
Expand Down Expand Up @@ -229,13 +232,10 @@ function NewChannelInternal({
/>
<div className="md:max-w-md max-w-full flex flex-col gap-5 flex-1">
<img
src="/images/illustrations/lightning-network-dark.svg"
src={LightningNetworkDarkSVG}
className="w-full hidden dark:block"
/>
<img
src="/images/illustrations/lightning-network-light.svg"
className="w-full dark:hidden"
/>
<img src={LightningNetworkLightSVG} className="w-full dark:hidden" />
<p className="text-muted-foreground">
Alby Hub works with selected service providers (LSPs) which provide
the best network connectivity and liquidity to receive payments.{" "}
Expand Down
10 changes: 5 additions & 5 deletions frontend/src/screens/channels/IncreaseOutgoingCapacity.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ import {
} from "src/types";
import { request } from "src/utils/request";

import LightningNetworkDarkSVG from "public/images/illustrations/lightning-network-dark.svg";
import LightningNetworkLightSVG from "public/images/illustrations/lightning-network-light.svg";

function getPeerKey(peer: RecommendedChannelPeer) {
return JSON.stringify(peer);
}
Expand Down Expand Up @@ -200,13 +203,10 @@ function NewChannelInternal({ network }: { network: Network }) {
/>
<div className="md:max-w-md max-w-full flex flex-col gap-5 flex-1">
<img
src="/images/illustrations/lightning-network-dark.svg"
src={LightningNetworkDarkSVG}
className="w-full hidden dark:block"
/>
<img
src="/images/illustrations/lightning-network-light.svg"
className="w-full dark:hidden"
/>
<img src={LightningNetworkLightSVG} className="w-full dark:hidden" />
<p className="text-muted-foreground">
Open a channel with on-chain funds. Both parties are free to close the
channel at any time. However, by keeping more funds on your side of
Expand Down
7 changes: 5 additions & 2 deletions frontend/src/screens/channels/auto/AutoChannel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ import { MempoolAlert } from "src/components/MempoolAlert";
import { PayLightningInvoice } from "src/components/PayLightningInvoice";
import { ChannelPublicPrivateAlert } from "src/components/channels/ChannelPublicPrivateAlert";

import LightningNetworkDarkSVG from "public/images/illustrations/lightning-network-dark.svg";
import LightningNetworkLightSVG from "public/images/illustrations/lightning-network-light.svg";

export function AutoChannel() {
const { data: info } = useInfo();
const { data: channels } = useChannels(true);
Expand Down Expand Up @@ -134,11 +137,11 @@ export function AutoChannel() {
<>
<div className="flex flex-col gap-6 max-w-md text-muted-foreground">
<img
src="/images/illustrations/lightning-network-dark.svg"
src={LightningNetworkDarkSVG}
className="w-full hidden dark:block"
/>
<img
src="/images/illustrations/lightning-network-light.svg"
src={LightningNetworkLightSVG}
className="w-full dark:hidden"
/>

Expand Down
7 changes: 5 additions & 2 deletions frontend/src/screens/channels/first/FirstChannel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ import { PayLightningInvoice } from "src/components/PayLightningInvoice";
import { Table, TableBody, TableCell, TableRow } from "src/components/ui/table";
import { ALBY_MIN_HOSTED_BALANCE_FOR_FIRST_CHANNEL } from "src/constants";

import LightningNetworkDarkSVG from "public/images/illustrations/lightning-network-dark.svg";
import LightningNetworkLightSVG from "public/images/illustrations/lightning-network-light.svg";

export function FirstChannel() {
const { data: info } = useInfo();
const { data: channels } = useChannels(true);
Expand Down Expand Up @@ -167,11 +170,11 @@ export function FirstChannel() {
<>
<div className="flex flex-col gap-6 max-w-md text-muted-foreground">
<img
src="/images/illustrations/lightning-network-dark.svg"
src={LightningNetworkDarkSVG}
className="w-full hidden dark:block"
/>
<img
src="/images/illustrations/lightning-network-light.svg"
src={LightningNetworkLightSVG}
className="w-full dark:hidden"
/>
{canPayForFirstChannel ? (
Expand Down
13 changes: 5 additions & 8 deletions frontend/src/screens/subwallets/SubwalletIntro.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import ExternalLink from "src/components/ExternalLink";
import ResponsiveButton from "src/components/ResponsiveButton";
import { Button } from "src/components/ui/button";

import SubWalletDarkSVG from "public/images/illustrations/sub-wallet-dark.svg";
import SubWalletLightSVG from "public/images/illustrations/sub-wallet-light.svg";

export function SubwalletIntro() {
return (
<div className="grid gap-4">
Expand All @@ -33,14 +36,8 @@ export function SubwalletIntro() {
<div>
<div className="flex flex-col gap-6 max-w-screen-md">
<div className="mb-2">
<img
src="/images/illustrations/sub-wallet-dark.svg"
className="w-72 hidden dark:block"
/>
<img
src="/images/illustrations/sub-wallet-light.svg"
className="w-72 dark:hidden"
/>
<img src={SubWalletDarkSVG} className="w-72 hidden dark:block" />
<img src={SubWalletLightSVG} className="w-72 dark:hidden" />
</div>
<div>
<div className="flex flex-row gap-3">
Expand Down
5 changes: 5 additions & 0 deletions frontend/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ export default defineConfig(({ command }) => ({
alias: {
src: path.resolve(__dirname, "./src"),
wailsjs: path.resolve(__dirname, "./wailsjs"),
// used to refrence public assets when importing images or other
// assets from the public folder
// this is necessary to inject the base path during build
public: "",
},
},
build: {
Expand All @@ -75,6 +79,7 @@ export default defineConfig(({ command }) => ({
cspNonce: "DEVELOPMENT",
}
: undefined,
base: process.env.BASE_PATH || "/",
}));

const DEVELOPMENT_NONCE = "'nonce-DEVELOPMENT'";
Expand Down