CoinbaseWalletSDK does not work with server side rendering - javascript

I was trying to load CoinbaseWalletSDK in my NextJS application, but it always throw an error of ReferenceError: localStorage is not defined due to it was imported before the window is loaded. I tried dynamic loading but it doesn't work. The following is what I am using at this moment.
export async function getServerSideProps({
params,
}: {
params: { project_id: string };
}) {
const project_id = params.project_id;
let project: any = fakeProjects[0];
if (project_id && typeof project_id === 'string' && !isNaN(parseInt(project_id))) {
const id = project_id;
project = fakeProjects.find(p => p.id === parseInt(id));
// Fetch project detail here
let item = await (
getNFTStatsByProjectId(
parseInt(project_id)
)
);
if (project && item && item['nftTotal'] && item['nftSold']) {
if (item.nftSold > item.nftTotal) {
item.nftSold = item.nftTotal;
}
project.nftTotal = item.nftTotal;
project.nftSold = item.nftSold;
}
}
const { coinbaseEth } = (await import('../../components/services/coinbase'));
return {
props: {
project: project,
coinbaseEth: coinbaseEth
},
};
}
And this is what I have in the coinbase service:
// TypeScript
import CoinbaseWalletSDK from '#coinbase/wallet-sdk'
import Web3 from 'web3'
const APP_NAME = 'Practice App'
const APP_LOGO_URL = process.env.WEBSITE_URL + '/logo.png'
const DEFAULT_ETH_JSONRPC_URL = 'https://mainnet.infura.io/v3/' + process.env.INFURA_PROJECT_ID
const DEFAULT_CHAIN_ID = 1
// Initialize Coinbase Wallet SDK
export const coinbaseWallet = new CoinbaseWalletSDK({
appName: APP_NAME,
appLogoUrl: APP_LOGO_URL,
darkMode: false
})
// Initialize a Web3 Provider object
export const coinbaseEth = coinbaseWallet.makeWeb3Provider(DEFAULT_ETH_JSONRPC_URL, DEFAULT_CHAIN_ID)
// Initialize a Web3 object
export const web3 = new Web3(coinbaseEth as any)
The new CoinbaseWalletSDK is where the error was thrown if that's a concern.
Based on my research, I will need to get it imported after the page is fully loaded (which is the point when "window" become available, as well as "localStorage"), which I have no clue how to achieve. Can anyone help me out on this?

I solved it by loading it later. What I did was to assign this variable with this function.
setTimeout(async () => {
coinbaseEth = (await import('../../components/services/coinbase')).coinbaseEth;
}, 1000)
I choose not to use useEffect because the value will be lost on render, which prevents the function to work properly.

Related

How to display Metaplex NFT in React using #metaplex/js and programs.metadata.Metadata

I am trying to run this 'getting started' example from the docs. However I think there's been a change and programs.metadata.Metadata (shown there) no longer works.
https://docs.metaplex.com/sdk/js/getting-started
They suggest this:
import { Connection, programs } from '#metaplex/js';
const connection = new Connection('devnet');
const tokenPublicKey = 'Gz3vYbpsB2agTsAwedtvtTkQ1CG9vsioqLW3r9ecNpvZ';
const run = async () => {
try {
const ownedMetadata = await programs.metadata.Metadata.load(connection, tokenPublicKey);
console.log(ownedMetadata);
} catch {
console.log('Failed to fetch metadata');
}
};
run();
I have this in my React app:
import { Connection, programs } from '#metaplex/js';
const connection = new Connection('devnet');
const tokenPublicKey = 'Gz3vYbpsB2agTsAwedtvtTkQ1CG9vsioqLW3r9ecNpvZ';
const run = async () => {
try {
const ownedMetadata = await programs.metadata.Metadata.load(connection, tokenPublicKey);
console.log(ownedMetadata);
} catch(error) {
console.log('Failed to fetch metadata');
console.log(error);
}
};
function App() {
return (
<div className="App">
<p onClick={run}>would be cool if it worked</p>
</div>
);
}
export default App;
I get an error as though programs.metadata.Metadata doesn't exist - "Cannot read properties of undefined (reading 'Metadata')". I even took this out of React and did a plain node script to just run the example code, which fails in the same way.
Any idea on how I could fix this?
For users that find this question via search:
There is now a better solution than using #metaplex-foundation/mpl-token-metadata directly. Have a look at #metaplex/js-next findByMint
Those lines should be all you need. imageUrl would be the image path.
import { Metaplex } from "#metaplex-foundation/js-next";
import { Connection, clusterApiUrl } from "#solana/web3.js";
const connection = new Connection(clusterApiUrl("mainnet-beta"));
const metaplex = new Metaplex(connection);
const mint = new PublicKey("ATe3DymKZadrUoqAMn7HSpraxE4gB88uo1L9zLGmzJeL");
const nft = await metaplex.nfts().findByMint(mint);
const imageUrl = nft.metadata.image;

