Creación de un gadget de datos de Google

Eric Bidelman, equipo de API de datos de Google
Octubre de 2008

Introducción

Público

Este artículo te guiará para crear un gadget de Blogger. Se supone que estás familiarizado con las API de datos de Google y la biblioteca cliente de JavaScript. Además, dominas JavaScript y tienes experiencia en la implementación de gadgets en OpenSocial con el uso de gadgets.* API específica.

Este ejemplo también demuestra cómo utilizar con éxito las bibliotecas externas en tus gadgets. Usé jQuery (principalmente para sus efectos de IU) y TinyMCE, un excelente complemento de editor de texto enriquecido WYSIWYG.

Motivación

Se necesita muy poco JavaScript para crear un gadget que utilice JSON con una de las API de datos de Google. La mayor molestia de un gadget es que los datos son públicos y de solo lectura. Para crear gadgets más interesantes, necesitas tener acceso a los datos privados de un usuario (algo que requiere autenticación). Hasta ahora, no había una gran manera de aprovechar las API de cuentas de Google. AuthSub requiere redireccionamientos del navegador y ClientLogin expone las credenciales de un usuario, del lado del cliente. Hasta el gadget de type="url" no ha sido conveniente.

Ingresa el proxy de OAuth.

Proxy OAuth

Si no estás familiarizado con OAuth, es un estándar de autenticación que permite que un usuario comparta sus datos privados con otro sitio web o gadget. La especificación OAuth requiere que todas las solicitudes de datos estén firmadas digitalmente. Esto es excelente por motivos de seguridad, pero en el caso de un gadget de JavaScript, no es seguro administrar claves privadas ni crear firmas digitales. También está la complicación de los problemas de diferentes dominios.

Afortunadamente, estos problemas se resuelven gracias a la función de la plataforma de gadgets denominada proxy de OAuth. El proxy de OAuth está diseñado para facilitarles la vida a los desarrolladores de gadgets. Oculta muchos de los detalles de autenticación de OAuth y hace el trabajo pesado por ti. El proxy firma las solicitudes de datos en nombre de tu gadget para que no tengas que administrar claves privadas ni preocuparte por firmar solicitudes. ¡Simplemente funciona!

El proxy de OAuth se basa en un proyecto de código abierto denominado Shindig, que es una implementación de la especificación del gadget.

Nota: El proxy OAuth solo es compatible con gadgets que utilizan la API de gadgets.* y se ejecutan en contenedores de OpenSocial. No es compatible con la API de gadgets heredados.

Cómo comenzar

El resto de este instructivo se centrará en la creación de un gadget para acceder a los datos de Blogger de un usuario. Revisaremos la autenticación (con el proxy de OAuth), usaremos la biblioteca cliente de JavaScript y, finalmente, publicaremos una entrada en Blogger.

Autenticación

En primer lugar, debemos indicarle al gadget que utilice OAuth. Para ello, agrega el elemento <OAuth> en la sección <ModulePrefs> del gadget:

<ModulePrefs>
...
<OAuth>
  <Service name="google">
    <Access url="https://www.google.com/accounts/OAuthGetAccessToken" method="GET" /> 
    <Request url="https://www.google.com/accounts/OAuthGetRequestToken?scope=http://www.blogger.com/feeds/" method="GET" /> 
    <Authorization url="https://www.google.com/accounts/OAuthAuthorizeToken?
                        oauth_callback=http://oauth.gmodules.com/gadgets/oauthcallback" /> 
  </Service>
</OAuth>
...
</ModulePrefs>

Los tres extremos de URL en el elemento <Service> corresponden a los extremos de token de OAuth de Google. Esta es la explicación de los parámetros de consulta:

  • scope

    Este parámetro es obligatorio en la URL de la solicitud. Tu gadget sólo podrá acceder a los datos de los scope que se utilicen en este parámetro. En este ejemplo, el gadget accederá a Blogger. Si tu gadget quiere acceder a más de una API de datos de Google, concatena los scope adicionales con el %20. Por ejemplo, si quieres acceder tanto a Calendario como a Blogger, configura el alcance en http://www.blogger.com/feeds/%20http://www.google.com/calendar/feeds/.

  • oauth_callback

    Este parámetro es opcional en la URL de autorización. La página de aprobación de OAuth redireccionará a esta URL después de que el usuario apruebe el acceso a sus datos. Puede optar por omitir este parámetro, establecer su propia "página aprobada" o, si lo desea, utilizar una http://oauth.gmodules.com/gadgets/oauthcallback. La última proporciona la mejor experiencia a los usuarios cuando instalan tu gadget por primera vez. Esa página proporciona un fragmento de JavaScript que cierra automáticamente la ventana emergente.

