Home | History | Annotate | Download | only in js
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 picasa = {}
      6 
      7 /**
      8  * LocalFile constructor.
      9  *
     10  * LocalFile object represents a file to be uploaded.
     11  */
     12 picasa.LocalFile = function(file) {
     13   this.file_ = file;
     14   this.caption = file.name;
     15 
     16   this.dataUrl_ = null;
     17   this.mime_ = file.type;
     18 };
     19 
     20 picasa.LocalFile.prototype = {
     21   /**
     22    * Reads data url from local file to show in img element.
     23    * @param {Function} callback Callback.
     24    */
     25   readData_: function(callback) {
     26     if (this.dataUrl_) {
     27       callback.call(this);
     28       return;
     29     }
     30 
     31     var reader = new FileReader();
     32     function onLoadCallback(e) {
     33       this.dataUrl_ = e.target.result;
     34       this.mime_ = this.dataUrl_.substring(0, this.dataUrl_.indexOf(';base64'));
     35       this.mime_ = this.mime_.substr(5);  // skip 'data:'
     36       callback.call(this);
     37     }
     38     reader.onload =  onLoadCallback.bind(this);
     39     reader.readAsDataURL(this.file_);
     40   },
     41 
     42   showInImage: function(img) {
     43     if (this.dataUrl_) {
     44       img.setAttribute('src', this.dataUrl_);
     45       return;
     46     }
     47 
     48     this.readData_(function() {
     49       img.setAttribute('src', this.dataUrl_);
     50     });
     51   },
     52 
     53   /**
     54    * @return {string} Mime type of the file.
     55    */
     56   get mimeType() {
     57     return this.mime_;
     58   }
     59 };
     60 
     61 
     62 /**
     63  * Album constructor.
     64  *
     65  * Album object stores information about picasa album.
     66  */
     67 picasa.Album = function(id, title, location, description, link) {
     68   this.id = id;
     69   this.title = title;
     70   this.location = location;
     71   this.description = description;
     72   this.link = link;
     73 };
     74 
     75 
     76 /**
     77  * Client constructor.
     78  *
     79  * Client object stores user credentials and gets from and sends to picasa
     80  * web server.
     81  */
     82 picasa.Client = function() {
     83 };
     84 
     85 
     86 picasa.Client.prototype = {
     87   __proto__: cr.EventTarget.prototype,
     88 
     89   /**
     90    * User credentials.
     91    * @type {string}
     92    * @private
     93    */
     94   authToken_: null,
     95 
     96   /**
     97    * User id.
     98    * @type {string}
     99    * @private
    100    */
    101   userID_: null,
    102 
    103   /**
    104    * List of user albums.
    105    * @type {Array.<picasa.Album>}
    106    * @private
    107    */
    108   albums_: null,
    109 
    110   /**
    111    * Url for captcha challenge, if required.
    112    * @type {string}
    113    * @private
    114    */
    115   captchaUrl_: null,
    116 
    117   /**
    118    * Captcha toekn, if required.
    119    * @type {string}
    120    * @private
    121    */
    122   captchaToken_: null,
    123 
    124   /**
    125    * Whether client is already authorized.
    126    * @type {boolean}
    127    */
    128   get authorized() {
    129     return !!this.authToken_;
    130   },
    131 
    132   /**
    133    * User id.
    134    * @type {string}
    135    */
    136   get userID() {
    137     return this.userID_ || '';
    138   },
    139 
    140   /**
    141    * List of albums.
    142    * @type {Array.<picasa.Album>}
    143    */
    144   get albums() {
    145     return this.albums_ || [];
    146   },
    147 
    148   /**
    149    * Captcha url to show to user, if needed.
    150    * @type {string}
    151    */
    152   get captchaUrl() {
    153     return this.captchaUrl_;
    154   },
    155 
    156   /**
    157    * Get user credential for picasa web server.
    158    * @param {string} login User login.
    159    * @param {string} password User password.
    160    * @param {Function(string)} callback Callback, which is passed 'status'
    161    *     parameter: either 'success', 'failure' or 'captcha'.
    162    * @param {?string=} opt_captcha Captcha answer, if was required.
    163    */
    164   login: function(login, password, callback, opt_captcha) {
    165     function xhrCallback(xhr) {
    166       if (xhr.status == 200) {
    167         this.authToken_ = this.extractResponseField_(xhr.responseText, 'Auth');
    168        this.userID_ = login;
    169         callback('success');
    170       } else {
    171         var response = xhr.responseText;
    172         var error = this.extractResponseField_(response, 'Error');
    173         if (error == 'CaptchaRequired') {
    174           this.captchaToken_ = this.extractResponseField_(response,
    175               'CaptchaToken');
    176           // Captcha url should prefixed with this.
    177           this.captchaUrl_ = 'http://www.google.com/accounts/' +
    178               this.extractResponseField_(response, 'CaptchaUrl');
    179           callback('captcha');
    180           return;
    181         }
    182         callback('failure');
    183       }
    184     }
    185 
    186     var content = 'accountType=HOSTED_OR_GOOGLE&Email=' + login +
    187         '&Passwd=' + password + '&service=lh2&source=ChromeOsPWAUploader';
    188     if (opt_captcha && this.captchaToken_) {
    189       content += '&logintoken=' + this.captchaToken_;
    190       content += '&logincaptcha=' + opt_captcha;
    191     }
    192     this.sendRequest('POST', 'https://www.google.com/accounts/ClientLogin',
    193         {'Content-type': 'application/x-www-form-urlencoded'},
    194         content,
    195         xhrCallback.bind(this));
    196   },
    197 
    198   /**
    199    * Logs out.
    200    */
    201   logout: function() {
    202     this.authToken_ = null;
    203     this.userID_ = null;
    204     this.captchaToken_ = null;
    205     this.captchatUrl_ = null;
    206   },
    207 
    208   /**
    209    * Extracts text field from text response.
    210    * @param {string} response The response.
    211    * @param {string} field Field name to extract value of.
    212    * @return {?string} Field value or null.
    213    */
    214   extractResponseField_: function(response, field) {
    215     var lines = response.split('\n');
    216     field += '=';
    217     for (var line, i = 0; line = lines[i]; i++) {
    218       if (line.indexOf(field) == 0) {
    219         return line.substr(field.length);
    220       }
    221     }
    222     return null;
    223   },
    224 
    225   /**
    226    * Sends request to web server.
    227    * @param {string} method Method to use (GET or POST).
    228    * @param {string} url Request url.
    229    * @param {Object.<string, string>} headers Request headers.
    230    * @param {*} body Request body.
    231    * @param {Function(XMLHttpRequest)} callback Callback.
    232    */
    233   sendRequest: function(method, url, headers, body, callback) {
    234     var xhr = new XMLHttpRequest();
    235     xhr.onreadystatechange = function() {
    236       if (xhr.readyState == 4) {
    237         callback(xhr);
    238       }
    239     };
    240     xhr.open(method, url, true);
    241     if (headers) {
    242       for (var header in headers) {
    243         if (headers.hasOwnProperty(header)) {
    244           xhr.setRequestHeader(header, headers[header]);
    245         }
    246       }
    247     }
    248     xhr.send(body);
    249     return xhr;
    250   },
    251 
    252   /**
    253    * Gets the feed from web server and parses it. Appends user credentials.
    254    * @param {string} url Feed url.
    255    * @param {Function(*)} callback Callback.
    256    */
    257   getFeed: function(url, callback) {
    258     var headers = {'Authorization': 'GoogleLogin auth=' + this.authToken_};
    259     this.sendRequest('GET', url + '?alt=json', headers, null, function(xhr) {
    260       if (xhr.status == 200) {
    261         var feed = JSON.parse(xhr.responseText);
    262         callback(feed);
    263       } else {
    264         callback(null);
    265       }
    266     });
    267   },
    268 
    269   /**
    270    * Posts the feed to web server. Appends user credentials.
    271    * @param {string} url Feed url.
    272    * @param {Object.<string, string>} headers Request headers.
    273    * @param {*} body Post body.
    274    * @param {Function(!string)} callback Callback taking response text or
    275    *     null in the case of failure.
    276    */
    277   postFeed: function(url, headers, body, callback) {
    278     headers['Authorization'] = 'GoogleLogin auth=' + this.authToken_;
    279     return this.sendRequest('POST', url, headers, body, function(xhr) {
    280       if (xhr.status >= 200 && xhr.status <= 202) {
    281         callback(xhr.responseText);
    282       } else {
    283         callback(null);
    284       }
    285     });
    286   },
    287 
    288   /**
    289    * Requests albums for the user and passes them to callback.
    290    * @param {Function(Array.<picasa.Album>)} callback Callback.
    291    */
    292   getAlbums: function(callback) {
    293     function feedCallback(feed) {
    294       feed = feed.feed;
    295       if (!feed.entry) {
    296         return;
    297       }
    298       this.albums_ = [];
    299       for (var entry, i = 0; entry = feed.entry[i]; i++) {
    300         this.albums_.push(this.albumFromEntry_(entry));
    301       }
    302       callback(this.albums_);
    303     }
    304 
    305     this.getFeed('https://picasaweb.google.com/data/feed/api/user/' +
    306         this.userID_, feedCallback.bind(this));
    307   },
    308 
    309   /**
    310    * Returns album object created from entry.
    311    * @param {*} entry The feed entry corresponding to album.
    312    * @return {picasa.Album} The album object.
    313    */
    314   albumFromEntry_: function(entry) {
    315     var altLink = '';
    316     for (var link, j = 0; link = entry.link[j]; j++) {
    317       if (link.rel == 'alternate') {
    318         altLink = link.href;
    319       }
    320     }
    321     return new picasa.Album(entry['gphoto$id']['$t'], entry.title['$t'],
    322         entry['gphoto$location']['$t'], entry.summary['$t'], altLink);
    323   },
    324 
    325   /**
    326    * Send request to create album.
    327    * @param {picasa.Album} album Album to create.
    328    * @param {Function(picasa.Album)} callback Callback taking updated album
    329    *     (for example, with created album id).
    330    */
    331   createAlbum: function(album, callback) {
    332     function postCallback(response) {
    333       if (response == null) {
    334         callback(null);
    335       } else {
    336         var entry = JSON.parse(response).entry;
    337         callback(this.albumFromEntry_(entry));
    338       }
    339     }
    340 
    341     var eol = '\n';
    342     var postData = '<entry xmlns="http://www.w3.org/2005/Atom"' + eol;
    343     postData += 'xmlns:media="http://search.yahoo.com/mrss/"' + eol;
    344     postData += 'xmlns:gphoto="http://schemas.google.com/photos/2007">' + eol;
    345     postData += '<title type="text">' + escape(album.title) + '</title>' + eol;
    346     postData += '<summary type="text">' + escape(album.description) +
    347         '</summary>' + eol;
    348     postData += '<gphoto:location>' + escape(album.location) +
    349         '</gphoto:location>' + eol;
    350     postData += '<gphoto:access>public</gphoto:access>';
    351     postData += '<category scheme="http://schemas.google.com/g/2005#kind" ' +
    352         'term="http://schemas.google.com/photos/2007#album"></category>' + eol;
    353     postData += '</entry>' + eol;
    354 
    355     var headers = {'Content-Type': 'application/atom+xml'};
    356     this.postFeed('https://picasaweb.google.com/data/feed/api/user/' +
    357         this.userID_ + '?alt=json', headers, postData, postCallback.bind(this));
    358   },
    359 
    360   /**
    361    * Uploads file to the given album.
    362    * @param {picasa.Album} album Album to upload to.
    363    * @param {picasa.LocalFile} file File to upload.
    364    * @param {Function(?string)} callback Callback.
    365    */
    366   uploadFile: function(album, file, callback) {
    367     var postData = file.file_;
    368     var headers = {
    369         'Content-Type': file.mimeType,
    370         'Slug': file.file_.name};
    371     return this.postFeed('https://picasaweb.google.com/data/feed/api/user/' +
    372         this.userID_ + '/albumid/' + album.id, headers, postData, callback);
    373   }
    374 };
    375