Override/Intercept XMLHttpRequest response in all browsers - xmlhttprequest

What do I want to achieve ?
I want to intercept the XMLHttpRequest and modify the response for some particular requests. (For ex. decrypt content and assign it to back response)
What I have done so far ?
Below code intercepts the request and modifies the response. It works in all browsers (Chrome, Firefox, Opera, Edge) except IE 11.
const dummySend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function () {
const _onreadystatechange = this.onreadystatechange;
this.onreadystatechange = function () {
if (this.readyState === 4) {
if (this.status === 200 || this.status === 1223) {
// as response is read-only and configurable, make it writable
Object.defineProperty(this, 'response', {writable: true});
this.response = modifyResponse(this.response);
}
}
if (_onreadystatechange) {
_onreadystatechange.apply(this, arguments);
}
}
dummySend.apply(__self, arguments);
}
What is the Issue ?
All of that doesn't work only in IE 11, The Error thrown is 'TypeError: Assignment to read-only property is not allowed in strict mode'.
Can someone please help me with this ?

I could do it the other way, which is to have a dummy XMLHttpRequest object exposed to the original requester and then handle the actual XMLHttpRequest yourself. Please read code for more clarity.
let oldXMLHttpRequest = window.XMLHttpRequest;
// define constructor for XMLHttpRequest proxy object
window.XMLHttpRequest = function() {
let _originalXhr = new oldXMLHttpRequest();
let _dummyXhr = this;
function decryptResponse(actualResponse) {
return base64Decrypted = decrypt(response, secret);
}
_dummyXhr.response = null;
// expose dummy open
_dummyXhr.open = function () {
const _arguments = [].slice.call(arguments);
// do any url modifications here before request open
_dummyXhr._url = _arguments[1];
return _originalXhr.open.apply(_originalXhr, _arguments);
};
// expose dummy send
_dummyXhr.send = function () {
let _onreadystatechange = _dummyXhr.onreadystatechange;
_originalXhr.onreadystatechange = function() {
if (this.readyState === 4 && (this.status === 200 || this.status === 1223)) {
_dummyXhr.response = decryptResponse(this.response);
}
// call callback that was assigned on our object
if (_onreadystatechange) {
_onreadystatechange.apply(_dummyXhr, arguments);
}
}
_originalXhr.send.apply(_originalXhr, arguments);
};
// iterate all properties in _originalXhr to proxy them according to their type
// For functions, we call _originalXhr and return the result
// For non-functions, we make getters/setters
// If the property already exists on _dummyXhr, then don't proxy it
for (let prop in _originalXhr) {
// skip properties we already have - this will skip both the above defined properties
// that we don't want to proxy and skip properties on the prototype belonging to Object
if (!(prop in _dummyXhr)) {
// create closure to capture value of prop
(function(prop) {
if (typeof _originalXhr[prop] === "function") {
// define our own property that calls the same method on the _originalXhr
Object.defineProperty(_dummyXhr, prop, {
value: function() {return _originalXhr[prop].apply(_originalXhr, arguments);}
});
} else {
// define our own property that just gets or sets the same prop on the _originalXhr
Object.defineProperty(_dummyXhr, prop, {
get: function() {return _originalXhr[prop];},
set: function(val) {_originalXhr[prop] = val;}
});
}
})(prop);
}
}

Related

How to make pagination work? Async await function in vue.js 3 setup

I was trying to make an app which lists a user's repositories from github using github API, however I'm having a big problem with fetching data from all pages (so far I can only get repos from one page). I tried to fix it by using an async/await function (instead of Promise), but it's also my first time using vue3 and I have no idea how to have a function inside of the setup() method.
The current code is here:
https://github.com/agzpie/user_repos
My try at using async/await, which didn't work:
import ListElement from "./components/ListElement";
import { ref, reactive, toRefs, watchEffect, computed } from "vue";
export default {
name: "App",
components: {
ListElement,
},
setup() {
const name = ref(null);
const userName = ref(null);
const state = reactive({ data: [] });
let success = ref(null);
const userNameValidator = /^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}$/i;
const split1 = reactive({ spl1: [] });
const split2 = reactive({ spl2: [] });
async function myFetch() {};
/*
* Check for input in the form and then fetch data
*/
watchEffect(() => {
if (!userName.value) return;
if (!userNameValidator.test(userName.value)) {
console.log("Username has invalid characters");
return;
}
let hasNext = false;
state.data = [];
do {
async function myFetch() {
let url = `https://api.github.com/users/${userName.value}/repos?per_page=5`;
let response = await fetch(url);
if (!response.ok) {
success.value = false;
throw new Error(`HTTP error! status: ${response.status}`);
}
success.value = true;
// check response.headers for Link to get next page url
split1.spl1 = response.headers.get("Link").split(",");
let j = 0;
while (j < split1.spl1.length) {
split2.spl2[j] = split1.spl1[j].split(";");
console.log(split2.spl2[j][0]);
console.log(split2.spl2[j][1]);
if (split2.spl2[j][1].includes("next")) {
let urlNext = split2.spl2[j][0].replace(/[<>(\s)*]/g, "");
console.log(urlNext);
url = urlNext;
hasNext = true;
break;
} else {
hasNext = false;
}
j++;
}
// second .then
let myData = await response.json();
state.data.push(...myData);
console.log("data", myData);
name.value = "";
}
myFetch().catch((err) => {
if (err.status == 404) {
console.log("User not found");
} else {
console.log(err.message);
console.log("oh no (internet probably)!");
}
});
} while (hasNext);
});
// Sort list by star count
const orderedList = computed(() => {
if (state.data == 0) {
return [];
}
return [...state.data].sort((a, b) => {
return a.stargazers_count < b.stargazers_count ? 1 : -1;
});
});
return {
myFetch,
success,
isActive: true,
name,
userName,
ListElement,
...toRefs(state),
orderedList,
};
},
};
Any help would be highly appreciated
The call to myFetch() near the end is a call to an async function without an await, so it is effectively going to loop (if hasNext was initialized to true, but it isn't) without waiting for it to complete.
You should probably change that line to await myFetch() and wrap it all with a try/catch block.
I also don't really care for the way you're directly updating state inside the async myFetch call (it could also be doing several of those if it looped) and perhaps it should be returning the data from myFetch instead, and then you can use let result = await myFetch() and then make use of that when it returns.
Also, instead of awaiting myFetch() result, you could not await it but push it onto a requests array and then use await Promise.all(requests) outside the loop and it is one operation to await, all requests running in parallel. In fact, it should probably be await Promise.allSettled(requests) in case one of them fails. See allSettled for more.
But also I wonder why you're reading it paged if the goal is to fetch them all anyway? To reduce load on the server? If that is true, issuing them paged but in parallel would probably increase the load since it will still read and return all the data but require multiple calls.

View js, listener for v-text-field without freeze ui

I have v-text-field
<v-text-field
name="name"
label="B/F/L Cost"
prefix="$"
#input="throttledSave"
v-model="$store.state.campaign.MyCost"
></v-text-field>
methods: {
save(val) {
console.log(val);
},
throttledSave(val) {
let DELAY = 5000;
return this.throttle(this.save(val), DELAY);
},
throttle(callback, limit) {
console.log("throttle");
var wait = false;
return function() {
if (!wait) {
console.log("throttle");
callback.call();
wait = true;
setTimeout(function() {
wait = false;
}, limit);
}
};
}
}
I need to add listener for text changed. When i have chenget the v-text-field my UI is freezing. How i can add listener to v-text-field without freezing?
"I think" there're some problems with your code:
You set your vue-x state directly to v-model v-model="$store.state.campaign.MyCost", it's bad practice
Your throttledSave method seems to not do what you want it to do, it calls save immediately and return the method throttle's execution (which executes nothing, then return a function without executing it anywhere).
The callback parameter of your throttle method actually got the return value of this.save instead of the function this.save itself, so it can't be called like callback.call(); (call is not a method of undefined)
I also suspect the logic of your throttle method
save(val) {
console.log(val); // this doesn't return a function, return undefined
},
throttledSave(val) {
let DELAY = 5000;
return this.throttle(this.save(val), DELAY); // you called this.save immediately, got undefined
},
throttle(callback, limit) {
console.log("throttle");
var wait = false;
return function() { // this function is returned, but will not be executed anywhere
if (!wait) {
console.log("throttle");
callback.call(); // callback is passed in throttledSave is not a function, it's the return result of a function (which this.save's is undefined)
wait = true;
setTimeout(function() { // I suspect the logic of your throttle
wait = false;
}, limit);
}
};
}
Solution: What you might want in throttledSave is:
computed: {
DELAY = () => 5000,
},
methods: {
throttledSave: this.throttle(function(val) { // make sure to write the throttle method correctly
this.save(val);
}, this.DELAY /* or 5000 directly instead */),
}
You might want to use lodash's throttle instead of writing one from scratch yourself.
import _ from 'lodash';
...
methods: {
save(val) {
console.log(val);
},
throttledSave: _.throttle(this.save, 5000),
// or:
throttledSave: _.throttle(function(val) {
console.log(val);
}, 5000),
}

TransactionInactiveError: Failed to execute 'get' on 'IDBObjectStore': The transaction is inactive or finished

This seems to be a Safari only bug. It does not occur in Chrome as far as I can tell. I have a very standard IndexedDB setup. I call initDb, save the result, and that provides me a nice way to make calls to the DB.
var initDb = function() {
// Setup DB. whenDB is a promise we use before executing any DB requests so we know the DB is fully set up.
parentDb = null;
var whenDb = new Promise(function(resolve, reject) {
var DBOpenRequest = window.indexedDB.open('groceries');
DBOpenRequest.onsuccess = function(event) {
parentDb = DBOpenRequest.result;
resolve();
};
DBOpenRequest.onupgradeneeded = function(event) {
var localDb = event.target.result;
localDb.createObjectStore('unique', {
keyPath: 'id'
});
};
});
// makeRequest needs to return an IndexedDB Request object.
// This function just wraps that in a promise.
var request = function(makeRequest, key) {
return new Promise(function(resolve, reject) {
var request = makeRequest();
request.onerror = function() {
reject('Request error');
};
request.onsuccess = function() {
if (request.result == undefined) {
reject(key + ' not found');
} else {
resolve(request.result);
}
};
});
};
// Open a very typical transaction
var transact = function(type, storeName) {
// Make sure DB is set up, then open transaction
return whenDb.then(function() {
var transaction = parentDb.transaction([storeName], type);
transaction.oncomplete = function(event) {
console.log('transcomplete')
};
transaction.onerror = function(event) {
console.log('Transaction not opened due to error: ' + transaction.error);
};
return transaction.objectStore(storeName);
});
};
// Shortcut function to open transaction and return standard Javascript promise that waits for DB query to finish
var read = function(storeName, key) {
return transact('readonly', storeName).then(function(transactionStore) {
return request(function() {
return transactionStore.get(key);
}, key);
});
};
// A test function that combines the previous transaction, request and read functions into one.
var test = function() {
return whenDb.then(function() {
var transaction = parentDb.transaction(['unique'], 'readonly');
transaction.oncomplete = function(event) {
console.log('transcomplete')
};
transaction.onerror = function(event) {
console.log('Transaction not opened due to error: ' + transaction.error);
};
var store = transaction.objectStore('unique');
return new Promise(function(resolve, reject) {
var request = store.get('groceryList');
request.onerror = function() {
console.log(request.error);
reject('Request error');
};
request.onsuccess = function() {
if (request.result == undefined) {
reject(key + ' not found');
} else {
resolve(request.result);
}
};
});
});
};
// Return an object for db interactions
return {
read: read,
test: test
};
};
var db = initDb();
When I call db.read('unique', 'test') in Safari I get the error:
TransactionInactiveError: Failed to execute 'get' on 'IDBObjectStore': The transaction is inactive or finished
The same call in Chrome gives no error, just the expected promise returns. Oddly enough, calling the db.test function in Safari works as expected as well. It literally seems to be that the separation of work into two functions in Safari is somehow causing this error.
In all cases transcomplete is logged AFTER either the error is thrown (in the case of the Safari bug) or the proper value is returned (as should happen). So the transaction has NOT closed before the error saying the transaction is inactive or finished is thrown.
Having a hard time tracking down the issue here.
Hmm, not confident in my answer, but my first guess is the pause that occurs between creating the transaction and starting a request allows the transaction to timeout and become inactive because it finds no requests active, such that a later request that does try to start is started on an inactive transaction. This can easily be solved by starting requests in the same epoch of the javascript event loop (the same tick) instead of deferring the start of a request.
The error is most likely in these lines:
var store = transaction.objectStore('unique');
return new Promise(function(resolve, reject) {
var request = store.get('groceryList');
You need to create the request immediately to avoid this error:
var store = transaction.objectStore('unique');
var request = store.get('groceryList');
One way to solve this might be simply to approach the code differently. Promises are intended to be composable. Code that uses promises generally wants to return control to the caller, so that the caller can control the flow. Some of your functions as they are currently written violate this design pattern. It is possible that by simply using a more appropriate design pattern, you will not run into this error, or at least you will be able to identify the problem more readily.
An additional point would be your mixed use of global variables. Variables like parentDb and db are just going to potentially cause problems on certain platforms unless you really are an expert at async code.
For example, start with a simple connect or open function that resolves to an open IDBDatabase variable.
function connect(name) {
return new Promise(function(resolve, reject) {
var openRequest = indexedDB.open(name);
openRequest.onsuccess = function() {
var db = openRequest.result;
resolve(db);
};
});
}
This will let you easily compose an open promise together with code that should run after it, like this:
connect('groceries').then(function(db) {
// do stuff with db here
});
Next, use a promise to encapsulate an operation. This is not a promise per request. Pass along the db variable instead of using a global one.
function getGroceryList(db, listId) {
return new Promise(function(resolve, reject) {
var txn = db.transaction('unique');
var store = txn.objectStore('unique');
var request = store.get(listId);
request.onsuccess = function() {
var list = request.result;
resolve(list);
};
request.onerror = function() {
reject(request.error);
};
});
}
Then compose it all together
connect().then(function(db) {
return getGroceryList(db, 'asdf');
}).catch(error);

How to return a value from a React Native functional component to the parent

In the case of this functional component, how can I return a value to another component that calls the function?
/**
* A custom fetch wrapper.
*/
import Config from '../config'
const _Fetch = (context: Object, endpoint: string, init: Object, key?: string) => {
var k = (typeof key !== 'undefined') ? key : 'data';
return fetch(Config.base+endpoint, init)
.then(response => {
if (response.ok) {
response.text().then((text) => {
var obj = tryParseJSON(text)
if (obj) {
// For jsonapi.
if (obj.data) {
obj = obj.data
}
var s = {
data: Object
}
s[k] = obj
context.setState(s)
return s // This value exists here, but how can I return it to the _Fetch caller?
}
})
.catch((error) => {
console.error(error)
})
}
})
function tryParseJSON(jsonString) {
try {
var o = JSON.parse(jsonString)
if (o && typeof o === "object") {
return o
}
}
catch (e) {
console.log(e)
}
return false
}
}
export default _Fetch
When I try to get the value in another function, it is undefined:
var myVar = _Fetch(context, end, init, key)
console.log(myVar); // undefined
Similar questions that don't quite help in my case:
React - Can A Child Component Send Value Back To Parent Form
How to return values from function - React native
The correct way to solve this problem would be to use Redux or some other store manager. If you are using redux then you can update store with new values and it will be available to all the components. This way, your parent can access it directly.
fetch operation is async and it can not directly return data to the invoking method.
If you don't want to use redux then a very standard javascript solution will be using callback.
You can pass a callback method to your fetch function. In your code, you can replace return s with a call to callback method.
e.g. replace return s with callback(s).

How to broadcast to other controllers when load with module.config or .run in Angularjs

I have a checking when reading the web page,then using the result to refresh sidebar by ng-repeat,but I have errors :
Uncaught Error: Unknown provider: $scope from myModule or
Uncaught Error: Unknown provider: $scope from sharedService
How can I resolve it?
Here is my code
module:
var myModule = angular.module('myModule', []);
service for broadcast:
myModule.factory('mySharedService', function($rootScope) { //service
var sharedService = {};
sharedService.keyHistory = [];
sharedService.linkHistory = [];
sharedService.prepForBroadcast = function(key,link) {
this.keyHistory = key;
this.linkHistory = link;
this.broadcastItem();
};
sharedService.prepForBroadcastAdd =function(key){
console.log(this.keyHistory.push(key));
//this.linkHistory = linkHistory+link;
this.broadcastItem();
};
sharedService.broadcastItem = function() {
$rootScope.$broadcast('handleBroadcast');
};
return sharedService;
});
config to do Checking:
myModule.config(function($scope,sharedService){
$.ajax({
url:"/fly/AJAX",
type:"POST",
contentType:'application/x-www-form-urlencoded; charset=UTF-8',
datatype:"json",
success:function(data){
if(data!=null){
var loginResult = $.parseJSON(data);
if (loginResult.success == true){
console.log("login success");
$("#userLable").html(loginResult.userName+'('+loginResult.loginID+')');//
if (loginResult.hasHistory==true) {
sharedService.prepForBroadcast(loginResult.searchHistory,[]);
console.log("broadcast");
}
};
}
}
});
});
SideCtrl:
function SideCtrl($scope,sharedService) {
$scope.$on('handleBroadcast', function() {
$scope.keyHistory =sharedService.keyHistory;
$scope.linkHistory = sharedService.linkHistory;
});
}
SideCtrl.$inject = ['$scope', 'mySharedService'];
THX !
The error is due to trying to request a $scope in a config block, which you can't do. If I understand what you're trying to do, then I also think you're over-complicating it. I'd solve the problem a little differently. The details would depend on your requirements and use case, but based on the information you gave...
I'd have a service responsible for communication with the server and storing the state:
app.factory( 'loginService', function ( $http ) {
var result;
function doRequest( data ) {
// just flesh out this post request to suit your needs...
return $http.post( '/fly/ajax', data, {} )
.then( function ( response ) {
// assuming you don't care about the headers, etc.
return response.data;
});
}
// Do it once initially
if ( ! angular.isDefined( result ) ) {
result = doRequest();
}
// return the service's public API
return {
getStatus: function () { return result; },
login: doRequest
};
});
Now the first time this service is requested, the $http request will be made. If you're accessing this from multiple controllers, the post will only occur once because of the isDefined statement. You can then use this in your controllers:
app.controller( 'MainCtrl', function( $scope, loginService ) {
loginService.getStatus().then( function ( data ) {
// do whatever you need to with your data.
// it is only guaranteed to exist as of now, because $http returns a promise
});
});
Every controller accesses it the same way, but it was still only called once! You can set values against the scope and access it from your views, if you want:
app.controller( 'MainCtrl', function( $scope, loginService ) {
loginService.getStatus().then( function ( data ) {
$scope.loginId = data.loginID;
});
});
And in your view:
<h1>Welcome, {{loginId || 'guest'}}!</h1>
And if you need to, you call the function again:
app.controller( 'MainCtrl', function( $scope, loginService ) {
// ...
loginService.login( $scope.user ).then( function ( data ) {
$scope.loginId = data.loginID;
});
// ...
});
As you can see, broadcasting an event is totally unnecessary.
I would do it differently. I would create some sort of more top-level controller, like function MainController($rootScope, $scope, sharedService) and wire it up with body: <body ng-controller='mainController' ng-init='init()'. After that you should create init() method in MainController.
Inside this initialization method I would call sharedService which should make AJAX request (via $http! that's the best practice, and it's very similar to jQuery) and broadcast proper event when required.
That way you make sure to call initialization just once (when MainController is initializing), you stick to the angular's best practices and avoid dodgy looking code.