Is Firestore Local persistence available for Windows/ - node.js

I am running the following code:
const { initializeApp } = require('firebase-admin/app');
const { getFirestore } = require('firebase-admin/firestore');
const {firestore} = require("firebase-admin");
const QuerySnapshot = firestore.QuerySnapshot;
initializeApp()
const db = getFirestore();
const initializeListener = (collectionName) => {
console.log('called function');
const query = db.collection(collectionName);
query.onSnapshot((querySnapshot) => {
querySnapshot.docs().
console.log('snapshot received');
querySnapshot.docChanges().forEach((change) => {
console.log('doc change found');
if (change.type === "added") {
console.log("New " + collectionName, change.doc.data());
}
});
}, (erry) => {
console.log(`Encountered error: ${err}`);
});
}
initializeListener('my_collection');
If running whilst offline I don't see the 'snapshot received' message until I go online. If offline persistence should be available here, how do I access it?

You are using the Firebase Admin SDK (a wrapper around the Google Cloud backend SDK), which does not have any sort of persistence on any platform. Offline persistence is only available for the web and client SDKs provided by Firebase. As you can see from the linked documentation:
Note: Offline persistence is supported only in Android, Apple, and web apps.

Related

Correctly fetch authentication tokens server-side in a node.js React app hosted on Cloud Run

