web worker containing xhr object with upload progress event handler - javascript

I'm trying to create a worker that uploads file attached with event handler that will update progress bar in my html page. the upload is already working fine in the worker.
I know that web worker has no access to DOM objects, which is where all my progress bars are. so, I'm wondering if there's any work around? I want to do the uploads in worker, but I also want the progress tracking. I'll be uploading several file in parallel, so, each worker (upload) will have its own progress bar.
is this doable? or should I let go of web worker altogether and choose different design?
this is what's inside my worker
onmessage = e => {
var xhr = new XMLHttpRequest();
xhr.addEventListener('progress', function(e) {
var done = e.position || e.loaded, total = e.totalSize || e.total;
console.log('xhr progress: ' + (Math.floor(done/total*1000)/10) + '%');
}, false);
if ( xhr.upload ) {
xhr.upload.onprogress = function(e) {
var done = e.position || e.loaded, total = e.totalSize || e.total;
console.log('xhr.upload progress: ' + done + ' / ' + total + ' = ' + (Math.floor(done/total*1000)/10) + '%');
};
}
xhr.onreadystatechange = function(e) {
if ( 4 == this.readyState ) {
console.log(['xhr upload complete', e]);
}
};
xhr.open('post', url, true);
xhr.setRequestHeader("Content-Type","multipart/form-data");
let formData = new FormData();
formData.append("thefile", e.data.payload);
xhr.send(formData)
}
I already prepared some event handler, but currently, it's just logging.
Thanks

