API requests as a result of changes to domain objects - reactjs

I'm having trouble wrapping my head around how to handle api calls as a result
of updates to data only the store should know how to perform (business logic).
The basic flow is as follows:
AdComponent#_changeGeoTargeting calls the action creator
UnpaidIntents#updateTargeting which dispatches an
ActionTypes.UPDATE_TARGETS action which is handled like so:
AdStore.dispatchToken = Dispatcher.register(action => {
switch(action.type) {
case ActionTypes.UPDATE_TARGETS:
// Business logic to update targeting from an action payload.
// `payload` is an object, e.g. `{ geos: geos }`, `{ devices: devices }`,
// etc.
_unpaid.targeting = _calcTargeting(
_unpaid.targeting, action.payload);
// Ajax call to fetch inventory based on `Ad`s parameters
WebAPIUtils.fetchInventoryPredictions(
_unpaid.start, _unpaid.end, _unpaid.targeting)
.then((resp) => {
var key = _makeCacheKey(
_unpaid.start, _unpaid.end, _unpaid.targeting);
// Updates store's inventory cache
_updateInventoryCache(key, resp);
})
.catch((error) => {
// How can I handle this error? If this request
// was executed inside an action I could have my
// `NotiticationsStore` listening for
// `ActionTypes.INVENTORY_PREDICTIONS_ERROR`
// and updating itself, but I can't dispatch here.
});
break;
default:
return true;
}
AdStore.emitChange();
return true;
});
The problem being that this call can't dispatch other actions since it's in a
store.
I could make the call in the action creator, but that requires it to know how
to update the Ad. I was under the impression that action creators should
be dumb "dispatcher helper methods", and something like this would violate those
principles:
UnpaidIntents.updateTargeting = (ad, value) => {
var targeting = _calcTargeting(ad.targeting, value);
WebAPIUtils.fetchInventoryPredictions(ad.start, ad.end, targeting)
.then((resp) => {
Dispatcher.dispatch({
type: ActionTypes.UPDATE_TARGETING,
payload: {
targeting: targeting,
inventory: resp,
},
});
})
.catch((error) => {
Dispatcher.dispatch({
type: ActionTypes.INVENTORY_PREDICTIONS_ERROR,
payload: error,
});
});
};
Would breaking out _calcTargeting into an AdUtils module and using that as
my business logic layer be the way to do this? I'm afaid if I have business
logic in utils and possibly also stores that things will get messy very quickly.
Can anyone give some guidance here?

Related

UseEffect/useCallback is not triggering on very fast changes

