Skip to content

Commit

Permalink
Merge pull request #2589 from dusk-network/feature-2588-add-node-url-…
Browse files Browse the repository at this point in the history
…function

explorer: Add function to return node URL
  • Loading branch information
kieranhall authored Oct 8, 2024
2 parents 56c82c9 + f729498 commit a3778df
Show file tree
Hide file tree
Showing 12 changed files with 161 additions and 119 deletions.
17 changes: 11 additions & 6 deletions explorer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,26 @@ Run `npm install` from the root folder to get the necessary dependencies.
The application defines these variables by reading a local `.env`

```
# can be empty string, must start with a slash otherwise, must not end with a slash
VITE_BASE_PATH=""
# *_PATH variables can be empty string, must start with a slash otherwise, must not end with a slash
VITE_API_ENDPOINT="https://api.dusk.network/v1"
VITE_BASE_PATH="" # Optional, set to '/explorer' when deploying to an 'apps.*' subdomain
VITE_BLOCKS_LIST_ENTRIES=100
VITE_CHAIN_INFO_ENTRIES=15
VITE_DUSK_MAINNET_NODE="nodes.dusk.network"
VITE_DUSK_TESTNET_NODE="testnet.nodes.dusk.network"
VITE_DUSK_DEVNET_NODE="devnet.nodes.dusk.network"
VITE_DEFAULT_NETWORK=0 #0=localnet 1=devnet 2=testnet 3=mainnet
VITE_MARKET_DATA_REFETCH_INTERVAL=120000
VITE_NODE_URL="" # Optional, set to (e.g. 'https://nodes.dusk.network' to) override default
VITE_REFETCH_INTERVAL=1000
VITE_RUSK_PATH="" # Optional, set to '/rusk' for dev mode
VITE_STATS_REFETCH_INTERVAL=1000
VITE_TRANSACTIONS_LIST_ENTRIES=100
```

## Environment variables and dev mode

The application defaults to setting the node URL to `/`. In dev mode, requests made on `/rusk` are passed through a proxy to `localhost:8080`. When the app is running in dev mode, set `VITE_RUSK_PATH` to "/rusk".

The application will determine which network it is connected to by the subdomain it is hosted under, to override this and connect to any node set `VITE_NODE_URL`. Note that only `https://` protocol URLs are valid.

## NPM scripts

