Twilio Programmable Voice call immediately completes - python

I am using the new Twilio Programmable Voice SDK with Swift and Python. I have begun with the respective quick starter projects, and for the most part things work. I'm able to get a valid access token, I'm able to successfully create a call, and I am even able to receive the call. The problem is on the caller side of the house.
When I try to make a call via the Swift SDK the call is disconnected before it ever starts to ring on the other end.
I have read in the Twilio docs that the client.calls.create function will immediately return a status of complete if you do not handle the status_callback event. I have tried to add this but whenever I do I get an error saying the the key status_callback is not a valid parameter for the client.calls.create function. Also, I cannot find any examples anywhere of actually how to handle the call status.
My question is what in the wold am I doing wrong here? Any help would be greatly appreciated.
Here is my Python code
#app.route('/outgoing', methods=['GET', 'POST'])
def outgoing():
account_sid = os.environ.get("ACCOUNT_SID", ACCOUNT_SID)
api_key = os.environ.get("API_KEY", API_KEY)
api_key_secret = os.environ.get("API_KEY_SECRET", API_KEY_SECRET)
from_number = [HARD_CODED_PHONE_NUMBER_FOR_CALLER]
to_number = [HARD_CODED_PHONE_NUMBER_FOR_RECIPIENT]
client = Client(api_key, api_key_secret, account_sid)
call = client.calls.create(url='http://twimlets.com/holdmusic?Bucket=com.twilio.music.ambient', to=to_number, from_=from_number)
# return str(call.sid)
resp = twilio.twiml.Response()
resp.say("Thank you for calling");
return str(resp)
Here is my relevant iOS code. Please bear in mind that this is NOT my full source. I have only provided what should be necessary in this event. My full source does include handling the registry and invite delegates. I also did not include my source that shows/hides my active call UI as there are no problems with that. This is simply to show how I am placing a call and receiving the call complete delegate.
class VoiceManager: NSObject, PKPushRegistryDelegate, TVONotificationDelegate, TVOCallDelegate, AVAudioPlayerDelegate {
//MARK: - Singleton
static let sharedManager = VoiceManager()
//MARK: - Private Constants
private let baseURLString = [MY_WEBAPP_ENDPOINT]
private let accessTokenEndpoint = "/accessToken"
//MARK: - Private Variables
private var deviceTokenString:String?
private var callInvite: TVOCallInvite?
private var call: TVOCall?
private var status: VoiceStatus = .idle
//MARK: - Getters
private func fetchAccessToken() -> String? {
guard let accessTokenURL = URL(string: baseURLString + accessTokenEndpoint) else {
return nil
}
return try? String.init(contentsOf: accessTokenURL, encoding: .utf8)
}
func placeCall(withParameters params: VoiceParameters, completion: #escaping (_ success: Bool, _ error: VAError?) -> Void) {
if (call != nil) {
call?.disconnect()
completion(false, .phoneCallInProgress)
status = .callEnded
hideActiveCallUI()
} else {
guard let accessToken = fetchAccessToken() else {
completion(false, .phoneAccessTokenFetchFailed)
return
}
guard let paramsDict = params.toDictionary() else {
completion(false, .phoneAccessTokenFetchFailed)
return
}
playOutgoingRingtone(completion: { [weak self] in
if let strongSelf = self {
strongSelf.call = VoiceClient.sharedInstance().call(accessToken, params: [:], delegate: strongSelf) //NOTE: The params here are not necessary as the phone numbers for now are hard coded on the server
if (strongSelf.call == nil) {
strongSelf.status = .callEnded
completion(false, .phoneCallFailed)
return
} else {
strongSelf.status = .callConnecting
self?.showActiveCallUI(withParameters: params)
completion(true, nil)
}
}
})
}
}
// MARK: TVOCallDelegate
func callDidConnect(_ call: TVOCall) {
NSLog("callDidConnect:")
self.call = call
status = .inCall
routeAudioToSpeaker()
}
func callDidDisconnect(_ call: TVOCall) {
NSLog("callDidDisconnect:")
playDisconnectSound()
self.call = nil
status = .callEnded
hideActiveCallUI()
}
func call(_ call: TVOCall, didFailWithError error: Error) {
NSLog("call:didFailWithError: \(error)");
self.call = nil
status = .callEnded
hideActiveCallUI()
}
}

