Como criar um gadget de dados do Google

Eric Bidelman, equipe de APIs de dados do Google
Outubro de 2008

Introdução

Público-alvo

Neste artigo, vamos mostrar como criar um gadget do Blogger. Ele pressupõe que você conhece as APIs Google Data e a biblioteca de cliente JavaScript. Você também precisa ter fluência em JavaScript e experiência na implementação de um gadget OpenSocial usando o gadgets.* API.

Este exemplo também demonstra como usar bibliotecas externas nos seus gadgets. Usei jQuery (principalmente para efeitos de interface) e TinyMCE, um ótimo plug-in de editor de texto avançado WYSIWYG.

Motivação

É necessário muito pouco JavaScript para criar um gadget que usa JSON com uma das APIs de dados do Google. O maior problema desse gadget é que os dados são públicos e somente leitura. Para criar gadgets mais interessantes, é necessário acessar os dados particulares de um usuário, o que exige autenticação. Até agora, não havia uma ótima maneira de aproveitar as APIs da Conta do Google. O AuthSub exige redirecionamentos do navegador, e o ClientLogin expõe as credenciais de um usuário no lado do cliente. Até mesmo hackear um gadget type="url" era inconveniente.

Insira o proxy do OAuth.

Proxy OAuth

Se você não conhece o OAuth, ele é um padrão de autenticação que permite que um usuário compartilhe dados particulares com outro site ou gadget. A especificação do OAuth exige que todas as solicitações de dados sejam assinadas digitalmente. Isso é ótimo para a segurança, mas, no caso de um gadget JavaScript, gerenciar chaves privadas e criar assinaturas digitais não é seguro. Há também a complicação adicional de problemas entre domínios.

Felizmente, esses problemas são resolvidos com um recurso da plataforma de gadgets chamado proxy OAuth. O proxy OAuth foi criado para facilitar a vida dos desenvolvedores de gadgets. Ele oculta grande parte dos detalhes de autenticação do OAuth e faz o trabalho pesado para você. O proxy assina solicitações de dados em nome do gadget. Assim, não é necessário gerenciar chaves privadas nem se preocupar com a assinatura de solicitações. Ele simplesmente funciona!

O proxy OAuth é baseado em um projeto de código aberto chamado Shindig, que é uma implementação da especificação de gadget.

Observação : o proxy OAuth só é compatível com gadgets que usam a API gadgets.* e são executados em contêineres OpenSocial. Ela não é compatível com a API de gadgets legados.

Primeiros passos

O restante deste tutorial vai se concentrar na criação de um gadget para acessar os dados do Blogger de um usuário. Vamos abordar a autenticação (usando o proxy OAuth), o uso da biblioteca de cliente JavaScript e, por fim, a postagem de uma entrada no Blogger.

Autenticação

Primeiro, precisamos dizer ao gadget para usar o OAuth. Para fazer isso, adicione o elemento <OAuth> à seção <ModulePrefs> do 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>

Os três endpoints de URL no elemento <Service> correspondem aos endpoints de token OAuth do Google. Confira a explicação dos parâmetros de consulta:

  • scope

    Esse parâmetro é obrigatório no URL da solicitação. Seu gadget só poderá acessar dados dos scopes usados nesse parâmetro. Neste exemplo, o gadget vai acessar o Blogger. Se o gadget quiser acessar mais de uma API de dados do Google, concatene os scopes adicionais com um %20. Por exemplo, se você quiser acessar o Google Agenda e o Blogger, defina o escopo como http://www.blogger.com/feeds/%20http://www.google.com/calendar/feeds/.

  • oauth_callback

    Esse parâmetro é opcional no URL de autorização. A página de aprovação do OAuth vai redirecionar para esse URL depois que o usuário aprovar o acesso aos dados dele. Você pode deixar esse parâmetro de fora, definir sua própria "página aprovada" ou, de preferência, usar http://oauth.gmodules.com/gadgets/oauthcallback. O último oferece a melhor experiência do usuário quando os usuários instalam o gadget pela primeira vez. Essa página fornece um snippet de JavaScript que fecha automaticamente a janela pop-up.