While not a front-end developer, I'm trying to set up a web app to show up a demo for a product. That app is based on the Sigma.js demo app demo repository.
You'll notice that this app relies on a graph which is hosted locally, which is loaded as:
/src/views/Root.tsx :
useEffect(() => {
fetch(`${process.env.PUBLIC_URL}/dataset.json`)
.then((res) => res.json())
.then((dataset: Dataset) => {...
// do things ....
and I wish to replace this by a call to another service which I also host on Cloud Run.
My first guess was to use the gcloud-auth-library, but I could not make it work - especially since it does not seem to support Webpack > 5 (I might be wrong here), the point here this lib introduces many problems in the app, and I thought I'd be better off trying the other way GCP suggests to handle auth tokens: by calling the Metadata server.
So I replaced the code above with:
Root.tsx :
import { getData } from "../getGraphData";
useEffect(() => {
getData()
.then((res) => res.json())
.then((dataset: Dataset) => {
// do even more things!
getGraphData.js :
import { getToken } from "./tokens";
const graphProviderUrl = '<my graph provider service URL>';
export const getData = async () => {
try {
const token = await getToken();
console.log(
"getGraphData.js :: getData : received token",
token
);
const request = await fetch(
`${graphProviderUrl}`,
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
const data = await request.json();
console.log("getGraphData.js :: getData : received graph", data);
return data;
} catch (error) {
console.log("getGraphData.js :: getData : error getting graph data", error);
return error.message;
}
};
tokens.js :
const targetAudience = '<my graph provider service base URL>'; // base URL as audience
const metadataServerAddress = "169.254.169.254"; // use this to shortcut DNS call to metadata.google.internal
export const getToken = async () => {
if (tokenExpired()) {
const token = await getValidTokenFromServer();
sessionStorage.setItem("accessToken", token.accessToken);
sessionStorage.setItem("expirationDate", newExpirationDate());
return token.accessToken;
} else {
console.log("tokens.js 11 | token not expired");
return sessionStorage.getItem("accessToken");
}
};
const newExpirationDate = () => {
var expiration = new Date();
expiration.setHours(expiration.getHours() + 1);
return expiration;
};
const tokenExpired = () => {
const now = Date.now();
const expirationDate = sessionStorage.getItem("expirationDate");
const expDate = new Date(expirationDate);
if (now > expDate.getTime()) {
return true; // token expired
}
return false; // valid token
};
const getValidTokenFromServer = async () => {
// get new token from server
try {
const request = await fetch(`http://${metadataServerAddress}/computeMetadata/v1/instance/service-accounts/default/token?audience=${targetAudience}`, {
headers: {
'Metadata-Flavor': 'Google'
}
});
const token = await request.json();
return token;
} catch (error) {
throw new Error("Issue getting new token", error.message);
}
};
I know that this kind of call will need to be done server-side. What I don't know is how to have it happen on a React + Node app. I've tried my best to integrate good practices but most questions related to this topic (request credentials through a HTTP (not HTTPS!) API call) end with answers that just say "you need to do this server-side", without providing more insight into the implementation.
There is a question with similar formulation and setting here but the single answer, no upvote and comments is a bit underwhelming. If the actual answer to the question is "you cannot ever call the metadata server from a react app and need to set up a third-party service to do so (e.g. firebase)", I'd be keen on having it said explicitly!
Please assume I have only a very superficial understanding of node.js and React!

cloud functions firebase v9 runTransaction

this is my cloud function:
const { getFirestore, runTransaction, FieldValue } = require('firebase-admin/firestore')
exports.purchasesStatistics = functions.firestore
.document('transactions/{purchaseId}')
.onUpdate((snap, context ) => {
if (snap.before.data().status === 'RECEIVED') {
return '0'
}
let purchasePaid = snap.after.data().status === 'RECEIVED' ? true : false
if (purchasePaid === false) {
return '0'
}
let allPurchase = snap.after.data()
functions.logger.log('allPurchase', allPurchase)
let ref = getFirestore().collection('statistics').doc('checkout')
return runTransaction(ref, (transaction) => {
return transaction.get(ref).then((doc) => {
functions.logger.log('documento atualizado:', doc.data())
return '0'
})
})
})
Buy, it's returning "runTransaction is not a function". What i'm doing wrong ? Didn't find proper way to use runTransaction on firebase v9
It appears that you are using the admin SDK for NodeJS. This SDK is initialized differently from the Web SDK V9 and the way you use the functions is also different. Here is a detailed guide on how to initialize the admin SDK from scratch. After trying to add or import the runTransaction() function as your sample code, I also received the same error message. I followed the get started guide in addition to the documentation example to properly use this function, by using it from the firestore object that is created:
const { initializeApp, applicationDefault, cert } = require('firebase-admin/app');
const { getFirestore } = require('firebase-admin/firestore');
const serviceAccount = require('/path-to-service-account'); //Path to your service account, depends on implementation
initializeApp({
credential: cert(serviceAccount)
});
const fireDB = getFirestore();
fireDB.runTransaction(); //Use the relevant args
This additional page contains a different example for using transactions.

How to initialize cosmosDB databases and containers in azure functions using the node sdk?

In my current implementation I have database initialization code that gets run on every function request, which is bad for performance reasons.
How to check if a container exists in cosmos DB using the node sdk?
It's best to create static connections on app initialization as described here:
https://learn.microsoft.com/en-us/azure/azure-functions/manage-connections#static-clients
but I'm having a bit of trouble with the initialization. Here is how the documentation describes it in javascript.
const cosmos = require('#azure/cosmos');
const endpoint = process.env.COSMOS_API_URL;
const key = process.env.COSMOS_API_KEY;
const { CosmosClient } = cosmos;
const client = new CosmosClient({ endpoint, key });
// All function invocations also reference the same database and container.
const container = client.database("MyDatabaseName").container("MyContainerName");
module.exports = async function (context) {
const { resources: itemArray } = await container.items.readAll().fetchAll();
context.log(itemArray);
}
The issues/questions I'm having are how do I do error handling if the database does not exist or if the container does not exist.
Do I need to separate my "createIfNotExists" logic from the functions app entirely?
If I try to run the createIfNotExists code on startup, I'm not able to do top level awaits and I have been getting promise rejections errors.
I'd like to do something like the following:
try
{
const cosmos = require('#azure/cosmos');
const endpoint = process.env.COSMOS_API_URL;
const key = process.env.COSMOS_API_KEY;
const { CosmosClient } = cosmos;
const client = new CosmosClient({ endpoint, key });
const db = await client.databases.createIfNotExists({id: "databaseId"});
const container1 = await db.container.createIfNotExists(containerDefinition1)
const container2 = await db.container.createIfNotExists(containerDefinition2)
}
catch(err)
{
handleError(err)
}
...
module.exports = async function (context) {
...
const {resources: items } = await container1.items.query(querySpec).fetchAll();
}
What's the best way to implement this? Any help is appreciated!
I think you need to handle each individually, for example
async onApplicationLoad() {
// Create DB if it doesn't exist
try {
await this.client.databases.createIfNotExists({ id: this.mDBId });
} catch (error) {
Logger.log(`Error creating database: ${error}`, 'AzureCosmosDbService');
}
// Create the containers if they don't exist
try {
await this.client.database(this.mDBId).containers.createIfNotExists({ id: this.mNoteContainerId });
await this.client.database(this.mDBId).containers.createIfNotExists({ id: this.mReportedNotesContainerId });
const iterator = this.client.database(this.mDBId).containers.readAll();
const { resources: containersList } = await iterator.fetchAll();
} catch (error) {
Logger.log(`Error creating containers: ${error}`, 'AzureCosmosDbService');
}
return;
}

how to stop cloud sql instance

I am using this gcloud command to stop my sql instance following this
gcloud sql instances patch my-sql-instance --activation-policy NEVER
how can I achieve the same in nodejs code?
is there a google library for the sql api in npm?
using googleapis
see also the api reference
const {google} = require('googleapis');
const {auth} = require('google-auth-library');
const sqladmin = google.sqladmin('v1beta4');
auth.getApplicationDefault((_err, authRes) => {
var req = {
auth: authRes.credential,
project: "my-project-id",
instance: "my-instance",
requestBody: {
"settings": {
"activationPolicy": "NEVER"
}
}
};
sqladmin.instances.patch(req, (err, res) => {
if (err) console.error(err);
if (res) console.info(res);
})
});

Not able to specify China Region for Azure Nodejs SDK

I'm trying to interact with Azure China Resources with Nodejs.
But could not find out how to authenticate the application with Azure China with nodejs SDK.
Tried #azure/arm-storage
import * as msRestNodeAuth from "#azure/ms-rest-nodeauth";
import { StorageManagementClient, StorageManagementModels, StorageManagementMappers } from "#azure/arm-storage";
const subscriptionId = process.env["AZURE_SUBSCRIPTION_ID"];
msRestNodeAuth.interactiveLogin().then((creds) => {
const client = new StorageManagementClient(creds, subscriptionId);
client.operations.list().then((result) => {
console.log("The result is:");
console.log(result);
});
}).catch((err) => {
console.error(err);
});
To connect to Azure China, you will need to specify the environment. Please see the code below:
const msRestNodeAuth = require("#azure/ms-rest-nodeauth");
const Environment = require("#azure/ms-rest-azure-env").Environment;
const mooncake = Environment.ChinaCloud;
msRestNodeAuth.interactiveLogin({
environment: mooncake
}).then((creds) => {
console.log(creds);
}).catch((err) => {
console.error(err);
});

Resources