Twilio developer evangelist here.
Your Swift code says that your phone numbers are hard coded on the server right now. The issue, as Robert has said too, is that you are using the REST API to generate a call when you get a callback from Twilio to your /outbound endpoint.
What is actually happening is that when you generate the call in Swift on the device that starts the call for the application. Twilio then makes an HTTP request to your /outbound endpoint to see what to do with that call. So, instead of generating a new call with the REST API, you need to respond with TwiML to tell Twilio what to do with the call next.
In this case, it sounds like you are trying to dial straight onto another number. For that, you should try the following response:
#app.route('/outgoing', methods=['GET', 'POST'])
def outgoing():
from_number = [HARD_CODED_PHONE_NUMBER_FOR_CALLER]
to_number = [HARD_CODED_PHONE_NUMBER_FOR_RECIPIENT]
resp = twilio.twiml.Response()
with resp.dial(callerId=from_number) as r:
r.number(to_number)
return str(resp)
Let me know if that helps.

Note: I have also responded in the ticket you created with Twilio Support.
Please check your account debugger for the whole pile of error notifications you are getting. Here's an example:
An attempt to retrieve content from
https://voiceapp-twilio.herokuapp.com/outgoing
https://voiceapp-twilio.herokuapp.com/outgoing returned the HTTP
status code 500.
The Python web server returned an error message, which includes this:
TypeError: create() got an unexpected keyword argument
'status_events' // Werkzeug Debugger
It looks like a code error in your Python outgoing() function. Most notably, you are attempting to use the REST API to create a new call, when you should actually be returning TwiML. You should be returning TwiML that includes the Dial verb to create the outgoing call leg.

Related

Exchanging data between Python Telethon library and Node.js

I faced such a problem: in my small test app I have simple node.js (express) server and python script, which allows me to interact with Telegram API using Telethon library. In my scenario I have to provide my python script a phone number and a password. These data is asked in the input mode, so I can't figure out, how am I able to:
Accept input request from python script on node.js side;
Provide these credentials by node.js and pass them back via python's input;
Repeat this actions several times to gain all info I need.
These are my test files:
file.py
import os
from telethon import TelegramClient
api_id = 12345
api_hash = 'hash'
session = 'testing'
proxy = None
client = TelegramClient(session, api_id, api_hash, proxy=proxy).start()
def get_ids_list():
ids_dict = {}
async def do_fetch():
async for dialog in client.iter_dialogs():
ids_dict[dialog.name] = dialog.id
with client:
client.loop.run_until_complete(do_fetch())
return ids_dict
def print_ids_list():
async def do_print():
async for dialog in client.iter_dialogs():
print(dialog.name, 'has ID', dialog.id)
with client:
client.loop.run_until_complete(do_print())
print_ids_list()
When this script is run, I'm prompted the following input:
Please enter your phone (or bot token):
And this is my index.js, in which I want to pass prepared data to this input:
import express from "express";
import { spawn } from "child_process";
const app = express();
const port = 3000;
app.get("/", (req, res) => {
var myPythonScript = "path/to/file.py";
var pythonExecutable = "python";
var uint8arrayToString = function (data) {
return String.fromCharCode.apply(null, data);
};
const scriptExecution = spawn(pythonExecutable, [myPythonScript]);
scriptExecution.stdout.on("data", (data) => {
console.log(uint8arrayToString(data));
});
scriptExecution.stderr.on("data", (data) => {
console.log(uint8arrayToString(data));
});
scriptExecution.on("exit", (code) => {
console.log("Process quit with code : " + code);
});
});
app.listen(port, () =>
console.log(`Example app listening on port ${port}!`)
);
So, is there a way to solve this case?
Using with client is equivalent to client.start(), and as stated:
By default, this method will be interactive (asking for user input if needed), and will handle 2FA if enabled too.
You need to instead do what it does manually, remove with block, and make a function to authenticate (or confirm if already authorized).
for a minimal func example:
....
if not client.is_connected():
await client.connect()
if not await client.is_user_authorized():
await client.send_code_request(phone)
# get the code somehow, and in a reasonably fast way
try:
await client.sign_in(phone, code)
except telethon.errors.SessionPasswordNeededError:
'''
Retry with password.
note: it's necessary to make it error once
even if you know you have a pass, iow don't pass password the first time.
'''
await client.sign_in(phone, code, password=password)
return client
else:
return client
Dealing with the steps sequentially and interactivly while waiting for needed params to login successfully while also keeping in mind you have time limit until code expires is your task to handle any of their undefined behavior dependant on your usecase.

