Tạo một Tiện ích dữ liệu của Google

Eric Bidelman, nhóm API dữ liệu của Google
Tháng 10 năm 2008

Giới thiệu

Đối tượng người xem

Bài viết này sẽ hướng dẫn bạn cách tạo một tiện ích của Blogger. Giả sử bạn đã quen thuộc với API Dữ liệu của Googlethư viện ứng dụng JavaScript. Bạn cũng phải thành thạo JavaScript và có kinh nghiệm triển khai tiện ích OpenSocial bằng cách sử dụng tiện ích.* API.

Ví dụ này cũng minh họa cách sử dụng thành công thư viện bên ngoài trong tiện ích của bạn. Tôi đã sử dụng jQuery (chủ yếu cho các hiệu ứng giao diện người dùng) và TinyMCE, một trình bổ trợ tuyệt vời cho trình chỉnh sửa văn bản WYSIWYG.

Động lực

Cần có rất ít JavaScript để tạo một tiện ích sử dụng JSON với một trong các API dữ liệu của Google. Sự khó chịu chính của tiện ích như vậy là dữ liệu công khai và ở chế độ chỉ đọc. Để xây dựng tiện ích thú vị hơn, bạn cần có quyền truy cập vào dữ liệu riêng tư của người dùng (điều gì đó cần xác thực). Cho đến nay, chưa có cách nào hiệu quả để tận dụng API Tài khoản Google. AuthSub yêu cầu chuyển hướng trình duyệt và ClientLogin tiết lộ thông tin đăng nhập của người dùng, phía máy khách. Ngay cả việc xâm nhập tiện ích type="url" cũng rất bất tiện.

Nhập Proxy OAuth.

Proxy OAuth

Nếu bạn chưa quen sử dụng OAuth, thì tiêu chuẩn xác thực cho phép người dùng chia sẻ dữ liệu riêng tư của họ với trang web hoặc tiện ích khác. Quy cách OAuth yêu cầu tất cả các yêu cầu dữ liệu phải có chữ ký số. Điều này rất tốt cho việc bảo mật nhưng trong trường hợp tiện ích JavaScript, việc quản lý khoá riêng tư và tạo chữ ký số không an toàn. Ngoài ra còn có thêm chức năng của các vấn đề về tên miền chéo.

May mắn thay, những sự cố này đã được giải quyết bằng cách tận dụng một tính năng từ nền tảng tiện ích có tên OAuth Proxy. Proxy OAuth được thiết kế để giúp các nhà phát triển tiện ích dễ dàng hơn. Nó ẩn nhiều thông tin xác thực của OAuth, và thực hiện phần việc nặng nhọc cho bạn. Proxy thay mặt tiện ích của bạn ký yêu cầu dữ liệu, vì vậy, bạn không cần phải quản lý khóa riêng tư hoặc lo lắng về việc yêu cầu ký. Ứng dụng này hoạt động rất hiệu quả!

Proxy OAuth dựa trên dự án nguồn mở có tên Shindig, là cách triển khai thông số kỹ thuật của tiện ích.

Lưu ý: Proxy OAuth chỉ được hỗ trợ cho các tiện ích sử dụng API gadgets.* và chạy trong các vùng chứa OpenSocial. Không hỗ trợ cho API tiện ích cũ.

Bắt đầu

Phần còn lại của hướng dẫn này sẽ tập trung vào việc tạo tiện ích để truy cập vào dữ liệu Blogger của người dùng. Chúng ta sẽ tìm hiểu quy trình xác thực (bằng OAuth Proxy), sử dụng thư viện ứng dụng JavaScript và cuối cùng là đăng mục cho Blogger.

Xác thực

Trước tiên, chúng ta cần yêu cầu tiện ích sử dụng OAuth. Để làm việc đó, hãy thêm phần tử <OAuth> vào phần <ModulePrefs> của tiện ích:

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

Ba điểm cuối URL trong phần tử <Service> tương ứng với các điểm cuối của mã thông báo OAuth của Google. Dưới đây là phần giải thích về các tham số truy vấn:

  • scope

    Tham số này là bắt buộc trong URL yêu cầu. Tiện ích của bạn sẽ chỉ có thể truy cập vào dữ liệu từ(các) scope được dùng trong thông số này. Trong ví dụ này, tiện ích sẽ truy cập Blogger. Nếu tiện ích của bạn muốn truy cập nhiều Google Data API, hãy nối thêm scope(các) API khác với %20. Ví dụ: nếu bạn muốn truy cập vào cả Lịch và Blogger, hãy đặt phạm vi thành http://www.blogger.com/feeds/%20http://www.google.com/calendar/feeds/.

  • oauth_callback

    Tham số này không bắt buộc trong URL uỷ quyền. Trang phê duyệt OAuth sẽ chuyển hướng đến URL này sau khi người dùng đã phê duyệt quyền truy cập vào dữ liệu của họ. Bạn có thể chọn bỏ thông số này, đặt thông số này thành "trang được phê duyệt" của riêng mình hoặc tốt nhất là sử dụng http://oauth.gmodules.com/gadgets/oauthcallback. Phiên bản sau này cung cấp trải nghiệm người dùng tốt nhất khi người dùng cài đặt tiện ích của bạn lần đầu tiên. Trang đó cung cấp một đoạn mã JavaScript tự động đóng cửa sổ bật lên.

