Upload file on server to google drive using javascript - javascript

I would like to allow users of my site to be able to upload files that they have created that are stored on my server to be uploaded to there google drive account.
I tried authenticated and passing this accesstoken to .net but couldnt get that flow to work.
Using existing access token for google drive request in .net
So now i need helping in doing this with just javascript. How can i download the file in the background and then pass it to the api?
I would like to avoid using the Save to Drive button if possible.
Here is my current code:
gapi.client.load('drive', 'v2', function() {
//How do i download a file and then pass it on.
var file =
insertFile(file);
});
/**
* Insert new file.
*
* #param {File} fileData File object to read data from.
* #param {Function} callback Function to call when the request is complete.
*/
function insertFile(fileData, callback) {
const boundary = '-------314159265358979323846';
const delimiter = "\r\n--" + boundary + "\r\n";
const close_delim = "\r\n--" + boundary + "--";
var reader = new FileReader();
reader.readAsBinaryString(fileData);
reader.onload = function(e) {
var contentType = fileData.type || 'application/octet-stream';
var metadata = {
'title': fileData.name,
'mimeType': contentType
};
var base64Data = btoa(reader.result);
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' +
base64Data +
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)
};
}
request.execute(callback);
}
}

There are many examples of downloading a file from GET request to binary string using XMLHttpRequest. For example, like this one. You can replace the part that reads file as a binary string reader.readAsBinaryString(fileData); to this code.

Related

How to update a zip file in Google Drive using Google Drive Javascript API

