Uploading file to google drive via drive API messes up umlauts - javascript

I am uploading a file to Google Drive with the following code snippet using the Drive API, but when I look at the contents of the file in Google Drive, the umlauts are messed up.
chrome.identity.getAuthToken({ interactive: true }, token => {
var metadata = {
name: 'testname.json',
mimeType: 'application/json'
};
var fileContent = {
title: "Test Title",
notes: "Ää",
last_changed: "1641862146889",
url: "https://www.example.org",
poster_url: "https://www.example.org"
};
var file = new Blob([JSON.stringify(fileContent)], { type: 'application/json' });
var form = new FormData();
form.append('metadata', new Blob([JSON.stringify(metadata)], { type: 'application/json' }));
form.append('file', file);
for (var pair of form.entries()) {
console.log(pair[0], pair[1].text());
}
var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart');
xhr.setRequestHeader('Authorization', 'Bearer ' + token);
xhr.responseType = 'json';
xhr.onload = () => {
var fileId = xhr.response.id;
/* Do something with xhr.response */
console.log(xhr.response);
};
xhr.send(form);
});
Messed up umlauts(notes property):
{"title":"Test Title","notes":"Ää","last_changed":"1641862146889","url":"https://www.example.org","poster_url":"https://www.example.org"}
Does someone know how to prevent this from happening? After some troubleshooting, I also think it is possible that the problem has something to do with Blob or FormData.

Related

How to Upload txt file with Cypress for API Testing - XMLHTTPRequest?

I'm trying to test an endpoint which will upload a file and give 200 response status code in cypress. As per some research cy.request cannot be used to upload a file for multipart/form-data so we need to use XMLHttp to upload such files. I have created below file to test the api but it doesn't work. Can someone please help what's wrong with my code ? Thank you.
Added below code under support/commands.ts(I will require a header to pass token from auth endpoint)
// Performs an XMLHttpRequest instead of a cy.request (able to send data as FormData - multipart/form-data)
Cypress.Commands.add('multipartFormRequest', (method,URL, formData,headers, done) => {
const xhr = new XMLHttpRequest();
xhr.open(method, URL);
xhr.setRequestHeader("accept", "application/json");
xhr.setRequestHeader("Content-Type", "multipart/form-data");
if (headers) {
headers.forEach(function(header) {
xhr.setRequestHeader(header.name, header.value);
});
}
xhr.onload = function (){
done(xhr);
};
xhr.onerror = function (){
done(xhr);
};
xhr.send(formData);
})
Test file to call multipartFormRequest:
const fileName = 'test_file.txt';
const method = 'POST';
const URL = "https://fakeurl.com/upload-file";
const headers = api.headersWithAuth(`${authToken}`);
const fileType = "application/text";
cy.fixture(fileName, 'binary').then((res) => {
const blob = Cypress.Blob.binaryStringToBlob(res, fileType);
const formData = new FormData();
formData.append('file', blob, fileName);
cy.multipartFormRequest(method, URL, headers, formData, function (response) {
expect(response.status).to.equal(200);
})
})
I'm getting this error message:-
Now, I'm getting status code as 0.
describe("Upload image", () => {
it("upload first image", () => {
const fileName = "image.jpeg";
const method = "POST";
const url = "https://api-demo.com/1";
const fileType = "image/jpeg";
cy.fixture(fileName, "binary")
.then((txtBin) => Cypress.Blob.binaryStringToBlob(txtBin))
.then((blob) => {
const formData = new FormData();
formData.append("image_data", blob, fileName);
formData.append("image_format", "jpeg");
cy.form_request(method, url, formData, function (response) {
expect(response.status).to.eq(200)
}
);
})
});
});
Cypress.Commands.add('form_request', (method, url, formData, done) => {
const xhr = new XMLHttpRequest();
xhr.open(method, url);
xhr.setRequestHeader("device", "331231");
xhr.setRequestHeader("city", "bangalore");
xhr.onload = function () {
done(xhr);
};
xhr.onerror = function () {
done(xhr);
};
xhr.send(formData);
})
Use
const blob = Cypress.Blob.binaryStringToBlob(res, fileType);
and remove the .then().
See Cypress.Blob
History
Version 5.0.0
Changes:
Return type of arrayBufferToBlob, base64StringToBlob, binaryStringToBlob, and dataURLToBlob methods changed from Promise<Blob> to Blob

