Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate the faucet page to Redocly #2243

Merged
merged 29 commits into from
Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
ecf58df
Get basic HTML loading for faucet page
JST5000 Oct 30, 2023
0913f19
Add xrpl.js implementation
JST5000 Nov 2, 2023
a144c81
Add sidebar and fix throbber
JST5000 Nov 2, 2023
a218d2f
Add translates
JST5000 Nov 2, 2023
e2e5988
Try to format sidebar
JST5000 Nov 2, 2023
851f10d
Fix formatting
JST5000 Nov 3, 2023
30f6555
Support xrpl.js
JST5000 Nov 7, 2023
602bd8b
Fix links
JST5000 Nov 7, 2023
d4c0cff
Comment out XRPLGuard for now
JST5000 Nov 8, 2023
3455f49
Make AMM Devnet faucet work
JST5000 Nov 9, 2023
751596c
Improve readability
JST5000 Nov 9, 2023
0171913
Update all instances of link + fix topnav
JST5000 Nov 21, 2023
9e18205
Remove unnecessary file
JST5000 Nov 21, 2023
25745c6
Use a more current version of xrpl
JST5000 Nov 21, 2023
04652de
Add missing loader while keys are generating
JST5000 Nov 21, 2023
2b9b4a5
Type with xrpl and remove unnecessary script
JST5000 Nov 21, 2023
625e551
Use string interpolation instead of multiple trans
JST5000 Nov 21, 2023
55f17ec
Move faucets into a json file
JST5000 Nov 21, 2023
61fa7a8
Remove the old faucet code
JST5000 Dec 6, 2023
b353dad
Use xrpl-beta directly
JST5000 Dec 6, 2023
078c0b3
Use dropsToXRP
JST5000 Dec 6, 2023
6fc1b26
Support hooks natively
JST5000 Dec 6, 2023
0a5eb7a
Remove AMM-Devnet
JST5000 Dec 6, 2023
a2bbc38
Revert changes to link path
JST5000 Dec 6, 2023
9e15ada
Revert link changes pt 2
JST5000 Dec 6, 2023
a90e965
Revert pt 3
JST5000 Dec 6, 2023
adda4a9
Use XRPLoader for loading icon
JST5000 Dec 6, 2023
6ccbb94
Fix small mistakes
JST5000 Dec 8, 2023
94513ff
Remove unnecessary changes
JST5000 Dec 8, 2023
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
28 changes: 28 additions & 0 deletions content/dev-tools/faucets.json
JST5000 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe put this file into a data folder or something

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it's useful to keep the data near where it's used in this case since it's super related to the test & there's not many files so it doesn't clutter a reader's ability to tell what all the files are for / doing.

"knownFaucets": [
{
"id": "faucet-select-testnet",
"wsUrl": "wss://s.altnet.rippletest.net:51233/",
"jsonRpcUrl": "https://s.altnet.rippletest.net:51234/",
"faucetUrl": "faucet.altnet.rippletest.net",
"shortName": "Testnet",
"desc": "Mainnet-like network for testing applications."
},
{
"id": "faucet-select-devnet",
"wsUrl": "wss://s.devnet.rippletest.net:51233/",
"jsonRpcUrl": "https://s.devnet.rippletest.net:51234/",
"faucetUrl": "faucet.devnet.rippletest.net",
"shortName": "Devnet",
"desc": "Preview of upcoming amendments."
},
{
"id": "faucet-select-xahau",
"wsUrl": "wss://xahau-test.net/",
"jsonRpcUrl": "https://xahau-test.net/",
"faucetUrl": "xahau-test.net",
"shortName": "Xahau-Testnet",
"desc": "Hooks (L1 smart contracts) enabled Xahau testnet."
}
]
}
203 changes: 203 additions & 0 deletions content/dev-tools/xrp-faucets.page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
import * as React from 'react';
import { useTranslate } from '@portal/hooks';
import { useState } from 'react';
import { Client, dropsToXrp, Wallet } from 'xrpl';
import * as faucetData from './faucets.json'
import XRPLoader from 'content/static/components/XRPLoader';

