I am developing a bot to link to my NodeJS application and am using quick replies to receive the user's email address and telephone number.
However, the reply contains a text and payload value that are the same, which makes catching the response and processing it impossible.. So I must be doing something wrong.
Here's what I send:
response = {
"text": "We need your phone number to match you with our records",
"quick_replies":[
{
"content_type":"user_phone_number",
"payload":"PHONE_NUMBER"
}
]
}
callSendAPI(sender_psid, response);
But when the user clicks their Quick Reply button I get:
{ sender: { id: '<some value>' },
recipient: { id: '<some value>' },
timestamp: 1622370102305,
message:
{ mid:
'<some value>',
text: 'me#example.com',
quick_reply: { payload: 'me#exmaple.com' }
}
}
How would I identify a specific Quick Reply response for processing?
With text replies I can assign a payload, then listen out for that payload being returned.
If the payload of a quick reply is dynamic, I don't see a way to process the user response since if (response.message.quick_reply.payload === 'PHONE_NUMBER') can't work here like the rest of the script.
Unfortunately, according to the docs, that's just how it is.
For an email/phone quick reply, the message.quick_reply.payload will either be the email or phone number as appropriate.
However, while the quick replies are available, the user can still manually type in a different email or phone number to what they have registered with Facebook - it's just for convenience. Because they can send back any free form text they like, you should be parsing the message.text property anyway.
parseResponseForEmailAndPhone(response) {
const text = response.message.text;
if (looksLikeAnEmail(text)) {
return { email: text };
} else if (looksLikeAPhoneNumber(text)) {
return { phone: text };
}
// TODO: handle other message
// unlikely, but could even be a sentence:
// - "my phone is +000000"
// - "my email is me#example.com"
// - "+000000 me#example.com"
// You also need to handle non-consent
// - "you don't need it"
// - "I don't have one"
// - "skip"
const result = {};
// please use a library for these instead,
// they are used here just as an example
const phoneMatches = /phoneRegEx/.exec(text);
const emailMatches = /emailRegEx/.exec(text);
if (phoneMatches) {
result.phone = phoneMatches[1];
}
if (emailMatches) {
result.email = emailMatches[1];
}
return result;
}
Related
How do i trigger the sending of an email when a document data is modified ?
Trigger Email only composes and sends an email based on the contents of a document written to a Cloud Firestore collection but not modified
I can’t figure this one out…
From checking the code we can see that the extension already processes all writes to the document:
export const processQueue = functions.handler.firestore.document.onWrite(
...
From looking a bit further it seems that the action the extension takes upon a write depends on the value of the delivery.state field in the document.
async function processWrite(change) {
...
const payload = change.after.data() as QueuePayload;
...
switch (payload.delivery.state) {
case "SUCCESS":
case "ERROR":
return null;
case "PROCESSING":
if (payload.delivery.leaseExpireTime.toMillis() < Date.now()) {
// Wrapping in transaction to allow for automatic retries (#48)
return admin.firestore().runTransaction((transaction) => {
transaction.update(change.after.ref, {
"delivery.state": "ERROR",
error: "Message processing lease expired.",
});
return Promise.resolve();
});
}
return null;
case "PENDING":
case "RETRY":
// Wrapping in transaction to allow for automatic retries (#48)
await admin.firestore().runTransaction((transaction) => {
transaction.update(change.after.ref, {
"delivery.state": "PROCESSING",
"delivery.leaseExpireTime": admin.firestore.Timestamp.fromMillis(
Date.now() + 60000
),
});
return Promise.resolve();
});
return deliver(payload, change.after.ref);
}
}
My guess is that if you clear that field, the extension will pick up on that change and try to mail the document again.
I'm struggling with this problem for more than a week... We have implemented push message in Chrome by using Firebase and a Service Worker. Everything works just fine, the messages are being sent and received correctly with the payload. On the service worker, we handle the push message to display a notification and the notificationclick event to send the user to a specific URL when clicking on it, then close the notification.
The problem is with 'old' notifications: if a user receives a message but doesn't clicks on it right away, it keeps there for a while then after some time (not sure how much) he clicks the notification - he gets redirected to https://[domain]/firebase-messaging-sw.js
We have traced the entire process: the notification gets received with all the info properly (the url is also correct, actually if he clicks right when the message is received it works just fine). But if the notification lives there for a while it gets 'emptied'.
The message being sent is pretty simple (just for showing there are no TTLs, nor expiration parameters being used). The curl command looks like that:
curl -X POST
-H "Authorization: key=[SERVER API KEY]"
-H "Content-Type: application/json"
-d '{
"notification":{
"click_action":"[URL to be redirected]",
"icon":"[A custom image]",
"title":"[News title]"},
"to":"/topics/[A topic]"
}'
"https://fcm.googleapis.com/fcm/send"
This is the code for processing the push message on the service worker:
self.addEventListener('push', function(event) {
console.log('Push message received', event);
var data = {};
if (event.data) {
data = event.data.json();
}
event.stopImmediatePropagation();
showNotification(data.notification);
});
function showNotification(notification) {
var click_action = notification.click_action; //<-- This is correct!
var options = {
body: notification.body,
icon: notification.icon,
subtitle: notification.subtitle,
data: {
url: click_action
}
};
if (self.registration.showNotification) {
return self.registration.showNotification(notification.title, options);
}
}
And the code for managing notificationclick event is pretty straightforward:
self.addEventListener('notificationclick', function(event) {
var url = '';
try {
url = event.notification.data.url; // <-- event.notification is null randomly!!
} catch (err) {}
event.waitUntil(self.clients.openWindow(url));
});
Is there any reason for loosing the payload after a certain time on the service worker context? Is this time specified on any documentation somewhere?
Thanks in advance for your help!
//payload of push message
{
"notification": {
"title": data.title,
"body": data.text,
"click_action": url,
"tag": "",
"icon": ""
},
"to": token
}
//in service woker
self.addEventListener("notificationclick", (event) => {
event.waitUntil(async function () {
const allClients = await clients.matchAll({
includeUncontrolled: true
});
let chatClient;
for (const client of allClients) {
if (client['url'].indexOf(event.notification.data.FCM_MSG.notification.click_action) >= 0) {
client.focus();
chatClient = client;
break;
}
}
if (!chatClient) {
chatClient = await clients.openWindow(event.notification.data.FCM_MSG.notification.click_action);
}
}());
});
so in this basically, on click of notification you are redirected to application ,and if the application is not open you are opening the application in new tab , as you are concerned that in event you are not able to get the click_action, could you try event.notification.data.FCM_MSG.notification.click_action by using this and see if you are getting the url, and add the event listener in beginning of service worker https://github.com/firebase/quickstart-js/issues/102
Just getting started with Firebase phone auth. Seems pretty slick however I've hit a wall with a bug.
{
"error": {
"errors": [
{
"domain": "global",
"reason": "invalid",
"message": "SESSION_EXPIRED"
}
],
"code": 400,
"message": "SESSION_EXPIRED"
}
}
Starting with the Captcha: (standard documentation code!)
var applicationVerifier = new firebase.auth.RecaptchaVerifier('recaptcha-container', {
'size': 'invisible',
'callback': function(response) {
},
'expired-callback': function() {
}
});
Its rendered and the captcha works well.
Next is the sign-in bit where you are sent the auth code to your phone. Works great:
$scope.signInWithPhoneNumber = function signInWithPhoneNumber() {
var phoneNumber = "*censored*";
var appVerifier = window.recaptchaVerifier;
firebase.auth().signInWithPhoneNumber(phoneNumber, applicationVerifier)
.then(function (confirmationResult) {
// SMS sent. Prompt user to type the code from the message, then sign the
// user in with confirmationResult.confirm(code).
window.confirmationResult = confirmationResult;
$scope.setConfirmationResult(confirmationResult);
alert('Result: ' + JSON.stringify(confirmationResult));
}).catch(function (error) {
// Error; SMS not sent
alert('Error: ' + error);
// ...
});
};
Finally its the authentication of the code that the user inputs from the text message. Here is when I get the error 400:
$scope.AuthenticateCode = function (code) {
var code = String(document.getElementById("auth_code").value);
var confirmationResult = $scope.getConfirmationResult();
alert(code);
confirmationResult.confirm(code).then(function (result) {
// User signed in successfully.
var user = result.user;
console.log('Signed In! ' + JSON.stringify(user));
// ...
}).catch(function (error) {
// User couldn't sign in (bad verification code?)
// ...
});
}//end of AuthenticateCode
The error is coming from the VerifyPhone method:
https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPhoneNumber?key=censored
Any help or ideas?
Many Thanks,
Kieran
Ok, there are 2 likely reasons:
The code expired. The user took too long to provide the SMS code and finish sign in.
The code was already successfully used. I think this is the likely reason. You need to get a new verificationId in that case. Get a new reCAPTCHA token via the invisible reCAPTCHA you are using.
You are most likely to forget the "Country Code" before the phone no.
That is why firebase throw error 400 which means invalid parameters
If it's an Ionic3 project, change the following lines:
Imports:
import { AngularFireAuth } from 'angularfire2/auth';
import firebase from 'firebase';
Create var:
public recaptchaVerifier: firebase.auth.RecaptchaVerifier;
on "ionViewDidLoad()"
this.recaptchaVerifier = new firebase.auth.RecaptchaVerifier('recaptcha-container');
on "your_method(phoneNumber: number)"
const appVerifier = this.recaptchaVerifier;
const phoneNumberString = "+" + phoneNumber;
this.fireAuth.auth.signInWithPhoneNumber(phoneNumberString, appVerifier)
.then(confirmationResult => {
// SMS sent. Prompt user to type the code from the message, then sign the
// user in with confirmationResult.confirm(code).
let prompt = this.alertCtrl.create({
title: 'Enter the Confirmation code',
inputs: [{ name: 'confirmationCode', placeholder: 'Confirmation Code' }],
buttons: [
{
text: 'Cancel',
handler: data => { console.log('Cancel clicked'); }
},
{
text: 'Send',
handler: data => {
confirmationResult.confirm(data.confirmationCode)
.then(result => {
// Phone number confirmed
}).catch(error => {
// Invalid
console.log(error);
});
}
}
]
});
prompt.present();
})
.catch(error => {
console.error("SMS not sent", error);
});
Reference:
Firebase Phone Number Authentication
I got into a similar situation when a POST request to google API was returning Bad Request 400. When the message was logged, it said:
All requests from this device are blocked due to Unusual Activity. Please try again later
The issue was when the ReCaptcha was sensing a bot out of my development environment and it worked well when I tried later. During the rest of the development, I turned off this feature for easy work.
I need a SMS-authentication in my Meteor app.
Let's say I have a simple form (in React-style, because I'm using React in frontend):
<form onSubmit={ this.submitPhone() }>
<input type='text' size='10' placeholder='Your phone here' />
<input type='submit' value='Send me a code'/>
</form>
User enters his phone number and submits the form. After that SMS-code is sent to the entered number. And a new form appears:
<form onSubmit={ this.submitCode() }>
<input type='text' size='5' placeholder='Enter code' />
<input type='submit' value='Sign In'/>
</form>
If user enters his code correctly, then Meteor should know that the user is logged in (with some _id, I think). If the code is not correct, then an error message is shown.
I found Twilio service and this package and it looks like it is exactly what I need. But I don't know how to use it at all.
I have tried only Meteor's default Accounts UI way of authentication a few months ago in the tutorials, but actually I don't know how to do such things, especially via SMS. I don't need such things like roles in my app, I even don't need usernames, passwords and e-mails. I just need to have a base of user _id and phone. So all I need is make user be able to sign in (first time signin is signup in this way).
Thank for your help, a detailed answer is really what I need this time.
First, you need to install one of the following packages:
http (To call the Twilio API directly from your methods) or,
accolver:twilio-meteor (You can try using the services this package provides off of the official Twilio-Node Helper Library)
Next, you should also install the okland:accounts-phone package to help enable login via phone number. Their GitHub provides easy to follow instructions on how to set it up.
Password
I would strongly recommend creating user accounts with a password, along with the phone number, since it is a good security feature to have, and is also required by default on Meteor Accounts package.
Verification Process
I will be giving an example using server-side Meteor methods, for frontend you can write your React handlers accordingly.
This example will be using the HTTP package, in your code you can modify it to include other wrapper packages like twilio-meteor if you wish.
Step 1:
Register your user and send verification SMS.
createNewUser method:
'createNewUser': function (password, phoneNumber) {
var min = 10000;
var max = 99999;
var random = Math.floor(Math.random() * (max - min + 1)) + min;
var verified = Meteor.users.find({username: phoneNumber}).fetch();
if (verified.length > 0) {
if (verified.length == 1 && verified[0].profile.isMobileVerified == 'NO') {
Meteor.users.remove({username: phoneNumber});
var user = {username: phoneNumber, password: password, profile: { randomSms: random, isMobileVerified: 'NO' }};
Meteor.call("sendSMS", random, phoneNumber);
Accounts.createUser(user);
return returnSuccess('Successfully created', phoneNumber);
} else {
return returnFaliure('Mobile number already exists', phoneNumber);
}
} else {
var user = {username: phoneNumber, password: password, profile: { randomSms: random, isMobileVerified: 'NO' }};
Meteor.call("sendSMS", random, phoneNumber);
Accounts.createUser(user);
return returnSuccess('Successfully created', phoneNumber);
}
},
sendSMS method:
'sendSMS': function (code, mobile) {
console.log(mobile);
HTTP.call(
"POST",
'https://api.twilio.com/{yyyy-dd-mm}/Accounts/' +
'{TWILIO_APPKEY}' + '/SMS/Messages.json', {
params: {
From: '+11234567890',
To: mobile,
Body: "Greetings! Your OTP is " + code
},
auth: '{TWILIO_APPKEY}' + ':' + '{TWILIO_PASSWORD}'
},
// Print error or success to console
function (error) {
if (error) {
console.log(error);
}
else {
console.log('SMS sent successfully.');
}
}
);
}
Step 2:
Ask user for verification code and check code input by user
verifySMS method:
'verifySMS': function (code, userid) {
console.log(userid);
var sms = Meteor.users.findOne({username: userid}).profile.randomSms;
if (sms == code) {
Meteor.users.update({username: userid}, {
$set: {"profile.isMobileVerified": "YES", "profile.randomSms": "--"}
});
return returnSuccess("Yes");
} else {
return returnSuccess("No");
}
},
Step 3:
From your React code handling, if code matches, approve the user, else display appropriate error message.
UPDATE to handle specific use case by OP:
(Example indicative of React code)
To have the user authenticated via SMS OTP code everytime before login, you will need to use the sendSMS method every time the user tries to login, update it in a collection of stored AuthCodes, verify the code each time, and handle case accordingly.
React Form:
You will need to render a form something like this inside your react JSX code container.
<form className="new-task" onSubmit={this.handleSubmit.bind(this)} >
<input
type="text"
ref="phoneNumberInput"
placeholder="Enter Phone Number"
/>
</form>
Write React function to login user:
handleSubmit() {
event.preventDefault();
// Find the phone number field via the React ref
const phoneNumber = ReactDOM.findDOMNode(this.refs.phoneNumberInput).value.trim();
Meteor.call('sendAuthCode', Meteor.userId(), phoneNumber, function(error, result) {
// Show a popup to user that code has been sent
});
}
Then, similar as above, create another form to have the user input the code sent to them, and send that to server for verification, e.g.
handleAuthCheck() {
event.preventDefault();
// Find the phone number field via the React ref
const phoneNumber = ReactDOM.findDOMNode(this.refs.phoneNumberInput).value.trim();
const code = ReactDOM.findDOMNode(this.refs.codeInput).value.trim();
Meteor.call('verifyAuthCode', Meteor.userId(), phoneNumber, code, function(error, result) {
// handle result accordingly
// you need to decide how you are going to login user
// you can create a custom module for that if you need to
});
}
AuthCodes Collection:
You will need to define a collection in a file and export it, so that it can be imported where needed.
export const AuthCodes = new Mongo.Collection('authcodes');
Meteor server methods:
Send SMS:
'sendAuthCode': function(userId, phoneNumber) {
var min = 10000;
var max = 99999;
var code = Math.floor(Math.random() * (max - min + 1)) + min;
Meteor.call("sendSMS", code, phoneNumber);
AuthCodes.insert({
userId: userId,
phoneNumber: phoneNumber,
code: code
});
}
Verify Code:
'verifyAuthCode': function(userId, phoneNumber, code) {
var authCode = AuthCodes.findOne({ phoneNumber: phoneNumber, code: code }) // You can also add userId check for added verification
if(typeof authCode !== "undefined" && authCode) {
// verification passed
return true;
} else {
// verification failed
return false;
}
}
I am looking at this block of code:
firebase.init({
onAuthStateChanged: function(data) { // optional but useful to immediately re-logon the user when he re-visits your app
console.log(data.loggedIn ? "Logged in to firebase" : "Logged out from firebase");
if (data.loggedIn) {
console.log("user's email address: " + (data.user.email ? data.user.email : "N/A"));
}
}
});
It's from the nativescript-firebase plugin authentication readme. I suspect it's the firebase instance, but can't be sure. I looked at the firebase.android.js file that contains the onAuthStateChanged listener, which leads me to believe that's what it is.
data is a Json, that means, to had any information, in this case had ifnrmation of user, , if you see this "onAuthStateChanged" that means a variable been created and that will be use, how parameter from a method
Information of user
data.user.email
get a boolean value
if (data.loggedIn) {
....
}
New Variable listener
var listener= {
onAuthStateChanged: function(data) {
......
}
};
listener will be used how parameter
// add the listener:
firebase.addAuthStateListener(listener);
// stop listening to auth state changes:
firebase.removeAuthStateListener(listener);
// check if already listening to auth state changes
firebase.hasAuthStateListener(listener);