Giter Site home page Giter Site logo

aaronius / penpal Goto Github PK

View Code? Open in Web Editor NEW
371.0 371.0 56.0 1.32 MB

A promise-based library for securely communicating with iframes via postMessage.

License: MIT License

JavaScript 37.07% HTML 11.57% TypeScript 51.25% Shell 0.11%
iframe postmessage promise

penpal's People

Contributors

aaronius avatar ahtcx avatar corollari avatar dependabot[bot] avatar gomah avatar jastor11 avatar kutneruri avatar lpellegr avatar marlon-tucker avatar mike-north avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

penpal's Issues

Serializing Objects with Functions causes an error

This isn't too hard of an issue to workaround, but it might be nice to handle in the core library.

Problem

If you pass an object to postMessage that contains a Function, the method throws an error (at least under Firefox).

Solution

This can be handled very simply using a method that filters out functions from passed data. I use a method that looks like this:

export function toSafeObject(data) {
	let newData = data;
	if(typeof data === 'function') {
		newData = undefined;
	} else if(Array.isArray(data)) {
		newData = data.map(toSafeObject);
	} else if(data !== null && typeof data === 'object') {
		newData = {};
		Object.keys(data).forEach((k) => {
			newData[k] = toSafeObject(data[k]);
		});
	}
	return newData;
}

I'd be happy to open up a PR with this change in place, along with some tests.

Feature request: allow subdomains of childOrigin

In #73, the ability to allow any child origin was added via '*'. We'd like to do the same thing, but with the restriction of only allowing subdomains of the original child origin as a safer alternative. We can sometimes redirect to a subdomain for certain customer configurations.

Proposed change: if the configured childOrigin domain begins with ., treat subdomains as equivalent.

Remove check that determines if `connectToParent` is running inside iframe

When calling connectToParent, Penpal checks to see if it is running inside an iframe and, if not, throws a NotInIframe error to make you aware of the problem. While this can be helpful, it causes problems when Penpal is running within a Cypress test. While there's a workaround available by disabling modifyObstructiveCode in Cypress, that can potentially cause other issues if some other JavaScript is attempting to framebust and Cypress actually needs to modify that obstructive code.

While the check Penpal provides can be helpful, developers are probably unlikely to call connectToParent outside of an iframe and they would probably notice the problem pretty easily without the check in place.

To avoid this problems with Cypress entirely and to avoid yet another configuration option, I propose removing the check from Penpal entirely. This will require a major version, however.

Connection promise gets rejected with error if destroyed by the user

Imagine the following common scenario:

  1. I have a single-page application.
  2. When user comes to a specific area, I create an iframe and connect to it with penpal.
  3. When user leaves that area, I destroy the connection and remove the iframe.

In this scenario, if user leaves the area before connection is established (it may take time due to network latency etc), connection promise is always resolved with an error logged in the console. This is nasty because from code perpective I do it right by calling connection.destroy() to let penpal know the connection is destroyed on purpose, so it's not an error and shouldn't be logged like that.

In order to hide the error, I have to eat all exceptions from connection.promise which potentially hides real connection problems.

Here is the code sample:

const page = document.getElementById('page');
const iframe = document.createElement('iframe');
iframe.src = 'child.html';

const connection = Penpal.connectToChild({iframe, debug: true });
page.appendChild(iframe);

// Emulate cleanup happening when user is leaving the page.
setTimeout( () => {
	connection.destroy();
	page.removeChild(iframe);
}, 0);

And here is the error logged in console:
Error in console

And here is the workaround I use:

const connection = Penpal.connectToChild({iframe, debug: true });
connection.promise.catch(()=>{}); // Ugly because it may hide real connection problems.
page.appendChild(iframe);

Ideally connection promise shouldn't get rejected with a error if connection was destoryed by a user. It should just never resolve, same way as child methods work on a destroyed connection.
We should see this error only if connection was aborted by penpal (for instance, by iframe watcher).

Export types

Hi, I just wanted to start using this library with typescript, but immediately noticed a big issue:
Types like Connection do not seem to be exported at all from this library.
In fact, the only export seems to be connectToChild, connectToParent and ErrorCode.

At least the generic type Connection would be highly required to properly type fields

Uncaught (in promise) DOMException: Failed to execute 'postMessage' on 'Window'

I'm experimenting with Penpal and I try throwing things in and out from and to the child page I need to have embedded just to find out if Penpal can handle that.
I'm very much satisfied and impressed with what it can do. Great piece of software!

I tried transferring child window object to parent which is obviously something that should fail.
Disclaimer: This report is not about that Penpal is incapable of sending child window to parent. I'm aware why it fails and it's clear to me that no sane person should try to do that for any reason.

It's about the fact that doing such stupid thing (either on purpose or by mistake) should result in a failed promise returned by the child method called in parent, instead of an uncaught error in child (which happens now).

Promise in parent never resolves. It stays pending indefinitely which does not allow parent to handle problem caused by the child. Wrapping the child method call in try-catch in parent code does not allow detecting the problem either. And that's the main problem I see in this behaviour.

