Bug 1069962: add support for Gravatar avatars. r=paolo
authorMike de Boer <mdeboer@mozilla.com>
Fri, 19 Sep 2014 17:02:00 +0200
changeset 206252 b26c709330d601cd9d66ad110b5f3de4ed5e111f
parent 206251 4b348553c600121d923c7c1496c72853daf41be0
child 206253 1a986002b2648cb9b540064c4cf8fe18656c5bbb
push id27519
push userkwierso@gmail.com
push dateFri, 19 Sep 2014 23:56:39 +0000
treeherdermozilla-central@a85324dfc960 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspaolo
bugs1069962
milestone35.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1069962: add support for Gravatar avatars. r=paolo
browser/components/loop/MozLoopAPI.jsm
browser/components/loop/content/js/contacts.js
browser/components/loop/content/js/contacts.jsx
--- a/browser/components/loop/MozLoopAPI.jsm
+++ b/browser/components/loop/MozLoopAPI.jsm
@@ -93,16 +93,25 @@ const injectObjectAPI = function(api, ta
   // the `contentObj` without Xrays.
   try {
     Object.seal(Cu.waiveXrays(contentObj));
   } catch (ex) {}
   return contentObj;
 };
 
 /**
+ * Get the two-digit hexadecimal code for a byte
+ *
+ * @param {byte} charCode
+ */
+const toHexString = function(charCode) {
+  return ("0" + charCode.toString(16)).slice(-2);
+};
+
+/**
  * Inject the loop API into the given window.  The caller must be sure the
  * window is a loop content window (eg, a panel, chatwindow, or similar).
  *
  * See the documentation on the individual functions for details of the API.
  *
  * @param {nsIDOMWindow} targetWindow The content window to attach the API.
  */
 function injectLoopAPI(targetWindow) {
@@ -505,16 +514,52 @@ function injectLoopAPI(targetWindow) {
      */
     telemetryAdd: {
       enumerable: true,
       writable: true,
       value: function(histogramId, value) {
         Services.telemetry.getHistogramById(histogramId).add(value);
       }
     },
+
+    /**
+     * Compose a URL pointing to the location of an avatar by email address.
+     * At the moment we use the Gravatar service to match email addresses with
+     * avatars. This might change in the future as avatars might come from another
+     * source.
+     *
+     * @param {String} emailAddress Users' email address
+     * @param {Number} size         Size of the avatar image to return in pixels.
+     *                              Optional. Default value: 40.
+     * @return the URL pointing to an avatar matching the provided email address.
+     */
+    getUserAvatar: {
+      enumerable: true,
+      writable: true,
+      value: function(emailAddress, size = 40) {
+        if (!emailAddress) {
+          return "";
+        }
+
+        // Do the MD5 dance.
+        let hasher = Cc["@mozilla.org/security/hash;1"]
+                       .createInstance(Ci.nsICryptoHash);
+        hasher.init(Ci.nsICryptoHash.MD5);
+        let stringStream = Cc["@mozilla.org/io/string-input-stream;1"]
+                             .createInstance(Ci.nsIStringInputStream);
+        stringStream.data = emailAddress.trim().toLowerCase();
+        hasher.updateFromStream(stringStream, -1);
+        let hash = hasher.finish(false);
+        // Convert the binary hash data to a hex string.
+        let md5Email = [toHexString(hash.charCodeAt(i)) for (i in hash)].join("");
+
+        // Compose the Gravatar URL.
+        return "http://www.gravatar.com/avatar/" + md5Email + ".jpg?default=blank&s=" + size;
+      }
+    },
   };
 
   function onStatusChanged(aSubject, aTopic, aData) {
     let event = new targetWindow.CustomEvent("LoopStatusChanged");
     targetWindow.dispatchEvent(event)
   };
 
   function onDOMWindowDestroyed(aSubject, aTopic, aData) {
--- a/browser/components/loop/content/js/contacts.js
+++ b/browser/components/loop/content/js/contacts.js
@@ -59,17 +59,19 @@ loop.contacts = (function(_, mozL10n) {
       let cx = React.addons.classSet;
       let contactCSSClass = cx({
         contact: true,
         blocked: this.props.contact.blocked
       });
 
       return (
         React.DOM.li({onClick: this.handleContactClick, className: contactCSSClass}, 
-          React.DOM.div({className: "avatar"}), 
+          React.DOM.div({className: "avatar"}, 
+            React.DOM.img({src: navigator.mozLoop.getUserAvatar(email.value)})
+          ), 
           React.DOM.div({className: "details"}, 
             React.DOM.div({className: "username"}, React.DOM.strong(null, names.firstName), " ", names.lastName, 
               React.DOM.i({className: cx({"icon icon-google": this.props.contact.category[0] == "google"})}), 
               React.DOM.i({className: cx({"icon icon-blocked": this.props.contact.blocked})})
             ), 
             React.DOM.div({className: "email"}, email.value)
           ), 
           React.DOM.div({className: "icons"}, 
@@ -146,17 +148,17 @@ loop.contacts = (function(_, mozL10n) {
       this.setState({contacts: {}});
     },
 
     sortContacts: function(contact1, contact2) {
       let comp = contact1.name[0].localeCompare(contact2.name[0]);
       if (comp !== 0) {
         return comp;
       }
-      // If names are equal, compare against unique ids make sure we have
+      // If names are equal, compare against unique ids to make sure we have
       // consistent ordering.
       return contact1._guid - contact2._guid;
     },
 
     render: function() {
       let viewForItem = item => {
         return ContactDetail({key: item._guid, contact: item})
       };
--- a/browser/components/loop/content/js/contacts.jsx
+++ b/browser/components/loop/content/js/contacts.jsx
@@ -59,17 +59,19 @@ loop.contacts = (function(_, mozL10n) {
       let cx = React.addons.classSet;
       let contactCSSClass = cx({
         contact: true,
         blocked: this.props.contact.blocked
       });
 
       return (
         <li onClick={this.handleContactClick} className={contactCSSClass}>
-          <div className="avatar" />
+          <div className="avatar">
+            <img src={navigator.mozLoop.getUserAvatar(email.value)} />
+          </div>
           <div className="details">
             <div className="username"><strong>{names.firstName}</strong> {names.lastName}
               <i className={cx({"icon icon-google": this.props.contact.category[0] == "google"})} />
               <i className={cx({"icon icon-blocked": this.props.contact.blocked})} />
             </div>
             <div className="email">{email.value}</div>
           </div>
           <div className="icons">
@@ -146,17 +148,17 @@ loop.contacts = (function(_, mozL10n) {
       this.setState({contacts: {}});
     },
 
     sortContacts: function(contact1, contact2) {
       let comp = contact1.name[0].localeCompare(contact2.name[0]);
       if (comp !== 0) {
         return comp;
       }
-      // If names are equal, compare against unique ids make sure we have
+      // If names are equal, compare against unique ids to make sure we have
       // consistent ordering.
       return contact1._guid - contact2._guid;
     },
 
     render: function() {
       let viewForItem = item => {
         return <ContactDetail key={item._guid} contact={item} />
       };