interface FaucetInfo {
id: string,
wsUrl: string,
jsonRpcUrl: string,
faucetUrl: string,
shortName: string,
desc: string,
}

async function waitForSequence(client: Client, address: string):
Promise<{ sequence: string, balance: string }>
{
let response;
while (true) {
try {
response = await client.request({
command: "account_info",
account: address,
ledger_index: "validated"
})
break
} catch(e) {
await new Promise(resolve => setTimeout(resolve, 1000))
}
}
console.log(response)

return { sequence: response.result.account_data.Sequence, balance: response.result.account_data.Balance}
}

function FaucetEndpoints({ faucet, givenKey } : { faucet: FaucetInfo, givenKey: string}) {
const { translate } = useTranslate();

return (<div key={givenKey}>
<h4>{translate(`${faucet.shortName} Servers`)}</h4>
<pre>
<code>
// WebSocket<br/>
{faucet.wsUrl}<br/>
<br/>
// JSON-RPC<br/>
{faucet.jsonRpcUrl}
</code>
</pre>
</div>)
}

function FaucetSidebar({ faucets }: { faucets: FaucetInfo[] }): React.JSX.Element {
return (<aside className="right-sidebar col-lg-6 order-lg-4" role="complementary">
{faucets.map(
(faucet) => <FaucetEndpoints faucet={faucet} key={faucet.shortName + " Endpoints"} givenKey={faucet.shortName + " Endpoints"}/>
)}
</aside>)
}

export default function XRPFaucets(): React.JSX.Element {
const { translate } = useTranslate();

const faucets: FaucetInfo[] = faucetData.knownFaucets

const [selectedFaucet, setSelectedFaucet] = useState(faucets[0])

return (
<div className="container-fluid" role="document" id="main_content_wrapper">
<div className="row">
<FaucetSidebar faucets={faucets}/>
<main className="main col-md-7 col-lg-6 order-md-3" role="main" id="main_content_body">
<section className="container-fluid pt-3 p-md-3">
<h1>{translate("XRP Faucets")}</h1>
<div className="content">
<p>{translate("These ")}<a href="parallel-networks.html">{translate("parallel XRP Ledger test networks")}</a> {translate("provide platforms for testing changes to the XRP Ledger and software built on it, without using real funds.")}</p>
<p>{translate("These funds are intended for")} <strong>{translate("testing")}</strong> {translate("only. Test networks' ledger history and balances are reset as necessary. Devnets may be reset without warning.")}</p>
<p>{translate("All balances and XRP on these networks are separate from Mainnet. As a precaution, do not use the Testnet or Devnet credentials on the Mainnet.")}</p>

<h3>{translate("Choose Network:")}</h3>
{ faucets.map((net) => (
<div className="form-check" key={"network-" + net.shortName}>
<input onChange={() => setSelectedFaucet(net)} className="form-check-input" type="radio"
name="faucet-selector" id={net.id} checked={selectedFaucet.shortName == net.shortName} />
<label className="form-check-label" htmlFor={net.id}>
<strong>{translate(net.shortName)}</strong>: {translate(net.desc)}
</label>
</div>
)) }

<br/>
<TestCredentials selectedFaucet={selectedFaucet}/>
</div>
</section>
</main>
</div>
</div>
)
}

