window.postMessage() to send data once the data exists - javascript

I'm sending data between a parent window and an embedded <iframe> using window.postMessage(). The parent window listens for a "mounted" message from the <iframe>, and sends a response message with a JavaScript object model as the data.
Here's the parent window event listener for the "mounted" message:
window.addEventListener('message', (event) => {
if (hasOwnProperty.call(event.data, 'mounted')) {
$('iframe')[0].contentWindow.postMessage({ model }, '*');
}
});
The <iframe> may send the "mounted" message several times in one session, and the parent window needs to respond with model each time.
However, on the initial page load, the parent window asynchronously retrieves model from an external API, which could resolve before or after the parent window receives a "mounted" message.
let model = {};
axios.get('/api/model')
.then((response) => {
model = response.data;
});
So, if the parent window received a "mounted" message before the model exists, I want my code to wait until model does exist and then send the response. How can I achieve this? I assume it's going to involve changing my postmessage({ model }, '*') call into a Promise, but I'm not sure how to attach that Promise to make it wait for the axios.get call to complete.
I'm aware of how Promise.all() works, but that wouldn't really work since the "mounted" message can be sent multiple times in a window session.
One solution
While writing this question, I realized that I could write a function that sends model only if model isn't empty, which both my "mounted" event listener and my asynchronous data retrieval could call. So, the message is always posted when the data is loaded from the external API (and it doesn't matter if the <iframe> hasn't mounted), and the event listener only responds once the model has loaded. Here's the complete code for the solution:
function postModel(model) {
if (!_.isEmpty(model)) {
$('iframe')[0].contentWindow.postMessage({ model }, '*');
}
}
let model = {};
axios.get('/api/model')
.then((response) => {
model = response.data;
postModel(model);
});
window.addEventListener('message', (event) => {
if (hasOwnProperty.call(event.data, 'mounted')) {
postModel(model);
}
});
This works for my purposes, but I'd still be interested to see if there is an async/await approach that would be better here.

Maybe you would find want you need with frameTalk.js.
FrameTalk supports Promises between iframes. So, you would do smth like that
frameTalk.sendPromise(window.top, "myFrameID", "methodNameToRun", ["methodAttributesToPass"])
.then(doOnSuccess, doOnFail);
see more at
https://github.com/inedu/frameTalk

Related

Using promise on button click in stenciljs

