penpal 7.0.0-beta.2 → 7.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -20
- package/cjs/CallOptions.d.ts +1 -2
- package/cjs/CallOptions.js +2 -1
- package/cjs/ErrorCodeObj.d.ts +9 -0
- package/cjs/ErrorCodeObj.js +14 -0
- package/cjs/PenpalError.d.ts +1 -1
- package/cjs/Reply.d.ts +1 -2
- package/cjs/Reply.js +2 -1
- package/cjs/backwardCompatibility.js +5 -6
- package/cjs/connect.js +3 -4
- package/cjs/connectCallHandler.js +4 -5
- package/cjs/connectRemoteProxy.js +26 -17
- package/cjs/guards.js +6 -7
- package/cjs/index.d.ts +1 -1
- package/cjs/index.js +3 -3
- package/cjs/indexForBundle.d.ts +8 -2
- package/cjs/indexForBundle.js +2 -2
- package/cjs/messengers/PortMessenger.d.ts +1 -4
- package/cjs/messengers/PortMessenger.js +22 -23
- package/cjs/messengers/WindowMessenger.d.ts +1 -13
- package/cjs/messengers/WindowMessenger.js +103 -104
- package/cjs/messengers/WorkerMessenger.d.ts +1 -6
- package/cjs/messengers/WorkerMessenger.js +46 -47
- package/cjs/shakeHands.js +7 -8
- package/cjs/types.d.ts +8 -7
- package/dist/penpal.js +238 -237
- package/dist/penpal.min.js +1 -1
- package/dist/penpal.min.js.map +1 -1
- package/lib/CallOptions.d.ts +1 -2
- package/lib/CallOptions.js +2 -1
- package/lib/ErrorCodeObj.d.ts +9 -0
- package/lib/ErrorCodeObj.js +12 -0
- package/lib/PenpalError.d.ts +1 -1
- package/lib/Reply.d.ts +1 -2
- package/lib/Reply.js +2 -1
- package/lib/backwardCompatibility.js +5 -6
- package/lib/connect.js +3 -4
- package/lib/connectCallHandler.js +4 -5
- package/lib/connectRemoteProxy.js +27 -18
- package/lib/guards.js +6 -7
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/indexForBundle.d.ts +8 -2
- package/lib/indexForBundle.js +1 -1
- package/lib/messengers/PortMessenger.d.ts +1 -4
- package/lib/messengers/PortMessenger.js +22 -23
- package/lib/messengers/WindowMessenger.d.ts +1 -13
- package/lib/messengers/WindowMessenger.js +103 -104
- package/lib/messengers/WorkerMessenger.d.ts +1 -6
- package/lib/messengers/WorkerMessenger.js +46 -47
- package/lib/shakeHands.js +7 -8
- package/lib/types.d.ts +8 -7
- package/package.json +3 -2
- package/cjs/enums.d.ts +0 -19
- package/cjs/enums.js +0 -25
- package/lib/enums.d.ts +0 -19
- package/lib/enums.js +0 -22
package/README.md
CHANGED
|
@@ -27,13 +27,13 @@
|
|
|
27
27
|
|
|
28
28
|
Install Penpal from npm as follows:
|
|
29
29
|
|
|
30
|
-
`npm install penpal`
|
|
30
|
+
`npm install penpal@next`
|
|
31
31
|
|
|
32
32
|
### Using a CDN
|
|
33
33
|
|
|
34
34
|
Alternatively, load a build of Penpal that is already hosted on a CDN:
|
|
35
35
|
|
|
36
|
-
`<script src="https://unpkg.com/penpal
|
|
36
|
+
`<script src="https://unpkg.com/penpal@next/dist/penpal.min.js"></script>`
|
|
37
37
|
|
|
38
38
|
Penpal will then be installed on `window.Penpal`. Usage is similar to if you were using it from npm, which is documented below, but instead of importing each module, you would access it on the `Penpal` global variable instead.
|
|
39
39
|
|
|
@@ -267,7 +267,7 @@ console.log(additionResult); // 8
|
|
|
267
267
|
### Window
|
|
268
268
|
|
|
269
269
|
```javascript
|
|
270
|
-
import {
|
|
270
|
+
import { PortMessenger, connect } from 'penpal';
|
|
271
271
|
|
|
272
272
|
const worker = new SharedWorker('shared-worker.js');
|
|
273
273
|
|
|
@@ -345,6 +345,16 @@ import { PortMessenger, connect } from 'penpal';
|
|
|
345
345
|
const initPenpal = async () => {
|
|
346
346
|
const { port1, port2 } = new MessageChannel();
|
|
347
347
|
|
|
348
|
+
navigator.serviceWorker.controller?.postMessage(
|
|
349
|
+
{
|
|
350
|
+
type: 'INIT_PENPAL',
|
|
351
|
+
port: port2,
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
transfer: [port2],
|
|
355
|
+
}
|
|
356
|
+
);
|
|
357
|
+
|
|
348
358
|
const messenger = new PortMessenger({
|
|
349
359
|
port: port1,
|
|
350
360
|
});
|
|
@@ -365,16 +375,6 @@ const initPenpal = async () => {
|
|
|
365
375
|
console.log(multiplicationResult); // 12
|
|
366
376
|
const divisionResult = await remote.divide(12, 4);
|
|
367
377
|
console.log(divisionResult); // 3
|
|
368
|
-
|
|
369
|
-
navigator.serviceWorker.controller?.postMessage(
|
|
370
|
-
{
|
|
371
|
-
type: 'INIT_PENPAL',
|
|
372
|
-
port: port2,
|
|
373
|
-
},
|
|
374
|
-
{
|
|
375
|
-
transfer: [port2],
|
|
376
|
-
}
|
|
377
|
-
);
|
|
378
378
|
};
|
|
379
379
|
|
|
380
380
|
if (navigator.serviceWorker.controller) {
|
|
@@ -566,13 +566,13 @@ const connection = connect({
|
|
|
566
566
|
|
|
567
567
|
## Parallel Connections
|
|
568
568
|
|
|
569
|
-
In fairly rare cases, you may wish to make parallel connections between two participants. To illustrate, let's use a scenario where you wish to make two parallel connections between
|
|
569
|
+
In fairly rare cases, you may wish to make parallel connections between two participants. To illustrate, let's use a scenario where you wish to make two parallel connections between a parent window and an iframe window. In other words, you will be calling `connect()` twice within the parent window and twice within the iframe window.
|
|
570
570
|
|
|
571
|
-
In an attempt to establish these two connections, Penpal in
|
|
571
|
+
In an attempt to establish these two connections, Penpal in the parent window will be calling `postMessage()` on the iframe's window object (`iframe.contentWindow`). By default, when Penpal within the iframe window receives these messages, it has no way to disambiguate messages related to the parent window's first call to `connect()` from messages related to the parent window's second call to `connect()`. As a result, the connections may fail to be properly established.
|
|
572
572
|
|
|
573
573
|
To prevent this issue, Penpal provides the concept of channels. A channel is a string identifier of your choosing that you may provide when calling `connect()` within both participants. When a channel is provided, it is used to disambiguate communication between parallel connections. This is better explained in code:
|
|
574
574
|
|
|
575
|
-
### Window
|
|
575
|
+
### Parent Window
|
|
576
576
|
|
|
577
577
|
```javascript
|
|
578
578
|
import { WindowMessenger, connect } from 'penpal';
|
|
@@ -613,14 +613,14 @@ const connectionB = connect({
|
|
|
613
613
|
});
|
|
614
614
|
```
|
|
615
615
|
|
|
616
|
-
### Window
|
|
616
|
+
### Iframe Window
|
|
617
617
|
|
|
618
618
|
```javascript
|
|
619
619
|
import { WindowMessenger, connect } from 'penpal';
|
|
620
620
|
|
|
621
621
|
const messengerA = new WindowMessenger({
|
|
622
622
|
remoteWindow: window.parent,
|
|
623
|
-
allowedOrigins: ['https://
|
|
623
|
+
allowedOrigins: ['https://parentorigin.example.com'],
|
|
624
624
|
});
|
|
625
625
|
|
|
626
626
|
const connectionA = connect({
|
|
@@ -650,7 +650,7 @@ const connectionB = connect({
|
|
|
650
650
|
});
|
|
651
651
|
```
|
|
652
652
|
|
|
653
|
-
Although we're using `WindowMessenger` here to
|
|
653
|
+
Although we're using `WindowMessenger` here to connect between a parent window and an iframe window, channels would similarly need to be used when using `WorkerMessenger` to make parallel connections to a worker. When using `PortMessenger`, channels are only needed when establishing parallel connections over a single pair of ports.
|
|
654
654
|
|
|
655
655
|
## Errors
|
|
656
656
|
|
|
@@ -680,7 +680,7 @@ The promise returned from a method call will be rejected with this error if the
|
|
|
680
680
|
|
|
681
681
|
When a connection is being established, the promise found at `connection.promise` will be rejected with this error if a message cannot be transmitted. When a method call is being made, the promise returned from the method call will be rejected with this error if a message cannot be transmitted.
|
|
682
682
|
|
|
683
|
-
### Error
|
|
683
|
+
### Referencing Error Codes
|
|
684
684
|
|
|
685
685
|
For your convenience, the above error codes can be imported and referenced as follows:
|
|
686
686
|
|
|
@@ -725,6 +725,14 @@ const remote = await connection.promise;
|
|
|
725
725
|
const multiplicationResult = await remote.multiply(2, 6);
|
|
726
726
|
```
|
|
727
727
|
|
|
728
|
+
When creating a worker, it's highly recommended that you add the following line of code at the top of your worker script depending on which type of worker you're creating:
|
|
729
|
+
|
|
730
|
+
- Dedicated (regular) worker: `declare const self: DedicatedWorkerGlobalScope;`
|
|
731
|
+
- Shared worker: `declare const self: SharedWorkerGlobalScope;`
|
|
732
|
+
- Service worker: `declare const self: ServiceWorkerGlobalScope;`
|
|
733
|
+
|
|
734
|
+
This lets TypeScript know which type of worker you're creating, and you'll run into fewer TypeScript errors.
|
|
735
|
+
|
|
728
736
|
### Exported Types
|
|
729
737
|
|
|
730
738
|
Penpal exports several types for your usage. Import types as follows:
|
package/cjs/CallOptions.d.ts
CHANGED
package/cjs/CallOptions.js
CHANGED
|
@@ -6,7 +6,8 @@ class CallOptions {
|
|
|
6
6
|
timeout;
|
|
7
7
|
// Allows TypeScript to distinguish between an actual instance of this
|
|
8
8
|
// class versus an object that looks structurally similar.
|
|
9
|
-
|
|
9
|
+
// eslint-disable-next-line no-unused-private-class-members
|
|
10
|
+
#brand = brand;
|
|
10
11
|
constructor(options) {
|
|
11
12
|
this.transferables = options?.transferables;
|
|
12
13
|
this.timeout = options?.timeout;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
declare const ErrorCodeObj: {
|
|
2
|
+
readonly ConnectionDestroyed: "CONNECTION_DESTROYED";
|
|
3
|
+
readonly ConnectionTimeout: "CONNECTION_TIMEOUT";
|
|
4
|
+
readonly InvalidArgument: "INVALID_ARGUMENT";
|
|
5
|
+
readonly MethodCallTimeout: "METHOD_CALL_TIMEOUT";
|
|
6
|
+
readonly MethodNotFound: "METHOD_NOT_FOUND";
|
|
7
|
+
readonly TransmissionFailed: "TRANSMISSION_FAILED";
|
|
8
|
+
};
|
|
9
|
+
export default ErrorCodeObj;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
// Not intended to be used internally. Can be useful externally
|
|
4
|
+
// in projects not using TypeScript. It has the `Obj` suffix to disambiguate
|
|
5
|
+
// it from the ErrorCode string union.
|
|
6
|
+
const ErrorCodeObj = {
|
|
7
|
+
ConnectionDestroyed: 'CONNECTION_DESTROYED',
|
|
8
|
+
ConnectionTimeout: 'CONNECTION_TIMEOUT',
|
|
9
|
+
InvalidArgument: 'INVALID_ARGUMENT',
|
|
10
|
+
MethodCallTimeout: 'METHOD_CALL_TIMEOUT',
|
|
11
|
+
MethodNotFound: 'METHOD_NOT_FOUND',
|
|
12
|
+
TransmissionFailed: 'TRANSMISSION_FAILED',
|
|
13
|
+
};
|
|
14
|
+
exports.default = ErrorCodeObj;
|
package/cjs/PenpalError.d.ts
CHANGED
package/cjs/Reply.d.ts
CHANGED
package/cjs/Reply.js
CHANGED
|
@@ -6,7 +6,8 @@ class Reply {
|
|
|
6
6
|
transferables;
|
|
7
7
|
// Allows TypeScript to distinguish between an actual instance of this
|
|
8
8
|
// class versus an object that looks structurally similar.
|
|
9
|
-
|
|
9
|
+
// eslint-disable-next-line no-unused-private-class-members
|
|
10
|
+
#brand = brand;
|
|
10
11
|
constructor(value, options) {
|
|
11
12
|
this.value = value;
|
|
12
13
|
this.transferables = options?.transferables;
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.downgradeMessage = exports.upgradeMessage = exports.isDeprecatedMessage = exports.DEPRECATED_PENPAL_PARTICIPANT_ID = void 0;
|
|
4
4
|
const namespace_1 = require("./namespace");
|
|
5
|
-
const enums_1 = require("./enums");
|
|
6
5
|
const guards_1 = require("./guards");
|
|
7
6
|
const PenpalBugError_1 = require("./PenpalBugError");
|
|
8
7
|
exports.DEPRECATED_PENPAL_PARTICIPANT_ID = 'deprecated-penpal';
|
|
@@ -34,7 +33,7 @@ const upgradeMessage = (message) => {
|
|
|
34
33
|
return {
|
|
35
34
|
namespace: namespace_1.default,
|
|
36
35
|
channel: undefined,
|
|
37
|
-
type:
|
|
36
|
+
type: 'SYN',
|
|
38
37
|
participantId: exports.DEPRECATED_PENPAL_PARTICIPANT_ID,
|
|
39
38
|
};
|
|
40
39
|
}
|
|
@@ -42,14 +41,14 @@ const upgradeMessage = (message) => {
|
|
|
42
41
|
return {
|
|
43
42
|
namespace: namespace_1.default,
|
|
44
43
|
channel: undefined,
|
|
45
|
-
type:
|
|
44
|
+
type: 'ACK2',
|
|
46
45
|
};
|
|
47
46
|
}
|
|
48
47
|
if (message.penpal === DeprecatedMessageType.Call) {
|
|
49
48
|
return {
|
|
50
49
|
namespace: namespace_1.default,
|
|
51
50
|
channel: undefined,
|
|
52
|
-
type:
|
|
51
|
+
type: 'CALL',
|
|
53
52
|
// Actually converting the ID to a string would break communication.
|
|
54
53
|
id: message.id,
|
|
55
54
|
methodPath: upgradeMethodPath(message.methodName),
|
|
@@ -61,7 +60,7 @@ const upgradeMessage = (message) => {
|
|
|
61
60
|
return {
|
|
62
61
|
namespace: namespace_1.default,
|
|
63
62
|
channel: undefined,
|
|
64
|
-
type:
|
|
63
|
+
type: 'REPLY',
|
|
65
64
|
// Actually converting the ID to a string would break communication.
|
|
66
65
|
callId: message.id,
|
|
67
66
|
value: message.returnValue,
|
|
@@ -71,7 +70,7 @@ const upgradeMessage = (message) => {
|
|
|
71
70
|
return {
|
|
72
71
|
namespace: namespace_1.default,
|
|
73
72
|
channel: undefined,
|
|
74
|
-
type:
|
|
73
|
+
type: 'REPLY',
|
|
75
74
|
// Actually converting the ID to a string would break communication.
|
|
76
75
|
callId: message.id,
|
|
77
76
|
isError: true,
|
package/cjs/connect.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const PenpalError_1 = require("./PenpalError");
|
|
4
|
-
const enums_1 = require("./enums");
|
|
5
4
|
const shakeHands_1 = require("./shakeHands");
|
|
6
5
|
const guards_1 = require("./guards");
|
|
7
6
|
const once_1 = require("./once");
|
|
@@ -12,10 +11,10 @@ const usedMessengers = new WeakSet();
|
|
|
12
11
|
*/
|
|
13
12
|
const connect = ({ messenger, methods = {}, timeout, channel, log, }) => {
|
|
14
13
|
if (!messenger) {
|
|
15
|
-
throw new PenpalError_1.default(
|
|
14
|
+
throw new PenpalError_1.default('INVALID_ARGUMENT', 'messenger must be defined');
|
|
16
15
|
}
|
|
17
16
|
if (usedMessengers.has(messenger)) {
|
|
18
|
-
throw new PenpalError_1.default(
|
|
17
|
+
throw new PenpalError_1.default('INVALID_ARGUMENT', 'A messenger can only be used for a single connection');
|
|
19
18
|
}
|
|
20
19
|
usedMessengers.add(messenger);
|
|
21
20
|
const connectionDestroyedHandlers = [messenger.destroy];
|
|
@@ -24,7 +23,7 @@ const connect = ({ messenger, methods = {}, timeout, channel, log, }) => {
|
|
|
24
23
|
const destroyMessage = {
|
|
25
24
|
namespace: namespace_1.default,
|
|
26
25
|
channel,
|
|
27
|
-
type:
|
|
26
|
+
type: 'DESTROY',
|
|
28
27
|
};
|
|
29
28
|
try {
|
|
30
29
|
messenger.sendMessage(destroyMessage);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const errorSerialization_1 = require("./errorSerialization");
|
|
4
|
-
const enums_1 = require("./enums");
|
|
5
4
|
const Reply_1 = require("./Reply");
|
|
6
5
|
const PenpalError_1 = require("./PenpalError");
|
|
7
6
|
const methodSerialization_1 = require("./methodSerialization");
|
|
@@ -10,7 +9,7 @@ const namespace_1 = require("./namespace");
|
|
|
10
9
|
const createErrorReplyMessage = (channel, callId, error) => ({
|
|
11
10
|
namespace: namespace_1.default,
|
|
12
11
|
channel,
|
|
13
|
-
type:
|
|
12
|
+
type: 'REPLY',
|
|
14
13
|
callId,
|
|
15
14
|
isError: true,
|
|
16
15
|
...(error instanceof Error
|
|
@@ -42,7 +41,7 @@ const connectCallHandler = (messenger, methods, channel, log) => {
|
|
|
42
41
|
try {
|
|
43
42
|
const method = (0, methodSerialization_1.getMethodAtMethodPath)(methodPath, methods);
|
|
44
43
|
if (!method) {
|
|
45
|
-
throw new PenpalError_1.default(
|
|
44
|
+
throw new PenpalError_1.default('METHOD_NOT_FOUND', `Method \`${(0, methodSerialization_1.formatMethodPath)(methodPath)}\` is not found.`);
|
|
46
45
|
}
|
|
47
46
|
let value = await method(...args);
|
|
48
47
|
if (value instanceof Reply_1.default) {
|
|
@@ -52,7 +51,7 @@ const connectCallHandler = (messenger, methods, channel, log) => {
|
|
|
52
51
|
replyMessage = {
|
|
53
52
|
namespace: namespace_1.default,
|
|
54
53
|
channel,
|
|
55
|
-
type:
|
|
54
|
+
type: 'REPLY',
|
|
56
55
|
callId,
|
|
57
56
|
value,
|
|
58
57
|
};
|
|
@@ -74,7 +73,7 @@ const connectCallHandler = (messenger, methods, channel, log) => {
|
|
|
74
73
|
// If a consumer attempts to send an object that's not
|
|
75
74
|
// cloneable (e.g., window), we want to ensure the receiver's promise
|
|
76
75
|
// gets rejected.
|
|
77
|
-
if (error.name ===
|
|
76
|
+
if (error.name === 'DataCloneError') {
|
|
78
77
|
replyMessage = createErrorReplyMessage(channel, callId, error);
|
|
79
78
|
log?.(`Sending ${(0, methodSerialization_1.formatMethodPath)(methodPath)}() reply`, replyMessage);
|
|
80
79
|
messenger.sendMessage(replyMessage);
|
|
@@ -3,18 +3,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const generateId_1 = require("./generateId");
|
|
4
4
|
const errorSerialization_1 = require("./errorSerialization");
|
|
5
5
|
const methodSerialization_1 = require("./methodSerialization");
|
|
6
|
-
const enums_1 = require("./enums");
|
|
7
6
|
const CallOptions_1 = require("./CallOptions");
|
|
8
7
|
const PenpalError_1 = require("./PenpalError");
|
|
9
8
|
const guards_1 = require("./guards");
|
|
10
9
|
const namespace_1 = require("./namespace");
|
|
10
|
+
const methodsToTreatAsNative = new Set(['apply', 'call', 'bind']);
|
|
11
11
|
const createRemoteProxy = (callback, log, path = []) => {
|
|
12
|
-
return new Proxy(path
|
|
12
|
+
return new Proxy(path.length
|
|
13
13
|
? () => {
|
|
14
14
|
// Intentionally empty
|
|
15
15
|
}
|
|
16
16
|
: Object.create(null), {
|
|
17
|
-
get(
|
|
17
|
+
get(target, prop) {
|
|
18
18
|
// If a promise is resolved with this proxy object, the JavaScript
|
|
19
19
|
// runtime will look for a `then` property on this object to determine
|
|
20
20
|
// if it should be treated as a promise (to support promise chaining).
|
|
@@ -24,24 +24,33 @@ const createRemoteProxy = (callback, log, path = []) => {
|
|
|
24
24
|
if (prop === 'then') {
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
27
|
+
// Because we're using a proxy and because Penpal supports developers
|
|
28
|
+
// exposing nested methods, we have a predicament. If a developer
|
|
29
|
+
// calls, for example, remote.auth.apply(), are they
|
|
30
|
+
// attempting to call a nested apply() method that a developer has
|
|
31
|
+
// explicitly exposed from the remote? Could they instead be attempting
|
|
32
|
+
// to call Function.prototype.apply() on the remote.auth() method?
|
|
33
|
+
// Without the remote telling the local Penpal which methods the
|
|
34
|
+
// developer has exposed, it has no way of knowing (and the main reason
|
|
35
|
+
// we use a proxy is so that Penpal doesn't have to communicate which
|
|
36
|
+
// methods are exposed). So, we treat certain methods as native methods
|
|
37
|
+
// and return the native method rather than a proxy. The downside of
|
|
38
|
+
// this is that if a developer has explicitly exposed a nested method
|
|
39
|
+
// with the same name as one of these native method names, the developer
|
|
40
|
+
// will be unable to call the exposed remote method because they will
|
|
41
|
+
// be calling the method on the Function prototype instead.
|
|
42
|
+
if (path.length && methodsToTreatAsNative.has(prop)) {
|
|
43
|
+
return Reflect.get(target, prop);
|
|
44
|
+
}
|
|
27
45
|
return createRemoteProxy(callback, log, [...path, prop]);
|
|
28
46
|
},
|
|
29
47
|
apply(target, _thisArg, args) {
|
|
30
|
-
if (log) {
|
|
31
|
-
const lastPathSegment = path.at(-1);
|
|
32
|
-
const builtInFunction = target[lastPathSegment];
|
|
33
|
-
if ((0, guards_1.isFunction)(builtInFunction)) {
|
|
34
|
-
log(`You may be attempting to call the native ` +
|
|
35
|
-
`\`${lastPathSegment}\` method which is not supported. Call ` +
|
|
36
|
-
`will be sent to remote.`);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
48
|
return callback(path, args);
|
|
40
49
|
},
|
|
41
50
|
});
|
|
42
51
|
};
|
|
43
52
|
const getDestroyedConnectionMethodCallError = (methodPath) => {
|
|
44
|
-
return new PenpalError_1.default(
|
|
53
|
+
return new PenpalError_1.default('CONNECTION_DESTROYED', `Method call ${(0, methodSerialization_1.formatMethodPath)(methodPath)}() failed due to destroyed connection`);
|
|
45
54
|
};
|
|
46
55
|
/**
|
|
47
56
|
* Creates a proxy. When methods are called on the proxy, a "call" message will
|
|
@@ -89,10 +98,10 @@ const connectRemoteProxy = (messenger, channel, log) => {
|
|
|
89
98
|
// running tests if we don't disambiguate the browser setTimeout
|
|
90
99
|
// from node's setTimeout. There may be a better way to configure
|
|
91
100
|
// Karma + Rollup + Typescript to avoid node type leakage.
|
|
92
|
-
const timeoutId = timeout
|
|
101
|
+
const timeoutId = timeout !== undefined
|
|
93
102
|
? window.setTimeout(() => {
|
|
94
103
|
replyHandlers.delete(callId);
|
|
95
|
-
reject(new PenpalError_1.default(
|
|
104
|
+
reject(new PenpalError_1.default('METHOD_CALL_TIMEOUT', `Method call ${(0, methodSerialization_1.formatMethodPath)(methodPath)}() timed out after ${timeout}ms`));
|
|
96
105
|
}, timeout)
|
|
97
106
|
: undefined;
|
|
98
107
|
replyHandlers.set(callId, { methodPath, resolve, reject, timeoutId });
|
|
@@ -100,7 +109,7 @@ const connectRemoteProxy = (messenger, channel, log) => {
|
|
|
100
109
|
const callMessage = {
|
|
101
110
|
namespace: namespace_1.default,
|
|
102
111
|
channel,
|
|
103
|
-
type:
|
|
112
|
+
type: 'CALL',
|
|
104
113
|
id: callId,
|
|
105
114
|
methodPath,
|
|
106
115
|
args: argsWithoutOptions,
|
|
@@ -109,7 +118,7 @@ const connectRemoteProxy = (messenger, channel, log) => {
|
|
|
109
118
|
messenger.sendMessage(callMessage, transferables);
|
|
110
119
|
}
|
|
111
120
|
catch (error) {
|
|
112
|
-
reject(new PenpalError_1.default(
|
|
121
|
+
reject(new PenpalError_1.default('TRANSMISSION_FAILED', error.message));
|
|
113
122
|
}
|
|
114
123
|
});
|
|
115
124
|
}, log);
|
package/cjs/guards.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.isDestroyMessage = exports.isReplyMessage = exports.isCallMessage = exports.isAck2Message = exports.isAck1Message = exports.isSynMessage = exports.isMessage = exports.isFunction = exports.isObject = void 0;
|
|
4
4
|
const namespace_1 = require("./namespace");
|
|
5
|
-
const enums_1 = require("./enums");
|
|
6
5
|
const isObject = (value) => {
|
|
7
6
|
return typeof value === 'object' && value !== null;
|
|
8
7
|
};
|
|
@@ -16,26 +15,26 @@ const isMessage = (data) => {
|
|
|
16
15
|
};
|
|
17
16
|
exports.isMessage = isMessage;
|
|
18
17
|
const isSynMessage = (message) => {
|
|
19
|
-
return message.type ===
|
|
18
|
+
return message.type === 'SYN';
|
|
20
19
|
};
|
|
21
20
|
exports.isSynMessage = isSynMessage;
|
|
22
21
|
const isAck1Message = (message) => {
|
|
23
|
-
return message.type ===
|
|
22
|
+
return message.type === 'ACK1';
|
|
24
23
|
};
|
|
25
24
|
exports.isAck1Message = isAck1Message;
|
|
26
25
|
const isAck2Message = (message) => {
|
|
27
|
-
return message.type ===
|
|
26
|
+
return message.type === 'ACK2';
|
|
28
27
|
};
|
|
29
28
|
exports.isAck2Message = isAck2Message;
|
|
30
29
|
const isCallMessage = (message) => {
|
|
31
|
-
return message.type ===
|
|
30
|
+
return message.type === 'CALL';
|
|
32
31
|
};
|
|
33
32
|
exports.isCallMessage = isCallMessage;
|
|
34
33
|
const isReplyMessage = (message) => {
|
|
35
|
-
return message.type ===
|
|
34
|
+
return message.type === 'REPLY';
|
|
36
35
|
};
|
|
37
36
|
exports.isReplyMessage = isReplyMessage;
|
|
38
37
|
const isDestroyMessage = (message) => {
|
|
39
|
-
return message.type ===
|
|
38
|
+
return message.type === 'DESTROY';
|
|
40
39
|
};
|
|
41
40
|
exports.isDestroyMessage = isDestroyMessage;
|
package/cjs/index.d.ts
CHANGED
|
@@ -5,6 +5,6 @@ export { default as PortMessenger } from './messengers/PortMessenger';
|
|
|
5
5
|
export { default as CallOptions } from './CallOptions';
|
|
6
6
|
export { default as Reply } from './Reply';
|
|
7
7
|
export { default as PenpalError } from './PenpalError';
|
|
8
|
+
export { default as ErrorCode } from './ErrorCodeObj';
|
|
8
9
|
export { default as debug } from './debug';
|
|
9
|
-
export { ErrorCode } from './enums';
|
|
10
10
|
export { Connection, RemoteProxy, Methods } from './types';
|
package/cjs/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.debug = exports.ErrorCode = exports.PenpalError = exports.Reply = exports.CallOptions = exports.PortMessenger = exports.WorkerMessenger = exports.WindowMessenger = exports.connect = void 0;
|
|
4
4
|
var connect_1 = require("./connect");
|
|
5
5
|
Object.defineProperty(exports, "connect", { enumerable: true, get: function () { return connect_1.default; } });
|
|
6
6
|
var WindowMessenger_1 = require("./messengers/WindowMessenger");
|
|
@@ -15,7 +15,7 @@ var Reply_1 = require("./Reply");
|
|
|
15
15
|
Object.defineProperty(exports, "Reply", { enumerable: true, get: function () { return Reply_1.default; } });
|
|
16
16
|
var PenpalError_1 = require("./PenpalError");
|
|
17
17
|
Object.defineProperty(exports, "PenpalError", { enumerable: true, get: function () { return PenpalError_1.default; } });
|
|
18
|
+
var ErrorCodeObj_1 = require("./ErrorCodeObj");
|
|
19
|
+
Object.defineProperty(exports, "ErrorCode", { enumerable: true, get: function () { return ErrorCodeObj_1.default; } });
|
|
18
20
|
var debug_1 = require("./debug");
|
|
19
21
|
Object.defineProperty(exports, "debug", { enumerable: true, get: function () { return debug_1.default; } });
|
|
20
|
-
var enums_1 = require("./enums");
|
|
21
|
-
Object.defineProperty(exports, "ErrorCode", { enumerable: true, get: function () { return enums_1.ErrorCode; } });
|
package/cjs/indexForBundle.d.ts
CHANGED
|
@@ -4,7 +4,6 @@ import PortMessenger from './messengers/PortMessenger';
|
|
|
4
4
|
import CallOptions from './CallOptions';
|
|
5
5
|
import Reply from './Reply';
|
|
6
6
|
import PenpalError from './PenpalError';
|
|
7
|
-
import { ErrorCode } from './enums';
|
|
8
7
|
declare const _default: {
|
|
9
8
|
connect: <TMethods extends import("./types").Methods>({ messenger, methods, timeout, channel, log, }: {
|
|
10
9
|
messenger: import("./messengers/Messenger").default;
|
|
@@ -20,6 +19,13 @@ declare const _default: {
|
|
|
20
19
|
Reply: typeof Reply;
|
|
21
20
|
PenpalError: typeof PenpalError;
|
|
22
21
|
debug: (prefix?: string) => import("./types").Log;
|
|
23
|
-
ErrorCode:
|
|
22
|
+
ErrorCode: {
|
|
23
|
+
readonly ConnectionDestroyed: "CONNECTION_DESTROYED";
|
|
24
|
+
readonly ConnectionTimeout: "CONNECTION_TIMEOUT";
|
|
25
|
+
readonly InvalidArgument: "INVALID_ARGUMENT";
|
|
26
|
+
readonly MethodCallTimeout: "METHOD_CALL_TIMEOUT";
|
|
27
|
+
readonly MethodNotFound: "METHOD_NOT_FOUND";
|
|
28
|
+
readonly TransmissionFailed: "TRANSMISSION_FAILED";
|
|
29
|
+
};
|
|
24
30
|
};
|
|
25
31
|
export default _default;
|
package/cjs/indexForBundle.js
CHANGED
|
@@ -7,8 +7,8 @@ const PortMessenger_1 = require("./messengers/PortMessenger");
|
|
|
7
7
|
const CallOptions_1 = require("./CallOptions");
|
|
8
8
|
const Reply_1 = require("./Reply");
|
|
9
9
|
const PenpalError_1 = require("./PenpalError");
|
|
10
|
+
const ErrorCodeObj_1 = require("./ErrorCodeObj");
|
|
10
11
|
const debug_1 = require("./debug");
|
|
11
|
-
const enums_1 = require("./enums");
|
|
12
12
|
exports.default = {
|
|
13
13
|
connect: connect_1.default,
|
|
14
14
|
WindowMessenger: WindowMessenger_1.default,
|
|
@@ -18,5 +18,5 @@ exports.default = {
|
|
|
18
18
|
Reply: Reply_1.default,
|
|
19
19
|
PenpalError: PenpalError_1.default,
|
|
20
20
|
debug: debug_1.default,
|
|
21
|
-
ErrorCode:
|
|
21
|
+
ErrorCode: ErrorCodeObj_1.default,
|
|
22
22
|
};
|
|
@@ -10,12 +10,9 @@ type Options = {
|
|
|
10
10
|
* Handles the details of communicating on a MessagePort.
|
|
11
11
|
*/
|
|
12
12
|
declare class PortMessenger implements Messenger {
|
|
13
|
-
private
|
|
14
|
-
private _validateReceivedMessage?;
|
|
15
|
-
private _messageCallbacks;
|
|
13
|
+
#private;
|
|
16
14
|
constructor({ port }: Options);
|
|
17
15
|
initialize: ({ validateReceivedMessage }: InitializeOptions) => void;
|
|
18
|
-
private _handleMessage;
|
|
19
16
|
sendMessage: (message: Message, transferables?: Transferable[]) => void;
|
|
20
17
|
addMessageHandler: (callback: MessageHandler) => void;
|
|
21
18
|
removeMessageHandler: (callback: MessageHandler) => void;
|
|
@@ -1,48 +1,47 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const PenpalError_1 = require("../PenpalError");
|
|
4
|
-
const enums_1 = require("../enums");
|
|
5
4
|
/**
|
|
6
5
|
* Handles the details of communicating on a MessagePort.
|
|
7
6
|
*/
|
|
8
7
|
class PortMessenger {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
#port;
|
|
9
|
+
#validateReceivedMessage;
|
|
10
|
+
#messageCallbacks = new Set();
|
|
12
11
|
constructor({ port }) {
|
|
13
12
|
if (!port) {
|
|
14
|
-
throw new PenpalError_1.default(
|
|
13
|
+
throw new PenpalError_1.default('INVALID_ARGUMENT', 'port must be defined');
|
|
15
14
|
}
|
|
16
|
-
this
|
|
15
|
+
this.#port = port;
|
|
17
16
|
}
|
|
18
17
|
initialize = ({ validateReceivedMessage }) => {
|
|
19
|
-
this
|
|
20
|
-
this.
|
|
21
|
-
this.
|
|
22
|
-
};
|
|
23
|
-
_handleMessage = ({ data }) => {
|
|
24
|
-
if (!this._validateReceivedMessage?.(data)) {
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
for (const callback of this._messageCallbacks) {
|
|
28
|
-
callback(data);
|
|
29
|
-
}
|
|
18
|
+
this.#validateReceivedMessage = validateReceivedMessage;
|
|
19
|
+
this.#port.addEventListener('message', this.#handleMessage);
|
|
20
|
+
this.#port.start();
|
|
30
21
|
};
|
|
31
22
|
sendMessage = (message, transferables) => {
|
|
32
|
-
this
|
|
23
|
+
this.#port?.postMessage(message, {
|
|
33
24
|
transfer: transferables,
|
|
34
25
|
});
|
|
35
26
|
};
|
|
36
27
|
addMessageHandler = (callback) => {
|
|
37
|
-
this.
|
|
28
|
+
this.#messageCallbacks.add(callback);
|
|
38
29
|
};
|
|
39
30
|
removeMessageHandler = (callback) => {
|
|
40
|
-
this.
|
|
31
|
+
this.#messageCallbacks.delete(callback);
|
|
41
32
|
};
|
|
42
33
|
destroy = () => {
|
|
43
|
-
this.
|
|
44
|
-
this.
|
|
45
|
-
this.
|
|
34
|
+
this.#port.removeEventListener('message', this.#handleMessage);
|
|
35
|
+
this.#port.close();
|
|
36
|
+
this.#messageCallbacks.clear();
|
|
37
|
+
};
|
|
38
|
+
#handleMessage = ({ data }) => {
|
|
39
|
+
if (!this.#validateReceivedMessage?.(data)) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
for (const callback of this.#messageCallbacks) {
|
|
43
|
+
callback(data);
|
|
44
|
+
}
|
|
46
45
|
};
|
|
47
46
|
}
|
|
48
47
|
exports.default = PortMessenger;
|
|
@@ -18,21 +18,9 @@ type Options = {
|
|
|
18
18
|
* Handles the details of communicating with a child window.
|
|
19
19
|
*/
|
|
20
20
|
declare class WindowMessenger implements Messenger {
|
|
21
|
-
private
|
|
22
|
-
private _allowedOrigins;
|
|
23
|
-
private _log?;
|
|
24
|
-
private _validateReceivedMessage?;
|
|
25
|
-
private _concreteRemoteOrigin?;
|
|
26
|
-
private _messageCallbacks;
|
|
27
|
-
private _port?;
|
|
28
|
-
private _isChildUsingDeprecatedProtocol;
|
|
21
|
+
#private;
|
|
29
22
|
constructor({ remoteWindow, allowedOrigins }: Options);
|
|
30
23
|
initialize: ({ log, validateReceivedMessage }: InitializeOptions) => void;
|
|
31
|
-
private _isAllowedOrigin;
|
|
32
|
-
private _getOriginForSendingMessage;
|
|
33
|
-
private _destroyPort;
|
|
34
|
-
private _handleMessageFromRemoteWindow;
|
|
35
|
-
private _handleMessageFromPort;
|
|
36
24
|
sendMessage: (message: Message, transferables?: Transferable[]) => void;
|
|
37
25
|
addMessageHandler: (callback: MessageHandler) => void;
|
|
38
26
|
removeMessageHandler: (callback: MessageHandler) => void;
|