Search code examples
javascriptpythongoogle-app-enginegoogle-chrome-extension

How can I send data to Chrome extension?


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

(Related question)

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)

Solution

  • 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!