In my previous post, we discussed how we were able to sync our files and folders with Google Drive. You can refer the post here - http://kiprosh.com/blog/google-drive-file-upload-pre-generated-file-id-for-uploads. Once we were done with the sync setup - next step is to ensure sync was accurate and timely i.e changes made in our application are accessed instantly in Google and more important changes made in Google are available in our application. We needed this without an impact on the performance - without polling Google to find out updates and nor writing any custom logic comparing timestamps to find the files added, files updated and files removed

For changes being instantly tracked we needed some watcher or webhook from Google - that will allow us to make changes in our application as soon as some change happens in Google. The API V3 https://developers.google.com/drive/v3/reference/files/watch does allow for the same. We will be able to learn in this post how do we set up an application to start tracking a file or a folder in Google.

Step 1: Registering your domain

Before you register your domain, one needs to verify - they own the domain. To help you verify your domain you can refer this link - https://support.google.com/webmasters/answer/35179

Once you have verified your domain, you can proceed to register your domain -

To register a verified domain name as one of the allowed domains for your project, do the following:

  • Go to the Domain verification page in the API Console.
  • Click Add domain.
  • Fill in the form, then again click Add domain.

Important Note: In case you are not able to verify your domain one of
the problems could be you are signed in to multiple Google accounts.
To resolve it was to open a new incognito window or private window,
and login into the Google Account where you need to add the domain.
This should definitely work. Make sure the domain is secured(https).

Once your domain is added successfully - it will be listed under the Allowed Domains section.

Step 2: Creating notification channels

Setting step 1 does not send any notifications to the application for any changes in any folder or files. We need to set up a notification channel for each resource to be watched. Once that is done successfully - you will start receiving notifications of the changes.

How to set up a notification channel?

You can watch a file or a folder or you can watch any change for a user.

The endpoint for setting up a notification channel for a resource(file/folder) -

POST https://www.googleapis.com/drive/v3/files/fileId/watch

The endpoint for setting up a notification channel for a User -

POST https://www.googleapis.com/drive/v3/changes/watch

The above endpoint requires following properties when making a request - id, type, address - please refer this resource for more details - https://developers.google.com/drive/v3/web/push

An id property string that uniquely identifies this new notification channel within your project. We recommend that you use a universally unique identifier (UUID) or any similar unique string. Maximum length: 64 characters.The ID value you set is echoed back in the X-Goog-Channel-Id HTTP header of every notification message that you receive for this channel.

A type property string set to the value web_hook.

An address property string set to the URL that listens and responds to notifications for this notification channel. This is your Webhook callback URL, and it must use HTTPS.

module Services
  module GoogleDriveSync
	module Notifications
  	class Base
    	include HTTParty

    	base_uri 'https://www.googleapis.com/drive/v3'

    	GOOGLE_DRIVE_FILE_URL = "https://www.googleapis.com/drive/v3/files"

     	attr_accessor :file, :access_token

     	def initialize(file, access_token)
      	self.file        	= file
      	self.access_token	= access_token
    	end

    	def headers
      	  {
        	    'Authorization' => "Bearer #{access_token}",
        	    'Content-Type'  => 'application/json'
      	  }
    	end
  	end
	end
  end
end

module Services
  module GoogleDriveSync
	module Notifications
  	class WatchFile < Base
    	def watch_params
          {
            kind:    	'api#channel',
            type:    	'web_hook',
            id:      	channel_id,
            resourceId:  file_id,
            resourceUri: "#{GOOGLE_DRIVE_FILE_URL}/#{file_id}",
            address: 	YOUR_APP_URL,
	           expiration:  (Time.now + 24.hours).to_i * 1000
          }
    	end

    	def channel_id
      	  SecureRandom.uuid
    	end

    	def process
      	  self.class.post("/files/#{file_id}/watch", headers: headers, body: watch_params.to_json)
    	end
  	end
	end
  end
end

Important note: If you notice the code above we have also set the expiration time to be 24 hours - that is the time your webhook is active if you do not set that property - default is 1 hour. So to keep the webhook active each day you need to again create the notification channel before its expired. We have a schedule setup to do this before the webhook expires.

Once a notification channel is created successfully you will get an HTTP 200 OK status code and a message body with some properties - you want to persist the id and resourceId if you wish to stop the channel later -

module Services
  module GoogleDriveSync
	module Notifications
  	class StopWatch < Base
    	def file_params
      	  {
        	    id:      	channel_id,
        	   resourceId:  resource_id
      	  }
    	end

    	def channel_id
      	  **from database**
    	end

    	def resource_id
      	  **from database**
    	end

    	def process
      	  self.class.post("/channels/stop", headers: headers, body: file_params.to_json)
    	end
  	end
	end
  end
end

Step 3: Receiving Notifications

Once you start getting notifications for any changes in your file/folder resource. The notification response will look similar to this -

POST https://mydomain.com/notifications // Your receiving URL.
X-Goog-Channel-ID: channel-ID-value
X-Goog-Channel-Token: channel-token-value
X-Goog-Channel-Expiration: expiration-date-and-time // In human-readable format; present only if channel expires.
X-Goog-Resource-ID: identifier-for-the-watched-resource
X-Goog-Resource-URI: version-specific-URI-of-the-watched-resource
X-Goog-Resource-State: sync
X-Goog-Message-Number: 1

You want to filter out some of the request based on the property X-Goog-Resource-State, it can have the following values - sync, add, remove, update, trash, untrash, or change. For files, we can the use state like update, trash, untrash(restore the file), remove(permanent deletion), sync state denotes a notification for a file whose notification channel is setup successfully. If we are watching a folder - we should look at the state - add for any new files added to the folder to sync those new files.

The above notification will not tell you exactly what changed - but if you check the property X-Goog-Changed you would be able to know categorically what changed - like properties of the folder(i.e name, description), content for a file or children i.e when a file is added to the folder.

Hope it helps. Let me know if any queries. Thanks