Exact error I saw in parent console:

index.js:234 Uncaught (in promise) DOMException: Failed to execute 'postMessage' on 'Window': #<Window> could not be cloned.
    at http://localhost:3100/vendor.bundle.js:36215:21
    at <anonymous>

Line in question nad lines that follow:

remote.postMessage({
  penpal: REPLY,
  id: id,
  resolution: resolution,
  returnValue: returnValue
}, remoteOrigin);

Help test v5!

Good news! Version 5 is about ready to ship and I could use your help testing it out.

To install v5, run npm install penpal@next. For the updated readme, see https://github.com/Aaronius/penpal/blob/5.x/README.md

Breaking Changes

Importing Modules

The only breaking changes are in how connectToParent, connectToChild, and error codes are imported. Penpal no longer ships with ES5. The ES6 code is set up in such a way that your bundler of choice should properly tree shake unused code.

New Features

TypeScript

Penpal is now built using TypeScript, so if you're using TypeScript, you now get types for free!

Regex Support for parentOrigin

When using connectToParent, you can now specify a regular expression for parentOrigin. By providing a regular expression, you can now load the iframe onto multiple, different domains while ensuring that communication is secured to those domains only.

While this was not a breaking change, it did require significant changes to the handshake process as described in #32 (comment) and due to the higher risk of the change I decided to include it with other breaking changes in v5 rather than adding it to v4.

Comment Below

If you have feedback, please post a comment below. Thanks!

Provide typing for Connection

As described in #55, it would be useful to provide a TypeScript typing for Connection (representing the Penpal connection) that developers can use to better type their applications.

dynamic iframe url

I'm using an iframe that includes redirects as part of it's setup, and I'm getting this error:

[Penpal] Parent: Handshake - Received SYN message from origin https://m73gees.scopes.teambit.dev which did not match expected origin https://symphony.bit.dev

The setup goes something like this:

----> https://symphony.bit.dev/api/resolve/teambit.organization
      (backend logic)

<---- redirect 302 to https://m73gees.scopes.teambit.dev
      (iframe redirects)

----> https://m73gees.scopes.teambit.dev

      getting SYN from m73gees.scopes.teambit.dev
(err) "did not match expected origin"

I wanted to disable the childOrigin check, but it does not seem to be possible. Leaving it as "" or undefined defaults to the iframe's src, and "*" doesn't work.

I don't have a way to know the final url of the iframe. Is there another property I can use? (like name="...", or title="...")

TypeScript error: TS2309: An export assignment cannot be used in a module with other exported elements.

I try to import Penpal like this.

import * as Penpal from 'penpal';

The, I got error:

Error:(43, 3) TS2309: An export assignment cannot be used in a module with other exported elements.


In the following line of index.t.ds.

export = Penpal;


When I import Penpal like this:

import Penpal from 'penpal';


I got error below:

Error:(3, 8) TS1192: Module ''penpal'' has no default export.


penpal 3.0.0 / typescript 2.6.2

Penpal does not work with iframe sandbox attribute

Hi :)

I am trying to use Penpal to communicate with a sandboxed iframe, but it does not work without the allow-same-origin flag:

<!-- THIS WORKS -->
<iframe src="sandbox.html" sandbox="allow-scripts allow-same-origin"></iframe>
<!-- THIS DOES NOT WORKS -->
<iframe src="sandbox.html" sandbox="allow-scripts"></iframe>

Unfortunately, I can't set allow-same-origin in the sandbox.

Any help? Thank you :)

Parent methods called multiple times after child reload

Thank you for writing this great library! It has been very easy to work with outside of this one issue.

After a child is reloaded, any methods that it calls on the parent are executed on the parent side multiple times. The child only receives one response, but the parent executes the method an additional time for every time the child is reloaded.

For example, if the child is reloaded three times, and then calls a parent method once, the parent will execute that method 4 times, and return only the first result to the child.

This is easy to reproduce by placing a console.log statement inside the 'add' method in the usage example, and right clicking inside the child pane and clicking 'reload frame'.

This is a fairly significant problem if the parent method has any side effects. I have not had a chance to look into the cause yet.

communications fail on IE Edge with srcdoc polyfill

There is currently an issue with the srcdoc attribute on IE edge, there is a workaround detailed in this pr for the srcdoc polyfill.

I am using penpal with react to insert an iframe with srcdoc content with the following config

        this.iframeEl = document.createElement('iframe');
        this.iframeEl.setAttribute('sandbox', 'allow-same-origin allow-scripts');

        this.iframeEl.setAttribute('srcdoc', iframeContent);
        if (needsPolyfill) {
            this.iframeEl.setAttribute('src', 'javascript: window.frameElement.getAttribute(\'srcDoc\');');
        }
        this.iframeEl.onload = this.onLoad;
        
        this.connection = Penpal.connectToChild({
            url: window.location.origin,
            iframe: this.iframeEl,
            methods: {
                  ...
            },
        });

