Make data pulled from a remote source available in NodeJS App - node.js

I'm just getting going with NodeJS and am trying to use jsforce (salesforce) to populate a dropdown on a form.
I've written a module the requires jsforce, sets login params, and connects.
``modules/sftools.js
const jsforce = require('jsforce')
const conn = new jsforce.Connection({
loginUrl: process.env.SF_LOGINURL
})
conn.login(process.env.SF_USER, process.env.SF_PASS)
exports.metaDropDown = async (field) =>
conn.sobject...describe..
return arrayOfValues
}
I want to make the value returned available throughout my app, so in index.js I've got
``index.js
const sftools= require('../modules/sftools')
const roles = sftools.metaDropDown(process.env.SF_ROLES)
and then I use some middleware to always set req.roles = roles.
I think the problem is that I'm requesting the roles before the connection is established, but I can't figure out the flow.
I tried logging in before the exports code, but I get an Invalid URL error, presumably because it isn't logged in yet.
I tried to put the login code directly into the metaDropdown export, which got rid of the error, but there is still no data returned.
Any suggestions?

I think the issue your having is that for the login function is expecting a callback as the third argument.
conn.login(process.env.SF_USER, process.env.SF_PASS, function() {
// do your role logic here.
})
Hope this helps.

Related

Is it possible to have protected routes in Remix.run, so the browser doesn't get the protected source code?

Is it possible to have protected routes in the Remix.run React framework, so that only admin users get the protected components, while regular users don't get the protected components at all as part of the JS bundle sent to the browser?
Also, this may require a form of code splitting on the front end side. Is code splitting supported in Remix.run?
this is a code snippet from a sample app I wrote, this is the home page and can only be accessed if the user is authenticated.
the redirect(`/login?${searchParams}`) will redirect if the user isn't authenticated
// Loaders provide data to components and are only ever called on the server, so
// you can connect to a database or run any server side code you want right next
// to the component that renders it.
// https://remix.run/api/conventions#loader
export let loader = async ({ request }) => {
const redirectTo = new URL(request.url).pathname;
let session = await getSession(request.headers.get("Cookie"));
// if there is no access token in the header then
// the user is not authenticated, go to login
if (!session.has("access_token")) {
let searchParams = new URLSearchParams([["redirectTo", redirectTo]]);
throw redirect(`/login?${searchParams}`);
} else {
// otherwise execute the query for the page, but first get token
const { user, error: sessionErr } = await supabaseClient.auth.api.getUser(
session.get("access_token")
);
// if no error then get then set authenticated session
// to match the user associated with the access_token
if (!sessionErr) {
// activate the session with the auth_token
supabaseClient.auth.setAuth(session.get("access_token"));
// now query the data you want from supabase
const { data: chargers, error } = await supabaseClient
.from("chargers")
.select("*");
// return data and any potential errors alont with user
return { chargers, error, user };
} else {
return { error: sessionErr };
}
}
};
You can protect routes by authorizing the user inside the loader of the Route, there you could decide to redirect it somewhere else or send a flag as part of the loader data so the UI can hide/show components based on it.
For the code splitting, Remix does it at the route level, but it doesn't support server-side code-splitting out of the box, you may be able to support it with react-loadable.
I hope it has, but not. Below is the official answer.
https://remix.run/docs/en/v1/pages/faq#how-can-i-have-a-parent-route-loader-validate-the-user-and-protect-all-child-routes
You can't 😅. During a client side transition, to make your app as speedy as possible, Remix will call all of your loaders in parallel, in separate fetch requests. Each one of them needs to have its own authentication check.
This is probably not different than what you were doing before Remix, it might just be more obvious now. Outside of Remix, when you make multiple fetches to your "API Routes", each of those endpoints needs to validate the user session. In other words, Remix route loaders are their own "API Route" and must be treated as such.
We recommend you create a function that validates the user session that can be added to any routes that require it.

How to pass arguments from Dart to Cloud functions written in Typescript and it also gives me error code UNAUTHENTICATED

