How to pass dynamic values to TwiML - javascript

I have an ASP.Net Core website in which I want to start an outbound call on the number provided on the page. For this, first I generate a token from the backend controller as:
var accountSid = ConfigurationManager.AppSettings["TwilioAccountSid"];
var authToken = ConfigurationManager.AppSettings["TwilioAuthToken"];
var appSid = ConfigurationManager.AppSettings["TwilioTwimlAppSid"];
var scopes = new HashSet<IScope>
{
{ new OutgoingClientScope(appSid) }
};
var capability = new ClientCapability(accountSid, authToken, scopes: scopes);
return capability.ToJwt();
Now on the page, once anyone enters the phone number and clicks on call, I initiate the call with the javascript function Twilio.Device.connect().
The TwiML app whose AppSID I am using while generating token has the Request URL which provides following TwiML:
<Response>
<Dial callerId="+1XXXYYY">+91XXXAAA</Dial>
</Response>
This works fine and I am able to receive call on the given hardcoded number in the TwiML. However I want to call on the number entered on the page.
So my question is how can I pass the numbered entered on the page(alongwith the CallerID as well) to this TwiML so that it dials to the specific entered number and not the hardcoded one?
If this is not possible then is there any other alternative available for my architecture?

Was able to finally start the call. For this, I used params in javascript method:
var params = {
phoneToCall: document.getElementById('phone-number').value,
callerIdToShow: document.getElementById('callerId').value
};
Twilio.Device.connect(params);
Then on the URL which returns the TwiML to dial, I received these params and used them in the xml as:
[HttpGet]
public HttpResponseMessage GetTwiML(string phoneToCall, string callerIdToShow)
{
string XML = $"<Response><Dial callerId=\"{callerIdToShow}\">{phoneToCall}</Dial></Response>";
return new HttpResponseMessage()
{
Content = new StringContent(XML, Encoding.UTF8, "application/xml")
};
}

Related

Handling and returning data via POST request in Node.js

I want to implement web application, which accept user input, encode or decode it and then return it to the user's screen. I can accept user input and process it, but can't return it to screen.
Here is the code snippet:
exports.postEncDec = (req, res, next) => {
let database64 = req.body.inputENC;
let decodedBase64 = new Buffer.from(dataBase64, "base64").toString("ascii");
console.log(decodedBase64);
};
Here I accept the data provided by the user and assign it to the dataBase64 variable. Then I decode that data and assign it to the decodedBase64 variable. Now I want to write this data to the input on the screen, but I can't. How can I write this decoded data to the screen (I can just write it to the console)?

how do I set a call status callback from the Twilio Voice JavaScript SDK?

