How do I "pause" Server Sent Events? - server-sent-events

How I pause a Server Sent Event connection? Is it even possible, or do I have to close the SSE and then reopen it and assign all of the event handlers again?
For example I would like to do (this is all theoretical, I do not know the pause/restart function names):
var source = new EventSource(url);
source.addEventListener("something", somefunction, false);
source.addEventListener("somethingElse", somefunction2, false);
//...some code
source.pause();
//...some code
source.restart();

Yes, that's what technically needs to happen but you can abstract it away. Note: This one only connects after you do .connect()
function EventSource2(url) {
this.url = url;
this.es = null;
this.listeners = {};
}
EventSource2.prototype = {
constructor: EventSource2,
connect: function() {
this.es = new EventSource(this.url);
this.bindEvents();
},
disconnect: function() {
this.es.close();
this.es = null;
},
bindEvents: function() {
for ( var type in this.listeners ) {
var evs = this.listeners[type];
for( var i = 0; i < evs.length; ++i ) {
this.es.addEventListener( type, evs[i], false );
}
}
},
addEventListener: function( type, fn ) {
if( !this.listeners[type] ) {
this.listeners[type] = [];
}
this.listeners[type].push( fn );
if( this.es ) {
this.bindEvents();
}
}
}
Usage:
var source = new EventSource2(url);
source.addEventListener("something", somefunction, false);
source.addEventListener("somethingElse", somefunction2, false);
source.connect();
source.disconnect();
source.connect();

Related

How to get the entire path in Next.js 13 in custom loader function, for server components only?

I have a loader function called getBlogData which is like this:
import { getFromCacheOrApi } from 'Base'
const getBlogData = async () => {
const { pathname } = { pathname: "" }
var url = '/blog/data?'
let matches = /\/blog(\/\d+)?\/?$/.exec(pathname)
if (matches != null) {
const pageNumber = matches[1]
if (pageNumber !== undefined) {
url += `&pageNumber=${pageNumber.replace('/', '')}`
}
}
else {
const secondSegments = ['category', 'tag', 'author', 'search']
if (pathname.split('/').length >= 2 && !secondSegments.includes(pathname.split('/')[2])) {
response.status = 404
return
}
for (let i = 0; i < secondSegments.length; i++) {
const segment = secondSegments[i]
if (pathname.startsWith(`/blog/${segment}`)) {
matches = new RegExp(`(?<=\\/blog\\/${segment}\\/)[^/]+\\/?(\\d+)?\\/?$`).exec(pathname)
if (matches == null) {
response.status = 404
return
}
else {
url += `&${segment}=${encodeURI(matches[0].split('/')[0])}`
const pageNumber = matches[1]
if (pageNumber !== undefined) {
url += `&pageNumber=${pageNumber}`
}
break
}
}
}
}
url = url.replace('?&', '?')
const data = await getFromCacheOrApi(url)
// console.log(params, response.status)
// if (pageNumber && isNaN(pageNumber)) {
// console.log(pageNumber, isNaN(pageNumber))
// response.status = 400
// return
// }
const { seoParameters } = data
return data
}
export default getBlogData
This function is only used in my page which is inside app directory in Next 13, which means that it's a server component, and I don't want to change it to a client component.
However, I need to access request data, in this particular case, the path of the URL.
How can I get that?

How to avoid blockin while uploading file using Meteor method

