Firebase Cloud Functions Not Running - arrays

I'm getting some unexpected behavior from Firebase Cloud Functions where it seems the function below does not run. My expectation is the data in the /posts endpoint will be logged to the console. I get no errors on deploying the function.
The function is for a backend-only action that the client/user is not involved in, so a trigger based on database events or https wont work for me without setting up another server to call the endpoint.
Is there any reason why the below would not log ?
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
getScheduledPosts = () => {
admin.database().ref("/posts")
.orderByKey()
.once("value")
.then( (snapshot) => {
console.log(snapshot);
})
.catch(err => {console.log(err)});
console.log("Posts Ran")
}
// Call this function
getScheduledPosts();

You're not defining a Cloud Function at all here. Because you don't have any Cloud Functions defined, the code you've written will never run. You have to export one from your index.js, and its definition has to be built using the firebase-functions SDK. If you're trying to create a database trigger (definitely read the docs there), it looks something like this:
exports.makeUppercase = functions.database.ref('/posts/{id}')
.onWrite(event => {
// do stuff here
})
Don't try to do "one-off" work that should be run when a function is deployed. That's not how Cloud Functions works. Functions are intended to be run in response to events that occur in your project.

Related

How to call Firebase cloud function from React stream chat app

Long story short, I'm knew to Firebase and I need to call this cloud function: "ext-auth-chat-getStreamUserToken" . The trigger is a HTTPS request to: https://europe-west2-FIREBASE-USER-INFO/ext-auth-chat-getStreamUserToken
Context: I've built a messaging app using Stream.io, with hard-coded user data and a placeholder token. Here's the key bit of code:
useEffect(() => {
async function init() {
const chatClient = StreamChat.getInstance(apiKey)
await chatClient.connectUser(user, chatClient.devToken(user.id))
const channel = chatClient.channel("messaging", "war-chat", {
image:
"https://i.picsum.photos/id/1006/200/200.jpg?hmac=yv53p45TOMz8bY4ZXUVRMFMO0_6d5vGuoWtE2hJhxlc",
name: "Oli",
members: [user.id],
//chat description
})
await channel.watch()
setClient(chatClient)
}
init()
if (client) return () => client.disconnectUser()
}, [])
I have installed the 'authenticate with stream chat' extension on Firebase. This creates various cloud functions, include one which creates a new user on Stream when a new user is created on firebase.
The only thing I need to do is modify this bit of code, to integrate firebase with stream chat:
await chatClient.connectUser(user, chatClient.devToken(user.id))
I can sort the user object. but how do I call the cloud function to get the token?
Per the source code of the getStreamUserToken Cloud Function (linked on the extension's product page), it is a Callable Cloud Function.
export const getStreamUserToken = functions.handler.https.onCall((data, context) => { /* ... */ });
Calling the Cloud Function can be done using the Firebase Client SDKs as documented with examples here in the docs. Because your cloud function resides in the europe-west2 region (and not the default us-central1 region), you will need to specify it when getting an instance of the Functions builder class:
import { getFunctions, httpsCallable } from "firebase/functions";
const functions = getFunctions(undefined, "europe-west2") // undefined is passed in so that the default app is used
const getStreamUserToken = functions.callable("ext-auth-chat-getStreamUserToken");
const streamUserToken = await getStreamUserToken();
The revoke function is called in the same way.
const revokeStreamUserToken = functions.callable("ext-auth-chat-revokeStreamUserToken");
await revokeStreamUserToken();

Trying to set thirdweb token, feed that to SDK, and Receive token module array from third web. Problem- The token module is empty

I am running VisualStudio Code, Sanity Studio, and Thirdweb
When I inspect the element on localhost:3000, I am able to see the array of my imported tokens on Sanity, but NOT on Thirdweb.
Here is a snippet of my code:
const sdk = new ThirdwebSDK(
new ethers.Wallet(
process.env.NEXT_PUBLIC_METAMASK_KEY,
ethers.getDefaultProvider('https://rinkeby_xyz),
),
)
const Portfolio = () => {
const[sanityTokens, setSanityTokens] = useState([])
const[thirdWebTokens, setThirdWebTokens] = useState([])
useEffect (() => {
const getSanityAndThirdWebTokens = async () => {
const coins = await fetch("https://xyz"
)
const sanityTokens = (await coins.json()).result
setSanityTokens(sanityTokens)
setThirdWebTokens(
sanityTokens.map(token => sdk.getTokenModule(token.contractAddress))
)
}
return getSanityAndThirdWebTokens()
}, [])
console.log('Sanity', sanityTokens)
console.log('Thirdweb', thirdWebTokens)
Error Message: Unhandled Runtime Error
TypeError: sdk.getTokenModule is not a function
How do I get the ThirdWeb array to show up?
A lot has changed with thirdwebSDK. getTokenModule is no longer a valid function as of this time, instead try sdk.getToken(token.contractAddress) in the part of the code where you have sdk.getTokenModule(token.contractAddress)
On the latest versions of thirdweb SDK, this is how you initialize the SDK:
const sdk = ThirdwebSDK.fromPrivateKey(PRIVATE_KEY, "rinkeby")
With that being said, you are not referencing a PRIVATE_KEY, you have NEXT_PUBLIC_METAMASK_KEY, which you should never do, this would make it so your private key is exposed to the client and anyone visiting your page will get full access to your wallet and will be able to steal all your funds.
What you should do is instantiate the SDK with a read-only RPC, no need to pass a private key at all in this situation. You can instantiate the SDK like this:
const sdk = new ThirdwebSDK("rinkeby")
And again, getTokenModule is not a function, you need to import getToken and pass the contract address.
I hope that helps.

API caching for next JS

I'm building an app with Next.js... we have 100k+ pages and content changes daily, so using SSR and getServerSideProps.
Some of our data is coming from a headless CMS provider that charges by the request. I'd like to cache the API responses from this server for 24hrs.
What is the best way of going about this?
Is there a common library most folks use to do this?
Just looking for suggestions of approaches I should investigate (or great examples of how to do this).
I used this npm package:
https://www.npmjs.com/package/memory-cache
And then something like this:
import cacheData from "memory-cache";
async function fetchWithCache(url, options) {
const value = cacheData.get(url);
if (value) {
return value;
} else {
const hours = 24;
const res = await fetch(url, options);
const data = await res.json();
cacheData.put(url, data, hours * 1000 * 60 * 60);
return data;
}
}
Then if you want to fetch something with using the cache just call this function. Or it can be used as a midware in the requests. It checks if the data is already in the cache and returns it, or if not - it puts the data into the cache under the key. The key can be anything, I am using the url for instance.
In addition to Tobias Lins' answer:
At least if deploying on Vercel, you can use set Cache-Control headers in getStaticProps, getServerSideProps, API routes, etc to cache responses on Vercel's edge network. This solution does not require any additional dependencies and very minimal code.
api route example - source Vercel
// pages/api/user.js
export default function handler(req, res) {
res.setHeader('Cache-Control', 's-maxage=86400');
res.status(200).json({ name: 'John Doe' });
}
Example in getServerSideProps - Source NextJS
// This value is considered fresh for ten seconds (s-maxage=10).
// If a request is repeated within the next 10 seconds, the previously
// cached value will still be fresh. If the request is repeated before 59 seconds,
// the cached value will be stale but still render (stale-while-revalidate=59).
//
// In the background, a revalidation request will be made to populate the cache
// with a fresh value. If you refresh the page, you will see the new value.
export async function getServerSideProps({ req, res }) {
res.setHeader(
'Cache-Control',
'public, s-maxage=10, stale-while-revalidate=59'
)
return {
props: {},
}
}
I believe you'd want to use:
res.setHeader('Cache-Control', 's-maxage=1440000')
Here are some other useful links for caching on Vercel:
https://vercel.com/docs/concepts/functions/edge-caching
https://vercel.com/docs/concepts/edge-network/overview
https://vercel.com/docs/concepts/edge-network/caching
https://vercel.com/docs/concepts/edge-network/headers
For your specific case, you also may want to look into using getStaticPaths with getStaticProps. You can use fallback: true on getStaticPaths to only build pages when they're visited (you can still build your post popular pages at initial build time).
https://nextjs.org/docs/basic-features/data-fetching#the-fallback-key-required
I know this is an old post, but for others googling (at least those deploying on Vercel), these solutions should help where revalidate in getStaticProps does not.
You could use getStaticProps from Next.js for SSG
They currently have a revalidate property that you can return, that defines how often the content should be re-fetched.
Take a look here:
https://nextjs.org/blog/next-9-5#stable-incremental-static-regeneration
This is how we did it without any 3rd party libraries, as in our use-case we only had to cache a relatively smaller amount of global data(header/footer menus) which was shared across the site.
The data was coming from a CMS via GraphQL.
We ran an async method getGlobalData on each page from on getStaticProps method and then returned the cached data to the page component via props.
import fs from 'fs';
import path from 'path';
// Cache files are stored inside ./next folder
const CACHE_PATH = path.join(__dirname, 'globalData.json');
export default async function getGlobalData() {
let cachedData;
// #1 - Look for cached data first
try {
cachedData = JSON.parse(fs.readFileSync(CACHE_PATH, 'utf8'));
} catch (error) {
console.log('❌ CACHE NOT INITIALIZED');
}
// #2 - Create Cache file if it doesn't exist
if (!cachedData) {
// Call your APIs to-be-cached here
const data = await fetchGlobalData();
// Store data in cache files
// this always rewrites/overwrites the previous file
try {
await fs.writeFileSync(
CACHE_PATH,
JSON.stringify(data),
err =>throw err
);
console.log('💾 CACHE FILE WRITTEN SUCCESSFULLY');
} catch (error) {
console.log('❌ ERROR WRITING MEMBERS CACHE TO FILE\n', error);
}
cachedData = data;
}
return cachedData;
}
Call getGlobalData method from getStaticProps.
export async function getStaticProps({ preview = false }) {
const globalData = await getGlobalData();
// call other page-specific/non-shared APIs here
// ...
return { props: { globalData } };
}
References
https://flaviocopes.com/nextjs-cache-data-globally/
Note if you get an error saying fs or path is unknown or invalid, then please understand that, the above code is supposed to be running or referenced "serverside" i.e only inside getStaticProps or getServerSideProps. If you import and reference it "browser-side", say somewhere inside your components or on the page (other than methods mentioned above), then you will get an error, as there is no filesystem(fs) or path modules on browser. They are only available on node.

How do I unit test calling a service with promise-retry using Mocha?

I have an action in my ReactJS project that calls a notification service. It is required that, if the service call fails once, I must try calling the service again only one time before proceeding with the error state in the application. I used the promise-retry module for this and was able to get it working locally. However, I am now trying to write unit tests (Mocha) for the promiseRetry-wrapped service calls themselves and having incredible difficulty getting meaningful tests to pass. First, here is the action that calls the service, wrapped in a promiseRetry.
import promiseRetry from 'promise-retry';
...
const sendNotification = () => {
return (dispatch, getState) => {
const request = buildNotificationRequest(getState);
dispatch(createNotificationAttempt());
promiseRetry((retry) => {
return createNotificationService(request)
.catch(retry);
}, {retries: 1}).then(
() => {
dispatch(createNotificationSuccess());
},
(error) => {
dispatch(createNotificationError(error));
}
);
};
};
Typically, the way that I would write unit tests for actions calling services is something like this:
describe('notification actions', () => {
beforeEach(() => {
sendNotification = sinon.stub(services, 'createNotificationService').returns(Promise.resolve({}));
});
it('should log an attempt', () => {
store.dispatch(notificationActions.sendNotification());
const actions = store.getActions();
expect(actions[0].type).to.equal(notificationActions.ACTION_TYPES.CREATE_NOTIFICATION_ATTEMPT);
});
});
This works fine for testing the initial attempt, but for some reason, even though I can debug and step through the tests and hit all of the code inside the promiseRetry, the actions inside of them (such as dispatch(createNotificationSuccess())) are not logged in the store, so I cannot run expect statements on them. Every angle I have tried up to this point only retrieves the attempt from the store, and I cannot get any data from the success or failure side of the Promise.
I have found some information on Stack Overflow about testing promise-retry itself, but I need to know that if I stub the service I'm calling and force it to fail, that it will log another attempt and another failure. Or, if I stub the service and force it to succeed, it will only log one attempt, one success, and complete. As I mentioned previously, the only action I am getting in the store is the attempt, and nothing about success or failure, even though stepping through debug shows that all of those lines of code are hit.
Here is an example of a test that I cannot get to pass:
import * as services from 'services.js';
...
describe('the first time the service call fails', () => {
const error = {status: 404};
beforeEach(() => {
sendNotification = sinon.stub(services, 'createNotificationService').returns(Promise.reject(error));
});
it('should log a retry', () => {
store.dispatch(notificationActions.sendNotification());
const actions = store.getActions();
expect(actions[0].type).to.equal(notificationActions.ACTION_TYPES.CREATE_NOTIFICATION_ATTEMPT); // this passes
expect(actions[1].type).to.equal(notificationActions.ACTION_TYPES.CREATE_NOTIFICATION_FAILURE); // this fails because there are no other actions logged in the store.
Maybe I am misunderstanding the way promise-retry works? Shouldn't it hit my error action (dispatch(createNotificationError(error)) the first time it fails, and the second time (if applicable)? If not, it should be at least logging two attempts. Any advice?

ReactJS where do I store the API URI?

Where would I store the API URI centrally in a ReactJS Application? The URI only changes between environments and should be easily configurable (i.e. through environment variables).
I have looked into this package and into the new Context API, but am unsure it's the best way to achieve this. I have also looked into dotenv, but I don't like that I would have to use process.env.REACT_APP_SERVICE_URI in every component that wants to access the API. What is the usual approach?
I am not using Redux.
I don't think you need an external dependency to do that.
I usually create simple module called api-client.js, which is responsible for calls to external API and defining endpoints.
In your case you might have:
import axios from 'axios' // some http client lib
const endpoint = process.env.REACT_APP_SERVICE_URI? process.env.REACT_APP_SERVICE_URI : 'https://foo.api.net/'
export default {
getAllProducts () {
return axios.get(endpoint + 'products').then(response => {
log.debug(`api client fetched ${response.data.length} items`)
return response.data
}).catch(err => {
log.error(err.message)
throw err
})
}
},
getProductById (id) {
...
},
}
You read process.env.REACT_APP_SERVICE_URI only once.
I like to put this module inside api directory (and any other API related stuff).

Resources