Chúng ta hiện đã có tiện ích bằng OAuth, người dùng cần phê duyệt quyền truy cập vào dữ liệu của họ. Dưới đây là quy trình xác thực:

  1. Tiện ích này tải lần đầu tiên và cố gắng truy cập vào dữ liệu Blogger của người dùng.
  2. Yêu cầu không thành công vì người dùng chưa cấp quyền truy cập vào tiện ích này. May mắn thay, đối tượng được trả về trong phản hồi đã chứa URL (response.oauthApprovalUrl), tại đó chúng tôi sẽ gửi người dùng để đăng nhập. Tiện ích này hiển thị "Đăng nhập vào Blogger" và đặt href này thành giá trị của oauthApprovalUrl.
  3. Tiếp theo, người dùng nhấp vào "Đăng nhập vào Blogger" và trang phê duyệt OAuth sẽ mở trong một cửa sổ riêng. Tiện ích này chờ người dùng hoàn tất quy trình phê duyệt bằng cách hiển thị đường liên kết: "Tôi đã phê duyệt quyền truy cập".
  4. Trong cửa sổ bật lên, người dùng sẽ chọn cấp/từ chối quyền truy cập vào tiện ích của chúng tôi. Sau khi nhấp vào "Cấp quyền truy cập", họ sẽ được chuyển đến http://oauth.gmodules.com/gadgets/oauthcallback và cửa sổ sẽ đóng.
  5. Tiện ích này nhận ra cửa sổ đã đóng và cố gắng truy cập vào Blogger lần thứ hai bằng cách yêu cầu lại dữ liệu của người dùng. Để phát hiện cửa sổ đóng, tôi đã sử dụng trình xử lý cửa sổ bật lên. Nếu bạn không sử dụng mã này, người dùng có thể nhấp vào "Tôi đã phê duyệt quyền truy cập" theo cách thủ công.
  6. Tiện ích giờ đây sẽ hiển thị giao diện người dùng thông thường của tiện ích. Chế độ xem này sẽ tồn tại trừ khi mã thông báo xác thực bị thu hồi trong phần Phát hành dữ liệu.

Vì vậy, từ các bước ở trên, các tiện ích có khái niệm về ba trạng thái khác nhau:

  1. Chưa được xác thực. Người dùng cần bắt đầu quy trình phê duyệt.
  2. Đang chờ người dùng phê duyệt quyền truy cập vào dữ liệu của họ.
  3. Đã xác thực. Các tiện ích cho biết trạng thái hoạt động bình thường của thiết bị.

Trong tiện ích của mình, tôi đã sử dụng các vùng chứa <div> để phân tách từng giai đoạn:

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

Mỗi <div> sẽ được hiển thị bằng chính showOnly() đó. Hãy xem tiện ích mẫu đầy đủ để biết thông tin chi tiết về hàm đó.

Sử dụng thư viện ứng dụng JavaScript

Để tìm nạp nội dung từ xa trong OpenSocial, bạn thực hiện lệnh gọi đến phương thức gadgets.io.makeRequest bằng API gadgets.*. Tuy nhiên, vì chúng tôi đang xây dựng một tiện ích Dữ liệu trên Google nên bạn không cần phải chạm vào các API gadgets.io.*. Thay vào đó, hãy tận dụng thư viện ứng dụng JavaScript có các phương thức đặc biệt để gửi yêu cầu đến từng dịch vụ dữ liệu của Google.

Lưu ý: Tại thời điểm viết bài này, thư viện JavaScript chỉ hỗ trợ Blogger, Lịch, Danh bạ, Tài chínhGoogle Base. Để sử dụng một trong những API khác, hãy sử dụng gadgets.io.makeRequest mà không cần thư viện.

Đang tải thư viện

Để tải thư viện JavaScript, hãy đưa trình tải chung vào phần <Content> và nhập thư viện sau khi khởi chạy tiện ích. Việc cung cấp lệnh gọi lại đến gadgets.util.registerOnLoadHandler() sẽ giúp xác định thời điểm tiện ích sẵn sàng:

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

Lệnh gọi đến blogger.useOAuth('google') yêu cầu thư viện sử dụng Proxy OAuth (thay vì AuthSubJS – phương thức xác thực thông thường). Cuối cùng, tiện ích cố gắng truy xuất dữ liệu Blogger của người dùng bằng cách gọi fetchData(). Phương thức đó được định nghĩa bên dưới.

Đang tìm nạp dữ liệu

Bây giờ mọi thứ đã được thiết lập, làm cách nào để chúng tôi thực sự GET hoặc POST dữ liệu cho Blogger?

Một mô hình phổ biến trong OpenSocial là xác định một hàm có tên là fetchData() trong tiện ích của bạn. Phương thức này thường xử lý các giai đoạn xác thực và tìm nạp dữ liệu bằng gadgets.io.makeRequest. Vì chúng tôi đang sử dụng thư viện ứng dụng JavaScript, nên gadgets.io.makeRequest sẽ được thay thế bằng lệnh gọi đến 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);
}

Khi hàm này được gọi lần thứ hai, response.feed sẽ chứa dữ liệu.

Lưu ý: getBlogFeed() sử dụng cùng một hàm cho lệnh gọi lại và trình xử lý lỗi.

Đăng bài lên Blogger

Bước cuối cùng là đăng một mục mới lên Blog. Mã bên dưới minh họa điều gì sẽ xảy ra khi người dùng nhấp vào nút "Lưu".

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

Kết luận

Bây giờ, bạn đã có các yếu tố nền tảng để bắt đầu mã hóa một tiện ích ngoài API Dữ liệu của Google.

Chúng tôi hy vọng bài viết này mang lại cho bạn sự đánh giá cao về độ đơn giản của OAuth Proxy khi xác thực tiện ích. Việc kết hợp công cụ mạnh mẽ này với thư viện ứng dụng JavaScript của Google Data sẽ giúp bạn dễ dàng xây dựng các tiện ích thú vị, giàu tính tương tác và tinh vi.

Nếu bạn có câu hỏi hoặc nhận xét về bài viết này, vui lòng truy cập vào diễn đàn thảo luận về API của Tài khoản Google.

Tài nguyên