Uploading a Folder to GDrive and get the Folder ID to use to upload files

Thanks for reading my question. I am working on the google drive api and can upload files from a text blob to the google drive. However, I need to add the folder ID manually and for users to use this and not get an error since they are trying to upload it into my folder. How can I create or just get a folder ID - maybe even upload to the root GDrive directory ? Any tips would be helpful.
Thanks
// Global vars
const SCOPE = 'https://www.googleapis.com/auth/drive.file';
const gdResponse = document.querySelector('#response');
const login = document.querySelector('#login');
const authStatus = document.querySelector('#auth-status');
const textWrap = document.querySelector('.textWrapper');
const addFolder = document.querySelector('#createFolder');
const uploadBtn = document.querySelector('#uploadFile');
// Save Button and Function
uploadBtn.addEventListener('click', uploadFile);
window.addEventListener('load', () => {
console.log('Loading init when page loads');
// Load the API's client and auth2 modules.
// Call the initClient function after the modules load.
gapi.load('client:auth2', initClient);
});
function initClient() {
const discoveryUrl =
'https://www.googleapis.com/discovery/v1/apis/drive/v3/rest';
// Initialize the gapi.client object, which app uses to make API requests.
// Get API key and client ID from API Console.
// 'scope' field specifies space-delimited list of access scopes.
gapi.client
.init({
apiKey: 'My-API-Key',
clientId:
'My-Client-ID',
discoveryDocs: [discoveryUrl],
scope: SCOPE,
})
.then(function () {
GoogleAuth = gapi.auth2.getAuthInstance();
// Listen for sign-in state changes.
GoogleAuth.isSignedIn.listen(updateSigninStatus);
// Actual upload of the file to GDrive
function uploadFile() {
let accessToken = gapi.auth.getToken().access_token; // Google Drive API Access Token
console.log('Upload Blob - Access Token: ' + accessToken);
let fileContent = document.querySelector('#content').value; // As a sample, upload a text file.
console.log('File Should Contain : ' + fileContent);
let file = new Blob([fileContent], { type: 'application/pdf' });
let metadata = {
name: 'Background Sync ' + date, // Filename
mimeType: 'text/plain', // mimeType at Google Drive
// For Testing Purpose you can change this File ID to a folder in your Google Drive
parents: ['Manually-entered-Folder-ID'], // Folder ID in Google Drive
// I'd like to have this automatically filled with a users folder ID
};
let form = new FormData();
form.append(
'metadata',
new Blob([JSON.stringify(metadata)], { type: 'application/json' })
);
form.append('file', file);
let xhr = new XMLHttpRequest();
xhr.open(
'post',
'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&fields=id'
);
xhr.setRequestHeader('Authorization', 'Bearer ' + accessToken);
xhr.responseType = 'json';
xhr.onload = () => {
console.log(
'Upload Successful to GDrive: File ID Returned - ' + xhr.response.id
); // Retrieve uploaded file ID.
gdResponse.innerHTML =
'Uploaded File. File Response ID : ' + xhr.response.id;
};
xhr.send(form);
}
I didn't include some of the unrelated stuff. I have something like this for the uploading of a folder and it's not working unsurprisingly.
function createFolder() {
var fileMetadata = {
name: 'WordQ-Backups',
mimeType: 'application/vnd.google-apps.folder',
};
drive.files.create(
{
resource: fileMetadata,
fields: 'id',
},
function (err, file) {
if (err) {
// Handle error
console.error(err);
} else {
console.log('Folder Id: ', file.id);
}
}
);
}
I believe your goal as follows.
You want to upload a file to the root folder or the created new folder.
For this, how about the following modification patterns?
Pattern 1:
When you want to put the file to the root folder, please try the following modification.
From:
parents: ['Manually-entered-Folder-ID'],
To:
parents: ['root'],
or, please remove parents: ['Manually-entered-Folder-ID'],. By this, the file is created to the root folder.
Pattern 2:
When you want to create new folder and put the file to the created folder, please try the following modification. In your script, unfortunately, I'm not sure about drive in createFolder() from your question. So I cannot understand about the issue of your createFolder(). So in this pattern, the folder is created with XMLHttpRequest.
Modified script:
function createFile(accessToken, folderId) {
console.log('File Should Contain : ' + fileContent);
let fileContent = document.querySelector('#content').value;
console.log('File Should Contain : ' + fileContent);
let file = new Blob([fileContent], { type: 'application/pdf' });
let metadata = {
name: 'Background Sync ' + date,
mimeType: 'text/plain',
parents: [folderId], // <--- Modified
};
let form = new FormData();
form.append(
'metadata',
new Blob([JSON.stringify(metadata)], { type: 'application/json' })
);
form.append('file', file);
let xhr = new XMLHttpRequest();
xhr.open(
'post',
'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&fields=id'
);
xhr.setRequestHeader('Authorization', 'Bearer ' + accessToken);
xhr.responseType = 'json';
xhr.onload = () => {
console.log(
'Upload Successful to GDrive: File ID Returned - ' + xhr.response.id
);
gdResponse.innerHTML =
'Uploaded File. File Response ID : ' + xhr.response.id;
};
xhr.send(form);
}
function createFolder(accessToken) {
const folderName = "sample"; // <--- Please set the folder name.
let metadata = {
name: folderName,
mimeType: 'application/vnd.google-apps.folder'
};
let xhr = new XMLHttpRequest();
xhr.open('post', 'https://www.googleapis.com/drive/v3/files?fields=id');
xhr.setRequestHeader('Authorization', 'Bearer ' + accessToken);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.responseType = 'json';
xhr.onload = () => {
const folderId = xhr.response.id;
console.log(folderId);
createFile(accessToken, folderId);
};
xhr.send(JSON.stringify(metadata));
}
// At first, this function is run.
function uploadFile() {
let accessToken = gapi.auth.getToken().access_token;
createFolder(accessToken);
}
Reference:
Files: create

sending blob from expo to express server

I am getting the blob of an image from a URI to send it to my server to store it. I fetch the image using a get request and everything is correct, however, the post request has something wrong. The blob which is received by the server always have the size 0. I am sure that i get the blob of the image correct since i could display it in the frontend after converting the blob to base64.
Here is the code.
const url = "https://lh3.googleusercontent.com/a-/AOh14Gj1THuWiRu7Vpn85YETJN-aMui7NE8bpnNWOzdi"
var x, base64data;
const scope = this
var formData = new FormData();
var request = new XMLHttpRequest();
request.responseType = "blob";
request.onload = function () {
const blob = request.response
formData.append("file", blob.data);
console.log(blob.data)
//Converting the image to base64 so i could display it,
//and make sure that the blob received is not corrupted
const fileReaderInstance = new FileReader();
fileReaderInstance.readAsDataURL(blob);
fileReaderInstance.onload = () => {
base64data = fileReaderInstance.result;
scope.setState({base64data: base64data})
}
//Sending the blob to the server
x = new XMLHttpRequest();
x.open("POST",`${link}images/upload/${data.id}`,true);
x.setRequestHeader("Content-type", "image/jpeg");
x.setRequestHeader("Content-Length", formData.length);
x.send(formData);
}
request.open("GET", url);
request.send();
The console.log(blob.data) shows
Object {
"blobId": "69B8ACFE-5E31-4F16-84D8-002B17399F7E",
"name": "unnamed.jpg",
"offset": 0,
"size": 74054,
"type": "image/jpeg",
}
Ther Server-side code.
router.post('/upload/:id', upload.single('file'), async (req, res) => {
console.log(req.file)
const imgId = req.file.id
const id = req.params.id;
if (!id) return res.send({ error: "Missing playerID" });
if (!idValidator.isMongoId(id))
return res.send({ error: "Invalid Id" })
const player = await Player.findById(id)
if (!player)
return res.send({ error: "Player does not exists" })
const oldImg = player.photo;
if(oldImg && oldImg.toString() !== "5d3ae42c3f2b2c379c361652"){
await Images.findByIdAndDelete(oldImg)
}
player.photo = imgId;
const updatedPlayer = await Player.findByIdAndUpdate(id, player,{new:true})
return res.send({player: updatedPlayer,msg:"Photo Updated!"})
});
The console.log(req.file) shows the following.
{ fieldname: 'file',
originalname: 'unnamed.jpg',
encoding: '7bit',
mimetype: 'image/jpeg',
id: 5e780359fbea1452b8409a3e,
filename: '9cdeec500a17eccbb302d8571058da59.jpg',
metadata: null,
bucketName: 'images',
chunkSize: 261120,
size: 0,
md5: 'd41d8cd98f00b204e9800998ecf8427e',
uploadDate: 2020-03-23T00:31:21.640Z,
contentType: 'image/jpeg' }
After a long search i found this and it worked finalllllyyy!!!!
postPicture(data) {
const apiUrl = `${link}images/upload/${data.id}`;
const uri = "https://lh3.googleusercontent.com/a-/AOh14Gj1THuWiRu7Vpn85YETJN-aMui7NE8bpnNWOzdi";
const fileType = "jpeg";
const formData = new FormData();
formData.append('file', {
uri,
name: `photo.${fileType}`,
type: `image/${fileType}`,
});
const options = {
method: 'POST',
body: formData,
headers: {
Accept: 'application/json',
'Content-Type': 'multipart/form-data',
},
};
return fetch(apiUrl, options);
}

404 on large file uploads using google drive api

I am trying to upload files to Google Drive via their api. When the file is over roughly 60MB it seems to be giving me a 404 or something went wrong screen.
I've tried several methods and noticed in 2015 alot of people were having similar issues because Google did not support resumable uploads from browser. I don't know if this has changed or not, but it seems to work fine for files under 60MB.
//const file = new File(['Hello, world!'], 'hello world.txt', { type: 'text/plain;charset=utf-8' });
const contentType = file.type || 'application/octet-stream';
const user = gapi.auth2.getAuthInstance().currentUser.get();
const oauthToken = user.getAuthResponse().access_token;
const initResumable = new XMLHttpRequest();
initResumable.open('POST', 'https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable', true);
initResumable.setRequestHeader('Authorization', 'Bearer ' + oauthToken);
initResumable.setRequestHeader('Content-Type', 'application/json');
initResumable.setRequestHeader('X-Upload-Content-Length', file.size);
initResumable.setRequestHeader('X-Upload-Content-Type', contentType);
initResumable.onreadystatechange = function () {
if (initResumable.readyState === XMLHttpRequest.DONE && initResumable.status === 200) {
const locationUrl = initResumable.getResponseHeader('Location');
const reader = new FileReader();
reader.onload = (e) => {
const uploadResumable = new XMLHttpRequest();
uploadResumable.open('PUT', locationUrl, true);
uploadResumable.setRequestHeader('Content-Type', contentType);
uploadResumable.setRequestHeader('X-Upload-Content-Type', contentType);
uploadResumable.onreadystatechange = function () {
if (uploadResumable.readyState === XMLHttpRequest.DONE && uploadResumable.status === 200) {
console.log(uploadResumable.response);
}
};
uploadResumable.send(reader.result);
};
reader.readAsArrayBuffer(file);
}
};
// You need to stringify the request body containing any file metadata
initResumable.send(JSON.stringify({
'name': file.name,
'mimeType': contentType,
'Content-Type': contentType,
'Content-Length': file.size
}));

Google Drive API and file uploads from the browser

I'm trying to upload a file with the Google Drive api, and I have the metadata correct, and I want to ensure that the actual file contents make it there. I have a simple page setup that looks like this:
<div id="upload">
<h6>File Upload Operations</h6>
<input type="file" placeholder='file' name='fileToUpload'>
<button id='uploadFile'>Upload File</button>
</div>
and I have a the javascript setup where the user is prompted to sign in first, and then they can upload a file. Here's the code: (currently only uploads the file metadata....)
let uploadButton = document.getElementById('uploadFile');
uploadButton.onclick = uploadFile;
const uploadFile = () => {
let ftu = document.getElementsByName('fileToUpload')[0].files[0];
console.dir(ftu);
gapi.client.drive.files.create({
'content-type': 'application/json;charset=utf-8',
uploadType: 'multipart',
name: ftu.name,
mimeType: ftu.type,
fields: 'id, name, kind'
}).then(response => {
console.dir(response);
console.log(`File: ${ftu.name} with MimeType of: ${ftu.type}`);
//Need code to upload the file contents......
});
};
First, I'm more familiar with the back end, so getting the file in bits from the <input type='file'> tag is a bit nebulous for me. On the bright side, the metadata is there. How can I get the file contents up to the api?
So According to some resources I've found in my three day search to get this going, the file simply cannot be uploaded via the gapi client. It must be uploaded through a true REST HTTP call. So let's use fetch!
const uploadFile = () => {
//initialize file data from the dom
let ftu = document.getElementsByName('fileToUpload')[0].files[0];
let file = new Blob([ftu]);
//this is to ensure the file is in a format that can be understood by the API
gapi.client.drive.files.create({
'content-type': 'application/json',
uploadType: 'multipart',
name: ftu.name,
mimeType: ftu.type,
fields: 'id, name, kind, size'
}).then(apiResponse => {
fetch(`https://www.googleapis.com/upload/drive/v3/files/${response.result.id}`, {
method: 'PATCH',
headers: new Headers({
'Authorization': `Bearer ${gapi.client.getToken().access_token}`,
'Content-Type': ftu.type
}),
body: file
}).then(res => console.log(res));
}
The Authorization Header is assigned from calling the gapi.client.getToken().access_token function, and basically this takes the empty object from the response on the gapi call and calls the fetch api to upload the actual bits of the file!
In your situation, when you upload a file using gapi.client.drive.files.create(), the empty file which has the uploaded metadata is created. If my understanding is correct, how about this workaround? I have experienced the same situation with you. At that time, I used this workaround.
Modification points:
Retrieve access token using gapi.
File is uploaded using XMLHttpRequest.
Modified script:
Please modify the script in uploadFile().
let ftu = document.getElementsByName('fileToUpload')[0].files[0];
var metadata = {
'name': ftu.name,
'mimeType': ftu.type,
};
var accessToken = gapi.auth.getToken().access_token; // Here gapi is used for retrieving the access token.
var form = new FormData();
form.append('metadata', new Blob([JSON.stringify(metadata)], {type: 'application/json'}));
form.append('file', ftu);
var xhr = new XMLHttpRequest();
xhr.open('post', 'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&fields=id,name,kind');
xhr.setRequestHeader('Authorization', 'Bearer ' + accessToken);
xhr.responseType = 'json';
xhr.onload = () => {
console.log(xhr.response);
};
xhr.send(form);
Note:
In this modified script, it supposes that Drive API is enabled at API console and the access token can be used for uploading file.
About fields, you are using id,name,kind. So this sample also uses them.
Reference:
gapi
If I misunderstand your question or this workaround was not useful for your situation, I'm sorry.
Edit:
When you want to use fetch, how about this sample script?
let ftu = document.getElementsByName('fileToUpload')[0].files[0];
var metadata = {
'name': ftu.name,
'mimeType': ftu.type,
};
var accessToken = gapi.auth.getToken().access_token; // Here gapi is used for retrieving the access token.
var form = new FormData();
form.append('metadata', new Blob([JSON.stringify(metadata)], {type: 'application/json'}));
form.append('file', ftu);
fetch('https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&fields=id,name,kind', {
method: 'POST',
headers: new Headers({'Authorization': 'Bearer ' + accessToken}),
body: form
}).then((res) => {
return res.json();
}).then(function(val) {
console.log(val);
});
With https://www.npmjs.com/package/#types/gapi.client.drive
const makeUploadUrl = (fileId: string, params: Record<string, boolean>) => {
const uploadUrl = new URL(
`https://www.googleapis.com/upload/drive/v3/files/${fileId}`
)
Object.entries({
...params,
uploadType: 'media',
}).map(([key, value]) => uploadUrl.searchParams.append(key, `${value}`))
return uploadUrl
}
const uploadDriveFile = async ({ file }: { file: File }) => {
const params = {
enforceSingleParent: true,
supportsAllDrives: true,
}
// create file handle
const { result } = await gapi.client.drive.files.create(params, {
// CAN'T have the upload type here!
name: file.name,
mimeType: file.type,
// any resource params you need...
driveId: process.env.DRIVE_ID,
parents: [process.env.FOLDER_ID],
})
// post the file data
await fetch(makeUploadUrl(result.id!, params), {
method: 'PATCH',
headers: new Headers({
Authorization: `Bearer ${gapi.client.getToken().access_token}`,
'Content-Type': file.type,
}),
body: file,
})
return result
})
}

Categories