I've created a Meteor method to upload a file, it's working well but until the file is fully uploaded, I cannot move around, all subscriptions seem to wait that the upload finishes... is there a way to avoid that ?
Here is the code on the server :
Meteor.publish('product-photo', function (productId) {
return Meteor.photos.find({productId: productId}, {limit: 1});
});
Meteor.methods({
/**
* Creates an photo
* #param obj
* #return {*}
*/
createPhoto: function (obj) {
check(obj, Object);
// Filter attributes
obj = filter(obj, [
'name',
'productId',
'size',
'type',
'url'
]);
// Check user
if (!this.userId) {
throw new Meteor.Error('not-connected');
}
// Check file name
if (typeof obj.name !== 'string' || obj.name.length > 255) {
throw new Meteor.Error('invalid-file-name');
}
// Check file type
if (typeof obj.type !== 'string' || [
'image/gif',
'image/jpg',
'image/jpeg',
'image/png'
].indexOf(obj.type) === -1) {
throw new Meteor.Error('invalid-file-type');
}
// Check file url
if (typeof obj.url !== 'string' || obj.url.length < 1) {
throw new Meteor.Error('invalid-file-url');
}
// Check file size
if (typeof obj.size !== 'number' || obj.size <= 0) {
throw new Meteor.Error('invalid-file-size');
}
// Check file max size
if (obj.size > 1024 * 1024) {
throw new Meteor.Error('file-too-large');
}
// Check if product exists
if (!obj.productId || Meteor.products.find({_id: obj.productId}).count() !== 1) {
throw new Meteor.Error('product-not-found');
}
// Limit the number of photos per user
if (Meteor.photos.find({productId: obj.productId}).count() >= 3) {
throw new Meteor.Error('max-photos-reached');
}
// Resize the photo if the data is in base64
if (typeof obj.url === 'string' && obj.url.indexOf('data:') === 0) {
obj.url = resizeImage(obj.url, 400, 400);
obj.size = obj.url.length;
obj.type = 'image/png';
}
// Add info
obj.createdAt = new Date();
obj.userId = this.userId;
return Meteor.photos.insert(obj);
}
});
And the code on the client :
Template.product.events({
'change [name=photo]': function (ev) {
var self = this;
readFilesAsDataURL(ev, function (event, file) {
var photo = {
name: file.name,
productId: self._id,
size: file.size,
type: file.type,
url: event.target.result
};
Session.set('uploadingPhoto', true);
// Save the file
Meteor.call('createPhoto', photo, function (err, photoId) {
Session.set('uploadingPhoto', false);
if (err) {
displayError(err);
} else {
notify(i18n("Transfert terminé pour {{name}}", photo));
}
});
});
}
});
I finally found the solution myself.
Explication : the code I used was blocking the subscriptions because it was using only one method call to transfer all the file from the first byte to the last one, that leads to block the thread (I think, the one reserved to each users on the server) until the transfer is complete.
Solution : I splitted the file into chunks of about 8KB, and send chunk by chunk, this way the thread or whatever was blocking the subscriptions is free after each chunk transfer.
The final working solution is on that post : How to write a file from an ArrayBuffer in JS
Client Code
// data comes from file.readAsArrayBuffer();
var total = data.byteLength;
var offset = 0;
var upload = function() {
var length = 4096; // chunk size
// adjust the last chunk size
if (offset + length > total) {
length = total - offset;
}
// I am using Uint8Array to create the chunk
// because it can be passed to the Meteor.method natively
var chunk = new Uint8Array(data, offset, length);
if (offset < total) {
// Send the chunk to the server and tell it what file to append to
Meteor.call('uploadFileData', fileId, chunk, function (err, length) {
if (!err) {
offset += length;
upload();
}
}
}
};
upload();
Server code
var fs = Npm.require('fs');
var Future = Npm.require('fibers/future');
Meteor.methods({
uploadFileData: function(fileId, chunk) {
var fut = new Future();
var path = '/uploads/' + fileId;
// I tried that with no success
chunk = String.fromCharCode.apply(null, chunk);
// how to write the chunk that is an Uint8Array to the disk ?
fs.appendFile(path, new Buffer(chunk), function (err) {
if (err) {
fut.throw(err);
} else {
fut.return(chunk.length);
}
});
return fut.wait();
}
});
Improving #Karl's code:
Client
This function breaks the file into chunks and sends them to the server one by one.
function uploadFile(file) {
const reader = new FileReader();
let _offset = 0;
let _total = file.size;
return new Promise((resolve, reject) => {
function readChunk() {
var length = 10 * 1024; // chunk size
// adjust the last chunk size
if (_offset + length > _total) {
length = _total - _offset;
}
if (_offset < _total) {
const slice = file.slice(_offset, _offset + length);
reader.readAsArrayBuffer(slice);
} else {
// EOF
setProgress(100);
resolve(true);
}
}
reader.onload = function readerOnload() {
let buffer = new Uint8Array(reader.result) // convert to binary
Meteor.call('fileUpload', file.name, buffer, _offset,
(error, length) => {
if (error) {
console.log('Oops, unable to import!');
return false;
} else {
_offset += length;
readChunk();
}
}
);
};
reader.onloadend = function readerOnloadend() {
setProgress(100 * _offset / _total);
};
readChunk();
});
}
Server
The server then writes to a file when offset is zero, or appends to its end otherwise, returning a promise, as I used an asynchronous function to write/append in order to avoid blocking the client.
if (Meteor.isServer) {
var fs = require('fs');
var Future = require('fibers/future');
}
Meteor.methods({
// Upload file from client to server
fileUpload(
fileName: string,
fileData: Uint8Array,
offset: number) {
check(fileName, String);
check(fileData, Uint8Array);
check(offset, Number);
console.log(`[x] Received file ${fileName} data length: ${fileData.length}`);
if (Meteor.isServer) {
const fut = new Future();
const filePath = '/tmp/' + fileName;
const buffer = new Buffer(fileData);
const jot = offset === 0 ? fs.writeFile : fs.appendFile;
jot(filePath, buffer, 'binary', (err) => {
if (err) {
fut.throw(err);
} else {
fut.return(buffer.length);
}
});
return fut.wait();
}
}
)};
Usage
uploadFile(file)
.then(() => {
/* do your stuff */
});

Meteor: observeChanges after the template is fully loaded

I am experimenting with meteor and I'm facing some code structuring issue.
What I want:
I use an observer to keep track of new document added on collection, but i want to be 'notified' only after the the template is fully rendered.
In my router.js file i have:
HospitalListController = RouteController.extend({
action: function() {
Meteor.subscribe('hospitals');
this.render('listHospitals');
}
});
My client listHospital.js file is
Template.listHospitals.onRendered(function(){
var first = true;
hospitalsCursor = Hospitals.find();
var totals = hospitalsCursor.count();
var loaded = 0;
HospitalsHandle = hospitalsCursor.observeChanges({
added : function(doc){
if( loaded != totals ){
loaded++;
}else{
console.log("added "+doc);
}
}
});
});
Is there a better way, maybe a 'meteor-way' to accomplish that?
You have to add a flag to ignore observeChange callback during initialization (found this solution here).
Template.listHospitals.onRendered(function(){
var initialized = false;
hospitalsCursor = Hospitals.find();
HospitalsHandle = hospitalsCursor.observeChanges({
added : function(doc){
if(initialized) {
// your logic
}
}
});
initialized = true;
});
This should work.
My actual working code:
Template.queueView.onRendered(function(){
var initialLoaded = false;
Queues.find().observeChanges({
added : function(id, doc){
if( initialLoaded ){
console.log("added "+id);
highlight();
beep();
}
},
removed : function(id, doc){
console.log("removed "+id);
highlight();
beep();
},
changed : function(){
console.log('modified!');
highlight();
beep();
}
});
Meteor.subscribe('queues', function(){
initialLoaded = true;
});
});

How to secure a route in AngularFire 0.6.0 (authRequired)?

In previous versions of angularFire, it was possible to secure selected routes by using "authRequired" and "pathTo" with Angular's $routeProvider. These no longer appear to work with AngularFire 0.6.0. What is the equivalent parameter/technique in Angular 0.6.0?
Routing was moved out of angularFire for the same reasons it was moved out of the core of Angular--to be less opinionated in how routing is conducted and which lib you should use.
You can still include routing by grabbing the module from angularFire-seed, which is plug-and-play ready.
The steps are:
add ngRoute and routeSecurity to your app dependencies
declare the loginRedirectPath constant
add authRequired where appropriate
Example:
// add routeSecurity to your dependency libs
angular.module('myApp', [..., 'ngRoute', 'firebase', 'routeSecurity']);
// declare the loginRedirectPath variable
angular.module('myApp').constant('loginRedirectPath', '/login')
// put authRequired in your routes
$routeProvider.when('/account', {
authRequired: true, // must authenticate before viewing this page
templateUrl: 'partials/account.html',
controller: 'AccountCtrl'
});
// live long and prosper
Here's a hard copy of the module as of 0.6.0 for compliance with SO policy; refer directly to the source for a current version:
(function(angular) {
angular.module('routeSecurity', [])
.run(['$injector', '$location', '$rootScope', 'loginRedirectPath', function($injector, $location, $rootScope, loginRedirectPath) {
if( $injector.has('$route') ) {
new RouteSecurityManager($location, $rootScope, $injector.get('$route'), loginRedirectPath);
}
}]);
function RouteSecurityManager($location, $rootScope, $route, path) {
this._route = $route;
this._location = $location;
this._rootScope = $rootScope;
this._loginPath = path;
this._redirectTo = null;
this._authenticated = !!($rootScope.auth && $rootScope.auth.user);
this._init();
}
RouteSecurityManager.prototype = {
_init: function() {
var self = this;
this._checkCurrent();
// Set up a handler for all future route changes, so we can check
// if authentication is required.
self._rootScope.$on("$routeChangeStart", function(e, next) {
self._authRequiredRedirect(next, self._loginPath);
});
self._rootScope.$on('$firebaseSimpleLogin:login', angular.bind(this, this._login));
self._rootScope.$on('$firebaseSimpleLogin:logout', angular.bind(this, this._logout));
self._rootScope.$on('$firebaseSimpleLogin:error', angular.bind(this, this._error));
},
_checkCurrent: function() {
// Check if the current page requires authentication.
if (this._route.current) {
this._authRequiredRedirect(this._route.current, this._loginPath);
}
},
_login: function() {
this._authenticated = true;
if( this._redirectTo ) {
this._redirect(this._redirectTo);
this._redirectTo = null;
}
else if( this._location.path() === this._loginPath ) {
this._location.replace();
this._location.path('/');
}
},
_logout: function() {
this._authenticated = false;
this._checkCurrent();
},
_error: function() {
if( !this._rootScope.auth || !this._rootScope.auth.user ) {
this._authenticated = false;
}
this._checkCurrent();
},
_redirect: function(path) {
this._location.replace();
this._location.path(path);
},
// A function to check whether the current path requires authentication,
// and if so, whether a redirect to a login page is needed.
_authRequiredRedirect: function(route, path) {
if (route.authRequired && !this._authenticated){
if (route.pathTo === undefined) {
this._redirectTo = this._location.path();
} else {
this._redirectTo = route.pathTo === path ? "/" : route.pathTo;
}
this._redirect(path);
}
else if( this._authenticated && this._location.path() === this._loginPath ) {
this._redirect('/');
}
}
};
})(angular);

Can I prevent events with conflict time?

How can I prevent events with conflict time? Is there any variable to set up?
No, there is not a variable to set, but you can use something like clientEvents which retrieves events that fullcalendar has in memory. You can use the function below in the eventDrop. In the case below it uses a function to filter out whether the event will have have an overlap or not.
function checkOverlap(event) {
var start = new Date(event.start);
var end = new Date(event.end);
var overlap = $('#calendar').fullCalendar('clientEvents', function(ev) {
if( ev == event)
return false;
var estart = new Date(ev.start);
var eend = new Date(ev.end);
return (Math.round(estart)/1000 < Math.round(end)/1000 && Math.round(eend) > Math.round(start));
});
if (overlap.length){
//either move this event to available timeslot or remove it
}
}
you can add eventOverlap : false in the celendar config,
http://fullcalendar.io/docs/event_ui/eventOverlap/
Correct overlap checking.
eventDrop: function(event, dayDelta, minuteDelta, allDay, revertFunc, jsEvent, ui, view) {
/// deny overlap of event
var start = new Date(event.start);
var end = new Date(event.end);
var overlap = $('#calendar').fullCalendar('clientEvents', function(ev) {
if( ev == event) {
return false;
}
var estart = new Date(ev.start);
var eend = new Date(ev.end);
return (
( Math.round(start) > Math.round(estart) && Math.round(start) < Math.round(eend) )
||
( Math.round(end) > Math.round(estart) && Math.round(end) < Math.round(eend) )
||
( Math.round(start) < Math.round(estart) && Math.round(end) > Math.round(eend) )
);
});
if (overlap.length){
revertFunc();
return false;
}
}
Add custom property in the event object overlap:false for example your event object will be
`{
title:'Event',
start: '2017-01-04T16:30:00',
end: '2017-01-04T16:40:00',
overlap:false
}`
Now override selectOverlap function,
selectOverlap: function(event) {
if(event.ranges && event.ranges.length >0) {
return (event.ranges.filter(function(range){
return (event.start.isBefore(range.end) &&
event.end.isAfter(range.start));
}).length)>0;
}
else {
return !!event && event.overlap;
}
},
It will not let the another event to override the already placed event.
This does the trick. It also handles resizing overlapping events
var calendar = new Calendar(calendarEl, {
selectOverlap: false,
eventOverlap: false
}
});

Resources