I'm trying to use the Twilio Programmable Voice JavaScript SDK to make an outbound call with a statusCallback and statusCallbackEvent so I can update another system after the call is completed.
Here's my code.
async function makeOutgoingCall() {
const params = {
// get the phone number to call from the DOM
To: phoneNumberInput.value,
CallerId: myCallerId,
statusCallback: OutBoundCallbackURL,
statusCallbackEvent: 'completed'
};
console.log(params);
if (device) {
log(`Attempting to call ${params.To} from caller id: ${params.CallerId} ...`);
// Twilio.Device.connect() returns a Call object
const call = await device.connect({ params });
dtmf_1.onclick = function(){call.sendDigits('1')};
dtmf_2.onclick = function(){call.sendDigits('2')};
dtmf_3.onclick = function(){call.sendDigits('3')};
dtmf_4.onclick = function(){call.sendDigits('4')};
dtmf_5.onclick = function(){call.sendDigits('5')};
dtmf_6.onclick = function(){call.sendDigits('6')};
dtmf_7.onclick = function(){call.sendDigits('7')};
dtmf_8.onclick = function(){call.sendDigits('8')};
dtmf_9.onclick = function(){call.sendDigits('9')};
dtmf_0.onclick = function(){call.sendDigits('0')};
dtmf_s.onclick = function(){call.sendDigits('*')};
dtmf_h.onclick = function(){call.sendDigits('#')};
/*
* add listeners to the Call
* "accepted" means the call has finished connecting and the state is now "open"
*/
call.on('accept', updateUIAcceptedOutgoingCall);
call.on('disconnect', updateUIDisconnectedOutgoingCall);
call.on('cancel', updateUIDisconnectedOutgoingCall);
call.on('reject', updateUIDisconnectedOutgoingCall);
outgoingCallHangupButton.onclick = () => {
log('Hanging up ...');
call.disconnect();
};
} else {
log('Unable to make call.');
}
}
I'd like it to send back TwiML like this:
<Response>
<Dial answerOnBridge="true" callerId="+19876543210">
<Number
statusCallbackEvent="completed"
statusCallback="https://myapp.com/calls/events"
statusCallbackMethod="POST">
+12349013030
</Number>
</Dial>
</Response>
But it's sending back this:
<Response>
<Dial answerOnBridge="true" callerId="+19876543210">
<Number>+1123456789</Number>
</Dial>
</Response>
I can't find a list of possible parameters for device.connect(). Not even sure that's what I need to edit.
Can someone help me out?
Updating this TwiML response is likely going to need to be done server side, so you may need to start from where you're generating the Twilio Access Token (that your client initially uses to register with Twilio) and trace that back to see where that server and specific handler live.
So, e.g., if there's an outgoingApplicationSid used to create the voice grant in the access token, you'll want to find the TwiML app with the corresponding sid in the Twilio console and follow the Voice Request URL its configured with to find the server. From within your server you should then be able to find where that TwiML response is created.
For a slightly more specific answer based on a couple assumptions:
Based on some of your code snippets above, it looks like you may have used the TwilioDevEd/voice-javascript-sdk-node as a springboard for your project.
If 1 is true, you likely set up a TwiML app in the Twilio console that points the Voice Request URL that tunnels to your locally running node server using ngrok as they direct in the README.
If those are true, then you'll want to look in your project for the voiceResponse handler and update the twiml.dial() call (which generates that response TwiML) to create the actual TwiML you want.
When Twilio makes the request to your TwiML app, it sends all the parameters that you pass to the connect method through as parameters in the body of the request. So, in your existing program, it appears that the To and CallerId parameters are being used. So you need to adjust your dial to also use the statusCallback and statusCallbackEvent parameters. That might look a bit like this:
const { statusCallback, statusCallbackEvent, CallerId, To } = req.body;
const twiml = new VoiceResponse();
const dial = twiml.dial({ callerId: CallerId });
dial.number({ statusCallback, statusCallbackEvent }, To);

Can't upload image to Cloudinary using client side signature call - invalid signature

