Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Client-side retrieval of Google Contact pictures

I'm fetching google contacts in a webapp using the Google JavaScript API and I'd like to retrieve their pictures.

I'm doing something like this (heavily simplified):

var token; // let's admit this is available already

function getPhotoUrl(entry, cb) {
  var link = entry.link.filter(function(link) {
    return link.type.indexOf("image") === 0;
  }).shift();
  if (!link)
    return cb(null);
  var request = new XMLHttpRequest();
  request.open("GET", link.href + "?v=3.0&access_token=" + token, true);
  request.responseType = "blob";
  request.onload = cb;
  request.send();
}

function onContactsLoad(responseText) {
  var data = JSON.parse(responseText);
  (data.feed.entry || []).forEach(function(entry) {
    getPhotoUrl(e, function(a, b, c) {
      console.log("pic", a, b, c);
    });
  });
}

But I'm getting this error both in Chrome and Firefox:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://www.google.com/m8/feeds/photos/media/<user_email>/<some_contact_id>?v=3.0&access_token=<obfuscated>. This can be fixed by moving the resource to the same domain or enabling CORS.

When looking at the response headers from the feeds/photos endpoint, I can see that Access-Control-Allow-Origin: * is not sent, hence the CORS error I get.

Note that Access-Control-Allow-Origin: * is sent when reaching the feeds/contacts endpoint, hence allowing cross-domain requests.

Is this a bug, or did I miss something from their docs?

like image 990
NiKo Avatar asked Oct 02 '22 19:10

NiKo


2 Answers

Assuming you only need the "profile picture", try actually moving the request for that image directly into HTML, by setting a full URL as the src element of an <img> tag (with a ?access_token=<youknowit> at the end).

E.g. using Angular.js

<img ng-src="{{contact.link[1].href + tokenForImages}}" alt="photo" />

With regard to CORS in general, there seem to be quite a few places where accessing the API from JS is not working as expected.

Hope this helps.

like image 119
bjoernklose Avatar answered Oct 05 '22 11:10

bjoernklose


Not able to comment yet, hence this answer…

Obviously you have already set up the proper client ID and JavaScript origins in the Google developers console.

It seems that the domain shared contacts API does not work as advertised and only abides by its CORS promise when you request JSONP data (your code indicates that you got your entry data using JSON). For JSON format, the API sets the access-control-allow-origin to * instead of the JavaScript origins you list for your project.

But as of today (2015-06-16), if you try to issue a GET, POST… with a different data type (e.g. atom/xml), the Google API will not set the access-control-allow-origin at all, hence your browser will deny your request to access the data (error 405).

This is clearly a bug, that prevents any programmatic use of the shared contacts API but for simple listing of entries: one can no longer create, update, delete entries nor access photos.

Please correct me if I'm mistaken (I wish I am); please comment or edit if you know the best way to file this bug with Google.

Note, for the sake of completeness, here's the code skeleton I use to access contacts (requires jQuery).

    <button id="authorize-button" style="visibility: hidden">Authorize</button>
    <script type="text/javascript">
        var clientId = 'TAKE-THIS-FROM-CONSOLE.apps.googleusercontent.com',
            apiKey = 'TAKE-THAT-FROM-GOOGLE-DEVELOPPERS-CONSOLE',
            scopes = 'https://www.google.com/m8/feeds';
        // Use a button to handle authentication the first time.
        function handleClientLoad () {
            gapi.client.setApiKey ( apiKey );
            window.setTimeout ( checkAuth, 1 );
        }
        function checkAuth() {
            gapi.auth.authorize({client_id: clientId, scope: scopes, immediate: true}, handleAuthResult);
        }
        function handleAuthResult ( authResult ) {
            var authorizeButton = document.getElementById ( 'authorize-button' );
            if ( authResult && !authResult.error ) {
                authorizeButton.style.visibility = 'hidden';
                var cif = {
                    method: 'GET',
                    url:  'https://www.google.com/m8/feeds/contacts/mydomain.com/full/',
                    data: {
                        "access_token": authResult.access_token,
                        "alt":          "json",
                        "max-results":  "10"
                    },
                    headers: { 
                        "Gdata-Version":    "3.0"    
                    },
                    xhrFields: {
                        withCredentials: true
                    },
                    dataType: "jsonp"
                };
                $.ajax ( cif ).done ( function ( result ) {
                        $ ( '#gcontacts' ).html ( JSON.stringify ( result, null, 3 ) );
                } );
            } else {
                authorizeButton.style.visibility = '';
                authorizeButton.onclick = handleAuthClick;
            }
        }
        function handleAuthClick ( event ) {
            gapi.auth.authorize ( { client_id: clientId, scope: scopes, immediate: false }, handleAuthResult );
            return false;
        }
    </script>
    <script src="https://apis.google.com/js/client.js?onload=handleClientLoad"></script>
    <pre id="gcontacts"></pre>

If you replace cif.data.alt by atom and/or cif.dataType by xml, you get the infamous error 405.

ps: cif is of course related to ajax ;-)

like image 24
Jean-Rene Bouvier Avatar answered Oct 05 '22 12:10

Jean-Rene Bouvier