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;
}
}
Related
I want to convert a jquery function into a javascript function:
window.parent.$.ajax({
type: 'GET',
url: "http://localhost:3063/corsService/GetCultureInformation",
contentType: "application/json",
dataType: "json",
success: function (data) {
numberDecimalDigit = data.NumberDecimalDigits;
},
async: false
});
I converted it to:
var request = new XMLHttpRequest();
request.open('GET', 'http://localhost:3063/corsService/GetCultureInformation', false);
request.onload = function() {
if (this.status >= 200 && this.status < 400) {
// Success!
numberDecimalDigit = data.NumberDecimalDigits;
var resp = this.response;
} else {
// We reached our target server, but it returned an error
}
};
request.onerror = function() {
// There was a connection error of some sort
};
request.send();
but I have errors:
1: XMLHttpRequest: Network Error 0x2efd, Could not complete the operation due to error 00002efd.
2: NetworkError
//Better use promises, as it reduces biolerplate heavy code of XMLhttp request provides.
function loadjson(file) {
return new Promise((resolve, reject) => {
return fetch(file).then((response) => {
if (response.ok) {
resolve(response.json());
} else {
reject(new Error("error"));
}
});
});
}
var newFile=loadjson("https://api.postalpincode.in/pincode/110001").then((data) => {
console.log(data);
});
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?
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 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;
Is there a way to unnest (or unchain) the chained anonymous functions below? I include my try after the script with nested anonymous functions. I want to define separately each function defined anonymously on its own to see clearly and understand where each function starts and ends and what they do. Thanks.
A script with nested anonymous functions that I want to separate (taken from here):
<html>
<script>
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.tabs.getSelected(null, function(tab) {
chrome.tabs.sendRequest(tab.id, {action: "getDOM"}, function(response) {
var firstParagraph = response.dom;
//}); moved to end to get the variable firstParagraph
var formData = new FormData();
formData.append("url", tab.url);
formData.append("title", tab.title);
formData.append("pitch", firstParagraph);
//formData.append("user_tag_list", "tag1, tag2");
var xhr = new XMLHttpRequest();
xhr.open("POST", "http://ting-1.appspot.com/submithandlertest", true);
xhr.onreadystatechange = function (aEvt) {
if (xhr.readyState == 4) {
if (xhr.status == 200){
console.log("request 200-OK");
chrome.browserAction.setBadgeText ( { text: "done" } );
setTimeout(function () {
chrome.browserAction.setBadgeText( { text: "" } );
}, 2000);
}else{
console.log("connection error");
chrome.browserAction.setBadgeText ( { text: "ERR" } );
}
}
};
xhr.send(formData);
}); //chrome.tabs.sendRequest
});
});
</script>
</html>
My try to unnest anonymous function and reconstruct the script:
functionForSendRequest = function (response) {var firstParagraph = response.dom;
var formData = new FormData();
formData.append("url", tab.url);
formData.append("title", tab.title);
formData.append("pitch", firstParagraph);
var xhr = new XMLHttpRequest();
xhr.open("POST", "http://ting-1.appspot.com/submithandlertest", true);
xhr.onreadystatechange = function (aEvt) {
if (xhr.readyState == 4){
if (xhr.status == 200){
console.log("request 200-OK");
chrome.browserAction.setBadgeText({text: "done" });
setTimeout(function(){
chrome.browserAction.setBadgeText({text: ""});}, 2000);}
else{console.log("connection error");
chrome.browserAction.setBadgeText({text: "ERR"});}}};
xhr.send(formData);}}
argumentToGetSelected = chrome.tabs.sendRequest(tab.id, {action: "getDOM"}, functionForSendRequest()}
...
functionForGetSelected = function (tab) {chrome.tabs.sendRequest(tab.id, {action: "getDOM"}, *function for getSelected goes here*)}
To see the logic clearly, instead of "unchaining" them why not just try to practice good indentation. Then you can visually follow each method according to its indentation level. Like this:
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.tabs.getSelected(null, function(tab) {
chrome.tabs.sendRequest(tab.id, {action: "getDOM"}, function(response) {
var firstParagraph = response.dom;
var formData = new FormData();
formData.append("url", tab.url);
formData.append("title", tab.title);
formData.append("pitch", firstParagraph);
//formData.append("user_tag_list", "tag1, tag2");
var xhr = new XMLHttpRequest();
xhr.open("POST", "http://ting-1.appspot.com/submithandlertest", true);
xhr.onreadystatechange = function (aEvt) {
if (xhr.readyState == 4) {
if (xhr.status == 200){
console.log("request 200-OK");
chrome.browserAction.setBadgeText ( { text: "done" } );
setTimeout(function () {
chrome.browserAction.setBadgeText( { text: "" } );
}, 2000);
} else {
console.log("connection error");
chrome.browserAction.setBadgeText ( { text: "ERR" } );
}
}
};
xhr.send(formData);
}); //chrome.tabs.sendRequest
}); // chrome.tabs.getSelected
}); // chrome.browserAction.onClicked.addListener
Or, if you want to "unchain", the only really obvious thing to do is to define that inner callback separately, like this:
var handle_request = function(response) {
var firstParagraph = response.dom;
var formData = new FormData();
formData.append("url", tab.url);
formData.append("title", tab.title);
formData.append("pitch", firstParagraph);
//formData.append("user_tag_list", "tag1, tag2");
var xhr = new XMLHttpRequest();
xhr.open("POST", "http://ting-1.appspot.com/submithandlertest", true);
xhr.onreadystatechange = function (aEvt) {
if (xhr.readyState == 4) {
if (xhr.status == 200){
console.log("request 200-OK");
chrome.browserAction.setBadgeText ( { text: "done" } );
setTimeout(function () {
chrome.browserAction.setBadgeText( { text: "" } );
}, 2000);
} else {
console.log("connection error");
chrome.browserAction.setBadgeText ( { text: "ERR" } );
}
}
};
xhr.send(formData);
}
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.tabs.getSelected(null, function(tab) {
chrome.tabs.sendRequest(tab.id, {action: "getDOM"}, handle_requeest);
});
});