Context is undefined in translation module

I tried to add a call to an endpoint in order to get translation. I have like this :
const loadLocales = async () => {
const context = require.context('./locales', true);
const data = await ApiService.post(`${translationToolUrl}/gateway/translations`, { project: 'myProject' });
const messages = context.keys()
.map((key) => ({ key, locale: key.match(/[-a-z0-9_]+/i)[0] }))
.reduce((msgs, { key, locale }) => ({
...msgs,
[locale]: extendMessages(context(key)),
}), {});
return { context, messages };
};
const { context, messages } = loadLocales();
i18n = new VueI18n({
locale: 'en',
fallbackLocale: 'en',
silentFallbackWarn: true,
messages,
});
if (module.hot) {
module.hot.accept(context.id, () => {
const { messages: newMessages } = loadLocales();
Object.keys(newMessages)
.filter((locale) => messages[locale] !== extendMessages(newMessages[locale]))
.forEach((locale) => {
const msgs = extendMessages(newMessages[locale]);
messages[locale] = msgs;
i18n.setLocaleMessage(locale, msgs);
});
});
}
I added this request : ApiService.post. But I have the error TypeError: context is undefined droped at this line module.hot.accept(context.id.... Have you an idea how I can solve that ? My scope was to add this request in order to get translations from database and from .json files for now. I want to do a merge between both for now, in the feature I will get only from database but this will be done step by step.
The problem is, that you trying to declare multiple const in the wrong way, independently of trying to declaring them twice. This shows in:
const { context, messages } = loadLocales();
This would couse context and messages to be undefined. This won´t give an error, as I replicated in a small example:
const {first, second} = 'Testing'
console.log(first)
console.log(second)
Both, first and second, will be undefined. If you try to declare multiple const at once, you need to do it this way:
const context = loadLocales(), messages = loadLocales();

Getting the Site URL in Next.JS [duplicate]

I want to get the page's full URL or site hostname like the image below on Static Site Generator.
I will try with window.location.hostname, but it doesn't work.
The error: window not defined.
If you want the hostname inside getInitialProps on server side, still you can get it from req
Home.getInitialProps = async(context) => {
const { req, query, res, asPath, pathname } = context;
if (req) {
let host = req.headers.host // will give you localhost:3000
}
}
With server-side rendering (getServerSideProps), you can use context.req.headers.host:
import type { GetServerSideProps, NextPage } from "next";
type Props = { host: string | null };
export const getServerSideProps: GetServerSideProps<Props> =
async context => ({ props: { host: context.req.headers.host || null } });
const Page: NextPage<Props> = ({ host }) => <p>Welcome to {host || "unknown host"}!</p>;
export default Page;
But with static generation (getStaticProps), the hostname is not available, because there is no request to get it from. In general, a server doesn't know its own public hostname, so you need to tell it. Using Next.js environment variables, put this in .env.local:
HOST=example.com
Then access it with process.env['HOST']:
import type { GetStaticProps } from "next";
export const getStaticProps: GetStaticProps<Props> =
async context => ({ props: { host: process.env['HOST'] || null }});
If you want to get the full URL:
import { useRouter } from 'next/router';
const { asPath } = useRouter();
const origin =
typeof window !== 'undefined' && window.location.origin
? window.location.origin
: '';
const URL = `${origin}${asPath}`;
console.log(URL);
The place where you are accessing the window make sure you add a check so that code is executed only on the browser and no during SSG"
if (typeof window !== 'undefined') {
const hostname = window.location.hostname;
}
Update:
If you have specified basePath in next.config.js:
module.exports = {
basePath: 'https://www.example.com/docs',
}
Then using useRouter, you can access the base path:
import { useRouter } from 'next/router'
function Component() {
const router = useRouter();
console.log({ basePath: router.basePath});
// { basePath: 'https://www.example.com/docs' }
...
}
But if you have a relative base path then you can use the first approach
Consider this package > next-absolute-url
import absoluteUrl from 'next-absolute-url'
const { origin } = absoluteUrl(req)
const apiURL = `${origin}/api/job.js`
If you deployed your Next.js app with now the apiURL will be something like https://your-app.now.sh/api/job.js.
However, if you are running the app locally the apiURL will be http://localhost:8000/api/job.js instead.
Using typeof window !== 'undefined' is the secure way. if (window) {} will run you into problems.
const hostname = typeof window !== 'undefined' && window.location.hostname ? window.location.hostname : '';
const origin = typeof window !== 'undefined' && window.location.origin ? window.location.origin : '';
Using above code will give you the frontend/outside hostname/origin the client using: example.com, www.example.com, www.example.com:80 and so on, not the localhost stuff. useRouter() will return the server side hostname/origin (localhost, localhost:3000)
I believe you're better of doing this with a combination of useRouter and useEffect hooks. In my case I wanted to dynamically set the og:url of my webpage. This is what I did. We have router.pathname as a dependency so that ogUrl is updated every time we move to a different page.
import { useRouter } from "next/router";
import { useState, useEffect } from "react";
const MyComponent = () => {
const router = useRouter();
const [ogUrl, setOgUrl] = useState("");
useEffect(() => {
const host = window.location.host;
const baseUrl = `https://${host}`;
setOgUrl(`${baseUrl}${router.pathname}`);
}, [router.pathname]);
return <div></div>
}
You need to ensure your access to window.location.hostname happens on the client-side only, and not during server-side rendering (where window does not exist). You can achieve that by moving it to a useEffect callback in your component.
function Component() {
useEffect(() => {
console.log(window.location.hostname)
console.log(window.location.href) // Logs `http://localhost:3000/blog/incididunt-ut-lobare-et-dolore`
}, [])
// Remaining code of the component
}
req.headers are Symbols and not Objects, so to get value, you use the get method
const host = req.headers.get("host"); // stackoverflow.com
AFAIK there are two ways of doing this:
Next JS provides us with the useRouter hook, first you have to import it in your component, then, to use the router object, you just have to declare it. For example:
const router = useRouter();
console.log(router.pathname);
const {pathname} = router; <---- To access the pathname directly.
Besides this, as #Xairoo said before, if you want to use the window object, you have to check if window !== 'undefined' to avoid errors. The window not defined error happens because Next JS use NodeJS to render the app and the window object is not defined in Node JS.
You can find a more detailed explanation in this link.
none oh the answers above solved the problem and this is the solution i figured it out :
function return_url(context) {
if (process.env.NODE_ENV === "production") {
// if you are hosting a http website use http instead of https
return `https://${context.req.rawHeaders[1]}`;
} else if (process.env.NODE_ENV !== "production") {
return "http://localhost:3000";
}
}
and on the getServerSideProps or getStaticProps functions you use
export async function getServerSideProps(context) {
let url = return_url(context);
const data = await fetch(`${url}/yourEndPoint`).then((res) => res.json());
return {
props: {
data: data,
},
};
}
Using a middleware.js file that you add to the root of your project can give you access to the host name and provide a lot of flexibility to perform actions based on it if needed.
https://nextjs.org/docs/advanced-features/middleware
// Example: redirecting a domain to a subdomain
import { NextResponse } from "next/server";
// This function can be marked `async` if using `await` inside
export function middleware(request) {
// Currently there is no main site so we redirect to the subdomain.
const host = request.headers.get("Host");
if (
process.env.NODE_ENV === "production" &&
host.startsWith("mydomain.com")
) {
return NextResponse.redirect(new URL("https://mysubdomain.mydomain.com"));
} else if (
process.env.NODE_ENV === "staging" &&
host.startsWith("staging.mydomain.com")
) {
return NextResponse.redirect(
new URL("https://mysubdomain-staging.mydomain.com")
);
}
}
in Next.js you can do like this,
by useEffect to get window.location.origin in client side,
and set it to state.
work fine in :
{
"next": "12.1.6",
"react": "18.1.0",
}
const Home: NextPage = () => {
const { asPath, query } = useRouter();
const [loading, setLoading] = useState(false);
const [loginCallBackURL, setLoginCallBackURL] = useState("");
useEffect(() => {
setLoginCallBackURL(
`${window.location.origin}/${query.redirect ? query.redirect : "user"}`,
);
}, []);
// if you do something like this, it can't get loginCallBackURL
// const loginCallBackURL = useMemo(() => {
// if (typeof window !== 'undefined') {
// return `${window.location.origin}/${
// query.redirect ? query.redirect : "user"
// }`;
// }
// return asPath;
// }, [asPath, query]);
return (
<div>
<Button
variant="contained"
href={queryString.stringifyUrl({
url: `${publicRuntimeConfig.API_HOST}/auth/google/login`,
query: {
callbackURL: loginCallBackURL,
},
})}
>
Sign in with google
</Button>
</div>
);
};
export default Home;
We can get current url like this:
import { useRouter } from 'next/router';
const router = useRouter();
const origin = typeof window !== 'undefined' && window.location.origin ? window.location.origin : '';
const address_url = origin+router.asPath;

Web3 error: Duplicated method constructor. This method is defined as RPC call and as Object method

Having the following issue when deploying my web3 inside of VueX:
TypeError: Duplicated method constructor. This method is defined as RPC call and as Object method.
I have deduced that the issue is in the declaration of web3 in setupWeb3 Action (see below).
I am assuming it is some typing issue?
I am declaring a global namespace in my main.ts as follows:
declare global {
interface Window {
ethereum:any;
web3:any;
}
}
Here are my Actions:
export const actions: ActionTree<Network, RootState> = {
setupWeb3(context: ActionContext<Network, RootState>) {
let web3;
if (window.ethereum) {
web3 = new Web3(window.ethereum);
window.ethereum.enable().then(enabled => console.log(enabled));
} else if (window.web3) {
web3 = new Web3(window.web3.currentProvider);
} else {
// TODO better handle of metamask
window.alert(
'Non-Ethereum browser detected. You should consider trying MetaMask!',
);
}
context.commit('SET_WEB3', web3);
},
async getNetworkData(context: ActionContext<Network, RootState>) {
const { Web3 } = context.getters;
const network = await Web3.eth.net.getNetworkType();
const networkId = await Web3.eth.net.getId();
const currentBlock = await Web3.eth.getBlockNumber();
context.commit('SET_NETWORK_DATA', {
network,
networkId,
currentBlock,
});
},
getAddress(context: ActionContext<Network, RootState>) {
const { Web3 } = context.getters;
Web3.eth.getAccounts().then((account: string[]) =>
context.commit("SET_ADDRESS", account[0]));
},
bootstrapContracts(context: ActionContext<Network, RootState>) {
const setupWeb3 = context.dispatch('setupWeb3');
const network = context.dispatch('getNetworkData');
const address = context.dispatch('getAddress');
Promise.all([setupWeb3, network, address,])
.then(() => {
context.dispatch('setupLootControls');
});
},
};
Turns out that I was on a beta release and by upgrading to
"web3": "^1.2.9"
Fixed the error for me.

Ember FastBoot SimpleDOM body is empty

I'm trying to read from the DOM in an instance initializer in the FastBoot environment but document.body seems to be empty. The following code works in the browser but returns nothing in FastBoot:
const ELEMENT_NODE_TYPE = 1;
// TODO(maros): Get this working with FastBoot. For some reason the SimpleDOM
// body is empty.
const findBootstrapData = (document) => {
for (let currentNode = document.body.firstChild; currentNode; currentNode = currentNode.nextSibling) {
if (currentNode.nodeType !== ELEMENT_NODE_TYPE) {
continue;
}
if (currentNode.getAttribute('class') === 'bootstrap-data') {
return JSON.parse(currentNode.firstChild.nodeValue);
}
}
};
export function initialize(appInstance) {
const store = appInstance.lookup('service:store');
const document = appInstance.lookup('service:-document');
const data = findBootstrapData(document);
if (data) {
store.push({ data: data.posts });
}
}
export default {
name: 'blog-posts',
initialize
};
The data that I'm trying to read has been injected into the {{content-for "body"}} section using an Ember CLI addon. This works perfectly without FastBoot.
How can I get this instance initializer working in FastBoot?
Edit:
For extra context, here's how I'm populating the DOM using an Ember CLI Addon:
/* eslint-env node */
'use strict';
const fs = require('fs');
const path = require('path');
const convertMarkdown = require('marked');
const parseFrontMatter = require('front-matter');
// Reads blog posts from `/posts`, compiles from markdown to HTML and stores
// as JSON in a script tag in the document. An instance initializer then picks
// it up and hydrates the store with it.
module.exports = {
name: 'blog-posts',
contentFor: function(type) {
if (type !== 'body') {
return;
}
const dirname = path.join(__dirname, '..', '..', 'posts');
const data = fs.readdirSync(dirname).map((filename, index) => {
if (!filename.endsWith('.md')) {
return;
}
const fileContent = fs.readFileSync(path.join(dirname, filename), 'utf-8');
const frontMatter = parseFrontMatter(fileContent);
return {
id: index + 1,
type: 'post',
attributes: {
title: frontMatter.attributes.title,
body: convertMarkdown(frontMatter.body),
},
};
});
return `
<script class="bootstrap-data" type="text/template">
${JSON.stringify({ posts: data })}
</script>
`;
}
};
document.body will always be empty during the app boot lifecycle. Since the rendering is not done yet (during the instance initializer phase), the fastboot placeholders aren't replaced.
You could do either of the following:
You could use the shoebox API to push and get the fastboot bootstrap data : http://ember-fastboot.com/docs/user-guide#the-shoebox
If you want to do it outside the app lifecycle, then you could do it after fastboot.visit request is done and after result.html() is called.

Categories

Resources