Tháng 10 năm 2008
Giới thiệu
Đối tượng
Bài viết này sẽ hướng dẫn bạn cách tạo một tiện ích Blogger. Giả sử bạn đã quen thuộc với Google Data API và thư viện ứng dụng JavaScript. Bạn cũng phải thông thạo JavaScript và có kinh nghiệm triển khai một tiện ích OpenSocial bằng cách sử dụng gadgets.* API.
Ví dụ này cũng minh hoạ cách sử dụng thành công các 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ợ trình chỉnh sửa văn bản đa dạng thức WYSIWYG tuyệt vời.
Động lực
Bạn chỉ cần một chú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. Điểm gây khó chịu chính của tiện ích như vậy là dữ liệu ở chế độ công khai và chỉ đọc. Để tạo ra nhữ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 (một việc đòi hỏi phải 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 trình duyệt chuyển hướng và ClientLogin hiển thị 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 vào một thiết bị type="url"
cũng gây bất tiện.
Nhập Proxy OAuth.
Uỷ quyền OAuth
Nếu bạn chưa quen với OAuth, thì đây là một 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 một trang web hoặc thiết bị khác. Quy cách OAuth yêu cầu tất cả các yêu cầu dữ liệu đều phải được ký bằng chữ ký số. Điều này rất tốt cho 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ố là không an toàn. Ngoài ra, còn có vấn đề phức tạp hơn là các vấn đề về nhiều miền.
May mắn thay, những vấn đề này được giải quyết bằng cách tận dụng một tính năng của nền tảng tiện ích có tên là OAuth Proxy. OAuth Proxy được thiết kế để giúp các nhà phát triển tiện ích làm việc dễ dàng hơn. Thư viện này ẩn phần lớn thông tin xác thực của OAuth và thực hiện những thao tác phức tạp cho bạn. Proxy thay mặt cho tiện ích của bạn ký các yêu cầu dữ liệu, vì vậy, bạn không cần quản lý khoá riêng tư hoặc lo lắng về việc ký các yêu cầu. Đơn giản là nó hiệu quả!
OAuth Proxy dựa trên một dự án nguồn mở có tên là Shindig, đây là một cách triển khai quy cách của tiện ích.
Lưu ý: OAuth Proxy 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.
API này không được 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 một tiện ích để truy cập vào dữ liệu của người dùng trên Blogger. Chúng ta sẽ xem xét quy trình xác thực (bằng cách sử dụng OAuth Proxy), sử dụng thư viện ứng dụng JavaScript và cuối cùng là đăng một mục lên 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 như vậy, hãy thêm phần tử <OAuth>
vào mục <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 mã thông báo OAuth của Google. Sau đâ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 tham số này. Trong ví dụ này, tiện ích sẽ truy cập vào Blogger. Nếu tiện ích của bạn muốn truy cập vào nhiều Google Data API, hãy nối(các)scope
bổ sung với%20
. Ví dụ: nếu bạn muốn truy cập cả Lịch và Blogger, hãy đặt phạm vi thànhhttp://www.blogger.com/feeds/%20http://www.google.com/calendar/feeds/
.oauth_callback
Tham số này là 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 tắt tham số này, đặt tham số này thành "trang được phê duyệt" của riêng bạn hoặc tốt nhất là sử dụng
http://oauth.gmodules.com/gadgets/oauthcallback
. Sau này mang lại trải nghiệm tốt nhất cho người dùng khi họ 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.
Bây giờ, khi chúng ta đã có tiện ích sử dụng OAuth, người dùng cần phê duyệt quyền truy cập vào dữ liệu của họ. Sau đây là quy trình xác thực:
- Tiện ích tải lần đầu tiên và cố gắng truy cập vào dữ liệu của người dùng trên Blogger.
- 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. Rất may, đối tượng được trả về trong phản hồi chứa một URL (
response.oauthApprovalUrl
) mà chúng ta sẽ gửi người dùng đến để đăng nhập. Tiện ích này hiển thị "Đăng nhập vào Blogger" và đặt href của tiện ích thành giá trị củaoauthApprovalUrl
. - 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ở ra trong một cửa sổ riêng. Tiện ích này sẽ đợi người dùng hoàn tất quy trình phê duyệt bằng cách hiển thị một đường liên kết: "Tôi đã phê duyệt quyền truy cập".
- 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. - Tiện ích nhận ra cửa sổ đã đóng và tìm cách 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 đã dùng một trình xử lý cửa sổ bật lên. Nếu bạn không sử dụng mã như vậ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.
- Tiện ích hiện hiển thị giao diện người dùng bình thường. Chế độ xem này sẽ duy trì trừ phi mã thông báo xác thực bị thu hồi trong IssuedAuthSubTokens.
Do đó, từ các bước trên, tiện ích có khái niệm về 3 trạng thái khác nhau:
- Không được xác thực. Người dùng cần bắt đầu quy trình phê duyệt.
- Đang chờ người dùng phê duyệt quyền truy cập vào dữ liệu của họ.
- Đã xác thực. Tiện ích này hiển thị trạng thái chức năng bình thường.
Trong tiện ích của mình, tôi đã dùng các vùng chứa <div>
để tách riêng 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ẽ xuất hiện riêng bằng showOnly()
. Hãy xem ví dụ đầy đủ về tiện ích để 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 sẽ gọi phương thức gadgets.io.makeRequest
bằng API gadgets.*
.
Tuy nhiên, vì chúng ta đang tạo một tiện ích Google Data, nên không cần phải sử dụng 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 để đưa ra yêu cầu cho 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ính và Google Base. Để sử dụng một trong các API khác, hãy 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 thêm trình tải chung vào phần <Content>
và nhập thư viện sau khi tiện ích được khởi chạy. Việc truyền một 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 của thư viện).
Cuối cùng, tiện ích này sẽ 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 xác định bên dưới.
Tìm nạp dữ liệu
Giờ đây, khi mọi thứ đã được thiết lập, làm cách nào để chúng ta thực sự GET
hoặc POST
dữ liệu vào 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 khác nhau và tìm nạp dữ liệu bằng cách sử dụng gadgets.io.makeRequest
. Vì chúng ta đang dùng thư viện ứng dụng JavaScript, nên gadgets.io.makeRequest
sẽ được thay thế bằng một 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); }
Lần thứ hai hàm này được gọi, response.feed
chứa dữ liệu.
Lưu ý: getBlogFeed()
sử dụng cùng một hàm cho trình xử lý lỗi và lệnh gọi lại.
Đăng một mục lên Blogger
Bước cuối cùng là đăng một mục mới lên Blog. Đoạn mã dưới đây minh hoạ những gì 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
Giờ đây, bạn đã có các khối xây dựng để bắt đầu lập trình một tiện ích dựa trên Google Data API.
Hy vọng bài viết này đã giúp bạn hiểu được mức độ đơn giản của việc xác thực thiết bị bằng Proxy OAuth. 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 giúp bạn dễ dàng tạo ra những tiện ích thú vị, mang 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 Tài khoản Google.
Tài nguyên
- Viết tiện ích OAuth (tài liệu đầy đủ về tiện ích)
- Sử dụng OAuth với Google Data API (bài viết về cách sử dụng OAuth với Google Data API)
- Xác thực OAuth cho ứng dụng web (tài liệu đầy đủ về OAuth)
- Thư viện ứng dụng JavaScript
- Diễn đàn thảo luận về API Tài khoản Google