Cannot save “custom blob” to Firestore - ADMIN SDK - javascript

Following up to an older question I had done here now I have the same problem with the admin SDK but cannot solve this.
I am trying to save a blob to the firestore but I get:
Error: Value for argument "data" is not a valid Firestore document. Couldn't serialize object of type "Blob" (found in field data). Firestore doesn't support JavaScript objects with custom prototypes (i.e. objects that were created via the "new" operator).
Here is how I convert my custom blob to a firestore blob:
// The import
import {firestore} from "firebase";
// The function
export const parseQueue = functions.region('europe-west2').pubsub.schedule('every 1 minutes').onRun(async (context) => {
....code...
// The problem
writePromises.push(
admin.firestore()
.collection('users')
.doc(userID)
.collection('events')
.doc(<string>event.getID())
.collection('activities')
.doc(<string>activity.getID())
.collection('streams')
.doc(stream.type)
.set({
type: stream.type,
data: firestore.Blob.fromBase64String(new Buffer((Pako.gzip(JSON.stringify(stream.data), {to: 'string'})), 'binary').toString('base64')),
}))
The above crashes when I call set with the error mentioned before.
The function firestore.Blob.fromBase64String works well and I do get back a blob that is fine.
What am I doing wrong?

The "firebase" module is not part of the Admin SDK, it's the client SDK. You can't mix usage of that with firebase-admin. Stick to only symbols and APIs provided by firebase-admin if you're going to pass them as parameters to firebase-admin SDK methods. You should probably remove the module "firebase" from your project completely.
You didn't show it, but I am assuming you already imported firebase-admin like one of these:
import * as admin from 'firebase-admin'
const admin = require('firebase-admin')
Now, any time you want to use Firestore APIs, you normally go through its provided firestore object:
const firestore = admin.firestore
You are already doing this once with your call to admin.firestore().collection().... It is basically re-exporting everything from the Cloud Firestore SDK for nodejs.
The Blob object you're using with the client SDK isn't provided by the server SDK. If you want to write a byte array, you will have to use node's Buffer object directly.
doc.set({
type: stream.type,
data: new Buffer((Pako.gzip(JSON.stringify(stream.data), {to: 'string'})), 'binary')
})
Or whatever you need to do to make the correct Buffer.

Related

How to use nock in order to mock Firebase/Firestore in NodeJS?

I have a REST API which written in NodeJS. The REST API gets a file and information and then uploads the file to the drive and stores the information inside my Firebase/Firestore DB. I'm trying to write tests in order to test my REST API, so I need to mock Firestore. I already mock the google-drive using nock by tracking the HTTPS requests that are made by googleapis. Now I'm trying to do the same thing with Firestore.
I tried different methods like, for example, for authentication I tried (based on the docs):
nock('https://firestore.googleapis.com/$discovery/rest')
.persist()
.post('')
.reply(200, { 'access_token': 'abc', 'refresh_token': '123', 'expires_in': 10 })
But nock does not follow the requests. How do I use nock to track the following code? Does firebase package actually makes requests to the API?
const firebase = require("firebase");
// Required for side-effects
require("firebase/firestore");
// First code - authenatiocation
firebase.initializeApp(firebaseConfig);
db = firebase.firestore();
// ...
// Second code - Get data from DB
const snapshot = await db.collection(collection).orderBy("timestamp", "desc").get();
// ...
// Third code - upload new report
const response = await db.collection(collection).doc(document_id).set(data);
If it's not possible with nock, then it can be achieved?
It could be argued that stubbing the Firebase methods would make more sense than mocking any API calls the library might internally make. The latter assumes knowledge about the internal functioning of the library and doesn't make full use of the benefits of abstraction.
You could use sinon to stub Firebase methods. Something like,
db = firebase.firestore();
const stub = sinon.stub(db, 'collection')
.returns({
orderBy: sinon.stub().returns({
get: sinon.stub().returns(yourDocObjectHere)
}),
doc: sinon.stub().returns({
set: sinon.stub().returns(someObjectHere)
})
})
and then assert like
sinon.assert.calledWith(db.collection.orderBy, 'timestamp', 'desc');

Can you call a Solidity contract method with only Metamask?

I’m wanting to use Metamask in my app to let users pay a fixed ETH fee (plus gas) to call a method from my Solidity contract. I looked at the Metamask documentation and the eth_sendTransaction method seems close to what I need; eth_sendTransaction would certainly allow me to request ETH from a user, but the “data” parameter is a bit confusing.
The Metamask docs say:
data is optional, but used for defining smart contract creation and interaction
and
also used for specifying contract methods and their parameters.
So “data” represents my method and its parameters, but how does Metamask (or window.ethereum, rather) know the contract whose methods I’m trying to call?
Don’t you normally have to provide a contract address and ABI/JSON in order to interact with a deployed contract? In short, is it possible to do what I’ve described with just Metamask alone? Or do you have to do other client-side setups in order to call a method with eth_sendTransaction?
Edit: by the way TylerH, the answer involved using web3.js. Maybe don't edit people's posts unless you know what the hell you're talking about. Just a thought...
Yes you will need the contract abi in order to get the information you need to include in the data that you're passing to the contract. There are also a few other things that you will need to accomplish this:
First you will need to make sure you download the ethers.js, and #alch/alchemy-web3 npm libraries into your application. Secondly you will need a provider API key from a platform like Alchemy in order to communicate with the contract abi. Lastly, you will need the contract abi which can be found at the bottom of the contract section of etherscan. There is plenty of information on how to obtain these things online, so I won't go over how to configure them here.
Once you have these, you are ready for the next step.
I suggest creating this in a utilities file somewhere in your applications file system, but the idea is this:
const alchemyKey = process.env.ALCHEMY_KEY;
const CONTRACT_ADDRESS = process.env.CONTRACT_ADDRESS;
const { createAlchemyWeb3 } = require("#alch/alchemy-web3");
const web3 = createAlchemyWeb3(alchemyKey);
const contractABI = require('../contract-abi.json');
export const contract = new web3.eth.Contract(contractABI, CONTRACT_ADDRESS);
export const yourMethod = () => {
if(window.ethereum.request({method: 'eth_requestAccounts'})){
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
const address = await signer.getAddress();
const tx = {
from: address,
to: CONTRACT_ADDRESS,
value: "some wei value", // this is the value in wei to send
data: contract.methods.YOUR_CONTRACT_METHOD_HERE().encodeABI()
}
const txHash = await window.ethereum.request({
method: 'eth_sendTransaction',
params: [tx]
});
// do something with your transaction hash here
console.log({txHash});
}else{
console.log('user must connect wallet');
}
}
So the value that is populated in the data field of our transaction comes from calling the method that we are trying to invoke in our contract. This is encoded, and then we pass this information along with the rest of our transaction data.
This is a very short and brief description as to what this does, and I hope this is what you're looking for. If you need any more help I'm always available to chat on Twitter #_syndk8.

Get the list of all Modules inside the Project on ThirdWeb

I was ultimately trying to get the list of all the modules I created inside my Project over the ThirdWeb.com dashboard and filter out the NFT collections out of it. So I asked on discord and someone told me to use the getAllModulesMetadata() to do so, I did something like this
const sdk = new ThirdwebSDK();
const NftModules = sdk
.getAppModule("<Project Address>")
.getAllModuleMetadata([ModuleType.NFT]);
I also did something like this but getting the same issue.
const sdk = new ThirdwebSDK();
async function getAllModules() {
const NftModules = await sdk
.getAppModule("<Project Address>")
.getAllModuleMetadata([ModuleType.NFT]);
}
getAllModules();
Now it gives this error
Unhandled Runtime Error
Error: call revert exception (method="getAllModulesOfType(uint256)", errorArgs=null, errorName=null, errorSignature=null, reason=null, code=CALL_EXCEPTION, version=abi/5.5.0)
Is something wrong? Any help will be appreciated :)
You need to pass in an RPC url or a signer in the ThirdwebSDK() constructor before you can use modules:
An RPC URL (which means the sdk is in read-only mode, you won't be able to change state of things on-chain, only fetch it). You can either use something like alchemy or use a public RPC for that particular chain.
A valid ethers signer (which could be connected from metamask, magiclink, coinbase wallet, etc)
A valid ethers.Wallet object, which can be initialised from a private key
const sdk = new ThirdwebSDK("your_rpc_url_or_signer");
Let me know if you still face any issues!

FirebaseError: [code=invalid-argument]: Function setDoc() called with invalid data

I am using Firebase SDK's within Node-red (as specified in NPM docs they can be used for IoT devices with NODE.js).
I can use all of the CRUD methods with Firebase RealtimeDatabase.
With Firebase Firestore I can only use READ and DELETE functionality.
SET and UPDATE results in weird errors that I couldn't find answers
anywhere on the internet.
I am importing Firebase SDK's through require() inside settiings.js and functionGlobalContext so I can access them in Node-red functions:
functionGlobalContext: {
firebase: require('firebase/app'),
firebaseDatabase: require('firebase/database'),
firebaseFirestore: require('firebase/firestore'),
// os:require('os'),
// jfive:require("johnny-five"),
// j5board:require("johnny-five").Board({repl:false})
},
First I initialize my whole Firebase project with this code (and everything initializes fine without errors):
//Load data from Global contexta
const firebase = global.get('firebase');
const firebaseDatabase = global.get('firebaseDatabase');
const firebaseFirestore = global.get('firebaseFirestore');
const firebaseConfig = {
//my Firebase credentials
};
//Set up Firebase
const app = firebase.initializeApp(firebaseConfig);
const database = firebaseDatabase.getDatabase();
const firestore = firebaseFirestore.getFirestore();
//Save the database reference to Global context
global.set('app', app);
global.set('database', database);
global.set('firestore', firestore);
And here I am trying basic SET operation with Firestore:
const ft = global.get('firebaseFirestore');
const firestore = global.get('firestore');
const frankDocRef = ft.doc(firestore, "users", "frank");
await ft.setDoc(frankDocRef, {
name: "Frank",
age: 12
});
Unfortunately even though this code is ctrl+c ctrl+v from Firestore docs I get this error:
"FirebaseError: [code=invalid-argument]: Function setDoc() called with invalid data. Data must be an object, but it was: a custom Object object (found in document users/frank)"
When I use the same code inside a web app everything works fine.
There has to be something going on under the hood with Node-red
I tried creating the object using various methods and all of them resulted in the same error.
Does anybody have any idea what could be going wrong here?

Firebase user authentication trigger that writes to realtime database

exports.setupDefaultPullups = functions.auth.user()
.onCreate(
async (user) => {
const dbRef= functions.database.ref;
let vl= await (dbRef.once('value').then( (snapshot) => {
return snapshot.ref.child('userInfo/'+user.uid).set(18);
}));
return vl;
}
);
I am trying to write a trigger for some initial set-up for a new user, in Firebase. However, the above code does not work. What is wrong with it?
Basically, upon registering a new user, I would like to set up his/her property "defaultPullUps" to, say, 18, using the above path.
EDIT: I am sorry for not giving details. Initially, there was an issue with the "async" keyword, that has been fixed by updating the node.js engine. Now, I get various error messages depending on how I tweak the code. Sometimes it says "... is not a function".
EDIT': Although I agree my question is not up to par, but there is a value in it: in online documentation of Firebase authentication triggers, how to access the "main" database is not shown https://firebase.google.com/docs/functions/auth-events
EDIT'': here is the entire message:
TypeError: Cannot read property 'child' of undefined
at exports.setupDefaultPullups.functions.auth.user.onCreate.user (/srv/index.js:15:36)
at cloudFunctionNewSignature (/srv/node_modules/firebase-functions/lib/cloud-functions.js:105:23)
at /worker/worker.js:756:24
at <anonymous>
at process._tickDomainCallback (internal/process/next_tick.js:228:7)
This line makes no sense:
const dbRef= functions.database.ref;
If you want to use the Firebase Realtime Database in a Cloud Function that is triggered from another source (such as Firebase Authentication in your case), you can do so by using the Firebase Admin SDK.
For an example of this, see the Initialize Firebase SDK for Cloud Functions section of the Get started with Cloud Functions documentation. Specifically, you'll need to import and initialize is using:
// The Cloud Functions for Firebase SDK to create Cloud Functions and setup triggers.
const functions = require('firebase-functions');
// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require('firebase-admin');
admin.initializeApp();
And then can access the database from your function using:
const dbRef= admin.database().ref();

Categories