I am trying to update a file in Google Drive using Javascript, but the acess_token returns null. I based my file in this question Google Drive API V3 (javascript) update file contents.
const update = () => {
gapi.load('client:auth2', () =>{
gapi.client.init({
apiKey: 'MY API KEY',
clientId: '**************.apps.googleusercontent.com',
discoveryDocs: ["https://www.googleapis.com/discovery/v1/apis/drive/v3/rest"],
scope: 'https://www.googleapis.com/auth/drive',
}).then(v => {
var content = 'Hello World';
var contentBlob = new Blob([content], {
'type': 'text/plain'
});
updateFileContent('File id', contentBlob, function(response) {
console.log(response);
});
})
})
const updateFileContent = (fileId, contentBlob, callback) => {
var xhr = new XMLHttpRequest();
xhr.responseType = 'json';
xhr.onreadystatechange = function() {
if (xhr.readyState != XMLHttpRequest.DONE) {
return;
}
callback(xhr.response);
};
xhr.open('PATCH', 'https://www.googleapis.com/upload/drive/v3/files/' + fileId + '?uploadType=media');
xhr.setRequestHeader('Authorization', 'Bearer ' + gapi.auth.getToken().access_token);
xhr.send(contentBlob);
}
Someone know what Im doing wrong?
Related
With manifest v2, I used URL.createObjectURL in order to return file from background to content. How do this with manifest v3 and Service Worker? I know about XMLHttpRequest to fetch. But what analog URL.createObjectURL?
// background.js
function onContentMessage(message, sender, sendResponse) {
if (message.action == 'requestFileGET') {
requestFileGET(message.url, (url) => sendResponse(url));
}
return true;
}
function requestFileGET(url, callback) {
let request = new XMLHttpRequest();
request.responseType = 'blob';
request.onreadystatechange = function () {
if (request.readyState == 4) {
let url = URL.createObjectURL(request.response);
callback(url);
}
};
request.open('GET', url);
request.send();
}
// content.js
backgroundFileGET(urlCover, (cover) => {
let url = `${HANDLER_UPLOADPIC}?kind=${kind}&sign=${sign}`;
let formData = new FormData();
formData.append('file', cover);
requestFilePOST(url, formData, callback);
});
function backgroundFileGET(url, callback) {
backgroundRequest('requestFileGET', url, (backgroungdUrl) => {
requestFileGET(backgroungdUrl, (file) => callback(file));
});
}
function backgroundRequest(action, url, callback) {
chrome.runtime.sendMessage({ action: action, url: url }, (response) => callback(response));
}
My Solution Store the data with a ID and open a new tab with a page like download.html#ID
let videoId = '#' + new Date().valueOf();
self.DownloadedVideoData[videoId] = { data: data, filename:
filename, mimetype:mimetype, segment:segment, action:'download' };
chrome.tabs.create({ url: "video.html" + videoId, active: false });
Inside the Page register a function to onload and your downloading Code
chrome.runtime.sendMessage({
action: 'GetVideoDataByID',
VideoID: self.VideoID,
}, function (videoData) {
if (videoData.action == 'download') self.DownloadVideo(videoData);
);
self.DownloadVideo = function (request) {
let blob = new Blob(request.data, { type: request.mimetype }),
downloadId;
chrome.downloads.onChanged.addListener(function (dl) {
if (dl.id === downloadId && dl.state && dl.state.current ==
'complete') {
window.close();
}
});
chrome.downloads.download({
url: URL.createObjectURL(blob),
filename: request.filename
}, function (id) {
downloadId = id;
window.close();
});
}
Now you need only a Listener in your Backround Worker like that
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
switch (request.action) {
case 'GetVideoDataByID':
sendResponse(self.GetVideoDataByID(request.VideoID));
break;
default:
break;
}
}
I'm having trouble with XMLHttpRequest I think, when I navigate to localhost/dashboard/downloadfile?file-name=hw3.txt the file downloads locally but If I use the function checkDownload() to start an XMLHttpRequest the file does not get downloaded.
Here is my client code:
function checkDownload() {
const filename = "hw3.txt";
const xhr = new XMLHttpRequest();
xhr.responseType = "blob";
xhr.open('GET', `/dashboard/downloadfile?file-name=${ filename }`);
xhr.onreadystatechange = () => {
if(xhr.readyState === 4) {
if(xhr.status === 200) {
}
}
}
xhr.send();
}
And then here is my server code:
app.get('/dashboard/downloadfile', requiresLogin, (req, res) => {
const userid = req.user.id;
const filename = req.query['file-name'];
db.getFileKey([userid, filename], (keyres) => {
const params = {
Bucket: S3_BUCKET,
Key: keyres.rows[0].filekey,
};
res.setHeader('Content-disposition', `attachment; filename=${ filename }`);
res.setHeader('Content-type', `${ mime.getType(keyres.rows[0].filetype) }`);
s3.getObject(params, (awserr, awsres) => {
if(awserr) console.log(awserr);
else console.log(awsres);
}).createReadStream().pipe(res);
});
});
I got it working. Instead of trying to create a read stream from s3.getObject() I generated a signed url to the s3 object on the server and returned that to the client, then used an 'a' html element with element.href = signedRequest and used javascript to click that element. The new problem I'm running into is that I can't figure out a way to set the metadata for the s3 object when it is initially uploaded, I needed to manually change the metadata on an individual s3 object through the aws console so that it had the header Content-Disposition: attachment; filename=${ filename }.
changed client code:
function initDownload(filename) {
const xhr = new XMLHttpRequest();
xhr.open('GET', `/sign-s3-get-request?file-name=${ filename }`);
xhr.onreadystatechange = () => {
if(xhr.readyState === 4) {
if(xhr.status === 200) {
const response = JSON.parse(xhr.responseText);
startDownload(response.signedRequest, response.url);
}
}
}
xhr.send();
}
function startDownload(signedRequest, url) {
var link = document.createElement('a');
link.href = signedRequest;
link.setAttribute('download', 'download');
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
changed server code:
app.get('/sign-s3-get-request', requiresLogin, (req, res) => {
const userid = req.user.id;
const filename = req.query['file-name'];
db.getFileKey([userid, filename], (keyres) => {
const s3Params = {
Bucket: S3_BUCKET,
Key: keyres.rows[0].filekey,
Expires: 60,
};
s3.getSignedUrl('getObject', s3Params, (err, data) => {
if (err) {
// eslint-disable-next-line
console.log(err);
res.end();
}
const returnData = {
signedRequest: data,
url: `https://${S3_BUCKET}.s3.amazonaws.com/${ keyres.rows[0].filekey }`,
};
res.write(JSON.stringify(returnData));
res.end();
});
});
});
You are getting a blob back from the server, so in order to download you need to do something when xhr.status === 200.
Something like this:
...
if(xhr.status === 200) {
var fileUrl = URL.createObjectURL(xhr.responseText)
window.location.replace(fileUrl)
}
...
To download having the URL you could use the attribute download of a tag:
<a download="something.txt" href="https://google.com">Download Google</a>
If you use xhr.responseType = "blob", you have to do somethig like:
function checkDownload() {
const filename = "hw3.txt";
const xhr = new XMLHttpRequest();
xhr.responseType = "blob";
xhr.open('GET', 'https://jsonplaceholder.typicode.com/todos/1');
xhr.onreadystatechange = () => {
if(xhr.readyState === 4) {
if(xhr.status === 200) {
var reader = new FileReader();
reader.readAsArrayBuffer(xhr.response);
reader.addEventListener("loadend", function() {
var a = new Int8Array(reader.result);
console.log(JSON.stringify(a, null, ' '));
});
}
}
}
xhr.send();
}
checkDownload()
But that code doesn't download the file.
I want to fire an http API and process it's response but I am getting following error:
Exception while executing function: Functions.getAccessObject. mscorlib: ReferenceError: XMLHttpRequest is not defined
Here's my Azure serverless function Code:
module.exports = function (context, req) {
function getAccessObject(context, successCallback, failureCallback) {
var APPLICATION_ID = "zzz";
var APPLICATION_SECRET = "zzz";
var REFRESH_TOKEN = "zzz";
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.open("GET", "https://xyz");
xhr.setRequestHeader("applicationid", APPLICATION_ID);
xhr.setRequestHeader("applicationsecret", APPLICATION_SECRET);
xhr.setRequestHeader("refreshtoken", REFRESH_TOKEN);
xhr.setRequestHeader("cache-control", "no-cache");
xhr.onreadystatechange = function() {
if (request.readyState === 4) {
if (request.status === 200) {
successCallback(context, request.responseText);
} else {
failureCallback(context, request.responseText);
}
}
};
request.send(null);
}
getAccessObject(context, registerForWebhookFunc, failureCallbackFunc);
}
I believe XHR is a browser API and not native to node.js. You can use the built-in http functionality of node.js (see example below) or alternatively other popular packages are available such as Axios that can do http requests. I believe there may even be one for XHR if you are set on using that.
const https = require('https');
https.get('http://myapi', (resp) => {
let data = '';
resp.on('data', (chunk) => {
data += chunk;
});
resp.on('end', () => {
console.log(JSON.parse(data));
});
}).on("error", (err) => {
console.log("Error: " + err.message);
});
}
I'm facing an issue with promises..and was banging my head around from almost an hour now...
this.getDocument(documentId)
.then(function (response) {
console.log('in first then')
return 'from first then';
}).then(function (response) {
console.log(response)
});
Ideally the above code should give following output
in first then
from first then
But the actual output is as follows
in first then
undefined
Now the problem here is why the second then is getting response as undefined when I'm returning something from the first then.
[EDIT 1] Adding getDoument code
function getDocument(documentID) {
var config = {
url: '<URL>' + documentID,
headers: {enter code here
'Content-Type': 'application/json'
}
};
return HttpRequest.get(config);
}
var HttpRequest = (function () {
function HttpRequest() {
}
HttpRequest.get = function (config) {
var deferred = Q.defer();
var http = new XMLHttpRequest();
var url = config.url;
var authToken = window.localStorage.getItem('token');
http.open("GET", url, true);
Object.keys(config.headers).map(function (k) {
http.setRequestHeader(k, config.headers[k]);
});
http.setRequestHeader("Authorization", 'Bearer ' + authToken);
http.onreadystatechange = function () {
if (http.readyState !== 4)
return;
var response = http.responseText;
if (http.status != 200)
return deferred.reject(response);
deferred.resolve(response);
// if (/^[^2]\d\d$/.exec(http.status)) return deferred.reject(http.status);
};
http.send();
return deferred.promise;
};
HttpRequest.post = function (config) {
};
return HttpRequest;
}());
I want to upload a video to YouTube from my phone device storage. However when I upload the file it comes through as blank. When I use the same upload code but with a web file, it works. Wondering where I am going wrong!
Method one
everything uploads correctly and the video plays on YouTube.
loadWebFile('assets/intro.mpg');
function loadWebFile(url) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = 'blob';
xhr.onload = function (e) {
uploadFile(xhr.response); // type: Blob
};
xhr.onerror = function (e) {
console.log('loadWebFile.onerror', e);
};
xhr.send();
};
Method two
The video title and description appears on YouTube, but the video is blank. I'm definitely passing through a valid file.
window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
if (window.webkitStorageInfo) {
window.webkitStorageInfo.requestQuota(access, 1024 * 1024, function (bytes) {
if (window.requestFileSystem) {
window.requestFileSystem(access, bytes, function (filesystem) {
loadFile('/Movies/intro.mpg');
}, me.onError);
} else {
window.alert('requestFileSystem not supported');
}
}, me.onError);
} else {
window.alert('webkitStorageInfo not supported');
}
// this sends an empty video to YouTube
function loadFile(path) {
filesystem.root.getFile(path, null, function (fileEntry) {
fileEntry.file(function (file) {
uploadFile(file); // type: File
});
}, function (e) {
console.log('loadFile.error', e);
});
}
Both methods share the same upload function:
// uploads using the YouTube script
// https://github.com/youtube/api-samples/blob/master/javascript/cors_upload.js
function uploadFile(file) {
var metadata = {
snippet: {
title: 'Video title',
description: 'Video description',
tags: 'Video tags',
categoryId: 22
},
status: {
privacyStatus: 'unlisted'
}
};
var uploader = new MediaUploader({
baseUrl: 'https://www.googleapis.com/upload/youtube/v3/videos',
file: file,
token: accessToken,
metadata: metadata,
params: {
part: Object.keys(metadata).join(',')
},
onError: function (e) {
console.log('onError', JSON.parse(e));
},
onProgress: function (e) {
console.log('onProgress', e);
},
onComplete: function (e) {
console.log('onComplete', JSON.parse(e));
}
});
uploader.upload();
};
I've have an example project with some of the code (minus the upload script) here:
https://github.com/kmturley/cordova-files
Here another working solution. I tested right now and it does work. You need standard cordova-plugin-file
function uploadFileToServer (fileUri, fileName, remoteUrl, callback) {
window.resolveLocalFileSystemURL(fileUri, function (fileEntry) {
fileEntry.file(function (file) {
var reader = new FileReader()
reader.onloadend = function () {
var blob = new Blob([new Uint8Array(this.result)], { type: 'application/octet-stream' })
var fd = new FormData()
fd.append('file', blob, fileName)
var xhr = new XMLHttpRequest()
xhr.open('POST', remoteUrl, true)
xhr.onload = function () {
if (xhr.status === 200) {
if (typeof callback === 'function') { callback() }
} else {
if (typeof callback === 'function') { callback(xhr.status) }
}
}
xhr.onerror = function (err) {
if (typeof callback === 'function') { callback(err) }
}
xhr.send(fd)
}
reader.readAsArrayBuffer(file)
}, function (err) {
if (typeof callback === 'function') { callback(err) }
})
})
}
You call it:
uploadFileToServer('file:///storage/emulated/0/Android/data/myfile.jpg',
'myfile.jpg',
'https://example.com/upload_url',
(err) => {
if (err) {
console.error('Error uploading file', err)
} else {
console.log('Upload done it with success')
}
})
The oficial file-transfer plugin is deprecated and when I'm writing this, its test script fails.
This made me use a pure javascript approach and it does work
function uploadFile (localPath, fileName, remoteUrl, callback) {
// loads local file with http GET request
var xhrLocal = new XMLHttpRequest()
xhrLocal.open('get', localPath)
xhrLocal.responseType = 'blob'
xhrLocal.onerror = () => {
callback(Error('An error ocurred getting localpath on' + localPath))
}
xhrLocal.onload = () => {
// when data is loaded creates a file reader to read data
var fr = new FileReader()
fr.onload = function (e) {
// fetch the data and accept the blob
console.log(e)
fetch(e.target.result)
.then(res => res.blob())
.then((res) => {
// now creates another http post request to upload the file
var formData = new FormData()
formData.append('imagefile', res, fileName)
// post form data
const xhrRemote = new XMLHttpRequest()
xhrRemote.responseType = 'json'
// log response
xhrRemote.onerror = () => {
callback(Error('An error ocurred uploading the file to ' + remoteUrl))
}
xhrRemote.onload = () => {
if (typeof callback === 'function') {
callback(null, 'File uploaded successful, ' + xhrRemote.response)
}
}
// create and send the reqeust
xhrRemote.open('POST', remoteUrl)
xhrRemote.send(formData)
})
}
fr.readAsDataURL(xhrLocal.response) // async call
}
xhrLocal.send()
}
Now just call it something like this
uploadFile('file:///storage/emulated/0/Android/data/myfile.jpg',
'myfile.jpg',
'https://example.com/upload_url',
(err, res) => {
if (err) {
console.error(err)
} else {
console.log(res)
}
})
So to upload files I realised for:
web files use a CORS upload:
https://github.com/youtube/api-samples/blob/master/javascript/cors_upload.js
local device files us the cordova file transfer plugin:
https://github.com/apache/cordova-plugin-file-transfer
The code i'm now using for local files which uploads the file and sets correct metadata:
function uploadVideo(fileURL) {
var options = new FileUploadOptions();
options.fileKey = 'file';
options.fileName = fileURL.substr(fileURL.lastIndexOf('/') + 1);
options.mimeType = 'video/mpg';
options.chunkedMode = false;
options.headers = {
Authorization: 'Bearer ' + accessToken
};
options.params = {
"": {
snippet: {
title: 'Video title',
description: 'Video description',
tags: 'Video tags',
categoryId: 22
},
status: {
privacyStatus: 'unlisted'
}
}
};
var ft = new FileTransfer();
ft.upload(fileURL, 'https://www.googleapis.com/upload/youtube/v3/videos?part=snippet,status', function (data) {
console.log('upload success', data);
}, function (e) {
console.log('upload error', e);
}, options, true);
ft.onprogress = function (progressEvent) {
console.log('onprogress: ' + ((progressEvent.loaded / progressEvent.total) * 100) + '%');
};
}
And I also had to modify the plugin to allow the metadata to be passed through to YouTube using a single request:
FileTransfer.java
lines 374 - 376
beforeData.append("Content-Disposition: form-data; name=\"").append(key.toString()).append("\";");
beforeData.append(" filename=\"").append("file.json").append('"').append(LINE_END);
beforeData.append("Content-Type: ").append("application/json").append(LINE_END).append(LINE_END);
If you do modify the plugin, remember cordova caches this code. I use this command to force it to update the plugin:
cordova platform remove android; cordova platform add android;