I would like to capture the contents of AJAX requests using Greasemonkey.
Does anybody know how to do this?
The accepted answer is almost correct, but it could use a slight improvement:
(function(open) {
XMLHttpRequest.prototype.open = function() {
this.addEventListener("readystatechange", function() {
console.log(this.readyState);
}, false);
open.apply(this, arguments);
};
})(XMLHttpRequest.prototype.open);
Prefer using apply + arguments over call because then you don't have to explicitly know all the arguments being given to open which could change!
How about modifying the XMLHttpRequest.prototype.open or send methods with replacements which set up their own callbacks and call the original methods? The callback can do its thing and then call the callback the original code specified.
In other words:
XMLHttpRequest.prototype.realOpen = XMLHttpRequest.prototype.open;
var myOpen = function(method, url, async, user, password) {
//do whatever mucking around you want here, e.g.
//changing the onload callback to your own version
//call original
this.realOpen (method, url, async, user, password);
}
//ensure all XMLHttpRequests use our custom open method
XMLHttpRequest.prototype.open = myOpen ;
Tested in Chrome 55 and Firefox 50.1.0
In my case I wanted to modify the responseText, which in Firefox was a read-only property, so I had to wrap the whole XMLHttpRequest object. I haven't implemented the whole API (particular the responseType), but it was good enough to use for all of the libraries I have.
Usage:
XHRProxy.addInterceptor(function(method, url, responseText, status) {
if (url.endsWith('.html') || url.endsWith('.htm')) {
return "<!-- HTML! -->" + responseText;
}
});
Code:
(function(window) {
var OriginalXHR = XMLHttpRequest;
var XHRProxy = function() {
this.xhr = new OriginalXHR();
function delegate(prop) {
Object.defineProperty(this, prop, {
get: function() {
return this.xhr[prop];
},
set: function(value) {
this.xhr.timeout = value;
}
});
}
delegate.call(this, 'timeout');
delegate.call(this, 'responseType');
delegate.call(this, 'withCredentials');
delegate.call(this, 'onerror');
delegate.call(this, 'onabort');
delegate.call(this, 'onloadstart');
delegate.call(this, 'onloadend');
delegate.call(this, 'onprogress');
};
XHRProxy.prototype.open = function(method, url, async, username, password) {
var ctx = this;
function applyInterceptors(src) {
ctx.responseText = ctx.xhr.responseText;
for (var i=0; i < XHRProxy.interceptors.length; i++) {
var applied = XHRProxy.interceptors[i](method, url, ctx.responseText, ctx.xhr.status);
if (applied !== undefined) {
ctx.responseText = applied;
}
}
}
function setProps() {
ctx.readyState = ctx.xhr.readyState;
ctx.responseText = ctx.xhr.responseText;
ctx.responseURL = ctx.xhr.responseURL;
ctx.responseXML = ctx.xhr.responseXML;
ctx.status = ctx.xhr.status;
ctx.statusText = ctx.xhr.statusText;
}
this.xhr.open(method, url, async, username, password);
this.xhr.onload = function(evt) {
if (ctx.onload) {
setProps();
if (ctx.xhr.readyState === 4) {
applyInterceptors();
}
return ctx.onload(evt);
}
};
this.xhr.onreadystatechange = function (evt) {
if (ctx.onreadystatechange) {
setProps();
if (ctx.xhr.readyState === 4) {
applyInterceptors();
}
return ctx.onreadystatechange(evt);
}
};
};
XHRProxy.prototype.addEventListener = function(event, fn) {
return this.xhr.addEventListener(event, fn);
};
XHRProxy.prototype.send = function(data) {
return this.xhr.send(data);
};
XHRProxy.prototype.abort = function() {
return this.xhr.abort();
};
XHRProxy.prototype.getAllResponseHeaders = function() {
return this.xhr.getAllResponseHeaders();
};
XHRProxy.prototype.getResponseHeader = function(header) {
return this.xhr.getResponseHeader(header);
};
XHRProxy.prototype.setRequestHeader = function(header, value) {
return this.xhr.setRequestHeader(header, value);
};
XHRProxy.prototype.overrideMimeType = function(mimetype) {
return this.xhr.overrideMimeType(mimetype);
};
XHRProxy.interceptors = [];
XHRProxy.addInterceptor = function(fn) {
this.interceptors.push(fn);
};
window.XMLHttpRequest = XHRProxy;
})(window);
You can replace the unsafeWindow.XMLHttpRequest object in the document with a wrapper. A little code (not tested):
var oldFunction = unsafeWindow.XMLHttpRequest;
unsafeWindow.XMLHttpRequest = function() {
alert("Hijacked! XHR was constructed.");
var xhr = oldFunction();
return {
open: function(method, url, async, user, password) {
alert("Hijacked! xhr.open().");
return xhr.open(method, url, async, user, password);
}
// TODO: include other xhr methods and properties
};
};
But this has one little problem: Greasemonkey scripts execute after a page loads, so the page can use or store the original XMLHttpRequest object during it's load sequence, so requests made before your script executes, or with the real XMLHttpRequest object wouldn't be tracked by your script. No way that I can see to work around this limitation.
I spent quite some time figuring out how to do this.
At first I was just overriding window.fetch but that stopped working for some reason - I believe it has to do with Tampermonkey trying to sandbox window (??) and I also tried unsafeWindow with the same results.
So. I started looking into overriding the requests at a lower level. The XMLHttpRequest (also that class name upper case lower case ew...)
Sean's answer was helpful to get started but didn't show how to override the responses after interception. The below does that:
let interceptors = [];
/*
* Add a interceptor.
*/
export const addInterceptor = (interceptor) => {
interceptors.push(interceptor);
};
/*
* Clear interceptors
*/
export const clearInterceptors = () => {
interceptors = [];
};
/*
* XML HTPP requests can be intercepted with interceptors.
* Takes a regex to match against requests made and a callback to process the response.
*/
const createXmlHttpOverride = (
open
) => {
return function (
method: string,
url,
async,
username,
password
) {
this.addEventListener(
"readystatechange",
function () {
if (this.readyState === 4) {
// Override `onreadystatechange` handler, there's no where else this can go.
// Basically replace the client's with our override for interception.
this.onreadystatechange = (function (
originalOnreadystatechange
) {
return function (ev) {
// Only intercept JSON requests.
const contentType = this.getResponseHeader("content-type");
if (!contentType || !contentType.includes("application/json")) {
return (
originalOnreadystatechange &&
originalOnreadystatechange.call(this, ev)
);
}
// Read data from response.
(async function () {
let success = false;
let data;
try {
data =
this.responseType === "blob"
? JSON.parse(await this.response.text())
: JSON.parse(this.responseText);
success = true;
} catch (e) {
console.error("Unable to parse response.");
}
if (!success) {
return (
originalOnreadystatechange &&
originalOnreadystatechange.call(this, ev)
);
}
for (const i in interceptors) {
const { regex, override, callback } = interceptors[i];
// Override.
const match = regex.exec(url);
if (match) {
if (override) {
try {
data = await callback(data);
} catch (e) {
logger.error(`Interceptor '${regex}' failed. ${e}`);
}
}
}
}
// Override the response text.
Object.defineProperty(this, "responseText", {
get() {
return JSON.stringify(data);
},
});
// Tell the client callback that we're done.
return (
originalOnreadystatechange &&
originalOnreadystatechange.call(this, ev)
);
}.call(this));
};
})(this.onreadystatechange);
}
},
false
);
open.call(this, method, url, async, username, password);
};
};
const main = () => {
const urlRegex = /providers/; // Match any url with "providers" in the url.
addInterceptor({
urlRegex,
callback: async (_data) => {
// Replace response data.
return JSON.parse({ hello: 'world' });
},
override: true
});
XMLHttpRequest.prototype.open = createXmlHttpOverride(
XMLHttpRequest.prototype.open
);
};
main();
Based on proposed solution I implemented 'xhr-extensions.ts' file which can be used in typescript solutions.
How to use:
Add file with code to your solution
Import like this
import { XhrSubscription, subscribToXhr } from "your-path/xhr-extensions";
Subscribe like this
const subscription = subscribeToXhr(xhr => {
if (xhr.status != 200) return;
... do something here.
});
Unsubscribe when you don't need subscription anymore
subscription.unsubscribe();
Content of 'xhr-extensions.ts' file
export class XhrSubscription {
constructor(
private callback: (xhr: XMLHttpRequest) => void
) { }
next(xhr: XMLHttpRequest): void {
return this.callback(xhr);
}
unsubscribe(): void {
subscriptions = subscriptions.filter(s => s != this);
}
}
let subscriptions: XhrSubscription[] = [];
export function subscribeToXhr(callback: (xhr: XMLHttpRequest) => void): XhrSubscription {
const subscription = new XhrSubscription(callback);
subscriptions.push(subscription);
return subscription;
}
(function (open) {
XMLHttpRequest.prototype.open = function () {
this.addEventListener("readystatechange", () => {
subscriptions.forEach(s => s.next(this));
}, false);
return open.apply(this, arguments);
};
})(XMLHttpRequest.prototype.open);
Not sure if you can do it with greasemonkey, but if you create an extension then you can use the observer service and the http-on-examine-response observer.
Related
I've been trying to work on this for a while. I'm working with google drive api -> and I'm trying to try to get the main script to re-run the request of the accessToken is incorrect and causes an error.
Can that be sent to the main script somehow?
Adding some code to show I've actually worked on this lol - left out some bc it's alot of other unrelated stuff.
I am using IndexedDB to pass info between SW and main Script
//////////////////////////////////////////////////////////////
//////////////// Check Database onload ///////////////////////
//////////////////////////////////////////////////////////////
window.addEventListener("load", checkUpload(), false);
function checkUpload() {
if (supportCheck()) {
let openRequest = indexedDB.open("GoogleDrive", 1);
openRequest.onsuccess = (e) => {
var db = e.target.result;
var objectStore = db
.transaction(["backups"], "readwrite")
.objectStore("backups");
var request = objectStore.get("1");
request.onerror = function () {
// Handle errors!
};
request.onsuccess = function (event) {
var data = event.target.result;
if (googleSignin.isAuthorizedForGDrive()) {
// Call SW Function
}
else {
//Google Sign in Error
}
let accessToken = gapi.auth.getToken().access_token;
data.access = accessToken;
// Put this updated object back into the database.
var requestUpdate = objectStore.put(data);
requestUpdate.onerror = function (event) {
// Do something with the error
};
requestUpdate.onsuccess = function (event) {
// Success - the data is updated!
// Call SW Function
};
}
}
}
}
}
//////////////////////////////////////////////////////////////
//////////////// Initialize Database Function ///////////////
//////////////////////////////////////////////////////////////
uploadBtn.addEventListener("click", handleUploadClick, false);
save.addEventListener("click", handleUploadClick, false);
//Adds/Create Data that is stored in IndexedDB so that the Service Worker can
access and use it
//ServiceWorker Call Function
function initSW() {
console.log("Script: Called InitSW()");
if ("serviceWorker" in navigator) {
navigator.serviceWorker
.register("serviceWorker.js")
.then((registration) => navigator.serviceWorker.ready)
.then((registration) => {
registration.sync.register("sendFile-sync").then(() => {
//Do Function using sync
try {
console.log("Script : Sync Registered");
} catch {
console.log("Script : Sync Not Registered");
}
});
});
}
}
SW
self.addEventListener("sync", (e) => {
if (e.tag === "sendFile-sync") {
console.log("SW Sync : Sync Found!");
e.waitUntil(fetchFile());
} else {
console.log("SW Sync : No Sync Found");
}
});
//Function Called above when sync is fired
function fetchFile() {
let openRequest = indexedDB.open("GoogleDrive", 1);
openRequest.onerror = function () {
};
openRequest.onsuccess = function () {
let db = openRequest.result;
let transaction = db.transaction(["backups"], 'readwrite');
let backups = transaction.objectStore("backups");
let request = backups.get("1");
request.onsuccess = function (event) {
let date = Date();
let accessToken = request.result.access;
console.log("SW Sync: Access Token - " + accessToken);
let BlobContent = request.result.text;
let file = BlobContent;
let metadata = {
name: "Backup " + date, // Filename
mimeType: "application/pdf", // mimeType at Google Drive
parents: ["root"], // Root Folder ID for testing
};
let form = new FormData();
form.append(
"metadata",
new Blob([JSON.stringify(metadata)], { type: "application/json" })
);
form.append("file", file);
fetch(
"https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&fields=id",
{
method: "POST",
headers: new Headers({ Authorization: "Bearer " + accessToken }),
body: form,
}
)
.then((res) => {
return res.json();
})
.then(function (val) {
console.log(val.error.message);
<!-- message is "invalid credentials --> in our scenario
}
})
}
request.onerror = function () {
console.log("SW Sync : Getting IndexedDB values error");
};
};
}
Sure, the ServiceWorker's clients are exposed in its self.clients property, from where you can find the correct Client with which you can communicate thanks to its postMessage() method.
How to find the correct client will depend on the situation, for instance in the install or activate events there should be only one client, so you should be able to reach it by doing
const clients = await self.clients.matchAll();
const client = clients[0];
client.postMessage("something bad happenned...");
In a fetch event, the clientId is exposed on the event instance, so you can do
const client = await self.clients.get(evt.clientId);
client.postMessage("something bad happenned...");
I must admit I don't know well the BackgroundSync API, so I'm not sure if in this sync event your page would be the only one client, however, you can certainly make your page open a private communication channel with the SW even before, which by the way, sounds like a better mean of passing your API's credentials than through IDB:
const channel = new MessageChannel();
channel.port1.onmessage = SWTalksToMe; // handle messages from SW
navigator.serviceWorker
.register("serviceWorker.js")
.then((registration) => navigator.serviceWorker.ready)
.then((registration) => {
registration.postMessage("", [channel.port2]));
return registration.sync.register("sendFile-sync")
})
//...
And in your ServiceWorker
self.addEventListener("message", evt => {
if(evt.ports) {
client_port = evt.ports[0];
}
});
Finally, if you wanted to communicate with all the clients, you could use a BroadcastChannel.
What I want to do is make a link that when clicked opens Google's Invisible Recaptcha. That part is working fine. But I also want to catch errors, and that's where it fails. It is failing in both the latest versions of Firefox and Chrome with an error that says, "uncaught exception: undefined" (Firefox) "uncaught (in promise) undefined" (Chrome) and traces back to the line where reject() is called. The call to alert('Error') never fires.
What am I doing wrong? Is there another way to do this?
<script>
var onloadCallback, onerrorCallback;
var promise = new Promise(function(resolve, reject) {
onerrorCallback = function() {
reject();
}
onloadCallback = function() {
var form = document.createElement('form'),
recaptcha = document.createElement('div');
form.method = 'post';
resolve(grecaptcha.render(recaptcha, {
sitekey: 'INVISIBLE RECAPTCHA SITE KEY',
size: 'invisible',
callback: function() {
form.submit()
}
}));
form.appendChild(recaptcha);
document.body.appendChild(form);
}
})
function userClick() {
promise
.then(grecaptcha.execute)
.catch(function(){alert('Error')});
}
</script>
<script async defer src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit&hl=en" onerror="onerrorCallback()"></script>
<p>Please click here to solve the recaptcha</p>
I found the problem. Here is how I fixed it:
<script>
var onloadCallback, onerrorCallback;
var promise = new Promise(function(resolve, reject) {
onerrorCallback = function() {
reject();
}
onloadCallback = function() {
var form = document.createElement('form'),
recaptcha = document.createElement('div');
form.method = 'post';
resolve(grecaptcha.render(recaptcha, {
sitekey: 'INVISIBLE RECAPTCHA SITE KEY',
size: 'invisible',
callback: function() {
form.submit()
}
}));
form.appendChild(recaptcha);
document.body.appendChild(form);
}
})
function userClick() {
promise
.then(function(id) {
grecaptcha.execute(id);
})
.catch(function(){alert('Error')});
}
</script>
<script async defer src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit&hl=en" onerror="onerrorCallback()"></script>
<p>Please click here to solve the recaptcha</p>
The problem was in the then() call. Due to grecaptcha being not defined, JavaScript halted and never made it to the catch() call. The error message printed on the console had nothing to do with that though.
I fixed it by wrapping the call to grecaptcha.execute in an anonymous function.
I wrote a class in EcmaScript 2015 which solves the outdated and unflexible Google reCAPTCHA code for me.
Usage - init and render:
const captchaAdapter = new CaptchaAdapter(recaptchaSiteKey);
captchaAdapter.renderRc(document.getElementById('newsletter-overlay-recaptcha'));
Usage - check / execute
const chekcCaptcha = captchaAdapter.triggerRcCheck();
chekcCaptcha.then((token) => {
submitNewsletter(token);
});
Class:
/**
* Invisible reCAPTCHA v2 with promises
*/
export default class CaptchaAdapter
{
constructor(recaptchaSiteKey) {
this.recaptchaSiteKey = recaptchaSiteKey;
}
/**
*
* #return {Promise<event>}
*/
loadRcScript()
{
if (window.loadingRecaptchaScript) {
return window.loadingRecaptchaScript;
}
window.loadingRecaptchaScript = new Promise((resolve) => {
const script = document.createElement('script');
script.src = 'https://www.google.com/recaptcha/api.js?render=' + this.recaptchaSiteKey;
script.addEventListener('load', (event) => {
window.grecaptcha.ready(() => {
resolve(event);
});
});
document.body.appendChild(script);
});
return window.loadingRecaptchaScript;
}
async renderRc(container)
{
this.container = container;
this.checkCallback = (token) => {
this.resolveCheck(token);
};
this.loadRcScript().then(() => {
const parameters = {
sitekey: this.recaptchaSiteKey,
callback: this.checkCallback,
size: 'invisible',
};
this.widgetId = window.grecaptcha.render(container, parameters);
});
}
fixRcPosition()
{
setTimeout(() => {
const currentPosition = document.documentElement.scrollTop || document.body.scrollTop;
document.querySelectorAll('body>div[style*="top:"]').forEach((captchaContainer) => {
captchaContainer.style.top = currentPosition + 'px';
});
}, 0.3 * 1000);
}
/**
* #return {Promise<token>}
*/
triggerRcCheck()
{
this.fixRcPosition();
return new Promise((resolve) => {
this.resolveCheck = resolve;
window.grecaptcha.ready(() => {
window.grecaptcha.execute(this.widgetId, { action: 'submit' });
});
});
}
}
I have a code in which the "onloadend" runs after the "get" task, and the code would work the right way if "get" ran first. So i need a way to switch them. I must use HttpClient and JavaScript, I cannot use jQuery or anything else.
Here is my full code in CodePen: https://codepen.io/vargaadam19/pen/BaabVbZ?editors=0010
Here is the onloadend:
export class HttpClient {
constructor(url) {
this.url = url;
this.xhr = new XMLHttpRequest();
this.xhr.onloadend = (event) => {
return this.xhr.result;
};
}
I set header here:
setHeader() {
this.xhr.setRequestHeader('Content-Type','application/json');
}
and here is the get function:
get(async, header) {
return new Promise((resolve, reject) => {
this.xhr.open('GET', this.url, async);
this.xhr.setRequestHeader(header.name, header.value);
this.xhr.send();
resolve(this.xhr.response);`enter code here`
});
}
And here is the function that's what I invite the get function
getAll() {
this.httpClient.get(true, this.header).then((result) => {
const data = !!result ? result : '[]';
const items = JSON.parse(data);
console.log('result:', result);
items.forEach(item => {
const todo = new Todo(item.id, item.name, item.status);
this.items.push(todo);
});
});
}
How can I solve this problem about the order of the tasks?
I have 2 files
1. api_service.ts
import { HttpClient } from 'aurelia-http-client';
import { autoinject } from "aurelia-framework";
import { ObserverLocator } from "aurelia-binding";
#autoinject()
export class ApiServices {
private app_url = 'http://example.com/';
private host = 'http://api.example.com:8080';
constructor(private store: Store, private client: HttpClient, private observerLocator: ObserverLocator) { }
GetInstallationId() {
let url = this.host + "/1/api/iid";
let iid;
this.client.get(url)
.catch((err) => { })
.then(res => {
let response = JSON.parse(res.response);
iid = response.iid;
});
return iid;
}
}
here I am trying to get some value from the server and in the HTTP response, I can see that I am getting it.
board_services.ts
import { Store } from './localstorage_service';
import { ApiServices } from './api_service';
import { autoinject } from "aurelia-framework";
import { ObserverLocator } from "aurelia-binding";
#autoinject()
export class AppServices {
private installation_ID: string;
constructor(private store: Store, private apiServices: ApiServices, private observerLocator: ObserverLocator) { }
getInstallationID() {
const iid = this.store.getValue('installation_ID');
console.log("iid = " + iid);
if (iid && iid != undefined) {
console.log('inside if iid');
// this.installation_ID = this.apiServices.GetInstallationId();
this.installation_ID = iid;
} else {
console.log('inside else iid');
this.installation_ID = this.apiServices.GetInstallationId();
}
this.store.setValue('installation_ID', this.installation_ID);
console.log("after if condition iid = " + iid);
return this.installation_ID;
}
}
here in this code I'm calling the function GetInstallationId() from api_services.ts but it is not waiting for the http request to complete. Is there a way to make the statement in the next statement execute after the GetInstallationId() is executed?
The short answer: you need to support asynchronous behavior.
You may do this via (a) Promises and/or (b) async/await. The former is supported in all browsers except IE 11 and there are polyfills like this one if you need to support IE 11. The latter is currently not supported in IE at all and as it requires new JS keywords/syntax must be used with a transpiler like Babel.
This answer will use Promises as they have fewer dependencies than async/await. The snippets below have been updated to use Promises -- look for the comments like:
// REPLACED THIS LINE ... // WITH THIS LINE
and
// ADDED THIS LINE
which identify the updates.
GetInstallationId is an asynchronous function and must return a promise.
class ApiServices {
...
GetInstallationId() {
let url = this.host + "/1/api/iid";
return new Promise(function(accept, reject) {
let iid;
this.client.get(url)
.catch((err) => { })
.then(res => {
let response = JSON.parse(res.response);
// REPLACED THIS LINE
// iid = response.iid;
// WITH THIS LINE which accepts the promise
accept(response.iid);
})
// ADDED THIS LINE to catch any errors
// e.g. when invalid response received
.catch(reject);
});
}
}
Because getInstallationID calls GetInstallationId, it too must return a promise. This example defines a separate function, handler, which is then bound via .bind(this) to ensure that whenever the function is executed, this will always refer back to the correct ApiService object.
class AppServices {
...
getInstallationID() {
function handler(accept, reject) {
const iid = this.store.getValue('installation_ID');
console.log("iid = " + iid);
if (iid && iid != undefined) {
console.log('inside if iid');
// this.installation_ID = this.apiServices.GetInstallationId();
this.installation_ID = iid;
} else {
console.log('inside else iid');
// REPLACED THIS LINE
// this.installation_ID = this.apiServices.GetInstallationId();
// WITH THIS LINE
this.apiServices.GetInstallationId().then(accept,reject);
}
this.store.setValue('installation_ID', this.installation_ID);
console.log("after if condition iid = " + iid);
// REPLACED THIS LINE
// return this.installation_ID;
// WITH THIS LINE
accept(this.installation_ID);
};
return new Promise(handler.bind(this));
}
...
}
The above function is just one example which will work cross browser. Another solution which requires ES6 support and/or transpilation is to use arrow functions:
//
// Arrow function version
//
class AppServices {
...
getInstallationID() {
// NOTE the => which is shorthand for `(function (accept,reject) {...}).bind(this)`
return new Promise((accept, reject) => {
const iid = this.store.getValue('installation_ID');
console.log("iid = " + iid);
if (iid && iid != undefined) {
console.log('inside if iid');
// this.installation_ID = this.apiServices.GetInstallationId();
this.installation_ID = iid;
} else {
console.log('inside else iid');
// REPLACED THIS LINE
// this.installation_ID = this.apiServices.GetInstallationId();
// WITH THIS LINE
this.apiServices.GetInstallationId().then(accept,reject);
}
this.store.setValue('installation_ID', this.installation_ID);
console.log("after if condition iid = " + iid);
// REPLACED THIS LINE
// return this.installation_ID;
// WITH THIS LINE
accept(this.installation_ID);
});
}
...
}
With the above refactoring, you can now call getInstallationID() from e.g. another view model like this:
import {AppServices} from '...';
import {autoinject} from 'aurelia-framework';
#autoinject()
export class SomeViewModel {
constructor(appServices) {
this.appServices = appServices;
}
callGetInstallationId() {
this.appServices.getInstallationID().then(function(id) {
// the ID that is passed here is the one that is `accept`ed.
// ie. if AppServices.getInstallationID calls accept('123'), then
// the '123' will be passed to this function as the first argument
}, function(e) {
// this function will be called if AppServices.getInstallationID
// calls reject. e will be the Error object, if any, that is passed
// to the reject method.
});
}
}
var e = require("./myApp.js");
var myServer = e.CreateServer(1337);
myServer.Register("/", "GET", function (req, res) { res.end("J") });
myServer.Register("/", "GET", function (req, res) { res.end("Ja") });
myServer.Start();
This is my "Wrapper":
module.exports = (function () {
function _createServer(port) {
var routingTable = [];
var port = port;
var server = require('http').createServer();
function _start() {
server.listen(port);
console.log("Server was started");
};
function RegisterRecord(url, method, fnc) {
this.url = url;
this.method = method;
this.fnc = fnc;
};
function _register(newUrl, newMethod, newFnc) {
if (_checkInput(newUrl, newMethod))
console.log("Register failed! Record with same URL and Method already exist");
else {
routingTable.push(new RegisterRecord(newUrl, newMethod, newFnc));
console.log("Register success!");
}
};
function _checkInput(newUrl, newMethod) {
return routingTable.some(function fnc(record) { record.url == newUrl && record.method == newMethod });
};
return {
Start: _start,
Register: _register,
ShutDown: _shutDown
};
};
return { CreateServer: _createServer };
})();
So the most important functions are "_register" and "checkInput".
My aim is that the same URL and Method are only allowed on time in the array routingTable. So when I execute the programm, the Command Promp prints two times Register success. But "/" and "GET" should only be allowed one time.
How can I compare the URL and method so that they can be unique?
PS: The "Wrapper" is in the JS File "./MyApp.js"
You need filter:
function _checkInput(newUrl, newMethod) {
return routingTable
.filter( function(el) {
return el.url === newUrl && el.method === newMethod;
})
.length > 0;
};
Upd. Of course, you can use the some - you just forgot to return a value from it:
function _checkInput(newUrl, newMethod) {
return routingTable
.some( function(el) {
// Need return
return el.url === newUrl && el.method === newMethod;
})
};