Dart function (passing token to sendToDevice):
Future<void> _sendNotification() async {
CloudFunctions functions = CloudFunctions.instance;
HttpsCallable callable = functions.getHttpsCallable(functionName: "sendToDevice");
callable.call({
'token': await FirebaseMessaging().getToken(),
});
}
index.ts file where I have defined sendToDevice method.
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
admin.initializeApp();
const fcm = admin.messaging();
export const sendToDevice = functions.firestore
.document('users/uid')
.onCreate(async snapshot => {
const payload: admin.messaging.MessagingPayload = {
notification: {
title: 'Dummy title',
body: `Dummy body`,
click_action: 'FLUTTER_NOTIFICATION_CLICK'
}
};
return fcm.sendToDevice(tokens, payload); // how to get tokens here passed from above function?
}
);
Questions:
How can I receive tokens passed from my Dart function _sendNotification to Typescript's sendToDevice function.
When I was directly passing tokens inside index.ts file, I was getting this exception:
[ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: PlatformException(functionsError, Cloud function failed with exception., {code: UNAUTHENTICATED, details: null, message: UNAUTHENTICATED})
Can anyone please explain if I am supposed to authenticate something here? The command firebase login shows I am already signed in. I am very new to Typescript so please bear with these stupid questions.
Your Flutter side of code seems right, what's wrong is on the Cloud Function.
The sendToDevice function is not a callable function. It is a Cloud Firestore Triggers, it is only meant to be automatically called whenever a document matches users/{uid} is created.
Instead, you'll want to create a Callable Function, see below
export const sendToDevice = functions.https
.onCall(async (data) => {
const { token } = data; // Data is what you'd send from callable.call
const payload: admin.messaging.MessagingPayload = {
notification: {
title: 'Dummy title',
body: `Dummy body`,
click_action: 'FLUTTER_NOTIFICATION_CLICK'
}
};
return fcm.sendToDevice(token, payload);
}
);
You have created a database trigger, what you should do is create a callable function as shown below
exports.sendToDevice = functions.https.onCall(async (data, context) => {
const payload: admin.messaging.MessagingPayload = {
notification: {
title: 'Dummy title',
body: `Dummy body`,
click_action: 'FLUTTER_NOTIFICATION_CLICK'
}
};
return await fcm.sendToDevice(data.token, payload);
});
There are few things to mention here:
1st The function used in 'getHttpsCallable' must be triggered by https trigger (reference here). Here we have a function triggered by firestore document create, so it won't work.
2nd You do not have parameter of your function, but you call it with parameters. If you need example of calling cloud function with parameter you can find it on pud.dev
3rd I do not have at the moment possibility to play with it, but I think that if you implement https triggered function with token parameter you should be able to pass this parameter.
I hope it will help!
UPDATE:
According to doc https triggered function has to be created with functions.https. There is a nice example in the doc. To function triggered this way you can add request body when you can pass needed data.
This answer might not solve your problem but will give you a few things to try, and you'll learn along the way. Unfortunately I wasn't able to get the callable https working with the emulator. I'll probably submit a github issue about it soon. The flutter app keeps just getting different types of undecipherable errors depending on the local URL I try.
It's good that you've fixed one of the problems: you were using document trigger (onCreate) instead of a https callable. But now, you're running a https callable and the Flutter apps needs to communicate with your functions directly. In the future, you could run the functions emulator locally, and do a lot of console.log'ing to understand if it actually gets triggered.
I have a few questions/ things you can try:
Is your user logged in the flutter app? FirebaseAuth.instance.currentUser() will tell you.
Does this problem happen on both iOS and android?
Add some logs to your typescript function, and redeploy. Read the latest logs through StackDriver or in terminal, firebase functions:log --only sendToDevice. (sendToDevice is your callable function name)
Are you deploying to the cloud and testing with the latest deployment of your functions? You can actually test with a local emulator. On Android, the url is 10.0.2.2:5001 as shown above. You also need to run adb reverse tcp:5001 tcp:5001 in the terminal. If you're on the cloud, then firebase login doesn't matter, I think your functions should already have the credentials.
To call the emulator https callable:
HttpsCallable callable = CloudFunctions.instance
.useFunctionsEmulator(origin: "http://10.0.2.2:5001")
.getHttpsCallable(functionName: "sendToDevice");
And iOS you need to follow the solution here.
One mistake I spotted. You should at least do return await fcm.sendToDevice() where you wait for the promise to resolve, because otherwise the cloud function runtime will terminate your function before it resolves. Alternatively, for debugging, instead of returning sendToDevice in your cloud function, you could have saved it into a variable, and console.log'd it. You would see its actually a promise (or a Future in dart's terminology) that hadn't actually resolved.
const messagingDevicesResponse: admin.messaging.MessagingDevicesResponse = await fcm.sendToDevice(
token,
payload
);
console.log({ messagingDevicesResponse });
return;
Make the function public
The problem is asociated with credentials. You can change the security policy of the CF and sheck if the problem is fixed. Se how to manage permisions on CF here

Error snap.data is not a function with firebase cloud function and onCreate