Ahora que tenemos nuestro gadget con OAuth, el usuario debe aprobar el acceso a sus datos. Este es el flujo de autenticación:

  1. El gadget se carga por primera vez e intenta acceder a los datos de Blogger del usuario.
  2. La solicitud falla porque el usuario no ha concedido acceso al gadget. Por suerte, el objeto que se muestra en la respuesta contiene una URL (response.oauthApprovalUrl) a la que enviaremos al usuario para que acceda. El gadget muestra la opción "Acceder a Blogger" y la configura en el valor de oauthApprovalUrl.
  3. A continuación, el usuario hace clic en "Acceder a Blogger" y la página de aprobación de OAuth se abre en otra ventana. El gadget espera a que el usuario termine el proceso de aprobación con un vínculo: “Aprobé el acceso”.
  4. En la ventana emergente, el usuario elegirá permitir/denegar el acceso a nuestro gadget. Cuando haga clic en “Otorgar acceso”, se lo redireccionará a http://oauth.gmodules.com/gadgets/oauthcallback y se cerrará la ventana.
  5. El gadget reconoce la ventana cerrada e intenta acceder a Blogger por segunda vez mediante una nueva solicitud de datos del usuario. Para detectar el cierre de la ventana, usé un controlador emergente. Si no usa ese código, el usuario puede hacer clic de forma manual en "Aprobé el acceso".
  6. El gadget ahora muestra su interfaz de usuario normal. Esta vista persistirá a menos que se revoque el token de autenticación en IssuedAuthSubTokens.

Así, de los pasos anteriores, los gadgets tienen la noción de tres estados diferentes:

  1. Sin autenticar. El usuario debe iniciar el proceso de aprobación.
  2. Esperando a que el usuario apruebe el acceso a sus datos.
  3. Autenticada. Los gadgets muestran su estado funcional normal.

En mi gadget, usé contenedores <div> para separar cada etapa:

<Content type="html">
<![CDATA[

<!-- Normal state of the gadget. The user is authenticated -->       
<div id="main" style="display:none">
  <form id="postForm" name="postForm" onsubmit="savePost(this); return false;">
     <div id="messages" style="display: none"></div>
     <div class="selectFeed">Publish to:
       <select id="postFeedUri" name="postFeedUri" disabled="disabled"><option>loading blog list...</option></select>
     </div>
     <h4 style="clear:both">Title</h4>
     <input type="text" id="title" name="title"/>
     <h4>Content</h4>
     <textarea id="content" name="content" style="width:100%;height:200px;"></textarea>
     <h4 style="float:left;">Labels (comma separated)</h4><img src="blogger.png" style="float:right"/>
     <input type="text" id="categories" name="categories"/>
     <p><input type="submit" id="submitButton" value="Save"/> 
     <input type="checkbox" id="draft" name="draft" checked="checked"/> <label for="draft">Draft?</label></p>
  </form>
</div>

<div id="approval" style="display: none">
  <a href="#" id="personalize">Sign in to Blogger</a>
</div>

<div id="waiting" style="display: none">
  <a href="#" id="approvalLink">I've approved access</a>
</di

<!-- An errors section is not necessary but great to have -->
<div id="errors" style="display: none"></div>
 
<!-- Also not necessary, but great for informing users -->     
<div id="loading">
  <h3>Loading...</h3>
  <p><img src="ajax-loader.gif"></p>
</div>

]]> 
</Content>

Cada <div> se muestra sola con showOnly(). Consulta el gadget de ejemplo completo para conocer los detalles de esa función.

Usar la biblioteca cliente de JavaScript

Para recuperar contenido remoto en OpenSocial, realiza una llamada al método gadgets.io.makeRequest con la API gadgets.*. Sin embargo, como estamos creando un gadget de datos de Google, no es necesario tocar las API de gadgets.io.*. En su lugar, aprovecha la biblioteca cliente de JavaScript, que tiene métodos especiales para realizar solicitudes a cada servicio de datos de Google.

Nota: Al momento de redactar este artículo, la biblioteca de JavaScript solo es compatible con Blogger, Calendario, Contacts, Finance y Google Base. Para usar una de las otras API, usa gadgets.io.makeRequest sin la biblioteca.

Cómo cargar la biblioteca

Para cargar la biblioteca de JavaScript, incluye el cargador común en la sección <Content> e importa la biblioteca una vez que el gadget se haya inicializado. Enviar una devolución de llamada a gadgets.util.registerOnLoadHandler() ayudará a determinar cuándo el gadget está listo:

