I'm using MessageChannel() to pass messages between a page and iframe. In my scenario, the iframe is the communications initiator and the page containing it receives, transforms and responds back to the iframe.
As I was implementing the system I at first took and saved a reference to the port passed to the iframe, cached it and continue to use it for each subsequent communication.
iframe:
window.onmessage = (e) => {
console.log("iframe port established");
//using this port for all following communications
parentPort = e.ports[0];
onmessage = establishedConnectionHandler;
}
I'm running all subsequent communications from the iframe to the parent through parentPort:
parentPort.postMessage({command, guid, message});
even though the docs state that the message channel is a one-shot communication this appears to work and makes initiating communications convenient.
My question - is this supported functionality or am I exploiting undefined behavior?
Here is the source.
Edit - I must have misunderstood the intent of the example MDN:
button.onclick = function(e) {
e.preventDefault();
var channel = new MessageChannel();
otherWindow.postMessage(textInput.value, '*', [channel.port2]);
channel.port1.onmessage = handleMessage;
function handleMessage(e) {
para.innerHTML = e.data;
textInput.value = '';
}
}
This is refactored in Kaiido's Plunker example.
That's not really clear what you are doing, even reading your code on github...
You seem to be confusing the WindowObject.postMessage method and the MessagePort's one. WindowObject's one should be used only once, at the negotiation part.
So let's take a step back to explain more basically how things should be understood:
You should think of message channels as a Yoghurt-pot Telephone® [pdf].
––––– –––––
po(r)t1 |~~~~~~~~~~~~~~~~~~~~~~~| po(r)t2
––––– –––––
One user has to create it.
Then he will give (transfer) one of the po(r)ts to the other user.
Once this is done, each user has only access to its own po(r)t.
So to be able to receive messages from the other user, they have to put their ear on their own po(r)t (attach an event handler).
And to send messages, they will say the message (postMessage) inside the only po(r)t they still have, the same they are listening to.
So to add some lines of code, what you should do is:
Generate The Yoghurt-pot telephone® a.k.a MessageChannel.
var yoghurt_phone = new MessageChannel();
Keep one of the po(r)t and give the other one to the other user (iframe). To do this, we use the WindowObject.postMessage method, which is not the same as the one we'll use to communicate through the MessagePorts.
mains_yoghurt_pot = yoghurt_phone.port1;
frame.contentWindow.postMessage( // this is like a physical meeting
'look I made a cool Yoghurt-phone', // some useless message
'*', // show your id?
[yoghurt_phone.port2] // TRANSFER the po(r)t
);
From the frame, receive the po(r)t and keep it tight.
window.onmessage = function physicalMeeting(evt) {
if(evt.ports && evt.ports.length) { // only if we have been given a po(r)t
frames_yoghurt_pot = evt.ports[0];
// ...
From now on, each user has its own po(r)t, and only a single po(r)t. So at both ends, you need to setup listeners on their own single po(r)t.
// from main doc
mains_yoghurt_pot.onmessage = frameTalksToMe;
// from iframe doc
frames_yoghurt_pot.onmessage = mainTalksToMe;
And then when one of the two users wants to tell something to the other one, they'll do from their own po(r)t.
// from main doc
mains_yoghurt_pot.postMessage('hello frame');
// or from iframe doc
frames_yoghurt_pot.postMessage('hello main');
Fixed OP's code as a plunker.
Related
window.onstorage = () => {
alert("A storage event from another tab!")
};
A storage event never executes within the tab that triggered it, instead it gets called in all other tabs on the same "namespace" (browser/domain. See: https://developer.mozilla.org/en-US/docs/Web/API/Window/storage_event).
How can I do the same but with my own custom event (x happens and then all other tabs on same namespace give rise to an event)?
Using localStorage along with the onstorage event is one way of broadcasting messages amongst tabs. However, you are enforced to encode/parse the message using JSON.stringify/JSON.parse. This prevents you from sending non-stringifiable objects like Blobs, Files etc. Also, the size of localStorage is limited (~10MB).
There is a better approach for this purpose, which is very similar to the postMessage API, called Broadcast Channel API. An example on how you can create your own event notification system:
// Window 1
const bc = new BroadcastChannel('my_channel'); // connect to my_channel
bc.postMessage({eventType: "myEvent", eventData: "Hi, this is an event"});
// Window 2
const bc = new BroadcastChannel('my_channel'); // connect to my_channel
bc.onmessage = function (ev) {
if(ev.data.eventType == "myEvent"){ // only listen for myEvent
console.log("Event myEvent received.");
console.log("Information of the event: %s", ev.data.eventData);
}
}
Notice that both windows must be connected to the same channel (in this case "my_channel"), and both windows must have the same origin.
So lately I have been learning JS and trying to interact with webpages, scraping at first but now also doing interactions on a specific webpage.
For instance, I have a webpage that contains a button, I want to press this button roughly every 30 seconds and then it refreshes (and the countdown starts again). I wrote to following script to do this:
var klikCount = 0;
function getPlayElement() {
var playElement = document.querySelector('.button_red');
return playElement;
}
function doKlik() {
var playElement = getPlayElement();
klikCount++;
console.log('Watched ' + klikCount);
playElement.click();
setTimeout(doKlik, 30000);
}
doKlik()
But now I need to step up my game, and every time I click the button a new window pops up and I need to perform an action in there too, then close it and go back to the 'main' script.
Is this possible through JS? Please keep in mind I am a total javascript noob and not aware of a lot of basic functionality.
Thank you,
Alex
DOM events have an isTrusted property that is true only when the event has been generated by the user, instead of synthetically, as it is for the el.click() case.
The popup is one of the numerous Web mechanism that works only if the click, or similar action, has been performed by the user, not the code itself.
Giving a page the ability to open infinite amount of popups has never been a great idea so that very long time ago they killed the feature in many ways.
You could, in your own tab/window, create iframes and perform actions within these frames through postMessage, but I'm not sure that's good enough for you.
Regardless, the code that would work if the click was generated from the user, is something like the following:
document.body.addEventListener(
'click',
event => {
const outer = open(
'about:blank',
'blanka',
'menubar=no,location=yes,resizable=no,scrollbars=no,status=yes'
);
outer.document.open();
outer.document.write('This is a pretty big popup!');
// post a message to the opener (aka current window)
outer.document.write(
'<script>opener.postMessage("O hi Mark!", "*");</script>'
);
// set a timer to close the popup
outer.document.write(
'<script>setTimeout(close, 1000)</script>'
);
outer.document.close();
// you could also outer.close()
// instead of waiting the timeout
}
);
// will receive the message and log
// "O hi Mark!"
addEventListener('message', event => {
console.log(event.data);
});
Every popup has an opener, and every different window can communicate via postMessage.
You can read more about window.open in MDN.
I have a script meant to be included by third party sites that creates an iframe (on our origin). Something like this:
// this script exists on the *host site*
const iframe = document.createElement("iframe");
iframe.src = "https://us.com/iframe";
parent.appendChild(iframe);
The script exposes an API which internally communicates with the iframe through postMessage, something like:
// This script exists on the *host site*
function getSomeProperty()
{
return new Promise(function (resolve, reject)
{
theIFrame.postMessage("getSomeProperty", "us.com")
window.addEventListener("message", function (event)
{
// ... get the response, making sure its from us.com, etc.
// This is just pseudo code, we don't create a new listener
// on every call in our real code.
resolve(answer);
}
});
}
All this is fine, my question is with regard to the iframe's corresponding "message" listener. Specifically, I want to only respect requests from the creating window/origin. In other words, if some parallel iframe on a separate origin (say, an ad) constructs a postMessage to us, we want to of course ignore this. My question is whether it is sufficient to simply check whether the sending window is window.parent:
const parent = window.parent;
// This event handler is in the *embedded iframe*
window.addEventListener("message", function (event)
{
// This is being sent from a window other than
// the one that created us, bail!
if (event.window !== parent)
return;
// it is safe to respond...
}
As far as I understand it, the only way that this would not be a sufficient check is if it was possible for window.parent to change origins while simultaneously keeping us around. I can imagine scenarios where an iframe could be removed from one host and appendChild'ed onto another host, but I believe this could only happen if the DOM manipulator was on the same origin (and thus have access to said DOM) and placing us into another window of the same origin too.
Currently, we employ an extra paranoid defense of passing the original origin to the iframe through the query string as such:
const iframe = document.createElement("iframe");
iframe.src = `https://us.com/iframe?location=${window.location.href}`;
Thus allowing us to check that window === window.parent AND that origin === originalOrigin. However, we'd really like to move away from this model since it necessarily breaks caching across different sites (as each one generates a different src URL due to the different query string). So, is it safe for us to move to solely checking window === window.parent?
When using the postMessage API, the only truly safe way to ensure messages are coming from where they say is to use a strict targetOrigin. This way you can ensure that messages are coming from a window you control.
You're right in saying that this condition would prevent incoming messages other than from the window that created them. However, an iFrame with src="https://us.com/iframe"; could still be created on any origin. What if it was created on a malicious page? This would still satisfy the condition such that window.parent === parent is true.
I want to keep a ServiceConfiguration collection in sync with a collection of settings. I've (nearly) accomplished this using observeChanges like so:
var handle = Settings.find().observeChanges({
changed: function (id, post) {
var insert = {};
post.hostName && (insert.host = post.hostName);
post.domainName && (insert.domain = post.domainName);
ServiceConfiguration.configurations.update({service: "xmpp"}, insert);
}
});
Meteor.publish("Settings", function() {
this.onStop = function () {
handle.stop();
};
return Settings.find();
});
The problem with this code however is that the publication's onStop method is called straight away, not when the client disconnects. The reason I'm using that callback is because the Meteor docs underline the importance of manually cancelling observeChanges handles, but if I cancel it this way, then I can't actually observe the changes to the collection. The code does however work fine if I don't stop() the handle.
So, can I not stop the handle or would that give me a memory leak? Or how should I go about keeping two Meteor collections in sync?
You can listen for updates to a collection using matb33:collection-hooks. This would be server side:
Settings.after.update(function(userId, doc, fieldNames, modifier, options){
var insert = {};
//...your logic
ServiceConfiguration.configurations.update({service: "xmpp"}, insert);
});
Check out https://github.com/matb33/meteor-collection-hooks
TL/DR - yes, you're fine to remove it.
If I understand correctly, this is a situation in which multiple users can subscribe to the "Settings" publication, potentially change settings in the Settings collection, and you need to automatically propagate these to the ServiceConfiguration collection.
If this is the case then you should not be trying to stop the observer as it's a global construct designed to monitor all changes by any user. The case in which you need to stop an obsever on publication closure is when the observer is run from within the publish function, so there is a new one generated for every connected user. If you didn't stop the observer under those circumstances, the same user could repeatedly connect and disconnect and you'd be left with a potentially unlimited number of running observers and your app would die.
Here however, you would only ever have one observer, which runs independently of the number of subscribing clients. In addition, you can't stop it when any individual publication is stopped, as there will presumably still be other client subscribers who could continue to make changes.
In summary, it's fine to remove the onStop block. Let me know if that doesn't make sense.
I have 3 Server-sent Events available to a page. Only one viewable at any time. I would like to stop the listener on 2 of the 3 event streams when 1 of them is active.
I have a switch statement testing for which is visible but can not pass the source.close() to my event directly as it is buried in a function:
var firstEventSource = function() {
var eventSrc = new EventSource('firstSTREAM.php');
eventSrc.addEventListener('message', onMessageHandler);
};
I was hoping to have fewer open connections to the server, especially with non-viewed data.
If you have a better suggestion I'm all ears!
Best,
T
function onMessageHandler(event) {
if ("your want to close that EventSource") {
event.target.close();
}
}
This question is hard to answer without more context, but I'll do my best.
You could think of the event resource as a pipe where you push all of your messages, and have the client listen for specific events, effectively multiplexing:
var handler = console.log.bind(console)
, events = new EventSource("/events")
events.addEventListener("new-friend", handler)
events.addEventListener("new-message", handler)
events.addEventListener("new-notification", handler)
This would reduce your connection count to exactly one, and would save you from doing costly reconnects whenever you switch between views. However, it has the drawback of your server pushing (possibly) unnecessary data down the pipe. After all, you're only viewing one message type at a time. You should consider whether this is an actual problem though. If your UI should update, perhaps with some kind of badge notification (like facebook's message or notification icons) then you will need to know about those messages even though the user may not be actively on that particular view. In any event, you should try to keep messages lean for performance sake.
If you can't or won't push all messages down the same pipe, you probably should go with your initial thought of having multiple resources or the ability to query the resource in question, and then opening and closing the connections. Bear in mind though that this could potentially be very costly, as the client could end up hammering the server with requests. Each view change would cause connections to be set up and tore down. It'd look something like this:
/* Assuming jquery is available and with the following html:
* <a class="stream" href="/friends>Friends</a>
* <a class="stream" href="/messages>Messages</a>
* <a class="stream" href="/notifications>Notifications</a>
*/
var currentEvents
, handler = console.log.bind(console)
$("a.stream").on("click", function() {
$el = $(this)
currentEvents && currentEvents.close()
currentEvents = new EventSource($el.attr("href"))
currentEvents.addEventListener("message", handler)
return false
})
In the end, it depends on context. If users aren't going to switch views very often, or the messages are really big, then you might want to go for the second approach. It'll feed less data down the pipe, but create and tear down connections as the user navigates. If the user often switches views however, or you can keep the message size reasonable, then I'd advocate multiplexing, like in the first solution. It'll keep one long-running connection where small messages of different types may be pushed to the client.