Dynamic callback endpoints with cloud functions - firebase

When a user triggers a function there’s a POST request going away to a partner. Within the body I need to include a unique endpoint callbackURL with an Id so they can send me status updates linked with a specific user. How can I accomplish that? I know how to setup static endpoints, but not create new ones for every request.

As Doug said in his comment above, you don't need a new URL (i.e. a new endpoint) for each different id. You can deploy only one HTTP Cloud Function (which exposes one endpoint) and, in the Cloud Function, you extract the value of id from the Request object with its originalUrl property, as follows:
exports.myWebhook = functions.https.onRequest((req, res) => {
const urlArray = req.originalUrl.split('/');
console.log(urlArray);
console.log(urlArray[1]);
const id = urlArray[1];
//Do whatever you need with id
//.....
//If you want to test that it works with a browser, you can send it back as a response to the browser
res.send(urlArray[1]);
});
You then call this Cloud Function with the following URI:
https://us-central1-yourprojectname.cloudfunctions.net/myWebhook/id/callback
Note that it is also possible to extract values from the Request body, see https://firebase.google.com/docs/functions/http-events?authuser=0#read_values_from_the_request.

Related

Firestore Cloud Function - Get request data object in onUpdate/onCreate

When writing firebase rules, you can access the request data via request.resource.data. This is useful because you can look at the nature of the request to determine its intent, its write target and permit or deny. This enables merging properties into an object within a document owned by a user, vs using a nested collection of documents.
I would like to access the same request data in the cloud function callbacks update/write/etc, but I don't see it, and I'm left to do an object compare with change.before and change.after. It's not a problem, but did I miss something in the documentation?
Per documentation: https://firebase.google.com/docs/firestore/extend-with-functions
exports.myFunctionName = functions.firestore.document('users/marie').onWrite((change, context) => {
// ... the change or context objects do not contain the request data
});
I had the exact same question when I realized that a function listening for updates was being triggered regardless of the property being updated, despite having a 'status' in data check. The catch that data represented handler.after.data. Although I wasn't able to access the request data, either from the handler or from the context, I was able to solve the problem by adding an additional check which serves the same purpose. Namely:
const dataBefore = handler.before.data();
const dataAfter = handler.after.data();
if (status in dataBefore && status in dataAfter) {
if (dataBefore.status === 'unpublished' && dataAfter.status === 'published') {
// handle update
}
}

Meteor publish overwrites another publish