I'm a Backend Dev and having limited knowledge in React still have to fix the problem
My project uses WebRTC for video calls. For signaling I'm using SignalR on my .NET backend.
On the frontend I have 2 classes:
signalRContext.tsx which holds an instance of HubConnection and listeners, onmessage is the relevant one.
const [currentSignal, setCurrentSignal] = useState<TCurrentSignal>
(InitialSignalR.currentSignal);
const initializeSignalListeners = (connection: HubConnection): void => {
console.log('START SIGNAL_R', connection);
connection.on('master', function (RoundInfo: IRoundInfo) {
console.log('MASTER', RoundInfo);
setCurrentSignal({ type: 'master', payload: RoundInfo });
});
connection.on('slave', function (RoundInfo: IRoundInfo) {
console.log('SLAVE', RoundInfo);
setCurrentSignal({ type: 'slave', payload: RoundInfo });
});
connection.on('message', function (message: TSignalRMessage) {
console.log('MESSAGE', message);
setCurrentSignal({ type: 'message', payload: message });
});
connection.on('endround', (payload) => {
console.log('END_ROUND');
setCurrentSignal({ type: 'endround', payload });
});
useRTCPeerConnection.ts which has the whole WebRtc relevant logic
import { useSignalRContext } from '../../../../core/contexts';
const {
signalrRRef,
currentSignal: { type, payload },
} = useSignalRContext();
useCallback(() => { //tried UseEffect as well
if (type === 'message') {
console.log('PAYLOAD', payload);
onMessage(payload as TSignalRMessage);
return;
}
}, [type, payload]);
My problem starts when WebRTC starts exchanging the ICE candidates and sends them sometimes twice per millisecond (see the last column).
The connection.on('message'... listener seems to be fast enough, I'm seeing all console.log('MESSAGE'... outputs in the console.
My problem is that the useCallback/useEffect logic is not firing on every payload change, like for 20 MESSAGE outputs I'm seeing 4-7 PAYLOAD outputs.
My assumption is that useEffect is simply not designed for such quick changes.
Is there any other concept better suitable to solve this problem or any improvement I could do here? Thinking on .NET I would just use the composition pattern and call the relevant method from peer connection class within the event handler in signalR class but not sure how to fix it here.
P.S. I've tried to wait until ICE candidates are gathered and sending them at once but the performance becomes not acceptable.

how intercept and stub the response of a rpc call in react with cypress

I want to intercept a rpc call that I made to the api in my react app. I'm using a custom hook that receives the buffer and the rpc method that I want to call and returns the data(something like react-query useQuery hook).
The thing is because of being a rpc call, the request urls of my requests are all the same and the response is binary, I can't distinguish the requests and intercept the one to stub.
One example of making a rpc call:
const {response, loading, error} = useRpc({
Buffer: GetUser,
Request: GetUserRequest
});
Edit 1:
I'm using
cy.fixture('fixutre-file').then((data) => {
const response = new TextDecoder().decode(res.body);
cy.intercept('https://example.com/', { method: 'POST' },
(req) => {
req.continue((res) => {
if ("some condition for distinguishing the request I want to intercept, here") {
res.send({ fixture: 'fixutre-file' });
}
});
});
}):
to get the response and decide whether or not intercept this req and instead send back my fixture data. But the response constant is still some unreadable string. What's wrong with my approach?
Edit 2:
Another approach that I used, was to use the cypress-protobuf package and encode my fixture.json file with the related protobuffer file:
cy.fixture('fixutre-file').then((data) => {
cy.task('protobufEncode', {
fixtureBody: data,
message: 'the_message',
protoFilePath: './protobuf/protofile.proto'
}).then((encodedData) => {
cy.intercept('https://example.com/', { method: 'POST' },
(req) => {
/////// approach 1(didn't work): ///////
// can't use this approach(because there is no identifier on
// req object to distinguish the requests I want to
// intercept)
// if ("some condition based on `req` here") {
// req.reply(encodedData);
// } else {
// req.continue();
// }
/////// approach 2: ///////
// using encodedData to compare it with res.body
req.continue(res => {
// can't compare res.body with encodedData, because
// encodedData is an empty string!
});
}).as('needToWait');
cy.wait('#needToWait').get('some selector').should('exist')
});
}):
Now the problem is:
encodedData is just an empty string, meaning it didn't work, so I can't compare the response with my fixture data to intercept the related request
You can simply check for some value from the request that distinguishes it from the other requests. Request bodies and headers are often good places to start. Additionally, you can use req.alias to conditionally assign an alias if you need to wait for that specific call.
cy.intercept('/foo', (req) => {
if (req.body.bar === true) { // or whatever logic indicates the call you want to intercept
req.alias = 'baz'; // conditionally assign alias
req.reply({foo: 'bar'}); // mock response
} else {
req.continue(); // do not mock response
}
});
cy.get('something')
.click()
.wait('#baz'); // waits for your specific 'baz' call to happen.

SIP integration with call conference in JS

I am developing an Electron application with the integration of React.js as a front-end framework, which will be more like a calling application.
In that application-specific users can have multiple calls incoming, outgoing, mute | unmute calls, hold | unhold calls, etc.
For this functionality to be achieved we have our own sip server, and for integrating that SIP server, on the frontend we are using a library which is known as SIP.JS.
SIP.JS provides us mostly all the predefined functions to make a call, receive a call, mute, unmute, blind transfer, attended transfer, etc.
But when it comes to having a call conference, it doesn't have proper documentation for that.
SIP.JS specifies to us that we can use FreeSWITCH as well as ASTERISK in order to achieve the functionality, but with our specific requirements, no additional server needs to be integrated.
We have also referred to rfc documentation for the call conference, but no such progress was there.
So far what we did is:
Registered the userAgent
Code for Incoming call integrated
Code for outgoing calls integrated
multiple session handling is achieved, for multiple calls
mute | unmute, hold | unhold.
DTMF functionality
Blind Transfer, Attended Transfer
Ring all Devices
In this scenario of call conference, I guess we have to make changes in Incoming and outgoing session handling functions.
For registration and incoming call in context:
const getUAConfig = async (_extension, _name) => {
let alreadyLogin = '';
try {
alreadyLogin = 'yes';
if (alreadyLogin == 'yes') {
_displayname = _name;
_sipUsername = _extension;
_sipServer = 'SIP SERVER';
_sipPassword = 'SIP PASSWORD';
_wssServer = 'WSS SERVER;
const uri = UserAgent.makeURI('sip:' + _sipUsername + '#' + _sipServer);
const transportOptions = {
wsServers: 'WSS SERVER',
traceSip: true,
maxReconnectionAttempts: 1,
};
const userAgentOptions = {
uri: uri,
transportOptions: transportOptions,
userAgentString: 'App name',
authorizationPassword: _sipPassword,
sipExtension100rel: 'Supported',
sipExtensionReplaces: 'Supported',
register: true,
contactTransport: 'wss',
dtmfType: 'info',
displayName: _name,
sessionDescriptionHandlerFactoryOptions: {
peerConnectionOptions: {
rtcpMuxPolicy: 'negotiate',
iceCheckingTimeout: 1000,
iceTransportPolicy: 'all',
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
},
},
};
userAgent = await new UserAgent(userAgentOptions);
const registerOptions = {
extraContactHeaderParams: [],
};
registerer = await new Registerer(userAgent, registerOptions);
registerer.stateChange.addListener((newState) => {
});
userAgent.start().then(async () => {
console.log('Connected with WebSocket.');
// Send REGISTER
await registerer
.register()
.then((request) => {
console.log('Successfully sent REGISTER, object is here');
dispatch({
type: USER_REGISTERED,
payload: true,
});
})
.catch((error) => {
console.log('Failed to send REGISTER');
});
});
return { userAgent, registerer };
} else {
return null;
}
} catch (error) {
console.log(error.message + '');
return null;
}
};
Outgoing functionality:
const dilaerFun = (inputNumber, userAgentInfo) => {
var session;
var uri = UserAgent.makeURI(
`URI which we wanna call (sip number)`
);
session = new Inviter(userAgentInfo, uri);
session
.invite()
.then((request) => {
console.log('Successfully sent INVITE');
sessionInfoAdd(session);
session.stateChange.addListener(async (state) => {
switch (state) {
case 'Established':
setMissedStatus(null);
console.log('established outgoing....');
//outgoing call log-----
const mediaElement = document.getElementById(
`mediaElement${session._id}`
);
const remoteStream = new MediaStream();
session.sessionDescriptionHandler.peerConnection
.getReceivers()
.forEach((receiver) => {
if (receiver.track) {
remoteStream.addTrack(receiver.track);
}
});
mediaElement.srcObject = remoteStream;
mediaElement.play();
break;
case 'Terminated':
console.log('terminated');
dispatch({
type: DEMO_STATE,
payload: session._id,
});
break;
default:
break;
}
});
})
.catch((error) => {
console.error(' Failed to INVITE');
console.error(error.toString());
});
};
Array of sessions are maintained by:
const sessionInfoAdd = (session) => {
dispatch({
type: SESSION_STORE,
payload: session,
});
};
Variable in which all sessions are stored is:
sessionInfo:[]
NOTE: getUAConfig() is called as soon as the application is started.
dialerFun() is called when we want to dial a specific number.
sessionInfoAdd() is called in both getUAConfig and dialerFun, as they are codes for incoming and outgoing calls.
when sessionInfoAdd() is triggered, the particular session which we get in return is added in the sessionInfo (Array) for the maintenance of sessions.
SIP.JS is just a library so you will have to get the conference setup on the FreeSWITCH or Asterisk (FreeSWITCH is the better in my opinion)
Doing this is fairly straight forward, at your app level you need a way to get calls across to the box after checking the details like access ID and any auth you want to add, (like a PIN.)
Once you have that done, you can forward that to an extension specifically set for conferencing or have a dynamic conference setup by send from the app towards a specific gateway/dialplan to do this.
The FreeSWITCH software has a steep learning curve on it but this helped me when I was doing something similar: https://freeswitch.org/confluence/display/FREESWITCH/mod_conference
You can also code you own conf if you wish.

How to use common API for dispatching multiple action creators in redux?

I have 2 objects in my state and I call a common API for getting data by passing the id in the request.
Following is my fetch call for the first Section which fires receiveSectionA to update sectionA in my state.
export function fetchSection(sectionCode){
return(dispatch,getState,api)=>{
const endPoint = 'url/?sectionCode='+sectionCode
const method = 'GET'
const isAuth = true
const promise = api(endPoint,method,isAuth)
promise
.then(response =>
response.json().then(json => ({
status:response.status ,
json
})
))
.then(
({ status, json }) => {
if( status >= 200 && status < 300) {
const sectionDictionary = utils.convertSectionsToDictionary(camelizeKeys(json))
dispatch(receiveSectionA(sectionDictionary))
}
if (status >= 400 ) {
//throw error
}
},
err => {
console.log("error"+err);
}
);
}
}
Now I making a same call for sectionB which fires following :-
dispatch(receiveSectionB(sectionDictionary))
Now since the above fetch call is same is there any way I can make this generic. I feel there is too much of code repeat.
I was thinking about switch cases to dispatch different actions based on sectionCode but I have around 20 sections and I think the code will get really complicated.
Is there a better to handle this?
As I understand you are looking for a dynamic function call. You can use eval which may lead to unexpected result. So, use with caution.
Expecting you have two function as receiveSectionA and receiveSectionB;
export function fetchSection(sectionCode){
return(dispatch,getState,api)=>{
....................
))
.then(
({ status, json }) => {
if( status >= 200 && status < 300) {
const sectionDictionary = utils.convertSectionsToDictionary(camelizeKeys(json))
let funToCall = 'receiveSection' + sectionCode + '(sectionDictionary )'; // preparing function to call
dispatch(eval(funToCall)); // Calling Function
}
if (status >= 400 ) {
//throw error
}
},
err => {
console.log("error"+err);
}
);
}
}
Edit:
Read more about eval especially the Security Section-
https://www.nczonline.net/blog/2013/06/25/eval-isnt-evil-just-misunderstood/
After pondering over this a bit, clearly eval is not a good option.
I thought I would try one of the amazing things that Javascript supports which is passing functions as parameters.
While dispatching an action I pass the receiveActionCreator as callback.
so my fetch would now be:-
export function fetchSection(callback,sectionCode){
return(dispatch,getState,api)=>{
....................
))
.then(
({ status, json }) => {
if( status >= 200 && status < 300) {
const sectionDictionary = utils.convertSectionsToDictionary(camelizeKeys(json))
dispatch(callback(sectionDictionary)); // Calling Function
}
if (status >= 400 ) {
//throw error
}
},
err => {
console.log("error"+err);
}
);
}
}
Now I can pass multiple receivers from different points when I dispatch the action from different sections by avoiding code repetition.
I cannot see much of your code from reducer, but seems to me you have too much logic around actions. Usually action is only intent to modify app state, so think twice about shape of your app state and just provide minimal information in actions that your reducer can compute the state. Also if you have to transform json and check status code for each request why not doing it in api module, if you can't do anything about errors then you should not handle them, handle in api or reflect them in app state. App business logic always live in reducers. After this your action will look better and you will not worry about code repeat. Hope this helps you.
So after searching for this I finally found something which I read in redux-docs .
I used reusing reducer logic to handle repeated actions,state. This basically wraps a parent function around my reducer function to which I pass id as parameter and then create action types from my id dynamically.
Following code should really explain it clearly. Its from the docs itself.
function createCounterWithNamedType(counterName = '') {
return function counter(state = 0, action) {
switch (action.type) {
case `INCREMENT_${counterName}`:
return state + 1;
case `DECREMENT_${counterName}`:
return state - 1;
default:
return state;
}
}
}