I have a cloud function that sends a welcome email every time a new user registers in the database.
The function correctly executes everything, sends the emails and these are received by the recipient, so far, everything is fine.
It works when I manually write the email address in the function, but when I want it to get the data from the realtime database, it gives me the error:
TypeError: snap.data is not a function
This is the code of my function:
const functions = require('firebase-functions');
const nodemailer = require("nodemailer");
const transport = nodemailer.createTransport({
service: "Gmail",
auth: {
user: "MY_EMAIL",
pass: "MY_EMAIL_PASSWORD"
}
})
exports.welcomeMail = functions.database.ref('/paso1/{id}').onCreate((snap, context) => {
const _name = snap.data().name;
return sendWelcomeMail(_name)
});
// aux functions
function sendWelcomeMail(name) {
return transport.sendMail({
from: "JohnDoe <sender#test.com>",
to: "myfriendemail#gmail.com",
subject: "Hello",
html: `
<h1<Hello ${name} </h1>
<p>nice to seeyou</p>
`
})
.then(r => r)
.catch(e => e);
}
This is my realtime database:
I have reviewed the documentation several times, I have tested with snap.val().{uid}; but all without success, I cannot recover the "name" field from the database.
Using const _name = snap.val().name; I get the same error
I am not sure what is failing.
The method you're looking for is snap.val(), not snap.data(). You might be confusing Realtime Database with Firestore. Firestore uses data() to get the raw data out of a DocumentSnapshot, but that's different than Realtime Database.
You have a typo. You declare snap and then refer to it as snapshot. To fix this problem, make sure the declaration and use match.
You're also using snapshot.data(), while data() doesn't exist on a Realtime Database snapshot (you're probably confusing it with Cloud Firestore).
So combining those two fixes, this should be much closer:
exports.welcomeMail = functions.database.ref('/paso1/{id}')
.onCreate((snapshot, context) => { // this line changed
const _name = snapshot.val().name;
...
I finally found what the mistake was.
Indeed, as you have indicated to me, the correct way to extract the data from the realtime database is by using .val()
However, I told you in the comments to the answers that I kept returning error.
It didn't work because I wasn't initializing the firebase SDK as an ADMIN, necessary to access, among other things, the realtime database.
https://firebase.google.com/docs/admin/setup
I hope my mistake will save other programmers time.
Thanks to all for the help

Is there a way to instantiate a new client on server side by firebase cloud function?

I am developing an app and trying to implement news feed by getstream.io using react native and firebase.
Is there a way to generate user token by using firebase cloud function. If there is, would you please give me a pointer how i can do so? (the snippet of codes in cloud function side and client side would be super helpful..)
I have seen similar questions, only to find out no specific tutorial.. any help is appreciated!
For the cloud function side you need to create a https.onRequest endpoint that calls createUserToken like so:
const functions = require('firebase-functions');
const stream = require('getstream');
const client = stream.connect('YOUR_STREAM_KEY', 'YOUR_STREAM_SECRET', 'YOUR_STREAM_ID');
exports.getStreamToken = functions.https.onRequest((req, res) => {
const token = client.createUserToken(req.body.userId);
return { token };
});
After that, deploy with firebase deploy --only functions in the terminal & get the url for the function from your firebase dashboard.
Then you can use the url in a POST request with axios or fetch or whatever like this:
const { data } = axios({
data: {
userId: 'lukesmetham', // Pass the user id for the user you want to generate the token for here.
},
method: 'POST',
url: 'CLOUD_FUNC_URL_HERE',
});
Now, data.token will be the returned stream token and you can save it to AsyncStorage or wherever you want to store it. Are you keeping your user data in firebase/firestore or stream itself? With a bit more background I can add to the above code for you depending on your setup! 😊 Hopefully this helps!
UPDATE:
const functions = require('firebase-functions');
const stream = require('getstream');
const client = stream.connect('YOUR_STREAM_KEY', 'YOUR_STREAM_SECRET', 'YOUR_STREAM_ID');
// The onCreate listener will listen to any NEW documents created
// in the user collection and will only run when it is created for the first time.
// We then use the {userId} wildcard (you can call this whatever you like.) Which will
// be filled with the document's key at runtime through the context object below.
exports.onCreateUser = functions.firestore.document('user/{userId}').onCreate((snapshot, context) => {
// Snapshot is the newly created user data.
const { avatar, email, name } = snapshot.val();
const { userId } = context.params; // this is the wildcard from the document param above.
// you can then pass this to the createUserToken function
// and do whatever you like with it from here
const streamToken = client.createUserToken(userId);
});
Let me know if that needs clearing up, these docs are super helpful for this topic too 😊
https://firebase.google.com/docs/functions/firestore-events

how to access hook object using .validate from feathers-hooks-common

I'm trying to create a simple validator in my feathersjs app and I need to have access to hook.app so I can retrieve the users service and check for uniqueness. Below is my code
checkUniqueEmail = (values, hook) => {
const userService = hook.app.service('users');
//below is my validation
}
The problem is that the hook variable is returning undefined instead of the hook object. The feathers-hooks-common github code it shows that this should be possible since hook is being pass as the 2nd parameter. (see below)
const results = validator(getItems(hook), hook); // line 18
I'm not sure what I'm doing wrong here.
I found a good documentation about feathers-hooks-common validate. It is better explained here. https://eddyystop.gitbooks.io/feathers-docs-common/content/hooks/validation.html
all you need to do is at create hook
//pass context or hook
create:[
validate((values,context) =>validations.signupAsync(values,context))
]
//at validations.signupAsync
clientValidations.signupAsync = (values,context) => {
const userService= context.app.service('users')
}

Resources