The Google Drive SDK opens up a great number of possibilities of integration with third-party applications and services. One such integration is via the new Web Intents API.
Web Intents lets web developers integrate their web apps with third-party services and avoid implementing the same feature with similar services over and over again. With Web Intents, a service can register itself to handle specific functionality, such as saving a file or sharing data. Client applications can then discover and interact with the service to use that specific functionality.
In this post, we’ll look at code for two example apps: a service that exposes a “save” web intent, and a client that consumes it to save files to Google Drive.
To demonstrate web intents, we wrote a proof of concept app called Cloudfilepicker.com. This is a service that lets users or applications save and retrieve data from Drive via the Web Intents API. With Cloudfilepicker, any application that fires a “save” intent can save to Google Drive without directly implementing the API.
To build a service application like cloudfilepicker.com that interacts with Drive and Web Intents, create a Chrome app whose manifest exposes a http://webintents.org/save intent action. For example:
http://webintents.org/save
{ "name": "Google Drive Web Intent", "version": "1", "app": { "launch": { "local_path": "index.html" } }, "intents": { "http://webintents.org/save": [ { "type" : ["image/png", "image/jpg", "image/jpeg"], "href": "save.html", "title": "Save to Drive" } ] } }
The intent declaration in the app manifest describes the functionality that your application offers, the data it can work with, and what to launch when the user chooses your application.
In order to use the Drive API, the page has to get an OAuth token for the user. The complete JavaScript implementation of the OAuth flow is available in the project repository.
Once the app receives a valid OAuth token, we can load the Drive client library and perform the file upload request using multipart upload. A Web Intent with action http://webintents.org/save can provide the file data in two ways: as a base64-encoded string or a blob, and our app should support both. With the former, we have to pass the string to the request, while with the latter we have to read the blob content and base64 encode it before we can pass it to insertBase64Data:
insertBase64Data
const boundary = '-------314159265358979323846'; const delimiter = "\r\n--" + boundary + "\r\n"; const close_delim = "\r\n--" + boundary + "--"; function makeApiCall(authResult) { gapi.client.load('drive', 'v2', function() { if(window.webkitIntent) { var data = window.webkitIntent.data; if(data.constructor.name == "Blob") { insertFileData(data, authResult, processResponse); } else if(typeof(data) == "string") { var meta = { 'title': "Test Image " + (new Date()).toJSON(), 'mimeType': window.webkitIntent.type }; insertBase64Data(data.replace(/data:image\/([^;]*);base64,/,""), window.webkitIntent.type, meta, authResult); } } }); } function insertBase64Data(data, contentType, metadata, authRequest, callback) { var multipartRequestBody = delimiter + 'Content-Type: application/json\r\n\r\n' + JSON.stringify(metadata) + delimiter + 'Content-Type: ' + contentType + '\r\n' + 'Content-Transfer-Encoding: base64\r\n' + '\r\n' + data + close_delim; var request = gapi.client.request({ 'path': '/upload/drive/v2/files', 'method': 'POST', 'params': {'uploadType': 'multipart'}, 'headers': { 'Content-Type': 'multipart/mixed; boundary="' + boundary + '"' }, 'body': multipartRequestBody}); if (!callback) { callback = function(file) { console.log(file.id); }; } request.execute(callback); } function insertFileData(fileData, authRequest, callback) { var reader = new FileReader(); reader.readAsBinaryString(fileData); reader.onload = function(e) { var contentType = fileData.type || 'application/octet-stream'; var metadata = { 'title': fileData.fileName, 'mimeType': contentType }; var base64Data = btoa(reader.result); insertBase64Data(base64Data, contentType, metadata, authRequest, callback); } }
Now, any web app that wants to save data to Google Drive could use this service instead of implementing the same functionality by invoking webkitStartActivity with a save intent.
webkitStartActivity
An example client app is http://www.imagemator.com. This app lets users manipulate and save images, but has no direct Drive API integration. When the user invokes the “save” intent in imagemator.com, if they have the “Save to Drive” app installed they will see Drive as an option in the list of apps that can fulfill that action:
If the user selects “Save to Drive”, the browser will send a request to the page listed as the href property of the intent declaration -- save.html in the sample manifest above. The following code shows how to trigger the save request:
href
var fileData = canvas.toDataURL(); var intent = new WebkitIntent({'action': 'http://webintents.org/save', 'type':'image/png', 'data': fileData}); var onSuccess = function(data) { // handle any data that might be sent back }; window.navigator.webkitStartActivity(intent, onSuccess);
To learn more about the Web Intents check webintents.org, and visit https://developers.google.com/drive to learn about the Google Drive SDK. The complete sample described in this post is also available on https://github.com/PaulKinlan/WebIntents/tree/master/server/demos/cloudfilepicker.