@adobe/uix-core 0.7.1-nightly.20230226 → 0.7.1-nightly.20230228
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/dist/cross-realm-object.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +131 -32
- package/dist/index.js.map +1 -1
- package/dist/logging-formatters.d.ts +34 -0
- package/dist/logging-formatters.d.ts.map +1 -0
- package/dist/promises/timed.d.ts +2 -2
- package/dist/promises/timed.d.ts.map +1 -1
- package/dist/tunnel/tunnel.d.ts +3 -1
- package/dist/tunnel/tunnel.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/cross-realm-object.ts +6 -2
- package/src/index.ts +1 -0
- package/src/logging-formatters.ts +57 -0
- package/src/promises/timed.ts +13 -5
- package/src/tunnel/tunnel.test.ts +41 -19
- package/src/tunnel/tunnel.ts +87 -26
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Copyright 2022 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { HostMethodAddress } from "./types";
|
|
14
|
+
import { isFunction, isIterable, isPrimitive } from "./value-assertions";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Try and format any type of value for logging.
|
|
18
|
+
*
|
|
19
|
+
* @privateRemarks
|
|
20
|
+
* **WARNING**: This is an expensive operation due to the JSON.stringify, and
|
|
21
|
+
* should only be done when debugging or in error conditions.
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
24
|
+
export function formatHostMethodArgument(argument: unknown): string {
|
|
25
|
+
try {
|
|
26
|
+
return JSON.stringify(argument, null, 2);
|
|
27
|
+
} catch (e) {
|
|
28
|
+
if (isIterable(argument)) {
|
|
29
|
+
return `Iterable<${argument.length}>`;
|
|
30
|
+
}
|
|
31
|
+
if (isPrimitive(argument) || isFunction(argument)) {
|
|
32
|
+
return `${argument}`;
|
|
33
|
+
}
|
|
34
|
+
return Object.prototype.toString.call(argument);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Try and format a remote method call as it would appear during debugging.
|
|
40
|
+
*
|
|
41
|
+
* @privateRemarks
|
|
42
|
+
* **WARNING**: This is an expensive operation due to the JSON.stringify, and
|
|
43
|
+
* should only be done when debugging or in error conditions. This Functions
|
|
44
|
+
* like {@link @adobe/uix-core/#timedPromise} which take logging strings also
|
|
45
|
+
* take callbacks for lazy evaluation of debugging messages. Use this only in
|
|
46
|
+
* such callbacks.
|
|
47
|
+
* @internal
|
|
48
|
+
*/
|
|
49
|
+
export function formatHostMethodAddress(address: HostMethodAddress) {
|
|
50
|
+
const path =
|
|
51
|
+
address.path?.length < 1
|
|
52
|
+
? "<Missing method path!>"
|
|
53
|
+
: address.path.join(".");
|
|
54
|
+
const name = address.name || "<Missing method name!>";
|
|
55
|
+
const args = address.args?.map(formatHostMethodArgument).join(",");
|
|
56
|
+
return `host.${path}.${name}(${args})`;
|
|
57
|
+
}
|
package/src/promises/timed.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* the original Promise, but if it doesn't resolve within the timeout interval,
|
|
4
4
|
* it will reject with a timeout error.
|
|
5
5
|
*
|
|
6
|
-
* @param
|
|
6
|
+
* @param describe - Job description to be used in the timeout error
|
|
7
7
|
* @param promise - Original promise to set a timeout for
|
|
8
8
|
* @param timeoutMs - Time to wait (ms) before rejecting
|
|
9
9
|
* @param onReject - Run when promise times out to clean up handles
|
|
@@ -12,21 +12,29 @@
|
|
|
12
12
|
* @internal
|
|
13
13
|
*/
|
|
14
14
|
export function timeoutPromise<T>(
|
|
15
|
-
|
|
15
|
+
describe: string | (() => string),
|
|
16
16
|
promise: Promise<T>,
|
|
17
17
|
ms: number,
|
|
18
|
-
onReject
|
|
18
|
+
onReject?: (e: Error) => void
|
|
19
19
|
): Promise<T> {
|
|
20
20
|
return new Promise((resolve, reject) => {
|
|
21
21
|
const cleanupAndReject = async (e: Error) => {
|
|
22
22
|
try {
|
|
23
|
-
|
|
23
|
+
if (onReject) {
|
|
24
|
+
await onReject(e);
|
|
25
|
+
}
|
|
24
26
|
} finally {
|
|
25
27
|
reject(e);
|
|
26
28
|
}
|
|
27
29
|
};
|
|
28
30
|
const timeout = setTimeout(() => {
|
|
29
|
-
cleanupAndReject(
|
|
31
|
+
cleanupAndReject(
|
|
32
|
+
new Error(
|
|
33
|
+
`${
|
|
34
|
+
typeof describe === "function" ? describe() : describe
|
|
35
|
+
} timed out after ${ms}ms`
|
|
36
|
+
)
|
|
37
|
+
);
|
|
30
38
|
}, ms);
|
|
31
39
|
promise
|
|
32
40
|
.then((result) => {
|
|
@@ -3,6 +3,22 @@ import { wait } from "../promises/wait";
|
|
|
3
3
|
import { Tunnel } from "./tunnel";
|
|
4
4
|
import { TunnelMessenger } from "./tunnel-messenger";
|
|
5
5
|
|
|
6
|
+
class FakeIframe {
|
|
7
|
+
contentWindow: MessagePort;
|
|
8
|
+
isConnected: boolean;
|
|
9
|
+
src: string;
|
|
10
|
+
private channel = new MessageChannel();
|
|
11
|
+
nodeName = "IFRAME";
|
|
12
|
+
constructor() {
|
|
13
|
+
this.contentWindow = new MessageChannel().port1;
|
|
14
|
+
}
|
|
15
|
+
destroy() {
|
|
16
|
+
this.channel.port1.close();
|
|
17
|
+
this.channel.port2.close();
|
|
18
|
+
this.channel = null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
6
22
|
const fakeConsole = {
|
|
7
23
|
error: jest.fn(),
|
|
8
24
|
warn: jest.fn(),
|
|
@@ -20,7 +36,6 @@ function tunnelHarness(
|
|
|
20
36
|
config = defaultTunnelConfig
|
|
21
37
|
): TunnelHarness {
|
|
22
38
|
const tunnel = new Tunnel(config);
|
|
23
|
-
tunnel.connect(port);
|
|
24
39
|
openPorts.push(port);
|
|
25
40
|
return {
|
|
26
41
|
tunnel,
|
|
@@ -52,6 +67,10 @@ describe("an EventEmitter dispatching and receiving from a MessagePort", () => {
|
|
|
52
67
|
local = tunnelHarness(channel.port1);
|
|
53
68
|
remote = tunnelHarness(channel.port2);
|
|
54
69
|
});
|
|
70
|
+
const connectTunnels = () => {
|
|
71
|
+
local.tunnel.connect(local.port);
|
|
72
|
+
remote.tunnel.connect(remote.port);
|
|
73
|
+
};
|
|
55
74
|
afterEach(() => {
|
|
56
75
|
while (openPorts.length > 0) {
|
|
57
76
|
openPorts.pop().close();
|
|
@@ -60,6 +79,7 @@ describe("an EventEmitter dispatching and receiving from a MessagePort", () => {
|
|
|
60
79
|
it("receives MessageEvents and emits local events to listeners", async () => {
|
|
61
80
|
const test1Handler = jest.fn();
|
|
62
81
|
local.tunnel.on("test1", test1Handler);
|
|
82
|
+
connectTunnels();
|
|
63
83
|
remote.port.postMessage({
|
|
64
84
|
type: "test1",
|
|
65
85
|
payload: {
|
|
@@ -75,6 +95,7 @@ describe("an EventEmitter dispatching and receiving from a MessagePort", () => {
|
|
|
75
95
|
const remoteConnectHandler = jest.fn();
|
|
76
96
|
local.tunnel.on("connected", localConnectHandler);
|
|
77
97
|
remote.tunnel.on("connected", remoteConnectHandler);
|
|
98
|
+
connectTunnels();
|
|
78
99
|
await wait(100);
|
|
79
100
|
expect(localConnectHandler).toHaveBeenCalledTimes(1);
|
|
80
101
|
expect(remoteConnectHandler).toHaveBeenCalledTimes(1);
|
|
@@ -82,22 +103,19 @@ describe("an EventEmitter dispatching and receiving from a MessagePort", () => {
|
|
|
82
103
|
it("#emitRemote() sends remote events after connect", async () => {
|
|
83
104
|
const messageListener = jest.fn();
|
|
84
105
|
remote.port.addEventListener("message", messageListener);
|
|
106
|
+
connectTunnels();
|
|
85
107
|
local.tunnel.emit("test2", { test2Payload: true });
|
|
86
108
|
local.tunnel.emit("test3", { test3Payload: true });
|
|
87
109
|
await wait(10);
|
|
88
|
-
expect(messageListener).toHaveBeenCalledTimes(
|
|
89
|
-
const
|
|
90
|
-
expect(connectMessageEvent).toHaveProperty("data", {
|
|
91
|
-
type: "connected",
|
|
92
|
-
});
|
|
93
|
-
const test2MessageEvent = messageListener.mock.calls[1][0];
|
|
110
|
+
expect(messageListener).toHaveBeenCalledTimes(2);
|
|
111
|
+
const test2MessageEvent = messageListener.mock.calls[0][0];
|
|
94
112
|
expect(test2MessageEvent).toHaveProperty("data", {
|
|
95
113
|
type: "test2",
|
|
96
114
|
payload: {
|
|
97
115
|
test2Payload: true,
|
|
98
116
|
},
|
|
99
117
|
});
|
|
100
|
-
const test3MessageEvent = messageListener.mock.calls[
|
|
118
|
+
const test3MessageEvent = messageListener.mock.calls[1][0];
|
|
101
119
|
expect(test3MessageEvent).toHaveProperty("data", {
|
|
102
120
|
type: "test3",
|
|
103
121
|
payload: {
|
|
@@ -106,16 +124,17 @@ describe("an EventEmitter dispatching and receiving from a MessagePort", () => {
|
|
|
106
124
|
});
|
|
107
125
|
});
|
|
108
126
|
it("exchanges events between two emitters sharing ports", async () => {
|
|
127
|
+
connectTunnels();
|
|
109
128
|
await testEventExchange(local.tunnel, remote.tunnel);
|
|
110
129
|
});
|
|
111
130
|
it("#connect(port) accepts a new messageport", async () => {
|
|
112
131
|
const connectHandler = jest.fn();
|
|
113
132
|
local.tunnel.on("connected", connectHandler);
|
|
114
|
-
remote.tunnel.on("reconnect", connectHandler);
|
|
115
133
|
const confirmHandler = jest.fn();
|
|
116
134
|
local.tunnel.on("confirm", confirmHandler);
|
|
117
135
|
const dispelHandler = jest.fn();
|
|
118
136
|
remote.tunnel.on("dispel", dispelHandler);
|
|
137
|
+
connectTunnels();
|
|
119
138
|
local.tunnel.emit("dispel", { dispelled: 1 });
|
|
120
139
|
remote.tunnel.emit("confirm", { confirmed: 1 });
|
|
121
140
|
await wait(10);
|
|
@@ -165,14 +184,13 @@ describe("static Tunnel.toIframe(iframe, options)", () => {
|
|
|
165
184
|
* https://github.com/jsdom/jsdom/blob/22f7c3c51829a6f14387f7a99e5cdf087f72e685/lib/jsdom/living/post-message.js#L31-L37
|
|
166
185
|
*/
|
|
167
186
|
describe.skip("creates a Tunnel connected to an iframe", () => {
|
|
168
|
-
it
|
|
187
|
+
it("listens for handshakes from the frame window", async () => {
|
|
169
188
|
let remoteTunnel: Tunnel;
|
|
170
189
|
const connectMessageHandler = jest.fn();
|
|
171
190
|
const acceptListener = jest.fn();
|
|
172
191
|
const targetOrigin = "https://example.com:4001";
|
|
173
|
-
const loadedFrame =
|
|
192
|
+
const loadedFrame = new FakeIframe() as unknown as HTMLIFrameElement;
|
|
174
193
|
loadedFrame.src = targetOrigin;
|
|
175
|
-
document.body.appendChild(loadedFrame);
|
|
176
194
|
loadedFrame.contentWindow.addEventListener("message", acceptListener);
|
|
177
195
|
const localTunnel = Tunnel.toIframe(loadedFrame, {
|
|
178
196
|
targetOrigin,
|
|
@@ -185,14 +203,18 @@ describe("static Tunnel.toIframe(iframe, options)", () => {
|
|
|
185
203
|
});
|
|
186
204
|
localTunnel.on("connected", connectMessageHandler);
|
|
187
205
|
await wait(100);
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
data: messenger.makeOffered("iframe-test-1"),
|
|
192
|
-
origin: loadedFrame.src,
|
|
193
|
-
source: loadedFrame.contentWindow,
|
|
194
|
-
})
|
|
206
|
+
window.postMessage(
|
|
207
|
+
messenger.makeOffered("iframe-test-1"),
|
|
208
|
+
loadedFrame.src
|
|
195
209
|
);
|
|
210
|
+
// fireEvent(
|
|
211
|
+
// window,
|
|
212
|
+
// new MessageEvent("message", {
|
|
213
|
+
// data: messenger.makeOffered("iframe-test-1"),
|
|
214
|
+
// origin: loadedFrame.src,
|
|
215
|
+
// source: loadedFrame.contentWindow,
|
|
216
|
+
// })
|
|
217
|
+
// );
|
|
196
218
|
await wait(100);
|
|
197
219
|
expect(acceptListener).toHaveBeenCalled();
|
|
198
220
|
const acceptEvent = acceptListener.mock.lastCall[0];
|
package/src/tunnel/tunnel.ts
CHANGED
|
@@ -2,6 +2,7 @@ import EventEmitter from "eventemitter3";
|
|
|
2
2
|
import { isIframe } from "../value-assertions";
|
|
3
3
|
import { TunnelMessenger } from "./tunnel-messenger";
|
|
4
4
|
import { unwrap } from "../message-wrapper";
|
|
5
|
+
import { quietConsole } from "../debuglog";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Child iframe will send offer messages to parent at this frequency until one
|
|
@@ -81,6 +82,7 @@ export class Tunnel extends EventEmitter {
|
|
|
81
82
|
private _messagePort: MessagePort;
|
|
82
83
|
|
|
83
84
|
config: TunnelConfig;
|
|
85
|
+
isConnected: boolean;
|
|
84
86
|
|
|
85
87
|
// #endregion Properties
|
|
86
88
|
|
|
@@ -120,24 +122,48 @@ export class Tunnel extends EventEmitter {
|
|
|
120
122
|
);
|
|
121
123
|
}
|
|
122
124
|
|
|
123
|
-
const source = target.contentWindow;
|
|
124
125
|
const config = Tunnel._normalizeConfig(options);
|
|
125
126
|
const tunnel = new Tunnel(config);
|
|
126
127
|
const messenger = new TunnelMessenger({
|
|
127
128
|
myOrigin: window.location.origin,
|
|
128
|
-
targetOrigin:
|
|
129
|
-
logger:
|
|
129
|
+
targetOrigin: config.targetOrigin,
|
|
130
|
+
logger: config.logger,
|
|
130
131
|
});
|
|
132
|
+
tunnel.on("destroyed", () =>
|
|
133
|
+
config.logger.log(
|
|
134
|
+
`Tunnel to iframe at ${config.targetOrigin} destroyed!`,
|
|
135
|
+
tunnel,
|
|
136
|
+
target
|
|
137
|
+
)
|
|
138
|
+
);
|
|
139
|
+
tunnel.on("connected", () =>
|
|
140
|
+
config.logger.log(
|
|
141
|
+
`Tunnel to iframe at ${config.targetOrigin} connected!`,
|
|
142
|
+
tunnel,
|
|
143
|
+
target
|
|
144
|
+
)
|
|
145
|
+
);
|
|
146
|
+
tunnel.on("error", (e) =>
|
|
147
|
+
config.logger.log(
|
|
148
|
+
`Tunnel to iframe at ${config.targetOrigin} error!`,
|
|
149
|
+
tunnel,
|
|
150
|
+
target,
|
|
151
|
+
e
|
|
152
|
+
)
|
|
153
|
+
);
|
|
131
154
|
let frameStatusCheck: number;
|
|
132
155
|
let timeout: number;
|
|
133
156
|
const offerListener = (event: MessageEvent) => {
|
|
134
157
|
if (
|
|
135
|
-
|
|
158
|
+
!tunnel.isConnected &&
|
|
159
|
+
isFromOrigin(event, target.contentWindow, config.targetOrigin) &&
|
|
136
160
|
messenger.isHandshakeOffer(event.data)
|
|
137
161
|
) {
|
|
138
162
|
const accepted = messenger.makeAccepted(unwrap(event.data).offers);
|
|
139
163
|
const channel = new MessageChannel();
|
|
140
|
-
|
|
164
|
+
target.contentWindow.postMessage(accepted, config.targetOrigin, [
|
|
165
|
+
channel.port1,
|
|
166
|
+
]);
|
|
141
167
|
tunnel.connect(channel.port2);
|
|
142
168
|
}
|
|
143
169
|
};
|
|
@@ -147,13 +173,11 @@ export class Tunnel extends EventEmitter {
|
|
|
147
173
|
window.removeEventListener("message", offerListener);
|
|
148
174
|
};
|
|
149
175
|
timeout = window.setTimeout(() => {
|
|
150
|
-
tunnel.
|
|
151
|
-
"error",
|
|
176
|
+
tunnel.abort(
|
|
152
177
|
new Error(
|
|
153
|
-
`Timed out awaiting initial message from iframe after ${config.timeout}ms`
|
|
178
|
+
`Timed out awaiting initial message from target iframe after ${config.timeout}ms`
|
|
154
179
|
)
|
|
155
180
|
);
|
|
156
|
-
tunnel.destroy();
|
|
157
181
|
}, config.timeout);
|
|
158
182
|
|
|
159
183
|
tunnel.on("destroyed", cleanup);
|
|
@@ -165,7 +189,16 @@ export class Tunnel extends EventEmitter {
|
|
|
165
189
|
*/
|
|
166
190
|
frameStatusCheck = window.setInterval(() => {
|
|
167
191
|
if (!target.isConnected) {
|
|
168
|
-
|
|
192
|
+
cleanup();
|
|
193
|
+
if (tunnel.isConnected) {
|
|
194
|
+
const frameDisconnectError = new Error(
|
|
195
|
+
`Tunnel target iframe at ${tunnel.config.targetOrigin} was disconnected from the document!`
|
|
196
|
+
);
|
|
197
|
+
Object.assign(frameDisconnectError, { target });
|
|
198
|
+
tunnel.abort(frameDisconnectError);
|
|
199
|
+
} else {
|
|
200
|
+
tunnel.destroy();
|
|
201
|
+
}
|
|
169
202
|
}
|
|
170
203
|
}, STATUSCHECK_MS);
|
|
171
204
|
|
|
@@ -192,6 +225,15 @@ export class Tunnel extends EventEmitter {
|
|
|
192
225
|
const key = makeKey();
|
|
193
226
|
const config = Tunnel._normalizeConfig(opts);
|
|
194
227
|
const tunnel = new Tunnel(config);
|
|
228
|
+
tunnel.on("destroyed", () =>
|
|
229
|
+
config.logger.log(`Tunnel ${key} to parent window destroyed!`, tunnel)
|
|
230
|
+
);
|
|
231
|
+
tunnel.on("connected", () =>
|
|
232
|
+
config.logger.log(`Tunnel ${key} to parent window connected!`, tunnel)
|
|
233
|
+
);
|
|
234
|
+
tunnel.on("error", (e) =>
|
|
235
|
+
config.logger.log(`Tunnel ${key} to parent window error!`, tunnel, e)
|
|
236
|
+
);
|
|
195
237
|
const messenger = new TunnelMessenger({
|
|
196
238
|
myOrigin: window.location.origin,
|
|
197
239
|
targetOrigin: config.targetOrigin,
|
|
@@ -221,21 +263,31 @@ export class Tunnel extends EventEmitter {
|
|
|
221
263
|
};
|
|
222
264
|
|
|
223
265
|
timeout = window.setTimeout(() => {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
266
|
+
if (!timedOut) {
|
|
267
|
+
timedOut = true;
|
|
268
|
+
tunnel.abort(
|
|
269
|
+
new Error(
|
|
270
|
+
`Timed out waiting for initial response from parent after ${config.timeout}ms`
|
|
271
|
+
)
|
|
272
|
+
);
|
|
273
|
+
}
|
|
231
274
|
}, config.timeout);
|
|
232
275
|
|
|
233
276
|
window.addEventListener("message", acceptListener);
|
|
234
|
-
tunnel.on("destroyed",
|
|
235
|
-
|
|
277
|
+
tunnel.on("destroyed", () => {
|
|
278
|
+
cleanup();
|
|
279
|
+
});
|
|
280
|
+
tunnel.on("connected", () => {
|
|
281
|
+
cleanup();
|
|
282
|
+
});
|
|
236
283
|
|
|
237
|
-
const sendOffer = () =>
|
|
238
|
-
|
|
284
|
+
const sendOffer = () => {
|
|
285
|
+
if (tunnel.isConnected) {
|
|
286
|
+
clearInterval(retrying);
|
|
287
|
+
} else {
|
|
288
|
+
source.postMessage(messenger.makeOffered(key), config.targetOrigin);
|
|
289
|
+
}
|
|
290
|
+
};
|
|
239
291
|
retrying = window.setInterval(sendOffer, RETRY_MS);
|
|
240
292
|
sendOffer();
|
|
241
293
|
|
|
@@ -253,17 +305,26 @@ export class Tunnel extends EventEmitter {
|
|
|
253
305
|
}
|
|
254
306
|
this._messagePort = remote;
|
|
255
307
|
remote.addEventListener("message", this._emitFromMessage);
|
|
256
|
-
this.
|
|
308
|
+
this.emitLocal("connected");
|
|
257
309
|
this._messagePort.start();
|
|
310
|
+
this.isConnected = true;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
abort(error: Error): void {
|
|
314
|
+
this.emitLocal("error", error);
|
|
315
|
+
this.destroy(error);
|
|
258
316
|
}
|
|
259
317
|
|
|
260
|
-
destroy(): void {
|
|
318
|
+
destroy(e?: Error): void {
|
|
261
319
|
if (this._messagePort) {
|
|
262
320
|
this._messagePort.close();
|
|
263
321
|
this._messagePort = null;
|
|
322
|
+
this.isConnected = false;
|
|
264
323
|
}
|
|
265
|
-
|
|
266
|
-
|
|
324
|
+
// don't add the argument to the logging if it doesn't exist; otherwise, on
|
|
325
|
+
// a normal destroy, it logs a confusing "undefined"
|
|
326
|
+
const context = e ? [e] : [];
|
|
327
|
+
this.emitLocal("destroyed", ...context);
|
|
267
328
|
// this.removeAllListeners(); // TODO: maybe necessary for memory leaks
|
|
268
329
|
}
|
|
269
330
|
|
|
@@ -289,8 +350,8 @@ export class Tunnel extends EventEmitter {
|
|
|
289
350
|
let errorMessage = "";
|
|
290
351
|
const config: Partial<TunnelConfig> = {
|
|
291
352
|
timeout: 4000,
|
|
292
|
-
logger: console,
|
|
293
353
|
...options,
|
|
354
|
+
logger: options.logger || quietConsole,
|
|
294
355
|
};
|
|
295
356
|
|
|
296
357
|
const timeoutMs = Number(config.timeout);
|