I can't seem to generate the correct signature to upload an image successfully.
Error message -
message:'Invalid Signature 953e29c9b5cac0f611e347d508aaff75f11f0547. String to sign - 'timestamp=1631318071'.'
Maybe someone can tell me what I'm going wrong? Maybe the order of something (according to the docs)?
FYI - I tried changing the order I pass in the parameters server side but it didn't seem to do much to successfully upload.
Here is how I generate the signature client side (C#) and pass data to the client (Angular)
DateTime foo = DateTime.Now;
long timeStamp = ((DateTimeOffset)foo).ToUnixTimeSeconds();
var p = new Dictionary<string, object>();
p.Add("api_key", _cloudinaryConfig.Value.ApiKey);
p.Add("api_secret", _cloudinaryConfig.Value.ApiSecret);
p.Add("cloud_name", _cloudinaryConfig.Value.CloudName);
p.Add("timestamp", timeStamp);
var signature = _cloudinary.Api.SignParameters(p);
return Ok(new {
cloudUploadUrl = _cloudUploadUrl,
cloudName = _cloudinaryConfig.Value.CloudName,
cloudApiKey = _cloudinaryConfig.Value.ApiKey,
cloudSignature = signature,
cloudTimeStamp = timeStamp
});
Here is the client trying to upload an image in Angular
const data = new FormData();
data.append('file', croppedImage.base64);
data.append("api_key", cloudinaryParams.cloudApiKey);
data.append("signature", cloudinaryParams.cloudSignature);
data.append("timestamp", cloudinaryParams.cloudTimeStamp);
const fileResult = await fetch(cloudinaryParams.cloudUploadUrl, {
method: 'POST',
body: data,
});
const fileData = await fileResult.json();
if (fileData.error) {
// do something to handle the error!
}
The string in the error message shows the parameters that are being checked on the server side against the signature you provided on the upload API call.
Based on that error, the only parameter that should be used to create the signature for your example upload API call is "timestamp", and it needs to have a value of 1631318071 on both the API call and in the function that created the signature: https://cloudinary.com/documentation/signatures
If I understand your C# code correctly, you can most likely fix this by removing all the p.add() calls except the one with the timestamp parameter

CORS block: WebApp POST to Sheet

I am getting an error due to CORS policy. It seems a common issue. I have read other questions and tried some of their solutions, but it still does not work.
My app does the following:
1- GET a list of values from a google sheet and show them in a drop down list in a web app (still using script.google.com)
2- POST the value that the user select in the drop down list from the web app to the sheet.
1 (GET) was working fine with no issues. When I added 2 (POST), it gave me the CORS error for the POST call. The GET seems fine.
Here is the code for POST:
function doPost(e) {
var value = JSON.parse(e.postData.contents).value;
var ss = SpreadsheetApp.openById('1Zbvy1x_DsBlcwK4FdjoY4m0MFvS_tYZlGtKvD36fDyk');
var sh = ss.getSheetByName('Dashboard');
sh.getRange(92, 2).setValue(value);
return ContentService.createTextOutput(JSON.stringify({message: "ok"})).setMimeType(ContentService.MimeType.JSON);
}
with HTML file:
<script>
function listQ() {
const index = this.selectedIndex;
if (index > 0) {
const e = document.getElementById("sel1");
const value = e.options[index].value;
const url = "https://script.google.com/a/google.com/macros/s/AKfycbxHX7cthji076OrqfY9ZpGa7jNDxKHUMf_ib7Ekmoo0Ir5DQF1Y/exec";
fetch(url, {
method: "POST",
body: JSON.stringify({index: index, value: value}),
})
.then(function (response) {
return response.json();
})
.then(function (data) {
console.log(data);
})
}
}
document.getElementById("sel1").addEventListener("change",listQ);
</script>
The GET part is fine, so no need to add the code here.
I have tried to change POST with PUT as suggested here: Change Google Sheet data based on user selection in Google Site (Web App) but it gave the same error.
I have also read this link, but mine is a POST request, not GET and I am not sure how to apply it to my case: CORS authorization on google sheets API requests
FYI The "origin" in the CORS error message is ...googleusercontent.com
UPDATE: I have also tried this: Google Apps Script cross-domain requests stopped working
I think I should implement that solution, but when I tried to add those mods in my code and it did not work. Probably I am adding the mods incorrectly since I am not 100% sure what I am doing. I get the error that the call back function is not defined. Here is what I did:
function doPost(e) {
var callback = e.parameter.callback;
var value = JSON.parse(e.postData.contents).value;
var ss = SpreadsheetApp.openById('1ROvDcIQ8JCGvvLvCvTKIqSor530Uj9ZJv-n6hQ761XA');
var sh = ss.getSheetByName('Dashboard');
sh.getRange(92, 2).setValue(value);
return ContentService.createTextOutput(callback+'('+JSON.stringify({message: "ok"})+')').setMimeType(ContentService.MimeType.JSON);
}
and in HTML side, I just modified the URL:
const url = "https://script.google.com/a/google.com/macros/s/AKfycbxHX7cthji076OrqfY9ZpGa7jNDxKHUMf_ib7Ekmoo0Ir5DQF1Y/exec?offset="+offset+"&baseDate="+baseDate+"&callback=?";
In Apps Script Web Apps, in order to access server-side functions from your current project, you don't need to make a GET or POST request to the Web App URL. You just need to use google.script.run.
In your exact case, you could do the following:
function listQ() {
const index = this.selectedIndex;
if (index > 0) {
const e = document.getElementById("sel1");
const value = e.options[index].value;
const body = { index: index, value: value };
google.script.run.withSuccessHandler(yourCallback).yourServerSideFunc(body);
}
}
function yourCallBack(response) {
// Whatever you want to do with the data retrieved from server-side
}
In the code above, the client-side function calls a server side function called yourServerSideFunc via google.script.run. If the server-side function returns successfully, a callback function is called (function yourCallback), whose purpose is to handle the data returned from the server (a callback is needed since, on its own, the function called by google.script.run returns void). This callback function receives the content returned by the server-side function as an argument.
And in the server-side, no need to use doPost, since you would not be making a POST request:
function yourServerSideFunc(body) {
var value = body["value"];
var ss = SpreadsheetApp.openById('1Zbvy1x_DsBlcwK4FdjoY4m0MFvS_tYZlGtKvD36fDyk');
var sh = ss.getSheetByName('Dashboard');
sh.getRange(92, 2).setValue(value);
return ContentService.createTextOutput(JSON.stringify({message: "ok"})).setMimeType(ContentService.MimeType.JSON);
}
Reference:
HTML Service: Communicate with Server Functions
Class google.script.run (Client-side API)

How to properly generate Facebook Graph API App Secret Proof in Javascript

I am making a server side Facebook Graph API call to the all_mutual_friends edge: https://developers.facebook.com/docs/graph-api/reference/user-context/all_mutual_friends/
The call works when the two users are friends, but returns no useful data when they users aren't friends. According to the docs, this is because I must sign the call with the appsecret_proof parameter. No matter what I try, I am not able to successfully pass this parameter. I am using jsrsasign running on Parse. I have tried every configuration of using the access token as the message and my appSecret as the key, and vice versa. I have also tried multiple combinations of utf8 and hex. Every time I receive the error: invalid appsecret_proof provided in the API argument
Code:
var Signer = require("cloud/vendor/jsrsasign/lib/jsrsasign.js");
var userId = request.params.userId;
var accessToken = request.params.accessToken;
var appSecret = "redactedStringPastedFromFacebook";
var signer = new Signer.Mac({alg: "hmacsha256", pass: appSecret});
var appSecretString = signer.doFinalString(accessToken);
var appSecretHex = signer.doFinalHex(accessToken);
var graphRequestURL = "https://graph.facebook.com/v2.5/" + userId;
var fields = "?fields=context.fields(all_mutual_friends.fields(name,picture.width(200).height(200)))";
//var authorization = "&access_token=" + accessToken; //this works, but only for existing friends
var authorization = "&access_token=" + accessToken + "&appsecret_proof=" + appSecretHex;
return Parse.Cloud.httpRequest({
url: graphRequestURL + fields + authorization,
method: "GET",
})
Most examples I have seen are in PHP or Python and the crypto routines are a bit more clear. This works in that both appSecretString and appSecretHex don't throw errors and look reasonable, however the values are always rejected by Facebook.
Notes:
I have triple checked the App Secret value provided by Facebook
I have been approved by Facebook to use the all_mutual_friends feature, which is a requirement for this particular call
I am using Parse, which isn't Node, and can't use NPM modules that have external dependencies, which is why I am using jsrsasign. I also tried using CryptoJS directly, but it is no longer maintained and doesn't have proper module support and jsrsasign seems to wrap it anyway.
Here it is:
import CryptoJS from 'crypto-js';
const accessToken = <your_page_access_token>;
const clientSecret = <your_app_client_secret>;
const appsecretProof = CryptoJS.HmacSHA256(accessToken, clientSecret).toString(CryptoJS.enc.Hex);

Categories