header = ds_map_create();
header[? "Content-Type"] = "application/json";
header[? "api"] = "My api key that im censoring :)";
json = ds_map_create()
{
json[? "from"] = "mailgun#lapaihui.org";
json[? "to"] = "My email that im also censoring :]";
json[? "subject"] = "Dear user";
json[? "text"] = "This is your game talking";
}
http_request("https://api.mailgun.net/v3/lapaihui.org/messages","POST",header,json);
basically im trying to send an email using mailgun api but something is just not working
if any netcode gods can help i will greatly apreciete it and credit it!
Without any information on the error you getting or why it's not working, I can't be certain what you need, but I have two working solutions that will hopefully help.
If you're sending from the browser:
function sendMgEmail(pFrom, pTo, pSubject, pText, mgApiKey){
const formData = new FormData();
formData.append('from', pFrom);
formData.append('to', pTo);
formData.append('subject', pSubject);
formData.append('text', pText);
const qXhr = new XMLHttpRequest;
const qMethod = 'POST';
const qUrl = 'https://api.mailgun.net/v3/{{YOUR_DOMAIN}}/messages';
qXhr.open(qMethod, qUrl);
qXhr.setRequestHeader('Authorization', 'Basic ' + window.btoa('api:' + mgApiKey));
qXhr.send(formData);
qXhr.onload = function() {
if(qXhr.status == '200' || qXhr.status == '201') {
console.log('email queued', qXhr.status, qXhr.responseText);
} else {
console.log('ERROR ', qXhr.status, qXhr.responseText);
}
}
}
If from a Nodejs application, the XMLHttpRequest approach does not seem to work:
First, refer to https://www.npmjs.com/package/mailgun.js?utm_source=recordnotfound.com#messages
Then, install form-data and mailgun.js
npm i form-data
npm i mailgun.js
Lastly, the code...
const FormData = require('form-data');
const Mailgun = require('mailgun.js');
exports.sendMgEmail(pFrom, pTo, pSubject, pText, mgApiKey) {
const mailgun = new Mailgun(FormData);
const mg = mailgun.client({username: 'api', key: mgApiKey})
mg.messages.create('{{YOUR_DOMAIN}}', {
from: pFrom,
to: pTo,
subject: pSubject,
text: pText
})
.then(msg => console.log(msg))
.catch(err => console.error(err));
}
Related
I am trying to receive notifications in an Expo React Native App.
The notifications will be sent using Azure Notification Hub REST API
I followed the steps below :
Added the Android project in Firebase Console
To get the Server Key I followed - Firebase messaging, where to get Server Key?
Configured the FCM ServerKey in Azure Notification Hub
Added the google-services.json at the root in my React Native App and modified app.json as mentioned in - https://docs.expo.dev/push-notifications/using-fcm/
To register in ANH, we first need the SAS Token - https://learn.microsoft.com/en-us/rest/api/notificationhubs/common-concepts I generated the token with the following code
const Crypto = require('crypto-js');
const resourceURI =
'http://myNotifHubNameSpace.servicebus.windows.net/myNotifHubName ';
const sasKeyName = 'DefaultListenSharedAccessSignature';
const sasKeyValue = 'xxxxxxxxxxxx';
const expiresInMins = 200;
let sasToken;
let location;
let registrationID;
let deviceToken;
function getSASToken(targetUri, sharedKey, ruleId, expiresInMins) {
targetUri = encodeURIComponent(targetUri.toLowerCase()).toLowerCase();
// Set expiration in seconds
var expireOnDate = new Date();
expireOnDate.setMinutes(expireOnDate.getMinutes() + expiresInMins);
var expires =
Date.UTC(
expireOnDate.getUTCFullYear(),
expireOnDate.getUTCMonth(),
expireOnDate.getUTCDate(),
expireOnDate.getUTCHours(),
expireOnDate.getUTCMinutes(),
expireOnDate.getUTCSeconds()
) / 1000;
var tosign = targetUri + '\n' + expires;
// using CryptoJS
//var signature = CryptoJS.HmacSHA256(tosign, sharedKey);
var signature = Crypto.HmacSHA256(tosign, sharedKey);
var base64signature = signature.toString(Crypto.enc.Base64);
//var base64signature = signature.toString(CryptoJS.enc.Base64);
var base64UriEncoded = encodeURIComponent(base64signature);
// construct autorization string
var token =
'SharedAccessSignature sr=' +
targetUri +
'&sig=' +
base64UriEncoded +
'&se=' +
expires +
'&skn=' +
ruleId;
console.log('signature:' + token);
return token;
}
I then called the create registration API - https://learn.microsoft.com/en-us/rest/api/notificationhubs/create-registration-id
The registrationID has to be extracted from the response header of the API Call
I used the following code to generate the ANH Regsitration ID
async function createRegistrationId() {
const endpoint =
'https://xxxxxx.servicebus.windows.net/xxxxxxx/registrationIDs/?api-version=2015-01';
sasToken = getSASToken(resourceURI, sasKeyValue, sasKeyName, expiresInMins);
const headers = {
Authorization: sasToken,
};
const options = {
method: 'POST',
headers: headers,
};
const response = await fetch(endpoint, options);
if (response.status !== 201) {
console.log(
'Unbale to create registration ID. Status Code: ' + response.status
);
}
console.log('Response Object : ', response);
for (var pair of response.headers.entries()) {
//console.log(pair[0] + ': ' + pair[1]);
}
location = response.headers.get('Location');
console.log('Location - ' + location);
console.log('Type - ' + response.type);
registrationID = location.substring(
location.lastIndexOf('registrationIDs/') + 'registrationIDs/'.length,
location.lastIndexOf('?api-version=2015-01')
);
console.log('Regsitration ID - ', registrationID);
return location;
}
Next step was to update this registration ID in ANH with the Native Device Token
I used expo-notifications package and the method getDevicePushTokenAsync() method to get the native device token
async function registerForPushNotificationsAsync() {
let token;
if (Device.isDevice) {
const { status: existingStatus } = await Notifications.getPermissionsAsync();
let finalStatus = existingStatus;
if (existingStatus !== 'granted') {
const {
status
} = await Notifications.requestPermissionsAsync();
finalStatus = status;
}
if (finalStatus !== 'granted') {
alert('Failed to get push token for push notification!');
return;
}
token = (await Notifications.getDevicePushTokenAsync()).data;
console.log(token);
} else {
alert('Must use physical device for Push Notifications');
}
if (Platform.OS === 'android') {
Notifications.setNotificationChannelAsync('default', {
name: 'default',
importance: Notifications.AndroidImportance.MAX,
vibrationPattern: [0, 250, 250, 250],
lightColor: '#FF231F7C',
});
}
return token;
}
The native device token was in the following format on Android device
c6RI81R7Rn66kWZ0rar3M2:APA91bEcbLXGwEZF-8hu1yGHfXgWBNuxr_4NY_MR8d7HEzeHAJrjoJnjUlneAIiVglCNIGUr11qkP1G4S76bx_H7NItxfQhZa_bgnQjqSlSaY4-oCoarDYWcY-Mz_ulW8rQZFy_SA6_j
I then called the updateRegistrationId API - https://learn.microsoft.com/en-us/rest/api/notificationhubs/create-update-registration
async function updateRegistraitonId() {
//IF you use registrationIDs as in returned location it was giving 401 error
const endpoint =
'https://xxxxx.servicebus.windows.net/xxxxxxx/registrations/' +
registrationID +
'?api-version=2015-01';
const endpoint1 = location;
const headers = {
Authorization: sasToken,
'Content-Type': 'application/atom+xml;type=entry;charset=utf-8',
};
//Remember to create well-formed XML using back-ticks
//else you may get 400 error
//If you use the tags element it was giving an error
const regDATA = `<entry xmlns="http://www.w3.org/2005/Atom">
<content type="application/xml">
<GcmRegistrationDescription xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/netservices/2010/10/servicebus/connect">
<GcmRegistrationId>${deviceToken}</GcmRegistrationId>
</GcmRegistrationDescription>
</content>
</entry>`;
const options = {
method: 'PUT',
headers: headers,
body: regDATA,
};
const response = await fetch(endpoint, options);
if (response.status !== 201) {
console.log(
'Looks like there was a problem. Status Code: ' + response.status
);
console.log('Response Object : ', response);
//return;
}
}
According to API documentation, I should get 201 response, I got 200 response code . I am not sure if this is the issue
After this I had the notification handling code to recieve the notification,similar to the code in - https://docs.expo.dev/versions/latest/sdk/notifications/
I then tried to send notification using Test Send from ANH, it failed with the error -
**
"The Token obtained from the Token Provider is wrong"
**
I checked in ANH Metrics, the error was categorized as GCM Authentication error, GCM Result:Mismatch SenderId
I tried to check for documentation to add the SenderId , but I couldnt find anyway to inlcude the SenderId also in the payload of updateRegistration call (in xml atom entry)
I tried to use the device token and send directly from Firebase Console, I did not receive it either.
I used the Direct Send API of Azure notification Hub but still did not receive anything
I am suspecting there could be some issue in the way I am handling notifiations in the client device, I can fix that later , but first I will have to resolve the error I am getting in Test Send in Azure NH
Any help to be able to successfully send using Test Send in ANH or pointers ahead for next steps will be much appreciated
I'm trying to make an app that can send payments to PayBill numbers with Safaricom's "Lipa Na M-Pesa" (a Kenyan thing). The call is a POST request to URL:
https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest
with header:
{
'Host': 'sandbox.safaricom.co.ke',
'Authorization': 'Bearer ${await mpesaAccessToken}',
'Content-Type': 'application/json',
}
and body:
{
"BusinessShortCode": "$businessShortCode",
"Password": "${generateLnmPassword(timeStamp)}",
"Timestamp": "$timeStamp",
"TransactionType": "CustomerPayBillOnline",
"Amount": "10",
"PartyA": "$userPhoneNumber",
"PartyB": "$businessShortCode",
"PhoneNumber": "$userPhoneNumber",
"CallBackURL": "?????????????????????????????",
"AccountReference": "account",
"TransactionDesc": "test",
}
I've received an access token, generated a password and made the call successfully, except for that CallBackURL thing... The M-Pesa docs describe their callback like this:
CallBackURL
This is the endpoint where you want the results of the transaction delivered. Same rules for Register URL API callbacks apply.
all API callbacks from transactional requests are POST requests, do not expect GET requests for callbacks. Also, the data is not formatted into application/x-www-form-urlencoded format, it is application/json, so do not expect the data in the usual POST fields/variables of your language, read the results directly from the incoming input stream.
(More info here, but you may need to be logged in: https://developer.safaricom.co.ke/get-started see "Lipa na M-Pesa")
My app is hosted on Firebase Cloud Firestore. Is there any way I can create a callback URL with them that will receive their callback as a document in a Firestore collection?...
Or would this be impossible, given that they would need authorization tokens and stuff to do so... and I can't influence what headers and body M-Pesa will send?
(PS Btw, I code in Flutter/Dart so plz don't answer in Javascript or anything! I'll be clueless... :p Flutter/Dart or just plain text will be fine. Thanks!)
Is there any way I can create a callback URL with them that will
receive their callback as a document in a Firestore collection?...
The most common way to do that in the Firebase ecosystem is to write an HTTPS Cloud Function that will be called by the Safaricom service.
Within the Cloud Function you will be able to update the Firestore document, based on the content of the POST request.
Something like:
exports.safaricom = functions.https.onRequest((req, res) => {
// Get the header and body through the req variable
// See https://firebase.google.com/docs/functions/http-events#read_values_from_the_request
return admin.firestore().collection('...').doc('...').update({ foo: bar })
.then(() => {
res.status(200).send("OK");
})
.catch(error => {
// ...
// See https://www.youtube.com/watch?v=7IkUgCLr5oA&t=1s&list=PLl-K7zZEsYLkPZHe41m4jfAxUi0JjLgSM&index=3
})
});
I did note that you ask us to not "answer in Javascript or anything" but in Flutter/Dart, but I don't think you will able to implement that in Flutter: you need to implement this webhook in an environment that you fully control and that exposes an API endpoint, like your own server or a Cloud Function.
Cloud Functions may seem complex at first sight, but implementing an HTTPS Cloud Functions is not that complicated. I suggest you read the Get Started documentation and watch the three videos about "JavaScript Promises" from the Firebase video series, and if you encounter any problem, ask a new question on SO.
Cloud functions are not Dart-based.
See below solution;
const functions = require("firebase-functions");
const admin = require("firebase-admin");
const parse = require("./parse");
admin.initializeApp();
exports.lmno_callback_url = functions.https.onRequest(async (req, res) => {
const callbackData = req.body.Body.stkCallback;
const parsedData = parse(callbackData);
let lmnoResponse = admin.firestore().collection('lmno_responses').doc('/' + parsedData.checkoutRequestID + '/');
let transaction = admin.firestore().collection('transactions').doc('/' + parsedData.checkoutRequestID + '/');
let wallets = admin.firestore().collection('wallets');
if ((await lmnoResponse.get()).exists) {
await lmnoResponse.update(parsedData);
} else {
await lmnoResponse.set(parsedData);
}
if ((await transaction.get()).exists) {
await transaction.update({
'amount': parsedData.amount,
'confirmed': true
});
} else {
await transaction.set({
'moneyType': 'money',
'type': 'deposit',
'amount': parsedData.amount,
'confirmed': true
});
}
let walletId = await transaction.get().then(value => value.data().toUserId);
let wallet = wallets.doc('/' + walletId + '/');
if ((await wallet.get()).exists) {
let balance = await wallet.get().then(value => value.data().moneyBalance);
await wallet.update({
'moneyBalance': parsedData.amount + balance
})
} else {
await wallet.set({
'moneyBalance': parsedData.amount
})
}
res.send("Completed");
});
Parse function.
const moment = require("moment");
function parse(responseData) {
const parsedData = {};
parsedData.merchantRequestID = responseData.MerchantRequestID;
parsedData.checkoutRequestID = responseData.CheckoutRequestID;
parsedData.resultDesc = responseData.ResultDesc;
parsedData.resultCode = responseData.ResultCode;
if (parsedData.resultCode === 0) {
responseData.CallbackMetadata.Item.forEach(element => {
switch (element.Name) {
case "Amount":
parsedData.amount = element.Value;
break;
case "MpesaReceiptNumber":
parsedData.mpesaReceiptNumber = element.Value;
break;
case "TransactionDate":
parsedData.transactionDate = moment(
element.Value,
"YYYYMMDDhhmmss"
).unix();
break;
case "PhoneNumber":
parsedData.phoneNumber = element.Value;
break;
}
});
}
return parsedData;
}
module.exports = parse;
I created a Google Sheet that uses a Google Script to generate short URLs via Firebase API.
This is the code in the Google Script
function URLShortener(longURL) {
var body = {
"dynamicLinkInfo": {
"domainUriPrefix": "https://go.example.com",
"link" : longURL
},
"suffix": {
"option": "SHORT"
}
};
var key = 'xxxxxxx'
var url = "https://firebasedynamiclinks.googleapis.com/v1/shortLinks?key=" + key;
var options = {
'method': 'POST',
"contentType": "application/json",
'payload': JSON.stringify(body),
};
var response = UrlFetchApp.fetch(url, options);
var json = response.getContentText();
var data = JSON.parse(json);
var obj = data["shortLink"];
return obj;
Logger.log(obj)
}
The script works and it generates URLs similar to https://go.example.com/Xdka but these link redirect to https://example.com/Xdka instead of the actual URL that is sent, e.g. https://example.com/final_url.
If I try to generate these short links from the Firebase dashboard the same happens.
Did I misunderstand how these short URLs work or am I missing something?
Just want to check, is there any API to add the authorized domain in a programmatical way instead of adding it manually by going to Firebase console?
Also, is there any limit on how many domains can be added as the authorized domains?
JavaScript in Cloud Functions solution
import { google } from "googleapis";
(async () => {
/**
* ! START - Update Firebase allowed domains
*/
// Change this to whatever you want
const URL_TO_ADD = "engineering.acme-corp.net";
// Acquire an auth client, and bind it to all future calls
const auth = new google.auth.GoogleAuth({
scopes: ["https://www.googleapis.com/auth/cloud-platform"],
});
const authClient = await auth.getClient();
google.options({ auth: authClient });
// Get the Identity Toolkit API client
const idToolkit = google.identitytoolkit("v3").relyingparty;
/**
* When calling the methods from the Identity Toolkit API, we are
* overriding the default target URLs and payloads (that interact
* with the v3 endpoint) so we can talk to the v2 endpoint, which is
* what Firebase Console uses.
*/
// Generate the request URL
const projectId = await auth.getProjectId();
const idToolkitConfigUrl = `https://identitytoolkit.googleapis.com/admin/v2/projects/${projectId}/config`;
// Get current config so we can use it when we later update it
const currentConfig = await idToolkit.getProjectConfig(undefined, {
url: idToolkitConfigUrl,
method: "GET",
});
// Update the config based on the values that already exist
await idToolkit.setProjectConfig(undefined, {
url: idToolkitConfigUrl,
method: "PATCH",
params: { updateMask: "authorizedDomains" },
body: JSON.stringify({
authorizedDomains: [
...(currentConfig.data.authorizedDomains || []),
URL_TO_ADD,
],
}),
});
})();
A quick note on other languages
The principles should be the same:
Find a way to interact with Google's identify toolkit API (maybe Google offers an SDK to your language)
Get current config
Set new config
If you can't find an SDK, you can also work with raw http requests: https://cloud.google.com/identity-platform/docs/reference/rest/v2/projects/getConfig (it's just a bit trickier to do authentication when doing everything manually)
There is no API for this - you must do it through the console. You can also file a feature request with Firebase support if you want.
There doesn't appear to be any documentation stating limits of number of domains. Again, reach out to Firebase support if the documentation is unclear.
Thanks #Jean Costa
Totally working for me.
Here is C# implementation
using Google.Apis.Auth.OAuth2;
using Newtonsoft.Json;
var serviceAccountJsonFile = "path to service account json";
var projectId = "your project ids";
var authorizedDomains = new
{
authorizedDomains = new string[] {
"localhost",
"******.firebaseapp.com",
"*********.web.app",
"abc.def.com"
}
}; // your desire authorized domain
List<string> scopes = new()
{
"https://www.googleapis.com/auth/identitytoolkit",
"https://www.googleapis.com/auth/firebase",
"https://www.googleapis.com/auth/cloud-platform"
};
var url = "https://identitytoolkit.googleapis.com/admin/v2/projects/" + projectId + "/config";
using var stream = new FileStream(serviceAccountJsonFile, FileMode.Open, FileAccess.Read);
var accessToken = GoogleCredential
.FromStream(stream) // Loads key file
.CreateScoped(scopes) // Gathers scopes requested
.UnderlyingCredential // Gets the credentials
.GetAccessTokenForRequestAsync().Result; // Gets the Access Token
var body = JsonConvert.SerializeObject(authorizedDomains);
using (var client = new HttpClient())
{
var request = new HttpRequestMessage(HttpMethod.Patch, url) {
Content = new StringContent(body,System.Text.Encoding.UTF8)
};
request.Headers.Add("Accept", "application/json");
request.Headers.Add("Authorization", "Bearer " + accessToken);
try
{
var response = client.SendAsync(request).Result;
Console.WriteLine(response.Content.ReadAsStringAsync().Result);
}
catch (HttpRequestException ex)
{
// Failed
}
}
Thanks #Jean Costa and #Yan Naing
here is my php implemetation
use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\Exception\TransferException;
use Google\Service\IdentityToolkit;
use Google\Service\IAMCredentials;
$KEY_FILE_LOCATION = storage_path('/app/credentials/service-account-1.json') ;
if (!file_exists($KEY_FILE_LOCATION)) {
throw new Exception(sprintf('file "%s" does not exist', $KEY_FILE_LOCATION));
}
$json= file_get_contents($KEY_FILE_LOCATION);
if (!$config = json_decode($json, true)) {
throw new Exception('invalid json for auth config');
}
$client = new \Google\Client();
$client->setAuthConfig($config );
$client->setScopes([ "https://www.googleapis.com/auth/identitytoolkit",
"https://www.googleapis.com/auth/firebase",
"https://www.googleapis.com/auth/cloud-platform"]);
$service = new IdentityToolkit($client);
// Get the Identity Toolkit API client
$idToolkit = $service->relyingparty;
//Get current config
$current_config= $idToolkit->getProjectConfig();
//Get service account access token
$access_token_req = new IAMCredentials\GenerateAccessTokenRequest();
$access_token_req->setScope( "https://www.googleapis.com/auth/firebase");
$credentials = new IAMCredentials($client);
$access_token = $credentials->projects_serviceAccounts->generateAccessToken("projects/-/serviceAccounts/{$config["client_email"]}" , $access_token_req )->getAccessToken();
// Generate the request URL (https://cloud.google.com/identity-platform/docs/reference/rest/v2/projects/updateConfig)
$idToolkitConfigUrl = "https://identitytoolkit.googleapis.com/admin/v2/projects/{$config["project_id"]}/config";
$authorized_domains = [ 'authorizedDomains' => array_merge( ['twomore.com'],$current_config->authorizedDomains)];
$client = new GuzzleClient( );
$response = null;
try {
$response = $client->request('PATCH', $idToolkitConfigUrl, [
'verify' => Helpers::isProduction() ? true : false ,
'http_errors'=> false, //off 4xx and 5xx exceptioins
'json' => $authorized_domains ,
'headers' => [
"Authorization" => "Bearer " . $access_token ,
"Accept" => "application/json",
]
]);
} catch (TransferException $e) {
throw new Exception( $e->getMessage());
}
$data = json_decode($response->getBody()->getContents(),true);
if($response->getStatusCode()!==200){
throw new Exception($response->getReasonPhrase() . ( isset($data['exception']['message']) ? " - " . $data['exception']['message'] : ""));
}
return response()->json(['data' => [
'authorized_domains' => $data['authorizedDomains']
]]);
I'm trying to parse incoming emails from the Sendgrid Inbound Webhook with Meteor, Picker and Body-Parser. I get the emails but when I log the request body I get an empty object. What am I missing here??
var bodyParser = require('body-parser');;
Picker.middleware( bodyParser.json() );
Picker.route('/incoming/', function(params, req, res, next) {
console.log("Body: " + JSON.stringify(req.body));
}
The problem was related to the content-type being multipart/form-data. Got it working like this:
var multiparty = require('multiparty');
var bodyParser = Npm.require('body-parser');
Picker.middleware(bodyParser.urlencoded({ extended: true }));
Picker.middleware(bodyParser.json());
Picker.route('/incoming/', function(params, req, res, next) {
var form = new multiparty.Form();
form.parse(req, function(err, fields, files) {
console.log("Heureka: " + JSON.stringify(fields) + JSON.stringify(files));
res.writeHead(200, {'content-type': 'text/plain'});
res.write('received upload:\n\n');
res.end("thanks");
});
});
I know this has already been answered but I've got an alternative solution using express and multer. I've created a repo express-sendgrid-inbound-parse to get you started.
I recommend leaving POST the raw, full MIME message unchecked as you can access to more email data.
console.log('dkim: ', body.dkim)
console.log('to: ', body.to)
console.log('cc: ', body.cc)
console.log('from: ', body.from)
console.log('subject: ', body.subject)
console.log('sender_ip: ', body.sender_ip)
console.log('spam_report: ', body.spam_report)
console.log('envelope: ', body.envelope)
console.log('charsets: ', body.charsets)
console.log('SPF: ', body.SPF)
console.log('spam_score: ', body.spam_score)
if (rawFullMimeMessageChecked) {
console.log('email: ', body.email)
} else {
console.log('headers: ', body.headers)
console.log('html: ', body.html)
console.log('text: ', body.text)
console.log('attachments: ', body.attachments)
console.log('attachment-info: ', body['attachment-info'])
console.log('content-ids: ', body['content-ids'])
}
It sounds like the incoming content from SendGrid doesn't have a application/json Content-Type, so bodyParser.json() can't parse it properly. Try adding a bodyParser.urlencoded() call as well, to try to parse a application/x-www-form-urlencoded Content-Type, to see if that helps. So something like:
var bodyParser = require('body-parser');
Picker.middleware(bodyParser.json());
Picker.middleware(bodyParser.urlencoded({ extended: false }));
Picker.route('/incoming/', function(params, req, res, next) {
console.log("Body: " + JSON.stringify(req.body));
}
You can also do this with multer. Here is the express server version:
const express = require(“express”);
const app = express();
var multer = require(“multer”);
var upload = multer();
app.post(“/”, upload.none(), function (req, res) {
console.log(req.body);
});