This is part of the series on sharing data between Apps Script and Node on various backends, Apps script library with plugins for multiple backend cache platforms but the Drive API introduces a number of tricky bits that deserve their own article that might be useful for general Drive usage. All this code is built into my https://github.com/brucemcpherson/bmcrusher-node module, but let’s take a look at what’s going on here.
Use a Service Account to impersonate yourself
Because we’re doing server side actions here, we can use a service account. That’s simpler than playing around with Node Oauth2 if you are using a pedefined Drive belonging to you. However there are a couple of extra wrinkles as a result of using a service account with your own Drive folder to host the data being shared between Apps Script and Node. Actually, you can of course share between Node and Node too, but in any case, you’ll still need to get accesss to a Drive.
Creating the service account
In the Cloud Console, enable the Drive API, create a service account key and pay particular attention to the items marked here. You’ll need them later.
You can then download the json file for that account and put it somewhere private in your Node project. Remember – Don’t commit it to github.
The first step in enabling impersonation is to enable G Suite delegation for that service account – done above when the service account is created. Next, we need to allow that that service account to have access to the scopes we’ll allow it to access. That’s in admin.google.com. You need to get the clientID from the service account, add it, and allow it the drive scope.
That’s the set up over, and we should be good to go. I’ll walk you through a utility module to interact with the Drive API from node
Writing your own Drive access module
If you are here because you are using the bmcrusher-node module, then you’re done. However if you are writing your own module, here are some tips.
You’ll need the googleapis module
And include this to start
Let’s assume your wrapper module will call this as pass over the JSON file and email address to impersonate. The first step is to get an auth object you can use with the Drive client.
You’ll need an instatiated client, with the auth built in. This will call the auth function from above. This will be the primary interface into the methods in this Drive access client. It’s important to use v3 (not v2) as there were a number of changes to property names between the two versions – and the code following will all be for v3.
Creating a file
The bmcrusher-node module writes base64 compressed versions of the data, so we only need to support text here, but it’s straightforward to extend this to other mimetypes if you need to. Rather nicely, the Drive API supports streaming, so we’ll be reading and writing all content using streams – this is the same approach I use with the Cloud Storage version. If you want to write the file at the top level, simply omit the parents argument, otherwise provide the id(s) of the file’s parent(s). I recommend you avoid having multiple parents for the same file. It’s just a pain.
Creating a folder
You may need to create a folder to put your file into. We can simply use the createFile method with a folder mimtype and no content. If this is a subfolder, the parent parameter should be the id of the parent folder of the folder being created. We defined the FOLDER mimeType earlier.
Getting files by name
We can use the LIST method with a query to get a collection of files that match that query. If you’re expecting a bunch of files then you’ll have to handle paging. My use case expects only 1, but handles a few. If the file is in a subfolder, then provide its parent id(s) in the same way as before.
Getting a file content by id
Once you’ve used get file by name to get the Ids, you can get the file content. This uses a stream to get the file content, so we’ll need a way to convert the stream into a string to return it – see later.
Converting a stream to a string
A stream is a chunk of bytes that have to be later concatenated and converted. Here’s a function to do that.
Removing a file
This is by id
That was the easy part, but let’s say the file you want find is in a folder structure with a path like /crusher/store/data/mydata.txt. We’ll need to iterate through that folder structure to find the id of the parent folder of mydata.txt. If we’re creating a file like that we may also want to create the folders as we go if they don’t already exist. There are a number of solutions to this, but asynchronous recursion is not a lot of fun. However an asynchronous iterator makes it a littler simpler.
for await of
Using an iterator makes the overall structure very simple and clean. The final result is the id of the last folder on the path. In this case, we also want to create any folders that are missing from the path as we loop through
This is a little more complex to grasp, but essentially an iterator follows the iterator protocol
If the iteration is complete, it returns this
otherwise it returns the next value
This function returns a closure containg the iterator that can be used by the for await … of .. loop.
It looks like a bit of a handful, but the pattern is pretty straightford. The job of the next() method is to return one of the two results above to be dealt with in the for loop. The source data is the folder path, split into an array of its components. Each time next() is called we slice off another component and find that using the previous list folder result as the parents of the query – and keep going till we run out of folders – optionally creating new ones as we go.
Here’s some example using all those methods
scrviz Apps Script bmCrusher: https://scrviz.web.app?repo=brucemcpherson%2FbmCrusher
scrviz Apps Script bmPreFiddler: https://scrviz.web.app?repo=brucemcpherson%2FbmPreFiddler
scrviz Apps Script cGoa: https://scrviz.web.app?repo=brucemcpherson%2FcGoa
github bmcrusher-node: https://github.com/brucemcpherson/bmcrusher-node