Version 7 is here! It contains quite a few new features and breaking changes, so let's get into it.
Migration
To ease the pain of migration, running Penpal v7 in a parent window is compatible with running Penpal v6 in a child iframe. Therefore, if it's difficult to upgrade Penpal in the parent window and iframe at the same time, update code in the parent window first and then update code in the child iframe. Compatibility with v6 will be removed in the next major version of Penpal (v8). Note that running Penpal v6 in the parent window is not compatible with running Penpal v7 in the child iframe.
Breaking changes
A WindowMessenger
is now required
What
Message transmission details between a parent window and a child iframe have been moved to a new WindowMessenger
class. You'll now pass an instance of WindowMessenger
as the messenger
option when calling connect
. See Usage with an Iframe in the README.
Why
Separating message transmission into a dedicated class allows Penpal to support additional contexts—such as communication between a window and a worker—while keeping the shared, high-level logic inside connect
.
connectToChild
and connectToParent
have been renamed
What
connectToChild
and connectToParent
are now unified into a single connect
function.
Why
With WindowMessenger
receiving a reference to the iframe window instead of connectToChild
, the connectToChild
and connectToParent
functions essentially became identical and no longer needed to be separate functions.
Origin defaults have changed
What
When calling connectToChild
, the default value for the childOrigin
option was previously derived from the iframe’s src
attribute. This is no longer the case. Penpal now defaults to restricting communication to the origin of the parent window’s HTML document. If you'd like to derive the origin from the iframe’s src
when specifying the child origin, you can use new URL(iframe.src).origin
. See Usage with an Iframe in the README.
When calling connectToParent
, the default value for the parentOrigin
option was *
. This has been changed as well. Penpal now defaults to restricting communication to the origin of the child iframe’s HTML document.
Why
In this version, Penpal only receives a reference to the iframe’s content window (iframe.contentWindow
) rather than the iframe itself, so it no longer attempts to derive the child’s origin when connecting.
When connecting to the parent window from the child iframe, I wasn’t comfortable with Penpal using *
as the default parentOrigin
. Even though the README recommended against using the default, developers might easily overlook it. As this library is security-conscious, it seems more appropriate to encourage developers to manually specify *
when necessary, rather than rely on the default.
Debugging has changed
What
Previously, to enable debugging, you needed to set the debug
option to true
when calling connectToChild
or connectToParent
. Now, you’ll need to set the log
option to a function of your choice. Penpal exports a simple debug
function that logs debug messages to the console, and you can use this as the value for the log
option. See Debugging in the README.
Why
Allowing a function to be provided instead of a boolean increases flexibility. For instance, you can use a more advanced logging library, like the popular debug package, or write your own custom logging function.
Errors have changed
What
Errors thrown by Penpal were previously instances of the native Error
class. They are now instances of a new PenpalError
class, which extends from Error
. Additionally, error codes (found on the code
property) have been updated. The code ConnectionDestroyed
is now CONNECTION_DESTROYED
, and ConnectionTimeout
has changed to CONNECTION_TIMEOUT
. If you were using ErrorCode.ConnectionDestroyed
or ErrorCode.ConnectionTimeout
properties to reference the error code strings, these properties remain unchanged. The NoIframeSrc
error code has been removed because Penpal is now only provided with a reference to the iframe’s content window (iframe.contentWindow
) rather than the iframe itself.
Note that some error codes have been added. See Errors in the README for all the latest error codes.
Why
I added the PenpalError
class because I was uncomfortable with Penpal adding a code
property directly to the native Error
instance. TypeScript was uncomfortable with it as well. It made more sense to create a new class that extends Error
and properly supports the code
property.
Object containing remote methods is now a Proxy
object
What
In earlier versions of Penpal, connection.promise
resolved to an object containing methods that reflected the remote methods. These methods were defined as standard properties on the object. In this version, however, this object is now a Proxy
and the methods are no longer defined as standard properties.
To illustrate, let's assume we're following this example from version 6 where multiply
and divide
methods are being exposed from an iframe and used in the parent window.
connection.promise.then((child) => {
child.multiply(2, 6).then((total) => console.log(total));
child.divide(12, 4).then((total) => console.log(total));
console.log(Object.keys(child)); // ['multiply', 'divide']
});
Note how multiply
and divide
are both keys on child
. Let's see how this plays out in version 7:
connection.promise.then((child) => {
child.multiply(2, 6).then((total) => console.log(total));
child.divide(12, 4).then((total) => console.log(total));
console.log(Object.keys(child)); // []
});
The calls to multiply
and divide
are still the same and function properly, but notice how Object.keys
returns an empty array. The child
object is not pre-built with multiply
and divide
properties, but instead dynamically handles method calls as they occur.
Why
Using a proxy object simplifies and reduces the amount of work Penpal needs to perform as well as minimizes required code. Penpal no longer needs to extract names and "paths" of methods, send them to the remote, and then rebuild objects with the same structure.
Types have changed
What
The AsyncMethodReturns
type has been renamed to RemoteProxy
. The CallSender
type has been removed.
Why
The name AsyncMethodReturns
was overly focused on implementation details. The new name, RemoteProxy
, more accurately reflects its role as a proxy for methods exposed by the remote. Additionally, the type has been enhanced to support new features such as method call timeouts and transferable objects.
The CallSender
type was redundant. The existing Methods
type serves as the replacement for CallSender
.
New features
Support for workers, windows, and other things using ports
Penpal now supports workers (dedicated workers, shared workers, and service workers), windows opened using window.open()
, and other things that use MessagePorts. This is facilitated by the new WindowMessenger
, WorkerMessenger
, and PortMessenger
classes. See the usage examples in the README for more information.
Method call timeouts
When calling a remote method, you can now specify a timeout. If the timeout elapses before hearing back from the remote, the promise returned from the method call with be rejected with an error. See Method Call Timeouts for more information.
Support for transferable objects
Browsers natively support transferable objects for transferring large data between contexts. This functionality is now available in Penpal. See Transferring Large Data for more details.
Support for specifying multiple origins from either side of the connection
When connecting from an iframe to a parent window, you've been able to use a regular expression to restrict communication to multiple origins. However, this feature was not available when connecting from the parent window to an iframe. Now, this capability is available and identical when connecting from either direction. Furthermore, instead of specifying just one origin or regular expression, you can now provide an array of origins or regular expressions, making things easier and more flexible.
Enhanced handshake protocol
When establishing a connection, Penpal performs a handshake to ensure both participants can send and receive messages. Previously, this process required calling connectToChild
in the parent window before calling connectToParent
in the iframe. Penpal now uses an enhanced handshake protocol that allows either participant to call connect
first.
MessagePort usage
In previous versions, Penpal transmitted all messages between the parent window and iframe using postMessage
on the respective window objects. Now, Penpal uses postMessage
only during the handshake, while all subsequent communication occurs over MessagePorts. This approach reduces noise on global window objects and reduces security check overhead.
Parallel connections
In rare cases, you may wish to establish parallel connections between two participants. Penpal now supports this through the use of channels. See Parallel Connections for more details.
Bug fixes
No bug fixes. There are no known bugs at the moment.