How to check if Metamask is connected after page refreshing - javascript

My dApp have to connect to MetaMask. There are two rude solutions in the docs: make user to click connect btn every time manually or just pop up connection confirmation after page load. I want to implement the only convenient solution: first time user connect manually by clicking the connect btn and interacting with MetaMask popup and then my dApp detect that connection is still established and use this connection. I can't find the solution, but i saw this in other dApps (Capture the ether for example) I use:
import detectEthereumProvider from '#metamask/detect-provider';
const provider = await detectEthereumProvider();
if (provider) {
connect(provider)
} else {
// kind of "Install the MetaMask please!"
}
function connect(provider) {
// How to check if the connection is here
if (//connection established) {
// Show the user connected account address
} else {
// Connect
provider.request({ method: "eth_requestAccounts" })
.then // some logic
}
}

I finally found a possible solution and it turned out to be as simple as it should be. There is an eth_accounts method in Ethereum JSON-RPC which allow us to ask for available accounts without actually requesting them. This way we can check if metamask is still connected (if there are any accounts) and avoid auto requesting or need of manually clicking "connect" every time. Simple example implementation could be:
// detect provider using #metamask/detect-provider
detectEthereumProvider().then((provider) => {
if (provider && provider.isMetaMask) {
provider.on('accountsChanged', handleAccountsChanged);
// connect btn is initially disabled
$('#connect-btn').addEventListener('click', connect);
checkConnection();
} else {
console.log('Please install MetaMask!');
}
});
function connect() {
ethereum
.request({ method: 'eth_requestAccounts' })
.then(handleAccountsChanged)
.catch((err) => {
if (err.code === 4001) {
console.log('Please connect to MetaMask.');
} else {
console.error(err);
}
});
}
function checkConnection() {
ethereum.request({ method: 'eth_accounts' }).then(handleAccountsChanged).catch(console.error);
}
function handleAccountsChanged(accounts) {
console.log(accounts);
if (accounts.length === 0) {
$('#connection-status').innerText = "You're not connected to MetaMask";
$('#connect-btn').disabled = false;
} else if (accounts[0] !== currentAccount) {
currentAccount = accounts[0];
$('#connection-status').innerText = `Address: ${currentAccount}`;
$('#connect-btn').disabled = true;
}
}

Use window.onload to initiate the isConnected() function when the webpage is loaded. The browser console will return a wallet address if it is connected.
window.onload = (event) => {
isConnected();
};
async function isConnected() {
const accounts = await ethereum.request({method: 'eth_accounts'});
if (accounts.length) {
console.log(`You're connected to: ${accounts[0]}`);
} else {
console.log("Metamask is not connected");
}
}