- `npm run build` generates the production build
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ exports[`Navbar > renders the Navbar component 1`] = `
<span
class="dusk-badge dusk-badge--variant-success dusk-navbar__menu--network"
>
Devnet
Local
</span>
<nav
Expand Down
4 changes: 4 additions & 0 deletions explorer/src/lib/dusk/string/ensureTrailingSlash.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/** @type {(s: string) => string} */
const ensureTrailingSlash = (s) => (s.endsWith("/") ? s : `${s}/`);

export default ensureTrailingSlash;
1 change: 1 addition & 0 deletions explorer/src/lib/dusk/string/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export { default as calculateAdaptiveCharCount } from "./calculateAdaptiveCharCount";
export { default as decodeHexString } from "./decodeHexString";
export { default as ensureTrailingSlash } from "./ensureTrailingSlash.js";
export { default as getRelativeTimeString } from "./getRelativeTimeString";
export { default as hexToBytes } from "./hexToBytes";
export { default as isValidHex } from "./isValidHex";
Expand Down
100 changes: 56 additions & 44 deletions explorer/src/lib/services/__tests__/duskAPI.spec.js

Large diffs are not rendered by default.

28 changes: 19 additions & 9 deletions explorer/src/lib/services/duskAPI.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
} from "lamb";

import { failureToRejection } from "$lib/dusk/http";
import { ensureTrailingSlash } from "$lib/dusk/string";
import { makeNodeUrl } from "$lib/url";

import {
calculateStats,
Expand All @@ -29,9 +31,6 @@ const transformBlocks = mapWith(transformBlock);
/** @type {(transactions: GQLTransaction[]) => Transaction[]} */
const transformTransactions = mapWith(transformTransaction);

/** @type {(s: string) => string} */
const ensureTrailingSlash = (s) => (s.endsWith("/") ? s : `${s}/`);

/**
* Adds the `Rusk-gqlvar-` prefix to all
* keys of the given object and calls `JSON.stringify`
Expand All @@ -58,16 +57,28 @@ const toHeadersVariables = unless(
*/
const makeAPIURL = (endpoint, params) =>
new URL(
`${endpoint}?${new URLSearchParams(params)}`,
`${endpoint}${import.meta.env.VITE_RUSK_PATH || ""}?${new URLSearchParams(params)}`,
ensureTrailingSlash(import.meta.env.VITE_API_ENDPOINT)
);

/**
* @param {string} node
* @param {string} endpoint
*/
function getNodeUrl(node, endpoint) {
const url = makeNodeUrl();

url.pathname = `${import.meta.env.VITE_RUSK_PATH || ""}${endpoint}`;

return url;
}

/**
* @param {string} node
* @param {{ query: string, variables?: Record<string, string | number> }} queryInfo
*/
const gqlGet = (node, queryInfo) =>
fetch(`https://${node}/02/Chain`, {
fetch(getNodeUrl(node, "/02/Chain"), {
body: JSON.stringify({
data: queryInfo.query,
topic: "gql",
Expand All @@ -85,12 +96,11 @@ const gqlGet = (node, queryInfo) =>

/**
* @param {string} node
* @param {"Chain" | "rusk"} target
* @param {"alive_nodes" | "provisioners"} topic
* @param {any} data
*/
const hostGet = (node, target, topic, data) =>
fetch(`https://${node}/2/${target}`, {
const hostGet = (node, topic, data) =>
fetch(getNodeUrl(node, `/2/rusk`), {
body: JSON.stringify({ data, topic }),
headers: {
Accept: "application/json",
Expand Down Expand Up @@ -119,7 +129,7 @@ const apiGet = (endpoint, params) =>
.then((res) => res.json());

/** @type {(node: string) => Promise<HostProvisioner[]>} */
const getProvisioners = (node) => hostGet(node, "rusk", "provisioners", "");
const getProvisioners = (node) => hostGet(node, "provisioners", "");

/** @type {(node: string) => Promise<number>} */
const getLastHeight = (node) =>
Expand Down
32 changes: 9 additions & 23 deletions explorer/src/lib/stores/__tests__/appStore.spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { afterAll, beforeEach, describe, expect, it, vi } from "vitest";
import { get } from "svelte/store";
import { makeNodeUrl } from "$lib/url";

import { changeMediaQueryMatches } from "$lib/dusk/test-helpers";

Expand Down Expand Up @@ -30,26 +31,14 @@ describe("appStore", () => {
it("should be a readable store holding the information needed throughout the whole application", async () => {
const { appStore } = await import("..");
const { env } = import.meta;
const nodeUrl = makeNodeUrl();

/** @type {NetworkOption[]}*/
const expectedNetworks = [
{ label: "Local", value: new URL("/", import.meta.url) },
{
label: "Devnet",
value: new URL(
`${window.location.protocol}${import.meta.env.VITE_DUSK_DEVNET_NODE}`
),
},
{
label: "Testnet",
value: new URL(
`${window.location.protocol}${import.meta.env.VITE_DUSK_TESTNET_NODE}`
),
},
{
label: "Mainnet",
value: new URL(
`${window.location.protocol}${import.meta.env.VITE_DUSK_MAINNET_NODE}`
),
},
{ label: "Local", value: nodeUrl },
{ label: "Devnet", value: nodeUrl },
{ label: "Testnet", value: nodeUrl },
{ label: "Mainnet", value: nodeUrl },
];

expect(appStore).toHaveProperty("subscribe", expect.any(Function));
Expand All @@ -62,10 +51,7 @@ describe("appStore", () => {
hasTouchSupport: false,
isSmallScreen: false,
marketDataFetchInterval: Number(env.VITE_MARKET_DATA_REFETCH_INTERVAL),
network:
expectedNetworks[
parseInt(import.meta.env.VITE_DEFAULT_NETWORK, 10) ?? 0
].value.host,
network: makeNodeUrl().host,
networks: expectedNetworks,
statsFetchInterval: Number(env.VITE_STATS_REFETCH_INTERVAL),
transactionsListEntries: Number(env.VITE_TRANSACTIONS_LIST_ENTRIES),
Expand Down
38 changes: 11 additions & 27 deletions explorer/src/lib/stores/appStore.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,16 @@
import { get, writable } from "svelte/store";
import { browser } from "$app/environment";

import { makeNodeUrl } from "$lib/url";

const nodeUrl = makeNodeUrl();

/** @type {NetworkOption[]}*/
const networks = [
{ label: "Local", value: new URL("/", import.meta.url) },
{
label: "Devnet",
value: new URL(
`${window.location.protocol}${import.meta.env.VITE_DUSK_DEVNET_NODE}`
),
},
{
label: "Testnet",
value: new URL(
`${window.location.protocol}${import.meta.env.VITE_DUSK_TESTNET_NODE}`
),
},
{
label: "Mainnet",
value: new URL(
`${window.location.protocol}${import.meta.env.VITE_DUSK_MAINNET_NODE}`
),
},
{ label: "Local", value: nodeUrl },
{ label: "Devnet", value: nodeUrl },
{ label: "Testnet", value: nodeUrl },
{ label: "Mainnet", value: nodeUrl },
];
const maxWidthMediaQuery = window.matchMedia("(max-width: 1024px)");
const browserDefaults = browser
Expand All @@ -33,15 +22,10 @@ const browserDefaults = browser
};
const DEFAULT_FETCH_INTERVAL = 1000;
const DEFAULT_MARKET_FETCH_INTERVAL = 120000;
const DEFAULT_NETWORK_INDEX = 0;
const DEFAULT_STATS_FETCH_INTERVAL = DEFAULT_FETCH_INTERVAL;

function getNetwork() {
const index =
Number(import.meta.env.VITE_DEFAULT_NETWORK) || DEFAULT_NETWORK_INDEX;
return (
networks[index]?.value.host || networks[DEFAULT_NETWORK_INDEX].value.host
);
function getNetworkHost() {
return makeNodeUrl().host;
}

/** @type {AppStoreContent} */
Expand All @@ -56,7 +40,7 @@ const initialState = {
marketDataFetchInterval:
Number(import.meta.env.VITE_MARKET_DATA_REFETCH_INTERVAL) ||
DEFAULT_MARKET_FETCH_INTERVAL,
network: getNetwork(),
network: getNetworkHost(),
networks,
statsFetchInterval:
Number(import.meta.env.VITE_STATS_REFETCH_INTERVAL) ||
Expand Down
1 change: 1 addition & 0 deletions explorer/src/lib/url/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as makeNodeUrl } from "./makeNodeUrl";
43 changes: 43 additions & 0 deletions explorer/src/lib/url/makeNodeUrl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* Constructs a node URL based on the subdomain
*
* @returns {URL} nodeUrl
*/
function makeNodeUrl() {
const domains = window.location.hostname.split(".");

let node;

switch (domains[0]) {
case "apps": // mainnet
node = new URL(
`${window.location.protocol}nodes.${window.location.host}`
);
break;
case "devnet":
node = new URL(
`${window.location.protocol}devnet.nodes.${window.location.host}`
);
break;
case "testnet":
node = new URL(
`${window.location.protocol}testnet.nodes.${window.location.host}`
);
break;
default: // localnet
node = new URL(
`${import.meta.env.VITE_RUSK_PATH || "/"}`,
import.meta.url
);

if (import.meta.env.VITE_NODE_URL) {
node = new URL(import.meta.env.VITE_NODE_URL, import.meta.url);
}

break;
}

return node;
}

export default makeNodeUrl;
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ exports[`Main layout > should render the app's main layout 1`] = `
<span
class="dusk-badge dusk-badge--variant-success dusk-navbar__menu--network"
>
Devnet
Local
</span>
<nav
Expand Down
12 changes: 4 additions & 8 deletions explorer/vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,11 @@ export default defineConfig(({ mode }) => {
API_ENDPOINT: env.VITE_API_ENDPOINT,
VITE_BLOCKS_LIST_ENTRIES: env.VITE_BLOCKS_LIST_ENTRIES,
VITE_CHAIN_INFO_ENTRIES: env.VITE_CHAIN_INFO_ENTRIES,
VITE_DEFAULT_NETWORK: env.VITE_DEFAULT_NETWORK,
VITE_DUSK_DEVNET_NODE: env.VITE_DUSK_DEVNET_NODE,
VITE_DUSK_MAINNET_NODE: env.VITE_DUSK_MAINNET_NODE,
VITE_DUSK_TESTNET_NODE: env.VITE_DUSK_TESTNET_NODE,
VITE_MARKET_DATA_REFETCH_INTERVAL:
env.VITE_MARKET_DATA_REFETCH_INTERVAL,
VITE_NODE_URL: env.VITE_NODE_URL,
VITE_REFETCH_INTERVAL: env.VITE_REFETCH_INTERVAL,
VITE_RUSK_PATH: env.VITE_RUSK_PATH,
VITE_STATS_REFETCH_INTERVAL: env.VITE_STATS_REFETCH_INTERVAL,
VITE_TRANSACTIONS_LIST_ENTRIES: env.VITE_TRANSACTIONS_LIST_ENTRIES,
},
Expand Down Expand Up @@ -61,12 +59,10 @@ export default defineConfig(({ mode }) => {
VITE_API_ENDPOINT: "https://api.dusk.network/v1",
VITE_BLOCKS_LIST_ENTRIES: "100",
VITE_CHAIN_INFO_ENTRIES: "15",
VITE_DEFAULT_NETWORK: "1", // To prevent snapshot mismatches on localhost
VITE_DUSK_DEVNET_NODE: "devnet.nodes.dusk.network",
VITE_DUSK_MAINNET_NODE: "nodes.dusk.network",
VITE_DUSK_TESTNET_NODE: "nodes.dusk.network",
VITE_MARKET_DATA_REFETCH_INTERVAL: "120000",
VITE_NODE_URL: "",
VITE_REFETCH_INTERVAL: "1000",
VITE_RUSK_PATH: "",
VITE_STATS_REFETCH_INTERVAL: "1000",
VITE_TRANSACTIONS_LIST_ENTRIES: "100",
},
Expand Down

0 comments on commit a3778df

Please sign in to comment.