async function generateFaucetCredentialsAndUpdateUI(
selectedFaucet: FaucetInfo,
setButtonClicked: React.Dispatch<React.SetStateAction<boolean>>,
setGeneratedCredentialsFaucet: React.Dispatch<React.SetStateAction<string>>,
setAddress: React.Dispatch<React.SetStateAction<string>>,
setSecret: React.Dispatch<React.SetStateAction<string>>,
setBalance: React.Dispatch<React.SetStateAction<string>>,
setSequence: React.Dispatch<React.SetStateAction<string>>): Promise<void> {

setButtonClicked(true)

// Clear existing credentials
setGeneratedCredentialsFaucet(selectedFaucet.shortName)
setAddress("")
setSecret("")
setBalance("")
setSequence("")
const { translate } = useTranslate();


const wallet = Wallet.generate()

const client = new Client(selectedFaucet.wsUrl)
await client.connect()

try {
setAddress(wallet.address)
setSecret(wallet.seed)

await client.fundWallet(wallet, { faucetHost: selectedFaucet.faucetUrl, usageContext: "xrpl.org-faucet" })

const response = await waitForSequence(client, wallet.address)

setSequence(response.sequence)
setBalance(response.balance)

} catch (e) {
alert(translate(`There was an error with the ${selectedFaucet.shortName} faucet. Please try again.`))
}
setButtonClicked(false)
}

function TestCredentials({selectedFaucet}) {
const { translate } = useTranslate();

const [generatedCredentialsFaucet, setGeneratedCredentialsFaucet] = useState("")
const [address, setAddress] = useState("")
const [secret, setSecret] = useState("")
const [balance, setBalance] = useState("")
const [sequence, setSequence] = useState("")
const [buttonClicked, setButtonClicked] = useState(false)

return (<div>
{/* <XRPLGuard> TODO: Re-add this once we find a good way to avoid browser/server mismatch errors */}
<div className="btn-toolbar" role="toolbar" aria-label="Button">
<button id="generate-creds-button" onClick={
() => generateFaucetCredentialsAndUpdateUI(
selectedFaucet,
setButtonClicked,
setGeneratedCredentialsFaucet,
setAddress,
setSecret,
setBalance,
setSequence)
} className="btn btn-primary mr-2 mb-2">
{translate(`Generate ${selectedFaucet.shortName} credentials`)}
</button>
</div>
{/* </XRPLGuard> */}


{generatedCredentialsFaucet && <div id="your-credentials">
<h2>{translate(`Your ${generatedCredentialsFaucet} Credentials`)}</h2>
</div>}

{(buttonClicked && address === "") && <XRPLoader message={translate("Generating keys..")}/>}

{address && <div id="address"><h3>{translate("Address")}</h3>{address}</div>}

{secret && <div id="secret"><h3>{translate("Secret")}</h3>{secret}</div>}

{(address && !balance) && (<div>
<br/>
<XRPLoader message={translate("Funding account...")}/>
</div>)}

{balance && <div id="balance">
<h3>{translate("Balance")}</h3>
{dropsToXrp(balance).toLocaleString("en")} {translate("XRP")}
</div>}

{sequence && <div id="sequence">
<h3>{translate("Sequence Number")}</h3>
{sequence}
</div>}

{(secret && !sequence) && <XRPLoader message={translate("Waiting...")}/>}

</div>
)
}
2 changes: 2 additions & 0 deletions content/sidebars.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,8 @@
- label: xrp-ledger.toml Checker
- label: Domain Verification Checker
- label: XRP Faucets
href: /dev-tools/xrp-faucets
page: /dev-tools/xrp-faucets.page.tsx
- label: Transaction Sender
- label: XRPL Learning Portal
href: https://learn.xrpl.org/
Expand Down
13 changes: 13 additions & 0 deletions content/static/components/XRPLoader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import * as React from 'react';

export interface XRPLoaderProps {
message?: string
}

export default function XRPLoader(props: XRPLoaderProps) {
return (
<div id="loader" style={{ display: "inline" }}>
<img alt="(loading)" className="throbber" src="/img/xrp-loader-96.png" />
{props.message}
</div>);
}
27 changes: 27 additions & 0 deletions content/static/js/type-helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
type TypeofType =
| 'bigint'
| 'boolean'
| 'function'
| 'number'
| 'object'
| 'string'
| 'undefined'

type TypeCheckFn = (thing: unknown) => boolean


/**
* Curried function for creating typeof checker functions.
* @param {string} type The type to check against (eg 'string', 'number')
* @param {function} [secondaryTest] Optional additional test function to run in cases where a type match isn't always a sure indicator.
* @returns {boolean} Whether the value matches the type
*/
const isTypeof =
<T>(type: TypeofType, secondaryTest?: TypeCheckFn) =>
(thing: unknown): thing is T => {
const matches = typeof thing === type
if (matches && secondaryTest) return secondaryTest(thing)
return matches
}

export const isFunction = isTypeof<(...args: unknown[]) => unknown>('function')
85 changes: 85 additions & 0 deletions content/static/js/xrpl-guard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { useTranslate } from '@portal/hooks';
import { isFunction } from './type-helpers'
import { FC } from 'react'
import { useEffect, useState } from 'react'
import React = require('react');
import XRPLoader from '../components/XRPLoader';

export const MIN_LOADER_MS = 1250
export const DEFAULT_TIMEOUT = 1000

const sleep = (delay) => new Promise((resolve) => setTimeout(resolve, delay))

/**
* Evaluate a check function will eventually resolve to `true`
*
* If check is initially true, immediatly return `isTrue`
* If check is initially false and becomes true, return true after `timeoutMs`
*/
export const useThrottledCheck = (
check: () => boolean,
timeoutMs = DEFAULT_TIMEOUT,
) => {
const [isTrue, setIsTrue] = useState(() => check())

useEffect(() => {
const doCheck = async (tries = 0) => {
const waitMs = 250,
waitedMs = tries * waitMs

if (check()) {
const debouncedDelay =
waitedMs < timeoutMs ? timeoutMs - (waitedMs % timeoutMs) : 0

setTimeout(() => setIsTrue(true), debouncedDelay)
return
}

await sleep(waitMs)

doCheck(tries + 1)
}

if (!isTrue) {
doCheck()
}
}, [check, isTrue])

return isTrue
}

/**
* Show a loading spinner if XRPL isn't loaded yet by
* waiting at least MIN_LOADER_MS before rendering children
* in order to make the visual loading transition smooth
*
* e.g. if xrpl loads after 500ms, wait
* another MIN_LOADER_MS - 500ms before rendering children
*
* @param {function} testCheck for testing only, a check function to use
*/
export const XRPLGuard: FC<{ testCheck?: () => boolean, children }> = ({
testCheck,
children,
}) => {

const { translate } = useTranslate();
const isXRPLLoaded = useThrottledCheck(
// @ts-expect-error - xrpl is added via a script tag (TODO: Directly import when xrpl.js 3.0 is released)
testCheck ?? (() => typeof xrpl === 'object'),
MIN_LOADER_MS,
)

return (
<>
{isXRPLLoaded ? (
isFunction(children) ? (
children()
) : (
children
)
) : <XRPLoader message={translate("Loading...")}/>
}
</>
)
}
3 changes: 2 additions & 1 deletion content/top-nav.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@
- label: Send XRP
href: /tutorials/send-xrp/
- label: XRP Faucets
href: /docs/dev-tools/xrp-faucets/
href: /dev-tools/xrp-faucets
page: /dev-tools/xrp-faucets.page.tsx
- label: XRPL Servers
href: /infrastructure/xrpl-servers/
- label: Dev Tools
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"dependencies": {
"@redocly/portal": "0.57.0",
"lottie-react": "^2.4.0",
"moment": "^2.29.4"
"moment": "^2.29.4",
"xrpl": "^3.0.0-beta.1"
},
"overrides": {
"react": "^17.0.2"
Expand Down
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"compilerOptions": {
"baseUrl": ".",
"jsx": "react",
"resolveJsonModule": true,
"paths": {
"@theme/*": [
"./@theme/*",
Expand Down
Loading