I am creating a part of a website where the user can design a certain picture. When they are done I want the picture to be sent to a company Google Drive. I do not have access to the backend of that website, therefore I need to make all this work just in JavaScript.
I set up the API key and OAuth in Google Developer Console. However, I can't find a good solution to make the authentication work. The gapi.auth.getToken() always returns 404. My code looks like this:
function initClient() {
gapi.client
.init({
apiKey: API_KEY,
clientId: CLIENT_ID,
scope: "https://www.googleapis.com/auth/drive",
discoveryDocs: [
"'https://www.googleapis.com/discovery/v1/apis/drive/v3/rest'",
],
})
.then(function () {
GoogleAuth = gapi.auth2.getAuthInstance();
GoogleAuth.isSignedIn.listen(updateSigninStatus);
});
}
function sendToDrive() {
gapi.load("client:auth2", initClient);
let file = canvas.toDataURL("image/png"); // file
var metadata = {
name: file.name,
mimeType: file.type,
parents: [FOLDER_ID],
};
var form = new FormData();
form.append(
"metadata",
new Blob([JSON.stringify(metadata)], { type: "application/json" })
);
form.append("file", file);
fetch(
"https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart",
{
method: "POST",
headers: new Headers({
Authorization: "Bearer " + gapi.auth.getToken().access_token, // Returns 404
}),
body: form,
}
)
.then((res) => {
return res.json();
})
.then(function (val) {
console.log(val);
});
}
Not sure where to look now - the OAuth and API Key configuration seems right, the code returns 404 with no explanation ("gapi.client.error: Pa"), and I couldn't find this kind of use case in the docs (backend side is always used there).
Thanks in advance for any help!
Related
I'm trying to get the contents of a file located a shared drive in which I am a content manager. The scope I'm using is https://www.googleapis.com/auth/drive.file.
I get 404 errors when I try to access files that are created with my app (which I know is required for the drive.file scope), but by someone else. Everything works as it should when I try to access files created by me.
Here is my code:
function getJsonContent(fileId, accessToken) {
let url = "https://www.googleapis.com/drive/v3/files/" + fileId + "?alt=media&supportsAllDrives=true";
console.log("Sending JSON read request.");
return fetch(url, {
'headers': {'Authorization': "Bearer " + accessToken}
})
.then(response => {
if (response.ok) {
console.log("JSON read successful.");
return response.json();
}
else {
throw "JSON read unsuccessful.";
}
});
}
Here is my authorization code:
function handleAuth(onAccept, onDenial) {
let onSignInUpdate = function(isSignedIn) {
if (isSignedIn) {
onAccept();
} else {
onDenial();
}
}
gapi.load('client:auth2', function() {
gapi.client.init({
apiKey: API_KEY,
clientId: CLIENT_ID,
discoveryDocs: DISCOVERY_DOCS,
scope: SCOPES
})
.then(function() {
gapi.auth2.getAuthInstance().isSignedIn.listen(onSignInUpdate);
onSignInUpdate(gapi.auth2.getAuthInstance().isSignedIn.get()); //Initial update
})
.catch(err => {
console.error(err);
});
});
}
//In another module, specific to a webpage:
function onAccept() {
accessToken = gapi.auth.getToken().access_token;
getJsonContent(<ACTUALFILEID>, accessToken)
.catch(err => {
console.error(err);
});
}
function onDenial() {
console.log("Permission rejected; redirecting to index.html.");
location.replace("index.html");
}
window.onload = function() {
handleAuth(onAccept, onDenial);
};
This is the file creation code:
function makeJsonFile(parentId, fileName, accessToken) {
let url = "https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&fields=id&keepRevisionForever=true&supportsAllDrives=true";
console.log("Sending JSON creation request.");
let file = new Blob([JSON.stringify([])], {'type': "application/json"}); //The object is an empty array initially
let metadata = {
'name': fileName,
'mimeType': "application/json",
'parents': [parentId],
}
let form = new FormData();
form.append("metadata", new Blob([JSON.stringify(metadata)], {type: "application/json"}));
form.append("file", file);
return fetch(url, { //Returns a promise that resolves with the id of the created file
'method': "POST",
'headers': {
'Authorization': "Bearer " + accessToken
},
'body': form
})
.then(response => {
if (response.ok) {
console.log("JSON created.")
return response.json();
}
else {
throw "JSON creation unsuccessful.";
}
})
.then(val => val.id);
}
Issue:
https://www.googleapis.com/auth/drive.file only gives you access to files that YOU have opened or created with the corresponding app. See this:
View and manage Google Drive files and folders that you have opened or created with this app.
In this case, it is irrelevant that the file is created on a shared drive. I'm thinking you will have to use a wider scope like https://www.googleapis.com/auth/drive, unfortunately.
Feature request:
Also, considering your situation, I think you might be interested in this Feature Request, regarding the possibility to restrict access to a specific folder. I think that could be very useful for you if it got implemented. I'd suggest you to star that issue, both to keep track of its development and to help prioritize its implemenation:
Drive Restrict access to folder when authorizing applications
Reference:
OAuth 2.0 Scopes for Google APIs > Drive API, v3
Authorizing requests with OAuth 2.0
I am trying to create a folder for a user, and I have been unsuccessful with api call attempts. My code is able to receive the correct access token, so I believe the be bug would be in createFolderTestFunction below.
async function redirectToDashboard() {
console.log("redirect to dashboard");
// var response = await requestTokenSilent();
var response;
if (!response || !response.status == 200) {
response = await requestTokenPopup();
}
if (response.accessToken) {
console.log(response);
createFolderTest(response.accessToken);
// location.href = hostname;
} else {
console.log("Unable to acquire token");
}
}
function createFolderTest(accessToken) {
var options = {
method: "POST",
headers: {
Authorization: accessToken,
"Content-Type": "application/json"
},
mode: "cors",
body: JSON.stringify({
displayName: "#COOLMONDAY"
})
};
var graphEndpoint = "https://outlook.office.com/api/v2.0/me/Inbox/";
fetch(graphEndpoint, options)
.then(resp => {
console.log(resp);
})
.catch(err => {
console.log(err);
});
}
A recommendation would be to get this working in Graph Explorer first. As this eliminates any issues with language being used and access token permissions.
https://developer.microsoft.com/en-us/graph/graph-explorer/preview
The Microsoft Graph endpoint is actually https://graph.microsoft.com/ , you can use the outlook url but moving forward Graph is where we invest in documentation, sdks and tooling.
As per the documentation https://learn.microsoft.com/en-us/graph/api/user-post-mailfolders?view=graph-rest-1.0&tabs=http
You should be using, you're missing 'mailfolders'
POST /me/mailFolders
You could also use our JavaScript SDK which makes mistakes like these a little easier with intellisense and strongly typed objects.
const options = {
authProvider,
};
const client = Client.init(options);
const mailFolder = {
displayName: "displayName-value"
};
let res = await client.api('/me/mailFolders')
.post(mailFolder);
https://learn.microsoft.com/en-us/graph/api/user-post-mailfolders?view=graph-rest-1.0&tabs=javascript
I am working a react app, I am using WordPress API and all is working well, but need to upload a image via wp-json/wp/v2/media
But I have no idea how to do it, I don't know which headers and data use.
The code is:
uploadImage(masterFile) {
const file = masterFile[0];
const { mediaUrl } = this.state;
var self = this;
let host = this.props.host + mediaUrl;
this.setState({
showOverlay: true,
overlayMsg: "Wait Upload the image..."
});
let user = cookie.load("user");
const myHeaders = {
Authorization: "Bearer " + user.token,
"Content-Disposition": 'attachment; filename=/"' + file.name + '/"',
"Content-Type": "image/jpeg"
};
let data = {
};
axios
.post(host, data, { headers: myHeaders })
.then(function(response) {
})
.catch(function(error) {
})
.then(function() {});
}
You have to install a Wordpress plugin to generate a token allowing you to do JWT authentication to the Wordpress Rest API.
Here is a plugin doing that, and there's a documentation attached detailing the configuration you have to do on the wordpress server:
https://wordpress.org/plugins/jwt-authentication-for-wp-rest-api/
Here is an example of JS code using JWT authentication, to upload an image on Wordpress:
https://gist.github.com/RubenPauwels1/db52705ffa0ef36b7a39407a463d9e93
I have a method that creates and upload a sheet to Google Drive. When a user clicks on a button that says "create a report", the report is created and uploaded to the user google drive. It works great. However, the response from Google after the sheet is being saved to google drive doesn't contain the sheet id nor the sheet URL. Users are saying that when they click on the "create report" button, they want the google sheet to open in a new tab after it's being saved to their google drive. They don't want to go in their drive to open the file manually. I was thinking that the response being return after the sheet is being uploaded would've contained at least the sheet id or the URL to the resource that's being created in google drive. Does anyone have an idea on how to accomplish what I am driving to do? I am using Google API to upload the file to the users drive.
Here is the response that get sent after the sheet is uploaded
"https://www.googleapis.com/upload/drive/v3/files?
uploadType=multipart&fields=id%2Cname%2Ckind", redirected: false,
status: 200, ok: true, …}
body: ReadableStream
bodyUsed: false
headers: Headers {}
ok: true
redirected: false
status: 200
statusText: ""
type: "cors"
url: "https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&fields=id%2Cname%2Ckind"
//Here is the code:
let metadata = {
name: excelFileName,
mimeType: "application/vnd.google-apps.spreadsheet",
};
let form = new FormData();
form.append('metadata', new Blob([JSON.stringify(metadata)], { type: 'application/json' }));
form.append('file', excelFile);
let url = new URL('https://www.googleapis.com/upload/drive/v3/files');
url.search = new URLSearchParams({ uploadType: 'multipart', fields: 'id,name,kind' });
try {
let res = await fetch(url, {
method: 'POST',
headers: new Headers({ 'Authorization': 'Bearer ' + gapi.auth.getToken().access_token }),
body: form
});
if (res.ok) {
console.log(gapi)
console.log(res)
alert(`"${excelFileName}" has successfully saved to your google drive!`);
} else {
console.error(`Encounter a problem saving excel file, ${excelFileName}, to google drive`);
}
} catch (ex) {
console.error(`Oops excel file for "${excelFileName}" wasn't saved. ${error}`)
}
When you uploaded a file, you want to retrieve the file ID and URLs of the uploaded file.
You want to achieve by modifying your current script.
If my understanding is correct, how about this modification?
Modification points:
In order to return the file ID and URLs, in this case, these fields are used.
exportLinks,id,kind,name,webContentLink,webViewLink
In order to retrieve the returned values from res, it uses res.json().
When above modification points are reflected to your script, it becomes as follows.
Modified script:
Please modify 2 parts of your script as follows.
From:
url.search = new URLSearchParams({ uploadType: 'multipart', fields: 'id,name,kind' });
To:
url.search = new URLSearchParams({ uploadType: 'multipart', fields: 'exportLinks,id,kind,name,webContentLink,webViewLink' });
And
From:
if (res.ok) {
console.log(gapi)
console.log(res)
To:
if (res.ok) {
res.json().then((value) => {
console.log(value)
});
References:
Files
Files: create
If I misunderstood your question and this was not the direction you want, I apologize.
You need to do 2 changes to obtain the URL from the file:
1) You have to add the webViewLink attribute [1] between your fields in the search parameters:
url.search = new URLSearchParams({ uploadType: 'multipart', fields: 'id,name,kind,webViewLink' });
This attribute is the link to open the file in the web.
2) The response body you get is a ReadableStream, you need to convert it to a json object that you can manipulate. This can be done using the json() function [2] to the response you’re getting, which will return the response body parsed to a Json object with the attributes: id, webViewLink, etc.
I tweaked and tested your code and worked as expected, showing the URL for the newly created file in the alert message:
let metadata = {
name: excelFileName,
mimeType: "application/vnd.google-apps.spreadsheet",
};
let form = new FormData();
form.append('metadata', new Blob([JSON.stringify(metadata)], { type: 'application/json' }));
form.append('file', excelFile);
let url = new URL('https://www.googleapis.com/upload/drive/v3/files');
url.search = new URLSearchParams({ uploadType: 'multipart', fields: 'id,name,kind,webViewLink' });
try {
const res = await fetch(url, {
method: 'POST',
headers: new Headers({ 'Authorization': 'Bearer ' + gapi.auth.getToken().access_token}),
body: form
});
//Convert response to json
const resJson = await res.json();
if (res.ok) {
console.log(gapi);
console.log(res);
console.log(resJson);
alert(`"${excelFileName}" has successfully saved to your google drive!: ` + resJson.webViewLink);
} else {
console.error(`Encounter a problem saving excel file, ${excelFileName}, to google drive:` + res.Json);
}
} catch (ex) {
console.error(`Oops excel file for "${excelFileName}" wasn't saved. ${ex}`)
}
All the code above inside a sync function of course, to be able to use the await statement.
[1] https://developers.google.com/drive/api/v3/reference/files#resource
[2] https://developer.mozilla.org/en-US/docs/Web/API/Body/json
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
})
}