Google Drive upload w/ Google API in React
Objective
To save text data as a Google Document in your drive.
Main Steps
- Authorise your app and enable google api
- Writing the code for user authentication
- Finally, for the uploading the data in google drive
Authorise your app and enable google api
The google docs for this step is enough to get through.
Here is the link
Some key points to remember:
- First create the API key in the credentials and then the Oauth Client Id
- Make sure you specify the website from which we will sending the requests, else you will receive CORs issue. Also you cannot use IP, if you working on localhost specify
localhost:<PORT>
After this step you will have two things:
- API_KEY
- Client_ID
Code for user Authentication
We will be using OAuth2.0 cause that's the only thing google allows.
Let's explain what will happen in this step.
- User will Sign In
- User will be asked if he/she authorises this app
- Once user gives a consent we will recieve a token and make further request.
Regarding oauth2.0 there is a lot of theory here
Coming to implementation:
We need to specify some script so that we can use the gapi.client
. Now if you are in your local machine using
gapi.client
may give you undefined
. Instead you should use the window.gapi.client
.
There is one github issue regarding this.
Coming back in react we will append this script to the body inside the componentDidMount()
function
componentDidMount(){
var script = document.createElement('script');
script.onload=this.handleClientLoad;
script.src="https://apis.google.com/js/api.js";
document.body.appendChild(script);
}
Function handleClientLoad will load the gapi.client
for us.
handleClientLoad = ()=>{
window.gapi.load('client:auth2', this.initClient);
}
As a callback we specify initClient
where we initialise the gapi.client
The call to gapi.client.init
specifies the following fields:
- API_KEY and CLIENT_ID : These specify your application's authorisation credentials. We have got these from the previous step.
-
Scope: It specifies a space-delimited list of access scopes that correspond to the resources that your application could access on the user's behalf. Here is a list of scopes. For our purpose we would need this
https://www.googleapis.com/discovery/v1/apis/drive/v3/rest
. - DiscoveryDocs: It identifies a list of API Discovery documents that your application uses. In this example, the code retrieves the discovery document for version 3 of the Google Drive API,
https://www.googleapis.com/discovery/v1/apis/drive/v3/rest
Put these at the top
var SCOPE = 'https://www.googleapis.com/auth/drive.file';
var discoveryUrl = 'https://www.googleapis.com/discovery/v1/apis/drive/v3/rest';
So, now let's define out initClient
function.
initClient = () => {
try{
window.gapi.client.init({
'apiKey': "<YOUR API KEY>",
'clientId': "<YOUR CLIENT ID>",
'scope': SCOPE,
'discoveryDocs': [discoveryUrl]
}).then(() => {
this.setState({
googleAuth: window.gapi.auth2.getAuthInstance()
})
this.state.googleAuth.isSignedIn.listen(this.updateSigninStatus);
document.getElementById('sign
document.getElementById('signout-btn').addEventListener('click', this.signOutFunction);
});
}catch(e){
console.log(e);
}
}
Once the client is initialised we get an Auth Instance and save it in a state variable, googltAuth
. The updateSigninStatus
function is a listener that listens for changes to the user's authorization status. Also we add some functions to the sign in and sign out buttons. So, before moving on we need to specify our states.
state = {
name: '',
googleAuth: ''
}
The name
variable is for other purpose, we will come later. Now if the user clicks on sign in button the signInFunction
will be triggered.
signInFunction =()=>{
this.state.googleAuth.signIn();
this.updateSigninStatus()
}
Since after sign in the state changes we will explicitly call the updateSigninStatus()
function. The signout function does something very similar.
signOutFunction =()=>{
this.state.googleAuth.signOut();
this.updateSigninStatus()
}
Now let's come to updateSignStatus()
. All it does is fetch some user details(here, the name and this is where we use the name state variable).
updateSignStatus = async ()=>{
var user = this.state.googleAuth.currentUser.get();
if (user.wc == null){
this.setState({
name: ''
});
}
else{
var isAuthorized = user.hasGrantedScopes(SCOPE);
if(isAuthorized){
this.setState({
name: user.Ot.Cd
});
//we will put the code of the third step here
}
}
}
Important thing to note here is that isAuthorized
is true only if the user grants the permissions to the app. Now once we are done upto here, now we can move to the final step of uploading the file.
Uploading the data in google drive
For uploading the data we have various methods. In our case we will use the Multipart method because we will not just create a file but also specify the meta data as well. All the code snippets in this step will be inside the region specified in the above step.
The steps mentioned in the google docs are:
-
Create a POST request to the method's /upload URI with the query parameter of uploadType=multipart:
POST https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart
-
Create the body of the request. Format the body according to the multipart/related content type [RFC 2387], which contains two parts:
. Metadata. The metadata must come first and must have a Content-Type header set to application/json; charset=UTF-8. Add the file's metadata in JSON format.
. Media. The media must come second and must have a Content-Type header of any MIME type. Add the file's data to the media part.Identify each part with a boundary string, preceded by two hyphens. In addition, add two hyphens after the final boundary string.
-
Add these top-level HTTP headers:
. Content-Type. Set to multipart/related and include the boundary string you're using to identify the different parts of the request. For example: Content-Type: multipart/related; boundary=foo_bar_baz
. Content-Length. Set to the total number of bytes in the request body.
Send the request.
So, let's create the metadata of the file
var fileName='mychat123';
var fileData='this is a sample data';
var contentType='text/plain'
var metadata = {
'name': fileName,
'mimeType': contentType
};
You can change the fileName
and fileData
and also change the contentType
accordingly, it will hold the MIME type of the data you will upload to drive.
Now the multipart body. It follows a particular standardisation, you can read more about it here
Without going into much details just copy the following.
const boundary='<ANY RANDOM STRING>'
const delimiter = "\r\n--" + boundary + "\r\n";
const close_delim = "\r\n--" + boundary + "--";
here boundary
will differentiate between the various part of the request body.
var multipartRequestBody =
delimiter +
'Content-Type: application/json; charset=UTF-8\r\n\r\n' +
JSON.stringify(metadata) +
delimiter +
'Content-Type: ' + contentType + '\r\n\r\n' +
fileData+'\r\n'+
close_delim;
This is a format and it needs to be followed. After this all we are left with is sending the request which we will do using the gapi.client.request
this will handle the Auth Token automatically.
var request = window.gapi.client.request({
'path': 'https://www.googleapis.com/upload/drive/v3/files',
'method': 'POST',
'params': {'uploadType': 'multipart'},
'headers': {
'Content-Type': 'multipart/related; boundary=' + boundary + ''
},
'body': multipartRequestBody});
request.execute(callback);
Now we are DONE!!.
To compile it all this was my App.js
import React, { Component } from 'react';
var SCOPE = 'https://www.googleapis.com/auth/drive.file';
var discoveryUrl = 'https://www.googleapis.com/discovery/v1/apis/drive/v3/rest';
class App extends Component {
state = {
name: '',
googleAuth: ''
}
componentDidMount(){
var script = document.createElement('script');
script.onload=this.handleClientLoad;
script.src="https://apis.google.com/js/api.js";
document.body.appendChild(script);
}
initClient = () => {
try{
window.gapi.client.init({
'apiKey': "",
'clientId': "",
'scope': SCOPE,
'discoveryDocs': [discoveryUrl]
}).then(() => {
this.setState({
googleAuth: window.gapi.auth2.getAuthInstance()
})
this.state.googleAuth.isSignedIn.listen(this.updateSigninStatus);
document.getElementById('signin-btn').addEventListener('click', this.signInFunction);
document.getElementById('signout-btn').addEventListener('click', this.signOutFunction);
});
}catch(e){
console.log(e);
}
}
signInFunction =()=>{
this.state.googleAuth.signIn();
this.updateSigninStatus()
}
signOutFunction =()=>{
this.state.googleAuth.signOut();
this.updateSigninStatus()
}
updateSigninStatus = ()=> {
this.setSigninStatus();
}
setSigninStatus= async ()=>{
var user = this.state.googleAuth.currentUser.get();
console.log(user)
if (user.wc == null){
this.setState({
name: ''
});
}
else{
var isAuthorized = user.hasGrantedScopes(SCOPE);
if(isAuthorized){
this.setState({
name: user.Ot.Cd
});
const boundary='foo_bar_baz'
const delimiter = "\r\n--" + boundary + "\r\n";
const close_delim = "\r\n--" + boundary + "--";
var fileName='mychat123';
var fileData='this is a sample data';
var contentType='text/plain'
var metadata = {
'name': fileName,
'mimeType': contentType
};
var multipartRequestBody =
delimiter +
'Content-Type: application/json; charset=UTF-8\r\n\r\n' +
JSON.stringify(metadata) +
delimiter +
'Content-Type: ' + contentType + '\r\n\r\n' +
fileData+'\r\n'+
close_delim;
console.log(multipartRequestBody);
var request = window.gapi.client.request({
'path': 'https://www.googleapis.com/upload/drive/v3/files',
'method': 'POST',
'params': {'uploadType': 'multipart'},
'headers': {
'Content-Type': 'multipart/related; boundary=' + boundary + ''
},
'body': multipartRequestBody});
request.execute(function(file) {
console.log(file)
});
}
}
}
handleClientLoad = ()=>{
window.gapi.load('client:auth2', this.initClient);
}
render() {
return (
<div className="App">
<div>UserName: <strong>{ this.state.name}</strong></div>
<button id="signin-btn">Sign In</button>
<button id="signout-btn">Sign Out</button>
</div>
);
}
}
export default App;
I had to go through a lot of documentation of Google APIs to be able to come to this. I tried many other approaches but at this was the one that worked for me. If you are stuck at any point do check out the Oauth Playground
Top comments (8)
Hi Arnab, great article and very clear.
Using your code, I'm able to do exactly what you did - create a plain text file with the content 'this is a sample data'.
However, what I'm trying to do is upload an Excel file, and it does create a file but the content is corrupt. Have you managed to upload a file like that?
I managed to upload picture and video by modifying this snippet
gist.github.com/tanaikech/bd53b366...
Hope this help!
Did you change the mime types accordingly?
Try using the Oauth playground I linked. You will get the exact error message.
Hi Arnab,
Yes from what I can see I've followed your instructions - and the file does get uploaded - it just seems to be corrupt for some reason.
I've tried with an image and got the same result.
This is how it looks (I've tried making tweaks to what you provided but same result):
--foo_bar_baz
content-disposition: form-data; name="metadata"; filename="test.png"
content-type: application/json
{"name":"test.png","mimeType":"image/png"}
--foo_bar_baz
content-disposition: form-data; name="file"; filename="test.png"
content-type: image/png
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAlgAAAJYCAMAAACJuGjuAAACdlBMVEUBCQMcHBIZHA8dGw0XGhAVGA4UFwwBBgICDAMDDwQSFgsPGA4eHA8QFAgXGQ0bGAweHBMOEgcXFwscHBAFEQYlHhQLFQsMEgYlHBIoHA4VFwkc.. etc
--foo_bar_baz--
And I get my response like so:
{
"kind": "drive#file",
"id": "13Htxa1Lcfk_VyDDtZPj0yYBrs2_zwO4u",
"name": "test.png",
"mimeType": "image/png"
}
Let me know if you have any ideas.
Thanks,
Kevin
This is exactly what I needed. I'll try to implement this, let's see what challenges arise as I code this.
Sure. I found it really challenging and not much resources were available for these. Hope it helps.
Superb post Arnab!
Thanks a lot @gautham !! ☺️☺️