mirror of
https://github.com/luxfi/explore.git
synced 2026-09-24 00:28:18 +00:00
Merge branch 'main' into lighthouse-report-fixes
This commit is contained in:
@@ -5,6 +5,7 @@ SENTRY_ORG=block-scout
|
||||
SENTRY_PROJECT=new-ui
|
||||
SENTRY_AUTH_TOKEN=xxx
|
||||
SENTRY_IGNORE_API_RESOLUTION_ERROR=1
|
||||
SENTRY_CSP_REPORT_URI=xxx
|
||||
NEXT_PUBLIC_BLOCKSCOUT_VERSION=xxx
|
||||
NEXT_PUBLIC_FOOTER_GITHUB_LINK=https://github.com/blockscout/blockscout
|
||||
NEXT_PUBLIC_FOOTER_TWITTER_LINK=https://www.twitter.com/blockscoutcom
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
const getCspPolicy = require('../../lib/csp/getCspPolicy');
|
||||
|
||||
async function headers() {
|
||||
return [
|
||||
{
|
||||
source: '/:path*',
|
||||
headers: [
|
||||
// security headers from here - https://nextjs.org/docs/advanced-features/security-headers
|
||||
{
|
||||
key: 'X-Frame-Options',
|
||||
value: 'SAMEORIGIN',
|
||||
},
|
||||
{
|
||||
key: 'X-Content-Type-Options',
|
||||
value: 'nosniff',
|
||||
},
|
||||
{
|
||||
key: 'Content-Security-Policy-Report-Only',
|
||||
value: getCspPolicy(),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
module.exports = headers;
|
||||
@@ -1,3 +1,4 @@
|
||||
import * as Sentry from '@sentry/nextjs';
|
||||
import type { NextApiRequest } from 'next';
|
||||
|
||||
import * as cookies from 'lib/cookies';
|
||||
@@ -7,8 +8,7 @@ export default function getUrlWithNetwork(_req: NextApiRequest, path: string) {
|
||||
const networkSubType = _req.cookies[cookies.NAMES.NETWORK_SUB_TYPE];
|
||||
|
||||
if (!networkType || !networkSubType) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`Incorrect network: NETWORK_TYPE=${ networkType } NETWORK_SUB_TYPE=${ networkSubType }`);
|
||||
Sentry.captureException(new Error('Incorrect network'), { extra: { networkType, networkSubType } });
|
||||
}
|
||||
|
||||
return `/${ networkType }/${ networkSubType }/${ path }`;
|
||||
|
||||
+11
-4
@@ -1,3 +1,5 @@
|
||||
import * as Sentry from '@sentry/nextjs';
|
||||
|
||||
export interface ErrorType<T> {
|
||||
error?: T;
|
||||
status: Response['status'];
|
||||
@@ -13,10 +15,15 @@ export default function clientFetch<Success, Error>(path: string, init?: Request
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
}),
|
||||
() => Promise.reject({
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
}),
|
||||
() => {
|
||||
const error = {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
};
|
||||
Sentry.captureException(new Error('Client fetch failed'), { extra: error, tags: { source: 'fetch' } });
|
||||
|
||||
return Promise.reject(error);
|
||||
},
|
||||
);
|
||||
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
const parseNetworkConfig = require('../networks/parseNetworkConfig');
|
||||
|
||||
const KEY_WORDS = {
|
||||
BLOB: 'blob:',
|
||||
DATA: 'data:',
|
||||
NONE: '\'none\'',
|
||||
REPORT_SAMPLE: `'report-sample'`,
|
||||
SELF: '\'self\'',
|
||||
STRICT_DYNAMIC: `'strict-dynamic'`,
|
||||
UNSAFE_INLINE: '\'unsafe-inline\'',
|
||||
UNSAFE_EVAL: '\'unsafe-eval\'',
|
||||
};
|
||||
|
||||
const MAIN_DOMAINS = [ '*.blockscout.com', 'blockscout.com' ];
|
||||
|
||||
const isDev = process.env.NODE_ENV === 'development';
|
||||
|
||||
function getNetworksExternalAssets() {
|
||||
const icons = parseNetworkConfig()
|
||||
.filter(({ icon }) => typeof icon === 'string')
|
||||
.map(({ icon }) => new URL(icon));
|
||||
|
||||
return icons;
|
||||
}
|
||||
|
||||
function makePolicyMap() {
|
||||
const networkExternalAssets = getNetworksExternalAssets();
|
||||
|
||||
return {
|
||||
'default-src': [
|
||||
KEY_WORDS.NONE,
|
||||
],
|
||||
|
||||
'connect-src': [
|
||||
KEY_WORDS.SELF,
|
||||
|
||||
// webpack hmr in safari doesn't recognize localhost as 'self' for some reason
|
||||
isDev ? 'ws://localhost:3000/_next/webpack-hmr' : '',
|
||||
|
||||
// client error monitoring
|
||||
'sentry.io', '*.sentry.io',
|
||||
],
|
||||
|
||||
'script-src': [
|
||||
KEY_WORDS.SELF,
|
||||
|
||||
// next.js generates and rebuilds source maps in dev using eval()
|
||||
// https://github.com/vercel/next.js/issues/14221#issuecomment-657258278
|
||||
isDev ? KEY_WORDS.UNSAFE_EVAL : '',
|
||||
|
||||
...MAIN_DOMAINS,
|
||||
|
||||
// hash of ColorModeScript
|
||||
'\'sha256-e7MRMmTzLsLQvIy1iizO1lXf7VWYoQ6ysj5fuUzvRwE=\'',
|
||||
],
|
||||
|
||||
'style-src': [
|
||||
KEY_WORDS.SELF,
|
||||
...MAIN_DOMAINS,
|
||||
|
||||
// google fonts
|
||||
'fonts.googleapis.com',
|
||||
|
||||
// yes, it is unsafe as it stands, but
|
||||
// - we cannot use hashes because all styles are generated dynamically
|
||||
// - we cannot use nonces since we are not following along SSR path
|
||||
// - and still there is very small damage that can be cause by CSS-based XSS-attacks
|
||||
// so we hope we are fine here till the first major incident :)
|
||||
KEY_WORDS.UNSAFE_INLINE,
|
||||
],
|
||||
|
||||
'img-src': [
|
||||
KEY_WORDS.SELF,
|
||||
KEY_WORDS.DATA,
|
||||
|
||||
...MAIN_DOMAINS,
|
||||
|
||||
// github avatars
|
||||
'avatars.githubusercontent.com',
|
||||
|
||||
// network assets
|
||||
...networkExternalAssets.map((url) => url.host),
|
||||
],
|
||||
|
||||
'font-src': [
|
||||
KEY_WORDS.DATA,
|
||||
|
||||
// google fonts
|
||||
'*.gstatic.com',
|
||||
'fonts.googleapis.com',
|
||||
],
|
||||
|
||||
'object-src': [
|
||||
KEY_WORDS.NONE,
|
||||
],
|
||||
|
||||
'base-uri': [
|
||||
KEY_WORDS.NONE,
|
||||
],
|
||||
|
||||
'report-uri': [
|
||||
process.env.SENTRY_CSP_REPORT_URI,
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function getCspPolicy() {
|
||||
const policyMap = makePolicyMap();
|
||||
|
||||
const policyString = Object.entries(policyMap)
|
||||
.map(([ key, value ]) => {
|
||||
if (!value || value.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
return [ key, value.join(' ') ].join(' ');
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join(';');
|
||||
|
||||
return policyString;
|
||||
}
|
||||
|
||||
module.exports = getCspPolicy;
|
||||
@@ -10,6 +10,8 @@ import poaSokolIcon from 'icons/networks/poa-sokol.svg';
|
||||
import poaIcon from 'icons/networks/poa.svg';
|
||||
import rskIcon from 'icons/networks/rsk.svg';
|
||||
|
||||
import parseNetworkConfig from './parseNetworkConfig';
|
||||
|
||||
// will change later when we agree how to host network icons
|
||||
const ICONS: Record<string, React.FunctionComponent<React.SVGAttributes<SVGElement>>> = {
|
||||
'xdai/mainnet': gnosisIcon,
|
||||
@@ -24,15 +26,13 @@ const ICONS: Record<string, React.FunctionComponent<React.SVGAttributes<SVGEleme
|
||||
'artis/sigma1': artisIcon,
|
||||
};
|
||||
|
||||
export const NETWORKS: Array<Network> = (() => {
|
||||
try {
|
||||
const networksFromConfig: Array<Network> = JSON.parse(process.env.NEXT_PUBLIC_SUPPORTED_NETWORKS || '[]');
|
||||
return networksFromConfig.map((network) => ({ ...network, icon: network.icon || ICONS[`${ network.type }/${ network.subType }`] }));
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
const NETWORKS: Array<Network> = (() => {
|
||||
const networksFromConfig: Array<Network> = parseNetworkConfig();
|
||||
return networksFromConfig.map((network) => ({ ...network, icon: network.icon || ICONS[`${ network.type }/${ network.subType }`] }));
|
||||
})();
|
||||
|
||||
export default NETWORKS;
|
||||
|
||||
// for easy env creation
|
||||
// const FOR_CONFIG = [
|
||||
// {
|
||||
@@ -105,13 +105,3 @@ export const NETWORKS: Array<Network> = (() => {
|
||||
// group: 'other',
|
||||
// },
|
||||
// ];
|
||||
|
||||
export const ACCOUNT_ROUTES = [ '/watchlist', '/tag_address', '/tag_transaction', '/public_tags_request', '/api_key', '/custom_abi' ];
|
||||
|
||||
export function isAccountRoute(route: string) {
|
||||
return ACCOUNT_ROUTES.includes(route);
|
||||
}
|
||||
|
||||
export function getAvailablePaths() {
|
||||
return NETWORKS.map(({ type, subType }) => ({ params: { network_type: type, network_sub_type: subType } }));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import NETWORKS from './availableNetworks';
|
||||
|
||||
export default function getAvailablePaths() {
|
||||
return NETWORKS.map(({ type, subType }) => ({ params: { network_type: type, network_sub_type: subType } }));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export const ACCOUNT_ROUTES = [ '/watchlist', '/tag_address', '/tag_transaction', '/public_tags_request', '/api_key', '/custom_abi' ];
|
||||
|
||||
export default function isAccountRoute(route: string) {
|
||||
return ACCOUNT_ROUTES.includes(route);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// should be CommonJS module since it used for next.config.js
|
||||
function parseNetworkConfig() {
|
||||
try {
|
||||
return JSON.parse(process.env.NEXT_PUBLIC_SUPPORTED_NETWORKS || '[]');
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = parseNetworkConfig;
|
||||
@@ -2,6 +2,8 @@ const { withSentryConfig } = require('@sentry/nextjs');
|
||||
const withReactSvg = require('next-react-svg');
|
||||
const path = require('path');
|
||||
|
||||
const headers = require('./configs/nextjs/headers');
|
||||
|
||||
const moduleExports = {
|
||||
include: path.resolve(__dirname, 'icons'),
|
||||
reactStrictMode: true,
|
||||
@@ -24,6 +26,7 @@ const moduleExports = {
|
||||
},
|
||||
];
|
||||
},
|
||||
headers,
|
||||
output: 'standalone',
|
||||
};
|
||||
|
||||
@@ -37,6 +40,9 @@ const sentryWebpackPluginOptions = {
|
||||
silent: true, // Suppresses all logs
|
||||
// For all available options, see:
|
||||
// https://github.com/getsentry/sentry-webpack-plugin#options.
|
||||
deploy: {
|
||||
env: process.env.VERCEL_ENV || process.env.NODE_ENV,
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = withReactSvg(withSentryConfig(moduleExports, sentryWebpackPluginOptions));
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
"@chakra-ui/theme-tools": "^2.0.2",
|
||||
"@emotion/react": "^11",
|
||||
"@emotion/styled": "^11",
|
||||
"@sentry/nextjs": "^7.11.1",
|
||||
"@sentry/nextjs": "^7.12.1",
|
||||
"@tanstack/react-query": "^4.0.10",
|
||||
"@tanstack/react-query-devtools": "^4.0.10",
|
||||
"@types/react-scroll": "^1.8.4",
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { NextPage, GetStaticPaths } from 'next';
|
||||
import Head from 'next/head';
|
||||
import React from 'react';
|
||||
|
||||
import { getAvailablePaths } from 'lib/networks';
|
||||
import getAvailablePaths from 'lib/networks/getAvailablePaths';
|
||||
import ApiKeys from 'ui/pages/ApiKeys';
|
||||
|
||||
const ApiKeysPage: NextPage = () => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { NextPage, GetStaticPaths } from 'next';
|
||||
import Head from 'next/head';
|
||||
import React from 'react';
|
||||
|
||||
import { getAvailablePaths } from 'lib/networks';
|
||||
import getAvailablePaths from 'lib/networks/getAvailablePaths';
|
||||
import CustomAbi from 'ui/pages/CustomAbi';
|
||||
|
||||
const CustomAbiPage: NextPage = () => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { NextPage, GetStaticPaths } from 'next';
|
||||
import Head from 'next/head';
|
||||
import React from 'react';
|
||||
|
||||
import { getAvailablePaths } from 'lib/networks';
|
||||
import getAvailablePaths from 'lib/networks/getAvailablePaths';
|
||||
import PublicTags from 'ui/pages/PublicTags';
|
||||
|
||||
const PublicTagsPage: NextPage = () => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { NextPage, GetStaticPaths } from 'next';
|
||||
import Head from 'next/head';
|
||||
import React from 'react';
|
||||
|
||||
import { getAvailablePaths } from 'lib/networks';
|
||||
import getAvailablePaths from 'lib/networks/getAvailablePaths';
|
||||
import PrivateTags from 'ui/pages/PrivateTags';
|
||||
|
||||
const AddressTagsPage: NextPage = () => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { NextPage, GetStaticPaths } from 'next';
|
||||
import Head from 'next/head';
|
||||
import React from 'react';
|
||||
|
||||
import { getAvailablePaths } from 'lib/networks';
|
||||
import getAvailablePaths from 'lib/networks/getAvailablePaths';
|
||||
import PrivateTags from 'ui/pages/PrivateTags';
|
||||
|
||||
const TransactionTagsPage: NextPage = () => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { NextPage, GetStaticPaths } from 'next';
|
||||
import Head from 'next/head';
|
||||
import React from 'react';
|
||||
|
||||
import { getAvailablePaths } from 'lib/networks';
|
||||
import getAvailablePaths from 'lib/networks/getAvailablePaths';
|
||||
import WatchList from 'ui/pages/Watchlist';
|
||||
|
||||
const WatchListPage: NextPage = () => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { NextPage, GetStaticPaths } from 'next';
|
||||
import Head from 'next/head';
|
||||
import React from 'react';
|
||||
|
||||
import { getAvailablePaths } from 'lib/networks';
|
||||
import getAvailablePaths from 'lib/networks/getAvailablePaths';
|
||||
import MyProfile from 'ui/pages/MyProfile';
|
||||
|
||||
const MyProfilePage: NextPage = () => {
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { NextPage, GetStaticPaths } from 'next';
|
||||
import { useRouter } from 'next/router';
|
||||
import React from 'react';
|
||||
|
||||
import { getAvailablePaths } from 'lib/networks';
|
||||
import getAvailablePaths from 'lib/networks/getAvailablePaths';
|
||||
import Page from 'ui/shared/Page/Page';
|
||||
|
||||
const Home: NextPage = () => {
|
||||
|
||||
@@ -13,10 +13,6 @@ class MyDocument extends Document {
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Raleway:ital,wght@0,500;0,600;1,400&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
</Head>
|
||||
<body>
|
||||
<ColorModeScript initialColorMode={ theme.config.initialColorMode }/>
|
||||
|
||||
@@ -5,8 +5,10 @@
|
||||
import * as Sentry from '@sentry/nextjs';
|
||||
|
||||
const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN;
|
||||
const ENV = process.env.NEXT_PUBLIC_VERCEL_ENV || process.env.NODE_ENV;
|
||||
|
||||
Sentry.init({
|
||||
environment: ENV,
|
||||
dsn: SENTRY_DSN,
|
||||
// Adjust this value in production, or use tracesSampler for greater control
|
||||
tracesSampleRate: 1.0,
|
||||
|
||||
@@ -5,8 +5,10 @@
|
||||
import * as Sentry from '@sentry/nextjs';
|
||||
|
||||
const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN;
|
||||
const ENV = process.env.VERCEL_ENV || process.env.NODE_ENV;
|
||||
|
||||
Sentry.init({
|
||||
environment: ENV,
|
||||
dsn: SENTRY_DSN,
|
||||
// Adjust this value in production, or use tracesSampler for greater control
|
||||
tracesSampleRate: 1.0,
|
||||
|
||||
@@ -4,7 +4,7 @@ import React from 'react';
|
||||
|
||||
import type { NetworkGroup } from 'types/networks';
|
||||
|
||||
import { NETWORKS } from 'lib/networks';
|
||||
import NETWORKS from 'lib/networks/availableNetworks';
|
||||
|
||||
import NetworkMenuLink from './NetworkMenuLink';
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import React from 'react';
|
||||
|
||||
import type { NetworkGroup } from 'types/networks';
|
||||
|
||||
import { NETWORKS } from 'lib/networks';
|
||||
import NETWORKS from 'lib/networks/availableNetworks';
|
||||
|
||||
import NetworkMenuLink from './NetworkMenuLink';
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { Network } from 'types/networks';
|
||||
|
||||
import checkIcon from 'icons/check.svg';
|
||||
import placeholderIcon from 'icons/networks/placeholder.svg';
|
||||
import { isAccountRoute } from 'lib/networks';
|
||||
import isAccountRoute from 'lib/networks/isAccountRoute';
|
||||
|
||||
import useColors from './useColors';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user