I assume you have already found Metamask docs on Ethereum Provider API. This section specifies three steps you need to do to make your app work:
Detect the Ethereum provider (window.ethereum)
Detect which Ethereum network the user is connected to
Get the user's Ethereum account(s)
Your snippet does the first part - it detects the provider.
As per this section, to detect network you can use the following code
const chainId = await ethereum.request({ method: 'eth_chainId' });
handleChainChanged(chainId);
ethereum.on('chainChanged', handleChainChanged);
function handleChainChanged(_chainId) {
window.location.reload();
}
And the most crucial part - fetching user account.
let currentAccount = null;
function handleAccountsChanged(accounts) {
if (accounts.length === 0) {
console.log('Please connect to MetaMask.');
} else if (accounts[0] !== currentAccount) {
currentAccount = accounts[0];
}
}
document.getElementById('connectButton', connect);
function connect() {
ethereum
.request({ method: 'eth_requestAccounts' })
.then(handleAccountsChanged)
.catch((err) => {
if (err.code === 4001) {
console.log('Please connect to MetaMask.');
} else {
console.error(err);
}
});
After the user logs in the first time, Metamask won't show the pop-up next time.

I think it's help you. In some case you noticedethereum.window.once('connect',()=>{}) is not worked and then disconnect event too.. i also face this problem and i don't know how to get userAccount address automatically after refresh so i started research on many youtube video and metamask api document. finally i got the answer.
import React, {useState,useEffect} from 'react';
import { ethers} from 'ethers';
function App(){
let [userAccount,setUserAccount] = useState({
isConnect:false,
Account:""
})
let isItConnect = async()=>{
let provider = new ethers.providers.Web3Provider(window.ethereum);
let accounts = await provider.send("eth_requestAccounts",[]);
console.log(accounts.length)
if(accounts.length>0){
return {
status:true,
userAddress:accounts[0]
}
}
else{
return {
status:false,
userAddress:""
}
}
}
let connect = async()=>{
let Status = await isItConnect();
localStorage.setItem('isConnected',Status.status)
setUserAccount((prev)=>{
return {...prev,Account:Status.userAddress}
})
}
window.ethereum.on('accountsChanged',async()=>{
localStorage.removeItem('isConnected');
setUserAccount((prev)=>{
return {...prev,Account:""}
})
connect()
})
useEffect(()=>{
let status = localStorage.getItem('isConnected')
if(status){
connect()
}
if(status === null){
if(window.ethereum.selectedAddress === null){
console.log('welcome User!')
}
else{
connect()
}
}
},[])
return (
<>
{userAccount.Account===""&& <button onClick={connect}>Connect Metamask!
</button>}
{userAccount.Account !==""&& <>
<p>{userAccount.Account}</p>
<p>Connected</p>
</>
)
}

Try using window.ethereum._state.account it will show array of accounts if connected else it will show an empty array, and use the length property to further access if connected to metamask or not.

This would get you the wallet address. returns false if not connected.
const getAccount = async () => await window.ethereum.request({method: 'eth_accounts'})[0] || false;
basic call from the DOM:
window.onload = event => {
const account = getAccount();
console.log(account ? `You're connected to: ${accounts}` : 'Metamask is not connected');
};
if using react:
componentDidMount() {
const account = getAccount();
console.log(account ? `You're connected to: ${accounts}` : 'Metamask is not connected');
}

Related

How can make my Program wait for Firebase's data before executing a function?

I'm a beginner in Next.js and Firebase. I was trying to make a log in system that has roles. I used Firebase Authentication and stored the account's roles on firestore. I connected the authentication account and firestore data by using the UID (from authentication) as the Firestore Document ID.
Ps. I'm not sure if this is the best way to do this but I am able to fetch data from a field by looking for the Document ID fetched using the UID.
What I need to happen is to get the role of the Account from the Firestore and use that for a function to push the correct page for that account type. The problem is, my pushToPage() fires first before my getData() gets the information it asks for from the firestore.
This is the LogIn function. I used auth.onAuthStateChanged to get the UID of the user.
var firebaseDocument = ' ';
var accountType = '';
var databaseRef = '';
function LogIn() {
signInWithEmailAndPassword(auth, email, password)
.then((response) => {
sessionStorage.setItem('Token', response.user.accessToken);
auth.onAuthStateChanged((user) => {
if (user) {
firebaseDocument = user.uid;
databaseRef = doc(database, 'users', firebaseDocument);
} else {
console.log('user logged out');
}
});
})
.catch((err) => {
console.log(err.code);
});
accountType = getData();
pushToPage(accountType);
}
This is the getData function where it fetches the account role
const getData = async () => {
try {
const docSnap = await getDoc(databaseRef);
if (docSnap.exists()) {
return docSnap.data().account_type;
} else {
console.log('Document does not exist');
}
} catch (e) {
console.log(e);
}
};
this is the pushToPage function that reads the var accountType value to decide on what page to go.
const pushToPage = (accountType) => {
if (accountType == 'management') {
router.push('/accounts/management');
} else if (accountType == 'dean') {
router.push('/accounts/deans');
} else {
router.push('/accounts/faculty');
}
};
Running this code does not only make my Program wait for the firebase response but also displays this error
Unhandled Runtime Error
FirebaseError: Expected first argument to collection() to be a CollectionReference, a DocumentReference or FirebaseFirestore
I didn't use collection() though.
Also, I searched for similar problems and I think I need to place a .then() function somewhere or somekind of Asynchronous Programming technique. Unfortunately, I struggle in understanding these.
I hope I can find help here. Thanks guys.
I don't think I need the .then() function anymore. I just called the pushToPage() inside the getData.
const getData = async () => {
try {
const docSnap = await getDoc(databaseRef);
if (docSnap.exists()) {
accountType = docSnap.data().account_type;
pushToPage(accountType);
} else {
console.log('Document does not exist');
}
} catch (e) {
console.log(e);
}
};

issue when interacting with Trust Wallet

I'm developing a dApp using web3 library. In some case the user has to decide how they want to connect, either MetaMask or WalletConnect.
So, when the user decides to connect with WalletConnect there is no problem with the connection itself but I have an issue when I want to interact with Smart Contract. Suppose I want to approve to user amount for stake or check the balance.
export const schooseProvider = provider => {
// debugger
console.log("schooseProvider: ", provider );
if(provider === "MetaMask"){
console.log("Selected Provider: ", window.ethereum)
W3 = new Web3(window.ethereum)
}
else if(provider === "WalletConnect"){
const provider = new WalletConnectProvider({
infuraId: '20c6beb49cd1402db84120a858bc74af',
bridge: 'https://bridge.walletconnect.org',
supportedChainIds,
rpc: {
3: 'https://ropsten.infura.io/v3/20c6beb49cd1402db84120a858bc74af'
}
})
console.log("Selected Provider: ", provider)
W3 = new Web3(provider)
}
}
export const approve = async (account) => {
try{
store.dispatch(updateAproveButtonsLoader(true))
await contract.methods.approve(stakeAddress,'10000000000000000000000000000000000000000000000000').send({from: account})
.once('receipt', function(receipt){
store.dispatch(updateApproveButtonsLoader(false))
store.dispatch(updateApproved(true))
checkAllowence(account)
})
.on('error', () => {
store.dispatch(updateApproveButtonsLoader(false))
})
}
catch(error){
store.dispatch(updateApproveButtonsLoader(false))
console.log(error)
}
}
Trust Wallet doesn't support testnets for now, however you can add it as custom chain

Non-Ethereum browser detected. You should consider trying MetaMask

Im having a simple UI where i need a metamask connect button
but when i use this code i keep getting the
"Non-Ethereum browser detected. You should consider trying MetaMask!" error even though i have metamsk up an running in my browser
This is the code here:
window.addEventListener('load', async () => {
// Modern dapp browsers...
if (window.ethereum) {
window.web3 = new Web3(ethereum);
try {
await ethereum.enable();
var accounts= await web3.eth.getAccounts();
var option={from: accounts[0] };
} catch (error) {
// User denied account access...
}
}
// Legacy dapp browsers...
else if (window.web3) {
window.web3 = new Web3(web3.currentProvider);
// Acccounts always exposed
web3.eth.sendTransaction({/* ... */});
}
// Non-dapp browsers...
else {
console.log('Non-Ethereum browser detected. You should consider trying MetaMask!');
}
const ethereumButton = document.querySelector('.enableEthereumButton');
const showAccount = document.querySelector('.showAccount');
ethereumButton.addEventListener('click', () => {
getAccount();
});
async function getAccount() {
const accounts = await ethereum.request({ method: 'eth_requestAccounts' });
const account = accounts[0];
showAccount.innerHTML = account;
};
and this are the 2 buttons for account and to connect
<button class="enableEthereumButton">Enable Ethereum</button>
<h2>Account: <span class="showAccount"></span></h2>
What do I need to do to make this work, i followed the metamask tutorial but they are written so bad, they are almost useless
You dont need to use window for metamask access. You can try replacing your if with somthing like this.
if (window.ethereum) {
const web3 = new Web3(ethereum);
try {
await ethereum.enable();
var accounts = await web3.eth.getAccounts();
console.log(accounts)
} catch (error) {
// User denied account access...
}
}
The getAccount() function isn't neccesary as you should be able to pull everything from the web3.eth.* anyway.

sendMessage() of react-native-firebase/messaging doesn't work in version 6.2.0

I have used react-native-firebase version 6.2.0
When I use react-native-firebase/messaging, I found out any sendMessage() function doesn’t work.
(I use android devices and virtual machine.)
I just follow the document here
At first, I registered remoteNotification and got FCM token from it. => init()
Then, I sent upstream remoteMessage => sendMessage()
But, I could not find out where are the messages. => could not receive any messages in device and in firebase cloud messaging console.
When I sent messages from firebase cloud messaging console, I could got the message at devices.
import messaging from '#react-native-firebase/messaging';
import firestore from '#react-native-firebase/firestore';
import store from 'project3/redux/store.js';
export async function init() {
const enabled = await messaging().hasPermission();
if (enabled) {
console.log('user has permissions');
} else {
console.log('user doesnt have permission');
const enabled2 = await messaging().requestPermission();
if (enabled2) {
console.log('requestPermission');
} else {
console.log('not requestPermission');
}
}
console.log('getToken');
await messaging().registerForRemoteNotifications();
const fcmToken = await messaging().getToken();
const uid = store.getState().email;
console.log('fmcToken : ' + fcmToken);
await firestore()
.doc(`users/${uid}`)
.update({
fcmToken: fcmToken,
});
console.log(
'isRegisteredForRemoteNotifications ' +
messaging().isRegisteredForRemoteNotifications,
);
messaging().onMessage(async remoteMessage => {
console.log('FCM Message Data:', remoteMessage.data);
});
messaging().onSendError(event => {
console.log(event.messageId);
console.log(event.error);
});
}
export async function sendMessage() {
console.log('sendMessage');
await messaging()
.sendMessage({
data: {
loggedIn: Date.now().toString(),
uid: store.getState().email,
},
})
.then(msg => {
console.log(msg);
});
}
Please help me.
I found many cases about below version 5.x.x of react-native-firebase.
But, there are very few cases about 6.x.x and guide isn't also sufficient.
You may save my weeks.

Using Firebase reauthenticate

I'll appreciate assistance with how to reauthenticate a user in Firebase. I wonder if it makes any sense adding all these great features if the documentation doesn't explain how to use it:
Currently, this is what I'm trying, and it ain't working. Errors as cannot read property 'credential' of undefined
In constructor:
constructor(#Inject(FirebaseApp) firebaseApp: any) {
this.auth = firebaseApp.auth();
console.log(this.auth);
}
then the function
changePassword(passwordData) {
if(passwordData.valid) {
console.log(passwordData.value);
// let us reauthenticate first irrespective of how long
// user's been logged in!
const user = this.auth.currentUser;
const credential = this.auth.EmailAuthProvider.credential(user.email, passwordData.value.oldpassword);
console.log(credential);
this.auth.reauthenticate(credential)
.then((_) => {
console.log('User reauthenticated');
this.auth.updatePassword(passwordData.value.newpassword)
.then((_) => {
console.log('Password changed');
})
.catch((error) => {
console.log(error);
})
})
.catch((error) => {
console.log(error);
})
}
}
The reauthenticate() method is called on a firebase.User, not on firebase.auth.Auth itself.
var user = firebase.app.auth().currentUser;
var credentials = firebase.auth.EmailAuthProvider.credential('puf#firebaseui.com', 'firebase');
user.reauthenticate(credentials);
Update (July 2017):
There are some breaking change in the 4.0 version of the Firebase Web SDK. From the release notes:
BREAKING: firebase.User.prototype.reauthenticate has been removed in favor of firebase.User.prototype.reauthenticateWithCredential.
As far as I can tell the reauthenticateWithCredentialis a drop-in replacement for the old method.
Here's some code that enabled users to (a) reauthenticate in Firebase and (b) change their passwords after reauthenticating for me. I researched for about an hour while writing this, so hopefully it saves someone a minute.
Wrote in VueJS:
changePassword() {
let self = this; // i use "self" to get around scope issues
var user = firebase.auth().currentUser;
var credential = firebase.auth.EmailAuthProvider.credential(
this.$store.state.userId, // references the user's email address
this.oldPassword
);
user.reauthenticateWithCredential(credential)
.then(function() {
// User re-authenticated.
user.updatePassword(self.newPassword)
.then(function() {
console.log("Password update successful!");
})
.catch(function(error) {
console.log(
"An error occurred while changing the password:",
error
);
});
})
.catch(function(error) {
console.log("Some kinda bug: ", error);
// An error happened.
});
Slight changes as of May 2019, see more details here. Code is as follows:
var user = firebase.auth().currentUser;
var credential = firebase.auth.EmailAuthProvider.credential(user.email, password);
// Prompt the user to re-provide their sign-in credentials
return user.reauthenticateWithCredential(credential);
Call changeEmail("new email","password") in onPressed directly to update the user email with no reauthentication required error
RaisedButton(
onPressed: () {
changeEmail(_emailController.text, _passwordController.text);
}
Future<void> changeEmail(String email, String password) async {
User user = await FirebaseAuth.instance.currentUser;
print(email);
print(password);
try {
try {
var authResult = await user.reauthenticateWithCredential(
EmailAuthProvider.getCredential(
email: user.email,
password: password,
),
);
user.updateEmail(email).then((_) {
print("Succesfull changed email");
_backthrow();
}).catchError((error) {
showAlertDialog(context, error.message);
print("email can't be changed" + error.toString());
});
return null;
} catch (e) {
print("2");
}
} catch (e) {
print(e.message);
showAlertDialog(context, e.message);
}
}
Hers a full example how to reauthenticate with Firebase
var pass = "abcdefg";
var user = firebase.auth().currentUser;
var credential = firebase.auth.EmailAuthProvider.credential(user.email, pass);
user.reauthenticateWithCredential(credential).then(() => {
console.log("Its good!");
}).catch((error) => {
console.log(error);
});
Since 2021: If you use Firebase JS API 9.x (the tree shakable version) this is the most recent way:
https://cloud.google.com/identity-platform/docs/web/reauth
With credentials
import { getAuth, reauthenticateWithCredential } from "firebase/auth";
const auth = getAuth();
const user = auth.currentUser;
// todo for you: prompt the user to re-provide their sign-in credentials
const credential = promptForCredentials();
reauthenticateWithCredential(user, credential).then(() => {
// ...
}).catch((error) => {
// ...
});
With popup
import { getAuth, reauthenticateWithPopup, OAuthProvider } from "firebase/auth";
const auth = getAuth();
// todo for you: change to appropriate provider
const provider = new OAuthProvider('apple.com');
reauthenticateWithPopup(auth.currentUser, provider)
.then((result) => {
// ...
})
.catch((error) => {
// ...
});
This is how I re-authenticate a user in Firebase:
import { getAuth, EmailAuthProvider, reauthenticateWithCredential } from "firebase/auth";
const auth = getAuth()
const reauthenticateUser = async (email, password) => {
const user = auth.currentUser;
try {
const credential = EmailAuthProvider.credential(email, password);
await reauthenticateWithCredential(user, credential)
} catch (error) {
Alert.alert("Error", "The email or password is incorrect. Please try again.")
}
}
I was getting that re-authentication error auth/requires-recent-login when saving the primary email.
I couldn't figure out how to implement that poorly documented reauthenticateWithCredential(credential) method, so, I simply logged-out the user and redirected to login page. It's a hack but It works like charm!
firebase.auth().signOut();

Categories

Resources