DUO-LABS WebAuthn: Server validation of credential failed: registration failed. error: registration rejected. error: unable to verify origin

I tried implementing a fingerprint-based authentication using DUO-lab's Python's webauthn package. I however ran into this error:
server validation of credential failed: registration failed. error: registration rejected. error: unable to verify origin..
When I checked the package's source code, I noticed this error unable to verify origin.. was raised when maybe your authenticator wasn't properly configured.
Is there a way I can specifically state that I only need platform authenticators rather than roaming authenticators without thickering with the package's source code? If there is, kindly include a full working code for Flask(This is what I am using now after the error chased me out of Django). My current configurations are:
RP_ID = 'nacesdecide.herokuapp.com' #The app is currently hosted on heroku
RP_NAME = 'nacesdecides nacesdecide'
ORIGIN = 'https://nacesdecide.herokuapp.com/'
The application is currently on heroku and can be accessed live via naces register. I want the application to use platform authenticators alone.
Update:
Some part of the code, on the client side (drafted from duo-lab's python webauthn flask demon js, is:
/**
* REGISTRATION FUNCTIONS
*/
/**
* Callback after the registration form is submitted.
* #param {Event} e
*/
const didClickRegister = async (e) => {
e.preventDefault();
// gather the data in the form
const form = document.querySelector("#register-form");
const formData = new FormData(form);
// post the data to the server to generate the PublicKeyCredentialCreateOptions
let credentialCreateOptionsFromServer;
try {
credentialCreateOptionsFromServer = await getCredentialCreateOptionsFromServer(
formData
);
} catch (err) {
showErrorAlert(`Failed to generate credential request options: ${err}`);
return console.error("Failed to generate credential request options:", err);
}
// convert certain members of the PublicKeyCredentialCreateOptions into
// byte arrays as expected by the spec.
const publicKeyCredentialCreateOptions = transformCredentialCreateOptions(
credentialCreateOptionsFromServer
);
// request the authenticator(s) to create a new credential keypair.
let credential;
*try {
credential = await navigator.credentials.create({
publicKey: publicKeyCredentialCreateOptions,
});*
} catch (err) {
showErrorAlert(`Error creating credential: ${err}`);
return console.error("Error creating credential:", err);
}
// we now have a new credential! We now need to encode the byte arrays
// in the credential into strings, for posting to our server.
const newAssertionForServer = transformNewAssertionForServer(credential);
// post the transformed credential data to the server for validation
// and storing the public key
let assertionValidationResponse;
try {
assertionValidationResponse = await postNewAssertionToServer(
newAssertionForServer
);
} catch (err) {
showErrorAlert(`Server validation of credential failed: ${err}`);
return console.error("Server validation of credential failed:", err);
}
// reload the page after a successful result
setTimeout(function () {
window.location.href = Flask.url_for("accounts.login");
}, 1000);
// window.location.reload();
};
On the server side, we have:
def webauthn_begin_activate():
# MakeCredentialOptions
username = request.form.get('register_username')
display_name = request.form.get('register_display_name')
if not util.validate_username(username):
return make_response(jsonify({'fail': 'Invalid username.'}), 401)
if not util.validate_display_name(display_name):
return make_response(jsonify({'fail': 'Invalid display name.'}), 401)
if User.query.filter_by(username=username).first():
return make_response(jsonify({'fail': 'User already exists.'}), 401)
#clear session variables prior to starting a new registration
session.pop('register_ukey', None)
session.pop('register_username', None)
session.pop('register_display_name', None)
session.pop('challenge', None)
session['register_username'] = username
session['register_display_name'] = display_name
challenge = util.generate_challenge(32)
ukey = util.generate_ukey()
# We strip the saved challenge of padding, so that we can do a byte
# comparison on the URL-safe-without-padding challenge we get back
# from the browser.
# We will still pass the padded version down to the browser so that the JS
# can decode the challenge into binary without too much trouble.
session['challenge'] = challenge.rstrip('=')
session['register_ukey'] = ukey
*make_credential_options = webauthn.WebAuthnMakeCredentialOptions(
challenge, RP_NAME, RP_ID, ukey, username, display_name,
'https://example.com')*
return jsonify(make_credential_options.registration_dict)
This function might also be of interest:
def verify_credential_info():
challenge = session['challenge']
username = session['register_username']
display_name = session['register_display_name']
ukey = session['register_ukey']
registration_response = request.form
trust_anchor_dir = os.path.join(
os.path.dirname(os.path.abspath(__file__)), TRUST_ANCHOR_DIR)
trusted_attestation_cert_required = True
self_attestation_permitted = True
none_attestation_permitted = True
webauthn_registration_response = webauthn.WebAuthnRegistrationResponse(
RP_ID,
ORIGIN,
registration_response,
challenge,
trust_anchor_dir,
trusted_attestation_cert_required,
self_attestation_permitted,
none_attestation_permitted,
uv_required=False) # User Verification
try:
webauthn_credential = webauthn_registration_response.verify()
except Exception as e:
return jsonify({'fail': 'Registration failed. Error: {}'.format(e)})
# Step 17.
#
# Check that the credentialId is not yet registered to any other user.
# If registration is requested for a credential that is already registered
# to a different user, the Relying Party SHOULD fail this registration
# ceremony, or it MAY decide to accept the registration, e.g. while deleting
# the older registration.
credential_id_exists = User.query.filter_by(
credential_id=webauthn_credential.credential_id).first()
if credential_id_exists:
return make_response(
jsonify({
'fail': 'Credential ID already exists.'
}), 401)
existing_user = User.query.filter_by(username=username).first()
if not existing_user:
if sys.version_info >= (3, 0):
webauthn_credential.credential_id = str(
webauthn_credential.credential_id, "utf-8")
webauthn_credential.public_key = str(
webauthn_credential.public_key, "utf-8")
user = User(
ukey=ukey,
username=username,
display_name=display_name,
pub_key=webauthn_credential.public_key,
credential_id=webauthn_credential.credential_id,
sign_count=webauthn_credential.sign_count,
rp_id=RP_ID,
icon_url='https://example.com')
db.session.add(user)
db.session.commit()
else:
return make_response(jsonify({'fail': 'User already exists.'}), 401)
flash('Successfully registered as {}.'.format(username))
return jsonify({'success': 'User successfully registered.'})
Second update: The full log below is what I have got:
webauthn.js:101
{id: "ATdDPQneoYF3tA6HYW8_dr2eBDy53VNoEIHRWUDfnmT2URKIs0SQ_lQ7BujdmcfM9Hc2xNH8bvLf4k3lQJ-7RX4",
rawId: "ATdDPQneoYF3tA6HYW8_dr2eBDy53VNoEIHRWUDfnmT2URKIs0SQ_lQ7BujdmcfM9Hc2xNH8bvLf4k3lQJ-7RX4",
type: "public-key",
attObj: "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjFD32HDgTSvc6zIlggmLLxXTKQyiabSwuLWNiTpJ3WQfmMoC_qX_QTuWPWHo4",
clientData: "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIj9pZFBhY2thZ2VOYW1lIjoiY29tLmFuZHJvaWQuY2hyb21lIn0", …}
attObj: "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjFD32HDgTSvcJxUiUIT6ViS4biCWKTR25PIW3beO9V5NdFAAAAALk_2WHy5kYvsSKCACJH3ngAQQE3Qz0J3qGBd7QOh2FvP3a9ngQ8ud1TaBCB0VlA355k9lESiLNEkP5UOwbo3ZnHzPR3NsTR_G7y3-JN5UCfu0V-pQECAyYgASFYID93HTRf5UtMsCsW9D5TyWQDSgMW2MDhiYWKnz3sq16zIlggmLLxXTKQyiabSwuLWNiTpJ3WQfmMoC_qX_QTuWPWHo4"
clientData: "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoidFNOS3g5RnVyWFI4dlhVdVBkVms5azhDcEhlMWMydnlrbkdwYUhseXZKYyIsIm9yaWdpbiI6Imh0dHBzOlwvXC9uYWNlc2RlY2lkZS5oZXJva3VhcHAuY29tIiwiYW5kcm9pZFBhY2thZ2VOYW1lIjoiY29tLmFuZHJvaWQuY2hyb21lIn0"
id: "ATdDPQneoYF3tA6HYW8_dr2eBDy53VNoEIHRWUDfnmT2URKIs0SQ_lQ7BujdmcfM9Hc2xNH8bvLf4k3lQJ-7RX4"
rawId: "ATdDPQneoYF3tA6HYW8_dr2eBDy53VNoEIHRWUDfnmT2URKIs0SQ_lQ7BujdmcfM9Hc2xNH8bvLf4k3lQJ-7RX4"
registrationClientExtensions: "{}"
type: "public-key"__proto__: Object
webauthn.js:107 Server validation of credential failed: Registration failed. Error: Registration rejected. Error: Unable to verify origin..
didClickRegister # webauthn.js:107
async function (async)
didClickRegister # webauthn.js:68
I think the issue is that there's a trailing slash on your ORIGIN value.
Peering into the attestation response's cliendDataJSON, the origin is reported as "https://nacesdecide.herokuapp.com":
Looking at how the Duo WebAuthn library verifies this response, the basic origin comparison is failing because your ORIGIN of "https://nacesdecide.herokuapp.com/" is not equivalent to the response's origin:
Response: "https://nacesdecide.herokuapp.com"
ORIGIN: "https://nacesdecide.herokuapp.com/"
If you remove that trailing slash then I'll bet everything will verify as expected.
#IAmKale answer solved the initial problem. However, it is important to be aware that you might run into server error: unexpected token < in JSON at position 0. I haven't found a concrete solution to this but ensuring that distinct username is used for registration fixed it. Also, it seems distinct devices are required for multiple registrations — one device per registration.

django-push-notification 401 unauthorised

I am trying to setup push notifications using the django-push-notifications, I have copied most of the code from the example. I have a button that calls the enablePushNotifications() function:
export function enablePushNotifications() {
console.log("enabling push notifications...");
if (!'serviceWorker' in navigator){
console.log("Push notifications not supported");
return;
}
navigator.serviceWorker.ready.then(
(registration)=>{
registration.pushManager.subscribe({
userVisibleOnly: true
}).then(
(sub)=>{
console.log(sub);
let endpointParts = sub.endpoint.split("/");
let registration_id = endpointParts[endpointParts.length - 1];
let data = {
'browser': loadVersionBrowser(navigator.userAgent).name.toUpperCase(),
'p256dh': btoa(String.fromCharCode.apply(null, new Uint8Array(sub.getKey('p256dh')))),
'auth': btoa(String.fromCharCode.apply(null, new Uint8Array(sub.getKey('auth')))),
'registration_id': registration_id,
'endpoint': sub.endpoint,
};
request(
"/api/subscribe",
data
)
}
).catch(
(error)=>console.error(error)
)
}
)
}
When I open the networking tab I don't see anything strange about the passed properties or headers. Specifically the browser value is correct. The implementation for the /api/subscribe URL is as follows:
import push_notifications.models as pushmodels
def registerPush(request):
data = requestFormatting.get_data(
request
)
device = pushmodels.WebPushDevice(
name=request.user.username,
active=True,
user=request.user,
registration_id=data["registration_id"],
p256dh=data["p256dh"],
auth=data["auth"],
browser=data["browser"]
)
device.save()
device.send_message("hello")
Calling this function raises a PushError: Push failed: 401 Unauthorized. I have tested on both chrome and firefox, both give the same error. I specify my FCM_API_KEY in settings, nothing else. I do not currently use VAPID, though I plan to in the future. I have tried various variations of the code, but nothing seems to work. The documentation is unclear on how to actually initialize devices, and I only picked a WebPushDevice objects since it seems to contain similar attributes as the data provided in the example.
This is my first time trying to use web push, so a little help would be much appreciated!

https request--Python code to C++

i have a function written in Python which calls Robinhood(a stock trading broker) API to get quote data("get_quote(self, symbol)" function in following code snipshot). And it works fine. Correct market data was returned.
import requests
import urllib
class Robinhood(object):
# All known endpoints as of September 5th, 2015
endpoints = {
"quotes": "https://api.robinhood.com/quotes/",
"user": "https://api.robinhood.com/user/",
"user/additional_info": "https://api.robinhood.com/user/additional_info/",
"user/basic_info": "https://api.robinhood.com/user/basic_info/",
"user/employment": "https://api.robinhood.com/user/employment/",
"user/investment_profile": "https://api.robinhood.com/user/investment_profile/",
"watchlists": "https://api.robinhood.com/watchlists/"
}
def get_quote(self, symbol):
''' Returns a qoute object for a given symbol including all data returned by Robinhood's API'''
data = { 'symbols' : symbol }
res = self.session.get(self.endpoints['quotes'], params=data)
if res.status_code == 200:
return res.json()['results']
else:
raise Exception("Could not retrieve quote: " + res.text)
I tried to implement this logic in C++ using Curl library. But it doesn't work. There was no compile or run time error but the program returned a single unreadable character instead of the market price of stock. It looks to me like my URL is not correctly set up but I couldn't figure out how to fix it. Does someone have an idea? Thank you!
std::string RobinhoodAPI::GetQuote(std::string ticker)
{
struct response resStr;
init_string(&resStr);
std::string url = "https://api.robinhood.com/quotes/symbols=AVP/";
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resStr);
resCode = curl_easy_perform(curl);
std::cout << std::string(resStr.ptr);
return std::string(resStr.ptr);
}
I have created an open api specification for the unofficial documentation of the robinhood api. With this you can generate http client for most languages.
Please visit here https://github.com/sabareeshkkanan/robinhood for the specification. And please visit this repo for how to generate client using this specification https://github.com/swagger-api/swagger-codegen