The onmessage events includes a .source property. This is how you send data back to the client. Inside your progress event listener you would add something like:
e.source.postMessage({uploadName: <unique name>, progress: done});
Watch out, though. You use e as both the onmessage handler data and the progress event handler. I would recommend making the names of the two unique.
Then in your page you listen for the message:
window.addEventListener('message', (data) => {
// ... read data.progress and send it to the right place on the page
}

Related

How to submit form with image uploads only if promise was not rejected in TinyMCE

I am making a form where the user uploads local files onto the TinyMCE editor to which upon form submission they will be sent to the Flask server to store the image files. I have automatic_uploads set to false for this and I'm also using a custom image_upload_handler.
My problem is that if one of the images is too big, say it exceeds 1MB, then I want to reject that and not submit the form (but also not reset everything the user has already written).
Handler and init functions
// Taken from the TinyMCE v6 docs example, but I added image size checking.
const example_image_upload_handler = (blobInfo, progress) => new Promise((resolve, reject) => {
// Reject any images uploaded that are too big.
var image_size = blobInfo.blob().size; // size in bytes
var image_name = blobInfo.blob().filename;
var max_size = 1048576
if( image_size > max_size ){
reject("File too large! Max 1MB");
return; // Why isn't this the end of it?
}
const xhr = new XMLHttpRequest();
xhr.withCredentials = false;
xhr.open('POST', '/upload_image');
xhr.upload.onprogress = (e) => {
progress(e.loaded / e.total * 100);
};
xhr.onload = () => {
if (xhr.status === 403) {
reject({ message: 'HTTP Error: ' + xhr.status, remove: true });
return;
}
if (xhr.status < 200 || xhr.status >= 300) {
reject('HTTP Error: ' + xhr.status);
return;
}
const json = JSON.parse(xhr.responseText);
if (!JSON || typeof json.location != 'string') {
reject('Invalid JSON: ' + xhr.responseText);
return;
}
resolve(json.location);
};
xhr.onerror = () => {
reject('Image upload failed due to a XHR Transport error. Code: ' + xhr.status);
};
const formData = new FormData();
formData.append('file', blobInfo.blob(), blobInfo.filename());
xhr.send(formData);
});
tinymce.init({
selector: '#content',
height: 500,
plugins: 'advlist auto-link lists link image charmap preview anchor page break wordcount',
toolbar_mode: 'floating',
automatic_uploads: false,
images_upload_handler: example_image_upload_handler,
});
Using uploadImages() to submit a form
document.addEventListener("DOMContentLoaded", function() {
const postSubmitButton = document.querySelector(".submit-post");
// Called when form button is submitted.
postSubmitButton.addEventListener("click", function() {
tinymce.activeEditor.uploadImages()
.then(() => {
document.forms[0].submit(); // I want to submit this only if the
}); // Promise is not rejected.
});
});
If a normal-sized image is posted it submits fine and the image is correctly uploaded to the server.
If, however, a larger-sized image is posted the form is still submitted even when the Promise gets rejected and returned. Then the image is stored as base64. I have no idea how to stop that.
I am entirely new to how Promises work and relatively new to Javascript so any help on this would be greatly appreciated.
Solved, but in a tacky way.
I used a global boolean variable to track whether the file size is okay or not as a condition to submit the form.

JavaScript Track click event before following link

I have a Javascript snippet which my clients put on their websites. In order to track clicks, I create an XMLHttpRequest, wait for the 200 status and then propagate the click.
Here's the relevant code for the click event:
function pingServer(done) {
var url = "http://www.example.com/api/events/?eventID=" + eventID;
var invocation = new XMLHttpRequest();
invocation.open('GET', url, true);
invocation.withCredentials = true;
invocation.onreadystatechange = function() {
if (invocation.readyState == invocation.LOADING && invocation.status == 200) {
(typeof done === 'function' && done());
}
};
invocation.send();
}
// get the href
var href = e.target.href;
// ping then redirect
pingServer(function () { window.location.href = href; });
It works perfectly, and I get the ping on my server. The problem is that the delay in waiting for the XMLHttpRequest 200 code is noticeable to the end user. The browser's spinner doesn't start spinning until after that wait. So to the user it's poor UX and it looks like the click didn't do anything.
Is there a more user friendly way of registering a click event while also immediately handling the link redirect?

Why does serviceworker cause every second jquery post to instantly stall out?

What I stated in the title only happens in chrome as in firefox "('serviceWorker' in navigator)" is always false and "console.warn('Service workers aren\'t supported in this browser.');" triggers instead.
If you clear the browser or run incognito then it works initially but when you log out for the first time and then log back in the problems start.
This is what it looks like in the network log
Here's the code in which I register the SW:
function activateSW(){
if('serviceWorker' in navigator){
if(window.location.pathname != '/'){
//register with API
if(!navigator.serviceWorker.controller) navigator.serviceWorker.register('/service-worker', { scope: '/' });
//once registration is complete
navigator.serviceWorker.ready.then(function(serviceWorkerRegistration){
//get subscription
serviceWorkerRegistration.pushManager.getSubscription().then(function(subscription){
//enable the user to alter the subscription
$('.js-enable-sub-test').removeAttr("disabled");
//set it to allready subscribed if it is so
if(subscription){
$('.js-enable-sub-test').prop("checked", true);
$('.js-enable-sub-test').parent().addClass('checked');
}
});
});
}
}else{
console.warn('Service workers aren\'t supported in this browser.');
}
}
'/service-worker' is a request that gets sent to index.php (via .htaccess). It eventually end up in this function:
function serviceworkerJS($params){
$hash = API::request('settings', 'getSWHash', '');
if($hash != false){
setcookie('SW_Hash', $hash, time() + (86400 * 365 * 10), "/");
header('Content-type: text/javascript');
echo "'use strict';
var hash = '".$hash."';";
include(ROOT_PATH.'public/js/service-worker.js');
}elseif(isset($_COOKIE['SW_Hash'])){
header('Content-type: text/javascript');
echo "'use strict';
var hash = '".$_COOKIE['SW_Hash']."';";
include(ROOT_PATH.'public/js/service-worker.js');
}else{
header('HTTP/1.1 404 Not Found');
}
}
Service-worker.js as seen in chrome://serviceworker-internals/ looks like this:
(Several references to the adress has been replaced by stars)
'use strict';
var hash = 'bd8e78963deebf350f851fbf8cdc5080';
var *****_API_ENDPOINT = 'https://*********.***/';
//For displaying notifications
function showNotification(title, body, icon, data, id) {
var notificationOptions = {
body: body,
icon: icon,
tag: id,
data: data
};
//possibly unnecessary
if(self.registration.showNotification){
return self.registration.showNotification(title, notificationOptions);
}else{
return new Notification(title, notificationOptions);
}
}
//asks the server for messages and sends them for displaying.
function getMessages(event){
//showNotification('debug', 'initial', '', '', 'debug1');
//build question
var FD = new FormData();
FD.append('hash', hash);
//ask start20 for the notifications
event.waitUntil(
fetch(*****_API_ENDPOINT + 'ajax-get-SW-notification/', {method: 'post', body: FD}).then(function(response){
//something went wrong
if (response.status !== 200){
console.log('Error communicating with ******, code: ' + response.status);
showNotification('debug', 'picnic', '', '', 'debug2');
throw new Error();
}
//decode the response
return response.json().then(function(data){
var len = data.notifications.length;
//showNotification('debug', len, '', '', 'propertyName');
//Display
for(var i = 0; i < len -1; i++){
showNotification(data.notifications[i].title,
data.notifications[i].body,
data.notifications[i].imageurl,
data.notifications[i].linkurl,
data.notifications[i].hash);
}
//the last one needs to be returned to complete the promise
return showNotification(data.notifications[len -1].title,
data.notifications[len -1].body,
data.notifications[len -1].imageurl,
data.notifications[len -1].linkurl,
data.notifications[len -1].hash);
});
})
);
}
//when the user installs a new SW
/*self.addEventListener('activate', function(event){
//getMessages(event);
//event.preventDefault();
event.waitUntil(return self.registration.showNotification('bicnic', { body: '*p' }));
});*/
//when the serviceworker gets a puch from the server
self.addEventListener('push', function(event){
getMessages(event);
event.preventDefault();
});
//get the link associated witht he message when a user clicks on it
self.addEventListener('notificationclick', function(event){
//ask if the notification has any link associated with it
var FD = new FormData();
FD.append('hash', event.notification.tag);
//get the link
event.waitUntil(
fetch(******_API_ENDPOINT + 'ajax-notification-has-link/', {method: 'post', body: FD}).then(function(response){
//something went wrong
if (response.status !== 200){
console.log('Error communicating with ********, code: ' + response.status);
return;
}
//decode the response
return response.json().then(function(data){
//if there's a link associated with the message hash
if(data.link){
console.log(******_API_ENDPOINT + 'notification-link/' + event.notification.tag);
return clients.openWindow(*****_API_ENDPOINT + 'notification-link/' + event.notification.tag);
}
});
})
);
});
//unnecessary?
/*self.addEventListener('install', function(event){
//event.preventDefault();
});
self.addEventListener("fetch", function(event) {
});//*/
Now if you comment away "if(!navigator.serviceWorker.controller) navigator.serviceWorker.register( '/service-worker', { scope: '/' });" then the issue disappears but ofc the serviceworker subscribing and unsubscribing stops working. (The if statement doesn't seem to do much and was only added in an attempt to solve this)
I've tried numerous versions of activateSW() with various conditions for the different expressions to run and haven't managed to make a version that works without breaking the serviceworker. I have also tried to catch errors on various points (registration, posts) but this has been unsuccessful as none of them throws any.
What I suspect might be the problem is that as you register the serviceworker an activate event is triggered. Now if you catch this and complete the event promise then you become unable to subscribe. However I suspect that the serviceworker remains active as you log out and this causes a problem.
If you have questions or want to see more code then just ask.
edit; here's the solution:
self.addEventListener("fetch", function(event) {
event.respondWith(
fetch(event.request)
);
});
What you're seeing is confusing noise in the Network panel of Chrome DevTools, but shouldn't have a practical effect on your application's behavior.
If a service worker controls a page but doesn't include a fetch event handler, current versions of Chrome (M44 stable, and M46 canary) end up logging all network requests in the Network panel with (canceled) status, like you're seeing. The actual request is still made without service worker intervention, which is the second, successful logged entry. Things should work as expected from the perspective of your page, but I completely understand the confusion.
I have heard that there are plans to make Chrome's service worker implementation behave much more like a "no-op" when a network request is made and there's no fetch event handler. I don't know how far along those plans are, but I would expect that the noise you're seeing logged to go away once that happens.

Push notifications ti.App resume event handler (titanium)

Here is my code
function registerPushNotifications() {
Titanium.Network.registerForPushNotifications({
types : [Titanium.Network.NOTIFICATION_TYPE_BADGE, Titanium.Network.NOTIFICATION_TYPE_ALERT],
success : function(e) {
var deviceToken = e.deviceToken;
Ti.API.info("Push notification device token is: " + deviceToken);
Ti.API.info("Push notification types: " + Titanium.Network.remoteNotificationTypes);
Ti.API.info("Push notification enabled: " + Titanium.Network.remoteNotificationsEnabled);
Ti.API.error("device Token is: " + e.deviceToken);
//return device Token to store in Model.
return e.deviceToken;
},
error : function(e) {
Ti.API.info("Error during registration: " + e.error);
},
callback : function(e) {
// called when a push notification is received.
//var data = JSON.parse(e.data);
var data = e.data;
var badge = data.badge;
if (badge > 0) {
Titanium.UI.iPhone.appBadge = badge;
}
var message = data.message;
if (message != '') {
var my_alert = Ti.UI.createAlertDialog({
title : '',
message : message
});
my_alert.show();
}
Ti.App.addEventListener('resume', function() {
alert('do another event if coming from background');
}
});
};
Depending on whether the push notification comes from the background or foreground I want to run different events.
I have tried
Ti.App.addEventListener('resume', function() {
but this fires EVERYTIME I return to the app from the background (the event handlers will be stacked every time a push notification to sent and fires all of them). As opposed to only executing that part of the callback code for push notifications that have come in from the background.
How do I know if the push notification came from the background or whilst app is running? cheers
Update:
Solved it.
If you check the call back object, you will find IsBackground as a property to check where the push notification came from.
Solution:
Check JSON object of callback for the isBackground boolean event.
If 1 it means that it was called from the background, if 0, this means that it wasn't.

progress bar for Multiple file upload using Html5

When I use progress event, I can update the progress bar for one uploading request:
function uploadFile(file) {
fileid=md5(file.name);
if {xhr[fileid] ;== undefined} {
xhr[fileid] = new XMLHttpRequest();
xhr[fileid].open('POST',' {% url upload %}', true);
xhr[fileid].setRequestHeader("X-File-Name", file.name);
xhr[fileid].setRequestHeader("X-File-id", fileid);
xhr[fileid].upload.addEventListener('progress', onprogressHandler, false);
xhr[fileid].upload.addEventListener('load',oncompleteHandler,false);
xhr[fileid].send(file);
}
function onprogressHandler(event) {
var percent = event.loaded/event.total*100;
var $target = $(event.target);
console.log(uploadHolder[fileid]);
uploadHolder[fileid].find(".upload-completed").css('width',percent+'%');
console.log('Upload progress: ' + percent + '%');
}
However, when I sent out more than 2 files upload requests at same time, Only the progress bar for the last file will be changed.
How do I determine which file upload request the event is attached to?
Update:
if I declare the fileid as local variable for uploadFile like var fileid, I cannot access fileid in the event handler. Using 'this' in the event handler give me the XMLHttpRequestUpload object.
You should look for "closure" concept for javascript. After that you'll understand the error. And this concept is so important i think, you should learn it :)

Categories

Resources