Reputation: 13515
I am developing a user authentication for my bookmarking extension. The extension works with the Google App Engine (Python) backend at http://ting-1.appspot.com/.
The first time the extension runs, background.html
opens options.html
in a new tab and the user enters his email address. I send the email address to the app like this (not tried yet):
in options.html
---------------
var formData = new FormData();
formData.append("extension_user", userEmail);
var xhr = new XMLHttpRequest();
xhr.open("POST", "http://ting-1.appspot.com/authsender", true);
xhr.send(formData);
In the app, the handler AuthSender
sends the confirmation email to the user:
class AuthSender(webapp.RequestHandler):
def post(self):
new_user = self.request.get("extension_user")
mail.send_mail(
sender="Ting <[email protected]>",
to=new_user,
subject="Ting Bookmarking: confirm your email",
body="""click on the link below to confirm your email:
<a href="http://ting-1.appspot.com/authHandler"></a>
Happy Bookmarking!""")
If the user clicks the link in the email AuthReceive
handler in the app will process the request and send confirmation to the extension. But I could not figure out how to send the confirmation to the extension.
When the user clicks on the link, how can I send confirmation back to the extension? Do I use XMLHttpRequest
again? How? And what kind of listener I need to put at the extension?
Below is a sketch of the AuthReceive
handler in the app:
class AuthReceive(InboundMailHandler):
def receive(self, message):
#---------------------------------------
#receive email // I know this part
#send confirmation to extension // I need help with this
#--------------------------------------
...
And in background.html
//...
// add listener and save the variable to localStorage
//...
Thanks for your help.
UPDATE
This is the code to conform to Moishe's answer. Everything works except the last step: handler.onmessage
is not triggered.
options.html
<html>
<head><title>Extension Options</title></head>
<body>
<p>Enter your gmail address:</p>
<textarea id="getEmail" style="margin-bottom: 4px; width: 250px; height: 20px">
</textarea><br />
<button id="save">Save</button>
<!--<button id="save">Clear</button>-->
<script type="text/javascript">
document.getElementById("getEmail").placeholder = "your gmail address" ;
//send entered gmail address to the server
document.getElementById("save").addEventListener
(
"click",
function ()
{
var userEmail = document.getElementById("getEmail").value;
var formData = new FormData();
formData.append("extension_user", userEmail);
var channel;
var socket;
var handler = new Object();
handler.onmessage =
function (evt)
{
//evt.data will be what the server sends in channel.send_message
console.log("evt.data received from authhandler: " + evt.data);
};
var xhr = new XMLHttpRequest();
xhr.onReadyStateChange = function()
{
//error handling etc not included
if (xhr.readyState == 4 && xhr.status == 200)
{
token = xhr.responseText;
channel = new goog.appengine.Channel(token);
socket = channel.open(handler);
}
};
xhr.open("POST", "http://ting-1.appspot.com/authsender", true);
xhr.send(formData);
console.log("formData sent to authsender: " + formData);
}, false
)
//...
</script>
</body>
</html>
AuthSender and AuthHandler
class AuthSender(webapp.RequestHandler):
def post(self):
new_user = self.request.get("extension_user")
link = "http://ting-1.appspot.com/authhandler?new_user=%s" % new_user
message = mail.EmailMessage()
message.sender="Ting <[email protected]>"
message.to=new_user
message.subject="Ting Bookmarking - Confirm your email"
message.body="""Click on the link to confirm your email: %s """ % link
message.send()
logging.info("message sent to: %s " % message.to)
token = channel.create_channel(new_user)
self.response.out.write(token)
class AuthHandler(webapp.RequestHandler):
def get(self):
new_user = self.request.get("new_user")
channel.send_message(new_user, new_user)
logging.info("new_user sent to client: %s" % new_user)
Working version
options.html
<html>
<head>
<title>Extension Options</title>
<!-- this does not work because it is local url
<script type="text/javascript" src="/_ah/channel/jsapi"></script>
-->
<script type="text/javascript" src="https://talkgadget.google.com/talkgadget/channel.js"></script>
</head>
<body>
<p>Enter your gmail address:</p>
<textarea id="getEmail" style="margin-bottom: 4px; width: 250px; height: 20px">
</textarea><br />
<button id="save">Save</button>
<script>
document.getElementById("getEmail").placeholder = "your gmail address" ;
document.getElementById("save").addEventListener
(
"click",
function ()
{
var userEmail = document.getElementById("getEmail").value;
var formData = new FormData();
formData.append("extension_user", userEmail);
var channel;
var socket;
var handler =
{
onopen: function () { alert("onopen") },
onerror: function () { alert("onerror") },
onclose: function () { alert("onclose") },
onmessage:
function (evt)
{
alert("evt.data is: " + evt.data)
}
};
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function()
{
if (xhr.readyState == 4 && xhr.status == 200)
{
token = xhr.responseText;
channel = new goog.appengine.Channel(token);
socket = channel.open(handler);
}
};
xhr.open("POST", "http://ting-1.appspot.com/authsender", true);
xhr.send(formData);
}, false
)
</script>
</body>
</html>
AuthSender and AuthHandler
class AuthSender(webapp.RequestHandler):
def post(self):
new_user = self.request.get("extension_user")
link = "http://ting-1.appspot.com/authhandler?new_user=%s" % new_user
message = mail.EmailMessage()
message.sender="Ting <[email protected]>"
message.to=new_user
message.subject="Ting Bookmarking - Confirm your email"
message.body="""Click on the link to confirm your email: %s """ % link
message.send()
token = channel.create_channel(new_user)
self.response.out.write(token)
class AuthHandler(webapp.RequestHandler):
def get(self):
new_user = self.request.get("new_user")
self.response.out.write(new_user)
channel.send_message(new_user, new_user)
Upvotes: 4
Views: 4091
Reputation: 8471
You can use the Channel API to accomplish this. Request a token at the same time you make the XMLHttpRequest, and open the channel to listen for messages. When your app processes the HTTP request corresponding to the user clicking on the link, send a message to the channel that was created for that user's extension.
More detail:
Basically, in options.html when you make the XHR, do something like this:
var channel;
var socket;
var handler = {
onmessage: function(evt) {
// evt.data will be what your server sends in channel.send_message
}
};
var xhr = new XMLHttpRequest();
xhr.onReadyStateChange = function() {
// error handling and whatnot elided
if (xhr.readyState == 4 and xhr.status == 200) {
// We got a response from the server. The responseText is
// a channel token so we can listen for a "verified" message.
token = xhr.responseText;
channel = new goog.appengine.Channel(token);
socket = channel.open(handler);
}
};
xhr.open("POST", "http://ting-1.appspot.com/authsender", true);
xhr.send(formData);
Then on your server, the handler for the 'authsender' page will do something like this:
class AuthSenderHandler(webapp.RequestHandler):
def post(self):
# get whatever data is in the form to send an email.
# let's say that user_id is a field we extracted either from a cookie or from
# the POST parameters themselves.
link = "http://your.server.com/authHandler?user_id=%s" % user_id
message = mail.EmailMessage()
message.body = """some stuff %s""" % link
# fill in other message fields, then send it.
# now we'll create a channel token using the user_id and return
# it to the client.
token = channel.create_channel(user_id)
self.response.out.write(token)
The above two functions will get your client listening on a channel. The next step is: what happens when the user clicks a link?
Before, we sent the link in an email including a user_id parameter (for purposes of illustration; you may want to use something else). Now when the user clicks on the link, it'll make an HTTP request to the authHandler
path with user_id
as a parameter. So we can use the user_id to identify the channel to send a message to, like so:
class AuthHandler(webapp.RequestHandler):
def get(self):
user_id = self.request.get("user_id")
# send a message indicating the user received the email and
# verified to our client page. You may want to send other data.
channel.send_message(user_id, "authorized")
# probably want to show a nice "you've been authorized" page or something
And then the handler.onmessage callback will be called and you can do whatever you need to do now that the user's verified their email address.
Hope that helps a bit!
Upvotes: 5