Agora que temos nosso gadget usando o OAuth, o usuário precisa aprovar o acesso aos dados dele. Confira o fluxo de autenticação:

  1. O gadget é carregado pela primeira vez e tenta acessar os dados do Blogger do usuário.
  2. A solicitação falha porque o usuário não concedeu acesso ao gadget. Felizmente, o objeto retornado na resposta contém um URL (response.oauthApprovalUrl) para onde vamos enviar o usuário para fazer login. O gadget mostra a mensagem "Fazer login no Blogger" e define o href como o valor de oauthApprovalUrl.
  3. Em seguida, o usuário clica em "Fazer login no Blogger", e a página de aprovação do OAuth é aberta em uma janela separada. O gadget aguarda a conclusão do processo de aprovação pelo usuário exibindo um link: "Aprovei o acesso".
  4. No pop-up, o usuário vai escolher conceder ou negar acesso ao nosso gadget. Depois que a pessoa clicar em "Conceder acesso", ela será redirecionada para http://oauth.gmodules.com/gadgets/oauthcallback e a janela será fechada.
  5. O gadget reconhece que a janela foi fechada e tenta acessar o Blogger uma segunda vez solicitando novamente os dados do usuário. Para detectar o fechamento da janela, usei um processador de pop-up. Se você não usar esse código, o usuário poderá clicar manualmente em "Aprovei o acesso".
  6. O gadget agora mostra a interface normal. Essa visualização vai persistir, a menos que o token de autenticação seja revogado em IssuedAuthSubTokens.

Portanto, de acordo com as etapas acima, os gadgets têm a noção de três estados diferentes:

  1. Não autenticado. O usuário precisa iniciar o processo de aprovação.
  2. Aguardando a aprovação do acesso aos dados do usuário.
  3. Autenticado. Os gadgets mostram o estado funcional normal.

No meu gadget, usei contêineres <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&gt

Cada <div> é exibido por si só usando showOnly(). Consulte o gadget de exemplo completo para mais detalhes sobre essa função.

Como usar a biblioteca de cliente JavaScript

Para buscar conteúdo remoto no OpenSocial, faça uma chamada ao método gadgets.io.makeRequest usando a API gadgets.*. No entanto, como estamos criando um gadget do Google Data, não é necessário usar as APIs gadgets.io.*. Em vez disso, use a biblioteca de cliente JavaScript, que tem métodos especiais para fazer solicitações a cada serviço de dados do Google.

Observação: no momento em que este artigo foi escrito, a biblioteca JavaScript só era compatível com Blogger, Agenda, Contatos, Finanças e Google Base. Para usar uma das outras APIs, use gadgets.io.makeRequest sem a biblioteca.

Carregar a biblioteca

Para carregar a biblioteca JavaScript, inclua o carregador comum na seção <Content> e importe a biblioteca depois que o gadget for inicializado. Ao transmitir um callback para gadgets.util.registerOnLoadHandler(), você ajuda a determinar quando o gadget está pronto:

<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&gt

A chamada para blogger.useOAuth('google') informa à biblioteca para usar o proxy OAuth (em vez de AuthSubJS, o método de autenticação normal). Por fim, o gadget tenta recuperar os dados do Blogger do usuário chamando fetchData(). Esse método é definido abaixo.

Buscando dados

Agora que tudo está configurado, como fazemos para GET ou POST dados para o Blogger?

Um paradigma comum no OpenSocial é definir uma função chamada fetchData() no gadget. Esse método geralmente processa as diferentes etapas de autenticação e busca dados usando gadgets.io.makeRequest. Como estamos usando a biblioteca de cliente JavaScript, gadgets.io.makeRequest é substituído por uma chamada para 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);
}

Na segunda vez que essa função é chamada, response.feed contém dados.

Observação: o getBlogFeed() usa a mesma função para o callback e o gerenciador de erros.

Postar uma entrada no Blogger

A última etapa é postar uma nova entrada em um blog. O código abaixo demonstra o que acontece quando o usuário clica no botão "Salvar".

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);
}

Conclusão

Agora você tem os elementos básicos para começar a programar um gadget com base nas APIs de dados do Google.

Esperamos que este artigo tenha mostrado como o proxy OAuth simplifica a autenticação de gadgets. Ao combinar essa ferramenta poderosa com a biblioteca de cliente JavaScript de dados do Google, é fácil criar gadgets interessantes, interativos e sofisticados.

Se você tiver dúvidas ou comentários sobre este artigo, acesse o fórum de discussão das APIs de Contas do Google.

Recursos