Success / Error Reporting from Service with Events / Observables

Using some documentation I found online, I've written a service method for saving some data like this:
#Injectable
export class BrandService {
brands$: Observable<Brand[]>;
private _brandsObserver: Observer<Brand[]>;
private _dataStore: {
brands: Brand[]
};
constructor(private http: Http) {
this.brands$ = new Observable(observer => this._brandsObserver = observer).share();
this._dataStore = { brands: []};
}
saveBrand(brand: Brand) {
var headers = new Headers();
headers.append('Content-Type', 'application/json');
return this.http.post("http://localhost:8080/api/brands", JSON.stringify(brand), { headers: headers })
.map( (response: Response) => response.json()).subscribe(
data => {
this._dataStore.brands.push(data);
this._brandsObserver.next(this._dataStore.brands);
},
error => console.log("Could not create Brand"));
}
}
What this allows me to do is push updates to my collection of Brands and my table on the view will observe these changes and update automatically so I don't have to manually refresh it. All is well with the world.
My problem is that since I'm subscribing to the http.post in the service, my component now has no way of knowing whether or not this call succeeded, which also means that, since I'm showing the form in a modal dialog, I don't know if I should close the dialog or display errors. My component simply does this...
this._brandService.saveBrand(this.brandForm.value);
So, I was thinking that I should figure out a way to a) fire an event in the service that I'm listening for in the component for when good / bad things happen and act accordingly, or b) figure out some way of observing some other properties in the service that I can act on when those changes are detected. But I'm pretty new to all this observable stuff and I don't really even know where to begin.
data => {
this._dataStore.brands.push(data);
this._brandsObserver.next(this._dataStore.brands);
// fire some success event or
// update some observed property
},
error => {
// fire some failure event or
// update some observed property
}
You could do the subscribe() at call site
saveBrand(brand: Brand) {
var headers = new Headers();
headers.append('Content-Type', 'application/json');
return this.http.post("http://localhost:8080/api/brands", JSON.stringify(brand), { headers: headers })
.map( (response: Response) => response.json()).map(
data => {
this._dataStore.brands.push(data);
this._brandsObserver.next(this._dataStore.brands);
});
}
this._brandService.saveBrand(this.brandForm.value)
.subscribe(
value => onSuccess(),
error => onError());
If you still want to do some generic error handling in saveBrand you can use the catch operator (like used in Intercepting errors and subscribing to)

Resources