Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replying to source iframe #1

Open
kunaakos opened this issue Jul 11, 2018 · 8 comments
Open

Replying to source iframe #1

kunaakos opened this issue Jul 11, 2018 · 8 comments

Comments

@kunaakos
Copy link

Hi!

I ran into an issue with multiple iframes from different domains communicating with the main window. I cannot create listeners on the main window from the iframes' contexts because of cross-domain restrictions.
The way this was handled before using postmsg-rpc, was by dispatching an event on the window we got the original message from:

window.addEventListener('message', (event) => {
  // ...
  event.source.postMessage(msg, '*')
});

Would it be possible to add this as an option to postmsg-rpc? Something like opts.replyToSender Happy to submit a PR!

@alanshaw
Copy link
Member

I'm not sure I understand the issue correctly. Can you not pass (...args) => window.parent.postMessage(...args) as the postMessage option to caller?

https://github.com/tableflip/postmsg-rpc#callerfuncname-options

@kunaakos
Copy link
Author

kunaakos commented Jul 12, 2018

Yeah, but to get the return value, I need to pass window.parent.addEventListener as the addListener option to caller - and creating event listeners on the parent window is not allowed from cross-domain iframes.

This is what I'm currently doing, exposing functions with hack() instead of expose():

const exposeOptions = {
	postMessage: (returnValue, targetOrigin) => {
		returnValue.res.source.postMessage({
			...returnValue,
			res: returnValue.res.data
		}, targetOrigin);
	},
	getMessageData: (event) => {
		return {
			...event.data,
			args: event.data.args
				? [event.source, ...event.data.args]
				: [event.source]
		};
	}
};

function hack(fnName, fn) {
	const wrappedFn = async (...args) => {
		return {
			source: args[0],
			data: await fn(...args.slice(1))
		};
	};
	expose(fnName, wrappedFn, exposeOptions);
}

If I'm missing something, and there's a better way, lemme know.

@alanshaw
Copy link
Member

Yeah, but to get the return value, I need to pass window.parent.addEventListener as the addListener option to caller - and creating event listeners on the parent window is allowed from cross-domain iframes.

You should use the default - window.addEventListener?

@kunaakos
Copy link
Author

correction: *not allowed from cross-domain iframes - that's a confusing typo to make, sry

If I use the default, I need the above hack. That works.
But if window.postMessage() is called in the parent, and an event listener is created using window.addEventlistener() in the child (iframe) context, it doesn't, because events are dispatched on the parent window object, not the child.

@alanshaw
Copy link
Member

In the parent can you pass postMessage option to expose as (...args) => document.querySelector('iframe').contentWindow.postMessage(...args)

@kunaakos
Copy link
Author

I have several, dynamically inserted iframes on the page, and several of those will call the same exposed method, so that approach doesn't work :(

@alanshaw
Copy link
Member

Gotcha, I think your hack is ok for now. I wanted to add this a while ago but my primary use for this library is in a webextension context where the postMessage API is similar but not the same. I'd like to add it in a way that allows it to be used in a web page but also in a web extension.

FYI the web extension API https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/runtime/onMessage

@kunaakos
Copy link
Author

I was thinking about sth like:

export default function expose (funcName, func, opts) {
  opts = opts || {}
  // ...
  const replyToEmitter = opts.replyToEmitter || false
  // ...
  const handler = function (event) {
    // ...
    const dispatch = replyToEmitter
      ? (msg) => event.source.postMessage(msg, targetOrigin)
      : (msg) => postMessage(msg, targetOrigin)
    // ...
    const onSuccess = (res) => {
      msg.res = res
      dispatch(msg)
    }
    const onError = (err) => {
      // ...
      dispatch(msg)
    }
    // ...
  }

This doesn't break compatibility AFAIK, but makes the config a bit more confusing, since setting opts.replyToEmitter to true would make opts.postMessage unnecessary. Or maybe adding something like 'emitter' as a possible value to opts.postMessage would be cleaner?

Either way, happy to submit a PR, but wasn't sure about how to approach this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants