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

WIP: feat(docs): ACT-1532 - Create Pages for the Linea JSON-RPC methods dynamically #1559

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
.docusaurus
.cache-loader
.idea
.yarn

# Misc
.DS_Store
Expand Down
34 changes: 34 additions & 0 deletions docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
// Note: type annotations allow type checking and IDEs autocompletion

require("dotenv").config();
const { fetchAndGenerateDynamicSidebarItems } = require("./src/helpers");
import { JSON_RPC_METHODS_LABEL, NETWORK_NAMES } from "./src/lib/constants.ts";
const upperFirst = require("lodash.upperfirst");
const { themes } = require("prism-react-renderer");
const { REF_ALLOW_LOGIN_PATH } = require("./src/lib/constants");
const codeTheme = themes.dracula;
Expand Down Expand Up @@ -36,6 +39,8 @@ const config = {
VERCEL_ENV: process.env.VERCEL_ENV,
DASHBOARD_PREVIEW_URL: process.env.DASHBOARD_PREVIEW_URL,
SENTRY_KEY: process.env.SENTRY_KEY,
sidebarData: {},
dynamicData: []
},

trailingSlash: true,
Expand Down Expand Up @@ -127,6 +132,35 @@ const config = {
editUrl: "https://github.com/MetaMask/metamask-docs/edit/main/",
sidebarPath: require.resolve("./services-sidebar.js"),
breadcrumbs: false,
sidebarItemsGenerator: async function ({ defaultSidebarItemsGenerator, ...args }) {
config.customFields.sidebarData = args;
let sidebarItems = await defaultSidebarItemsGenerator(args);
const dynamicSidebarItems = await fetchAndGenerateDynamicSidebarItems(NETWORK_NAMES.linea);
config.customFields.dynamicData = dynamicSidebarItems;
const updatedItems = sidebarItems.map(item => {
if (item?.label === upperFirst(NETWORK_NAMES.linea) && item?.items) {
return {
...item,
items:
[
...item.items.filter(({ label }) => label !== JSON_RPC_METHODS_LABEL),
...dynamicSidebarItems.map(dynamicItem => {
const jsonRpcCategory = item.items.find(({ label }) => label === JSON_RPC_METHODS_LABEL);
if (jsonRpcCategory) {
return {
...dynamicItem,
...{ items: [...dynamicItem.items, ...jsonRpcCategory.items.filter(refItem => refItem.type === "category")] }
};
}
return dynamicItem;
})
]
};
}
return item;
});
return [...updatedItems];
},
},
],
[
Expand Down
14 changes: 8 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"launchdarkly-js-client-sdk": "^3.3.0",
"lodash.debounce": "^4.0.8",
"lodash.isplainobject": "^4.0.6",
"lodash.upperfirst": "^4.3.1",
"node-polyfill-webpack-plugin": "^2.0.1",
"prettier": "^3.3.3",
"prism-react-renderer": "^2.1.0",
Expand Down
4 changes: 2 additions & 2 deletions services/tutorials/ethereum/authenticate-with-jwt.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import TabItem from '@theme/TabItem';
# Authenticate with JWT

This tutorial demonstrates how to create and apply a JSON Web Token (JWT) to authenticate an
[`eth_blockNumber`](../../api/networks/ethereum/json-rpc-methods/eth_blocknumber.mdx) API request
[`eth_blockNumber`](/services/reference/ethereum/json-rpc-methods/eth_blocknumber/) API request
with Node.js.

Developers can configure the expiry time and scope of JWTs to enhance the security profile of their dapps.
Expand Down Expand Up @@ -157,7 +157,7 @@ Replace the following values in the `.env` file:

- `<YOUR-API-KEY>` with your API key from the Infura dashboard.
- `<YOUR-JWT-KEY-ID>` with the JWT's key ID. This is generated by Infura, and you can find it in the Infura dashboard. The code in [Step 4](#4-create-and-apply-your-jwt) applies this ID to the JWT header to allow Infura to identify which key was used to sign the JWT.
- `<NETWORK-URL>` with the URL of an Infura network for which your key has access rights, and that supports the method [`eth_blockNumber`](../../api/networks/ethereum/json-rpc-methods/eth_blocknumber.mdx).
- `<NETWORK-URL>` with the URL of an Infura network for which your key has access rights, and that supports the method [`eth_blockNumber`](/services/reference/ethereum/json-rpc-methods/eth_blocknumber/).

:::warning Important

Expand Down
142 changes: 142 additions & 0 deletions src/components/CustomReferencePage/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import Layout from "@theme/Layout";
import ParserOpenRPC from "@site/src/components/ParserOpenRPC";
import React, { useEffect, useState } from "react";
import DocSidebar from '@theme/DocSidebar';
import styles from "@site/src/theme/Layout/styles.module.css"
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import upperFirst from "lodash.upperfirst"
import { JSON_RPC_METHODS_LABEL, lineaSidebarNames, NETWORK_NAMES } from "@site/src/lib/constants";

const formatMenuLabel = (label) => {
const menuItem = lineaSidebarNames.find(name => name.old === label);
if (menuItem) {
return menuItem.new;
}
return label;
}

function generateSidebarItems(docs) {
const categories = {};

docs.forEach((doc) => {
if (doc.id === 'index') {
categories['Introduction'] = {
type: 'link',
href: '/services',
label: upperFirst(doc.frontMatter?.sidebar_label || doc.title),
};
return;
}

const pathParts = doc.sourceDirName.split('/');
let currentCategory = categories;
let isIndexPage = doc.id.endsWith('/index');
pathParts.map(pathPart => formatMenuLabel(pathPart)).forEach((part, index) => {
if (!currentCategory[part]) {
if (isIndexPage && index === pathParts.length - 2) {
currentCategory[part] = {
type: 'category',
label: upperFirst(doc.frontMatter?.sidebar_label || doc.frontMatter?.title || part),
collapsed: false,
collapsible: true,
href: `/services/reference`,
items: []
};
} else {
currentCategory[part] = {
type: 'category',
label: upperFirst(part),
href: `/services/${doc.sourceDirName}`,
collapsed: part !== "Get started",
collapsible: true,
items: []
};
}
}

if (index === pathParts.length - 1 && !isIndexPage) {
currentCategory[part].items.push({
type: 'link',
label: doc.frontMatter?.title || doc.title,
href: `/services/${doc.id.replace(/\/index$/, '')}`,
sidebar_position: doc.frontMatter?.sidebar_position || Number.MAX_SAFE_INTEGER
});
}
currentCategory = currentCategory[part].items;
});
});

const convertToArray = (categoryObj) => {
return Object.values(categoryObj).map((category) => {
if (category.items && typeof category.items === 'object') {

Check failure on line 71 in src/components/CustomReferencePage/index.tsx

View workflow job for this annotation

GitHub Actions / Lint / Lint Code Base, Spelling, Link Check

Property 'items' does not exist on type 'unknown'.

Check failure on line 71 in src/components/CustomReferencePage/index.tsx

View workflow job for this annotation

GitHub Actions / Lint / Lint Code Base, Spelling, Link Check

Property 'items' does not exist on type 'unknown'.
category.items = convertToArray(category.items);

Check failure on line 72 in src/components/CustomReferencePage/index.tsx

View workflow job for this annotation

GitHub Actions / Lint / Lint Code Base, Spelling, Link Check

Property 'items' does not exist on type 'unknown'.

Check failure on line 72 in src/components/CustomReferencePage/index.tsx

View workflow job for this annotation

GitHub Actions / Lint / Lint Code Base, Spelling, Link Check

Property 'items' does not exist on type 'unknown'.
if (category.items.every(item => item.sidebar_position !== undefined)) {

Check failure on line 73 in src/components/CustomReferencePage/index.tsx

View workflow job for this annotation

GitHub Actions / Lint / Lint Code Base, Spelling, Link Check

Property 'items' does not exist on type 'unknown'.
category.items.sort((a, b) => (a.sidebar_position || Number.MAX_SAFE_INTEGER) - (b.sidebar_position || Number.MAX_SAFE_INTEGER));

Check failure on line 74 in src/components/CustomReferencePage/index.tsx

View workflow job for this annotation

GitHub Actions / Lint / Lint Code Base, Spelling, Link Check

Property 'items' does not exist on type 'unknown'.
}
}
return category;
});
};
return convertToArray(categories);
}

const sidebar_wrapper_classes = "theme-doc-sidebar-container docSidebarContainer_node_modules-@docusaurus-theme-classic-lib-theme-DocRoot-Layout-Sidebar-styles-module"

const CustomReferencePage = (props) => {
const customData = props.route.customData;
const { siteConfig } = useDocusaurusContext();
const [formattedData, setFormattedData] = useState([]);

useEffect(() => {
setFormattedData(generateSidebarItems(siteConfig.customFields.sidebarData.docs).map(item => {

Check failure on line 91 in src/components/CustomReferencePage/index.tsx

View workflow job for this annotation

GitHub Actions / Lint / Lint Code Base, Spelling, Link Check

Property 'docs' does not exist on type 'unknown'.
if (item?.label === "Reference" && item?.items) {

Check failure on line 92 in src/components/CustomReferencePage/index.tsx

View workflow job for this annotation

GitHub Actions / Lint / Lint Code Base, Spelling, Link Check

Property 'label' does not exist on type 'unknown'.

Check failure on line 92 in src/components/CustomReferencePage/index.tsx

View workflow job for this annotation

GitHub Actions / Lint / Lint Code Base, Spelling, Link Check

Property 'items' does not exist on type 'unknown'.
return {
...item,

Check failure on line 94 in src/components/CustomReferencePage/index.tsx

View workflow job for this annotation

GitHub Actions / Lint / Lint Code Base, Spelling, Link Check

Spread types may only be created from object types.
items: item.items.map(referenceItem => {
if (referenceItem?.label === upperFirst(NETWORK_NAMES.linea) && referenceItem?.items) {
return {
...referenceItem,
items: [
...referenceItem.items.filter(({ label }) => label !== JSON_RPC_METHODS_LABEL),
...siteConfig.customFields.dynamicData.map(dynamicItem => {
const jsonRpcCategory = referenceItem.items.find(({ label }) => label === JSON_RPC_METHODS_LABEL);
if (jsonRpcCategory) {
return {
...dynamicItem,
...{ href: "/services/reference/linea/json-rpc-methods/" },
...{ items: [...dynamicItem.items, ...jsonRpcCategory.items.filter(refItem => refItem.type === "category")] }
};
}
return dynamicItem;
})
]
};
}
return referenceItem;
})
}
}
return item;
}));
}, []);

return formattedData ? (
<Layout>
<div className={styles.pageWrapper}>
<aside className={sidebar_wrapper_classes}>
<DocSidebar sidebar={formattedData} path="" onCollapse={() => {}} isHidden={false} />
</aside>
<div className={styles.mainContainer}>
<div className={styles.contentWrapper}>
<ParserOpenRPC
network={NETWORK_NAMES.linea}
method={customData.name}
/>
</div>
</div>
</div>
</Layout>
) : null;
};

export default CustomReferencePage;
3 changes: 2 additions & 1 deletion src/components/ParserOpenRPC/RequestBox/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export default function RequestBox({
const exampleRequest = useMemo(() => {
const preparedParams = JSON.stringify(paramsData, null, 2);
const preparedShellParams = JSON.stringify(paramsData);
const NETWORK_URL = "https://linea-mainnet.infura.io";
const API_KEY = userAPIKey ? userAPIKey : "<YOUR-API-KEY>";
if (isMetamaskNetwork) {
return `await window.ethereum.request({\n "method": "${method}",\n "params": ${preparedParams.replace(/"([^"]+)":/g, '$1:')},\n});`;
Expand All @@ -59,7 +60,7 @@ export default function RequestBox({
}, [response, defExampleResponse]);

const methodsWithRequiredWalletConnection = ["eth_accounts", "eth_sendTransaction", "personal_sign", "eth_signTypedData_v4"];
const isRunAndCustomizeRequestDisabled = methodsWithRequiredWalletConnection.includes(method) ?
const isRunAndCustomizeRequestDisabled = isMetamaskNetwork && methodsWithRequiredWalletConnection.includes(method) ?
!isMetamaskInstalled :
false;

Expand Down
10 changes: 4 additions & 6 deletions src/components/ParserOpenRPC/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import React, {
useEffect,
} from "react";
import { usePluginData } from "@docusaurus/useGlobalData";
import { useLocation } from "@docusaurus/router";
import { ResponseItem, NETWORK_NAMES } from "@site/src/plugins/plugin-json-rpc";
import { ResponseItem } from "@site/src/plugins/plugin-json-rpc";
import { NETWORK_NAMES } from "@site/src/lib/constants";
import DetailsBox from "@site/src/components/ParserOpenRPC/DetailsBox";
import InteractiveBox from "@site/src/components/ParserOpenRPC/InteractiveBox";
import RequestBox from "@site/src/components/ParserOpenRPC/RequestBox";
Expand Down Expand Up @@ -49,8 +49,6 @@ export default function ParserOpenRPC({
extraContent,
}: ParserProps) {
if (!method || !network) return null;
const location = useLocation();
const { pathname } = location;
const [isModalOpen, setModalOpen] = useState(false);
const [reqResult, setReqResult] = useState(undefined);
const [paramsData, setParamsData] = useState([]);
Expand Down Expand Up @@ -301,8 +299,8 @@ export default function ParserOpenRPC({
</div>
<div className={global.colRight}>
<div className={global.stickyCol}>
{pathname.startsWith(REF_PATH) && <ProjectsBox />}
{!pathname.startsWith(REF_PATH) && !metaMaskAccount && (
{!isMetamaskNetwork && <ProjectsBox />}
{isMetamaskNetwork && !metaMaskAccount && (
<AuthBox isMetamaskNetwork={isMetamaskNetwork} />
)}
<RequestBox
Expand Down
29 changes: 29 additions & 0 deletions src/helpers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { RPC_NETWORK_URL } from "../lib/constants";

export const fetchAndGenerateDynamicSidebarItems = async (networkName) => {
try {
const response = await fetch(`${RPC_NETWORK_URL}/${networkName}`);
const data = await response.json();
const dynamicItems = data.methods.map((method) => ({
type: "link",
label: method.name,
href: `/services/reference/linea/json-rpc-methods/${method.name}`,
})).sort((a, b) => a.label.localeCompare(b.label));
return [
{
type: "category",
label: "JSON-RPC methods",
link: {
type: 'generated-index',
slug: "/services/reference/linea/json-rpc-methods/"
},
collapsed: true,
collapsible: true,
items: dynamicItems,
},
];
} catch (error) {
console.error("Error fetching methods:", error);
return [];
}
}
Loading
Loading