extend a library with own logic / bypass constructor - javascript

I want to add some add some extra logic (logging, trace stuff) into the main function of superagent: https://github.com/visionmedia/superagent/blob/master/lib/client.js#L444
So I need to extend superagent, and want to provide the same API, kind of passthrough all functions. I tried to solve it via different mechanisms: Object.create, prototype, deep copy, but I didn't get it working.
I don't want to manipulate the source code of superagent, just require it and wrap it, add my extra logic and call, passthrough the origin function. I think it's kind of aspect oriented.
// edit
So what don't work for me is to bypass the Request constructor:
function Request(method, url) {
var self = this;
Emitter.call(this);
this._query = this._query || [];
this.method = method;
this.url = url;
this.header = {};
this._header = {};
this.on('end', function(){
try {
var res = new Response(self);
if ('HEAD' == method) res.text = null;
self.callback(null, res);
} catch(e) {
var err = new Error('Parser is unable to parse the response');
err.parse = true;
err.original = e;
self.callback(err);
}
});
}

I got it almost working with this code:
var superagent = require('superagent');
var uuid = require('uuid');
var map = {};
var init = function() {
var supderdebug = function(method, url) {
console.log("pass through: root");
return superagent.apply(this, arguments);
}
var methods = ['get', 'head', 'del', 'patch','post', 'put'];
methods.forEach(function(method) {
var origin = superagent[method];
supderdebug[method] = function(url) {
console.log("pass through: "+method+"('"+url+"')");
var request = origin.apply(this, arguments);
var id = uuid();
map[id] = request;
return request;
}
});
_end = superagent.Request.prototype.end;
superagent.Request.prototype.end = function(fn) {
console.log("pass through: end");
return _end.apply(this, arguments);
}
_callback = superagent.Request.prototype.callback;
superagent.Request.prototype.callback = function(err, res) {
console.log("pass through: callback");
if (err) {
console.log(err);
}
var response = _callback.apply(this, arguments);
return response;
}
return supderdebug;
}
module.exports.init = init
Usage:
var sd = require("supderdebug").init();
Then I get the same API as superagent provides when I require it: var superagent = require("superagent")
But I cannot do the same with the superagent.Request and sa.Response. It doesn't work when I do:
superagent.Request.prototype.constructor = function(method, url)
// my hook
}
And there is another side effect, it would be nice if there is a solution without this side effect:
When requiring both my library and superagent, the superagent is not the origin anymore, because I overwrite the functions of superagent.

You need to send in the existing function
superagent.Request.prototype.end = function(end) {
return function() {
console.log("before end");
var request = end.apply(this, arguments);
console.log("after end");
return request;
};
}(superagent.Request.prototype.end);

Related

How can I fire off a dynamically created list of http requests in Angular 8?