I have two publish method as below but when I subscribe to one of the publish method in client search page, it is being overwritten with the other one which is meant for index page.
Server
Meteor.publish("task.index", function() {
TaskCollection.find()
}
Meteor.publish("task.index.search", function(state) {
TaskCollection.find({ state: state })
}
Client - search page
Meteor.subscribe("task.index.search", state)
// this will always be overwritten with "task.index" published collection
Client - index page
Meteor.subscribe("task.index")
Does anyone know how to avoid this?
Welcome to SO!
There is a high chance the "override" you see is just the normal Meteor behaviour for Publish/Subscribe mechanism.
Your "task.index" publication sends all your TaskCollection documents to the Client.
Therefore any other publication on that same TaskCollection will send documents that the Client already knows.
Then in your Client, filtering some documents from TaskCollection is independent from your subscription and publication. Just perform your TaskCollection.find({ state: state }) Client side, and you will get the documents you need.
When you publish only a subset of documents of a Collection, it happens that what you publish is exactly already the filtered documents that you want to display on your Client, therefore on your Client you just display all Collection documents you know of. But you have to understand that these are 2 different steps:
Subscription to send some documents to the Client. Several Subscriptions may be set, filling the same Collection on the Client.
Filtering on the Client, based on the documents sent by the (possibly several) Subscription(s).
See also: Publish subscribe doesn't seem to work
If your client index and search pages are different templates, you can subscribe to the documents at respective template level.
Client - search page:
Template.search.created = function () {
const template = this;
template.subscribe('task.index.search', state);
}
Template.search.rendered = function () {
console.log("Client search : " + TaskCollection.find().fetch().length);
}
Client - index page:
Template.index.created = function () {
const template = this;
template.subscribe('task.index');
}
Template.index.rendered = function () {
console.log(""Index : " + TaskCollection.find().fetch().length);
}
But, it is always advisable to filter the documents on the client as well.

Redux Form post to /user/:id

I am using redux form, but I would like to post the data from the form to this route user/:id.
But my action just get data from the form, what is the best to way to send this id for action create?
export async function createUser(props){
const request = await post(`/users`, props);
return {
type: CREATE_USER,
payload: request
}
}
How can I pass the id to change my url to /users/${id}
I don't really see anything here related to redux-form, but assuming your id is in your props object, you could do:
const request = await post(`/users/${props.id}`, props);
However, it seems pretty strange that you would even have an id if you are creating the user. Usually the primary key is generated upon creation, so what you have already would be just fine, and your server-side CRUD API should be smarter about not expecting an id.

Cloud Function Firebase, error sending value back

I'm trying to send back simple value from firebase but error appearing like this
mycode is :
exports.getTotalPrice = functions.https.onRequest((req, res) => {
admin.database().ref('carresult').once('value').then(function(snapshot) {
var totalPrice = snapshot.val().price;
res.status(200).send(totalPrice);
});
});
ps. In error 65000 is the value I need it to send back.
The Express documentation for res.send([body]) indicates:
The body parameter can be a Buffer object, a String, an object, or an
Array
In your database, /carresult/price is likely stored as a number, making totalPrice an invalid parameter to send(). Your options are to store it as a String convert it to a String before passing to send(), or leave it a number and send it back as a property of an object: send({price: totalPrice}).
exports.getTotalPrice = functions.https.onRequest((req, res) => {
admin.database().ref('carresult').once('value').then(function(snapshot) {
var totalPrice = snapshot.val().price;
res.status(200).send(String(totalPrice)); // <= ADDED String()
});
});
Also note that performing a database read (asynchronous) in an HTTPS function is risky, as Frank van Puffelen explains in this answer:
Note that this is a tricky pattern. The call to the database happens
asynchronously and may take some time to complete. While waiting for
that, the HTTP function may time out and be terminated by the Google
Cloud Functions system...As a general rule I'd recommend using a Firebase Database SDK or its REST API to access the database and not rely on a HTTP function as middleware.

meteor-shopify User Creation/ Login after Auth callback

Assuming I want to create users upon authorizing the app, how would I grab their email during the onAuth callback...? Looks like the callback assumes the user is already logged in. Am I thinking about it correctly?
I noticed when installing the Fishbowl Prizes app, after auth I can click on the accounts tab and see that all my account info is pre-populated from my shopify store account (name, email, address, etc).
I'm not sure if I should go by the title or the content of the post in terms of answering your question, so I'll provide a very simple example of how to get the info from the API and do something with it here.
I have provided a more in depth answer related specifically to grabbing the details from the API for user account creation here: https://github.com/froatsnook/meteor-shopify/issues/15#issuecomment-177413630
Looks like the callback assumes the user is already logged in.
The userId param is undefined if there is no user. If your onAuth operations don't need to do anything with the user, you can just leave it out of the params. In your case you'll just want to handle it conditionally using an if/else block:
if(!userId){
// do stuff
} else {
// do other stuff
}
On to the example of grabbing those details from the API:
All the prepopulated information you are seeing is available from the Shopify API in the shop object. You already have the access token when onAuth callbacks are fired, so you can just grab it from the API immediately after you have inserted the shop's Keyset.
For the sake of simplicity, in this example we'll assume the user already exists and is logged in. In your server-side onAuth callback (after you have inserted the keyset) you can do something like this to add those fields to the user's profile object:
Shopify.onAuth(function(access_token, authConfig, userId) {
var shopUUID = uuid.new(); // Not secure to name keyset same as the shop!
Shopify.addKeyset(shopUUID, {
access_token: access_token
});
var api = new Shopify.API({
shop: authConfig.shop,
keyset: shopUUID
});
// get the Shop object from the API
var shopObj = api.getShop();
var userInfo = {
'profile.name': shopObj.shop_owner,
'profile.email': shopObj.email,
'profile.phone': shopObj.phone,
'profile.shopName': shopObj.name
};
Meteor.users.update({_id: userId}, {$set: userInfo})
});
Then you can use them in templates like this:
{{currentUser.profile.name}} or {{currentUser.profile.email}}
Or in functions like so:
var realName = Meteor.user().profile.name
or
var userEmail = Meteor.user().profile.email etc
For a more about using this data for user creation, see my explanation here:
https://github.com/froatsnook/meteor-shopify/issues/15#issuecomment-177413630

Resources