<Content type="html">
<![CDATA[
  ...
  <script src="https://www.google.com/jsapi"></script>
  <script type="text/javascript">
  var blogger = null;  // make our service object global for later
  
  // Load the JS library and try to fetch data once it's ready
  function initGadget() {  
    google.load('gdata', '1.x', {packages: ['blogger']});  // Save overhead, only load the Blogger service
    google.setOnLoadCallback(function () {
      blogger = new google.gdata.blogger.BloggerService('google-BloggerGadget-v1.0');
      blogger.useOAuth('google');
      fetchData();
    });
  }
  gadgets.util.registerOnLoadHandler(initGadget);
  </script>
  ...
]]> 
</Content>

La llamada a blogger.useOAuth('google') le indica a la biblioteca que use el proxy de OAuth (en lugar de AuthSubJS, su método de autenticación normal). Por último, el gadget intenta recuperar los datos de Blogger del usuario mediante una llamada a fetchData(). Ese método se define a continuación.

Obteniendo datos

Ahora que todo está configurado, ¿cómo semos los datos de GET o POST a Blogger?

Un paradigma común en OpenSocial es definir una función llamada fetchData() en tu gadget. Por lo general, este método controla las diferentes etapas de autenticación y recupera datos con gadgets.io.makeRequest. Como estamos usando la biblioteca cliente de JavaScript, gadgets.io.makeRequest se reemplaza por una llamada a blogger.getBlogFeed():

function fetchData() {
  jQuery('#errors').hide();
  
  var callback = function(response) {
    if (response.oauthApprovalUrl) {
      // You can set the sign in link directly:
      // jQuery('#personalize').get(0).href = response.oauthApprovalUrl
      
      // OR use the popup.js handler
      var popup = shindig.oauth.popup({
        destination: response.oauthApprovalUrl,
        windowOptions: 'height=600,width=800',
        onOpen: function() {
          showOnly('waiting');
        },
        onClose: function() {
          showOnly('loading');
          fetchData();
        }
      });
      jQuery('#personalize').get(0).onclick = popup.createOpenerOnClick();
      jQuery('#approvalLink').get(0).onclick = popup.createApprovedOnClick();
      
      showOnly('approval');
    } else if (response.feed) {
      showResults(response);
      showOnly('main');
    } else {
      jQuery('#errors').html('Something went wrong').fadeIn();
      showOnly('errors');
    }
  };
  
  blogger.getBlogFeed('http://www.blogger.com/feeds/default/blogs', callback, callback);
}

La segunda vez que se llama a esta función, response.feed contiene datos.

Nota: getBlogFeed() usa la misma función para su controlador de devolución de llamada y error.

Publicar una entrada en Blogger

El último paso es publicar una entrada nueva en un blog. El siguiente código demuestra qué sucede cuando el usuario hace clic en el botón "Guardar".

function savePost(form) { 
  jQuery('#messages').fadeOut();
  jQuery('#submitButton').val('Publishing...').attr('disabled', 'disabled');
  
  // trim whitespace from the input tags
  var input = form.categories.value;
  var categories = jQuery.trim(input) != '' ? input.split(',') : [];   
  jQuery.each(categories, function(i, value) {
    var label = jQuery.trim(value);
    categories[i] = {
      scheme: 'http://www.blogger.com/atom/ns#',
      term: label
    };
  });

  // construct the blog post entry
  var newEntry = new google.gdata.blogger.BlogPostEntry({
    title: {
      type: 'text', 
      text: form.title.value
    },
    content: {
      type: 'text', 
      text: form.content.value
    },
    categories: categories
  });
  
  // publish as draft?
  var isDraft = form.draft.checked;
  if (isDraft) {
    newEntry.setControl({draft: {value: google.gdata.Draft.VALUE_YES}});
  }
  
  // callback for insertEntry()
  var handleInsert = function(entryRoot) {
    var entry = entryRoot.entry;
    var str = isDraft ? '(as draft)' : '<a href="' + entry.getHtmlLink().getHref() + '" target="_blankt">View it</a>';

    jQuery('#messages').html('Post published! ' + str).fadeIn();
    jQuery('#submitButton').val('Save').removeAttr('disabled');
  };
  
  // error handler for insertEntry()
  var handleError = function(e) {
    var msg = e.cause ? e.cause.statusText + ': ' : '';
    msg += e.message;
    alert('Error: ' + msg);
  };
  
  blogger.insertEntry(form.postFeedUri.value, newEntry, handleInsert, handleError);
}

Conclusión

Ahora tienes los componentes fundamentales para comenzar a codificar un gadget sobre las Google Data API.

Esperamos que este artículo te haya agradecido la facilidad con la que el proxy OAuth facilita la autenticación de gadgets. Si combinas esta herramienta potente con la biblioteca cliente de JavaScript de datos de Google, puedes crear fácilmente gadgets interesantes, interactivos y sofisticados.

Si tienes preguntas o comentarios sobre este artículo, visita el foro de debate de las API de cuentas de Google.

Recursos