Can't make GAE Channel API work on local computer

I am creating a small application to test how GAE Channel API works. I think I have done all as it's described in the documentation but when I launch it, it shows an error in FireFox error log about syntax in the beginning and then another repeating error that an element wasn't found that.
Here is the first error info:
Source: http://127.0.0.1:8080/_ah/channel/dev?command=connect&channel=channel-773698929-185804764220139124118
Line 1, symbol 1
Here is the url where my javascript code tries to connect repeatedly and it raises the second error:
http://127.0.0.1:8080/_ah/channel/dev?command=poll&channel=channel-2071442473-185804764220139124118&client=1
I get the token through a JSON request with jQuery $.get. Then I run this code to get the token and open the channel. The error begins to show just when I run socket = channel.open(handler):
var response = JSON.parse(data);
var token = response.token.toString();
channel = new goog.appengine.Channel(token);
var handler = {
'onopen': onOpened,
'onmessage': onMessage,
'onerror': function() {
},
'onclose': function() {
}
};
socket = channel.open(handler);
Here is the server side code in Python to open the channel:
class OpenChannel(webapp.RequestHandler):
def get(self):
user = users.get_current_user()
token = channel.create_channel(user.user_id())
serialized = json.dumps({'token': token})
self.response.headers['Content-Type'] = "application/json"
self.response.out.write(serialized)
What's my error and what can I do? Thanks!
It seems that Channel API works on localhost different way than on GAE hosting. I uploaded it to the cloud and it works well now. Though it looks like it working fine on the local computer, it shows permanent JS error repeating in the error log.
You could try removing the handler argument and adding the handlers as methods of the socket object i.e. socket.onopen = function() {}; etc. That worked for me. But you are right. According to this, you should be able to get this working by using the handler argument. Hmm.

Categories