I am currently working on a stenciljs App and would like to get some info via a REST service. This REST call is implemented as a function which returns a Promise. Now, if I use this rest call in componentwillload Lifecycle Method, it works fine:
async componentWillLoad() {
return getUserData().then( userData => {
console.log("got userdata", userData);
});
}
Now, I would like to get this info when the user clicks a button. For that, I call this method in the click handler for this button:
<button id="mybutton" onClick={()=>this._handleButtonClick()></button>
_handleButtonClick() {
return getUserData().then( userData => {
console.log("got userdata", userData);
});
}
But this does not work, I see in the Browser console Network Tab that a network call is made but it returns no data. How can I make this thing work
Looks like you are missing closing brace (})?
onClick={()=>this._handleButtonClick() <- here
but assuming that is just a typo, maybe you can just store the result of getUserData in state object (that way, it will trigger render() function as your data change)?
Here is how I'm doing the fetch (just as an example)
fetch(`REST endpoint`, {
method: 'GET'
headers: { // Content-Type and security token }
}).then ((response: Response) => response.json())
.then(data => {
this.dataList = data;
});
(so here, I'm storing result as "dataList", which is defined with #State() dataList)
I noticed your button doesn't have an attribute type="button".
You could be running into an edge case scenario where if a button is the first button within a form, and isn't marked as type="button", the browser defaults it to <button type="submit">. At that point, clicking on the button might trigger the API call because of the registered listener, but it'll also trigger a form submit.
Since API calls take some time to complete, by the time the response is returned your page may have reloaded because of a form submit.
Again, this is an edge case scenario but looks like a reasonable explanation for what you're observing.

React app communicate with iframe via postMessage: where to setup callbacks?

I have a website which communicates with a third party iframe.
There are 3 methods: login, sign and get information. They all work similar. I embed the third party iframe and send a message to it via window.postMessage(...) to it. After I have sent a message to the iframe I get back a response.
I can't come up with a clean architecture to encapsulate the communication.
Current idea:
class iFrameCommunicator{
init() {
window.addEventListener('message', (message) => {
if (message == "login_success"){
setGlobalLoginState(true)
}
if (message == "sign_success"){
setGlobalSignState(true)
}
})
}
}
login() {
window.postMessage({command: "login"})
}
sign(payload) {
window.postMessage({command: "sign", payload: payload})
}
}
Login page
<button onClick={() => iFrameCommunicator.login()}>Login</button>
With this approach I have to monitor the GlobalLoginState to see if the login call was successful. I would like the login function (and the other functions) to return a Promise instead.
Is there a way to refactor the code so that every function which communicates with the iframe returns a Promise without having to setup an addEventListener for every function call?
So 3rd party iframe takes three messages, presumably you can't control what you get back?
However if you know what you are getting back on success / fail can you add a listener for the success / fail in your app - a bit flaky as its 3rd party and message may change, but that is what we do in a loader / iframe setup that we control.
Sending and listening for messages either side - we also pass a function into the iframe for invocation, but you can't given its not your iframe so callbacks presumably won't be possible.
What did you end up doing?
Have you considered the usage of useEffect which depends on your GlobalLoginState?
Then updated your state based on other events? To be honest, it is quite very difficult to give an answer not knowing exactly the other context or availabilities within your webapp.

Typescript Angular HTTP request get only last result

What is the "best" (common) way to make sure that my Angular HTTP request only returns the newest response data. (I am using Angulars HttpClient)
Lets say the user submits a new request before the response of the previous request is returned. My backend needs more time to process the first request so the second request returns before the first one -> the view get's updated and later the first request returns. Now the view gets updated with old data. Thats obviously not what I want.
I found this solution to cancel the request but is that really the only way? Are there any build in functionalities to achive that?
if ( this.subscription ) {
this.subscription.unsubscribe();
}
this.subscription = this.http.get( 'myAPI' )
.subscribe(resp => {
// handle result ...
});
I know there is the switchMap() operator which only returns the latest data but as far as I know you can only use it inside of an observable. In my case I am not observing any input changes. I simply call a function on form submit via (ngSubmit)="doRequest()" directive in HTML-Template.
// get called on submit form
doRequest() {
this.http.get('myAPI')
.subscribe(resp => {
// handle result ...
});
}
Is there a way to use the switchMap operator or do you have any other solutions? Thanks for taking your time :)
PS: Of course in my real project I have an api service class with different functions doing the requests and just returning http response observables. So I subscribe to such an observable in doRequest() function.
you just make the clicks / requests a stream that you can switch off of... the user's requests / clicks are a stream of events that you can observe and react to.
private requestSource = new Subject();
request() {
this.requestSource.next();
}
ngOnInit() {
this.requestSource.switchMap(req => this.http.get('myApi'))
.subscribe(response => console.log(response, "latest"));
}
modern client programming rule of thumb: everything is a stream, even if it doesn't look like one right away.
Edit: In the case of rxjs 6 or latest version, add pipe operator with the above code.
this.requestSource.pipe(switchMap(...));

Angular $http service, how to cancel / unsubscribe pending requests?

I have an AngularJS application which perform
- 1 request to fetch the main user profile, that contains references to the user friends,
- and then 1 request per friend to retrieve the friend profile.
When we click on a friend's profile, we load this profile as the main profile.
I am in the RDF / semantic web world, so I can't model these interactions differently, this is not the point of the question.
This is an early version of the application I'm trying to build, that can help you understand what's my problem: http://sebastien.lorber.free.fr/addressbook/app/
The code looks like:
$scope.currentProfileUri = 'http://bblfish.net/people/henry/card#me';
$scope.$watch('currentProfileUri', function() {
console.debug("Change current profile uri called with " + $scope.currentProfileUri);
loadFullProfile($scope.currentProfileUri);
})
// called when we click on a user's friend
$scope.changeCurrentProfileUri = function changeCurrentProfileUri(uri) {
$scope.currentProfileUri = uri;
}
function loadFullProfile(subject) {
$scope.personPg = undefined;
// we don't want to capture the scope variable in the closure because there may be concurrency issues is multiple calls to loadFullProfile are done
var friendsPgArray = [];
$scope.friendPgs = friendsPgArray;
fetchPerson(subject).then(function(personPg) {
$scope.personPg = personPg
fetchFriends(personPg,function onFriendFound(relationshipPg) {
friendsPgArray.push(relationshipPg);
});
},function(error) {
console.error("Can't retrieve full profile at "+uri+" because of: "+error);
});
}
So the friends are appended to the UI as they come, when the http response is available in the promise.
The problem is that the function changeCurrentProfileUri can be called multiple times, and it is possible that it is called by while there are still pending requests to load the current users's friends.
What I'd like to know is if it's possible, on changeCurrentProfileUri call, to cancel the previous http requests that are still pending? Because I don't need them anymore since I'm trying to load another user profile.
These pending requests will fill an instance of friendsPgArray that is not in the scope anymore and won't be put in the scope, so it is just useless.
Using Java Futures, or frameworks like RxScala or RxJava, I've seen there's generally some kind of "cancel" or "unsubscribe" method which permits to de-register interest for a future result. Is it possible to do such a thing with Javascript? and with AngularJS?
Yes, it is! Please, see this section of angular $http service docs. Note timeout field in config object. It does, I think, exactly what you need. You may resolve this pormise, then request will be marked as cancelled and on error handler will be called. In error handler you may filter out this cases by there http status code - it will be equal to 0.
Here is fiddle demonstrating this

Native Messaging postMessage with callback

So, I create a port
var port = chrome.runtime.connectNative("my.native.app");
And I'll define
port.onMessage.addListener(onNativeMessage);
port.onDisconnect.addListener(onDisconnect);
So when I call
port.postMessage({"text":"messsage"});
It goes to my native application using standard in/out and I get my response.
Here's my problem: I have several functions that expect a response, and others that do not. I want to be able to post a message and wait for a response from the native application (which is continually running). How is this done?
I am aware of "one time messaging" via sendMessageNative which works great, except I use my native application as a state machine, so it kills my application after it is done, which is no good.
You could add another listener to onNativeMessage to call your callback and then de-register.
Something like this, with closures:
function callbackOnId(ev, id, callback) {
var listener = ( function(port, id) {
var handler = function(msg) {
if(msg.id == id) {
ev.removeListener(handler);
callback(msg);
}
}
return handler;
})(ev, id, callback);
ev.addListener(listener);
}
/* ... */
callbackOnId(port.onMessage, "someRequestId", callback);
port.postMessage({"text":"message", "id": "someRequestId"});
Now, the first time your port receives a message containing "id": "someRequestId", callback will be called with that message, after which the listener will de-register itself.
If you need it, you can add an ability to remove the listener and/or modify the message checks.
My solution is to assign each message an unique id, and use a map to store the callback function for each message id (if there is a callback for the message).
When you receive a message from the host, check the message id and lookup the callback map. If there is a callback bound to that message, pass the response data and call it!

Categories

Resources