I am quite unexperienced with file compression and google drive api. In my project I am using zip.js and Google Drive Api. The zip.js functions well because I download directly the generated zip file and my device can decompress it. Eventhough I don't add any extra data to the content of the zip, the zip file size is changed (doubled), and is corrupted when it is updated.
My function to generate a zip file:
function createZip(data,fileName,callback){
var blob = new Blob([ data ], {
type : "text/xml"
});
zipBlob(fileName, blob, function(zippedBlob){
console.log(zippedBlob)
// saveAs(zippedBlob,"test.zip") //this test.zip is a valid file.
var reader = new FileReader();
reader.addEventListener("loadend", function() {
// reader.result contains the contents of blob as a typed array
console.log(reader.result);
callback(reader.result);
});
reader.readAsText(zippedBlob)
})
function zipBlob(filename, blob, callback) {
zip.createWriter(new zip.BlobWriter("application/zip"), function(zipWriter) {
zipWriter.add(filename, new zip.BlobReader(blob), function() {
zipWriter.close(callback);
});
}, function(error){
console.error(error)
});
}
}
My function to update the file:
this.updateFile = function(id, text, accessToken, callback) {
var boundary = '-------314159265358979323846',
delimiter = "\r\n--" + boundary + "\r\n",
close_delim = "\r\n--" + boundary + "--",
mimeType = 'application/zip';
var multipartRequestBody =
delimiter + 'Content-Type: application/json\r\n\r\n' +
delimiter + 'Content-Type:' + mimeType + '\r\n\r\n' +
text +
close_delim;
gapi.client.request({
'path': '/upload/drive/v3/files/'+id,
'method': 'PATCH',
'params': { 'uploadType': 'multipart' },
'headers': { 'Content-Type': 'multipart/mixed; boundary="' + boundary + '"', 'Authorization': 'Bearer ' + accessToken, },
'body': multipartRequestBody
}).execute(function(file) {
if (typeof callback === "function") callback(file);
}, function(error) {
console.error(error)
callback(error);
});
}
I wonder if the FileReader corrupts the content because the reader.result is different than the original content of the zip. I use this function to read the file content:
this.readFile = function(fileId, callback) {
var request = gapi.client.drive.files.get({
fileId: fileId,
alt: 'media',
Range : 'bytes=100-200'
})
request.then(function(response) {
console.log(response);
if (typeof callback === "function") callback(response.body);
}, function(error) {
console.error(error)
})
//return request;
}
The original content of the zip as text:
PKÊL PV-A2.xmlUXñçûZÂÌZùì½m,I÷]þC£?I#ÌNøK¼=F °KÃ(­«Ýgz=Crçß+²ne§y¸¥U6... (some thousands characters more)
File reader content as text:
PK�U�L PV-A2.xml�]�<��w�O1�+(���� �#�6`#+ �Ґ`��r��EÑ�������̊s"���0�..(some thousands characters more)
SOLUTION
Get base64 data from the zip file (remember to strip data:application/zip;base64, out):
// saveAs(zippedBlob,"test.zip") //this test.zip is a valid file.
var reader = new FileReader();
reader.addEventListener("loadend", function() {
// reader.result contains the contents of blob as a typed array
callback(reader.result.replace("data:application/zip;base64,",""));
});
reader.readAsDataURL(zippedBlob)
Add Content-Transfer-Encoding: base64 to multipartRequestBody and use the base64 content instead.
var multipartRequestBody =
delimiter + 'Content-Type: application/json\r\n\r\n' +
delimiter + 'Content-Type:' + mimeType + '\r\n' +
'Content-Transfer-Encoding: base64\r\n\r\n' +
base64Data +
close_delim;
If text is the zip file which was converted to base64, how about this modification? I think that your script is almost correct. As a modification, it gives the encode method of the file using Content-Transfer-Encoding.
From :
delimiter + 'Content-Type:' + mimeType + '\r\n\r\n' +
text +
To :
delimiter + 'Content-Type: ' + mimeType + '\r\n' + // Modified
'Content-Transfer-Encoding: base64\r\n\r\n' + // Added
text +
Note :
In my environment, I confirmed that the same error with your situation occurs using your script. And also I confirmed that when this modification is reflected to your script, the zip file is updated, and the file can be unzipped.
But if this was not the solution of your situation, I'm sorry.

Send post request on Google API using javascript on browser

I am getting 404 error on my $.ajax request in Google API.
I have these codes,
var asyncLoad = require('react-async-loader');
var CLIENT_ID = '<SOME_ID>';
var DISCOVERY_DOCS = ["https://www.googleapis.com/discovery/v1/apis/drive/v3/rest"];
var SCOPES = 'https://www.googleapis.com/auth/drive';
const mapScriptToProps = state => ({
gapi: {
globalPath: 'gapi',
url: 'https://apis.google.com/js/api.js'
}
});
#asyncLoad(mapScriptToProps)
...
I used async loader of react to get the Google API before the bundle.js.
Then I get the gapi in the properties. Here is my next codes for submitting a form.
submitForm(e){
e.preventDefault();
var data = this.refs.file.files[0];
var self = this;
var formData = new FormData();
formData.append('data', data);
$.ajax({
url: "https://www.googleapis.com/upload/drive/v3?uploadType=media&access_token="+encodeURIComponent(self.state.token),
type: "POST",
processData: false,
data: formData,
beforeSend: function (xhr) {
/* Authorization header */
xhr.setRequestHeader("Authorization", "Bearer " + self.state.token);
xhr.setRequestHeader('X-Upload-Content-Length', data.size);
xhr.setRequestHeader("Content-Type", "image/jpeg");
xhr.setRequestHeader('X-Upload-Content-Type', "image/jpeg");
},
success: function(data){
if(typeof data === "string") data = JSON.parse(data);
console.log(data);
if(data.success){
console.log("done");
}else {
console.log("error");
}
}
});
}
So here, I call submitForm function when the button upload is clicked. I have also file input with ref="file". This is run in client (browser) side. I got 404 error.
What I am trying to do here is to upload an image file to google drive. How can I do this right? Any solution for my problem?
The 404 error is caused because the url is missing the files part: https://www.googleapis.com/upload/drive/v3/files?uploadType=media. See Google Drive APIs > REST - Files: create.
I managed to upload an image that was read from <input type="file" accept="image/*"> with FileReader.readAsDataURL() method as follows:
var metadata = {
name: 'image.jpg',
mimeType: 'image/jpeg'
}
var user = gapi.auth2.getAuthInstance().currentUser.get();
var oauthToken = user.getAuthResponse(true).access_token;
var boundary = 'foo_bar_baz';
var data = '--' + boundary + '\n';
data += 'content-type: application/json; charset=UTF-8' + '\n\n';
data += JSON.stringify(metadata) + '\n';
data += '--' + boundary + '\n';
var dataURL = 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAgAAZABkAAD/7AARRHVja3kAAQAEAAAAZAAA/+EKhWh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8APD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZ...'
var dataURLparts = dataURL.split(',', 2);
var dataURLheaderParts = dataURLparts[0].split(':');
var dataURLheaderPayloadParts = dataURLheaderParts[1].split(';');
data += 'content-transfer-encoding: ' + dataURLheaderPayloadParts[1] + '\n';
data += 'content-type: ' + dataURLheaderPayloadParts[0] + '\n\n';
data += dataURLparts[1] + '\n';
data += '--' + boundary + '--';
$.ajax({
type: 'POST',
url: 'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart',
beforeSend: function (xhr) {
xhr.setRequestHeader("Authorization", "Bearer " + oauthToken);
},
contentType: 'multipart/related; boundary=' + boundary,
data: data,
processData: false
}).done(function(response) {
console.log(response);
});
After many hours of hair pulling and viewing the same answers. The only thing that worked for me was to change from the multipart request (that Google documents) to using FormData.
Credit to this answer.
While the data arrived to Drive (successful upload), it wasnt processed correctly so image or PDF were not viewable and downloading it showed it was saved in base64.
const metadata = JSON.stringify({
name: myFile.name,
mimeType: myFile.type,
});
const requestData = new FormData();
requestData.append("metadata", new Blob([metadata], {
type: "application/json"
}));
requestData.append("file", items[0].file);
const xhr = new XMLHttpRequest();
xhr.open("POST", "https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart");
const token = gapi.auth2.getAuthInstance().currentUser.get().getAuthResponse().access_token;
xhr.setRequestHeader("Authorization", `Bearer ${token}`);
xhr.send(requestData);

JavaScript Google Drive API V3 - Upload a file to a folder

I have been trying to upload a simple text file that holds a user's email and name to a folder that is in the root of my drive. I create the folder and then upload the file to that folder using its ID.
Everything works fine except for the config file not being added to the new folder. It just sits in the root along with the new folder.
I have looked over many of the questions related to this and have tried solutions from almost all of them. This question in particular (Insert a file to a particular folder using google-drive-api) seems like my exact problem. But as you can see, I implemented his change, and I still have the same problem.
Below is the function that is called to do this.
/**
* Creates the correct directory structure and a config file in the user's drive;
*/
function setupDrive(email, name) {
// TODO create CSR folder and config file inside
createFolder('CSR');
uploadConfig(email, name);
checkAuth();
}
This is the createFolder() function.
/**
* Creates a folder with the given name in the drive;
*/
function createFolder(dirName) {
var metadata = {
'name' : dirName,
'mimeType' : 'application/vnd.google-apps.folder'
};
var request = gapi.client.request({
'path': '/drive/v3/files',
'method': 'POST',
'body': JSON.stringify(metadata)});
request.execute();
}
This is the uploadConfig() function.
/**
* Uploads a config file to the CSR folder with the given email and name;
*/
function uploadConfig(email, name) {
var request = gapi.client.request({
'path': '/drive/v3/files',
'method': 'GET',
'q': 'name="CSR", trashed="false", mimeType="application/vnd.google-apps.folder"',
'fields': "nextPageToken, files(id, name)"
});
request.execute(function (results) {
var files = results.files;
var csrID = '';
if (files && files.length > 0) {
csrID = files[0].id;
}
uploadFile('config', email + '\n' + name, 'plain', csrID);
});
}
And finally, the uploadFile() function.
/**
* Uploads either a plain text file or a CSV file to the user's Google Drive in the CSR folder;
*/
function uploadFile(fileName, fileContent, mimeType, parentID) {
console.log(parentID); //check that a parentID is being passed in
var auth_token = gapi.auth.getToken().access_token;
var metaType = '';
var bodyType = '';
if (mimeType == 'csv') {
metaType = 'application/vnd.google-apps.spreadsheet';
bodyType = 'text/csv\r\n\r\n';
} else if (mimeType == 'plain') {
metaType = 'text/plain\r\n\r\n';
bodyType = 'text/plain\r\n\r\n';
}
const boundary = '-------314159265358979323846';
const delimiter = "\r\n--" + boundary + "\r\n";
const close_delim = "\r\n--" + boundary + "--";
var metadata = {
'name': fileName,
'mimeType': metaType,
'parents':[{'id': parentID}]
};
var multipartRequestBody =
delimiter + 'Content-Type: application/json\r\n\r\n' +
JSON.stringify(metadata) +
delimiter + 'Content-Type: ' + bodyType +
fileContent +
close_delim;
var request = gapi.client.request({
'path': '/upload/drive/v3/files',
'method': 'POST',
'params': {'uploadType': 'multipart'},
'headers': { 'Content-Type': 'multipart/form-data; boundary="' + boundary + '"', 'Authorization': 'Bearer ' + auth_token, },
'body': multipartRequestBody
})
request.execute(function (file) {
console.log("Wrote to file " + file.name + " id: " + file.id);
});
}
Any help is greatly appreciated, and I apologize for so much code!
EDIT:
I was able to get it to work when I did everything through REST V2. However, I'd still be interested in seeing a solution that would allow me to use V3.
I don't see any error with your code as compared with this related SO post. It stated that v2 process is very similar with v3. You already changed the version in the url to v3 and the parameter according to the v3. Also stated in this thread, be noted that in v3, there is no longer a parents collection. Instead, you get the parents property by doing a files.get with the child's ID. Ideally, you would use the fields parameter to restrict the response to just the parent(s). This SO answer might also help wherein the OP only used parents: ['parentID'] in the metadata.
This works fine with V3
function createFile(){
const boundary = '-------314159265358979323846';
const delimiter = "\r\n--" + boundary + "\r\n";
const close_delim = "\r\n--" + boundary + "--";
var fileContent = 'It works :)';
var metadata = {
'name': 'myFile',
'mimeType': 'text/plain\r\n\r\n'
};
var multipartRequestBody = delimiter + 'Content-Type: application/json\r\n\r\n' + JSON.stringify(metadata) + delimiter + 'Content-Type: ' + 'text/plain\r\n\r\n' + fileContent + close_delim;
gapi.client.request({
'path': '/upload/drive/v3/files',
'method': 'POST',
'params': {
'uploadType': 'multipart'
},
'headers': {
'Content-Type': 'multipart/related; boundary="' + boundary + '"'
},
'body': multipartRequestBody
}).then(function(response){
console.log(response);
});
}

Upload an image file using the request object

In the Firefox extension I am currently developing, I've been trying for days now to upload an image file to a server using the request module of the Firefox Add-on SDK.
I managed to upload a text file but I cannot upload any other kind of files.
My final goal is to upload a screenshot so I really need to be able to upload an image.
Here is my current code:
params= file.read("C:\\FullPath\h3nf5v2c.png", "b");
//multipart form data
boundary = "---------------------------132611019532525";
var snapShotUpload = Request({
url : "https://myurl.com",
headers : {
"Referer" : "https://myurl.com",
"Content-Type" : "multipart/form-data; boundary=" + boundary,
},
content : "--" + boundary + "\r\n" + "Content-Disposition: form-data; name='Upload_FileName'; filename='h3nf5v2c.png'\r\nContent-Type: application/octet-stream\r\n\r\n" + params + "\r\n--" + boundary + "--\r\n",
onComplete: function(response) {
console.log(response.text);
}
});
console.log("request built");
snapShotUpload.post();
The uploaded image is corrupted and I can't read it.
So my question is:
How do I post an image using the request module of the Firefox Add-on SDK?
Thank you Wladimir,
I didn't actually modify the Request module but simply used an XMLHttpRequest instead.
Here is the code I used if anybody is interested:
function sendImage(file){
fName = "h3nf5v2c.png";
// prepare the MIME POST data
var boundaryString = '---------------------------132611019532525';
var boundary = '--' + boundaryString;
var requestbody = boundary + '\r\n'
+ 'Content-Disposition: form-data; name="Upload_FileName"; filename="'
+ fName + '"' + '\r\n'
+ 'Content-Type: image/png' + '\r\n'
+ '\r\n'
+ file.read()
+ '\r\n'
+ boundary + '--\r\n';
// Send
var http_request = Components.classes["#mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(Components.interfaces.nsIXMLHttpRequest);
http_request.onreadystatechange = function() {
if (http_request.readyState == 4 && http_request.status == 200) {
console.log(http_request.responseText);
}
};
http_request.open('POST', 'http://myUrl.com', true);
http_request.setRequestHeader("Referer", "http://myUrl.com");
http_request.setRequestHeader("Content-type", "multipart/form-data; boundary=" + boundaryString);
//http_request.setRequestHeader("Connection", "close");
http_request.setRequestHeader("Content-length", requestbody.length);
http_request.sendAsBinary(requestbody);
}
The file parameter of the function is a file object from the file module.
The sendAsBinary function at the end is very important because I'm working client side only and I had to simulate an insert via a form.
If you have access to the server side, sending a base64 encoded version of the image and decoding it server-side is probably simpler.
Unfortunately, the answer is: you cannot. The request module is mainly meant to transmit url-encoded data so anything that isn't a string is assumed to be an object containing key-value pairs that need to be encoded. So sending a string is your only option if you need to send multipart content yet XMLHttpRequest will always encode strings as UTF-8 which will produce rather undesirable results when you are trying to send raw binary data (such as an image data).
It would be much easier to use a FormData object but even if you create one - the request module won't pass it to XMLHttpRequest. There is no simple solution short of modifying the request module (file packages/addon-kit/lib/request.js of the Add-on SDK). Find this line:
let data = stringify(content);
Changing it into the following code should work:
let {Ci} = require("chrome");
let data = content;
if (!(content instanceof Ci.nsIDOMFormData))
data = stringify(content);
This makes sure that FormData objects aren't changed.
Note that you might not have FormData and File defined in your main.js. If that's the case "stealing" them from any JavaScript module should work:
let {Cu} = require("chrome");
var {FormData, File} = Cu.import("resource://gre/modules/Services.jsm")
I think there is another way to post an image on your server. You can use the base64 encoding.
I never test it but I found this subject on the web which explains how to convert the image. I hope this can help you.
What can simplify your life is using FormData and XMLHttpRequests together as in this example.
function uploadFiles(url, files) {
var formData = new FormData();
for (var i = 0, file; file = files[i]; ++i) {
formData.append(file.name, file);
}
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.onload = function(e) { ... };
xhr.send(formData); // multipart/form-data
}
document.querySelector('input[type="file"]').addEventListener('change', function(e) {
uploadFiles('/server', this.files);
}, false);
source: http://www.html5rocks.com/en/tutorials/file/xhr2/

Update file content on google drive using javascript

Based on #Nvico great answer i was able to upload files to google drive, the problem is the code on the answer creates a new file each time, is there a way given an already created file id to update its content directly (using Files:update api) without creation of a new one ?
currently my solution is to use Files:delete api each time i want to update the file to remove the old one then create a new file using #Nvico code
Instead of using the drive.files.insert endpoint, you can use almost the same code to send an update request to the drive.files.update endpoint:
/**
* Update existing file.
*
* #param {String} fileId ID of the file to update.
* #param {Object} fileMetadata existing Drive file's metadata.
* #param {File} fileData File object to read data from.
* #param {Function} callback Callback function to call when the request is complete.
*/
function updateFile(fileId, fileMetadata, fileData, callback) {
const boundary = '-------314159265358979323846';
const delimiter = "\r\n--" + boundary + "\r\n";
const close_delim = "\r\n--" + boundary + "--";
var reader = new FileReader();
reader.readAsBinaryString(fileData);
reader.onload = function(e) {
var contentType = fileData.type || 'application/octect-stream';
// Updating the metadata is optional and you can instead use the value from drive.files.get.
var base64Data = btoa(reader.result);
var multipartRequestBody =
delimiter +
'Content-Type: application/json\r\n\r\n' +
JSON.stringify(fileMetadata) +
delimiter +
'Content-Type: ' + contentType + '\r\n' +
'Content-Transfer-Encoding: base64\r\n' +
'\r\n' +
base64Data +
close_delim;
var request = gapi.client.request({
'path': '/upload/drive/v2/files/' + fileId,
'method': 'PUT',
'params': {'uploadType': 'multipart', 'alt': 'json'},
'headers': {
'Content-Type': 'multipart/mixed; boundary="' + boundary + '"'
},
'body': multipartRequestBody});
if (!callback) {
callback = function(file) {
console.log(file)
};
}
request.execute(callback);
}
}
The main difference is in the request URL and method:
PUT /upload/drive/v2/files/<FILE_ID>

Categories