In a previous AngularJS app that I am migrating to Angular 8, I had a function that would download functions by binding them and placing them in an array, waiting to be called using a reduce function. For example:
function stageForDownload() {
$scope.files.forEach(function (file) {
if (file.checked) {
$scope.downloadFunctions.push(downloadFile.bind(null, file));
}
});
}
function downloadStaged() {
$scope.downloadFunctions.reduce(
function (prev, next) {
return prev.then(next);
}, Promise.resolve())
.then( /* do something now that all files are downloaded */ );
}
}
This code would essentially resolve promises in a one by one fashion until the list of functions were empty. As in Angular 8, the structure of promises works in a different way (meaning they use the ECMA 6 implementation now) and I am unsure of how to migrate this code. Furthering my confusion, the HttpClient angular provides now returns an Observable which can be subscribed to; and while rsjx's forkJoin() method seems to support exactly what I want to do, it will not accept a list of bound functions.
I simply just need to know when all of the functions are completed, as they are voids that run an export service method to download a file. So I do not necessarily need to return / subscribe to any data from these methods that are being reduced.
Edit:
There are two more functions involved that I forgot to mention. Here is downloadFile, which is responsible for calling the exportService.
function downloadFile(file) {
var deferred = $q.defer();
$scope.date = formatDate($scope.datepicker.selectedDate);
$scope.fileDate = dateToYMD($scope.datepicker.selectedDate);
exportService.exportData(file.FileNamePrefix + " " + $scope.fileDate + ".xlsx", 'SOME_API_LOCATION' + $scope.date, file).then(
function () {
deferred.resolve();
},
function (error) {
deferred.reject();
notificationService.displayError("Internal Error!");
});
return deferred.promise;
}
And here is the exportService itself:
(function (app) {
'uuse strict';
app.factory('exportService', exportService);
exportService.$inject = ['$q', '$http'];
function exportService($q, $http) {
var service = {
exportData: exportData,
createFilename: createFilename
};
function exportData(filename, url, data) {
var config = {
responseType: 'arraybuffer'
};
return $http.post(url, data, config).then(
function (response) {
var deferred = $q.defer();
var data = response.data;
var status = response.status;
var headers = response.headers();
var octetStreamMime = 'application/octet-stream';
var success = false;
var contentType = headers['content-type'] || octetStreamMime;
try {
// Try using msSaveBlob if supported
var blob = new Blob([data], { type: contentType });
if (navigator.msSaveBlob)
navigator.msSaveBlob(blob, filename);
else {
// Try using other saveBlob implementations, if available
var saveBlob = navigator.webkitSaveBlob || navigator.mozSaveBlob || navigator.saveBlob;
if (saveBlob === undefined) throw "Not supported";
saveBlob(blob, filename);
}
success = true;
deferred.resolve();
} catch (ex) {
}
if (!success) {
// Get the blob url creator
var urlCreator = window.URL || window.webkitURL || window.mozURL || window.msURL;
if (urlCreator) {
// Try to use a download link
var link = document.createElement('a');
if ('download' in link) {
// Try to simulate a click
try {
// Prepare a blob URL
var blob = new Blob([data], { type: contentType });
var url = urlCreator.createObjectURL(blob);
link.setAttribute('href', url);
// Set the download attribute (Supported in Chrome 14+ / Firefox 20+)
link.setAttribute("download", filename);
// Simulate clicking the download link
var event = document.createEvent('MouseEvents');
event.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
link.dispatchEvent(event);
success = true;
deferred.resolve();
} catch (ex) {
}
}
if (!success) {
// Fallback to window.location method
try {
var blob = new Blob([data], { type: octetStreamMime });
var url = urlCreator.createObjectURL(blob);
window.location = url;
success = true;
deferred.resolve();
} catch (ex) {
deferred.reject();
}
}
}
}
return deferred.promise;
},
function (error) {
return $q.reject(error);
});
}
}
})(angular.module('app'));
So it appears my problem is not necessarily firing off a dynamically created list of http requests, but rather how to convert promises using $q into ECMA6+ promises.
You can use from to turn Promise into Observable.
const observables: Observable<any>[] = promises.map(promise => from(promise));
After that, you can unleash RxJS. You can use forkJoin to get an Observable of all your promises:
forkJoin(observables).subscribe(files => {
// do things with files
});
The way forkJoin is implemented it will only fire once the observables complete. It will not be an issue here, but if you want to batch requests that do not complete, consider zip.
If you want to do use HttpClient, you would just have a different source of your observables.
const observables: Observable<any>[] = urls.map(url => this.httpClient.get(url));
but the forkJoin would be the same. The key is that forkJoin accepts an array of Observables.

I wrote some killer Javascript. How can I make it easily reusable?

