aaronius / penpal Goto Github PK
View Code? Open in Web Editor NEWA promise-based library for securely communicating with iframes via postMessage.
License: MIT License
A promise-based library for securely communicating with iframes via postMessage.
License: MIT License
This isn't too hard of an issue to workaround, but it might be nice to handle in the core library.
If you pass an object to postMessage
that contains a Function
, the method throws an error (at least under Firefox).
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.
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.
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.
Imagine the following common scenario:
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).
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
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);
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
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.
Penpal is now built using TypeScript, so if you're using TypeScript, you now get types for free!
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.
If you have feedback, please post a comment below. Thanks!
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.
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="..."
)
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
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 :)
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.
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.
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!!
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
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)
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:
which is being called unconditionally:
penpal/src/child/connectToParent.ts
Line 59 in 26f493d
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.
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.
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?
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.
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.)
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:
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.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);
}
}
});
}
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));
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):
penpal/src/parent/monitorIframeRemoval.ts
Lines 14 to 26 in da8df79
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?
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 ?
@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.
Line 392 in 843476f
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!
I noticed that if I reload the page in the iframe the API is not re-established on neither side:
Penpal.connectToChild
again (even if one should call it manually)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
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
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.
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
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
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();
});
});
});
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.
When tests are running on SauceLabs, the process fails. Here's the last relevant message:
09 05 2020 16:58:47.168:WARN [launcher]: Chrome on SauceLabs have not captured in 60000 ms, killing.
See an example failed build here: https://travis-ci.org/github/Aaronius/penpal/builds/685098099
I've tried several configuration changes but haven't had success fixing the problem. Alternatively, explore different testing solutions entirely.
I need to specify the child's origin, when the parent and the child are on the same domain, but different sub-domains.
I'm getting the following error:
Parent received handshake from origin https://child.domain.com which did not match expected origin https://parent.domain.com
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?
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?
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]
}
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
It would nice to see CI running for this project and for that to include some browser testing. ๐
I noticed that IE support is only for IE 10+, but it's nice to see that as a badge on the project page:
Sauce Labs has free browser testing for open source projects: https://saucelabs.com/open-source https://saucelabs.com/beta/signup/OSS/None
The library is currently not compatible with IE11.
There are two issues:
postMessage
supports only string payloadgetOriginFromUrl
seems broken, e.g. protocol and port was missingPossible 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();
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.
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:
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.
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?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.