It is working as expected when the polyfill is not needed, but in IE Edge where the polyfill is needed, penpal clobbers over the src attribute I have assigned and the polyfill fails to work.

If I work around it by using the technique detailed in the issue above, penpal fails to complete the handshake process here because event.origin no longer matches childOrigin

penpal should respect the src attribute if given an iframe element, or there should be some way for me to define what I expect the event.origin to be when the handshake process initializes (I could see this second point being an issue if there is some sort of oauth process in the iframe that causes the frame to end at a different place than it was initialized). As a nuclear option, it would be nice to be able to opt out of that specific layer of security if I am using srcdoc because I am in control of the content in the iframe.

connected fail

when i switch choice and the component what contains iframe would be reloaded, if the iframe dom structure change( iframe become the grandson but last time is son of some dom ), connected would be failed
Can anyone tell me how to fix? And it didn't go into promise.catch either!!

How to detect if connection with child was lost?

Hi

I am currently using penpal in a setup at a client but we stumbled upon the following "issue":

When the original page in the iframe - that has the penpal library - is replaced by a page that does not have the library and we want to do a call to the child, we get this:

[Penpal] Parent: Sending getState() call

So Child has a function getState. Parent asks the child to do the getState function. This all works when the child has the library running, but we don't get anything when that initial page is changed to another page.
It just "hangs" on the "Sending call"... No error, no disconnect, nothing.

The code we use is this from the parent to the child. We don't see any Error from the catch.
connection.current.promise .then((child) => { console.log(" Child doGetSTate ", child); return child .getState() .then((state) => console.log("save state to local storage ")) .catch((error) => console.log("an error occured in getState", error)); }) .catch((error) => console.log(" error occured in doGetState() ", error));

What are we doing wrong?
TL;DR: we need to know how to intercept or detect when the connection with the child page is lost, so that we cannot do a call anymore to the child, because it just "hangs": [Penpal] Parent: Sending getState() call

Thanks!

Mario

Feature suggestion: allow nested `methods` object

I thought it may be useful to be able to pass nested object (tree of any depth with functions as leafs) as methods:

Penpal.connectToParent({
    methods: {
      method1(),
      foo: {
        foo1(),
      },
      bar: {
        bar1(),
        baz: {
          baz1()
        }
      }
    },
});
Penpal
  .connectToChild({url})
  .promise
  .then(api => {
    api.method1();
    api.foo.foo1();
    api.bar.bar1();
    api.bar.baz.baz1();
  })

Why I think it may be useful:
The child may have extensive API (in particular: already existing API, currently exposed to window) as it is in my case.
To fit Penpal in my needs I have to flatten that API, which I currently do by flattening the API object
with https://www.npmjs.com/package/object-squish and then call methods as api['bar.baz.baz1'](); which is not very handy but it allows preserving already existing structure.

I thought it may be useful to have that baked right into Penpal (with both serialisation and deserialisation off course)

Option to skip iframe validation?