I have been playing around with Chromes filestorage API. I have built a couple of functions that together automatically downloads a json-object and stores it as a string. If the last server-request was done within 24 hours. I automatically use the last version of the file. I use this for managing a huge data-dump that I do statistical analysis on.
The entire system only has one function that needs to be exposed. It's getData.
Currently all these functions are global variables. How should I make this contained in an orderly way.
//This file will cache serverdata every day.
var onInitFs,
errorHandler,
fileSystemInit,
saveFile,
readFile,
fileSystem,
getData;
//request rights to save files to system.
fileSystemInit = function(){
//Browser specific
window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
navigator.webkitPersistentStorage.requestQuota(1048*1048*256, function(grantedBytes) {
//once approved (or if previously approved):
window.requestFileSystem(PERSISTENT, grantedBytes, onInitFs, errorHandler);
}, function(e) {
console.log('Error', e);
});
};
//make filesystem global.
onInitFs = function(fs) {
fileSystem = fs;
};
fileSystemInit();
saveFile = function(url, content, callback){
var filename = makeFilename(url)
if(!fileSystem){
console.log('no filesystem registered')
return;
}
fileSystem.root.getFile(filename, {create: true}, function(fileEntry) {
fileEntry.createWriter(function(fileWriter) {
var blob = new Blob([JSON.stringify(content)], {type: 'application/json'});
fileWriter.write(blob);
fileWriter.onwriteend = function(e) {
console.debug('Write completed.', e);
if(callback){
callback();
}
};
fileWriter.onerror = function(e) {
console.log('Write failed: ', e);
};
}, errorHandler);
}, errorHandler);
};
readFile = function(url, callback){
var filename = makeFilename(url)
if(!fileSystem){
console.log('no filesystem registered');
return;
}
fileSystem.root.getFile(filename, {}, function(fileEntry){
//this object reads files.
var reader = new FileReader();
//register callback for read files
reader.onloadend = function(e){
var callbackValue = JSON.parse(this.result)
callback(callbackValue);
};
//read file-function
fileEntry.file(function(file){
reader.readAsText(file);
},errorHandler);
},errorHandler);
};
makeFilename = function(url){
return url.replace(/\W/g, '') +'.json'
}
errorHandler = function(e) {
console.log('Error: ', e);
};
getData = function(url, callbackNewData, callbackOldData){
var lastDownloaded = localStorage.getItem(url+'lastDownloaded'),
oneDay = 1000*60*60*24;
//update data if the data is old.
window.setTimeout(function(){
if(!lastDownloaded || new Date()-new Date(lastDownloaded) > oneDay ){
console.debug('downloading '+url);
d3.json(url, function(data){
localStorage.setItem(url+'lastDownloaded',new Date());
console.debug('saving '+url);
saveFile(url, data, function(){
callbackNewData(url);
});
});
}else{
callbackOldData(url);
}
}, 200);
};
You can wrap the whole thing in an anonymous function and expose getData only. This is the easiest way to do.
var getDataFromUrl = function () {
//This file will cache serverdata every day.
var onInitFs,
errorHandler,
fileSystemInit,
saveFile,
readFile,
fileSystem,
getData;
// Your original code here ...
return getData; // This exposes the getData function.
})();
In this way you only exposes one global function getDataFromUrl, which is exactly the public API.
For more modern usage, you may want to check out Common JS Modules and Browserify, which let you do exports and require both in browser and NodeJS. There is also a UMD Pattern for exporting libraries.
(function(window){
'use strict'
var onInitFs,errorHandler,fileSystemInit,saveFile,readFile,fileSystem,getData;
fileSystemInit = function(){
// your code
};
//make filesystem global.
onInitFs = function(fs) {
//your code
};
fileSystemInit();
saveFile = function(url, content, callback){
//your code
};
readFile = function(url, callback){
//your code
};
makeFilename = function(url){
//your code
}
errorHandler = function(e) {
//your code
};
getData = function(url, callbackNewData, callbackOldData){
//your code
};
window.HimmatorsFileStorageAPI = getData; // you can change the name here
})(window);
And you can use it simply by including this script in your page and then calling
HimmatorsFileStorageAPI(url, callbackNewData, callbackOldData);
The Module pattern would be a good start: http://addyosmani.com/resources/essentialjsdesignpatterns/book/#modulepatternjavascript
Pseudo-class also would be great:
var StorageInterface = function(arg1, arg2){
this.arg1 = arg1;
this.arg2 = arg2;
}
StorageInterface.prototype.method = function(arg3, arg4){
return arg3 + arg4 + this.arg1 + this.arg2;
}
var si = new StorageInterface(100, 500);
si.method(3, 4);
Just prototype the heck out of it :-) And use some scoped instances(using var that = this) to pass elements back to the parent objects from different scopes.
Now you can just start a new FileSystemInstance() to do your magic.
If you wish to make the more "arcane" methods private you could consider moving them to a object within your object and such, but in the end anyone with true perserverance will be able to access them. So I advice to go with a public way, and name the private methods _fileSystemInit, so people who read the code know it's an internalised method.
//This file will cache serverdata every day.
function FileSystemInstance() {
this.fileSystem = null;
this.requestFileSystem = null;
this.fileSystemInit();
}
//request rights to save files to system.
FileSystemInstance.prototype.fileSystemInit = function(){
//Browser specific
this.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
this.requestFileSystem = this.requestFileSystem.bind(window);
console.log(this.requestFileSystem);
var that = this;
console.log(that.requestFileSystem);
navigator.webkitPersistentStorage.requestQuota(1048*1048*256, function(grantedBytes) {
//once approved (or if previously approved):
console.log(that.requestFileSystem);
that.requestFileSystem(PERSISTENT, grantedBytes, function(fs){that.onInitFs(fs)}, function(e){that.errorHandler(e)});
}, function(e) {
console.log('Error', e);
});
};
//make filesystem global.
FileSystemInstance.prototype.onInitFs = function(fs) {
this.fileSystem = fs;
};
FileSystemInstance.prototype.saveFile = function(url, content, callback){
var filename = this.makeFilename(url)
if(!fileSystem){
console.log('no filesystem registered')
return;
}
this.fileSystem.root.getFile(filename, {create: true}, function(fileEntry) {
fileEntry.createWriter(function(fileWriter) {
var blob = new Blob([JSON.stringify(content)], {type: 'application/json'});
fileWriter.write(blob);
fileWriter.onwriteend = function(e) {
console.debug('Write completed.', e);
if(callback){
callback();
}
};
fileWriter.onerror = function(e) {
console.log('Write failed: ', e);
};
}, errorHandler);
}, errorHandler);
};
FileSystemInstance.prototype.readFile = function(url, callback){
var filename = this.makeFilename(url)
if(!this.fileSystem){
throw new Error('no filesystem registered');
}
this.fileSystem.root.getFile(filename, {}, function(fileEntry){
//this object reads files.
var reader = new FileReader();
//register callback for read files
reader.onloadend = function(e){
var callbackValue = JSON.parse(this.result)
callback(callbackValue);
};
//read file-function
fileEntry.file(function(file){
reader.readAsText(file);
},errorHandler);
},errorHandler);
};
FileSystemInstance.prototype.makeFilename = function(url){
return url.replace(/\W/g, '') +'.json'
}
FileSystemInstance.prototype.errorHandler = function(e) {
console.error('Error: ', e);
};
FileSystemInstance.prototype.getData = function(url, callbackNewData, callbackOldData){
var that = this;
var lastDownloaded = localStorage.getItem(url+'lastDownloaded'),
oneDay = 1000*60*60*24;
//update data if the data is old.
window.setTimeout(function(){
if(!lastDownloaded || new Date()-new Date(lastDownloaded) > oneDay ){
console.debug('downloading '+url);
d3.json(url, function(data){
localStorage.setItem(url+'lastDownloaded',new Date());
console.debug('saving '+url);
that.saveFile(url, data, function(){
callbackNewData(url);
});
});
}else{
callbackOldData(url);
}
}, 200);
};
FileSystem = new FileSystemInstance();
var data = FileSystem.getData();
console.log("Data is: ",data);

Javascript call method in another method

Now I'm trying to implement Unity Webgl with jslib. I'm so confused about how to call method in another method's function. I want to call method Recv when message was coming (ws.onmessage). But, it show "TypeError: this.Recv is undefined". Could you please help me figure out this source? Thank you !!!!!
Here's my source code
var ws = null;
var init_url = "";
var received_msg = "";
var error_msg = "";
var WebsocketLib = {
Hello: function(){
window.alert("Hello,world!");
},
InitSocket: function(url){
init_url = Pointer_stringify(url);
console.log("InitWebSocket: "+init_url);
ws = new WebSocket(init_url);
ws.onopen = function(evt){
console.log("Connect");
isConnected = false;
ws.send("hello");
};
ws.onclose = function(evt) {
console.log("Close");
isConnected = false;
};
ws.onmessage = function(evt) {
received_msg = evt.data;
console.log("[recv] "+received_msg);
this.Recv.call(this);
};
ws.onerror = function(evt) {
error_msg = evt.data;
console.log("[error] "+error_msg);
this.Error.call(this);
};
},
Recv: function(){
console.log("[recv] "+received_msg);
var buffer = _malloc(received_msg.length + 1);
writeStringToMemory(returnStr, buffer);
return buffer;
},
Error: function(){
console.log("[error] "+error_msg);
var buffer = _malloc(error_msg.length + 1);
writeStringToMemory(error_msg, buffer);
return buffer;
}
}
Inside of ws.onmessage this will refer to ws (as we're inside a method of ws) and not WebsocketLib.
However, inside Initsocket, where you define the handlers, this would correctly (in the sense that this is what you want) refer to the WebsocketLib object, so you can create a bound function to bind the outer this value to be used as this inside the event handler, like this:
ws.onmessage = function(evt) {
received_msg = evt.data;
console.log("[recv] "+received_msg);
this.Recv.call(this);
}.bind(this);
in JavaScript the value of this behaves differently than in other languages. Its value depends on how the function is called. You can read more about it in the Mozilla MDN page.
To solve your specific problem you can:
InitSocket: function(url){
var that = this; // [1]
init_url = Pointer_stringify(url);
console.log("InitWebSocket: "+init_url);
ws = new WebSocket(init_url);
ws.onopen = function(evt){
console.log("Connect");
isConnected = false;
ws.send("hello");
};
ws.onclose = function(evt) {
console.log("Close");
isConnected = false;
};
ws.onmessage = function(evt) {
received_msg = evt.data;
console.log("[recv] "+received_msg);
that.Recv.call(that); // [2]
};
ws.onerror = function(evt) {
error_msg = evt.data;
console.log("[error] "+error_msg);
that.Error.call(that); // [2]
};
},
In line 1 I bind the this variable to a custom variable that I decided to call that (but you can call it as you want). Then in line 2 I used that instead of this.
Inside the ws.onmessage function the value of this is not referring to the instance of WebsocketLib, so you need to use this "trick" and access the right this value using the one saved in the closure, inside the value of that.

How to Pipe Response to a File in Co-Request module & NodeJs?

I am using Co-Request to read Zip file from http url, and i have below code to read from server..
The code works already. But I dont know how to write the response Zip to a file.
var co = require( "co" );
var request = require( "co-request" );
var options = {
url: "http://www.example.com/sample.zip",
headers: {
'Token': Appconfig.Affiliate_Token,
'Affiliate-Id' : Appconfig.Affiliate_Id
}
}
console.log( "Downloading : zip file" );
var j = yield request( options );
Co-Request is actually wrapper for Request and I have found below code to pipe file to stream. But not sure how to write the same using Co-Request with yield.
request.get('http://example.com/img.png').pipe(request.put('http://example.com/img.png'))
Please help how to write response zip to a file using yield and co-request
I think request cant pipe after data has been emitted from the response
use request instead of co-request, write a promise to achieve this
var co = require('co');
var request = require('request');
var fs = require('fs');
var url = 'http://google.com/doodle.png';
var requestPipToFile = function(url, filepath) {
return new Promise(function(resolve, reject) {
try {
var stream = fs.createWriteStream(filepath);
stream.on('finish', function() {
console.log("pipe finish");
return resolve(true);
});
return request(url).pipe(stream);
} catch (e) {
return reject(e);
}
});
};
co(function*() {
var value = (yield requestPipToFile(url, './outfile'));
return value;
}).then(function(value) {
return console.log(value);
}).catch(function(err) {
return console.error(err);
});

Use HTTP-on-modify-request to redirect URL

I am building an add-on for Firefox that redirect request to a new URL if the URL match some conditions. I've tried this, and it does not work.
I register an observer on HTTP-on-modify-request to process the URL, if the URL match my condition, I will redirect to a new URL.
Here is my code:
var Cc = Components.classes;
var Ci = Components.interfaces;
var Cr = Components.results;
var newUrl = "https://google.com";
function isInBlacklist(url) {
// here will be somemore condition, I just use youtube.com to test
if (url.indexOf('youtube.com') != -1) {
return true;
}
return false;
}
exports.main = function(options,callbacks) {
// Create observer
httpRequestObserver =
{
observe: function (subject, topic, data) {
if (topic == "http-on-modify-request") {
var httpChannel = subject.QueryInterface(Ci.nsIHttpChannel);
var uri = httpChannel.URI;
var domainLoc = uri.host;
if (isInBlacklist(domainLoc) === true) {
httpChannel.cancel(Cr.NS_BINDING_ABORTED);
var gBrowser = utils.getMostRecentBrowserWindow().gBrowser;
var domWin = channel.notificationCallbacks.getInterface(Ci.nsIDOMWindow);
var browser = gBrowser.getBrowserForDocument(domWin.top.document);
browser.loadURI(newUrl);
}
}
},
register: function () {
var observerService = Cc["#mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
observerService.addObserver(this, "http-on-modify-request", false);
},
unregister: function () {
var observerService = Cc["#mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
observerService.removeObserver(this, "http-on-modify-request");
}
};
//register observer
httpRequestObserver.register();
};
exports.onUnload = function(reason) {
httpRequestObserver.unregister();
};
I am new to Firefox add-on development.
You can redirect a channel by calling nsIHttpChannel.redirectTo.
This is not possible once the channel is opened, but in http-on-modify-request it will work.
So in your code, you can do something like:
Cu.import("resource://gre/modules/Services.jsm");
// ...
if (condition) {
httpChannel.redirectTo(
Services.io.newURI("http://example.org/", null, null));
}
It looks like you might be using the Add-on SDK. In that case, read up on Using Chrome Authority.
You could simply do a
httpChannel.URI.spec = newUrl;
instead of
httpChannel.cancel(Cr.NS_BINDING_ABORTED);
...
browser.loadURI(newUrl);
Not sure how 'safe' it would be in your case, since I'm not exactly sure how other headers in the request (e.g. Cookie) would be manipulated when you change the URL to point to an entirely different domain at this stage.

Categories