I'm using Penpal in an app, and I'm trying to test that app via Cypress ( https://www.cypress.io/ ).

Unfortunately, Cypress is finicky about dealing with iframes. Part of that is that it appears to modify the value of window.top in order to host your app in its test environment, or something along those lines.

Penpal is currently doing an iframe validation check:

if (window === window.top) {

which is being called unconditionally:

validateWindowIsIframe();

It seems like Cypress's experimentalSourceRewriting option successfully modifies Penpal enough to let the test continue, but that rewriting process takes a very long time to run as the test starts.

I can confirm that if I hand-edit the contents of node_modules/penpal to comment out the validateIframe() check, that my app loads okay in Cypress.

Would it be possible to get an option added to connectToParent() to allow skipping that check? It looks like all that's needed is adding a boolean to the Options type and then moving the validate check into an if statement.

TypeScript declaration files no longer found

Since #23 TypeScript declarations are no longer found.

Could not find a declaration file for module 'penpal'. '[...]/node_modules/penpal/lib/index.js' implicitly has an 'any' type.
  Try `npm install @types/penpal` if it exists or add a new declaration (.d.ts) file containing `declare module 'penpal';` ts(7016)

When installed from npm only the ./dist and ./lib folders and downloaded, ./lib contains index.d.ts which is copied from ./types during the build script. The problem is that package.json sets types to ./types/index.d.ts which doesn't exist when installed from npm.

I would have submitted a PR but am unsure what to do since either ./types exists (when installed from npm) or ./lib exists (when cloning the git repository) so in one of the cases the package.json would be incorrect.

Simple fix would be to add ./types to the npm download.

@mike-north

Latest chrome update breaks the penpal

I am using penpal 5.0.0 for cross domain message. Basically I have three web applications A, B, and C on different domains. Both A and B load C as iframe and write/read something to the iframe, so A and B can exchange information. It was all working fine. But today it is not working any more. I check the chrome version It is the latest Chrome 84.0.4147.89 and just updated yesterday on my machine. The same applications are working fine on Edge and Firefox. Does anybody know what the latest change on chrome might break my application?

Please make connectToParent and connectToChild accept a generic type parameter.

Hello,

In the 3.x branch, the TS Typings for these methods were generic and thus allowed the returned promise to be correctly typed. In the 5.x branch at the moment, this promise is typed to CallSender which is just a function dictionary and so there's no type safety.

Something like this would keep compatibility

connectToParent<TParent = CallSender>(...)

So if a generic parameter isn't provided, it falls back to CallSender.

Am happy to provide a pull request if you want.

Loading Penpal with System.js fails to connect

System.js loads assets asynchronously (in development) and this causes the iframe's 'load' event to fire pre-emptively, for the purposes of Penpal. The child's connectToParent function has not been called, so it cannot receieve the message from the parent that is sent after the iframe's load event fires.

We have yet to find a way to cause System.js to load synchronously, except with bundling, which is only used in production. Currently we have developed a work-around that involves exposing a version of the handleIframeLoaded function that only calls the child.postMessage HANDSHAKE part of the function (which we have dubbed 'manualHandshake'). We then have the child send a message to the parent once System.js has done its work, to trigger a re-handshake.

The other solutions we tried, such as trying to re-fiore the Load event, didn't work because we are inside a situation where CORS is blocking us, which is what we wanted Penpal for in the first place.

Do you have any opinions or advice on how to address this issue without a code change to the repository? Alternately, we could submit a pull request with our solution if the solution sounds reasonable and useful. (Current 'issue' with the pull request is that it would change the return interface of connectToChild to return this extra handshaking function as well.)

Question: Why does penpal use Window#postMessage instead of MessageChannel?

MessageChannel has the same features and support matrix as postMessage, but it has a couple of nice extra things. You may have already considered MessageChannel and opted against it; if so, I'd be curious to know why. You might have a good reason that I should avoid them too.

The difference is that a MessageChannel is an independent object that two realms can use instead of sharing the window's global message event stream.

The nice things:

  • Instead of having to check origins on every message event, you only have to check origins once, when using window.postMessage to send the MessageChannel's port to the worker or frame. Once the MessageChannel is handed off, no other origins have a reference to it, so the security model is a little easier to think about.
  • MessageChannels are independent event streams, so you don't need to continuously filter out extraneous messages from other libraries.
  • Potentially a lot less bookkeeping when handling multiple instances.

The change would be an additional step in establishing the link, something like:

(simplified, with fake internal functions)

function connectToChild(child) {
  return new Promise((resolve, reject) => {
    const channel = new MessageChannel();
    channel.port1.addEventListener(function finishSetup(event) {
      try {
        validateOrigin(event.origin, child);
        validateAck(event.data);
        channel.port1.removeEventListener(finishSetup);
        resolve(channel.port1);
      } catch(e) {
        reject(e);
      }
    });
    child.postMessage(somePenpalNonce, [channel.port2);
  });
}
function connectToParent() {
  return new Promise((resolve, reject) => {
    window.addEventListener('message', function receivePort(event) {
      try {
        validateOrigin(event.origin);
        validateSyn(event.data);
        window.removeEventListener(receivePort);
        resolve(event.ports[0]);
      } catch(e) {
        reject(e);
      }
    }
  });
}

What's the best way to get primitive properties from child/parent?

It seems like anything you want to pass over the postmessage, you just put in methods. But what if you want to pass over static properties, like 'max': 5000. Obviously you could use a getter-ish method and slap it in methods, but I'm wondering if that's really the best way, or if there could be some way to access properties immediately from the connection.promise's resonse? eg

connection.promise.then(child =>  console.log(child.max));

Iframe Removal Monitoring not Working with Custom Elements

Let's say you have a single-page Web app that makes use of web components (for instance a lit-element Web app). The page is rendered through a custom element that has an iframe in its Shadow DOM. This custom element interacts with this iframe using Penpal.

In this scenario, the connection between the parent and child is automatically destroyed after 60 seconds.

After investigation, I identified the problem is caused by a condition that assumes the iframe is not hosted within a custom element (see line 17 below):

export default (iframe: HTMLIFrameElement, destructor: Destructor) => {
const { destroy, onDestroy } = destructor;
const checkIframeInDocIntervalId = setInterval(() => {
if (!document.contains(iframe)) {
clearInterval(checkIframeInDocIntervalId);
destroy();
}
}, CHECK_IFRAME_IN_DOC_INTERVAL);
onDestroy(() => {
clearInterval(checkIframeInDocIntervalId);
})
}

On line 17, document always references the window document. However, with the scenario described the iframe is hosted in the Shadow DOM of the custom element and not the window document. As a consequence, as soon as the monitoring code triggers, the condition !document.contains(iframe) evaluates to true and the connection is destroyed.

A simple solution that I tested with success locally is to replace !document.contains(iframe) by !iframe.isConnected. The property isConnected considers the right context object depending on whether the iframe is in normal DOM or shadow DOM. It seems to also fit the browsers supported by the library (i.e. only IE11 has no support for this property).

Would you accept a PR to fix the issue with the proposed solution?

More generally, would you accept another PR that allows disabling iframe removal monitoring?

Compatibility v4-v5

Hi,

We use penpal quite a lot (thanks for the lib :)) but we recently came into some troubles. Our model is to have one main application loading one frame (at a time) but loading frame from different endpoint depending on the use case (so basically one app, multiple frames)

We started to received feedback that things was not working anymore. After a quick search we noticed that the version 5 was released and was the cause of the issue: developers uses the latest version (for instance https://unpkg.com/penpal/dist/penpal.min.js) while our application is using the version 4. We asked them to downgrade to version 4 and it fixed the problem.

We didn't really care much about the issue until we decided to check the reason. As per the release page:

This need has been raised several times in the past and was a fairly common use case. While this was not a breaking change, it did require significant changes to the handshake process as described in #32 (comment) and due to the higher risk of the change I decided to include it with other breaking changes in v5 rather than adding it to v4.

Our understanding is that due to handshake process changes, v4 and v5 will never be compatible. That also mean from our point of view that we will forever be stuck on v4 (because we can't ask all differnet frame maintainer to upgrade at the same time).

Any thoughts / advices / plan on v4/v5 compatibility ?

Cannot read property 'parentWindow' of null

@Aaronius we are using Penpal for embedded lessons in ConveYour.com. We โค๏ธ it! Thank you!

I just upgraded from 3.0.7 to 3.1.0 and now we are getting this exception often. Here's the related line.

const child = iframe.contentWindow || iframe.contentDocument.parentWindow;

It looks like in the previous version this variable was set prior to handleMessage method being created so held in a closure (right?).

What should I change in my setup to make sure I'm compliant with this change? Thank you!

Reconnecting if child reloaded

I noticed that if I reload the page in the iframe the API is not re-established on neither side:

  • in parent there's no event to listen to in order to call Penpal.connectToChild again (even if one should call it manually)
  • in child, even though the page starts again it can't connect to already existing parent

It's easy for the end user to break the experience by triggering child reload (one can always trigger it by using context menu element "Reload Frame" in Chrome for example). It's also easy to break the experience for child: if it has to reload (for either reason) the whole thing just stops working

IE 11 support

Realising a little late in the day that this library only supported IE 11 in the 3.x branch. I'm using the current 4.x release due to being able to apply install on an existing iframe - functionality required for my project ๐Ÿ˜ซ.

Before I dive in to it, is IE 11 support a matter of transpiling/polyfilling or is it entirely incompatible?

Thanks in advance

Make parentOrigin on connectToParent required

Currently the parentOrigin option on connectToParent is optional but highly recommended. If it isn't specified, the child is allowed to communicate with any parent origin (rather than a specific parent origin). Instead, I think it should be required, and if a developer would like the child to communicate with any parent origin, they should provide a value of *. This change would force developers to consider the ramifications for their applications and make a more deliberate decision.

Angular integration

Hi, i have one question..
it's possible to easily integrate your library in an Angular 2+ project?

I've try to import has a node_module and it seems to work,
but i encountered some problem when Penpal create some promises.

My app never resolve the promise .. is it possible that it's zone.js?
I see that promises are wrapped from it

Add the ability to apply properties to the iframe

Hi, I have just started to use your library to help with the Payment Request API and for this the allowpaymentrequest property needs to be set on the iframe.

Is there a way to introduce a way of setting these properties as part the the connectToChild method?

Thanks

Multiple children on same origin

When there are multiple Children on the same domain, the children will produce cross-talk. This is because the parent's postMessage channel is shared between the children.

The solution to this is for every child to get a childId to identify it uniquely. I implemented this approach for Postmate, but I'd like to switch to Penpal for the additional feature it provides.

Here's a test case that I wrote up to illustrate the problem.

I'll start working on a pull request. Any contribution guidelines?

  it('should call an asynchronous function on the child, but only the right child!', (done) => {
    const c1 = Penpal.connectToChild({
      url: `http://${HOST}:9000/child.html`
    });
    const c2 = Penpal.connectToChild({
      url: `http://${HOST}:9000/child.html`
    });

    c1.promise.then((child) => {
      child.multiplyAsync(2, 5).then((value) => {
        expect(value).toEqual(10);
        connection.destroy();
        done();
      });
    });
    c2.promise.then((child) => {
      child.multiplyAsync(3, 5).then((value) => {
        expect(value).toEqual(15);
        connection.destroy();
        done();
      });
    });

  });

Types for the response of Promise returned after connection established

connection.promise.then((child ? Type ?? ) => {
  child.multiply(2, 6).then((total) => console.log(total));
  child.divide(12, 4).then((total) => console.log(total));
});

Here I have some methods that my child provides.And I want to include that methods inside this child.I want to use a new interface that will look like something like this:

interface Child extends<PenpalChildType> {
...some custom methods comes from children 
}

Is there any way to do this ? I tried but could not be implemented this.
Any help is appreciated ! Thanks.

Including penpal in babel producing errors

Hi. I'm using penpal in my Rollup + TS project and getting a bunch of errors, when trying to include node_modules/penpal/**/*.js in babel processing. Babel is configured to support IE 11 (penpal is not used in this case, but syntax needs to be transpiled cause it's bundled in single file), so when i don't include penpal i'm getting a syntax error in IE, but when i include - everything seems working fine in IE also, but i got errors during build:

Errors & warns

(!) Error when using sourcemap for reporting an error: Can't resolve original location of error.
node_modules/penpal/lib/parent/connectToChild.js (3:12)
(!) Error when using sourcemap for reporting an error: Can't resolve original location of error.
node_modules/penpal/lib/parent/connectToChild.js (101:8)
(!) Error when using sourcemap for reporting an error: Can't resolve original location of error.
node_modules/penpal/lib/child/connectToParent.js (3:12)
(!) Error when using sourcemap for reporting an error: Can't resolve original location of error.
node_modules/penpal/lib/createLogger.js (3:12)
(!) Error when using sourcemap for reporting an error: Can't resolve original location of error.
node_modules/penpal/lib/createLogger.js (24:8)
(!) Error when using sourcemap for reporting an error: Can't resolve original location of error.
node_modules/penpal/lib/createDestructor.js (3:12)
(!) Error when using sourcemap for reporting an error: Can't resolve original location of error.
node_modules/penpal/lib/createDestructor.js (28:8)
(!) Error when using sourcemap for reporting an error: Can't resolve original location of error.
node_modules/penpal/lib/methodSerialization.js (4:12)
(!) Error when using sourcemap for reporting an error: Can't resolve original location of error.
node_modules/penpal/lib/startConnectionTimeout.js (3:12)
(!) Error when using sourcemap for reporting an error: Can't resolve original location of error.
node_modules/penpal/lib/startConnectionTimeout.js (33:8)
(!) Error when using sourcemap for reporting an error: Can't resolve original location of error.
node_modules/penpal/lib/parent/handleAckMessageFactory.js (3:12)
(!) Error when using sourcemap for reporting an error: Can't resolve original location of error.
node_modules/penpal/lib/parent/handleAckMessageFactory.js (68:8)
(!) Error when using sourcemap for reporting an error: Can't resolve original location of error.
node_modules/penpal/lib/parent/handleSynMessageFactory.js (3:12)
(!) Error when using sourcemap for reporting an error: Can't resolve original location of error.
node_modules/penpal/lib/parent/handleSynMessageFactory.js (32:8)
(!) Error when using sourcemap for reporting an error: Can't resolve original location of error.
node_modules/penpal/lib/parent/getOriginFromSrc.js (3:12)
(!) Error when using sourcemap for reporting an error: Can't resolve original location of error.
node_modules/penpal/lib/parent/getOriginFromSrc.js (61:8)
(!) Error when using sourcemap for reporting an error: Can't resolve original location of error.
node_modules/penpal/lib/parent/monitorIframeRemoval.js (3:12)
(!) Error when using sourcemap for reporting an error: Can't resolve original location of error.
node_modules/penpal/lib/parent/monitorIframeRemoval.js (36:8)
(!) Error when using sourcemap for reporting an error: Can't resolve original location of error.
node_modules/penpal/lib/parent/validateIframeHasSrcOrSrcDoc.js (3:12)
(!) Error when using sourcemap for reporting an error: Can't resolve original location of error.
node_modules/penpal/lib/parent/validateIframeHasSrcOrSrcDoc.js (14:8)
(!) Error when using sourcemap for reporting an error: Can't resolve original location of error.
node_modules/penpal/lib/child/handleSynAckMessageFactory.js (3:12)
(!) Error when using sourcemap for reporting an error: Can't resolve original location of error.
node_modules/penpal/lib/child/handleSynAckMessageFactory.js (58:8)
(!) Error when using sourcemap for reporting an error: Can't resolve original location of error.
node_modules/penpal/lib/connectCallSender.js (3:12)
(!) Error when using sourcemap for reporting an error: Can't resolve original location of error.
node_modules/penpal/lib/connectCallSender.js (136:8)
(!) Error when using sourcemap for reporting an error: Can't resolve original location of error.
node_modules/penpal/lib/connectCallReceiver.js (3:12)
(!) Error when using sourcemap for reporting an error: Can't resolve original location of error.
node_modules/penpal/lib/connectCallReceiver.js (115:8)
(!) Error when using sourcemap for reporting an error: Can't resolve original location of error.
node_modules/penpal/lib/errorSerialization.js (3:12)
(!) Error when using sourcemap for reporting an error: Can't resolve original location of error.
node_modules/penpal/lib/generateId.js (3:12)
(!) Error when using sourcemap for reporting an error: Can't resolve original location of error.
node_modules/penpal/lib/generateId.js (14:8)
/Users/me/GIT/widget/node_modules/core-js/internals/an-instance.jshttp://localhost:10001 -> /Users/ext.ymamedov/GIT/widget-next/dist/bundles
/Users/me/GIT/widget/node_modules/core-js/internals/array-method-has-species-support.js(!) `this` has been rewritten to `undefined`
https://rollupjs.org/guide/en/#error-this-is-undefined
node_modules/penpal/lib/parent/connectToChild.js
1: import { newArrowCheck as _newArrowCheck } from "\0rollupPluginBabelHelpers.js";
2: 
3: var _this = this;
               ^
4: 
5: import "core-js/modules/es.object.to-string.js";
...and 1 other occurrence
node_modules/penpal/lib/child/connectToParent.js
1: import { newArrowCheck as _newArrowCheck } from "\0rollupPluginBabelHelpers.js";
2: 
3: var _this = this;
               ^
4: 
5: import "core-js/modules/es.regexp.constructor.js";
...and 1 other occurrence
node_modules/penpal/lib/createLogger.js
1: import { newArrowCheck as _newArrowCheck } from "\0rollupPluginBabelHelpers.js";
2: 
3: var _this = this;
               ^
4: 
5: import "core-js/modules/es.array.concat.js";
...and 1 other occurrence

...and 13 other files

Babel config:

esmBundled: {
      presets: [
        [
          "@babel/env",
          {
            targets: {
              browsers: "ie >= 10, safari >= 10",
            },
            modules: "auto",
            spec: true,
            useBuiltIns: "usage",
            forceAllTransforms: true,
            corejs: {
              version: 3.9,
            },
          },
        ],
        "@babel/typescript",
      ],
      plugins: [
        [
          "module-resolver",
          {
            alias: {
              "@": "./src",
            },
          },
        ],
      ],
    },

Maybe you can help me solving this?

TypeScript imports with Penpal 5.2.0

Using Penpal 5.1.1, I was importing types and defining variables in a TypeScript project as follows:

import {AsyncMethodReturns, Connection, TCallSender, connectToParent} from 'penpal';

protected parentConnection: Connection<TCallSender>;
protected parent: AsyncMethodReturns<TCallSender>;

this.parentConnection = connectToParent({...});
this.parent = await this.parentConnection.promise;

Unfortunately, using Penpal 5.2.0, I cannot find the equivalent.

It seems TCallSender was replaced by CallSender and most types need to be imported from the lib folder:

import {AsyncMethodReturns, CallSender} from 'penpal/lib/types';

My main issue is about Connection which seems not exported. How to import it?

Examples for using with React Hooks

It would be nice to have some examples using React, possibly even adding custom hooks for both connectToParent and connectToChild. I've been playing around with penpal in a React project and I can't seem to come up with the best way to destroy the connection. Any feedback would be appreciated:

const useConnectToChild = (iframe, {methods}) => {
  
  const [connection, setConnection] = useState(null)
  useEffect(() => {
    if (iframe) {
      setConnection(connectToChild(iframe, {methods}))
    }
    return () => connection.destory() // You can't really call connection.destroy here, it will cause an endless re-render. 
    // Maybe throw it in a separate useEffect? 
  }, [connection])

  return [connection]
}

[Penpal] Parent: Awaiting handshake

I was trying to create a iframe and communicate back with my react-server

But unable to get a handshake back from iframe/child html which i used as a src connectToChild

Am I missing Something?

Help me out

Source code was below

parent code react js file:-

import React, { useEffect, useRef, useState } from 'react';
import { connectToChild } from 'penpal';

function ParentComponent() {
  const [child, setChild] = useState(null);
  const inputRef = useRef();
  useEffect(() => {
    const iframe = document.createElement('iframe');
    iframe.src = 'src/child.html';

    // Connect to the child iframe
    let connection = connectToChild({
    //   childOrigin:'src/child.html',
      iframe:iframe,
      methods: {
        sendMessage(message) {
          // Handle received messages from the child
          console.log('Received from child:', message);
        },
      },
      debug:true,
    })

    setChild(connection)

    return () => {
    //   connection.destroy();
    };
  }, []);

  const sendMessageToChild = async () => {
    const message = inputRef.current.value;
    console.log("sendMessageToChild--->",message)
    child.promise.then((message) => {
        console.log("childState--->",message)
      message.sendMessage('Hello from parent!');
    });
    
  };

  return (
    <div>
      <h1>Parent Component</h1>
      <input type="text" ref={inputRef} />
      <button onClick={sendMessageToChild}>Send to Child</button> 
    </div>
  );
}

export default ParentComponent;

child html

<!DOCTYPE html>
<html>
<head>
  <title>Child Component</title>
<script src="https://unpkg.com/penpal@^6/dist/penpal.min.js"></script>

</head>
<body>
  <h1>Child Component</h1>
  <script>
    const button = document.getElementById('myButton');
    console.log('......',Penpal)
    const child = Penpal.connectToParent({
      methods: {
        sendMessage: (message) => {
          return `Received message from parent: ${message}`;
        }
      },
      debug:true
    });
    
    child.promise.then((parent) => {
          parent.sendMessage('Hello from child!');
        
      });
  </script>
</body>
</html>


@Aaronius can you help me out

IE11 compatibility

The library is currently not compatible with IE11.

There are two issues:

  • postMessage supports only string payload
  • getOriginFromUrl seems broken, e.g. protocol and port was missing

Possible URI fix var childOrigin = new URI(window.location.href).path(url).origin(); with https://medialize.github.io/URI.js/

A way for postMessage IE compatibility:

let Compat = {

  init: () => {
    var ieVersion = Compat.detectIE();
    Compat.stringifyPostMessages = (ieVersion !== false && Compat.detectIE() <= 11);
  },

  detectIE: () => {
    var ua = window.navigator.userAgent;

    var msie = ua.indexOf('MSIE ');
    if (msie > 0) {
      // IE 10 or older => return version number
      return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);
    }

    var trident = ua.indexOf('Trident/');
    if (trident > 0) {
      // IE 11 => return version number
      var rv = ua.indexOf('rv:');
      return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10);
    }

    var edge = ua.indexOf('Edge/');
    if (edge > 0) {
      // Edge (IE 12+) => return version number
      return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10);
    }

    // other browser
    return false;
  },

  postMessage: (target, data, remoteOrigin) => {

    if (Compat.stringifyPostMessages) {
      return Compat.postMessageCompat(target, data, remoteOrigin);
    } else {
      target.postMessage(data, remoteOrigin);
    }

  },

  addMessageListener: (target, listener) => {
    if (Compat.stringifyPostMessages) {
      return Compat.addMessageListenerCompat(target, listener);
    } else {
      target.addEventListener(MESSAGE, listener, false);

      return {
        remove: () => {
          target.removeEventListener(MESSAGE, listener);
        }
      };
    }
  },

  postMessageCompat: (target, data, remoteOrigin) => {

    let str = JSON.stringify(data);
    target.postMessage(str, remoteOrigin);

  },

  addMessageListenerCompat: (target, listener) => {

    let wrapped = (e) => {

      if (e.data && e.data.startsWith("{")) {
        let data = JSON.parse(e.data);
        let ec = {
          data: data,
          source: e.source,
          origin: e.origin
        };
        listener(ec);
      } else {
        listener(e);
      }

    };

    target.addEventListener(MESSAGE, wrapped);

    return {
      remove: () => {
        target.removeEventListener(MESSAGE, wrapped);
      }
    };

  }

};

Compat.init();

Feature suggestion: parent/child connection timeout should reject promise

As far as I can see if I run child sand-alone and I make an attempt to connect to the parent the promise stays pending indefinitely.

It's super-easy to detect "just" running stand-alone in child (by checking if window.parent !== window), but it becomes harder if connection was not properly established (for any reason in fact)

Currently I use Promise.race for that but it seems like a reasonable thing to have it baked in.

Penpal should be capable of transferring Error objects as rejection reasons

As MDN Page on Promise.reject mentions rejecting promises with Error instances is a good practice.

There's even a prefer-promise-reject-errors ESLint rule for that.

Penpal fails with

index.js:234 Uncaught (in promise) DOMException: Failed to execute 'postMessage' on 'Window': Error: <error message here> could not be cloned.
    at http://localhost:3100/vendor.bundle.js:45082:21
    at <anonymous>

It should be capable of handling Error instances.
I'd expect for either of those to happen instead of getting the DOMException thrown:

  • [Easy] Panpal should transfer error messages as rejection reason.
  • [Powerful] Penpal should create its own Error with message describing the situation and possibly containing the original message. getsentry/raven-js may be a source of inspiration on how to serialise traces (long story short: it uses csnover/TraceKit)

SRC-less iframes report ERR_NOT_IN_IFRAME

I'm creating an iframe with JS, building my own document (with Penpal in head) and writing it in, the iframe loads but Penpal fails with: connectToParent() must be called within an iframe when it actually is and the parent has Penpal running and listening.

The check that's failing is here: https://github.com/Aaronius/penpal/blob/master/src/connectToParent.js#L32

I can only assume window does equal window.top when the document has been built from the parent.

Here's a poor test-case on jsbin:
https://jsbin.com/huhocig/5/edit?html,js,output

Poor because jsbin uses iframes, so the condition is met but hopefully you'll get the gist of how I'm building my iframe.

Is there a way around this, or can the check be disabled? If I manually comment out the check - everything works as expected.

error "Cannot read properties of null (reading 'postMessage')"

version: 5.3.0
I am getting getting this error:

handleSynMessageFactory.js:16 Uncaught TypeError: Cannot read properties of null (reading 'postMessage')
    at handleSynMessageFactory.js:16:1
    at handleMessage (connectToChild.js:35:1)

It seems to come form this line of code:

// at parent/handleSynMessageFactory:
event.source.postMessage(synAckMessage, originForSending);

Usually the source is a window, so I'm guessing this is happening because the parent window is getting a message from an iframe that already closed (similar to the connectionDestroyed error during handshake).

Maybe add a null guard there?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.